pokeemerald/src/dodrio_berry_picking.c
2021-10-30 19:54:15 -04:00

5222 lines
151 KiB
C

#include "global.h"
#include "malloc.h"
#include "bg.h"
#include "dodrio_berry_picking.h"
#include "dynamic_placeholder_text_util.h"
#include "event_data.h"
#include "gpu_regs.h"
#include "international_string_util.h"
#include "item.h"
#include "link.h"
#include "link_rfu.h"
#include "m4a.h"
#include "main.h"
#include "palette.h"
#include "minigame_countdown.h"
#include "random.h"
#include "save.h"
#include "script.h"
#include "sound.h"
#include "string_util.h"
#include "strings.h"
#include "task.h"
#include "text_window.h"
#include "window.h"
#include "constants/items.h"
#include "constants/songs.h"
// Note that in this file 'Dodrio Berry Picking' is often
// shortened to DodrioGame or just Game for convenience
#define MAX_SCORE 999990
#define MAX_BERRIES 9999
// The minimum score needed to receive a prize
#define PRIZE_SCORE 3000
// Difficulty increases as berries are eaten. The rate of new berries increases and the types of berries changes
// When the max difficulty is reached it starts again from the beginning
#define NUM_DIFFICULTIES 7
#define MAX_FALL_DIST 10 // The number of times a berry needs to fall before hitting the ground
#define EAT_FALL_DIST 7 // The number of times a berry needs to fall to be available to eat
enum {
BG_INTERFACE,
BG_TREE_LEFT,
BG_TREE_RIGHT,
BG_SCENERY
};
enum {
FUNC_INTRO,
FUNC_INIT_COUNTDOWN,
FUNC_COUNTDOWN,
FUNC_WAIT_START,
FUNC_PLAY_GAME,
FUNC_INIT_RESULTS,
FUNC_RESULTS,
FUNC_ASK_PLAY_AGAIN,
FUNC_END_LINK,
FUNC_EXIT,
FUNC_RESET_GAME,
FUNC_WAIT_END_GAME,
};
enum {
GFXFUNC_LOAD,
GFXFUNC_SHOW_NAMES,
GFXFUNC_SHOW_RESULTS,
GFXFUNC_MSG_PLAY_AGAIN,
GFXFUNC_MSG_SAVING,
GFXFUNC_MSG_COMM_STANDBY,
GFXFUNC_ERASE_MSG,
GFXFUNC_MSG_PLAYER_DROPPED,
GFXFUNC_STOP,
GFXFUNC_IDLE,
};
enum {
PACKET_READY_START = 1,
PACKET_GAME_STATE,
PACKET_PICK_STATE,
PACKET_READY_END,
};
enum {
PLAY_AGAIN_NONE,
PLAY_AGAIN_YES,
PLAY_AGAIN_NO,
PLAY_AGAIN_DROPPED = 5,
};
enum {
PICK_NONE, // Dodrio standing still
PICK_RIGHT, // Dodrio reaching right
PICK_MIDDLE, // Dodrio reaching up
PICK_LEFT, // Dodrio reaching left
PICK_DISABLED, // Dodrio down after game over
};
enum {
BERRY_BLUE,
BERRY_GREEN,
BERRY_GOLD,
BERRY_MISSED,
BERRY_PRIZE,
BERRY_IN_ROW,
NUM_BERRY_IDS
};
#define NUM_BERRY_TYPES 4 // Blue, Green, Gold, and 'missed'
// Eaten anim comes after the normal & missed versions of other berries
#define ANIM_EATEN (BERRY_MISSED * 2)
enum {
BERRYSTATE_NONE,
BERRYSTATE_PICKED, // Berry has been picked by a Dodrio, replaced with blue hit sprite (still falling)
BERRYSTATE_EATEN, // Berry has been eaten (after being picked), berry is gone now
BERRYSTATE_SQUISHED, // Berry has hit the ground
};
enum {
INPUTSTATE_NONE,
INPUTSTATE_TRY_PICK,
INPUTSTATE_PICKED,
INPUTSTATE_ATE_BERRY,
INPUTSTATE_BAD_MISS,
};
// Colors for status bar squares
// Colored gray when a berry is missed
// Flash red when few yellow squares remain
enum {
STATUS_YELLOW,
STATUS_GRAY,
STATUS_RED,
};
#define NUM_STATUS_SQUARES 10
// Berries fall in predefined columns.
// A total of 10 are available, though fewer will be used with < 5 players
// The 11th column is a repeat of the 1st column wrapped around, so only
// the values 0-9 are unique 'valid' columns
#define NUM_BERRY_COLUMNS 11
#define GFXTAG_DODRIO 0
#define GFXTAG_STATUS 1
#define GFXTAG_BERRIES 2
#define GFXTAG_CLOUD 5
#define GFXTAG_COUNTDOWN 7
#define PALTAG_DODRIO_NORMAL 0
#define PALTAG_DODRIO_SHINY 1
#define PALTAG_STATUS 2
#define PALTAG_BERRIES 3
#define PALTAG_CLOUD 6
#define PALTAG_COUNTDOWN 8
#define NUM_CLOUDS 2
#define PLAYER_NONE 0xFF
struct DodrioGame_Gfx
{
u16 ALIGNED(4) tilemapBuffers[3][BG_SCREEN_SIZE];
bool32 finished;
u8 ALIGNED(4) taskId;
u8 ALIGNED(4) windowIds[MAX_RFU_PLAYERS + 5]; // The latter 5 are never used
u8 ALIGNED(4) state;
u8 ALIGNED(4) loadState;
u16 ALIGNED(4) timer;
u8 ALIGNED(4) cursorSelection;
u8 ALIGNED(4) playAgainState;
void (*func)(void);
}; // size = 0x302C
struct StatusBar
{
u8 unused[12];
bool8 entered[NUM_STATUS_SQUARES];
s16 yChange[NUM_STATUS_SQUARES];
u16 spriteIds[NUM_STATUS_SQUARES];
u16 flashTimer;
}; // size = 0x40
struct DodrioGame_Berries
{
u8 ids[NUM_BERRY_COLUMNS];
u8 fallDist[NUM_BERRY_COLUMNS];
};
struct DodrioGame_PlayerCommData
{
u8 pickState;
bool8 ALIGNED(4) ateBerry;
bool8 ALIGNED(4) missedBerry;
};
struct DodrioGame_Player
{
u8 name[16];
bool32 receivedGameStatePacket; // Never read
struct DodrioGame_Berries berries;
struct DodrioGame_PlayerCommData comm;
u32 unused;
}; // size = 0x3C
// Because Dodrio is required for this minigame,
// the only relevant information about the selected
// Pokémon is whether or not it's shiny
struct DodrioGame_MonInfo
{
bool8 isShiny;
};
struct DodrioGame_ScoreResults
{
u8 ranking;
u32 score;
};
struct DodrioGame
{
/*0x0000*/ void (*exitCallback)(void);
/*0x0004*/ u8 ALIGNED(4) taskId;
/*0x0008*/ u8 ALIGNED(4) playersReceived;
/*0x000C*/ u8 ALIGNED(4) startState;
/*0x0010*/ u8 ALIGNED(4) state;
/*0x0014*/ u8 ALIGNED(4) timer;
/*0x0018*/ u8 ALIGNED(4) funcId;
/*0x001C*/ u8 ALIGNED(4) prevFuncId; // Set, never read
/*0x0020*/ bool8 ALIGNED(4) isLeader;
/*0x0024*/ u8 ALIGNED(4) numPlayers;
/*0x0028*/ u8 ALIGNED(4) multiplayerId;
/*0x0029*/ u8 unused1[7];
/*0x0030*/ u8 ALIGNED(4) countdownEndDelay;
/*0x0034*/ u8 ALIGNED(4) posToPlayerId[MAX_RFU_PLAYERS];
/*0x003C*/ u8 ALIGNED(4) unused2; // Set to 0, never read
/*0x0040*/ u8 ALIGNED(4) numGraySquares;
/*0x0044*/ u8 ALIGNED(4) berryColStart;
/*0x0048*/ u8 ALIGNED(4) berryColEnd;
/*0x004A*/ u16 berryResults[MAX_RFU_PLAYERS][NUM_BERRY_IDS];
/*0x0086*/ u16 berriesEaten[MAX_RFU_PLAYERS];
/*0x0090*/ u8 ALIGNED(4) difficulty[MAX_RFU_PLAYERS];
/*0x0098*/ u8 ALIGNED(4) pickStateQueue[4];
/*0x009C*/ u8 ALIGNED(4) eatTimer[NUM_BERRY_COLUMNS];
/*0x00A8*/ u8 ALIGNED(4) inputState[MAX_RFU_PLAYERS];
/*0x00B0*/ u8 ALIGNED(4) inputDelay[MAX_RFU_PLAYERS];
/*0x00B8*/ u8 ALIGNED(4) berryEatenBy[NUM_BERRY_COLUMNS];
/*0x00C4*/ u8 ALIGNED(4) berryState[NUM_BERRY_COLUMNS];
/*0x00D0*/ u8 ALIGNED(4) fallTimer[NUM_BERRY_COLUMNS];
/*0x00DC*/ u8 ALIGNED(4) newBerryTimer[NUM_BERRY_COLUMNS];
/*0x00E8*/ u8 ALIGNED(4) prevBerryIds[NUM_BERRY_COLUMNS];
/*0x00F4*/ u8 ALIGNED(4) playersAttemptingPick[NUM_BERRY_COLUMNS][2];
/*0x010C*/ u8 ALIGNED(4) playAgainStates[MAX_RFU_PLAYERS];
/*0x0112*/ u16 berriesPickedInRow;
/*0x0114*/ u16 maxBerriesPickedInRow;
/*0x0118*/ bool32 startCountdown; // Never read
/*0x011C*/ bool32 startGame;
/*0x0120*/ bool32 berriesFalling;
/*0x0124*/ u8 ALIGNED(4) clearRecvCmdTimer;
/*0x0128*/ bool8 ALIGNED(4) clearRecvCmds;
/*0x012C*/ bool32 allReadyToEnd;
/*0x0130*/ bool32 readyToEnd[MAX_RFU_PLAYERS];
/*0x0144*/ bool8 ALIGNED(4) playingPickSound;
/*0x0148*/ bool8 ALIGNED(4) playingSquishSound[NUM_BERRY_COLUMNS];
/*0x0154*/ u8 ALIGNED(4) endSoundState;
/*0x0158*/ bool8 ALIGNED(4) readyToStart[MAX_RFU_PLAYERS];
/*0x0160*/ struct DodrioGame_Gfx gfx;
/*0x318C*/ struct DodrioGame_MonInfo monInfo[MAX_RFU_PLAYERS];
/*0x31A0*/ struct DodrioGame_Player players[MAX_RFU_PLAYERS];
/*0x32CC*/ struct DodrioGame_Player player;
/*0x3308*/ struct DodrioGame_ScoreResults scoreResults[MAX_RFU_PLAYERS];
}; // size = 0x3330
EWRAM_DATA static struct DodrioGame * sGame = NULL;
EWRAM_DATA static u16 * sDodrioSpriteIds[MAX_RFU_PLAYERS] = {NULL};
EWRAM_DATA static u16 * sCloudSpriteIds[NUM_CLOUDS] = {NULL};
EWRAM_DATA static u16 * sBerrySpriteIds[NUM_BERRY_COLUMNS] = {NULL};
EWRAM_DATA static u16 * sBerryIconSpriteIds[NUM_BERRY_TYPES] = {NULL};
EWRAM_DATA static struct StatusBar * sStatusBar = NULL;
EWRAM_DATA static struct DodrioGame_Gfx * sGfx = NULL;
static bool32 sExitingGame;
static void ResetTasksAndSprites(void);
static void InitDodrioGame(struct DodrioGame *);
static void Task_StartDodrioGame(u8);
static void DoGameIntro(void);
static void InitCountdown(void);
static void DoCountdown(void);
static void WaitGameStart(void);
static void PlayGame_Leader(void);
static void PlayGame_Member(void);
static void WaitEndGame_Leader(void);
static void WaitEndGame_Member(void);
static void InitResults_Leader(void);
static void InitResults_Member(void);
static void DoResults(void);
static void AskPlayAgain(void);
static void EndLink(void);
static void ExitGame(void);
static void ResetGame(void);
static void Task_NewGameIntro(u8);
static void Task_CommunicateMonInfo(u8);
static void RecvLinkData_Leader(void);
static void SendLinkData_Leader(void);
static void RecvLinkData_Member(void);
static void SendLinkData_Member(void);
static void HandleSound_Leader(void);
static void HandleSound_Member(void);
static void CB2_DodrioGame(void);
static void VBlankCB_DodrioGame(void);
static void InitMonInfo(struct DodrioGame_MonInfo *, struct Pokemon *);
static void CreateTask_(TaskFunc, u8);
static void CreateDodrioGameTask(TaskFunc);
static void SetGameFunc(u8);
static bool32 SlideTreeBordersOut(void);
static void InitFirstWaveOfBerries(void);
static bool32 TryPickBerry(u8, u8, u8);
static void UpdateFallingBerries(void);
static void UpdateGame_Leader(void);
static void UpdateGame_Member(void);
static void GetActiveBerryColumns(u8, u8*, u8*);
static bool32 AllPlayersReadyToStart(void);
static void ResetReadyToStart(void);
static bool32 ReadyToEndGame_Leader(void);
static bool32 ReadyToEndGame_Member(void);
static void TryIncrementDifficulty(u8);
static u8 GetPlayerIdAtColumn(u8);
static u8 GetNewBerryId(u8, u8);
static void IncrementBerryResult(u8, u8, u8);
static void UpdateBerriesPickedInRow(bool32);
static void SetMaxBerriesPickedInRow(void);
static void ResetForPlayAgainPrompt(void);
static void SetRandomPrize(void);
static void TryUpdateRecords(void);
static u8 UpdatePickStateQueue(u8);
static void HandleWaitPlayAgainInput(void);
static void ResetPickState(void);
static u32 GetHighestScore(void);
static void SendPacket_ReadyToStart(bool32);
static void SendPacket_GameState(struct DodrioGame_Player *,
struct DodrioGame_PlayerCommData *,
struct DodrioGame_PlayerCommData *,
struct DodrioGame_PlayerCommData *,
struct DodrioGame_PlayerCommData *,
struct DodrioGame_PlayerCommData *,
u8 , bool32 , bool32 );
static bool32 RecvPacket_GameState(u32,
struct DodrioGame_Player *,
struct DodrioGame_PlayerCommData *,
struct DodrioGame_PlayerCommData *,
struct DodrioGame_PlayerCommData *,
struct DodrioGame_PlayerCommData *,
struct DodrioGame_PlayerCommData *,
u8 *, bool32 *, bool32 *);
static void SendPacket_PickState(u8);
static bool32 RecvPacket_PickState(u32, u8 *);
static void SendPacket_ReadyToEnd(bool32);
static bool32 RecvPacket_ReadyToEnd(u32);
static void LoadDodrioGfx(void);
static void CreateDodrioSprite(struct DodrioGame_MonInfo *, u8, u8, u8);
static void StartDodrioMissedAnim(u8);
static void StartDodrioIntroAnim(u8);
static void FreeDodrioSprites(u8);
static void SetAllDodrioInvisibility(bool8, u8);
static void CreateStatusBarSprites(void);
static void FreeStatusBar(void);
static void SetStatusBarInvisibility(bool8);
static void InitStatusBarPos(void);
static bool32 DoStatusBarIntro(void);
static void LoadBerryGfx(void);
static void CreateBerrySprites(void);
static void FreeBerrySprites(void);
static void CreateCloudSprites(void);
static void ResetCloudPos(void);
static void StartCloudMovement(void);
static void FreeCloudSprites(void);
static void SetCloudInvisibility(bool8);
static void ResetBerryAndStatusBarSprites(void);
static void ResetGfxState(void);
static void InitGameGfx(struct DodrioGame_Gfx *);
static void SetGfxFuncById(u8);
static bool32 IsGfxFuncActive(void);
static u8 GetPlayAgainState(void);
static void SetBerryInvisibility(u8, bool8);
static void SetBerryIconsInvisibility(bool8);
static void SetBerryAnim(u16, u8);
static void SetBerryYPos(u8, u8);
static void SetDodrioAnim(u8, u8);
static u8 GetNewBerryIdByDifficulty(u8, u8);
static void UpdateStatusBarAnim(u8);
static u32 RecvPacket_ReadyToStart(u32);
static u32 IncrementWithLimit(u32, u32);
static u32 Min(u32, u32);
static u32 GetScore(u8);
static void Task_ShowDodrioBerryPickingRecords(u8);
static void Task_TryRunGfxFunc(u8);
static void PrintRecordsText(u8, s32);
static void SpriteCB_Status(struct Sprite *);
static void SpriteCB_Dodrio(struct Sprite *);
static u32 DoDodrioMissedAnim(struct Sprite *);
static u32 DoDodrioIntroAnim(struct Sprite *);
static s16 GetDodrioXPos(u8, u8);
static void SetDodrioInvisibility(bool8, u8);
static void LoadGfx(void);
static bool32 LoadBgGfx(void);
static void InitBgs(void);
static void SetGfxFunc(void (*func)(void));
static void (*GetGfxFunc(void))(void);
static void ShowNames(void);
static void ShowResults(void);
static void Msg_WantToPlayAgain(void);
static void Msg_SavingDontTurnOff(void);
static void Msg_CommunicationStandby(void);
static void EraseMessage(void);
static void Msg_SomeoneDroppedOut(void);
static void StopGfxFuncs(void);
static void GfxIdle(void);
// For each player, the array is a list of all the columns starting with the column to their left
// Only the range of active columns is read from the array (dependent on the number of players),
// so the arrays are spaced such that the numbers in the center are where the data that's read starts and end.
static const u8 sActiveColumnMap[MAX_RFU_PLAYERS][MAX_RFU_PLAYERS][NUM_BERRY_COLUMNS] =
{
{ // 1 player (never used), columns 4-6.
// Sometimes read to get default order regardless of the current number of players
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
},
{ // 2 players (never used), columns 3-6
{0, 1, 2, 3, 4, 5, 6, 3, 8, 9, 0},
{0, 1, 2, 5, 6, 3, 4, 5, 8, 9, 0},
},
{ // 3 players, columns 2-7
{0, 1, 2, 3, 4, 5, 6, 7, 2, 9, 0},
{0, 1, 4, 5, 6, 7, 2, 3, 4, 9, 0},
{0, 1, 6, 7, 2, 3, 4, 5, 6, 9, 0},
},
{ // 4 players, columns 1-8
{0, 1, 2, 3, 4, 5, 6, 7, 8, 1, 0},
{0, 3, 4, 5, 6, 7, 8, 1, 2, 3, 0},
{0, 5, 6, 7, 8, 1, 2, 3, 4, 5, 0},
{0, 7, 8, 1, 2, 3, 4, 5, 6, 7, 0},
},
{ // 5 players, all columns (0-9)
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 },
{ 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2 },
{ 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4 },
{ 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 },
{ 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8 },
},
};
// A table for which falling berry column corresponds to which Dodrio head for each player
// The numbers in each array are the column number for each head, {left, middle, right}
// Dependent on the number of players
static const u8 sDodrioHeadToColumnMap[MAX_RFU_PLAYERS][MAX_RFU_PLAYERS][3] =
{
{ // 1 player (never used)
{4, 5, 6},
},
{ // 2 players (never used)
{3, 4, 5},
{5, 6, 3},
},
{ // 3 players
{4, 5, 6},
{6, 7, 2},
{2, 3, 4},
},
{ // 4 players
{3, 4, 5},
{5, 6, 7},
{7, 8, 1},
{1, 2, 3},
},
{ // 5 players
{4, 5, 6},
{6, 7, 8},
{8, 9, 0},
{0, 1, 2},
{2, 3, 4},
},
};
// A table of player ids and their neighbor, dependent on the total number of players
// {L, M, R}, where M is the player in question, L is their neighbor to the left, and R is their neighbor to the right
static const u8 sDodrioNeighborMap[MAX_RFU_PLAYERS][MAX_RFU_PLAYERS][3] =
{
{ // 1 player (never used)
{1, 0, 1},
},
{ // 2 players (never used)
{1, 0, 1},
{0, 1, 0},
},
{ // 3 players
{2, 0, 1},
{0, 1, 2},
{1, 2, 0},
},
{ // 4 players
{3, 0, 1},
{0, 1, 2},
{1, 2, 3},
{2, 3, 0},
},
{ // 5 players
{4, 0, 1},
{0, 1, 2},
{1, 2, 3},
{2, 3, 4},
{3, 4, 0},
},
};
#define x 9 // No player at this column. This may go out of bounds if this is returned
// Takes the number of players and a column and returns the player id at that column.
// Note that the assignment is somewhat arbitrary as players share neighboring columns.
ALIGNED(4)
static const u8 sPlayerIdAtColumn[MAX_RFU_PLAYERS][NUM_BERRY_COLUMNS] =
{
{x, x, x, x, 1, 1, 1, x, x, x, x}, // 1 player
{x, x, x, 0, 0, 1, 1, 0, x, x, x}, // 2 players
{x, x, 2, 2, 0, 0, 1, 1, 1, x, x}, // 3 players
{x, 3, 3, 0, 0, 1, 1, 2, 2, 3, x}, // 4 players
{3, 3, 4, 4, 0, 0, 1, 1, 2, 2, 3}, // 5 players
};
#undef x
// Each array contains the columns that belong solely to one player, dependent on the number of players
// When determing how difficult the berries in a column should be, the highest
// difficulty of the players sharing that column is used.
// This table is used to skip that check, and instead automatically use the
// difficulty of the only player who can use the column.
static const u8 sUnsharedColumns[MAX_RFU_PLAYERS][MAX_RFU_PLAYERS] =
{
{5},
{4, 6},
{3, 5, 7},
{2, 4, 6, 8},
#ifndef BUGFIX
{1, 3, 5, 6, 9}, // BUG: Column 6 is shared, 7 is not. As a result, the player in column 7 will have their difficulty influenced by their neighbors
#else
{1, 3, 5, 7, 9},
#endif
};
// Duplicate and unused gfx. Feel free to remove.
static const u32 sDuplicateGfx[] = INCBIN_U32("graphics/dodrio_berry_picking/bg.gbapal",
"graphics/dodrio_berry_picking/tree_border.gbapal",
"graphics/dodrio_berry_picking/dodrio.gbapal",
"graphics/dodrio_berry_picking/shiny.gbapal",
"graphics/dodrio_berry_picking/status.gbapal",
"graphics/dodrio_berry_picking/berries.gbapal",
"graphics/dodrio_berry_picking/berries.4bpp.lz",
"graphics/dodrio_berry_picking/cloud.gbapal",
"graphics/dodrio_berry_picking/bg.4bpp.lz",
"graphics/dodrio_berry_picking/tree_border.4bpp.lz",
"graphics/dodrio_berry_picking/status.4bpp.lz",
"graphics/dodrio_berry_picking/cloud.4bpp.lz",
"graphics/dodrio_berry_picking/dodrio.4bpp.lz",
"graphics/dodrio_berry_picking/bg.bin.lz",
"graphics/dodrio_berry_picking/tree_border_right.bin.lz",
"graphics/dodrio_berry_picking/tree_border_left.bin.lz");
static const u8 sBerryFallDelays[][3] =
{
{ [BERRY_BLUE] = 40, [BERRY_GREEN] = 24, [BERRY_GOLD] = 13 },
{ [BERRY_BLUE] = 32, [BERRY_GREEN] = 19, [BERRY_GOLD] = 10 },
{ [BERRY_BLUE] = 22, [BERRY_GREEN] = 13, [BERRY_GOLD] = 7 },
};
// How far the outer tree borders should slide to reveal the game screen.
// Dependent on how many players are playing.
// Curiously the 2-player screen is narrower than the 1-player, though neither
// gets used as there's a 3 player minimum
ALIGNED(4)
static const u8 sTreeBorderXPos[MAX_RFU_PLAYERS] = {8, 5, 8, 11, 15};
// The number of berries eaten needed to progress to the next difficulty
ALIGNED(4)
static const u8 sDifficultyThresholds[NUM_DIFFICULTIES] = {5, 10, 20, 30, 50, 70, 100};
ALIGNED(4)
static const u8 sPrizeBerryIds[][10] =
{
{ // Possible prizes with 3 players
ITEM_TO_BERRY(ITEM_RAZZ_BERRY) - 1,
ITEM_TO_BERRY(ITEM_BLUK_BERRY) - 1,
ITEM_TO_BERRY(ITEM_NANAB_BERRY) - 1,
ITEM_TO_BERRY(ITEM_WEPEAR_BERRY) - 1,
ITEM_TO_BERRY(ITEM_PINAP_BERRY) - 1,
ITEM_TO_BERRY(ITEM_PINAP_BERRY) - 1,
ITEM_TO_BERRY(ITEM_WEPEAR_BERRY) - 1,
ITEM_TO_BERRY(ITEM_NANAB_BERRY) - 1,
ITEM_TO_BERRY(ITEM_BLUK_BERRY) - 1,
ITEM_TO_BERRY(ITEM_RAZZ_BERRY) - 1
},
{ // Possible prizes with 4 players
ITEM_TO_BERRY(ITEM_POMEG_BERRY) - 1,
ITEM_TO_BERRY(ITEM_KELPSY_BERRY) - 1,
ITEM_TO_BERRY(ITEM_QUALOT_BERRY) - 1,
ITEM_TO_BERRY(ITEM_HONDEW_BERRY) - 1,
ITEM_TO_BERRY(ITEM_GREPA_BERRY) - 1,
ITEM_TO_BERRY(ITEM_TAMATO_BERRY) - 1,
ITEM_TO_BERRY(ITEM_CORNN_BERRY) - 1,
ITEM_TO_BERRY(ITEM_MAGOST_BERRY) - 1,
ITEM_TO_BERRY(ITEM_RABUTA_BERRY) - 1,
ITEM_TO_BERRY(ITEM_NOMEL_BERRY) - 1
},
{ // Possible prizes with 5 players
ITEM_TO_BERRY(ITEM_SPELON_BERRY) - 1,
ITEM_TO_BERRY(ITEM_PAMTRE_BERRY) - 1,
ITEM_TO_BERRY(ITEM_WATMEL_BERRY) - 1,
ITEM_TO_BERRY(ITEM_DURIN_BERRY) - 1,
ITEM_TO_BERRY(ITEM_BELUE_BERRY) - 1,
ITEM_TO_BERRY(ITEM_BELUE_BERRY) - 1,
ITEM_TO_BERRY(ITEM_DURIN_BERRY) - 1,
ITEM_TO_BERRY(ITEM_WATMEL_BERRY) - 1,
ITEM_TO_BERRY(ITEM_PAMTRE_BERRY) - 1,
ITEM_TO_BERRY(ITEM_SPELON_BERRY) - 1
},
};
static void (*const sLeaderFuncs[])(void) =
{
[FUNC_INTRO] = DoGameIntro,
[FUNC_INIT_COUNTDOWN] = InitCountdown,
[FUNC_COUNTDOWN] = DoCountdown,
[FUNC_WAIT_START] = WaitGameStart,
[FUNC_PLAY_GAME] = PlayGame_Leader,
[FUNC_INIT_RESULTS] = InitResults_Leader,
[FUNC_RESULTS] = DoResults,
[FUNC_ASK_PLAY_AGAIN] = AskPlayAgain,
[FUNC_END_LINK] = EndLink,
[FUNC_EXIT] = ExitGame,
[FUNC_RESET_GAME] = ResetGame,
[FUNC_WAIT_END_GAME] = WaitEndGame_Leader
};
static void (*const sMemberFuncs[])(void) =
{
[FUNC_INTRO] = DoGameIntro,
[FUNC_INIT_COUNTDOWN] = InitCountdown,
[FUNC_COUNTDOWN] = DoCountdown,
[FUNC_WAIT_START] = WaitGameStart,
[FUNC_PLAY_GAME] = PlayGame_Member,
[FUNC_INIT_RESULTS] = InitResults_Member,
[FUNC_RESULTS] = DoResults,
[FUNC_ASK_PLAY_AGAIN] = AskPlayAgain,
[FUNC_END_LINK] = EndLink,
[FUNC_EXIT] = ExitGame,
[FUNC_RESET_GAME] = ResetGame,
[FUNC_WAIT_END_GAME] = WaitEndGame_Member
};
void StartDodrioBerryPicking(u16 partyId, void (*exitCallback)(void))
{
sExitingGame = FALSE;
if (gReceivedRemoteLinkPlayers != 0 && (sGame = AllocZeroed(sizeof(*sGame))))
{
ResetTasksAndSprites();
InitDodrioGame(sGame);
sGame->exitCallback = exitCallback;
sGame->multiplayerId = GetMultiplayerId();
sGame->player = sGame->players[sGame->multiplayerId];
InitMonInfo(&sGame->monInfo[sGame->multiplayerId], &gPlayerParty[partyId]);
CreateTask(Task_StartDodrioGame, 1);
SetMainCallback2(CB2_DodrioGame);
SetRandomPrize();
GetActiveBerryColumns(sGame->numPlayers, &sGame->berryColStart, &sGame->berryColEnd);
StopMapMusic();
PlayNewMapMusic(MUS_RG_BERRY_PICK);
}
else
{
// Exit - Alloc failed, or players not connected
SetMainCallback2(exitCallback);
return;
}
}
static void ResetTasksAndSprites(void)
{
ResetTasks();
ResetSpriteData();
FreeAllSpritePalettes();
}
static void InitDodrioGame(struct DodrioGame * game)
{
u8 i;
game->startState = 0;
game->state = 0;
game->timer = 0;
game->funcId = FUNC_INTRO;
game->prevFuncId = FUNC_INTRO;
game->startGame = FALSE;
game->berriesFalling = FALSE;
game->countdownEndDelay = 0;
game->numGraySquares = 0;
game->unused2 = 0;
game->allReadyToEnd = FALSE;
for (i = 0; i < ARRAY_COUNT(game->pickStateQueue); i++)
game->pickStateQueue[i] = PICK_NONE;
for (i = 0; i < MAX_RFU_PLAYERS; i++)
{
game->inputState[i] = INPUTSTATE_NONE;
game->inputDelay[i] = 0;
game->berryResults[i][BERRY_BLUE] = 0;
game->berryResults[i][BERRY_GREEN] = 0;
game->berryResults[i][BERRY_GOLD] = 0;
game->berryResults[i][BERRY_MISSED] = 0;
game->berryResults[i][BERRY_IN_ROW] = 0;
game->playAgainStates[i] = PLAY_AGAIN_NONE;
game->readyToEnd[i] = FALSE;
}
for (i = 0; i < NUM_BERRY_COLUMNS; i++)
{
game->fallTimer[i] = 0;
game->newBerryTimer[i] = 0;
game->berryState[i] = BERRYSTATE_NONE;
game->playersAttemptingPick[i][0] = PLAYER_NONE;
game->playersAttemptingPick[i][1] = PLAYER_NONE;
}
game->isLeader = GetMultiplayerId() == 0 ? TRUE : FALSE;
game->numPlayers = GetLinkPlayerCount();
game->posToPlayerId[0] = GetMultiplayerId();
for (i = 1; i < game->numPlayers; i++)
{
game->posToPlayerId[i] = game->posToPlayerId[i - 1] + 1;
if (game->posToPlayerId[i] > game->numPlayers - 1)
game->posToPlayerId[i] %= game->numPlayers;
}
}
static void Task_StartDodrioGame(u8 taskId)
{
u8 i, numPlayers;
switch (sGame->startState)
{
case 0:
SetVBlankCallback(NULL);
CreateTask_(Task_CommunicateMonInfo, 4);
sGame->startState++;
break;
case 1:
if (!FuncIsActiveTask(Task_CommunicateMonInfo))
{
InitGameGfx(&sGame->gfx);
sGame->startState++;
}
break;
case 2:
if (!IsGfxFuncActive())
{
Rfu_SetLinkStandbyCallback();
sGame->startState++;
}
break;
case 3:
if (IsLinkTaskFinished())
{
if (gReceivedRemoteLinkPlayers != 0)
{
LoadWirelessStatusIndicatorSpriteGfx();
CreateWirelessStatusIndicatorSprite(0, 0);
}
sGame->startState++;
}
break;
case 4:
numPlayers = sGame->numPlayers;
LoadDodrioGfx();
for (i = 0; i < numPlayers; i++)
CreateDodrioSprite(&sGame->monInfo[sGame->posToPlayerId[i]], i, sGame->posToPlayerId[i], sGame->numPlayers);
SetAllDodrioInvisibility(FALSE, sGame->numPlayers);
sGame->startState++;
break;
case 5:
LoadBerryGfx();
CreateBerrySprites();
CreateCloudSprites();
CreateStatusBarSprites();
sGame->startState++;
break;
case 6:
BlendPalettes(PALETTES_ALL, 0x10, 0x00);
BeginNormalPaletteFade(PALETTES_ALL, 0, 16, 0, 0);
SetVBlankCallback(VBlankCB_DodrioGame);
sGame->startState++;
break;
case 7:
UpdatePaletteFade();
if (!gPaletteFade.active)
sGame->startState++;
break;
default:
DestroyTask(taskId);
CreateDodrioGameTask(Task_NewGameIntro);
break;
}
}
static void Task_DodrioGame_Leader(u8 taskId)
{
RecvLinkData_Leader();
sLeaderFuncs[sGame->funcId]();
if (!sExitingGame)
UpdateGame_Leader();
SendLinkData_Leader();
}
static void Task_DodrioGame_Member(u8 taskId)
{
RecvLinkData_Member();
sMemberFuncs[sGame->funcId]();
if (!sExitingGame)
UpdateGame_Member();
SendLinkData_Member();
}
static void DoGameIntro(void)
{
switch (sGame->state)
{
case 0:
StartDodrioIntroAnim(1);
SetGfxFuncById(GFXFUNC_SHOW_NAMES);
sGame->state++;
break;
case 1:
if (!IsGfxFuncActive())
SetGameFunc(FUNC_INIT_COUNTDOWN);
break;
}
}
static void InitCountdown(void)
{
switch (sGame->state)
{
case 0:
InitFirstWaveOfBerries();
sGame->state++;
break;
default:
sGame->startCountdown = TRUE;
SetGameFunc(FUNC_COUNTDOWN);
break;
}
}
static void DoCountdown(void)
{
switch (sGame->state)
{
case 0:
StartMinigameCountdown(GFXTAG_COUNTDOWN, PALTAG_COUNTDOWN, 120, 80, 0);
sGame->state++;
break;
case 1:
Rfu_SetLinkStandbyCallback();
sGame->state++;
break;
case 2:
if (IsLinkTaskFinished())
{
sGame->state++;
sGame->countdownEndDelay = 0;
}
break;
case 3:
if (!IsMinigameCountdownRunning())
sGame->state++;
break;
case 4:
if (++sGame->countdownEndDelay > 5)
{
Rfu_SetLinkStandbyCallback();
sGame->state++;
}
break;
case 5:
if (IsLinkTaskFinished())
SetGameFunc(FUNC_WAIT_START);
break;
}
}
static void WaitGameStart(void)
{
switch (sGame->state)
{
case 0:
if (sGame->startGame)
SetGameFunc(FUNC_PLAY_GAME);
break;
}
}
static void PlayGame_Leader(void)
{
switch (sGame->state)
{
case 0:
if (sGame->numGraySquares < NUM_STATUS_SQUARES)
{
if (sGame->inputState[0] == INPUTSTATE_NONE)
{
if (JOY_NEW(DPAD_UP))
{
if (sGame->players[0].comm.pickState == PICK_NONE)
{
sGame->players[0].comm.ateBerry = FALSE;
sGame->players[0].comm.pickState = UpdatePickStateQueue(PICK_MIDDLE);
}
}
else if (JOY_NEW(DPAD_RIGHT))
{
if (sGame->players[0].comm.pickState == PICK_NONE)
{
sGame->players[0].comm.ateBerry = FALSE;
sGame->players[0].comm.pickState = UpdatePickStateQueue(PICK_RIGHT);
}
}
else if (JOY_NEW(DPAD_LEFT))
{
if (sGame->players[0].comm.pickState == PICK_NONE)
{
sGame->players[0].comm.ateBerry = FALSE;
sGame->players[0].comm.pickState = UpdatePickStateQueue(PICK_LEFT);
}
}
else
{
sGame->players[0].comm.pickState = UpdatePickStateQueue(PICK_NONE);
}
}
}
else
{
SetGameFunc(FUNC_WAIT_END_GAME);
}
UpdateFallingBerries();
HandleSound_Leader();
break;
}
}
static void PlayGame_Member(void)
{
if (sGame->numGraySquares < NUM_STATUS_SQUARES)
{
if (JOY_NEW(DPAD_UP))
{
if (sGame->players[sGame->multiplayerId].comm.pickState == PICK_NONE)
{
sGame->player.comm.pickState = PICK_MIDDLE;
}
}
else if (JOY_NEW(DPAD_RIGHT))
{
if (sGame->players[sGame->multiplayerId].comm.pickState == PICK_NONE)
{
sGame->player.comm.pickState = PICK_RIGHT;
}
}
else if (JOY_NEW(DPAD_LEFT))
{
if (sGame->players[sGame->multiplayerId].comm.pickState == PICK_NONE)
{
sGame->player.comm.pickState = PICK_LEFT;
}
}
else
{
sGame->player.comm.pickState = PICK_NONE;
}
}
else
{
SetGameFunc(FUNC_WAIT_END_GAME);
}
HandleSound_Member();
}
static void WaitEndGame_Leader(void)
{
u8 i;
UpdateFallingBerries();
HandleSound_Leader();
if (ReadyToEndGame_Leader() == TRUE)
{
SetMaxBerriesPickedInRow();
SetGameFunc(FUNC_INIT_RESULTS);
}
else
{
sGame->allReadyToEnd = TRUE;
for (i = 1; i < sGame->numPlayers; i++)
{
if (sGame->readyToEnd[i] != TRUE)
{
sGame->allReadyToEnd = FALSE;
break;
}
}
}
}
static void WaitEndGame_Member(void)
{
HandleSound_Member();
if (ReadyToEndGame_Member() == TRUE)
SetGameFunc(FUNC_INIT_RESULTS);
}
static bool32 AllLinkBlocksReceived(void)
{
u8 recvStatus = GetBlockReceivedStatus();
u8 playerFlags = GetLinkPlayerCountAsBitFlags();
if (recvStatus == playerFlags)
{
ResetBlockReceivedFlags();
return TRUE;
}
else
{
return FALSE;
}
}
static void InitResults_Leader(void)
{
switch (sGame->state)
{
case 0:
if (SendBlock(0, sGame->berryResults, sizeof(sGame->berryResults)))
{
sGame->playersReceived = 0;
sGame->state++;
}
break;
case 1:
if (IsLinkTaskFinished())
{
sGame->state++;
}
break;
case 2:
if (AllLinkBlocksReceived())
{
sGame->playersReceived = sGame->numPlayers;
}
if (sGame->playersReceived >= sGame->numPlayers)
{
sGame->timer++;
sGame->state++;
}
break;
default:
if (WaitFanfare(TRUE))
{
SetGameFunc(FUNC_RESULTS);
FadeOutAndPlayNewMapMusic(MUS_RG_VICTORY_WILD, 4);
}
break;
}
}
static void InitResults_Member(void)
{
u8 i;
switch (sGame->state) {
case 0:
if (SendBlock(0, sGame->berryResults[sGame->timer], sizeof(sGame->berryResults))) {
sGame->playersReceived = 0;
sGame->state++;
}
break;
case 1:
if (IsLinkTaskFinished()) {
sGame->state++;
}
break;
case 2:
if (AllLinkBlocksReceived()) {
for (i = 0; i < sGame->numPlayers; i++) {
memcpy(sGame->berryResults, gBlockRecvBuffer, sizeof(sGame->berryResults));
sGame->playersReceived = sGame->numPlayers;
}
}
if (sGame->playersReceived >= sGame->numPlayers) {
sGame->timer++;
sGame->state++;
}
break;
default:
if (WaitFanfare(TRUE)) {
sGame->maxBerriesPickedInRow = sGame->berryResults[sGame->multiplayerId][BERRY_IN_ROW];
SetGameFunc(FUNC_RESULTS);
FadeOutAndPlayNewMapMusic(MUS_RG_VICTORY_WILD, 4);
}
break;
}
}
static void DoResults(void)
{
u8 playAgainState = PLAY_AGAIN_YES;
u8 i;
switch (sGame->state)
{
case 0:
TryUpdateRecords();
SetStatusBarInvisibility(TRUE);
ResetCloudPos();
SetCloudInvisibility(TRUE);
SetGfxFuncById(GFXFUNC_SHOW_RESULTS);
sGame->state++;
break;
case 1:
if (!IsGfxFuncActive())
{
SetGfxFuncById(GFXFUNC_MSG_COMM_STANDBY);
sGame->state++;
}
break;
case 2:
playAgainState = GetPlayAgainState();
if (SendBlock(0, &playAgainState, sizeof(playAgainState)))
sGame->state++;
break;
case 3:
if (IsLinkTaskFinished())
{
sGame->state++;
sGame->playersReceived = 0;
}
break;
case 4:
if (AllLinkBlocksReceived())
{
for (i = 0; i < sGame->numPlayers; i++)
{
*(&sGame->playAgainStates[i]) = *(u8 *)gBlockRecvBuffer[i];
sGame->playersReceived = sGame->numPlayers;
}
}
if (sGame->playersReceived >= sGame->numPlayers)
{
if (++sGame->timer >= 120)
{
SetGfxFuncById(GFXFUNC_ERASE_MSG);
sGame->state++;
}
}
break;
default:
if (!IsGfxFuncActive())
SetGameFunc(FUNC_ASK_PLAY_AGAIN);
break;
}
}
static void AskPlayAgain(void)
{
u8 playAgainState;
u8 i;
switch (sGame->state)
{
case 0:
if (GetHighestScore() >= PRIZE_SCORE)
{
SetGfxFuncById(GFXFUNC_MSG_SAVING);
}
sGame->state++;
break;
case 1:
if (!IsGfxFuncActive())
{
SetGfxFuncById(GFXFUNC_MSG_PLAY_AGAIN);
sGame->state++;
}
break;
case 2:
ResetBerryAndStatusBarSprites();
ResetForPlayAgainPrompt();
sGame->state++;
break;
case 3:
if ((playAgainState = GetPlayAgainState()) != PLAY_AGAIN_NONE)
{
sGame->state++;
}
break;
case 4:
if (!IsGfxFuncActive())
{
SetGfxFuncById(GFXFUNC_MSG_COMM_STANDBY);
sGame->state++;
}
break;
case 5:
playAgainState = GetPlayAgainState();
if (SendBlock(0, &playAgainState, sizeof(playAgainState)))
{
sGame->playersReceived = 0;
sGame->state++;
}
break;
case 6:
if (IsLinkTaskFinished())
sGame->state++;
break;
case 7:
if (AllLinkBlocksReceived())
{
for (i = 0; i < sGame->numPlayers; i++)
{
*(&sGame->playAgainStates[i]) = *(u8 *)gBlockRecvBuffer[i];
sGame->playersReceived = sGame->numPlayers;
}
}
if (sGame->playersReceived >= sGame->numPlayers)
{
if (++sGame->timer >= 120)
{
ResetPickState();
SetGfxFuncById(GFXFUNC_ERASE_MSG);
sGame->state++;
}
}
else
{
HandleWaitPlayAgainInput();
}
break;
default:
if (!IsGfxFuncActive())
{
for (i = 0; i < sGame->numPlayers; i++)
{
if (sGame->playAgainStates[i] == PLAY_AGAIN_NO)
{
SetGameFunc(FUNC_END_LINK);
return;
}
}
SetGameFunc(FUNC_RESET_GAME);
}
break;
}
}
static void EndLink(void)
{
switch (sGame->state)
{
case 0:
SetCloseLinkCallback();
SetGfxFuncById(GFXFUNC_MSG_PLAYER_DROPPED);
sGame->state++;
break;
case 1:
if (!IsGfxFuncActive())
sGame->state++;
break;
case 2:
if (GetPlayAgainState() == PLAY_AGAIN_DROPPED)
sGame->state++;
break;
default:
if (gReceivedRemoteLinkPlayers == 0)
{
SetGameFunc(FUNC_EXIT);
}
break;
}
}
static void ExitGame(void)
{
switch (sGame->state)
{
case 0:
BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, 0);
sGame->state++;
break;
case 1:
UpdatePaletteFade();
if (!gPaletteFade.active)
sGame->state++;
break;
case 2:
FreeBerrySprites();
FreeStatusBar();
FreeDodrioSprites(sGame->numPlayers);
FreeCloudSprites();
sExitingGame = TRUE;
SetGfxFuncById(GFXFUNC_STOP);
sGame->state++;
break;
default:
if (!IsGfxFuncActive())
{
SetMainCallback2(sGame->exitCallback);
DestroyTask(sGame->taskId);
Free(sGame);
FreeAllWindowBuffers();
}
break;
}
}
static void ResetGame(void)
{
switch (sGame->state)
{
case 0:
SetGfxFuncById(GFXFUNC_IDLE);
BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, 0);
sGame->state++;
break;
case 1:
UpdatePaletteFade();
if (!gPaletteFade.active)
{
sGame->state++;
}
break;
case 2:
ChangeBgX(0, 0, 0);
ChangeBgY(0, 0, 0);
ChangeBgX(1, 0, 0);
ChangeBgY(1, 0, 0);
ChangeBgX(2, 0, 0);
ChangeBgY(2, 0, 0);
ChangeBgX(3, 0, 0);
ChangeBgY(3, 0, 0);
sGame->state++;
break;
case 3:
StopMapMusic();
sGame->state++;
break;
case 4:
PlayNewMapMusic(MUS_RG_BERRY_PICK);
StartCloudMovement();
sGame->state++;
break;
case 5:
BlendPalettes(PALETTES_ALL, 16, 0);
BeginNormalPaletteFade(PALETTES_ALL, 0, 16, 0, 0);
sGame->state++;
break;
case 6:
UpdatePaletteFade();
if (!gPaletteFade.active)
sGame->state++;
break;
default:
DestroyTask(sGame->taskId);
CreateDodrioGameTask(Task_NewGameIntro);
ResetGfxState();
InitDodrioGame(sGame);
if (gReceivedRemoteLinkPlayers == 0)
sGame->numPlayers = 1;
SetRandomPrize();
SetCloudInvisibility(FALSE);
break;
}
}
static void Task_NewGameIntro(u8 taskId)
{
switch (sGame->state)
{
case 0:
if (SlideTreeBordersOut() == TRUE)
sGame->state++;
break;
case 1:
InitStatusBarPos();
sGame->state++;
break;
case 2:
if (DoStatusBarIntro() == TRUE)
sGame->state++;
break;
default:
if (sGame->isLeader)
CreateDodrioGameTask(Task_DodrioGame_Leader);
else
CreateDodrioGameTask(Task_DodrioGame_Member);
DestroyTask(taskId);
break;
}
}
#define tState data[0]
static void Task_CommunicateMonInfo(u8 taskId)
{
s16 * data = gTasks[taskId].data;
u8 i;
switch (tState)
{
case 0:
if (SendBlock(0, &sGame->monInfo[sGame->multiplayerId].isShiny, sizeof(sGame->monInfo[sGame->multiplayerId].isShiny)))
{
sGame->playersReceived = 0;
tState++;
}
break;
case 1:
if (IsLinkTaskFinished())
tState++;
break;
case 2:
if (AllLinkBlocksReceived())
{
for (i = 0; i < sGame->numPlayers; i++)
{
*(u8 *)&sGame->monInfo[i] = *(u8 *)gBlockRecvBuffer[i];
sGame->playersReceived = sGame->numPlayers;
}
}
if (sGame->playersReceived >= sGame->numPlayers)
{
DestroyTask(taskId);
SetGfxFuncById(GFXFUNC_ERASE_MSG);
sGame->state++;
}
break;
}
}
#undef tState
static void RecvLinkData_Gameplay(void)
{
u8 i;
u8 numPlayers = sGame->numPlayers;
sGame->players[0].receivedGameStatePacket = RecvPacket_GameState(0,
&sGame->players[0],
&sGame->players[0].comm,
&sGame->players[1].comm,
&sGame->players[2].comm,
&sGame->players[3].comm,
&sGame->players[4].comm,
&sGame->numGraySquares,
&sGame->berriesFalling,
&sGame->allReadyToEnd);
sGame->clearRecvCmds = TRUE;
for (i = 1; i < numPlayers; i++)
{
if (sGame->inputState[i] == INPUTSTATE_NONE && !RecvPacket_PickState(i, &sGame->players[i].comm.pickState))
{
sGame->players[i].comm.pickState = PICK_NONE;
sGame->clearRecvCmds = FALSE;
}
}
if (++sGame->clearRecvCmdTimer >= 60)
{
if (sGame->clearRecvCmds)
{
ClearRecvCommands();
sGame->clearRecvCmdTimer = 0;
}
else if (sGame->clearRecvCmdTimer > 70)
{
ClearRecvCommands();
sGame->clearRecvCmdTimer = 0;
}
}
for (i = 0; i < numPlayers; i++)
{
if (sGame->players[i].comm.pickState != PICK_NONE && sGame->inputState[i] == INPUTSTATE_NONE)
{
sGame->inputState[i] = INPUTSTATE_TRY_PICK;
}
switch (sGame->inputState[i])
{
case INPUTSTATE_NONE:
default:
break;
case INPUTSTATE_TRY_PICK:
case INPUTSTATE_PICKED:
case INPUTSTATE_ATE_BERRY:
if (++sGame->inputDelay[i] >= 6)
{
sGame->inputDelay[i] = 0;
sGame->inputState[i] = INPUTSTATE_NONE;
sGame->players[i].comm.pickState = PICK_NONE;
sGame->players[i].comm.ateBerry = FALSE;
sGame->players[i].comm.missedBerry = FALSE;
}
break;
case INPUTSTATE_BAD_MISS:
// Tried to pick with no berry in range, long delay until next input
if (++sGame->inputDelay[i] >= 40)
{
sGame->inputDelay[i] = 0;
sGame->inputState[i] = INPUTSTATE_NONE;
sGame->players[i].comm.pickState = PICK_NONE;
sGame->players[i].comm.ateBerry = FALSE;
sGame->players[i].comm.missedBerry = FALSE;
}
break;
}
}
}
static void RecvLinkData_ReadyToEnd(void)
{
u8 i;
u8 numPlayers = sGame->numPlayers;
sGame->players[0].receivedGameStatePacket = RecvPacket_GameState(0,
&sGame->players[0],
&sGame->players[0].comm,
&sGame->players[1].comm,
&sGame->players[2].comm,
&sGame->players[3].comm,
&sGame->players[4].comm,
&sGame->numGraySquares,
&sGame->berriesFalling,
&sGame->allReadyToEnd);
sGame->clearRecvCmds = TRUE;
for (i = 1; i < numPlayers; i++)
{
if (RecvPacket_ReadyToEnd(i))
{
sGame->readyToEnd[i] = TRUE;
sGame->clearRecvCmds = FALSE;
}
}
if (++sGame->clearRecvCmdTimer >= 60)
{
if (sGame->clearRecvCmds)
{
ClearRecvCommands();
sGame->clearRecvCmdTimer = 0;
}
else if (sGame->clearRecvCmdTimer > 70)
{
ClearRecvCommands();
sGame->clearRecvCmdTimer = 0;
}
}
}
static void RecvLinkData_Leader(void)
{
switch (sGame->funcId)
{
case FUNC_WAIT_START:
if (AllPlayersReadyToStart() == TRUE)
{
ResetReadyToStart();
sGame->startGame = TRUE;
}
break;
case FUNC_PLAY_GAME:
RecvLinkData_Gameplay();
break;
case FUNC_WAIT_END_GAME:
RecvLinkData_ReadyToEnd();
break;
}
}
static void SendLinkData_Leader(void)
{
switch (sGame->funcId)
{
case FUNC_PLAY_GAME:
SendPacket_GameState(&sGame->player,
&sGame->players[0].comm,
&sGame->players[1].comm,
&sGame->players[2].comm,
&sGame->players[3].comm,
&sGame->players[4].comm,
sGame->numGraySquares,
sGame->berriesFalling,
sGame->allReadyToEnd);
break;
case FUNC_WAIT_END_GAME:
SendPacket_GameState(&sGame->player,
&sGame->players[0].comm,
&sGame->players[1].comm,
&sGame->players[2].comm,
&sGame->players[3].comm,
&sGame->players[4].comm,
sGame->numGraySquares,
sGame->berriesFalling,
sGame->allReadyToEnd);
break;
}
}
static void RecvLinkData_Member(void)
{
switch (sGame->funcId)
{
case FUNC_PLAY_GAME:
RecvPacket_GameState(sGame->multiplayerId,
&sGame->players[sGame->multiplayerId],
&sGame->players[0].comm,
&sGame->players[1].comm,
&sGame->players[2].comm,
&sGame->players[3].comm,
&sGame->players[4].comm,
&sGame->numGraySquares,
&sGame->berriesFalling,
&sGame->allReadyToEnd);
break;
case FUNC_WAIT_END_GAME:
RecvPacket_GameState(sGame->multiplayerId,
&sGame->players[sGame->multiplayerId],
&sGame->players[0].comm,
&sGame->players[1].comm,
&sGame->players[2].comm,
&sGame->players[3].comm,
&sGame->players[4].comm,
&sGame->numGraySquares,
&sGame->berriesFalling,
&sGame->allReadyToEnd);
break;
}
}
static void SendLinkData_Member(void)
{
switch (sGame->funcId)
{
case FUNC_WAIT_START:
SendPacket_ReadyToStart(TRUE);
sGame->startGame = TRUE;
break;
case FUNC_PLAY_GAME:
if (sGame->player.comm.pickState != PICK_NONE)
{
SendPacket_PickState(sGame->player.comm.pickState);
}
break;
case FUNC_WAIT_END_GAME:
if (!sGame->berriesFalling && !sGame->allReadyToEnd)
SendPacket_ReadyToEnd(TRUE);
break;
}
}
static void HandleSound_Leader(void)
{
if (sGame->players[sGame->multiplayerId].comm.pickState == PICK_NONE)
{
if (!IsSEPlaying())
sGame->playingPickSound = FALSE;
}
else if (sGame->players[sGame->multiplayerId].comm.ateBerry == TRUE)
{
if (!sGame->playingPickSound)
{
m4aSongNumStop(SE_SUCCESS);
PlaySE(SE_SUCCESS);
sGame->playingPickSound = TRUE;
}
}
else if (sGame->players[sGame->multiplayerId].comm.missedBerry == TRUE)
{
if (!sGame->playingPickSound && !IsSEPlaying())
{
PlaySE(SE_BOO);
StartDodrioMissedAnim(1);
sGame->playingPickSound = TRUE;
}
}
if (sGame->endSoundState == 0 && sGame->numGraySquares >= NUM_STATUS_SQUARES)
{
// Ready to play game over sound
StopMapMusic();
sGame->endSoundState = 1;
}
else if (sGame->endSoundState == 1)
{
// Play game over sound
PlayFanfareByFanfareNum(FANFARE_TOO_BAD);
sGame->endSoundState = 2;
}
}
static void HandleSound_Member(void)
{
u8 berryStart = sGame->berryColStart;
u8 berryEnd = sGame->berryColEnd;
u8 i;
if (sGame->players[sGame->multiplayerId].comm.pickState == PICK_NONE)
{
if (sGame->players[sGame->multiplayerId].comm.ateBerry != TRUE
&& sGame->players[sGame->multiplayerId].comm.missedBerry != TRUE)
sGame->playingPickSound = 0;
}
else if (sGame->players[sGame->multiplayerId].comm.ateBerry == TRUE)
{
if (!sGame->playingPickSound)
{
m4aSongNumStop(SE_SUCCESS);
PlaySE(SE_SUCCESS);
sGame->playingPickSound = TRUE;
}
}
else if (sGame->players[sGame->multiplayerId].comm.missedBerry == TRUE)
{
if (!sGame->playingPickSound && !IsSEPlaying())
{
PlaySE(SE_BOO);
StartDodrioMissedAnim(1);
sGame->playingPickSound = TRUE;
}
}
for (i = berryStart; i < berryEnd; i++)
{
struct DodrioGame_Berries * berries = &sGame->players[sGame->multiplayerId].berries;
if (berries->fallDist[i] >= MAX_FALL_DIST)
{
if (!sGame->playingSquishSound[i])
{
PlaySE(SE_BALLOON_RED + berries->ids[i]);
sGame->playingSquishSound[i] = TRUE;
}
}
else
{
sGame->playingSquishSound[i] = FALSE;
}
}
if (sGame->endSoundState == 0 && sGame->numGraySquares >= NUM_STATUS_SQUARES)
{
// Ready to play game over sound
StopMapMusic();
sGame->endSoundState = 1;
}
else if (sGame->endSoundState == 1)
{
// Play game over sound
PlayFanfareByFanfareNum(FANFARE_TOO_BAD);
sGame->endSoundState = 2;
}
}
static void CB2_DodrioGame(void)
{
RunTasks();
AnimateSprites();
BuildOamBuffer();
UpdatePaletteFade();
}
static void VBlankCB_DodrioGame(void)
{
TransferPlttBuffer();
LoadOam();
ProcessSpriteCopyRequests();
}
static void InitMonInfo(struct DodrioGame_MonInfo * monInfo, struct Pokemon * mon)
{
monInfo->isShiny = IsMonShiny(mon);
}
static void CreateTask_(TaskFunc func, u8 priority)
{
CreateTask(func, priority);
}
static void CreateDodrioGameTask(TaskFunc func)
{
sGame->taskId = CreateTask(func, 1);
sGame->state = 0;
sGame->startState = 0;
sGame->timer = 0;
}
static void SetGameFunc(u8 funcId)
{
sGame->prevFuncId = sGame->funcId;
sGame->funcId = funcId;
sGame->state = 0;
sGame->timer = 0;
}
static bool32 SlideTreeBordersOut(void)
{
u8 x = sGame->timer / 4;
sGame->timer++;
if (x != 0 && sGame->timer % 4 == 0)
{
if (x < sTreeBorderXPos[sGame->numPlayers - 1])
{
// Update position
SetGpuReg(REG_OFFSET_BG1HOFS, (x * 8)); // BG_TREE_LEFT
SetGpuReg(REG_OFFSET_BG2HOFS, -(x * 8)); // BG_TREE_RIGHT
return FALSE;
}
else
{
// Animation finished
return TRUE;
}
}
else
{
return FALSE;
}
}
static void InitFirstWaveOfBerries(void)
{
u8 i;
u8 berryStart = sGame->berryColStart;
u8 berryEnd = sGame->berryColEnd;
for (i = berryStart; i < berryEnd; i++)
{
struct DodrioGame_Berries * berries = &sGame->player.berries;
berries->fallDist[i] = (i % 2 == 0) ? 1 : 0;
berries->ids[i] = BERRY_BLUE;
}
}
// This function checks every berry and resolves if it should be eaten or not.
// It's run in a loop that handles moving each individual berry, which means
// that every time any berry moves, every single berry is checked.
static void HandlePickBerries(void)
{
u8 berryStart = sGame->berryColStart;
u8 berryEnd = sGame->berryColEnd;
u8 numPlayers = sGame->numPlayers;
u8 i, j, k, column;
// Game is already over
if (sGame->numGraySquares >= NUM_STATUS_SQUARES)
return;
for (i = 0; i < numPlayers; i++)
{
u8 *pickState = &sGame->players[i].comm.pickState;
if (*pickState != PICK_NONE && sGame->inputState[i] == INPUTSTATE_TRY_PICK)
{
// Player is attempting to pick a berry
for (j = berryStart; j < berryEnd; j++)
{
column = sActiveColumnMap[0][0][j];
// Attempt has already been checked
if (sGame->playersAttemptingPick[column][0] == i
|| sGame->playersAttemptingPick[column][1] == i)
break;
// Check berry pick attempt
if (TryPickBerry(i, *pickState, column) == TRUE)
{
// Attempt was successful
for (k = 0; k < ARRAY_COUNT(sGame->playersAttemptingPick[0]); k++)
{
if (sGame->playersAttemptingPick[column][k] == PLAYER_NONE)
{
sGame->playersAttemptingPick[column][k] = i;
sGame->inputState[i] = INPUTSTATE_PICKED;
sGame->berryState[column] = BERRYSTATE_PICKED;
break;
}
}
break;
}
if (sGame->players[i].comm.missedBerry == TRUE)
break;
}
}
}
for (j = berryStart; j < berryEnd; j++)
{
u8 playerIdMissed = PLAYER_NONE;
column = sActiveColumnMap[0][0][j];
if (sGame->berryState[column] == BERRYSTATE_PICKED)
{
s32 delayRemaining;
u8 playerIdPicked, delayStage = sGame->difficulty[GetPlayerIdAtColumn(column)] / NUM_DIFFICULTIES;
if (delayStage >= ARRAY_COUNT(sBerryFallDelays) - 1)
delayStage = ARRAY_COUNT(sBerryFallDelays) - 1;
delayRemaining = sBerryFallDelays[delayStage][sGame->players[0].berries.ids[column]] - sGame->fallTimer[column];
if (delayRemaining < 6)
sGame->eatTimer[column] += delayRemaining;
if (++sGame->eatTimer[column] >= 6)
{
sGame->eatTimer[column] = 0;
if (sGame->playersAttemptingPick[column][0] == PLAYER_NONE
&& sGame->playersAttemptingPick[column][1] == PLAYER_NONE)
{
// No players attempting to pick this berry
continue;
}
else if (sGame->playersAttemptingPick[column][0] != PLAYER_NONE
&& sGame->playersAttemptingPick[column][1] == PLAYER_NONE)
{
// One player attempting to pick this berry
playerIdPicked = sGame->playersAttemptingPick[column][0];
}
else
{
// Two players attempting to pick this berry
// Randomly give it to one of them
u8 playerId1 = sGame->playersAttemptingPick[column][0];
i = sGame->playersAttemptingPick[column][1]; // playerId2. Have to re-use the variable to match.
if (!(Random() & 1))
{
playerIdPicked = playerId1;
playerIdMissed = i;
}
else
{
playerIdPicked = i;
playerIdMissed = playerId1;
}
}
// Eat berry
sGame->player.berries.fallDist[column] = EAT_FALL_DIST;
sGame->berryState[column] = BERRYSTATE_EATEN;
sGame->inputState[playerIdPicked] = INPUTSTATE_ATE_BERRY;
sGame->berryEatenBy[column] = playerIdPicked;
sGame->players[playerIdPicked].comm.ateBerry = TRUE;
#ifdef UBFIX
if (playerIdMissed != PLAYER_NONE)
#endif
sGame->players[playerIdMissed].comm.missedBerry = TRUE; // UB: playerIdMissed can be PLAYER_NONE here, which is out of bounds
sGame->berriesEaten[playerIdPicked]++;
IncrementBerryResult(0, column, playerIdPicked);
UpdateBerriesPickedInRow(TRUE);
TryIncrementDifficulty(playerIdPicked);
sGame->prevBerryIds[column] = sGame->player.berries.ids[column];
sGame->player.berries.ids[column] = BERRY_MISSED; // Just to clear berry id, wasn't actually missed
sGame->playersAttemptingPick[column][0] = PLAYER_NONE;
sGame->playersAttemptingPick[column][1] = PLAYER_NONE;
}
}
}
}
static bool32 TryPickBerry(u8 playerId, u8 pickState, u8 column)
{
s32 pick = 0;
u8 numPlayersIdx = sGame->numPlayers - 1;
struct DodrioGame_Berries * berries = &sGame->player.berries;
switch (pickState)
{
case PICK_LEFT:
default:
pick = 0;
break;
case PICK_MIDDLE:
pick = 1;
break;
case PICK_RIGHT:
pick = 2;
break;
}
// Check if berry is within range to be picked
if (berries->fallDist[column] == EAT_FALL_DIST - 1 || berries->fallDist[column] == EAT_FALL_DIST)
{
// Check if this berry is the one the player is trying to pick
if (column == sDodrioHeadToColumnMap[numPlayersIdx][playerId][pick])
{
// Check if berry has been picked/eaten by another player
if (sGame->berryState[column] == BERRYSTATE_PICKED || sGame->berryState[column] == BERRYSTATE_EATEN)
{
// Missed berry, picked by someone else
sGame->players[playerId].comm.missedBerry = TRUE;
return FALSE;
}
else
{
// Successfully picked berry
return TRUE;
}
}
}
else
{
// Check if this berry is the one the player is trying to pick
if (column == sDodrioHeadToColumnMap[numPlayersIdx][playerId][pick])
{
// Missed berry, out of range
sGame->inputState[playerId] = INPUTSTATE_BAD_MISS;
sGame->players[playerId].comm.missedBerry = TRUE;
}
}
return FALSE;
}
static void UpdateFallingBerries(void)
{
u8 berryStart = sGame->berryColStart;
u8 berryEnd = sGame->berryColEnd;
u8 delayStage = 0;
u8 otherBerryMissed = 0;
u8 i;
sGame->berriesFalling = FALSE;
for (i = berryStart; i < berryEnd - 1; i++)
{
struct DodrioGame *game = sGame;
if (sGame->berryState[i] == BERRYSTATE_NONE || sGame->berryState[i] == BERRYSTATE_PICKED)
{
sGame->berriesFalling = TRUE;
if (game->player.berries.fallDist[i] >= MAX_FALL_DIST)
{
// Berry hit the ground
game->player.berries.fallDist[i] = MAX_FALL_DIST;
sGame->berryState[i] = BERRYSTATE_SQUISHED;
if (!sGame->playingSquishSound[i])
{
sGame->playingSquishSound[i] = TRUE;
PlaySE(SE_BALLOON_RED + game->player.berries.ids[i]);
}
if (sGame->numGraySquares < NUM_STATUS_SQUARES || otherBerryMissed == TRUE)
{
otherBerryMissed = TRUE;
sGame->playingSquishSound[i] = FALSE;
if (sGame->numGraySquares < NUM_STATUS_SQUARES)
sGame->numGraySquares++;
IncrementBerryResult(BERRY_MISSED, i, 0);
UpdateBerriesPickedInRow(FALSE);
}
}
else
{
// Berry is still falling
u8 delay;
delayStage = sGame->difficulty[GetPlayerIdAtColumn(i)] / NUM_DIFFICULTIES;
if (delayStage >= ARRAY_COUNT(sBerryFallDelays) - 1)
delayStage = ARRAY_COUNT(sBerryFallDelays) - 1;
delay = sBerryFallDelays[delayStage][game->player.berries.ids[i]];
if (++sGame->fallTimer[i] >= delay)
{
game->player.berries.fallDist[i]++;
sGame->fallTimer[i] = 0;
}
HandlePickBerries();
}
}
else if (sGame->berryState[i] == BERRYSTATE_EATEN)
{
// Berry has been eaten, wait and create a new berry
sGame->berriesFalling = TRUE;
if (++sGame->newBerryTimer[i] >= 20)
{
sGame->players[sGame->berryEatenBy[i]].comm.ateBerry = FALSE;
sGame->newBerryTimer[i] = 0;
sGame->fallTimer[i] = 0;
sGame->berryState[i] = BERRYSTATE_NONE;
game->player.berries.fallDist[i] = 1;
game->player.berries.ids[i] = GetNewBerryId(GetPlayerIdAtColumn(i), i);
}
}
else if (sGame->berryState[i] == BERRYSTATE_SQUISHED)
{
// Berry has already hit the ground, wait and create a new berry
if (++sGame->newBerryTimer[i] >= 20)
{
if (sGame->numGraySquares < NUM_STATUS_SQUARES)
{
sGame->newBerryTimer[i] = 0;
sGame->fallTimer[i] = 0;
sGame->berryState[i] = BERRYSTATE_NONE;
game->player.berries.fallDist[i] = 1;
sGame->prevBerryIds[i] = game->player.berries.ids[i];
game->player.berries.ids[i] = GetNewBerryId(GetPlayerIdAtColumn(i), i);
}
}
}
}
}
static void UpdateBerrySprites(void)
{
u8 i;
u8 berryStart = sGame->berryColStart;
u8 berryEnd = sGame->berryColEnd;
for (i = berryStart; i < berryEnd; i++)
{
struct DodrioGame_Player *player = &sGame->players[sGame->multiplayerId];
u8 column = sActiveColumnMap[sGame->numPlayers - 1][sGame->multiplayerId][i];
if (player->berries.fallDist[column] != 0)
SetBerryInvisibility(i, FALSE);
else
SetBerryInvisibility(i, TRUE);
if (player->berries.fallDist[column] >= MAX_FALL_DIST)
{
// Berry was missed, set squished anim
SetBerryAnim(i, player->berries.ids[column] + BERRY_MISSED);
SetBerryYPos(i, player->berries.fallDist[column] * 2 - 1);
}
else if (player->berries.ids[column] == 3)
{
// Berry was picked, set eaten anim
player->berries.fallDist[column] = EAT_FALL_DIST;
SetBerryAnim(i, ANIM_EATEN);
SetBerryYPos(i, player->berries.fallDist[column] * 2 - 1);
}
else
{
// Berry is still falling
SetBerryAnim(i, player->berries.ids[column]);
SetBerryYPos(i, player->berries.fallDist[column] * 2);
}
}
}
static void UpdateAllDodrioAnims(void)
{
u8 i, numPlayers;
numPlayers = sGame->numPlayers;
for (i = 0; i < numPlayers; i++)
{
struct DodrioGame_Player *player = &sGame->players[i];
SetDodrioAnim(i, player->comm.pickState);
}
}
static void SetAllDodrioDisabled(void)
{
u8 i, numPlayers;
numPlayers = sGame->numPlayers;
for (i = 0; i < numPlayers; i++)
SetDodrioAnim(i, PICK_DISABLED);
}
static void UpdateGame_Leader(void)
{
UpdateBerrySprites();
if (sGame->numGraySquares >= NUM_STATUS_SQUARES)
SetAllDodrioDisabled();
else
UpdateAllDodrioAnims();
UpdateStatusBarAnim(sGame->numGraySquares);
}
// Identical to UpdateGame_Leader
static void UpdateGame_Member(void)
{
UpdateBerrySprites();
if (sGame->numGraySquares >= NUM_STATUS_SQUARES)
SetAllDodrioDisabled();
else
UpdateAllDodrioAnims();
UpdateStatusBarAnim(sGame->numGraySquares);
}
static void GetActiveBerryColumns(u8 numPlayers, u8 *start, u8 *end)
{
switch (numPlayers)
{
case 1:
*start = 4, *end = 7;
break;
case 2:
*start = 3, *end = 8;
break;
case 3:
*start = 2, *end = 9;
break;
case 4:
*start = 1, *end = 10;
break;
case 5:
*start = 0, *end = 11;
break;
}
}
static bool32 AllPlayersReadyToStart(void)
{
u8 i, numPlayers;
numPlayers = sGame->numPlayers;
for (i = 1; i < numPlayers; i++)
{
if (sGame->readyToStart[i] == FALSE)
sGame->readyToStart[i] = RecvPacket_ReadyToStart(i);
}
numPlayers = numPlayers; // Needed to force compiler to keep loop below
#ifdef BUGFIX
i = 1; // i isn't reset, loop below never runs. As a result, game can begin before all players ready
#endif
for (; i < numPlayers; i++)
{
if (sGame->readyToStart[i] == FALSE)
return FALSE;
}
return TRUE;
}
static void ResetReadyToStart(void)
{
u8 i;
for (i = 0; i < MAX_RFU_PLAYERS; i++)
sGame->readyToStart[i] = FALSE;
}
static bool32 ReadyToEndGame_Leader(void)
{
if (sGame->numGraySquares >= NUM_STATUS_SQUARES && !sGame->berriesFalling)
{
sGame->numGraySquares = NUM_STATUS_SQUARES;
if (sGame->allReadyToEnd)
return TRUE;
}
return FALSE;
}
static bool32 ReadyToEndGame_Member(void)
{
u8 i, berryStart, berryEnd;
if (sGame->numGraySquares >= NUM_STATUS_SQUARES)
{
berryStart = sGame->berryColStart;
berryEnd = sGame->berryColEnd;
sGame->numGraySquares = NUM_STATUS_SQUARES;
if (sGame->allReadyToEnd)
{
for (i = berryStart; i < berryEnd; i++)
{
struct DodrioGame_Player *player = &sGame->players[sGame->multiplayerId];
u8 column = sActiveColumnMap[sGame->numPlayers - 1][sGame->multiplayerId][i];
if (player->berries.fallDist[column] != MAX_FALL_DIST)
return FALSE;
}
return TRUE;
}
}
return FALSE;
}
static void TryIncrementDifficulty(u8 playerId)
{
u8 threshold = sDifficultyThresholds[sGame->difficulty[playerId] % NUM_DIFFICULTIES] + (sGame->difficulty[playerId] / NUM_DIFFICULTIES) * 100;
if (sGame->berriesEaten[playerId] >= threshold)
sGame->difficulty[playerId]++;
}
static u8 GetPlayerIdAtColumn(u8 column)
{
return sPlayerIdAtColumn[sGame->numPlayers - 1][column];
}
// Get a berry id for when a new falling berry is created.
// What type of berry it is depends on the current difficulty
// level of players who can pick berries from that column.
static u8 GetNewBerryId(u8 playerId, u8 column)
{
u8 i, highestDifficulty;
u8 numPlayersIdx = sGame->numPlayers - 1;
u8 leftPlayer = sDodrioNeighborMap[numPlayersIdx][playerId][0];
u8 middlePlayer = sDodrioNeighborMap[numPlayersIdx][playerId][1];
u8 rightPlayer = sDodrioNeighborMap[numPlayersIdx][playerId][2];
for (i = 0; sUnsharedColumns[numPlayersIdx][i] != 0; i++)
{
// If only one player can use this column, just use their difficulty
if (column == sUnsharedColumns[numPlayersIdx][i])
return GetNewBerryIdByDifficulty(sGame->difficulty[middlePlayer], column);
}
// This column is shared, get the highest difficulty of adjacent players
if (sGame->difficulty[leftPlayer] > sGame->difficulty[middlePlayer])
highestDifficulty = sGame->difficulty[leftPlayer];
else
highestDifficulty = sGame->difficulty[middlePlayer];
if (sGame->difficulty[rightPlayer] > highestDifficulty)
highestDifficulty = sGame->difficulty[rightPlayer];
return GetNewBerryIdByDifficulty(highestDifficulty, column);
}
// The berry types cycle through different distributions depending on the difficulty
static u8 GetNewBerryIdByDifficulty(u8 difficulty, u8 column)
{
u8 prevBerryId = sGame->prevBerryIds[column];
switch (difficulty % NUM_DIFFICULTIES)
{
default: return BERRY_BLUE;
case 0: return BERRY_BLUE;
case 1: return BERRY_GREEN;
case 2: return BERRY_GOLD;
case 3:
if (prevBerryId == BERRY_BLUE)
return BERRY_GREEN;
else
return BERRY_BLUE;
case 4:
if (prevBerryId == BERRY_BLUE)
return BERRY_GOLD;
else
return BERRY_BLUE;
case 5:
if (prevBerryId == BERRY_GOLD)
return BERRY_GREEN;
else
return BERRY_GOLD;
case 6:
if (prevBerryId == BERRY_BLUE)
return BERRY_GREEN;
else if (prevBerryId == BERRY_GREEN)
return BERRY_GOLD;
else
return BERRY_BLUE;
}
}
static bool32 IsTotalBerriesMissedOver10(u16 berryResults[MAX_RFU_PLAYERS][NUM_BERRY_IDS])
{
int missed = 0, i = 0;
for (; i < GetLinkPlayerCount(); missed += berryResults[i][BERRY_MISSED], i++)
;
if (missed > 10)
return TRUE;
else
return FALSE;
}
// Despite being set up to take a berry id as an argument, this
// function is only ever given BERRY_BLUE or BERRY_MISSED.
// It reads the actual berry id (if necessary) from ids
static void IncrementBerryResult(u8 berryIdArg, u8 column, u8 playerId)
{
u8 berryId;
u8 numPlayers = sGame->numPlayers;
switch (berryIdArg)
{
case BERRY_BLUE:
case BERRY_GREEN:
case BERRY_GOLD:
berryId = sGame->players[0].berries.ids[column];
sGame->berryResults[playerId][berryId] = IncrementWithLimit(sGame->berryResults[playerId][berryId], 20000);
break;
case BERRY_MISSED:
if (IsTotalBerriesMissedOver10(sGame->berryResults))
break;
switch (numPlayers)
{
case 5:
switch (column)
{
case 0:
sGame->berryResults[2][BERRY_MISSED]++;
sGame->berryResults[3][BERRY_MISSED]++;
break;
case 1:
sGame->berryResults[3][BERRY_MISSED]++;
break;
case 2:
sGame->berryResults[3][BERRY_MISSED]++;
sGame->berryResults[4][BERRY_MISSED]++;
break;
case 3:
sGame->berryResults[4][BERRY_MISSED]++;
break;
case 4:
sGame->berryResults[4][BERRY_MISSED]++;
sGame->berryResults[0][BERRY_MISSED]++;
break;
case 5:
sGame->berryResults[0][BERRY_MISSED]++;
break;
case 6:
sGame->berryResults[0][BERRY_MISSED]++;
sGame->berryResults[1][BERRY_MISSED]++;
break;
case 7:
sGame->berryResults[1][BERRY_MISSED]++;
break;
case 8:
sGame->berryResults[1][BERRY_MISSED]++;
sGame->berryResults[2][BERRY_MISSED]++;
break;
case 9:
sGame->berryResults[2][BERRY_MISSED]++;
break;
}
break;
case 4:
switch (column)
{
case 1:
sGame->berryResults[2][BERRY_MISSED]++;
sGame->berryResults[3][BERRY_MISSED]++;
break;
case 2:
sGame->berryResults[3][BERRY_MISSED]++;
break;
case 3:
sGame->berryResults[3][BERRY_MISSED]++;
sGame->berryResults[0][BERRY_MISSED]++;
break;
case 4:
sGame->berryResults[0][BERRY_MISSED]++;
break;
case 5:
sGame->berryResults[0][BERRY_MISSED]++;
sGame->berryResults[1][BERRY_MISSED]++;
break;
case 6:
sGame->berryResults[1][BERRY_MISSED]++;
break;
case 7:
sGame->berryResults[1][BERRY_MISSED]++;
sGame->berryResults[2][BERRY_MISSED]++;
break;
case 8:
sGame->berryResults[2][BERRY_MISSED]++;
break;
}
break;
case 3:
switch (column)
{
case 2:
sGame->berryResults[1][BERRY_MISSED]++;
sGame->berryResults[2][BERRY_MISSED]++;
break;
case 3:
sGame->berryResults[2][BERRY_MISSED]++;
break;
case 4:
sGame->berryResults[2][BERRY_MISSED]++;
sGame->berryResults[0][BERRY_MISSED]++;
break;
case 5:
sGame->berryResults[0][BERRY_MISSED]++;
break;
case 6:
sGame->berryResults[0][BERRY_MISSED]++;
sGame->berryResults[1][BERRY_MISSED]++;
break;
case 7:
sGame->berryResults[1][BERRY_MISSED]++;
break;
}
break;
case 2:
switch (column)
{
case 3:
sGame->berryResults[0][BERRY_MISSED]++;
sGame->berryResults[1][BERRY_MISSED]++;
break;
case 4:
sGame->berryResults[0][BERRY_MISSED]++;
break;
case 5:
sGame->berryResults[0][BERRY_MISSED]++;
sGame->berryResults[1][BERRY_MISSED]++;
break;
case 6:
sGame->berryResults[1][BERRY_MISSED]++;
break;
}
break;
}
break;
}
}
static void UpdateBerriesPickedInRow(bool32 picked)
{
// The 'berries picked in row' stat is only
// counted for games with all 5 players
if (sGame->numPlayers != MAX_RFU_PLAYERS)
return;
if (picked == TRUE)
{
if (++sGame->berriesPickedInRow > sGame->maxBerriesPickedInRow)
sGame->maxBerriesPickedInRow = sGame->berriesPickedInRow;
if (sGame->berriesPickedInRow > MAX_BERRIES)
sGame->berriesPickedInRow = MAX_BERRIES;
}
else // missed
{
if (sGame->berriesPickedInRow > sGame->maxBerriesPickedInRow)
sGame->maxBerriesPickedInRow = sGame->berriesPickedInRow;
sGame->berriesPickedInRow = 0;
}
}
static void SetMaxBerriesPickedInRow(void)
{
u8 i;
for (i = 0; i < sGame->numPlayers; i++)
sGame->berryResults[i][BERRY_IN_ROW] = sGame->maxBerriesPickedInRow;
}
static void ResetForPlayAgainPrompt(void)
{
u8 i, j;
for (i = 0; i < MAX_RFU_PLAYERS; i++)
{
for (j = 0; j < NUM_BERRY_COLUMNS; j++)
sGame->players[i].berries.fallDist[j] = 0;
sGame->players[i].comm.pickState = PICK_NONE;
sGame->players[i].comm.ateBerry = FALSE;
sGame->difficulty[i] = 0;
sGame->berriesEaten[i] = 0;
sGame->scoreResults[i].ranking = 0;
sGame->scoreResults[i].score = 0;
sGame->berryResults[i][BERRY_BLUE] = 0;
sGame->berryResults[i][BERRY_GREEN] = 0;
sGame->berryResults[i][BERRY_GOLD] = 0;
sGame->berryResults[i][BERRY_MISSED] = 0;
sGame->berryResults[i][BERRY_PRIZE] = 0;
sGame->berryResults[i][BERRY_IN_ROW] = 0;
}
sGame->endSoundState = 0;
sGame->berriesPickedInRow = 0;
sGame->numGraySquares = 0;
UpdateAllDodrioAnims();
UpdateBerrySprites();
}
static const s16 sBerryScoreMultipliers[] = {
[BERRY_BLUE] = 10,
[BERRY_GREEN] = 30,
[BERRY_GOLD] = 50,
[BERRY_MISSED] = 50 // Subtracted
};
static void SetRandomPrize(void)
{
u8 i, prizeSet = 0, prizeIdx = 0;
switch (sGame->numPlayers)
{
case 4: prizeSet = 1; break;
case 5: prizeSet = 2; break;
}
prizeIdx = Random() % ARRAY_COUNT(sPrizeBerryIds[0]);
for (i = 0; i < MAX_RFU_PLAYERS; i++)
sGame->berryResults[i][BERRY_PRIZE] = sPrizeBerryIds[prizeSet][prizeIdx];
}
static u32 GetBerriesPicked(u8 playerId)
{
u32 sum = sGame->berryResults[playerId][BERRY_BLUE]
+ sGame->berryResults[playerId][BERRY_GREEN]
+ sGame->berryResults[playerId][BERRY_GOLD];
return min(sum, MAX_BERRIES);
}
static void TryUpdateRecords(void)
{
u32 berriesPicked = Min(GetBerriesPicked(sGame->multiplayerId), MAX_BERRIES); // Min here is redundant
u32 score = Min(GetScore(sGame->multiplayerId), MAX_SCORE);
if (gSaveBlock2Ptr->berryPick.bestScore < score)
gSaveBlock2Ptr->berryPick.bestScore = score;
if (gSaveBlock2Ptr->berryPick.berriesPicked < berriesPicked)
gSaveBlock2Ptr->berryPick.berriesPicked = berriesPicked;
if (gSaveBlock2Ptr->berryPick.berriesPickedInRow < sGame->maxBerriesPickedInRow)
gSaveBlock2Ptr->berryPick.berriesPickedInRow = sGame->maxBerriesPickedInRow;
}
// Enqueue the given state, and dequeue and return
// the state that should be used next
static u8 UpdatePickStateQueue(u8 pickState)
{
u8 i, nextState;
nextState = sGame->pickStateQueue[ARRAY_COUNT(sGame->pickStateQueue) - 1];
for (i = ARRAY_COUNT(sGame->pickStateQueue) - 1; i != 0; i--)
sGame->pickStateQueue[i] = sGame->pickStateQueue[i - 1];
sGame->pickStateQueue[0] = pickState;
return nextState;
}
// The player may extend their Dodrio's heads while they wait for
// other players to respond to the "Play again?" prompt
static void HandleWaitPlayAgainInput(void)
{
if (sGame->inputDelay[sGame->multiplayerId] == 0)
{
if (JOY_NEW(DPAD_UP))
{
sGame->players[sGame->multiplayerId].comm.pickState = PICK_MIDDLE;
sGame->inputDelay[sGame->multiplayerId] = 6;
PlaySE(SE_M_CHARM);
}
else if (JOY_NEW(DPAD_LEFT))
{
sGame->players[sGame->multiplayerId].comm.pickState = PICK_LEFT;
sGame->inputDelay[sGame->multiplayerId] = 6;
PlaySE(SE_M_CHARM);
}
else if (JOY_NEW(DPAD_RIGHT))
{
sGame->players[sGame->multiplayerId].comm.pickState = PICK_RIGHT;
sGame->inputDelay[sGame->multiplayerId] = 6;
PlaySE(SE_M_CHARM);
}
else
{
sGame->players[sGame->multiplayerId].comm.pickState = PICK_NONE;
}
}
else
{
sGame->inputDelay[sGame->multiplayerId]--;
}
}
static void ResetPickState(void)
{
sGame->players[sGame->multiplayerId].comm.pickState = PICK_NONE;
}
static u16 GetPrizeItemId(void)
{
return sGame->berryResults[sGame->multiplayerId][BERRY_PRIZE] + FIRST_BERRY_INDEX;
}
static u8 GetNumPlayers(void)
{
return sGame->numPlayers;
}
static u8 *GetPlayerName(u8 id)
{
if (gReceivedRemoteLinkPlayers)
return gLinkPlayers[id].name;
else
return sGame->players[id].name;
}
static u16 GetBerryResult(u8 playerId, u8 berryId)
{
return sGame->berryResults[playerId][berryId];
}
static u32 GetScore(u8 playerId)
{
u8 i;
u32 scoreLost, score = 0;
// Sum up points for berries picked
for (i = 0; i < BERRY_MISSED; i++)
score += sGame->berryResults[playerId][i] * sBerryScoreMultipliers[i];
// Get points lost for berries missed
scoreLost = sGame->berryResults[playerId][BERRY_MISSED] * sBerryScoreMultipliers[BERRY_MISSED];
if (score <= scoreLost)
return 0;
else
return score - scoreLost;
}
static u32 GetHighestScore(void)
{
u8 i, numPlayers = sGame->numPlayers;
u32 maxScore = GetScore(0);
for (i = 1; i < numPlayers; i++)
{
u32 score = GetScore(i);
if (score > maxScore)
maxScore = score;
}
return Min(maxScore, MAX_SCORE);
}
static u32 GetHighestBerryResult(u8 berryId)
{
u8 i, numPlayers = sGame->numPlayers;
u16 maxScore = sGame->berryResults[0][berryId];
for (i = 0; i < numPlayers; i++)
{
u16 score = sGame->berryResults[i][berryId];
if (score > maxScore)
maxScore = score;
}
return maxScore;
}
static u32 GetScoreByRanking(u8 ranking)
{
u32 scores[MAX_RFU_PLAYERS], temp;
s16 unsorted = TRUE;
u8 i;
u8 numPlayers = sGame->numPlayers;
for (i = 0; i < numPlayers; i++)
scores[i] = temp = GetScore(i);
// Sort the scores in the array highest to lowest
while (unsorted)
{
unsorted = FALSE;
for (i = 0; i < numPlayers - 1; i++)
{
if (scores[i] < scores[i + 1])
{
SWAP(scores[i], scores[i + 1], temp);
unsorted = TRUE;
}
}
}
return scores[ranking];
}
static u32 SetScoreResults(void)
{
u8 i, ranking = 0, nextRanking = 0, playersRanked = 0;
u8 numPlayers = sGame->numPlayers;
GetHighestScore(); // Useless call
if (GetHighestScore() == 0)
{
// No one scored any points, put everyone in last place with a score of 0.
// Presumably this was supposed to then return, as the assignments in this
// loop are then overwritten by the rest of the function
for (i = 0; i < numPlayers; i++)
{
sGame->scoreResults[i].ranking = MAX_RFU_PLAYERS - 1;
sGame->scoreResults[i].score = 0;
}
}
// Set scores
for (i = 0; i < numPlayers; i++)
sGame->scoreResults[i].score = Min(GetScore(i), MAX_SCORE);
// Set rankings
do
{
u32 score = GetScoreByRanking(ranking);
u8 curRanking = nextRanking;
// Find all players with the score for this ranking.
// Increment nextRanking but not curRanking to allow
// for ties
for (i = 0; i < numPlayers; i++)
{
if (score == sGame->scoreResults[i].score)
{
sGame->scoreResults[i].ranking = curRanking;
nextRanking++;
playersRanked++;
}
}
ranking = nextRanking;
} while (playersRanked < numPlayers);
return 0;
}
static void GetScoreResults(struct DodrioGame_ScoreResults *dst, u8 playerId)
{
*dst = sGame->scoreResults[playerId];
}
// Unused
// Returns where the specified player's score ranks, 0 being first (highest score)
static u8 GetScoreRanking(u8 playerId)
{
u8 i, ranking = 0;
u8 numPlayers = sGame->numPlayers;
u32 playersScore;
u32 scores[MAX_RFU_PLAYERS] = {0};
for (i = 0; i < numPlayers; i++)
scores[i] = GetScore(i);
playersScore = scores[playerId];
for (i = 0; i < MAX_RFU_PLAYERS; i++)
{
if (i != playerId && playersScore < scores[i])
ranking++;
}
return ranking;
}
enum {
PRIZE_RECEIVED,
PRIZE_FILLED_BAG,
PRIZE_NO_ROOM,
NO_PRIZE,
};
static u8 TryGivePrize(void)
{
u8 multiplayerId = sGame->multiplayerId;
u16 itemId = GetPrizeItemId();
if (GetScore(multiplayerId) != GetHighestScore())
return NO_PRIZE;
if (!CheckBagHasSpace(itemId, 1))
return PRIZE_NO_ROOM;
AddBagItem(itemId, 1);
if (!CheckBagHasSpace(itemId, 1))
return PRIZE_FILLED_BAG;
return PRIZE_RECEIVED;
}
static u32 IncrementWithLimit(u32 a, u32 max)
{
if (a < max)
return a + 1;
else
return max;
}
static u32 Min(u32 a, u32 b)
{
if (a < b)
return a;
else
return b;
}
static u8 GetPlayerIdByPos(u8 id)
{
return sGame->posToPlayerId[id];
}
void IsDodrioInParty(void)
{
int i;
for (i = 0; i < PARTY_SIZE; i++)
{
if (GetMonData(&gPlayerParty[i], MON_DATA_SANITY_HAS_SPECIES)
&& GetMonData(&gPlayerParty[i], MON_DATA_SPECIES2) == SPECIES_DODRIO)
{
gSpecialVar_Result = TRUE;
return;
}
}
gSpecialVar_Result = FALSE;
}
#define NUM_RECORD_TYPES 3
void ShowDodrioBerryPickingRecords(void)
{
u8 taskId = CreateTask(Task_ShowDodrioBerryPickingRecords, 0);
Task_ShowDodrioBerryPickingRecords(taskId);
}
static const struct WindowTemplate sWindowTemplates_Records =
{
.bg = 0,
.tilemapLeft = 5,
.tilemapTop = 1,
.width = 20,
.height = 11,
.paletteNum = 15,
.baseBlock = 0x1,
};
static const u8 *const sRecordsTexts[NUM_RECORD_TYPES] = {gText_BerriesPicked, gText_BestScore, gText_BerriesInRowFivePlayers};
static const u8 sRecordNumMaxDigits[NUM_RECORD_TYPES] = {4, 7, 4};
ALIGNED(4)
static const u8 sRecordTextYCoords[NUM_RECORD_TYPES][2] = {{25}, {41}, {57}};
static const u8 sRecordNumYCoords[NUM_RECORD_TYPES][2] = {{25}, {41}, {73}};
#define tState data[0]
#define tWindowId data[1]
static void Task_ShowDodrioBerryPickingRecords(u8 taskId)
{
struct WindowTemplate window;
s32 i, width, widthCurr;
s16 *data = gTasks[taskId].data;
switch (tState)
{
case 0:
window = sWindowTemplates_Records;
width = GetStringWidth(FONT_NORMAL, gText_BerryPickingRecords, 0);
for (i = 0; i < ARRAY_COUNT(sRecordsTexts); i++)
{
widthCurr = GetStringWidth(FONT_NORMAL, sRecordsTexts[i], 0) + 50;
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, 3);
tState++;
break;
case 1:
if (!IsDma3ManagerBusyWithBgCopy())
tState++;
break;
case 2:
if (JOY_NEW(A_BUTTON | B_BUTTON))
{
rbox_fill_rectangle(tWindowId);
CopyWindowToVram(tWindowId, 1);
tState++;
}
break;
case 3:
if (!IsDma3ManagerBusyWithBgCopy())
{
RemoveWindow(tWindowId);
DestroyTask(taskId);
EnableBothScriptContexts();
}
break;
}
}
#undef tState
#undef tWindowId
static void PrintRecordsText(u8 windowId, s32 width)
{
s32 i, x, numWidth;
s32 recordNums[NUM_RECORD_TYPES];
recordNums[0] = gSaveBlock2Ptr->berryPick.berriesPicked;
recordNums[1] = gSaveBlock2Ptr->berryPick.bestScore;
recordNums[2] = gSaveBlock2Ptr->berryPick.berriesPickedInRow;
LoadUserWindowBorderGfx_(windowId, 0x21D, 0xD0);
DrawTextBorderOuter(windowId, 0x21D, 0xD);
FillWindowPixelBuffer(windowId, PIXEL_FILL(1));
AddTextPrinterParameterized(windowId, FONT_NORMAL, gText_BerryPickingRecords, GetStringCenterAlignXOffset(FONT_NORMAL, gText_BerryPickingRecords, width * 8), 1, TEXT_SPEED_FF, NULL);
for (i = 0; i < NUM_RECORD_TYPES; i++)
{
ConvertIntToDecimalStringN(gStringVar1, recordNums[i], STR_CONV_MODE_LEFT_ALIGN, sRecordNumMaxDigits[i]);
numWidth = GetStringWidth(FONT_NORMAL, gStringVar1, -1);
AddTextPrinterParameterized(windowId, FONT_NORMAL, sRecordsTexts[i], 0, sRecordTextYCoords[i][0], TEXT_SPEED_FF, NULL);
x = (width * 8) - numWidth;
AddTextPrinterParameterized(windowId, FONT_NORMAL, gStringVar1, x, sRecordNumYCoords[i][0], TEXT_SPEED_FF, NULL);
}
PutWindowTilemap(windowId);
}
// Debug functions?
static const u16 sDebug_BerryResults[MAX_RFU_PLAYERS][4] =
{
{
[BERRY_BLUE] = MAX_BERRIES,
[BERRY_GREEN] = 0,
[BERRY_GOLD] = 90,
[BERRY_MISSED] = MAX_BERRIES
},
{
[BERRY_BLUE] = MAX_BERRIES,
[BERRY_GREEN] = MAX_BERRIES,
[BERRY_GOLD] = 70,
[BERRY_MISSED] = MAX_BERRIES
},
{
[BERRY_BLUE] = MAX_BERRIES,
[BERRY_GREEN] = 0,
[BERRY_GOLD] = MAX_BERRIES,
[BERRY_MISSED] = 0
},
{
[BERRY_BLUE] = MAX_BERRIES,
[BERRY_GREEN] = MAX_BERRIES,
[BERRY_GOLD] = 60,
[BERRY_MISSED] = 0
},
{
[BERRY_BLUE] = MAX_BERRIES,
[BERRY_GREEN] = MAX_BERRIES,
[BERRY_GOLD] = MAX_BERRIES,
[BERRY_MISSED] = 0
},
};
static const u8 sJPText_Vowels[] = _("あいうえおかき");
static const u8 sText_ABCDEFG[] = _("ABCDEFG");
static const u8 sText_0123456[] = _("0123456");
static const u8 *const sDebug_PlayerNames[] =
{
sJPText_Vowels,
sJPText_Vowels,
sJPText_Vowels,
sText_ABCDEFG,
sText_0123456
};
static void Debug_UpdateNumPlayers(void)
{
sGame->numPlayers = GetLinkPlayerCount();
}
static void Debug_SetPlayerNamesAndResults(void)
{
u8 i, playerId;
for (playerId = sGame->numPlayers; playerId < ARRAY_COUNT(sDebug_PlayerNames); playerId++)
StringCopy(gLinkPlayers[playerId].name, sDebug_PlayerNames[playerId]);
sGame->numPlayers = MAX_RFU_PLAYERS;
for (i = 0; i < NUM_BERRY_TYPES; i++)
{
for (playerId = 0; playerId < sGame->numPlayers; playerId++)
sGame->berryResults[playerId][i] = sDebug_BerryResults[playerId][i];
}
}
struct ReadyToStartPacket
{
u8 id;
bool8 ALIGNED(4) ready;
};
static void SendPacket_ReadyToStart(bool32 ready)
{
struct ReadyToStartPacket packet;
packet.id = PACKET_READY_START;
packet.ready = ready;
Rfu_SendPacket(&packet);
}
static u32 RecvPacket_ReadyToStart(u32 playerId)
{
struct ReadyToStartPacket *packet;
if ((gRecvCmds[0][0] & RFUCMD_MASK) != RFUCMD_SEND_PACKET)
return FALSE;
packet = (void *)&gRecvCmds[playerId][1];
if (packet->id == PACKET_READY_START)
return packet->ready;
return FALSE;
}
struct GameStatePacket
{
u8 id;
u8 fallDist_Col0:4;
u8 fallDist_Col1:4;
u16 fallDist_Col2:4;
u16 fallDist_Col3:4;
u16 fallDist_Col4:4;
u16 fallDist_Col5:4;
u16 fallDist_Col6:4;
u16 fallDist_Col7:4;
u16 fallDist_Col8:4;
u16 fallDist_Col9:4;
u16 berryId_Col0:2;
u16 berryId_Col1:2;
u16 berryId_Col2:2;
u16 berryId_Col3:2;
u16 berryId_Col4:2;
u16 berryId_Col5:2;
u16 berryId_Col6:2;
u16 berryId_Col7:2;
u8 berryId_Col8:2;
u8 berryId_Col9:2;
u8 pickState_Player1:2;
u8 pickState_Player2:2;
u8 pickState_Player3:2;
u8 pickState_Player4:2;
u8 pickState_Player5:2;
bool8 ateBerry_Player1:1;
bool8 ateBerry_Player2:1;
bool8 ateBerry_Player3:1;
bool8 ateBerry_Player4:1;
bool8 ateBerry_Player5:1;
u8 numGraySquares:5;
bool8 allReadyToEnd:1;
bool8 berriesFalling:1;
bool8 missedBerry_Player1:1;
bool8 missedBerry_Player2:1;
bool8 missedBerry_Player3:1;
bool8 missedBerry_Player4:1;
bool8 missedBerry_Player5:1;
};
static void SendPacket_GameState(struct DodrioGame_Player *player,
struct DodrioGame_PlayerCommData *player1,
struct DodrioGame_PlayerCommData *player2,
struct DodrioGame_PlayerCommData *player3,
struct DodrioGame_PlayerCommData *player4,
struct DodrioGame_PlayerCommData *player5,
u8 numGraySquares,
bool32 berriesFalling,
bool32 allReadyToEnd)
{
struct GameStatePacket packet;
struct DodrioGame_Berries *berries = &player->berries;
packet.id = PACKET_GAME_STATE;
packet.fallDist_Col0 = berries->fallDist[0];
packet.fallDist_Col1 = berries->fallDist[1];
packet.fallDist_Col2 = berries->fallDist[2];
packet.fallDist_Col3 = berries->fallDist[3];
packet.fallDist_Col4 = berries->fallDist[4];
packet.fallDist_Col5 = berries->fallDist[5];
packet.fallDist_Col6 = berries->fallDist[6];
packet.fallDist_Col7 = berries->fallDist[7];
packet.fallDist_Col8 = berries->fallDist[8];
packet.fallDist_Col9 = berries->fallDist[9];
packet.berryId_Col0 = berries->ids[0];
packet.berryId_Col1 = berries->ids[1];
packet.berryId_Col2 = berries->ids[2];
packet.berryId_Col3 = berries->ids[3];
packet.berryId_Col4 = berries->ids[4];
packet.berryId_Col5 = berries->ids[5];
packet.berryId_Col6 = berries->ids[6];
packet.berryId_Col7 = berries->ids[7];
packet.berryId_Col8 = berries->ids[8];
packet.berryId_Col9 = berries->ids[9];
packet.pickState_Player1 = player1->pickState;
packet.pickState_Player2 = player2->pickState;
packet.pickState_Player3 = player3->pickState;
packet.pickState_Player4 = player4->pickState;
packet.pickState_Player5 = player5->pickState;
packet.ateBerry_Player1 = player1->ateBerry;
packet.ateBerry_Player2 = player2->ateBerry;
packet.ateBerry_Player3 = player3->ateBerry;
packet.ateBerry_Player4 = player4->ateBerry;
packet.ateBerry_Player5 = player5->ateBerry;
packet.missedBerry_Player1 = player1->missedBerry;
packet.missedBerry_Player2 = player2->missedBerry;
packet.missedBerry_Player3 = player3->missedBerry;
packet.missedBerry_Player4 = player4->missedBerry;
packet.missedBerry_Player5 = player5->missedBerry;
packet.numGraySquares = numGraySquares;
packet.berriesFalling = berriesFalling;
packet.allReadyToEnd = allReadyToEnd;
Rfu_SendPacket(&packet);
}
static bool32 RecvPacket_GameState(u32 playerId,
struct DodrioGame_Player *player,
struct DodrioGame_PlayerCommData *player1,
struct DodrioGame_PlayerCommData *player2,
struct DodrioGame_PlayerCommData *player3,
struct DodrioGame_PlayerCommData *player4,
struct DodrioGame_PlayerCommData *player5,
u8 *numGraySquares,
bool32 *berriesFalling,
bool32 *allReadyToEnd)
{
struct GameStatePacket *packet;
struct DodrioGame_Berries *berries = &player->berries;
if ((gRecvCmds[0][0] & RFUCMD_MASK) != RFUCMD_SEND_PACKET)
return FALSE;
packet = (void *)&gRecvCmds[0][1];
if (packet->id == PACKET_GAME_STATE)
{
berries->fallDist[0] = packet->fallDist_Col0;
berries->fallDist[1] = packet->fallDist_Col1;
berries->fallDist[2] = packet->fallDist_Col2;
berries->fallDist[3] = packet->fallDist_Col3;
berries->fallDist[4] = packet->fallDist_Col4;
berries->fallDist[5] = packet->fallDist_Col5;
berries->fallDist[6] = packet->fallDist_Col6;
berries->fallDist[7] = packet->fallDist_Col7;
berries->fallDist[8] = packet->fallDist_Col8;
berries->fallDist[9] = packet->fallDist_Col9;
berries->fallDist[10] = packet->fallDist_Col0;
berries->ids[0] = packet->berryId_Col0;
berries->ids[1] = packet->berryId_Col1;
berries->ids[2] = packet->berryId_Col2;
berries->ids[3] = packet->berryId_Col3;
berries->ids[4] = packet->berryId_Col4;
berries->ids[5] = packet->berryId_Col5;
berries->ids[6] = packet->berryId_Col6;
berries->ids[7] = packet->berryId_Col7;
berries->ids[8] = packet->berryId_Col8;
berries->ids[9] = packet->berryId_Col9;
berries->ids[10] = packet->berryId_Col0;
player1->pickState = packet->pickState_Player1;
player1->ateBerry = packet->ateBerry_Player1;
player1->missedBerry = packet->missedBerry_Player1;
player2->pickState = packet->pickState_Player2;
player2->ateBerry = packet->ateBerry_Player2;
player2->missedBerry = packet->missedBerry_Player2;
player3->pickState = packet->pickState_Player3;
player3->ateBerry = packet->ateBerry_Player3;
player3->missedBerry = packet->missedBerry_Player3;
player4->pickState = packet->pickState_Player4;
player4->ateBerry = packet->ateBerry_Player4;
player4->missedBerry = packet->missedBerry_Player4;
player5->pickState = packet->pickState_Player5;
player5->ateBerry = packet->ateBerry_Player5;
player5->missedBerry = packet->missedBerry_Player5;
*numGraySquares = packet->numGraySquares;
*berriesFalling = packet->berriesFalling;
*allReadyToEnd = packet->allReadyToEnd;
return TRUE;
}
return FALSE;
}
struct PickStatePacket
{
u8 id;
u8 ALIGNED(4) pickState;
};
static void SendPacket_PickState(u8 pickState)
{
struct PickStatePacket packet;
packet.id = PACKET_PICK_STATE;
packet.pickState = pickState;
Rfu_SendPacket(&packet);
}
static bool32 RecvPacket_PickState(u32 playerId, u8 *pickState)
{
struct PickStatePacket *packet;
if ((gRecvCmds[0][0] & RFUCMD_MASK) != RFUCMD_SEND_PACKET)
return FALSE;
packet = (void *)&gRecvCmds[playerId][1];
if (packet->id == PACKET_PICK_STATE)
{
*pickState = packet->pickState;
return TRUE;
}
return FALSE;
}
struct ReadyToEndPacket
{
u8 id;
bool32 ready;
};
static void SendPacket_ReadyToEnd(bool32 ready)
{
struct ReadyToEndPacket packet;
packet.id = PACKET_READY_END;
packet.ready = ready;
Rfu_SendPacket(&packet);
}
static bool32 RecvPacket_ReadyToEnd(u32 playerId)
{
struct ReadyToEndPacket *packet;
if ((gRecvCmds[0][0] & RFUCMD_MASK) != RFUCMD_SEND_PACKET)
return FALSE;
packet = (void *)&gRecvCmds[playerId][1];
if (packet->id == PACKET_READY_END)
return packet->ready;
return FALSE;
}
static const struct BgTemplate sBgTemplates[] =
{
{
.bg = BG_INTERFACE,
.charBaseIndex = 0,
.mapBaseIndex = 30,
.screenSize = 0,
.paletteMode = 0,
.priority = 0,
.baseTile = 0
},
{
.bg = BG_TREE_LEFT,
.charBaseIndex = 2,
.mapBaseIndex = 12,
.screenSize = 1,
.paletteMode = 0,
.priority = 1,
.baseTile = 0
},
{
.bg = BG_TREE_RIGHT,
.charBaseIndex = 2,
.mapBaseIndex = 14,
.screenSize = 1,
.paletteMode = 0,
.priority = 1,
.baseTile = 0
},
{
.bg = BG_SCENERY,
.charBaseIndex = 3,
.mapBaseIndex = 31,
.screenSize = 0,
.paletteMode = 0,
.priority = 2,
.baseTile = 0
},
};
static const struct WindowTemplate sWindowTemplate_Dummy = DUMMY_WIN_TEMPLATE;
static const struct WindowTemplate sWindowTemplates_Results[] =
{
{
.bg = BG_INTERFACE,
.tilemapLeft = 1,
.tilemapTop = 1,
.width = 28,
.height = 2,
.paletteNum = 13,
.baseBlock = 0x13,
},
{
.bg = BG_INTERFACE,
.tilemapLeft = 1,
.tilemapTop = 5,
.width = 28,
.height = 14,
.paletteNum = 13,
.baseBlock = 0x4B,
}
};
static const struct WindowTemplate sWindowTemplate_Prize =
{
.bg = BG_INTERFACE,
.tilemapLeft = 1,
.tilemapTop = 5,
.width = 28,
.height = 7,
.paletteNum = 13,
.baseBlock = 0x4B,
};
enum {
WIN_PLAY_AGAIN,
WIN_YES_NO,
};
static const struct WindowTemplate sWindowTemplates_PlayAgain[] =
{
[WIN_PLAY_AGAIN] = {
.bg = BG_INTERFACE,
.tilemapLeft = 1,
.tilemapTop = 8,
.width = 19,
.height = 3,
.paletteNum = 13,
.baseBlock = 0x13,
},
[WIN_YES_NO] = {
.bg = BG_INTERFACE,
.tilemapLeft = 22,
.tilemapTop = 7,
.width = 6,
.height = 4,
.paletteNum = 13,
.baseBlock = 0x4C,
}
};
static const struct WindowTemplate sWindowTemplate_DroppedOut =
{
.bg = BG_INTERFACE,
.tilemapLeft = 4,
.tilemapTop = 6,
.width = 22,
.height = 5,
.paletteNum = 13,
.baseBlock = 0x13,
};
static const struct WindowTemplate sWindowTemplate_CommStandby =
{
.bg = BG_INTERFACE,
.tilemapLeft = 5,
.tilemapTop = 8,
.width = 19,
.height = 3,
.paletteNum = 13,
.baseBlock = 0x13,
};
// Unused duplicate of sActiveColumnMap
static const u8 sActiveColumnMap_Duplicate[MAX_RFU_PLAYERS][MAX_RFU_PLAYERS][NUM_BERRY_COLUMNS] =
{
{
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
},
{
{0, 1, 2, 3, 4, 5, 6, 3, 8, 9, 0},
{0, 1, 2, 5, 6, 3, 4, 5, 8, 9, 0},
},
{
{0, 1, 2, 3, 4, 5, 6, 7, 2, 9, 0},
{0, 1, 4, 5, 6, 7, 2, 3, 4, 9, 0},
{0, 1, 6, 7, 2, 3, 4, 5, 6, 9, 0},
},
{
{0, 1, 2, 3, 4, 5, 6, 7, 8, 1, 0},
{0, 3, 4, 5, 6, 7, 8, 1, 2, 3, 0},
{0, 5, 6, 7, 8, 1, 2, 3, 4, 5, 0},
{0, 7, 8, 1, 2, 3, 4, 5, 6, 7, 0},
},
{
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
{2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2},
{4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4},
{6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6},
{8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8},
},
};
// Unused duplicate of sDodrioHeadToColumnMap
static const u8 sDodrioHeadToColumnMap_Duplicate[MAX_RFU_PLAYERS][MAX_RFU_PLAYERS][3] =
{
{
{4, 5, 6},
},
{
{3, 4, 5},
{5, 6, 3},
},
{
{4, 5, 6},
{6, 7, 2},
{2, 3, 4},
},
{
{3, 4, 5},
{5, 6, 7},
{7, 8, 1},
{1, 2, 3},
},
{
{4, 5, 6},
{6, 7, 8},
{8, 9, 0},
{0, 1, 2},
{2, 3, 4},
},
};
// Unused duplicate of sDodrioNeighborMap
static const u8 sDodrioNeighborMap_Duplicate[MAX_RFU_PLAYERS][MAX_RFU_PLAYERS][3] =
{
{
{1, 0, 1},
},
{
{1, 0, 1},
{0, 1, 0},
},
{
{2, 0, 1},
{0, 1, 2},
{1, 2, 0},
},
{
{3, 0, 1},
{0, 1, 2},
{1, 2, 3},
{2, 3, 0},
},
{
{4, 0, 1},
{0, 1, 2},
{1, 2, 3},
{2, 3, 4},
{3, 4, 0},
},
};
// Unused duplicate of sPlayerIdAtColumn
ALIGNED(4)
static const u8 sPlayerIdAtColumn_Duplicate[MAX_RFU_PLAYERS][NUM_BERRY_COLUMNS] =
{
{9, 9, 9, 9, 1, 1, 1, 9, 9, 9, 9},
{9, 9, 9, 0, 0, 1, 1, 0, 9, 9, 9},
{9, 9, 2, 2, 0, 0, 1, 1, 1, 9, 9},
{9, 3, 3, 0, 0, 1, 1, 2, 2, 3, 9},
{3, 3, 4, 4, 0, 0, 1, 1, 2, 2, 3},
};
// Unused duplicate of sUnsharedColumns
static const u8 sUnsharedColumns_Duplicate[MAX_RFU_PLAYERS][MAX_RFU_PLAYERS] =
{
{5},
{4, 6},
{3, 5, 7},
{2, 4, 6, 8},
{1, 3, 5, 6, 9},
};
static const u16 sBg_Pal[] = INCBIN_U16("graphics/dodrio_berry_picking/bg.gbapal",
"graphics/dodrio_berry_picking/tree_border.gbapal");
static const u16 sDodrioNormal_Pal[] = INCBIN_U16("graphics/dodrio_berry_picking/dodrio.gbapal");
static const u16 sDodrioShiny_Pal[] = INCBIN_U16("graphics/dodrio_berry_picking/shiny.gbapal");
static const u16 sStatus_Pal[] = INCBIN_U16("graphics/dodrio_berry_picking/status.gbapal");
static const u16 sBerries_Pal[] = INCBIN_U16("graphics/dodrio_berry_picking/berries.gbapal");
static const u32 sBerries_Gfx[] = INCBIN_U32("graphics/dodrio_berry_picking/berries.4bpp.lz");
static const u16 sCloud_Pal[] = INCBIN_U16("graphics/dodrio_berry_picking/cloud.gbapal");
static const u32 sBg_Gfx[] = INCBIN_U32("graphics/dodrio_berry_picking/bg.4bpp.lz");
static const u32 sTreeBorder_Gfx[] = INCBIN_U32("graphics/dodrio_berry_picking/tree_border.4bpp.lz");
static const u32 sStatus_Gfx[] = INCBIN_U32("graphics/dodrio_berry_picking/status.4bpp.lz");
static const u32 sCloud_Gfx[] = INCBIN_U32("graphics/dodrio_berry_picking/cloud.4bpp.lz");
static const u32 sDodrio_Gfx[] = INCBIN_U32("graphics/dodrio_berry_picking/dodrio.4bpp.lz");
static const u32 sBg_Tilemap[] = INCBIN_U32("graphics/dodrio_berry_picking/bg.bin.lz");
static const u32 sTreeBorderRight_Tilemap[] = INCBIN_U32("graphics/dodrio_berry_picking/tree_border_right.bin.lz");
static const u32 sTreeBorderLeft_Tilemap[] = INCBIN_U32("graphics/dodrio_berry_picking/tree_border_left.bin.lz");
static const struct OamData sOamData_Dodrio =
{
.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
};
// Used by the status bar and the results screen berry icons
static const struct OamData sOamData_16x16_Priority0 =
{
.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 = 0,
.paletteNum = 0,
.affineParam = 0
};
static const struct OamData sOamData_Berry =
{
.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 = 2,
.paletteNum = 0,
.affineParam = 0
};
static const struct OamData sOamData_Cloud =
{
.y = 0,
.affineMode = ST_OAM_AFFINE_OFF,
.objMode = ST_OAM_OBJ_NORMAL,
.mosaic = 0,
.bpp = ST_OAM_4BPP,
.shape = SPRITE_SHAPE(64x32),
.x = 0,
.matrixNum = 0,
.size = SPRITE_SIZE(64x32),
.tileNum = 0,
.priority = 3,
.paletteNum = 0,
.affineParam = 0
};
static const union AnimCmd sAnim_Dodrio_Normal[] =
{
ANIMCMD_FRAME(0, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnim_Dodrio_PickRight[] =
{
ANIMCMD_FRAME(64, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnim_Dodrio_PickMiddle[] =
{
ANIMCMD_FRAME(128, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnim_Dodrio_PickLeft[] =
{
ANIMCMD_FRAME(192, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnim_Dodrio_Down[] =
{
ANIMCMD_FRAME(256, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd *const sAnims_Dodrio[] =
{
[PICK_NONE] = sAnim_Dodrio_Normal,
[PICK_RIGHT] = sAnim_Dodrio_PickRight,
[PICK_MIDDLE] = sAnim_Dodrio_PickMiddle,
[PICK_LEFT] = sAnim_Dodrio_PickLeft,
[PICK_DISABLED] = sAnim_Dodrio_Down,
// There is an unused 6th frame of Dodrio's graphic
};
static const union AnimCmd sAnims_StatusBar_Yellow[] =
{
ANIMCMD_FRAME(0, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnims_StatusBar_Gray[] =
{
ANIMCMD_FRAME(4, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnims_StatusBar_Red[] =
{
ANIMCMD_FRAME(8, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd *const sAnims_StatusBar[] =
{
[STATUS_YELLOW] = sAnims_StatusBar_Yellow,
[STATUS_GRAY] = sAnims_StatusBar_Gray,
[STATUS_RED] = sAnims_StatusBar_Red
};
static const union AnimCmd sAnim_Berry_Blue[] =
{
ANIMCMD_FRAME(0, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnim_Berry_Green[] =
{
ANIMCMD_FRAME(4, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnim_Berry_Gold[] =
{
ANIMCMD_FRAME(8, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnim_Berry_BlueSquished[] =
{
ANIMCMD_FRAME(12, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnim_Berry_GreenSquished[] =
{
ANIMCMD_FRAME(16, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnim_Berry_GoldSquished[] =
{
ANIMCMD_FRAME(20, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnim_Berry_Eaten[] =
{
ANIMCMD_FRAME(24, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnim_Berry_Empty1[] =
{
ANIMCMD_FRAME(28, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd sAnim_Berry_Empty2[] =
{
ANIMCMD_FRAME(32, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd *const sAnims_Berry[] =
{
[BERRY_BLUE] = sAnim_Berry_Blue,
[BERRY_GREEN] = sAnim_Berry_Green,
[BERRY_GOLD] = sAnim_Berry_Gold,
[BERRY_BLUE + BERRY_MISSED] = sAnim_Berry_BlueSquished,
[BERRY_GREEN + BERRY_MISSED] = sAnim_Berry_GreenSquished,
[BERRY_GOLD + BERRY_MISSED] = sAnim_Berry_GoldSquished,
[ANIM_EATEN] = sAnim_Berry_Eaten,
sAnim_Berry_Empty1,
sAnim_Berry_Empty2
};
static const union AnimCmd sAnim_Cloud[] =
{
ANIMCMD_FRAME(0, 20),
ANIMCMD_JUMP(0)
};
static const union AnimCmd *const sAnims_Cloud[] =
{
sAnim_Cloud
};
static void LoadDodrioGfx(void)
{
void *ptr = AllocZeroed(0x3000);
struct SpritePalette normal = {sDodrioNormal_Pal, PALTAG_DODRIO_NORMAL};
struct SpritePalette shiny = {sDodrioShiny_Pal, PALTAG_DODRIO_SHINY};
LZ77UnCompWram(sDodrio_Gfx, ptr);
if (ptr)
{
struct SpriteSheet sheet = {ptr, 0x3000, GFXTAG_DODRIO};
LoadSpriteSheet(&sheet);
Free(ptr);
}
LoadSpritePalette(&normal);
LoadSpritePalette(&shiny);
}
static void CreateDodrioSprite(struct DodrioGame_MonInfo * monInfo, u8 playerId, u8 id, u8 numPlayers)
{
struct SpriteTemplate template =
{
.tileTag = GFXTAG_DODRIO,
.paletteTag = monInfo->isShiny, // PALTAG_DODRIO_NORMAL / PALTAG_DODRIO_SHINY
.oam = &sOamData_Dodrio,
.anims = sAnims_Dodrio,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_Dodrio,
};
sDodrioSpriteIds[id] = AllocZeroed(4);
*sDodrioSpriteIds[id] = CreateSprite(&template, GetDodrioXPos(playerId, numPlayers), 136, 3);
SetDodrioInvisibility(TRUE, id);
}
#define sState data[0]
#define sTimer data[1]
#define sUnused1 data[2]
#define sUnused2 data[3]
#define sUnused3 data[4]
static void SpriteCB_Dodrio(struct Sprite *sprite)
{
switch (sprite->sState)
{
case 0:
break;
case 1:
DoDodrioMissedAnim(sprite);
break;
case 2:
DoDodrioIntroAnim(sprite);
break;
}
}
static void StartDodrioMissedAnim(u8 unused)
{
struct Sprite *sprite = &gSprites[*sDodrioSpriteIds[GetMultiplayerId()]];
sprite->sState = 1;
sprite->sTimer = 0;
sprite->sUnused1 = 0;
sprite->sUnused2 = 0;
sprite->sUnused3 = 0;
}
static void StartDodrioIntroAnim(u8 unused)
{
struct Sprite *sprite = &gSprites[*sDodrioSpriteIds[GetMultiplayerId()]];
sprite->sState = 2;
sprite->sTimer = 0;
sprite->sUnused1 = 0;
sprite->sUnused2 = 0;
sprite->sUnused3 = 0;
}
// Do animation where Dodrio shakes horizontally after reaching for a berry and missing
static u32 DoDodrioMissedAnim(struct Sprite *sprite)
{
s8 x;
u8 state = (++sprite->sTimer / 2) % 4;
if (sprite->sTimer >= 3)
{
switch (state)
{
default:
x = 1;
break;
case 1:
case 2:
x = -1;
break;
}
sprite->x += x;
if (++sprite->sTimer >= 40)
{
sprite->sState = 0;
sprite->x = GetDodrioXPos(0, GetNumPlayers());
}
}
return 0;
}
// Does the intro animation where the player's Dodrio
// cycles through extending each head twice
#define FRAMES_PER_STATE 13
#define NUM_INTRO_PICK_STATES PICK_DISABLED // Cycle through 'Normal' and each head, but exclude the Disabled state
static u32 DoDodrioIntroAnim(struct Sprite *sprite)
{
u8 pickState = (++sprite->sTimer / FRAMES_PER_STATE) % NUM_INTRO_PICK_STATES;
// Play a sound effect at the start of each head extension
if (sprite->sTimer % FRAMES_PER_STATE == 0 && pickState != PICK_NONE)
PlaySE(SE_M_CHARM);
if (sprite->sTimer >= FRAMES_PER_STATE * NUM_INTRO_PICK_STATES * 2)
{
// End animation
sprite->sState = 0;
pickState = PICK_NONE;
}
SetDodrioAnim(GetMultiplayerId(), pickState);
return 0;
}
#undef sState
#undef sTimer
#undef sUnused1
#undef sUnused2
#undef sUnused3
static void FreeDodrioSprites(u8 numPlayers)
{
u8 i;
for (i = 0; i < numPlayers; i++)
{
struct Sprite *sprite = &gSprites[*sDodrioSpriteIds[i]];
if (sprite)
DestroySpriteAndFreeResources(sprite);
#ifdef BUGFIX
FREE_AND_SET_NULL(sDodrioSpriteIds[i]); // Memory should be freed here but is not.
#endif
}
}
static void SetDodrioInvisibility(bool8 invisible, u8 id)
{
gSprites[*sDodrioSpriteIds[id]].invisible = invisible;
}
static void SetAllDodrioInvisibility(bool8 invisible, u8 count)
{
u8 i;
for (i = 0; i < count; i++)
SetDodrioInvisibility(invisible, i);
}
static void SetDodrioAnim(u8 id, u8 pickState)
{
StartSpriteAnim(&gSprites[*sDodrioSpriteIds[id]], pickState);
}
static void SpriteCB_Status(struct Sprite *sprite)
{
}
static void InitStatusBarPos(void)
{
u8 i;
for (i = 0; i < NUM_STATUS_SQUARES; i++)
{
struct Sprite *sprite = &gSprites[sStatusBar->spriteIds[i]];
sprite->x = (i * 16) + 48;
sprite->y = -8 - (i * 8);
sStatusBar->entered[i] = FALSE;
}
}
static void CreateStatusBarSprites(void)
{
u8 i;
void *ptr = AllocZeroed(0x180);
struct SpritePalette pal = {sStatus_Pal, PALTAG_STATUS};
LZ77UnCompWram(sStatus_Gfx, ptr);
// This check should be one line up.
if (ptr)
{
struct SpriteSheet sheet = {ptr, 0x180, GFXTAG_STATUS};
struct SpriteTemplate template =
{
.tileTag = GFXTAG_STATUS,
.paletteTag = PALTAG_STATUS,
.oam = &sOamData_16x16_Priority0,
.anims = sAnims_StatusBar,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_Status,
};
sStatusBar = AllocZeroed(sizeof(*sStatusBar));
LoadSpriteSheet(&sheet);
LoadSpritePalette(&pal);
for (i = 0; i < NUM_STATUS_SQUARES; i++)
sStatusBar->spriteIds[i] = CreateSprite(&template, (i * 16) + 48, -8 - (i * 8), 0);
}
Free(ptr);
}
static void FreeStatusBar(void)
{
u8 i;
for (i = 0; i < NUM_STATUS_SQUARES; i++)
{
struct Sprite *sprite = &gSprites[sStatusBar->spriteIds[i]];
if (sprite)
DestroySpriteAndFreeResources(sprite);
}
FREE_AND_SET_NULL(sStatusBar);
}
// Progress an animation where each square of the
// status bar drops down into view, bounces up,
// then settles into position.
// Returns TRUE if the animation is complete
static bool32 DoStatusBarIntro(void)
{
u8 i;
bool32 animActive = FALSE;
for (i = 0; i < NUM_STATUS_SQUARES; i++)
{
struct Sprite *sprite = &gSprites[sStatusBar->spriteIds[i]];
sStatusBar->yChange[i] = 2;
if (sStatusBar->entered[i] && sprite->y == 8)
continue;
animActive = TRUE;
if (sprite->y == 8)
{
if (sStatusBar->entered[i])
continue;
// Square has entered screen, play click
// sound and reverse direction
sStatusBar->entered[i] = TRUE;
sStatusBar->yChange[i] = -16;
PlaySE(SE_CLICK);
}
sprite->y += sStatusBar->yChange[i];
}
if (animActive)
return FALSE;
else
return TRUE;
}
// The status bar at the top changes color depending on the game performance.
// The squares start out yellow. For every berry missed, a square is colored gray.
// If there are 4 or fewer yellow squares left they also flash red
static void UpdateStatusBarAnim(u8 numEmpty)
{
u8 i;
if (numEmpty > NUM_STATUS_SQUARES)
{
// All squares gray
for (i = 0; i < NUM_STATUS_SQUARES; i++)
StartSpriteAnim(&gSprites[sStatusBar->spriteIds[i]], STATUS_GRAY);
}
else
{
// At least 1 square is yellow
for (i = 0; i < NUM_STATUS_SQUARES - numEmpty; i++)
{
if (numEmpty > 6)
{
// Flash the yellow squares red
// The flash cycles faster the fewer yellow squares remain
sStatusBar->flashTimer += numEmpty - 6;
if (sStatusBar->flashTimer > 30)
sStatusBar->flashTimer = 0;
else if (sStatusBar->flashTimer > 10)
StartSpriteAnim(&gSprites[sStatusBar->spriteIds[i]], STATUS_RED);
else
StartSpriteAnim(&gSprites[sStatusBar->spriteIds[i]], STATUS_YELLOW);
}
else
{
// Set yellow squares, no flash
StartSpriteAnim(&gSprites[sStatusBar->spriteIds[i]], STATUS_YELLOW);
}
}
// Set remaining squares gray
for (; i < NUM_STATUS_SQUARES; i++)
StartSpriteAnim(&gSprites[sStatusBar->spriteIds[i]], STATUS_GRAY);
}
}
static void SetStatusBarInvisibility(bool8 invisible)
{
u8 i;
for (i = 0; i < NUM_STATUS_SQUARES; i++)
gSprites[sStatusBar->spriteIds[i]].invisible = invisible;
}
static const u8 sUnusedSounds[] = {
SE_M_CHARM,
SE_NOTE_C,
SE_NOTE_D,
SE_NOTE_E,
SE_NOTE_F,
SE_NOTE_G,
SE_NOTE_A,
SE_NOTE_B,
SE_NOTE_C_HIGH,
SE_RG_CARD_OPEN
};
static void LoadBerryGfx(void)
{
void *ptr = AllocZeroed(0x480);
struct SpritePalette pal = {sBerries_Pal, PALTAG_BERRIES};
LZ77UnCompWram(sBerries_Gfx, ptr);
if (ptr)
{
struct SpriteSheet sheet = {ptr, 0x480, GFXTAG_BERRIES};
LoadSpriteSheet(&sheet);
}
LoadSpritePalette(&pal);
Free(ptr);
}
static const s16 sBerryIconXCoords[] = {88, 128, 168, 208};
static void CreateBerrySprites(void)
{
u8 i;
s16 x;
struct SpriteTemplate berry =
{
.tileTag = GFXTAG_BERRIES,
.paletteTag = PALTAG_BERRIES,
.oam = &sOamData_Berry,
.anims = sAnims_Berry,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCallbackDummy,
};
struct SpriteTemplate berryIcon =
{
.tileTag = GFXTAG_BERRIES,
.paletteTag = PALTAG_BERRIES,
.oam = &sOamData_16x16_Priority0,
.anims = sAnims_Berry,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCallbackDummy,
};
// Create berry sprites that fall during gameplay
for (i = 0; i < NUM_BERRY_COLUMNS; i++)
{
sBerrySpriteIds[i] = AllocZeroed(4);
x = i * 16;
*sBerrySpriteIds[i] = CreateSprite(&berry, x + (i * 8), 8, 1);
SetBerryInvisibility(i, TRUE);
}
// Create berry icon sprites for results screen
for (i = 0; i < NUM_BERRY_TYPES; i++)
{
sBerryIconSpriteIds[i] = AllocZeroed(4);
if (i == BERRY_MISSED)
*sBerryIconSpriteIds[i] = CreateSprite(&berryIcon, sBerryIconXCoords[i], 49, 0);
else
*sBerryIconSpriteIds[i] = CreateSprite(&berryIcon, sBerryIconXCoords[i], 52, 0);
StartSpriteAnim(&gSprites[*sBerryIconSpriteIds[i]], i);
}
SetBerryIconsInvisibility(TRUE);
}
static void FreeBerrySprites(void)
{
struct Sprite *sprite;
u8 i;
for (i = 0; i < NUM_BERRY_COLUMNS; i++)
{
sprite = &gSprites[*sBerrySpriteIds[i]];
if (sprite)
DestroySprite(sprite);
FREE_AND_SET_NULL(sBerrySpriteIds[i]);
}
for (i = 0; i < NUM_BERRY_TYPES; i++)
{
sprite = &gSprites[*sBerryIconSpriteIds[i]];
if (sprite)
DestroySprite(sprite);
FREE_AND_SET_NULL(sBerryIconSpriteIds[i]);
}
}
static void SetBerryInvisibility(u8 id, bool8 invisible)
{
gSprites[*sBerrySpriteIds[id]].invisible = invisible;
}
static void SetBerryIconsInvisibility(bool8 invisible)
{
u8 i;
for (i = 0; i < NUM_BERRY_TYPES; i++)
gSprites[*sBerryIconSpriteIds[i]].invisible = invisible;
}
static void SetBerryYPos(u8 id, u8 y)
{
gSprites[*sBerrySpriteIds[id]].y = y * 8;
}
static void SetBerryAnim(u16 id, u8 animNum)
{
StartSpriteAnim(&gSprites[*sBerrySpriteIds[id]], animNum);
}
// Unused
static void UnusedSetSpritePos(u8 spriteId)
{
gSprites[spriteId].x = 20 * spriteId + 50;
gSprites[spriteId].y = 50;
}
// Gamefreak made a mistake there and goes out of bounds for the data array as it holds 8 elements
// in turn overwriting sprite's subpriority and subsprites fields.
#ifdef UBFIX
#define sFrozen data[1]
#else
#define sFrozen data[10]
#endif // UBFIX
static void SpriteCB_Cloud(struct Sprite *sprite)
{
u8 i;
static const u8 moveDelays[] = {30, 20};
if (sprite->sFrozen != TRUE)
{
for (i = 0; i < NUM_CLOUDS; i++)
{
if (++sCloudSpriteIds[i][1] > moveDelays[i])
{
sprite->x--;
sCloudSpriteIds[i][1] = 0;
}
}
}
}
static const s16 sCloudStartCoords[NUM_CLOUDS][2] =
{
{230, 55},
{ 30, 74}
};
static void CreateCloudSprites(void)
{
u8 i;
void *ptr = AllocZeroed(0x400);
struct SpritePalette pal = {sCloud_Pal, PALTAG_CLOUD};
LZ77UnCompWram(sCloud_Gfx, ptr);
if (ptr)
{
struct SpriteSheet sheet = {ptr, 0x400, GFXTAG_CLOUD};
struct SpriteTemplate template =
{
.tileTag = GFXTAG_CLOUD,
.paletteTag = PALTAG_CLOUD,
.oam = &sOamData_Cloud,
.anims = sAnims_Cloud,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_Cloud,
};
LoadSpriteSheet(&sheet);
LoadSpritePalette(&pal);
for (i = 0; i < NUM_CLOUDS; i++)
{
sCloudSpriteIds[i] = AllocZeroed(4);
*sCloudSpriteIds[i] = CreateSprite(&template, sCloudStartCoords[i][0], sCloudStartCoords[i][1], 4);
}
}
Free(ptr);
}
static void ResetCloudPos(void)
{
u8 i;
for (i = 0; i < NUM_CLOUDS; i++)
{
struct Sprite *sprite = &gSprites[*sCloudSpriteIds[i]];
sprite->sFrozen = TRUE;
sprite->x = sCloudStartCoords[i][0];
sprite->y = sCloudStartCoords[i][1];
}
}
static void StartCloudMovement(void)
{
u8 i;
for (i = 0; i < NUM_CLOUDS; i++)
{
struct Sprite *sprite = &gSprites[*sCloudSpriteIds[i]];
sprite->sFrozen = FALSE;
}
}
static void FreeCloudSprites(void)
{
u8 i;
for (i = 0; i < NUM_CLOUDS; i++)
{
struct Sprite *sprite = &gSprites[*sCloudSpriteIds[i]];
if (sprite)
DestroySprite(sprite);
FREE_AND_SET_NULL(sCloudSpriteIds[i]);
}
}
static void SetCloudInvisibility(bool8 invisible)
{
u8 i;
for (i = 0; i < NUM_CLOUDS; i++)
gSprites[*sCloudSpriteIds[i]].invisible = invisible;
}
#undef sFrozen
static s16 GetDodrioXPos(u8 playerId, u8 numPlayers)
{
s16 x = 0;
switch (numPlayers)
{
case 1:
x = 15;
break;
case 2:
switch (playerId)
{
case 0: x = 12; break;
case 1: x = 18; break;
}
break;
case 3:
switch (playerId)
{
case 0: x = 15; break;
case 1: x = 21; break;
case 2: x = 9; break;
}
break;
case 4:
switch (playerId)
{
case 0: x = 12; break;
case 1: x = 18; break;
case 2: x = 24; break;
case 3: x = 6; break;
}
break;
case 5:
switch (playerId)
{
case 0: x = 15; break;
case 1: x = 21; break;
case 2: x = 27; break;
case 3: x = 3; break;
case 4: x = 9; break;
}
break;
}
return x * 8;
}
static void ResetBerryAndStatusBarSprites(void)
{
u8 i;
for (i = 0; i < NUM_BERRY_COLUMNS; i++)
{
SetBerryInvisibility(i, TRUE);
SetBerryYPos(i, 1);
}
SetStatusBarInvisibility(FALSE);
}
static void LoadWindowFrameGfx(u8 frameId)
{
LoadBgTiles(BG_INTERFACE, GetWindowFrameTilesPal(frameId)->tiles, 0x120, 1);
LoadPalette(GetWindowFrameTilesPal(frameId)->pal, 0xA0, 0x20);
}
static void LoadUserWindowFrameGfx(void)
{
LoadUserWindowBorderGfx_(0, 0xA, 0xB0);
}
static void ResetGfxState(void)
{
sGfx->finished = FALSE;
sGfx->state = 0;
sGfx->loadState = 0;
sGfx->cursorSelection = 0;
sGfx->playAgainState = PLAY_AGAIN_NONE;
}
static void DrawYesNoMessageWindow(const struct WindowTemplate *template)
{
u8 pal = 10;
FillBgTilemapBufferRect(BG_INTERFACE, 1, template->tilemapLeft - 1, template->tilemapTop - 1, 1, 1, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 2, template->tilemapLeft, template->tilemapTop - 1, template->width, 1, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 3, template->tilemapLeft + template->width, template->tilemapTop - 1, 1, 1, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 4, template->tilemapLeft - 1, template->tilemapTop, 1, template->height, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 6, template->tilemapLeft + template->width, template->tilemapTop, 1, template->height, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 7, template->tilemapLeft - 1, template->tilemapTop + template->height, 1, 1, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 8, template->tilemapLeft, template->tilemapTop + template->height, template->width, 1, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 9, template->tilemapLeft + template->width, template->tilemapTop + template->height, 1, 1, pal);
}
static void DrawMessageWindow(const struct WindowTemplate *template)
{
u8 pal = 11;
FillBgTilemapBufferRect(BG_INTERFACE, 10, template->tilemapLeft - 1, template->tilemapTop - 1, 1, 1, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 11, template->tilemapLeft, template->tilemapTop - 1, template->width, 1, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 12, template->tilemapLeft + template->width, template->tilemapTop - 1, 1, 1, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 13, template->tilemapLeft - 1, template->tilemapTop, 1, template->height, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 15, template->tilemapLeft + template->width, template->tilemapTop, 1, template->height, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 16, template->tilemapLeft - 1, template->tilemapTop + template->height, 1, 1, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 17, template->tilemapLeft, template->tilemapTop + template->height, template->width, 1, pal);
FillBgTilemapBufferRect(BG_INTERFACE, 18, template->tilemapLeft + template->width, template->tilemapTop + template->height, 1, 1, pal);
}
static void InitGameGfx(struct DodrioGame_Gfx *ptr)
{
sGfx = ptr;
sGfx->finished = FALSE;
sGfx->state = 0;
sGfx->loadState = 0;
sGfx->cursorSelection = 0;
sGfx->playAgainState = PLAY_AGAIN_NONE;
sGfx->taskId = CreateTask(Task_TryRunGfxFunc, 3);
SetGfxFunc(LoadGfx);
}
// Unused
static void FreeAllWindowBuffers_(void)
{
FreeAllWindowBuffers();
}
// Data used by functions below.
struct WinCoords
{
u8 left;
u8 top;
};
enum {
COLORID_GRAY,
COLORID_RED,
COLORID_BLUE,
COLORID_GREEN, // Unused
};
static const u8 sTextColorTable[][3] =
{
[COLORID_GRAY] = {TEXT_COLOR_WHITE, TEXT_COLOR_DARK_GRAY, TEXT_COLOR_LIGHT_GRAY},
[COLORID_RED] = {TEXT_COLOR_WHITE, TEXT_COLOR_RED, TEXT_COLOR_LIGHT_RED},
[COLORID_BLUE] = {TEXT_COLOR_WHITE, TEXT_COLOR_BLUE, TEXT_COLOR_LIGHT_BLUE},
[COLORID_GREEN] = {TEXT_COLOR_WHITE, TEXT_COLOR_GREEN, TEXT_COLOR_LIGHT_GREEN},
};
static const struct WinCoords sNameWindowCoords_1Player[] = {{12, 6}};
static const struct WinCoords sNameWindowCoords_2Players[] = {{9, 10}, {15, 6}};
static const struct WinCoords sNameWindowCoords_3Players[] = {{12, 6}, {18, 10}, {6, 10}};
static const struct WinCoords sNameWindowCoords_4Players[] = {{9, 10}, {15, 6}, {21, 10}, {3, 6}};
static const struct WinCoords sNameWindowCoords_5Players[] = {{12, 6}, {18, 10}, {23, 6}, {1, 6}, {6, 10}};
static const struct WinCoords *const sNameWindowCoords[MAX_RFU_PLAYERS] =
{
sNameWindowCoords_1Player,
sNameWindowCoords_2Players,
sNameWindowCoords_3Players,
sNameWindowCoords_4Players,
sNameWindowCoords_5Players,
};
static const u8 *const sRankingTexts[MAX_RFU_PLAYERS] =
{
gText_1Colon,
gText_2Colon,
gText_3Colon,
gText_4Colon,
gText_5Colon,
};
static const u16 sResultsXCoords[] = {92, 132, 172, 212};
static const u16 sResultsYCoords[] = {33, 49, 65, 81, 97};
static const u16 sRankingYCoords[] = {17, 33, 49, 65, 81};
struct
{
u8 id;
void (*func)(void);
} const sGfxFuncs[] =
{
{GFXFUNC_LOAD, LoadGfx}, // Element not used, LoadGfx is passed directly to SetGfxFunc
{GFXFUNC_SHOW_NAMES, ShowNames},
{GFXFUNC_SHOW_RESULTS, ShowResults},
{GFXFUNC_MSG_PLAY_AGAIN, Msg_WantToPlayAgain},
{GFXFUNC_MSG_SAVING, Msg_SavingDontTurnOff},
{GFXFUNC_MSG_COMM_STANDBY, Msg_CommunicationStandby},
{GFXFUNC_ERASE_MSG, EraseMessage},
{GFXFUNC_MSG_PLAYER_DROPPED, Msg_SomeoneDroppedOut},
{GFXFUNC_STOP, StopGfxFuncs},
{GFXFUNC_IDLE, GfxIdle},
};
static void SetGfxFuncById(u8 funcId)
{
u8 i;
for (i = 0; i < ARRAY_COUNT(sGfxFuncs); i++)
{
if (sGfxFuncs[i].id == funcId)
SetGfxFunc(sGfxFuncs[i].func);
}
}
static void Task_TryRunGfxFunc(u8 taskId)
{
// Continue calling function until it
// has reached its finished state.
// Another will not be called until
// readied by SetGfxFunc
if (!sGfx->finished)
GetGfxFunc()();
}
static void LoadGfx(void)
{
switch (sGfx->state)
{
case 0:
InitBgs();
sGfx->state++;
break;
case 1:
if (LoadBgGfx() == TRUE)
sGfx->state++;
break;
case 2:
CopyToBgTilemapBuffer(BG_SCENERY, sBg_Tilemap, 0, 0);
CopyToBgTilemapBuffer(BG_TREE_LEFT, sTreeBorderLeft_Tilemap, 0, 0);
CopyToBgTilemapBuffer(BG_TREE_RIGHT, sTreeBorderRight_Tilemap, 0, 0);
CopyBgTilemapBufferToVram(BG_SCENERY);
CopyBgTilemapBufferToVram(BG_TREE_LEFT);
CopyBgTilemapBufferToVram(BG_TREE_RIGHT);
sGfx->state++;
break;
case 3:
ShowBg(BG_INTERFACE);
ShowBg(BG_SCENERY);
ShowBg(BG_TREE_LEFT);
ShowBg(BG_TREE_RIGHT);
sGfx->state++;
break;
case 4:
LoadWindowFrameGfx(gSaveBlock2Ptr->optionsWindowFrameType);
LoadUserWindowFrameGfx();
sGfx->state++;
break;
default:
sGfx->finished = TRUE;
break;
}
}
static void ShowNames(void)
{
u8 i, numPlayers, playerId, colorsId, *name;
u32 left;
struct WindowTemplate window;
const struct WinCoords *coords;
switch (sGfx->state)
{
case 0:
numPlayers = GetNumPlayers();
coords = sNameWindowCoords[numPlayers - 1];
window.bg = BG_INTERFACE;
window.width = 7;
window.height = 2;
window.paletteNum = 13;
window.baseBlock = 0x13;
for (i = 0; i < numPlayers; coords++, i++)
{
colorsId = COLORID_GRAY;
playerId = GetPlayerIdByPos(i);
left = (56 - GetStringWidth(FONT_NORMAL, GetPlayerName(playerId), -1)) / 2u;
window.tilemapLeft = coords->left;
window.tilemapTop = coords->top;
sGfx->windowIds[i] = AddWindow(&window);
ClearWindowTilemap(sGfx->windowIds[i]);
FillWindowPixelBuffer(sGfx->windowIds[i], PIXEL_FILL(1));
if (playerId == GetMultiplayerId())
colorsId = COLORID_BLUE;
name = GetPlayerName(playerId);
AddTextPrinterParameterized3(sGfx->windowIds[i], FONT_NORMAL, left, 1, sTextColorTable[colorsId], -1, name);
CopyWindowToVram(sGfx->windowIds[i], 2);
window.baseBlock += 0xE;
DrawMessageWindow(&window);
}
sGfx->state++;
break;
case 1:
if (!IsDma3ManagerBusyWithBgCopy())
{
numPlayers = GetNumPlayers();
for (i = 0; i < numPlayers; i++)
PutWindowTilemap(sGfx->windowIds[i]);
CopyBgTilemapBufferToVram(BG_INTERFACE);
sGfx->state++;
}
break;
default:
if (++sGfx->state > 180)
{
numPlayers = GetNumPlayers();
for (i = 0; i < numPlayers; i++)
{
ClearWindowTilemap(sGfx->windowIds[i]);
RemoveWindow(sGfx->windowIds[i]);
}
FillBgTilemapBufferRect_Palette0(BG_INTERFACE, 0, 0, 0, 30, 20);
CopyBgTilemapBufferToVram(BG_INTERFACE);
sGfx->finished = TRUE;
}
break;
}
}
static void PrintRankedScores(u8 numPlayers_)
{
u8 i, ranking = 0, rankedPlayers = 0;
u8 numPlayers = numPlayers_; // Needed to match
u8 *name;
u32 x, numWidth;
u8 numString[32];
u8 playersByRanking[MAX_RFU_PLAYERS] = {0, 1, 2, 3, 4};
struct DodrioGame_ScoreResults temp, scoreResults[MAX_RFU_PLAYERS];
// Get all players scores and rankings
for (i = 0; i < numPlayers; i++)
{
playersByRanking[i] = i;
GetScoreResults(&temp, i);
scoreResults[i] = temp;
}
// Sort player ids by ranking
if (GetHighestScore() != 0)
{
do
{
for (i = 0; i < numPlayers; i++)
{
if (scoreResults[i].ranking == ranking)
{
playersByRanking[rankedPlayers] = i;
rankedPlayers++;
}
}
ranking = rankedPlayers;
} while (rankedPlayers < numPlayers);
}
// Put any player with a score of 0 at lowest ranking
for (i = 0; i < numPlayers; i++)
{
if (scoreResults[i].score == 0)
scoreResults[i].ranking = numPlayers - 1;
}
// Print text
x = 216 - GetStringWidth(FONT_NORMAL, gText_SpacePoints, 0);
for (i = 0; i < numPlayers; i++)
{
u8 colorsId = COLORID_GRAY;
u8 playerId = playersByRanking[i];
u32 points = scoreResults[playerId].score;
AddTextPrinterParameterized(sGfx->windowIds[1], FONT_NORMAL, sRankingTexts[scoreResults[playerId].ranking], 8, sRankingYCoords[i], -1, NULL);
if (playerId == GetMultiplayerId())
colorsId = COLORID_BLUE;
name = GetPlayerName(playerId);
AddTextPrinterParameterized3(sGfx->windowIds[1], FONT_NORMAL, 28, sRankingYCoords[i], sTextColorTable[colorsId], -1, name);
ConvertIntToDecimalStringN(numString, points, STR_CONV_MODE_LEFT_ALIGN, 7);
numWidth = GetStringWidth(FONT_NORMAL, numString, -1);
AddTextPrinterParameterized(sGfx->windowIds[1], FONT_NORMAL, numString, x - numWidth, sRankingYCoords[i], -1, NULL);
AddTextPrinterParameterized(sGfx->windowIds[1], FONT_NORMAL, gText_SpacePoints, x, sRankingYCoords[i], -1, NULL);
}
}
static void ShowResults(void)
{
u8 i, j, prizeState, numPlayers = GetNumPlayers();
u8 *name;
u32 strWidth, x;
switch (sGfx->state)
{
case 0:
SetScoreResults();
sGfx->timer = 0;
sGfx->state++;
break;
case 1:
sGfx->windowIds[0] = AddWindow(&sWindowTemplates_Results[0]);
sGfx->windowIds[1] = AddWindow(&sWindowTemplates_Results[1]);
ClearWindowTilemap(sGfx->windowIds[0]);
ClearWindowTilemap(sGfx->windowIds[1]);
DrawMessageWindow(&sWindowTemplates_Results[0]);
DrawMessageWindow(&sWindowTemplates_Results[1]);
sGfx->state++;
break;
case 2:
FillWindowPixelBuffer(sGfx->windowIds[0], PIXEL_FILL(1));
FillWindowPixelBuffer(sGfx->windowIds[1], PIXEL_FILL(1));
strWidth = GetStringWidth(FONT_NORMAL, gText_BerryPickingResults, -1);
x = (224 - strWidth) / 2;
AddTextPrinterParameterized(sGfx->windowIds[0], FONT_NORMAL, gText_BerryPickingResults, x, 1, -1, NULL);
AddTextPrinterParameterized(sGfx->windowIds[1], FONT_NORMAL, gText_10P30P50P50P, 68, 17, -1, NULL);
for (i = 0; i < numPlayers; i++)
{
u8 colorsId = COLORID_GRAY;
if (i == GetMultiplayerId())
colorsId = COLORID_BLUE;
name = GetPlayerName(i);
AddTextPrinterParameterized3(sGfx->windowIds[1], FONT_NORMAL, 0, sResultsYCoords[i], sTextColorTable[colorsId], -1, name);
for (j = 0; j < 4; j++)
{
u32 width;
u16 berriesPicked = Min(GetBerryResult(i, j), MAX_BERRIES);
u16 maxBerriesPicked = Min(GetHighestBerryResult(j), MAX_BERRIES);
ConvertIntToDecimalStringN(gStringVar4, berriesPicked, STR_CONV_MODE_LEFT_ALIGN, 4);
width = GetStringWidth(FONT_NORMAL, gStringVar4, -1);
// If player got the most of a berry type, highlight their number in red
if (maxBerriesPicked == berriesPicked && maxBerriesPicked != 0)
AddTextPrinterParameterized3(sGfx->windowIds[1], FONT_NORMAL, sResultsXCoords[j] - width, sResultsYCoords[i], sTextColorTable[COLORID_RED], -1, gStringVar4);
else
AddTextPrinterParameterized(sGfx->windowIds[1], FONT_NORMAL, gStringVar4, sResultsXCoords[j] - width, sResultsYCoords[i], -1, NULL);
}
}
CopyWindowToVram(sGfx->windowIds[0], 2);
CopyWindowToVram(sGfx->windowIds[1], 2);
sGfx->state++;
break;
case 3:
if (!IsDma3ManagerBusyWithBgCopy())
{
PutWindowTilemap(sGfx->windowIds[0]);
PutWindowTilemap(sGfx->windowIds[1]);
}
CopyBgTilemapBufferToVram(BG_INTERFACE);
SetBerryIconsInvisibility(FALSE);
sGfx->state++;
break;
case 4:
if (++sGfx->timer >= 30 && JOY_NEW(A_BUTTON))
{
sGfx->timer = 0;
PlaySE(SE_SELECT);
SetBerryIconsInvisibility(TRUE);
sGfx->state++;
}
break;
case 5:
FillWindowPixelBuffer(sGfx->windowIds[0], PIXEL_FILL(1));
FillWindowPixelBuffer(sGfx->windowIds[1], PIXEL_FILL(1));
strWidth = GetStringWidth(FONT_NORMAL, gText_AnnouncingRankings, -1);
x = (224 - strWidth) / 2;
AddTextPrinterParameterized(sGfx->windowIds[0], FONT_NORMAL, gText_AnnouncingRankings, x, 1, -1, NULL);
sGfx->state++;
break;
case 6:
PrintRankedScores(numPlayers);
CopyWindowToVram(sGfx->windowIds[0], 2);
CopyWindowToVram(sGfx->windowIds[1], 2);
sGfx->state++;
break;
case 7:
if (!IsDma3ManagerBusyWithBgCopy())
{
PutWindowTilemap(sGfx->windowIds[0]);
PutWindowTilemap(sGfx->windowIds[1]);
}
CopyBgTilemapBufferToVram(BG_INTERFACE);
sGfx->state++;
break;
case 8:
if (++sGfx->timer >= 30 && JOY_NEW(A_BUTTON))
{
sGfx->timer = 0;
PlaySE(SE_SELECT);
if (GetHighestScore() < PRIZE_SCORE)
{
sGfx->state = 127; // Skip to end, past giving prize
}
else
{
StopMapMusic();
sGfx->state++;
}
FillBgTilemapBufferRect_Palette0(BG_INTERFACE, 0, 0, 5, 30, 15);
RemoveWindow(sGfx->windowIds[1]);
sGfx->windowIds[1] = AddWindow(&sWindowTemplate_Prize);
ClearWindowTilemap(sGfx->windowIds[1]);
DrawMessageWindow(&sWindowTemplate_Prize);
}
break;
case 9:
PlayNewMapMusic(MUS_LEVEL_UP);
FillWindowPixelBuffer(sGfx->windowIds[0], PIXEL_FILL(1));
FillWindowPixelBuffer(sGfx->windowIds[1], PIXEL_FILL(1));
strWidth = GetStringWidth(FONT_NORMAL, gText_AnnouncingPrizes, -1);
x = (224 - strWidth) / 2;
AddTextPrinterParameterized(sGfx->windowIds[0], FONT_NORMAL, gText_AnnouncingPrizes, x, 1, -1, NULL);
DynamicPlaceholderTextUtil_Reset();
CopyItemName(GetPrizeItemId(), gStringVar1);
DynamicPlaceholderTextUtil_SetPlaceholderPtr(0, gStringVar1);
DynamicPlaceholderTextUtil_ExpandPlaceholders(gStringVar4, gText_FirstPlacePrize);
AddTextPrinterParameterized(sGfx->windowIds[1], FONT_NORMAL, gStringVar4, 0, 1, -1, NULL);
prizeState = TryGivePrize();
if (prizeState != PRIZE_RECEIVED && prizeState != NO_PRIZE)
{
DynamicPlaceholderTextUtil_Reset();
CopyItemName(GetPrizeItemId(), gStringVar1);
DynamicPlaceholderTextUtil_SetPlaceholderPtr(0, gStringVar1);
if (prizeState == PRIZE_NO_ROOM)
DynamicPlaceholderTextUtil_ExpandPlaceholders(gStringVar4, gText_CantHoldAnyMore);
else if (prizeState == PRIZE_FILLED_BAG)
DynamicPlaceholderTextUtil_ExpandPlaceholders(gStringVar4, gText_FilledStorageSpace);
AddTextPrinterParameterized(sGfx->windowIds[1], FONT_NORMAL, gStringVar4, 0, 41, -1, NULL);
}
CopyWindowToVram(sGfx->windowIds[0], 2);
CopyWindowToVram(sGfx->windowIds[1], 2);
sGfx->state++;
break;
case 10:
if (!IsDma3ManagerBusyWithBgCopy())
{
PutWindowTilemap(sGfx->windowIds[0]);
PutWindowTilemap(sGfx->windowIds[1]);
}
CopyBgTilemapBufferToVram(BG_INTERFACE);
FadeOutAndFadeInNewMapMusic(MUS_RG_VICTORY_WILD, 20, 10);
sGfx->state++;
break;
case 11:
if (++sGfx->timer >= 30 && JOY_NEW(A_BUTTON))
{
sGfx->timer = 0;
PlaySE(SE_SELECT);
sGfx->state++;
}
break;
default:
ClearWindowTilemap(sGfx->windowIds[0]);
ClearWindowTilemap(sGfx->windowIds[1]);
RemoveWindow(sGfx->windowIds[0]);
RemoveWindow(sGfx->windowIds[1]);
FillBgTilemapBufferRect_Palette0(BG_INTERFACE, 0, 0, 0, 30, 20);
CopyBgTilemapBufferToVram(BG_INTERFACE);
sGfx->finished = TRUE;
break;
}
}
static void Msg_WantToPlayAgain(void)
{
u8 y;
switch (sGfx->state)
{
case 0:
// Create windows
sGfx->windowIds[WIN_PLAY_AGAIN] = AddWindow(&sWindowTemplates_PlayAgain[WIN_PLAY_AGAIN]);
sGfx->windowIds[WIN_YES_NO] = AddWindow(&sWindowTemplates_PlayAgain[WIN_YES_NO]);
ClearWindowTilemap(sGfx->windowIds[WIN_PLAY_AGAIN]);
ClearWindowTilemap(sGfx->windowIds[WIN_YES_NO]);
DrawMessageWindow(&sWindowTemplates_PlayAgain[WIN_PLAY_AGAIN]);
DrawYesNoMessageWindow(&sWindowTemplates_PlayAgain[WIN_YES_NO]);
sGfx->state++;
sGfx->cursorSelection = PLAY_AGAIN_NONE;
sGfx->playAgainState = PLAY_AGAIN_NONE;
break;
case 1:
// Print text
FillWindowPixelBuffer(sGfx->windowIds[WIN_PLAY_AGAIN], PIXEL_FILL(1));
FillWindowPixelBuffer(sGfx->windowIds[WIN_YES_NO], PIXEL_FILL(1));
AddTextPrinterParameterized(sGfx->windowIds[WIN_PLAY_AGAIN], FONT_NORMAL, gText_WantToPlayAgain, 0, 5, -1, NULL);
AddTextPrinterParameterized(sGfx->windowIds[WIN_YES_NO], FONT_NORMAL, gText_Yes, 8, 1, -1, NULL);
AddTextPrinterParameterized(sGfx->windowIds[WIN_YES_NO], FONT_NORMAL, gText_No, 8, 17, -1, NULL);
AddTextPrinterParameterized(sGfx->windowIds[WIN_YES_NO], FONT_NORMAL, gText_SelectorArrow2, 0, 1, -1, NULL);
CopyWindowToVram(sGfx->windowIds[WIN_PLAY_AGAIN], 2);
CopyWindowToVram(sGfx->windowIds[WIN_YES_NO], 2);
sGfx->state++;
break;
case 2:
// Draw windows
if (!IsDma3ManagerBusyWithBgCopy())
{
PutWindowTilemap(sGfx->windowIds[WIN_PLAY_AGAIN]);
PutWindowTilemap(sGfx->windowIds[WIN_YES_NO]);
}
CopyBgTilemapBufferToVram(BG_INTERFACE);
sGfx->state++;
break;
case 3:
// Handle input
y = sGfx->cursorSelection;
if (y == PLAY_AGAIN_NONE)
y = PLAY_AGAIN_YES;
FillWindowPixelBuffer(sGfx->windowIds[WIN_YES_NO], PIXEL_FILL(1));
AddTextPrinterParameterized(sGfx->windowIds[WIN_YES_NO], FONT_NORMAL, gText_Yes, 8, 1, -1, NULL);
AddTextPrinterParameterized(sGfx->windowIds[WIN_YES_NO], FONT_NORMAL, gText_No, 8, 17, -1, NULL);
AddTextPrinterParameterized(sGfx->windowIds[WIN_YES_NO], FONT_NORMAL, gText_SelectorArrow2, 0, ((y - 1) * 16) + 1, -1, NULL);
CopyWindowToVram(sGfx->windowIds[WIN_YES_NO], 3);
// Increment state only if A or B button have been pressed.
if (JOY_NEW(A_BUTTON))
{
PlaySE(SE_SELECT);
if (sGfx->cursorSelection == PLAY_AGAIN_NONE)
sGfx->cursorSelection = PLAY_AGAIN_YES;
sGfx->state++;
}
else if (JOY_NEW(DPAD_UP | DPAD_DOWN))
{
PlaySE(SE_SELECT);
switch (sGfx->cursorSelection)
{
case PLAY_AGAIN_NONE:
sGfx->cursorSelection = PLAY_AGAIN_NO;
break;
case PLAY_AGAIN_YES:
sGfx->cursorSelection = PLAY_AGAIN_NO;
break;
case PLAY_AGAIN_NO:
sGfx->cursorSelection = PLAY_AGAIN_YES;
break;
}
}
else if (JOY_NEW(B_BUTTON))
{
PlaySE(SE_SELECT);
sGfx->cursorSelection = PLAY_AGAIN_NO;
sGfx->state++;
}
break;
default:
sGfx->playAgainState = sGfx->cursorSelection;
ClearWindowTilemap(sGfx->windowIds[WIN_PLAY_AGAIN]);
ClearWindowTilemap(sGfx->windowIds[WIN_YES_NO]);
RemoveWindow(sGfx->windowIds[WIN_PLAY_AGAIN]);
RemoveWindow(sGfx->windowIds[WIN_YES_NO]);
FillBgTilemapBufferRect_Palette0(BG_INTERFACE, 0, 0, 0, 30, 20);
CopyBgTilemapBufferToVram(BG_INTERFACE);
sGfx->finished = TRUE;
break;
}
}
static void Msg_SavingDontTurnOff(void)
{
switch (sGfx->state)
{
case 0:
DrawDialogueFrame(0, FALSE);
AddTextPrinterParameterized2(0, FONT_NORMAL, gText_SavingDontTurnOffPower, 0, NULL, 2, 1, 3);
sGfx->state++;
break;
case 1:
CopyWindowToVram(0, 3);
sGfx->state++;
break;
case 2:
if (!IsDma3ManagerBusyWithBgCopy())
{
CreateTask(Task_LinkSave, 0);
sGfx->state++;
}
break;
case 3:
if (!FuncIsActiveTask(Task_LinkSave))
sGfx->state++;
break;
default:
FillBgTilemapBufferRect_Palette0(BG_INTERFACE, 0, 0, 0, 30, 20);
CopyBgTilemapBufferToVram(BG_INTERFACE);
sGfx->finished = TRUE;
break;
}
}
static void Msg_CommunicationStandby(void)
{
switch (sGfx->state)
{
case 0:
sGfx->windowIds[0] = AddWindow(&sWindowTemplate_CommStandby);
ClearWindowTilemap(sGfx->windowIds[0]);
DrawMessageWindow(&sWindowTemplate_CommStandby);
sGfx->state++;
break;
case 1:
FillWindowPixelBuffer(sGfx->windowIds[0], PIXEL_FILL(1));
AddTextPrinterParameterized(sGfx->windowIds[0], FONT_NORMAL, gText_CommunicationStandby3, 0, 5, -1, NULL);
CopyWindowToVram(sGfx->windowIds[0], 2);
sGfx->state++;
break;
case 2:
if (!IsDma3ManagerBusyWithBgCopy())
PutWindowTilemap(sGfx->windowIds[0]);
CopyBgTilemapBufferToVram(BG_INTERFACE);
sGfx->state++;
break;
default:
sGfx->finished = TRUE;
break;
}
}
static void EraseMessage(void)
{
ClearWindowTilemap(sGfx->windowIds[0]);
RemoveWindow(sGfx->windowIds[0]);
FillBgTilemapBufferRect_Palette0(BG_INTERFACE, 0, 0, 0, 30, 20);
CopyBgTilemapBufferToVram(BG_INTERFACE);
sGfx->finished = TRUE;
}
static void Msg_SomeoneDroppedOut(void)
{
switch (sGfx->state)
{
case 0:
sGfx->windowIds[0] = AddWindow(&sWindowTemplate_DroppedOut);
ClearWindowTilemap(sGfx->windowIds[0]);
DrawMessageWindow(&sWindowTemplate_DroppedOut);
sGfx->state++;
sGfx->timer = 0;
sGfx->cursorSelection = 0;
sGfx->playAgainState = PLAY_AGAIN_NONE;
break;
case 1:
FillWindowPixelBuffer(sGfx->windowIds[0], PIXEL_FILL(1));
AddTextPrinterParameterized(sGfx->windowIds[0], FONT_NORMAL, gText_SomeoneDroppedOut, 0, 5, -1, NULL);
CopyWindowToVram(sGfx->windowIds[0], 2);
sGfx->state++;
break;
case 2:
if (!IsDma3ManagerBusyWithBgCopy())
PutWindowTilemap(sGfx->windowIds[0]);
CopyBgTilemapBufferToVram(BG_INTERFACE);
sGfx->state++;
break;
case 3:
if (++sGfx->timer >= 120)
sGfx->state++;
break;
default:
sGfx->playAgainState = PLAY_AGAIN_DROPPED;
ClearWindowTilemap(sGfx->windowIds[0]);
RemoveWindow(sGfx->windowIds[0]);
FillBgTilemapBufferRect_Palette0(BG_INTERFACE, 0, 0, 0, 30, 20);
CopyBgTilemapBufferToVram(BG_INTERFACE);
sGfx->finished = TRUE;
break;
}
}
static void StopGfxFuncs(void)
{
DestroyTask(sGfx->taskId);
sGfx->finished = TRUE;
}
static void GfxIdle(void)
{
}
static void SetGfxFunc(void (*func)(void))
{
sGfx->state = 0;
sGfx->finished = FALSE;
sGfx->func = func;
}
static void (*GetGfxFunc(void))(void)
{
return sGfx->func;
}
static bool32 IsGfxFuncActive(void)
{
if (sGfx->finished == TRUE)
return FALSE;
else
return TRUE;
}
static u8 GetPlayAgainState(void)
{
return sGfx->playAgainState;
}
static void InitBgs(void)
{
DmaClearLarge16(3, (void *)VRAM, VRAM_SIZE, 0x1000);
DmaClear32(3,(void *)OAM, OAM_SIZE);
DmaClear16(3, (void *)PLTT, PLTT_SIZE);
SetGpuReg(REG_OFFSET_DISPCNT, 0);
ResetBgsAndClearDma3BusyFlags(0);
InitBgsFromTemplates(0, sBgTemplates, ARRAY_COUNT(sBgTemplates));
ChangeBgX(0, 0, 0);
ChangeBgY(0, 0, 0);
ChangeBgX(1, 0, 0);
ChangeBgY(1, 0, 0);
ChangeBgX(2, 0, 0);
ChangeBgY(2, 0, 0);
ChangeBgX(3, 0, 0);
ChangeBgY(3, 0, 0);
InitStandardTextBoxWindows();
InitTextBoxGfxAndPrinters();
SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_ON | DISPCNT_OBJ_1D_MAP);
SetBgTilemapBuffer(BG_SCENERY, sGfx->tilemapBuffers[0]);
SetBgTilemapBuffer(BG_TREE_LEFT, sGfx->tilemapBuffers[1]);
SetBgTilemapBuffer(BG_TREE_RIGHT, sGfx->tilemapBuffers[2]);
}
static bool32 LoadBgGfx(void)
{
switch (sGfx->loadState)
{
case 0:
LoadPalette(sBg_Pal, 0, sizeof(sBg_Pal));
break;
case 1:
ResetTempTileDataBuffers();
break;
case 2:
DecompressAndCopyTileDataToVram(BG_SCENERY, sBg_Gfx, 0, 0, 0);
break;
case 3:
DecompressAndCopyTileDataToVram(BG_TREE_LEFT, sTreeBorder_Gfx, 0, 0, 0);
break;
case 4:
if (FreeTempTileDataBuffersIfPossible() == TRUE)
return FALSE;
break;
case 5:
LoadPalette(GetTextWindowPalette(3), 0xD0, 0x20);
break;
default:
sGfx->loadState = 0;
return TRUE;
}
sGfx->loadState++;
return FALSE;
}