Shining Ball ‐ a ball to capture a pokemon and make it shiny
judicornadamsfoster edited this page 2026-10-09 16:42:27 +01:00

Shining Ball

Inspired by the Shiny Ball from Pokemon Prism, this new pokeball will 100% capture a wild/scripted pokemon battle and convert it to a shiny, with a shiny sparkle and tinkle SE on capture, and a new sprite using the strange ball from RHH's emerald-expansion.

shin_ball_gif

This code was created by a friend of mine, MewInfinity, full credit to them
ball image png and palettes sourced from the Strange Ball from RHH's pokeemerald-expansion

Before starting this guide:
if you have already added pokeballs or new items into your hack, adjust any numbers or line placings as appropriate
and always keep a backup


How To Add To Your Hack

pull from my git branch or add manually using the below code

git remote add tenaya15 https://github.com/tenaya15/tenaya15emerald.git
git pull tenaya15 shiny_ball

compare branch


first, download this zip file and extract the graphics folder to your root directory.

root.zip

This will place the needed image and palette files into the correct folders.


data/battle_scripts_2.s

in the first section
add after .4byte BattleScript_BallThrow @ ITEM_PREMIER_BALL

	.4byte BattleScript_ShinyBallThrow   @ ITEM_SHINING_BALL

then add to the end of the file:


BattleScript_ShinyBallThrow::
    printstring STRINGID_PLAYERUSEDITEM
    handleballthrow


include/constants/items.h

IF YOU USE AN UP-TO-DATE POKEEMERALD THAT USES enum FOR ITEMS:

delete ITEM_034,
and add after ITEM_PREMIER_BALL,

ITEM_SHINING_BALL,

IF YOU USE AN OLDER POKEEMERALD THAT STILL USES NUMBERS FOR DEFINING ITEMS:

delete #define ITEM_034 52 and move everything from after the balls down 1 number

#define ITEM_SHINING_BALL 52

FOR BOTH OLD AND UP-TO-DATE DECOMPS:

change #define LAST_BALL ITEM_PREMIER_BALL to

#define LAST_BALL ITEM_SHINING_BALL

include/graphics.h

add to the end of file, before #endif //GUARD_GRAPHICS_H


// ITEM_SHINING_BALL
extern const u32 gBallGfx_Shining[];
extern const u32 gBallPal_Shining[];
extern const u32 gItemIcon_ShiningBall[];
extern const u32 gItemIconPalette_ShiningBall[];


include/pokeball.h

in the first enum list
add after BALL_PREMIER,

    BALL_SHINING,

include/pokemon.h

add to end of file, before #endif // GUARD_POKEMON_H

bool8 SetMonPersonalitySafely(struct Pokemon *mon, u32 personality);

src/data/graphics/items.h

IMPORTANT NOTE: there are two versions to this step depending if you have an up-to-date pokeemerald decomp that uses INCGFX or an older one that still uses INCBIN.

IF YOU HAVE AN UP-TO-DATE VERSION THAT USES INCGFX

add to the end of the file:


// ITEM_SHINING_BALL
const u32 gItemIcon_ShiningBall[] = INCGFX_U32("graphics/items/icons/shining_ball.png", ".4bpp.lz");
const u32 gItemIconPalette_ShiningBall[] = INCGFX_U32("graphics/items/icon_palettes/shining_ball.pal", ".gbapal.lz");

OR

IF YOU HAVE AN OLDER VERSION THAT STILL USES INCBIN

add to the end of the file:


// ITEM_SHINING_BALL
const u32 gItemIcon_ShiningBall[] = INCBIN_U32("graphics/items/icons/shining_ball.4bpp.lz");
const u32 gItemIconPalette_ShiningBall[] = INCBIN_U32("graphics/items/icon_palettes/shining_ball.gbapal.lz");


src/data/graphics/pokeballs.c

IMPORTANT NOTE: there are two versions to this step depending if you have an up-to-date pokeemerald decomp that uses INCGFX or an older one that still uses INCBIN.

IF YOU HAVE AN UP-TO-DATE VERSION THAT USES INCGFX

add to the end of file:


// ITEM_SHINING_BALL
const u32 gBallGfx_Shining[] = INCGFX_U32("graphics/balls/shining.png", ".4bpp.lz");
const u32 gBallPal_Shining[] = INCGFX_U32("graphics/balls/shining.pal", ".gbapal.lz");

OR

IF YOU HAVE AN OLDER VERSION THAT STILL USES INCBIN

add to the end of the file:


// ITEM_SHINING_BALL
const u32 gBallGfx_Shining[] = INCBIN_U32("graphics/balls/shining.4bpp.lz");
const u32 gBallPal_Shining[] = INCBIN_U32("graphics/balls/shining.gbapal.lz");


src/data/text/item_descriptions.h

add to the end of the file


static const u8 sShiningBallDesc[] = _(
    "A super-rare BALL\n"
    "that somehow makes\n"
    "a Pokémon shiny!");

note: if you store custom item descriptions in a different file, add it there instead.


src/data/items.h

delete the entire section for [ITEM_034] = including both { and },

add after the section for [ITEM_PREMIER_BALL] =


    [ITEM_SHINING_BALL] =
    {
        .name = _("SHINING BALL"),
        .itemId = ITEM_SHINING_BALL,
        .price = 0,
        .description = sShiningBallDesc,
        .pocket = POCKET_POKE_BALLS,
        .type = ITEM_SHINING_BALL - FIRST_BALL,
        .fieldUseFunc = ItemUseOutOfBattle_CannotUse,
        .battleUsage = ITEM_B_USE_OTHER,
        .battleUseFunc = ItemUseInBattle_PokeBall,
        .secondaryId = ITEM_SHINING_BALL - FIRST_BALL,
},


src/data/item_icon_table.h

add after [ITEM_PREMIER_BALL] = {gItemIcon_PremierBall, gItemIconPalette_LuxuryBall},

    [ITEM_SHINING_BALL] = {gItemIcon_ShiningBall, gItemIconPalette_ShiningBall},

then delete line [ITEM_034] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark},


src/battle_anim_throw.c

add after #define TAG_PARTICLES_PREMIERBALL 55031

#define TAG_PARTICLES_SHININGBALL 55032

__

in function static const struct CompressedSpriteSheet sBallParticleSpriteSheets[POKEBALL_COUNT] =
add after [BALL_PREMIER] = {gBattleAnimSpriteGfx_Particles, 0x100, TAG_PARTICLES_PREMIERBALL},

    [BALL_SHINING] = {gBattleAnimSpriteGfx_Particles, 0x100, TAG_PARTICLES_SHININGBALL},

__

in function static const struct CompressedSpritePalette sBallParticlePalettes[POKEBALL_COUNT] =
add after [BALL_PREMIER] = {gBattleAnimSpritePal_CircleImpact, TAG_PARTICLES_PREMIERBALL},

    [BALL_SHINING] = {gBattleAnimSpritePal_CircleImpact, TAG_PARTICLES_SHININGBALL},

__

in function static const u8 sBallParticleAnimNums[POKEBALL_COUNT] =
add after [BALL_PREMIER] = 4,

    [BALL_SHINING] = 1,

__

in function static const TaskFunc sBallParticleAnimationFuncs[POKEBALL_COUNT] =

add after [BALL_PREMIER] = PremierBallOpenParticleAnimation,

    [BALL_SHINING] = MasterBallOpenParticleAnimation,

__

in function static const struct SpriteTemplate sBallParticleSpriteTemplates[POKEBALL_COUNT] =

add after the section for [BALL_PREMIER] = { ... }

    [BALL_SHINING] = {
        .tileTag = TAG_PARTICLES_SHININGBALL,
        .paletteTag = TAG_PARTICLES_SHININGBALL,
        .oam = &gOamData_AffineOff_ObjNormal_8x8,
        .anims = sAnims_BallParticles,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },

__

in function const u16 gBallOpenFadeColors[] =

add after [BALL_PREMIER] = RGB(31, 9, 10),

    [BALL_SHINING] = RGB(31, 27, 8),

__

in function u8 ItemIdToBallId(u16 ballItem)
add after return BALL_PREMIER; (line 754 in an un-modified file)

    case ITEM_SHINING_BALL:
        return BALL_SHINING;

__

in function static void SpriteCB_Ball_Capture_Step(struct Sprite *sprite)

add after the final else if section and before the final } of the function (else if (sprite->sTimer == 315) { ... })


    if (gLastUsedItem == ITEM_SHINING_BALL)
    {
        struct Pokemon *mon =
            &gEnemyParty[gBattlerPartyIndexes[gBattleAnimTarget]];

        TryShinyAnimation(gBattleAnimTarget, mon);
    }

src/battle_script_commands.c

in function static void Cmd_handleballthrow(void) (at line 9905 in an unmodified file)

in the first else section
add inbetween u8 catchRate; and if (gLastUsedItem == ITEM_SAFARI_BALL)


        if (gLastUsedItem == ITEM_SHINING_BALL)
        {
            struct Pokemon *mon;
            u32 otId;
            u32 personality;

            mon = &gEnemyParty[gBattlerPartyIndexes[gBattlerTarget]];

            otId = GetMonData(mon, MON_DATA_OT_ID);

            do
            {
                personality = Random32();
            } while (!IsShinyOtIdPersonality(otId, personality));

            if (!SetMonPersonalitySafely(mon, personality))
            {
            gBattlescriptCurrInstr = BattleScript_BallThrow;
            return;
            }

            CalculateMonStats(mon);

            if (!IsMonShiny(mon))
            {
            gBattlescriptCurrInstr = BattleScript_BallThrow;
            return;
            }

        SetMonData(mon, MON_DATA_POKEBALL, &gLastUsedItem);

        BtlController_EmitBallThrowAnim(B_COMM_TO_CONTROLLER, BALL_3_SHAKES_SUCCESS);

        MarkBattlerForControllerExec(gActiveBattler);

        gBattlescriptCurrInstr = BattleScript_SuccessBallThrow;

        if (CalculatePlayerPartyCount() == PARTY_SIZE)
            gBattleCommunication[MULTISTRING_CHOOSER] = 0;
        else
            gBattleCommunication[MULTISTRING_CHOOSER] = 1;
        gBattleSpritesDataPtr->healthBoxesData[gBattlerTarget].triedShinyMonAnim = FALSE;
        gBattleSpritesDataPtr->healthBoxesData[gBattlerTarget].finishedShinyMonAnim = FALSE;

        return;
        }


src/pokeball.c

add after #define GFX_TAG_PREMIER_BALL 55011

#define GFX_TAG_SHINING_BALL 55012

in function const struct CompressedSpriteSheet gBallSpriteSheets[POKEBALL_COUNT] =

add after [BALL_PREMIER] = {gBallGfx_Premier, 384, GFX_TAG_PREMIER_BALL},

    [BALL_SHINING] = {gBallGfx_Shining, 384, GFX_TAG_SHINING_BALL},

in function const struct CompressedSpritePalette gBallSpritePalettes[POKEBALL_COUNT] =

add after [BALL_PREMIER] = {gBallPal_Premier, GFX_TAG_PREMIER_BALL},

    [BALL_SHINING] = {gBallPal_Shining, GFX_TAG_SHINING_BALL},

in function const struct SpriteTemplate gBallSpriteTemplates[POKEBALL_COUNT] =

add after section for [BALL_PREMIER] = { ... },

    [BALL_SHINING] =
    {
        .tileTag = GFX_TAG_SHINING_BALL,
        .paletteTag = GFX_TAG_SHINING_BALL,
        .oam = &sBallOamData,
        .anims = sBallAnimSequences,
        .images = NULL,
        .affineAnims = sAffineAnim_BallRotate,
        .callback = SpriteCB_BallThrow,
    },

src/pokemon.c

add after the section for function static union PokemonSubstruct *GetSubstruct(struct BoxPokemon *boxMon, u32 personality, u8 substructType)

bool8 SetMonPersonalitySafely(struct Pokemon *mon, u32 personality)
{
    struct BoxPokemon *boxMon = &mon->box;
    union PokemonSubstruct substructs[4];
    u32 oldPersonality = boxMon->personality;
    u8 i;

    if (oldPersonality == personality)
        return TRUE;

    DecryptBoxMon(boxMon);

    if (CalculateBoxMonChecksum(boxMon) != boxMon->checksum)
    {
        EncryptBoxMon(boxMon);
        return FALSE;
    }

    for (i = 0; i < ARRAY_COUNT(substructs); i++)
    {
        memcpy(&substructs[i],
               GetSubstruct(boxMon, oldPersonality, i),
               sizeof(union PokemonSubstruct));
    }

    boxMon->personality = personality;

    for (i = 0; i < ARRAY_COUNT(substructs); i++)
    {
        memcpy(GetSubstruct(boxMon, personality, i),
               &substructs[i],
               sizeof(union PokemonSubstruct));
    }

    boxMon->checksum = CalculateBoxMonChecksum(boxMon);

    EncryptBoxMon(boxMon);

    return TRUE;
}

For best results, run make clean before running make

This will clear out any potential artifact errors