diff --git a/include/constants/regions.h b/include/constants/regions.h new file mode 100644 index 000000000000..3f7397ced6d5 --- /dev/null +++ b/include/constants/regions.h @@ -0,0 +1,21 @@ +#ifndef GUARD_CONSTANTS_REGIONS_H +#define GUARD_CONSTANTS_REGIONS_H + +// Core-series regions +enum Region +{ + REGION_NONE, + REGION_KANTO, + REGION_JOHTO, + REGION_HOENN, + REGION_SINNOH, + REGION_UNOVA, + REGION_KALOS, + REGION_ALOLA, + REGION_GALAR, + REGION_HISUI, + REGION_PALDEA, + REGIONS_COUNT, +}; + +#endif // GUARD_CONSTANTS_REGIONS_H diff --git a/include/daycare.h b/include/daycare.h index 4d5b470f8b3e..37a325d6556c 100644 --- a/include/daycare.h +++ b/include/daycare.h @@ -34,5 +34,6 @@ void ShowDaycareLevelMenu(void); void ChooseSendDaycareMon(void); u8 GetEggMovesBySpecies(u16 species, u16 *eggMoves); bool8 SpeciesCanLearnEggMove(u16 species, u16 move); +void StorePokemonInDaycare(struct Pokemon *mon, struct DaycareMon *daycareMon); #endif // GUARD_DAYCARE_H diff --git a/include/pokemon.h b/include/pokemon.h index 8aca6c58d589..1fcd45979dc2 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" @@ -910,5 +911,7 @@ const u8 *GetMoveAnimationScript(u16 moveId); void UpdateDaysPassedSinceFormChange(u16 days); void TrySetDayLimitToFormChange(struct Pokemon *mon); u32 CheckDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler); +u32 GetRegionalFormByRegion(u32 species, u32 region); +bool32 IsSpeciesForeignRegionalForm(u32 species, u32 currentRegion); #endif // GUARD_POKEMON_H diff --git a/include/regions.h b/include/regions.h new file mode 100644 index 000000000000..2f44896ea2b0 --- /dev/null +++ b/include/regions.h @@ -0,0 +1,12 @@ +#ifndef GUARD_REGIONS_H +#define GUARD_REGIONS_H + +#include "constants/regions.h" + +static inline u32 GetCurrentRegion(void) +{ + // TODO: Since there's no current multi-region support, we have this constant for the purposes of regional form comparisons. + return REGION_HOENN; +} + +#endif // GUARD_REGIONS_H diff --git a/src/daycare.c b/src/daycare.c index 4997f4efe913..49fce40c0a3f 100644 --- a/src/daycare.c +++ b/src/daycare.c @@ -21,6 +21,7 @@ #include "list_menu.h" #include "overworld.h" #include "item.h" +#include "regions.h" #include "constants/form_change_types.h" #include "constants/items.h" #include "constants/hold_effects.h" @@ -244,7 +245,7 @@ static void TransferEggMoves(void) } } -static void StorePokemonInDaycare(struct Pokemon *mon, struct DaycareMon *daycareMon) +void StorePokemonInDaycare(struct Pokemon *mon, struct DaycareMon *daycareMon) { if (MonHasMail(mon)) { @@ -979,9 +980,12 @@ STATIC_ASSERT(P_SCATTERBUG_LINE_FORM_BREED == SPECIES_SCATTERBUG_ICY_SNOW || (P_ static u16 DetermineEggSpeciesAndParentSlots(struct DayCare *daycare, u8 *parentSlots) { - u16 i; - u16 species[DAYCARE_MON_COUNT]; - u16 eggSpecies; + u32 i; + u32 species[DAYCARE_MON_COUNT]; + u32 eggSpecies, parentSpecies; + bool32 hasMotherEverstone, hasFatherEverstone, motherIsForeign, fatherIsForeign; + bool32 motherEggSpecies, fatherEggSpecies; + u32 currentRegion = GetCurrentRegion(); for (i = 0; i < DAYCARE_MON_COUNT; i++) { @@ -998,7 +1002,24 @@ static u16 DetermineEggSpeciesAndParentSlots(struct DayCare *daycare, u8 *parent } } - eggSpecies = GetEggSpecies(species[parentSlots[0]]); + motherEggSpecies = GetEggSpecies(species[parentSlots[0]]); + fatherEggSpecies = GetEggSpecies(species[parentSlots[1]]); + hasMotherEverstone = ItemId_GetHoldEffect(GetBoxMonData(&daycare->mons[parentSlots[0]].mon, MON_DATA_HELD_ITEM)) == HOLD_EFFECT_PREVENT_EVOLVE; + hasFatherEverstone = ItemId_GetHoldEffect(GetBoxMonData(&daycare->mons[parentSlots[1]].mon, MON_DATA_HELD_ITEM)) == HOLD_EFFECT_PREVENT_EVOLVE; + motherIsForeign = IsSpeciesForeignRegionalForm(motherEggSpecies, currentRegion); + fatherIsForeign = IsSpeciesForeignRegionalForm(fatherEggSpecies, currentRegion); + + if (hasMotherEverstone) + parentSpecies = motherEggSpecies; + else if (fatherIsForeign && hasFatherEverstone) + parentSpecies = fatherEggSpecies; + else if (motherIsForeign) + parentSpecies = GetRegionalFormByRegion(motherEggSpecies, currentRegion); + else + parentSpecies = motherEggSpecies; + + eggSpecies = GetEggSpecies(parentSpecies); + if (eggSpecies == SPECIES_NIDORAN_F && daycare->offspringPersonality & EGG_GENDER_MALE) eggSpecies = SPECIES_NIDORAN_M; else if (eggSpecies == SPECIES_ILLUMISE && daycare->offspringPersonality & EGG_GENDER_MALE) @@ -1043,6 +1064,9 @@ static void _GiveEggFromDaycare(struct DayCare *daycare) u8 parentSlots[DAYCARE_MON_COUNT] = {0}; bool8 isEgg; + if (GetDaycareCompatibilityScore(daycare) == PARENTS_INCOMPATIBLE) + return; + species = DetermineEggSpeciesAndParentSlots(daycare, parentSlots); if (P_INCENSE_BREEDING < GEN_9) AlterEggSpeciesWithIncenseItem(&species, daycare); diff --git a/src/pokemon.c b/src/pokemon.c index c60e95cc6af4..a74b20da7951 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -55,6 +55,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" @@ -6977,3 +6978,70 @@ u32 CheckDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler) return moveType; return gMovesInfo[move].type; } + +bool32 IsSpeciesRegionalForm(u32 species) +{ + return gSpeciesInfo[species].isAlolanForm + || gSpeciesInfo[species].isGalarianForm + || gSpeciesInfo[species].isHisuianForm + || gSpeciesInfo[species].isPaldeanForm; +} + +bool32 IsSpeciesRegionalFormFromRegion(u32 species, u32 region) +{ + switch (region) + { + case REGION_ALOLA: return gSpeciesInfo[species].isAlolanForm; + case REGION_GALAR: return gSpeciesInfo[species].isGalarianForm; + case REGION_HISUI: return gSpeciesInfo[species].isHisuianForm; + case REGION_PALDEA: return gSpeciesInfo[species].isPaldeanForm; + default: return FALSE; + } +} + +bool32 SpeciesHasRegionalForm(u32 species) +{ + u32 formId; + const u16 *formTable = GetSpeciesFormTable(species); + for (formId = 0; formTable != NULL && formTable[formId] != FORM_SPECIES_END; formId++) + { + if (IsSpeciesRegionalForm(formTable[formId])) + return TRUE; + } + return FALSE; +} + +u32 GetRegionalFormByRegion(u32 species, u32 region) +{ + u32 formId = 0; + u32 firstFoundSpecies = 0; + const u16 *formTable = GetSpeciesFormTable(species); + + if (formTable != NULL) + { + for (formId = 0; formTable[formId] != FORM_SPECIES_END; formId++) + { + if (firstFoundSpecies == 0) + firstFoundSpecies = formTable[formId]; + + if (IsSpeciesRegionalFormFromRegion(formTable[formId], region)) + return formTable[formId]; + } + if (firstFoundSpecies != 0) + return firstFoundSpecies; + } + return species; +} + +bool32 IsSpeciesForeignRegionalForm(u32 species, u32 currentRegion) +{ + u32 i; + for (i = 0; i < REGIONS_COUNT; i++) + { + if (currentRegion != i && IsSpeciesRegionalFormFromRegion(species, i)) + return TRUE; + else if (currentRegion == i && SpeciesHasRegionalForm(species) && !IsSpeciesRegionalFormFromRegion(species, i)) + return TRUE; + } + return FALSE; +} diff --git a/test/daycare.c b/test/daycare.c new file mode 100644 index 000000000000..1740cc22f5fb --- /dev/null +++ b/test/daycare.c @@ -0,0 +1,131 @@ +#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;); } + 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;); } + 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;); } + 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;); } + 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;); } + 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 offspring = 0, region = 0; + ASSUME(P_FAMILY_MEOWTH == TRUE); + ASSUME(P_ALOLAN_FORMS == TRUE); + ASSUME(P_GALARIAN_FORMS == TRUE); + + ZeroPlayerPartyMons(); + + region = GetCurrentRegion(); + if (region == REGION_ALOLA) { + PARAMETRIZE { offspring = SPECIES_MEOWTH_ALOLA; RUN_OVERWORLD_SCRIPT(givemon SPECIES_MEOWTH, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_ALOLA, 1, gender=MON_FEMALE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_ALOLA; RUN_OVERWORLD_SCRIPT(givemon SPECIES_MEOWTH, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_ALOLA, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_ALOLA; RUN_OVERWORLD_SCRIPT(givemon SPECIES_MEOWTH, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_GALAR, 1, gender=MON_FEMALE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_GALAR; RUN_OVERWORLD_SCRIPT(givemon SPECIES_MEOWTH, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_GALAR, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_ALOLA; RUN_OVERWORLD_SCRIPT(givemon SPECIES_DIGLETT, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_GALAR, 1, gender=MON_FEMALE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_GALAR; RUN_OVERWORLD_SCRIPT(givemon SPECIES_DIGLETT, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_GALAR, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_GALAR; RUN_OVERWORLD_SCRIPT(givemon SPECIES_PERRSERKER, 1, gender=MON_MALE, item=ITEM_EVERSTONE; givemon SPECIES_PERSIAN, 1, gender=MON_FEMALE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH; RUN_OVERWORLD_SCRIPT(givemon SPECIES_PERRSERKER, 1, gender=MON_MALE, item=ITEM_EVERSTONE; givemon SPECIES_PERSIAN, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH; RUN_OVERWORLD_SCRIPT(givemon SPECIES_PERSIAN_ALOLA, 1, gender=MON_MALE, item=ITEM_EVERSTONE; givemon SPECIES_PERSIAN, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + } else if (region == REGION_GALAR) { + PARAMETRIZE { offspring = SPECIES_MEOWTH_GALAR; RUN_OVERWORLD_SCRIPT(givemon SPECIES_MEOWTH, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_ALOLA, 1, gender=MON_FEMALE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_ALOLA; RUN_OVERWORLD_SCRIPT(givemon SPECIES_MEOWTH, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_ALOLA, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_GALAR; RUN_OVERWORLD_SCRIPT(givemon SPECIES_MEOWTH, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_GALAR, 1, gender=MON_FEMALE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_GALAR; RUN_OVERWORLD_SCRIPT(givemon SPECIES_MEOWTH, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_GALAR, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_GALAR; RUN_OVERWORLD_SCRIPT(givemon SPECIES_DIGLETT, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_GALAR, 1, gender=MON_FEMALE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_GALAR; RUN_OVERWORLD_SCRIPT(givemon SPECIES_DIGLETT, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_GALAR, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_GALAR; RUN_OVERWORLD_SCRIPT(givemon SPECIES_PERRSERKER, 1, gender=MON_MALE, item=ITEM_EVERSTONE; givemon SPECIES_PERSIAN, 1, gender=MON_FEMALE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH; RUN_OVERWORLD_SCRIPT(givemon SPECIES_PERRSERKER, 1, gender=MON_MALE, item=ITEM_EVERSTONE; givemon SPECIES_PERSIAN, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH; RUN_OVERWORLD_SCRIPT(givemon SPECIES_PERSIAN_ALOLA, 1, gender=MON_MALE, item=ITEM_EVERSTONE; givemon SPECIES_PERSIAN, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + } else { + PARAMETRIZE { offspring = SPECIES_MEOWTH; RUN_OVERWORLD_SCRIPT(givemon SPECIES_MEOWTH, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_ALOLA, 1, gender=MON_FEMALE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_ALOLA; RUN_OVERWORLD_SCRIPT(givemon SPECIES_MEOWTH, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_ALOLA, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH; RUN_OVERWORLD_SCRIPT(givemon SPECIES_MEOWTH, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_GALAR, 1, gender=MON_FEMALE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_GALAR; RUN_OVERWORLD_SCRIPT(givemon SPECIES_MEOWTH, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_GALAR, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH; RUN_OVERWORLD_SCRIPT(givemon SPECIES_DIGLETT, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_GALAR, 1, gender=MON_FEMALE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_GALAR; RUN_OVERWORLD_SCRIPT(givemon SPECIES_DIGLETT, 1, gender=MON_MALE; givemon SPECIES_MEOWTH_GALAR, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH_GALAR; RUN_OVERWORLD_SCRIPT(givemon SPECIES_PERRSERKER, 1, gender=MON_MALE, item=ITEM_EVERSTONE; givemon SPECIES_PERSIAN, 1, gender=MON_FEMALE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH; RUN_OVERWORLD_SCRIPT(givemon SPECIES_PERRSERKER, 1, gender=MON_MALE, item=ITEM_EVERSTONE; givemon SPECIES_PERSIAN, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_MEOWTH; RUN_OVERWORLD_SCRIPT(givemon SPECIES_PERSIAN_ALOLA, 1, gender=MON_MALE, item=ITEM_EVERSTONE; givemon SPECIES_PERSIAN, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + } + STORE_IN_DAYCARE_AND_GET_EGG(); + + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), offspring); +}