From c5d0386a166a30d725775395394e70efb2267cf6 Mon Sep 17 00:00:00 2001 From: resetes12 Date: Tue, 30 Sep 2025 09:26:09 +0200 Subject: [PATCH] Ported heart and shiny icon indicator from HnS (Credit: dylanfalzone1) --- graphics/summary_screen/heart.png | Bin 0 -> 242 bytes graphics/summary_screen/shiny_icon.png | Bin 0 -> 177 bytes src/pokemon_summary_screen.c | 265 ++++++++++++++++++++++++- 3 files changed, 260 insertions(+), 5 deletions(-) create mode 100755 graphics/summary_screen/heart.png create mode 100755 graphics/summary_screen/shiny_icon.png diff --git a/graphics/summary_screen/heart.png b/graphics/summary_screen/heart.png new file mode 100755 index 0000000000000000000000000000000000000000..4e8ac2226d7fb660e8a496edb8731a36bc40056b GIT binary patch literal 242 zcmeAS@N?(olHy`uVBq!ia0vp^96)Tr!3-q7ul5uHQjEnx?oJHr&dIz4ats1|LR=Y^ z{{R2}{r{^|_A)U1mypoZ)cn6+Vs9-_8avRu-CRxEyHjO%!xP324;cUb zotu7M-}dL$^XF{@eYW)%_;-EmWjgGBEs? zkkHiB{J&pfZ!J(7JJ7u2_ZBE4P!i-9%nXL`?X~$jHDKzz}P>)Dq+<22WQ%mvv4FO#rr$H>Cgo literal 0 HcmV?d00001 diff --git a/src/pokemon_summary_screen.c b/src/pokemon_summary_screen.c index d6effb56e..b55a02b89 100755 --- a/src/pokemon_summary_screen.c +++ b/src/pokemon_summary_screen.c @@ -111,23 +111,127 @@ enum { #define MOVE_SELECTOR_SPRITES_COUNT 10 #define TYPE_ICON_SPRITE_COUNT (MAX_MON_MOVES + 1) + +#define TAG_FRIENDSHIP_ICON 30007 // pick an unused tag (30005/30006 are used in your file) + +enum +{ + FRIENDSHIP_LEVEL_0, + FRIENDSHIP_LEVEL_1, + FRIENDSHIP_LEVEL_2, + FRIENDSHIP_LEVEL_3, + FRIENDSHIP_LEVEL_4, + FRIENDSHIP_LEVEL_5, + FRIENDSHIP_LEVEL_6, + FRIENDSHIP_LEVEL_COUNT +}; + // for the spriteIds field in PokemonSummaryScreenData enum { SPRITE_ARR_ID_MON, SPRITE_ARR_ID_BALL, SPRITE_ARR_ID_STATUS, - SPRITE_ARR_ID_TYPE, // 2 for mon types, 5 for move types(4 moves and 1 to learn), used interchangeably, because mon types and move types aren't shown on the same screen - SPRITE_ARR_ID_MOVE_SELECTOR1 = SPRITE_ARR_ID_TYPE + TYPE_ICON_SPRITE_COUNT, // 10 sprites that make up the selector + SPRITE_ARR_ID_FRIENDSHIP, // NEW: friendship heart icon + SPRITE_ARR_ID_TYPE, // 2 for mon types, 5 for move types..., used interchangeably + SPRITE_ARR_ID_MOVE_SELECTOR1 = SPRITE_ARR_ID_TYPE + TYPE_ICON_SPRITE_COUNT, SPRITE_ARR_ID_MOVE_SELECTOR2 = SPRITE_ARR_ID_MOVE_SELECTOR1 + MOVE_SELECTOR_SPRITES_COUNT, SPRITE_ARR_ID_COUNT = SPRITE_ARR_ID_MOVE_SELECTOR2 + MOVE_SELECTOR_SPRITES_COUNT }; + #define TILE_EMPTY_APPEAL_HEART 0x1039 #define TILE_FILLED_APPEAL_HEART 0x103A #define TILE_FILLED_JAM_HEART 0x103C #define TILE_EMPTY_JAM_HEART 0x103D +// Tags — use your free slots (you told me 30006/30005 were open earlier) +#define TAG_SHINY_STAR_TILE 30006 +#define TAG_SHINY_STAR_PAL 30005 + +static const u16 sFriendshipLevelToThreshold[FRIENDSHIP_LEVEL_COUNT] = { 0, 42, 85, 128, 170, 212, 250 }; +static const u16 sFriendshipIcon_Pal[] = INCBIN_U16("graphics/summary_screen/heart.gbapal"); +static const u32 sFriendshipIcon_Gfx[] = INCBIN_U32("graphics/summary_screen/heart.4bpp.lz"); + +static const u16 sMarkings_Pal[] = INCBIN_U16("graphics/summary_screen/markings.gbapal"); + +// Use your shiny_icon asset. Toolchain should emit the .4bpp.lz during build. +static const u32 sStarObjTiles[] = INCBIN_U32("graphics/summary_screen/shiny_icon.4bpp.lz"); + +// 8x8, 4bpp OAM (small star) +static const struct OamData sStarObjOamData = +{ + .y = 0, + .affineMode = ST_OAM_AFFINE_OFF, + .objMode = ST_OAM_OBJ_NORMAL, + .mosaic = FALSE, + .bpp = ST_OAM_4BPP, + .shape = SPRITE_SHAPE(8x8), + .x = 0, + .matrixNum = 0, + .size = SPRITE_SIZE(8x8), + .tileNum = 0, + .priority = 3, // ontop of mon + .paletteNum = 0, +}; + +static const struct OamData sOamData_FriendshipIcon = +{ + .affineMode = ST_OAM_AFFINE_OFF, + .objMode = ST_OAM_OBJ_NORMAL, + .mosaic = 0, + .bpp = ST_OAM_4BPP, + .shape = SPRITE_SHAPE(8x8), // was 16x16 + .size = SPRITE_SIZE(8x8), // was 16x16 + .priority = 3, // ontop of mon +}; + +static const union AnimCmd sAnim_Friendship_0[] = { ANIMCMD_FRAME(0, 0), ANIMCMD_END }; +static const union AnimCmd sAnim_Friendship_1[] = { ANIMCMD_FRAME(1, 0), ANIMCMD_END }; +static const union AnimCmd sAnim_Friendship_2[] = { ANIMCMD_FRAME(2, 0), ANIMCMD_END }; +static const union AnimCmd sAnim_Friendship_3[] = { ANIMCMD_FRAME(3, 0), ANIMCMD_END }; +static const union AnimCmd sAnim_Friendship_4[] = { ANIMCMD_FRAME(4, 0), ANIMCMD_END }; +static const union AnimCmd sAnim_Friendship_5[] = { ANIMCMD_FRAME(5, 0), ANIMCMD_END }; +static const union AnimCmd sAnim_Friendship_6[] = { ANIMCMD_FRAME(6, 0), ANIMCMD_END }; + +static const union AnimCmd *const sAnimTable_FriendshipIcon[] = +{ + sAnim_Friendship_0, + sAnim_Friendship_1, + sAnim_Friendship_2, + sAnim_Friendship_3, + sAnim_Friendship_4, + sAnim_Friendship_5, + sAnim_Friendship_6, +}; + +static const struct SpriteTemplate sSpriteTemplate_FriendshipIcon = +{ + .tileTag = TAG_FRIENDSHIP_ICON, + .paletteTag = TAG_FRIENDSHIP_ICON, + .oam = &sOamData_FriendshipIcon, + .anims = sAnimTable_FriendshipIcon, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCallbackDummy, +}; + +// Single-frame anim +static const union AnimCmd sStarObjAnim0[] = { + ANIMCMD_FRAME(0, 0), + ANIMCMD_END, +}; +static const union AnimCmd *const sStarObjAnimTable[] = { + sStarObjAnim0, +}; + +// Small holder like FR +struct ShinyStarObjData { + struct Sprite *sprite; + u16 tileTag, palTag; +}; +static EWRAM_DATA struct ShinyStarObjData *sShinyStarObjData = NULL; + static EWRAM_DATA struct PokemonSummaryScreenData { /*0x00*/ union { @@ -755,6 +859,14 @@ static const struct OamData sOamData_SplitIcons = .priority = 0, }; + +static const struct CompressedSpriteSheet sSpriteSheet_FriendshipIcon = +{ + .data = sFriendshipIcon_Gfx, + .size = 7 * 32, // 7 tiles * 32 bytes per 8×8 4bpp tile + .tag = TAG_FRIENDSHIP_ICON +}; + static const struct CompressedSpriteSheet sSpriteSheet_SplitIcons = { .data = sSplitIcons_Gfx, @@ -762,6 +874,13 @@ static const struct CompressedSpriteSheet sSpriteSheet_SplitIcons = .tag = TAG_SPLIT_ICONS, }; +static const struct SpritePalette sSpritePal_FriendshipIcon = +{ + .data = sFriendshipIcon_Pal, + .tag = TAG_FRIENDSHIP_ICON +}; + + static const struct SpritePalette sSpritePal_SplitIcons = { .data = sSplitIcons_Pal, @@ -1150,7 +1269,6 @@ static const struct SpriteTemplate sSpriteTemplate_StatusCondition = .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallbackDummy }; -static const u16 sMarkings_Pal[] = INCBIN_U16("graphics/summary_screen/markings.gbapal"); // code static u8 ShowSplitIcon(u32 split) @@ -1213,6 +1331,100 @@ void ShowPokemonSummaryScreen(u8 mode, void *mons, u8 monIndex, u8 maxMonIndex, SetMainCallback2(CB2_InitSummaryScreen); } + +// Create (FR pattern: decompress into temp buffer, then LoadSpriteSheet/Palette) +static void CreateShinyStarObj(u16 tileTag, u16 palTag) +{ + void *gfxBuffer = NULL; + + if (sShinyStarObjData != NULL) + return; + + sShinyStarObjData = AllocZeroed(sizeof(*sShinyStarObjData)); + if (sShinyStarObjData == NULL) + return; + + // One 8x8 tile (32 bytes). Keep 0x40 (FR style) to be safe if your converter emits 2 tiles. + gfxBuffer = AllocZeroed(0x20 * 2); + if (gfxBuffer == NULL) + goto fail; + + LZ77UnCompWram(sStarObjTiles, gfxBuffer); + + struct SpriteSheet sheet = { + .data = gfxBuffer, + .size = 0x20 * 2, // safe for 1–2 tiles + .tag = tileTag, + }; + struct SpritePalette pal = { .data = sFriendshipIcon_Pal, .tag = palTag }; + struct SpriteTemplate tmpl = { + .tileTag = tileTag, + .paletteTag = palTag, + .oam = &sStarObjOamData, + .anims = sStarObjAnimTable, + .images = NULL, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = SpriteCallbackDummy, + }; + + LoadSpriteSheet(&sheet); + LoadSpritePalette(&pal); + + // Position: bottom-left, just below the Poké Ball (ball is at 16,136 in your file). + // Tweak a couple pixels if you want it tighter/looser. + u8 spriteId = CreateSprite(&tmpl, 68, 37, 0); //HnS Shiny star location + sShinyStarObjData->sprite = &gSprites[spriteId]; + sShinyStarObjData->tileTag = tileTag; + sShinyStarObjData->palTag = palTag; + + // Start hidden; we’ll reveal if shiny. + sShinyStarObjData->sprite->invisible = TRUE; + +fail: + if (gfxBuffer) + Free(gfxBuffer); +} + +static void DestroyShinyStarObj(void) +{ + if (sShinyStarObjData == NULL) + return; + + if (sShinyStarObjData->sprite != NULL) + DestroySpriteAndFreeResources(sShinyStarObjData->sprite); + + Free(sShinyStarObjData); + sShinyStarObjData = NULL; +} + +// FR-style gate: only visible if current mon is shiny and not an egg +static void HideShowShinyStar(bool8 invisible) +{ + if (sShinyStarObjData == NULL || sShinyStarObjData->sprite == NULL) + return; + + if (IsMonShiny(&sMonSummaryScreen->currentMon) && !sMonSummaryScreen->summary.isEgg) + sShinyStarObjData->sprite->invisible = invisible; + else + sShinyStarObjData->sprite->invisible = TRUE; + + // Keep it anchored bottom-left. If you later add a page with a different layout, + // you can branch here like FR and move the icon. + sShinyStarObjData->sprite->x = 68; + sShinyStarObjData->sprite->y = 37;//HnS Shiny star location +} + +static void ShowShinyStarObjIfMonShiny(void) +{ + if (sShinyStarObjData == NULL || sShinyStarObjData->sprite == NULL) + return; + + if (IsMonShiny(&sMonSummaryScreen->currentMon) && !sMonSummaryScreen->summary.isEgg) + HideShowShinyStar(FALSE); + else + HideShowShinyStar(TRUE); +} + void ShowSelectMovePokemonSummaryScreen(struct Pokemon *mons, u8 monIndex, u8 maxMonIndex, void (*callback)(void), u16 newMove) { ShowPokemonSummaryScreen(SUMMARY_MODE_SELECT_MOVE, mons, monIndex, maxMonIndex, callback); @@ -1246,6 +1458,32 @@ static void CB2_InitSummaryScreen(void) while (MenuHelpers_ShouldWaitForLinkRecv() != TRUE && LoadGraphics() != TRUE && MenuHelpers_IsLinkActive() != TRUE); } +static void SetFriendshipSprite(void) +{ + u8 *spriteId = &sMonSummaryScreen->spriteIds[SPRITE_ARR_ID_FRIENDSHIP]; + u16 friendship = GetMonData(&sMonSummaryScreen->currentMon, MON_DATA_FRIENDSHIP); + u8 level = 0; + + // Map friendship (0..255) into 6 animation levels using BW thresholds. + // 0:[0-49], 1:[50-99], 2:[100-149], 3:[150-199], 4:[200-249], 5:[250-255] + while (level + 1 < FRIENDSHIP_LEVEL_COUNT + && friendship >= sFriendshipLevelToThreshold[level + 1]) + { + level++; + } + StartSpriteAnim(&gSprites[*spriteId], level); + if (*spriteId == SPRITE_NONE) + *spriteId = CreateSprite(&sSpriteTemplate_FriendshipIcon, 68, 92, 0); //HnS Friendship heart location + + StartSpriteAnim(&gSprites[*spriteId], level); + SetSpriteInvisibility(SPRITE_ARR_ID_FRIENDSHIP, FALSE); +} + +static void TrySetInfoPageIcons(void) +{ + SetFriendshipSprite(); +} + static bool8 LoadGraphics(void) { switch (gMain.state) @@ -1327,6 +1565,7 @@ static bool8 LoadGraphics(void) break; case 17: sMonSummaryScreen->spriteIds[SPRITE_ARR_ID_MON] = LoadMonGfxAndSprite(&sMonSummaryScreen->currentMon, &sMonSummaryScreen->switchCounter); + ShowShinyStarObjIfMonShiny(); if (sMonSummaryScreen->spriteIds[SPRITE_ARR_ID_MON] != SPRITE_NONE) { sMonSummaryScreen->switchCounter = 0; @@ -1339,6 +1578,8 @@ static bool8 LoadGraphics(void) break; case 19: CreateCaughtBallSprite(&sMonSummaryScreen->currentMon); + CreateShinyStarObj(TAG_SHINY_STAR_TILE, TAG_SHINY_STAR_PAL); + ShowShinyStarObjIfMonShiny(); // add this line gMain.state++; break; case 20: @@ -1347,6 +1588,7 @@ static bool8 LoadGraphics(void) break; case 21: SetTypeIcons(); + TrySetInfoPageIcons(); gMain.state++; break; case 22: @@ -1453,6 +1695,8 @@ static bool8 DecompressGraphics(void) LoadCompressedPalette(gMoveTypes_Pal, OBJ_PLTT_ID(13), 3 * PLTT_SIZE_4BPP); LoadCompressedSpriteSheet(&sSpriteSheet_SplitIcons); LoadSpritePalette(&sSpritePal_SplitIcons); + LoadCompressedSpriteSheet(&sSpriteSheet_FriendshipIcon); + LoadSpritePalette(&sSpritePal_FriendshipIcon); sMonSummaryScreen->switchCounter = 0; return TRUE; } @@ -1609,6 +1853,7 @@ static void CloseSummaryScreen(u8 taskId) gLastViewedMonIndex = sMonSummaryScreen->curMonIndex; SummaryScreen_DestroyAnimDelayTask(); ResetSpriteData(); + DestroyShinyStarObj(); FreeAllSpritePalettes(); StopCryAndClearCrySongs(); m4aMPlayVolumeControl(&gMPlayInfo_BGM, TRACKS_ALL, 0x100); @@ -1771,6 +2016,8 @@ static void Task_ChangeSummaryMon(u8 taskId) break; case 6: CreateCaughtBallSprite(&sMonSummaryScreen->currentMon); + CreateShinyStarObj(TAG_SHINY_STAR_TILE, TAG_SHINY_STAR_PAL); + ShowShinyStarObjIfMonShiny(); // add this line break; case 7: if (sMonSummaryScreen->summary.ailment != AILMENT_NONE) @@ -1780,14 +2027,17 @@ static void Task_ChangeSummaryMon(u8 taskId) break; case 8: sMonSummaryScreen->spriteIds[SPRITE_ARR_ID_MON] = LoadMonGfxAndSprite(&sMonSummaryScreen->currentMon, &data[1]); + ShowShinyStarObjIfMonShiny(); if (sMonSummaryScreen->spriteIds[SPRITE_ARR_ID_MON] == SPRITE_NONE) return; gSprites[sMonSummaryScreen->spriteIds[SPRITE_ARR_ID_MON]].data[2] = 1; + ShowShinyStarObjIfMonShiny(); TryDrawExperienceProgressBar(); data[1] = 0; break; case 9: SetTypeIcons(); + TrySetInfoPageIcons(); break; case 10: PrintMonInfo(); @@ -1932,6 +2182,8 @@ static void PssScrollRightEnd(u8 taskId) // display right DrawPagination(); PutPageWindowTilemaps(sMonSummaryScreen->currPageIndex); SetTypeIcons(); + TrySetInfoPageIcons(); + ShowShinyStarObjIfMonShiny(); TryDrawExperienceProgressBar(); SwitchTaskToFollowupFunc(taskId); } @@ -1981,6 +2233,8 @@ static void PssScrollLeftEnd(u8 taskId) // display left DrawPagination(); PutPageWindowTilemaps(sMonSummaryScreen->currPageIndex); SetTypeIcons(); + TrySetInfoPageIcons(); + ShowShinyStarObjIfMonShiny(); TryDrawExperienceProgressBar(); SwitchTaskToFollowupFunc(taskId); } @@ -4025,9 +4279,8 @@ static void SetSpriteInvisibility(u8 spriteArrayId, bool8 invisible) static void HidePageSpecificSprites(void) { - // Keeps Pok�mon, caught ball and status sprites visible. + // Keeps Pokémon, caught ball, status, and friendship sprites visible. u8 i; - for (i = SPRITE_ARR_ID_TYPE; i < ARRAY_COUNT(sMonSummaryScreen->spriteIds); i++) { if (sMonSummaryScreen->spriteIds[i] != SPRITE_NONE) @@ -4035,6 +4288,8 @@ static void HidePageSpecificSprites(void) } } + + static void SetTypeIcons(void) { switch (sMonSummaryScreen->currPageIndex)