#include "global.h" #include "gflib.h" #include "data.h" #include "decompress.h" #include "dynamic_placeholder_text_util.h" #include "item.h" #include "item_menu_icons.h" #include "menu.h" #include "new_menu_helpers.h" #include "pokemon_special_anim_internal.h" #include "strings.h" #include "text_window.h" #include "trig.h" #include "constants/songs.h" static void LoadBgGfxByAnimType(u16 animType); static void Task_ZoomAnim(u8 taskId); static void SetSpriteWithCloseness(struct Sprite * sprite, u8 closeness); static bool8 IsZoomSpriteCBActive(struct Sprite * sprite); static void MonSpriteZoom_UpdateYPos(struct Sprite * sprite, u8 closeness); static void ItemSpriteZoom_UpdateYPos(struct Sprite * sprite, u8 closeness); static void StartMonWiggleAnim(struct PokemonSpecialAnimScene * scene, u8 frameLen, u8 niter, u8 amplitude); static void StopMonWiggleAnim(struct PokemonSpecialAnimScene * scene); static void SpriteCallback_MonSpriteWiggle(struct Sprite * sprite); static void LoadMonSpriteGraphics(u16 *tilees, u16 *palette); static struct Sprite * PSA_CreateItemIconObject(u16 itemId); static u16 GetBlendColorByItemId(u16 itemId); static void Task_ItemUseOnMonAnim(u8 taskId); static void CreateSprites_UseItem_OutwardSpiralDots(u8 taskId, s16 *data, struct Sprite * sprite); static void SpriteCB_OutwardSpiralDots(struct Sprite * sprite); static void InitItemIconSpriteState(struct PokemonSpecialAnimScene * scene, struct Sprite * sprite, u8 closeness); static void MachineSetWobbleInit(void); static void MachineSetWobble_SetCB(struct Sprite * sprite); static bool8 MachineSetWobbleCBIsRunning(void); static void SpriteCB_MachineSetWobble(struct Sprite * sprite); static void StartZoomOutAnimForUseTM(u8 closeness); static void CreateStarSprites(struct PokemonSpecialAnimScene * scene); static bool8 AnyStarSpritesActive(void); static void SpriteCB_Star(struct Sprite * sprite); static void PSAScene_SeedRandomInTask(struct PokemonSpecialAnimScene * scene); static void StopMakingOutwardSpiralDots(void); static void Task_UseItem_OutwardSpiralDots(u8 taskId); static u16 PSAScene_RandomFromTask(u8 taskId); static void SpriteCallback_UseItem_OutwardSpiralDots(struct Sprite * sprite); static void LoadOutwardSpiralDotsGfx(void); static bool32 IsOutwardSpiralDotsTaskRunning(void); static void Task_LevelUpVerticalSprites(u8 taskId); static void CreateLevelUpVerticalSprite(u8 taskId, s16 *data); static void SpriteCB_LevelUpVertical(struct Sprite * sprite); static const u16 sBgPals_PSA_Any[] = INCBIN_U16("graphics/pokemon_special_anim/unk_845963C.gbapal"); static const u16 sBgPals_PSA_Anim4[] = INCBIN_U16("graphics/pokemon_special_anim/unk_845965C.gbapal"); static const u32 sBg3Tiles_PSA[] = INCBIN_U32("graphics/pokemon_special_anim/unk_845967C.4bpp.lz"); static const u32 sBg3Map_PSA[] = INCBIN_U32("graphics/pokemon_special_anim/unk_845973C.bin.lz"); static const u16 sSpritePals_LevelUpVertical[] = INCBIN_U16("graphics/pokemon_special_anim/unk_8459868.gbapal"); static const u32 sSpriteTiles_LevelUpVertical[] = INCBIN_U32("graphics/pokemon_special_anim/unk_8459888.4bpp.lz"); static const u16 sSpritePals_Star[] = INCBIN_U16("graphics/pokemon_special_anim/unk_84598A4.gbapal"); static const u32 sSpriteTiles_Star[] = INCBIN_U32("graphics/pokemon_special_anim/unk_84598C4.4bpp.lz"); static const u16 sSpritePals_UseItem_OutwardSpiralDots[] = INCBIN_U16("graphics/pokemon_special_anim/unk_8459940.gbapal"); static const u32 sSpriteTiles_UseItem_OutwardSpiralDots[] = INCBIN_U32("graphics/pokemon_special_anim/unk_8459960.4bpp.lz"); static const struct BgTemplate sBgTemplates[] = { { .bg = 0, .charBaseIndex = 0, .mapBaseIndex = 31, .screenSize = 0, .paletteMode = 0, .priority = 0, .baseTile = 0x001 }, { .bg = 3, .charBaseIndex = 3, .mapBaseIndex = 28, .screenSize = 0, .paletteMode = 0, .priority = 3, .baseTile = 0x000 } }; static const struct WindowTemplate sWindowTemplates[] = { { .bg = 0, .tilemapLeft = 1, .tilemapTop = 15, .width = 28, .height = 4, .paletteNum = 15, .baseBlock = 0x00a }, DUMMY_WIN_TEMPLATE }; static const u8 *const s1_2_and_Poof_textPtrs[] = { gUnknown_841B2ED, // 1, gUnknown_841B2F1, // 2, and ‥ ‥ ‥ gUnknown_841B2FF, // Poof! }; static const u16 sUnref_84599A4[] = { 0, 16, 68 }; static const u16 sAffineScales[] = { 0x100, 0x155, 0x1AA, 0x200 }; static const s8 sStarCoordOffsets[][2] = { {-8, -8}, { 6, -13}, { 8, -8} }; static const struct CompressedSpriteSheet sSpriteSheet_Star = { sSpriteTiles_Star, 0x80, 2 }; static const struct SpritePalette sSpritePalette_Star = { sSpritePals_Star, 2 }; static const struct CompressedSpriteSheet sSpriteSheet_UseItem_OutwardSpiralDots = { sSpriteTiles_UseItem_OutwardSpiralDots, 0x60, 5 }; static const struct SpritePalette sSpritePalette_UseItem_OutwardSpiralDots = { sSpritePals_UseItem_OutwardSpiralDots, 5 }; static const struct OamData sOamData_MonSprite = { .y = 0, .affineMode = ST_OAM_AFFINE_DOUBLE, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(64x64), .x = 0, .matrixNum = 0, .size = SPRITE_SIZE(64x64), .tileNum = 0x000, .priority = 1, .paletteNum = 0 }; static const union AffineAnimCmd gUnknown_84599E0[] = { AFFINEANIMCMD_FRAME(0x100, 0x100, 0, 0), AFFINEANIMCMD_END }; static const union AffineAnimCmd gUnknown_84599F0[] = { AFFINEANIMCMD_FRAME(0x155, 0x155, 0, 0), AFFINEANIMCMD_END }; static const union AffineAnimCmd gUnknown_8459A00[] = { AFFINEANIMCMD_FRAME(0x1AA, 0x1AA, 0, 0), AFFINEANIMCMD_END }; static const union AffineAnimCmd gUnknown_8459A10[] = { AFFINEANIMCMD_FRAME(0x200, 0x200, 0, 0), AFFINEANIMCMD_END }; static const union AffineAnimCmd *const sAffineAnimTable_Zoom[] = { gUnknown_84599E0, gUnknown_84599F0, gUnknown_8459A00, gUnknown_8459A10 }; static const struct SpriteTemplate sSpriteTemplate_MonSprite = { .tileTag = 0, .paletteTag = 0, .oam = &sOamData_MonSprite, .anims = gDummySpriteAnimTable, .images = NULL, .affineAnims = sAffineAnimTable_Zoom, .callback = SpriteCallbackDummy }; static const union AffineAnimCmd sAffineAnim_ItemZoom_0[] = { AFFINEANIMCMD_FRAME(0x100, 0x100, 0, 0), AFFINEANIMCMD_FRAME(-28, -28, 0, 8), AFFINEANIMCMD_END }; static const union AffineAnimCmd sAffineAnim_ItemZoom_1[] = { AFFINEANIMCMD_FRAME(0x155, 0x155, 0, 0), AFFINEANIMCMD_FRAME(-37, -37, 0, 8), AFFINEANIMCMD_END }; static const union AffineAnimCmd sAffineAnim_ItemZoom_2[] = { AFFINEANIMCMD_FRAME(0x1AA, 0x1AA, 0, 0), AFFINEANIMCMD_FRAME(-47, -47, 0, 8), AFFINEANIMCMD_END }; static const union AffineAnimCmd sAffineAnim_ItemZoom_3[] = { AFFINEANIMCMD_FRAME(0x200, 0x200, 0, 0), AFFINEANIMCMD_FRAME(-56, -56, 0, 8), AFFINEANIMCMD_END }; static const union AffineAnimCmd *const sAffineAnimTable_ItemZoom[] = { sAffineAnim_ItemZoom_0, sAffineAnim_ItemZoom_1, sAffineAnim_ItemZoom_2, sAffineAnim_ItemZoom_3 }; static const struct OamData sOamData_LevelUpVertical = { .y = 0, .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_BLEND, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(8x16), .x = 0, .matrixNum = 0, .size = SPRITE_SIZE(8x16), .tileNum = 0x000, .priority = 1, .paletteNum = 0 }; static const union AnimCmd gUnknown_8459AC0[] = { ANIMCMD_FRAME(0, 3), ANIMCMD_END }; static const union AnimCmd *const sAnimTable_LevelUpVertical[] = { gUnknown_8459AC0 }; static const struct SpriteTemplate sSpriteTemplate_LevelUpVertical = { .tileTag = 0, .paletteTag = 0, .oam = &sOamData_LevelUpVertical, .anims = sAnimTable_LevelUpVertical, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCB_LevelUpVertical }; static const struct OamData sOamData_Star = { .y = 0, .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(16x16), .x = 0, .matrixNum = 0, .size = SPRITE_SIZE(16x16), .tileNum = 0x000, .priority = 1, .paletteNum = 0 }; static const struct SpriteTemplate sSpriteTemplate_Star = { .tileTag = 2, .paletteTag = 2, .oam = &sOamData_Star, .anims = gDummySpriteAnimTable, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCB_Star }; static const struct OamData sOamData_UseItem_OutwardSpiralDots = { .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 = 0x000, .priority = 0, .paletteNum = 0 }; static const union AnimCmd gUnknown_8459B0C[] = { ANIMCMD_FRAME(0, 16), ANIMCMD_FRAME(1, 8), ANIMCMD_FRAME(2, 4), ANIMCMD_END }; static const union AnimCmd gUnknown_8459B1C[] = { ANIMCMD_FRAME(1, 4), ANIMCMD_FRAME(0, 4), ANIMCMD_END }; static const union AnimCmd *const sAnimTable_UseItem_OutwardSpiralDots[] = { gUnknown_8459B0C, gUnknown_8459B1C }; static const struct SpriteTemplate sSpriteTemplate_UseItem_OutwardSpiralDots = { .tileTag = 5, .paletteTag = 5, .oam = &sOamData_UseItem_OutwardSpiralDots, .anims = sAnimTable_UseItem_OutwardSpiralDots, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallback_UseItem_OutwardSpiralDots }; void InitPokemonSpecialAnimScene(struct PokemonSpecialAnimScene * buffer, u16 animType) { FreeAllWindowBuffers(); ResetTempTileDataBuffers(); SetGpuReg(REG_OFFSET_DISPCNT, 0); ResetBgsAndClearDma3BusyFlags(FALSE); InitBgsFromTemplates(0, sBgTemplates, NELEMS(sBgTemplates)); InitWindows(sWindowTemplates); ChangeBgX(0, 0, 0); ChangeBgY(0, 0, 0); ChangeBgX(3, 0, 0); ChangeBgY(3, 0, 0); SetBgTilemapBuffer(0, buffer->field_0914); SetBgTilemapBuffer(3, buffer->field_1914); RequestDma3Fill(0, (void *)BG_VRAM, 0x20, DMA3_32BIT); FillBgTilemapBufferRect_Palette0(0, 0x000, 0, 0, 32, 32); LoadBgGfxByAnimType(animType); FillWindowPixelBuffer(0, PIXEL_FILL(0)); TextWindow_SetUserSelectedFrame(0, 0x000, 0xe0); CopyWindowToVram(0, COPYWIN_BOTH); ShowBg(0); ShowBg(3); HideBg(1); HideBg(2); CopyBgTilemapBufferToVram(0); CopyBgTilemapBufferToVram(3); SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP | DISPCNT_OBJ_ON); SetGpuReg(REG_OFFSET_BLDCNT, 0); } bool8 PokemonSpecialAnimSceneInitIsNotFinished(void) { if (!FreeTempTileDataBuffersIfPossible()) return IsDma3ManagerBusyWithBgCopy(); else return TRUE; } void PSA_FreeWindowBuffers(void) { FreeAllWindowBuffers(); } void PSA_ShowMessageWindow(void) { PutWindowTilemap(0); FillWindowPixelBuffer(0, PIXEL_FILL(1)); DrawTextBorderOuter(0, 0x001, 0xE); CopyWindowToVram(0, COPYWIN_BOTH); } void PSA_HideMessageWindow(void) { ClearWindowTilemap(0); ClearStdWindowAndFrameToTransparent(0, FALSE); CopyWindowToVram(0, COPYWIN_MAP); } void PSA_PrintMessage(u8 messageId) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); u16 itemId = PSA_GetItemId(); u16 strWidth = 0; u8 textSpeed = GetTextSpeedSetting(); struct Pokemon * pokemon = PSA_GetPokemon(); u16 level; u8 *str; switch (messageId) { case 0: // Item was used on Mon str = StringCopy(scene->textBuf, ItemId_GetName(itemId)); str = StringCopy(str, gUnknown_841B285); GetMonData(pokemon, MON_DATA_NICKNAME, str); StringAppend(scene->textBuf, gUnknown_841B293); break; case 1: // Mon's level was elevated to level level = GetMonData(pokemon, MON_DATA_LEVEL); GetMonData(pokemon, MON_DATA_NICKNAME, scene->textBuf); str = StringAppend(scene->textBuf, gUnknown_841B295); if (level < MAX_LEVEL) level++; str = ConvertIntToDecimalStringN(str, level, STR_CONV_MODE_LEFT_ALIGN, level < MAX_LEVEL ? 2 : 3); StringAppend(str, gUnknown_841B2A7); break; case 9: // Mon learned move DynamicPlaceholderTextUtil_Reset(); DynamicPlaceholderTextUtil_SetPlaceholderPtr(0, PSA_GetMonNickname()); DynamicPlaceholderTextUtil_SetPlaceholderPtr(1, PSA_GetNameOfMoveToTeach()); DynamicPlaceholderTextUtil_ExpandPlaceholders(scene->textBuf, gUnknown_841B32E); break; case 4: // poof! strWidth += GetStringWidth(2, gUnknown_841B2F1, -1); // fallthrough case 3: // 2 and... strWidth += GetStringWidth(2, gUnknown_841B2ED, -1); // fallthrough case 2: // 1 StringCopy(scene->textBuf, s1_2_and_Poof_textPtrs[messageId - 2]); textSpeed = 1; break; case 5: // Mon forgot move DynamicPlaceholderTextUtil_Reset(); DynamicPlaceholderTextUtil_SetPlaceholderPtr(0, PSA_GetMonNickname()); DynamicPlaceholderTextUtil_SetPlaceholderPtr(1, PSA_GetNameOfMoveForgotten()); DynamicPlaceholderTextUtil_ExpandPlaceholders(scene->textBuf, gUnknown_841B306); break; case 6: // And... StringCopy(scene->textBuf, gUnknown_841B315); break; case 7: // Machine set! StringCopy(scene->textBuf, gUnknown_841B31B); break; case 8: // Huh? StringCopy(scene->textBuf, gUnknown_841B329); break; default: return; } AddTextPrinterParameterized5(0, 2, scene->textBuf, strWidth, 0, textSpeed, NULL, 0, 4); } void PSA_AfterPoof_ClearMessageWindow(void) { FillWindowPixelBuffer(0, PIXEL_FILL(1)); CopyWindowToVram(0, COPYWIN_GFX); } bool8 PSA_IsMessagePrintTaskActive(void) { return IsTextPrinterActive(0); } void PSA_DarkenMonSprite(void) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); scene->state = 0; BlendPalettes((0x10000 << IndexOfSpritePaletteTag(0)) | 4, 16, RGB_BLACK); CreateStarSprites(scene); } bool8 PSA_RunPoofAnim(void) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); switch (scene->state) { case 0: if (!AnyStarSpritesActive()) { BeginNormalPaletteFade((0x10000 << IndexOfSpritePaletteTag(0)) | 4, -1, 16, 0, RGB_BLACK); scene->state++; } break; case 1: if (!gPaletteFade.active) return FALSE; break; } return TRUE; } void PSA_UseTM_SetUpZoomOutAnim(void) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); scene->state = 0; } void PSA_UseTM_CleanUpForCancel(void) { StopMakingOutwardSpiralDots(); ResetPaletteFadeControl(); } bool8 PSA_UseTM_RunZoomOutAnim(void) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); switch (scene->state) { case 0: StartZoomOutAnimForUseTM(0); scene->state++; break; case 1: if (!PSA_IsZoomTaskActive()) { scene->field_0004 = 0; scene->state++; } break; case 2: scene->field_0004++; if (scene->field_0004 > 20) scene->state++; break; case 3: StartMonWiggleAnim(scene, 1, 0, 1); scene->field_0004 = 0; scene->state++; break; case 4: scene->field_0004++; if (scene->field_0004 > 0) { scene->field_0004 = 0; PlaySE(SE_M_MEGA_KICK); BeginNormalPaletteFade(0x00000001, 2, 0, 12, RGB(8, 13, 31)); PSAScene_SeedRandomInTask(scene); scene->state++; } break; case 5: scene->field_0004++; if (scene->field_0004 > 70) { StopMonWiggleAnim(scene); BeginNormalPaletteFade(0x00000001, 6, 12, 0, RGB(8, 13, 31)); scene->field_0004 = 0; scene->state++; } break; case 6: scene->field_0004++; if (!IsOutwardSpiralDotsTaskRunning() && scene->field_0004 > 40) { scene->field_0004 = 0; scene->state++; } break; case 7: scene->field_0004++; if (scene->field_0004 > 20) { scene->state++; } break; case 8: PlaySE(SE_EXP_MAX); DestroySprite(scene->itemIconSprite); scene->state++; break; default: return FALSE; } return TRUE; } void PSA_UseTM_SetUpMachineSetWobble(void) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); scene->state = 0; } bool8 PSA_UseTM_RunMachineSetWobble(void) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); switch (scene->state) { case 0: MachineSetWobbleInit(); PlaySE(SE_SWITCH); scene->state++; break; case 1: return MachineSetWobbleCBIsRunning(); } return TRUE; } // There may once have been plans to put the battle level up // anim in with using Rare Candy, but they were scrapped // at a later stage of development UNUSED void PSA_CreateLevelUpVerticalSpritesTask(void) { CreateLevelUpVerticalSpritesTask(120, 56, 4, 4, 2, 0); } bool8 PSA_LevelUpVerticalSpritesTaskIsRunning(void) { return LevelUpVerticalSpritesTaskIsRunning(); } UNUSED void PSA_DrawLevelUpWindowPg1(u16 *statsBefore, u16 *statsAfter) { DrawTextBorderOuter(1, 0x001, 0xE); DrawLevelUpWindowPg1(1, statsBefore, statsAfter, TEXT_COLOR_WHITE, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_LIGHT_GRAY); PutWindowTilemap(1); CopyWindowToVram(1, COPYWIN_BOTH); } UNUSED void PSA_DrawLevelUpWindowPg2(u16 *currStats) { DrawLevelUpWindowPg2(1, currStats, TEXT_COLOR_WHITE, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_LIGHT_GRAY); CopyWindowToVram(1, COPYWIN_GFX); } UNUSED bool8 PSA_IsCopyingLevelUpWindowToVram(void) { return IsDma3ManagerBusyWithBgCopy(); } static void LoadBgGfxByAnimType(u16 animType) { CopyToBgTilemapBuffer(3, sBg3Map_PSA, 0, 0x000); DecompressAndCopyTileDataToVram(3, sBg3Tiles_PSA, 0, 0x000, 0); if (animType != 4) LoadPalette(sBgPals_PSA_Any, 0x00, 0x20); else LoadPalette(sBgPals_PSA_Anim4, 0x00, 0x20); } void PSA_CreateMonSpriteAtCloseness(u8 closeness) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); struct Pokemon * pokemon = PSA_GetPokemon(); u16 species = GetMonData(pokemon, MON_DATA_SPECIES); u32 personality = GetMonData(pokemon, MON_DATA_PERSONALITY); u8 r1 = Menu2_GetMonSpriteAnchorCoord(species, personality, 2); void * r6; void * r9; void * r4; u8 spriteId; if (r1 != 0xFF) { scene->monSpriteY1 = 0x48; scene->monSpriteY2 = r1 + 0x30; } else { scene->monSpriteY1 = 0x48; scene->monSpriteY2 = 0x60; } r6 = Alloc(0x2000); r9 = Alloc(0x2000); r4 = Alloc(0x100); if (r6 != NULL && r9 != NULL && r4 != NULL) { HandleLoadSpecialPokePic(&gMonFrontPicTable[species], r6, species, personality); LZ77UnCompWram(GetMonFrontSpritePal(pokemon), r4); LoadMonSpriteGraphics(r6, r4); spriteId = CreateSprite(&sSpriteTemplate_MonSprite, 120, scene->monSpriteY1, 4); if (spriteId != MAX_SPRITES) { scene->monSprite = &gSprites[spriteId]; MonSpriteZoom_UpdateYPos(scene->monSprite, closeness); } else scene->monSprite = NULL; scene->lastCloseness = closeness; } if (r6 != NULL) Free(r6); if (r9 != NULL) Free(r9); if (r4 != NULL) Free(r4); } #define tState data[0] #define tCurrCloseness data[1] #define tFinalCloseness data[2] #define tDeltaCloseness data[3] #define tTimer data[4] #define tDelay data[5] #define tOff_MonSprite 6 #define tHasItemSprite data[8] #define tOff_ItemSprite 9 void PSA_SetUpZoomAnim(u8 closeness) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); u8 taskId; if (closeness != scene->lastCloseness) { taskId = CreateTask(Task_ZoomAnim, 4); SetWordTaskArg(taskId, tOff_MonSprite, (uintptr_t)scene->monSprite); gTasks[taskId].tCurrCloseness = scene->lastCloseness; gTasks[taskId].tFinalCloseness = closeness; gTasks[taskId].tDelay = 6; if (closeness > scene->lastCloseness) gTasks[taskId].tDeltaCloseness = 1; else gTasks[taskId].tDeltaCloseness = -1; } } bool8 PSA_IsZoomTaskActive(void) { return FuncIsActiveTask(Task_ZoomAnim); } static void Task_ZoomAnim(u8 taskId) { s16 *data = gTasks[taskId].data; struct Sprite * sprite = (void *)GetWordTaskArg(taskId, tOff_MonSprite); switch (tState) { case 0: SetSpriteWithCloseness(sprite, tCurrCloseness); if (tHasItemSprite) SetSpriteWithCloseness((void *)GetWordTaskArg(taskId, tOff_ItemSprite), tCurrCloseness); tCurrCloseness += tDeltaCloseness; tState++; break; case 1: if (!IsZoomSpriteCBActive(sprite)) { PlaySE(SE_BALL_TRAY_EXIT); MonSpriteZoom_UpdateYPos(sprite, tCurrCloseness); if (tHasItemSprite) ItemSpriteZoom_UpdateYPos((void *)GetWordTaskArg(taskId, tOff_ItemSprite), tCurrCloseness); if (tCurrCloseness == tFinalCloseness) { PSA_GetSceneWork()->lastCloseness = tFinalCloseness; DestroyTask(taskId); } else { tTimer = 0; tState = 2; } } break; case 2: tTimer++; if (tTimer > tDelay) tState = 0; break; } } static void SetSpriteWithCloseness(struct Sprite * sprite, u8 closeness) { sprite->data[0] = 0; sprite->data[1] = 0; sprite->data[2] = closeness; } static bool8 IsZoomSpriteCBActive(struct Sprite * sprite) { return sprite->callback != SpriteCallbackDummy; } static s16 GetSpriteOffsetByScale(s16 pos, u8 closeness) { return (pos * sAffineScales[closeness]) >> 8; } // FIXME: better math static u16 GetYPosByScale(u16 pos) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); s32 v = ((((((scene->monSpriteY2 - scene->monSpriteY1) << 16) >> 8) / 256 * (pos - 256)) << 8) >> 16); return v += scene->monSpriteY1; } static void MonSpriteZoom_UpdateYPos(struct Sprite * sprite, u8 closeness) { if (closeness > 3) closeness = 3; PSA_GetSceneWork(); // return value not used StartSpriteAffineAnim(sprite, closeness); sprite->y = GetYPosByScale(sAffineScales[closeness]); } static void ItemSpriteZoom_UpdateYPos(struct Sprite * sprite, u8 closeness) { MonSpriteZoom_UpdateYPos(sprite, closeness); sprite->x2 = GetSpriteOffsetByScale(sprite->data[6] - 32, closeness); sprite->y2 = GetSpriteOffsetByScale(sprite->data[7] - 32, closeness); } static void StartMonWiggleAnim(struct PokemonSpecialAnimScene * scene, u8 frameLen, u8 niter, u8 amplitude) { // frameLen: frame duration // niter = 0: iterate ad infinitum scene->monSprite->data[0] = frameLen; scene->monSprite->data[1] = niter; scene->monSprite->data[2] = amplitude; scene->monSprite->callback = SpriteCallback_MonSpriteWiggle; } static void StopMonWiggleAnim(struct PokemonSpecialAnimScene * scene) { scene->monSprite->x2 = 0; scene->monSprite->callback = SpriteCallbackDummy; } static void SpriteCallback_MonSpriteWiggle(struct Sprite * sprite) { sprite->data[7]++; if (sprite->data[7] > sprite->data[0]) { sprite->data[7] = 0; sprite->data[6]++; if (sprite->data[1] != 0 && sprite->data[6] >= sprite->data[1]) { sprite->x2 = 0; sprite->callback = SpriteCallbackDummy; } else if (sprite->data[6] & 1) sprite->x2 = sprite->data[2]; else sprite->x2 = -sprite->data[2]; } } static void LoadMonSpriteGraphics(u16 *tiles, u16 *palette) { struct SpriteSheet spriteSheet; struct SpritePalette spritePalette; spriteSheet.data = tiles; spriteSheet.size = 0x800; spriteSheet.tag = 0; spritePalette.data = palette; spritePalette.tag = 0; LoadSpriteSheet(&spriteSheet); LoadSpritePalette(&spritePalette); } #undef tOff_ItemSprite #undef tHasItemSprite #undef tOff_MonSprite #undef tDelay #undef tTimer #undef tDeltaCloseness #undef tFinalCloseness #undef tCurrCloseness #undef tState #define tState data[0] #define tTimer data[1] #define tCloseness data[2] #define tYpos data[3] #define tOff_ItemSprite 4 #define tData6 data[6] #define tData7 data[7] #define tActiveSprCt data[8] #define tBlendColor data[9] #define tTimerReset data[10] #define tSuppressDots data[11] void PSA_SetUpItemUseOnMonAnim(u16 itemId, u8 closeness, bool32 a2) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); u8 taskId; scene->itemIconSprite = PSA_CreateItemIconObject(itemId); if (scene->itemIconSprite != NULL) { InitItemIconSpriteState(scene, scene->itemIconSprite, closeness); StartSpriteAffineAnim(scene->itemIconSprite, closeness); scene->itemIconSprite->invisible = TRUE; taskId = CreateTask(Task_ItemUseOnMonAnim, 2); SetWordTaskArg(taskId, tOff_ItemSprite, (uintptr_t)scene->itemIconSprite); gTasks[taskId].tCloseness = closeness; gTasks[taskId].tYpos = GetYPosByScale(sAffineScales[closeness]); gTasks[taskId].tData6 = a2; gTasks[taskId].tBlendColor = GetBlendColorByItemId(itemId); } } static u16 GetBlendColorByItemId(u16 itemId) { return RGB_WHITE; } void CreateItemIconSpriteAtMaxCloseness(u16 itemId) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); scene->itemIconSprite = PSA_CreateItemIconObject(itemId); if (scene->itemIconSprite != NULL) { StartSpriteAffineAnim(scene->itemIconSprite, 3); InitItemIconSpriteState(scene, scene->itemIconSprite, 3); } } static struct Sprite * PSA_CreateItemIconObject(u16 itemId) { u8 spriteId; struct Sprite * sprite; spriteId = AddItemIconObject(1, 1, itemId); if (spriteId == MAX_SPRITES) return NULL; gSprites[spriteId].oam.affineMode = ST_OAM_AFFINE_DOUBLE; gSprites[spriteId].oam.priority = 1; gSprites[spriteId].subpriority = 1; gSprites[spriteId].affineAnims = sAffineAnimTable_Zoom; gSprites[spriteId].callback = SpriteCallbackDummy; InitSpriteAffineAnim(&gSprites[spriteId]); return &gSprites[spriteId]; } bool8 PSA_IsItemUseOnMonAnimActive(void) { return FuncIsActiveTask(Task_ItemUseOnMonAnim); } static void Task_ItemUseOnMonAnim(u8 taskId) { s16 *data = gTasks[taskId].data; struct Sprite * sprite = (void *)GetWordTaskArg(taskId, tOff_ItemSprite); switch (tState) { case 0: tTimer++; if (tTimer > 20) { tTimer = 0; PlaySE(SE_M_SWAGGER2); sprite->invisible = FALSE; if (!tSuppressDots) LoadOutwardSpiralDotsGfx(); tState = 1; } break; case 1: tTimer++; if (tTimer > 30) { tTimer = 0; sprite->affineAnims = sAffineAnimTable_ItemZoom; StartSpriteAffineAnim(sprite, tCloseness); BeginNormalPaletteFade(0x10000 << IndexOfSpritePaletteTag(1), -2, 0, 12, tBlendColor); tState = 2; PlaySE(SE_M_MILK_DRINK); } break; case 2: if (sprite->affineAnimEnded) { sprite->invisible = TRUE; tTimerReset = 20; tState = 3; } break; case 3: tTimer++; if (tTimer > tTimerReset) { tTimer = 0; if (!tSuppressDots) CreateSprites_UseItem_OutwardSpiralDots(taskId, data, sprite); if (tData7 == 0) PlaySE(SE_M_REVERSAL); tData7++; if (tData7 > 2) tState = 4; else tTimerReset = 8; } break; case 4: if (tActiveSprCt == 0) { if (tData6) DestroySprite(sprite); DestroyTask(taskId); } break; } } static void CreateSprites_UseItem_OutwardSpiralDots(u8 taskId, s16 *data, struct Sprite * sprite) { int x = sprite->x + sprite->x2 - 4; int y = sprite->y + sprite->y2 - 4; u8 spriteId; int i; BlendPalettes(0x10000 << IndexOfSpritePaletteTag(5), 16, tBlendColor); for (i = 0; i < 15; i++) { spriteId = CreateSprite(&sSpriteTemplate_UseItem_OutwardSpiralDots, x, y, 0); if (spriteId != MAX_SPRITES) { gSprites[spriteId].data[1] = i << 4; gSprites[spriteId].data[7] = taskId; gSprites[spriteId].callback = SpriteCB_OutwardSpiralDots; StartSpriteAnim(&gSprites[spriteId], 1); tActiveSprCt++; } } } static void SpriteCB_OutwardSpiralDots(struct Sprite * sprite) { s16 *data = sprite->data; if (data[0] < 16) { data[0]++; data[1] += 7; data[1] &= 0xFF; data[2] += 4; sprite->x2 = (data[2] * gSineTable[data[1] + 0x40]) >> 8; sprite->y2 = (data[2] * gSineTable[data[1]]) >> 8; } else { gTasks[data[7]].tActiveSprCt--; DestroySprite(sprite); } } void PSA_UseItem_CleanUpForCancel(void) { u8 taskId = FindTaskIdByFunc(Task_ItemUseOnMonAnim); if (taskId != 0xFF) { gTasks[taskId].tSuppressDots = TRUE; } } static void InitItemIconSpriteState(struct PokemonSpecialAnimScene * scene, struct Sprite * sprite, u8 closeness) { u16 species, x, y; u32 personality; if (closeness == 3) { sprite->x = 120; sprite->y = scene->monSpriteY2; } else { sprite->x = 120; sprite->y = scene->monSpriteY1; } sprite->x += 4; sprite->y += 4; species = PSA_GetMonSpecies(); personality = PSA_GetMonPersonality(); switch (PSA_GetAnimType()) { case 4: { x = Menu2_GetMonSpriteAnchorCoord(species, personality, 0); y = Menu2_GetMonSpriteAnchorCoord(species, personality, 1); if (x == 0xFF) x = 0; if (y == 0xFF) y = 0; sprite->data[6] = x; sprite->data[7] = y; break; } default: { x = Menu2_GetMonSpriteAnchorCoord(species, personality, 3); y = Menu2_GetMonSpriteAnchorCoord(species, personality, 4); if (x == 0xFF) x = 0; if (y == 0xFF) y = 0; sprite->data[6] = x; sprite->data[7] = y; break; } } ItemSpriteZoom_UpdateYPos(sprite, closeness); } #undef tSuppressDots #undef tTimerReset #undef tBlendColor #undef tActiveSprCt #undef tData7 #undef tData6 #undef tOff_ItemSprite #undef tYpos #undef tCloseness #undef tTimer #undef tState static void MachineSetWobbleInit(void) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); MachineSetWobble_SetCB(scene->monSprite); MachineSetWobble_SetCB(scene->itemIconSprite); } static void MachineSetWobble_SetCB(struct Sprite * sprite) { sprite->data[0] = 0; sprite->data[1] = 0; sprite->callback = SpriteCB_MachineSetWobble; } static bool8 MachineSetWobbleCBIsRunning(void) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); return scene->monSprite->callback != SpriteCallbackDummy; } static void SpriteCB_MachineSetWobble(struct Sprite * sprite) { switch (sprite->data[0]) { case 0: sprite->x += 3; sprite->data[0]++; break; case 1: sprite->data[1]++; if (sprite->data[1] > 30) { sprite->x -= 3; sprite->callback = SpriteCallbackDummy; } break; } } // Redefined from above #define tState data[0] #define tCurrCloseness data[1] #define tFinalCloseness data[2] #define tDeltaCloseness data[3] #define tTimer data[4] #define tDelay data[5] #define tOff_MonSprite 6 #define tHasItemSprite data[8] #define tOff_ItemSprite 9 static void StartZoomOutAnimForUseTM(u8 closeness) { struct PokemonSpecialAnimScene * scene = PSA_GetSceneWork(); u8 taskId; if (closeness != scene->lastCloseness) { taskId = CreateTask(Task_ZoomAnim, 1); SetWordTaskArg(taskId, tOff_MonSprite, (uintptr_t)scene->monSprite); SetWordTaskArg(taskId, tOff_ItemSprite, (uintptr_t)scene->itemIconSprite); gTasks[taskId].tCurrCloseness = scene->lastCloseness; gTasks[taskId].tFinalCloseness = closeness; gTasks[taskId].tHasItemSprite = 1; gTasks[taskId].tDelay = 6; if (closeness > scene->lastCloseness) gTasks[taskId].tDeltaCloseness = 1; else gTasks[taskId].tDeltaCloseness = -1; } } #undef tOff_ItemSprite #undef tHasItemSprite #undef tOff_MonSprite #undef tDelay #undef tTimer #undef tDeltaCloseness #undef tFinalCloseness #undef tCurrCloseness #undef tState static void CreateStarSprites(struct PokemonSpecialAnimScene * scene) { int i; u8 spriteId; u16 species; u32 personality; LoadCompressedSpriteSheet(&sSpriteSheet_Star); LoadSpritePalette(&sSpritePalette_Star); scene->field_0002 = 0; for (i = 0; i < 3; i++) { spriteId = CreateSprite(&sSpriteTemplate_Star, 120 + sStarCoordOffsets[i][0], scene->monSpriteY2 + sStarCoordOffsets[i][1], 2); if (spriteId != MAX_SPRITES) { species = PSA_GetMonSpecies(); personality = PSA_GetMonPersonality(); gSprites[spriteId].data[3] = sStarCoordOffsets[i][0] * 8; gSprites[spriteId].data[4] = sStarCoordOffsets[i][1] * 8; gSprites[spriteId].x += GetSpriteOffsetByScale(Menu2_GetMonSpriteAnchorCoordMinusx20(species, personality, 0), 3); gSprites[spriteId].y += GetSpriteOffsetByScale(Menu2_GetMonSpriteAnchorCoordMinusx20(species, personality, 1), 3); scene->field_0002++; } } } static u8 AnyStarSpritesActive(void) { return PSA_GetSceneWork()->field_0002; } static void SpriteCB_Star(struct Sprite * sprite) { sprite->data[0]++; if (sprite->data[0] < 10) { sprite->data[1] += sprite->data[3]; sprite->data[2] += sprite->data[4]; sprite->x2 = sprite->data[1] >> 4; sprite->y2 = sprite->data[2] >> 4; } else { PSA_GetSceneWork()->field_0002--; DestroySprite(sprite); } } // ======================================================== #define tState data[0] #define tTimer data[1] #define tActiveSprCt data[2] #define tOff_RngState 3 #define tAngle data[5] #define tMadeSprCt data[6] #define tsRadius data[0] #define tsSpeed data[1] #define tsXinit data[2] #define tsYinit data[3] #define tsXorig data[4] #define tsYorig data[5] #define tsTaskId data[6] static void PSAScene_SeedRandomInTask(struct PokemonSpecialAnimScene * scene) { u8 taskId; LoadOutwardSpiralDotsGfx(); taskId = CreateTask(Task_UseItem_OutwardSpiralDots, 1); SetWordTaskArg(taskId, tOff_RngState, 2022069025); gTasks[taskId].tAngle = 0xE0; } static void StopMakingOutwardSpiralDots(void) { u8 taskId = FindTaskIdByFunc(Task_UseItem_OutwardSpiralDots); if (taskId != 0xFF) gTasks[taskId].tState = 1; } static void Task_UseItem_OutwardSpiralDots(u8 taskId) { s16 *data = gTasks[taskId].data; switch (tState) { case 0: if (tTimer == 0) { u32 spriteId, x, y, x2, y2, ampl; struct Sprite * sprite = PSA_GetSceneWork()->itemIconSprite; x = sprite->x + sprite->x2; y = sprite->y + sprite->y2; ampl = (PSAScene_RandomFromTask(taskId) % 21) + 70; x2 = x + ((u32)(gSineTable[tAngle + 0x40] * ampl) >> 8); y2 = y + ((u32)(gSineTable[tAngle ] * ampl) >> 8); tAngle += 0x4C; tAngle &= 0xFF; spriteId = CreateSprite(&sSpriteTemplate_UseItem_OutwardSpiralDots, x2, y2, 0); if (spriteId != MAX_SPRITES) { gSprites[spriteId].tsRadius = 0; gSprites[spriteId].tsSpeed = (PSAScene_RandomFromTask(taskId) & 1) + 6; gSprites[spriteId].tsXinit = x2; gSprites[spriteId].tsYinit = y2; gSprites[spriteId].tsXorig = x; gSprites[spriteId].tsYorig = y; gSprites[spriteId].tsTaskId = taskId; tActiveSprCt++; } tMadeSprCt++; if (tMadeSprCt > 47) tState++; } else tTimer--; break; case 1: if (tActiveSprCt == 0) DestroyTask(taskId); break; } } static u16 PSAScene_RandomFromTask(u8 taskId) { u32 state = GetWordTaskArg(taskId, tOff_RngState); state = state * 1103515245 + 24691; SetWordTaskArg(taskId, tOff_RngState, state); return state >> 16; } static void SpriteCallback_UseItem_OutwardSpiralDots(struct Sprite * sprite) { int x; int y; sprite->tsRadius += sprite->tsSpeed; if (sprite->tsRadius > 255) { gTasks[sprite->tsTaskId].tActiveSprCt--; DestroySprite(sprite); } else { x = (sprite->tsXorig - sprite->tsXinit) * sprite->tsRadius; y = (sprite->tsYorig - sprite->tsYinit) * sprite->tsRadius; sprite->x = (x >> 8) + sprite->tsXinit; sprite->y = (y >> 8) + sprite->tsYinit; } } static void LoadOutwardSpiralDotsGfx(void) { LoadCompressedSpriteSheet(&sSpriteSheet_UseItem_OutwardSpiralDots); LoadSpritePalette(&sSpritePalette_UseItem_OutwardSpiralDots); } static bool32 IsOutwardSpiralDotsTaskRunning(void) { return FuncIsActiveTask(Task_UseItem_OutwardSpiralDots); } #undef tsTaskId #undef tsYorig #undef tsXorig #undef tsYinit #undef tsXinit #undef tsSpeed #undef tsRadius #undef tMadeSprCt #undef tAngle #undef tOff_RngState #undef tActiveSprCt #undef tTimer #undef tState // ======================================================== #define tState data[0] #define tActiveSprCt data[1] #define tMadeSprCt data[2] #define tTimer data[3] #define tXpos data[4] #define tYpos data[5] #define tTileTag data[6] #define tPaletteTag data[7] #define tPriority data[8] #define tSubpriority data[9] #define tsYsubpixel data[1] #define tsSpeed data[2] #define tsTaskId data[7] void CreateLevelUpVerticalSpritesTask(u16 x, u16 y, u16 tileTag, u16 paletteTag, u16 priority, u16 subpriority) { static struct CompressedSpriteSheet spriteSheet; static struct SpritePalette spritePalette; u8 taskId; spriteSheet.tag = tileTag; spriteSheet.data = sSpriteTiles_LevelUpVertical; spriteSheet.size = sSpriteTiles_LevelUpVertical[0] >> 8; spritePalette.data = sSpritePals_LevelUpVertical; spritePalette.tag = paletteTag; LoadCompressedSpriteSheet(&spriteSheet); LoadSpritePalette(&spritePalette); taskId = CreateTask(Task_LevelUpVerticalSprites, 0); gTasks[taskId].tXpos = x - 32; gTasks[taskId].tYpos = y + 32; gTasks[taskId].tTileTag = tileTag; gTasks[taskId].tPaletteTag = paletteTag; gTasks[taskId].tPriority = priority; gTasks[taskId].tSubpriority = subpriority; SetGpuReg(REG_OFFSET_BLDCNT, BLDCNT_EFFECT_NONE | BLDCNT_TGT2_ALL); SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(12, 6)); } bool8 LevelUpVerticalSpritesTaskIsRunning(void) { return FuncIsActiveTask(Task_LevelUpVerticalSprites); } static void Task_LevelUpVerticalSprites(u8 taskId) { s16 *data = gTasks[taskId].data; switch (tState) { case 0: if (tTimer == 0) { tTimer++; CreateLevelUpVerticalSprite(taskId, data); if (tMadeSprCt > 17) tState++; } else { tTimer++; if (tTimer == 2) tTimer = 0; } break; case 1: if (tActiveSprCt == 0) { FreeSpriteTilesByTag(tTileTag); FreeSpritePaletteByTag(tPaletteTag); DestroyTask(taskId); } break; } } static void CreateLevelUpVerticalSprite(u8 taskId, s16 *data) { u8 spriteId; struct SpriteTemplate template = sSpriteTemplate_LevelUpVertical; template.tileTag = tTileTag; template.paletteTag = tPaletteTag; tMadeSprCt++; spriteId = CreateSprite(&template, ((tMadeSprCt * 219) & 0x3F) + tXpos, tYpos, tSubpriority); if (spriteId != MAX_SPRITES) { gSprites[spriteId].oam.priority = tPriority; gSprites[spriteId].tsYsubpixel = 0; // similar to the LCRNG in random.c, but seeding from data[2] gSprites[spriteId].tsSpeed = ((tMadeSprCt * 1103515245 + 24691) & 0x3F) + 0x20; gSprites[spriteId].tsTaskId = taskId; tActiveSprCt++; } } static void SpriteCB_LevelUpVertical(struct Sprite * sprite) { sprite->tsYsubpixel -= sprite->tsSpeed; sprite->y2 = sprite->tsYsubpixel >> 4; if (sprite->y2 < -0x40) { gTasks[sprite->tsTaskId].tActiveSprCt--; DestroySprite(sprite); } } #undef tsTaskId #undef tsSpeed #undef tsYsubpixel #undef tSubpriority #undef tPriority #undef tPaletteTag #undef tTileTag #undef tYpos #undef tXpos #undef tTimer #undef tMadeSprCt #undef tActiveSprCt #undef tState // ======================================================== static const u8 *const sLevelUpWindowStatNames[] = { gUnknown_841B2A9, gUnknown_841B2B7, gUnknown_841B2BE, gUnknown_841B2CC, gUnknown_841B2D4, gUnknown_841B2C6 }; void DrawLevelUpWindowPg1(u16 windowId, u16 *beforeStats, u16 *afterStats, u8 bgColor, u8 fgColor, u8 shadowColor) { s16 diffStats[6]; u8 textbuf[12]; u8 textColor[3]; u16 i; u8 x; FillWindowPixelBuffer(windowId, PIXEL_FILL(bgColor)); diffStats[0] = afterStats[0] - beforeStats[0]; diffStats[1] = afterStats[1] - beforeStats[1]; diffStats[2] = afterStats[2] - beforeStats[2]; diffStats[3] = afterStats[4] - beforeStats[4]; diffStats[4] = afterStats[5] - beforeStats[5]; diffStats[5] = afterStats[3] - beforeStats[3]; textColor[0] = bgColor; textColor[1] = fgColor; textColor[2] = shadowColor; for (i = 0; i < 6; i++) { AddTextPrinterParameterized3(windowId, 2, 0, i * 15, textColor, TEXT_SPEED_FF, sLevelUpWindowStatNames[i]); StringCopy(textbuf, diffStats[i] >= 0 ? gUnknown_841B2DC : gUnknown_841B2E5); AddTextPrinterParameterized3(windowId, 2, 56, i * 15, textColor, TEXT_SPEED_FF, textbuf); textbuf[0] = CHAR_SPACE; x = abs(diffStats[i]) < 10 ? 12 : 6; ConvertIntToDecimalStringN(textbuf + 1, abs(diffStats[i]), STR_CONV_MODE_LEFT_ALIGN, 2); AddTextPrinterParameterized3(windowId, 2, x + 56, i * 15, textColor, TEXT_SPEED_FF, textbuf); } } void DrawLevelUpWindowPg2(u16 windowId, u16 *currStats, u8 bgColor, u8 fgColor, u8 shadowColor) { s16 statsRearrange[6]; u8 textbuf[12]; u8 textColor[3]; u16 i; u8 ndigits; u16 x; FillWindowPixelBuffer(windowId, PIXEL_FILL(bgColor)); statsRearrange[0] = currStats[0]; statsRearrange[1] = currStats[1]; statsRearrange[2] = currStats[2]; statsRearrange[3] = currStats[4]; statsRearrange[4] = currStats[5]; statsRearrange[5] = currStats[3]; textColor[0] = bgColor; textColor[1] = fgColor; textColor[2] = shadowColor; for (i = 0; i < 6; i++) { if (statsRearrange[i] >= 100) ndigits = 3; else if (statsRearrange[i] >= 10) ndigits = 2; else ndigits = 1; ConvertIntToDecimalStringN(textbuf, statsRearrange[i], STR_CONV_MODE_LEFT_ALIGN, ndigits); x = 6 * (4 - ndigits); AddTextPrinterParameterized3(windowId, 2, 0, i * 15, textColor, TEXT_SPEED_FF, sLevelUpWindowStatNames[i]); AddTextPrinterParameterized3(windowId, 2, 56 + x, i * 15, textColor, TEXT_SPEED_FF, textbuf); } }