2 Show a throbber animation while the game is saving
meejle edited this page 2023-03-19 21:52:34 +00:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Credit to me (meejle), and to Anon822 for helping me finish it off when I got stuck. 😬

ezgif com-video-to-gif

Step 1: Add throbber.png to your graphics/text_window folder

Get it here.

It's designed to fit with the default Pokémon Emerald text window, so you might want to redesign it. Just keep to the usual 16-colour palette, and keep each frame of animation contained within a 32×64px "block" (like below), and you can't go wrong.

Screenshot from 2023-03-19 17-45-42

Step 2: Make changes to src/start_menu.c

First, add #include "decompress.h" to the includes at the top of the file.

Then, add this big block of code to start_menu.c. It doesn't matter where you put it, as long as it comes before static u8 SaveSavingMessageCallback(void). I put it at the top, just above // Menu actions.

#define TAG_THROBBER 0x1000
static const u16 sThrobber_Pal[] = INCBIN_U16("graphics/text_window/throbber.gbapal");
const u32 gThrobber_Gfx[] = INCBIN_U32("graphics/text_window/throbber.4bpp.lz");
static u8 spriteId;

static const struct OamData sOam_Throbber =
{
    .y = DISPLAY_HEIGHT,
    .affineMode = ST_OAM_AFFINE_OFF,
    .objMode = ST_OAM_OBJ_NORMAL,
    .mosaic = FALSE,
    .bpp = ST_OAM_4BPP,
    .shape = SPRITE_SHAPE(32x64),
    .x = 0,
    .matrixNum = 0,
    .size = SPRITE_SIZE(32x64),
    .tileNum = 0,
    .priority = 0,
    .paletteNum = 0,
    .affineParam = 0,
};

static const union AnimCmd sAnim_Throbber[] =
{
    ANIMCMD_FRAME(0, 4),
    ANIMCMD_FRAME(32, 4),
    ANIMCMD_FRAME(64, 4),
    ANIMCMD_FRAME(96, 4),
    ANIMCMD_FRAME(128, 4),
    ANIMCMD_FRAME(160, 4),
    ANIMCMD_FRAME(192, 4),
    ANIMCMD_FRAME(224, 4),
    ANIMCMD_JUMP(0),
};

static const union AnimCmd * const sAnims_Throbber[] = { sAnim_Throbber, };

static const struct CompressedSpriteSheet sSpriteSheet_Throbber[] =
{
    {
        .data = gThrobber_Gfx,
        .size = 0x3200,
        .tag = TAG_THROBBER
    },
    {}
};

static const struct SpritePalette sSpritePalettes_Throbber[] =
{
    {
        .data = sThrobber_Pal,
        .tag = TAG_THROBBER
    },
    {},
};

static const struct SpriteTemplate sSpriteTemplate_Throbber =
{
    .tileTag = TAG_THROBBER,
    .paletteTag = TAG_THROBBER,
    .oam = &sOam_Throbber,
    .anims = sAnims_Throbber,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCallbackDummy
};

void ShowThrobber(void)
{
    LoadCompressedSpriteSheet(&sSpriteSheet_Throbber[0]);
    LoadSpritePalettes(sSpritePalettes_Throbber);

    // 217 and 123 are the x and y coordinates (in pixels)
    spriteId = CreateSprite(&sSpriteTemplate_Throbber, 217, 123, 2);
};

Next, find the SaveSavingMessageCallback function. We just need to add ShowThrobber(); to the very top of the function, like this:

static u8 SaveSavingMessageCallback(void)
{
    ShowThrobber();
    ShowSaveMessage(gText_SavingDontTurnOff, SaveDoSaveCallback);
    return SAVE_IN_PROGRESS;
}

Finally, in the SaveDoSaveCallback function, we just need to replace this if statement:

    if (saveStatus == SAVE_STATUS_OK)
        ShowSaveMessage(gText_PlayerSavedGame, SaveSuccessCallback);
    else
        ShowSaveMessage(gText_SaveError, SaveErrorCallback);

With this one:

    if (saveStatus == SAVE_STATUS_OK)
    {
        ShowSaveMessage(gText_PlayerSavedGame, SaveSuccessCallback);
        DestroySprite(&gSprites[spriteId]);
    }
    else
    {
        ShowSaveMessage(gText_SaveError, SaveErrorCallback);
        DestroySprite(&gSprites[spriteId]);
    }

Step 3: Make changes to src/save.c

We're almost done. In save.c, right before the WriteSaveSectorOrSlot function, we'll add this:

static void VBlankCB_Saving(void)
{
    AnimateSprites();
    BuildOamBuffer();
    LoadOam();
    ProcessSpriteCopyRequests();
}

Then, in the WriteSaveSectorOrSlot function itself, we need to add IntrCallback prevVblankCB; right at the top, so it looks like this:

static u8 WriteSaveSectorOrSlot(u16 sectorId, const struct SaveSectorLocation *locations)
{
    IntrCallback prevVblankCB;
    u32 status;
    u16 i;

And finally, we're going to add some lines before and after this for statement:

        for (i = 0; i < NUM_SECTORS_PER_SLOT; i++)
            HandleWriteSector(i, locations);

So that it looks like this:

        prevVblankCB = gMain.vblankCallback;
        SetVBlankCallback(VBlankCB_Saving);
        for (i = 0; i < NUM_SECTORS_PER_SLOT; i++)
            HandleWriteSector(i, locations);
        SetVBlankCallback(prevVblankCB);

Then build, save, and enjoy!