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 01/28] 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 7a7fddac98..4449040a73 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 02/28] 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 0000000000..3f7397ced6 --- /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 4d5b470f8b..37a325d655 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 706b0cbf65..ff3d06cdd1 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 0000000000..2f44896ea2 --- /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 4997f4efe9..49fce40c0a 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 01a82d66e0..437ed55e4b 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 0000000000..1f142f1154 --- /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 03/28] 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 88cd06d596..fff1961a9c 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 4449040a73..a07ac4579f 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 aa312797b2..6bf34b8f75 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 bc026110ee..8760dd597c 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 f67a24bba1..3819fc2043 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 14178edfe34f3a6d0d0d150ae5a5ef59df0e4930 Mon Sep 17 00:00:00 2001 From: tertu Date: Mon, 3 Feb 2025 13:03:13 -0600 Subject: [PATCH 04/28] Don't use SeedRng some places where it isn't necessary (#6156) --- include/random.h | 9 +++++++-- src/battle_pyramid.c | 18 ++++++++++++------ src/contest_painting.c | 2 -- src/daycare.c | 8 ++++---- src/record_mixing.c | 7 ++----- 5 files changed, 25 insertions(+), 19 deletions(-) diff --git a/include/random.h b/include/random.h index 1a735097d3..80e4ea8a24 100644 --- a/include/random.h +++ b/include/random.h @@ -30,7 +30,7 @@ struct Sfc32State { typedef struct Sfc32State rng_value_t; -#define RNG_VALUE_EMPTY {} +#define RNG_VALUE_EMPTY {0} // Calling this function directly is discouraged. // Use LocalRandom() instead. @@ -43,9 +43,14 @@ static inline u32 _SFC32_Next(struct Sfc32State *state) return result; } +static inline u32 LocalRandom32(rng_value_t *val) +{ + return _SFC32_Next(val); +} + static inline u16 LocalRandom(rng_value_t *val) { - return _SFC32_Next(val) >> 16; + return LocalRandom32(val) >> 16; } u32 Random32(void); diff --git a/src/battle_pyramid.c b/src/battle_pyramid.c index c881dab38f..33f2717597 100644 --- a/src/battle_pyramid.c +++ b/src/battle_pyramid.c @@ -983,8 +983,10 @@ static void SetPickupItem(void) { int i; int itemIndex; - int rand; + int randVal; + u32 randSeedIndex, randSeed; u8 id; + rng_value_t rand; u32 lvlMode = gSaveBlock2Ptr->frontier.lvlMode; u32 floor = gSaveBlock2Ptr->frontier.curChallengeBattleNum; u32 round = (gSaveBlock2Ptr->frontier.pyramidWinStreaks[lvlMode] / FRONTIER_STAGES_PER_CHALLENGE) % TOTAL_PYRAMID_ROUNDS; @@ -994,15 +996,19 @@ static void SetPickupItem(void) id = GetPyramidFloorTemplateId(); itemIndex = (gSpecialVar_LastTalked - sPyramidFloorTemplates[id].numTrainers) - 1; - rand = gSaveBlock2Ptr->frontier.pyramidRandoms[itemIndex / 2]; - SeedRng2(rand); + randSeedIndex = (itemIndex & 1) * 2; + randSeed = (u32)gSaveBlock2Ptr->frontier.pyramidRandoms[randSeedIndex + 1] << 16; + randSeed |= gSaveBlock2Ptr->frontier.pyramidRandoms[randSeedIndex]; + rand = LocalRandomSeed(randSeed); - for (i = 0; i < itemIndex + 1; i++) - rand = Random2() % 100; + for (i = 0; i < itemIndex / 2; i++) + LocalRandom(&rand); + + randVal = LocalRandom(&rand) % 100; for (i = sPickupItemOffsets[floor]; i < ARRAY_COUNT(sPickupItemSlots); i++) { - if (rand < sPickupItemSlots[i][0]) + if (randVal < sPickupItemSlots[i][0]) break; } diff --git a/src/contest_painting.c b/src/contest_painting.c index 8b647be12f..1911e70689 100644 --- a/src/contest_painting.c +++ b/src/contest_painting.c @@ -212,7 +212,6 @@ static void ShowContestPainting(void) gMain.state++; break; case 2: - SeedRng(gMain.vblankCounter1); InitKeys(); InitContestPaintingWindow(); gMain.state++; @@ -595,4 +594,3 @@ static void CreateContestPaintingPicture(u8 contestWinnerId, bool8 isForArtist) InitPaintingMonOamData(contestWinnerId); LoadContestPaintingFrame(contestWinnerId, isForArtist); } - diff --git a/src/daycare.c b/src/daycare.c index 6b6b84ac58..4e69dd9012 100644 --- a/src/daycare.c +++ b/src/daycare.c @@ -549,14 +549,15 @@ static void _TriggerPendingDaycareEgg(struct DayCare *daycare) { s32 parent; s32 natureTries = 0; + rng_value_t personalityRand; - SeedRng2(gMain.vblankCounter2); + personalityRand = LocalRandomSeed(gMain.vblankCounter2); parent = GetParentToInheritNature(daycare); // don't inherit nature if (parent < 0) { - daycare->offspringPersonality = (Random2() << 16) | ((Random() % 0xfffe) + 1); + daycare->offspringPersonality = (LocalRandom(&personalityRand) << 16) | ((Random() % 0xfffe) + 1); } // inherit nature else @@ -566,7 +567,7 @@ static void _TriggerPendingDaycareEgg(struct DayCare *daycare) do { - personality = (Random2() << 16) | (Random()); + personality = (LocalRandom(&personalityRand) << 16) | (Random()); if (wantedNature == GetNatureFromPersonality(personality) && personality != 0) break; // found a personality with the same nature @@ -1566,4 +1567,3 @@ static u8 ModifyBreedingScoreForOvalCharm(u8 score) return score; } - diff --git a/src/record_mixing.c b/src/record_mixing.c index 7d78119f86..1ff36d1fe8 100644 --- a/src/record_mixing.c +++ b/src/record_mixing.c @@ -770,14 +770,12 @@ static void ReceiveDaycareMailData(struct RecordMixingDaycareMail *records, size bool8 canHoldItem[MAX_LINK_PLAYERS][DAYCARE_MON_COUNT]; u8 idxs[MAX_LINK_PLAYERS][2]; u8 numDaycareCanHold; - u16 oldSeed; bool32 anyRS; + rng_value_t localRngState = LocalRandomSeed(gLinkPlayers[0].trainerId); // Seed RNG to the first player's trainer id so that // every player has the same random swap occur // (see the other use of Random2 in this function) - oldSeed = Random2(); - SeedRng2(gLinkPlayers[0].trainerId); linkPlayerCount = GetLinkPlayerCount(); for (i = 0; i < MAX_LINK_PLAYERS; i++) { @@ -907,7 +905,7 @@ static void ReceiveDaycareMailData(struct RecordMixingDaycareMail *records, size itemId2 = GetDaycareMailItemId(&mixMail->mail[1]); if ((!itemId1 && !itemId2) || (itemId1 && itemId2)) - idxs[j][DAYCARE_SLOT] = Random2() % 2; + idxs[j][DAYCARE_SLOT] = LocalRandom32(&localRngState) % 2; else if (itemId1 && !itemId2) idxs[j][DAYCARE_SLOT] = 0; else if (!itemId1 && itemId2) @@ -958,7 +956,6 @@ static void ReceiveDaycareMailData(struct RecordMixingDaycareMail *records, size mixMail = (void *)records + multiplayerId * recordSize; memcpy(&gSaveBlock1Ptr->daycare.mons[0].mail, &mixMail->mail[0], sizeof(struct DaycareMail)); memcpy(&gSaveBlock1Ptr->daycare.mons[1].mail, &mixMail->mail[1], sizeof(struct DaycareMail)); - SeedRng(oldSeed); } From e9c14e35ca7b37bbda16f06a03d0242d279cf85f Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Mon, 3 Feb 2025 16:34:15 -0300 Subject: [PATCH 05/28] Cleaned up party data access GetPartyBattlerData (#6172) --- src/battle_ai_util.c | 12 +---- src/battle_anim.c | 7 +-- src/battle_anim_dark.c | 9 +--- src/battle_anim_effects_3.c | 67 +++++++------------------ src/battle_anim_mons.c | 89 +++++++++------------------------ src/battle_anim_throw.c | 5 +- src/battle_anim_utility_funcs.c | 27 ++-------- src/battle_anim_water.c | 22 ++------ src/battle_dynamax.c | 5 +- src/battle_script_commands.c | 9 +--- src/pokeball.c | 7 +-- 11 files changed, 56 insertions(+), 203 deletions(-) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index ad5943cc0e..caa27f653b 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2699,19 +2699,9 @@ static bool32 AnyUsefulStatIsRaised(u32 battler) return FALSE; } -struct Pokemon *GetPartyBattlerPartyData(u32 battlerId, u32 switchBattler) -{ - struct Pokemon *mon; - if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) - mon = &gPlayerParty[switchBattler]; - else - mon = &gEnemyParty[switchBattler]; - return mon; -} - static bool32 PartyBattlerShouldAvoidHazards(u32 currBattler, u32 switchBattler) { - struct Pokemon *mon = GetPartyBattlerPartyData(currBattler, switchBattler); + struct Pokemon *mon = &GetBattlerParty(currBattler)[switchBattler]; u32 ability = GetMonAbility(mon); // we know our own party data u32 holdEffect; u32 species = GetMonData(mon, MON_DATA_SPECIES); diff --git a/src/battle_anim.c b/src/battle_anim.c index c12a88bec7..38648310a7 100644 --- a/src/battle_anim.c +++ b/src/battle_anim.c @@ -370,12 +370,7 @@ void LaunchBattleAnimation(u32 animType, u32 animId) InitPrioritiesForVisibleBattlers(); UpdateOamPriorityInAllHealthboxes(0, sAnimHideHpBoxes); for (i = 0; i < MAX_BATTLERS_COUNT; i++) - { - if (GetBattlerSide(i) != B_SIDE_PLAYER) - gAnimBattlerSpecies[i] = GetMonData(&gEnemyParty[gBattlerPartyIndexes[i]], MON_DATA_SPECIES); - else - gAnimBattlerSpecies[i] = GetMonData(&gPlayerParty[gBattlerPartyIndexes[i]], MON_DATA_SPECIES); - } + gAnimBattlerSpecies[i] = GetMonData(GetPartyBattlerData(i), MON_DATA_SPECIES); } else { diff --git a/src/battle_anim_dark.c b/src/battle_anim_dark.c index fd9ffdf923..1cada8f825 100644 --- a/src/battle_anim_dark.c +++ b/src/battle_anim_dark.c @@ -932,16 +932,9 @@ void AnimTask_MetallicShine(u8 taskId) } if (IsContest()) - { species = gContestResources->moveAnim->species; - } else - { - if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER) - species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_SPECIES); - else - species = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_SPECIES); - } + species = GetMonData(GetPartyBattlerData(gBattleAnimAttacker), MON_DATA_SPECIES); spriteId = GetAnimBattlerSpriteId(ANIM_ATTACKER); newSpriteId = CreateInvisibleSpriteCopy(gBattleAnimAttacker, spriteId, species); diff --git a/src/battle_anim_effects_3.c b/src/battle_anim_effects_3.c index 1530fa37a5..6f34f479b9 100644 --- a/src/battle_anim_effects_3.c +++ b/src/battle_anim_effects_3.c @@ -3417,51 +3417,24 @@ void AnimTask_RolePlaySilhouette(u8 taskId) isShiny = gContestResources->moveAnim->targetIsShiny; species = gContestResources->moveAnim->targetSpecies; xOffset = 20; - priority = GetBattlerSpriteBGPriority(gBattleAnimAttacker); } else { + struct Pokemon *mon = GetPartyBattlerData(gBattleAnimTarget); if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER) - { isBackPic = FALSE; - personality = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gBattleAnimTarget]], MON_DATA_PERSONALITY); - isShiny = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gBattleAnimTarget]], MON_DATA_IS_SHINY); - if (gBattleSpritesDataPtr->battlerData[gBattleAnimTarget].transformSpecies == SPECIES_NONE) - { - if (GetBattlerSide(gBattleAnimTarget) == B_SIDE_PLAYER) - species = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gBattleAnimTarget]], MON_DATA_SPECIES); - else - species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattleAnimTarget]], MON_DATA_SPECIES); - } - else - { - species = gBattleSpritesDataPtr->battlerData[gBattleAnimTarget].transformSpecies; - } - - xOffset = 20; - priority = GetBattlerSpriteBGPriority(gBattleAnimAttacker); - } else - { isBackPic = TRUE; - personality = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattleAnimTarget]], MON_DATA_PERSONALITY); - isShiny = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattleAnimTarget]], MON_DATA_IS_SHINY); - if (gBattleSpritesDataPtr->battlerData[gBattleAnimTarget].transformSpecies == SPECIES_NONE) - { - if (GetBattlerSide(gBattleAnimTarget) == B_SIDE_PLAYER) - species = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gBattleAnimTarget]], MON_DATA_SPECIES); - else - species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattleAnimTarget]], MON_DATA_SPECIES); - } - else - { - species = gBattleSpritesDataPtr->battlerData[gBattleAnimTarget].transformSpecies; - } + personality = GetMonData(mon, MON_DATA_PERSONALITY); + isShiny = GetMonData(mon, MON_DATA_IS_SHINY); + if (gBattleSpritesDataPtr->battlerData[gBattleAnimTarget].transformSpecies == SPECIES_NONE) + species = GetMonData(mon, MON_DATA_SPECIES); + else + species = gBattleSpritesDataPtr->battlerData[gBattleAnimTarget].transformSpecies; - xOffset = -20; - priority = GetBattlerSpriteBGPriority(gBattleAnimAttacker); - } + xOffset = -20; } + priority = GetBattlerSpriteBGPriority(gBattleAnimAttacker); coord1 = GetBattlerSpriteCoord(gBattleAnimAttacker, BATTLER_COORD_X); coord2 = GetBattlerSpriteCoord(gBattleAnimAttacker, BATTLER_COORD_Y); @@ -5331,28 +5304,22 @@ void AnimTask_SnatchOpposingMonMove(u8 taskId) } else { + struct Pokemon *mon = GetPartyBattlerData(gBattleAnimAttacker); + personality = GetMonData(mon, MON_DATA_PERSONALITY); + isShiny = GetMonData(mon, MON_DATA_IS_SHINY); + if (gBattleSpritesDataPtr->battlerData[gBattleAnimAttacker].transformSpecies == SPECIES_NONE) + species = GetMonData(mon, MON_DATA_SPECIES); + else + species = gBattleSpritesDataPtr->battlerData[gBattleAnimAttacker].transformSpecies; + if (GetBattlerSide(gBattleAnimAttacker) == B_SIDE_PLAYER) { - personality = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_PERSONALITY); - isShiny = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_IS_SHINY); - if (gBattleSpritesDataPtr->battlerData[gBattleAnimAttacker].transformSpecies == SPECIES_NONE) - species = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_SPECIES); - else - species = gBattleSpritesDataPtr->battlerData[gBattleAnimAttacker].transformSpecies; - subpriority = gSprites[GetAnimBattlerSpriteId(ANIM_TARGET)].subpriority + 1; isBackPic = FALSE; x = DISPLAY_WIDTH + 32; } else { - personality = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_PERSONALITY); - isShiny = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_IS_SHINY); - if (gBattleSpritesDataPtr->battlerData[gBattleAnimAttacker].transformSpecies == SPECIES_NONE) - species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_SPECIES); - else - species = gBattleSpritesDataPtr->battlerData[gBattleAnimAttacker].transformSpecies; - subpriority = gSprites[GetAnimBattlerSpriteId(ANIM_TARGET)].subpriority - 1; isBackPic = TRUE; x = -32; diff --git a/src/battle_anim_mons.c b/src/battle_anim_mons.c index 2df2b23089..f5517366ac 100644 --- a/src/battle_anim_mons.c +++ b/src/battle_anim_mons.c @@ -143,28 +143,16 @@ u8 GetBattlerYDelta(u8 battlerId, u16 species) u8 ret; species = SanitizeSpeciesId(species); - if (GetBattlerSide(battlerId) == B_SIDE_PLAYER || IsContest()) + if (IsContest()) { if (species == SPECIES_UNOWN) { - if (IsContest()) - { - if (gContestResources->moveAnim->hasTargetAnim) - personality = gContestResources->moveAnim->targetPersonality; - else - personality = gContestResources->moveAnim->personality; - } + if (gContestResources->moveAnim->hasTargetAnim) + personality = gContestResources->moveAnim->targetPersonality; else - { - spriteInfo = gBattleSpritesDataPtr->battlerData; - if (!spriteInfo[battlerId].transformSpecies) - personality = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_PERSONALITY); - else - personality = gTransformedPersonalities[battlerId]; - } + personality = gContestResources->moveAnim->personality; species = GetUnownSpeciesId(personality); } - ret = gSpeciesInfo[species].backPicYOffset; } else { @@ -172,14 +160,17 @@ u8 GetBattlerYDelta(u8 battlerId, u16 species) { spriteInfo = gBattleSpritesDataPtr->battlerData; if (!spriteInfo[battlerId].transformSpecies) - personality = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerId]], MON_DATA_PERSONALITY); + personality = GetMonData(GetPartyBattlerData(battlerId), MON_DATA_PERSONALITY); else personality = gTransformedPersonalities[battlerId]; - species = GetUnownSpeciesId(personality); } - ret = gSpeciesInfo[species].frontPicYOffset; } + + if (GetBattlerSide(battlerId) == B_SIDE_PLAYER || IsContest()) + ret = gSpeciesInfo[species].backPicYOffset; + else + ret = gSpeciesInfo[species].frontPicYOffset; return ret; } @@ -279,22 +270,13 @@ u8 GetBattlerYCoordWithElevation(u8 battlerId) y = GetBattlerSpriteCoord(battlerId, BATTLER_COORD_Y); if (!IsContest()) { - if (GetBattlerSide(battlerId) != B_SIDE_PLAYER) - { - spriteInfo = gBattleSpritesDataPtr->battlerData; - if (!spriteInfo[battlerId].transformSpecies) - species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerId]], MON_DATA_SPECIES); - else - species = spriteInfo[battlerId].transformSpecies; - } + spriteInfo = gBattleSpritesDataPtr->battlerData; + + if (!spriteInfo[battlerId].transformSpecies) + species = GetMonData(GetPartyBattlerData(battlerId), MON_DATA_SPECIES); else - { - spriteInfo = gBattleSpritesDataPtr->battlerData; - if (!spriteInfo[battlerId].transformSpecies) - species = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_SPECIES); - else - species = spriteInfo[battlerId].transformSpecies; - } + species = spriteInfo[battlerId].transformSpecies; + if (GetBattlerSide(battlerId) != B_SIDE_PLAYER) y -= GetBattlerElevation(battlerId, species); } @@ -845,19 +827,8 @@ bool8 IsBattlerSpritePresent(u8 battlerId) if (GetBattlerPosition(battlerId) == 0xff) return FALSE; - if (!gBattleStruct->spriteIgnore0Hp) - { - if (GetBattlerSide(battlerId) == B_SIDE_OPPONENT) - { - if (GetMonData(&gEnemyParty[gBattlerPartyIndexes[battlerId]], MON_DATA_HP) == 0) - return FALSE; - } - else - { - if (GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_HP) == 0) - return FALSE; - } - } + if (!gBattleStruct->spriteIgnore0Hp && GetMonData(GetPartyBattlerData(battlerId), MON_DATA_HP) == 0) + return FALSE; return TRUE; } } @@ -1874,26 +1845,16 @@ static u16 GetBattlerYDeltaFromSpriteId(u8 spriteId) } else { - if (GetBattlerSide(i) == B_SIDE_PLAYER) - { - spriteInfo = gBattleSpritesDataPtr->battlerData; - if (!spriteInfo[battlerId].transformSpecies) - species = GetMonData(&gPlayerParty[gBattlerPartyIndexes[i]], MON_DATA_SPECIES); - else - species = spriteInfo[battlerId].transformSpecies; - - return gSpeciesInfo[species].backPicYOffset; - } + spriteInfo = gBattleSpritesDataPtr->battlerData; + if (!spriteInfo[battlerId].transformSpecies) + species = GetMonData(GetPartyBattlerData(i), MON_DATA_SPECIES); else - { - spriteInfo = gBattleSpritesDataPtr->battlerData; - if (!spriteInfo[battlerId].transformSpecies) - species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[i]], MON_DATA_SPECIES); - else - species = spriteInfo[battlerId].transformSpecies; + species = spriteInfo[battlerId].transformSpecies; + if (GetBattlerSide(i) == B_SIDE_PLAYER) + return gSpeciesInfo[species].backPicYOffset; + else return gSpeciesInfo[species].frontPicYOffset; - } } } } diff --git a/src/battle_anim_throw.c b/src/battle_anim_throw.c index a8c6e4077e..0ede51954a 100644 --- a/src/battle_anim_throw.c +++ b/src/battle_anim_throw.c @@ -908,10 +908,7 @@ void AnimTask_SwitchOutBallEffect(u8 taskId) u32 selectedPalettes; spriteId = gBattlerSpriteIds[gBattleAnimAttacker]; - if (GetBattlerSide(gBattleAnimAttacker) == B_SIDE_PLAYER) - ballId = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_POKEBALL); - else - ballId = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_POKEBALL); + ballId = GetMonData(GetPartyBattlerData(gBattleAnimAttacker), MON_DATA_POKEBALL); switch (gTasks[taskId].data[0]) { diff --git a/src/battle_anim_utility_funcs.c b/src/battle_anim_utility_funcs.c index a8de8eddd9..47850bc56a 100644 --- a/src/battle_anim_utility_funcs.c +++ b/src/battle_anim_utility_funcs.c @@ -316,16 +316,9 @@ void AnimTask_DrawFallingWhiteLinesOnAttacker(u8 taskId) } if (IsContest()) - { species = gContestResources->moveAnim->species; - } else - { - if (GetBattlerSide(gBattleAnimAttacker) != B_SIDE_PLAYER) - species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_SPECIES); - else - species = GetMonData(&gPlayerParty[gBattlerPartyIndexes[gBattleAnimAttacker]], MON_DATA_SPECIES); - } + species = GetMonData(GetPartyBattlerData(gBattleAnimAttacker), MON_DATA_SPECIES); spriteId = GetAnimBattlerSpriteId(ANIM_ATTACKER); newSpriteId = CreateInvisibleSpriteCopy(gBattleAnimAttacker, spriteId, species); @@ -458,16 +451,9 @@ static void StatsChangeAnimation_Step1(u8 taskId) } if (IsContest()) - { sAnimStatsChangeData->species = gContestResources->moveAnim->species; - } else - { - if (GetBattlerSide(sAnimStatsChangeData->battler1) != B_SIDE_PLAYER) - sAnimStatsChangeData->species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[sAnimStatsChangeData->battler1]], MON_DATA_SPECIES); - else - sAnimStatsChangeData->species = GetMonData(&gPlayerParty[gBattlerPartyIndexes[sAnimStatsChangeData->battler1]], MON_DATA_SPECIES); - } + sAnimStatsChangeData->species = GetMonData(GetPartyBattlerData(sAnimStatsChangeData->battler1), MON_DATA_SPECIES); gTasks[taskId].func = StatsChangeAnimation_Step2; } @@ -840,16 +826,9 @@ void StartMonScrollingBgMask(u8 taskId, int UNUSED unused, u16 scrollSpeed, u8 b SetGpuReg(REG_OFFSET_BG1CNT, bg1Cnt); if (IsContest()) - { species = gContestResources->moveAnim->species; - } else - { - if (GetBattlerSide(battler) != B_SIDE_PLAYER) - species = GetMonData(&gEnemyParty[gBattlerPartyIndexes[battler]], MON_DATA_SPECIES); - else - species = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battler]], MON_DATA_SPECIES); - } + species = GetMonData(GetPartyBattlerData(battler), MON_DATA_SPECIES); spriteId = CreateInvisibleSpriteCopy(battler, gBattlerSpriteIds[battler], species); if (includePartner) diff --git a/src/battle_anim_water.c b/src/battle_anim_water.c index 65f8178d13..4265e8d03c 100644 --- a/src/battle_anim_water.c +++ b/src/battle_anim_water.c @@ -1349,25 +1349,11 @@ static u8 GetWaterSpoutPowerForAnim(void) u8 i; u16 hp; u16 maxhp; - u16 partyIndex; - struct Pokemon *slot; + struct Pokemon *slot = GetPartyBattlerData(gBattleAnimAttacker); - if (GetBattlerSide(gBattleAnimAttacker) == B_SIDE_PLAYER) - { - partyIndex = gBattlerPartyIndexes[gBattleAnimAttacker]; - slot = &gPlayerParty[partyIndex]; - maxhp = GetMonData(slot, MON_DATA_MAX_HP); - hp = GetMonData(slot, MON_DATA_HP); - maxhp /= 4; - } - else - { - partyIndex = gBattlerPartyIndexes[gBattleAnimAttacker]; - slot = &gEnemyParty[partyIndex]; - maxhp = GetMonData(slot, MON_DATA_MAX_HP); - hp = GetMonData(slot, MON_DATA_HP); - maxhp /= 4; - } + maxhp = GetMonData(slot, MON_DATA_MAX_HP); + hp = GetMonData(slot, MON_DATA_HP); + maxhp /= 4; for (i = 0; i < 3; i++) { if (hp < maxhp * (i + 1)) diff --git a/src/battle_dynamax.c b/src/battle_dynamax.c index cae562bf4e..3a931af470 100644 --- a/src/battle_dynamax.c +++ b/src/battle_dynamax.c @@ -197,13 +197,10 @@ void ActivateDynamax(u32 battler) // Unsets the flags used for Dynamaxing and reverts max HP if needed. void UndoDynamax(u32 battler) { - u8 side = GetBattlerSide(battler); - u8 monId = gBattlerPartyIndexes[battler]; - // Revert HP if battler is still Dynamaxed. if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX) { - struct Pokemon *mon = (side == B_SIDE_PLAYER) ? &gPlayerParty[monId] : &gEnemyParty[monId]; + struct Pokemon *mon = GetPartyBattlerData(battler); uq4_12_t mult = GetDynamaxLevelHPMultiplier(GetMonData(mon, MON_DATA_DYNAMAX_LEVEL), TRUE); gBattleMons[battler].hp = UQ_4_12_TO_INT((GetMonData(mon, MON_DATA_HP) * mult + 1) + UQ_4_12_ROUND); // round up SetMonData(mon, MON_DATA_HP, &gBattleMons[battler].hp); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 8bf9c8731c..33c05ee7c0 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -14755,16 +14755,9 @@ static void Cmd_trywish(void) case 1: // heal effect PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, gBattlerTarget, gWishFutureKnock.wishPartyId[gBattlerTarget]) if (B_WISH_HP_SOURCE >= GEN_5) - { - if (GetBattlerSide(gBattlerTarget) == B_SIDE_PLAYER) - gBattleStruct->moveDamage[gBattlerTarget] = max(1, GetMonData(&gPlayerParty[gWishFutureKnock.wishPartyId[gBattlerTarget]], MON_DATA_MAX_HP) / 2); - else - gBattleStruct->moveDamage[gBattlerTarget] = max(1, GetMonData(&gEnemyParty[gWishFutureKnock.wishPartyId[gBattlerTarget]], MON_DATA_MAX_HP) / 2); - } + gBattleStruct->moveDamage[gBattlerTarget] = max(1, GetMonData(&GetBattlerParty(gBattlerTarget)[gWishFutureKnock.wishPartyId[gBattlerTarget]], MON_DATA_MAX_HP) / 2); else - { gBattleStruct->moveDamage[gBattlerTarget] = max(1, GetNonDynamaxMaxHP(gBattlerAttacker) / 2); - } gBattleStruct->moveDamage[gBattlerTarget] *= -1; if (gBattleMons[gBattlerTarget].hp == gBattleMons[gBattlerTarget].maxHP) diff --git a/src/pokeball.c b/src/pokeball.c index 2b168cd7e3..6db184b496 100644 --- a/src/pokeball.c +++ b/src/pokeball.c @@ -993,16 +993,11 @@ static void SpriteCB_ReleaseMonFromBall(struct Sprite *sprite) u16 wantedCryCase; u8 taskId; + mon = GetPartyBattlerData(battlerId); if (GetBattlerSide(battlerId) != B_SIDE_PLAYER) - { - mon = &gEnemyParty[gBattlerPartyIndexes[battlerId]]; pan = 25; - } else - { - mon = &gPlayerParty[gBattlerPartyIndexes[battlerId]]; pan = -25; - } if ((battlerId == GetBattlerAtPosition(B_POSITION_PLAYER_LEFT) || battlerId == GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT)) && IsDoubleBattle() && gBattleSpritesDataPtr->animationData->introAnimActive) From a642759798b672e23fc7eecd0fbe233116f1ae14 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Mon, 3 Feb 2025 23:32:29 +0100 Subject: [PATCH 06/28] Changed two LocalRandom calls to new LocalRandom32 (#6173) Co-authored-by: Hedara --- src/trainer_pools.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/trainer_pools.c b/src/trainer_pools.c index a3ea1de22b..a2db4d81f5 100644 --- a/src/trainer_pools.c +++ b/src/trainer_pools.c @@ -257,7 +257,7 @@ static void RandomizePoolIndices(const struct Trainer *trainer, u8 *poolIndexArr u32 seed = GetPoolSeed(trainer); localRngState = LocalRandomSeed(seed); // Replace the LocalRandom with LocalRandom32 when implemented - rnd = LocalRandom(&localRngState) + (LocalRandom(&localRngState) << 16); + rnd = LocalRandom32(&localRngState); } else { @@ -284,7 +284,7 @@ static void RandomizePoolIndices(const struct Trainer *trainer, u8 *poolIndexArr if (usedBits + numBits > 32) { if (B_POOL_SETTING_CONSISTENT_RNG) - rnd = LocalRandom(&localRngState) + (LocalRandom(&localRngState) << 16); + rnd = LocalRandom32(&localRngState); else rnd = Random32(); usedBits = 0; 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 07/28] 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 a2d6c0fe10..855851586b 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 08/28] 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 71373cdd58..e60bbdaf22 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 b90b1e2546b8c9985b536e5cbd518926c262d89f Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Wed, 5 Feb 2025 00:04:05 +0100 Subject: [PATCH 09/28] Clean up - Add ability args instead of calcing the ability again (#6186) --- include/battle_script_commands.h | 6 +++--- src/battle_ai_main.c | 2 +- src/battle_ai_switch_items.c | 6 +++--- src/battle_ai_util.c | 2 +- src/battle_script_commands.c | 20 ++++++++++---------- src/battle_util.c | 14 +++++++------- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index 7326647d41..8f6bd8dd95 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -42,9 +42,9 @@ bool32 CanPoisonType(u8 battlerAttacker, u8 battlerTarget); bool32 CanParalyzeType(u8 battlerAttacker, u8 battlerTarget); bool32 CanUseLastResort(u8 battlerId); u32 IsFlowerVeilProtected(u32 battler); -u32 IsLeafGuardProtected(u32 battler); -bool32 IsShieldsDownProtected(u32 battler); -u32 IsAbilityStatusProtected(u32 battler); +u32 IsLeafGuardProtected(u32 battler, u32 ability); +bool32 IsShieldsDownProtected(u32 battler, u32 ability); +u32 IsAbilityStatusProtected(u32 battler, u32 ability); bool32 TryResetBattlerStatChanges(u8 battler); bool32 CanCamouflage(u8 battlerId); u32 GetNaturePowerMove(u32 battler); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 24f60ecfde..740328dab9 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -983,7 +983,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) RETURN_SCORE_MINUS(10); break; case ABILITY_SHIELDS_DOWN: - if (IsShieldsDownProtected(battlerAtk) && IsNonVolatileStatusMoveEffect(moveEffect)) + if (IsShieldsDownProtected(battlerAtk, aiData->abilities[battlerAtk]) && IsNonVolatileStatusMoveEffect(moveEffect)) RETURN_SCORE_MINUS(10); break; case ABILITY_LEAF_GUARD: diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index c341fb5175..c2f742d273 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -1312,9 +1312,9 @@ static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon && ability != ABILITY_IMMUNITY && ability != ABILITY_POISON_HEAL && ability != ABILITY_COMATOSE && status == 0 && !(hazardFlags & SIDE_STATUS_SAFEGUARD) - && !(IsAbilityOnSide(battler, ABILITY_PASTEL_VEIL)) - && !(IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) - && !(IsAbilityStatusProtected(battler)) + && !IsAbilityOnSide(battler, ABILITY_PASTEL_VEIL) + && !IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN) + && !IsAbilityStatusProtected(battler, ability) && heldItemEffect != HOLD_EFFECT_CURE_PSN && heldItemEffect != HOLD_EFFECT_CURE_STATUS && IsMonGrounded(heldItemEffect, ability, defType1, defType2))) { diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index caa27f653b..9135884942 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -3031,7 +3031,7 @@ bool32 AI_CanGetFrostbite(u32 battler, u32 ability) || ability == ABILITY_COMATOSE || IS_BATTLER_OF_TYPE(battler, TYPE_ICE) || gBattleMons[battler].status1 & STATUS1_ANY - || IsAbilityStatusProtected(battler) + || IsAbilityStatusProtected(battler, ability) || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD) return FALSE; return TRUE; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 33c05ee7c0..f5af50f450 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -9320,25 +9320,25 @@ u32 IsFlowerVeilProtected(u32 battler) return 0; } -u32 IsLeafGuardProtected(u32 battler) +u32 IsLeafGuardProtected(u32 battler, u32 ability) { if (IsBattlerWeatherAffected(battler, B_WEATHER_SUN)) - return GetBattlerAbility(battler) == ABILITY_LEAF_GUARD; + return ability == ABILITY_LEAF_GUARD; else return 0; } -bool32 IsShieldsDownProtected(u32 battler) +bool32 IsShieldsDownProtected(u32 battler, u32 ability) { - return (GetBattlerAbility(battler) == ABILITY_SHIELDS_DOWN + return (ability == ABILITY_SHIELDS_DOWN && GetFormIdFromFormSpeciesId(gBattleMons[battler].species) < GetFormIdFromFormSpeciesId(SPECIES_MINIOR_CORE_RED)); // Minior is not in core form } -u32 IsAbilityStatusProtected(u32 battler) +u32 IsAbilityStatusProtected(u32 battler, u32 ability) { - return IsFlowerVeilProtected(battler) - || IsLeafGuardProtected(battler) - || IsShieldsDownProtected(battler); + return IsLeafGuardProtected(battler, ability) + || IsShieldsDownProtected(battler, ability) + || IsFlowerVeilProtected(battler); } u32 GetHighestStatId(u32 battler) @@ -9612,7 +9612,7 @@ static void Cmd_various(void) case VARIOUS_JUMP_IF_SHIELDS_DOWN_PROTECTED: { VARIOUS_ARGS(const u8 *jumpInstr); - if (IsShieldsDownProtected(battler)) + if (IsShieldsDownProtected(battler, GetBattlerAbility(battler))) { gBattlerAbility = battler; gBattlescriptCurrInstr = cmd->jumpInstr; @@ -11169,7 +11169,7 @@ static void Cmd_various(void) case VARIOUS_JUMP_IF_LEAF_GUARD_PROTECTED: { VARIOUS_ARGS(const u8 *jumpInstr); - if (IsLeafGuardProtected(battler)) + if (IsLeafGuardProtected(battler, GetBattlerAbility(battler))) { gBattlerAbility = battler; gBattlescriptCurrInstr = cmd->jumpInstr; diff --git a/src/battle_util.c b/src/battle_util.c index 42cfffc677..7fed3d243a 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2687,7 +2687,7 @@ u8 DoBattlerEndTurnEffects(void) if (!(gStatuses3[battler] & STATUS3_YAWN) && !(gBattleMons[battler].status1 & STATUS1_ANY) && battlerAbility != ABILITY_VITAL_SPIRIT && battlerAbility != ABILITY_INSOMNIA && !UproarWakeUpCheck(battler) - && !IsLeafGuardProtected(battler)) + && !IsLeafGuardProtected(battler, battlerAbility)) { CancelMultiTurnMoves(battler); gEffectBattler = gBattlerTarget = battler; @@ -6810,7 +6810,7 @@ bool32 CanBeSlept(u32 battler, u32 ability, enum SleepClauseBlock isBlockedBySle || gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SAFEGUARD || gBattleMons[battler].status1 & STATUS1_ANY || IsAbilityOnSide(battler, ABILITY_SWEET_VEIL) - || IsAbilityStatusProtected(battler) + || IsAbilityStatusProtected(battler, ability) || IsBattlerTerrainAffected(battler, STATUS_FIELD_ELECTRIC_TERRAIN | STATUS_FIELD_MISTY_TERRAIN)) return FALSE; return TRUE; @@ -6825,7 +6825,7 @@ bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef, u32 defAbility) || defAbility == ABILITY_COMATOSE || defAbility == ABILITY_PURIFYING_SALT || IsAbilityOnSide(battlerDef, ABILITY_PASTEL_VEIL) - || IsAbilityStatusProtected(battlerDef) + || IsAbilityStatusProtected(battlerDef, defAbility) || IsBattlerTerrainAffected(battlerDef, STATUS_FIELD_MISTY_TERRAIN)) return FALSE; return TRUE; @@ -6841,7 +6841,7 @@ bool32 CanBeBurned(u32 battler, u32 ability) || ability == ABILITY_COMATOSE || ability == ABILITY_THERMAL_EXCHANGE || ability == ABILITY_PURIFYING_SALT - || IsAbilityStatusProtected(battler) + || IsAbilityStatusProtected(battler, ability) || IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) return FALSE; return TRUE; @@ -6855,7 +6855,7 @@ bool32 CanBeParalyzed(u32 battler, u32 ability) || ability == ABILITY_COMATOSE || ability == ABILITY_PURIFYING_SALT || gBattleMons[battler].status1 & STATUS1_ANY - || IsAbilityStatusProtected(battler) + || IsAbilityStatusProtected(battler, ability) || IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) return FALSE; return TRUE; @@ -6871,7 +6871,7 @@ bool32 CanBeFrozen(u32 battler) || ability == ABILITY_COMATOSE || ability == ABILITY_PURIFYING_SALT || gBattleMons[battler].status1 & STATUS1_ANY - || IsAbilityStatusProtected(battler) + || IsAbilityStatusProtected(battler, ability) || IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) return FALSE; return TRUE; @@ -6886,7 +6886,7 @@ bool32 CanGetFrostbite(u32 battler) || ability == ABILITY_COMATOSE || ability == ABILITY_PURIFYING_SALT || gBattleMons[battler].status1 & STATUS1_ANY - || IsAbilityStatusProtected(battler) + || IsAbilityStatusProtected(battler, ability) || IsBattlerTerrainAffected(battler, STATUS_FIELD_MISTY_TERRAIN)) return FALSE; return TRUE; From cad1a3756602f75aa6db32790538cac5ef3b5824 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Wed, 5 Feb 2025 15:27:21 +0100 Subject: [PATCH 10/28] Tachyon Cutter and Salt Cure animations (#6182) Co-authored-by: Hedara --- data/battle_anim_scripts.s | 79 +++++++++++++++++- .../battle_anims/sprites/salt_particle.png | Bin 0 -> 239 bytes include/constants/battle_anim.h | 1 + include/graphics.h | 2 + src/battle_anim_mons.c | 6 ++ src/battle_anim_psychic.c | 11 +++ src/battle_anim_rock.c | 22 +++++ src/data/battle_anim.h | 2 + src/graphics.c | 3 + 9 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 graphics/battle_anims/sprites/salt_particle.png diff --git a/data/battle_anim_scripts.s b/data/battle_anim_scripts.s index 58d9ddafd8..ceca0ee2d9 100644 --- a/data/battle_anim_scripts.s +++ b/data/battle_anim_scripts.s @@ -18215,10 +18215,50 @@ TeraStarstormCreateBeam:: createsprite gTeraStarstormBeamSpriteTemplate, ANIM_BATTLER, 1, 2, 0, 0, 0, 20 return +gBattleAnimMove_TachyonCutter:: + loadspritegfx ANIM_TAG_CUT + loadspritegfx ANIM_TAG_BUBBLE + createsprite gTachyonCutterSpriteTemplate, ANIM_ATTACKER, 2, 40, -32, 0 + createsprite gTachyonCutterSpriteTemplate, ANIM_ATTACKER, 2, 40, -32, 1 + playsewithpan SE_M_RAZOR_WIND2, SOUND_PAN_ATTACKER + waitforvisualfinish + end + +gBattleAnimMove_SaltCure:: + loadspritegfx ANIM_TAG_SALT_PARTICLE + loadspritegfx ANIM_TAG_WATER_ORB + call SaltCureEffect + call SaltCureEffect + call SaltCureEffect + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + waitforvisualfinish + call gBattleAnimGeneral_SaltCureDamage + end + +SaltCureEffect: + createsprite gSaltCureSwirlSpriteTemplate, ANIM_TARGET, 2, 0, 28, 384, 50, 8, 50, ANIM_TARGET + delay 2 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + createsprite gSaltCureSwirlSpriteTemplate, ANIM_TARGET, 2, 0, 32, 240, 40, 11, -46, ANIM_TARGET + delay 2 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + createsprite gSaltCureSwirlSpriteTemplate, ANIM_TARGET, 2, 0, 33, 416, 40, 4, 42, ANIM_TARGET + delay 2 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + createsprite gSaltCureSwirlSpriteTemplate, ANIM_TARGET, 2, 0, 31, 288, 45, 6, -42, ANIM_TARGET + delay 2 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + createsprite gSaltCureSwirlSpriteTemplate, ANIM_TARGET, 2, 0, 28, 448, 45, 11, 46, ANIM_TARGET + delay 2 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + createsprite gSaltCureSwirlSpriteTemplate, ANIM_TARGET, 2, 0, 33, 464, 50, 10, -50, ANIM_TARGET + delay 2 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + return + gBattleAnimMove_TeraBlast:: gBattleAnimMove_OrderUp:: gBattleAnimMove_GlaiveRush:: -gBattleAnimMove_SaltCure:: gBattleAnimMove_TripleDive:: gBattleAnimMove_Doodle:: gBattleAnimMove_Ruination:: @@ -18233,7 +18273,6 @@ gBattleAnimMove_MagicalTorque:: gBattleAnimMove_Psyblade:: gBattleAnimMove_MatchaGotcha:: gBattleAnimMove_MightyCleave:: -gBattleAnimMove_TachyonCutter:: gBattleAnimMove_SupercellSlam:: end @to do @@ -28927,7 +28966,41 @@ General_AffectionHangedOn_3Hearts: end gBattleAnimGeneral_SaltCureDamage:: - goto gBattleAnimStatus_Freeze + loadspritegfx ANIM_TAG_SALT_PARTICLE + createsprite gSaltCureCrystalSpriteTemplate, ANIM_TARGET, 2, -10, -10, 1 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + delay 4 + createsprite gSaltCureCrystalSpriteTemplate, ANIM_TARGET, 2, 10, 20, 1 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + delay 4 + createsprite gSaltCureCrystalSpriteTemplate, ANIM_TARGET, 2, -29, 0, 1 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + delay 4 + createsprite gSaltCureCrystalSpriteTemplate, ANIM_TARGET, 2, 29, -20, 1 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + delay 4 + createsprite gSaltCureCrystalSpriteTemplate, ANIM_TARGET, 2, -5, 10, 1 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + delay 4 + createsprite gSaltCureCrystalSpriteTemplate, ANIM_TARGET, 2, 17, -12, 1 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + delay 4 + createsprite gSaltCureCrystalSpriteTemplate, ANIM_TARGET, 2, -20, 0, 1 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + delay 4 + createsprite gSaltCureCrystalSpriteTemplate, ANIM_TARGET, 2, -15, 15, 1 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + delay 4 + createsprite gSaltCureCrystalSpriteTemplate, ANIM_TARGET, 2, 26, -5, 1 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + delay 4 + createsprite gSaltCureCrystalSpriteTemplate, ANIM_TARGET, 2, 0, 0, 1 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + delay 4 + createsprite gSaltCureCrystalSpriteTemplate, ANIM_TARGET, 2, 20, 2, 1 + playsewithpan SE_M_ICY_WIND, SOUND_PAN_TARGET + waitforvisualfinish + end gBattleAnimGeneral_Rainbow:: call RainDrops diff --git a/graphics/battle_anims/sprites/salt_particle.png b/graphics/battle_anims/sprites/salt_particle.png new file mode 100644 index 0000000000000000000000000000000000000000..a418f0a17222d863eb40941b0de404cc7df9f906 GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0vp^0zmA*!3-o-7PBt_QjEnx?oJHr&dIz4ats1|LR_y` z@0z`K`{~<%{{R2)0alXGp-l5k1hlZIzDHMDmy6|h*CGp+19C&}tF?-oPl t)Y%%f+&Xuv*Q}g<{QA3Rg}45i^s^`YW{ExL9C!@mG*4GQmvv4FO#t<$RZRc@ literal 0 HcmV?d00001 diff --git a/include/constants/battle_anim.h b/include/constants/battle_anim.h index 4026a91c81..b5ccb7bf25 100644 --- a/include/constants/battle_anim.h +++ b/include/constants/battle_anim.h @@ -418,6 +418,7 @@ #define ANIM_TAG_PURPLE_CHAIN (ANIM_SPRITES_START + 404) #define ANIM_TAG_PINKVIO_ORB (ANIM_SPRITES_START + 405) #define ANIM_TAG_STARSTORM (ANIM_SPRITES_START + 406) +#define ANIM_TAG_SALT_PARTICLE (ANIM_SPRITES_START + 407) // battlers #define ANIM_ATTACKER 0 diff --git a/include/graphics.h b/include/graphics.h index 87ccadacc9..e0109f8b20 100644 --- a/include/graphics.h +++ b/include/graphics.h @@ -2895,6 +2895,8 @@ extern const u32 gBattleAnimSpriteGfx_PinkVioletOrb[]; extern const u32 gBattleAnimSpritePal_PinkVioletOrb[]; extern const u32 gBattleAnimSpriteGfx_TeraStarstormBeam[]; extern const u32 gBattleAnimSpritePal_TeraStarstormBeam[]; +extern const u32 gBattleAnimSpriteGfx_SaltParticle[]; +extern const u32 gBattleAnimSpritePal_SaltParticle[]; extern const u32 gBattleAnimBgImage_Dark[]; extern const u32 gBattleAnimBgImage_Ghost[]; diff --git a/src/battle_anim_mons.c b/src/battle_anim_mons.c index f5517366ac..718ffb0a49 100644 --- a/src/battle_anim_mons.c +++ b/src/battle_anim_mons.c @@ -1502,6 +1502,12 @@ void TranslateAnimSpriteToTargetMonLocation(struct Sprite *sprite) StoreSpriteCallbackInData6(sprite, DestroyAnimSprite); } +// arg0: start x offset +// arg1: start y offset +// arg2: end x offset +// arg3: end y offset +// arg4: duration +// arg5: arc amplitude void AnimThrowProjectile(struct Sprite *sprite) { InitSpritePosToAnimAttacker(sprite, TRUE); diff --git a/src/battle_anim_psychic.c b/src/battle_anim_psychic.c index a9c1f0cf67..0b4b67f3b7 100644 --- a/src/battle_anim_psychic.c +++ b/src/battle_anim_psychic.c @@ -1379,3 +1379,14 @@ void AnimPsychoBoost(struct Sprite *sprite) break; } } + +const struct SpriteTemplate gTachyonCutterSpriteTemplate = +{ + .tileTag = ANIM_TAG_CUT, + .paletteTag = ANIM_TAG_BUBBLE, + .oam = &gOamData_AffineOff_ObjBlend_32x32, + .anims = gCuttingSliceAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = AnimCuttingSlice, +}; diff --git a/src/battle_anim_rock.c b/src/battle_anim_rock.c index ec7bc535df..0aaf5f1609 100644 --- a/src/battle_anim_rock.c +++ b/src/battle_anim_rock.c @@ -1026,3 +1026,25 @@ void AnimTask_SeismicTossBgAccelerateDownAtEnd(u8 taskId) DestroyAnimVisualTask(taskId); } } + +const struct SpriteTemplate gSaltCureCrystalSpriteTemplate = +{ + .tileTag = ANIM_TAG_SALT_PARTICLE, + .paletteTag = ANIM_TAG_SALT_PARTICLE, + .oam = &gOamData_AffineNormal_ObjBlend_16x16, + .anims = gAnims_IceCrystalLarge, + .images = NULL, + .affineAnims = gAffineAnims_IceCrystalHit, + .callback = AnimIceEffectParticle, +}; + +const struct SpriteTemplate gSaltCureSwirlSpriteTemplate = +{ + .tileTag = ANIM_TAG_SALT_PARTICLE, + .paletteTag = ANIM_TAG_SALT_PARTICLE, + .oam = &gOamData_AffineNormal_ObjBlend_16x16, + .anims = gAnims_WaterMudOrb, + .images = NULL, + .affineAnims = gAffineAnims_Whirlpool, + .callback = AnimParticleInVortex, +}; diff --git a/src/data/battle_anim.h b/src/data/battle_anim.h index 5f1643f82d..ad0d2d13d8 100644 --- a/src/data/battle_anim.h +++ b/src/data/battle_anim.h @@ -1467,6 +1467,7 @@ const struct CompressedSpriteSheet gBattleAnimPicTable[] = {gBattleAnimSpriteGfx_PurpleChain, 0x1000, ANIM_TAG_PURPLE_CHAIN}, {gBattleAnimSpriteGfx_PinkVioletOrb, 0x0080, ANIM_TAG_PINKVIO_ORB}, {gBattleAnimSpriteGfx_TeraStarstormBeam, 0x200, ANIM_TAG_STARSTORM}, + {gBattleAnimSpriteGfx_SaltParticle, 0x400, ANIM_TAG_SALT_PARTICLE}, }; const struct CompressedSpritePalette gBattleAnimPaletteTable[] = @@ -1935,6 +1936,7 @@ const struct CompressedSpritePalette gBattleAnimPaletteTable[] = {gBattleAnimSpritePal_PurpleChain, ANIM_TAG_PURPLE_CHAIN}, {gBattleAnimSpritePal_PinkVioletOrb, ANIM_TAG_PINKVIO_ORB}, {gBattleAnimSpritePal_TeraStarstormBeam, ANIM_TAG_STARSTORM}, + {gBattleAnimSpritePal_SaltParticle, ANIM_TAG_SALT_PARTICLE}, }; const struct BattleAnimBackground gBattleAnimBackgroundTable[] = diff --git a/src/graphics.c b/src/graphics.c index 19b9570079..b8c95be297 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -1290,6 +1290,9 @@ const u32 gBattleAnimSpritePal_FlatRock[] = INCBIN_U32("graphics/battle_anims/sp const u32 gBattleAnimSpriteGfx_TeraStarstormBeam[] = INCBIN_U32("graphics/battle_anims/sprites/starstorm_beam.4bpp.lz"); const u32 gBattleAnimSpritePal_TeraStarstormBeam[] = INCBIN_U32("graphics/battle_anims/sprites/starstorm_beam.gbapal.lz"); +const u32 gBattleAnimSpriteGfx_SaltParticle[] = INCBIN_U32("graphics/battle_anims/sprites/salt_particle.4bpp.lz"); +const u32 gBattleAnimSpritePal_SaltParticle[] = INCBIN_U32("graphics/battle_anims/sprites/salt_particle.gbapal.lz"); + const u32 gBattleAnimUnusedPal_Unknown2[] = INCBIN_U32("graphics/battle_anims/unused/unknown_2.gbapal.lz"); #include "data/graphics/trainers.h" From dc21befcc9e0b0801ed6f3d1a0aa95caf59bd322 Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:48:51 -0500 Subject: [PATCH 11/28] CanAbilityX Function Cleanup (#6183) Co-authored-by: ghoulslash --- include/battle_util.h | 14 +- src/battle_ai_main.c | 7 +- src/battle_ai_util.c | 14 +- src/battle_util.c | 329 +++++++++++++++++++++--------------------- 4 files changed, 173 insertions(+), 191 deletions(-) diff --git a/include/battle_util.h b/include/battle_util.h index 0dd0d9ce81..f60fb32d18 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -22,13 +22,10 @@ #define MOVE_LIMITATION_PLACEHOLDER (1 << 15) #define MOVE_LIMITATIONS_ALL 0xFFFF -enum MoveBlocked +enum AbilityEffectOptions { - MOVE_BLOCKED_BY_NO_ABILITY, - MOVE_BLOCKED_BY_SOUNDPROOF_OR_BULLETPROOF, - MOVE_BLOCKED_BY_DAZZLING, - MOVE_BLOCKED_BY_PARTNER_DAZZLING, - MOVE_BLOCKED_BY_GOOD_AS_GOLD, + ABILITY_CHECK_TRIGGER, + ABILITY_RUN_SCRIPT, }; enum MoveAbsorbed @@ -216,9 +213,8 @@ u32 AtkCanceller_MoveSuccessOrder(void); void SetAtkCancellerForCalledMove(void); bool32 HasNoMonsToSwitch(u32 battler, u8 r1, u8 r2); bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, bool32 viaAbility); -u32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 move, u32 abilityDef); -u32 CanPartnerAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 move, u32 abilityDef); -u32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 move, u32 moveType); +bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 move, u32 abilityDef, enum AbilityEffectOptions option); +bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 move, u32 moveType, enum AbilityEffectOptions option); u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 moveArg); bool32 TryPrimalReversion(u32 battler); bool32 IsNeutralizingGasOnField(void); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 740328dab9..0a1dcf7679 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -893,10 +893,10 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) // target ability checks if (!DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move)) { - if (CanAbilityBlockMove(battlerAtk, battlerDef, move, aiData->abilities[battlerDef])) + if (CanAbilityBlockMove(battlerAtk, battlerDef, move, aiData->abilities[battlerDef], FALSE)) RETURN_SCORE_MINUS(20); - if (CanAbilityAbsorbMove(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, moveType)) + if (CanAbilityAbsorbMove(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, moveType, FALSE)) RETURN_SCORE_MINUS(20); switch (aiData->abilities[battlerDef]) @@ -997,9 +997,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) // target partner ability checks & not attacking partner if (isDoubleBattle) { - if (CanPartnerAbilityBlockMove(battlerAtk, battlerDef, move, aiData->abilities[BATTLE_PARTNER(battlerDef)])) - RETURN_SCORE_MINUS(20); - switch (aiData->abilities[BATTLE_PARTNER(battlerDef)]) { case ABILITY_LIGHTNING_ROD: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 9135884942..745c8294e4 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -424,18 +424,11 @@ bool32 IsDamageMoveUnusable(u32 battlerAtk, u32 battlerDef, u32 move, u32 moveTy { struct AiLogicData *aiData = AI_DATA; u32 battlerDefAbility; - u32 partnerBattlerDefAbility; if (DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move)) - { battlerDefAbility = ABILITY_NONE; - partnerBattlerDefAbility = ABILITY_NONE; - } else - { battlerDefAbility = aiData->abilities[battlerDef]; - partnerBattlerDefAbility = aiData->abilities[BATTLE_PARTNER(battlerDef)]; - } if (battlerDef == BATTLE_PARTNER(battlerAtk)) battlerDefAbility = aiData->abilities[battlerDef]; @@ -443,13 +436,10 @@ bool32 IsDamageMoveUnusable(u32 battlerAtk, u32 battlerDef, u32 move, u32 moveTy if (gBattleStruct->battlerState[battlerDef].commandingDondozo) return TRUE; - if (CanAbilityBlockMove(battlerAtk, battlerDef, move, aiData->abilities[battlerDef])) + if (CanAbilityBlockMove(battlerAtk, battlerDef, move, aiData->abilities[battlerDef], FALSE)) return TRUE; - if (CanPartnerAbilityBlockMove(battlerAtk, battlerDef, move, partnerBattlerDefAbility)) - return TRUE; - - if (CanAbilityAbsorbMove(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, moveType)) + if (CanAbilityAbsorbMove(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, moveType, FALSE)) return TRUE; switch (GetMoveEffect(move)) diff --git a/src/battle_util.c b/src/battle_util.c index 7fed3d243a..7525bc6250 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4229,67 +4229,87 @@ static void ChooseStatBoostAnimation(u32 battler) #undef ANIM_STAT_ACC #undef ANIM_STAT_EVASION -u32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 move, u32 abilityDef) +bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 move, u32 abilityDef, enum AbilityEffectOptions option) { - enum MoveBlocked effect = MOVE_BLOCKED_BY_NO_ABILITY; - + const u8 *battleScriptBlocksMove = NULL; + u32 atkPriority = AI_DATA->aiCalcInProgress ? GetBattleMovePriority(battlerAtk, move) : GetChosenMovePriority(battlerAtk); + u32 moveTarget = GetBattlerMoveTargetType(battlerAtk, move); + u32 battlerAbility = battlerDef; + switch (abilityDef) { case ABILITY_SOUNDPROOF: - if (IsSoundMove(move) && !(GetBattlerMoveTargetType(battlerAtk, move) & MOVE_TARGET_USER)) - effect = MOVE_BLOCKED_BY_SOUNDPROOF_OR_BULLETPROOF; + if (IsSoundMove(move) && !(moveTarget & MOVE_TARGET_USER)) + { + if (gBattleMons[battlerAtk].status2 & STATUS2_MULTIPLETURNS) + gHitMarker |= HITMARKER_NO_PPDEDUCT; + battleScriptBlocksMove = BattleScript_SoundproofProtected; + } break; case ABILITY_BULLETPROOF: if (IsBallisticMove(move)) - effect = MOVE_BLOCKED_BY_SOUNDPROOF_OR_BULLETPROOF; + { + if (gBattleMons[battlerAtk].status2 & STATUS2_MULTIPLETURNS) + gHitMarker |= HITMARKER_NO_PPDEDUCT; + battleScriptBlocksMove = BattleScript_SoundproofProtected; + } break; case ABILITY_DAZZLING: case ABILITY_QUEENLY_MAJESTY: case ABILITY_ARMOR_TAIL: - if (GetBattlerSide(battlerAtk) != GetBattlerSide(battlerDef)) + if (atkPriority > 0 && !IsAlly(battlerAtk, battlerDef)) { - u32 priority = AI_DATA->aiCalcInProgress ? GetBattleMovePriority(battlerAtk, move) : GetChosenMovePriority(battlerAtk); - if (priority > 0) - effect = MOVE_BLOCKED_BY_DAZZLING; + if (gBattleMons[battlerAtk].status2 & STATUS2_MULTIPLETURNS) + gHitMarker |= HITMARKER_NO_PPDEDUCT; + battleScriptBlocksMove = BattleScript_DazzlingProtected; } break; case ABILITY_GOOD_AS_GOLD: if (IsBattleMoveStatus(move)) { - u32 moveTarget = GetBattlerMoveTargetType(battlerAtk, move); if (!(moveTarget & MOVE_TARGET_OPPONENTS_FIELD) && !(moveTarget & MOVE_TARGET_ALL_BATTLERS)) - effect = MOVE_BLOCKED_BY_GOOD_AS_GOLD; + battleScriptBlocksMove = BattleScript_GoodAsGoldActivates; } break; } - if (!effect) - effect = CanPartnerAbilityBlockMove(battlerAtk, battlerDef, move, GetBattlerAbility(BATTLE_PARTNER(battlerDef))); - return effect; -} - -u32 CanPartnerAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 move, u32 abilityDef) -{ - switch (abilityDef) + // Check def partner ability + if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battlerDef))) { - case ABILITY_DAZZLING: - case ABILITY_QUEENLY_MAJESTY: - case ABILITY_ARMOR_TAIL: - if (GetBattlerSide(battlerAtk) != GetBattlerSide(battlerDef)) + switch (GetBattlerAbility(BATTLE_PARTNER(battlerDef))) { - s32 priority = AI_DATA->aiCalcInProgress ? GetBattleMovePriority(battlerAtk, move) : GetChosenMovePriority(battlerAtk); - if (priority > 0) - return MOVE_BLOCKED_BY_PARTNER_DAZZLING; + case ABILITY_DAZZLING: + case ABILITY_QUEENLY_MAJESTY: + case ABILITY_ARMOR_TAIL: + if (atkPriority > 0 && !IsAlly(battlerAtk, BATTLE_PARTNER(battlerDef))) + { + if (gBattleMons[battlerAtk].status2 & STATUS2_MULTIPLETURNS) + gHitMarker |= HITMARKER_NO_PPDEDUCT; + battlerAbility = BATTLE_PARTNER(battlerDef); + battleScriptBlocksMove = BattleScript_DazzlingProtected; + } + break; } - break; } - return MOVE_BLOCKED_BY_NO_ABILITY; + + if (battleScriptBlocksMove == NULL) + return FALSE; + + if (option == ABILITY_RUN_SCRIPT) + { + gBattleScripting.battler = gBattlerAbility = battlerAbility; + gBattlescriptCurrInstr = battleScriptBlocksMove; + } + return TRUE; } -u32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 move, u32 moveType) +bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 move, u32 moveType, enum AbilityEffectOptions option) { enum MoveAbsorbed effect = MOVE_ABSORBED_BY_NO_ABILITY; + const u8 *battleScript = NULL; + u32 statId = 0; + u32 statAmount = 1; switch (abilityDef) { @@ -4311,33 +4331,130 @@ u32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 mov break; case ABILITY_MOTOR_DRIVE: if (moveType == TYPE_ELECTRIC && GetMoveTarget(move) != MOVE_TARGET_ALL_BATTLERS) // Potential bug in singles (might be solved with simu hp reudction) + { effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; + statId = STAT_SPEED; + } break; case ABILITY_LIGHTNING_ROD: if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5 && moveType == TYPE_ELECTRIC && GetMoveTarget(move) != MOVE_TARGET_ALL_BATTLERS) // Potential bug in singles (might be solved with simu hp reudction) + { effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; + statId = STAT_SPATK; + } break; case ABILITY_STORM_DRAIN: if (B_REDIRECT_ABILITY_IMMUNITY >= GEN_5 && moveType == TYPE_WATER) + { effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; + statId = STAT_SPATK; + } break; case ABILITY_SAP_SIPPER: if (moveType == TYPE_GRASS) + { effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; + statId = STAT_ATK; + } break; case ABILITY_WELL_BAKED_BODY: if (moveType == TYPE_FIRE) + { effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; + statAmount = 2; + statId = STAT_DEF; + } break; case ABILITY_WIND_RIDER: if (IsWindMove(move) && !(GetBattlerMoveTargetType(battlerAtk, move) & MOVE_TARGET_USER)) + { effect = MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY; + statId = STAT_ATK; + } break; case ABILITY_FLASH_FIRE: if (moveType == TYPE_FIRE && (B_FLASH_FIRE_FROZEN >= GEN_5 || !(gBattleMons[battlerDef].status1 & STATUS1_FREEZE))) effect = MOVE_ABSORBED_BY_BOOST_FLASH_FIRE; break; } + + if (effect == MOVE_ABSORBED_BY_NO_ABILITY || option == ABILITY_CHECK_TRIGGER) + return effect; + + switch (effect) + { + default: + return FALSE; + case MOVE_ABSORBED_BY_DRAIN_HP_ABILITY: + gBattleStruct->pledgeMove = FALSE; + if (IsBattlerAtMaxHp(battlerDef) || (B_HEAL_BLOCKING >= GEN_5 && gStatuses3[battlerDef] & STATUS3_HEAL_BLOCK)) + { + if ((gProtectStructs[battlerAtk].notFirstStrike)) + battleScript = BattleScript_MonMadeMoveUseless; + else + battleScript = BattleScript_MonMadeMoveUseless_PPLoss; + } + else + { + if (gProtectStructs[battlerAtk].notFirstStrike) + battleScript = BattleScript_MoveHPDrain; + else + battleScript = BattleScript_MoveHPDrain_PPLoss; + + gBattleStruct->moveDamage[battlerDef] = GetNonDynamaxMaxHP(battlerDef) / 4; + if (gBattleStruct->moveDamage[battlerDef] == 0) + gBattleStruct->moveDamage[battlerDef] = 1; + gBattleStruct->moveDamage[battlerDef] *= -1; + } + break; + case MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY: + gBattleStruct->pledgeMove = FALSE; + if (!CompareStat(battlerDef, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + if ((gProtectStructs[battlerAtk].notFirstStrike)) + battleScript = BattleScript_MonMadeMoveUseless; + else + battleScript = BattleScript_MonMadeMoveUseless_PPLoss; + } + else + { + if (gProtectStructs[battlerAtk].notFirstStrike) + battleScript = BattleScript_MoveStatDrain; + else + battleScript = BattleScript_MoveStatDrain_PPLoss; + + SET_STATCHANGER(statId, statAmount, FALSE); + if (B_ABSORBING_ABILITY_STRING < GEN_5) + PREPARE_STAT_BUFFER(gBattleTextBuff1, statId); + } + break; + case MOVE_ABSORBED_BY_BOOST_FLASH_FIRE: + gBattleStruct->pledgeMove = FALSE; + if (!gDisableStructs[battlerDef].flashFireBoosted) + { + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FLASH_FIRE_BOOST; + if (gProtectStructs[battlerAtk].notFirstStrike) + battleScript = BattleScript_FlashFireBoost; + else + battleScript = BattleScript_FlashFireBoost_PPLoss; + gDisableStructs[battlerDef].flashFireBoosted = TRUE; + } + else + { + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FLASH_FIRE_NO_BOOST; + if (gProtectStructs[battlerAtk].notFirstStrike) + battleScript = BattleScript_FlashFireBoost; + else + battleScript = BattleScript_FlashFireBoost_PPLoss; + } + break; + } + + if (battleScript != NULL) + { + gMultiHitCounter = 0; // Prevent multi-hit moves from hitting more than once after move has been absorbed. + gBattlescriptCurrInstr = battleScript; + } return effect; } @@ -5470,153 +5587,35 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITYEFFECT_WOULD_BLOCK: - effect = CanAbilityBlockMove(gBattlerAttacker, battler, move, gLastUsedAbility); + effect = CanAbilityBlockMove(gBattlerAttacker, battler, move, gLastUsedAbility, ABILITY_CHECK_TRIGGER); if (effect && gLastUsedAbility != 0xFFFF) RecordAbilityBattle(battler, gLastUsedAbility); - break; + return effect; case ABILITYEFFECT_MOVES_BLOCK: + effect = CanAbilityBlockMove(gBattlerAttacker, battler, move, gLastUsedAbility, ABILITY_RUN_SCRIPT); + + // prankster check + if (effect == 0 + && GetChosenMovePriority(gBattlerAttacker) > 0 + && BlocksPrankster(move, gBattlerAttacker, gBattlerTarget, TRUE) + && !(IsBattleMoveStatus(move) && (gLastUsedAbility == ABILITY_MAGIC_BOUNCE || gProtectStructs[gBattlerTarget].bounceMove))) { - effect = CanAbilityBlockMove(gBattlerAttacker, battler, move, gLastUsedAbility); - const u8 * battleScriptBlocksMove = NULL; - switch (effect) - { - case MOVE_BLOCKED_BY_SOUNDPROOF_OR_BULLETPROOF: - if (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS) - gHitMarker |= HITMARKER_NO_PPDEDUCT; - battleScriptBlocksMove = BattleScript_SoundproofProtected; - break; - case MOVE_BLOCKED_BY_DAZZLING: - case MOVE_BLOCKED_BY_PARTNER_DAZZLING: - if (effect == MOVE_BLOCKED_BY_PARTNER_DAZZLING) - gBattleScripting.battler = BATTLE_PARTNER(battler); - else - gBattleScripting.battler = battler; - if (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS) - gHitMarker |= HITMARKER_NO_PPDEDUCT; - battleScriptBlocksMove = BattleScript_DazzlingProtected; - break; - case MOVE_BLOCKED_BY_GOOD_AS_GOLD: - battleScriptBlocksMove = BattleScript_GoodAsGoldActivates; - break; - default: - if (GetChosenMovePriority(gBattlerAttacker) > 0 - && BlocksPrankster(move, gBattlerAttacker, gBattlerTarget, TRUE) - && !(IsBattleMoveStatus(move) && (gLastUsedAbility == ABILITY_MAGIC_BOUNCE || gProtectStructs[gBattlerTarget].bounceMove))) - { - if (!IsDoubleBattle() - || !(GetBattlerMoveTargetType(gBattlerAttacker, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY))) - CancelMultiTurnMoves(gBattlerAttacker); // Don't cancel moves that can hit two targets bc one target might not be protected - gBattleScripting.battler = gBattlerAbility = gBattlerTarget; - battleScriptBlocksMove = BattleScript_DarkTypePreventsPrankster; - effect = 1; - } - } - if (effect) - gBattlescriptCurrInstr = battleScriptBlocksMove; + if (!IsDoubleBattle() + || !(GetBattlerMoveTargetType(gBattlerAttacker, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY))) + CancelMultiTurnMoves(gBattlerAttacker); // Don't cancel moves that can hit two targets bc one target might not be protected + gBattleScripting.battler = gBattlerAbility = gBattlerTarget; + gBattlescriptCurrInstr = BattleScript_DarkTypePreventsPrankster; + effect = 1; } break; case ABILITYEFFECT_WOULD_ABSORB: - effect = CanAbilityAbsorbMove(gBattlerAttacker, battler, gLastUsedAbility, move, moveType); + effect = CanAbilityAbsorbMove(gBattlerAttacker, battler, gLastUsedAbility, move, moveType, ABILITY_CHECK_TRIGGER); gBattleStruct->pledgeMove = FALSE; if (effect && gLastUsedAbility != 0xFFFF) RecordAbilityBattle(battler, gLastUsedAbility); return effect; case ABILITYEFFECT_ABSORBING: - { - u32 statId = 0; - u32 statAmount = 1; - effect = CanAbilityAbsorbMove(gBattlerAttacker, battler, gLastUsedAbility, move, moveType); - if (effect) - { - switch(gLastUsedAbility) - { - case ABILITY_MOTOR_DRIVE: - statId = STAT_SPEED; - break; - case ABILITY_LIGHTNING_ROD: - case ABILITY_STORM_DRAIN: - statId = STAT_SPATK; - break; - case ABILITY_SAP_SIPPER: - case ABILITY_WIND_RIDER: - statId = STAT_ATK; - break; - case ABILITY_WELL_BAKED_BODY: - statAmount = 2; - statId = STAT_DEF; - break; - } - } - switch (effect) - { - case MOVE_ABSORBED_BY_DRAIN_HP_ABILITY: - gBattleStruct->pledgeMove = FALSE; - if (IsBattlerAtMaxHp(battler) || (B_HEAL_BLOCKING >= GEN_5 && gStatuses3[battler] & STATUS3_HEAL_BLOCK)) - { - if ((gProtectStructs[gBattlerAttacker].notFirstStrike)) - gBattlescriptCurrInstr = BattleScript_MonMadeMoveUseless; - else - gBattlescriptCurrInstr = BattleScript_MonMadeMoveUseless_PPLoss; - } - else - { - if (gProtectStructs[gBattlerAttacker].notFirstStrike) - gBattlescriptCurrInstr = BattleScript_MoveHPDrain; - else - gBattlescriptCurrInstr = BattleScript_MoveHPDrain_PPLoss; - - gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / 4; - if (gBattleStruct->moveDamage[battler] == 0) - gBattleStruct->moveDamage[battler] = 1; - gBattleStruct->moveDamage[battler] *= -1; - } - break; - case MOVE_ABSORBED_BY_STAT_INCREASE_ABILITY: - gBattleStruct->pledgeMove = FALSE; - if (!CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN)) - { - if ((gProtectStructs[gBattlerAttacker].notFirstStrike)) - gBattlescriptCurrInstr = BattleScript_MonMadeMoveUseless; - else - gBattlescriptCurrInstr = BattleScript_MonMadeMoveUseless_PPLoss; - } - else - { - if (gProtectStructs[gBattlerAttacker].notFirstStrike) - gBattlescriptCurrInstr = BattleScript_MoveStatDrain; - else - gBattlescriptCurrInstr = BattleScript_MoveStatDrain_PPLoss; - - SET_STATCHANGER(statId, statAmount, FALSE); - if (B_ABSORBING_ABILITY_STRING < GEN_5) - PREPARE_STAT_BUFFER(gBattleTextBuff1, statId); - } - break; - case MOVE_ABSORBED_BY_BOOST_FLASH_FIRE: - gBattleStruct->pledgeMove = FALSE; - if (!gDisableStructs[battler].flashFireBoosted) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FLASH_FIRE_BOOST; - if (gProtectStructs[gBattlerAttacker].notFirstStrike) - gBattlescriptCurrInstr = BattleScript_FlashFireBoost; - else - gBattlescriptCurrInstr = BattleScript_FlashFireBoost_PPLoss; - gDisableStructs[battler].flashFireBoosted = TRUE; - } - else - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FLASH_FIRE_NO_BOOST; - if (gProtectStructs[gBattlerAttacker].notFirstStrike) - gBattlescriptCurrInstr = BattleScript_FlashFireBoost; - else - gBattlescriptCurrInstr = BattleScript_FlashFireBoost_PPLoss; - } - break; - } - if (effect) - gMultiHitCounter = 0; // Prevent multi-hit moves from hitting more than once after move has been absorbed. - - } + effect = CanAbilityAbsorbMove(gBattlerAttacker, battler, gLastUsedAbility, move, moveType, ABILITY_RUN_SCRIPT); break; case ABILITYEFFECT_MOVE_END: // Think contact abilities. switch (gLastUsedAbility) From 4359cbb77cbd03fe72b39fb78b2e1efa0f39bff7 Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:49:53 -0500 Subject: [PATCH 12/28] SwitchType enum to clean up GetMostSuitableMonToSwitchInto (#6184) --- include/battle_ai_switch_items.h | 8 +++++++- src/battle_ai_switch_items.c | 12 ++++++------ src/battle_controller_opponent.c | 2 +- src/battle_controller_player_partner.c | 2 +- src/battle_main.c | 10 +++++----- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/include/battle_ai_switch_items.h b/include/battle_ai_switch_items.h index 28eb318b2f..d5fbc701a5 100644 --- a/include/battle_ai_switch_items.h +++ b/include/battle_ai_switch_items.h @@ -1,9 +1,15 @@ #ifndef GUARD_BATTLE_AI_SWITCH_ITEMS_H #define GUARD_BATTLE_AI_SWITCH_ITEMS_H +enum SwitchType +{ + SWITCH_AFTER_KO, + SWITCH_MID_BATTLE, +}; + void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId); void AI_TrySwitchOrUseItem(u32 battler); -u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd); +u32 GetMostSuitableMonToSwitchInto(u32 battler, enum SwitchType switchType); bool32 ShouldSwitch(u32 battler); bool32 IsMonGrounded(u16 heldItemEffect, u32 ability, u8 type1, u8 type2); diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index c2f742d273..6e754121c0 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -1732,7 +1732,7 @@ static bool32 CanAbilityTrapOpponent(u16 ability, u32 opponent) return FALSE; } -static inline bool32 IsFreeSwitch(bool32 isSwitchAfterKO, u32 battlerSwitchingOut, u32 opposingBattler) +static inline bool32 IsFreeSwitch(enum SwitchType switchType, u32 battlerSwitchingOut, u32 opposingBattler) { bool32 movedSecond = GetBattlerTurnOrderNum(battlerSwitchingOut) > GetBattlerTurnOrderNum(opposingBattler) ? TRUE : FALSE; @@ -1755,7 +1755,7 @@ static inline bool32 IsFreeSwitch(bool32 isSwitchAfterKO, u32 battlerSwitchingOu } // Post KO check has to be last because the GetMostSuitableMonToSwitchInto call in OpponentHandleChoosePokemon assumes a KO rather than a forced switch choice - if (isSwitchAfterKO) + if (switchType == SWITCH_AFTER_KO) return TRUE; else return FALSE; @@ -1777,7 +1777,7 @@ static inline bool32 CanSwitchinWin1v1(u32 hitsToKOAI, u32 hitsToKOPlayer, bool3 // This function splits switching behaviour depending on whether the switch is free. // Everything runs in the same loop to minimize computation time. This makes it harder to read, but hopefully the comments can guide you! -static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, u32 battler, u32 opposingBattler, u32 battlerIn1, u32 battlerIn2, bool32 isSwitchAfterKO) +static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, u32 battler, u32 opposingBattler, u32 battlerIn1, u32 battlerIn2, enum SwitchType switchType) { int revengeKillerId = PARTY_SIZE, slowRevengeKillerId = PARTY_SIZE, fastThreatenId = PARTY_SIZE, slowThreatenId = PARTY_SIZE, damageMonId = PARTY_SIZE; int batonPassId = PARTY_SIZE, typeMatchupId = PARTY_SIZE, typeMatchupEffectiveId = PARTY_SIZE, defensiveMonId = PARTY_SIZE, aceMonId = PARTY_SIZE, trapperId = PARTY_SIZE; @@ -1786,7 +1786,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, s32 playerMonHP = gBattleMons[opposingBattler].hp, maxDamageDealt = 0, damageDealt = 0; u32 aiMove, hitsToKOAI, maxHitsToKO = 0; u16 bestResist = UQ_4_12(1.0), bestResistEffective = UQ_4_12(1.0), typeMatchup; - bool32 isFreeSwitch = IsFreeSwitch(isSwitchAfterKO, battlerIn1, opposingBattler), isSwitchinFirst, canSwitchinWin1v1; + bool32 isFreeSwitch = IsFreeSwitch(switchType, battlerIn1, opposingBattler), isSwitchinFirst, canSwitchinWin1v1; // Iterate through mons for (i = firstId; i < lastId; i++) @@ -1979,7 +1979,7 @@ static u32 GetNextMonInParty(struct Pokemon *party, int firstId, int lastId, u32 return PARTY_SIZE; } -u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd) +u32 GetMostSuitableMonToSwitchInto(u32 battler, enum SwitchType switchType) { u32 opposingBattler = 0; u32 bestMonId = PARTY_SIZE; @@ -2024,7 +2024,7 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd) // Only use better mon selection if AI_FLAG_SMART_MON_CHOICES is set for the trainer. if (AI_THINKING_STRUCT->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_MON_CHOICES && !IsDoubleBattle()) // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic { - bestMonId = GetBestMonIntegrated(party, firstId, lastId, battler, opposingBattler, battlerIn1, battlerIn2, switchAfterMonKOd); + bestMonId = GetBestMonIntegrated(party, firstId, lastId, battler, opposingBattler, battlerIn1, battlerIn2, switchType); return bestMonId; } diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index adeae79652..28e572d1a5 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -680,7 +680,7 @@ static void OpponentHandleChoosePokemon(u32 battler) // Switching out else if (gBattleStruct->AI_monToSwitchIntoId[battler] == PARTY_SIZE) { - chosenMonId = GetMostSuitableMonToSwitchInto(battler, TRUE); + chosenMonId = GetMostSuitableMonToSwitchInto(battler, SWITCH_AFTER_KO); if (chosenMonId == PARTY_SIZE) { s32 battler1, battler2, firstId, lastId; diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index 4c3163dd9a..a57bb8e2a5 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -387,7 +387,7 @@ static void PlayerPartnerHandleChoosePokemon(u32 battler) // Switching out else if (gBattleStruct->monToSwitchIntoId[battler] >= PARTY_SIZE || !IsValidForBattle(&gPlayerParty[gBattleStruct->monToSwitchIntoId[battler]])) { - chosenMonId = GetMostSuitableMonToSwitchInto(battler, TRUE); + chosenMonId = GetMostSuitableMonToSwitchInto(battler, SWITCH_AFTER_KO); if (chosenMonId == PARTY_SIZE || !IsValidForBattle(&gPlayerParty[chosenMonId])) // just switch to the next mon { diff --git a/src/battle_main.c b/src/battle_main.c index f47c54bcf5..6e51a4d69f 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4145,7 +4145,7 @@ enum STATE_SELECTION_SCRIPT_MAY_RUN }; -void SetupAISwitchingData(u32 battler, bool32 isAiRisky) +void SetupAISwitchingData(u32 battler, enum SwitchType switchType) { s32 opposingBattler = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerPosition(battler))); @@ -4154,7 +4154,7 @@ void SetupAISwitchingData(u32 battler, bool32 isAiRisky) { AI_DATA->aiSwitchPredictionInProgress = TRUE; AI_DATA->battlerDoingPrediction = battler; - AI_DATA->mostSuitableMonId[opposingBattler] = GetMostSuitableMonToSwitchInto(opposingBattler, isAiRisky); + AI_DATA->mostSuitableMonId[opposingBattler] = GetMostSuitableMonToSwitchInto(opposingBattler, switchType); if (ShouldSwitch(opposingBattler)) AI_DATA->shouldSwitch |= (1u << opposingBattler); AI_DATA->aiSwitchPredictionInProgress = FALSE; @@ -4164,7 +4164,7 @@ void SetupAISwitchingData(u32 battler, bool32 isAiRisky) } // AI's data - AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, isAiRisky); + AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, switchType); if (ShouldSwitch(battler)) AI_DATA->shouldSwitch |= (1u << battler); } @@ -4182,7 +4182,7 @@ static void HandleTurnActionSelectionState(void) case STATE_TURN_START_RECORD: // Recorded battle related action on start of every turn. RecordedBattle_CopyBattlerMoves(battler); gBattleCommunication[battler] = STATE_BEFORE_ACTION_CHOSEN; - u32 isAiRisky = AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_RISKY; // Risky AI switches aggressively even mid battle + enum SwitchType switchType = (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_RISKY) ? SWITCH_AFTER_KO : SWITCH_MID_BATTLE; // Risky AI switches aggressively even mid battle // Do AI score computations here so we can use them in AI_TrySwitchOrUseItem if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) @@ -4192,7 +4192,7 @@ static void HandleTurnActionSelectionState(void) // Setup battler data BattleAI_SetupAIData(0xF, battler); - SetupAISwitchingData(battler, isAiRisky); + SetupAISwitchingData(battler, switchType); // Do scoring gBattleStruct->aiMoveOrAction[battler] = BattleAI_ChooseMoveOrAction(battler); 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 13/28] 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 855851586b..4dc9c9b45e 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 9ad4ee7e5f..ce35cf72c1 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!"); + } +} From 41bda980318c2bb37b12d99994f1863abea30625 Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Thu, 6 Feb 2025 12:22:01 -0500 Subject: [PATCH 14/28] Unify setreflect/setlightscreen and MOVE_EFFECT_REFLECT/LIGHT_SCREEN (#6196) Co-authored-by: ghoulslash --- src/battle_script_commands.c | 107 ++++++++++++++++------------------- 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index f5af50f450..1e57dfab2c 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -3090,6 +3090,50 @@ void StealTargetItem(u8 battlerStealer, u8 battlerItem) TrySaveExchangedItem(battlerItem, gLastUsedItem); } +static inline bool32 TrySetReflect(u32 battler) +{ + u32 side = GetBattlerSide(battler); + if (!(gSideStatuses[side] & SIDE_STATUS_REFLECT)) + { + gSideStatuses[side] |= SIDE_STATUS_REFLECT; + if (GetBattlerHoldEffect(battler, TRUE) == HOLD_EFFECT_LIGHT_CLAY) + gSideTimers[side].reflectTimer = gBattleTurnCounter + 8; + else + gSideTimers[side].reflectTimer = gBattleTurnCounter + 5; + gSideTimers[side].reflectBattlerId = battler; + + if (IsDoubleBattle() && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, battler) == 2) + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_REFLECT_DOUBLE; + else + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_REFLECT_SINGLE; + + return TRUE; + } + return FALSE; +} + +static inline bool32 TrySetLightScreen(u32 battler) +{ + u32 side = GetBattlerSide(battler); + if (!(gSideStatuses[side] & SIDE_STATUS_LIGHTSCREEN)) + { + gSideStatuses[side] |= SIDE_STATUS_LIGHTSCREEN; + if (GetBattlerHoldEffect(battler, TRUE) == HOLD_EFFECT_LIGHT_CLAY) + gSideTimers[side].lightscreenTimer = gBattleTurnCounter + 8; + else + gSideTimers[side].lightscreenTimer = gBattleTurnCounter + 5; + gSideTimers[side].lightscreenBattlerId = battler; + + if (IsDoubleBattle() && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, battler) == 2) + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_LIGHTSCREEN_DOUBLE; + else + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_LIGHTSCREEN_SINGLE; + + return TRUE; + } + return FALSE; +} + #define INCREMENT_RESET_RETURN \ { \ gBattlescriptCurrInstr++; \ @@ -3110,7 +3154,6 @@ void SetMoveEffect(bool32 primary, bool32 certain) bool32 mirrorArmorReflected = (GetBattlerAbility(gBattlerTarget) == ABILITY_MIRROR_ARMOR); u32 flags = 0; u32 battlerAbility; - u32 side; bool8 activateAfterFaint = FALSE; // NULL move effect @@ -4220,41 +4263,15 @@ void SetMoveEffect(bool32 primary, bool32 certain) } break; case MOVE_EFFECT_REFLECT: - side = GetBattlerSide(gBattlerAttacker); - if (!(gSideStatuses[side] & SIDE_STATUS_REFLECT)) + if (TrySetReflect(gBattlerAttacker)) { - gSideStatuses[side] |= SIDE_STATUS_REFLECT; - if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LIGHT_CLAY) - gSideTimers[side].reflectTimer = gBattleTurnCounter + 8; - else - gSideTimers[side].reflectTimer = gBattleTurnCounter + 5; - gSideTimers[side].reflectBattlerId = gBattlerAttacker; - - if (IsDoubleBattle() && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, gBattlerAttacker) == 2) - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_REFLECT_DOUBLE; - else - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_REFLECT_SINGLE; - BattleScriptPush(gBattlescriptCurrInstr + 1); gBattlescriptCurrInstr = BattleScript_MoveEffectReflect; } break; case MOVE_EFFECT_LIGHT_SCREEN: - side = GetBattlerSide(gBattlerAttacker); - if (!(gSideStatuses[side] & SIDE_STATUS_LIGHTSCREEN)) + if (TrySetLightScreen(gBattlerAttacker)) { - gSideStatuses[side] |= SIDE_STATUS_LIGHTSCREEN; - if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LIGHT_CLAY) - gSideTimers[side].lightscreenTimer = gBattleTurnCounter + 8; - else - gSideTimers[side].lightscreenTimer = gBattleTurnCounter + 5; - gSideTimers[side].lightscreenBattlerId = gBattlerAttacker; - - if (IsDoubleBattle() && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, gBattlerAttacker) == 2) - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_LIGHTSCREEN_DOUBLE; - else - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_LIGHTSCREEN_SINGLE; - BattleScriptPush(gBattlescriptCurrInstr + 1); gBattlescriptCurrInstr = BattleScript_MoveEffectLightScreen; } @@ -11626,26 +11643,12 @@ static void Cmd_setfieldweather(void) static void Cmd_setreflect(void) { CMD_ARGS(); - - if (gSideStatuses[GetBattlerSide(gBattlerAttacker)] & SIDE_STATUS_REFLECT) + if (!TrySetReflect(gBattlerAttacker)) { gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SIDE_STATUS_FAILED; } - else - { - gSideStatuses[GetBattlerSide(gBattlerAttacker)] |= SIDE_STATUS_REFLECT; - if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LIGHT_CLAY) - gSideTimers[GetBattlerSide(gBattlerAttacker)].reflectTimer = gBattleTurnCounter + 8; - else - gSideTimers[GetBattlerSide(gBattlerAttacker)].reflectTimer = gBattleTurnCounter + 5; - gSideTimers[GetBattlerSide(gBattlerAttacker)].reflectBattlerId = gBattlerAttacker; - if (IsDoubleBattle() && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, gBattlerAttacker) == 2) - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_REFLECT_DOUBLE; - else - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_REFLECT_SINGLE; - } gBattlescriptCurrInstr = cmd->nextInstr; } @@ -12674,25 +12677,11 @@ static void Cmd_setlightscreen(void) { CMD_ARGS(); - if (gSideStatuses[GetBattlerSide(gBattlerAttacker)] & SIDE_STATUS_LIGHTSCREEN) + if (!TrySetLightScreen(gBattlerAttacker)) { gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SIDE_STATUS_FAILED; } - else - { - gSideStatuses[GetBattlerSide(gBattlerAttacker)] |= SIDE_STATUS_LIGHTSCREEN; - if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LIGHT_CLAY) - gSideTimers[GetBattlerSide(gBattlerAttacker)].lightscreenTimer = gBattleTurnCounter + 8; - else - gSideTimers[GetBattlerSide(gBattlerAttacker)].lightscreenTimer = gBattleTurnCounter + 5; - gSideTimers[GetBattlerSide(gBattlerAttacker)].lightscreenBattlerId = gBattlerAttacker; - - if (IsDoubleBattle() && CountAliveMonsInBattle(BATTLE_ALIVE_SIDE, gBattlerAttacker) == 2) - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_LIGHTSCREEN_DOUBLE; - else - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_LIGHTSCREEN_SINGLE; - } gBattlescriptCurrInstr = cmd->nextInstr; } From 3ba4e3c0c73197e807e4ac38b8ef5c1a6d16cea5 Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:17:57 -0500 Subject: [PATCH 15/28] Sheer Force Test Fixes (#6198) Co-authored-by: ghoulslash --- test/battle/ability/sheer_force.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/battle/ability/sheer_force.c b/test/battle/ability/sheer_force.c index e06e56c2c5..bd8d57a168 100644 --- a/test/battle/ability/sheer_force.c +++ b/test/battle/ability/sheer_force.c @@ -33,7 +33,7 @@ SINGLE_BATTLE_TEST("Sheer Force doesn't boost Eruption", s16 damage) PLAYER(SPECIES_TAUROS) { Ability(ability); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_PRESENT); } + TURN { MOVE(player, MOVE_ERUPTION); } } SCENE { HP_BAR(opponent, captureDamage: &results[i].damage); } FINALLY { @@ -50,7 +50,7 @@ SINGLE_BATTLE_TEST("Sheer Force doesn't boost Water Spout", s16 damage) PLAYER(SPECIES_TAUROS) { Ability(ability); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_PRESENT); } + TURN { MOVE(player, MOVE_WATER_SPOUT); } } SCENE { HP_BAR(opponent, captureDamage: &results[i].damage); } FINALLY { @@ -347,7 +347,7 @@ SINGLE_BATTLE_TEST("Sheer Force doesn't boost Comeuppance", s16 damage) EXPECT_NE(results[0].damage, 0); } } -SINGLE_BATTLE_TEST("Sheer Force doesn't boost Comeuppance", s16 damage) +SINGLE_BATTLE_TEST("Sheer Force doesn't boost Payback", s16 damage) { u16 ability = 0; PARAMETRIZE { ability = ABILITY_SHEER_FORCE; } From f9d1fdcf4cedb6435cc234ceeaed4ddce954e429 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Thu, 6 Feb 2025 21:12:42 +0100 Subject: [PATCH 16/28] Follower Object Event refactor (#6129) Co-authored-by: Hedara --- include/constants/event_objects.h | 20 +-- include/event_object_movement.h | 8 -- src/event_object_movement.c | 225 +++++++++++++----------------- src/load_save.c | 2 +- src/pokemon_sprite_visualizer.c | 18 ++- src/scrcmd.c | 2 +- src/shop.c | 2 +- 7 files changed, 117 insertions(+), 160 deletions(-) diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index 8891ab27fb..219b990c5d 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -270,19 +270,23 @@ #define OBJ_EVENT_GFX_VAR_E (OBJ_EVENT_GFX_VARS + 0xE) #define OBJ_EVENT_GFX_VAR_F (OBJ_EVENT_GFX_VARS + 0xF) -#define OBJ_EVENT_GFX_MON_BASE 0x200 // 512 -#define OBJ_EVENT_GFX_SPECIES_BITS 12 // This will need to be updated when NUM_SPECIES is > ~3.5k -#define OBJ_EVENT_GFX_SPECIES_MASK ((1 << OBJ_EVENT_GFX_SPECIES_BITS) - 1) +#define OBJ_EVENT_MON (1 << 13) +#define OBJ_EVENT_MON_SHINY (1 << 14) +#define OBJ_EVENT_MON_FEMALE (1 << 15) +#define OBJ_EVENT_MON_SPECIES_MASK (OBJ_EVENT_MON - 1) // Used to call a specific species' follower graphics. Useful for static encounters. -#define OBJ_EVENT_GFX_SPECIES(name) (SPECIES_##name + OBJ_EVENT_GFX_MON_BASE) -#define OBJ_EVENT_GFX_SPECIES_SHINY(name) (SPECIES_##name + OBJ_EVENT_GFX_MON_BASE + SPECIES_SHINY_TAG) +#define OBJ_EVENT_GFX_SPECIES(name) (SPECIES_##name + OBJ_EVENT_MON) +#define OBJ_EVENT_GFX_SPECIES_SHINY(name) (SPECIES_##name + OBJ_EVENT_MON + OBJ_EVENT_MON_SHINY) +#define OBJ_EVENT_GFX_SPECIES_FEMALE(name) (SPECIES_##name + OBJ_EVENT_MON + OBJ_EVENT_MON_FEMALE) +#define OBJ_EVENT_GFX_SPECIES_SHINY_FEMALE(name) (SPECIES_##name + OBJ_EVENT_MON + OBJ_EVENT_MON_SHINY + OBJ_EVENT_MON_FEMALE) -#define OW_SPECIES(x) (((x)->graphicsId & OBJ_EVENT_GFX_SPECIES_MASK) - OBJ_EVENT_GFX_MON_BASE) -#define OW_FORM(x) ((x)->graphicsId >> OBJ_EVENT_GFX_SPECIES_BITS) +#define OW_SPECIES(x) ((x)->graphicsId & OBJ_EVENT_MON_SPECIES_MASK) +#define OW_SHINY(x) ((x)->graphicsId & OBJ_EVENT_MON_SHINY) +#define OW_FEMALE(x) ((x)->graphicsId & OBJ_EVENT_MON_FEMALE) // Whether Object Event is an OW pokemon -#define IS_OW_MON_OBJ(obj) ((obj)->graphicsId >= OBJ_EVENT_GFX_MON_BASE) +#define IS_OW_MON_OBJ(obj) ((obj)->graphicsId & OBJ_EVENT_MON) #define SHADOW_SIZE_S 0 #define SHADOW_SIZE_M 1 diff --git a/include/event_object_movement.h b/include/event_object_movement.h index 4b1b3e1f9f..1dc604138b 100644 --- a/include/event_object_movement.h +++ b/include/event_object_movement.h @@ -107,13 +107,6 @@ struct LockedAnimObjectEvents u8 count; }; -struct FollowerSpriteVisualizerData -{ - u16 currentmonId; - bool8 isShiny; - bool8 isFemale; -}; - extern const struct OamData gObjectEventBaseOam_32x8; extern const struct OamData gObjectEventBaseOam_32x32; extern const struct OamData gObjectEventBaseOam_64x64; @@ -153,7 +146,6 @@ void RemoveFollowingPokemon(void); struct ObjectEvent *GetFollowerObject(void); void TrySpawnObjectEvents(s16 cameraX, s16 cameraY); u8 CreateObjectGraphicsSprite(u16, void (*)(struct Sprite *), s16 x, s16 y, u8 subpriority); -u8 CreateObjectGraphicsFollowerSpriteForVisualizer(u16, void (*)(struct Sprite *), s16 x, s16 y, u8 subpriority, struct FollowerSpriteVisualizerData *data); u8 TrySpawnObjectEvent(u8 localId, u8 mapNum, u8 mapGroup); u8 SpawnSpecialObjectEventParameterized(u16 graphicsId, u8 movementBehavior, u8 localId, s16 x, s16 y, u8 elevation); u8 SpawnSpecialObjectEvent(struct ObjectEventTemplate *); diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 0645d7073a..c0b24c9cbe 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -185,7 +185,7 @@ static void SetSpriteDataForNormalStep(struct Sprite *, u8, u8); static void InitSpriteForFigure8Anim(struct Sprite *); static bool8 AnimateSpriteInFigure8(struct Sprite *); u8 GetDirectionToFace(s16 x1, s16 y1, s16 x2, s16 y2); -static void FollowerSetGraphics(struct ObjectEvent *, u16, u8, bool8); +static void FollowerSetGraphics(struct ObjectEvent * objectEvent, u32 species, bool32 shiny, bool32 female); static void ObjectEventSetGraphics(struct ObjectEvent *, const struct ObjectEventGraphicsInfo *); static void SpriteCB_VirtualObject(struct Sprite *); static void DoShadowFieldEffect(struct ObjectEvent *); @@ -196,14 +196,17 @@ static u8 DoJumpSpriteMovement(struct Sprite *); static u8 DoJumpSpecialSpriteMovement(struct Sprite *); static void CreateLevitateMovementTask(struct ObjectEvent *); static void DestroyLevitateMovementTask(u8); -static bool8 GetFollowerInfo(u16 *species, u8 *form, u8 *shiny); -static u8 LoadDynamicFollowerPalette(u16 species, u8 form, bool32 shiny); -static const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u16 species, u8 form); +static bool8 GetFollowerInfo(u32 *species, bool32 *shiny, bool32 *female); +static u32 LoadDynamicFollowerPalette(u32 species, bool32 shiny, bool32 female); +static const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u32 species, bool32 shiny, bool32 female); static bool8 NpcTakeStep(struct Sprite *); static bool8 IsElevationMismatchAt(u8, s16, s16); static bool8 AreElevationsCompatible(u8, u8); static void CopyObjectGraphicsInfoToSpriteTemplate_WithMovementType(u16 graphicsId, u16 movementType, struct SpriteTemplate *spriteTemplate, const struct SubspriteTable **subspriteTables); +static u16 GetGraphicsIdForMon(u32 species, bool32 shiny, bool32 female); +static u16 GetUnownSpecies(struct Pokemon *mon); + static const struct SpriteFrameImage sPicTable_PechaBerryTree[]; static void StartSlowRunningAnim(struct ObjectEvent *objectEvent, struct Sprite *sprite, u8 direction); @@ -1678,17 +1681,8 @@ static u8 TrySetupObjectEventSprite(const struct ObjectEventTemplate *objectEven if (OW_GFX_COMPRESS) spriteTemplate->tileTag = LoadSheetGraphicsInfo(graphicsInfo, objectEvent->graphicsId, NULL); - if (objectEvent->graphicsId >= OBJ_EVENT_GFX_MON_BASE + SPECIES_SHINY_TAG) - { + if (objectEvent->graphicsId & OBJ_EVENT_MON && objectEvent->graphicsId & OBJ_EVENT_MON_SHINY) objectEvent->shiny = TRUE; - objectEvent->graphicsId -= SPECIES_SHINY_TAG; - } - - if (objectEvent->graphicsId >= OBJ_EVENT_GFX_MON_BASE + SPECIES_SHINY_TAG) - { - objectEvent->shiny = TRUE; - objectEvent->graphicsId -= SPECIES_SHINY_TAG; - } spriteId = CreateSprite(spriteTemplate, 0, 0, 0); if (spriteId == MAX_SPRITES) @@ -1700,7 +1694,7 @@ static u8 TrySetupObjectEventSprite(const struct ObjectEventTemplate *objectEven sprite = &gSprites[spriteId]; // Use palette from species palette table if (spriteTemplate->paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) - sprite->oam.paletteNum = LoadDynamicFollowerPalette(OW_SPECIES(objectEvent), OW_FORM(objectEvent), objectEvent->shiny); + sprite->oam.paletteNum = LoadDynamicFollowerPalette(OW_SPECIES(objectEvent), OW_SHINY(objectEvent), OW_FEMALE(objectEvent)); if (OW_GFX_COMPRESS && sprite->usingSheet) sprite->sheetSpan = GetSpanPerImage(sprite->oam.shape, sprite->oam.size); GetMapCoordsFromSpritePos(objectEvent->currentCoords.x + cameraX, objectEvent->currentCoords.y + cameraY, &sprite->x, &sprite->y); @@ -1812,13 +1806,20 @@ static void UNUSED MakeSpriteTemplateFromObjectEventTemplate(const struct Object // Loads information from graphicsId, with shininess separate // also can write palette tag to the template -static u8 LoadDynamicFollowerPaletteFromGraphicsId(u16 graphicsId, bool8 shiny, struct SpriteTemplate *template) +static u32 LoadDynamicFollowerPaletteFromGraphicsId(u16 graphicsId, struct SpriteTemplate *template) { - u16 species = ((graphicsId & OBJ_EVENT_GFX_SPECIES_MASK) - OBJ_EVENT_GFX_MON_BASE); - u8 form = (graphicsId >> OBJ_EVENT_GFX_SPECIES_BITS); - u8 paletteNum = LoadDynamicFollowerPalette(species, form, shiny); + u16 species = graphicsId & OBJ_EVENT_MON_SPECIES_MASK; + bool32 shiny = graphicsId & OBJ_EVENT_MON_SHINY; + bool32 female = graphicsId & OBJ_EVENT_MON_FEMALE; + u8 paletteNum = LoadDynamicFollowerPalette(species, shiny, female); if (template) - template->paletteTag = species; + { + template->paletteTag = species + OBJ_EVENT_MON; + if (shiny) + template->paletteTag += OBJ_EVENT_MON_SHINY; + if (female) + template->paletteTag += OBJ_EVENT_MON_FEMALE; + } return paletteNum; } @@ -1830,7 +1831,7 @@ u8 CreateObjectGraphicsSprite(u16 graphicsId, void (*callback)(struct Sprite *), const struct ObjectEventGraphicsInfo *graphicsInfo = GetObjectEventGraphicsInfo(graphicsId); struct Sprite *sprite; u8 spriteId; - bool32 isShiny = graphicsId >= SPECIES_SHINY_TAG + OBJ_EVENT_GFX_MON_BASE; + bool32 isShiny = graphicsId & OBJ_EVENT_MON_SHINY; spriteTemplate = Alloc(sizeof(struct SpriteTemplate)); CopyObjectGraphicsInfoToSpriteTemplate(graphicsId, callback, spriteTemplate, &subspriteTables); @@ -1847,52 +1848,7 @@ u8 CreateObjectGraphicsSprite(u16 graphicsId, void (*callback)(struct Sprite *), if (spriteTemplate->paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) { - u32 paletteNum = LoadDynamicFollowerPaletteFromGraphicsId(graphicsId, isShiny, spriteTemplate); - spriteTemplate->paletteTag = GetSpritePaletteTagByPaletteNum(paletteNum); - } - else if (spriteTemplate->paletteTag != TAG_NONE) - { - LoadObjectEventPalette(spriteTemplate->paletteTag); - } - - spriteId = CreateSprite(spriteTemplate, x, y, subpriority); - - Free(spriteTemplate); - - if (spriteId != MAX_SPRITES && subspriteTables != NULL) - { - sprite = &gSprites[spriteId]; - if (OW_GFX_COMPRESS && graphicsInfo->compressed) - sprite->sheetSpan = GetSpanPerImage(sprite->oam.shape, sprite->oam.size); - SetSubspriteTables(sprite, subspriteTables); - sprite->subspriteMode = SUBSPRITES_IGNORE_PRIORITY; - } - return spriteId; -} - -// Horrible workaround for sprite the visualizer, this should probably be reworked later -u8 CreateObjectGraphicsFollowerSpriteForVisualizer(u16 graphicsId, void (*callback)(struct Sprite *), s16 x, s16 y, u8 subpriority, struct FollowerSpriteVisualizerData *data) -{ - struct SpriteTemplate *spriteTemplate; - const struct SubspriteTable *subspriteTables; - const struct ObjectEventGraphicsInfo *graphicsInfo = GetObjectEventGraphicsInfo(graphicsId); - struct Sprite *sprite; - u8 spriteId; - bool32 isShiny = data->isShiny; - - spriteTemplate = Alloc(sizeof(struct SpriteTemplate)); - CopyObjectGraphicsInfoToSpriteTemplate(graphicsId, callback, spriteTemplate, &subspriteTables); - - if (OW_GFX_COMPRESS) - { - // Checking only for compressed here so as not to mess with decorations - if (graphicsInfo->compressed) - spriteTemplate->tileTag = LoadSheetGraphicsInfo(graphicsInfo, graphicsId, NULL); - } - - if (spriteTemplate->paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) - { - u32 paletteNum = LoadDynamicFollowerPaletteFromGraphicsId(graphicsId, isShiny, spriteTemplate); + u32 paletteNum = LoadDynamicFollowerPaletteFromGraphicsId(graphicsId, spriteTemplate); spriteTemplate->paletteTag = GetSpritePaletteTagByPaletteNum(paletteNum); } else if (spriteTemplate->paletteTag != TAG_NONE) @@ -1988,19 +1944,18 @@ struct ObjectEvent *GetFollowerObject(void) } // Return graphicsInfo for a pokemon species & form -static const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u16 species, u8 form) +static const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u32 species, bool32 shiny, bool32 female) { const struct ObjectEventGraphicsInfo *graphicsInfo = NULL; #if OW_POKEMON_OBJECT_EVENTS switch (species) { - case SPECIES_UNOWN: // Letters >A are defined as species >= NUM_SPECIES, so are not contiguous with A - form %= NUM_UNOWN_FORMS; - graphicsInfo = &gSpeciesInfo[form ? SPECIES_UNOWN_B + form - 1 : species].overworldData; + case SPECIES_UNOWN: // Deal with Unown forms later + graphicsInfo = &gSpeciesInfo[species].overworldData; break; default: #if P_GENDER_DIFFERENCES - if (form == 1 && gSpeciesInfo[species].overworldDataFemale.paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) + if (female && gSpeciesInfo[species].overworldDataFemale.paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) { graphicsInfo = &gSpeciesInfo[species].overworldDataFemale; } @@ -2024,17 +1979,18 @@ static const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u16 species, } // Find, or load, the palette for the specified pokemon info -static u8 LoadDynamicFollowerPalette(u16 species, u8 form, bool32 shiny) +static u32 LoadDynamicFollowerPalette(u32 species, bool32 shiny, bool32 female) { u32 paletteNum; - bool32 female = (form == 1); // Use standalone palette, unless entry is OOB or NULL (fallback to front-sprite-based) #if OW_POKEMON_OBJECT_EVENTS == TRUE && OW_PKMN_OBJECTS_SHARE_PALETTES == FALSE if ((shiny && gSpeciesInfo[species].overworldPalette) || (!shiny && gSpeciesInfo[species].overworldShinyPalette)) { struct SpritePalette spritePalette; - u16 palTag = shiny ? (species + SPECIES_SHINY_TAG + OBJ_EVENT_PAL_TAG_DYNAMIC) : (species + OBJ_EVENT_PAL_TAG_DYNAMIC); + u16 palTag = species + OBJ_EVENT_MON + (shiny ? OBJ_EVENT_MON_SHINY : 0); + if (female && gSpeciesInfo[species].overworldShinyPaletteFemale != NULL) + palTag += OBJ_EVENT_MON_FEMALE; // palette already loaded if ((paletteNum = IndexOfSpritePaletteTag(palTag)) < 16) return paletteNum; @@ -2056,7 +2012,6 @@ static u8 LoadDynamicFollowerPalette(u16 species, u8 form, bool32 shiny) spritePalette.data = gSpeciesInfo[species].overworldPalette; } - // Check if pal data must be decompressed if (IsLZ77Data(spritePalette.data, PLTT_SIZE_4BPP, PLTT_SIZE_4BPP)) { @@ -2087,13 +2042,11 @@ static u8 LoadDynamicFollowerPalette(u16 species, u8 form, bool32 shiny) } // Set graphics & sprite for a follower object event by species & shininess. -static void FollowerSetGraphics(struct ObjectEvent *objEvent, u16 species, u8 form, bool8 shiny) +static void FollowerSetGraphics(struct ObjectEvent *objEvent, u32 species, bool32 shiny, bool32 female) { - const struct ObjectEventGraphicsInfo *graphicsInfo = SpeciesToGraphicsInfo(species, form); + const struct ObjectEventGraphicsInfo *graphicsInfo = SpeciesToGraphicsInfo(species, shiny, female); ObjectEventSetGraphics(objEvent, graphicsInfo); - objEvent->graphicsId = (OBJ_EVENT_GFX_MON_BASE + species) & OBJ_EVENT_GFX_SPECIES_MASK; - objEvent->graphicsId |= form << OBJ_EVENT_GFX_SPECIES_BITS; - objEvent->shiny = shiny; + objEvent->graphicsId = GetGraphicsIdForMon(species, shiny, female); if (graphicsInfo->paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) // Use palette from species palette table { struct Sprite *sprite = &gSprites[objEvent->spriteId]; @@ -2101,7 +2054,7 @@ static void FollowerSetGraphics(struct ObjectEvent *objEvent, u16 species, u8 fo sprite->inUse = FALSE; FieldEffectFreePaletteIfUnused(sprite->oam.paletteNum); sprite->inUse = TRUE; - sprite->oam.paletteNum = LoadDynamicFollowerPalette(species, form, shiny); + sprite->oam.paletteNum = LoadDynamicFollowerPalette(species, shiny, female); } else if (gWeatherPtr->currWeather != WEATHER_FOG_HORIZONTAL) // don't want to weather blend in fog { @@ -2114,9 +2067,9 @@ static void FollowerSetGraphics(struct ObjectEvent *objEvent, u16 species, u8 fo static void RefreshFollowerGraphics(struct ObjectEvent *objEvent) { u32 species = OW_SPECIES(objEvent); - u32 form = OW_FORM(objEvent); - u32 shiny = objEvent->shiny; - const struct ObjectEventGraphicsInfo *graphicsInfo = SpeciesToGraphicsInfo(species, form); + bool32 shiny = OW_SHINY(objEvent); + bool32 female = OW_FEMALE(objEvent); + const struct ObjectEventGraphicsInfo *graphicsInfo = SpeciesToGraphicsInfo(species, shiny, female); struct Sprite *sprite = &gSprites[objEvent->spriteId]; u32 i = FindObjectEventPaletteIndexByTag(graphicsInfo->paletteTag); @@ -2145,7 +2098,7 @@ static void RefreshFollowerGraphics(struct ObjectEvent *objEvent) sprite->inUse = FALSE; FieldEffectFreePaletteIfUnused(sprite->oam.paletteNum); sprite->inUse = TRUE; - sprite->oam.paletteNum = LoadDynamicFollowerPalette(species, form, shiny); + sprite->oam.paletteNum = LoadDynamicFollowerPalette(species, shiny, female); } else if (i != 0xFF) { @@ -2172,23 +2125,22 @@ static u16 GetOverworldCastformSpecies(void) return SPECIES_CASTFORM_NORMAL; } -static bool8 GetMonInfo(struct Pokemon *mon, u16 *species, u8 *form, u8 *shiny) +static bool8 GetMonInfo(struct Pokemon *mon, u32 *species, bool32 *shiny, bool32 *female) { - *form = 0; // default if (!mon) { *species = SPECIES_NONE; - *form = 0; - *shiny = 0; + *shiny = FALSE; + *female = FALSE; return FALSE; } *species = GetMonData(mon, MON_DATA_SPECIES); - *form = GetMonGender(mon) == MON_FEMALE; - *shiny = IsMonShiny(mon); + *shiny = IsMonShiny(mon) ? OBJ_EVENT_MON_SHINY : 0; + *female = GetMonGender(mon) == MON_FEMALE ? OBJ_EVENT_MON_FEMALE : 0; switch (*species) { case SPECIES_UNOWN: - *form = GET_UNOWN_LETTER(mon->box.personality); + *species = GetUnownSpecies(mon); break; case SPECIES_CASTFORM: // form is based on overworld weather *species = GetOverworldCastformSpecies(); @@ -2198,9 +2150,9 @@ static bool8 GetMonInfo(struct Pokemon *mon, u16 *species, u8 *form, u8 *shiny) } // Retrieve graphic information about the following pokemon, if any -static bool8 GetFollowerInfo(u16 *species, u8 *form, u8 *shiny) +static bool8 GetFollowerInfo(u32 *species, bool32 *shiny, bool32 *female) { - return GetMonInfo(GetFirstLiveMon(), species, form, shiny); + return GetMonInfo(GetFirstLiveMon(), species, shiny, female); } // Update following pokemon if any @@ -2208,18 +2160,18 @@ void UpdateFollowingPokemon(void) { struct ObjectEvent *objEvent = GetFollowerObject(); struct Sprite *sprite; - u16 species; - bool8 shiny; - u8 form; + u32 species; + bool32 shiny; + bool32 female; // Don't spawn follower if: // 1. GetFollowerInfo returns FALSE // 2. Map is indoors and gfx is larger than 32x32 // 3. flag is set if (OW_POKEMON_OBJECT_EVENTS == FALSE || OW_FOLLOWERS_ENABLED == FALSE - || !GetFollowerInfo(&species, &form, &shiny) - || SpeciesToGraphicsInfo(species, form) == NULL - || (gMapHeader.mapType == MAP_TYPE_INDOOR && SpeciesToGraphicsInfo(species, form)->oam->size > ST_OAM_SIZE_2) + || !GetFollowerInfo(&species, &shiny, &female) + || SpeciesToGraphicsInfo(species, shiny, female) == NULL + || (gMapHeader.mapType == MAP_TYPE_INDOOR && SpeciesToGraphicsInfo(species, shiny, female)->oam->size > ST_OAM_SIZE_2) || FlagGet(FLAG_TEMP_HIDE_FOLLOWER)) { RemoveFollowingPokemon(); @@ -2233,7 +2185,7 @@ void UpdateFollowingPokemon(void) struct ObjectEventTemplate template = { .localId = OBJ_EVENT_ID_FOLLOWER, - .graphicsId = OBJ_EVENT_GFX_MON_BASE + species, + .graphicsId = GetGraphicsIdForMon(species, shiny, female), .flagId = 0, .x = gSaveBlock1Ptr->pos.x, .y = gSaveBlock1Ptr->pos.y, @@ -2241,7 +2193,7 @@ void UpdateFollowingPokemon(void) .elevation = gObjectEvents[objId].active ? gObjectEvents[objId].currentElevation : 3, .movementType = MOVEMENT_TYPE_FOLLOW_PLAYER, // store form info in template - .trainerRange_berryTreeId = (form & 0x1F) | (shiny << 5), + //.trainerRange_berryTreeId = (form & 0x1F) | (shiny << 5), // ???? what? }; if ((objId = SpawnSpecialObjectEvent(&template)) >= OBJECT_EVENTS_COUNT) return; @@ -2250,12 +2202,12 @@ void UpdateFollowingPokemon(void) } sprite = &gSprites[objEvent->spriteId]; // Follower appearance changed; move to player and set invisible - if (species != OW_SPECIES(objEvent) || shiny != objEvent->shiny || form != OW_FORM(objEvent)) + if (species != OW_SPECIES(objEvent) || shiny != OW_SHINY(objEvent) || female != OW_FEMALE(objEvent)) { MoveObjectEventToMapCoords(objEvent, gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x, gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y); - FollowerSetGraphics(objEvent, species, form, shiny); + FollowerSetGraphics(objEvent, species, shiny, female); objEvent->invisible = TRUE; } sprite->data[6] = 0; // set animation data @@ -2696,7 +2648,7 @@ static void SpawnObjectEventOnReturnToField(u8 objectEventId, s16 x, s16 y) if (spriteTemplate.paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) { - u32 paletteNum = LoadDynamicFollowerPalette(OW_SPECIES(objectEvent), OW_FORM(objectEvent), objectEvent->shiny); + u32 paletteNum = LoadDynamicFollowerPalette(OW_SPECIES(objectEvent), OW_SHINY(objectEvent), OW_FEMALE(objectEvent)); spriteTemplate.paletteTag = GetSpritePaletteTagByPaletteNum(paletteNum); } else if (spriteTemplate.paletteTag != TAG_NONE) @@ -2891,24 +2843,14 @@ static void SetBerryTreeGraphics(struct ObjectEvent *objectEvent, struct Sprite const struct ObjectEventGraphicsInfo *GetObjectEventGraphicsInfo(u16 graphicsId) { - u32 form = 0; - if (graphicsId >= OBJ_EVENT_GFX_VARS && graphicsId <= OBJ_EVENT_GFX_VAR_F) graphicsId = VarGetObjectEventGraphicsId(graphicsId - OBJ_EVENT_GFX_VARS); - if (graphicsId > OBJ_EVENT_GFX_SPECIES_MASK) - { - form = graphicsId >> OBJ_EVENT_GFX_SPECIES_BITS; - graphicsId = graphicsId & OBJ_EVENT_GFX_SPECIES_MASK; - } - if (graphicsId == OBJ_EVENT_GFX_BARD) - { return gMauvilleOldManGraphicsInfoPointers[GetCurrentMauvilleOldMan()]; - } - if (graphicsId >= OBJ_EVENT_GFX_MON_BASE) - return SpeciesToGraphicsInfo(graphicsId - OBJ_EVENT_GFX_MON_BASE, form); + if (graphicsId & OBJ_EVENT_MON) + return SpeciesToGraphicsInfo(graphicsId & OBJ_EVENT_MON_SPECIES_MASK, graphicsId & OBJ_EVENT_MON_SHINY, graphicsId & OBJ_EVENT_MON_FEMALE); if (graphicsId >= NUM_OBJ_EVENT_GFX) graphicsId = OBJ_EVENT_GFX_NINJA_BOY; @@ -5385,7 +5327,7 @@ static bool8 UpdateFollowerTransformEffect(struct ObjectEvent *objectEvent, stru objectEvent->graphicsId = multi; break; } - objectEvent->graphicsId += OBJ_EVENT_GFX_MON_BASE; + objectEvent->graphicsId += OBJ_EVENT_MON; RefreshFollowerGraphics(objectEvent); break; case TRANSFORM_TYPE_RANDOM_WILD: @@ -5396,7 +5338,7 @@ static bool8 UpdateFollowerTransformEffect(struct ObjectEvent *objectEvent, stru objectEvent->graphicsId = multi; break; } - objectEvent->graphicsId += OBJ_EVENT_GFX_MON_BASE; + objectEvent->graphicsId += OBJ_EVENT_MON; RefreshFollowerGraphics(objectEvent); objectEvent->graphicsId = multi; break; @@ -7457,7 +7399,7 @@ bool8 MovementAction_ExitPokeball_Step1(struct ObjectEvent *objectEvent, struct // Set graphics, palette, and affine animation else if (sprite->sDuration == animStepFrame) { - FollowerSetGraphics(objectEvent, OW_SPECIES(objectEvent), OW_FORM(objectEvent), objectEvent->shiny); + FollowerSetGraphics(objectEvent, OW_SPECIES(objectEvent), OW_SHINY(objectEvent), OW_FEMALE(objectEvent)); LoadFillColorPalette(RGB_WHITE, OBJ_EVENT_PAL_TAG_WHITE, sprite); // Initialize affine animation sprite->affineAnims = sAffineAnims_PokeballFollower; @@ -7474,7 +7416,7 @@ bool8 MovementAction_ExitPokeball_Step1(struct ObjectEvent *objectEvent, struct sprite->affineAnimEnded = TRUE; FreeSpriteOamMatrix(sprite); sprite->oam.affineMode = ST_OAM_AFFINE_OFF; - FollowerSetGraphics(objectEvent, OW_SPECIES(objectEvent), OW_FORM(objectEvent), objectEvent->shiny); + FollowerSetGraphics(objectEvent, OW_SPECIES(objectEvent), OW_SHINY(objectEvent), OW_FEMALE(objectEvent)); } return FALSE; } @@ -7530,7 +7472,7 @@ bool8 MovementAction_EnterPokeball_Step1(struct ObjectEvent *objectEvent, struct bool8 MovementAction_EnterPokeball_Step2(struct ObjectEvent *objectEvent, struct Sprite *sprite) { - FollowerSetGraphics(objectEvent, OW_SPECIES(objectEvent), OW_FORM(objectEvent), objectEvent->shiny); + FollowerSetGraphics(objectEvent, OW_SPECIES(objectEvent), OW_SHINY(objectEvent), OW_FEMALE(objectEvent)); objectEvent->invisible = TRUE; sprite->sTypeFuncId = 0; sprite->sSpeedFlip = 0; @@ -10992,20 +10934,23 @@ void GetDaycareGraphics(struct ScriptContext *ctx) { u16 varGfx[] = {ScriptReadHalfword(ctx), ScriptReadHalfword(ctx)}; u16 varForm[] = {ScriptReadHalfword(ctx), ScriptReadHalfword(ctx)}; - u16 specGfx; - u8 form; - u8 shiny; + u32 specGfx; + bool32 shiny; + bool32 female; s32 i; for (i = 0; i < 2; i++) { - GetMonInfo((struct Pokemon *) &gSaveBlock1Ptr->daycare.mons[i].mon, &specGfx, &form, &shiny); + GetMonInfo((struct Pokemon *) &gSaveBlock1Ptr->daycare.mons[i].mon, &specGfx, &shiny, &female); if (specGfx == SPECIES_NONE) break; // Assemble gfx ID like FollowerSetGraphics - specGfx = (OBJ_EVENT_GFX_MON_BASE + specGfx) & OBJ_EVENT_GFX_SPECIES_MASK; - specGfx |= form << OBJ_EVENT_GFX_SPECIES_BITS; - VarSet(varGfx[i], specGfx); - VarSet(varForm[i], form | (shiny << 5)); + specGfx = specGfx + OBJ_EVENT_MON; + if (shiny) + specGfx += OBJ_EVENT_MON_SHINY; + if (female) + specGfx += OBJ_EVENT_MON_FEMALE; + VarSet(varGfx[i], (u16)specGfx); + VarSet(varForm[i], 0); // This shouldn't be needed anymore, track down } gSpecialVar_Result = i; } @@ -11056,3 +11001,21 @@ bool8 MovementActionFunc_RunSlow_Step1(struct ObjectEvent *objectEvent, struct S } return FALSE; } + +static u16 GetGraphicsIdForMon(u32 species, bool32 shiny, bool32 female) +{ + u16 graphicsId = species + OBJ_EVENT_MON; + if (shiny) + graphicsId += OBJ_EVENT_MON_SHINY; + if (female) + graphicsId += OBJ_EVENT_MON_FEMALE; + return graphicsId; +} + +static u16 GetUnownSpecies(struct Pokemon *mon) +{ + u32 form = GET_UNOWN_LETTER(mon->box.personality); + if (form == 0) + return SPECIES_UNOWN; + return SPECIES_UNOWN_B + form - 1; +} diff --git a/src/load_save.c b/src/load_save.c index 6fde540b8c..9c94808536 100644 --- a/src/load_save.c +++ b/src/load_save.c @@ -235,7 +235,7 @@ void LoadObjectEvents(void) // Try to restore saved inactive follower if (gObjectEvents[i].localId == OBJ_EVENT_ID_FOLLOWER && !gObjectEvents[i].active && - gObjectEvents[i].graphicsId >= OBJ_EVENT_GFX_MON_BASE) + gObjectEvents[i].graphicsId & OBJ_EVENT_MON) gObjectEvents[i].active = TRUE; } } diff --git a/src/pokemon_sprite_visualizer.c b/src/pokemon_sprite_visualizer.c index c6bb39faaf..f798b08b97 100644 --- a/src/pokemon_sprite_visualizer.c +++ b/src/pokemon_sprite_visualizer.c @@ -1310,7 +1310,7 @@ void CB2_Pokemon_Sprite_Visualizer(void) gSprites[data->iconspriteId].oam.priority = 0; //Follower Sprite - data->followerspriteId = CreateObjectGraphicsSprite(OBJ_EVENT_GFX_MON_BASE + species, SpriteCB_Follower, VISUALIZER_FOLLOWER_X, VISUALIZER_FOLLOWER_Y, 0); + data->followerspriteId = CreateObjectGraphicsSprite(OBJ_EVENT_MON + species, SpriteCB_Follower, VISUALIZER_FOLLOWER_X, VISUALIZER_FOLLOWER_Y, 0); gSprites[data->followerspriteId].oam.priority = 0; gSprites[data->followerspriteId].anims = sAnims_Follower; @@ -2001,18 +2001,16 @@ static void ReloadPokemonSprites(struct PokemonSpriteVisualizer *data) gSprites[data->iconspriteId].oam.priority = 0; //Follower Sprite - u16 graphicsId = (OBJ_EVENT_GFX_MON_BASE + species) & OBJ_EVENT_GFX_SPECIES_MASK; - struct FollowerSpriteVisualizerData followerData; - followerData.currentmonId = graphicsId; - followerData.isFemale = data->isFemale; - followerData.isShiny = data->isShiny; - graphicsId |= data->isFemale << OBJ_EVENT_GFX_SPECIES_BITS; - data->followerspriteId = CreateObjectGraphicsFollowerSpriteForVisualizer(graphicsId, + u16 graphicsId = species + OBJ_EVENT_MON; + if (data->isShiny) + graphicsId += OBJ_EVENT_MON_SHINY; + if (data->isFemale) + graphicsId += OBJ_EVENT_MON_FEMALE; + data->followerspriteId = CreateObjectGraphicsSprite(graphicsId, SpriteCB_Follower, VISUALIZER_FOLLOWER_X, VISUALIZER_FOLLOWER_Y, - 0, - &followerData); + 0); gSprites[data->followerspriteId].oam.priority = 0; gSprites[data->followerspriteId].anims = sAnims_Follower; diff --git a/src/scrcmd.c b/src/scrcmd.c index 87e79745bd..58a7c510a6 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -1701,7 +1701,7 @@ bool8 ScrCmd_vmessage(struct ScriptContext *ctx) bool8 ScrCmd_bufferspeciesname(struct ScriptContext *ctx) { u8 stringVarIndex = ScriptReadByte(ctx); - u16 species = VarGet(ScriptReadHalfword(ctx)) & OBJ_EVENT_GFX_SPECIES_MASK; // ignore possible shiny / form bits + u16 species = VarGet(ScriptReadHalfword(ctx)) & OBJ_EVENT_MON_SPECIES_MASK; // ignore possible shiny / form bits StringCopy(sScriptStringVars[stringVarIndex], GetSpeciesName(species)); return FALSE; diff --git a/src/shop.c b/src/shop.c index effb638984..d73e4ecd13 100644 --- a/src/shop.c +++ b/src/shop.c @@ -871,7 +871,7 @@ static void BuyMenuCollectObjectEventData(void) u8 objEventId = GetObjectEventIdByXY(facingX - 4 + x, facingY - 2 + y); // skip if invalid or an overworld pokemon that is not following the player - if (objEventId != OBJECT_EVENTS_COUNT && !(gObjectEvents[objEventId].active && gObjectEvents[objEventId].graphicsId >= OBJ_EVENT_GFX_MON_BASE && gObjectEvents[objEventId].localId != OBJ_EVENT_ID_FOLLOWER)) + if (objEventId != OBJECT_EVENTS_COUNT && !(gObjectEvents[objEventId].active && gObjectEvents[objEventId].graphicsId & OBJ_EVENT_MON && gObjectEvents[objEventId].localId != OBJ_EVENT_ID_FOLLOWER)) { sShopData->viewportObjects[numObjects][OBJ_EVENT_ID] = objEventId; sShopData->viewportObjects[numObjects][X_COORD] = x; From 58eb635499b921cc751c381d73408e6cf4fb3a71 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Thu, 6 Feb 2025 21:20:38 +0100 Subject: [PATCH 17/28] Follower Object Event refactor (#6129) Co-authored-by: Hedara From 473cf265a40a6bb88bc6daaa4e82d29c076c077f Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Thu, 6 Feb 2025 20:03:57 -0300 Subject: [PATCH 18/28] Fixed regression from master/upcoming merge (#6199) --- src/battle_util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/battle_util.c b/src/battle_util.c index f43e77a1f6..2b284a27de 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8783,8 +8783,8 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) else if (gProtectStructs[battlerDef].maxGuarded && IsMoveBlockedByMaxGuard(move)) isProtected = TRUE; else 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) isProtected = FALSE; else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_CRAFTY_SHIELD && IsBattleMoveStatus(move) && GetMoveEffect(move) != EFFECT_COACHING) isProtected = TRUE; From 765a12f52056bf4a12806992f2bf8cad2a1a652f Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 7 Feb 2025 01:02:39 +0100 Subject: [PATCH 19/28] Convert max effects to normal move effects (#6143) --- asm/macros/battle_script.inc | 38 ++- data/battle_scripts_1.s | 159 +++++++--- include/battle_dynamax.h | 56 +--- include/battle_scripts.h | 10 +- include/constants/battle.h | 228 ++++++++------ include/move.h | 6 - src/battle_ai_util.c | 4 +- src/battle_anim_new.c | 24 +- src/battle_dynamax.c | 542 +-------------------------------- src/battle_script_commands.c | 549 +++++++++++++++++++++++++++++++++- src/battle_util.c | 2 +- src/data/moves_info.h | 301 +++++++++++++++---- test/battle/gimmick/dynamax.c | 66 ++-- test/battle/sleep_clause.c | 4 +- 14 files changed, 1151 insertions(+), 838 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index d57d95ffcd..ae47761d7c 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1555,13 +1555,43 @@ .4byte \failInstr .endm - .macro trysetstatus1, ptr:req - callnative BS_TrySetStatus1 + .macro trysetparalysis, ptr:req + callnative BS_TrySetParalysis .4byte \ptr .endm - .macro trysetstatus2, ptr:req - callnative BS_TrySetStatus2 + .macro trysetpoison, ptr:req + callnative BS_TrySetPoison + .4byte \ptr + .endm + + .macro trysetpoisonparalysis, ptr:req + callnative BS_TrySetPoisonParalyzis + .4byte \ptr + .endm + + .macro tryseteffectspore, ptr:req + callnative BS_TrySetEffectSpore + .4byte \ptr + .endm + + .macro trysetconfusion, ptr:req + callnative BS_TrySetConfusion + .4byte \ptr + .endm + + .macro trysetinfatuation, ptr:req + callnative BS_TrySetInfatuation + .4byte \ptr + .endm + + .macro trysetescapeprevention, ptr:req + callnative BS_TrySetEscapePrevention + .4byte \ptr + .endm + + .macro trysettorment, ptr:req + callnative BS_TrySetTorment .4byte \ptr .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index eb84d295fe..7f796eb0be 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -9699,7 +9699,7 @@ BattleScript_EffectMaxMove:: resultmessage waitmessage B_WAIT_TIME_LONG tryfaintmon BS_TARGET - setmaxmoveeffect + setadditionaleffects moveendall end @@ -9773,61 +9773,158 @@ BattleScript_EffectTryReducePP:: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd -BattleScript_EffectStatus1Foes:: +BattleScript_EffectParalyzeSide:: savetarget copybyte sBATTLER, gBattlerTarget -BattleScript_Status1FoesLoop: - jumpifabsent BS_TARGET, BattleScript_Status1FoesIncrement - trysetstatus1 BattleScript_Status1FoesIncrement +BattleScript_ParalyzeSideLoop: + jumpifabsent BS_TARGET, BattleScript_ParalyzeSideIncrement + trysetparalysis BattleScript_ParalyzeSideIncrement statusanimation BS_TARGET updatestatusicon BS_TARGET printfromtable gStatusConditionsStringIds waitmessage B_WAIT_TIME_LONG updatestatusicon BS_EFFECT_BATTLER waitstate -BattleScript_Status1FoesIncrement: - jumpifbytenotequal gBattlerTarget, sBATTLER, BattleScript_Status1FoesEnd - setallytonexttarget BattleScript_Status1FoesLoop -BattleScript_Status1FoesEnd: +BattleScript_ParalyzeSideIncrement: + jumpifbytenotequal gBattlerTarget, sBATTLER, BattleScript_ParalyzeSideEnd + setallytonexttarget BattleScript_ParalyzeSideLoop +BattleScript_ParalyzeSideEnd: restoretarget goto BattleScript_MoveEnd -BattleScript_EffectStatus2Foes:: +BattleScript_EffectPoisonSide:: savetarget copybyte sBATTLER, gBattlerTarget -BattleScript_Status2FoesLoop: - jumpifabsent BS_TARGET, BattleScript_Status2FoesIncrement - trysetstatus2 BattleScript_Status2FoesIncrement - jumpifbyte CMP_EQUAL, gBattleCommunication, 1, BattleScript_DoConfuseAnim - jumpifbyte CMP_EQUAL, gBattleCommunication, 2, BattleScript_DoInfatuationAnim -BattleScript_Status2FoesPrintMessage: +BattleScript_PoisonSideLoop: + jumpifabsent BS_TARGET, BattleScript_PoisonSideIncrement + trysetpoison BattleScript_PoisonSideIncrement + statusanimation BS_TARGET + updatestatusicon BS_TARGET + printfromtable gStatusConditionsStringIds + waitmessage B_WAIT_TIME_LONG + updatestatusicon BS_EFFECT_BATTLER + waitstate +BattleScript_PoisonSideIncrement: + jumpifbytenotequal gBattlerTarget, sBATTLER, BattleScript_PoisonSideEnd + setallytonexttarget BattleScript_PoisonSideLoop +BattleScript_PoisonSideEnd: + restoretarget + goto BattleScript_MoveEnd + +BattleScript_EffectPoisonParalyzeSide:: + savetarget + copybyte sBATTLER, gBattlerTarget +BattleScript_PoisonParalyzeSideLoop: + jumpifabsent BS_TARGET, BattleScript_PoisonParalyzeSideIncrement + trysetpoisonparalysis BattleScript_PoisonParalyzeSideIncrement + statusanimation BS_TARGET + updatestatusicon BS_TARGET + printfromtable gStatusConditionsStringIds + waitmessage B_WAIT_TIME_LONG + updatestatusicon BS_EFFECT_BATTLER + waitstate +BattleScript_PoisonParalyzeSideIncrement: + jumpifbytenotequal gBattlerTarget, sBATTLER, BattleScript_PoisonParalyzeSideEnd + setallytonexttarget BattleScript_PoisonParalyzeSideLoop +BattleScript_PoisonParalyzeSideEnd: + restoretarget + goto BattleScript_MoveEnd + +BattleScript_EffectEffectSporeSide:: + savetarget + copybyte sBATTLER, gBattlerTarget +BattleScript_EffectSporeSideLoop: + jumpifabsent BS_TARGET, BattleScript_EffectSporeSideIncrement + tryseteffectspore BattleScript_EffectSporeSideIncrement + statusanimation BS_TARGET + updatestatusicon BS_TARGET + printfromtable gStatusConditionsStringIds + waitmessage B_WAIT_TIME_LONG + updatestatusicon BS_EFFECT_BATTLER + waitstate +BattleScript_EffectSporeSideIncrement: + jumpifbytenotequal gBattlerTarget, sBATTLER, BattleScript_EffectSporeSideEnd + setallytonexttarget BattleScript_EffectSporeSideLoop +BattleScript_EffectSporeSideEnd: + restoretarget + goto BattleScript_MoveEnd + +BattleScript_EffectConfuseSide:: + savetarget + copybyte sBATTLER, gBattlerTarget +BattleScript_ConfuseSideLoop: + jumpifabsent BS_TARGET, BattleScript_ConfuseSideIncrement + trysetconfusion BattleScript_ConfuseSideIncrement + status2animation BS_EFFECT_BATTLER, STATUS2_CONFUSION +BattleScript_ConfuseSidePrintMessage: printfromtable gStatus2StringIds waitmessage B_WAIT_TIME_LONG -BattleScript_Status2FoesIncrement: - jumpifbytenotequal gBattlerTarget, sBATTLER, BattleScript_Status2FoesEnd - setallytonexttarget BattleScript_Status2FoesLoop -BattleScript_Status2FoesEnd: +BattleScript_ConfuseSideIncrement: + jumpifbytenotequal gBattlerTarget, sBATTLER, BattleScript_ConfuseSideEnd + setallytonexttarget BattleScript_ConfuseSideLoop +BattleScript_ConfuseSideEnd: restoretarget jumpifbyte CMP_EQUAL, gBattleCommunication + 1, 1, BattleScript_PrintCoinsScattered @ Gold Rush goto BattleScript_MoveEnd -BattleScript_DoConfuseAnim: - status2animation BS_EFFECT_BATTLER, STATUS2_CONFUSION - goto BattleScript_Status2FoesPrintMessage - -BattleScript_DoInfatuationAnim: - status2animation BS_EFFECT_BATTLER, STATUS2_INFATUATION - goto BattleScript_Status2FoesPrintMessage - BattleScript_PrintCoinsScattered: printstring STRINGID_COINSSCATTERED goto BattleScript_MoveEnd +BattleScript_EffectInfatuateSide:: + savetarget + copybyte sBATTLER, gBattlerTarget +BattleScript_InfatuateSideLoop: + jumpifabsent BS_TARGET, BattleScript_InfatuateSideIncrement + trysetinfatuation BattleScript_InfatuateSideIncrement + status2animation BS_EFFECT_BATTLER, STATUS2_INFATUATION +BattleScript_InfatuateSidePrintMessage: + printfromtable gStatus2StringIds + waitmessage B_WAIT_TIME_LONG +BattleScript_InfatuateSideIncrement: + jumpifbytenotequal gBattlerTarget, sBATTLER, BattleScript_InfatuateSideEnd + setallytonexttarget BattleScript_InfatuateSideLoop +BattleScript_InfatuateSideEnd: + restoretarget + goto BattleScript_MoveEnd + +BattleScript_EffectTormentSide:: + savetarget + copybyte sBATTLER, gBattlerTarget +BattleScript_TormentSideLoop: + jumpifabsent BS_TARGET, BattleScript_TormentSideIncrement + trysettorment BattleScript_TormentSideIncrement +BattleScript_TormentSidePrintMessage: + printfromtable gStatus2StringIds + waitmessage B_WAIT_TIME_LONG +BattleScript_TormentSideIncrement: + jumpifbytenotequal gBattlerTarget, sBATTLER, BattleScript_TormentSideEnd + setallytonexttarget BattleScript_TormentSideLoop +BattleScript_TormentSideEnd: + restoretarget + goto BattleScript_MoveEnd + BattleScript_TormentEnds:: printstring STRINGID_TORMENTEDNOMORE waitmessage B_WAIT_TIME_LONG end2 +BattleScript_EffectMeanLookSide:: + savetarget + copybyte sBATTLER, gBattlerTarget +BattleScript_MeanLookSideLoop: + jumpifabsent BS_TARGET, BattleScript_MeanLookSideIncrement + trysetescapeprevention BattleScript_MeanLookSideIncrement +BattleScript_MeanLookSidePrintMessage: + printfromtable gStatus2StringIds + waitmessage B_WAIT_TIME_LONG +BattleScript_MeanLookSideIncrement: + jumpifbytenotequal gBattlerTarget, sBATTLER, BattleScript_MeanLookSideEnd + setallytonexttarget BattleScript_MeanLookSideLoop +BattleScript_MeanLookSideEnd: + restoretarget + goto BattleScript_MoveEnd + BattleScript_EffectRaiseCritAlliesAnim:: savetarget copybyte gBattlerTarget, gBattlerAttacker @@ -9905,12 +10002,6 @@ BattleScript_RecycleBerriesAlliesEnd: restoretarget goto BattleScript_MoveEnd -BattleScript_EffectStonesurge:: - setstealthrock BattleScript_MoveEnd - printfromtable gDmgHazardsStringIds - waitmessage B_WAIT_TIME_LONG - goto BattleScript_MoveEnd - BattleScript_EffectSteelsurge:: setsteelsurge BattleScript_MoveEnd printfromtable gDmgHazardsStringIds diff --git a/include/battle_dynamax.h b/include/battle_dynamax.h index 9957c37a10..0762601c89 100644 --- a/include/battle_dynamax.h +++ b/include/battle_dynamax.h @@ -3,59 +3,6 @@ #define DYNAMAX_TURNS_COUNT 3 -enum MaxMoveEffect -{ - MAX_EFFECT_NONE, - MAX_EFFECT_RAISE_TEAM_ATTACK, - MAX_EFFECT_RAISE_TEAM_DEFENSE, - MAX_EFFECT_RAISE_TEAM_SPEED, - MAX_EFFECT_RAISE_TEAM_SP_ATK, - MAX_EFFECT_RAISE_TEAM_SP_DEF, - MAX_EFFECT_LOWER_ATTACK, - MAX_EFFECT_LOWER_DEFENSE, - MAX_EFFECT_LOWER_SPEED, - MAX_EFFECT_LOWER_SP_ATK, - MAX_EFFECT_LOWER_SP_DEF, - MAX_EFFECT_SUN, - MAX_EFFECT_RAIN, - MAX_EFFECT_SANDSTORM, - MAX_EFFECT_HAIL, - MAX_EFFECT_MISTY_TERRAIN, - MAX_EFFECT_GRASSY_TERRAIN, - MAX_EFFECT_ELECTRIC_TERRAIN, - MAX_EFFECT_PSYCHIC_TERRAIN, - MAX_EFFECT_VINE_LASH, - MAX_EFFECT_WILDFIRE, - MAX_EFFECT_CANNONADE, - MAX_EFFECT_EFFECT_SPORE_FOES, - MAX_EFFECT_PARALYZE_FOES, - MAX_EFFECT_CONFUSE_FOES_PAY_DAY, - MAX_EFFECT_CRIT_PLUS, - MAX_EFFECT_MEAN_LOOK, - MAX_EFFECT_AURORA_VEIL, - MAX_EFFECT_INFATUATE_FOES, - MAX_EFFECT_RECYCLE_BERRIES, - MAX_EFFECT_POISON_FOES, - MAX_EFFECT_STEALTH_ROCK, - MAX_EFFECT_DEFOG, - MAX_EFFECT_POISON_PARALYZE_FOES, - MAX_EFFECT_HEAL_TEAM, - MAX_EFFECT_SPITE, - MAX_EFFECT_GRAVITY, - MAX_EFFECT_VOLCALITH, - MAX_EFFECT_SANDBLAST_FOES, - MAX_EFFECT_YAWN_FOE, - MAX_EFFECT_LOWER_EVASIVENESS_FOES, - MAX_EFFECT_AROMATHERAPY, - MAX_EFFECT_CONFUSE_FOES, - MAX_EFFECT_STEELSURGE, - MAX_EFFECT_TORMENT_FOES, - MAX_EFFECT_LOWER_SPEED_2_FOES, - MAX_EFFECT_FIRE_SPIN_FOES, - MAX_EFFECT_FIXED_POWER, - MAX_EFFECT_BYPASS_PROTECT, -}; - bool32 CanDynamax(u32 battler); bool32 IsGigantamaxed(u32 battler); void ApplyDynamaxHPMultiplier(struct Pokemon* mon); @@ -67,12 +14,11 @@ bool32 IsMoveBlockedByMaxGuard(u32 move); bool32 IsMoveBlockedByDynamax(u32 move); u16 GetMaxMove(u32 battler, u32 baseMove); -u8 GetMaxMovePower(u32 move); +u32 GetMaxMovePower(u32 move); bool32 IsMaxMove(u32 move); void ChooseDamageNonTypesString(u8 type); void BS_UpdateDynamax(void); -void BS_SetMaxMoveEffect(void); void BS_SetSteelsurge(void); void BS_TrySetStatus1(void); void BS_TrySetStatus2(void); diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 5b02c4a225..64e6018bcb 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -543,8 +543,14 @@ extern const u8 BattleScript_EffectAuroraVeilSuccess[]; extern const u8 BattleScript_EffectGravitySuccess[]; extern const u8 BattleScript_EffectYawnSuccess[]; extern const u8 BattleScript_EffectTryReducePP[]; -extern const u8 BattleScript_EffectStatus1Foes[]; -extern const u8 BattleScript_EffectStatus2Foes[]; +extern const u8 BattleScript_EffectParalyzeSide[]; +extern const u8 BattleScript_EffectPoisonSide[]; +extern const u8 BattleScript_EffectPoisonParalyzeSide[]; +extern const u8 BattleScript_EffectEffectSporeSide[]; +extern const u8 BattleScript_EffectConfuseSide[]; +extern const u8 BattleScript_EffectInfatuateSide[]; +extern const u8 BattleScript_EffectTormentSide[]; +extern const u8 BattleScript_EffectMeanLookSide[]; extern const u8 BattleScript_TormentEnds[]; extern const u8 BattleScript_EffectRaiseCritAlliesAnim[]; extern const u8 BattleScript_EffectHealOneSixthAllies[]; diff --git a/include/constants/battle.h b/include/constants/battle.h index 7f09e7f89b..13a91ade64 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -322,102 +322,150 @@ enum BattleWeather #define B_WEATHER_ANY (B_WEATHER_RAIN | B_WEATHER_SANDSTORM | B_WEATHER_SUN | B_WEATHER_HAIL | B_WEATHER_STRONG_WINDS | B_WEATHER_SNOW | B_WEATHER_FOG) #define B_WEATHER_PRIMAL_ANY (B_WEATHER_RAIN_PRIMAL | B_WEATHER_SUN_PRIMAL | B_WEATHER_STRONG_WINDS) -// Move Effects -#define MOVE_EFFECT_SLEEP 1 -#define MOVE_EFFECT_POISON 2 -#define MOVE_EFFECT_BURN 3 -#define MOVE_EFFECT_FREEZE 4 -#define MOVE_EFFECT_PARALYSIS 5 -#define MOVE_EFFECT_TOXIC 6 -#define MOVE_EFFECT_FROSTBITE 7 +enum MoveEffects +{ + MOVE_EFFECT_NONE, + MOVE_EFFECT_SLEEP, + MOVE_EFFECT_POISON, + MOVE_EFFECT_BURN, + MOVE_EFFECT_FREEZE, + MOVE_EFFECT_PARALYSIS, + MOVE_EFFECT_TOXIC, + MOVE_EFFECT_FROSTBITE, + MOVE_EFFECT_CONFUSION, + MOVE_EFFECT_FLINCH, + MOVE_EFFECT_TRI_ATTACK, + MOVE_EFFECT_UPROAR, + MOVE_EFFECT_PAYDAY, + MOVE_EFFECT_CHARGING, + MOVE_EFFECT_WRAP, + MOVE_EFFECT_ATK_PLUS_1, + MOVE_EFFECT_DEF_PLUS_1, + MOVE_EFFECT_SPD_PLUS_1, + MOVE_EFFECT_SP_ATK_PLUS_1, + MOVE_EFFECT_SP_DEF_PLUS_1, + MOVE_EFFECT_ACC_PLUS_1, + MOVE_EFFECT_EVS_PLUS_1, + MOVE_EFFECT_ATK_MINUS_1, + MOVE_EFFECT_DEF_MINUS_1, + MOVE_EFFECT_SPD_MINUS_1, + MOVE_EFFECT_SP_ATK_MINUS_1, + MOVE_EFFECT_SP_DEF_MINUS_1, + MOVE_EFFECT_ACC_MINUS_1, + MOVE_EFFECT_EVS_MINUS_1, + MOVE_EFFECT_REMOVE_ARG_TYPE, + MOVE_EFFECT_RECHARGE, + MOVE_EFFECT_RAGE, + MOVE_EFFECT_STEAL_ITEM, + MOVE_EFFECT_PREVENT_ESCAPE, + MOVE_EFFECT_NIGHTMARE, + MOVE_EFFECT_ALL_STATS_UP, + MOVE_EFFECT_REMOVE_STATUS, + MOVE_EFFECT_ATK_DEF_DOWN, + MOVE_EFFECT_ATK_PLUS_2, + MOVE_EFFECT_DEF_PLUS_2, + MOVE_EFFECT_SPD_PLUS_2, + MOVE_EFFECT_SP_ATK_PLUS_2, + MOVE_EFFECT_SP_DEF_PLUS_2, + MOVE_EFFECT_ACC_PLUS_2, + MOVE_EFFECT_EVS_PLUS_2, + MOVE_EFFECT_ATK_MINUS_2, + MOVE_EFFECT_DEF_MINUS_2, + MOVE_EFFECT_SPD_MINUS_2, + MOVE_EFFECT_SP_ATK_MINUS_2, + MOVE_EFFECT_SP_DEF_MINUS_2, + MOVE_EFFECT_ACC_MINUS_2, + MOVE_EFFECT_EVS_MINUS_2, + MOVE_EFFECT_SCALE_SHOT, + MOVE_EFFECT_THRASH, + MOVE_EFFECT_KNOCK_OFF, + MOVE_EFFECT_DEF_SPDEF_DOWN, + MOVE_EFFECT_CLEAR_SMOG, + MOVE_EFFECT_SMACK_DOWN, + MOVE_EFFECT_FLAME_BURST, + MOVE_EFFECT_FEINT, + MOVE_EFFECT_SPECTRAL_THIEF, + MOVE_EFFECT_V_CREATE, + MOVE_EFFECT_HAPPY_HOUR, + MOVE_EFFECT_CORE_ENFORCER, + MOVE_EFFECT_THROAT_CHOP, + MOVE_EFFECT_INCINERATE, + MOVE_EFFECT_BUG_BITE, + MOVE_EFFECT_RECOIL_HP_25, + MOVE_EFFECT_TRAP_BOTH, + MOVE_EFFECT_ROUND, + MOVE_EFFECT_STOCKPILE_WORE_OFF, + MOVE_EFFECT_DIRE_CLAW, + MOVE_EFFECT_STEALTH_ROCK, + MOVE_EFFECT_SPIKES, + MOVE_EFFECT_SYRUP_BOMB, + MOVE_EFFECT_FLORAL_HEALING, + MOVE_EFFECT_SECRET_POWER, + MOVE_EFFECT_PSYCHIC_NOISE, + MOVE_EFFECT_TERA_BLAST, + MOVE_EFFECT_ORDER_UP, + MOVE_EFFECT_ION_DELUGE, + MOVE_EFFECT_HAZE, + MOVE_EFFECT_LEECH_SEED, + MOVE_EFFECT_REFLECT, + MOVE_EFFECT_LIGHT_SCREEN, + MOVE_EFFECT_SALT_CURE, + MOVE_EFFECT_EERIE_SPELL, + MOVE_EFFECT_RAISE_TEAM_ATTACK, + MOVE_EFFECT_RAISE_TEAM_DEFENSE, + MOVE_EFFECT_RAISE_TEAM_SPEED, + MOVE_EFFECT_RAISE_TEAM_SP_ATK, + MOVE_EFFECT_RAISE_TEAM_SP_DEF, + MOVE_EFFECT_LOWER_ATTACK_SIDE, + MOVE_EFFECT_LOWER_DEFENSE_SIDE, + MOVE_EFFECT_LOWER_SPEED_SIDE, + MOVE_EFFECT_LOWER_SP_ATK_SIDE, + MOVE_EFFECT_LOWER_SP_DEF_SIDE, + MOVE_EFFECT_SUN, + MOVE_EFFECT_RAIN, + MOVE_EFFECT_SANDSTORM, + MOVE_EFFECT_HAIL, + MOVE_EFFECT_MISTY_TERRAIN, + MOVE_EFFECT_GRASSY_TERRAIN, + MOVE_EFFECT_ELECTRIC_TERRAIN, + MOVE_EFFECT_PSYCHIC_TERRAIN, + MOVE_EFFECT_VINE_LASH, + MOVE_EFFECT_WILDFIRE, + MOVE_EFFECT_CANNONADE, + MOVE_EFFECT_EFFECT_SPORE_SIDE, + MOVE_EFFECT_PARALYZE_SIDE, + MOVE_EFFECT_CONFUSE_PAY_DAY_SIDE, + MOVE_EFFECT_CRIT_PLUS_SIDE, + MOVE_EFFECT_PREVENT_ESCAPE_SIDE, + MOVE_EFFECT_AURORA_VEIL, + MOVE_EFFECT_INFATUATE_SIDE, + MOVE_EFFECT_RECYCLE_BERRIES, + MOVE_EFFECT_POISON_SIDE, + MOVE_EFFECT_DEFOG, + MOVE_EFFECT_POISON_PARALYZE_SIDE, + MOVE_EFFECT_HEAL_TEAM, + MOVE_EFFECT_SPITE, + MOVE_EFFECT_GRAVITY, + MOVE_EFFECT_VOLCALITH, + MOVE_EFFECT_SANDBLAST_SIDE, + MOVE_EFFECT_YAWN_FOE, + MOVE_EFFECT_LOWER_EVASIVENESS_SIDE, + MOVE_EFFECT_AROMATHERAPY, + MOVE_EFFECT_CONFUSE_SIDE, + MOVE_EFFECT_STEELSURGE, + MOVE_EFFECT_TORMENT_SIDE, + MOVE_EFFECT_LOWER_SPEED_2_SIDE, + MOVE_EFFECT_FIRE_SPIN_SIDE, + MOVE_EFFECT_FIXED_POWER, + NUM_MOVE_EFFECTS +}; + #define PRIMARY_STATUS_MOVE_EFFECT MOVE_EFFECT_FROSTBITE // All above move effects apply primary status #if B_USE_FROSTBITE == TRUE #define MOVE_EFFECT_FREEZE_OR_FROSTBITE MOVE_EFFECT_FROSTBITE #else #define MOVE_EFFECT_FREEZE_OR_FROSTBITE MOVE_EFFECT_FREEZE #endif -#define MOVE_EFFECT_CONFUSION 8 -#define MOVE_EFFECT_FLINCH 9 -#define MOVE_EFFECT_TRI_ATTACK 10 -#define MOVE_EFFECT_UPROAR 11 -#define MOVE_EFFECT_PAYDAY 12 -#define MOVE_EFFECT_CHARGING 13 -#define MOVE_EFFECT_WRAP 14 -#define MOVE_EFFECT_ATK_PLUS_1 15 -#define MOVE_EFFECT_DEF_PLUS_1 16 -#define MOVE_EFFECT_SPD_PLUS_1 17 -#define MOVE_EFFECT_SP_ATK_PLUS_1 18 -#define MOVE_EFFECT_SP_DEF_PLUS_1 19 -#define MOVE_EFFECT_ACC_PLUS_1 20 -#define MOVE_EFFECT_EVS_PLUS_1 21 -#define MOVE_EFFECT_ATK_MINUS_1 22 -#define MOVE_EFFECT_DEF_MINUS_1 23 -#define MOVE_EFFECT_SPD_MINUS_1 24 -#define MOVE_EFFECT_SP_ATK_MINUS_1 25 -#define MOVE_EFFECT_SP_DEF_MINUS_1 26 -#define MOVE_EFFECT_ACC_MINUS_1 27 -#define MOVE_EFFECT_EVS_MINUS_1 28 -#define MOVE_EFFECT_REMOVE_ARG_TYPE 29 -#define MOVE_EFFECT_RECHARGE 30 -#define MOVE_EFFECT_RAGE 31 -#define MOVE_EFFECT_STEAL_ITEM 32 -#define MOVE_EFFECT_PREVENT_ESCAPE 33 -#define MOVE_EFFECT_NIGHTMARE 34 -#define MOVE_EFFECT_ALL_STATS_UP 35 -#define MOVE_EFFECT_REMOVE_STATUS 36 -#define MOVE_EFFECT_ATK_DEF_DOWN 37 -#define MOVE_EFFECT_ATK_PLUS_2 38 -#define MOVE_EFFECT_DEF_PLUS_2 39 -#define MOVE_EFFECT_SPD_PLUS_2 40 -#define MOVE_EFFECT_SP_ATK_PLUS_2 41 -#define MOVE_EFFECT_SP_DEF_PLUS_2 42 -#define MOVE_EFFECT_ACC_PLUS_2 43 -#define MOVE_EFFECT_EVS_PLUS_2 44 -#define MOVE_EFFECT_ATK_MINUS_2 45 -#define MOVE_EFFECT_DEF_MINUS_2 46 -#define MOVE_EFFECT_SPD_MINUS_2 47 -#define MOVE_EFFECT_SP_ATK_MINUS_2 48 -#define MOVE_EFFECT_SP_DEF_MINUS_2 49 -#define MOVE_EFFECT_ACC_MINUS_2 50 -#define MOVE_EFFECT_EVS_MINUS_2 51 -#define MOVE_EFFECT_SCALE_SHOT 52 -#define MOVE_EFFECT_THRASH 53 -#define MOVE_EFFECT_KNOCK_OFF 54 -#define MOVE_EFFECT_DEF_SPDEF_DOWN 55 -#define MOVE_EFFECT_CLEAR_SMOG 56 -#define MOVE_EFFECT_SMACK_DOWN 57 -#define MOVE_EFFECT_FLAME_BURST 58 -#define MOVE_EFFECT_FEINT 59 -#define MOVE_EFFECT_SPECTRAL_THIEF 60 -#define MOVE_EFFECT_V_CREATE 61 -#define MOVE_EFFECT_HAPPY_HOUR 62 -#define MOVE_EFFECT_CORE_ENFORCER 63 -#define MOVE_EFFECT_THROAT_CHOP 64 -#define MOVE_EFFECT_INCINERATE 65 -#define MOVE_EFFECT_BUG_BITE 66 -#define MOVE_EFFECT_RECOIL_HP_25 67 -#define MOVE_EFFECT_TRAP_BOTH 68 -#define MOVE_EFFECT_ROUND 69 -#define MOVE_EFFECT_STOCKPILE_WORE_OFF 70 -#define MOVE_EFFECT_DIRE_CLAW 71 -#define MOVE_EFFECT_STEALTH_ROCK 72 -#define MOVE_EFFECT_SPIKES 73 -#define MOVE_EFFECT_SYRUP_BOMB 74 -#define MOVE_EFFECT_FLORAL_HEALING 75 -#define MOVE_EFFECT_SECRET_POWER 76 -#define MOVE_EFFECT_PSYCHIC_NOISE 77 -#define MOVE_EFFECT_TERA_BLAST 78 -#define MOVE_EFFECT_ORDER_UP 79 -#define MOVE_EFFECT_ION_DELUGE 80 -#define MOVE_EFFECT_AROMATHERAPY 81 // No functionality yet -#define MOVE_EFFECT_HAZE 82 -#define MOVE_EFFECT_LEECH_SEED 83 -#define MOVE_EFFECT_REFLECT 84 -#define MOVE_EFFECT_LIGHT_SCREEN 85 -#define MOVE_EFFECT_SALT_CURE 86 -#define MOVE_EFFECT_EERIE_SPELL 87 - -#define NUM_MOVE_EFFECTS 88 #define MOVE_EFFECT_AFFECTS_USER 0x2000 #define MOVE_EFFECT_CERTAIN 0x4000 diff --git a/include/move.h b/include/move.h index 67206d9ba2..855dba10c3 100644 --- a/include/move.h +++ b/include/move.h @@ -122,7 +122,6 @@ struct MoveInfo u16 type; u16 fixedDamage; u16 absorbPercentage; - u16 maxEffect; } argument; // primary/secondary effects @@ -489,11 +488,6 @@ static inline u32 GetMoveAbsorbPercentage(u32 moveId) return gMovesInfo[moveId].argument.absorbPercentage; } -static inline u32 GetMoveMaxEffect(u32 moveId) -{ - return gMovesInfo[SanitizeMoveId(moveId)].argument.maxEffect; -} - static inline const struct AdditionalEffect *GetMoveAdditionalEffectById(u32 moveId, u32 effect) { return &gMovesInfo[SanitizeMoveId(moveId)].additionalEffects[effect]; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 745c8294e4..a516093a04 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2393,8 +2393,8 @@ static inline bool32 IsMoveSleepClauseTrigger(u32 move) switch (additionalEffect->moveEffect) { // Skip MOVE_EFFECT_SLEEP as moves with a secondary chance of applying sleep are allowed by Smogon's rules (ie. Relic Song) - case MAX_EFFECT_EFFECT_SPORE_FOES: - case MAX_EFFECT_YAWN_FOE: + case MOVE_EFFECT_EFFECT_SPORE_SIDE: + case MOVE_EFFECT_YAWN_FOE: return TRUE; } } diff --git a/src/battle_anim_new.c b/src/battle_anim_new.c index 426c196141..f344b711d6 100644 --- a/src/battle_anim_new.c +++ b/src/battle_anim_new.c @@ -9267,21 +9267,15 @@ void AnimTask_DynamaxGrowth(u8 taskId) // from CFRU void AnimTask_GetWeatherToSet(u8 taskId) { - switch (GetMoveMaxEffect(gCurrentMove)) - { - case MAX_EFFECT_SUN: - gBattleAnimArgs[ARG_RET_ID] = ANIM_WEATHER_SUN; - break; - case MAX_EFFECT_RAIN: - gBattleAnimArgs[ARG_RET_ID] = ANIM_WEATHER_RAIN; - break; - case MAX_EFFECT_SANDSTORM: - gBattleAnimArgs[ARG_RET_ID] = ANIM_WEATHER_SANDSTORM; - break; - case MAX_EFFECT_HAIL: - gBattleAnimArgs[ARG_RET_ID] = ANIM_WEATHER_HAIL; - break; - } + if (MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_SUN)) + gBattleAnimArgs[ARG_RET_ID] = ANIM_WEATHER_SUN; + else if (MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_RAIN)) + gBattleAnimArgs[ARG_RET_ID] = ANIM_WEATHER_RAIN; + else if (MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_SANDSTORM)) + gBattleAnimArgs[ARG_RET_ID] = ANIM_WEATHER_SANDSTORM; + else if (MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_HAIL)) + gBattleAnimArgs[ARG_RET_ID] = ANIM_WEATHER_HAIL; + DestroyAnimVisualTask(taskId); } diff --git a/src/battle_dynamax.c b/src/battle_dynamax.c index 3a931af470..3e8d0e9db7 100644 --- a/src/battle_dynamax.c +++ b/src/battle_dynamax.c @@ -23,7 +23,7 @@ #include "constants/items.h" #include "constants/moves.h" -static u8 GetMaxPowerTier(u32 move); +static u32 GetMaxPowerTier(u32 move); struct GMaxMove { @@ -315,11 +315,11 @@ enum }; // Gets the base power of a Max Move. -u8 GetMaxMovePower(u32 move) +u32 GetMaxMovePower(u32 move) { - u8 tier; + u32 tier; // G-Max Drum Solo, G-Max Hydrosnipe, and G-Max Fireball always have 160 base power. - if (GetMoveMaxEffect(GetMaxMove(gBattlerAttacker, move)) == MAX_EFFECT_FIXED_POWER) + if (MoveHasAdditionalEffect(move, MOVE_EFFECT_FIXED_POWER)) return 160; // Exceptions to all other rules below: @@ -367,7 +367,7 @@ u8 GetMaxMovePower(u32 move) } } -static u8 GetMaxPowerTier(u32 move) +static u32 GetMaxPowerTier(u32 move) { u32 strikeCount = GetMoveStrikeCount(move); if (strikeCount >= 2 && strikeCount <= 5) @@ -468,42 +468,6 @@ void ChooseDamageNonTypesString(u8 type) } } -// Returns the status effect that should be applied by a G-Max Move. -static u32 GetMaxMoveStatusEffect(u32 move) -{ - u8 maxEffect = GetMoveMaxEffect(move); - switch (maxEffect) - { - // Status 1 - case MAX_EFFECT_PARALYZE_FOES: - return STATUS1_PARALYSIS; - case MAX_EFFECT_POISON_FOES: - return STATUS1_POISON; - case MAX_EFFECT_POISON_PARALYZE_FOES: - { - static const u8 sStunShockEffects[] = {STATUS1_PARALYSIS, STATUS1_POISON}; - return RandomElement(RNG_G_MAX_STUN_SHOCK, sStunShockEffects); - } - case MAX_EFFECT_EFFECT_SPORE_FOES: - { - static const u8 sBefuddleEffects[] = {STATUS1_PARALYSIS, STATUS1_POISON, STATUS1_SLEEP}; - return RandomElement(RNG_G_MAX_BEFUDDLE, sBefuddleEffects); - } - // Status 2 - case MAX_EFFECT_CONFUSE_FOES: - case MAX_EFFECT_CONFUSE_FOES_PAY_DAY: - return STATUS2_CONFUSION; - case MAX_EFFECT_INFATUATE_FOES: - return STATUS2_INFATUATION; - case MAX_EFFECT_MEAN_LOOK: - return STATUS2_ESCAPE_PREVENTION; - case MAX_EFFECT_TORMENT_FOES: - return STATUS2_TORMENT; - default: - return STATUS1_NONE; - } -} - // Updates Dynamax HP multipliers and healthboxes. void BS_UpdateDynamax(void) { @@ -518,502 +482,6 @@ void BS_UpdateDynamax(void) gBattlescriptCurrInstr = cmd->nextInstr; } -// Activates the secondary effect of a Max Move. -void BS_SetMaxMoveEffect(void) -{ - NATIVE_ARGS(); - u16 effect = 0; - u8 maxEffect = GetMoveMaxEffect(gCurrentMove); - - // Don't continue if the move didn't land. - if (gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) - { - gBattlescriptCurrInstr = cmd->nextInstr; - return; - } - - switch (maxEffect) - { - case MAX_EFFECT_RAISE_TEAM_ATTACK: - case MAX_EFFECT_RAISE_TEAM_DEFENSE: - case MAX_EFFECT_RAISE_TEAM_SPEED: - case MAX_EFFECT_RAISE_TEAM_SP_ATK: - case MAX_EFFECT_RAISE_TEAM_SP_DEF: - if (!NoAliveMonsForEitherParty()) - { - // Max Effects are ordered by stat ID. - SET_STATCHANGER(maxEffect, 1, FALSE); - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectRaiseStatAllies; - effect++; - } - break; - case MAX_EFFECT_LOWER_ATTACK: - case MAX_EFFECT_LOWER_DEFENSE: - case MAX_EFFECT_LOWER_SPEED: - case MAX_EFFECT_LOWER_SP_ATK: - case MAX_EFFECT_LOWER_SP_DEF: - case MAX_EFFECT_LOWER_SPEED_2_FOES: - case MAX_EFFECT_LOWER_EVASIVENESS_FOES: - if (!NoAliveMonsForEitherParty()) - { - u8 statId = 0; - u8 stage = 1; - switch (maxEffect) - { - case MAX_EFFECT_LOWER_SPEED_2_FOES: - statId = STAT_SPEED; - stage = 2; - break; - case MAX_EFFECT_LOWER_EVASIVENESS_FOES: - statId = STAT_EVASION; - break; - default: - // Max Effects are ordered by stat ID. - statId = maxEffect - MAX_EFFECT_LOWER_ATTACK + 1; - break; - } - SET_STATCHANGER(statId, stage, TRUE); - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectLowerStatFoes; - effect++; - } - break; - case MAX_EFFECT_SUN: - case MAX_EFFECT_RAIN: - case MAX_EFFECT_SANDSTORM: - case MAX_EFFECT_HAIL: - { - u8 weather = 0, msg = 0; - switch (maxEffect) - { - case MAX_EFFECT_SUN: - weather = BATTLE_WEATHER_SUN; - msg = B_MSG_STARTED_SUNLIGHT; - break; - case MAX_EFFECT_RAIN: - weather = BATTLE_WEATHER_RAIN; - msg = B_MSG_STARTED_RAIN; - break; - case MAX_EFFECT_SANDSTORM: - weather = BATTLE_WEATHER_SANDSTORM; - msg = B_MSG_STARTED_SANDSTORM; - break; - case MAX_EFFECT_HAIL: - weather = BATTLE_WEATHER_HAIL; - msg = B_MSG_STARTED_HAIL; - break; - } - if (TryChangeBattleWeather(gBattlerAttacker, weather, FALSE)) - { - gBattleCommunication[MULTISTRING_CHOOSER] = msg; - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectSetWeather; - effect++; - } - break; - } - case MAX_EFFECT_MISTY_TERRAIN: - case MAX_EFFECT_GRASSY_TERRAIN: - case MAX_EFFECT_ELECTRIC_TERRAIN: - case MAX_EFFECT_PSYCHIC_TERRAIN: - { - u32 statusFlag = 0; - switch (GetMoveEffectArg_MoveProperty(gCurrentMove)) - { - case MAX_EFFECT_MISTY_TERRAIN: - statusFlag = STATUS_FIELD_MISTY_TERRAIN; - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_MISTY; - break; - case MAX_EFFECT_GRASSY_TERRAIN: - statusFlag = STATUS_FIELD_GRASSY_TERRAIN; - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_GRASSY; - break; - case MAX_EFFECT_ELECTRIC_TERRAIN: - statusFlag = STATUS_FIELD_ELECTRIC_TERRAIN; - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_ELECTRIC; - break; - case MAX_EFFECT_PSYCHIC_TERRAIN: - statusFlag = STATUS_FIELD_PSYCHIC_TERRAIN; - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_PSYCHIC; - break; - } - if (!(gFieldStatuses & statusFlag) && statusFlag != 0) - { - gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; - gFieldStatuses |= statusFlag; - if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_TERRAIN_EXTENDER) - gFieldTimers.terrainTimer = gBattleTurnCounter + 8; - else - gFieldTimers.terrainTimer = gBattleTurnCounter + 5; - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectSetTerrain; - effect++; - } - break; - } - case MAX_EFFECT_VINE_LASH: - case MAX_EFFECT_CANNONADE: - case MAX_EFFECT_WILDFIRE: - case MAX_EFFECT_VOLCALITH: - { - u8 side = GetBattlerSide(gBattlerTarget); - if (!(gSideStatuses[side] & SIDE_STATUS_DAMAGE_NON_TYPES)) - { - u32 moveType = GetMoveType(gCurrentMove); - gSideStatuses[side] |= SIDE_STATUS_DAMAGE_NON_TYPES; - gSideTimers[side].damageNonTypesTimer = gBattleTurnCounter + 5; // damage is dealt for 4 turns, ends on 5th - gSideTimers[side].damageNonTypesType = moveType; - BattleScriptPush(gBattlescriptCurrInstr + 1); - ChooseDamageNonTypesString(moveType); - gBattlescriptCurrInstr = BattleScript_DamageNonTypesStarts; - effect++; - } - break; - } - case MAX_EFFECT_STEALTH_ROCK: - if (!(gSideStatuses[GetBattlerSide(gBattlerTarget)] & SIDE_STATUS_STEALTH_ROCK)) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_POINTEDSTONESFLOAT; - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectStonesurge; - effect++; - } - break; - case MAX_EFFECT_STEELSURGE: - if (!(gSideStatuses[GetBattlerSide(gBattlerTarget)] & SIDE_STATUS_STEELSURGE)) - { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SHARPSTEELFLOATS; - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectSteelsurge; - effect++; - } - break; - case MAX_EFFECT_DEFOG: - if (gSideStatuses[GetBattlerSide(gBattlerTarget)] & SIDE_STATUS_SCREEN_ANY - || gSideStatuses[GetBattlerSide(gBattlerTarget)] & SIDE_STATUS_HAZARDS_ANY - || gSideStatuses[GetBattlerSide(gBattlerAttacker)] & SIDE_STATUS_HAZARDS_ANY - || gFieldStatuses & STATUS_FIELD_TERRAIN_ANY) - { - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_DefogTryHazards; - effect++; - } - break; - case MAX_EFFECT_AURORA_VEIL: - if (!(gSideStatuses[GetBattlerSide(gBattlerAttacker)] & SIDE_STATUS_AURORA_VEIL)) - { - gSideStatuses[GetBattlerSide(gBattlerAttacker)] |= SIDE_STATUS_AURORA_VEIL; - if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LIGHT_CLAY) - gSideTimers[GetBattlerSide(gBattlerAttacker)].auroraVeilTimer = gBattleTurnCounter + 8; - else - gSideTimers[GetBattlerSide(gBattlerAttacker)].auroraVeilTimer = gBattleTurnCounter + 5; - gSideTimers[GetBattlerSide(gBattlerAttacker)].auroraVeilBattlerId = gBattlerAttacker; - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_SAFEGUARD; - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectAuroraVeilSuccess; - effect++; - } - break; - case MAX_EFFECT_GRAVITY: - if (!(gFieldStatuses & STATUS_FIELD_GRAVITY)) - { - gFieldStatuses |= STATUS_FIELD_GRAVITY; - gFieldTimers.gravityTimer = gBattleTurnCounter + 5; - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectGravitySuccess; - effect++; - } - break; - case MAX_EFFECT_SANDBLAST_FOES: - case MAX_EFFECT_FIRE_SPIN_FOES: - { - // Affects both opponents, but doesn't print strings so we can handle it here. - u8 battler; - for (battler = 0; battler < MAX_BATTLERS_COUNT; ++battler) - { - if (GetBattlerSide(battler) != GetBattlerSide(gBattlerTarget)) - continue; - if (!(gBattleMons[battler].status2 & STATUS2_WRAPPED)) - { - gBattleMons[battler].status2 |= STATUS2_WRAPPED; - if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_GRIP_CLAW) - #if B_BINDING_TURNS >= GEN_5 - gDisableStructs[battler].wrapTurns = 7; - else - gDisableStructs[battler].wrapTurns = (Random() % 2) + 4; - #else - gDisableStructs[battler].wrapTurns = 5; - else - gDisableStructs[battler].wrapTurns = (Random() % 4) + 2; - #endif - // The Wrap effect does not expire when the user switches, so here's some cheese. - gBattleStruct->wrappedBy[battler] = gBattlerTarget; - if (maxEffect == MAX_EFFECT_SANDBLAST_FOES) - gBattleStruct->wrappedMove[battler] = MOVE_SAND_TOMB; - else - gBattleStruct->wrappedMove[battler] = MOVE_FIRE_SPIN; - } - } - break; - } - case MAX_EFFECT_YAWN_FOE: - { - static const u8 sSnoozeEffects[] = {TRUE, FALSE}; - if (!(gStatuses3[gBattlerTarget] & STATUS3_YAWN) - && CanBeSlept(gBattlerTarget, GetBattlerAbility(gBattlerTarget), BLOCKED_BY_SLEEP_CLAUSE) - && RandomElement(RNG_G_MAX_SNOOZE, sSnoozeEffects)) // 50% chance of success - { - gStatuses3[gBattlerTarget] |= STATUS3_YAWN_TURN(2); - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectYawnSuccess; - effect++; - } - break; - } - case MAX_EFFECT_SPITE: - if (gLastMoves[gBattlerTarget] != MOVE_NONE - && gLastMoves[gBattlerTarget] != MOVE_UNAVAILABLE) - { - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectTryReducePP; - effect++; - } - break; - case MAX_EFFECT_PARALYZE_FOES: - case MAX_EFFECT_POISON_FOES: - case MAX_EFFECT_POISON_PARALYZE_FOES: - case MAX_EFFECT_EFFECT_SPORE_FOES: - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectStatus1Foes; - effect++; - break; - case MAX_EFFECT_CONFUSE_FOES_PAY_DAY: - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) - { - u16 payday = gPaydayMoney; - gPaydayMoney += (gBattleMons[gBattlerAttacker].level * 100); - if (payday > gPaydayMoney) - gPaydayMoney = 0xFFFF; - gBattleCommunication[CURSOR_POSITION] = 1; // add "Coins scattered." message - } - // fall through - case MAX_EFFECT_CONFUSE_FOES: - case MAX_EFFECT_INFATUATE_FOES: - case MAX_EFFECT_TORMENT_FOES: - case MAX_EFFECT_MEAN_LOOK: - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectStatus2Foes; - effect++; - break; - case MAX_EFFECT_CRIT_PLUS: - gBattleStruct->bonusCritStages[gBattlerAttacker]++; - gBattleStruct->bonusCritStages[BATTLE_PARTNER(gBattlerAttacker)]++; - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectRaiseCritAlliesAnim; - effect++; - break; - case MAX_EFFECT_HEAL_TEAM: - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectHealOneSixthAllies; - effect++; - break; - case MAX_EFFECT_AROMATHERAPY: - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectCureStatusAllies; - effect++; - break; - case MAX_EFFECT_RECYCLE_BERRIES: - { - static const u8 sReplenishEffects[] = {TRUE, FALSE}; - if (RandomElement(RNG_G_MAX_REPLENISH, sReplenishEffects)) // 50% chance of success - { - BattleScriptPush(gBattlescriptCurrInstr + 1); - gBattlescriptCurrInstr = BattleScript_EffectRecycleBerriesAllies; - effect++; - } - break; - } - } - - if (!effect) - gBattlescriptCurrInstr = cmd->nextInstr; -} - -// Sets up sharp steel on the target's side. -void BS_SetSteelsurge(void) -{ - NATIVE_ARGS(const u8 *failInstr); - u8 targetSide = GetBattlerSide(gBattlerTarget); - if (gSideStatuses[targetSide] & SIDE_STATUS_STEELSURGE) - { - gBattlescriptCurrInstr = cmd->failInstr; - } - else - { - gSideStatuses[targetSide] |= SIDE_STATUS_STEELSURGE; - gSideTimers[targetSide].steelsurgeAmount = 1; - gBattlescriptCurrInstr = cmd->nextInstr; - } -} - -// Applies the status1 effect associated with a given G-Max Move. -// Could be expanded to function for any move. -void BS_TrySetStatus1(void) -{ - NATIVE_ARGS(const u8 *failInstr); - u8 effect = 0; - u32 status1 = GetMaxMoveStatusEffect(gCurrentMove); - switch (status1) - { - case STATUS1_POISON: - if (CanBePoisoned(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerTarget))) - { - gBattleMons[gBattlerTarget].status1 |= STATUS1_POISON; - gBattleCommunication[MULTISTRING_CHOOSER] = 0; - effect++; - } - break; - case STATUS1_PARALYSIS: - if (CanBeParalyzed(gBattlerTarget, GetBattlerAbility(gBattlerTarget))) - { - gBattleMons[gBattlerTarget].status1 |= STATUS1_PARALYSIS; - gBattleCommunication[MULTISTRING_CHOOSER] = 3; - effect++; - } - break; - case STATUS1_SLEEP: - if (CanBeSlept(gBattlerTarget, GetBattlerAbility(gBattlerTarget), BLOCKED_BY_SLEEP_CLAUSE)) - { - if (B_SLEEP_TURNS >= GEN_5) - gBattleMons[gBattlerTarget].status1 |= STATUS1_SLEEP_TURN((Random() % 3) + 2); - else - gBattleMons[gBattlerTarget].status1 |= STATUS1_SLEEP_TURN((Random() % 4) + 3); - - TryActivateSleepClause(gBattlerTarget, gBattlerPartyIndexes[gBattlerTarget]); - gBattleCommunication[MULTISTRING_CHOOSER] = 4; - effect++; - } - break; - } - if (effect) - { - gEffectBattler = gBattlerTarget; - BtlController_EmitSetMonData(gBattlerTarget, BUFFER_A, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1); - MarkBattlerForControllerExec(gBattlerTarget); - gBattlescriptCurrInstr = cmd->nextInstr; - } - else - { - gBattlescriptCurrInstr = cmd->failInstr; - } -} - -// Applies the status2 effect associated with a given G-Max Move. -void BS_TrySetStatus2(void) -{ - NATIVE_ARGS(const u8 *failInstr); - u8 effect = 0; - u32 status2 = GetMaxMoveStatusEffect(gCurrentMove); - switch (status2) - { - case STATUS2_CONFUSION: - if (CanBeConfused(gBattlerTarget)) - { - gBattleMons[gBattlerTarget].status2 |= STATUS2_CONFUSION_TURN(((Random()) % 4) + 2); - gBattleCommunication[MULTISTRING_CHOOSER] = 0; - gBattleCommunication[MULTIUSE_STATE] = 1; - effect++; - } - break; - case STATUS2_INFATUATION: - { - u8 atkGender = GetGenderFromSpeciesAndPersonality(gBattleMons[gBattlerAttacker].species, gBattleMons[gBattlerAttacker].personality); - u8 defGender = GetGenderFromSpeciesAndPersonality(gBattleMons[gBattlerTarget].species, gBattleMons[gBattlerTarget].personality); - if (!(gBattleMons[gBattlerTarget].status2 & STATUS2_INFATUATION) - && gBattleMons[gBattlerTarget].ability != ABILITY_OBLIVIOUS - && !IsAbilityOnSide(gBattlerTarget, ABILITY_AROMA_VEIL) - && atkGender != defGender - && atkGender != MON_GENDERLESS - && defGender != MON_GENDERLESS) - { - gBattleMons[gBattlerTarget].status2 |= STATUS2_INFATUATED_WITH(gBattlerAttacker); - gBattleCommunication[MULTISTRING_CHOOSER] = 1; - gBattleCommunication[MULTIUSE_STATE] = 2; - effect++; - } - break; - } - case STATUS2_ESCAPE_PREVENTION: - if (!(gBattleMons[gBattlerTarget].status2 & STATUS2_ESCAPE_PREVENTION)) - { - gBattleMons[gBattlerTarget].status2 |= STATUS2_ESCAPE_PREVENTION; - gDisableStructs[gBattlerTarget].battlerPreventingEscape = gBattlerAttacker; - gBattleCommunication[MULTISTRING_CHOOSER] = 2; - effect++; - } - break; - case STATUS2_TORMENT: - if (!(gBattleMons[gBattlerTarget].status2 & STATUS2_TORMENT) - && !IsAbilityOnSide(gBattlerTarget, ABILITY_AROMA_VEIL)) - { - gBattleMons[gBattlerTarget].status2 |= STATUS2_TORMENT; - gDisableStructs[gBattlerTarget].tormentTimer = 3; // 3 turns excluding current turn - gBattleCommunication[MULTISTRING_CHOOSER] = 3; - effect++; - } - break; - } - if (effect) - { - gEffectBattler = gBattlerTarget; - gBattlescriptCurrInstr = cmd->nextInstr; - } - else - { - gBattlescriptCurrInstr = cmd->failInstr; - } -} - -// Heals one-sixth of the target's HP, including for Dynamaxed targets. -void BS_HealOneSixth(void) -{ - NATIVE_ARGS(const u8* failInstr); - gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].maxHP / 6; - if (gBattleStruct->moveDamage[gBattlerTarget] == 0) - gBattleStruct->moveDamage[gBattlerTarget] = 1; - gBattleStruct->moveDamage[gBattlerTarget] *= -1; - - if (gBattleMons[gBattlerTarget].hp == gBattleMons[gBattlerTarget].maxHP) - gBattlescriptCurrInstr = cmd->failInstr; // fail - else - gBattlescriptCurrInstr = cmd->nextInstr; // can heal -} - -// Recycles the target's item if it is specifically holding a berry. -void BS_TryRecycleBerry(void) -{ - NATIVE_ARGS(const u8 *failInstr); - u16* usedHeldItem = &gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerTarget]][GetBattlerSide(gBattlerTarget)]; - if (gBattleMons[gBattlerTarget].item == ITEM_NONE - && gBattleStruct->changedItems[gBattlerTarget] == ITEM_NONE // Will not inherit an item - && ItemId_GetPocket(*usedHeldItem) == POCKET_BERRIES) - { - gLastUsedItem = *usedHeldItem; - *usedHeldItem = ITEM_NONE; - gBattleMons[gBattlerTarget].item = gLastUsedItem; - - BtlController_EmitSetMonData(gBattlerTarget, BUFFER_A, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].item), &gBattleMons[gBattlerTarget].item); - MarkBattlerForControllerExec(gBattlerTarget); - - gBattlescriptCurrInstr = cmd->nextInstr; - } - else - { - gBattlescriptCurrInstr = cmd->failInstr; - } -} - // Goes to the jump instruction if the target is Dynamaxed. void BS_JumpIfDynamaxed(void) { diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 56baac23b5..b0fe3281bf 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -4244,9 +4244,6 @@ void SetMoveEffect(bool32 primary, bool32 certain) gBattlescriptCurrInstr = BattleScript_MoveEffectIonDeluge; } break; - // TODO: The moves aromatherapy and heal bell need a refactor first - // case MOVE_EFFECT_AROMATHERAPY: - // break; case MOVE_EFFECT_HAZE: for (i = 0; i < gBattlersCount; i++) TryResetBattlerStatChanges(i); @@ -4321,6 +4318,322 @@ void SetMoveEffect(bool32 primary, bool32 certain) } } break; + case MOVE_EFFECT_RAISE_TEAM_ATTACK: + if (!NoAliveMonsForEitherParty()) + { + // Max Effects are ordered by stat ID. + SET_STATCHANGER(STAT_ATK, 1, FALSE); + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectRaiseStatAllies; + } + break; + case MOVE_EFFECT_RAISE_TEAM_DEFENSE: + if (!NoAliveMonsForEitherParty()) + { + // Max Effects are ordered by stat ID. + SET_STATCHANGER(STAT_DEF, 1, FALSE); + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectRaiseStatAllies; + } + break; + case MOVE_EFFECT_RAISE_TEAM_SPEED: + if (!NoAliveMonsForEitherParty()) + { + // Max Effects are ordered by stat ID. + SET_STATCHANGER(STAT_SPEED, 1, FALSE); + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectRaiseStatAllies; + } + break; + case MOVE_EFFECT_RAISE_TEAM_SP_ATK: + if (!NoAliveMonsForEitherParty()) + { + // Max Effects are ordered by stat ID. + SET_STATCHANGER(STAT_SPATK, 1, FALSE); + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectRaiseStatAllies; + } + break; + case MOVE_EFFECT_RAISE_TEAM_SP_DEF: + if (!NoAliveMonsForEitherParty()) + { + // Max Effects are ordered by stat ID. + SET_STATCHANGER(STAT_SPDEF, 1, FALSE); + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectRaiseStatAllies; + } + break; + case MOVE_EFFECT_LOWER_ATTACK_SIDE: + case MOVE_EFFECT_LOWER_DEFENSE_SIDE: + case MOVE_EFFECT_LOWER_SPEED_SIDE: + case MOVE_EFFECT_LOWER_SP_ATK_SIDE: + case MOVE_EFFECT_LOWER_SP_DEF_SIDE: + case MOVE_EFFECT_LOWER_SPEED_2_SIDE: + case MOVE_EFFECT_LOWER_EVASIVENESS_SIDE: + if (!NoAliveMonsForEitherParty()) + { + u32 statId = 0; + u32 stage = 1; + switch (gBattleScripting.moveEffect) + { + case MOVE_EFFECT_LOWER_SPEED_2_SIDE: + statId = STAT_SPEED; + stage = 2; + break; + case MOVE_EFFECT_LOWER_EVASIVENESS_SIDE: + statId = STAT_EVASION; + break; + default: + // Max Effects are ordered by stat ID. + statId = gBattleScripting.moveEffect - MOVE_EFFECT_LOWER_ATTACK_SIDE + 1; + break; + } + SET_STATCHANGER(statId, stage, TRUE); + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectLowerStatFoes; + } + break; + case MOVE_EFFECT_SUN: + case MOVE_EFFECT_RAIN: + case MOVE_EFFECT_SANDSTORM: + case MOVE_EFFECT_HAIL: + { + u8 weather = 0, msg = 0; + switch (gBattleScripting.moveEffect) + { + case MOVE_EFFECT_SUN: + weather = BATTLE_WEATHER_SUN; + msg = B_MSG_STARTED_SUNLIGHT; + break; + case MOVE_EFFECT_RAIN: + weather = BATTLE_WEATHER_RAIN; + msg = B_MSG_STARTED_RAIN; + break; + case MOVE_EFFECT_SANDSTORM: + weather = BATTLE_WEATHER_SANDSTORM; + msg = B_MSG_STARTED_SANDSTORM; + break; + case MOVE_EFFECT_HAIL: + weather = BATTLE_WEATHER_HAIL; + msg = B_MSG_STARTED_HAIL; + break; + } + if (TryChangeBattleWeather(gBattlerAttacker, weather, FALSE)) + { + gBattleCommunication[MULTISTRING_CHOOSER] = msg; + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectSetWeather; + } + break; + } + case MOVE_EFFECT_MISTY_TERRAIN: + case MOVE_EFFECT_GRASSY_TERRAIN: + case MOVE_EFFECT_ELECTRIC_TERRAIN: + case MOVE_EFFECT_PSYCHIC_TERRAIN: + { + u32 statusFlag = 0; + switch (gBattleScripting.moveEffect) + { + case MOVE_EFFECT_MISTY_TERRAIN: + statusFlag = STATUS_FIELD_MISTY_TERRAIN; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_MISTY; + break; + case MOVE_EFFECT_GRASSY_TERRAIN: + statusFlag = STATUS_FIELD_GRASSY_TERRAIN; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_GRASSY; + break; + case MOVE_EFFECT_ELECTRIC_TERRAIN: + statusFlag = STATUS_FIELD_ELECTRIC_TERRAIN; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_ELECTRIC; + break; + case MOVE_EFFECT_PSYCHIC_TERRAIN: + statusFlag = STATUS_FIELD_PSYCHIC_TERRAIN; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_TERRAIN_SET_PSYCHIC; + break; + } + if (!(gFieldStatuses & statusFlag) && statusFlag != 0) + { + gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; + gFieldStatuses |= statusFlag; + if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_TERRAIN_EXTENDER) + gFieldTimers.terrainTimer = gBattleTurnCounter + 8; + else + gFieldTimers.terrainTimer = gBattleTurnCounter + 5; + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectSetTerrain; + } + break; + } + case MOVE_EFFECT_VINE_LASH: + case MOVE_EFFECT_CANNONADE: + case MOVE_EFFECT_WILDFIRE: + case MOVE_EFFECT_VOLCALITH: + { + u8 side = GetBattlerSide(gBattlerTarget); + if (!(gSideStatuses[side] & SIDE_STATUS_DAMAGE_NON_TYPES)) + { + u32 moveType = GetMoveType(gCurrentMove); + gSideStatuses[side] |= SIDE_STATUS_DAMAGE_NON_TYPES; + gSideTimers[side].damageNonTypesTimer = gBattleTurnCounter + 5; // damage is dealt for 4 turns, ends on 5th + gSideTimers[side].damageNonTypesType = moveType; + BattleScriptPush(gBattlescriptCurrInstr + 1); + ChooseDamageNonTypesString(moveType); + gBattlescriptCurrInstr = BattleScript_DamageNonTypesStarts; + } + break; + } + case MOVE_EFFECT_STEELSURGE: + if (!(gSideStatuses[GetBattlerSide(gBattlerTarget)] & SIDE_STATUS_STEELSURGE)) + { + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SHARPSTEELFLOATS; + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectSteelsurge; + } + break; + case MOVE_EFFECT_DEFOG: + if (gSideStatuses[GetBattlerSide(gBattlerTarget)] & SIDE_STATUS_SCREEN_ANY + || gSideStatuses[GetBattlerSide(gBattlerTarget)] & SIDE_STATUS_HAZARDS_ANY + || gSideStatuses[GetBattlerSide(gBattlerAttacker)] & SIDE_STATUS_HAZARDS_ANY + || gFieldStatuses & STATUS_FIELD_TERRAIN_ANY) + { + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_DefogTryHazards; + } + break; + case MOVE_EFFECT_AURORA_VEIL: + if (!(gSideStatuses[GetBattlerSide(gBattlerAttacker)] & SIDE_STATUS_AURORA_VEIL)) + { + gSideStatuses[GetBattlerSide(gBattlerAttacker)] |= SIDE_STATUS_AURORA_VEIL; + if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LIGHT_CLAY) + gSideTimers[GetBattlerSide(gBattlerAttacker)].auroraVeilTimer = gBattleTurnCounter + 8; + else + gSideTimers[GetBattlerSide(gBattlerAttacker)].auroraVeilTimer = gBattleTurnCounter + 5; + gSideTimers[GetBattlerSide(gBattlerAttacker)].auroraVeilBattlerId = gBattlerAttacker; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SET_SAFEGUARD; + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectAuroraVeilSuccess; + } + break; + case MOVE_EFFECT_GRAVITY: + if (!(gFieldStatuses & STATUS_FIELD_GRAVITY)) + { + gFieldStatuses |= STATUS_FIELD_GRAVITY; + gFieldTimers.gravityTimer = gBattleTurnCounter + 5; + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectGravitySuccess; + } + break; + case MOVE_EFFECT_SANDBLAST_SIDE: + case MOVE_EFFECT_FIRE_SPIN_SIDE: + { + // Affects both opponents, but doesn't print strings so we can handle it here. + u8 battler; + for (battler = 0; battler < MAX_BATTLERS_COUNT; ++battler) + { + if (GetBattlerSide(battler) != GetBattlerSide(gBattlerTarget)) + continue; + if (!(gBattleMons[battler].status2 & STATUS2_WRAPPED)) + { + gBattleMons[battler].status2 |= STATUS2_WRAPPED; + if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_GRIP_CLAW) + gDisableStructs[battler].wrapTurns = (B_BINDING_TURNS >= GEN_5) ? 7 : 5; + else + gDisableStructs[battler].wrapTurns = (Random() % 2) + 4; + // The Wrap effect does not expire when the user switches, so here's some cheese. + gBattleStruct->wrappedBy[battler] = gBattlerTarget; + if (gBattleScripting.moveEffect == MOVE_EFFECT_SANDBLAST_SIDE) + gBattleStruct->wrappedMove[battler] = MOVE_SAND_TOMB; + else + gBattleStruct->wrappedMove[battler] = MOVE_FIRE_SPIN; + } + } + break; + } + case MOVE_EFFECT_YAWN_FOE: + { + if (!(gStatuses3[gBattlerTarget] & STATUS3_YAWN) + && CanBeSlept(gBattlerTarget, GetBattlerAbility(gBattlerTarget), BLOCKED_BY_SLEEP_CLAUSE) + && RandomPercentage(RNG_G_MAX_SNOOZE, 50)) + { + gStatuses3[gBattlerTarget] |= STATUS3_YAWN_TURN(2); + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectYawnSuccess; + } + break; + } + case MOVE_EFFECT_SPITE: + if (gLastMoves[gBattlerTarget] != MOVE_NONE + && gLastMoves[gBattlerTarget] != MOVE_UNAVAILABLE) + { + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectTryReducePP; + } + break; + case MOVE_EFFECT_PARALYZE_SIDE: + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectParalyzeSide; + break; + case MOVE_EFFECT_POISON_SIDE: + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectPoisonSide; + break; + case MOVE_EFFECT_POISON_PARALYZE_SIDE: + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectPoisonParalyzeSide; + break; + case MOVE_EFFECT_EFFECT_SPORE_SIDE: + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectEffectSporeSide; + break; + case MOVE_EFFECT_CONFUSE_PAY_DAY_SIDE: + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) + { + u32 payday = gPaydayMoney; + gPaydayMoney += (gBattleMons[gBattlerAttacker].level * 100); + if (payday > gPaydayMoney) + gPaydayMoney = 0xFFFF; + gBattleCommunication[CURSOR_POSITION] = 1; // add "Coins scattered." message + } + // fall through + case MOVE_EFFECT_CONFUSE_SIDE: + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectConfuseSide; + break; + case MOVE_EFFECT_INFATUATE_SIDE: + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectInfatuateSide; + break; + case MOVE_EFFECT_TORMENT_SIDE: + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectTormentSide; + break; + case MOVE_EFFECT_PREVENT_ESCAPE_SIDE: + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectMeanLookSide; + break; + case MOVE_EFFECT_CRIT_PLUS_SIDE: + gBattleStruct->bonusCritStages[gBattlerAttacker]++; + gBattleStruct->bonusCritStages[BATTLE_PARTNER(gBattlerAttacker)]++; + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectRaiseCritAlliesAnim; + break; + case MOVE_EFFECT_HEAL_TEAM: + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectHealOneSixthAllies; + break; + case MOVE_EFFECT_AROMATHERAPY: + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectCureStatusAllies; + break; + case MOVE_EFFECT_RECYCLE_BERRIES: + { + if (RandomPercentage(RNG_G_MAX_REPLENISH, 50)) + { + BattleScriptPush(gBattlescriptCurrInstr + 1); + gBattlescriptCurrInstr = BattleScript_EffectRecycleBerriesAllies; + } + break; + } } } } @@ -13580,7 +13893,7 @@ static void Cmd_tryspiteppreduce(void) { s32 ppToDeduct = B_PP_REDUCED_BY_SPITE >= GEN_4 ? 4 : (Random() & 3) + 2; // G-Max Depletion only deducts 2 PP. - if (IsMaxMove(gCurrentMove) && GetMoveMaxEffect(gCurrentMove) == MAX_EFFECT_SPITE) + if (IsMaxMove(gCurrentMove) && MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_SPITE)) ppToDeduct = 2; if (gBattleMons[gBattlerTarget].pp[i] < ppToDeduct) @@ -17996,3 +18309,231 @@ void BS_SwapStats(void) PREPARE_STAT_BUFFER(gBattleTextBuff1, stat); gBattlescriptCurrInstr = cmd->nextInstr; } + +static void TrySetParalysis(const u8 *nextInstr, const u8 *failInstr) +{ + if (CanBeParalyzed(gBattlerTarget, GetBattlerAbility(gBattlerTarget))) + { + gBattleMons[gBattlerTarget].status1 |= STATUS1_PARALYSIS; + gBattleCommunication[MULTISTRING_CHOOSER] = 3; + gEffectBattler = gBattlerTarget; + BtlController_EmitSetMonData(gBattlerTarget, BUFFER_A, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1); + MarkBattlerForControllerExec(gBattlerTarget); + gBattlescriptCurrInstr = nextInstr; + } + else + { + gBattlescriptCurrInstr = failInstr; + } +} + +static void TrySetPoison(const u8 *nextInstr, const u8 *failInstr) +{ + if (CanBePoisoned(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerTarget))) + { + gBattleMons[gBattlerTarget].status1 |= STATUS1_POISON; + gBattleCommunication[MULTISTRING_CHOOSER] = 0; + gEffectBattler = gBattlerTarget; + BtlController_EmitSetMonData(gBattlerTarget, BUFFER_A, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1); + MarkBattlerForControllerExec(gBattlerTarget); + gBattlescriptCurrInstr = nextInstr; + } + else + { + gBattlescriptCurrInstr = failInstr; + } +} + +static void TrySetSleep(const u8 *nextInstr, const u8 *failInstr) +{ + if (CanBeSlept(gBattlerTarget, GetBattlerAbility(gBattlerTarget), BLOCKED_BY_SLEEP_CLAUSE)) + { + if (B_SLEEP_TURNS >= GEN_5) + gBattleMons[gBattlerTarget].status1 |= STATUS1_SLEEP_TURN((Random() % 3) + 2); + else + gBattleMons[gBattlerTarget].status1 |= STATUS1_SLEEP_TURN((Random() % 4) + 3); + + TryActivateSleepClause(gBattlerTarget, gBattlerPartyIndexes[gBattlerTarget]); + gBattleCommunication[MULTISTRING_CHOOSER] = 4; + gEffectBattler = gBattlerTarget; + BtlController_EmitSetMonData(gBattlerTarget, BUFFER_A, REQUEST_STATUS_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].status1), &gBattleMons[gBattlerTarget].status1); + MarkBattlerForControllerExec(gBattlerTarget); + gBattlescriptCurrInstr = nextInstr; + } + else + { + gBattlescriptCurrInstr = failInstr; + } +} + +void BS_TrySetParalysis(void) +{ + NATIVE_ARGS(const u8 *failInstr); + TrySetParalysis(cmd->nextInstr, cmd->failInstr); +} + +void BS_TrySetPoison(void) +{ + NATIVE_ARGS(const u8 *failInstr); + TrySetPoison(cmd->nextInstr, cmd->failInstr); +} + +void BS_TrySetPoisonParalyzis(void) +{ + NATIVE_ARGS(const u8 *failInstr); + + static const u32 sStunShockEffects[] = {STATUS1_PARALYSIS, STATUS1_POISON}; + u32 status = RandomElement(RNG_G_MAX_STUN_SHOCK, sStunShockEffects); + + if (status == STATUS1_PARALYSIS) + TrySetParalysis(cmd->nextInstr, cmd->failInstr); + else + TrySetPoison(cmd->nextInstr, cmd->failInstr); +} + +void BS_TrySetEffectSpore(void) +{ + NATIVE_ARGS(const u8 *failInstr); + + static const u32 sBefuddleEffects[] = {STATUS1_PARALYSIS, STATUS1_POISON, STATUS1_SLEEP}; + u32 status = RandomElement(RNG_G_MAX_BEFUDDLE, sBefuddleEffects); + + if (status == STATUS1_PARALYSIS) + TrySetParalysis(cmd->nextInstr, cmd->failInstr); + else if (status == STATUS1_POISON) + TrySetPoison(cmd->nextInstr, cmd->failInstr); + else + TrySetSleep(cmd->nextInstr, cmd->failInstr); +} + +void BS_TrySetConfusion(void) +{ + NATIVE_ARGS(const u8 *failInstr); + + if (CanBeConfused(gBattlerTarget)) + { + gBattleMons[gBattlerTarget].status2 |= STATUS2_CONFUSION_TURN(((Random()) % 4) + 2); + gBattleCommunication[MULTISTRING_CHOOSER] = 0; + gBattleCommunication[MULTIUSE_STATE] = 1; + gEffectBattler = gBattlerTarget; + gBattlescriptCurrInstr = cmd->nextInstr; + } + else + { + gBattlescriptCurrInstr = cmd->failInstr; + } +} + +void BS_TrySetInfatuation(void) +{ + NATIVE_ARGS(const u8 *failInstr); + + if (!(gBattleMons[gBattlerTarget].status2 & STATUS2_INFATUATION) + && gBattleMons[gBattlerTarget].ability != ABILITY_OBLIVIOUS + && !IsAbilityOnSide(gBattlerTarget, ABILITY_AROMA_VEIL) + && AreBattlersOfOppositeGender(gBattlerAttacker, gBattlerTarget)) + { + gBattleMons[gBattlerTarget].status2 |= STATUS2_INFATUATED_WITH(gBattlerAttacker); + gBattleCommunication[MULTISTRING_CHOOSER] = 1; + gBattleCommunication[MULTIUSE_STATE] = 2; + gEffectBattler = gBattlerTarget; + gBattlescriptCurrInstr = cmd->nextInstr; + } + else + { + gBattlescriptCurrInstr = cmd->failInstr; + } +} + +void BS_TrySetEscapePrevention(void) +{ + NATIVE_ARGS(const u8 *failInstr); + + if (!(gBattleMons[gBattlerTarget].status2 & STATUS2_ESCAPE_PREVENTION)) + { + gBattleMons[gBattlerTarget].status2 |= STATUS2_ESCAPE_PREVENTION; + gDisableStructs[gBattlerTarget].battlerPreventingEscape = gBattlerAttacker; + gBattleCommunication[MULTISTRING_CHOOSER] = 2; + gEffectBattler = gBattlerTarget; + gBattlescriptCurrInstr = cmd->nextInstr; + } + else + { + gBattlescriptCurrInstr = cmd->failInstr; + } +} + +void BS_TrySetTorment(void) +{ + NATIVE_ARGS(const u8 *failInstr); + + if (!(gBattleMons[gBattlerTarget].status2 & STATUS2_TORMENT) + && !IsAbilityOnSide(gBattlerTarget, ABILITY_AROMA_VEIL)) + { + gBattleMons[gBattlerTarget].status2 |= STATUS2_TORMENT; + gDisableStructs[gBattlerTarget].tormentTimer = 3; // 3 turns excluding current turn + gBattleCommunication[MULTISTRING_CHOOSER] = 3; + gEffectBattler = gBattlerTarget; + gBattlescriptCurrInstr = cmd->nextInstr; + } + else + { + gBattlescriptCurrInstr = cmd->failInstr; + } +} + +// Heals one-sixth of the target's HP, including for Dynamaxed targets. +void BS_HealOneSixth(void) +{ + NATIVE_ARGS(const u8* failInstr); + gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].maxHP / 6; + if (gBattleStruct->moveDamage[gBattlerTarget] == 0) + gBattleStruct->moveDamage[gBattlerTarget] = 1; + gBattleStruct->moveDamage[gBattlerTarget] *= -1; + + if (gBattleMons[gBattlerTarget].hp == gBattleMons[gBattlerTarget].maxHP) + gBattlescriptCurrInstr = cmd->failInstr; // fail + else + gBattlescriptCurrInstr = cmd->nextInstr; // can heal +} + +// Recycles the target's item if it is specifically holding a berry. +void BS_TryRecycleBerry(void) +{ + NATIVE_ARGS(const u8 *failInstr); + u16* usedHeldItem = &gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gBattlerTarget]][GetBattlerSide(gBattlerTarget)]; + if (gBattleMons[gBattlerTarget].item == ITEM_NONE + && gBattleStruct->changedItems[gBattlerTarget] == ITEM_NONE // Will not inherit an item + && ItemId_GetPocket(*usedHeldItem) == POCKET_BERRIES) + { + gLastUsedItem = *usedHeldItem; + *usedHeldItem = ITEM_NONE; + gBattleMons[gBattlerTarget].item = gLastUsedItem; + + BtlController_EmitSetMonData(gBattlerTarget, BUFFER_A, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[gBattlerTarget].item), &gBattleMons[gBattlerTarget].item); + MarkBattlerForControllerExec(gBattlerTarget); + + gBattlescriptCurrInstr = cmd->nextInstr; + } + else + { + gBattlescriptCurrInstr = cmd->failInstr; + } +} + +// Sets up sharp steel on the target's side. +void BS_SetSteelsurge(void) +{ + NATIVE_ARGS(const u8 *failInstr); + u8 targetSide = GetBattlerSide(gBattlerTarget); + if (gSideStatuses[targetSide] & SIDE_STATUS_STEELSURGE) + { + gBattlescriptCurrInstr = cmd->failInstr; + } + else + { + gSideStatuses[targetSide] |= SIDE_STATUS_STEELSURGE; + gSideTimers[targetSide].steelsurgeAmount = 1; + gBattlescriptCurrInstr = cmd->nextInstr; + } +} diff --git a/src/battle_util.c b/src/battle_util.c index 2b284a27de..d300bd5d2c 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8778,7 +8778,7 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) bool32 isProtected = FALSE; if ((IsZMove(move) || IsMaxMove(move)) - && (!gProtectStructs[battlerDef].maxGuarded || GetMoveMaxEffect(move) == MAX_EFFECT_BYPASS_PROTECT)) + && (!gProtectStructs[battlerDef].maxGuarded || MoveIgnoresProtect(move))) isProtected = FALSE; // Z-Moves and Max Moves bypass protection (except Max Guard). else if (gProtectStructs[battlerDef].maxGuarded && IsMoveBlockedByMaxGuard(move)) isProtected = TRUE; diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 76ed741f77..0a71a24ab7 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -21249,8 +21249,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_SUN }, .battleAnimScript = gBattleAnimMove_MaxFlare, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_SUN, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_FLUTTERBY] = @@ -21265,8 +21269,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_LOWER_SP_ATK }, .battleAnimScript = gBattleAnimMove_MaxFlutterby, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_LOWER_SP_ATK_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_LIGHTNING] = @@ -21281,8 +21289,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_ELECTRIC_TERRAIN }, .battleAnimScript = gBattleAnimMove_MaxLightning, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_ELECTRIC_TERRAIN, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_STRIKE] = @@ -21297,8 +21309,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_LOWER_SPEED }, .battleAnimScript = gBattleAnimMove_MaxStrike, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_LOWER_SPEED_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_KNUCKLE] = @@ -21313,8 +21329,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_RAISE_TEAM_ATTACK }, .battleAnimScript = gBattleAnimMove_MaxKnuckle, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_RAISE_TEAM_ATTACK, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_PHANTASM] = @@ -21329,8 +21349,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_LOWER_DEFENSE }, .battleAnimScript = gBattleAnimMove_MaxPhantasm, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_LOWER_DEFENSE_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_HAILSTORM] = @@ -21345,8 +21369,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_HAIL }, .battleAnimScript = gBattleAnimMove_MaxHailstorm, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_HAIL, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_OOZE] = @@ -21361,8 +21389,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_RAISE_TEAM_SP_ATK }, .battleAnimScript = gBattleAnimMove_MaxOoze, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_RAISE_TEAM_SP_ATK, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_GEYSER] = @@ -21377,8 +21409,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_RAIN }, .battleAnimScript = gBattleAnimMove_MaxGeyser, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_RAIN, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_AIRSTREAM] = @@ -21393,8 +21429,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_RAISE_TEAM_SPEED }, .battleAnimScript = gBattleAnimMove_MaxAirstream, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_RAISE_TEAM_SPEED, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_STARFALL] = @@ -21409,8 +21449,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_MISTY_TERRAIN }, .battleAnimScript = gBattleAnimMove_MaxStarfall, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_MISTY_TERRAIN, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_WYRMWIND] = @@ -21425,8 +21469,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_LOWER_ATTACK }, .battleAnimScript = gBattleAnimMove_MaxWyrmwind, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_LOWER_ATTACK_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_MINDSTORM] = @@ -21441,8 +21489,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_PSYCHIC_TERRAIN }, .battleAnimScript = gBattleAnimMove_MaxMindstorm, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_PSYCHIC_TERRAIN, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_ROCKFALL] = @@ -21457,8 +21509,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_SANDSTORM }, .battleAnimScript = gBattleAnimMove_MaxRockfall, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_SANDSTORM, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_QUAKE] = @@ -21473,9 +21529,13 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_RAISE_TEAM_SP_DEF }, .skyBattleBanned = B_EXTRAPOLATED_MOVE_FLAGS, .battleAnimScript = gBattleAnimMove_MaxQuake, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_RAISE_TEAM_SP_DEF, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_DARKNESS] = @@ -21490,8 +21550,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_LOWER_SP_DEF }, .battleAnimScript = gBattleAnimMove_MaxDarkness, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_LOWER_SP_DEF_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_OVERGROWTH] = @@ -21506,8 +21570,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_GRASSY_TERRAIN }, .battleAnimScript = gBattleAnimMove_MaxOvergrowth, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_GRASSY_TERRAIN, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_MAX_STEELSPIKE] = @@ -21522,8 +21590,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_RAISE_TEAM_DEFENSE }, .battleAnimScript = gBattleAnimMove_MaxSteelspike, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_RAISE_TEAM_DEFENSE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_VINE_LASH] = @@ -21538,8 +21610,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_VINE_LASH }, .battleAnimScript = gBattleAnimMove_GMaxVineLash, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_VINE_LASH, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_WILDFIRE] = @@ -21554,8 +21630,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_WILDFIRE }, .battleAnimScript = gBattleAnimMove_GMaxWildfire, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_WILDFIRE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_CANNONADE] = @@ -21570,8 +21650,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_CANNONADE }, .battleAnimScript = gBattleAnimMove_GMaxCannonade, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_CANNONADE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_BEFUDDLE] = @@ -21586,8 +21670,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_EFFECT_SPORE_FOES }, .battleAnimScript = gBattleAnimMove_GMaxBefuddle, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_EFFECT_SPORE_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_VOLT_CRASH] = @@ -21602,8 +21690,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_PARALYZE_FOES }, .battleAnimScript = gBattleAnimMove_GMaxVoltCrash, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_PARALYZE_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_GOLD_RUSH] = @@ -21618,8 +21710,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_CONFUSE_FOES_PAY_DAY }, .battleAnimScript = gBattleAnimMove_GMaxGoldRush, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_CONFUSE_PAY_DAY_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_CHI_STRIKE] = @@ -21634,8 +21730,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_CRIT_PLUS }, .battleAnimScript = gBattleAnimMove_GMaxChiStrike, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_CRIT_PLUS_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_TERROR] = @@ -21650,8 +21750,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_MEAN_LOOK }, .battleAnimScript = gBattleAnimMove_GMaxTerror, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_PREVENT_ESCAPE_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_FOAM_BURST] = @@ -21666,8 +21770,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_LOWER_SPEED_2_FOES }, .battleAnimScript = gBattleAnimMove_GMaxFoamBurst, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_LOWER_SPEED_2_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_RESONANCE] = @@ -21682,8 +21790,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_AURORA_VEIL }, .battleAnimScript = gBattleAnimMove_GMaxResonance, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_AURORA_VEIL, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_CUDDLE] = @@ -21698,8 +21810,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_INFATUATE_FOES }, .battleAnimScript = gBattleAnimMove_GMaxCuddle, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_INFATUATE_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_REPLENISH] = @@ -21714,8 +21830,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_RECYCLE_BERRIES }, .battleAnimScript = gBattleAnimMove_GMaxReplenish, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_RECYCLE_BERRIES, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_MALODOR] = @@ -21730,8 +21850,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_POISON_FOES }, .battleAnimScript = gBattleAnimMove_GMaxMalodor, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_POISON_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_MELTDOWN] = @@ -21746,8 +21870,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_TORMENT_FOES }, .battleAnimScript = gBattleAnimMove_GMaxMeltdown, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_TORMENT_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_DRUM_SOLO] = @@ -21762,9 +21890,13 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_FIXED_POWER }, .ignoresTargetAbility = TRUE, .battleAnimScript = gBattleAnimMove_GMaxDrumSolo, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_FIXED_POWER, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_FIREBALL] = @@ -21779,9 +21911,13 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_FIXED_POWER }, .ignoresTargetAbility = TRUE, .battleAnimScript = gBattleAnimMove_GMaxFireball, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_FIXED_POWER, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_HYDROSNIPE] = @@ -21796,9 +21932,13 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_FIXED_POWER }, .ignoresTargetAbility = TRUE, .battleAnimScript = gBattleAnimMove_GMaxHydrosnipe, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_FIXED_POWER, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_WIND_RAGE] = @@ -21813,8 +21953,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_DEFOG }, .battleAnimScript = gBattleAnimMove_GMaxWindRage, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_DEFOG, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_GRAVITAS] = @@ -21829,8 +21973,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_GRAVITY }, .battleAnimScript = gBattleAnimMove_GMaxGravitas, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_GRAVITY, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_STONESURGE] = @@ -21845,8 +21993,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_STEALTH_ROCK }, .battleAnimScript = gBattleAnimMove_GMaxStonesurge, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_STEALTH_ROCK, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_VOLCALITH] = @@ -21861,8 +22013,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_VOLCALITH }, .battleAnimScript = gBattleAnimMove_GMaxVolcalith, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_VOLCALITH, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_TARTNESS] = @@ -21877,8 +22033,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_LOWER_EVASIVENESS_FOES }, .battleAnimScript = gBattleAnimMove_GMaxTartness, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_LOWER_EVASIVENESS_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_SWEETNESS] = @@ -21893,8 +22053,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_AROMATHERAPY }, .battleAnimScript = gBattleAnimMove_GMaxSweetness, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_AROMATHERAPY, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_SANDBLAST] = @@ -21909,8 +22073,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_SANDBLAST_FOES }, .battleAnimScript = gBattleAnimMove_GMaxSandblast, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_SANDBLAST_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_STUN_SHOCK] = @@ -21925,8 +22093,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_POISON_PARALYZE_FOES }, .battleAnimScript = gBattleAnimMove_GMaxStunShock, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_POISON_PARALYZE_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_CENTIFERNO] = @@ -21941,8 +22113,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_FIRE_SPIN_FOES }, .battleAnimScript = gBattleAnimMove_GMaxCentiferno, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_FIRE_SPIN_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_SMITE] = @@ -21957,8 +22133,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_CONFUSE_FOES }, .battleAnimScript = gBattleAnimMove_GMaxSmite, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_CONFUSE_SIDE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, @@ -21974,8 +22154,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_YAWN_FOE }, .battleAnimScript = gBattleAnimMove_GMaxSnooze, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_YAWN_FOE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_FINALE] = @@ -21990,8 +22174,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_HEAL_TEAM }, .battleAnimScript = gBattleAnimMove_GMaxFinale, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_HEAL_TEAM, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_STEELSURGE] = @@ -22006,8 +22194,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_STEELSURGE }, .battleAnimScript = gBattleAnimMove_GMaxSteelsurge, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_STEELSURGE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_DEPLETION] = @@ -22022,8 +22214,12 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_SPITE }, .battleAnimScript = gBattleAnimMove_GMaxDepletion, + .additionalEffects = ADDITIONAL_EFFECTS({ + .moveEffect = MOVE_EFFECT_SPITE, + .chance = 100, + .sheerForceBoost = SHEER_FORCE_NO_BOOST, + }), }, [MOVE_G_MAX_ONE_BLOW] = @@ -22038,14 +22234,14 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_BYPASS_PROTECT }, + .ignoresProtect = TRUE, .battleAnimScript = gBattleAnimMove_GMaxOneBlow, }, [MOVE_G_MAX_RAPID_FLOW] = { .name = COMPOUND_STRING("G-Max Rapid Flow"), - .description = sNullDescription, //ANIM TODO + .description = sNullDescription, .effect = EFFECT_MAX_MOVE, .power = 10, .type = TYPE_WATER, @@ -22054,8 +22250,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_PHYSICAL, - .argument = { .maxEffect = MAX_EFFECT_BYPASS_PROTECT }, + .ignoresProtect = TRUE, .battleAnimScript = gBattleAnimMove_GMaxRapidFlow, }, - }; diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index 4b5c05dfa7..b2f659faa0 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -683,7 +683,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Strike lowers single opponent's speed") { GIVEN { // Fails?: ASSUME(GetMaxMove(B_POSITION_PLAYER_LEFT, MOVE_TACKLE) == MOVE_MAX_STRIKE); - ASSUME(GetMoveMaxEffect(MOVE_MAX_STRIKE) == MAX_EFFECT_LOWER_SPEED); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_STRIKE, MOVE_EFFECT_LOWER_SPEED_SIDE)); OPPONENT(SPECIES_WOBBUFFET) { Speed(100); } PLAYER(SPECIES_WOBBUFFET) { Speed(80); } } WHEN { @@ -707,7 +707,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Strike lowers single opponent's speed") DOUBLE_BATTLE_TEST("(DYNAMAX) Max Strike lowers both opponents' speed") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_MAX_STRIKE) == MAX_EFFECT_LOWER_SPEED); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_STRIKE, MOVE_EFFECT_LOWER_SPEED_SIDE)); PLAYER(SPECIES_WOBBUFFET) { Speed(80); } PLAYER(SPECIES_WOBBUFFET) { Speed(79); } OPPONENT(SPECIES_WOBBUFFET) {Speed(100); } @@ -744,7 +744,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) Max Knuckle raises both allies' attack") { s16 damage[4]; GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_MAX_KNUCKLE) == MAX_EFFECT_RAISE_TEAM_ATTACK); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_KNUCKLE, MOVE_EFFECT_RAISE_TEAM_ATTACK)); ASSUME(GetMoveCategory(MOVE_CLOSE_COMBAT) == DAMAGE_CATEGORY_PHYSICAL); ASSUME(GetMoveCategory(MOVE_TACKLE) == DAMAGE_CATEGORY_PHYSICAL); PLAYER(SPECIES_WOBBUFFET); @@ -786,7 +786,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) Max Knuckle raises both allies' attack") SINGLE_BATTLE_TEST("(DYNAMAX) Max Flare sets up sunlight") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_MAX_FLARE) == MAX_EFFECT_SUN); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_FLARE, MOVE_EFFECT_SUN)); OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { @@ -802,7 +802,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Flare sets up sunlight") SINGLE_BATTLE_TEST("(DYNAMAX) Max Geyser sets up heavy rain") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_MAX_GEYSER) == MAX_EFFECT_RAIN); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_GEYSER, MOVE_EFFECT_RAIN)); OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { @@ -818,7 +818,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Geyser sets up heavy rain") SINGLE_BATTLE_TEST("(DYNAMAX) Max Hailstorm sets up hail") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_MAX_HAILSTORM) == MAX_EFFECT_HAIL); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_HAILSTORM, MOVE_EFFECT_HAIL)); OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { @@ -834,7 +834,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Hailstorm sets up hail") SINGLE_BATTLE_TEST("(DYNAMAX) Max Rockfall sets up a sandstorm") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_MAX_ROCKFALL) == MAX_EFFECT_SANDSTORM); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_ROCKFALL, MOVE_EFFECT_SANDSTORM)); OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { @@ -851,7 +851,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Overgrowth sets up Grassy Terrain") { s32 maxHP = 490; // Because of recalculated stats upon Dynamaxing GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_MAX_OVERGROWTH) == MAX_EFFECT_GRASSY_TERRAIN); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_OVERGROWTH, MOVE_EFFECT_GRASSY_TERRAIN)); ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].baseHP == 190); OPPONENT(SPECIES_WOBBUFFET) { MaxHP(maxHP); HP(maxHP / 2); }; PLAYER(SPECIES_WOBBUFFET) { MaxHP(maxHP); HP(maxHP / 2); }; @@ -871,7 +871,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Overgrowth sets up Grassy Terrain") SINGLE_BATTLE_TEST("(DYNAMAX) Max Mindstorm sets up Psychic Terrain") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_MAX_MINDSTORM) == MAX_EFFECT_PSYCHIC_TERRAIN); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_MINDSTORM, MOVE_EFFECT_PSYCHIC_TERRAIN)); OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { @@ -888,7 +888,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Mindstorm sets up Psychic Terrain") SINGLE_BATTLE_TEST("(DYNAMAX) Max Lightning sets up Electric Terrain") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_MAX_LIGHTNING) == MAX_EFFECT_ELECTRIC_TERRAIN); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_LIGHTNING, MOVE_EFFECT_ELECTRIC_TERRAIN)); OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { @@ -903,7 +903,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Lightning sets up Electric Terrain") SINGLE_BATTLE_TEST("(DYNAMAX) Max Starfall sets up Misty Terrain") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_MAX_STARFALL) == MAX_EFFECT_MISTY_TERRAIN); + ASSUME(MoveHasAdditionalEffect(MOVE_MAX_STARFALL, MOVE_EFFECT_MISTY_TERRAIN)); OPPONENT(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET); } WHEN { @@ -918,7 +918,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Max Starfall sets up Misty Terrain") SINGLE_BATTLE_TEST("(DYNAMAX) G-Max Stonesurge sets up Stealth Rocks") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_STONESURGE) == MAX_EFFECT_STEALTH_ROCK); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_STONESURGE, MOVE_EFFECT_STEALTH_ROCK)); PLAYER(SPECIES_DREDNAW) { GigantamaxFactor(TRUE); } OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -938,7 +938,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) G-Max Stonesurge sets up Stealth Rocks") SINGLE_BATTLE_TEST("(DYNAMAX) G-Max Steelsurge sets up sharp steel") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_STEELSURGE) == MAX_EFFECT_STEELSURGE); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_STEELSURGE, MOVE_EFFECT_STEELSURGE)); PLAYER(SPECIES_COPPERAJAH) { GigantamaxFactor(TRUE); } OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_HATTERENE); @@ -969,7 +969,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) G-Max Hydrosnipe has fixed power and ignores abili PARAMETRIZE { move = MOVE_WATER_GUN; } PARAMETRIZE { move = MOVE_HYDRO_CANNON; } GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_HYDROSNIPE) == MAX_EFFECT_FIXED_POWER); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_HYDROSNIPE, MOVE_EFFECT_FIXED_POWER)); PLAYER(SPECIES_INTELEON) { GigantamaxFactor(TRUE); } OPPONENT(SPECIES_ARCTOVISH) { Ability(ABILITY_WATER_ABSORB); } } WHEN { @@ -985,7 +985,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) G-Max Hydrosnipe has fixed power and ignores abili DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Volt Crash paralyzes both opponents") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_VOLT_CRASH) == MAX_EFFECT_PARALYZE_FOES); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_VOLT_CRASH, MOVE_EFFECT_PARALYZE_SIDE)); PLAYER(SPECIES_PIKACHU) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_PICHU); OPPONENT(SPECIES_WOBBUFFET); @@ -1012,7 +1012,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Stun Shock paralyzes or poisons both opponen PARAMETRIZE { statusAnim = B_ANIM_STATUS_PRZ; rng = STATUS1_PARALYSIS; } PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = STATUS1_POISON; } GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_STUN_SHOCK) == MAX_EFFECT_POISON_PARALYZE_FOES); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_STUN_SHOCK, MOVE_EFFECT_POISON_PARALYZE_SIDE)); PLAYER(SPECIES_TOXTRICITY) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_TOXEL); OPPONENT(SPECIES_WOBBUFFET); @@ -1049,7 +1049,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Stun Shock paralyzes or poisons both opponen DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Stun Shock chooses statuses before considering immunities") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_STUN_SHOCK) == MAX_EFFECT_POISON_PARALYZE_FOES); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_STUN_SHOCK, MOVE_EFFECT_POISON_PARALYZE_SIDE)); PLAYER(SPECIES_TOXTRICITY) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_TOXEL); OPPONENT(SPECIES_GARBODOR); @@ -1082,7 +1082,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Befuddle paralyzes, poisons, or sleeps both PARAMETRIZE { statusAnim = B_ANIM_STATUS_PSN; rng = STATUS1_POISON; } PARAMETRIZE { statusAnim = B_ANIM_STATUS_SLP; rng = STATUS1_SLEEP; } GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_BEFUDDLE) == MAX_EFFECT_EFFECT_SPORE_FOES); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_BEFUDDLE, MOVE_EFFECT_EFFECT_SPORE_SIDE)); PLAYER(SPECIES_BUTTERFREE) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_CATERPIE); OPPONENT(SPECIES_WOBBUFFET); @@ -1126,7 +1126,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Befuddle paralyzes, poisons, or sleeps both DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Gold Rush confuses both opponents and generates money") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_GOLD_RUSH) == MAX_EFFECT_CONFUSE_FOES_PAY_DAY); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_GOLD_RUSH, MOVE_EFFECT_CONFUSE_PAY_DAY_SIDE)); PLAYER(SPECIES_MEOWTH) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_PERSIAN); OPPONENT(SPECIES_WOBBUFFET); @@ -1146,7 +1146,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Gold Rush confuses both opponents and genera DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Smite confuses both opponents") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_SMITE) == MAX_EFFECT_CONFUSE_FOES); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_SMITE, MOVE_EFFECT_CONFUSE_SIDE)); PLAYER(SPECIES_HATTERENE) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_HATENNA); OPPONENT(SPECIES_WOBBUFFET); @@ -1165,7 +1165,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Smite confuses both opponents") DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Cuddle infatuates both opponents, if possible") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_CUDDLE) == MAX_EFFECT_INFATUATE_FOES); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_CUDDLE, MOVE_EFFECT_INFATUATE_SIDE)); PLAYER(SPECIES_EEVEE) { Gender(MON_MALE); GigantamaxFactor(TRUE); } PLAYER(SPECIES_EEVEE); OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); } @@ -1186,7 +1186,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Cuddle infatuates both opponents, if possibl DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Terror traps both opponents") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_TERROR) == MAX_EFFECT_MEAN_LOOK); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_TERROR, MOVE_EFFECT_PREVENT_ESCAPE_SIDE)); PLAYER(SPECIES_GENGAR) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_GASTLY); OPPONENT(SPECIES_WOBBUFFET); @@ -1205,7 +1205,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Terror traps both opponents") SINGLE_BATTLE_TEST("(DYNAMAX) Baton Pass passes G-Max Terror's escape prevention effect") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_TERROR) == MAX_EFFECT_MEAN_LOOK); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_TERROR, MOVE_EFFECT_PREVENT_ESCAPE_SIDE)); PLAYER(SPECIES_GENGAR) { GigantamaxFactor(TRUE); } OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WYNAUT); @@ -1222,7 +1222,7 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Baton Pass passes G-Max Terror's escape prevention DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Meltdown torments both opponents for 3 turns") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_MELTDOWN) == MAX_EFFECT_TORMENT_FOES); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_MELTDOWN, MOVE_EFFECT_TORMENT_SIDE)); PLAYER(SPECIES_MELMETAL) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_MELTAN); OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_SPLASH, MOVE_CELEBRATE); } @@ -1259,7 +1259,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Wildfire sets a field effect that damages no { s16 damage; GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_WILDFIRE) == MAX_EFFECT_WILDFIRE); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_WILDFIRE, MOVE_EFFECT_WILDFIRE)); PLAYER(SPECIES_CHARIZARD) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_CHARMANDER); OPPONENT(SPECIES_WOBBUFFET) { HP(600); MaxHP(600); } @@ -1305,7 +1305,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Replenish recycles allies' berries 50\% of t { PASSES_RANDOMLY(1, 2, RNG_G_MAX_REPLENISH); GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_REPLENISH) == MAX_EFFECT_RECYCLE_BERRIES); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_REPLENISH, MOVE_EFFECT_RECYCLE_BERRIES)); PLAYER(SPECIES_SNORLAX) { Item(ITEM_APICOT_BERRY); GigantamaxFactor(TRUE); } PLAYER(SPECIES_MUNCHLAX) { Item(ITEM_APICOT_BERRY); Ability(ABILITY_THICK_FAT); } OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_APICOT_BERRY); } @@ -1333,7 +1333,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Snooze makes only the target drowsy") { PASSES_RANDOMLY(1, 2, RNG_G_MAX_SNOOZE); GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_SNOOZE) == MAX_EFFECT_YAWN_FOE); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_SNOOZE, MOVE_EFFECT_YAWN_FOE)); ASSUME(GetMoveCategory(MOVE_DARK_PULSE) == DAMAGE_CATEGORY_SPECIAL); // Otherwise, Blissey faints. PLAYER(SPECIES_GRIMMSNARL) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_IMPIDIMP); @@ -1357,7 +1357,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Finale heals allies by 1/6 of their health") { s16 damage1, damage2; GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_FINALE) == MAX_EFFECT_HEAL_TEAM); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_FINALE, MOVE_EFFECT_HEAL_TEAM)); PLAYER(SPECIES_ALCREMIE) { HP(1); GigantamaxFactor(TRUE); } PLAYER(SPECIES_MILCERY) { HP(1); } OPPONENT(SPECIES_WOBBUFFET); @@ -1377,7 +1377,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Finale heals allies by 1/6 of their health") DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Sweetness cures allies' status conditions") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_SWEETNESS) == MAX_EFFECT_AROMATHERAPY); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_SWEETNESS, MOVE_EFFECT_AROMATHERAPY)); PLAYER(SPECIES_APPLETUN) { Status1(STATUS1_POISON); GigantamaxFactor(TRUE); } PLAYER(SPECIES_APPLIN) { Status1(STATUS1_POISON); } OPPONENT(SPECIES_WOBBUFFET); @@ -1397,7 +1397,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Sweetness cures allies' status conditions") DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Centiferno traps both opponents in Fire Spin") { GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_CENTIFERNO) == MAX_EFFECT_FIRE_SPIN_FOES); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_CENTIFERNO, MOVE_EFFECT_FIRE_SPIN_SIDE)); PLAYER(SPECIES_CENTISKORCH) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_SIZZLIPEDE); PLAYER(SPECIES_SIZZLIPEDE); @@ -1426,7 +1426,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Chi Strike boosts allies' crit chance") u32 j; GIVEN { ASSUME(B_CRIT_CHANCE >= GEN_6); - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_CHI_STRIKE) == MAX_EFFECT_CRIT_PLUS); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_CHI_STRIKE, MOVE_EFFECT_CRIT_PLUS_SIDE)); PLAYER(SPECIES_MACHAMP) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_MACHOP); OPPONENT(SPECIES_WOBBUFFET); @@ -1458,7 +1458,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Depletion takes away 2 PP from the target's { GIVEN { ASSUME(GetMoveCategory(MOVE_DRAGON_CLAW) == DAMAGE_CATEGORY_PHYSICAL); // Otherwise Sableye faints. - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_DEPLETION) == MAX_EFFECT_SPITE); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_DEPLETION, MOVE_EFFECT_SPITE)); PLAYER(SPECIES_DURALUDON) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_WYNAUT); // Dynamax behaves weird with test turn order because stats are recalculated. @@ -1480,7 +1480,7 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max One Blow bypasses Max Guard for full damage" PARAMETRIZE { protect = TRUE; } PARAMETRIZE { protect = FALSE; } GIVEN { - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_ONE_BLOW) == MAX_EFFECT_BYPASS_PROTECT); + ASSUME(MoveIgnoresProtect(MOVE_G_MAX_RAPID_FLOW)); PLAYER(SPECIES_URSHIFU) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_KUBFU); OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/battle/sleep_clause.c b/test/battle/sleep_clause.c index a5f28f4f22..ae20a0acad 100644 --- a/test/battle/sleep_clause.c +++ b/test/battle/sleep_clause.c @@ -507,7 +507,7 @@ DOUBLE_BATTLE_TEST("Sleep Clause: G-Max Befuddle can only sleep one opposing mon { GIVEN { FLAG_SET(B_FLAG_SLEEP_CLAUSE); - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_BEFUDDLE) == MAX_EFFECT_EFFECT_SPORE_FOES); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_BEFUDDLE, MOVE_EFFECT_EFFECT_SPORE_SIDE)); PLAYER(SPECIES_BUTTERFREE) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_CATERPIE); OPPONENT(SPECIES_WOBBUFFET); @@ -1131,7 +1131,7 @@ DOUBLE_BATTLE_TEST("Sleep Clause: Sleep clause is deactivated when a sleeping mo { GIVEN { FLAG_SET(B_FLAG_SLEEP_CLAUSE); - ASSUME(GetMoveMaxEffect(MOVE_G_MAX_SWEETNESS) == MAX_EFFECT_AROMATHERAPY); + ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_SWEETNESS, MOVE_EFFECT_AROMATHERAPY)); ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_SLEEP); PLAYER(SPECIES_APPLETUN) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_WOBBUFFET); From b51598c14d414a19b89e162536fa02e5e17d3387 Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Fri, 7 Feb 2025 09:30:57 -0300 Subject: [PATCH 20/28] Removed Trainer Slides footgun (#6205) --- include/constants/trainer_slide.h | 1 + test/battle/trainer_slides.c | 26 +++++++++++++------------- test/battle/trainer_slides.h | 26 +++++++++++++------------- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/include/constants/trainer_slide.h b/include/constants/trainer_slide.h index 5ddab7be00..26635b164b 100644 --- a/include/constants/trainer_slide.h +++ b/include/constants/trainer_slide.h @@ -13,6 +13,7 @@ enum ComparisonOperators enum TrainerSlideType { + TRAINER_SLIDE_NONE, TRAINER_SLIDE_BEFORE_FIRST_TURN, TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT, TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT, diff --git a/test/battle/trainer_slides.c b/test/battle/trainer_slides.c index ee8b632f25..b5bb127a0c 100644 --- a/test/battle/trainer_slides.c +++ b/test/battle/trainer_slides.c @@ -4,7 +4,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Before First Turn") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_BEFORE_FIRST_TURN; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_BEFORE_FIRST_TURN; GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -18,7 +18,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Before First Turn") SINGLE_BATTLE_TEST("Trainer Slide: Player Lands First Critical Hit") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT; GIVEN { ASSUME(GetMoveEffect(MOVE_LASER_FOCUS) == EFFECT_LASER_FOCUS); @@ -37,7 +37,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Player Lands First Critical Hit") SINGLE_BATTLE_TEST("Trainer Slide: Enemy Lands First Critical Hit") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT; GIVEN { ASSUME(GetMoveEffect(MOVE_LASER_FOCUS) == EFFECT_LASER_FOCUS); @@ -56,7 +56,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Enemy Lands First Critical Hit") SINGLE_BATTLE_TEST("Trainer Slide: Player Lands First STAB Hit") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE; GIVEN { ASSUME((GetMoveType(MOVE_VINE_WHIP)) == gSpeciesInfo[SPECIES_BULBASAUR].types[0]); @@ -73,7 +73,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Player Lands First STAB Hit") SINGLE_BATTLE_TEST("Trainer Slide: Player Lands First Super Effective Hit") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT; GIVEN { ASSUME(GetMoveType(MOVE_BITE) == TYPE_DARK); @@ -91,7 +91,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Player Lands First Super Effective Hit") SINGLE_BATTLE_TEST("Trainer Slide: Player Lands First Down") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_PLAYER_LANDS_FIRST_DOWN; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_PLAYER_LANDS_FIRST_DOWN; GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -107,7 +107,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Player Lands First Down") SINGLE_BATTLE_TEST("Trainer Slide: Enemy Mon Unaffected") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_ENEMY_MON_UNAFFECTED; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_ENEMY_MON_UNAFFECTED; GIVEN { ASSUME(B_SHEER_COLD_IMMUNITY >= GEN_7); ASSUME(gSpeciesInfo[SPECIES_GLALIE].types[0] == TYPE_ICE); @@ -124,7 +124,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Enemy Mon Unaffected") SINGLE_BATTLE_TEST("Trainer Slide: Last Switchin") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_LAST_SWITCHIN; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_LAST_SWITCHIN; GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -139,7 +139,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Last Switchin") SINGLE_BATTLE_TEST("Trainer Slide: Last Half Hp") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_LAST_HALF_HP; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_LAST_HALF_HP; GIVEN { ASSUME(gMovesInfo[MOVE_SUPER_FANG].effect == EFFECT_SUPER_FANG); ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].baseHP == 190); @@ -154,7 +154,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Last Half Hp") SINGLE_BATTLE_TEST("Trainer Slide: Last Low Hp") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_LAST_LOW_HP; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_LAST_LOW_HP; GIVEN { ASSUME(GetMoveEffect(MOVE_FALSE_SWIPE) == EFFECT_FALSE_SWIPE); PLAYER(SPECIES_WOBBUFFET) { Attack(999);} @@ -168,7 +168,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Last Low Hp") SINGLE_BATTLE_TEST("Trainer Slide: Mega Evolution") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_MEGA_EVOLUTION; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_MEGA_EVOLUTION; GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -184,7 +184,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Mega Evolution") SINGLE_BATTLE_TEST("Trainer Slide: Z Move") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_Z_MOVE; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_Z_MOVE; GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_NORMALIUM_Z); } @@ -200,7 +200,7 @@ SINGLE_BATTLE_TEST("Trainer Slide: Z Move") SINGLE_BATTLE_TEST("Trainer Slide: Dynamax") { - gBattleTestRunnerState->data.recordedBattle.opponentA = 1 + TRAINER_SLIDE_DYNAMAX; + gBattleTestRunnerState->data.recordedBattle.opponentA = TRAINER_SLIDE_DYNAMAX; GIVEN { PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/trainer_slides.h b/test/battle/trainer_slides.h index 3c4dff852a..cb62a00800 100644 --- a/test/battle/trainer_slides.h +++ b/test/battle/trainer_slides.h @@ -1,54 +1,54 @@ [DIFFICULTY_NORMAL] = { - [1 + TRAINER_SLIDE_BEFORE_FIRST_TURN] = + [TRAINER_SLIDE_BEFORE_FIRST_TURN] = { [TRAINER_SLIDE_BEFORE_FIRST_TURN] = COMPOUND_STRING("This message plays before the first turn.{PAUSE_UNTIL_PRESS}"), }, - [1 + TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT] = + [TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT] = { [TRAINER_SLIDE_PLAYER_LANDS_FIRST_CRITICAL_HIT] = COMPOUND_STRING("This message plays after the player lands their first critical hit.{PAUSE_UNTIL_PRESS}"), }, - [1 + TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT] = + [TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT] = { [TRAINER_SLIDE_ENEMY_LANDS_FIRST_CRITICAL_HIT] = COMPOUND_STRING("This message plays after the enemy lands their first critical hit.{PAUSE_UNTIL_PRESS}"), }, - [1 + TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT] = + [TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT] = { [TRAINER_SLIDE_PLAYER_LANDS_FIRST_SUPER_EFFECTIVE_HIT] = COMPOUND_STRING("This message plays after the player lands their first super effective hit.{PAUSE_UNTIL_PRESS}"), }, - [1 + TRAINER_SLIDE_PLAYER_LANDS_FIRST_DOWN] = + [TRAINER_SLIDE_PLAYER_LANDS_FIRST_DOWN] = { [TRAINER_SLIDE_PLAYER_LANDS_FIRST_DOWN] = COMPOUND_STRING("This message plays after the player KOs one enemy mon.{PAUSE_UNTIL_PRESS}"), }, - [1 + TRAINER_SLIDE_ENEMY_MON_UNAFFECTED] = + [TRAINER_SLIDE_ENEMY_MON_UNAFFECTED] = { [TRAINER_SLIDE_ENEMY_MON_UNAFFECTED] = COMPOUND_STRING("Player attacked enemy with ineffective move.{PAUSE_UNTIL_PRESS}"), }, - [1 + TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE] = + [TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE] = { [TRAINER_SLIDE_PLAYER_LANDS_FIRST_STAB_MOVE] = COMPOUND_STRING("Player lands their first STAB move.{PAUSE_UNTIL_PRESS}"), }, - [1 + TRAINER_SLIDE_LAST_SWITCHIN] = + [TRAINER_SLIDE_LAST_SWITCHIN] = { [TRAINER_SLIDE_LAST_SWITCHIN] = COMPOUND_STRING("This message plays after the enemy switches in their last Pokemon.{PAUSE_UNTIL_PRESS}"), }, - [1 + TRAINER_SLIDE_LAST_HALF_HP] = + [TRAINER_SLIDE_LAST_HALF_HP] = { [TRAINER_SLIDE_LAST_HALF_HP] = COMPOUND_STRING("Enemy last Mon has < 51% HP.{PAUSE_UNTIL_PRESS}"), }, - [1 + TRAINER_SLIDE_LAST_LOW_HP] = + [TRAINER_SLIDE_LAST_LOW_HP] = { [TRAINER_SLIDE_LAST_LOW_HP] = COMPOUND_STRING("Enemy last Mon has < 26% HP.{PAUSE_UNTIL_PRESS}"), }, - [1 + TRAINER_SLIDE_MEGA_EVOLUTION] = + [TRAINER_SLIDE_MEGA_EVOLUTION] = { [TRAINER_SLIDE_MEGA_EVOLUTION] = COMPOUND_STRING("This message plays before the enemy activates the Mega Evolution gimmick.{PAUSE_UNTIL_PRESS}"), }, - [1 + TRAINER_SLIDE_Z_MOVE] = + [TRAINER_SLIDE_Z_MOVE] = { [TRAINER_SLIDE_Z_MOVE] = COMPOUND_STRING("This message plays before the enemy activates the Z-Move gimmick.{PAUSE_UNTIL_PRESS}"), }, - [1 + TRAINER_SLIDE_DYNAMAX] = + [TRAINER_SLIDE_DYNAMAX] = { [TRAINER_SLIDE_DYNAMAX] = COMPOUND_STRING("This message plays before the enemy activates the Dynamax gimmick.{PAUSE_UNTIL_PRESS}"), }, From 96ee24c72614b733f9d709a9cebd4fc41dd7b42a Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Fri, 7 Feb 2025 10:40:10 -0500 Subject: [PATCH 21/28] Minor Terrain Seed Test Improvement (#6207) Co-authored-by: ghoulslash --- test/battle/hold_effect/seeds.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/battle/hold_effect/seeds.c b/test/battle/hold_effect/seeds.c index 22e94baa8c..11aa9037db 100644 --- a/test/battle/hold_effect/seeds.c +++ b/test/battle/hold_effect/seeds.c @@ -40,6 +40,8 @@ SINGLE_BATTLE_TEST("Electric Seed raises the holder's Defense on Electric Terrai SEND_IN_MESSAGE("Wobbuffet"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Using Electric Seed, the Defense of Wobbuffet rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); } } @@ -69,6 +71,8 @@ SINGLE_BATTLE_TEST("Grassy Seed raises the holder's Defense on Grassy Terrain") SEND_IN_MESSAGE("Wobbuffet"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Using Grassy Seed, the Defense of Wobbuffet rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1); } } @@ -98,6 +102,8 @@ SINGLE_BATTLE_TEST("Misty Seed raises the holder's Sp. Defense on Misty Terrain" SEND_IN_MESSAGE("Wobbuffet"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Using Misty Seed, the Sp. Def of Wobbuffet rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); } } @@ -127,5 +133,7 @@ SINGLE_BATTLE_TEST("Psychic Seed raises the holder's Sp. Defense on Psychic Terr SEND_IN_MESSAGE("Wobbuffet"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); MESSAGE("Using Psychic Seed, the Sp. Def of Wobbuffet rose!"); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE + 1); } } From e77fe32dec398196ff87cf34627a784cc25e5a4b Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Fri, 7 Feb 2025 10:42:08 -0500 Subject: [PATCH 22/28] Fix Choice'd mons referring to incorrect move when switching (#6204) --- src/battle_ai_switch_items.c | 2 +- test/battle/ai/ai_choice.c | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 6e754121c0..0001ad6c78 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -875,7 +875,7 @@ static bool32 ShouldSwitchIfBadChoiceLock(u32 battler) if (HOLD_EFFECT_CHOICE(holdEffect) && gBattleMons[battler].ability != ABILITY_KLUTZ) { - if (GetMoveCategory(gLastUsedMove) == DAMAGE_CATEGORY_STATUS) + if (GetMoveCategory(AI_DATA->lastUsedMove[battler]) == DAMAGE_CATEGORY_STATUS) return SetSwitchinAndSwitch(battler, PARTY_SIZE); } diff --git a/test/battle/ai/ai_choice.c b/test/battle/ai/ai_choice.c index f1f13a373e..c3d3e02c03 100644 --- a/test/battle/ai/ai_choice.c +++ b/test/battle/ai/ai_choice.c @@ -42,6 +42,20 @@ AI_SINGLE_BATTLE_TEST("Choiced Pokémon switch out after using a status move onc } } +AI_SINGLE_BATTLE_TEST("Choiced Pokémon only consider their own status moves when determining if they should switch") +{ + GIVEN + { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_RISKY | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_ZIGZAGOON) { Speed(4); Moves(MOVE_TAIL_WHIP, MOVE_TACKLE); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(5); Moves(MOVE_TACKLE); Item(ITEM_CHOICE_BAND); } + OPPONENT(SPECIES_ZIGZAGOON) { Speed(5); Moves(MOVE_TACKLE); } + } WHEN { + TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_TAIL_WHIP); } + TURN { EXPECT_MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_TAIL_WHIP); } + } +} + AI_SINGLE_BATTLE_TEST("Choiced Pokémon won't use stat boosting moves") { // Moves defined by MOVE_TARGET_USER (with exceptions?) From f9ad4097154183146fd1bd9a4fae675870e7460e Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Fri, 7 Feb 2025 13:37:36 -0300 Subject: [PATCH 23/28] Adjusted G-Max Depletion test (#6201) --- test/battle/gimmick/dynamax.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/battle/gimmick/dynamax.c b/test/battle/gimmick/dynamax.c index b2f659faa0..05b48cc5ed 100644 --- a/test/battle/gimmick/dynamax.c +++ b/test/battle/gimmick/dynamax.c @@ -1459,10 +1459,11 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Depletion takes away 2 PP from the target's GIVEN { ASSUME(GetMoveCategory(MOVE_DRAGON_CLAW) == DAMAGE_CATEGORY_PHYSICAL); // Otherwise Sableye faints. ASSUME(MoveHasAdditionalEffect(MOVE_G_MAX_DEPLETION, MOVE_EFFECT_SPITE)); + ASSUME(GetMovePP(MOVE_CELEBRATE) >= 3); PLAYER(SPECIES_DURALUDON) { GigantamaxFactor(TRUE); } PLAYER(SPECIES_WYNAUT); // Dynamax behaves weird with test turn order because stats are recalculated. - OPPONENT(SPECIES_SABLEYE) { Ability(ABILITY_PRANKSTER); } + OPPONENT(SPECIES_SABLEYE) { Ability(ABILITY_PRANKSTER); Moves(MOVE_CELEBRATE); } OPPONENT(SPECIES_WYNAUT); } WHEN { TURN { MOVE(playerLeft, MOVE_DRAGON_CLAW, target: opponentLeft, gimmick: GIMMICK_DYNAMAX); } @@ -1470,6 +1471,8 @@ DOUBLE_BATTLE_TEST("(DYNAMAX) G-Max Depletion takes away 2 PP from the target's MESSAGE("The opposing Sableye used Celebrate!"); MESSAGE("Duraludon used G-Max Depletion!"); MESSAGE("The opposing Sableye's PP was reduced!"); + } THEN { + EXPECT_EQ(opponentLeft->pp[0], GetMovePP(MOVE_CELEBRATE) - 3); // 1 from regular use + 2 from G-Max Depletion } } From 82b0eff16c47b7a623ee69f825ac50f32d93342b Mon Sep 17 00:00:00 2001 From: Bassoonian Date: Fri, 7 Feb 2025 19:26:16 +0100 Subject: [PATCH 24/28] Fix typo in Nuzzle's description (#6209) --- src/data/moves_info.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 50f8cebbd3..a7c46bd1fc 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -15157,7 +15157,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = { .name = COMPOUND_STRING("Nuzzle"), .description = COMPOUND_STRING( - "Rubs its cheecks against\n" + "Rubs its cheeks against\n" "the foe, paralyzing it."), .effect = EFFECT_HIT, .power = 20, From 41882b267df9eac9dbfbfe6fe4e897c9975aa82c Mon Sep 17 00:00:00 2001 From: Frank DeBlasio <35279583+fdeblasio@users.noreply.github.com> Date: Sat, 8 Feb 2025 15:51:22 -0500 Subject: [PATCH 25/28] Consolidate duplicate dialogue of nature girl in Battle Frontier (#6195) --- data/maps/BattleFrontier_Lounge5/scripts.inc | 116 +++---------------- include/strings.h | 31 ++--- src/pokemon.c | 50 ++++---- 3 files changed, 53 insertions(+), 144 deletions(-) diff --git a/data/maps/BattleFrontier_Lounge5/scripts.inc b/data/maps/BattleFrontier_Lounge5/scripts.inc index b81c748e5d..7dc4251fcc 100644 --- a/data/maps/BattleFrontier_Lounge5/scripts.inc +++ b/data/maps/BattleFrontier_Lounge5/scripts.inc @@ -51,138 +51,60 @@ BattleFrontier_Lounge5_Text_NatureGirlNoneShown:: .string "Boo!\n" .string "Cheapie!$" -BattleFrontier_Lounge5_Text_NatureGirlHardy:: +BattleFrontier_Lounge5_Text_NatureGirlAttackHighAttackLow:: .string "Hmhm…\p" .string "This one says it likes to battle!\n" .string "It will battle even if it has a lot\l" .string "of ouchies!$" -BattleFrontier_Lounge5_Text_NatureGirlLonely:: - .string "Hmhm…\p" - .string "This one says it likes to be sneaky!\n" - .string "But if it gets enough ouchies,\l" - .string "it will hit back!$" - -BattleFrontier_Lounge5_Text_NatureGirlBrave:: +BattleFrontier_Lounge5_Text_NatureGirlAttackHighDefenseLow:: .string "Hmhm…\p" .string "This one says it likes to battle!\n" .string "But if it gets enough ouchies,\l" .string "it will worry about itself!$" -BattleFrontier_Lounge5_Text_NatureGirlAdamant:: - .string "Hmhm…\p" - .string "This one says it likes to battle!\n" - .string "It will battle even if it has a lot\l" - .string "of ouchies!$" - -BattleFrontier_Lounge5_Text_NatureGirlNaughty:: - .string "Hmhm…\p" - .string "This one says it looks after itself!\n" - .string "But if it gets enough ouchies,\l" - .string "it will hit back!$" - -BattleFrontier_Lounge5_Text_NatureGirlBold:: - .string "Hmhm…\p" - .string "This one says it likes to be sneaky!\n" - .string "But if it gets enough ouchies,\l" - .string "it will worry about itself!$" - -BattleFrontier_Lounge5_Text_NatureGirlDocileNaiveQuietQuirky:: - .string "Hmhm…\p" - .string "This one says it likes to battle!\n" - .string "It will battle even if it has a lot\l" - .string "of ouchies!$" - -BattleFrontier_Lounge5_Text_NatureGirlRelaxed:: - .string "Hmhm…\p" - .string "This one says it likes to be sneaky!\n" - .string "But if it gets enough ouchies,\l" - .string "it will hit back!$" - -BattleFrontier_Lounge5_Text_NatureGirlImpish:: - .string "Hmhm…\p" - .string "This one says it likes to battle!\n" - .string "But if it gets enough ouchies,\l" - .string "it will worry about itself!$" - -BattleFrontier_Lounge5_Text_NatureGirlLax:: - .string "Hmhm…\p" - .string "This one says it likes to be sneaky!\n" - .string "It says it likes to be sneaky even\l" - .string "if it has a lot of ouchies!$" - -BattleFrontier_Lounge5_Text_NatureGirlTimid:: +BattleFrontier_Lounge5_Text_NatureGirlAttackHighSupportLow:: .string "Hmhm…\p" .string "This one says it likes to battle!\n" .string "But if it gets enough ouchies,\l" .string "it will turn sneaky!$" -BattleFrontier_Lounge5_Text_NatureGirlHasty:: +BattleFrontier_Lounge5_Text_NatureGirlDefenseHighAttackLow:: .string "Hmhm…\p" - .string "This one says it likes to battle!\n" - .string "It will battle even if it has a lot\l" - .string "of ouchies!$" - -BattleFrontier_Lounge5_Text_NatureGirlSerious:: - .string "Hmhm…\p" - .string "This one says it likes to be sneaky!\n" - .string "It says it likes to be sneaky even\l" - .string "if it has a lot of ouchies!$" - -BattleFrontier_Lounge5_Text_NatureGirlJolly:: - .string "Hmhm…\p" - .string "This one says it likes to be sneaky!\n" + .string "This one says it looks after itself!\n" .string "But if it gets enough ouchies,\l" - .string "it will worry about itself!$" + .string "it will hit back!$" -BattleFrontier_Lounge5_Text_NatureGirlModest:: +BattleFrontier_Lounge5_Text_NatureGirlDefenseHighDefenseLow:: .string "Hmhm…\p" .string "This one says it looks after itself!\n" .string "It says it worries about itself whether\l" .string "or not it has a lot of ouchies!$" -BattleFrontier_Lounge5_Text_NatureGirlMild:: +BattleFrontier_Lounge5_Text_NatureGirlDefenseHighSupportLow:: .string "Hmhm…\p" .string "This one says it looks after itself!\n" .string "But if it gets enough ouchies,\l" .string "it will turn sneaky!$" -BattleFrontier_Lounge5_Text_NatureGirlBashful:: +BattleFrontier_Lounge5_Text_NatureGirlSupportHighAttackLow:: .string "Hmhm…\p" - .string "This one says it looks after itself!\n" - .string "It says it worries about itself even\l" - .string "if it has a lot of ouchies!$" + .string "This one says it likes to be sneaky!\n" + .string "But if it gets enough ouchies,\l" + .string "it will hit back!$" -BattleFrontier_Lounge5_Text_NatureGirlRash:: +BattleFrontier_Lounge5_Text_NatureGirlSupportHighDefenseLow:: + .string "Hmhm…\p" + .string "This one says it likes to be sneaky!\n" + .string "But if it gets enough ouchies,\l" + .string "it will worry about itself!$" + +BattleFrontier_Lounge5_Text_NatureGirlSupportHighSupportLow:: .string "Hmhm…\p" .string "This one says it likes to be sneaky!\n" .string "It says it likes to be sneaky even\l" .string "if it has a lot of ouchies!$" -BattleFrontier_Lounge5_Text_NatureGirlCalm:: - .string "Hmhm…\p" - .string "This one says it looks after itself!\n" - .string "It says it worries about itself even\l" - .string "if it has a lot of ouchies!$" - -BattleFrontier_Lounge5_Text_NatureGirlGentle:: - .string "Hmhm…\p" - .string "This one says it looks after itself!\n" - .string "But if it gets enough ouchies,\l" - .string "it will hit back!$" - -BattleFrontier_Lounge5_Text_NatureGirlSassy:: - .string "Hmhm…\p" - .string "This one says it likes to battle!\n" - .string "But if it gets enough ouchies,\l" - .string "it will turn sneaky!$" - -BattleFrontier_Lounge5_Text_NatureGirlCareful:: - .string "Hmhm…\p" - .string "This one says it looks after itself!\n" - .string "But if it gets enough ouchies,\l" - .string "it will turn sneaky!$" - BattleFrontier_Lounge5_Text_NatureGirlEgg:: .string "That's silly! An EGG is asleep!\n" .string "I can't talk to it!$" diff --git a/include/strings.h b/include/strings.h index 06a3a658a2..9eb678d23e 100644 --- a/include/strings.h +++ b/include/strings.h @@ -553,28 +553,15 @@ extern const u8 BattleFrontier_Lounge2_Text_PyramidKingSilverMons[]; extern const u8 BattleFrontier_Lounge2_Text_PyramidKingGoldMons[]; // Battle Frontier Nature Girl -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlHardy[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlLonely[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlBrave[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlAdamant[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlNaughty[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlBold[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlDocileNaiveQuietQuirky[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlRelaxed[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlImpish[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlLax[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlTimid[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlHasty[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlSerious[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlJolly[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlModest[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlMild[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlBashful[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlRash[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlCalm[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlGentle[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlSassy[]; -extern const u8 BattleFrontier_Lounge5_Text_NatureGirlCareful[]; +extern const u8 BattleFrontier_Lounge5_Text_NatureGirlAttackHighAttackLow[]; +extern const u8 BattleFrontier_Lounge5_Text_NatureGirlAttackHighDefenseLow[]; +extern const u8 BattleFrontier_Lounge5_Text_NatureGirlAttackHighSupportLow[]; +extern const u8 BattleFrontier_Lounge5_Text_NatureGirlDefenseHighAttackLow[]; +extern const u8 BattleFrontier_Lounge5_Text_NatureGirlDefenseHighDefenseLow[]; +extern const u8 BattleFrontier_Lounge5_Text_NatureGirlDefenseHighSupportLow[]; +extern const u8 BattleFrontier_Lounge5_Text_NatureGirlSupportHighAttackLow[]; +extern const u8 BattleFrontier_Lounge5_Text_NatureGirlSupportHighDefenseLow[]; +extern const u8 BattleFrontier_Lounge5_Text_NatureGirlSupportHighSupportLow[]; // Battle Frontier Gambler extern const u8 BattleFrontier_Lounge3_Text_ChallengeBattleTowerSingle[]; diff --git a/src/pokemon.c b/src/pokemon.c index 830b1878f1..128f09f514 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -382,7 +382,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_ATK, .backAnim = 0, .pokeBlockAnim = {ANIM_HARDY, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlHardy, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlAttackHighAttackLow, .battlePalacePercents = PALACE_STYLE(61, 7, 61, 7), //32% support >= 50% HP, 32% support < 50% HP .battlePalaceFlavorText = B_MSG_EAGER_FOR_MORE, .battlePalaceSmokescreen = PALACE_TARGET_STRONGER, @@ -394,7 +394,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_DEF, .backAnim = 2, .pokeBlockAnim = {ANIM_LONELY, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlLonely, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlSupportHighAttackLow, .battlePalacePercents = PALACE_STYLE(20, 25, 84, 8), //55%, 8% .battlePalaceFlavorText = B_MSG_GLINT_IN_EYE, .battlePalaceSmokescreen = PALACE_TARGET_STRONGER, @@ -406,7 +406,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPEED, .backAnim = 0, .pokeBlockAnim = {ANIM_BRAVE, AFFINE_TURN_UP}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlBrave, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlAttackHighDefenseLow, .battlePalacePercents = PALACE_STYLE(70, 15, 32, 60), //15%, 8% .battlePalaceFlavorText = B_MSG_GETTING_IN_POS, .battlePalaceSmokescreen = PALACE_TARGET_WEAKER, @@ -418,7 +418,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPATK, .backAnim = 0, .pokeBlockAnim = {ANIM_ADAMANT, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlAdamant, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlAttackHighAttackLow, .battlePalacePercents = PALACE_STYLE(38, 31, 70, 15), //31%, 15% .battlePalaceFlavorText = B_MSG_GLINT_IN_EYE, .battlePalaceSmokescreen = PALACE_TARGET_STRONGER, @@ -430,7 +430,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPDEF, .backAnim = 0, .pokeBlockAnim = {ANIM_NAUGHTY, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlNaughty, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlDefenseHighAttackLow, .battlePalacePercents = PALACE_STYLE(20, 70, 70, 22), //10%, 8% .battlePalaceFlavorText = B_MSG_GLINT_IN_EYE, .battlePalaceSmokescreen = PALACE_TARGET_WEAKER, @@ -442,7 +442,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_ATK, .backAnim = 1, .pokeBlockAnim = {ANIM_BOLD, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlBold, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlSupportHighDefenseLow, .battlePalacePercents = PALACE_STYLE(30, 20, 32, 58), //50%, 10% .battlePalaceFlavorText = B_MSG_GETTING_IN_POS, .battlePalaceSmokescreen = PALACE_TARGET_WEAKER, @@ -454,7 +454,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_DEF, .backAnim = 1, .pokeBlockAnim = {ANIM_DOCILE, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlDocileNaiveQuietQuirky, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlAttackHighAttackLow, .battlePalacePercents = PALACE_STYLE(56, 22, 56, 22), //22%, 22% .battlePalaceFlavorText = B_MSG_EAGER_FOR_MORE, .battlePalaceSmokescreen = PALACE_TARGET_RANDOM, @@ -466,7 +466,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPEED, .backAnim = 1, .pokeBlockAnim = {ANIM_RELAXED, AFFINE_TURN_UP_AND_DOWN}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlRelaxed, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlSupportHighAttackLow, .battlePalacePercents = PALACE_STYLE(25, 15, 75, 15), //60%, 10% .battlePalaceFlavorText = B_MSG_GLINT_IN_EYE, .battlePalaceSmokescreen = PALACE_TARGET_STRONGER, @@ -478,7 +478,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPATK, .backAnim = 0, .pokeBlockAnim = {ANIM_IMPISH, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlImpish, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlAttackHighDefenseLow, .battlePalacePercents = PALACE_STYLE(69, 6, 28, 55), //25%, 17% .battlePalaceFlavorText = B_MSG_GETTING_IN_POS, .battlePalaceSmokescreen = PALACE_TARGET_STRONGER, @@ -490,7 +490,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPDEF, .backAnim = 1, .pokeBlockAnim = {ANIM_LAX, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlLax, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlSupportHighSupportLow, .battlePalacePercents = PALACE_STYLE(35, 10, 29, 6), //55%, 65% .battlePalaceFlavorText = B_MSG_GROWL_DEEPLY, .battlePalaceSmokescreen = PALACE_TARGET_STRONGER, @@ -502,7 +502,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_ATK, .backAnim = 2, .pokeBlockAnim = {ANIM_TIMID, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlTimid, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlAttackHighSupportLow, .battlePalacePercents = PALACE_STYLE(62, 10, 30, 20), //28%, 50% .battlePalaceFlavorText = B_MSG_GROWL_DEEPLY, .battlePalaceSmokescreen = PALACE_TARGET_WEAKER, @@ -514,7 +514,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_DEF, .backAnim = 0, .pokeBlockAnim = {ANIM_HASTY, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlHasty, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlAttackHighAttackLow, .battlePalacePercents = PALACE_STYLE(58, 37, 88, 6), //5%, 6% .battlePalaceFlavorText = B_MSG_GLINT_IN_EYE, .battlePalaceSmokescreen = PALACE_TARGET_WEAKER, @@ -526,7 +526,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPEED, .backAnim = 1, .pokeBlockAnim = {ANIM_SERIOUS, AFFINE_TURN_DOWN}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlSerious, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlSupportHighSupportLow, .battlePalacePercents = PALACE_STYLE(34, 11, 29, 11), //55%, 60% .battlePalaceFlavorText = B_MSG_EAGER_FOR_MORE, .battlePalaceSmokescreen = PALACE_TARGET_WEAKER, @@ -538,7 +538,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPATK, .backAnim = 0, .pokeBlockAnim = {ANIM_JOLLY, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlJolly, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlSupportHighDefenseLow, .battlePalacePercents = PALACE_STYLE(35, 5, 35, 60), //60%, 5% .battlePalaceFlavorText = B_MSG_GETTING_IN_POS, .battlePalaceSmokescreen = PALACE_TARGET_STRONGER, @@ -550,7 +550,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPDEF, .backAnim = 0, .pokeBlockAnim = {ANIM_NAIVE, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlDocileNaiveQuietQuirky, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlAttackHighAttackLow, .battlePalacePercents = PALACE_STYLE(56, 22, 56, 22), //22%, 22% .battlePalaceFlavorText = B_MSG_EAGER_FOR_MORE, .battlePalaceSmokescreen = PALACE_TARGET_RANDOM, @@ -562,7 +562,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_ATK, .backAnim = 2, .pokeBlockAnim = {ANIM_MODEST, AFFINE_TURN_DOWN_SLOW}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlModest, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlDefenseHighDefenseLow, .battlePalacePercents = PALACE_STYLE(35, 45, 34, 60), //20%, 6% .battlePalaceFlavorText = B_MSG_GETTING_IN_POS, .battlePalaceSmokescreen = PALACE_TARGET_WEAKER, @@ -574,7 +574,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_DEF, .backAnim = 2, .pokeBlockAnim = {ANIM_MILD, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlMild, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlDefenseHighSupportLow, .battlePalacePercents = PALACE_STYLE(44, 50, 34, 6), //6%, 60% .battlePalaceFlavorText = B_MSG_GROWL_DEEPLY, .battlePalaceSmokescreen = PALACE_TARGET_STRONGER, @@ -586,7 +586,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPEED, .backAnim = 2, .pokeBlockAnim = {ANIM_QUIET, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlDocileNaiveQuietQuirky, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlAttackHighAttackLow, .battlePalacePercents = PALACE_STYLE(56, 22, 56, 22), //22%, 22% .battlePalaceFlavorText = B_MSG_EAGER_FOR_MORE, .battlePalaceSmokescreen = PALACE_TARGET_WEAKER, @@ -598,7 +598,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPATK, .backAnim = 2, .pokeBlockAnim = {ANIM_BASHFUL, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlBashful, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlDefenseHighDefenseLow, .battlePalacePercents = PALACE_STYLE(30, 58, 30, 58), //12%, 12% .battlePalaceFlavorText = B_MSG_EAGER_FOR_MORE, .battlePalaceSmokescreen = PALACE_TARGET_WEAKER, @@ -610,7 +610,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPDEF, .backAnim = 1, .pokeBlockAnim = {ANIM_RASH, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlRash, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlSupportHighSupportLow, .battlePalacePercents = PALACE_STYLE(30, 13, 27, 6), //57%, 67% .battlePalaceFlavorText = B_MSG_GROWL_DEEPLY, .battlePalaceSmokescreen = PALACE_TARGET_STRONGER, @@ -622,7 +622,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_ATK, .backAnim = 1, .pokeBlockAnim = {ANIM_CALM, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlCalm, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlDefenseHighDefenseLow, .battlePalacePercents = PALACE_STYLE(40, 50, 25, 62), //10%, 13% .battlePalaceFlavorText = B_MSG_GETTING_IN_POS, .battlePalaceSmokescreen = PALACE_TARGET_STRONGER, @@ -634,7 +634,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_DEF, .backAnim = 2, .pokeBlockAnim = {ANIM_GENTLE, AFFINE_TURN_DOWN_SLIGHT}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlGentle, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlDefenseHighAttackLow, .battlePalacePercents = PALACE_STYLE(18, 70, 90, 5), //12%, 5% .battlePalaceFlavorText = B_MSG_GLINT_IN_EYE, .battlePalaceSmokescreen = PALACE_TARGET_STRONGER, @@ -646,7 +646,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPEED, .backAnim = 1, .pokeBlockAnim = {ANIM_SASSY, AFFINE_TURN_UP_HIGH}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlSassy, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlAttackHighSupportLow, .battlePalacePercents = PALACE_STYLE(88, 6, 22, 20), //6%, 58% .battlePalaceFlavorText = B_MSG_GROWL_DEEPLY, .battlePalaceSmokescreen = PALACE_TARGET_WEAKER, @@ -658,7 +658,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPATK, .backAnim = 2, .pokeBlockAnim = {ANIM_CAREFUL, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlCareful, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlDefenseHighSupportLow, .battlePalacePercents = PALACE_STYLE(42, 50, 42, 5), //8%, 53% .battlePalaceFlavorText = B_MSG_GROWL_DEEPLY, .battlePalaceSmokescreen = PALACE_TARGET_WEAKER, @@ -670,7 +670,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = .statDown = STAT_SPDEF, .backAnim = 1, .pokeBlockAnim = {ANIM_QUIRKY, AFFINE_NONE}, - .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlDocileNaiveQuietQuirky, + .natureGirlMessage = BattleFrontier_Lounge5_Text_NatureGirlAttackHighAttackLow, .battlePalacePercents = PALACE_STYLE(56, 22, 56, 22), //22%, 22% .battlePalaceFlavorText = B_MSG_EAGER_FOR_MORE, .battlePalaceSmokescreen = PALACE_TARGET_STRONGER, From 6f541bb4490021907d43d3bface6edb5ebcbe45f Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Sat, 8 Feb 2025 16:42:25 -0500 Subject: [PATCH 26/28] Fix Liquid Ooze + Leech Seed Test (#6217) Co-authored-by: ghoulslash --- data/battle_scripts_1.s | 4 ++++ test/battle/ability/liquid_ooze.c | 11 +++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 7f796eb0be..8f739ac707 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -5952,10 +5952,14 @@ BattleScript_SafeguardEnds:: waitmessage B_WAIT_TIME_LONG end2 +@ Leech Seed end turn effect +@ attacker is the victim +@ target is the leech seeder (recieving HP) BattleScript_LeechSeedTurnDrainLiquidOoze:: call BattleScript_LeechSeedTurnDrain copybyte gBattlerAbility, gBattlerAttacker call BattleScript_AbilityPopUp + copybyte gBattlerAttacker, gBattlerTarget @ needed to get liquid ooze message correct goto BattleScript_LeechSeedTurnDrainGainHp BattleScript_LeechSeedTurnDrainHealBlock:: diff --git a/test/battle/ability/liquid_ooze.c b/test/battle/ability/liquid_ooze.c index 6398a0be2b..fb20522937 100644 --- a/test/battle/ability/liquid_ooze.c +++ b/test/battle/ability/liquid_ooze.c @@ -108,9 +108,11 @@ SINGLE_BATTLE_TEST("Liquid Ooze causes Strength Sap users to lose HP instead of } } -SINGLE_BATTLE_TEST("Liquid Ooze causes leech seedee to faint before seeder") +/* * https://bulbapedia.bulbagarden.net/wiki/Liquid_Ooze_(Ability)#In_battle: + * If the recipient of Leech Seed's effect were to faint due to Liquid Ooze on the same turn as the victim of Leech Seed, then the victim faints before the recipient. This means that the victim's team loses the battle if both teams had their final Pokémon sent out. + */ +SINGLE_BATTLE_TEST("Liquid Ooze causes leech seed victim to faint before seeder") { - KNOWN_FAILING; // Message fails u16 ability; PARAMETRIZE { ability = ABILITY_CLEAR_BODY; } PARAMETRIZE { ability = ABILITY_LIQUID_OOZE; } @@ -120,16 +122,17 @@ SINGLE_BATTLE_TEST("Liquid Ooze causes leech seedee to faint before seeder") } WHEN { TURN { MOVE(player, MOVE_LEECH_SEED); } } SCENE { + // Player seeds opponent MESSAGE("Bulbasaur used Leech Seed!"); // Drain at end of turn ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_LEECH_SEED_DRAIN, opponent); if (ability != ABILITY_LIQUID_OOZE) { - MESSAGE("The opposing Tentacool's health is sapped by Leech Seed!"); MESSAGE("The opposing Tentacool fainted!"); + MESSAGE("The opposing Tentacool's health is sapped by Leech Seed!"); } else { + MESSAGE("The opposing Tentacool fainted!"); ABILITY_POPUP(opponent, ABILITY_LIQUID_OOZE); MESSAGE("Bulbasaur sucked up the liquid ooze!"); - MESSAGE("The opposing Tentacool fainted!"); MESSAGE("Bulbasaur fainted!"); } } From 43bce41796f8298aea53813c21ebf144f371dcae Mon Sep 17 00:00:00 2001 From: Zatsu <118256341+fakuzatsu@users.noreply.github.com> Date: Sat, 8 Feb 2025 23:54:58 +0000 Subject: [PATCH 27/28] Add caught mon to full party by sending a different mon to the Box (#6058) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- asm/macros/battle_script.inc | 6 +- data/battle_scripts_2.s | 6 +- data/text/pc_transfer.inc | 6 + include/config/battle.h | 2 + include/constants/battle_string_ids.h | 14 +- include/constants/party_menu.h | 4 +- include/naming_screen.h | 2 + include/strings.h | 5 + src/battle_controller_player.c | 3 +- src/battle_message.c | 11 +- src/battle_script_commands.c | 178 +++++++++++++++++++++----- src/data/party_menu.h | 1 + src/naming_screen.c | 22 ++-- src/party_menu.c | 74 ++++++++++- src/strings.c | 2 + 15 files changed, 268 insertions(+), 68 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index ae47761d7c..63b1be9f76 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1277,8 +1277,9 @@ .byte 0xef .endm - .macro givecaughtmon + .macro givecaughtmon passInstr:req .byte 0xf0 + .4byte \passInstr .endm .macro trysetcaughtmondexflags failInstr:req @@ -1290,9 +1291,8 @@ .byte 0xf2 .endm - .macro trygivecaughtmonnick successInstr:req + .macro trygivecaughtmonnick .byte 0xf3 - .4byte \successInstr .endm .macro subattackerhpbydmg diff --git a/data/battle_scripts_2.s b/data/battle_scripts_2.s index bd87dbf3ef..572d076fab 100644 --- a/data/battle_scripts_2.s +++ b/data/battle_scripts_2.s @@ -195,13 +195,13 @@ BattleScript_TryNicknameCaughtMon:: printstring STRINGID_GIVENICKNAMECAPTURED waitstate setbyte gBattleCommunication, 0 - trygivecaughtmonnick BattleScript_GiveCaughtMonEnd - givecaughtmon + trygivecaughtmonnick + givecaughtmon BattleScript_SuccessBallThrowEnd printfromtable gCaughtMonStringIds waitmessage B_WAIT_TIME_LONG goto BattleScript_SuccessBallThrowEnd BattleScript_GiveCaughtMonEnd:: - givecaughtmon + givecaughtmon BattleScript_SuccessBallThrowEnd BattleScript_SuccessBallThrowEnd:: setbyte gBattleOutcome, B_OUTCOME_CAUGHT finishturn diff --git a/data/text/pc_transfer.inc b/data/text/pc_transfer.inc index 3fa255340b..b6995d1111 100644 --- a/data/text/pc_transfer.inc +++ b/data/text/pc_transfer.inc @@ -22,6 +22,12 @@ gText_PkmnTransferredLanettesPCBoxFull:: .string "{STR_VAR_2} was transferred to\n" .string "BOX “{STR_VAR_1}.”$" +gText_PkmnSentToPCAfterCatch:: + .string "{STR_VAR_2} was sent to\n" + .string "{B_PC_CREATOR_NAME} PC.\p" + .string "It was placed in \n" + .string "BOX “{STR_VAR_1}”.$" + gText_NoMoreRoomForPokemon:: .string "There's no more room for POKéMON!\p" .string "The POKéMON BOXES are full and\n" diff --git a/include/config/battle.h b/include/config/battle.h index 898fc4416b..f7b2825b0b 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -250,6 +250,8 @@ #define B_LAST_USED_BALL TRUE // If TRUE, the "last used ball" feature from Gen 7 will be implemented #define B_LAST_USED_BALL_BUTTON R_BUTTON // If last used ball is implemented, this button (or button combo) will trigger throwing the last used ball. #define B_LAST_USED_BALL_CYCLE TRUE // If TRUE, then holding B_LAST_USED_BALL_BUTTON while pressing the D-Pad cycles through the balls +#define B_CATCH_SWAP_INTO_PARTY GEN_LATEST // In Gen 7+, the option to swap the caught wild mon to the party will appear, allowing you to send a different mon to the box. +#define B_CATCH_SWAP_CHECK_HMS TRUE // If TRUE, the catch swap feature above will prevent returning mons to the box if they know HMs. // Other settings #define B_DOUBLE_WILD_CHANCE 0 // % chance of encountering two Pokémon in a Wild Encounter. diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index a70ccbe5e8..3ba637c690 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -727,8 +727,10 @@ #define STRINGID_SUPEREFFECTIVETWOFOES 725 #define STRINGID_NOTVERYEFFECTIVETWOFOES 726 #define STRINGID_ITDOESNTAFFECTTWOFOES 727 +#define STRINGID_SENDCAUGHTMONPARTYORBOX 728 +#define STRINGID_PKMNSENTTOPCAFTERCATCH 729 -#define BATTLESTRINGS_COUNT 728 +#define BATTLESTRINGS_COUNT 730 // This is the string id that gBattleStringsTable starts with. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, @@ -892,10 +894,12 @@ #define B_MSG_WEAKEN_FIRE 1 // gCaughtMonStringIds -#define B_MSG_SENT_SOMEONES_PC 0 -#define B_MSG_SENT_LANETTES_PC 1 -#define B_MSG_SOMEONES_BOX_FULL 2 -#define B_MSG_LANETTES_BOX_FULL 3 +#define B_MSG_NO_MESSSAGE_SKIP 0 +#define B_MSG_SENT_SOMEONES_PC 1 +#define B_MSG_SENT_LANETTES_PC 2 +#define B_MSG_SOMEONES_BOX_FULL 3 +#define B_MSG_LANETTES_BOX_FULL 4 +#define B_MSG_SWAPPED_INTO_PARTY 5 // gPrimalWeatherBlocksStringIds #define B_MSG_PRIMAL_WEATHER_FIZZLED_BY_RAIN 0 diff --git a/include/constants/party_menu.h b/include/constants/party_menu.h index e997dd566d..76360d8ee8 100644 --- a/include/constants/party_menu.h +++ b/include/constants/party_menu.h @@ -48,7 +48,7 @@ #define PARTY_ACTION_CHOOSE_AND_CLOSE 11 #define PARTY_ACTION_MOVE_TUTOR 12 #define PARTY_ACTION_MINIGAME 13 -#define PARTY_ACTION_REUSABLE_ITEM 14 // Unused. The only reusable items are handled separately +#define PARTY_ACTION_SEND_MON_TO_BOX 14 #define PARTY_ACTION_CHOOSE_FAINTED_MON 15 // IDs for DisplayPartyMenuStdMessage, to display the message at the bottom of the party menu @@ -82,6 +82,8 @@ #define PARTY_MSG_WHICH_APPLIANCE 27 #define PARTY_MSG_CHOOSE_SECOND_FUSION 28 #define PARTY_MSG_NO_POKEMON 29 +#define PARTY_MSG_CHOOSE_MON_FOR_BOX 30 + #define PARTY_MSG_NONE 127 // IDs for DisplayPartyPokemonDescriptionText, to display a message in the party pokemon's box diff --git a/include/naming_screen.h b/include/naming_screen.h index a0cd3cf74b..315868c3ab 100644 --- a/include/naming_screen.h +++ b/include/naming_screen.h @@ -12,6 +12,8 @@ enum { NAMING_SCREEN_CODE, }; +extern void BattleMainCB2(void); + void DoNamingScreen(u8 templateNum, u8 *destBuffer, u16 monSpecies, u16 monGender, u32 monPersonality, MainCallback returnCallback); #endif // GUARD_NAMING_SCREEN_H diff --git a/include/strings.h b/include/strings.h index 9eb678d23e..588d513d4b 100644 --- a/include/strings.h +++ b/include/strings.h @@ -1538,6 +1538,7 @@ extern const u8 gText_PkmnTransferredSomeonesPCBoxFull[]; extern const u8 gText_PkmnTransferredLanettesPCBoxFull[]; extern const u8 gText_PkmnTransferredSomeonesPC[]; extern const u8 gText_PkmnTransferredLanettesPC[]; +extern const u8 gText_PkmnSentToPCAfterCatch[]; // hall of fame extern const u8 gText_LeagueChamp[]; @@ -1571,6 +1572,7 @@ extern const u8 gDaycareText_PlayOther[]; extern const u8 gText_ChoosePokemon[]; extern const u8 gText_ChoosePokemonCancel[]; extern const u8 gText_ChoosePokemonConfirm[]; +extern const u8 gText_SendWhichMonToPC[]; extern const u8 gText_MoveToWhere[]; extern const u8 gText_TeachWhichPokemon[]; extern const u8 gText_UseOnWhichPokemon[]; @@ -2416,4 +2418,7 @@ extern const u8 gText_PlayerScurriedBackHome[]; extern const u8 gText_Relearn[]; // move relearner from summary screen extern const u8 gText_Rename[]; // change nickname from summary screen +// Switch Caught Mon into Party +extern const u8 gText_CannotSendMonToBoxHM[]; + #endif // GUARD_STRINGS_H diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index 13b9213b8e..78b7852374 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -2174,7 +2174,8 @@ static void PlayerHandleChoosePokemon(u32 battler) gBattlePartyCurrentOrder[i] = gBattleResources->bufferA[battler][4 + i]; if (gBattleTypeFlags & BATTLE_TYPE_ARENA && (gBattleResources->bufferA[battler][1] & 0xF) != PARTY_ACTION_CANT_SWITCH - && (gBattleResources->bufferA[battler][1] & 0xF) != PARTY_ACTION_CHOOSE_FAINTED_MON) + && (gBattleResources->bufferA[battler][1] & 0xF) != PARTY_ACTION_CHOOSE_FAINTED_MON + && (gBattleResources->bufferA[battler][1] & 0xF) != PARTY_ACTION_SEND_MON_TO_BOX) { BtlController_EmitChosenMonReturnValue(battler, BUFFER_B, gBattlerPartyIndexes[battler] + 1, gBattlePartyCurrentOrder); PlayerBufferExecCompleted(battler); diff --git a/src/battle_message.c b/src/battle_message.c index 1538aec1c9..c463474d40 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -891,6 +891,8 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = [STRINGID_SUPEREFFECTIVETWOFOES] = COMPOUND_STRING("It's super effective on {B_DEF_NAME_WITH_PREFIX2} and {B_DEF_PARTNER_NAME}!"), [STRINGID_NOTVERYEFFECTIVETWOFOES] = COMPOUND_STRING("It's not very effective on {B_DEF_NAME_WITH_PREFIX2} and {B_DEF_PARTNER_NAME}!"), [STRINGID_ITDOESNTAFFECTTWOFOES] = COMPOUND_STRING("It doesn't affect {B_DEF_NAME_WITH_PREFIX2} and {B_DEF_PARTNER_NAME}…"), + [STRINGID_SENDCAUGHTMONPARTYORBOX] = COMPOUND_STRING("Add {B_DEF_NAME} to your party?"), + [STRINGID_PKMNSENTTOPCAFTERCATCH] = gText_PkmnSentToPCAfterCatch, }; const u16 gTrainerUsedItemStringIds[] = @@ -1361,10 +1363,11 @@ const u16 gFlashFireStringIds[] = const u16 gCaughtMonStringIds[] = { - [B_MSG_SENT_SOMEONES_PC] = STRINGID_PKMNTRANSFERREDSOMEONESPC, - [B_MSG_SENT_LANETTES_PC] = STRINGID_PKMNTRANSFERREDLANETTESPC, - [B_MSG_SOMEONES_BOX_FULL] = STRINGID_PKMNBOXSOMEONESPCFULL, - [B_MSG_LANETTES_BOX_FULL] = STRINGID_PKMNBOXLANETTESPCFULL, + [B_MSG_SENT_SOMEONES_PC] = STRINGID_PKMNTRANSFERREDSOMEONESPC, + [B_MSG_SENT_LANETTES_PC] = STRINGID_PKMNTRANSFERREDLANETTESPC, + [B_MSG_SOMEONES_BOX_FULL] = STRINGID_PKMNBOXSOMEONESPCFULL, + [B_MSG_LANETTES_BOX_FULL] = STRINGID_PKMNBOXLANETTESPCFULL, + [B_MSG_SWAPPED_INTO_PARTY] = STRINGID_PKMNSENTTOPCAFTERCATCH, }; const u16 gRoomsStringIds[] = diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index b0fe3281bf..6efa734d10 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -302,6 +302,16 @@ static const u16 sTrappingMoves[NUM_TRAPPING_MOVES] = static const u16 sWhiteOutBadgeMoney[9] = { 8, 16, 24, 36, 48, 64, 80, 100, 120 }; +enum GiveCaughtMonStates +{ + GIVECAUGHTMON_CHECK_PARTY_SIZE, + GIVECAUGHTMON_ASK_ADD_TO_PARTY, + GIVECAUGHTMON_HANDLE_INPUT, + GIVECAUGHTMON_DO_CHOOSE_MON, + GIVECAUGHTMON_HANDLE_CHOSEN_MON, + GIVECAUGHTMON_GIVE_AND_SHOW_MSG, +}; + #define STAT_CHANGE_WORKED 0 #define STAT_CHANGE_DIDNT_WORK 1 @@ -16111,41 +16121,145 @@ static void Cmd_handleballthrow(void) static void Cmd_givecaughtmon(void) { - CMD_ARGS(); + CMD_ARGS(const u8 *passInstr); + enum GiveCaughtMonStates state = gBattleCommunication[MULTIUSE_STATE]; - if (B_RESTORE_HELD_BATTLE_ITEMS >= GEN_9) + switch (state) { - u16 lostItem = gBattleStruct->itemLost[B_SIDE_OPPONENT][gBattlerPartyIndexes[GetCatchingBattler()]].originalItem; - if (lostItem != ITEM_NONE && ItemId_GetPocket(lostItem) != POCKET_BERRIES) - SetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_HELD_ITEM, &lostItem); // Restore non-berry items - } - - if (GiveMonToPlayer(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]]) != MON_GIVEN_TO_PARTY) - { - if (!ShouldShowBoxWasFullMessage()) + case GIVECAUGHTMON_CHECK_PARTY_SIZE: + if (CalculatePlayerPartyCount() == PARTY_SIZE && B_CATCH_SWAP_INTO_PARTY >= GEN_7) { - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SENT_SOMEONES_PC; - StringCopy(gStringVar1, GetBoxNamePtr(VarGet(VAR_PC_BOX_TO_SEND_MON))); - GetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_NICKNAME, gStringVar2); + PrepareStringBattle(STRINGID_SENDCAUGHTMONPARTYORBOX, gBattlerAttacker); + gBattleCommunication[MSG_DISPLAY] = 1; + gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_ASK_ADD_TO_PARTY; } else { - StringCopy(gStringVar1, GetBoxNamePtr(VarGet(VAR_PC_BOX_TO_SEND_MON))); // box the mon was sent to - GetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_NICKNAME, gStringVar2); - StringCopy(gStringVar3, GetBoxNamePtr(GetPCBoxToSendMon())); //box the mon was going to be sent to - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SOMEONES_BOX_FULL; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_NO_MESSSAGE_SKIP; + gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_GIVE_AND_SHOW_MSG; + } + break; + case GIVECAUGHTMON_ASK_ADD_TO_PARTY: + HandleBattleWindow(YESNOBOX_X_Y, 0); + BattlePutTextOnWindow(gText_BattleYesNoChoice, B_WIN_YESNO); + gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_HANDLE_INPUT; + gBattleCommunication[CURSOR_POSITION] = 0; + BattleCreateYesNoCursorAt(0); + break; + case GIVECAUGHTMON_HANDLE_INPUT: + if (JOY_NEW(DPAD_UP) && gBattleCommunication[CURSOR_POSITION] != 0) + { + PlaySE(SE_SELECT); + BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); + gBattleCommunication[CURSOR_POSITION] = 0; + BattleCreateYesNoCursorAt(0); + } + if (JOY_NEW(DPAD_DOWN) && gBattleCommunication[CURSOR_POSITION] == 0) + { + PlaySE(SE_SELECT); + BattleDestroyYesNoCursorAt(gBattleCommunication[CURSOR_POSITION]); + gBattleCommunication[CURSOR_POSITION] = 1; + BattleCreateYesNoCursorAt(1); + } + if (JOY_NEW(A_BUTTON)) + { + PlaySE(SE_SELECT); + if (gBattleCommunication[CURSOR_POSITION] == 0) + { + gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_DO_CHOOSE_MON; + } + else + { + gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_GIVE_AND_SHOW_MSG; + } + } + else if (JOY_NEW(B_BUTTON)) + { + PlaySE(SE_SELECT); + gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_GIVE_AND_SHOW_MSG; + } + break; + case GIVECAUGHTMON_DO_CHOOSE_MON: + if (!gPaletteFade.active) + { + BtlController_EmitChoosePokemon(gBattlerAttacker, BUFFER_A, PARTY_ACTION_SEND_MON_TO_BOX, PARTY_SIZE, ABILITY_NONE, gBattleStruct->battlerPartyOrders[gBattlerAttacker]); + MarkBattlerForControllerExec(gBattlerAttacker); + gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_HANDLE_CHOSEN_MON; + } + break; + case GIVECAUGHTMON_HANDLE_CHOSEN_MON: + if (gSelectedMonPartyId != PARTY_SIZE) + { + if (gSelectedMonPartyId > PARTY_SIZE) + { + // Choosing Pokemon was cancelled + gSelectedMonPartyId = PARTY_SIZE; + gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_GIVE_AND_SHOW_MSG; + } + else + { + // Mon chosen, try to put it in the PC + if (CopyMonToPC(&gPlayerParty[gSelectedMonPartyId]) == MON_GIVEN_TO_PC) + { + GetMonNickname(&gPlayerParty[gSelectedMonPartyId], gStringVar2); + StringCopy(gStringVar1, GetBoxNamePtr(GetPCBoxToSendMon())); + ZeroMonData(&gPlayerParty[gSelectedMonPartyId]); + gBattleStruct->itemLost[B_SIDE_PLAYER][gSelectedMonPartyId].originalItem = ITEM_NONE; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWAPPED_INTO_PARTY; + gSelectedMonPartyId = PARTY_SIZE; + gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_GIVE_AND_SHOW_MSG; + } + else + { + gSelectedMonPartyId = PARTY_SIZE; + gBattleCommunication[MULTIUSE_STATE] = GIVECAUGHTMON_GIVE_AND_SHOW_MSG; + } + } + } + break; + case GIVECAUGHTMON_GIVE_AND_SHOW_MSG: + if (B_RESTORE_HELD_BATTLE_ITEMS >= GEN_9) + { + u16 lostItem = gBattleStruct->itemLost[B_SIDE_OPPONENT][gBattlerPartyIndexes[GetCatchingBattler()]].originalItem; + if (lostItem != ITEM_NONE && ItemId_GetPocket(lostItem) != POCKET_BERRIES) + SetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_HELD_ITEM, &lostItem); // Restore non-berry items } - // Change to B_MSG_SENT_LANETTES_PC or B_MSG_LANETTES_BOX_FULL - if (FlagGet(FLAG_SYS_PC_LANETTE)) - gBattleCommunication[MULTISTRING_CHOOSER]++; + if (GiveMonToPlayer(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]]) != MON_GIVEN_TO_PARTY + && gBattleCommunication[MULTISTRING_CHOOSER] != B_MSG_SWAPPED_INTO_PARTY) + { + if (!ShouldShowBoxWasFullMessage()) + { + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SENT_SOMEONES_PC; + StringCopy(gStringVar1, GetBoxNamePtr(VarGet(VAR_PC_BOX_TO_SEND_MON))); + GetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_NICKNAME, gStringVar2); + } + else + { + StringCopy(gStringVar1, GetBoxNamePtr(VarGet(VAR_PC_BOX_TO_SEND_MON))); // box the mon was sent to + GetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_NICKNAME, gStringVar2); + StringCopy(gStringVar3, GetBoxNamePtr(GetPCBoxToSendMon())); //box the mon was going to be sent to + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SOMEONES_BOX_FULL; + } + + // Change to B_MSG_SENT_LANETTES_PC or B_MSG_LANETTES_BOX_FULL + if (FlagGet(FLAG_SYS_PC_LANETTE)) + gBattleCommunication[MULTISTRING_CHOOSER]++; + } + + gBattleResults.caughtMonSpecies = GetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_SPECIES, NULL); + GetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_NICKNAME, gBattleResults.caughtMonNick); + gBattleResults.caughtMonBall = GetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_POKEBALL, NULL); + + gSelectedMonPartyId = PARTY_SIZE; + gBattleCommunication[MULTIUSE_STATE] = 0; + + if (gBattleCommunication[MULTISTRING_CHOOSER] == B_MSG_NO_MESSSAGE_SKIP) + gBattlescriptCurrInstr = cmd->passInstr; + else + gBattlescriptCurrInstr = cmd->nextInstr; + break; } - - gBattleResults.caughtMonSpecies = GetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_SPECIES, NULL); - GetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_NICKNAME, gBattleResults.caughtMonNick); - gBattleResults.caughtMonBall = GetMonData(&gEnemyParty[gBattlerPartyIndexes[GetCatchingBattler()]], MON_DATA_POKEBALL, NULL); - - gBattlescriptCurrInstr = cmd->nextInstr; } static void Cmd_trysetcaughtmondexflags(void) @@ -16288,7 +16402,7 @@ void BattleDestroyYesNoCursorAt(u8 cursorPosition) static void Cmd_trygivecaughtmonnick(void) { - CMD_ARGS(const u8 *successInstr); + CMD_ARGS(); switch (gBattleCommunication[MULTIUSE_STATE]) { @@ -16343,7 +16457,7 @@ static void Cmd_trygivecaughtmonnick(void) GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattlerTarget]], MON_DATA_SPECIES), GetMonGender(&gEnemyParty[gBattlerPartyIndexes[gBattlerTarget]]), GetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattlerTarget]], MON_DATA_PERSONALITY, NULL), - BattleMainCB2); + ReshowBattleScreenAfterMenu); gBattleCommunication[MULTIUSE_STATE]++; } @@ -16352,14 +16466,12 @@ static void Cmd_trygivecaughtmonnick(void) if (gMain.callback2 == BattleMainCB2 && !gPaletteFade.active) { SetMonData(&gEnemyParty[gBattlerPartyIndexes[gBattlerTarget]], MON_DATA_NICKNAME, gBattleStruct->caughtMonNick); - gBattlescriptCurrInstr = cmd->successInstr; + gBattleCommunication[MULTIUSE_STATE]++; } break; case 4: - if (CalculatePlayerPartyCount() == PARTY_SIZE) - gBattlescriptCurrInstr = cmd->nextInstr; - else - gBattlescriptCurrInstr = cmd->successInstr; + gBattleCommunication[MULTIUSE_STATE] = 0; + gBattlescriptCurrInstr = cmd->nextInstr; break; } } diff --git a/src/data/party_menu.h b/src/data/party_menu.h index 168387dbf4..ec7026fcb5 100644 --- a/src/data/party_menu.h +++ b/src/data/party_menu.h @@ -660,6 +660,7 @@ static const u8 *const sActionStringTable[] = [PARTY_MSG_WHICH_APPLIANCE] = gText_WhichAppliance, [PARTY_MSG_CHOOSE_SECOND_FUSION] = gText_NextFusionMon, [PARTY_MSG_NO_POKEMON] = COMPOUND_STRING("You have no POKéMON."), + [PARTY_MSG_CHOOSE_MON_FOR_BOX] = gText_SendWhichMonToPC, }; static const u8 *const sDescriptionStringTable[] = diff --git a/src/naming_screen.c b/src/naming_screen.c index f5f780ed1c..e1307aa658 100644 --- a/src/naming_screen.c +++ b/src/naming_screen.c @@ -672,18 +672,8 @@ static bool8 MainState_PressedOKButton(void) SetInputState(INPUT_STATE_DISABLED); SetCursorFlashing(FALSE); TryStartButtonFlash(BUTTON_COUNT, FALSE, TRUE); - if (sNamingScreen->templateNum == NAMING_SCREEN_CAUGHT_MON - && CalculatePlayerPartyCount() >= PARTY_SIZE) - { - DisplaySentToPCMessage(); - sNamingScreen->state = STATE_WAIT_SENT_TO_PC_MESSAGE; - return FALSE; - } - else - { - sNamingScreen->state = STATE_FADE_OUT; - return TRUE; - } + sNamingScreen->state = STATE_FADE_OUT; + return TRUE; } static bool8 MainState_FadeOut(void) @@ -699,7 +689,11 @@ static bool8 MainState_Exit(void) { if (sNamingScreen->templateNum == NAMING_SCREEN_PLAYER) SeedRngAndSetTrainerId(); - SetMainCallback2(sNamingScreen->returnCallback); + if (sNamingScreen->templateNum == NAMING_SCREEN_CAUGHT_MON + && CalculatePlayerPartyCount() < PARTY_SIZE) + SetMainCallback2(BattleMainCB2); + else + SetMainCallback2(sNamingScreen->returnCallback); DestroyTask(FindTaskIdByFunc(Task_NamingScreen)); FreeAllWindowBuffers(); FREE_AND_SET_NULL(sNamingScreen); @@ -707,7 +701,7 @@ static bool8 MainState_Exit(void) return FALSE; } -static void DisplaySentToPCMessage(void) +static UNUSED void DisplaySentToPCMessage(void) { u8 stringToDisplay = 0; diff --git a/src/party_menu.c b/src/party_menu.c index bda62ba3d1..784f13acdd 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -174,6 +174,14 @@ enum { #define MENU_DIR_RIGHT 2 #define MENU_DIR_LEFT -2 +#define HM_MOVES_END 0xFFFF + +static const u16 sHMMoves[] = +{ + MOVE_CUT, MOVE_FLY, MOVE_SURF, MOVE_STRENGTH, MOVE_FLASH, + MOVE_ROCK_SMASH, MOVE_WATERFALL, MOVE_DIVE, HM_MOVES_END +}; + enum { CAN_LEARN_MOVE, CANNOT_LEARN_MOVE, @@ -320,6 +328,7 @@ static void HandleChooseMonSelection(u8, s8 *); static u16 PartyMenuButtonHandler(s8 *); static s8 *GetCurrentPartySlotPtr(void); static bool8 IsSelectedMonNotEgg(u8 *); +static bool8 DoesSelectedMonKnowHM(u8 *); static void PartyMenuRemoveWindow(u8 *); static void CB2_SetUpExitToBattleScreen(void); static void Task_ClosePartyMenuAfterText(u8); @@ -1524,6 +1533,30 @@ static void HandleChooseMonSelection(u8 taskId, s8 *slotPtr) } break; } + case PARTY_ACTION_SEND_MON_TO_BOX: + { + u8 partyId = GetPartyIdFromBattleSlot((u8)*slotPtr); + if (partyId == 0 || ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && partyId == 2) + || ((gBattleTypeFlags & BATTLE_TYPE_MULTI) && partyId >= (PARTY_SIZE / 2))) + { + // Can't select if mon is currently on the field, or doesn't belong to you + PlaySE(SE_FAILURE); + } + else if (DoesSelectedMonKnowHM((u8 *)slotPtr)) + { + PlaySE(SE_FAILURE); + DisplayPartyMenuMessage(gText_CannotSendMonToBoxHM, FALSE); + ScheduleBgCopyTilemapToVram(2); + gTasks[taskId].func = Task_ReturnToChooseMonAfterText; + } + else + { + PlaySE(SE_SELECT); + gSelectedMonPartyId = partyId; + Task_ClosePartyMenu(taskId); + } + break; + } default: case PARTY_ACTION_ABILITY_PREVENTS: case PARTY_ACTION_SWITCHING: @@ -1544,6 +1577,25 @@ static bool8 IsSelectedMonNotEgg(u8 *slotPtr) return TRUE; } +static bool8 DoesSelectedMonKnowHM(u8 *slotPtr) +{ + if (B_CATCH_SWAP_CHECK_HMS == FALSE) + return FALSE; + + for (u32 i = 0; i < MAX_MON_MOVES; i++) + { + u32 j = 0; + u16 move = GetMonData(&gPlayerParty[*slotPtr], MON_DATA_MOVE1 + i); + + while (sHMMoves[j] != HM_MOVES_END) + { + if (sHMMoves[j++] == move) + return TRUE; + } + } + return FALSE; +} + static void HandleChooseMonCancel(u8 taskId, s8 *slotPtr) { switch (gPartyMenu.action) @@ -1561,6 +1613,11 @@ static void HandleChooseMonCancel(u8 taskId, s8 *slotPtr) PlaySE(SE_SELECT); CancelParticipationPrompt(taskId); break; + case PARTY_ACTION_SEND_MON_TO_BOX: + PlaySE(SE_SELECT); + gSelectedMonPartyId = PARTY_SIZE + 1; + Task_ClosePartyMenu(taskId); + break; default: PlaySE(SE_SELECT); if (DisplayCancelChooseMonYesNo(taskId) != TRUE) @@ -1924,7 +1981,10 @@ static void Task_ReturnToChooseMonAfterText(u8 taskId) } else { - DisplayPartyMenuStdMessage(PARTY_MSG_CHOOSE_MON); + if (gPartyMenu.action == PARTY_ACTION_SEND_MON_TO_BOX) + DisplayPartyMenuStdMessage(PARTY_MSG_CHOOSE_MON_FOR_BOX); + else + DisplayPartyMenuStdMessage(PARTY_MSG_CHOOSE_MON); gTasks[taskId].func = Task_HandleChooseMonInput; } } @@ -4665,8 +4725,7 @@ void ItemUseCB_Medicine(u8 taskId, TaskFunc task) if (!IsItemFlute(item)) { PlaySE(SE_USE_ITEM); - if (gPartyMenu.action != PARTY_ACTION_REUSABLE_ITEM) - RemoveBagItem(item, 1); + RemoveBagItem(item, 1); } else { @@ -7124,7 +7183,14 @@ static u8 GetPartyLayoutFromBattleType(void) void OpenPartyMenuInBattle(u8 partyAction) { - InitPartyMenu(PARTY_MENU_TYPE_IN_BATTLE, GetPartyLayoutFromBattleType(), partyAction, FALSE, PARTY_MSG_CHOOSE_MON, Task_HandleChooseMonInput, CB2_SetUpReshowBattleScreenAfterMenu); + u8 partyMessage; + + if (partyAction == PARTY_ACTION_SEND_MON_TO_BOX) + partyMessage = PARTY_MSG_CHOOSE_MON_FOR_BOX; + else + partyMessage = PARTY_MSG_CHOOSE_MON; + + InitPartyMenu(PARTY_MENU_TYPE_IN_BATTLE, GetPartyLayoutFromBattleType(), partyAction, FALSE, partyMessage, Task_HandleChooseMonInput, CB2_SetUpReshowBattleScreenAfterMenu); ReshowBattleScreenDummy(); UpdatePartyToBattleOrder(); } diff --git a/src/strings.c b/src/strings.c index 37feea0da0..c112db4616 100644 --- a/src/strings.c +++ b/src/strings.c @@ -319,6 +319,7 @@ const u8 gText_CurrentIsTooFast[] = _("The current is much too fast!"); const u8 gText_DoWhatWithMail[] = _("Do what with the MAIL?"); const u8 gText_ChoosePokemonCancel[] = _("Choose POKéMON or CANCEL."); const u8 gText_ChoosePokemonConfirm[] = _("Choose POKéMON and confirm."); +const u8 gText_SendWhichMonToPC[] = _("Send which POKéMON to the PC?"); const u8 gText_EnjoyCycling[] = _("Let's enjoy cycling!"); const u8 gText_InUseAlready_PM[] = _("This is in use already."); const u8 gText_AlreadyHoldingOne[] = _("{STR_VAR_1} is already holding\none {STR_VAR_2}."); @@ -1293,3 +1294,4 @@ const u8 gText_AM[] = _("AM"); const u8 gText_PM[] = _("PM"); const u8 gText_Relearn[] = _("{START_BUTTON} RELEARN"); // future note: don't decap this, because it mimics the summary screen BG graphics which will not get decapped const u8 gText_Rename[] = _("RENAME"); +const u8 gText_CannotSendMonToBoxHM[] = _("Cannot send that mon to the box,\nbecause it knows a HM move.{PAUSE_UNTIL_PRESS}"); From a8c9857a56532103bbdda02ad34120d75b637916 Mon Sep 17 00:00:00 2001 From: psf <77138753+pkmnsnfrn@users.noreply.github.com> Date: Sun, 9 Feb 2025 04:06:22 -0800 Subject: [PATCH 28/28] Prevented fanfares from playing in headless mode (#6219) --- src/sound.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/sound.c b/src/sound.c index db59e7e73c..9a67f2c21b 100644 --- a/src/sound.c +++ b/src/sound.c @@ -8,6 +8,7 @@ #include "constants/cries.h" #include "constants/songs.h" #include "task.h" +#include "test_runner.h" struct Fanfare { @@ -237,6 +238,13 @@ bool8 IsFanfareTaskInactive(void) static void Task_Fanfare(u8 taskId) { + if (gTestRunnerHeadless) + { + DestroyTask(taskId); + sFanfareCounter = 0; + return; + } + if (sFanfareCounter) { sFanfareCounter--;