#include "global.h" #include "malloc.h" #include "battle_anim.h" #include "bg.h" #include "data.h" #include "decompress.h" #include "dynamic_placeholder_text_util.h" #include "event_data.h" #include "international_string_util.h" #include "item.h" #include "link.h" #include "link_rfu.h" #include "main.h" #include "menu.h" #include "minigame_countdown.h" #include "palette.h" #include "random.h" #include "digit_obj_util.h" #include "save.h" #include "script.h" #include "sound.h" #include "sprite.h" #include "string_util.h" #include "strings.h" #include "task.h" #include "text_window.h" #include "trig.h" #include "pokemon.h" #include "pokemon_jump.h" #include "constants/rgb.h" #include "constants/songs.h" #include "constants/items.h" #define MAX_JUMP_SCORE 99990 #define MAX_JUMPS 9999 #define JUMP_PEAK (-30) enum { BG_INTERFACE, BG_BONUSES, BG_VENUSAUR, BG_SCENERY, }; // IDs for persistent windows enum { WIN_POINTS, WIN_TIMES, NUM_WINDOWS }; enum { PACKET_MON_INFO = 1, PACKET_UNUSED, PACKET_LEADER_STATE, PACKET_MEMBER_STATE, }; enum { JUMP_TYPE_NORMAL, JUMP_TYPE_FAST, JUMP_TYPE_SLOW, }; enum { FUNC_GAME_INTRO, FUNC_WAIT_ROUND, FUNC_GAME_ROUND, FUNC_GAME_OVER, FUNC_ASK_PLAY_AGAIN, FUNC_RESET_GAME, FUNC_EXIT, FUNC_GIVE_PRIZE, FUNC_SAVE, FUNC_NONE }; enum { GFXFUNC_LOAD, GFXFUNC_SHOW_NAMES, GFXFUNC_SHOW_NAMES_HIGHLIGHT, GFXFUNC_ERASE_NAMES, GFXFUNC_MSG_PLAY_AGAIN, GFXFUNC_MSG_SAVING, GFXFUNC_ERASE_MSG, GFXFUNC_MSG_PLAYER_DROPPED, GFXFUNC_MSG_COMM_STANDBY, GFXFUNC_COUNTDOWN, }; enum { VINE_HIGHEST, VINE_DOWNSWING_HIGHER, VINE_DOWNSWING_HIGH, VINE_DOWNSWING_LOW, VINE_DOWNSWING_LOWER, VINE_LOWEST, VINE_UPSWING_LOWER, VINE_UPSWING_LOW, VINE_UPSWING_HIGH, VINE_UPSWING_HIGHER, NUM_VINESTATES }; // Used to compare limits for vineStateTimer // The upper 8 bits of vineStateTimer are the vine state, // the lower 8 bits are a timer to the next state. // When the timer is incremented above 255, it increments // the vine state and the timer is reset. #define VINE_STATE_TIMER(vineState)(((vineState) << 8) | 0xFF) enum { MONSTATE_NORMAL, // Pokémon is either on the ground or in the middle of a jump MONSTATE_JUMP, // Pokémon has begun a jump MONSTATE_HIT, // Pokémon got hit by the vine }; enum { JUMPSTATE_NONE, JUMPSTATE_SUCCESS, // Cleared vine JUMPSTATE_FAILURE, // Hit vine }; #define PLAY_AGAIN_NO 1 #define PLAY_AGAIN_YES 2 #define TAG_MON1 0 #define TAG_MON2 1 // MON2-5 used implicitly by adding multiplayer id to tag #define TAG_MON3 2 #define TAG_MON4 3 #define TAG_MON5 4 #define GFXTAG_VINE1 5 #define GFXTAG_VINE2 6 #define GFXTAG_VINE3 7 #define GFXTAG_VINE4 8 #define GFXTAG_COUNTDOWN 9 #define GFXTAG_STAR 10 #define PALTAG_1 5 #define PALTAG_2 6 #define PALTAG_COUNTDOWN 7 #define TAG_DIGITS 800 #define VINE_SPRITES_PER_SIDE 4 // Vine rope is divided into 8 sprites, 4 per side copied and flipped horizontally // Used by SetLinkTimeInterval to get a bit mask for capping // a timer that controls how frequently link data is sent. #define LINK_INTERVAL_NONE 0 #define LINK_INTERVAL_SHORT 3 // 3 frame interval #define LINK_INTERVAL_MEDIUM 4 // 7 frame interval #define LINK_INTERVAL_LONG 5 // 15 frame interval #define LINK_TIMER_STOPPED 0x1111 struct PokemonJump_MonInfo { u16 species; u32 otId; u32 personality; }; struct PokemonJump_Player { int jumpOffset; int jumpOffsetIdx; u32 unused; u16 monJumpType; u16 jumpTimeStart; u16 monState; u16 prevMonState; int jumpState; bool32 funcFinished; u8 name[11]; }; struct PokemonJumpGfx { bool32 funcFinished; u16 mainState; u8 taskId; u8 unused1[3]; u8 resetVineState; u8 resetVineTimer; u8 vineState; u8 msgWindowState; u8 vinePalNumDownswing; u8 vinePalNumUpswing; u16 unused2; u16 msgWindowId; u16 fanfare; u32 bonusTimer; u16 nameWindowIds[MAX_RFU_PLAYERS]; u8 itemName[64]; u8 itemQuantityStr[64]; u8 prizeMsg[256]; u16 tilemapBuffer[0x4000]; struct Sprite *monSprites[MAX_RFU_PLAYERS]; struct Sprite *starSprites[MAX_RFU_PLAYERS]; struct Sprite *vineSprites[VINE_SPRITES_PER_SIDE * 2]; u8 unused3[12]; u8 monSpriteSubpriorities[MAX_RFU_PLAYERS]; }; struct PokemonJump_CommData { u8 funcId; u8 receivedBonusFlags; u16 data; // Multi-use u16 jumpsInRow; u32 jumpScore; }; struct PokemonJump { MainCallback exitCallback; u8 taskId; u8 numPlayers; u8 multiplayerId; u8 startDelayTimer; u16 mainState; u16 helperState; u16 excellentsInRow; u16 excellentsInRowRecord; bool32 gameOver; u32 vineState; u32 prevVineState; int vineSpeed; u32 vineSpeedAccel; u32 rngSeed; u32 nextVineSpeed; int linkTimer; u32 linkTimerLimit; u16 vineStateTimer; bool16 ignoreJumpInput; u16 unused1; u16 unused2; // Set to 0, never read u16 timer; u16 prizeItemId; u16 prizeItemQuantity; u16 playAgainComm; u8 unused3; // Set to 0, never read u8 playAgainState; bool8 allowVineUpdates; bool8 isLeader; bool8 funcActive; bool8 allPlayersReady; u16 vineTimer; u8 nextFuncId; bool8 showBonus; u16 vineSpeedDelay; u8 vineBaseSpeedIdx; u8 vineSpeedStage; int numPlayersAtPeak; bool32 initScoreUpdate; bool32 updateScore; bool32 unused4; // Set to TRUE, never read bool32 giveBonus; bool32 skipJumpUpdate; bool32 atMaxSpeedStage; struct PokemonJump_CommData comm; bool8 atJumpPeak[MAX_RFU_PLAYERS]; bool8 atJumpPeak2[MAX_RFU_PLAYERS]; bool8 atJumpPeak3[MAX_RFU_PLAYERS]; u8 memberFuncIds[MAX_RFU_PLAYERS]; u16 playAgainStates[MAX_RFU_PLAYERS]; u16 jumpTimeStarts[MAX_RFU_PLAYERS]; struct PokemonJumpGfx jumpGfx; struct PokemonJump_MonInfo monInfo[MAX_RFU_PLAYERS]; struct PokemonJump_Player players[MAX_RFU_PLAYERS]; struct PokemonJump_Player *player; }; struct PokemonJumpMons { u16 species; u16 jumpType; }; static void InitGame(struct PokemonJump *); static void ResetForNewGame(struct PokemonJump *); static void InitPlayerAndJumpTypes(void); static void ResetPlayersForNewGame(void); static s16 GetPokemonJumpSpeciesIdx(u16 species); static void InitJumpMonInfo(struct PokemonJump_MonInfo *, struct Pokemon *); static void CB2_PokemonJump(void); static void Task_StartPokemonJump(u8); static void Task_PokemonJump_Leader(u8); static void SendLinkData_Leader(void); static void Task_PokemonJump_Member(u8); static void SendLinkData_Member(void); static bool32 GameIntro_Leader(void); static bool32 WaitRound_Leader(void); static bool32 GameRound_Leader(void); static bool32 GameOver_Leader(void); static bool32 GameOver_Member(void); static bool32 AskPlayAgain_Leader(void); static bool32 AskPlayAgain_Member(void); static bool32 ResetGame_Leader(void); static bool32 ResetGame_Member(void); static bool32 ExitGame(void); static bool32 GivePrize_Leader(void); static bool32 GivePrize_Member(void); static bool32 SavePokeJump(void); static bool32 DoGameIntro(void); static bool32 HandleSwingRound(void); static bool32 DoVineHitEffect(void); static bool32 GameIntro_Member(void); static bool32 WaitRound_Member(void); static bool32 GameRound_Member(void); static bool32 TryGivePrize(void); static bool32 DoPlayAgainPrompt(void); static bool32 ClosePokeJumpLink(void); static bool32 CloseMessageAndResetScore(void); static void Task_CommunicateMonInfo(u8); static void SetTaskWithPokeJumpStruct(TaskFunc, u8); static void InitVineState(void); static void ResetVineState(void); static void UpdateVineState(void); static int GetVineSpeed(void); static void UpdateVineSpeed(void); static int PokeJumpRandom(void); static void ResetVineAfterHit(void); static void ResetPlayersJumpStates(void); static void ResetPlayersMonState(void); static bool32 IsPlayersMonState(u16); static void SetMonStateJump(void); static void UpdateGame(void); static void TryUpdateVineSwing(void); static void DisallowVineUpdates(void); static void AllowVineUpdates(void); static void HandleMonState(void); static void UpdateJump(int); static void TryUpdateScore(void); static bool32 UpdateVineHitStates(void); static bool32 AllPlayersJumpedOrHit(void); static bool32 DidAllPlayersClearVine(void); static bool32 ShouldPlayAgain(void); static void AddJumpScore(int); static int GetPlayersAtJumpPeak(void); static bool32 AreLinkQueuesEmpty(void); static int GetNumPlayersForBonus(u8 *); static void ClearUnreadField(void); static int GetScoreBonus(int); static void TryUpdateExcellentsRecord(u16); static bool32 HasEnoughScoreForPrize(void); static u16 GetPrizeData(void); static void UnpackPrizeData(u16, u16 *, u16 *); static u16 GetPrizeItemId(void); static u16 GetPrizeQuantity(void); static u16 GetQuantityLimitedByBag(u16, u16); static void SpriteCB_Star(struct Sprite *); static void SpriteCB_MonHitShake(struct Sprite *); static void SpriteCB_MonHitFlash(struct Sprite *); static void SpriteCB_MonIntroBounce(struct Sprite *); static void UpdateVineSwing(int); static void StartPokeJumpGfx(struct PokemonJumpGfx *); static void InitPokeJumpGfx(struct PokemonJumpGfx *); static void FreeWindowsAndDigitObj(void); static void SetUpPokeJumpGfxFuncById(int); static bool32 IsPokeJumpGfxFuncFinished(void); static void SetUpResetVineGfx(void); static bool32 ResetVineGfx(void); static void PrintPrizeMessage(u16, u16); static void PrintPrizeFilledBagMessage(u16); static void PrintNoRoomForPrizeMessage(u16); static bool32 DoPrizeMessageAndFanfare(void); static void ClearMessageWindow(void); static void SetMonSpriteY(u32, s16); static void StartMonHitShake(u8); static bool32 RemoveMessageWindow(void); static void PrintScore(int); static s8 HandlePlayAgainInput(void); static int DoSameJumpTimeBonus(u8); static void PrintJumpsInRow(u16); static void StartMonHitFlash(u8); static int IsMonHitShakeActive(int); static void StopMonHitFlash(void); static void ResetMonSpriteSubpriorities(void); static void StartMonIntroBounce(int); static int IsMonIntroBounceActive(void); static void SendPacket_MonInfo(struct PokemonJump_MonInfo *); static bool32 RecvPacket_MonInfo(int, struct PokemonJump_MonInfo *); static void SendPacket_LeaderState(struct PokemonJump_Player *, struct PokemonJump_CommData *); static bool32 RecvPacket_LeaderState(struct PokemonJump_Player *, struct PokemonJump_CommData *); static void SendPacket_MemberState(struct PokemonJump_Player *, u8, u16); static bool32 RecvPacket_MemberStateToLeader(struct PokemonJump_Player *, int, u8 *, u16 *); static bool32 RecvPacket_MemberStateToMember(struct PokemonJump_Player *, int); static bool32 TryUpdateRecords(u32, u16, u16); static void IncrementGamesWithMaxPlayers(void); static void Task_RunPokeJumpGfxFunc(u8); static void ShowBonus(u8); static void Task_UpdateBonus(u8); static void LoadPokeJumpGfx(void); static void InitDigitPrinters(void); static void PrintScoreSuffixes(void); static void CreateJumpMonSprites(void); static void AddPlayerNameWindows(void); static void DrawPlayerNameWindows(void); static void SetUpPokeJumpGfxFunc(void (*func)(void)); static void PrintPokeJumpPlayerNames(bool32); static u32 AddMessageWindow(u32, u32, u32, u32); static void CreatePokeJumpYesNoMenu(u16, u16, u8); static void PrintPlayerNamesNoHighlight(void); static void PrintPlayerNamesWithHighlight(void); static void ErasePlayerNames(void); static void Msg_WantToPlayAgain(void); static void Msg_SavingDontTurnOff(void); static void EraseMessage(void); static void Msg_SomeoneDroppedOut(void); static void DoPokeJumpCountdown(void); static void Msg_CommunicationStandby(void); static void Task_ShowPokemonJumpRecords(u8); static void PrintRecordsText(u16, int); static void TruncateToFirstWordOnly(u8 *); EWRAM_DATA static struct PokemonJump *sPokemonJump = NULL; EWRAM_DATA static struct PokemonJumpGfx *sPokemonJumpGfx = NULL; /* According to the clerk, the Pokémon allowed in Pokémon Jump are all <= 28 inches, and do not only swim, burrow, or fly. */ static const struct PokemonJumpMons sPokeJumpMons[] = { { .species = SPECIES_BULBASAUR, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_CHARMANDER, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_SQUIRTLE, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_CATERPIE, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_METAPOD, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_WEEDLE, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_KAKUNA, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_RATTATA, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_RATICATE, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_PIKACHU, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_SANDSHREW, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_NIDORAN_F, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_NIDORAN_M, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_CLEFAIRY, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_VULPIX, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_JIGGLYPUFF, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_ODDISH, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_PARAS, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_MEOWTH, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_PSYDUCK, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_MANKEY, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_GROWLITHE, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_POLIWAG, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_BELLSPROUT, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_SHELLDER, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_KRABBY, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_EXEGGCUTE, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_CUBONE, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_DITTO, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_EEVEE, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_OMANYTE, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_KABUTO, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_CHIKORITA, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_CYNDAQUIL, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_TOTODILE, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_SPINARAK, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_PICHU, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_CLEFFA, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_IGGLYBUFF, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_TOGEPI, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_MAREEP, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_BELLOSSOM, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_MARILL, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_SUNKERN, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_WOOPER, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_PINECO, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_SNUBBULL, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_SHUCKLE, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_TEDDIURSA, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_SLUGMA, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_SWINUB, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_HOUNDOUR, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_PHANPY, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_PORYGON2, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_TYROGUE, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_SMOOCHUM, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_ELEKID, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_MAGBY, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_LARVITAR, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_TREECKO, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_TORCHIC, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_MUDKIP, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_MARSHTOMP, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_POOCHYENA, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_ZIGZAGOON, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_LINOONE, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_WURMPLE, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_SILCOON, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_CASCOON, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_LOTAD, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_SEEDOT, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_RALTS, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_KIRLIA, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_SURSKIT, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_SHROOMISH, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_NINCADA, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_WHISMUR, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_AZURILL, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_SKITTY, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_SABLEYE, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_MAWILE, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_ARON, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_MEDITITE, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_ELECTRIKE, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_PLUSLE, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_MINUN, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_VOLBEAT, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_ILLUMISE, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_ROSELIA, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_GULPIN, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_NUMEL, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_TORKOAL, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_SPOINK, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_TRAPINCH, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_CACNEA, .jumpType = JUMP_TYPE_SLOW, }, { .species = SPECIES_ANORITH, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_WYNAUT, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_SNORUNT, .jumpType = JUMP_TYPE_NORMAL, }, { .species = SPECIES_CLAMPERL, .jumpType = JUMP_TYPE_FAST, }, { .species = SPECIES_BAGON, .jumpType = JUMP_TYPE_FAST, }, }; void StartPokemonJump(u16 partyId, MainCallback exitCallback) { u8 taskId; if (gReceivedRemoteLinkPlayers) { sPokemonJump = Alloc(sizeof(*sPokemonJump)); if (sPokemonJump) { ResetTasks(); taskId = CreateTask(Task_StartPokemonJump, 1); sPokemonJump->mainState = 0; sPokemonJump->exitCallback = exitCallback; sPokemonJump->taskId = taskId; sPokemonJump->multiplayerId = GetMultiplayerId(); InitJumpMonInfo(&sPokemonJump->monInfo[sPokemonJump->multiplayerId], &gPlayerParty[partyId]); InitGame(sPokemonJump); SetWordTaskArg(taskId, 2, (u32)sPokemonJump); SetMainCallback2(CB2_PokemonJump); return; } } // Exit - Players not connected, or alloc failed SetMainCallback2(exitCallback); } static void FreePokemonJump(void) { FreeWindowsAndDigitObj(); Free(sPokemonJump); } static void InitGame(struct PokemonJump *jump) { jump->numPlayers = GetLinkPlayerCount(); jump->comm.funcId = FUNC_RESET_GAME; jump->comm.data = 0; InitPlayerAndJumpTypes(); ResetForNewGame(jump); if (jump->numPlayers == MAX_RFU_PLAYERS) IncrementGamesWithMaxPlayers(); } static void ResetForNewGame(struct PokemonJump *jump) { int i; jump->vineState = VINE_UPSWING_LOWER; jump->prevVineState = VINE_UPSWING_LOWER; jump->vineTimer = 0; jump->vineSpeed = 0; jump->updateScore = FALSE; jump->isLeader = GetMultiplayerId() == 0; jump->mainState = 0; jump->helperState = 0; jump->excellentsInRow = 0; jump->excellentsInRowRecord = 0; jump->initScoreUpdate = FALSE; jump->unused2 = 0; jump->unused3 = 0; jump->numPlayersAtPeak = 0; jump->allowVineUpdates = FALSE; jump->allPlayersReady = FALSE; jump->funcActive = TRUE; jump->comm.jumpScore = 0; jump->comm.receivedBonusFlags = 0; jump->comm.jumpsInRow = 0; jump->unused4 = TRUE; jump->showBonus = FALSE; jump->skipJumpUpdate = FALSE; jump->giveBonus = FALSE; jump->linkTimer = 0; jump->linkTimerLimit = 0; ResetPlayersForNewGame(); ResetPlayersJumpStates(); for (i = 0; i < MAX_RFU_PLAYERS; i++) { jump->atJumpPeak[i] = FALSE; jump->jumpTimeStarts[i] = 0; } } static void InitPlayerAndJumpTypes(void) { int i, index; for (i = 0; i < MAX_RFU_PLAYERS; i++) { index = GetPokemonJumpSpeciesIdx(sPokemonJump->monInfo[i].species); sPokemonJump->players[i].monJumpType = sPokeJumpMons[index].jumpType; } sPokemonJump->player = &sPokemonJump->players[sPokemonJump->multiplayerId]; } static void ResetPlayersForNewGame(void) { int i; for (i = 0; i < MAX_RFU_PLAYERS; i++) { sPokemonJump->players[i].jumpTimeStart = 0; sPokemonJump->players[i].monState = MONSTATE_NORMAL; sPokemonJump->players[i].prevMonState = MONSTATE_NORMAL; sPokemonJump->players[i].jumpOffset = 0; sPokemonJump->players[i].jumpOffsetIdx = INT_MAX; sPokemonJump->players[i].jumpState = JUMPSTATE_NONE; sPokemonJump->memberFuncIds[i] = FUNC_NONE; } } static s16 GetPokemonJumpSpeciesIdx(u16 species) { u32 i; for (i = 0; i < ARRAY_COUNT(sPokeJumpMons); i++) { if (sPokeJumpMons[i].species == species) return i; } return -1; // species isnt allowed } static void InitJumpMonInfo(struct PokemonJump_MonInfo *monInfo, struct Pokemon *mon) { monInfo->species = GetMonData(mon, MON_DATA_SPECIES); monInfo->otId = GetMonData(mon, MON_DATA_OT_ID); monInfo->personality = GetMonData(mon, MON_DATA_PERSONALITY); } static void VBlankCB_PokemonJump(void) { TransferPlttBuffer(); LoadOam(); ProcessSpriteCopyRequests(); } static void CB2_PokemonJump(void) { RunTasks(); AnimateSprites(); BuildOamBuffer(); UpdatePaletteFade(); } static void SetPokeJumpTask(TaskFunc func) { sPokemonJump->taskId = CreateTask(func, 1); sPokemonJump->mainState = 0; } static void Task_StartPokemonJump(u8 taskId) { switch (sPokemonJump->mainState) { case 0: SetVBlankCallback(NULL); ResetSpriteData(); FreeAllSpritePalettes(); SetTaskWithPokeJumpStruct(Task_CommunicateMonInfo, 5); FadeOutMapMusic(4); sPokemonJump->mainState++; break; case 1: if (!FuncIsActiveTask(Task_CommunicateMonInfo)) { StartPokeJumpGfx(&sPokemonJump->jumpGfx); LoadWirelessStatusIndicatorSpriteGfx(); CreateWirelessStatusIndicatorSprite(0, 0); sPokemonJump->mainState++; } break; case 2: if (!IsPokeJumpGfxFuncFinished() && IsNotWaitingForBGMStop() == TRUE) { FadeOutAndPlayNewMapMusic(MUS_RG_POKE_JUMP, 8); sPokemonJump->mainState++; } break; case 3: if (IsLinkTaskFinished()) { BlendPalettes(PALETTES_ALL, 16, RGB_BLACK); BeginNormalPaletteFade(PALETTES_ALL, -1, 16, 0, RGB_BLACK); SetVBlankCallback(VBlankCB_PokemonJump); sPokemonJump->mainState++; } break; case 4: UpdatePaletteFade(); if (!gPaletteFade.active) { sPokemonJump->startDelayTimer = 0; sPokemonJump->mainState++; } break; case 5: sPokemonJump->startDelayTimer++; if (sPokemonJump->startDelayTimer >= 20) { if (sPokemonJump->isLeader) SetPokeJumpTask(Task_PokemonJump_Leader); else SetPokeJumpTask(Task_PokemonJump_Member); InitVineState(); DestroyTask(taskId); } break; } } static void SetLinkTimeInterval(int intervalId) { if (intervalId == LINK_INTERVAL_NONE) { // Link data is sent when timer reaches 0. // Set timer to 1 and set limit to special // 'stopped' value so timer won't change sPokemonJump->linkTimerLimit = LINK_TIMER_STOPPED; sPokemonJump->linkTimer = 1; } else { sPokemonJump->linkTimerLimit = (1 << (intervalId - 1)) - 1; sPokemonJump->linkTimer = 0; } } static void SetFunc_Leader(u8 funcId) { int i; sPokemonJump->comm.funcId = funcId; sPokemonJump->mainState = 0; sPokemonJump->helperState = 0; sPokemonJump->funcActive = TRUE; sPokemonJump->allPlayersReady = FALSE; for (i = 1; i < sPokemonJump->numPlayers; i++) sPokemonJump->players[i].funcFinished = FALSE; } static void RecvLinkData_Leader(void) { int i; int numReady; u16 monState; u8 funcId; u16 playAgainState; for (i = 1, numReady = 0; i < sPokemonJump->numPlayers; i++) { monState = sPokemonJump->players[i].monState; if (RecvPacket_MemberStateToLeader(&sPokemonJump->players[i], i, &funcId, &playAgainState)) { sPokemonJump->playAgainStates[i] = playAgainState; sPokemonJump->memberFuncIds[i] = funcId; sPokemonJump->players[i].prevMonState = monState; } // Group member has finished currently assigned function if (sPokemonJump->players[i].funcFinished && sPokemonJump->memberFuncIds[i] == sPokemonJump->comm.funcId) numReady++; } if (numReady == sPokemonJump->numPlayers - 1) sPokemonJump->allPlayersReady = TRUE; } static bool32 (* const sPokeJumpLeaderFuncs[])(void) = { [FUNC_GAME_INTRO] = GameIntro_Leader, [FUNC_WAIT_ROUND] = WaitRound_Leader, [FUNC_GAME_ROUND] = GameRound_Leader, [FUNC_GAME_OVER] = GameOver_Leader, [FUNC_ASK_PLAY_AGAIN] = AskPlayAgain_Leader, [FUNC_RESET_GAME] = ResetGame_Leader, [FUNC_EXIT] = ExitGame, [FUNC_GIVE_PRIZE] = GivePrize_Leader, [FUNC_SAVE] = SavePokeJump, }; static void Task_PokemonJump_Leader(u8 taskId) { RecvLinkData_Leader(); TryUpdateScore(); if (!sPokemonJump->funcActive && sPokemonJump->allPlayersReady) { SetFunc_Leader(sPokemonJump->nextFuncId); SetLinkTimeInterval(LINK_INTERVAL_SHORT); } if (sPokemonJump->funcActive == TRUE) { if (!sPokeJumpLeaderFuncs[sPokemonJump->comm.funcId]()) { sPokemonJump->funcActive = FALSE; sPokemonJump->players[sPokemonJump->multiplayerId].funcFinished = TRUE; } } UpdateGame(); SendLinkData_Leader(); } static void SendLinkData_Leader(void) { if (!sPokemonJump->linkTimer) SendPacket_LeaderState(sPokemonJump->players, &sPokemonJump->comm); if (sPokemonJump->linkTimerLimit != LINK_TIMER_STOPPED) { sPokemonJump->linkTimer++; sPokemonJump->linkTimer &= sPokemonJump->linkTimerLimit; } } static void SetFunc_Member(u8 funcId) { sPokemonJump->comm.funcId = funcId; sPokemonJump->mainState = 0; sPokemonJump->helperState = 0; sPokemonJump->funcActive = TRUE; sPokemonJump->players[sPokemonJump->multiplayerId].funcFinished = FALSE; } static void RecvLinkData_Member(void) { int i; u16 monState; struct PokemonJump_CommData leaderData; monState = sPokemonJump->players[0].monState; if (RecvPacket_LeaderState(sPokemonJump->players, &leaderData)) { if (sPokemonJump->players[sPokemonJump->multiplayerId].funcFinished == TRUE && leaderData.funcId != sPokemonJump->comm.funcId) { SetFunc_Member(leaderData.funcId); } if (sPokemonJump->comm.jumpScore != leaderData.jumpScore) { sPokemonJump->comm.jumpScore = leaderData.jumpScore; sPokemonJump->updateScore = TRUE; sPokemonJump->comm.receivedBonusFlags = leaderData.receivedBonusFlags; if (sPokemonJump->comm.receivedBonusFlags) sPokemonJump->showBonus = TRUE; else sPokemonJump->showBonus = FALSE; } sPokemonJump->comm.data = leaderData.data; sPokemonJump->comm.jumpsInRow = leaderData.jumpsInRow; sPokemonJump->players[0].prevMonState = monState; } for (i = 1; i < sPokemonJump->numPlayers; i++) { if (i != sPokemonJump->multiplayerId) { monState = sPokemonJump->players[i].monState; if (RecvPacket_MemberStateToMember(&sPokemonJump->players[i], i)) sPokemonJump->players[i].prevMonState = monState; } } } static bool32 (* const sPokeJumpMemberFuncs[])(void) = { [FUNC_GAME_INTRO] = GameIntro_Member, [FUNC_WAIT_ROUND] = WaitRound_Member, [FUNC_GAME_ROUND] = GameRound_Member, [FUNC_GAME_OVER] = GameOver_Member, [FUNC_ASK_PLAY_AGAIN] = AskPlayAgain_Member, [FUNC_RESET_GAME] = ResetGame_Member, [FUNC_EXIT] = ExitGame, [FUNC_GIVE_PRIZE] = GivePrize_Member, [FUNC_SAVE] = SavePokeJump, }; static void Task_PokemonJump_Member(u8 taskId) { RecvLinkData_Member(); if (sPokemonJump->funcActive) { if (!sPokeJumpMemberFuncs[sPokemonJump->comm.funcId]()) { sPokemonJump->funcActive = FALSE; sPokemonJump->players[sPokemonJump->multiplayerId].funcFinished = TRUE; SetLinkTimeInterval(LINK_INTERVAL_SHORT); } } UpdateGame(); SendLinkData_Member(); } static void SendLinkData_Member(void) { if (!sPokemonJump->linkTimer) SendPacket_MemberState(&sPokemonJump->players[sPokemonJump->multiplayerId], sPokemonJump->comm.funcId, sPokemonJump->playAgainComm); if (sPokemonJump->linkTimerLimit != LINK_TIMER_STOPPED) { sPokemonJump->linkTimer++; sPokemonJump->linkTimer &= sPokemonJump->linkTimerLimit; } } static bool32 GameIntro_Leader(void) { switch (sPokemonJump->mainState) { case 0: SetLinkTimeInterval(LINK_INTERVAL_SHORT); sPokemonJump->mainState++; // fall through case 1: if (!DoGameIntro()) { sPokemonJump->comm.data = sPokemonJump->vineTimer; sPokemonJump->nextFuncId = FUNC_WAIT_ROUND; return FALSE; } break; } return TRUE; } static bool32 GameIntro_Member(void) { switch (sPokemonJump->mainState) { case 0: SetLinkTimeInterval(LINK_INTERVAL_NONE); sPokemonJump->rngSeed = sPokemonJump->comm.data; sPokemonJump->mainState++; // fall through case 1: return DoGameIntro(); } return TRUE; } static bool32 WaitRound_Leader(void) { switch (sPokemonJump->mainState) { case 0: ResetPlayersJumpStates(); SetLinkTimeInterval(LINK_INTERVAL_LONG); sPokemonJump->mainState++; break; case 1: if (sPokemonJump->allPlayersReady) { sPokemonJump->nextFuncId = FUNC_GAME_ROUND; return FALSE; } break; } return TRUE; } static bool32 WaitRound_Member(void) { switch (sPokemonJump->mainState) { case 0: ResetPlayersJumpStates(); SetLinkTimeInterval(LINK_INTERVAL_NONE); sPokemonJump->vineTimer = sPokemonJump->comm.data; sPokemonJump->mainState++; // fall through case 1: if (AreLinkQueuesEmpty()) return FALSE; break; } return TRUE; } static bool32 GameRound_Leader(void) { if (!HandleSwingRound()) { sPokemonJump->comm.data = sPokemonJump->vineTimer; sPokemonJump->nextFuncId = FUNC_WAIT_ROUND; } else if (UpdateVineHitStates()) { return TRUE; } else { // Someone hit the vine ResetVineAfterHit(); sPokemonJump->nextFuncId = FUNC_GAME_OVER; } return FALSE; } static bool32 GameRound_Member(void) { if (!HandleSwingRound()) ; else if (UpdateVineHitStates()) return TRUE; else // Someone hit the vine ResetVineAfterHit(); return FALSE; } static bool32 GameOver_Leader(void) { switch (sPokemonJump->mainState) { case 0: UpdateVineHitStates(); if (AllPlayersJumpedOrHit()) sPokemonJump->mainState++; break; case 1: if (!DoVineHitEffect()) { if (HasEnoughScoreForPrize()) { sPokemonJump->comm.data = GetPrizeData(); sPokemonJump->nextFuncId = FUNC_GIVE_PRIZE; } else if (sPokemonJump->comm.jumpsInRow >= 200) { sPokemonJump->comm.data = sPokemonJump->excellentsInRowRecord; sPokemonJump->nextFuncId = FUNC_SAVE; } else { sPokemonJump->comm.data = sPokemonJump->excellentsInRowRecord; sPokemonJump->nextFuncId = FUNC_ASK_PLAY_AGAIN; } sPokemonJump->mainState++; return FALSE; } break; case 2: return FALSE; } return TRUE; } static bool32 GameOver_Member(void) { switch (sPokemonJump->mainState) { case 0: if (!UpdateVineHitStates()) ResetVineAfterHit(); if (AllPlayersJumpedOrHit()) sPokemonJump->mainState++; break; case 1: if (!DoVineHitEffect()) { sPokemonJump->mainState++; return FALSE; } break; case 2: return FALSE; } return TRUE; } static bool32 AskPlayAgain_Leader(void) { switch (sPokemonJump->mainState) { case 0: SetLinkTimeInterval(LINK_INTERVAL_MEDIUM); sPokemonJump->mainState++; // fall through case 1: if (!DoPlayAgainPrompt()) { TryUpdateRecords(sPokemonJump->comm.jumpScore, sPokemonJump->comm.jumpsInRow, sPokemonJump->comm.data); sPokemonJump->mainState++; } break; case 2: if (sPokemonJump->allPlayersReady) { if (ShouldPlayAgain()) sPokemonJump->nextFuncId = FUNC_RESET_GAME; else sPokemonJump->nextFuncId = FUNC_EXIT; sPokemonJump->mainState++; return FALSE; } break; case 3: return FALSE; } return TRUE; } static bool32 AskPlayAgain_Member(void) { switch (sPokemonJump->mainState) { case 0: SetLinkTimeInterval(LINK_INTERVAL_NONE); sPokemonJump->mainState++; // fall through case 1: if (!DoPlayAgainPrompt()) { TryUpdateRecords(sPokemonJump->comm.jumpScore, sPokemonJump->comm.jumpsInRow, sPokemonJump->comm.data); sPokemonJump->playAgainComm = sPokemonJump->playAgainState; return FALSE; } break; } return TRUE; } static bool32 ResetGame_Leader(void) { switch (sPokemonJump->mainState) { case 0: if (!CloseMessageAndResetScore()) sPokemonJump->mainState++; break; case 1: if (sPokemonJump->allPlayersReady) { ResetForNewGame(sPokemonJump); sPokemonJump->rngSeed = Random(); sPokemonJump->comm.data = sPokemonJump->rngSeed; sPokemonJump->nextFuncId = FUNC_GAME_INTRO; return FALSE; } break; } return TRUE; } static bool32 ResetGame_Member(void) { switch (sPokemonJump->mainState) { case 0: if (!CloseMessageAndResetScore()) { ResetForNewGame(sPokemonJump); sPokemonJump->mainState++; return FALSE; } break; case 1: return FALSE; } return TRUE; } static bool32 ExitGame(void) { switch (sPokemonJump->mainState) { case 0: sPokemonJump->mainState = 1; break; case 1: SetLinkTimeInterval(LINK_INTERVAL_NONE); sPokemonJump->mainState++; break; case 2: if (!ClosePokeJumpLink()) { SetMainCallback2(sPokemonJump->exitCallback); FreePokemonJump(); } break; } return TRUE; } static bool32 GivePrize_Leader(void) { switch (sPokemonJump->mainState) { case 0: SetLinkTimeInterval(LINK_INTERVAL_MEDIUM); sPokemonJump->mainState++; break; case 1: if (!TryGivePrize()) { sPokemonJump->comm.data = sPokemonJump->excellentsInRowRecord; sPokemonJump->nextFuncId = FUNC_SAVE; return FALSE; } break; } return TRUE; } static bool32 GivePrize_Member(void) { SetLinkTimeInterval(LINK_INTERVAL_NONE); if (!TryGivePrize()) return FALSE; else return TRUE; } static bool32 SavePokeJump(void) { switch (sPokemonJump->mainState) { case 0: TryUpdateRecords(sPokemonJump->comm.jumpScore, sPokemonJump->comm.jumpsInRow, sPokemonJump->comm.data); SetUpPokeJumpGfxFuncById(GFXFUNC_MSG_SAVING); sPokemonJump->mainState++; break; case 1: if (!IsPokeJumpGfxFuncFinished()) { SetLinkTimeInterval(LINK_INTERVAL_NONE); sPokemonJump->mainState++; } break; case 2: if (AreLinkQueuesEmpty()) { CreateTask(Task_LinkFullSave, 6); sPokemonJump->mainState++; } break; case 3: if (!FuncIsActiveTask(Task_LinkFullSave)) { ClearMessageWindow(); sPokemonJump->mainState++; } break; case 4: if (!RemoveMessageWindow()) { sPokemonJump->nextFuncId = FUNC_ASK_PLAY_AGAIN; return FALSE; } break; } return TRUE; } static bool32 DoGameIntro(void) { switch (sPokemonJump->helperState) { case 0: SetUpPokeJumpGfxFuncById(GFXFUNC_SHOW_NAMES_HIGHLIGHT); ResetMonSpriteSubpriorities(); sPokemonJump->helperState++; break; case 1: if (!IsPokeJumpGfxFuncFinished()) { StartMonIntroBounce(sPokemonJump->multiplayerId); sPokemonJump->timer = 0; sPokemonJump->helperState++; } break; case 2: if (++sPokemonJump->timer > 120) { SetUpPokeJumpGfxFuncById(GFXFUNC_ERASE_NAMES); sPokemonJump->helperState++; } break; case 3: if (IsPokeJumpGfxFuncFinished() != TRUE && IsMonIntroBounceActive() != TRUE) sPokemonJump->helperState++; break; case 4: SetUpPokeJumpGfxFuncById(GFXFUNC_COUNTDOWN); sPokemonJump->helperState++; break; case 5: if (!IsPokeJumpGfxFuncFinished()) { DisallowVineUpdates(); SetUpResetVineGfx(); sPokemonJump->helperState++; } break; case 6: if (!ResetVineGfx()) { AllowVineUpdates(); ResetVineState(); sPokemonJump->helperState++; return FALSE; } break; case 7: return FALSE; } return TRUE; } // Update the vine and wait for player to input a jump // Returns false when vine reaches the 'hit' point, after // which input is ignored static bool32 HandleSwingRound(void) { UpdateVineState(); if (sPokemonJump->ignoreJumpInput) { sPokemonJump->ignoreJumpInput = FALSE; return FALSE; } switch (sPokemonJump->helperState) { case 0: if (IsPlayersMonState(MONSTATE_NORMAL)) sPokemonJump->helperState++; else break; // fall through case 1: if (JOY_NEW(A_BUTTON)) { SetMonStateJump(); SetLinkTimeInterval(LINK_INTERVAL_SHORT); sPokemonJump->helperState++; } break; case 2: if (IsPlayersMonState(MONSTATE_JUMP) == TRUE) sPokemonJump->helperState++; break; case 3: if (IsPlayersMonState(MONSTATE_NORMAL) == TRUE) sPokemonJump->helperState = 0; break; } return TRUE; } static bool32 DoVineHitEffect(void) { int i; switch (sPokemonJump->helperState) { case 0: for (i = 0; i < sPokemonJump->numPlayers; i++) { if (IsMonHitShakeActive(i) == TRUE) return TRUE; } sPokemonJump->helperState++; break; case 1: for (i = 0; i < sPokemonJump->numPlayers; i++) { if (sPokemonJump->players[i].monState == MONSTATE_HIT) StartMonHitFlash(i); } SetUpPokeJumpGfxFuncById(GFXFUNC_SHOW_NAMES); sPokemonJump->timer = 0; sPokemonJump->helperState++; break; case 2: if (++sPokemonJump->timer > 100) { SetUpPokeJumpGfxFuncById(GFXFUNC_ERASE_NAMES); sPokemonJump->timer = 0; sPokemonJump->helperState++; } break; case 3: if (!IsPokeJumpGfxFuncFinished()) { StopMonHitFlash(); sPokemonJump->comm.receivedBonusFlags = 0; ResetPlayersMonState(); sPokemonJump->helperState++; return FALSE; } break; case 4: return FALSE; } return TRUE; } static bool32 TryGivePrize(void) { switch (sPokemonJump->helperState) { case 0: UnpackPrizeData(sPokemonJump->comm.data, &sPokemonJump->prizeItemId, &sPokemonJump->prizeItemQuantity); PrintPrizeMessage(sPokemonJump->prizeItemId, sPokemonJump->prizeItemQuantity); sPokemonJump->helperState++; break; case 1: case 4: if (!DoPrizeMessageAndFanfare()) { sPokemonJump->timer = 0; sPokemonJump->helperState++; } break; case 2: case 5: // Wait to continue after message sPokemonJump->timer++; if (JOY_NEW(A_BUTTON | B_BUTTON) || sPokemonJump->timer > 180) { ClearMessageWindow(); sPokemonJump->helperState++; } break; case 3: if (!RemoveMessageWindow()) { sPokemonJump->prizeItemQuantity = GetQuantityLimitedByBag(sPokemonJump->prizeItemId, sPokemonJump->prizeItemQuantity); if (sPokemonJump->prizeItemQuantity && AddBagItem(sPokemonJump->prizeItemId, sPokemonJump->prizeItemQuantity)) { if (!CheckBagHasSpace(sPokemonJump->prizeItemId, 1)) { // An item was given successfully, but no room for any more. // It's possible the full prize quantity had to be limited PrintPrizeFilledBagMessage(sPokemonJump->prizeItemId); sPokemonJump->helperState = 4; // Do message } else { sPokemonJump->helperState = 6; // Exit break; } } else { PrintNoRoomForPrizeMessage(sPokemonJump->prizeItemId); sPokemonJump->helperState = 4; // Do message } } break; case 6: if (!RemoveMessageWindow()) return FALSE; break; } return TRUE; } static bool32 DoPlayAgainPrompt(void) { s8 input; switch (sPokemonJump->helperState) { case 0: SetUpPokeJumpGfxFuncById(GFXFUNC_MSG_PLAY_AGAIN); sPokemonJump->helperState++; break; case 1: if (!IsPokeJumpGfxFuncFinished()) sPokemonJump->helperState++; break; case 2: input = HandlePlayAgainInput(); switch (input) { case MENU_B_PRESSED: case 1: // No sPokemonJump->playAgainState = PLAY_AGAIN_NO; SetUpPokeJumpGfxFuncById(GFXFUNC_ERASE_MSG); sPokemonJump->helperState++; break; case 0: // Yes sPokemonJump->playAgainState = PLAY_AGAIN_YES; SetUpPokeJumpGfxFuncById(GFXFUNC_ERASE_MSG); sPokemonJump->helperState++; break; } break; case 3: if (!IsPokeJumpGfxFuncFinished()) sPokemonJump->helperState++; break; case 4: SetUpPokeJumpGfxFuncById(GFXFUNC_MSG_COMM_STANDBY); sPokemonJump->helperState++; break; case 5: if (!IsPokeJumpGfxFuncFinished()) { sPokemonJump->helperState++; return FALSE; } break; case 6: return FALSE; } return TRUE; } static bool32 ClosePokeJumpLink(void) { switch (sPokemonJump->helperState) { case 0: ClearMessageWindow(); sPokemonJump->helperState++; break; case 1: if (!RemoveMessageWindow()) { SetUpPokeJumpGfxFuncById(GFXFUNC_MSG_PLAYER_DROPPED); sPokemonJump->helperState++; } break; case 2: if (!IsPokeJumpGfxFuncFinished()) { sPokemonJump->timer = 0; sPokemonJump->helperState++; } break; case 3: if (++sPokemonJump->timer > 120) { BeginNormalPaletteFade(PALETTES_ALL, -1, 0, 16, RGB_BLACK); sPokemonJump->helperState++; } break; case 4: if (!gPaletteFade.active) { SetCloseLinkCallback(); sPokemonJump->helperState++; } break; case 5: if (!gReceivedRemoteLinkPlayers) return FALSE; break; } return TRUE; } static bool32 CloseMessageAndResetScore(void) { switch (sPokemonJump->helperState) { case 0: ClearMessageWindow(); PrintScore(0); sPokemonJump->helperState++; break; case 1: if (!RemoveMessageWindow()) { sPokemonJump->helperState++; return FALSE; } break; case 2: return FALSE; } return TRUE; } #define tState data[0] #define tNumReceived data[1] #define tReceivedPacket(playerId) data[(playerId) + 2] #define DATAIDX_GAME_STRUCT 14 static void Task_CommunicateMonInfo(u8 taskId) { int i; s16 *data = gTasks[taskId].data; struct PokemonJump *jump = (struct PokemonJump *)GetWordTaskArg(taskId, DATAIDX_GAME_STRUCT); switch (tState) { case 0: for (i = 0; i < MAX_RFU_PLAYERS; i++) tReceivedPacket(i) = FALSE; tState++; // fall through case 1: SendPacket_MonInfo(&jump->monInfo[jump->multiplayerId]); for (i = 0; i < MAX_RFU_PLAYERS; i++) { if (!tReceivedPacket(i) && RecvPacket_MonInfo(i, &jump->monInfo[i])) { StringCopy(jump->players[i].name, gLinkPlayers[i].name); tReceivedPacket(i) = TRUE; tNumReceived++; if (tNumReceived == jump->numPlayers) { InitPlayerAndJumpTypes(); DestroyTask(taskId); break; } } } break; } } static void SetTaskWithPokeJumpStruct(TaskFunc func, u8 taskPriority) { u8 taskId = CreateTask(func, taskPriority); SetWordTaskArg(taskId, DATAIDX_GAME_STRUCT, (u32)sPokemonJump); } #undef tState #undef tNumReceived #undef tReceivedPacket #undef DATAIDX_GAME_STRUCT static void InitVineState(void) { sPokemonJump->vineTimer = 0; sPokemonJump->vineState = VINE_UPSWING_LOWER; sPokemonJump->vineStateTimer = 0; sPokemonJump->vineSpeed = 0; sPokemonJump->ignoreJumpInput = FALSE; sPokemonJump->gameOver = FALSE; } static void ResetVineState(void) { sPokemonJump->vineTimer = 0; sPokemonJump->vineStateTimer = VINE_STATE_TIMER(VINE_UPSWING_LOWER); sPokemonJump->vineState = VINE_UPSWING_LOW; sPokemonJump->ignoreJumpInput = FALSE; sPokemonJump->gameOver = FALSE; sPokemonJump->vineSpeedStage = 0; sPokemonJump->vineBaseSpeedIdx = 0; sPokemonJump->vineSpeedAccel = 0; sPokemonJump->vineSpeedDelay = 0; sPokemonJump->atMaxSpeedStage = FALSE; UpdateVineSpeed(); } static void UpdateVineState(void) { if (sPokemonJump->allowVineUpdates) { sPokemonJump->vineTimer++; sPokemonJump->vineStateTimer += GetVineSpeed(); if (sPokemonJump->vineStateTimer >= VINE_STATE_TIMER(NUM_VINESTATES - 1)) sPokemonJump->vineStateTimer -= VINE_STATE_TIMER(NUM_VINESTATES - 1); sPokemonJump->prevVineState = sPokemonJump->vineState; sPokemonJump->vineState = sPokemonJump->vineStateTimer >> 8; // If beginning upswing if (sPokemonJump->vineState > VINE_UPSWING_LOWER && sPokemonJump->prevVineState < VINE_UPSWING_LOW) { sPokemonJump->ignoreJumpInput++; UpdateVineSpeed(); } } } static int GetVineSpeed(void) { int speed; if (sPokemonJump->gameOver) return 0; speed = sPokemonJump->vineSpeed; if (sPokemonJump->vineStateTimer <= VINE_STATE_TIMER(VINE_LOWEST)) { // If at or below lowest, then vine is in downswing // Increase speed in downswing sPokemonJump->vineSpeedAccel += 80; speed += sPokemonJump->vineSpeedAccel / 256; } return speed; } static const u16 sVineBaseSpeeds[] = {26, 31, 36, 41, 46, 51, 56, 61}; static const u16 sVineSpeedDelays[] = {0, 1, 1, 2}; static void UpdateVineSpeed(void) { int baseSpeed; sPokemonJump->vineSpeedAccel = 0; if (sPokemonJump->vineSpeedDelay) { sPokemonJump->vineSpeedDelay--; if (sPokemonJump->atMaxSpeedStage) { if (PokeJumpRandom() % 4) { sPokemonJump->vineSpeed = sPokemonJump->nextVineSpeed; } else { if (sPokemonJump->nextVineSpeed > 54) sPokemonJump->vineSpeed = 30; else sPokemonJump->vineSpeed = 82; } } } else { if (!(sPokemonJump->vineBaseSpeedIdx & ARRAY_COUNT(sVineBaseSpeeds))) { sPokemonJump->nextVineSpeed = sVineBaseSpeeds[sPokemonJump->vineBaseSpeedIdx] + (sPokemonJump->vineSpeedStage * 7); sPokemonJump->vineSpeedDelay = sVineSpeedDelays[PokeJumpRandom() % ARRAY_COUNT(sVineSpeedDelays)] + 2; sPokemonJump->vineBaseSpeedIdx++; } else { if (sPokemonJump->vineBaseSpeedIdx == ARRAY_COUNT(sVineBaseSpeeds)) { if (sPokemonJump->vineSpeedStage < 3) sPokemonJump->vineSpeedStage++; else sPokemonJump->atMaxSpeedStage = TRUE; } baseSpeed = sVineBaseSpeeds[15 - sPokemonJump->vineBaseSpeedIdx]; sPokemonJump->nextVineSpeed = baseSpeed + (sPokemonJump->vineSpeedStage * 7); if (++sPokemonJump->vineBaseSpeedIdx > 15) { if (PokeJumpRandom() % 4 == 0) sPokemonJump->nextVineSpeed -= 5; sPokemonJump->vineBaseSpeedIdx = 0; } } sPokemonJump->vineSpeed = sPokemonJump->nextVineSpeed; } } static int PokeJumpRandom(void) { sPokemonJump->rngSeed = ISO_RANDOMIZE1(sPokemonJump->rngSeed); return sPokemonJump->rngSeed >> 16; } static void ResetVineAfterHit(void) { sPokemonJump->gameOver = TRUE; sPokemonJump->vineState = VINE_UPSWING_LOWER; sPokemonJump->vineStateTimer = VINE_STATE_TIMER(VINE_LOWEST); AllowVineUpdates(); } static int IsGameOver(void) { return sPokemonJump->gameOver; } static void ResetPlayersJumpStates(void) { int i; for (i = 0; i < MAX_RFU_PLAYERS; i++) sPokemonJump->players[i].jumpState = JUMPSTATE_NONE; } static void ResetPlayersMonState(void) { sPokemonJump->player->monState = MONSTATE_NORMAL; sPokemonJump->player->prevMonState = MONSTATE_NORMAL; } static bool32 IsPlayersMonState(u16 monState) { if (sPokemonJump->players[sPokemonJump->multiplayerId].monState == monState) return TRUE; else return FALSE; } static void SetMonStateJump(void) { sPokemonJump->player->jumpTimeStart = sPokemonJump->vineTimer; sPokemonJump->player->prevMonState = sPokemonJump->player->monState; sPokemonJump->player->monState = MONSTATE_JUMP; } static void SetMonStateHit(void) { sPokemonJump->player->prevMonState = sPokemonJump->player->monState; sPokemonJump->player->monState = MONSTATE_HIT; sPokemonJump->player->jumpTimeStart = sPokemonJump->vineTimer; sPokemonJump->player->jumpState = JUMPSTATE_FAILURE; } static void SetMonStateNormal(void) { sPokemonJump->player->prevMonState = sPokemonJump->player->monState; sPokemonJump->player->monState = MONSTATE_NORMAL; } static const u16 sSoundEffects[MAX_RFU_PLAYERS - 1] = {SE_SHOP, SE_SHINY, SE_M_MORNING_SUN, SE_RG_POKE_JUMP_SUCCESS}; static void UpdateGame(void) { if (sPokemonJump->updateScore) { PrintScore(sPokemonJump->comm.jumpScore); sPokemonJump->updateScore = FALSE; if (sPokemonJump->showBonus) { int numPlayers = DoSameJumpTimeBonus(sPokemonJump->comm.receivedBonusFlags); PlaySE(sSoundEffects[numPlayers - 2]); sPokemonJump->showBonus = FALSE; } } PrintJumpsInRow(sPokemonJump->comm.jumpsInRow); HandleMonState(); TryUpdateVineSwing(); } static void TryUpdateVineSwing(void) { if (sPokemonJump->allowVineUpdates) UpdateVineSwing(sPokemonJump->vineState); } static void DisallowVineUpdates(void) { sPokemonJump->allowVineUpdates = FALSE; } static void AllowVineUpdates(void) { sPokemonJump->allowVineUpdates = TRUE; } #define F_SE_JUMP (1 << 0) #define F_SE_FAIL (1 << 1) static void HandleMonState(void) { int i; int soundFlags = 0; int numPlayers = sPokemonJump->numPlayers; for (i = 0; i < numPlayers; i++) { switch (sPokemonJump->players[i].monState) { case MONSTATE_NORMAL: SetMonSpriteY(i, 0); break; case MONSTATE_JUMP: if (sPokemonJump->players[i].prevMonState != MONSTATE_JUMP || sPokemonJump->players[i].jumpTimeStart != sPokemonJump->jumpTimeStarts[i]) { // This is a new jump, play SE and init fields for jump handling if (i == sPokemonJump->multiplayerId) sPokemonJump->players[i].prevMonState = MONSTATE_JUMP; soundFlags |= F_SE_JUMP; sPokemonJump->players[i].jumpOffsetIdx = INT_MAX; sPokemonJump->jumpTimeStarts[i] = sPokemonJump->players[i].jumpTimeStart; } UpdateJump(i); break; case MONSTATE_HIT: if (sPokemonJump->players[i].prevMonState != MONSTATE_HIT) { if (i == sPokemonJump->multiplayerId) sPokemonJump->players[i].prevMonState = MONSTATE_HIT; soundFlags |= F_SE_FAIL; StartMonHitShake(i); } break; } } if (soundFlags & F_SE_FAIL) PlaySE(SE_RG_POKE_JUMP_FAILURE); else if (soundFlags & F_SE_JUMP) PlaySE(SE_LEDGE); } static const s8 sJumpOffsets[][48] = { [JUMP_TYPE_NORMAL] = { -3, -6, -8, -10, -13, -15, -17, -19, -21, -23, -25, -27, -28, -29, JUMP_PEAK, JUMP_PEAK, JUMP_PEAK, -28, -27, -26, -25, -23, -22, -20, -18, -17, -15, -13, -11, -8, -6, -4, -1}, [JUMP_TYPE_FAST] = { -3, -6, -9, -11, -14, -16, -18, -20, -22, -24, -26, -28, -29, JUMP_PEAK, JUMP_PEAK, -28, -26, -24, -22, -20, -18, -16, -14, -11, -9, -6, -4, -1}, [JUMP_TYPE_SLOW] = { -3, -6, -9, -11, -13, -15, -17, -19, -21, -23, -25, -27, -28, -29, JUMP_PEAK, JUMP_PEAK, JUMP_PEAK, JUMP_PEAK, -29, -29, -28, -28, -27, -27, -26, -25, -24, -22, -20, -18, -16, -14, -12, -11, -9, -6, -4, -1}, }; static void UpdateJump(int multiplayerId) { int jumpOffsetIdx; int jumpOffset; struct PokemonJump_Player *player; if (sPokemonJump->skipJumpUpdate) // Always false return; player = &sPokemonJump->players[multiplayerId]; if (player->jumpOffsetIdx != INT_MAX) { player->jumpOffsetIdx++; jumpOffsetIdx = player->jumpOffsetIdx; } else { jumpOffsetIdx = sPokemonJump->vineTimer - player->jumpTimeStart; if (jumpOffsetIdx >= 65000) { jumpOffsetIdx -= 65000; jumpOffsetIdx += sPokemonJump->vineTimer; } player->jumpOffsetIdx = jumpOffsetIdx; } if (jumpOffsetIdx < 4) return; jumpOffsetIdx -= 4; if (jumpOffsetIdx < (int)ARRAY_COUNT(sJumpOffsets[0])) jumpOffset = sJumpOffsets[player->monJumpType][jumpOffsetIdx]; else jumpOffset = 0; SetMonSpriteY(multiplayerId, jumpOffset); if (jumpOffset == 0 && multiplayerId == sPokemonJump->multiplayerId) SetMonStateNormal(); player->jumpOffset = jumpOffset; } static void TryUpdateScore(void) { if (sPokemonJump->vineState == VINE_UPSWING_HIGH && sPokemonJump->prevVineState == VINE_UPSWING_LOW) { // Vine has passed through the point where it // would hit the players, allow score to update if (!sPokemonJump->initScoreUpdate) { ClearUnreadField(); sPokemonJump->numPlayersAtPeak = 0; sPokemonJump->initScoreUpdate = TRUE; sPokemonJump->comm.receivedBonusFlags = 0; } else { if (sPokemonJump->numPlayersAtPeak == MAX_RFU_PLAYERS) { // An 'excellent' is the max 5 players all jumping synchronously sPokemonJump->excellentsInRow++; TryUpdateExcellentsRecord(sPokemonJump->excellentsInRow); } else { sPokemonJump->excellentsInRow = 0; } if (sPokemonJump->numPlayersAtPeak > 1) { sPokemonJump->giveBonus = TRUE; // Unclear why atJumpPeak needed to be copied over twice memcpy(sPokemonJump->atJumpPeak3, sPokemonJump->atJumpPeak2, sizeof(u8) * MAX_RFU_PLAYERS); } ClearUnreadField(); sPokemonJump->numPlayersAtPeak = 0; sPokemonJump->initScoreUpdate = TRUE; sPokemonJump->comm.receivedBonusFlags = 0; if (sPokemonJump->comm.jumpsInRow < MAX_JUMPS) sPokemonJump->comm.jumpsInRow++; AddJumpScore(10); SetLinkTimeInterval(LINK_INTERVAL_SHORT); } } if (sPokemonJump->giveBonus && (DidAllPlayersClearVine() == TRUE || sPokemonJump->vineState == VINE_HIGHEST)) { int numPlayers = GetNumPlayersForBonus(sPokemonJump->atJumpPeak3); AddJumpScore(GetScoreBonus(numPlayers)); SetLinkTimeInterval(LINK_INTERVAL_SHORT); sPokemonJump->giveBonus = FALSE; } if (sPokemonJump->initScoreUpdate) { int numAtPeak = GetPlayersAtJumpPeak(); if (numAtPeak > sPokemonJump->numPlayersAtPeak) { sPokemonJump->numPlayersAtPeak = numAtPeak; memcpy(sPokemonJump->atJumpPeak2, sPokemonJump->atJumpPeak, sizeof(u8) * MAX_RFU_PLAYERS); } } } // Returns FALSE if any player was hit by vine static bool32 UpdateVineHitStates(void) { int i; if (sPokemonJump->vineState == VINE_UPSWING_LOWER && sPokemonJump->player->jumpOffset == 0) { // Vine is in position to hit the player and jump offset is 0. // Unless the player had just jumped and has been forced to the ground // by someone else getting hit, the player has been hit if (sPokemonJump->player->prevMonState == MONSTATE_JUMP && IsGameOver() == TRUE) { sPokemonJump->player->jumpState = JUMPSTATE_SUCCESS; } else { // Hit vine SetMonStateHit(); SetLinkTimeInterval(LINK_INTERVAL_SHORT); } } if (sPokemonJump->vineState == VINE_UPSWING_LOW && sPokemonJump->prevVineState == VINE_UPSWING_LOWER && sPokemonJump->player->monState != MONSTATE_HIT) { sPokemonJump->player->jumpState = JUMPSTATE_SUCCESS; SetLinkTimeInterval(LINK_INTERVAL_SHORT); } for (i = 0; i < sPokemonJump->numPlayers; i++) { if (sPokemonJump->players[i].monState == MONSTATE_HIT) return FALSE; } return TRUE; } // Has everyone either jumped or been hit by the vine static bool32 AllPlayersJumpedOrHit(void) { int i; int numPlayers = sPokemonJump->numPlayers; int numJumpedOrHit = 0; for (i = 0; i < numPlayers; i++) { if (sPokemonJump->players[i].jumpState != JUMPSTATE_NONE) numJumpedOrHit++; } return numJumpedOrHit == numPlayers; } static bool32 DidAllPlayersClearVine(void) { int i; for (i = 0; i < sPokemonJump->numPlayers; i++) { if (sPokemonJump->players[i].jumpState != JUMPSTATE_SUCCESS) return FALSE; } return TRUE; } static bool32 ShouldPlayAgain(void) { int i; if (sPokemonJump->playAgainState == PLAY_AGAIN_NO) return FALSE; for (i = 1; i < sPokemonJump->numPlayers; i++) { if (sPokemonJump->playAgainStates[i] == PLAY_AGAIN_NO) return FALSE; } return TRUE; } static void AddJumpScore(int score) { sPokemonJump->comm.jumpScore += score; sPokemonJump->updateScore = TRUE; if (sPokemonJump->comm.jumpScore >= MAX_JUMP_SCORE) sPokemonJump->comm.jumpScore = MAX_JUMP_SCORE; } static int GetPlayersAtJumpPeak(void) { int i; int numAtPeak = 0; int numPlayers = sPokemonJump->numPlayers; for (i = 0; i < numPlayers; i++) { if (sPokemonJump->players[i].jumpOffset == JUMP_PEAK) { sPokemonJump->atJumpPeak[i] = TRUE; numAtPeak++; } else { sPokemonJump->atJumpPeak[i] = FALSE; } } return numAtPeak; } static bool32 AreLinkQueuesEmpty(void) { return !gRfu.recvQueue.count && !gRfu.sendQueue.count; } static int GetNumPlayersForBonus(u8 *arg0) { int i = 0; int flags = 0; int count = 0; for (; i < MAX_RFU_PLAYERS; i++) { if (arg0[i]) { flags |= 1 << i; count++; } } sPokemonJump->comm.receivedBonusFlags = flags; if (flags) sPokemonJump->showBonus = TRUE; return count; } static void ClearUnreadField(void) { sPokemonJump->unused3 = 0; } // Bonuses given depend on the number of // players that jumped at the same time static const int sScoreBonuses[MAX_RFU_PLAYERS + 1] = {0, 0, 50, 100, 200, 500}; static int GetScoreBonus(int numPlayers) { return sScoreBonuses[numPlayers]; } static void TryUpdateExcellentsRecord(u16 excellentsInRow) { if (excellentsInRow > sPokemonJump->excellentsInRowRecord) sPokemonJump->excellentsInRowRecord = excellentsInRow; } static const u16 sPrizeItems[] = { ITEM_LEPPA_BERRY, ITEM_LUM_BERRY, ITEM_SITRUS_BERRY, ITEM_FIGY_BERRY, ITEM_WIKI_BERRY, ITEM_MAGO_BERRY, ITEM_AGUAV_BERRY, ITEM_IAPAPA_BERRY }; struct { u32 score; u32 quantity; } static const sPrizeQuantityData[] = { { .score = 5000, .quantity = 1}, { .score = 8000, .quantity = 2}, { .score = 12000, .quantity = 3}, { .score = 16000, .quantity = 4}, { .score = 20000, .quantity = 5}, }; static bool32 HasEnoughScoreForPrize(void) { if (sPokemonJump->comm.jumpScore >= sPrizeQuantityData[0].score) return TRUE; else return FALSE; } static u16 GetPrizeData(void) { u16 itemId = GetPrizeItemId(); u16 quantity = GetPrizeQuantity(); return (quantity << 12) | (itemId & 0xFFF); } static void UnpackPrizeData(u16 data, u16 *itemId, u16 *quantity) { *quantity = data >> 12; *itemId = data & 0xFFF; } static u16 GetPrizeItemId(void) { u16 index = Random() % ARRAY_COUNT(sPrizeItems); return sPrizeItems[index]; } static u16 GetPrizeQuantity(void) { u32 quantity, i; quantity = 0; for (i = 0; i < ARRAY_COUNT(sPrizeQuantityData); i++) { if (sPokemonJump->comm.jumpScore >= sPrizeQuantityData[i].score) quantity = sPrizeQuantityData[i].quantity; else break; } return quantity; } static u16 GetQuantityLimitedByBag(u16 item, u16 quantity) { while (quantity && !CheckBagHasSpace(item, quantity)) quantity--; return quantity; } static u16 GetNumPokeJumpPlayers(void) { return GetLinkPlayerCount(); } static u16 GetPokeJumpMultiplayerId(void) { return sPokemonJump->multiplayerId; } static struct PokemonJump_MonInfo *GetMonInfoByMultiplayerId(u8 multiplayerId) { return &sPokemonJump->monInfo[multiplayerId]; } static u8 *GetPokeJumpPlayerName(u8 multiplayerId) { return sPokemonJump->players[multiplayerId].name; } bool32 IsSpeciesAllowedInPokemonJump(u16 species) { return GetPokemonJumpSpeciesIdx(species) > -1; } void IsPokemonJumpSpeciesInParty(void) { int i; for (i = 0; i < PARTY_SIZE; i++) { if (GetMonData(&gPlayerParty[i], MON_DATA_SANITY_HAS_SPECIES)) { u16 species = GetMonData(&gPlayerParty[i], MON_DATA_SPECIES2); if (IsSpeciesAllowedInPokemonJump(species)) { gSpecialVar_Result = TRUE; return; } } } gSpecialVar_Result = FALSE; } static const u16 sPokeJumpPal1[] = INCBIN_U16("graphics/pokemon_jump/pal1.gbapal"); static const u16 sPokeJumpPal2[] = INCBIN_U16("graphics/pokemon_jump/pal2.gbapal"); static const u32 sVine1_Gfx[] = INCBIN_U32("graphics/pokemon_jump/vine1.4bpp.lz"); static const u32 sVine2_Gfx[] = INCBIN_U32("graphics/pokemon_jump/vine2.4bpp.lz"); static const u32 sVine3_Gfx[] = INCBIN_U32("graphics/pokemon_jump/vine3.4bpp.lz"); static const u32 sVine4_Gfx[] = INCBIN_U32("graphics/pokemon_jump/vine4.4bpp.lz"); static const u32 sStar_Gfx[] = INCBIN_U32("graphics/pokemon_jump/star.4bpp.lz"); static const struct CompressedSpriteSheet sCompressedSpriteSheets[] = { {sVine1_Gfx, 0x600, GFXTAG_VINE1}, {sVine2_Gfx, 0xC00, GFXTAG_VINE2}, {sVine3_Gfx, 0x600, GFXTAG_VINE3}, {sVine4_Gfx, 0x600, GFXTAG_VINE4}, {sStar_Gfx, 0x200, GFXTAG_STAR}, }; static const struct SpritePalette sSpritePalettes[] = { {sPokeJumpPal1, PALTAG_1}, {sPokeJumpPal2, PALTAG_2}, }; static const struct OamData sOamData_JumpMon; static const struct SpriteTemplate sSpriteTemplate_Vine1; static const struct SpriteTemplate sSpriteTemplate_Vine2; static const struct SpriteTemplate sSpriteTemplate_Vine3; static const struct SpriteTemplate sSpriteTemplate_Vine4; static const struct SpriteTemplate sSpriteTemplate_JumpMon = { .tileTag = TAG_MON1, .paletteTag = TAG_MON1, .oam = &sOamData_JumpMon, .anims = gDummySpriteAnimTable, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallbackDummy, }; static const s16 sVineYCoords[VINE_SPRITES_PER_SIDE][NUM_VINESTATES] = { {96, 96, 96, 114, 120, 120, 120, 114, 96, 96}, {70, 80, 96, 114, 120, 128, 120, 114, 96, 80}, {50, 72, 96, 114, 128, 136, 128, 114, 96, 72}, {42, 72, 96, 114, 128, 136, 128, 114, 96, 72}, }; static const s16 sVineXCoords[VINE_SPRITES_PER_SIDE * 2] = {16, 40, 72, 104, 136, 168, 200, 224}; static const struct SpriteTemplate *const sSpriteTemplates_Vine[VINE_SPRITES_PER_SIDE] = { &sSpriteTemplate_Vine1, &sSpriteTemplate_Vine2, &sSpriteTemplate_Vine3, &sSpriteTemplate_Vine4, }; static const struct OamData sOamData_JumpMon = { .y = 0, .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = 0, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(64x64), .x = 0, .matrixNum = 0, .size = SPRITE_SIZE(64x64), .tileNum = 0, .priority = 2, .paletteNum = 0, .affineParam = 0 }; static const struct OamData sOamData_Vine16x32 = { .y = 0, .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = 0, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(16x32), .x = 0, .matrixNum = 0, .size = SPRITE_SIZE(16x32), .tileNum = 0, .priority = 2, .paletteNum = 0, .affineParam = 0 }; static const struct OamData sOamData_Vine32x32 = { .y = 0, .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = 0, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(32x32), .x = 0, .matrixNum = 0, .size = SPRITE_SIZE(32x32), .tileNum = 0, .priority = 2, .paletteNum = 0, .affineParam = 0 }; static const struct OamData sOamData_Vine32x16 = { .y = 0, .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = 0, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(32x16), .x = 0, .matrixNum = 0, .size = SPRITE_SIZE(32x16), .tileNum = 0, .priority = 2, .paletteNum = 0, .affineParam = 0 }; static const union AnimCmd sAnims_Vine_Highest[] = { ANIMCMD_FRAME(0, 1), ANIMCMD_END }; static const union AnimCmd sAnims_Vine_Higher[] = { ANIMCMD_FRAME(8, 1), ANIMCMD_END }; static const union AnimCmd sAnims_Vine_High[] = { ANIMCMD_FRAME(16, 1), ANIMCMD_END }; static const union AnimCmd sAnims_Vine_Low[] = { ANIMCMD_FRAME(24, 1), ANIMCMD_END }; static const union AnimCmd sAnims_Vine_Lower[] = { ANIMCMD_FRAME(32, 1), ANIMCMD_END }; static const union AnimCmd sAnims_Vine_Lowest[] = { ANIMCMD_FRAME(40, 1), ANIMCMD_END }; static const union AnimCmd sAnims_VineTall_Highest[] = { ANIMCMD_FRAME(0, 1), ANIMCMD_END }; static const union AnimCmd sAnims_VineTall_Higher[] = { ANIMCMD_FRAME(16, 1), ANIMCMD_END }; static const union AnimCmd sAnims_VineTall_High[] = { ANIMCMD_FRAME(32, 1), ANIMCMD_END }; static const union AnimCmd sAnims_VineTall_Low[] = { ANIMCMD_FRAME(48, 1), ANIMCMD_END }; static const union AnimCmd sAnims_VineTall_Lower[] = { ANIMCMD_FRAME(64, 1), ANIMCMD_END }; static const union AnimCmd sAnims_VineTall_Lowest[] = { ANIMCMD_FRAME(80, 1), ANIMCMD_END }; static const union AnimCmd *const sAnims_Vine[] = { sAnims_Vine_Highest, sAnims_Vine_Higher, sAnims_Vine_High, sAnims_Vine_Low, sAnims_Vine_Lower, sAnims_Vine_Lowest }; // Vine 2 needs its own set of anims because the graphic is twice as large static const union AnimCmd *const sAnims_VineTall[] = { sAnims_VineTall_Highest, sAnims_VineTall_Higher, sAnims_VineTall_High, sAnims_VineTall_Low, sAnims_VineTall_Lower, sAnims_VineTall_Lowest }; static const struct SpriteTemplate sSpriteTemplate_Vine1 = { .tileTag = GFXTAG_VINE1, .paletteTag = PALTAG_1, .oam = &sOamData_Vine16x32, .anims = sAnims_Vine, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallbackDummy, }; static const struct SpriteTemplate sSpriteTemplate_Vine2 = { .tileTag = GFXTAG_VINE2, .paletteTag = PALTAG_1, .oam = &sOamData_Vine32x32, .anims = sAnims_VineTall, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallbackDummy, }; static const struct SpriteTemplate sSpriteTemplate_Vine3 = { .tileTag = GFXTAG_VINE3, .paletteTag = PALTAG_1, .oam = &sOamData_Vine32x16, .anims = sAnims_Vine, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallbackDummy, }; static const struct SpriteTemplate sSpriteTemplate_Vine4 = { .tileTag = GFXTAG_VINE4, .paletteTag = PALTAG_1, .oam = &sOamData_Vine32x16, .anims = sAnims_Vine, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallbackDummy, }; static const struct OamData sOamData_Star = { .y = 0, .affineMode = ST_OAM_AFFINE_OFF, .objMode = ST_OAM_OBJ_NORMAL, .mosaic = 0, .bpp = ST_OAM_4BPP, .shape = SPRITE_SHAPE(16x16), .x = 0, .matrixNum = 0, .size = SPRITE_SIZE(16x16), .tileNum = 0, .priority = 1, .paletteNum = 0, .affineParam = 0 }; static const union AnimCmd sAnim_Star_Still[] = { ANIMCMD_FRAME(0, 0), ANIMCMD_END }; static const union AnimCmd sAnim_Star_Spinning[] = { ANIMCMD_FRAME(0, 4), ANIMCMD_FRAME(4, 4), ANIMCMD_FRAME(8, 4), ANIMCMD_FRAME(12, 4), ANIMCMD_LOOP(1), ANIMCMD_FRAME(0, 4), ANIMCMD_END }; static const union AnimCmd *const sAnims_Star[] = { sAnim_Star_Still, sAnim_Star_Spinning }; static const struct SpriteTemplate sSpriteTemplate_Star = { .tileTag = GFXTAG_STAR, .paletteTag = PALTAG_1, .oam = &sOamData_Star, .anims = sAnims_Star, .images = NULL, .affineAnims = gDummySpriteAffineAnimTable, .callback = SpriteCallbackDummy, }; static void LoadSpriteSheetsAndPalettes(struct PokemonJumpGfx *jumpGfx) { int i; for (i = 0; i < ARRAY_COUNT(sCompressedSpriteSheets); i++) LoadCompressedSpriteSheet(&sCompressedSpriteSheets[i]); for (i = 0; i < ARRAY_COUNT(sSpritePalettes); i++) LoadSpritePalette(&sSpritePalettes[i]); jumpGfx->vinePalNumDownswing = IndexOfSpritePaletteTag(PALTAG_1); jumpGfx->vinePalNumUpswing = IndexOfSpritePaletteTag(PALTAG_2); } static void ResetPokeJumpSpriteData(struct Sprite *sprite) { int i; for (i = 0; i < (int)ARRAY_COUNT(sprite->data); i++) sprite->data[i] = 0; } static void CreateJumpMonSprite(struct PokemonJumpGfx *jumpGfx, struct PokemonJump_MonInfo *monInfo, s16 x, s16 y, u8 multiplayerId) { struct SpriteTemplate spriteTemplate; struct SpriteSheet spriteSheet; struct CompressedSpritePalette spritePalette; u8 *buffer; u8 *unusedBuffer; u8 subpriority; u8 spriteId; spriteTemplate = sSpriteTemplate_JumpMon; buffer = Alloc(0x2000); unusedBuffer = Alloc(MON_PIC_SIZE); if (multiplayerId == GetPokeJumpMultiplayerId()) subpriority = 3; else subpriority = multiplayerId + 4; if (buffer && unusedBuffer) { HandleLoadSpecialPokePic( &gMonStillFrontPicTable[monInfo->species], buffer, monInfo->species, monInfo->personality); spriteSheet.data = buffer; spriteSheet.tag = multiplayerId; spriteSheet.size = MON_PIC_SIZE; LoadSpriteSheet(&spriteSheet); spritePalette.data = GetMonSpritePalFromSpeciesAndPersonality(monInfo->species, monInfo->otId, monInfo->personality); spritePalette.tag = multiplayerId; LoadCompressedSpritePalette(&spritePalette); Free(buffer); Free(unusedBuffer); spriteTemplate.tileTag += multiplayerId; spriteTemplate.paletteTag += multiplayerId; spriteId = CreateSprite(&spriteTemplate, x, y, subpriority); if (spriteId != MAX_SPRITES) { jumpGfx->monSprites[multiplayerId] = &gSprites[spriteId]; jumpGfx->monSpriteSubpriorities[multiplayerId] = subpriority; return; } } jumpGfx->monSprites[multiplayerId] = NULL; } #define sState data[0] #define sTimer data[1] #define sOffset data[7] // Never read static void DoStarAnim(struct PokemonJumpGfx *jumpGfx, int multiplayerId) { ResetPokeJumpSpriteData(jumpGfx->starSprites[multiplayerId]); jumpGfx->starSprites[multiplayerId]->sOffset = jumpGfx->monSprites[multiplayerId] - gSprites; jumpGfx->starSprites[multiplayerId]->invisible = FALSE; jumpGfx->starSprites[multiplayerId]->y = 96; jumpGfx->starSprites[multiplayerId]->callback = SpriteCB_Star; StartSpriteAnim(jumpGfx->starSprites[multiplayerId], 1); } static void SpriteCB_Star(struct Sprite *sprite) { switch (sprite->sState) { case 0: if (sprite->animEnded) { sprite->invisible = TRUE; sprite->callback = SpriteCallbackDummy; } break; case 1: sprite->y--; sprite->sTimer++; if (sprite->y <= 72) { sprite->y = 72; sprite->sState++; } break; case 2: if (++sprite->sTimer >= 48) { sprite->invisible = TRUE; sprite->callback = SpriteCallbackDummy; } break; } } #undef sState #undef sTimer #undef sOffset static void Gfx_StartMonHitShake(struct PokemonJumpGfx *jumpGfx, int multiplayerId) { jumpGfx->monSprites[multiplayerId]->callback = SpriteCB_MonHitShake; jumpGfx->monSprites[multiplayerId]->y2 = 0; ResetPokeJumpSpriteData(jumpGfx->monSprites[multiplayerId]); } static bool32 Gfx_IsMonHitShakeActive(struct PokemonJumpGfx *jumpGfx, int multiplayerId) { return jumpGfx->monSprites[multiplayerId]->callback == SpriteCB_MonHitShake; } #define sTimer data[1] #define sNumShakes data[2] static void SpriteCB_MonHitShake(struct Sprite *sprite) { if (++sprite->sTimer > 1) { if (++sprite->sNumShakes & 1) sprite->y2 = 2; else sprite->y2 = -2; sprite->sTimer = 0; } if (sprite->sNumShakes > 12) { sprite->y2 = 0; sprite->callback = SpriteCallbackDummy; } } #undef sTimer #undef sNumShakes static void Gfx_StartMonHitFlash(struct PokemonJumpGfx *jumpGfx, int multiplayerId) { ResetPokeJumpSpriteData(jumpGfx->monSprites[multiplayerId]); jumpGfx->monSprites[multiplayerId]->callback = SpriteCB_MonHitFlash; } static void Gfx_StopMonHitFlash(struct PokemonJumpGfx *jumpGfx) { int i; u16 numPlayers = GetNumPokeJumpPlayers(); for (i = 0; i < numPlayers; i++) { if (jumpGfx->monSprites[i]->callback == SpriteCB_MonHitFlash) { jumpGfx->monSprites[i]->invisible = FALSE; jumpGfx->monSprites[i]->callback = SpriteCallbackDummy; jumpGfx->monSprites[i]->subpriority = 10; } } } #define sTimer data[0] static void SpriteCB_MonHitFlash(struct Sprite *sprite) { if (++sprite->sTimer > 3) { sprite->sTimer = 0; sprite->invisible ^= 1; } } #undef sTimer static void Gfx_ResetMonSpriteSubpriorities(struct PokemonJumpGfx *jumpGfx) { int i; u16 numPlayers = GetNumPokeJumpPlayers(); for (i = 0; i < numPlayers; i++) jumpGfx->monSprites[i]->subpriority = jumpGfx->monSpriteSubpriorities[i]; } static void Gfx_StartMonIntroBounce(struct PokemonJumpGfx *jumpGfx, int multiplayerId) { ResetPokeJumpSpriteData(jumpGfx->monSprites[multiplayerId]); jumpGfx->monSprites[multiplayerId]->callback = SpriteCB_MonIntroBounce; } static bool32 Gfx_IsMonIntroBounceActive(struct PokemonJumpGfx *jumpGfx) { int i; u16 numPlayers = GetNumPokeJumpPlayers(); for (i = 0; i < numPlayers; i++) { if (jumpGfx->monSprites[i]->callback == SpriteCB_MonIntroBounce) return TRUE; } return FALSE; } #define sState data[0] #define sHopPos data[1] #define sNumHops data[2] static void SpriteCB_MonIntroBounce(struct Sprite *sprite) { switch (sprite->sState) { case 0: PlaySE(SE_BIKE_HOP); sprite->sHopPos = 0; sprite->sState++; // fall through case 1: sprite->sHopPos += 4; if (sprite->sHopPos > 127) sprite->sHopPos = 0; sprite->y2 = -(gSineTable[sprite->sHopPos] >> 3); if (sprite->sHopPos == 0) { if (++sprite->sNumHops < 2) sprite->sState = 0; else sprite->callback = SpriteCallbackDummy; } break; } } #undef sState #undef sHopPos #undef sNumHops static void CreateStarSprite(struct PokemonJumpGfx *jumpGfx, s16 x, s16 y, u8 multiplayerId) { u8 spriteId = CreateSprite(&sSpriteTemplate_Star, x, y, 1); if (spriteId != MAX_SPRITES) { gSprites[spriteId].invisible = TRUE; jumpGfx->starSprites[multiplayerId] = &gSprites[spriteId]; } } static void CreateVineSprites(struct PokemonJumpGfx *jumpGfx) { int i; int count; u8 spriteId; count = 0; for (i = 0; i < VINE_SPRITES_PER_SIDE; i++) { spriteId = CreateSprite(sSpriteTemplates_Vine[i], sVineXCoords[count], sVineYCoords[i][0], 2); jumpGfx->vineSprites[count] = &gSprites[spriteId]; count++; } for (i = VINE_SPRITES_PER_SIDE - 1; i >= 0; i--) { spriteId = CreateSprite(sSpriteTemplates_Vine[i], sVineXCoords[count], sVineYCoords[i][0], 2); jumpGfx->vineSprites[count] = &gSprites[spriteId]; jumpGfx->vineSprites[count]->hFlip = TRUE; count++; } } static void UpdateVineAnim(struct PokemonJumpGfx *jumpGfx, int vineState) { int i, count, palNum; int priority; if (vineState > VINE_LOWEST) { // animNums for vine on upswing are same as // on downswing but in reverse vineState = NUM_VINESTATES - vineState; priority = 3; // Set vine behind Pokémon palNum = jumpGfx->vinePalNumUpswing; } else { priority = 2; // Set vine in front of Pokémon palNum = jumpGfx->vinePalNumDownswing; } count = 0; for (i = 0; i < VINE_SPRITES_PER_SIDE; i++) { jumpGfx->vineSprites[count]->y = sVineYCoords[i][vineState]; jumpGfx->vineSprites[count]->oam.priority = priority; jumpGfx->vineSprites[count]->oam.paletteNum = palNum; StartSpriteAnim(jumpGfx->vineSprites[count], vineState); count++; } for (i = VINE_SPRITES_PER_SIDE - 1; i >= 0; i--) { jumpGfx->vineSprites[count]->y = sVineYCoords[i][vineState]; jumpGfx->vineSprites[count]->oam.priority = priority; jumpGfx->vineSprites[count]->oam.paletteNum = palNum; StartSpriteAnim(jumpGfx->vineSprites[count], vineState); count++; } } static void StartPokeJumpCountdown(struct PokemonJumpGfx *jumpGfx) { StartMinigameCountdown(GFXTAG_COUNTDOWN, PALTAG_COUNTDOWN, 120, 80, 0); Gfx_ResetMonSpriteSubpriorities(jumpGfx); } static bool32 IsPokeJumpCountdownRunning(void) { return IsMinigameCountdownRunning(); } static void StartPokeJumpGfx(struct PokemonJumpGfx *jumpGfx) { u8 taskId; sPokemonJumpGfx = jumpGfx; InitPokeJumpGfx(sPokemonJumpGfx); taskId = CreateTask(Task_RunPokeJumpGfxFunc, 3); sPokemonJumpGfx->taskId = taskId; SetWordTaskArg(sPokemonJumpGfx->taskId, 2, (u32) sPokemonJumpGfx); SetUpPokeJumpGfxFunc(LoadPokeJumpGfx); } static void FreeWindowsAndDigitObj(void) { FreeAllWindowBuffers(); DigitObjUtil_Free(); } static void InitPokeJumpGfx(struct PokemonJumpGfx *jumpGfx) { jumpGfx->mainState = 0; jumpGfx->funcFinished = FALSE; jumpGfx->msgWindowId = WINDOW_NONE; } static const u16 sInterface_Pal[] = INCBIN_U16("graphics/pokemon_jump/interface.gbapal"); static const u16 sBg_Pal[] = INCBIN_U16("graphics/pokemon_jump/bg.gbapal"); static const u32 sBg_Gfx[] = INCBIN_U32("graphics/pokemon_jump/bg.4bpp.lz"); static const u32 sBg_Tilemap[] = INCBIN_U32("graphics/pokemon_jump/bg.bin.lz"); static const u16 sVenusaur_Pal[] = INCBIN_U16("graphics/pokemon_jump/venusaur.gbapal"); static const u32 sVenusaur_Gfx[] = INCBIN_U32("graphics/pokemon_jump/venusaur.4bpp.lz"); static const u32 sVenusaur_Tilemap[] = INCBIN_U32("graphics/pokemon_jump/venusaur.bin.lz"); static const u16 sBonuses_Pal[] = INCBIN_U16("graphics/pokemon_jump/bonuses.gbapal"); static const u32 sBonuses_Gfx[] = INCBIN_U32("graphics/pokemon_jump/bonuses.4bpp.lz"); static const u32 sBonuses_Tilemap[] = INCBIN_U32("graphics/pokemon_jump/bonuses.bin.lz"); static const struct BgTemplate sBgTemplates[] = { { .bg = BG_INTERFACE, .charBaseIndex = 0, .mapBaseIndex = 27, .screenSize = 0, .paletteMode = 0, .priority = 0, .baseTile = 0 }, { .bg = BG_VENUSAUR, .charBaseIndex = 1, .mapBaseIndex = 30, .screenSize = 2, .paletteMode = 0, .priority = 2, .baseTile = 0 }, { .bg = BG_BONUSES, .charBaseIndex = 2, .mapBaseIndex = 12, .screenSize = 3, .paletteMode = 0, .priority = 1, .baseTile = 0 }, { .bg = BG_SCENERY, .charBaseIndex = 3, .mapBaseIndex = 29, .screenSize = 0, .paletteMode = 0, .priority = 3, .baseTile = 0 }, }; static const struct WindowTemplate sWindowTemplates[] = { [WIN_POINTS] = { .bg = BG_INTERFACE, .tilemapLeft = 19, .tilemapTop = 0, .width = 6, .height = 2, .paletteNum = 2, .baseBlock = 0x13, }, [WIN_TIMES] = { .bg = BG_INTERFACE, .tilemapLeft = 8, .tilemapTop = 0, .width = 6, .height = 2, .paletteNum = 2, .baseBlock = 0x1F, }, DUMMY_WIN_TEMPLATE, }; struct { int id; void (*func)(void); } static const sPokeJumpGfxFuncs[] = { {GFXFUNC_LOAD, LoadPokeJumpGfx}, // Element not used, LoadPokeJumpGfx is passed directly to SetUpPokeJumpGfxFunc {GFXFUNC_SHOW_NAMES, PrintPlayerNamesNoHighlight}, {GFXFUNC_SHOW_NAMES_HIGHLIGHT, PrintPlayerNamesWithHighlight}, {GFXFUNC_ERASE_NAMES, ErasePlayerNames}, {GFXFUNC_MSG_PLAY_AGAIN, Msg_WantToPlayAgain}, {GFXFUNC_MSG_SAVING, Msg_SavingDontTurnOff}, {GFXFUNC_ERASE_MSG, EraseMessage}, {GFXFUNC_MSG_PLAYER_DROPPED, Msg_SomeoneDroppedOut}, {GFXFUNC_COUNTDOWN, DoPokeJumpCountdown}, {GFXFUNC_MSG_COMM_STANDBY, Msg_CommunicationStandby}, }; static void SetUpPokeJumpGfxFuncById(int id) { int i; for (i = 0; i < ARRAY_COUNT(sPokeJumpGfxFuncs); i++) { if (sPokeJumpGfxFuncs[i].id == id) SetUpPokeJumpGfxFunc(sPokeJumpGfxFuncs[i].func); } } static bool32 IsPokeJumpGfxFuncFinished(void) { return (sPokemonJumpGfx->funcFinished != TRUE); } static void SetUpPokeJumpGfxFunc(void (*func)(void)) { SetWordTaskArg(sPokemonJumpGfx->taskId, 0, (u32) func); sPokemonJumpGfx->mainState = 0; sPokemonJumpGfx->funcFinished = FALSE; } static void Task_RunPokeJumpGfxFunc(u8 taskId) { if (!sPokemonJumpGfx->funcFinished) { // Read the function set in the data by SetUpPokeJumpGfxFunc void (*func)(void) = (void *)(GetWordTaskArg(taskId, 0)); func(); } } static void LoadPokeJumpGfx(void) { switch (sPokemonJumpGfx->mainState) { case 0: ResetBgsAndClearDma3BusyFlags(0); InitBgsFromTemplates(0, sBgTemplates, ARRAY_COUNT(sBgTemplates)); InitWindows(sWindowTemplates); ResetTempTileDataBuffers(); LoadSpriteSheetsAndPalettes(sPokemonJumpGfx); InitDigitPrinters(); LoadPalette(sBg_Pal, 0, 0x20); DecompressAndCopyTileDataToVram(BG_SCENERY, sBg_Gfx, 0, 0, 0); DecompressAndCopyTileDataToVram(BG_SCENERY, sBg_Tilemap, 0, 0, 1); LoadPalette(sVenusaur_Pal, 0x30, 0x20); DecompressAndCopyTileDataToVram(BG_VENUSAUR, sVenusaur_Gfx, 0, 0, 0); DecompressAndCopyTileDataToVram(BG_VENUSAUR, sVenusaur_Tilemap, 0, 0, 1); LoadPalette(sBonuses_Pal, 0x10, 0x20); DecompressAndCopyTileDataToVram(BG_BONUSES, sBonuses_Gfx, 0, 0, 0); DecompressAndCopyTileDataToVram(BG_BONUSES, sBonuses_Tilemap, 0, 0, 1); LoadPalette(sInterface_Pal, 0x20, 0x20); SetBgTilemapBuffer(BG_INTERFACE, sPokemonJumpGfx->tilemapBuffer); FillBgTilemapBufferRect_Palette0(BG_INTERFACE, 0, 0, 0, 0x20, 0x20); PrintScoreSuffixes(); PrintScore(0); LoadUserWindowBorderGfxOnBg(0, 1, 0xE0); CopyBgTilemapBufferToVram(BG_INTERFACE); CopyBgTilemapBufferToVram(BG_VENUSAUR); CopyBgTilemapBufferToVram(BG_BONUSES); ResetBgPositions(); sPokemonJumpGfx->mainState++; break; case 1: if (!FreeTempTileDataBuffersIfPossible()) { CreateJumpMonSprites(); CreateVineSprites(sPokemonJumpGfx); UpdateVineAnim(sPokemonJumpGfx, VINE_UPSWING_LOWER); ShowBg(BG_SCENERY); ShowBg(BG_INTERFACE); ShowBg(BG_VENUSAUR); HideBg(BG_BONUSES); sPokemonJumpGfx->mainState++; } break; case 2: sPokemonJumpGfx->funcFinished = TRUE; break; } } static void PrintPlayerNamesNoHighlight(void) { switch (sPokemonJumpGfx->mainState) { case 0: AddPlayerNameWindows(); sPokemonJumpGfx->mainState++; break; case 1: if (!IsDma3ManagerBusyWithBgCopy()) { PrintPokeJumpPlayerNames(FALSE); sPokemonJumpGfx->mainState++; } break; case 2: if (!IsDma3ManagerBusyWithBgCopy()) { DrawPlayerNameWindows(); sPokemonJumpGfx->mainState++; } break; case 3: if (!IsDma3ManagerBusyWithBgCopy()) sPokemonJumpGfx->funcFinished = TRUE; break; } } static void PrintPlayerNamesWithHighlight(void) { switch (sPokemonJumpGfx->mainState) { case 0: AddPlayerNameWindows(); sPokemonJumpGfx->mainState++; break; case 1: if (!IsDma3ManagerBusyWithBgCopy()) { PrintPokeJumpPlayerNames(TRUE); sPokemonJumpGfx->mainState++; } break; case 2: if (!IsDma3ManagerBusyWithBgCopy()) { DrawPlayerNameWindows(); sPokemonJumpGfx->mainState++; } break; case 3: if (!IsDma3ManagerBusyWithBgCopy()) sPokemonJumpGfx->funcFinished = TRUE; break; } } static void ErasePlayerNames(void) { int i, numPlayers; numPlayers = GetNumPokeJumpPlayers(); switch (sPokemonJumpGfx->mainState) { case 0: for (i = 0; i < numPlayers; i++) ClearWindowTilemap(sPokemonJumpGfx->nameWindowIds[i]); CopyBgTilemapBufferToVram(BG_INTERFACE); sPokemonJumpGfx->mainState++; break; case 1: if (!IsDma3ManagerBusyWithBgCopy()) { for (i = 0; i < numPlayers; i++) RemoveWindow(sPokemonJumpGfx->nameWindowIds[i]); sPokemonJumpGfx->funcFinished = TRUE; } break; } } static void Msg_WantToPlayAgain(void) { switch (sPokemonJumpGfx->mainState) { case 0: sPokemonJumpGfx->msgWindowId = AddMessageWindow(1, 8, 20, 2); AddTextPrinterParameterized(sPokemonJumpGfx->msgWindowId, FONT_NORMAL, gText_WantToPlayAgain2, 0, 1, TEXT_SKIP_DRAW, NULL); CopyWindowToVram(sPokemonJumpGfx->msgWindowId, COPYWIN_GFX); sPokemonJumpGfx->mainState++; break; case 1: if (!IsDma3ManagerBusyWithBgCopy()) { PutWindowTilemap(sPokemonJumpGfx->msgWindowId); DrawTextBorderOuter(sPokemonJumpGfx->msgWindowId, 1, 14); CreatePokeJumpYesNoMenu(23, 7, 0); CopyBgTilemapBufferToVram(BG_INTERFACE); sPokemonJumpGfx->mainState++; } break; case 2: if (!IsDma3ManagerBusyWithBgCopy()) sPokemonJumpGfx->funcFinished = TRUE; break; } } static void Msg_SavingDontTurnOff(void) { switch (sPokemonJumpGfx->mainState) { case 0: sPokemonJumpGfx->msgWindowId = AddMessageWindow(2, 7, 26, 4); AddTextPrinterParameterized(sPokemonJumpGfx->msgWindowId, FONT_NORMAL, gText_SavingDontTurnOffPower, 0, 1, TEXT_SKIP_DRAW, NULL); CopyWindowToVram(sPokemonJumpGfx->msgWindowId, COPYWIN_GFX); sPokemonJumpGfx->mainState++; break; case 1: if (!IsDma3ManagerBusyWithBgCopy()) { PutWindowTilemap(sPokemonJumpGfx->msgWindowId); DrawTextBorderOuter(sPokemonJumpGfx->msgWindowId, 1, 14); CopyBgTilemapBufferToVram(BG_INTERFACE); sPokemonJumpGfx->mainState++; } break; case 2: if (!IsDma3ManagerBusyWithBgCopy()) sPokemonJumpGfx->funcFinished = TRUE; break; } } static void EraseMessage(void) { switch (sPokemonJumpGfx->mainState) { case 0: ClearMessageWindow(); EraseYesNoWindow(); CopyBgTilemapBufferToVram(BG_INTERFACE); sPokemonJumpGfx->mainState++; break; case 1: if (!RemoveMessageWindow() && !IsDma3ManagerBusyWithBgCopy()) sPokemonJumpGfx->funcFinished = TRUE; break; } } static void Msg_SomeoneDroppedOut(void) { switch (sPokemonJumpGfx->mainState) { case 0: sPokemonJumpGfx->msgWindowId = AddMessageWindow(2, 8, 22, 4); AddTextPrinterParameterized(sPokemonJumpGfx->msgWindowId, FONT_NORMAL, gText_SomeoneDroppedOut2, 0, 1, TEXT_SKIP_DRAW, NULL); CopyWindowToVram(sPokemonJumpGfx->msgWindowId, COPYWIN_GFX); sPokemonJumpGfx->mainState++; break; case 1: if (!IsDma3ManagerBusyWithBgCopy()) { PutWindowTilemap(sPokemonJumpGfx->msgWindowId); DrawTextBorderOuter(sPokemonJumpGfx->msgWindowId, 1, 14); CopyBgTilemapBufferToVram(BG_INTERFACE); sPokemonJumpGfx->mainState++; } break; case 2: if (!IsDma3ManagerBusyWithBgCopy()) sPokemonJumpGfx->funcFinished = TRUE; break; } } static void Msg_CommunicationStandby(void) { switch (sPokemonJumpGfx->mainState) { case 0: sPokemonJumpGfx->msgWindowId = AddMessageWindow(7, 10, 16, 2); AddTextPrinterParameterized(sPokemonJumpGfx->msgWindowId, FONT_NORMAL, gText_CommunicationStandby4, 0, 1, TEXT_SKIP_DRAW, NULL); CopyWindowToVram(sPokemonJumpGfx->msgWindowId, COPYWIN_GFX); sPokemonJumpGfx->mainState++; break; case 1: if (!IsDma3ManagerBusyWithBgCopy()) { PutWindowTilemap(sPokemonJumpGfx->msgWindowId); DrawTextBorderOuter(sPokemonJumpGfx->msgWindowId, 1, 14); CopyBgTilemapBufferToVram(BG_INTERFACE); sPokemonJumpGfx->mainState++; } break; case 2: if (!IsDma3ManagerBusyWithBgCopy()) sPokemonJumpGfx->funcFinished = TRUE; break; } } static void DoPokeJumpCountdown(void) { switch (sPokemonJumpGfx->mainState) { case 0: StartPokeJumpCountdown(sPokemonJumpGfx); sPokemonJumpGfx->mainState++; break; case 1: if (!IsPokeJumpCountdownRunning()) sPokemonJumpGfx->funcFinished = TRUE; break; } } static void SetUpResetVineGfx(void) { sPokemonJumpGfx->resetVineState = 0; sPokemonJumpGfx->resetVineTimer = 0; sPokemonJumpGfx->vineState = VINE_UPSWING_LOWER; UpdateVineSwing(sPokemonJumpGfx->vineState); } static bool32 ResetVineGfx(void) { switch (sPokemonJumpGfx->resetVineState) { case 0: sPokemonJumpGfx->resetVineTimer++; if (sPokemonJumpGfx->resetVineTimer > 10) { sPokemonJumpGfx->resetVineTimer = 0; sPokemonJumpGfx->vineState++; if (sPokemonJumpGfx->vineState >= NUM_VINESTATES) { sPokemonJumpGfx->vineState = VINE_HIGHEST; sPokemonJumpGfx->resetVineState++; } } UpdateVineSwing(sPokemonJumpGfx->vineState); if (sPokemonJumpGfx->vineState != VINE_UPSWING_LOW) break; case 1: return FALSE; } return TRUE; } static void PrintPrizeMessage(u16 itemId, u16 quantity) { CopyItemNameHandlePlural(itemId, sPokemonJumpGfx->itemName, quantity); ConvertIntToDecimalStringN(sPokemonJumpGfx->itemQuantityStr, quantity, STR_CONV_MODE_LEFT_ALIGN, 1); DynamicPlaceholderTextUtil_Reset(); DynamicPlaceholderTextUtil_SetPlaceholderPtr(0, sPokemonJumpGfx->itemName); DynamicPlaceholderTextUtil_SetPlaceholderPtr(1, sPokemonJumpGfx->itemQuantityStr); DynamicPlaceholderTextUtil_ExpandPlaceholders(sPokemonJumpGfx->prizeMsg, gText_AwesomeWonF701F700); sPokemonJumpGfx->msgWindowId = AddMessageWindow(4, 8, 22, 4); AddTextPrinterParameterized(sPokemonJumpGfx->msgWindowId, FONT_NORMAL, sPokemonJumpGfx->prizeMsg, 0, 1, TEXT_SKIP_DRAW, NULL); CopyWindowToVram(sPokemonJumpGfx->msgWindowId, COPYWIN_GFX); sPokemonJumpGfx->fanfare = MUS_LEVEL_UP; sPokemonJumpGfx->msgWindowState = 0; } static void PrintPrizeFilledBagMessage(u16 itemId) { CopyItemName(itemId, sPokemonJumpGfx->itemName); DynamicPlaceholderTextUtil_Reset(); DynamicPlaceholderTextUtil_SetPlaceholderPtr(0, sPokemonJumpGfx->itemName); DynamicPlaceholderTextUtil_ExpandPlaceholders(sPokemonJumpGfx->prizeMsg, gText_FilledStorageSpace2); sPokemonJumpGfx->msgWindowId = AddMessageWindow(4, 8, 22, 4); AddTextPrinterParameterized(sPokemonJumpGfx->msgWindowId, FONT_NORMAL, sPokemonJumpGfx->prizeMsg, 0, 1, TEXT_SKIP_DRAW, NULL); CopyWindowToVram(sPokemonJumpGfx->msgWindowId, COPYWIN_GFX); sPokemonJumpGfx->fanfare = MUS_DUMMY; sPokemonJumpGfx->msgWindowState = 0; } static void PrintNoRoomForPrizeMessage(u16 itemId) { CopyItemName(itemId, sPokemonJumpGfx->itemName); DynamicPlaceholderTextUtil_Reset(); DynamicPlaceholderTextUtil_SetPlaceholderPtr(0, sPokemonJumpGfx->itemName); DynamicPlaceholderTextUtil_ExpandPlaceholders(sPokemonJumpGfx->prizeMsg, gText_CantHoldMore); sPokemonJumpGfx->msgWindowId = AddMessageWindow(4, 9, 22, 2); AddTextPrinterParameterized(sPokemonJumpGfx->msgWindowId, FONT_NORMAL, sPokemonJumpGfx->prizeMsg, 0, 1, TEXT_SKIP_DRAW, NULL); CopyWindowToVram(sPokemonJumpGfx->msgWindowId, COPYWIN_GFX); sPokemonJumpGfx->fanfare = MUS_DUMMY; sPokemonJumpGfx->msgWindowState = 0; } static bool32 DoPrizeMessageAndFanfare(void) { switch (sPokemonJumpGfx->msgWindowState) { case 0: if (!IsDma3ManagerBusyWithBgCopy()) { PutWindowTilemap(sPokemonJumpGfx->msgWindowId); DrawTextBorderOuter(sPokemonJumpGfx->msgWindowId, 1, 14); CopyBgTilemapBufferToVram(BG_INTERFACE); sPokemonJumpGfx->msgWindowState++; } break; case 1: if (IsDma3ManagerBusyWithBgCopy()) break; if (sPokemonJumpGfx->fanfare == MUS_DUMMY) { sPokemonJumpGfx->msgWindowState += 2; return FALSE; } PlayFanfare(sPokemonJumpGfx->fanfare); sPokemonJumpGfx->msgWindowState++; case 2: if (!IsFanfareTaskInactive()) break; sPokemonJumpGfx->msgWindowState++; case 3: return FALSE; } return TRUE; } static void ClearMessageWindow(void) { if (sPokemonJumpGfx->msgWindowId != WINDOW_NONE) { rbox_fill_rectangle(sPokemonJumpGfx->msgWindowId); CopyWindowToVram(sPokemonJumpGfx->msgWindowId, COPYWIN_MAP); sPokemonJumpGfx->msgWindowState = 0; } } static bool32 RemoveMessageWindow(void) { if (sPokemonJumpGfx->msgWindowId == WINDOW_NONE) return FALSE; switch (sPokemonJumpGfx->msgWindowState) { case 0: if (!IsDma3ManagerBusyWithBgCopy()) { RemoveWindow(sPokemonJumpGfx->msgWindowId); sPokemonJumpGfx->msgWindowId = WINDOW_NONE; sPokemonJumpGfx->msgWindowState++; } else break; case 1: return FALSE; } return TRUE; } static s8 HandlePlayAgainInput(void) { return Menu_ProcessInputNoWrapClearOnChoose(); } static u32 AddMessageWindow(u32 left, u32 top, u32 width, u32 height) { u32 windowId; struct WindowTemplate window; window.bg = BG_INTERFACE; window.tilemapLeft = left; window.tilemapTop = top; window.width = width; window.height = height; window.paletteNum = 0xF; window.baseBlock = 0x43; windowId = AddWindow(&window); FillWindowPixelBuffer(windowId, 0x11); return windowId; } static void CreatePokeJumpYesNoMenu(u16 left, u16 top, u8 cursorPos) { struct WindowTemplate window; u8 a = cursorPos; window.bg = BG_INTERFACE; window.tilemapLeft = left; window.tilemapTop = top; window.width = 6; window.height = 4; window.paletteNum = 2; window.baseBlock = 0x2B; CreateYesNoMenu(&window, 1, 0xD, a); } // "Points" for jump score and "times" for number of jumps in a row static void PrintScoreSuffixes(void) { u8 color[] = {TEXT_COLOR_TRANSPARENT, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_LIGHT_GRAY}; PutWindowTilemap(WIN_POINTS); PutWindowTilemap(WIN_TIMES); FillWindowPixelBuffer(WIN_POINTS, 0); FillWindowPixelBuffer(WIN_TIMES, 0); AddTextPrinterParameterized3(WIN_POINTS, FONT_SMALL, 0, 1, color, 0, gText_SpacePoints2); AddTextPrinterParameterized3(WIN_TIMES, FONT_SMALL, 0, 1, color, 0, gText_SpaceTimes3); } // The venusaurs in the background are actually an empty 256x512 bg with 3 pairs of venusaurs on it. // The below array is used to get values for where to set the bg Y to show the corresponding // venusaur pair in their state of swinging the vine rope // NEUTRAL/DOWN/UP refers to which direction the Venusaur is facing as it swings the vine enum { VENUSAUR_NEUTRAL, VENUSAUR_DOWN, VENUSAUR_UP, }; static const u8 sVenusaurStates[] = { [VINE_HIGHEST] = VENUSAUR_UP, [VINE_DOWNSWING_HIGHER] = VENUSAUR_UP, [VINE_DOWNSWING_HIGH] = VENUSAUR_NEUTRAL, [VINE_DOWNSWING_LOW] = VENUSAUR_NEUTRAL, [VINE_DOWNSWING_LOWER] = VENUSAUR_DOWN, [VINE_LOWEST] = VENUSAUR_DOWN, [VINE_UPSWING_LOWER] = VENUSAUR_DOWN, [VINE_UPSWING_LOW] = VENUSAUR_NEUTRAL, [VINE_UPSWING_HIGH] = VENUSAUR_NEUTRAL, [VINE_UPSWING_HIGHER] = VENUSAUR_UP, }; static const struct CompressedSpriteSheet sSpriteSheet_Digits = {gMinigameDigits_Gfx, 0, TAG_DIGITS}; static const struct SpritePalette sSpritePalette_Digits = {gMinigameDigits_Pal, TAG_DIGITS}; static const u16 sPlayerNameWindowCoords_2Players[] = { 6, 8, 16, 8 }; static const u16 sPlayerNameWindowCoords_3Players[] = { 6, 8, 11, 6, 16, 8 }; static const u16 sPlayerNameWindowCoords_4Players[] = { 2, 6, 6, 8, 16, 8, 20, 6 }; static const u16 sPlayerNameWindowCoords_5Players[] = { 2, 6, 6, 8, 11, 6, 16, 8, 20, 6 }; static const u16 *const sPlayerNameWindowCoords[MAX_RFU_PLAYERS - 1] = { sPlayerNameWindowCoords_2Players, sPlayerNameWindowCoords_3Players, sPlayerNameWindowCoords_4Players, sPlayerNameWindowCoords_5Players, }; static const s16 sMonXCoords_2Players[] = {88, 152}; static const s16 sMonXCoords_3Players[] = {88, 120, 152}; static const s16 sMonXCoords_4Players[] = {56, 88, 152, 184}; static const s16 sMonXCoords_5Players[] = {56, 88, 120, 152, 184}; static const s16 *const sMonXCoords[MAX_RFU_PLAYERS - 1] = { sMonXCoords_2Players, sMonXCoords_3Players, sMonXCoords_4Players, sMonXCoords_5Players, }; static void CreateJumpMonSprites(void) { int i, y, playersCount = GetNumPokeJumpPlayers(); const s16 *xCoords = sMonXCoords[playersCount - 2]; for (i = 0; i < playersCount; i++) { struct PokemonJump_MonInfo *monInfo = GetMonInfoByMultiplayerId(i); y = gMonFrontPicCoords[monInfo->species].y_offset; CreateJumpMonSprite(sPokemonJumpGfx, monInfo, *xCoords, y + 112, i); CreateStarSprite(sPokemonJumpGfx, *xCoords, 112, i); xCoords++; } } static void SetMonSpriteY(u32 id, s16 y) { sPokemonJumpGfx->monSprites[id]->y2 = y; } static void UpdateVineSwing(int vineState) { UpdateVineAnim(sPokemonJumpGfx, vineState); ChangeBgY(BG_VENUSAUR, (sVenusaurStates[vineState] * 5) << 13, BG_COORD_SET); } static int DoSameJumpTimeBonus(u8 flags) { int i, numPlayers; for (i = 0, numPlayers = 0; i < MAX_RFU_PLAYERS; i++) { if (flags & 1) { // Player was part of a synchronous jump // Give a bonus to them DoStarAnim(sPokemonJumpGfx, i); numPlayers++; } flags >>= 1; } ShowBonus(numPlayers - 2); return numPlayers; } static void InitDigitPrinters(void) { struct DigitObjUtilTemplate template = { .shape = SPRITE_SHAPE(8x8), .size = SPRITE_SIZE(8x8), .strConvMode = 0, .priority = 1, .oamCount = 5, .xDelta = 8, .x = 108, .y = 6, .spriteSheet = (void*) &sSpriteSheet_Digits, .spritePal = &sSpritePalette_Digits, }; DigitObjUtil_Init(NUM_WINDOWS); DigitObjUtil_CreatePrinter(WIN_POINTS, 0, &template); template.oamCount = 4; template.x = 30; template.y = 6; DigitObjUtil_CreatePrinter(WIN_TIMES, 0, &template); } static void PrintScore(int num) { DigitObjUtil_PrintNumOn(WIN_POINTS, num); } static void PrintJumpsInRow(u16 num) { DigitObjUtil_PrintNumOn(WIN_TIMES, num); } static void StartMonHitShake(u8 multiplayerId) { Gfx_StartMonHitShake(sPokemonJumpGfx, multiplayerId); } static void StartMonHitFlash(u8 multiplayerId) { Gfx_StartMonHitFlash(sPokemonJumpGfx, multiplayerId); } static int IsMonHitShakeActive(int multiplayerId) { return Gfx_IsMonHitShakeActive(sPokemonJumpGfx, multiplayerId); } static void StopMonHitFlash(void) { Gfx_StopMonHitFlash(sPokemonJumpGfx); } static void ResetMonSpriteSubpriorities(void) { Gfx_ResetMonSpriteSubpriorities(sPokemonJumpGfx); } static void StartMonIntroBounce(int multiplayerId) { Gfx_StartMonIntroBounce(sPokemonJumpGfx, multiplayerId); } static int IsMonIntroBounceActive(void) { return Gfx_IsMonIntroBounceActive(sPokemonJumpGfx); } static void AddPlayerNameWindows(void) { struct WindowTemplate window; int i, playersCount = GetNumPokeJumpPlayers(); const u16 *winCoords = sPlayerNameWindowCoords[playersCount - 2]; window.bg = BG_INTERFACE; window.width = 8; window.height = 2; window.paletteNum = 2; window.baseBlock = 0x2B; for (i = 0; i < playersCount; i++) { window.tilemapLeft = winCoords[0]; window.tilemapTop = winCoords[1]; sPokemonJumpGfx->nameWindowIds[i] = AddWindow(&window); ClearWindowTilemap(sPokemonJumpGfx->nameWindowIds[i]); window.baseBlock += 0x10; winCoords += 2; } CopyBgTilemapBufferToVram(BG_INTERFACE); } static void PrintPokeJumpPlayerName(int multiplayerId, u8 bgColor, u8 fgColor, u8 shadow) { u32 x; u8 colors[3] = {bgColor, fgColor, shadow}; FillWindowPixelBuffer(sPokemonJumpGfx->nameWindowIds[multiplayerId], 0); x = 64 - GetStringWidth(FONT_NORMAL, GetPokeJumpPlayerName(multiplayerId), -1); x /= 2; AddTextPrinterParameterized3(sPokemonJumpGfx->nameWindowIds[multiplayerId], FONT_NORMAL, x, 1, colors, TEXT_SKIP_DRAW, GetPokeJumpPlayerName(multiplayerId)); CopyWindowToVram(sPokemonJumpGfx->nameWindowIds[multiplayerId], COPYWIN_GFX); } static void PrintPokeJumpPlayerNames(bool32 highlightSelf) { int i, multiplayerId, playersCount = GetNumPokeJumpPlayers(); if (!highlightSelf) { for (i = 0; i < playersCount; i++) PrintPokeJumpPlayerName(i, TEXT_COLOR_TRANSPARENT, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_LIGHT_GRAY); } else { // Highlight own name multiplayerId = GetPokeJumpMultiplayerId(); for (i = 0; i < playersCount; i++) { if (multiplayerId != i) PrintPokeJumpPlayerName(i, TEXT_COLOR_TRANSPARENT, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_LIGHT_GRAY); else PrintPokeJumpPlayerName(i, TEXT_COLOR_TRANSPARENT, TEXT_COLOR_RED, TEXT_COLOR_LIGHT_RED); } } } static void DrawPlayerNameWindows(void) { int i, playersCount = GetNumPokeJumpPlayers(); for (i = 0; i < playersCount; i++) PutWindowTilemap(sPokemonJumpGfx->nameWindowIds[i]); CopyBgTilemapBufferToVram(BG_INTERFACE); } static void ShowBonus(u8 bonusId) { sPokemonJumpGfx->bonusTimer = 0; ChangeBgX(BG_BONUSES, (bonusId / 2) * 256 * 256, BG_COORD_SET); ChangeBgY(BG_BONUSES, (((bonusId % 2) * 256) - 40) * 256, BG_COORD_SET); ShowBg(BG_BONUSES); CreateTask(Task_UpdateBonus, 4); } static bool32 UpdateBonus(void) { if (sPokemonJumpGfx->bonusTimer >= 32) { return FALSE; } else { ChangeBgY(BG_BONUSES, 128, BG_COORD_ADD); if (++sPokemonJumpGfx->bonusTimer >= 32) HideBg(BG_BONUSES); return TRUE; } } static void Task_UpdateBonus(u8 taskId) { if (!UpdateBonus()) DestroyTask(taskId); } struct MonInfoPacket { u8 id; u16 species; u32 personality; u32 otId; }; static void SendPacket_MonInfo(struct PokemonJump_MonInfo *monInfo) { struct MonInfoPacket packet; packet.id = PACKET_MON_INFO, packet.species = monInfo->species, packet.otId = monInfo->otId, packet.personality = monInfo->personality, Rfu_SendPacket(&packet); } static bool32 RecvPacket_MonInfo(int multiplayerId, struct PokemonJump_MonInfo *monInfo) { struct MonInfoPacket packet; if ((gRecvCmds[multiplayerId][0] & RFUCMD_MASK) != RFUCMD_SEND_PACKET) return FALSE; memcpy(&packet, &gRecvCmds[multiplayerId][1], sizeof(packet)); if (packet.id == PACKET_MON_INFO) { monInfo->species = packet.species; monInfo->otId = packet.otId; monInfo->personality = packet.personality; return TRUE; } return FALSE; } struct UnusedPacket { u8 id; u32 data; u32 filler; }; // Data packet that's never sent // No function to read it either static void SendPacket_Unused(u32 data) { struct UnusedPacket packet; packet.id = PACKET_UNUSED; packet.data = data; Rfu_SendPacket(&packet); } struct LeaderStatePacket { u8 id; u8 funcId; u8 monState; u8 receivedBonusFlags:5; // 1 bit for each player (MAX_RFU_PLAYERS) u8 jumpState:3; u16 jumpTimeStart; u16 vineTimer; u32 jumpsInRow:15; u32 jumpScore:17; }; static void SendPacket_LeaderState(struct PokemonJump_Player *player, struct PokemonJump_CommData *comm) { struct LeaderStatePacket packet; packet.id = PACKET_LEADER_STATE; packet.jumpScore = comm->jumpScore; packet.receivedBonusFlags = comm->receivedBonusFlags; packet.funcId = comm->funcId; packet.vineTimer = comm->data; packet.jumpsInRow = comm->jumpsInRow; packet.monState = player->monState; packet.jumpState = player->jumpState; packet.jumpTimeStart = player->jumpTimeStart; Rfu_SendPacket(&packet); } // Used by group members to read the state of the group leader static bool32 RecvPacket_LeaderState(struct PokemonJump_Player *player, struct PokemonJump_CommData *comm) { struct LeaderStatePacket packet; if ((gRecvCmds[0][0] & RFUCMD_MASK) != RFUCMD_SEND_PACKET) return FALSE; memcpy(&packet, &gRecvCmds[0][1], sizeof(packet)); if (packet.id != PACKET_LEADER_STATE) return FALSE; comm->jumpScore = packet.jumpScore; comm->receivedBonusFlags = packet.receivedBonusFlags; comm->funcId = packet.funcId; comm->data = packet.vineTimer; comm->jumpsInRow = packet.jumpsInRow; player->monState = packet.monState; player->jumpState = packet.jumpState; player->jumpTimeStart = packet.jumpTimeStart; return TRUE; } struct MemberStatePacket { u8 id; u8 monState; u8 jumpState; bool8 funcFinished; u16 jumpTimeStart; u8 funcId; u16 playAgainState; }; static void SendPacket_MemberState(struct PokemonJump_Player *player, u8 funcId, u16 playAgainState) { struct MemberStatePacket packet; packet.id = PACKET_MEMBER_STATE; packet.monState = player->monState; packet.jumpState = player->jumpState; packet.funcFinished = player->funcFinished; packet.jumpTimeStart = player->jumpTimeStart; packet.funcId = funcId; packet.playAgainState = playAgainState; Rfu_SendPacket(&packet); } // Used by the group leader to read the state of group members static bool32 RecvPacket_MemberStateToLeader(struct PokemonJump_Player *player, int multiplayerId, u8 *funcId, u16 *playAgainState) { struct MemberStatePacket packet; if ((gRecvCmds[multiplayerId][0] & RFUCMD_MASK) != RFUCMD_SEND_PACKET) return FALSE; memcpy(&packet, &gRecvCmds[multiplayerId][1], sizeof(packet)); if (packet.id != PACKET_MEMBER_STATE) return FALSE; player->monState = packet.monState; player->jumpState = packet.jumpState; player->funcFinished = packet.funcFinished; player->jumpTimeStart = packet.jumpTimeStart; *funcId = packet.funcId; *playAgainState = packet.playAgainState; return TRUE; } // Used by group members to read the state of other group members static bool32 RecvPacket_MemberStateToMember(struct PokemonJump_Player *player, int multiplayerId) { struct MemberStatePacket packet; if ((gRecvCmds[multiplayerId][0] & RFUCMD_MASK) != RFUCMD_SEND_PACKET) return FALSE; memcpy(&packet, &gRecvCmds[multiplayerId][1], sizeof(packet)); if (packet.id != PACKET_MEMBER_STATE) return FALSE; player->monState = packet.monState; player->jumpState = packet.jumpState; player->funcFinished = packet.funcFinished; player->jumpTimeStart = packet.jumpTimeStart; return TRUE; } static struct PokemonJumpRecords *GetPokeJumpRecords(void) { return &gSaveBlock2Ptr->pokeJump; } void ResetPokemonJumpRecords(void) { struct PokemonJumpRecords *records = GetPokeJumpRecords(); records->jumpsInRow = 0; records->bestJumpScore = 0; records->excellentsInRow = 0; records->gamesWithMaxPlayers = 0; records->unused2 = 0; records->unused1 = 0; } static bool32 TryUpdateRecords(u32 jumpScore, u16 jumpsInRow, u16 excellentsInRow) { struct PokemonJumpRecords *records = GetPokeJumpRecords(); bool32 newRecord = FALSE; if (records->bestJumpScore < jumpScore && jumpScore <= MAX_JUMP_SCORE) records->bestJumpScore = jumpScore, newRecord = TRUE; if (records->jumpsInRow < jumpsInRow && jumpsInRow <= MAX_JUMPS) records->jumpsInRow = jumpsInRow, newRecord = TRUE; if (records->excellentsInRow < excellentsInRow && excellentsInRow <= MAX_JUMPS) records->excellentsInRow = excellentsInRow, newRecord = TRUE; return newRecord; } static void IncrementGamesWithMaxPlayers(void) { struct PokemonJumpRecords *records = GetPokeJumpRecords(); if (records->gamesWithMaxPlayers < 9999) records->gamesWithMaxPlayers++; } void ShowPokemonJumpRecords(void) { u8 taskId = CreateTask(Task_ShowPokemonJumpRecords, 0); Task_ShowPokemonJumpRecords(taskId); } static const struct WindowTemplate sWindowTemplate_Records = { .bg = 0, .tilemapLeft = 1, .tilemapTop = 1, .width = 28, .height = 9, .paletteNum = 15, .baseBlock = 0x1, }; static const u8 *const sRecordsTexts[] = {gText_JumpsInARow, gText_BestScore2, gText_ExcellentsInARow}; #define tState data[0] #define tWindowId data[1] static void Task_ShowPokemonJumpRecords(u8 taskId) { struct WindowTemplate window; int i, width, widthCurr; s16 *data = gTasks[taskId].data; switch (tState) { case 0: window = sWindowTemplate_Records; width = GetStringWidth(FONT_NORMAL, gText_PkmnJumpRecords, 0); for (i = 0; i < ARRAY_COUNT(sRecordsTexts); i++) { widthCurr = GetStringWidth(FONT_NORMAL, sRecordsTexts[i], 0) + 38; if (widthCurr > width) width = widthCurr; } width = (width + 7) / 8; if (width & 1) width++; window.tilemapLeft = (30 - width) / 2; window.width = width; tWindowId = AddWindow(&window); PrintRecordsText(tWindowId, width); CopyWindowToVram(tWindowId, COPYWIN_FULL); tState++; break; case 1: if (!IsDma3ManagerBusyWithBgCopy()) tState++; break; case 2: if (JOY_NEW(A_BUTTON | B_BUTTON)) { rbox_fill_rectangle(tWindowId); CopyWindowToVram(tWindowId, COPYWIN_MAP); tState++; } break; case 3: if (!IsDma3ManagerBusyWithBgCopy()) { RemoveWindow(tWindowId); DestroyTask(taskId); EnableBothScriptContexts(); } break; } } #undef tState #undef tWindowId static void PrintRecordsText(u16 windowId, int width) { int i, x; int recordNums[3]; struct PokemonJumpRecords *records = GetPokeJumpRecords(); recordNums[0] = records->jumpsInRow; recordNums[1] = records->bestJumpScore; recordNums[2] = records->excellentsInRow; LoadUserWindowBorderGfx_(windowId, 0x21D, 0xD0); DrawTextBorderOuter(windowId, 0x21D, 0xD); FillWindowPixelBuffer(windowId, PIXEL_FILL(1)); AddTextPrinterParameterized(windowId, FONT_NORMAL, gText_PkmnJumpRecords, GetStringCenterAlignXOffset(FONT_NORMAL, gText_PkmnJumpRecords, width * 8), 1, TEXT_SKIP_DRAW, NULL); for (i = 0; i < ARRAY_COUNT(sRecordsTexts); i++) { AddTextPrinterParameterized(windowId, FONT_NORMAL, sRecordsTexts[i], 0, 25 + (i * 16), TEXT_SKIP_DRAW, NULL); ConvertIntToDecimalStringN(gStringVar1, recordNums[i], STR_CONV_MODE_LEFT_ALIGN, 5); TruncateToFirstWordOnly(gStringVar1); x = (width * 8) - GetStringWidth(FONT_NORMAL, gStringVar1, 0); AddTextPrinterParameterized(windowId, FONT_NORMAL, gStringVar1, x, 25 + (i * 16), TEXT_SKIP_DRAW, NULL); } PutWindowTilemap(windowId); } static void TruncateToFirstWordOnly(u8 *str) { for (;*str != EOS; str++) { if (*str == CHAR_SPACE) { *str = EOS; break; } } }