From b6ff973f3821dc2fc0508d0c912b6b838819d7d2 Mon Sep 17 00:00:00 2001 From: kittenchilly Date: Thu, 23 Nov 2023 15:59:51 -0600 Subject: [PATCH] Add support for legendary fusion (#3274) * Legendary Mon Fusion Evolution Compatibility Added Kyurem, Calyrex, and Necrozma Fusions have been implemented, however, the animation for the the process is incomplete, the party menu screen needs to be updated while the screen is whited out and it has proved beyond me right now. Smh * Added Fusion Move Prompts Handles cases for learning the fusion moves from the other fused mon and deleting the move when you unfuse (plus learning confusion (well a set choice in the data struct) if there are no moves left after deleting one) * Fix Bug With Fusion Mon Move Learning If the main fusion came after the fused mon in the party order it would try to teach the move to the next member in the party * Apply suggestions from code review Co-authored-by: LOuroboros * Fix build * Fix fusion items being used up * Fixed animations * RefreshPartyMenu * Apply suggestions from code review Co-authored-by: Eduardo Quezada D'Ottone * Apply suggestions * Update form_change_tables.h * Fix animation and simplify fusion tables * Fix party full message * Remove trailing whitespaces * Update party_menu.c * Update party_menu.c * Make IsFusionMon better * Apply suggestions from code review Co-authored-by: Bassoonian --------- Co-authored-by: TeamAquasHideout Co-authored-by: Eduardo Quezada D'Ottone Co-authored-by: LOuroboros Co-authored-by: Bassoonian --- include/constants/form_change_types.h | 1 + include/constants/party_menu.h | 1 + include/item_use.h | 1 + include/party_menu.h | 1 + include/pokemon.h | 13 + include/pokemon_storage_system.h | 2 + include/strings.h | 1 + src/data/items.h | 23 +- src/data/party_menu.h | 1 + src/data/pokemon/form_change_table_pointers.h | 25 ++ src/data/pokemon/form_change_tables.h | 19 + src/item_use.c | 7 + src/party_menu.c | 420 ++++++++++++++++++ src/strings.c | 3 +- 14 files changed, 509 insertions(+), 9 deletions(-) diff --git a/include/constants/form_change_types.h b/include/constants/form_change_types.h index 38481869bc..11a25d8966 100644 --- a/include/constants/form_change_types.h +++ b/include/constants/form_change_types.h @@ -11,6 +11,7 @@ #define DAY 1 #define NIGHT 2 +#define FUSION_TERMINATOR 0xFF #define FORM_CHANGE_TERMINATOR 0 // Form change that activates when the specified item is given to or taken from the selected Pokémon. diff --git a/include/constants/party_menu.h b/include/constants/party_menu.h index 93d5e5d57b..c860b425d4 100644 --- a/include/constants/party_menu.h +++ b/include/constants/party_menu.h @@ -80,6 +80,7 @@ #define PARTY_MSG_DO_WHAT_WITH_MAIL 25 #define PARTY_MSG_ALREADY_HOLDING_ONE 26 #define PARTY_MSG_WHICH_APPLIANCE 27 +#define PARTY_MSG_CHOOSE_SECOND_FUSION 28 #define PARTY_MSG_NONE 127 // IDs for DisplayPartyPokemonDescriptionText, to display a message in the party pokemon's box diff --git a/include/item_use.h b/include/item_use.h index a4dd92663d..5abe87f057 100644 --- a/include/item_use.h +++ b/include/item_use.h @@ -31,6 +31,7 @@ void ItemUseOutOfBattle_FormChange(u8); void ItemUseOutOfBattle_FormChange_ConsumedOnUse(u8); void ItemUseOutOfBattle_RotomCatalog(u8); void ItemUseOutOfBattle_ZygardeCube(u8); +void ItemUseOutOfBattle_Fusion(u8); void ItemUseOutOfBattle_Honey(u8); void ItemUseOutOfBattle_CannotUse(u8); void ItemUseOutOfBattle_ExpShare(u8); diff --git a/include/party_menu.h b/include/party_menu.h index 304eae1266..c50abc276a 100644 --- a/include/party_menu.h +++ b/include/party_menu.h @@ -68,6 +68,7 @@ void ItemUseCB_FormChange(u8 taskId, TaskFunc task); void ItemUseCB_FormChange_ConsumedOnUse(u8 taskId, TaskFunc task); void ItemUseCB_RotomCatalog(u8 taskId, TaskFunc task); void ItemUseCB_ZygardeCube(u8 taskId, TaskFunc task); +void ItemUseCB_Fusion(u8 taskId, TaskFunc task); const u8* GetItemEffect(u16 item); u8 GetItemEffectType(u16 item); void CB2_PartyMenuFromStartMenu(void); diff --git a/include/pokemon.h b/include/pokemon.h index 30abf8a037..ff78373fa5 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -426,6 +426,19 @@ struct FormChange u16 param3; }; +struct Fusion +{ + u16 fusionStorageIndex; + u16 itemId; + u16 targetSpecies1; + u16 targetSpecies2; + u16 fusingIntoMon; + u16 fusionMove; + u16 unfuseForgetMove; +}; + +extern const struct Fusion *const gFusionTablePointers[NUM_SPECIES]; + #define NUM_UNOWN_FORMS 28 #define GET_UNOWN_LETTER(personality) (( \ diff --git a/include/pokemon_storage_system.h b/include/pokemon_storage_system.h index eb5f9a992d..5ab1b4cb8f 100644 --- a/include/pokemon_storage_system.h +++ b/include/pokemon_storage_system.h @@ -6,6 +6,7 @@ #define IN_BOX_COLUMNS 6 // Number of columns, 5 Pokémon per column #define IN_BOX_COUNT (IN_BOX_ROWS * IN_BOX_COLUMNS) #define BOX_NAME_LENGTH 8 +#define MAX_FUSION_STORAGE 4 /* COLUMNS @@ -22,6 +23,7 @@ struct PokemonStorage /*0x0001*/ struct BoxPokemon boxes[TOTAL_BOXES_COUNT][IN_BOX_COUNT]; /*0x8344*/ u8 boxNames[TOTAL_BOXES_COUNT][BOX_NAME_LENGTH + 1]; /*0x83C2*/ u8 boxWallpapers[TOTAL_BOXES_COUNT]; + /*0x8432*/ struct Pokemon fusions[MAX_FUSION_STORAGE]; }; extern struct PokemonStorage *gPokemonStoragePtr; diff --git a/include/strings.h b/include/strings.h index cd56d71e36..9184b4663a 100644 --- a/include/strings.h +++ b/include/strings.h @@ -944,6 +944,7 @@ extern const u8 gText_UsedVar2WildLured[]; extern const u8 gText_UsedVar2WildRepelled[]; extern const u8 gText_BoxFull[]; extern const u8 gText_WontHaveEffect[]; +extern const u8 gText_NextFusionMon[]; extern const u8 gText_LevelSymbol[]; extern const u8 gText_PkmnInfo[]; diff --git a/src/data/items.h b/src/data/items.h index 984c611ca6..670d7dd2e5 100644 --- a/src/data/items.h +++ b/src/data/items.h @@ -8502,6 +8502,7 @@ const struct Item gItems[] = { .name = _("Gracidea"), .price = 0, + .importance = 1, .description = sGracideaDesc, .pocket = POCKET_KEY_ITEMS, .type = ITEM_USE_PARTY_MENU, @@ -8512,6 +8513,7 @@ const struct Item gItems[] = { .name = _("Reveal Glass"), .price = 0, + .importance = 1, .description = sRevealGlassDesc, .pocket = POCKET_KEY_ITEMS, .type = ITEM_USE_PARTY_MENU, @@ -8522,10 +8524,11 @@ const struct Item gItems[] = { .name = _("DNA Splicers"), .price = 0, + .importance = 1, .description = sDNASplicersDesc, .pocket = POCKET_KEY_ITEMS, - .type = ITEM_USE_BAG_MENU, - .fieldUseFunc = ItemUseOutOfBattle_CannotUse, // Todo: ItemUseOutOfBattle_FormChange_Fusion + .type = ITEM_USE_PARTY_MENU, + .fieldUseFunc = ItemUseOutOfBattle_Fusion, }, [ITEM_ZYGARDE_CUBE] = @@ -8543,6 +8546,7 @@ const struct Item gItems[] = { .name = _("Prison Bottle"), .price = 0, + .importance = 1, .description = sPrisonBottleDesc, .pocket = POCKET_KEY_ITEMS, .type = ITEM_USE_PARTY_MENU, @@ -8553,30 +8557,33 @@ const struct Item gItems[] = { .name = _("N-Solarizer"), .price = 0, + .importance = 1, .description = sNSolarizerDesc, .pocket = POCKET_KEY_ITEMS, - .type = ITEM_USE_BAG_MENU, - .fieldUseFunc = ItemUseOutOfBattle_CannotUse, // Todo: ItemUseOutOfBattle_FormChange_Fusion + .type = ITEM_USE_PARTY_MENU, + .fieldUseFunc = ItemUseOutOfBattle_Fusion, }, [ITEM_N_LUNARIZER] = { .name = _("N-Lunarizer"), .price = 0, + .importance = 1, .description = sNLunarizerDesc, .pocket = POCKET_KEY_ITEMS, - .type = ITEM_USE_BAG_MENU, - .fieldUseFunc = ItemUseOutOfBattle_CannotUse, // Todo: ItemUseOutOfBattle_FormChange_Fusion + .type = ITEM_USE_PARTY_MENU, + .fieldUseFunc = ItemUseOutOfBattle_Fusion, }, [ITEM_REINS_OF_UNITY] = { .name = _("ReinsOfUnity"), .price = 0, + .importance = 1, .description = sReinsOfUnityDesc, .pocket = POCKET_KEY_ITEMS, - .type = ITEM_USE_BAG_MENU, - .fieldUseFunc = ItemUseOutOfBattle_CannotUse, // Todo: ItemUseOutOfBattle_FormChange_Fusion + .type = ITEM_USE_PARTY_MENU, + .fieldUseFunc = ItemUseOutOfBattle_Fusion, }, // Battle Mechanic Key Items diff --git a/src/data/party_menu.h b/src/data/party_menu.h index b12ca40651..168520a48c 100644 --- a/src/data/party_menu.h +++ b/src/data/party_menu.h @@ -658,6 +658,7 @@ static const u8 *const sActionStringTable[] = [PARTY_MSG_DO_WHAT_WITH_MAIL] = gText_DoWhatWithMail, [PARTY_MSG_ALREADY_HOLDING_ONE] = gText_AlreadyHoldingOne, [PARTY_MSG_WHICH_APPLIANCE] = gText_WhichAppliance, + [PARTY_MSG_CHOOSE_SECOND_FUSION] = gText_NextFusionMon, }; static const u8 *const sDescriptionStringTable[] = diff --git a/src/data/pokemon/form_change_table_pointers.h b/src/data/pokemon/form_change_table_pointers.h index 42be2a904f..0180c16ca1 100644 --- a/src/data/pokemon/form_change_table_pointers.h +++ b/src/data/pokemon/form_change_table_pointers.h @@ -375,3 +375,28 @@ const struct FormChange *const gFormChangeTablePointers[NUM_SPECIES] = [SPECIES_OGERPON_CORNERSTONE_MASK_TERA] = sOgerponFormChangeTable, #endif }; + +const struct Fusion *const gFusionTablePointers[NUM_SPECIES] = +{ +#if P_GEN_5_POKEMON == TRUE + [SPECIES_KYUREM] = sKyuremFusionTable, + [SPECIES_KYUREM_BLACK] = sKyuremFusionTable, + [SPECIES_KYUREM_WHITE] = sKyuremFusionTable, + [SPECIES_RESHIRAM] = sKyuremFusionTable, + [SPECIES_ZEKROM] = sKyuremFusionTable, +#endif +#if P_GEN_7_POKEMON == TRUE + [SPECIES_NECROZMA] = sNecrozmaFusionTable, + [SPECIES_NECROZMA_DAWN_WINGS] = sNecrozmaFusionTable, + [SPECIES_NECROZMA_DUSK_MANE] = sNecrozmaFusionTable, + [SPECIES_SOLGALEO] = sNecrozmaFusionTable, + [SPECIES_LUNALA] = sNecrozmaFusionTable, +#endif +#if P_GEN_8_POKEMON == TRUE + [SPECIES_CALYREX] = sCalyrexFusionTable, + [SPECIES_CALYREX_ICE_RIDER] = sCalyrexFusionTable, + [SPECIES_CALYREX_SHADOW_RIDER] = sCalyrexFusionTable, + [SPECIES_SPECTRIER] = sCalyrexFusionTable, + [SPECIES_GLASTRIER] = sCalyrexFusionTable, +#endif +}; diff --git a/src/data/pokemon/form_change_tables.h b/src/data/pokemon/form_change_tables.h index ccb736bba8..7080ad2444 100644 --- a/src/data/pokemon/form_change_tables.h +++ b/src/data/pokemon/form_change_tables.h @@ -407,6 +407,12 @@ static const struct FormChange sLandorusFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; +static const struct Fusion sKyuremFusionTable[] = { + {0, ITEM_DNA_SPLICERS, SPECIES_KYUREM, SPECIES_RESHIRAM, SPECIES_KYUREM_WHITE}, + {0, ITEM_DNA_SPLICERS, SPECIES_KYUREM, SPECIES_ZEKROM, SPECIES_KYUREM_BLACK}, + {FUSION_TERMINATOR}, +}; + static const struct FormChange sKeldeoFormChangeTable[] = { {FORM_CHANGE_MOVE, SPECIES_KELDEO_RESOLUTE, MOVE_SECRET_SWORD, WHEN_LEARNED}, {FORM_CHANGE_MOVE, SPECIES_KELDEO_ORDINARY, MOVE_SECRET_SWORD, WHEN_FORGOTTEN}, @@ -588,6 +594,13 @@ static const struct FormChange sMiniorYellowFormChangeTable[] = { {FORM_CHANGE_END_BATTLE, SPECIES_MINIOR_CORE_YELLOW}, {FORM_CHANGE_TERMINATOR}, }; + +static const struct Fusion sNecrozmaFusionTable[] = { + {1, ITEM_N_SOLARIZER, SPECIES_NECROZMA, SPECIES_SOLGALEO, SPECIES_NECROZMA_DUSK_MANE, MOVE_SUNSTEEL_STRIKE, MOVE_CONFUSION}, + {2, ITEM_N_LUNARIZER, SPECIES_NECROZMA, SPECIES_LUNALA, SPECIES_NECROZMA_DAWN_WINGS, MOVE_MOONGEIST_BEAM, MOVE_CONFUSION}, + {FUSION_TERMINATOR}, +}; + static const struct FormChange sNecrozmaDuskManeFormChangeTable[] = { {FORM_CHANGE_BATTLE_ULTRA_BURST, SPECIES_NECROZMA_ULTRA, ITEM_ULTRANECROZIUM_Z}, {FORM_CHANGE_TERMINATOR}, @@ -635,6 +648,12 @@ static const struct FormChange sZamazentaFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; +static const struct Fusion sCalyrexFusionTable[] = { + {3, ITEM_REINS_OF_UNITY, SPECIES_CALYREX, SPECIES_GLASTRIER, SPECIES_CALYREX_ICE_RIDER, MOVE_GLACIAL_LANCE, MOVE_CONFUSION}, + {3, ITEM_REINS_OF_UNITY, SPECIES_CALYREX, SPECIES_SPECTRIER, SPECIES_CALYREX_SHADOW_RIDER, MOVE_ASTRAL_BARRAGE, MOVE_CONFUSION}, + {FUSION_TERMINATOR}, +}; + static const struct FormChange sEnamorusFormChangeTable[] = { {FORM_CHANGE_ITEM_USE, SPECIES_ENAMORUS_INCARNATE, ITEM_REVEAL_GLASS}, {FORM_CHANGE_ITEM_USE, SPECIES_ENAMORUS_THERIAN, ITEM_REVEAL_GLASS}, diff --git a/src/item_use.c b/src/item_use.c index 602bda27af..11d92e7427 100644 --- a/src/item_use.c +++ b/src/item_use.c @@ -1344,6 +1344,13 @@ void ItemUseOutOfBattle_ZygardeCube(u8 taskId) } } +void ItemUseOutOfBattle_Fusion(u8 taskId) +{ + gItemUseCB = ItemUseCB_Fusion; + gTasks[taskId].data[0] = FALSE; + SetUpItemUseCallback(taskId); +} + void Task_UseHoneyOnField(u8 taskId) { //ResetInitialPlayerAvatarState(); diff --git a/src/party_menu.c b/src/party_menu.c index 798f9541d2..37e042a96a 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -247,7 +247,9 @@ void (*gItemUseCB)(u8, TaskFunc); static void ResetPartyMenu(void); static void CB2_InitPartyMenu(void); +static void CB2_ReloadPartyMenu(void); static bool8 ShowPartyMenu(void); +static bool8 ReloadPartyMenu(void); static void SetPartyMonsAllowedInMinigame(void); static void ExitPartyMenu(void); static bool8 AllocPartyMenuBg(void); @@ -552,6 +554,25 @@ static void InitPartyMenu(u8 menuType, u8 layout, u8 partyAction, bool8 keepCurs } } +static void RefreshPartyMenu(void) //Refreshes the party menu without restarting tasks +{ + u16 i; + + sPartyMenuInternal->exitCallback = NULL; + sPartyMenuInternal->lastSelectedSlot = 0; + sPartyMenuInternal->spriteIdConfirmPokeball = 0x7F; + sPartyMenuInternal->spriteIdCancelPokeball = 0x7F; + + for (i = 0; i < ARRAY_COUNT(sPartyMenuInternal->data); i++) + sPartyMenuInternal->data[i] = 0; + for (i = 0; i < ARRAY_COUNT(sPartyMenuInternal->windowId); i++) + sPartyMenuInternal->windowId[i] = WINDOW_NONE; + + gTextFlags.autoScroll = 0; + CalculatePlayerPartyCount(); + SetMainCallback2(CB2_ReloadPartyMenu); +} + static void CB2_UpdatePartyMenu(void) { RunTasks(); @@ -577,6 +598,15 @@ static void CB2_InitPartyMenu(void) } } +static void CB2_ReloadPartyMenu(void) +{ + while (TRUE) + { + if (MenuHelpers_ShouldWaitForLinkRecv() == TRUE || ReloadPartyMenu() == TRUE || MenuHelpers_IsLinkActive() == TRUE) + break; + } +} + static bool8 ShowPartyMenu(void) { switch (gMain.state) @@ -701,6 +731,119 @@ static bool8 ShowPartyMenu(void) return FALSE; } +static bool8 ReloadPartyMenu(void) +{ + switch (gMain.state) + { + case 0: + SetVBlankHBlankCallbacksToNull(); + ClearScheduledBgCopiesToVram(); + gMain.state++; + break; + case 1: + ScanlineEffect_Stop(); + gMain.state++; + break; + case 2: + ResetPaletteFade(); + gPaletteFade.bufferTransferDisabled = TRUE; + gMain.state++; + break; + case 3: + ResetSpriteData(); + gMain.state++; + break; + case 4: + FreeAllSpritePalettes(); + gMain.state++; + break; + case 5: + SetPartyMonsAllowedInMinigame(); + gMain.state++; + break; + case 6: + if (!AllocPartyMenuBg()) + { + ExitPartyMenu(); + return TRUE; + } + else + { + sPartyMenuInternal->data[0] = 0; + gMain.state++; + } + break; + case 7: + if (AllocPartyMenuBgGfx()) + gMain.state++; + break; + case 8: + InitPartyMenuWindows(gPartyMenu.layout); + gMain.state++; + break; + case 9: + InitPartyMenuBoxes(gPartyMenu.layout); + sPartyMenuInternal->data[0] = 0; + gMain.state++; + break; + case 10: + LoadHeldItemIcons(); + gMain.state++; + break; + case 11: + LoadPartyMenuPokeballGfx(); + gMain.state++; + break; + case 12: + LoadPartyMenuAilmentGfx(); + gMain.state++; + break; + case 13: + LoadMonIconPalettes(); + gMain.state++; + break; + case 14: + if (CreatePartyMonSpritesLoop()) + { + sPartyMenuInternal->data[0] = 0; + gMain.state++; + } + break; + case 15: + if (RenderPartyMenuBoxes()) + { + sPartyMenuInternal->data[0] = 0; + gMain.state++; + } + break; + case 16: + CreateCancelConfirmPokeballSprites(); + gMain.state++; + break; + case 17: + CreateCancelConfirmWindows(sPartyMenuInternal->chooseHalf); + gMain.state++; + break; + case 18: + gMain.state++; + break; + case 19: + BlendPalettes(PALETTES_ALL, 16, RGB_WHITEALPHA); + gPaletteFade.bufferTransferDisabled = FALSE; + gMain.state++; + break; + case 20: + BeginNormalPaletteFade(PALETTES_ALL, 0, 16, 0, RGB_WHITEALPHA); + gMain.state++; + break; + default: + SetVBlankCallback(VBlankCB_PartyMenu); + SetMainCallback2(CB2_UpdatePartyMenu); + return TRUE; + } + return FALSE; +} + static void ExitPartyMenu(void) { BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK); @@ -5707,11 +5850,44 @@ void ItemUseCB_EvolutionStone(u8 taskId, TaskFunc task) } } +#define FUSE_MON 1 +#define UNFUSE_MON 2 +#define SECOND_FUSE_MON 3 + #define tState data[0] #define tTargetSpecies data[1] #define tAnimWait data[2] #define tNextFunc 3 +#define fusionType data[7] +#define firstFusion data[8] +#define firstFusionSlot data[9] +#define fusionResult data[10] +#define secondFusionSlot data[11] +#define unfuseSecondMon data[12] +#define moveToLearn data[13] +#define forgetMove data[14] +#define storageIndex data[15] + +static void Task_TryItemUseFusionChange(u8 taskId); +static void SpriteCB_FormChangeIconMosaic(struct Sprite *sprite); + +u8 IsFusionMon(u16 species) +{ + u16 i; + const struct Fusion *itemFusion = gFusionTablePointers[species]; + for (i = 0; itemFusion[i].fusionStorageIndex != FUSION_TERMINATOR; i++) + { + if (itemFusion[i].fusingIntoMon == species) + return UNFUSE_MON; + else if (itemFusion[i].targetSpecies1 == species) + return FUSE_MON; + else if (itemFusion[i].targetSpecies2 == species) + return SECOND_FUSE_MON; + } + return FALSE; +} + void FormChangeTeachMove(u8 taskId, u32 move, u32 slot) { struct Pokemon *mon; @@ -5771,6 +5947,250 @@ bool32 DoesMonHaveAnyMoves(struct Pokemon *mon) return FALSE; } +bool32 TryItemUseFusionChange(u8 taskId, TaskFunc task) +{ + u16 targetSpecies = gTasks[taskId].fusionResult; + s8 *slotPtr = GetCurrentPartySlotPtr(); + *slotPtr = gTasks[taskId].firstFusionSlot; + if (gTasks[taskId].fusionType == FUSE_MON) + AnimatePartySlot(gTasks[taskId].secondFusionSlot, 0); + AnimatePartySlot(*slotPtr, 1); + + if (targetSpecies != SPECIES_NONE) + { + gPartyMenuUseExitCallback = TRUE; + SetWordTaskArg(taskId, tNextFunc, (u32)task); + gTasks[taskId].func = Task_TryItemUseFusionChange; + gTasks[taskId].tState = 0; + gTasks[taskId].tTargetSpecies = targetSpecies; + gTasks[taskId].tAnimWait = 0; + return TRUE; + } + else + { + gPartyMenuUseExitCallback = FALSE; + PlaySE(SE_SELECT); + DisplayPartyMenuMessage(gText_WontHaveEffect, TRUE); + ScheduleBgCopyTilemapToVram(2); + gTasks[taskId].func = task; + return FALSE; + } +} + +static void Task_TryItemUseFusionChange(u8 taskId) +{ + struct Pokemon *mon = &gPlayerParty[gTasks[taskId].firstFusionSlot]; + struct Sprite *icon = &gSprites[sPartyMenuBoxes[gTasks[taskId].firstFusionSlot].monSpriteId]; + struct Pokemon *mon2; + struct Sprite *icon2 = &gSprites[sPartyMenuBoxes[gTasks[taskId].secondFusionSlot].monSpriteId]; + u16 targetSpecies; + + switch (gTasks[taskId].tState) + { + case 0: + if (gTasks[taskId].fusionType == FUSE_MON) + { + mon2 = &gPlayerParty[gTasks[taskId].secondFusionSlot]; + CopyMon(&gPokemonStoragePtr->fusions[gTasks[taskId].storageIndex], mon2, sizeof(*mon2)); + ZeroMonData(&gPlayerParty[gTasks[taskId].secondFusionSlot]); + } + else + { + mon2 = &gPokemonStoragePtr->fusions[gTasks[taskId].storageIndex]; + GiveMonToPlayer(mon2); + ZeroMonData(&gPokemonStoragePtr->fusions[gTasks[taskId].storageIndex]); + } + targetSpecies = gTasks[taskId].tTargetSpecies; + SetMonData(mon, MON_DATA_SPECIES, &targetSpecies); + CalculateMonStats(mon); + CompactPartySlots(); + CalculatePlayerPartyCount(); + gTasks[taskId].tState++; + PlaySE(SE_M_TELEPORT); + break; + case 1: + targetSpecies = gTasks[taskId].tTargetSpecies; + if (gTasks[taskId].tAnimWait == 0) + { + icon->oam.mosaic = TRUE; + icon->data[0] = 10; + icon->data[1] = 1; + icon->data[2] = taskId; + icon->callback = SpriteCB_FormChangeIconMosaic; + SetGpuReg(REG_OFFSET_MOSAIC, (icon->data[0] << 12) | (icon->data[1] << 8)); + if (gTasks[taskId].fusionType == FUSE_MON) + { + icon2->oam.mosaic = TRUE; + icon2->data[0] = 10; + icon2->data[1] = 1; + icon2->data[2] = taskId; + icon2->callback = SpriteCB_FormChangeIconMosaic; + SetGpuReg(REG_OFFSET_MOSAIC, (icon2->data[0] << 12) | (icon2->data[1] << 8)); + } + } + + if (++gTasks[taskId].tAnimWait == 60) + { + BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_WHITEALPHA); + gTasks[taskId].tState++; + } + break; + case 2: + if (gPaletteFade.active) + break; + if (gTasks[taskId].fusionType == FUSE_MON && gTasks[taskId].firstFusionSlot > gTasks[taskId].secondFusionSlot) + { + gTasks[taskId].firstFusionSlot--; + gPartyMenu.slotId--; + } + RefreshPartyMenu(); + gTasks[taskId].tState++; + break; + case 3: + BeginNormalPaletteFade(PALETTES_ALL, 16, 0, 0, RGB_WHITEALPHA); + gTasks[taskId].tState++; + break; + case 4: + targetSpecies = gTasks[taskId].tTargetSpecies; + PlayCry_Normal(targetSpecies, 0); + gTasks[taskId].tState++; + break; + case 5: + if (IsCryFinished()) + { + GetMonNickname(mon, gStringVar1); + StringExpandPlaceholders(gStringVar4, gText_PkmnTransformed); + DisplayPartyMenuMessage(gStringVar4, FALSE); + ScheduleBgCopyTilemapToVram(2); + gTasks[taskId].tState++; + } + break; + case 6: + if (!IsPartyMenuTextPrinterActive()) + { + if (gTasks[taskId].moveToLearn != 0) + { + if (gTasks[taskId].fusionType == FUSE_MON) + FormChangeTeachMove(taskId, gTasks[taskId].moveToLearn, gTasks[taskId].firstFusionSlot); + else + { + DeleteMove(mon, gTasks[taskId].forgetMove); + if (!DoesMonHaveAnyMoves(mon)) + FormChangeTeachMove(taskId, gTasks[taskId].moveToLearn, gTasks[taskId].firstFusionSlot); + } + } + gTasks[taskId].tState++; + } + break; + case 7: + gTasks[taskId].func = (void *)GetWordTaskArg(taskId, tNextFunc); + break; + } +} + +void ItemUseCB_Fusion(u8 taskId, TaskFunc taskFunc) +{ + u16 i; + struct Task *task = &gTasks[taskId]; + u16 species = GetMonData(&gPlayerParty[gPartyMenu.slotId], MON_DATA_SPECIES); + const struct Fusion *itemFusion = gFusionTablePointers[species]; + + PlaySE(SE_SELECT); + switch (IsFusionMon(species)) + { + case FALSE: // Cancel if Not a Fuse Mon + break; + case UNFUSE_MON: + if (task->fusionType == FUSE_MON) // Cancel if An already Fused Mon Is Chosen For The Second Fusion Mon + break; + if (gPlayerPartyCount == PARTY_SIZE) + { + gPartyMenuUseExitCallback = FALSE; + DisplayPartyMenuMessage(gText_YourPartysFull, TRUE); + ScheduleBgCopyTilemapToVram(2); + task->func = taskFunc; + return; + } + for (i = 0; itemFusion[i].fusionStorageIndex != FUSION_TERMINATOR; i++) // Loops through fusion table and checks if the mon can be unfused + { + if (gPokemonStoragePtr->fusions[itemFusion[i].fusionStorageIndex].level == 0) + continue; + if (itemFusion[i].itemId == gSpecialVar_ItemId && GetMonData(&gPokemonStoragePtr->fusions[itemFusion[i].fusionStorageIndex], MON_DATA_SPECIES) == itemFusion[i].targetSpecies2) + { + task->fusionType = UNFUSE_MON; + task->firstFusion = species; + task->firstFusionSlot = gPartyMenu.slotId; + task->storageIndex = itemFusion[i].fusionStorageIndex; + task->fusionResult = itemFusion[i].targetSpecies1; + task->unfuseSecondMon = itemFusion[i].targetSpecies2; + task->moveToLearn = itemFusion[i].unfuseForgetMove; + task->forgetMove = itemFusion[i].fusionMove; + TryItemUseFusionChange(taskId, taskFunc); + return; + } + } + break; + case FUSE_MON: + if (task->fusionType == FUSE_MON) // Cancel If Second Mon is Another First Fusion Mon + break; + for (i = 0; itemFusion[i].fusionStorageIndex != FUSION_TERMINATOR; i++) // Run through the Fusion table for each species and check if the item matches one of the entries + { + if (itemFusion[i].itemId == gSpecialVar_ItemId) + { + task->fusionType = FUSE_MON; + task->firstFusion = species; + task->firstFusionSlot = gPartyMenu.slotId; + task->storageIndex = itemFusion[i].fusionStorageIndex; + task->func = Task_HandleChooseMonInput; + gPartyMenuUseExitCallback = FALSE; + sPartyMenuInternal->exitCallback = NULL; + PartyMenuRemoveWindow(&sPartyMenuInternal->windowId[0]); + DisplayPartyMenuStdMessage(PARTY_MSG_CHOOSE_SECOND_FUSION); + return; + } + } + break; + case SECOND_FUSE_MON: + if (task->fusionType != FUSE_MON) // Cancel if Secondary Fusion Mon Chosen First + break; + for (i = 0; itemFusion[i].fusionStorageIndex != FUSION_TERMINATOR; i++) // run through fusion table and check if the fusion works + { + if (gPokemonStoragePtr->fusions[itemFusion[i].fusionStorageIndex].level != 0) + continue; + if (itemFusion[i].itemId == gSpecialVar_ItemId && itemFusion[i].targetSpecies1 == task->firstFusion) + { + task->storageIndex = itemFusion[i].fusionStorageIndex; + task->fusionResult = itemFusion[i].fusingIntoMon; + task->secondFusionSlot = gPartyMenu.slotId; + task->moveToLearn = itemFusion[i].fusionMove; + // Start Fusion + TryItemUseFusionChange(taskId, taskFunc); + return; + } + } + break; + } + // No Effect Exit + gPartyMenuUseExitCallback = FALSE; + DisplayPartyMenuMessage(gText_WontHaveEffect, TRUE); + ScheduleBgCopyTilemapToVram(2); + task->func = taskFunc; + return; +} + +#undef FUSE_MON +#undef UNFUSE_MON +#undef SECOND_FUSE_MON + +#undef fusionType +#undef firstFusion +#undef firstFusionSlot +#undef fusionResult +#undef secondFusionSlot +#undef unfuseSecondMon +#undef moveToLearn +#undef forgetMove +#undef storageIndex static void SpriteCB_FormChangeIconMosaic(struct Sprite *sprite) { diff --git a/src/strings.c b/src/strings.c index 72d6ff3e0a..f23ad3fca5 100644 --- a/src/strings.c +++ b/src/strings.c @@ -377,6 +377,7 @@ const u8 gText_HP3[] = _("HP"); const u8 gText_SpAtk3[] = _("SP. ATK"); const u8 gText_SpDef3[] = _("SP. DEF"); const u8 gText_WontHaveEffect[] = _("It won't have any effect.{PAUSE_UNTIL_PRESS}"); +const u8 gText_NextFusionMon[] = _("Choose {PKMN} to fuse with."); const u8 gText_CantBeUsedOnPkmn[] = _("This can't be used on\nthat POKéMON.{PAUSE_UNTIL_PRESS}"); const u8 gText_PkmnCantSwitchOut[] = _("{STR_VAR_1} can't be switched\nout!{PAUSE_UNTIL_PRESS}"); const u8 gText_PkmnAlreadyInBattle[] = _("{STR_VAR_1} is already\nin battle!{PAUSE_UNTIL_PRESS}"); @@ -881,7 +882,7 @@ const u8 gText_PkmnWasReleased[] = _("{DYNAMIC 0} was released."); const u8 gText_ByeByePkmn[] = _("Bye-bye, {DYNAMIC 0}!"); const u8 gText_MarkYourPkmn[] = _("Mark your POKéMON."); const u8 gText_ThatsYourLastPkmn[] = _("That's your last POKéMON!"); -const u8 gText_YourPartysFull[] = _("Your party's full!"); +const u8 gText_YourPartysFull[] = _("Your party's full!{PAUSE_UNTIL_PRESS}"); const u8 gText_YoureHoldingAPkmn[] = _("You're holding a POKéMON!"); const u8 gText_WhichOneWillYouTake[] = _("Which one will you take?"); const u8 gText_YouCantReleaseAnEgg[] = _("You can't release an EGG.");