#include "global.h" #include "gflib.h" #include "m4a.h" #include "task.h" #include "scanline_effect.h" #include "libgcnmultiboot.h" #include "new_menu_helpers.h" #include "link.h" #include "menu.h" #include "random.h" #include "save.h" #include "new_game.h" #include "title_screen.h" #include "decompress.h" #include "util.h" #include "trig.h" #include "constants/songs.h" #include "constants/sound.h" /* The intro is grouped into the following scenes - Copyright screen - GF Logo Scene 1. Brief close up shot of grass Scene 2. A panning wide shot followed by a close-up of Gengar/Nidorino Scene 3. A fight between Gengar/Nidorino After this it progresses to the title screen */ enum { GFXTAG_STAR, GFXTAG_SPARKLES_SMALL, GFXTAG_SPARKLES_BIG, GFXTAG_GF_LOGO, GFXTAG_PRESENTS, GFXTAG_SCENE3_NIDORINO, GFXTAG_SCENE2_GENGAR, GFXTAG_SCENE2_NIDORINO, GFXTAG_SCENE3_GRASS, GFXTAG_SCENE3_GENGAR, GFXTAG_SCENE3_SWIPE, GFXTAG_SCENE3_RECOIL_DUST, }; enum { PALTAG_STAR, PALTAG_SPARKLES, PALTAG_UNUSED_2, PALTAG_GF, PALTAG_UNUSED_4, PALTAG_UNUSED_5, PALTAG_GENGAR, PALTAG_NIDORINO, PALTAG_SCENE3_GRASS, PALTAG_UNUSED_9, PALTAG_SCENE3_SWIPE, PALTAG_SCENE3_RECOIL_DUST, }; // Background IDs for Game Freak logo scene enum { BG_GF_TEXT_LOGO = 2, BG_GF_BACKGROUND }; // Background IDs for Scene 1 enum { BG_SCENE1_GRASS, BG_SCENE1_BACKGROUND, BG_SCENE1_UNUSED1, BG_SCENE1_UNUSED2 }; #define PALSLOT_SCENE1_GRASS 1 #define PALSLOT_SCENE1_BG 2 // Background IDs for Scene 2 enum { BG_SCENE2_PLANTS, BG_SCENE2_NIDORINO, BG_SCENE2_GENGAR, BG_SCENE2_BACKGROUND // Bg for wide shot on upper half, close up on lower half }; // Background IDs for Scene 3 enum { BG_SCENE3_GENGAR, BG_SCENE3_BACKGROUND, BG_SCENE3_UNUSED1, BG_SCENE3_UNUSED2 }; enum { ANIM_NIDORINO_NORMAL, ANIM_NIDORINO_CRY, ANIM_NIDORINO_CROUCH, ANIM_NIDORINO_HOP, ANIM_NIDORINO_ATTACK, }; enum { ANIM_SPARKLE_LOOP, ANIM_SPARKLE_ONCE, }; enum { ANIM_SWIPE_TOP, ANIM_SWIPE_BOTTOM, }; enum { AFFINEANIM_NORMAL, AFFINEANIM_ZOOM, }; // Window ids for sWindowTemplates (only one) enum { WIN_GF_TEXT_LOGO, WIN_COUNT }; #define NUM_GENGAR_BACK_SPRITES 4 #define COLOSSEUM_GAME_CODE 0x65366347 // "Gc6e" in ASCII struct IntroSequenceData; typedef void (*IntroCallback)(struct IntroSequenceData *); struct IntroSequenceData { IntroCallback callback; u8 state; u8 taskId; bool8 gengarAttackLanded; u16 data[5]; // [0] and [1] are set but never read, the rest are unused u16 timer; struct Sprite *gameFreakLogoArtSprite; struct Sprite *scene3NidorinoSprite; struct Sprite *scene2GengarSprite; struct Sprite *scene2NidorinoSprite; struct Sprite *scene3GrassSprite; struct Sprite *scene3GengarSprites[NUM_GENGAR_BACK_SPRITES]; u8 unused0[4]; u8 gameFreakLogoGfx[0x400]; u8 gameFreakTextGfx[0x400]; u8 unused1[0x2080]; }; // size: 0x28BC static EWRAM_DATA struct GcmbStruct sGcmb = {0}; static EWRAM_DATA u16 sUnusedScene3Var0 = 0; // Set but never read static EWRAM_DATA u16 sUnusedScene3Var1 = 0; // Set but never read static EWRAM_DATA u16 sNidorinoJumpMult = 0; static EWRAM_DATA u16 sNidorinoAnimDelayTime = 0; static EWRAM_DATA u16 sNidorinoJumpDiv = 0; static EWRAM_DATA u16 sNidorinoRecoilReturnTime = 0; static EWRAM_DATA u16 sNidorinoUnusedVar = 0; // Set but never read static EWRAM_DATA u16 sStarSpeedX = 0; static EWRAM_DATA u16 sStarSpeedY = 0; static EWRAM_DATA u16 sStarSparklesXmodMask = 0; static EWRAM_DATA u16 sStarSparklesUnusedVar = 0; // Set but never read static EWRAM_DATA u16 sStarSparklesSpawnRate = 0; static EWRAM_DATA u16 sStarSparklesFlickerStartTime = 0; static EWRAM_DATA u16 sStarSparklesDestroySpriteTime = 0; static EWRAM_DATA u16 sStarSparklesGravityShift = 0; static EWRAM_DATA u16 sStarSparklesXspeed = 0; static EWRAM_DATA u16 sStarSparklesYspeed = 0; static EWRAM_DATA u16 sStarSparklesXprecision = 0; static EWRAM_DATA u16 sStarSparklesYprecision = 0; // General static void CB2_SetUpIntro(void); static void CB2_Intro(void); static void VBlankCB_Intro(void); static void Intro_ResetGpuRegs(void); static void StartIntroSequence(void); static void Task_CallIntroCallback(u8 taskId); static void SetIntroCB(struct IntroSequenceData * ptr, IntroCallback cb); static void IntroCB_Init(struct IntroSequenceData * ptr); static void LoadFightSceneSpriteGraphics(void); static void IntroCB_ExitToTitleScreen(struct IntroSequenceData * ptr); // GF scene static void IntroCB_GF_OpenWindow(struct IntroSequenceData * ptr); static void IntroCB_GF_Star(struct IntroSequenceData * ptr); static void IntroCB_GF_RevealName(struct IntroSequenceData * ptr); static void IntroCB_GF_RevealLogo(struct IntroSequenceData * ptr); static void GFScene_LoadGfxCreateStar(void); static void GFScene_StartNameSparklesSmall(void); static void GFScene_StartNameSparklesBig(void); static void GFScene_Task_NameSparklesSmall(u8 taskId); static void GFScene_Task_NameSparklesBig(u8 taskId); static struct Sprite *GFScene_CreateLogoSprite(void); static void GFScene_CreatePresentsSprite(void); static void SpriteCB_Star(struct Sprite *sprite); static void SpriteCB_SparklesSmall_Star(struct Sprite *sprite); static void SpriteCB_SparklesSmall_Name(struct Sprite *sprite); static void SpriteCB_SparklesBig(struct Sprite *sprite); // Scene 1 static void IntroCB_Scene1(struct IntroSequenceData * ptr); static void Scene1_Task_AnimateGrass(u8 taskId); static void Scene1_StartGrassScrolling(void); static void Scene1_Task_BgZoom(u8 taskId); // Scene 2 static void IntroCB_Scene2(struct IntroSequenceData * ptr); static void Scene2_Task_PanForest(u8 taskId); static void Scene2_Task_PanMons(u8 taskId); static void Scene2_CreateMonSprites(struct IntroSequenceData * ptr); static void Scene2_DestroyMonSprites(struct IntroSequenceData * ptr); // Scene 3 static void IntroCB_Scene3_Entrance(struct IntroSequenceData * ptr); static void IntroCB_Scene3_Fight(struct IntroSequenceData * ptr); static void Scene3_StartBgScroll(void); static void Scene3_Task_GengarBounce(u8 taskId); static void Scene3_CreateGrassSprite(struct IntroSequenceData * ptr); static void Scene3_CreateGengarSprite(struct IntroSequenceData * ptr); static void Scene3_StartNidorinoCry(struct IntroSequenceData * ptr); static void Scene3_StartNidorinoHop(struct Sprite *sprite, u16 time, s16 targetX, u8 heightShift); static void Scene3_StartGengarAttack(struct IntroSequenceData * ptr); static void Scene3_Task_GengarAttack(u8 taskId); static void Scene3_NidorinoZoom(struct IntroSequenceData * ptr); static void Scene3_GengarZoom(struct IntroSequenceData * ptr); static void Scene3_CreateGengarSwipeSprites(void); static void Scene3_Task_GengarEnter(u8 taskId); static void Scene3_CreateNidorinoSprite(struct IntroSequenceData * ptr); static void Scene3_StartNidorinoEntrance(struct Sprite *sprite, s16 xStart, s16 xEnd, u16 speed); static void Scene3_SpriteCB_NidorinoEnter(struct Sprite *sprite); static bool32 Scene3_IsNidorinoEntering(struct IntroSequenceData * ptr); static void Scene3_StartNidorinoRecoil(struct IntroSequenceData * ptr); static bool8 Scene3_NidorinoAnimIsRunning(struct IntroSequenceData * ptr); static void CreateNidorinoRecoilDustSprites(s16 x, s16 y, s16 seed); static void Scene3_StartNidorinoAttack(struct IntroSequenceData * ptr); static void SpriteCB_Grass(struct Sprite *sprite); static void SpriteCB_GengarSwipe(struct Sprite *sprite); static void SpriteCB_RecoilDust(struct Sprite *sprite); static void SpriteCB_NidorinoCry(struct Sprite *sprite); static void SpriteCB_NidorinoRecoil(struct Sprite *sprite); static void SpriteCB_NidorinoHop(struct Sprite *sprite); static void SpriteCB_NidorinoAttack(struct Sprite *sprite); extern const u32 gMultiBootProgram_PokemonColosseum_Start[]; extern const u32 gMultiBootProgram_PokemonColosseum_End[]; static const u16 sCopyright_Pal[] = INCBIN_U16("graphics/intro/copyright.gbapal"); static const u8 sCopyright_Gfx[] = INCBIN_U8( "graphics/intro/copyright.4bpp.lz"); static const u8 sCopyright_Map[] = INCBIN_U8( "graphics/intro/copyright.bin.lz"); // Game Freak static const u16 sGameFreakBg_Pal[] = INCBIN_U16("graphics/intro/game_freak/bg.gbapal"); static const u8 sGameFreakBg_Gfx[] = INCBIN_U8( "graphics/intro/game_freak/bg.4bpp.lz"); static const u8 sGameFreakBg_Map[] = INCBIN_U8( "graphics/intro/game_freak/bg.bin.lz"); static const u16 sGameFreakLogo_Pal[] = INCBIN_U16("graphics/intro/game_freak/logo.gbapal"); static const u8 sGameFreakText_Gfx[] = INCBIN_U8( "graphics/intro/game_freak/game_freak.4bpp.lz"); static const u32 sGameFreakLogo_Gfx[] = INCBIN_U32("graphics/intro/game_freak/logo.4bpp.lz"); static const u16 sStar_Pal[] = INCBIN_U16("graphics/intro/game_freak/star.gbapal"); static const u32 sStar_Gfx[] = INCBIN_U32("graphics/intro/game_freak/star.4bpp.lz"); static const u16 sSparkles_Pal[] = INCBIN_U16("graphics/intro/game_freak/sparkles.gbapal"); static const u32 sSparklesSmall_Gfx[] = INCBIN_U32("graphics/intro/game_freak/sparkles_small.4bpp.lz"); static const u32 sSparklesBig_Gfx[] = INCBIN_U32("graphics/intro/game_freak/sparkles_big.4bpp.lz"); static const u32 sPresents_Gfx[] = INCBIN_U32("graphics/intro/game_freak/presents.4bpp.lz"); // Scene 1 (Grass close up) static const u16 sScene1_Grass_Pal[] = INCBIN_U16("graphics/intro/scene_1/grass.gbapal"); static const u8 sScene1_Grass_Gfx[] = INCBIN_U8( "graphics/intro/scene_1/grass.4bpp.lz"); static const u8 sScene1_Grass_Map[] = INCBIN_U8( "graphics/intro/scene_1/grass.bin.lz"); static const u16 sScene1_Bg_Pal[] = INCBIN_U16("graphics/intro/scene_1/bg.gbapal"); static const u8 sScene1_Bg_Gfx[] = INCBIN_U8( "graphics/intro/scene_1/bg.4bpp.lz"); static const u8 sScene1_Bg_Map[] = INCBIN_U8( "graphics/intro/scene_1/bg.bin.lz"); // Scenes 2 and 3 (Gengar and Nidorino) static const u16 sScene2_Bg_Pal[] = INCBIN_U16("graphics/intro/scene_2/bg.gbapal"); static const u8 sScene2_Bg_Gfx[] = INCBIN_U8( "graphics/intro/scene_2/bg.4bpp.lz"); static const u8 sScene2_Bg_Map[] = INCBIN_U8( "graphics/intro/scene_2/bg.bin.lz"); static const u16 sScene2_Plants_Pal[] = INCBIN_U16("graphics/intro/scene_2/plants.gbapal"); // Unused static const u8 sScene2_Plants_Gfx[] = INCBIN_U8( "graphics/intro/scene_2/plants.4bpp.lz"); static const u8 sScene2_Plants_Map[] = INCBIN_U8( "graphics/intro/scene_2/plants.bin.lz"); static const u16 sGengar_Pal[] = INCBIN_U16("graphics/intro/gengar.gbapal"); // Used by multiple scenes static const u8 sScene2_GengarClose_Gfx[] = INCBIN_U8( "graphics/intro/scene_2/gengar_close.4bpp.lz"); static const u8 sScene2_GengarClose_Map[] = INCBIN_U8( "graphics/intro/scene_2/gengar_close.bin.lz"); static const u16 sScene2_NidorinoClose_Pal[] = INCBIN_U16("graphics/intro/scene_2/nidorino_close.gbapal"); static const u8 sScene2_NidorinoClose_Gfx[] = INCBIN_U8( "graphics/intro/scene_2/nidorino_close.4bpp.lz"); static const u8 sScene2_NidorinoClose_Map[] = INCBIN_U8( "graphics/intro/scene_2/nidorino_close.bin.lz"); static const u16 sScene3_Bg_Pal[] = INCBIN_U16("graphics/intro/scene_3/bg.gbapal"); static const u8 sScene3_Bg_Gfx[] = INCBIN_U8( "graphics/intro/scene_3/bg.4bpp.lz"); static const u8 sScene3_Bg_Map[] = INCBIN_U8( "graphics/intro/scene_3/bg.bin.lz"); static const u8 sScene3_GengarAnim_Gfx[] = INCBIN_U8( "graphics/intro/scene_3/gengar_anim.4bpp.lz"); static const u8 sScene3_GengarAnim_Map[] = INCBIN_U8( "graphics/intro/scene_3/gengar_anim.bin.lz"); static const u32 sScene2_Gengar_Gfx[] = INCBIN_U32("graphics/intro/scene_2/gengar.4bpp.lz"); static const u16 sNidorino_Pal[] = INCBIN_U16("graphics/intro/nidorino.gbapal"); // Used by multiple scenes static const u32 sScene2_Nidorino_Gfx[] = INCBIN_U32("graphics/intro/scene_2/nidorino.4bpp.lz"); static const u16 sScene3_Grass_Pal[] = INCBIN_U16("graphics/intro/scene_3/grass.gbapal"); static const u32 sScene3_Grass_Gfx[] = INCBIN_U32("graphics/intro/scene_3/grass.4bpp.lz"); static const u32 sScene3_GengarStatic_Gfx[] = INCBIN_U32("graphics/intro/scene_3/gengar_static.4bpp.lz"); static const u32 sScene3_Nidorino_Gfx[] = INCBIN_U32("graphics/intro/scene_3/nidorino.4bpp.lz"); static const u16 sScene3_Swipe_Pal[] = INCBIN_U16("graphics/intro/scene_3/swipe.gbapal"); static const u16 sScene3_RecoilDust_Pal[] = INCBIN_U16("graphics/intro/scene_3/recoil_dust.gbapal"); static const u32 sScene3_Swipe_Gfx[] = INCBIN_U32("graphics/intro/scene_3/swipe.4bpp.lz"); static const u32 sScene3_RecoilDust_Gfx[] = INCBIN_U32("graphics/intro/scene_3/recoil_dust.4bpp.lz"); static const struct BgTemplate sBgTemplates_GameFreakScene[] = { { .bg = BG_GF_BACKGROUND, .charBaseIndex = 3, .mapBaseIndex = 31, .screenSize = 0, .paletteMode = 0, .priority = 3, .baseTile = 0x000 }, { .bg = BG_GF_TEXT_LOGO, .charBaseIndex = 3, .mapBaseIndex = 30, .screenSize = 0, .paletteMode = 0, .priority = 2, .baseTile = 0x010 } }; static const struct BgTemplate sBgTemplates_Scene1[] = { { .bg = BG_SCENE1_GRASS, .charBaseIndex = 0, .mapBaseIndex = 28, .screenSize = 2, .paletteMode = 0, .priority = 0, .baseTile = 0x000 }, { .bg = BG_SCENE1_BACKGROUND, .charBaseIndex = 1, .mapBaseIndex = 30, .screenSize = 2, .paletteMode = 0, .priority = 0, .baseTile = 0x000 } }; static const struct BgTemplate sBgTemplates_Scene2[] = { { .bg = BG_SCENE2_BACKGROUND, .charBaseIndex = 1, .mapBaseIndex = 30, .screenSize = 2, .paletteMode = 0, .priority = 3, .baseTile = 0x000 }, { .bg = BG_SCENE2_PLANTS, .charBaseIndex = 0, .mapBaseIndex = 29, .screenSize = 0, .paletteMode = 0, .priority = 0, .baseTile = 0x000 }, { .bg = BG_SCENE2_GENGAR, .charBaseIndex = 3, .mapBaseIndex = 27, .screenSize = 0, .paletteMode = 0, .priority = 2, .baseTile = 0x000 }, { .bg = BG_SCENE2_NIDORINO, .charBaseIndex = 2, .mapBaseIndex = 28, .screenSize = 0, .paletteMode = 0, .priority = 1, .baseTile = 0x000 } }; static const struct BgTemplate sBgTemplates_Scene3[] = { { .bg = BG_SCENE3_BACKGROUND, .charBaseIndex = 0, .mapBaseIndex = 29, .screenSize = 0, .paletteMode = 0, .priority = 1, .baseTile = 0x000 }, { .bg = BG_SCENE3_GENGAR, .charBaseIndex = 1, .mapBaseIndex = 30, .screenSize = 2, .paletteMode = 0, .priority = 0, .baseTile = 0x000 } }; static const struct WindowTemplate sWindowTemplates[WIN_COUNT + 1] = { [WIN_GF_TEXT_LOGO] = { .bg = BG_GF_TEXT_LOGO, .tilemapLeft = 6, .tilemapTop = 4, .width = 18, .height = 9, .paletteNum = 0xD, .baseBlock = 0x000 }, [WIN_COUNT] = DUMMY_WIN_TEMPLATE }; static const u8 sGengarZoomMatrixAnchors[NUM_GENGAR_BACK_SPRITES][2] = { {63, 63}, { 0, 63}, {63, 0}, { 0, 0} }; static const struct CompressedSpriteSheet sSpriteSheets_GameFreakScene[] = { {sStar_Gfx, 0x80, GFXTAG_STAR}, {sSparklesSmall_Gfx, 0x80, GFXTAG_SPARKLES_SMALL}, {sSparklesBig_Gfx, 0x800, GFXTAG_SPARKLES_BIG}, {sGameFreakLogo_Gfx, 0x400, GFXTAG_GF_LOGO}, {sPresents_Gfx, 0x100, GFXTAG_PRESENTS} }; static const struct SpritePalette sSpritePalettes_GameFreakScene[] = { {sStar_Pal, PALTAG_STAR}, {sSparkles_Pal, PALTAG_SPARKLES}, {sGameFreakLogo_Pal, PALTAG_GF}, {0} }; static const struct Coords16 sTextSparkleCoords[] = { { 72, 80}, {136, 74}, {168, 80}, {120, 80}, {104, 86}, { 88, 74}, {184, 74}, { 56, 86}, {152, 86} }; static const struct OamData sOam_Star = { .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(16x16), .matrixNum = 0, .size = SPRITE_SIZE(16x16), .tileNum = 0x000, .priority = 2, .paletteNum = 0 }; static const struct OamData sOam_SparklesSmall = { .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(8x8), .matrixNum = 0, .size = SPRITE_SIZE(8x8), .tileNum = 0x000, .priority = 2, .paletteNum = 0 }; static const union AnimCmd sAnim_SparklesSmall_Loop[] = { ANIMCMD_FRAME(0, 4), ANIMCMD_FRAME(1, 4), ANIMCMD_FRAME(2, 4), ANIMCMD_FRAME(3, 4), ANIMCMD_JUMP(0) }; static const union AnimCmd sAnim_SparklesSmall_Once[] = { ANIMCMD_FRAME(0, 4), ANIMCMD_FRAME(1, 4), ANIMCMD_FRAME(2, 4), ANIMCMD_FRAME(3, 4), ANIMCMD_END }; static const union AnimCmd *const sAnims_SparklesSmall[] = { [ANIM_SPARKLE_LOOP] = sAnim_SparklesSmall_Loop, [ANIM_SPARKLE_ONCE] = sAnim_SparklesSmall_Once }; static const struct SpriteTemplate sSpriteTemplate_Star = { .tileTag = GFXTAG_STAR, .paletteTag = PALTAG_STAR, .oam = &sOam_Star, .anims = gDummySpriteAnimTable, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCB_Star }; static const struct SpriteTemplate sSpriteTemplate_SparklesSmall = { .tileTag = GFXTAG_SPARKLES_SMALL, .paletteTag = PALTAG_SPARKLES, .oam = &sOam_SparklesSmall, .anims = sAnims_SparklesSmall, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCB_SparklesSmall_Star }; static const struct OamData sOam_SparklesBig = { .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(32x32), .matrixNum = 0, .size = SPRITE_SIZE(32x32), .tileNum = 0x000, .priority = 2, .paletteNum = 0 }; static const union AnimCmd sAnim_SparklesBig[] = { ANIMCMD_FRAME(0, 8), ANIMCMD_FRAME(16, 8), ANIMCMD_FRAME(32, 8), ANIMCMD_FRAME(48, 8), ANIMCMD_END }; static const union AnimCmd *const sAnims_SparklesBig[] = { sAnim_SparklesBig }; static const struct SpriteTemplate sSpriteTemplate_SparklesBig = { .tileTag = GFXTAG_SPARKLES_BIG, .paletteTag = PALTAG_SPARKLES, .oam = &sOam_SparklesBig, .anims = sAnims_SparklesBig, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCB_SparklesBig }; static const struct OamData sOam_GameFreakLogo = { .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_BLEND, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(32x64), .matrixNum = 0, .size = SPRITE_SIZE(32x64), .tileNum = 0x000, .priority = 3, .paletteNum = 0 }; static const struct SpriteTemplate sSpriteTemplate_GameFreakLogoArt = { .tileTag = GFXTAG_GF_LOGO, .paletteTag = PALTAG_GF, .oam = &sOam_GameFreakLogo, .anims = gDummySpriteAnimTable, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallbackDummy }; static const struct OamData sOam_PresentsText = { .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_BLEND, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(32x8), .matrixNum = 0, .size = SPRITE_SIZE(32x8), .tileNum = 0x000, .priority = 3, .paletteNum = 0 }; static const struct SpriteTemplate sSpriteTemplate_Presents = { .tileTag = GFXTAG_PRESENTS, .paletteTag = PALTAG_GF, .oam = &sOam_PresentsText, .anims = gDummySpriteAnimTable, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallbackDummy }; static const struct OamData sOam_Scene3_Nidorino = { .affineMode = ST_OAM_AFFINE_DOUBLE, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(64x64), .matrixNum = 0, .size = SPRITE_SIZE(64x64), .tileNum = 0x000, .priority = 1, .paletteNum = 0 }; static const union AnimCmd sAnim_Scene3_Nidorino_Normal[] = { ANIMCMD_FRAME(0, 1), ANIMCMD_END }; static const union AnimCmd sAnim_Scene3_Nidorino_Cry[] = { ANIMCMD_FRAME(64, 1), ANIMCMD_END }; static const union AnimCmd sAnim_Scene3_Nidorino_Crouch[] = { ANIMCMD_FRAME(128, 1), ANIMCMD_END }; static const union AnimCmd sAnim_Scene3_Nidorino_Hop[] = { ANIMCMD_FRAME(192, 1), ANIMCMD_END }; static const union AnimCmd sAnim_Scene3_Nidorino_Attack[] = { ANIMCMD_FRAME(256, 1), ANIMCMD_END }; static const union AnimCmd *const sAnims_Scene3_Nidorino[] = { [ANIM_NIDORINO_NORMAL] = sAnim_Scene3_Nidorino_Normal, [ANIM_NIDORINO_CRY] = sAnim_Scene3_Nidorino_Cry, [ANIM_NIDORINO_CROUCH] = sAnim_Scene3_Nidorino_Crouch, [ANIM_NIDORINO_HOP] = sAnim_Scene3_Nidorino_Hop, [ANIM_NIDORINO_ATTACK] = sAnim_Scene3_Nidorino_Attack }; static const union AffineAnimCmd sAffineAnim_Scene3_Mons_Normal[] = { AFFINEANIMCMD_FRAME(256, 256, 0, 0), AFFINEANIMCMD_END }; static const union AffineAnimCmd sAffineAnim_Scene3_Mons_Zoom[] = { AFFINEANIMCMD_FRAME(256, 256, 0, 0), AFFINEANIMCMD_FRAME(32, 32, 0, 8), AFFINEANIMCMD_END }; static const union AffineAnimCmd *const sAffineAnims_Scene3_Mons[] = { [AFFINEANIM_NORMAL] = sAffineAnim_Scene3_Mons_Normal, [AFFINEANIM_ZOOM] = sAffineAnim_Scene3_Mons_Zoom }; static const struct SpriteTemplate sSpriteTemplate_Scene3_Nidorino = { .tileTag = GFXTAG_SCENE3_NIDORINO, .paletteTag = PALTAG_NIDORINO, .oam = &sOam_Scene3_Nidorino, .anims = sAnims_Scene3_Nidorino, .images = NULL, .affineAnims = sAffineAnims_Scene3_Mons, .callback = SpriteCallbackDummy }; static const struct OamData sOam_Scene2_Mons = { .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(64x64), .matrixNum = 0, .size = SPRITE_SIZE(64x64), .tileNum = 0x000, .priority = 1, .paletteNum = 0 }; static const struct SpriteTemplate sSpriteTemplate_Scene2_Nidorino = { .tileTag = GFXTAG_SCENE2_NIDORINO, .paletteTag = PALTAG_NIDORINO, .oam = &sOam_Scene2_Mons, .anims = gDummySpriteAnimTable, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallbackDummy }; static const struct SpriteTemplate sSpriteTemplate_Scene2_Gengar = { .tileTag = GFXTAG_SCENE2_GENGAR, .paletteTag = PALTAG_GENGAR, .oam = &sOam_Scene2_Mons, .anims = gDummySpriteAnimTable, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallbackDummy }; static const struct OamData sOam_Grass = { .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(64x32), .matrixNum = 0, .size = SPRITE_SIZE(64x32), .tileNum = 0x000, .priority = 0, .paletteNum = 0 }; static const union AnimCmd sAnim_Grass_Static[] = { ANIMCMD_FRAME(0, 0), ANIMCMD_END }; static const union AnimCmd sAnim_Grass_Rustle[] = { ANIMCMD_FRAME(32, 4), ANIMCMD_FRAME(0, 4), ANIMCMD_END }; static const union AnimCmd *const sAnims_Grass[] = { sAnim_Grass_Static, sAnim_Grass_Rustle // Unused }; static const struct SpriteTemplate sSpriteTemplate_Grass = { .tileTag = GFXTAG_SCENE3_GRASS, .paletteTag = PALTAG_SCENE3_GRASS, .oam = &sOam_Grass, .anims = sAnims_Grass, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallbackDummy }; static const struct OamData sOam_Scene3_Gengar = { .affineMode = ST_OAM_AFFINE_DOUBLE, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(64x64), .matrixNum = 0, .size = SPRITE_SIZE(64x64), .tileNum = 0x000, .priority = 1, .paletteNum = 0 }; static const union AnimCmd sAnim_Scene3_Gengar_TopLeft[] = { ANIMCMD_FRAME(0, 0), ANIMCMD_END }; static const union AnimCmd sAnim_Scene3_Gengar_TopRight[] = { ANIMCMD_FRAME(64, 0), ANIMCMD_END }; static const union AnimCmd sAnim_Scene3_Gengar_BottomLeft[] = { ANIMCMD_FRAME(96, 0), ANIMCMD_END }; static const union AnimCmd sAnim_Scene3_Gengar_BottomRight[] = { ANIMCMD_FRAME(160, 0), ANIMCMD_END }; static const union AnimCmd *const sAnims_Scene3_Gengar[NUM_GENGAR_BACK_SPRITES] = { sAnim_Scene3_Gengar_TopLeft, sAnim_Scene3_Gengar_TopRight, sAnim_Scene3_Gengar_BottomLeft, sAnim_Scene3_Gengar_BottomRight }; static const struct SpriteTemplate sSpriteTemplate_Scene3_Gengar = { .tileTag = GFXTAG_SCENE3_GENGAR, .paletteTag = PALTAG_GENGAR, .oam = &sOam_Scene3_Gengar, .anims = sAnims_Scene3_Gengar, .images = NULL, .affineAnims = sAffineAnims_Scene3_Mons, .callback = SpriteCallbackDummy }; static const struct OamData sOam_Swipe = { .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(32x64), .matrixNum = 0, .size = SPRITE_SIZE(32x64), .tileNum = 0x000, .priority = 1, .paletteNum = 0 }; static const union AnimCmd sAnim_Swipe_Top[] = { ANIMCMD_FRAME(0, 8), ANIMCMD_FRAME(32, 4), ANIMCMD_END }; static const union AnimCmd sAnim_Swipe_Bottom[] = { ANIMCMD_FRAME(64, 8), ANIMCMD_FRAME(72, 4), ANIMCMD_END }; static const union AnimCmd *const sAnims_Swipe[] = { [ANIM_SWIPE_TOP] = sAnim_Swipe_Top, [ANIM_SWIPE_BOTTOM] = sAnim_Swipe_Bottom }; static const struct SpriteTemplate sSpriteTemplate_GengarSwipe = { .tileTag = GFXTAG_SCENE3_SWIPE, .paletteTag = PALTAG_SCENE3_SWIPE, .oam = &sOam_Swipe, .anims = sAnims_Swipe, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCB_GengarSwipe }; static const struct OamData sOam_RecoilDust = { .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = FALSE, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(16x16), .matrixNum = 0, .size = SPRITE_SIZE(16x16), .tileNum = 0x000, .priority = 1, .paletteNum = 0 }; static const union AnimCmd sAnim_RecoilDust[] = { ANIMCMD_FRAME(0, 10), ANIMCMD_FRAME(4, 10), ANIMCMD_FRAME(8, 10), ANIMCMD_FRAME(12, 8), ANIMCMD_END }; static const union AnimCmd *const sAnims_RecoilDust[] = { sAnim_RecoilDust }; static const struct SpriteTemplate sSpriteTemplate_NidorinoRecoilDust = { .tileTag = GFXTAG_SCENE3_RECOIL_DUST, .paletteTag = PALTAG_SCENE3_RECOIL_DUST, .oam = &sOam_RecoilDust, .anims = sAnims_RecoilDust, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCB_RecoilDust }; static const struct CompressedSpriteSheet sFightSceneSpriteSheets[] = { {sScene2_Gengar_Gfx, 0x800, GFXTAG_SCENE2_GENGAR}, {sScene2_Nidorino_Gfx, 0x800, GFXTAG_SCENE2_NIDORINO}, {sScene3_Nidorino_Gfx, 0x2800, GFXTAG_SCENE3_NIDORINO}, {sScene3_Grass_Gfx, 0x800, GFXTAG_SCENE3_GRASS}, {sScene3_GengarStatic_Gfx, 0x1800, GFXTAG_SCENE3_GENGAR}, {sScene3_Swipe_Gfx, 0xA00, GFXTAG_SCENE3_SWIPE}, {sScene3_RecoilDust_Gfx, 0x200, GFXTAG_SCENE3_RECOIL_DUST} }; // POTENTIAL UB // This array is passed to LoadSpritePalettes in LoadFightSceneSpriteGraphics. // LoadSpritePalettes uses a {0} entry to signal end of array. // Because such an entry is absent in this case, the function // continues reading into the next .rodata section. static const struct SpritePalette sFightSceneSpritePalettes[] = { {sGengar_Pal, PALTAG_GENGAR}, {sNidorino_Pal, PALTAG_NIDORINO}, {sScene3_Grass_Pal, PALTAG_SCENE3_GRASS}, {sScene3_Swipe_Pal, PALTAG_SCENE3_SWIPE}, {sScene3_RecoilDust_Pal, PALTAG_SCENE3_RECOIL_DUST}, #ifdef BUGFIX {0} #endif }; static void VBlankCB_Copyright(void) { LoadOam(); ProcessSpriteCopyRequests(); TransferPlttBuffer(); } static void CB2_WaitFadeBeforeSetUpIntro(void) { if (!UpdatePaletteFade()) SetMainCallback2(CB2_SetUpIntro); } static void LoadCopyrightGraphics(u16 charBase, u16 screenBase, u16 palOffset) { LZ77UnCompVram(sCopyright_Gfx, (void *)BG_VRAM + charBase); LZ77UnCompVram(sCopyright_Map, (void *)BG_VRAM + screenBase); LoadPalette(sCopyright_Pal, palOffset, sizeof(sCopyright_Pal)); } static void SerialCB_CopyrightScreen(void) { GameCubeMultiBoot_HandleSerialInterrupt(&sGcmb); } static bool8 SetUpCopyrightScreen(void) { switch (gMain.state) { case 0: SetVBlankCallback(NULL); SetGpuReg(REG_OFFSET_BLDCNT, 0); SetGpuReg(REG_OFFSET_BLDALPHA, 0); SetGpuReg(REG_OFFSET_BLDY, 0); ((vu16*)PLTT)[0] = RGB_WHITE; SetGpuReg(REG_OFFSET_DISPCNT, 0); SetGpuReg(REG_OFFSET_BG0HOFS, 0); SetGpuReg(REG_OFFSET_BG0VOFS, 0); DmaFill16(3, 0, VRAM, VRAM_SIZE); DmaFill32(3, 0, OAM, OAM_SIZE); DmaFill16(3, 0, PLTT + sizeof(vu16), PLTT_SIZE - sizeof(vu16)); ResetPaletteFade(); LoadCopyrightGraphics(0 * BG_CHAR_SIZE, 7 * BG_SCREEN_SIZE, 0); ScanlineEffect_Stop(); ResetTasks(); ResetSpriteData(); FreeAllSpritePalettes(); BeginNormalPaletteFade(PALETTES_ALL, 0, 16, 0, RGB_WHITEALPHA); SetGpuReg(REG_OFFSET_BG0CNT, BGCNT_PRIORITY(0) | BGCNT_CHARBASE(0) | BGCNT_16COLOR | BGCNT_SCREENBASE(7)); EnableInterrupts(INTR_FLAG_VBLANK); SetVBlankCallback(VBlankCB_Copyright); SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_1D_MAP | DISPCNT_BG0_ON); SetSerialCallback(SerialCB_CopyrightScreen); GameCubeMultiBoot_Init(&sGcmb); // fallthrough default: UpdatePaletteFade(); gMain.state++; GameCubeMultiBoot_Main(&sGcmb); break; case 140: GameCubeMultiBoot_Main(&sGcmb); if (sGcmb.gcmb_field_2 != 1) { BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK); gMain.state++; } break; case 141: if (!UpdatePaletteFade()) { gMain.state++; if (sGcmb.gcmb_field_2 != 0) { if (sGcmb.gcmb_field_2 == 2) { if (*(u32 *)(EWRAM_START + 0xAC) == COLOSSEUM_GAME_CODE) { CpuCopy16(gMultiBootProgram_PokemonColosseum_Start, (void *)EWRAM_START, 0x28000); *(u32 *)(EWRAM_START + 0xAC) = COLOSSEUM_GAME_CODE; } GameCubeMultiBoot_ExecuteProgram(&sGcmb); } } else { GameCubeMultiBoot_Quit(); SetSerialCallback(SerialCB); } return FALSE; } break; case 142: ResetSerial(); SetMainCallback2(CB2_WaitFadeBeforeSetUpIntro); break; } return TRUE; } void CB2_InitCopyrightScreenAfterBootup(void) { if (!SetUpCopyrightScreen()) { ResetMenuAndMonGlobals(); Save_ResetSaveCounters(); LoadGameSave(SAVE_NORMAL); if (gSaveFileStatus == SAVE_STATUS_EMPTY || gSaveFileStatus == SAVE_STATUS_INVALID) Sav2_ClearSetDefault(); SetPokemonCryStereo(gSaveBlock2Ptr->optionsSound); } } void CB2_InitCopyrightScreenAfterTitleScreen(void) { SetUpCopyrightScreen(); } static void CB2_SetUpIntro(void) { switch (gMain.state) { default: gMain.state = 0; // fallthrough case 0: SetVBlankCallback(NULL); SetGpuReg(REG_OFFSET_DISPCNT, 0); InitHeap(gHeap, HEAP_SIZE); ResetTasks(); ResetSpriteData(); ResetPaletteFade(); ResetTempTileDataBuffers(); Intro_ResetGpuRegs(); DmaFill16(3, 0, VRAM, VRAM_SIZE); DmaFill32(3, 0, OAM, OAM_SIZE); DmaFill16(3, 0, PLTT, PLTT_SIZE); FillPalette(RGB_BLACK, 0, 0x400); ResetBgsAndClearDma3BusyFlags(FALSE); InitBgsFromTemplates(0, sBgTemplates_GameFreakScene, ARRAY_COUNT(sBgTemplates_GameFreakScene)); break; case 1: LoadPalette(sGameFreakBg_Pal, 0x00, sizeof(sGameFreakBg_Pal)); DecompressAndCopyTileDataToVram(BG_GF_BACKGROUND, sGameFreakBg_Gfx, 0, 0, 0); DecompressAndCopyTileDataToVram(BG_GF_BACKGROUND, sGameFreakBg_Map, 0, 0, 1); LoadPalette(sGameFreakLogo_Pal, 0xD0, sizeof(sGameFreakLogo_Pal)); break; case 2: if (!FreeTempTileDataBuffersIfPossible()) { StartIntroSequence(); BlendPalettes(PALETTES_ALL, 16, RGB_BLACK); SetMainCallback2(CB2_Intro); SetVBlankCallback(VBlankCB_Intro); } return; } gMain.state++; } static void CB2_Intro(void) { RunTasks(); AnimateSprites(); BuildOamBuffer(); UpdatePaletteFade(); } static void VBlankCB_Intro(void) { LoadOam(); ProcessSpriteCopyRequests(); TransferPlttBuffer(); } static void Intro_ResetGpuRegs(void) { SetGpuReg(REG_OFFSET_DISPCNT, 0); SetGpuReg(REG_OFFSET_BLDCNT, 0); SetGpuReg(REG_OFFSET_BLDALPHA, 0); SetGpuReg(REG_OFFSET_BLDY, 0); SetGpuReg(REG_OFFSET_BG0HOFS, 0); SetGpuReg(REG_OFFSET_BG0VOFS, 0); SetGpuReg(REG_OFFSET_BG1HOFS, 0); SetGpuReg(REG_OFFSET_BG1VOFS, 0); SetGpuReg(REG_OFFSET_BG2HOFS, 0); SetGpuReg(REG_OFFSET_BG2VOFS, 0); SetGpuReg(REG_OFFSET_BG3HOFS, 0); SetGpuReg(REG_OFFSET_BG3VOFS, 0); SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_OBJ_1D_MAP | DISPCNT_OBJ_ON); } static void StartIntroSequence(void) { struct IntroSequenceData * ptr = Alloc(sizeof(*ptr)); SetIntroCB(ptr, IntroCB_Init); ptr->taskId = CreateTask(Task_CallIntroCallback, 3); SetWordTaskArg(ptr->taskId, 0, (uintptr_t)ptr); } static void SetIntroCB(struct IntroSequenceData * ptr, IntroCallback cb) { ptr->callback = cb; ptr->state = 0; } static void Task_CallIntroCallback(u8 taskId) { struct IntroSequenceData * ptr = (void *)GetWordTaskArg(taskId, 0); // End intro early if player presses A/Start/Select if (JOY_NEW(A_BUTTON | START_BUTTON | SELECT_BUTTON) && ptr->callback != IntroCB_ExitToTitleScreen) SetIntroCB(ptr, IntroCB_ExitToTitleScreen); ptr->callback(ptr); } static void IntroCB_Init(struct IntroSequenceData * this) { switch (this->state) { case 0: InitWindows(sWindowTemplates); LZ77UnCompWram(sGameFreakText_Gfx, this->gameFreakTextGfx); LZ77UnCompWram(sGameFreakLogo_Gfx, this->gameFreakLogoGfx); FillBgTilemapBufferRect(BG_GF_TEXT_LOGO, 0x000, 0, 0, 32, 32, 0x11); FillWindowPixelBuffer(WIN_GF_TEXT_LOGO, PIXEL_FILL(0)); BlitBitmapToWindow(WIN_GF_TEXT_LOGO, this->gameFreakTextGfx, 0, 40, 144, 16); PutWindowTilemap(WIN_GF_TEXT_LOGO); CopyWindowToVram(WIN_GF_TEXT_LOGO, COPYWIN_FULL); this->state++; break; case 1: if (!IsDma3ManagerBusyWithBgCopy()) SetIntroCB(this, IntroCB_GF_OpenWindow); break; } } static void IntroCB_GF_OpenWindow(struct IntroSequenceData * this) { switch (this->state) { case 0: SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_WIN1_ON); SetGpuReg(REG_OFFSET_WININ, WININ_WIN1_ALL); SetGpuReg(REG_OFFSET_WINOUT, 0); SetGpuReg(REG_OFFSET_WIN1H, DISPLAY_WIDTH); SetGpuReg(REG_OFFSET_WIN1V, 0); this->timer = 0; this->state++; break; case 1: ShowBg(BG_GF_BACKGROUND); BlendPalettes(PALETTES_ALL, 0, RGB_BLACK); this->state++; break; case 2: // Extend window height in both directions from midpoint until it reaches a narrow "theatric" view this->timer += 8; if (this->timer >= 48) this->timer = 48; SetGpuReg(REG_OFFSET_WIN1V, WIN_RANGE(DISPLAY_HEIGHT / 2 - this->timer, DISPLAY_HEIGHT / 2 + this->timer)); if (this->timer == 48) SetIntroCB(this, IntroCB_GF_Star); break; } } static void IntroCB_GF_Star(struct IntroSequenceData * this) { switch (this->state) { case 0: PlaySE(MUS_GAME_FREAK); GFScene_LoadGfxCreateStar(); this->timer = 0; this->state++; break; case 1: if (++this->timer == 30) { GFScene_StartNameSparklesSmall(); this->timer = 0; this->state++; } break; case 2: this->timer++; if (this->timer == 90) SetIntroCB(this, IntroCB_GF_RevealName); break; } } static void IntroCB_GF_RevealName(struct IntroSequenceData * this) { switch (this->state) { case 0: GFScene_StartNameSparklesBig(); this->timer = 0; this->state++; break; case 1: if (++this->timer >= 40) this->state++; break; case 2: SetGpuReg(REG_OFFSET_BLDCNT, BLDCNT_TGT1_BG2 | BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL); StartBlendTask(0, 16, 16, 0, 48, 0); this->state++; break; case 3: ShowBg(BG_GF_TEXT_LOGO); this->state++; break; case 4: if (!IsBlendTaskActive()) { SetGpuReg(REG_OFFSET_BLDCNT, 0); this->timer = 0; this->state++; } break; case 5: if (++this->timer > 50) SetIntroCB(this, IntroCB_GF_RevealLogo); break; } } static void IntroCB_GF_RevealLogo(struct IntroSequenceData * this) { switch (this->state) { case 0: SetGpuReg(REG_OFFSET_BLDCNT, BLDCNT_TGT1_OBJ | BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL); StartBlendTask(0, 16, 16, 0, 16, 0); this->data[0] = 0; // Never read this->data[1] = 16; // Never read this->timer = 0; this->state++; break; case 1: this->gameFreakLogoArtSprite = GFScene_CreateLogoSprite(); this->state++; break; case 2: if (!IsBlendTaskActive()) { BlitBitmapToWindow(WIN_GF_TEXT_LOGO, this->gameFreakLogoGfx, 0x38, 0x06, 0x20, 0x40); BlitBitmapToWindow(WIN_GF_TEXT_LOGO, this->gameFreakTextGfx, 0x00, 0x28, 0x90, 0x10); CopyWindowToVram(WIN_GF_TEXT_LOGO, COPYWIN_GFX); this->state++; } break; case 3: if (!IsDma3ManagerBusyWithBgCopy()) { DestroySprite(this->gameFreakLogoArtSprite); #if REVISION >= 1 GFScene_CreatePresentsSprite(); #endif this->timer = 0; this->state++; } break; case 4: if (++this->timer > 90) { SetGpuRegBits(REG_OFFSET_BLDCNT, BLDCNT_TGT1_BG2); StartBlendTask(16, 0, 0, 16, 20, 0); this->state++; } break; case 5: if (!IsBlendTaskActive()) { HideBg(BG_GF_TEXT_LOGO); this->state++; } break; case 6: ResetSpriteData(); FreeAllSpritePalettes(); this->timer = 0; this->state++; break; case 7: if (++this->timer > 20) { SetGpuReg(REG_OFFSET_BLDCNT, 0); SetIntroCB(this, IntroCB_Scene1); } break; } } static void IntroCB_Scene1(struct IntroSequenceData * this) { switch (this->state) { case 0: SetVBlankCallback(NULL); LoadPalette(sScene1_Grass_Pal, 16 * PALSLOT_SCENE1_GRASS, sizeof(sScene1_Grass_Pal)); LoadPalette(sScene1_Bg_Pal, 16 * PALSLOT_SCENE1_BG, sizeof(sScene1_Bg_Pal)); BlendPalettes((1 << PALSLOT_SCENE1_GRASS) | (1 << PALSLOT_SCENE1_BG), 16, RGB_WHITE); InitBgsFromTemplates(0, sBgTemplates_Scene1, ARRAY_COUNT(sBgTemplates_Scene1)); DecompressAndCopyTileDataToVram(BG_SCENE1_BACKGROUND, sScene1_Bg_Gfx, 0, 0, 0); DecompressAndCopyTileDataToVram(BG_SCENE1_BACKGROUND, sScene1_Bg_Map, 0, 0, 1); ShowBg(BG_SCENE1_BACKGROUND); HideBg(BG_SCENE1_GRASS); HideBg(BG_SCENE1_UNUSED1); HideBg(BG_SCENE1_UNUSED2); LoadFightSceneSpriteGraphics(); SetVBlankCallback(VBlankCB_Intro); this->state++; break; case 1: if (!FreeTempTileDataBuffersIfPossible()) { DecompressAndCopyTileDataToVram(BG_SCENE1_GRASS, sScene1_Grass_Gfx, 0, 0, 0); DecompressAndCopyTileDataToVram(BG_SCENE1_GRASS, sScene1_Grass_Map, 0, 0, 1); ResetBgPositions(); ShowBg(BG_SCENE1_BACKGROUND); this->state++; } break; case 2: if (!FreeTempTileDataBuffersIfPossible()) { ShowBg(BG_SCENE1_GRASS); CreateTask(Scene1_Task_AnimateGrass, 0); BeginNormalPaletteFade((1 << PALSLOT_SCENE1_GRASS) | (1 << PALSLOT_SCENE1_BG), -2, 16, 0, RGB_WHITE); this->state++; } break; case 3: if (!gPaletteFade.active) { m4aSongNumStart(MUS_INTRO_FIGHT); this->timer = 0; this->state++; } break; case 4: if (++this->timer == 20) { // Start animation for transitioning to the next scene CreateTask(Scene1_Task_BgZoom, 0); Scene1_StartGrassScrolling(); } if (this->timer >= 30) { // End scene BlendPalettes(PALETTES_ALL & ~1, 16, RGB_WHITE); DestroyTask(FindTaskIdByFunc(Scene1_Task_AnimateGrass)); DestroyTask(FindTaskIdByFunc(Scene1_Task_BgZoom)); SetIntroCB(this, IntroCB_Scene2); } break; case 5: // Never reached if (!gPaletteFade.active) { DestroyTask(FindTaskIdByFunc(Scene1_Task_AnimateGrass)); DestroyTask(FindTaskIdByFunc(Scene1_Task_BgZoom)); SetIntroCB(this, IntroCB_Scene2); } break; } } #define tTimer data[0] #define tFrame data[1] #define tExiting data[2] #define tScroll data[3] static void Scene1_Task_AnimateGrass(u8 taskId) { s16 * data = gTasks[taskId].data; // Each of the 3 frames of the bg grass animation is separated vertically on the tilemap. // The conditional below changes the frame by setting the y coordinate of the bg. if (++tTimer > 5) { tTimer = 0; if (++tFrame >= 3) tFrame = 0; ChangeBgY(BG_SCENE1_GRASS, tFrame << 15, BG_COORD_SET); } // When it's time to progress to the next scene, the grass is meant to scroll downward offscreen. // This scrolling is overwritten by the coord change above, and so the grass "stutters" back upward. // They don't mask the bg, so if it were to continue scrolling offscreen it would reveal the frame above on the tilemap. if (tExiting) { tScroll += 0x120; ChangeBgY(BG_SCENE1_GRASS, tScroll, BG_COORD_SUB); } } static void Scene1_StartGrassScrolling(void) { u8 taskId = FindTaskIdByFunc(Scene1_Task_AnimateGrass); gTasks[taskId].tExiting = TRUE; } #undef tTimer #undef tFrame #undef tExiting #undef tScroll #define tTimer data[0] #define tFrame data[1] // Have the silhouetted forest background "zoom in" during the transition to the next scene. // Same as the grass animation above, this achieved by separating frames vertically on the bg tilemap. static void Scene1_Task_BgZoom(u8 taskId) { s16 * data = gTasks[taskId].data; if (++tTimer > 3) { tTimer = 0; if (tFrame < 2) tFrame++; ChangeBgY(BG_SCENE1_BACKGROUND, tFrame << 15, BG_COORD_SET); } } #undef tTimer #undef tFrame static void IntroCB_Scene2(struct IntroSequenceData * this) { switch (this->state) { case 0: BlendPalettes(PALETTES_ALL & ~1, 16, RGB_WHITE); InitBgsFromTemplates(0, sBgTemplates_Scene2, ARRAY_COUNT(sBgTemplates_Scene2)); DecompressAndCopyTileDataToVram(BG_SCENE2_BACKGROUND, sScene2_Bg_Gfx, 0, 0, 0); DecompressAndCopyTileDataToVram(BG_SCENE2_BACKGROUND, sScene2_Bg_Map, 0, 0, 1); ShowBg(BG_SCENE2_BACKGROUND); this->state++; break; case 1: if (!FreeTempTileDataBuffersIfPossible()) { SetVBlankCallback(NULL); LoadPalette(sScene2_Bg_Pal, 0x10, sizeof(sScene2_Bg_Pal)); LoadPalette(sGengar_Pal, 0x50, sizeof(sGengar_Pal)); LoadPalette(sScene2_NidorinoClose_Pal, 0x60, sizeof(sScene2_NidorinoClose_Pal)); BlendPalettes(PALETTES_ALL & ~1, 16, RGB_WHITE); DecompressAndCopyTileDataToVram(BG_SCENE2_PLANTS, sScene2_Plants_Gfx, 0, 0, 0); DecompressAndCopyTileDataToVram(BG_SCENE2_PLANTS, sScene2_Plants_Map, 0, 0, 1); DecompressAndCopyTileDataToVram(BG_SCENE2_NIDORINO, sScene2_NidorinoClose_Gfx, 0, 0, 0); DecompressAndCopyTileDataToVram(BG_SCENE2_NIDORINO, sScene2_NidorinoClose_Map, 0, 0, 1); DecompressAndCopyTileDataToVram(BG_SCENE2_GENGAR, sScene2_GengarClose_Gfx, 0, 0, 0); DecompressAndCopyTileDataToVram(BG_SCENE2_GENGAR, sScene2_GengarClose_Map, 0, 0, 1); ResetBgPositions(); ShowBg(BG_SCENE2_PLANTS); HideBg(BG_SCENE2_NIDORINO); // Hide bgs for the close up shot HideBg(BG_SCENE2_GENGAR); ChangeBgY(BG_SCENE2_GENGAR, 0x0001CE00, BG_COORD_SET); ChangeBgY(BG_SCENE2_NIDORINO, 0x00002800, BG_COORD_SET); CreateTask(Scene2_Task_PanForest, 0); Scene2_CreateMonSprites(this); BlendPalettes(PALETTES_ALL & ~1, 16, RGB_WHITE); SetVBlankCallback(VBlankCB_Intro); this->state++; } break; case 2: if (!FreeTempTileDataBuffersIfPossible()) { BeginNormalPaletteFade(PALETTES_ALL & ~1, -2, 16, 0, RGB_WHITE); this->state++; } break; case 3: if (!gPaletteFade.active) { this->timer = 0; this->state++; } break; case 4: if (++this->timer >= 60) { this->timer = 0; DestroyTask(FindTaskIdByFunc(Scene2_Task_PanForest)); Scene2_DestroyMonSprites(this); CreateTask(Scene2_Task_PanMons, 0); ChangeBgY(BG_SCENE2_BACKGROUND, 0x00010000, BG_COORD_SET); // Move background from upper half (wide shot) to lower half (close up) HideBg(BG_SCENE2_PLANTS); ShowBg(BG_SCENE2_BACKGROUND); ShowBg(BG_SCENE2_NIDORINO); ShowBg(BG_SCENE2_GENGAR); this->state++; } break; case 5: if (!IsDma3ManagerBusyWithBgCopy()) { this->timer = 0; this->state++; } break; case 6: if (++this->timer >= 60) { DestroyTask(FindTaskIdByFunc(Scene2_Task_PanMons)); SetIntroCB(this, IntroCB_Scene3_Entrance); } break; } } // Pan the background trees right and the foreground plants left in the wide shot static void Scene2_Task_PanForest(u8 taskId) { ChangeBgX(BG_SCENE2_BACKGROUND, 0x0E0, BG_COORD_SUB); ChangeBgX(BG_SCENE2_PLANTS, 0x110, BG_COORD_ADD); } // Pan Gengar up and Nidorino down in the close up shot static void Scene2_Task_PanMons(u8 taskId) { ChangeBgY(BG_SCENE2_GENGAR, 0x020, BG_COORD_ADD); ChangeBgY(BG_SCENE2_NIDORINO, 0x024, BG_COORD_SUB); } // Create the Gengar/Nidorino sprites for the wide shot in scene 2 static void Scene2_CreateMonSprites(struct IntroSequenceData * this) { u8 spriteId; this->scene2GengarSprite = NULL; this->scene2NidorinoSprite = NULL; spriteId = CreateSprite(&sSpriteTemplate_Scene2_Nidorino, 168, 80, 11); if (spriteId != MAX_SPRITES) this->scene2NidorinoSprite = &gSprites[spriteId]; spriteId = CreateSprite(&sSpriteTemplate_Scene2_Gengar, 72, 80, 12); if (spriteId != MAX_SPRITES) this->scene2GengarSprite = &gSprites[spriteId]; } static void Scene2_DestroyMonSprites(struct IntroSequenceData * this) { if (this->scene2GengarSprite != NULL) DestroySprite(this->scene2GengarSprite); if (this->scene2NidorinoSprite != NULL) DestroySprite(this->scene2NidorinoSprite); } // Set up the scene 3 graphics, then start the scrolling to get Gengar and Nidorino in their fight positions static void IntroCB_Scene3_Entrance(struct IntroSequenceData * this) { switch (this->state) { case 0: LoadPalette(sScene3_Bg_Pal, 0x10, sizeof(sScene3_Bg_Pal)); LoadPalette(sGengar_Pal, 0x50, sizeof(sGengar_Pal)); BlendPalettes(PALETTES_ALL & ~1, 16, RGB_WHITE); InitBgsFromTemplates(0, sBgTemplates_Scene3, ARRAY_COUNT(sBgTemplates_Scene3)); DecompressAndCopyTileDataToVram(BG_SCENE3_BACKGROUND, sScene3_Bg_Gfx, 0, 0, 0); DecompressAndCopyTileDataToVram(BG_SCENE3_BACKGROUND, sScene3_Bg_Map, 0, 0, 1); ShowBg(BG_SCENE3_BACKGROUND); HideBg(BG_SCENE3_GENGAR); HideBg(BG_SCENE3_UNUSED1); HideBg(BG_SCENE3_UNUSED2); ResetBgPositions(); this->state++; SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_WIN0_ON); SetGpuRegBits(REG_OFFSET_WININ, WININ_WIN0_BG1 | WININ_WIN0_OBJ); ClearGpuRegBits(REG_OFFSET_WININ, WININ_WIN0_BG0); SetGpuRegBits(REG_OFFSET_WINOUT, 0); SetGpuReg(REG_OFFSET_WIN0V, WIN_RANGE(32, DISPLAY_HEIGHT - 32)); SetGpuReg(REG_OFFSET_WIN0H, WIN_RANGE(0, DISPLAY_WIDTH / 2)); break; case 1: if (!FreeTempTileDataBuffersIfPossible()) { DecompressAndCopyTileDataToVram(BG_SCENE3_GENGAR, sScene3_GengarAnim_Gfx, 0, 0, 0); DecompressAndCopyTileDataToVram(BG_SCENE3_GENGAR, sScene3_GengarAnim_Map, 0, 0, 1); sUnusedScene3Var0 = 4; sUnusedScene3Var1 = 52; ChangeBgX(BG_SCENE3_GENGAR, 0x00001800, BG_COORD_SET); ChangeBgY(BG_SCENE3_GENGAR, 0x0001F000, BG_COORD_SET); this->state++; } break; case 2: if (!FreeTempTileDataBuffersIfPossible()) { BlendPalettes(PALETTES_ALL & ~1, 0, RGB_WHITE); ShowBg(BG_SCENE3_GENGAR); CreateTask(Scene3_Task_GengarBounce, 0); Scene3_CreateNidorinoSprite(this); Scene3_StartNidorinoEntrance(this->scene3NidorinoSprite, 0, 180, 52); CreateTask(Scene3_Task_GengarEnter, 0); Scene3_StartBgScroll(); this->timer = 0; this->state++; } break; case 3: if (++this->timer == 16) Scene3_CreateGrassSprite(this); if (!Scene3_IsNidorinoEntering(this) && !FuncIsActiveTask(Scene3_Task_GengarEnter)) SetIntroCB(this, IntroCB_Scene3_Fight); break; } } #define tSlow data[0] // Pan the background trees right during the fight scene. // It pans quickly while Gengar/Nidorino are sliding onscreen, and it pans slowly thereafter. static void Scene3_Task_BgScroll(u8 taskId) { if (!gTasks[taskId].tSlow) ChangeBgX(BG_SCENE3_BACKGROUND, 0x400, BG_COORD_SUB); else ChangeBgX(BG_SCENE3_BACKGROUND, 0x020, BG_COORD_SUB); } static void Scene3_StartBgScroll(void) { CreateTask(Scene3_Task_BgScroll, 0); } static void Scene3_SlowBgScroll(void) { u8 taskId = FindTaskIdByFunc(Scene3_Task_BgScroll); gTasks[taskId].tSlow = TRUE; } #undef tSlow #define tPaused data[0] #define tTimer data[1] #define tState data[2] // Gengar has an "idle" animation where it bounces a little static void Scene3_Task_GengarBounce(u8 taskId) { s16 * data = gTasks[taskId].data; if (!tPaused) { if (++tTimer >= 30) { tTimer = 0; tState ^= 1; // Alternate between the 0th (normal) and 1st (slightly crouched) bg frames ChangeBgY(BG_SCENE3_GENGAR, (tState << 15) + 0x1F000, BG_COORD_SET); } } } static void Scene3_PauseGengarBounce(void) { u8 taskId = FindTaskIdByFunc(Scene3_Task_GengarBounce); gTasks[taskId].tPaused = TRUE; } static void Scene3_ResumeGengarBounce(void) { u8 taskId = FindTaskIdByFunc(Scene3_Task_GengarBounce); gTasks[taskId].tPaused = FALSE; } static bool8 Scene3_IsGengarMidBounce(void) { u8 taskId = FindTaskIdByFunc(Scene3_Task_GengarBounce); return gTasks[taskId].tState; } #undef tPaused #undef tTimer #undef tState // The small clump of grass that passes by in the foreground during the fight static void Scene3_CreateGrassSprite(struct IntroSequenceData * this) { u8 spriteId = CreateSprite(&sSpriteTemplate_Grass, 296, 112, 7); if (spriteId != MAX_SPRITES) { this->scene3GrassSprite = &gSprites[spriteId]; this->scene3GrassSprite->callback = SpriteCB_Grass; } else this->scene3GrassSprite = NULL; } #define sState data[0] #define sBaseX data[1] #define sVeloc data[2] static void SpriteCB_Grass(struct Sprite *sprite) { s16 * data = sprite->data; switch (sState) { case 0: sBaseX = sprite->x << 5; sVeloc = 160; sState++; // fallthrough case 1: sBaseX -= sVeloc; sprite->x = sBaseX >> 5; if (sprite->x <= 52) { Scene3_SlowBgScroll(); sState++; } break; case 2: sBaseX -= 32; sprite->x = sBaseX >> 5; if (sprite->x <= -32) { sprite->invisible = TRUE; sprite->sState++; DestroySprite(sprite); } break; } } #undef sState #undef sBaseX #undef sVeloc static void IntroCB_Scene3_Fight(struct IntroSequenceData * this) { switch (this->state) { case 0: this->timer = 0; this->state++; break; case 1: if (++this->timer > 30) { Scene3_StartNidorinoCry(this); this->state++; } break; case 2: if (!Scene3_NidorinoAnimIsRunning(this)) { this->timer = 0; this->state++; } break; case 3: if (++this->timer > 30) { Scene3_PauseGengarBounce(); Scene3_StartGengarAttack(this); this->timer = 0; this->state++; } break; case 4: if (this->gengarAttackLanded) { Scene3_StartNidorinoRecoil(this); this->state++; } break; case 5: if (!Scene3_NidorinoAnimIsRunning(this)) { Scene3_ResumeGengarBounce(); this->timer = 0; this->state++; } break; case 6: if (++this->timer > 16) { // Nidorino's 1st hop backwards in preparation to attack Scene3_StartNidorinoHop(this->scene3NidorinoSprite, 8, 12, 5); this->state++; } break; case 7: if (!Scene3_NidorinoAnimIsRunning(this)) { // Nidorino's 2nd hop backwards in preparation to attack Scene3_StartNidorinoHop(this->scene3NidorinoSprite, 8, 12, 5); this->state++; } break; case 8: if (!Scene3_NidorinoAnimIsRunning(this)) { this->timer = 0; this->state++; } break; case 9: if (++this->timer > 20) { Scene3_StartNidorinoAttack(this); this->timer = 0; this->state++; } break; case 10: if (!Scene3_IsGengarMidBounce()) { Scene3_PauseGengarBounce(); Scene3_CreateGengarSprite(this); this->state++; } break; case 11: HideBg(BG_SCENE3_GENGAR); this->timer = 0; this->state++; break; case 12: if (++this->timer == 48) BeginNormalPaletteFade((1 << 1) | (1 << 2), 2, 0, 16, RGB_WHITE); if (this->timer > 120) { Scene3_NidorinoZoom(this); Scene3_GengarZoom(this); this->state++; this->timer = 0; } break; case 13: if (++this->timer > 8) { CpuFill16(RGB_WHITE, gPlttBufferUnfaded + 16, 64); BeginNormalPaletteFade(PALETTES_ALL & ~1, -2, 0, 16, RGB_BLACK); this->state++; } break; case 14: if (!gPaletteFade.active) { this->timer = 0; this->state++; } break; case 15: if (++this->timer > 60) SetIntroCB(this, IntroCB_ExitToTitleScreen); break; default: if (JOY_NEW(R_BUTTON)) { BlendPalettes(PALETTES_OBJECTS | (1 << 2) | (1 << 5) | (1 << 6), 0, RGB_WHITE); this->scene3NidorinoSprite->x2 = 0; this->scene3NidorinoSprite->x = 180; this->state = 1; this->timer = 30; } break; } } static void Scene3_CalcCenterToCornerVec(struct Sprite *sprite) { CalcCenterToCornerVec(sprite, sprite->oam.shape, sprite->oam.size, sprite->oam.affineMode); } static void Scene3_CreateGengarSprite(struct IntroSequenceData * this) { int i; // Not using a subsprite table for this for (i = 0; i < NUM_GENGAR_BACK_SPRITES; i++) { int x = (i & 1) * 48 + 49; int y = (i / 2) * 64 + 72; u8 spriteId = CreateSprite(&sSpriteTemplate_Scene3_Gengar, x, y, 8); if (spriteId != MAX_SPRITES) { StartSpriteAnim(&gSprites[spriteId], i); this->scene3GengarSprites[i] = &gSprites[spriteId]; if (i & 1) this->scene3GengarSprites[i]->oam.shape = ST_OAM_V_RECTANGLE; Scene3_CalcCenterToCornerVec(this->scene3GengarSprites[i]); } } } static void Scene3_NidorinoZoom(struct IntroSequenceData * this) { this->scene3NidorinoSprite->x += this->scene3NidorinoSprite->x2; this->scene3NidorinoSprite->y += this->scene3NidorinoSprite->y2; SetSpriteMatrixAnchor(this->scene3NidorinoSprite, 0, 42); this->scene3NidorinoSprite->callback = SpriteCallbackDummy; StartSpriteAffineAnim(this->scene3NidorinoSprite, AFFINEANIM_ZOOM); } static void SpriteCB_Idle(struct Sprite *sprite) { } static void Scene3_GengarZoom(struct IntroSequenceData * this) { int i; for (i = 0; i < NUM_GENGAR_BACK_SPRITES; i++) { StartSpriteAffineAnim(this->scene3GengarSprites[i], AFFINEANIM_ZOOM); this->scene3GengarSprites[i]->callback = SpriteCB_Idle; SetSpriteMatrixAnchor(this->scene3GengarSprites[i], sGengarZoomMatrixAnchors[i][0], sGengarZoomMatrixAnchors[i][1]); } } static void IntroCB_ExitToTitleScreen(struct IntroSequenceData * this) { switch (this->state) { case 0: FillPalette(RGB_BLACK, 0, 0x400); this->state++; break; case 1: if (!FreeTempTileDataBuffersIfPossible()) { DestroyTask(this->taskId); Free(this); DisableInterrupts(INTR_FLAG_HBLANK); SetHBlankCallback(NULL); SetMainCallback2(CB2_InitTitleScreen); } break; } } // Sprite data for SpriteCB_Star #define sStar_BaseX data[0] #define sStar_BaseY data[1] #define sStar_SpeedX data[2] #define sStar_SpeedY data[3] #define sStar_SinIdx data[4] #define sStar_SparkleTimer data[5] #define sStar_SparkleRngSeed data[6] static void GFScene_LoadGfxCreateStar(void) { int i; u8 spriteId; static EWRAM_DATA u32 sStarSparklesRngSeed = 0; for (i = 0; i < ARRAY_COUNT(sSpriteSheets_GameFreakScene); i++) LoadCompressedSpriteSheet(&sSpriteSheets_GameFreakScene[i]); LoadSpritePalettes(sSpritePalettes_GameFreakScene); sStarSpeedX = 96; sStarSpeedY = 16; sStarSparklesXmodMask = 0x07; sStarSparklesUnusedVar = 5; sStarSparklesSpawnRate = 8; sStarSparklesFlickerStartTime = 90; sStarSparklesDestroySpriteTime = 120; sStarSparklesXspeed = 1; sStarSparklesYspeed = 1; sStarSparklesXprecision = 5; sStarSparklesYprecision = 5; if (sStarSparklesRngSeed == 0) sStarSparklesRngSeed = 354128453; spriteId = CreateSprite(&sSpriteTemplate_Star, 248, 55, 0); if (spriteId != MAX_SPRITES) { gSprites[spriteId].sStar_BaseX = 248 << 4; gSprites[spriteId].sStar_BaseY = 55 << 4; gSprites[spriteId].sStar_SpeedX = sStarSpeedX; gSprites[spriteId].sStar_SpeedY = sStarSpeedY; StoreWordInTwoHalfwords((u16 *)&gSprites[spriteId].sStar_SparkleRngSeed, sStarSparklesRngSeed); } } // Sprite data for SpriteCB_SparklesSmall_Star #define sSmSparkleStar_BaseX data[0] #define sSmSparkleStar_BaseY data[1] #define sSmSparkleStar_SpeedX data[2] #define sSmSparkleStar_SpeedY data[3] #define sSmSparkleStar_FallSpeed data[4] #define sSmSparkleStar_FallDist data[5] #define sSmSparkleStar_Timer data[7] static void GFScene_CreateStarSparkle(s16 x, s16 y, u16 random) { static EWRAM_DATA s16 sYmod = 0; u8 spriteId; s16 xMod = (random & sStarSparklesXmodMask) + 2; s16 yMod = sYmod; if (++sYmod > 3) sYmod = -3; x += xMod; y += yMod; if (x > 0 && x < DISPLAY_WIDTH) { spriteId = CreateSprite(&sSpriteTemplate_SparklesSmall, x, y, 1); if (spriteId != MAX_SPRITES) { gSprites[spriteId].sSmSparkleStar_BaseX = x << sStarSparklesXprecision; gSprites[spriteId].sSmSparkleStar_BaseY = y << sStarSparklesYprecision; gSprites[spriteId].sSmSparkleStar_SpeedX = sStarSparklesXspeed * xMod; gSprites[spriteId].sSmSparkleStar_SpeedY = sStarSparklesYspeed * yMod; } } } #define tSparkleIdx data[0] #define tNumLoops data[1] #define tTimer data[2] static void GFScene_StartNameSparklesSmall(void) { CreateTask(GFScene_Task_NameSparklesSmall, 1); } // Sprite data for SpriteCB_SparklesSmall_Name #define sSmSparkleName_State data[0] #define sSmSparkleName_BaseY data[1] #define sSmSparkleName_AnimTimer data[2] #define sSmSparkleName_NumLoops data[3] #define sSmSparkleName_DestroyTimer data[4] static void GFScene_Task_NameSparklesSmall(u8 taskId) { s16 * data = gTasks[taskId].data; u8 i; u8 spriteId; tTimer++; data[3]++; // Unused if (tTimer > 6) { tTimer = 0; i = tSparkleIdx; spriteId = CreateSprite(&sSpriteTemplate_SparklesSmall, sTextSparkleCoords[i].x, sTextSparkleCoords[i].y, 2); StartSpriteAnim(&gSprites[spriteId], ANIM_SPARKLE_ONCE); gSprites[spriteId].callback = SpriteCB_SparklesSmall_Name; gSprites[spriteId].sSmSparkleName_BaseY = sTextSparkleCoords[i].y << 4; gSprites[spriteId].sSmSparkleName_AnimTimer = 120; gSprites[spriteId].sSmSparkleName_NumLoops = tNumLoops; if (gSprites[spriteId].sSmSparkleName_NumLoops < 0) gSprites[spriteId].sSmSparkleName_NumLoops = 1; if (++tSparkleIdx >= ARRAY_COUNT(sTextSparkleCoords)) { if (++tNumLoops > 1) DestroyTask(taskId); else tSparkleIdx = 0; } } } #undef tSparkleIdx #undef tNumLoops #undef tTimer #define tTimer data[0] #define tSparkleIdx data[1] #define tNumSparkles data[2] static void GFScene_StartNameSparklesBig(void) { CreateTask(GFScene_Task_NameSparklesBig, 2); } static void GFScene_Task_NameSparklesBig(u8 taskId) { s16 * data = gTasks[taskId].data; u8 i; if (tTimer == 0) { i = tSparkleIdx; tSparkleIdx += 4; if (tSparkleIdx >= ARRAY_COUNT(sTextSparkleCoords)) tSparkleIdx -= ARRAY_COUNT(sTextSparkleCoords); CreateSprite(&sSpriteTemplate_SparklesBig, sTextSparkleCoords[i].x, sTextSparkleCoords[i].y, 3); if (++tNumSparkles >= (int)ARRAY_COUNT(sTextSparkleCoords)) DestroyTask(taskId); } if (++tTimer > 9) tTimer = 0; } #undef tTimer #undef tSparkleIdx #undef tNumSparkles static struct Sprite *GFScene_CreateLogoSprite(void) { u8 spriteId = CreateSprite(&sSpriteTemplate_GameFreakLogoArt, 120, 70, 4); return &gSprites[spriteId]; } #if REVISION >= 1 static void GFScene_CreatePresentsSprite(void) { int i; for (i = 0; i < 2; i++) gSprites[CreateSprite(&sSpriteTemplate_Presents, 104 + 32 * i, 108, 5)].oam.tileNum += i * 4; } #endif #define tState data[0] #define tTimer data[1] #define tSinIdx data[3] #define tBaseX data[4] #define IDX_INTRO_DATA 5 // A pointer to the IntroSequenceData will be stored at data[5] and data[6] #define tFrame data[7] #define tMultY data[8] #define tMultX data[9] static void Scene3_StartGengarAttack(struct IntroSequenceData * this) { u8 taskId; this->gengarAttackLanded = FALSE; taskId = CreateTask(Scene3_Task_GengarAttack, 4); SetWordTaskArg(taskId, IDX_INTRO_DATA, (uintptr_t)this); gTasks[taskId].tSinIdx = 64; gTasks[taskId].tBaseX = GetBgX(BG_SCENE3_GENGAR); } static void Scene3_ApplyGengarAnim(int frame, int xSub, int ySub, int xBase) { ChangeBgY(BG_SCENE3_GENGAR, (frame << 15) + 0x1F000, BG_COORD_SET); ChangeBgX(BG_SCENE3_GENGAR, xBase, BG_COORD_SET); ChangeBgX(BG_SCENE3_GENGAR, xSub << 8, BG_COORD_SUB); ChangeBgY(BG_SCENE3_GENGAR, ySub << 8, BG_COORD_SUB); } static void Scene3_Task_GengarAttack(u8 taskId) { s16 * data = gTasks[taskId].data; s32 xSub, ySub; s32 sinIdx; switch (tState) { case 0: tFrame = 2; // Gengar raises arm up tTimer = 0; tMultY = 6; tMultX = 32; tState++; break; case 1: // Gengar moves in a backward arc tSinIdx -= 2; if (++tTimer > 15) { tTimer = 0; tState++; } break; case 2: // Gengar pauses at end of backward arc if (++tTimer == 14) ((struct IntroSequenceData *)GetWordTaskArg(taskId, IDX_INTRO_DATA))->gengarAttackLanded = TRUE; if (tTimer > 15) { tTimer = 0; tState++; } break; case 3: // Gengar moves in a forward arc tSinIdx += 8; if (++tTimer == 4) { Scene3_CreateGengarSwipeSprites(); tMultY = 32; tMultX = 48; tFrame = 3; // Gengar swipes arm down } if (tTimer > 7) { tTimer = 0; tState++; } break; case 4: // Gengar moves in a backward arc to its original position tSinIdx -= 8; if (++tTimer > 3) { tFrame = 0; // Gengar returns to normal posture tSinIdx = 64; tTimer = 0; tState++; } break; case 5: DestroyTask(taskId); return; } // Animate current movement arc / frame change sinIdx = tSinIdx; xSub = -((gSineTable[sinIdx + 64] * tMultX) >> 8); ySub = tMultY - ((gSineTable[sinIdx] * tMultY) >> 8); Scene3_ApplyGengarAnim(tFrame, xSub, ySub, tBaseX); } #undef tState #undef tTimer #undef tSinIdx #undef tBaseX #undef IDX_INTRO_DATA #undef tFrame #undef tMultY #undef tMultX static void Scene3_CreateGengarSwipeSprites(void) { u8 spriteId = CreateSprite(&sSpriteTemplate_GengarSwipe, 132, 78, 6); // Implicitly ANIM_SWIPE_TOP spriteId = CreateSprite(&sSpriteTemplate_GengarSwipe, 132, 118, 6); if (spriteId != MAX_SPRITES) { gSprites[spriteId].oam.shape = SPRITE_SHAPE(32x16); gSprites[spriteId].oam.size = SPRITE_SIZE(32x16); Scene3_CalcCenterToCornerVec(&gSprites[spriteId]); StartSpriteAnim(&gSprites[spriteId], ANIM_SWIPE_BOTTOM); } } static void SpriteCB_GengarSwipe(struct Sprite *sprite) { sprite->invisible ^= 1; if (sprite->animEnded) DestroySprite(sprite); } #define tState data[0] #define tSpeed data[1] #define tMoves data[2] // Scroll Gengar into position for the fight static void Scene3_Task_GengarEnter(u8 taskId) { s16 * data = gTasks[taskId].data; static EWRAM_DATA u32 sGengarScroll = 0; switch (tState) { case 0: tSpeed = 0x400; tState++; // fallthrough case 1: // Don't decelerate for the first 40 movements if (++tMoves >= 40 && tSpeed > 16) tSpeed -= 16; sGengarScroll = ChangeBgX(BG_SCENE3_GENGAR, tSpeed, BG_COORD_ADD); if (sGengarScroll >= 0x8000) ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_WIN0_ON); if (sGengarScroll >= 0xEF00) { ChangeBgX(BG_SCENE3_GENGAR, 0xEF00, BG_COORD_SET); DestroyTask(taskId); } break; } } #undef tState #undef tSpeed #undef tMoves static void SpriteCB_Star(struct Sprite *sprite) { u32 random; sprite->sStar_BaseX -= sprite->sStar_SpeedX; sprite->sStar_BaseY += sprite->sStar_SpeedY; sprite->sStar_SinIdx += 48; sprite->x = sprite->sStar_BaseX >> 4; sprite->y = sprite->sStar_BaseY >> 4; sprite->y2 = gSineTable[(sprite->sStar_SinIdx >> 4) + 64] >> 5; sprite->sStar_SparkleTimer++; if (sprite->sStar_SparkleTimer % sStarSparklesSpawnRate) { LoadWordFromTwoHalfwords(&sprite->sStar_SparkleRngSeed, &random); random = ISO_RANDOMIZE1(random); StoreWordInTwoHalfwords(&sprite->sStar_SparkleRngSeed, random); random >>= 16; GFScene_CreateStarSparkle(sprite->x, sprite->y + sprite->y2, random); } if (sprite->x < -8) DestroySprite(sprite); } // Callback for the sparkles that trail behind the star static void SpriteCB_SparklesSmall_Star(struct Sprite *sprite) { sprite->sSmSparkleStar_BaseX += sprite->sSmSparkleStar_SpeedX; sprite->sSmSparkleStar_BaseY += sprite->sSmSparkleStar_SpeedY; sprite->sSmSparkleStar_FallDist += ++sprite->sSmSparkleStar_FallSpeed; sprite->sSmSparkleStar_Timer++; sprite->x = (u16)sprite->sSmSparkleStar_BaseX >> sStarSparklesXprecision; sprite->y = sprite->sSmSparkleStar_BaseY >> sStarSparklesYprecision; if (sStarSparklesGravityShift && sprite->sSmSparkleStar_SpeedY < 0) sprite->y2 = sprite->sSmSparkleStar_FallDist >> sStarSparklesGravityShift; if (sprite->sSmSparkleStar_Timer > sStarSparklesFlickerStartTime) { sprite->invisible = !sprite->invisible; if (sprite->sSmSparkleStar_Timer > sStarSparklesDestroySpriteTime) DestroySprite(sprite); } if (sprite->y + sprite->y2 < 0 || sprite->y + sprite->y2 > DISPLAY_HEIGHT) DestroySprite(sprite); } // Callback for the small sparkles during the "Game Freak" text reveal static void SpriteCB_SparklesSmall_Name(struct Sprite *sprite) { if (sprite->sSmSparkleName_AnimTimer) { sprite->sSmSparkleName_AnimTimer--; sprite->sSmSparkleName_BaseY++; sprite->y = sprite->sSmSparkleName_BaseY >> 4; if (sprite->y > 86) { sprite->y = 74; sprite->sSmSparkleName_BaseY = 74 << 4; } if (sprite->animEnded) { if (sprite->sSmSparkleName_State == 0) { sprite->x += 26; if (sprite->x > 188) { sprite->x = (188 * 2) - sprite->x; sprite->sSmSparkleName_State = 1; } } else { sprite->x -= 26; if (sprite->x < 52) { sprite->x = (52 * 2) - sprite->x; sprite->sSmSparkleName_State = 0; } } StartSpriteAnim(sprite, ANIM_SPARKLE_ONCE); } } else { if (sprite->sSmSparkleName_NumLoops) DestroySprite(sprite); if (sprite->animEnded) StartSpriteAnim(sprite, ANIM_SPARKLE_LOOP); sprite->sSmSparkleName_BaseY += 4; sprite->y = sprite->sSmSparkleName_BaseY >> 4; if (++sprite->sSmSparkleName_DestroyTimer > 50) DestroySprite(sprite); } } // Callback for the big sparkles during the "Game Freak" text reveal static void SpriteCB_SparklesBig(struct Sprite *sprite) { if (sprite->animEnded) DestroySprite(sprite); } static void Scene3_CreateNidorinoSprite(struct IntroSequenceData * this) { u8 spriteId = CreateSprite(&sSpriteTemplate_Scene3_Nidorino, 0, 0, 9); this->scene3NidorinoSprite = &gSprites[spriteId]; } #define sX data[0] #define sSpeed data[1] #define sTime data[2] // Not read #define sTargetX data[3] #define sTimer data[4] static void Scene3_StartNidorinoEntrance(struct Sprite *sprite, s16 xStart, s16 x1, u16 time) { sprite->sX = xStart << 4; sprite->sSpeed = ((x1 - xStart) << 4) / time; sprite->sTime = time; sprite->sTargetX = x1; sprite->sTimer = 0; sprite->x = xStart; sprite->y = 100; sprite->callback = Scene3_SpriteCB_NidorinoEnter; } static void Scene3_SpriteCB_NidorinoEnter(struct Sprite *sprite) { if (++sprite->sTimer >= 40) { // Start decelerating after 40 frames if (sprite->sSpeed > 1) sprite->sSpeed--; } sprite->sX += sprite->sSpeed; sprite->x = sprite->sX >> 4; if (sprite->x >= sprite->sTargetX) { // Reached final position sprite->x = sprite->sTargetX; sprite->callback = SpriteCallbackDummy; } } static bool32 Scene3_IsNidorinoEntering(struct IntroSequenceData * ptr) { return ptr->scene3NidorinoSprite->callback == Scene3_SpriteCB_NidorinoEnter ? TRUE : FALSE; } #undef sX #undef sSpeed #undef sTime #undef sTargetX #undef sTimer #define sState data[0] #define sStateTimer data[1] #define sBounceTimer data[2] static void Scene3_StartNidorinoCry(struct IntroSequenceData * ptr) { StartSpriteAnim(ptr->scene3NidorinoSprite, ANIM_NIDORINO_CROUCH); ptr->scene3NidorinoSprite->sState = 0; ptr->scene3NidorinoSprite->sStateTimer = 0; ptr->scene3NidorinoSprite->y2 = 3; ptr->scene3NidorinoSprite->callback = SpriteCB_NidorinoCry; } static void SpriteCB_NidorinoCry(struct Sprite *sprite) { switch (sprite->sState) { case 0: if (++sprite->sStateTimer > 8) { StartSpriteAnim(sprite, ANIM_NIDORINO_CRY); sprite->y2 = 0; sprite->sState++; } break; case 1: PlayCry_ByMode(SPECIES_NIDORINO, 0x3F, CRY_MODE_DOUBLES); sprite->sStateTimer = 0; sprite->sState++; break; case 2: if (++sprite->sBounceTimer > 1) { // Nidorino bounces slightly while crying sprite->sBounceTimer = 0; sprite->y2 = sprite->y2 == 0 ? 1 : 0; } if (++sprite->sStateTimer > 48) { StartSpriteAnim(sprite, ANIM_NIDORINO_NORMAL); sprite->y2 = 0; sprite->callback = SpriteCallbackDummy; } break; } } #undef sState #undef sStateTimer #undef sBounceTimer #define sState data[0] #define sStateTimer data[1] #define sOffsetX data[2] #define sSinIdx data[3] #define sLandTimer data[4] #define sSlowdownTimer data[5] #define sRandSeed data[6] #define sSpeedX data[7] static void Scene3_StartNidorinoRecoil(struct IntroSequenceData * ptr) { sNidorinoRecoilReturnTime = 16; sNidorinoJumpMult = 3; sNidorinoJumpDiv = 5; sNidorinoAnimDelayTime = 0; StartSpriteAnim(ptr->scene3NidorinoSprite, ANIM_NIDORINO_CROUCH); ptr->scene3NidorinoSprite->sState = 0; ptr->scene3NidorinoSprite->sStateTimer = 0; ptr->scene3NidorinoSprite->sOffsetX = 0; ptr->scene3NidorinoSprite->sSinIdx = 0; ptr->scene3NidorinoSprite->sLandTimer = 0; ptr->scene3NidorinoSprite->sSpeedX = 40; ptr->scene3NidorinoSprite->callback = SpriteCB_NidorinoRecoil; } static void SpriteCB_NidorinoRecoil(struct Sprite *sprite) { switch (sprite->sState) { case 0: if (++sprite->sStateTimer > 4) { StartSpriteAnim(sprite, ANIM_NIDORINO_HOP); sprite->sState++; } break; case 1: // Nidorino jumping backwards in the air sprite->sOffsetX += sprite->sSpeedX; sprite->sSinIdx += 8; sprite->x2 = sprite->sOffsetX >> 4; sprite->y2 = -((gSineTable[sprite->sSinIdx] * sNidorinoJumpMult) >> sNidorinoJumpDiv); sprite->sSlowdownTimer++; if (sprite->sSlowdownTimer > sNidorinoAnimDelayTime) { sprite->sSlowdownTimer = 0; sprite->sSpeedX--; } if (++sprite->sLandTimer > 15) { // Nidorino hits the ground StartSpriteAnim(sprite, ANIM_NIDORINO_CROUCH); sprite->sStateTimer = 0; sprite->sRandSeed = 0x4757; sprite->sSpeedX = 28; sprite->sState++; } break; case 2: // Nidorino sliding on the ground sprite->sOffsetX += sprite->sSpeedX; sprite->x2 = sprite->sOffsetX >> 4; if (++sprite->sStateTimer > 6) { // The position of each subsequent dust sprite is "random", but with a fixed // initial seed so that they'll be in the same positions between intro runs CreateNidorinoRecoilDustSprites(sprite->x + sprite->x2, sprite->y + sprite->y2, sprite->sRandSeed); sprite->sRandSeed *= RAND_MULT; } if (sprite->sStateTimer > 12) { StartSpriteAnim(sprite, ANIM_NIDORINO_NORMAL); sprite->sStateTimer = 0; sprite->sState++; } break; case 3: // Nidorino hops back to its original position if (++sprite->sStateTimer > 16) Scene3_StartNidorinoHop(sprite, sNidorinoRecoilReturnTime, -sprite->x2, 4); break; } } #undef sState #undef sStateTimer #undef sOffsetX #undef sSinIdx #undef sLandTimer #undef sSlowdownTimer #undef sRandSeed #undef sSpeedX static bool8 Scene3_NidorinoAnimIsRunning(struct IntroSequenceData * ptr) { return ptr->scene3NidorinoSprite->callback == SpriteCallbackDummy ? FALSE : TRUE; } #define sState data[0] #define sX data[1] #define sY data[2] #define sSpeedX data[3] #define sSpeedY data[4] #define sInvisibleTimer data[7] static void CreateNidorinoRecoilDustSprites(s16 x, s16 y, s16 seed) { int i; u8 spriteId; // Recoil dust sprites are created in pairs at the same initial position but with different speeds. // Only one of each pair will be visible at a time. for (i = 0; i < 2; i++) { spriteId = CreateSprite(&sSpriteTemplate_NidorinoRecoilDust, x - 22, y + 24, 10); if (spriteId != MAX_SPRITES) { gSprites[spriteId].sSpeedX = (seed % 13) + 8; gSprites[spriteId].sSpeedY = seed % 3; gSprites[spriteId].sInvisibleTimer = i; seed *= RAND_MULT; } } } static void SpriteCB_RecoilDust(struct Sprite *sprite) { s16 * data = sprite->data; switch (sprite->sState) { case 0: sX = sprite->x << 4; sY = sprite->y << 4; sprite->sState++; // fallthrough case 1: sX -= sSpeedX; sY += sSpeedY; sprite->x = sX >> 4; sprite->y = sY >> 4; if (sprite->animEnded) DestroySprite(sprite); break; } // Recoil dust flashes in and out if (++sInvisibleTimer > 1) { sInvisibleTimer = 0; sprite->invisible ^= 1; } } #undef sState #undef sX #undef sY #undef sSpeedX #undef sSpeedY #undef sInvisibleTimer #define sState data[0] #define sAirTime data[1] #define sOffsetX data[2] #define sSpeedX data[3] #define sSinIdx data[4] #define sSpeedY data[5] #define sTimer data[6] #define sHeightShift data[7] static void Scene3_StartNidorinoHop(struct Sprite *sprite, u16 time, s16 targetX, u8 heightShift) { sprite->sState = 0; sprite->sAirTime = time; sprite->sOffsetX = sprite->x2 << 4; sprite->sSpeedX = (targetX << 4) / time; sprite->sSinIdx = 0; sprite->sSpeedY = 0x800 / time; sprite->sTimer = 0; sprite->sHeightShift = heightShift; StartSpriteAnim(sprite, ANIM_NIDORINO_CROUCH); sprite->callback = SpriteCB_NidorinoHop; } static void SpriteCB_NidorinoHop(struct Sprite *sprite) { switch (sprite->sState) { case 0: if (++sprite->sTimer > 4) { StartSpriteAnim(sprite, ANIM_NIDORINO_HOP); sprite->sTimer = 0; sprite->sState++; } break; case 1: if (--sprite->sAirTime) { // Nidorino moving through the air sprite->sOffsetX += sprite->sSpeedX; sprite->sSinIdx += sprite->sSpeedY; sprite->x2 = sprite->sOffsetX >> 4; sprite->y2 = -(gSineTable[sprite->sSinIdx >> 4] >> sprite->sHeightShift); } else { // Nidorino lands sprite->x2 = (u16)sprite->sOffsetX >> 4; sprite->y2 = 0; StartSpriteAnim(sprite, ANIM_NIDORINO_CROUCH); if (sprite->sHeightShift == 5) { // This is used by the short hops before Nidorino's attack. // The last state is skipped so that Nidorino will stay in the crouched animation. sprite->callback = SpriteCallbackDummy; } else { sprite->sTimer = 0; sprite->sState++; } } break; case 2: if (++sprite->sTimer > 4) { StartSpriteAnim(sprite, ANIM_NIDORINO_NORMAL); sprite->callback = SpriteCallbackDummy; } break; } } #undef sState #undef sAirTime #undef sOffsetX #undef sSpeedX #undef sSinIdx #undef sSpeedY #undef sTimer #undef sHeightShift #define sState data[0] #define sTimer data[1] #define sShakeTimer data[2] #define sSpeed data[7] static void Scene3_StartNidorinoAttack(struct IntroSequenceData * ptr) { ptr->scene3NidorinoSprite->sState = 0; ptr->scene3NidorinoSprite->sTimer = 0; ptr->scene3NidorinoSprite->sShakeTimer = 0; ptr->scene3NidorinoSprite->data[3] = 0; // Unused ptr->scene3NidorinoSprite->data[4] = 0; // Unused ptr->scene3NidorinoSprite->data[5] = 0; // Unused ptr->scene3NidorinoSprite->x += ptr->scene3NidorinoSprite->x2; ptr->scene3NidorinoSprite->x2 = 0; sNidorinoUnusedVar = 36; sNidorinoAnimDelayTime = 40; sNidorinoJumpMult = 3; sNidorinoJumpDiv = 4; ptr->scene3NidorinoSprite->sSpeed = 36; StartSpriteAnim(ptr->scene3NidorinoSprite, ANIM_NIDORINO_CROUCH); ptr->scene3NidorinoSprite->callback = SpriteCB_NidorinoAttack; } static void SpriteCB_NidorinoAttack(struct Sprite *sprite) { switch (sprite->sState) { case 0: if (++sprite->sTimer & 1) { // Nidorino shakes horizontally before attacking if (++sprite->sShakeTimer & 1) sprite->x2++; else sprite->x2--; } if (sprite->sTimer > 17) { sprite->sTimer = 0; sprite->sState++; } break; case 1: if (++sprite->sTimer >= sNidorinoAnimDelayTime) { StartSpriteAnim(sprite, ANIM_NIDORINO_ATTACK); sprite->sTimer = 0; sprite->sShakeTimer = 0; sprite->sState++; } break; case 2: // Nidorino jumps at Gengar sprite->sTimer += sprite->sSpeed; sprite->x2 = -(sprite->sTimer >> 4); sprite->y2 = -((gSineTable[sprite->sTimer >> 4] * sNidorinoJumpMult) >> sNidorinoJumpDiv); sprite->sShakeTimer++; // Does nothing if (sprite->sSpeed > 12) sprite->sSpeed--; // Decelerate as jump progresses if ((sprite->sTimer >> 4) > 63) sprite->callback = SpriteCallbackDummy; break; } } #undef sState #undef sTimer #undef sShakeTimer #undef sSpeed static void LoadFightSceneSpriteGraphics(void) { int i; for (i = 0; i < ARRAY_COUNT(sFightSceneSpriteSheets); i++) LoadCompressedSpriteSheet(&sFightSceneSpriteSheets[i]); LoadSpritePalettes(sFightSceneSpritePalettes); }