pokeemerald/src/berry_blender.c
aaaaaa123456789 7dc95a0103 Undo PokeCodec's PRs
This commit undoes most of PokeCodec's PRs after the debate in chat. Some
harmless or completely superseded PRs have been left alone, as there is not
much benefit in attempting to undo them.

Reverts #1104, #1108, #1115, #1118, #1119, #1124, #1126, #1127, #1132, #1136,
#1137, #1139, #1140, #1144, #1148, #1149, #1150, #1153, #1155, #1177, #1179,
#1180, #1181, #1182 and #1183.
2020-09-13 06:30:55 -03:00

3900 lines
119 KiB
C

#include "global.h"
#include "overworld.h"
#include "berry_blender.h"
#include "bg.h"
#include "window.h"
#include "task.h"
#include "sprite.h"
#include "sound.h"
#include "m4a.h"
#include "bg.h"
#include "palette.h"
#include "decompress.h"
#include "malloc.h"
#include "gpu_regs.h"
#include "text.h"
#include "text_window.h"
#include "event_data.h"
#include "main.h"
#include "link.h"
#include "link_rfu.h"
#include "item_menu_icons.h"
#include "berry.h"
#include "item.h"
#include "string_util.h"
#include "international_string_util.h"
#include "random.h"
#include "menu.h"
#include "pokeblock.h"
#include "trig.h"
#include "tv.h"
#include "item_menu.h"
#include "battle_records.h"
#include "graphics.h"
#include "new_game.h"
#include "save.h"
#include "strings.h"
#include "constants/berry.h"
#include "constants/game_stat.h"
#include "constants/items.h"
#include "constants/rgb.h"
#include "constants/songs.h"
enum {
SCORE_BEST,
SCORE_GOOD,
SCORE_MISS,
NUM_SCORE_TYPES,
};
// Redundant with the above. Reversed
enum {
PROXIMITY_MISS,
PROXIMITY_GOOD,
PROXIMITY_BEST,
};
enum {
SCOREANIM_GOOD,
SCOREANIM_MISS,
SCOREANIM_BEST_FLASH,
SCOREANIM_BEST_STATIC,
};
enum {
PLAY_AGAIN_YES,
PLAY_AGAIN_NO,
CANT_PLAY_NO_BERRIES,
CANT_PLAY_NO_PKBLCK_SPACE
};
enum {
BLENDER_MISTER,
BLENDER_LADDIE,
BLENDER_LASSIE,
BLENDER_MASTER,
BLENDER_DUDE,
BLENDER_MISS
};
#define BLENDER_MAX_PLAYERS MAX_LINK_PLAYERS
#define NO_PLAYER 0xFF
#define MAX_PROGRESS_BAR 1000
#define MAX_ARROW_POS 0x10000 // By virtue of being u16
#define MIN_ARROW_SPEED 0x80
#define ARROW_FALL_ROTATION 0x5800 // The amount the arrow spins as it falls in at the start
// Tile offsets
#define PROGRESS_BAR_FILLED_TOP 0x80E9
#define PROGRESS_BAR_FILLED_BOTTOM 0x80F9
#define PROGRESS_BAR_EMPTY_TOP 0x80E1
#define PROGRESS_BAR_EMPTY_BOTTOM 0x80F1
#define RPM_DIGIT 0x8072
// Tile and palette tags
#define GFXTAG_COUNTDOWN_NUMBERS 12345
#define GFXTAG_START 12346
#define GFXTAG_PARTICLES 23456
#define GFXTAG_PLAYER_ARROW 46545
#define GFXTAG_SCORE_SYMBOLS 48888
#define PALTAG_PLAYER_ARROW 12312
#define PALTAG_MISC 46546
// Last berry that an NPC can put in
#define NUM_NPC_BERRIES ITEM_TO_BERRY(ITEM_ASPEAR_BERRY)
struct BlenderBerry
{
u16 itemId;
u8 name[BERRY_NAME_LENGTH + 1];
u8 flavors[FLAVOR_COUNT + 1]; // 5 flavors, + 1 for feel
};
struct TimeAndRPM
{
u32 time;
u16 maxRPM;
};
struct BlenderGameBlock
{
struct TimeAndRPM timeRPM;
u16 scores[BLENDER_MAX_PLAYERS][NUM_SCORE_TYPES];
};
struct TvBlenderStruct
{
u8 name[11];
u8 pokeblockFlavor;
u8 pokeblockColor;
u8 pokeblockSheen;
};
struct BerryBlender
{
u8 mainState;
u8 loadGfxState;
u8 unused0[66];
u16 unk0; // never read
u8 scoreIconIds[NUM_SCORE_TYPES];
u16 arrowPos;
s16 speed;
u16 maxRPM;
u8 playerArrowSpriteIds[BLENDER_MAX_PLAYERS];
u8 playerArrowSpriteIds2[BLENDER_MAX_PLAYERS];
u8 unused1[11];
u8 gameEndState;
u16 playerContinueResponses[BLENDER_MAX_PLAYERS];
u16 canceledPlayerCmd;
u16 canceledPlayerId;
u16 playAgainState;
u8 slowdownTimer;
u16 chosenItemId[BLENDER_MAX_PLAYERS];
u8 numPlayers;
u8 unused2[16];
u16 arrowIdToPlayerId[BLENDER_MAX_PLAYERS];
u16 playerIdToArrowId[BLENDER_MAX_PLAYERS];
u8 yesNoAnswer;
u8 stringVar[100];
u32 gameFrameTime;
s32 framesToWait;
u32 unk1; // never read
u8 unused3[4];
u8 playerToThrowBerry;
u16 progressBarValue;
u16 maxProgressBarValue;
u16 centerScale;
u16 bg_X;
u16 bg_Y;
u8 opponentTaskIds[BLENDER_MAX_PLAYERS - 1];
u8 perfectOpponents; // for debugging, NPCs will always hit Best
u16 scores[BLENDER_MAX_PLAYERS][NUM_SCORE_TYPES];
u8 playerPlaces[BLENDER_MAX_PLAYERS];
struct BgAffineSrcData bgAffineSrc;
u16 savedMusic;
struct BlenderBerry blendedBerries[BLENDER_MAX_PLAYERS];
struct TimeAndRPM smallBlock;
u32 linkPlayAgainState;
u8 ownRanking;
struct TvBlenderStruct tvBlender;
u8 tilemapBuffers[2][BG_SCREEN_SIZE];
s16 textState;
void *tilesBuffer;
struct BlenderGameBlock gameBlock;
};
static void SetBgPos(void);
static void Task_HandleOpponent1(u8);
static void Task_HandleOpponent2(u8);
static void Task_HandleOpponent3(u8);
static void Task_HandleBerryMaster(u8);
static void Task_PlayPokeblockFanfare(u8);
static void SpriteCB_PlayerArrow(struct Sprite *);
static void SpriteCB_ScoreSymbol(struct Sprite *);
static void SpriteCB_CountdownNumber(struct Sprite *);
static void SpriteCB_Start(struct Sprite *);
static void SpriteCB_ScoreSymbolBest(struct Sprite *);
static void InitLocalPlayers(u8);
static void CB2_LoadBerryBlender(void);
static void UpdateBlenderCenter(void);
static bool32 Blender_PrintText(s16 *, const u8 *, s32 );
static void StartBlender(void);
static void CB2_StartBlenderLink(void);
static void CB2_StartBlenderLocal(void);
static void Blender_DummiedOutFunc(s16, s16);
static void CB2_PlayBlender(void);
static void DrawBlenderCenter(struct BgAffineSrcData *);
static bool8 UpdateBlenderLandScreenShake(void);
static void SetPlayerIdMaps(void);
static void PrintPlayerNames(void);
static void InitBlenderBgs(void);
static void SetPlayerBerryData(u8, u16);
static void Blender_AddTextPrinter(u8, const u8 *, u8, u8, s32, s32);
static void ResetLinkCmds(void);
static void CreateParticleSprites(void);
static void ShakeBgCoordForHit(s16*, u16);
static void TryUpdateProgressBar(u16, u16);
static void UpdateRPM(u16);
static void RestoreBgCoords(void);
static void ProcessLinkPlayerCmds(void);
static void CB2_EndBlenderGame(void);
static bool8 PrintBlendingRanking(void);
static bool8 PrintBlendingResults(void);
static void CB2_CheckPlayAgainLocal(void);
static void CB2_CheckPlayAgainLink(void);
static void UpdateProgressBar(u16, u16);
static void PrintMadePokeblockString(struct Pokeblock *, u8 *);
static bool32 TryAddContestLinkTvShow(struct Pokeblock *, struct TvBlenderStruct *);
EWRAM_DATA static struct BerryBlender *sBerryBlender = NULL;
EWRAM_DATA static s32 sDebug_PokeblockFactorFlavors[FLAVOR_COUNT] = {0};
EWRAM_DATA static s32 sDebug_PokeblockFactorFlavorsAfterRPM[FLAVOR_COUNT] = {0};
EWRAM_DATA static u32 sDebug_PokeblockFactorRPM = 0;
static s16 sPokeblockFlavors[FLAVOR_COUNT + 1]; // + 1 for feel
static s16 sPokeblockPresentFlavors[FLAVOR_COUNT + 1];
static s16 sDebug_MaxRPMStage;
static s16 sDebug_GameTimeStage;
u8 gInGameOpponentsNo;
static const u16 sBlenderCenter_Pal[] = INCBIN_U16("graphics/berry_blender/center.gbapal");
static const u8 sBlenderCenter_Tilemap[] = INCBIN_U8("graphics/berry_blender/center_map.bin");
static const u16 sBlenderOuter_Pal[] = INCBIN_U16("graphics/berry_blender/outer.gbapal");
static const u16 sUnused_Pal[] = INCBIN_U16("graphics/berry_blender/unused.gbapal");
static const u16 sEmpty_Pal[16 * 14] = {0};
// unused text
static const u8 sUnusedText_YesNo[] = _("YES\nNO");
static const u8 sUnusedText_2[] = _("");
static const u8 sUnusedText_Space[] = _(" ");
static const u8 sUnusedText_Terminating[] = _("Terminating.");
static const u8 sUnusedText_LinkPartnerNotFound[] = _("Link partner(s) not found.\nPlease try again.\p");
static const u8 sText_BerryBlenderStart[] = _("Starting up the BERRY BLENDER.\pPlease select a BERRY from your BAG\nto put in the BERRY BLENDER.\p");
static const u8 sText_NewParagraph[] = _("\p");
static const u8 sText_WasMade[] = _(" was made!");
static const u8 sText_Mister[] = _("MISTER");
static const u8 sText_Laddie[] = _("LADDIE");
static const u8 sText_Lassie[] = _("LASSIE");
static const u8 sText_Master[] = _("MASTER");
static const u8 sText_Dude[] = _("DUDE");
static const u8 sText_Miss[] = _("MISS");
static const u8* const sBlenderOpponentsNames[] =
{
[BLENDER_MISTER] = sText_Mister,
[BLENDER_LADDIE] = sText_Laddie,
[BLENDER_LASSIE] = sText_Lassie,
[BLENDER_MASTER] = sText_Master,
[BLENDER_DUDE] = sText_Dude,
[BLENDER_MISS] = sText_Miss
};
static const u8 sText_PressAToStart[] = _("Press the A Button to start.");
static const u8 sText_PleaseWaitAWhile[] = _("Please wait a while.");
static const u8 sText_CommunicationStandby[] = _("Communication standby…");
static const u8 sText_WouldLikeToBlendAnotherBerry[] = _("Would you like to blend another BERRY?");
static const u8 sText_RunOutOfBerriesForBlending[] = _("You've run out of BERRIES for\nblending in the BERRY BLENDER.\p");
static const u8 sText_YourPokeblockCaseIsFull[] = _("Your {POKEBLOCK} CASE is full.\p");
static const u8 sText_HasNoBerriesToPut[] = _(" has no BERRIES to put in\nthe BERRY BLENDER.");
static const u8 sText_ApostropheSPokeblockCaseIsFull[] = _("'s {POKEBLOCK} CASE is full.\p");
static const u8 sText_BlendingResults[] = _("RESULTS OF BLENDING");
static const u8 sText_BerryUsed[] = _("BERRY USED");
static const u8 sText_SpaceBerry[] = _(" BERRY");
static const u8 sText_Time[] = _("Time:");
static const u8 sText_Min[] = _(" min. ");
static const u8 sText_Sec[] = _(" sec.");
static const u8 sText_MaximumSpeed[] = _("MAXIMUM SPEED");
static const u8 sText_RPM[] = _(" RPM");
static const u8 sText_Dot[] = _(".");
static const u8 sText_NewLine[] = _("\n");
static const u8 sText_Space[] = _(" ");
static const u8 sText_Ranking[] = _("RANKING");
static const u8 sText_TheLevelIs[] = _("The level is ");
static const u8 sText_TheFeelIs[] = _(", and the feel is ");
static const u8 sText_Dot2[] = _(".");
static const struct BgTemplate sBgTemplates[3] =
{
{
.bg = 0,
.charBaseIndex = 3,
.mapBaseIndex = 31,
.screenSize = 0,
.paletteMode = 0,
.priority = 0,
.baseTile = 0,
},
{
.bg = 1,
.charBaseIndex = 2,
.mapBaseIndex = 12,
.screenSize = 0,
.paletteMode = 0,
.priority = 1,
.baseTile = 0,
},
{
.bg = 2,
.charBaseIndex = 0,
.mapBaseIndex = 8,
.screenSize = 1,
.paletteMode = 1,
.priority = 0,
.baseTile = 0,
}
};
static const struct WindowTemplate sWindowTemplates[] =
{
{
.bg = 0,
.tilemapLeft = 1,
.tilemapTop = 6,
.width = 7,
.height = 2,
.paletteNum = 14,
.baseBlock = 0x28,
},
{
.bg = 0,
.tilemapLeft = 22,
.tilemapTop = 6,
.width = 7,
.height = 2,
.paletteNum = 14,
.baseBlock = 0x36,
},
{
.bg = 0,
.tilemapLeft = 1,
.tilemapTop = 12,
.width = 7,
.height = 2,
.paletteNum = 14,
.baseBlock = 0x44,
},
{
.bg = 0,
.tilemapLeft = 22,
.tilemapTop = 12,
.width = 7,
.height = 2,
.paletteNum = 14,
.baseBlock = 0x52,
},
{
.bg = 0,
.tilemapLeft = 2,
.tilemapTop = 15,
.width = 27,
.height = 4,
.paletteNum = 14,
.baseBlock = 0x60,
},
{
.bg = 0,
.tilemapLeft = 5,
.tilemapTop = 3,
.width = 21,
.height = 14,
.paletteNum = 14,
.baseBlock = 0x60,
},
DUMMY_WIN_TEMPLATE
};
static const struct WindowTemplate sYesNoWindowTemplate_ContinuePlaying =
{
.bg = 0,
.tilemapLeft = 21,
.tilemapTop = 9,
.width = 5,
.height = 4,
.paletteNum = 14,
.baseBlock = 0xCC
};
static const s8 sPlayerArrowQuadrant[BLENDER_MAX_PLAYERS][2] =
{
{-1, -1},
{ 1, -1},
{-1, 1},
{ 1, 1}
};
static const u8 sPlayerArrowPos[BLENDER_MAX_PLAYERS][2] =
{
{ 72, 32},
{168, 32},
{ 72, 128},
{168, 128}
};
static const u8 sPlayerIdMap[BLENDER_MAX_PLAYERS - 1][BLENDER_MAX_PLAYERS] =
{
{NO_PLAYER, 0, 1, NO_PLAYER}, // 2 Players
{NO_PLAYER, 0, 1, 2}, // 3 Players
{ 0, 1, 2, 3} // 4 Players
};
// Blender arrow positions:
//
// 0x0000 (limit 0x10000)
// . .
// . .
// 0x4000 . . 0xC000
// . .
// . .
// . .
// 0x8000
//
static const u16 sArrowStartPos[] = {
0,
MAX_ARROW_POS / 4 * 3, // 0xC000
MAX_ARROW_POS / 4, // 0x4000
MAX_ARROW_POS / 4 * 2 // 0x8000
};
static const u8 sArrowStartPosIds[BLENDER_MAX_PLAYERS - 1] = {1, 1, 0};
static const u8 sArrowHitRangeStart[BLENDER_MAX_PLAYERS] = {32, 224, 96, 160};
static const TaskFunc sLocalOpponentTasks[] =
{
Task_HandleOpponent1,
Task_HandleOpponent2,
Task_HandleOpponent3
};
static const struct OamData sOam_PlayerArrow =
{
.y = 0,
.affineMode = ST_OAM_AFFINE_OFF,
.objMode = ST_OAM_OBJ_NORMAL,
.mosaic = 0,
.bpp = ST_OAM_4BPP,
.shape = SPRITE_SHAPE(32x32),
.x = 0,
.matrixNum = 0,
.size = SPRITE_SIZE(32x32),
.tileNum = 0,
.priority = 1,
.paletteNum = 0,
.affineParam = 0,
};
static const union AnimCmd sAnim_PlayerArrow_TopLeft[] =
{
ANIMCMD_FRAME(16, 5, .vFlip = TRUE, .hFlip = TRUE),
ANIMCMD_END
};
static const union AnimCmd sAnim_PlayerArrow_TopRight[] =
{
ANIMCMD_FRAME(16, 5, .vFlip = TRUE),
ANIMCMD_END
};
static const union AnimCmd sAnim_PlayerArrow_BottomLeft[] =
{
ANIMCMD_FRAME(16, 5, .hFlip = TRUE),
ANIMCMD_END
};
static const union AnimCmd sAnim_PlayerArrow_BottomRight[] =
{
ANIMCMD_FRAME(16, 5, 0, 0),
ANIMCMD_END
};
static const union AnimCmd sAnim_PlayerArrow_TopLeft_Flash[] =
{
ANIMCMD_FRAME(48, 2, .vFlip = TRUE, .hFlip = TRUE),
ANIMCMD_FRAME(32, 5, .vFlip = TRUE, .hFlip = TRUE),
ANIMCMD_FRAME(48, 3, .vFlip = TRUE, .hFlip = TRUE),
ANIMCMD_FRAME(16, 5, .vFlip = TRUE, .hFlip = TRUE),
ANIMCMD_END
};
static const union AnimCmd sAnim_PlayerArrow_TopRight_Flash[] =
{
ANIMCMD_FRAME(48, 2, .vFlip = TRUE),
ANIMCMD_FRAME(32, 5, .vFlip = TRUE),
ANIMCMD_FRAME(48, 3, .vFlip = TRUE),
ANIMCMD_FRAME(16, 5, .vFlip = TRUE),
ANIMCMD_END
};
static const union AnimCmd sAnim_PlayerArrow_BottomLeft_Flash[] =
{
ANIMCMD_FRAME(48, 2, .hFlip = TRUE),
ANIMCMD_FRAME(32, 5, .hFlip = TRUE),
ANIMCMD_FRAME(48, 3, .hFlip = TRUE),
ANIMCMD_FRAME(16, 5, .hFlip = TRUE),
ANIMCMD_END
};
static const union AnimCmd sAnim_PlayerArrow_BottomRight_Flash[] =
{
ANIMCMD_FRAME(48, 2, 0, 0),
ANIMCMD_FRAME(32, 5, 0, 0),
ANIMCMD_FRAME(48, 3, 0, 0),
ANIMCMD_FRAME(16, 5, 0, 0),
ANIMCMD_END
};
static const union AnimCmd sAnim_PlayerArrow_TopLeft_Off[] =
{
ANIMCMD_FRAME(0, 5, .vFlip = TRUE, .hFlip = TRUE),
ANIMCMD_END
};
static const union AnimCmd sAnim_PlayerArrow_TopRight_Off[] =
{
ANIMCMD_FRAME(0, 5, .vFlip = TRUE),
ANIMCMD_END
};
static const union AnimCmd sAnim_PlayerArrow_BottomLeft_Off[] =
{
ANIMCMD_FRAME(0, 5, .hFlip = TRUE),
ANIMCMD_END
};
static const union AnimCmd sAnim_PlayerArrow_BottomRight_Off[] =
{
ANIMCMD_FRAME(0, 5, 0, 0),
ANIMCMD_END
};
static const union AnimCmd *const sAnims_PlayerArrow[] =
{
sAnim_PlayerArrow_TopLeft,
sAnim_PlayerArrow_TopRight,
sAnim_PlayerArrow_BottomLeft,
sAnim_PlayerArrow_BottomRight,
sAnim_PlayerArrow_TopLeft_Flash,
sAnim_PlayerArrow_TopRight_Flash,
sAnim_PlayerArrow_BottomLeft_Flash,
sAnim_PlayerArrow_BottomRight_Flash,
sAnim_PlayerArrow_TopLeft_Off,
sAnim_PlayerArrow_TopRight_Off,
sAnim_PlayerArrow_BottomLeft_Off,
sAnim_PlayerArrow_BottomRight_Off
};
static const struct SpriteSheet sSpriteSheet_PlayerArrow =
{
gBerryBlenderPlayerArrow_Gfx, 0x800, GFXTAG_PLAYER_ARROW
};
static const struct SpritePalette sSpritePal_BlenderMisc =
{
gBerryBlenderMiscPalette, PALTAG_MISC
};
static const struct SpritePalette sSpritePal_PlayerArrow =
{
gBerryBlenderArrowPalette, PALTAG_PLAYER_ARROW
};
static const struct SpriteTemplate sSpriteTemplate_PlayerArrow =
{
.tileTag = GFXTAG_PLAYER_ARROW,
.paletteTag = PALTAG_PLAYER_ARROW,
.oam = &sOam_PlayerArrow,
.anims = sAnims_PlayerArrow,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_PlayerArrow
};
static const struct OamData sOam_ScoreSymbols =
{
.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 union AnimCmd sAnim_ScoreSymbols_Good[] =
{
ANIMCMD_FRAME(0, 20),
ANIMCMD_END
};
static const union AnimCmd sAnim_ScoreSymbols_Miss[] =
{
ANIMCMD_FRAME(4, 20, 1, 0),
ANIMCMD_END
};
static const union AnimCmd sAnim_ScoreSymbols_BestFlash[] =
{
ANIMCMD_FRAME(8, 4),
ANIMCMD_FRAME(12, 4),
ANIMCMD_FRAME(8, 4),
ANIMCMD_FRAME(12, 4),
ANIMCMD_FRAME(8, 4),
ANIMCMD_END
};
static const union AnimCmd sAnim_ScoreSymbols_BestStatic[] =
{
ANIMCMD_FRAME(8, 4),
ANIMCMD_END
};
static const union AnimCmd *const sAnims_ScoreSymbols[] =
{
[SCOREANIM_GOOD] = sAnim_ScoreSymbols_Good,
[SCOREANIM_MISS] = sAnim_ScoreSymbols_Miss,
[SCOREANIM_BEST_FLASH] = sAnim_ScoreSymbols_BestFlash,
[SCOREANIM_BEST_STATIC] = sAnim_ScoreSymbols_BestStatic,
};
static const struct SpriteSheet sSpriteSheet_ScoreSymbols =
{
gBerryBlenderScoreSymbols_Gfx, 0x200, GFXTAG_SCORE_SYMBOLS
};
static const struct SpriteTemplate sSpriteTemplate_ScoreSymbols =
{
.tileTag = GFXTAG_SCORE_SYMBOLS,
.paletteTag = PALTAG_MISC,
.oam = &sOam_ScoreSymbols,
.anims = sAnims_ScoreSymbols,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_ScoreSymbol
};
static const struct OamData sOam_Particles =
{
.y = 0,
.affineMode = ST_OAM_AFFINE_OFF,
.objMode = ST_OAM_OBJ_NORMAL,
.mosaic = 0,
.bpp = ST_OAM_4BPP,
.shape = SPRITE_SHAPE(8x8),
.x = 0,
.matrixNum = 0,
.size = SPRITE_SIZE(8x8),
.tileNum = 0,
.priority = 1,
.paletteNum = 0,
.affineParam = 0,
};
static const union AnimCmd sAnim_SparkleCrossToX[] =
{
ANIMCMD_FRAME(0, 3),
ANIMCMD_FRAME(1, 4),
ANIMCMD_FRAME(3, 5),
ANIMCMD_FRAME(1, 4),
ANIMCMD_FRAME(0, 3),
ANIMCMD_END
};
static const union AnimCmd sAnim_SparkleXToCross[] =
{
ANIMCMD_FRAME(0, 3),
ANIMCMD_FRAME(2, 4),
ANIMCMD_FRAME(4, 5),
ANIMCMD_FRAME(2, 4),
ANIMCMD_FRAME(0, 3),
ANIMCMD_END
};
static const union AnimCmd sAnim_SparkleFull[] =
{
ANIMCMD_FRAME(0, 2),
ANIMCMD_FRAME(1, 2),
ANIMCMD_FRAME(2, 2),
ANIMCMD_FRAME(4, 4),
ANIMCMD_FRAME(3, 3),
ANIMCMD_FRAME(2, 2),
ANIMCMD_FRAME(1, 2),
ANIMCMD_FRAME(0, 2),
ANIMCMD_END
};
static const union AnimCmd sAnim_GreenArrow[] =
{
ANIMCMD_FRAME(5, 5, 1, 1),
ANIMCMD_END
};
static const union AnimCmd sAnim_GreenDot[] =
{
ANIMCMD_FRAME(6, 5, 1, 1),
ANIMCMD_END
};
static const union AnimCmd *const sAnims_Particles[] =
{
sAnim_SparkleCrossToX, // Only this effect is ever used, rest go unused
sAnim_SparkleXToCross,
sAnim_SparkleFull,
sAnim_GreenArrow,
sAnim_GreenDot,
};
static const struct SpriteSheet sSpriteSheet_Particles =
{
gBerryBlenderParticles_Gfx, 0xE0, GFXTAG_PARTICLES
};
static const struct SpriteTemplate sSpriteTemplate_Particles =
{
.tileTag = GFXTAG_PARTICLES,
.paletteTag = PALTAG_MISC,
.oam = &sOam_Particles,
.anims = sAnims_Particles,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCallbackDummy
};
static const struct OamData sOam_CountdownNumbers =
{
.y = 0,
.affineMode = ST_OAM_AFFINE_OFF,
.objMode = ST_OAM_OBJ_NORMAL,
.mosaic = 0,
.bpp = ST_OAM_4BPP,
.shape = SPRITE_SHAPE(32x32),
.x = 0,
.matrixNum = 0,
.size = SPRITE_SIZE(32x32),
.tileNum = 0,
.priority = 1,
.paletteNum = 0,
.affineParam = 0,
};
static const union AnimCmd sAnim_CountdownNumbers_3[] =
{
ANIMCMD_FRAME(32, 30),
ANIMCMD_END
};
static const union AnimCmd sAnim_CountdownNumbers_2[] =
{
ANIMCMD_FRAME(16, 30),
ANIMCMD_END
};
static const union AnimCmd sAnim_CountdownNumbers_1[] =
{
ANIMCMD_FRAME(0, 30),
ANIMCMD_END
};
static const union AnimCmd *const sAnims_CountdownNumbers[] =
{
sAnim_CountdownNumbers_3,
sAnim_CountdownNumbers_2,
sAnim_CountdownNumbers_1,
};
static const struct SpriteSheet sSpriteSheet_CountdownNumbers =
{
gBerryBlenderCountdownNumbers_Gfx, 0x600, GFXTAG_COUNTDOWN_NUMBERS
};
static const struct SpriteTemplate sSpriteTemplate_CountdownNumbers =
{
.tileTag = GFXTAG_COUNTDOWN_NUMBERS,
.paletteTag = PALTAG_MISC,
.oam = &sOam_CountdownNumbers,
.anims = sAnims_CountdownNumbers,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_CountdownNumber
};
static const struct OamData sOam_Start =
{
.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 = 1,
.paletteNum = 0,
.affineParam = 0,
};
static const union AnimCmd sAnim_Start[] =
{
ANIMCMD_FRAME(0, 30),
ANIMCMD_END
};
static const union AnimCmd *const sAnims_Start[] =
{
sAnim_Start,
};
static const struct SpriteSheet sSpriteSheet_Start =
{
gBerryBlenderStart_Gfx, 0x400, GFXTAG_START
};
static const struct SpriteTemplate sSpriteTemplate_Start =
{
.tileTag = GFXTAG_START,
.paletteTag = PALTAG_MISC,
.oam = &sOam_Start,
.anims = sAnims_Start,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_Start
};
// Data for throwing the berries in at the start
// x, y, bounce speed, x speed, y speed
static const s16 sBerrySpriteData[][5] =
{
{-10, 20, 10, 2, 1},
{250, 20, 10, -2, 1},
{-10, 140, 10, 2, -1},
{250, 140, 10, -2, -1},
};
// There are only 5 different berries the NPCs will ever use
// Each of these sets represents 3 berries chosen to be used by the NPCs
// If the player's berry is one of the 5 possible berries, a set is chosen that excludes it
static const u8 sOpponentBerrySets[NUM_NPC_BERRIES * 2][3] =
{
// These sets are used if the player chose one of the 5 NPC berries
{ITEM_TO_BERRY(ITEM_ASPEAR_BERRY) - 1, ITEM_TO_BERRY(ITEM_RAWST_BERRY) - 1, ITEM_TO_BERRY(ITEM_PECHA_BERRY) - 1}, // player chose Cheri Berry
{ITEM_TO_BERRY(ITEM_CHERI_BERRY) - 1, ITEM_TO_BERRY(ITEM_ASPEAR_BERRY) - 1, ITEM_TO_BERRY(ITEM_RAWST_BERRY) - 1}, // player chose Chesto Berry
{ITEM_TO_BERRY(ITEM_CHESTO_BERRY) - 1, ITEM_TO_BERRY(ITEM_CHERI_BERRY) - 1, ITEM_TO_BERRY(ITEM_ASPEAR_BERRY) - 1}, // player chose Pecha Berry
{ITEM_TO_BERRY(ITEM_PECHA_BERRY) - 1, ITEM_TO_BERRY(ITEM_CHESTO_BERRY) - 1, ITEM_TO_BERRY(ITEM_CHERI_BERRY) - 1}, // player chose Rawst Berry
{ITEM_TO_BERRY(ITEM_RAWST_BERRY) - 1, ITEM_TO_BERRY(ITEM_PECHA_BERRY) - 1, ITEM_TO_BERRY(ITEM_CHESTO_BERRY) - 1}, // player chose Aspear Berry
// These sets are used if the player chose a different berry (set is selected by player's berry % 5)
{ITEM_TO_BERRY(ITEM_CHERI_BERRY) - 1, ITEM_TO_BERRY(ITEM_PECHA_BERRY) - 1, ITEM_TO_BERRY(ITEM_RAWST_BERRY) - 1}, // player chose Leppa, Figy, ...
{ITEM_TO_BERRY(ITEM_CHESTO_BERRY) - 1, ITEM_TO_BERRY(ITEM_RAWST_BERRY) - 1, ITEM_TO_BERRY(ITEM_ASPEAR_BERRY) - 1}, // player chose Oran, Wiki, ...
{ITEM_TO_BERRY(ITEM_PECHA_BERRY) - 1, ITEM_TO_BERRY(ITEM_ASPEAR_BERRY) - 1, ITEM_TO_BERRY(ITEM_CHERI_BERRY) - 1}, // player chose Persim, Mago, ...
{ITEM_TO_BERRY(ITEM_RAWST_BERRY) - 1, ITEM_TO_BERRY(ITEM_CHERI_BERRY) - 1, ITEM_TO_BERRY(ITEM_CHESTO_BERRY) - 1}, // player chose Lum, Aguav, ...
{ITEM_TO_BERRY(ITEM_ASPEAR_BERRY) - 1, ITEM_TO_BERRY(ITEM_CHESTO_BERRY) - 1, ITEM_TO_BERRY(ITEM_PECHA_BERRY) - 1}, // player chose Sitrus, Iapapa, ...
};
// Berry master's berries follow the same rules as above, but instead of explicitly listing
// the alternate sets if the player chooses one of these berries, it implicitly uses these berries - 5, i.e. Tamato - Nomel
static const u8 sBerryMasterBerries[] = {
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
};
// "0 players" is link
static const u8 sNumPlayersToSpeedDivisor[] = {1, 1, 2, 3, 4};
// Black pokeblocks will use one of these random combinations of flavors
static const u8 sBlackPokeblockFlavorFlags[] = {
(1 << FLAVOR_SOUR) | (1 << FLAVOR_BITTER) | (1 << FLAVOR_SWEET),
(1 << FLAVOR_SOUR) | (1 << FLAVOR_SWEET) | (1 << FLAVOR_DRY),
(1 << FLAVOR_SOUR) | (1 << FLAVOR_DRY) | (1 << FLAVOR_SPICY),
(1 << FLAVOR_SOUR) | (1 << FLAVOR_BITTER) | (1 << FLAVOR_DRY),
(1 << FLAVOR_SOUR) | (1 << FLAVOR_BITTER) | (1 << FLAVOR_SPICY),
(1 << FLAVOR_BITTER) | (1 << FLAVOR_SWEET) | (1 << FLAVOR_DRY),
(1 << FLAVOR_BITTER) | (1 << FLAVOR_SWEET) | (1 << FLAVOR_SPICY),
(1 << FLAVOR_BITTER) | (1 << FLAVOR_DRY) | (1 << FLAVOR_SPICY),
(1 << FLAVOR_SWEET) | (1 << FLAVOR_DRY) | (1 << FLAVOR_SPICY),
(1 << FLAVOR_SOUR) | (1 << FLAVOR_SWEET) | (1 << FLAVOR_SPICY),
};
static const u8 sUnused[] =
{
0xfe, 0x02, 0x02, 0xce, 0xd0, 0x37, 0x44, 0x07, 0x1f, 0x0c, 0x10,
0x00, 0xff, 0xfe, 0x91, 0x72, 0xce, 0xd0, 0x37, 0x44, 0x07, 0x1f,
0x0c, 0x10, 0x00, 0xff, 0x06, 0x27, 0x02, 0xff, 0x00, 0x0c, 0x48,
0x02, 0xff, 0x00, 0x01, 0x1f, 0x02, 0xff, 0x00, 0x16, 0x37, 0x02,
0xff, 0x00, 0x0d, 0x50, 0x4b, 0x02, 0xff, 0x06, 0x06, 0x06, 0x06,
0x05, 0x03, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x02
};
static const struct WindowTemplate sBlenderRecordWindowTemplate =
{
.bg = 0,
.tilemapLeft = 6,
.tilemapTop = 4,
.width = 18,
.height = 11,
.paletteNum = 15,
.baseBlock = 8
};
static void UpdateHitPitch(void)
{
m4aMPlayPitchControl(&gMPlayInfo_SE2, 0xFFFF, 2 * (sBerryBlender->speed - MIN_ARROW_SPEED));
}
static void VBlankCB_BerryBlender(void)
{
SetBgPos();
SetBgAffine(2, sBerryBlender->bgAffineSrc.texX, sBerryBlender->bgAffineSrc.texY,
sBerryBlender->bgAffineSrc.scrX, sBerryBlender->bgAffineSrc.scrY,
sBerryBlender->bgAffineSrc.sx, sBerryBlender->bgAffineSrc.sy,
sBerryBlender->bgAffineSrc.alpha);
LoadOam();
ProcessSpriteCopyRequests();
TransferPlttBuffer();
}
static bool8 LoadBerryBlenderGfx(void)
{
switch (sBerryBlender->loadGfxState)
{
case 0:
sBerryBlender->tilesBuffer = AllocZeroed(GetDecompressedDataSize(gBerryBlenderCenter_Gfx) + 100);
LZDecompressWram(gBerryBlenderCenter_Gfx, sBerryBlender->tilesBuffer);
sBerryBlender->loadGfxState++;
break;
case 1:
CopyToBgTilemapBuffer(2, sBlenderCenter_Tilemap, 0x400, 0);
CopyBgTilemapBufferToVram(2);
LoadPalette(sBlenderCenter_Pal, 0, 0x100);
sBerryBlender->loadGfxState++;
break;
case 2:
LoadBgTiles(2, sBerryBlender->tilesBuffer, GetDecompressedDataSize(gBerryBlenderCenter_Gfx), 0);
sBerryBlender->loadGfxState++;
break;
case 3:
LZDecompressWram(gBerryBlenderOuter_Gfx, sBerryBlender->tilesBuffer);
sBerryBlender->loadGfxState++;
break;
case 4:
LoadBgTiles(1, sBerryBlender->tilesBuffer, GetDecompressedDataSize(gBerryBlenderOuter_Gfx), 0);
sBerryBlender->loadGfxState++;
break;
case 5:
LZDecompressWram(gBerryBlenderOuter_Tilemap, sBerryBlender->tilesBuffer);
sBerryBlender->loadGfxState++;
break;
case 6:
CopyToBgTilemapBuffer(1, sBerryBlender->tilesBuffer, GetDecompressedDataSize(gBerryBlenderOuter_Tilemap), 0);
CopyBgTilemapBufferToVram(1);
sBerryBlender->loadGfxState++;
break;
case 7:
LoadPalette(sBlenderOuter_Pal, 0x80, 0x20);
sBerryBlender->loadGfxState++;
break;
case 8:
LoadSpriteSheet(&sSpriteSheet_PlayerArrow);
LoadSpriteSheet(&sSpriteSheet_Particles);
LoadSpriteSheet(&sSpriteSheet_ScoreSymbols);
sBerryBlender->loadGfxState++;
break;
case 9:
LoadSpriteSheet(&sSpriteSheet_CountdownNumbers);
LoadSpriteSheet(&sSpriteSheet_Start);
LoadSpritePalette(&sSpritePal_PlayerArrow);
LoadSpritePalette(&sSpritePal_BlenderMisc);
Free(sBerryBlender->tilesBuffer);
sBerryBlender->loadGfxState = 0;
return TRUE;
}
return FALSE;
}
static void DrawBlenderBg(void)
{
FillBgTilemapBufferRect_Palette0(0, 0, 0, 0, 0x1E, 0x14);
CopyBgTilemapBufferToVram(0);
ShowBg(0);
ShowBg(1);
SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_OBJ_ON | DISPCNT_OBJ_1D_MAP);
ChangeBgX(0, 0, 0);
ChangeBgY(0, 0, 0);
ChangeBgX(1, 0, 0);
ChangeBgY(1, 0, 0);
}
static void InitBerryBlenderWindows(void)
{
if (InitWindows(sWindowTemplates))
{
s32 i;
DeactivateAllTextPrinters();
for (i = 0; i < 5; i++)
FillWindowPixelBuffer(i, PIXEL_FILL(0));
FillBgTilemapBufferRect_Palette0(0, 0, 0, 0, 0x1E, 0x14);
Menu_LoadStdPalAt(0xE0);
}
}
// gSpecialVar_0x8004 is the number of NPC opponents
// Set to 0 indicates it's a link blender
void DoBerryBlending(void)
{
if (sBerryBlender == NULL)
sBerryBlender = AllocZeroed(sizeof(*sBerryBlender));
sBerryBlender->gameEndState = 0;
sBerryBlender->mainState = 0;
sBerryBlender->gameEndState = 0;
InitLocalPlayers(gSpecialVar_0x8004);
SetMainCallback2(CB2_LoadBerryBlender);
}
// Show the blender screen initially and prompt to choose a berry
static void CB2_LoadBerryBlender(void)
{
s32 i;
switch (sBerryBlender->mainState)
{
case 0:
SetGpuReg(REG_OFFSET_DISPCNT, 0);
ResetSpriteData();
FreeAllSpritePalettes();
SetVBlankCallback(NULL);
ResetBgsAndClearDma3BusyFlags(0);
InitBgsFromTemplates(1, sBgTemplates, ARRAY_COUNT(sBgTemplates));
SetBgTilemapBuffer(1, sBerryBlender->tilemapBuffers[0]);
SetBgTilemapBuffer(2, sBerryBlender->tilemapBuffers[1]);
LoadUserWindowBorderGfx(0, 1, 0xD0);
LoadMessageBoxGfx(0, 0x14, 0xF0);
InitBerryBlenderWindows();
sBerryBlender->mainState++;
sBerryBlender->maxProgressBarValue = 0;
sBerryBlender->progressBarValue = 0;
sBerryBlender->centerScale = 80;
sBerryBlender->bg_X = 0;
sBerryBlender->bg_Y = 0;
sBerryBlender->loadGfxState = 0;
UpdateBlenderCenter();
break;
case 1:
if (LoadBerryBlenderGfx())
{
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
sBerryBlender->playerArrowSpriteIds[i] = CreateSprite(&sSpriteTemplate_PlayerArrow, sPlayerArrowPos[i][0], sPlayerArrowPos[i][1], 1);
StartSpriteAnim(&gSprites[sBerryBlender->playerArrowSpriteIds[i]], i + 8);
}
if (gReceivedRemoteLinkPlayers && gWirelessCommType)
{
LoadWirelessStatusIndicatorSpriteGfx();
CreateWirelessStatusIndicatorSprite(0, 0);
}
SetVBlankCallback(VBlankCB_BerryBlender);
sBerryBlender->mainState++;
}
break;
case 2:
BeginNormalPaletteFade(0xFFFFFFFF, 0, 0x10, 0, RGB_BLACK);
UpdateBlenderCenter();
sBerryBlender->mainState++;
break;
case 3:
DrawBlenderBg();
if (!gPaletteFade.active)
sBerryBlender->mainState++;
break;
case 4:
if (Blender_PrintText(&sBerryBlender->textState, sText_BerryBlenderStart, GetPlayerTextSpeedDelay()))
sBerryBlender->mainState++;
break;
case 5:
BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 0x10, RGB_BLACK);
sBerryBlender->mainState++;
break;
case 6:
if (!gPaletteFade.active)
{
// Go to bag menu to choose berry, set callback to StartBlender
FreeAllWindowBuffers();
UnsetBgTilemapBuffer(2);
UnsetBgTilemapBuffer(1);
SetVBlankCallback(NULL);
ChooseBerryForMachine(StartBlender);
sBerryBlender->mainState = 0;
}
break;
}
AnimateSprites();
BuildOamBuffer();
RunTextPrinters();
UpdatePaletteFade();
}
#define sTargetY data[0]
#define sX data[1]
#define sY data[2]
#define sBounceSpeed data[3]
#define sYUpSpeed data[4]
#define sBounces data[5]
#define sXSpeed data[6]
#define sYDownSpeed data[7]
// For throwing berries into the machine
static void SpriteCB_Berry(struct Sprite* sprite)
{
sprite->sX += sprite->sXSpeed;
sprite->sY -= sprite->sYUpSpeed;
sprite->sY += sprite->sYDownSpeed;
sprite->sTargetY += sprite->sYDownSpeed;
sprite->sYUpSpeed--;
if (sprite->sTargetY < sprite->sY)
{
sprite->sBounceSpeed = sprite->sYUpSpeed = sprite->sBounceSpeed - 1;
if (++sprite->sBounces > 3)
DestroySprite(sprite);
else
PlaySE(SE_BALL_TRAY_EXIT);
}
sprite->pos1.x = sprite->sX;
sprite->pos1.y = sprite->sY;
}
static void SetBerrySpriteData(struct Sprite* sprite, s16 x, s16 y, s16 bounceSpeed, s16 xSpeed, s16 ySpeed)
{
sprite->sTargetY = y;
sprite->sX = x;
sprite->sY = y;
sprite->sBounceSpeed = bounceSpeed;
sprite->sYUpSpeed = 10;
sprite->sBounces = 0;
sprite->sXSpeed = xSpeed;
sprite->sYDownSpeed = ySpeed;
sprite->callback = SpriteCB_Berry;
}
#undef sTargetY
#undef sX
#undef sY
#undef sBounceSpeed
#undef sYUpSpeed
#undef sBounces
#undef sXSpeed
#undef sYDownSpeed
static void CreateBerrySprite(u16 a0, u8 playerId)
{
u8 spriteId = CreateSpinningBerrySprite(a0 + FIRST_BERRY_INDEX - 10, 0, 80, playerId & 1);
SetBerrySpriteData(&gSprites[spriteId],
sBerrySpriteData[playerId][0],
sBerrySpriteData[playerId][1],
sBerrySpriteData[playerId][2],
sBerrySpriteData[playerId][3],
sBerrySpriteData[playerId][4]);
}
static void ConvertItemToBlenderBerry(struct BlenderBerry* berry, u16 itemId)
{
const struct Berry *berryInfo = GetBerryInfo(ITEM_TO_BERRY(itemId));
berry->itemId = itemId;
StringCopy(berry->name, berryInfo->name);
berry->flavors[FLAVOR_SPICY] = berryInfo->spicy;
berry->flavors[FLAVOR_DRY] = berryInfo->dry;
berry->flavors[FLAVOR_SWEET] = berryInfo->sweet;
berry->flavors[FLAVOR_BITTER] = berryInfo->bitter;
berry->flavors[FLAVOR_SOUR] = berryInfo->sour;
berry->flavors[FLAVOR_COUNT] = berryInfo->smoothness;
}
static void InitLocalPlayers(u8 opponentsNum)
{
switch (opponentsNum)
{
case 0: // Link games have 0 in-game opponents
gInGameOpponentsNo = 0;
break;
case 1:
gInGameOpponentsNo = 1;
sBerryBlender->numPlayers = 2;
StringCopy(gLinkPlayers[0].name, gSaveBlock2Ptr->playerName);
if (!FlagGet(FLAG_HIDE_LILYCOVE_CONTEST_HALL_BLEND_MASTER))
StringCopy(gLinkPlayers[1].name, sBlenderOpponentsNames[BLENDER_MASTER]);
else
StringCopy(gLinkPlayers[1].name, sBlenderOpponentsNames[BLENDER_MISTER]);
gLinkPlayers[0].language = GAME_LANGUAGE;
gLinkPlayers[1].language = GAME_LANGUAGE;
break;
case 2:
gInGameOpponentsNo = 2;
sBerryBlender->numPlayers = 3;
StringCopy(gLinkPlayers[0].name, gSaveBlock2Ptr->playerName);
StringCopy(gLinkPlayers[1].name, sBlenderOpponentsNames[BLENDER_DUDE]);
StringCopy(gLinkPlayers[2].name, sBlenderOpponentsNames[BLENDER_LASSIE]);
gLinkPlayers[0].language = GAME_LANGUAGE;
gLinkPlayers[1].language = GAME_LANGUAGE;
gLinkPlayers[2].language = GAME_LANGUAGE;
break;
case 3:
gInGameOpponentsNo = 3;
sBerryBlender->numPlayers = 4;
StringCopy(gLinkPlayers[0].name, gSaveBlock2Ptr->playerName);
StringCopy(gLinkPlayers[1].name, sBlenderOpponentsNames[BLENDER_MISS]);
StringCopy(gLinkPlayers[2].name, sBlenderOpponentsNames[BLENDER_LADDIE]);
StringCopy(gLinkPlayers[3].name, sBlenderOpponentsNames[BLENDER_LASSIE]);
gLinkPlayers[0].language = GAME_LANGUAGE;
gLinkPlayers[1].language = GAME_LANGUAGE;
gLinkPlayers[2].language = GAME_LANGUAGE;
gLinkPlayers[3].language = GAME_LANGUAGE;
break;
}
}
static void StartBlender(void)
{
s32 i;
SetGpuReg(REG_OFFSET_DISPCNT, 0);
if (sBerryBlender == NULL)
sBerryBlender = AllocZeroed(sizeof(*sBerryBlender));
sBerryBlender->mainState = 0;
sBerryBlender->unk1 = 0;
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
sBerryBlender->chosenItemId[i] = ITEM_NONE;
InitLocalPlayers(gSpecialVar_0x8004);
if (gSpecialVar_0x8004 == 0)
SetMainCallback2(CB2_StartBlenderLink);
else
SetMainCallback2(CB2_StartBlenderLocal);
}
static void CB2_StartBlenderLink(void)
{
s32 i, j;
switch (sBerryBlender->mainState)
{
case 0:
InitBlenderBgs();
gLinkType = LINKTYPE_BERRY_BLENDER;
sBerryBlender->slowdownTimer = 0;
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
sBerryBlender->playerContinueResponses[i] = 0;
for (j = 0; j < NUM_SCORE_TYPES; j++)
{
sBerryBlender->scores[i][j] = 0;
}
}
sBerryBlender->playAgainState = 0;
sBerryBlender->maxRPM = 0;
sBerryBlender->loadGfxState = 0;
sBerryBlender->mainState++;
break;
case 1:
if (LoadBerryBlenderGfx())
{
sBerryBlender->mainState++;
UpdateBlenderCenter();
}
break;
case 2:
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
sBerryBlender->playerArrowSpriteIds2[i] = CreateSprite(&sSpriteTemplate_PlayerArrow, sPlayerArrowPos[i][0], sPlayerArrowPos[i][1], 1);
StartSpriteAnim(&gSprites[sBerryBlender->playerArrowSpriteIds2[i]], i + 8);
}
if (gReceivedRemoteLinkPlayers && gWirelessCommType)
{
LoadWirelessStatusIndicatorSpriteGfx();
CreateWirelessStatusIndicatorSprite(0, 0);
}
sBerryBlender->mainState++;
break;
case 3:
BeginNormalPaletteFade(0xFFFFFFFF, 0, 0x10, 0, RGB_BLACK);
sBerryBlender->mainState++;
break;
case 4:
DrawBlenderBg();
if (!gPaletteFade.active)
{
sBerryBlender->mainState++;
}
break;
case 5:
Blender_PrintText(&sBerryBlender->textState, sText_CommunicationStandby, 0);
sBerryBlender->mainState = 8;
sBerryBlender->framesToWait = 0;
break;
case 8:
// Send berry choice to link partners
sBerryBlender->mainState++;
sBerryBlender->playerToThrowBerry = 0;
ConvertItemToBlenderBerry(&sBerryBlender->blendedBerries[0], gSpecialVar_ItemId);
memcpy(gBlockSendBuffer, &sBerryBlender->blendedBerries[0], sizeof(struct BlenderBerry));
SetLinkStandbyCallback();
sBerryBlender->framesToWait = 0;
break;
case 9:
if (IsLinkTaskFinished())
{
ResetBlockReceivedFlags();
if (GetMultiplayerId() == 0)
SendBlockRequest(4);
sBerryBlender->mainState++;
}
break;
case 10:
if (++sBerryBlender->framesToWait > 20)
{
// Wait for partners' berries
ClearDialogWindowAndFrameToTransparent(4, TRUE);
if (GetBlockReceivedStatus() == GetLinkPlayerCountAsBitFlags())
{
for (i = 0; i < GetLinkPlayerCount(); i++)
{
memcpy(&sBerryBlender->blendedBerries[i], &gBlockRecvBuffer[i][0], sizeof(struct BlenderBerry));
sBerryBlender->chosenItemId[i] = sBerryBlender->blendedBerries[i].itemId;
}
ResetBlockReceivedFlags();
sBerryBlender->mainState++;
}
}
break;
case 11:
sBerryBlender->numPlayers = GetLinkPlayerCount();
// Throw 1 player's berry in
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
if (sBerryBlender->playerToThrowBerry == sPlayerIdMap[sBerryBlender->numPlayers - 2][i])
{
CreateBerrySprite(sBerryBlender->chosenItemId[sBerryBlender->playerToThrowBerry], i);
break;
}
}
sBerryBlender->framesToWait = 0;
sBerryBlender->mainState++;
sBerryBlender->playerToThrowBerry++;
break;
case 12:
if (++sBerryBlender->framesToWait > 60)
{
if (sBerryBlender->playerToThrowBerry >= sBerryBlender->numPlayers)
{
// Finished throwing berries in
sBerryBlender->mainState++;
sBerryBlender->arrowPos = sArrowStartPos[sArrowStartPosIds[sBerryBlender->numPlayers - 2]] - ARROW_FALL_ROTATION;
}
else
{
// Haven't finished throwing berries in, go back to prev step
sBerryBlender->mainState--;
}
sBerryBlender->framesToWait = 0;
}
break;
case 13:
if (IsLinkTaskFinished())
{
sBerryBlender->mainState++;
DrawBlenderCenter(&sBerryBlender->bgAffineSrc);
PlaySE(SE_FALL);
ShowBg(2);
}
break;
case 14:
SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG2_ON);
sBerryBlender->arrowPos += 0x200;
sBerryBlender->centerScale += 4;
if (sBerryBlender->centerScale > 255)
{
SetGpuRegBits(REG_OFFSET_BG2CNT, 2);
sBerryBlender->mainState++;
sBerryBlender->centerScale = 256;
sBerryBlender->arrowPos = sArrowStartPos[sArrowStartPosIds[sBerryBlender->numPlayers - 2]];
sBerryBlender->framesToWait = 0;
PlaySE(SE_TRUCK_DOOR);
SetPlayerIdMaps();
PrintPlayerNames();
}
DrawBlenderCenter(&sBerryBlender->bgAffineSrc);
break;
case 15:
if (UpdateBlenderLandScreenShake())
{
sBerryBlender->framesToWait = 0;
sBerryBlender->mainState++;
}
DrawBlenderCenter(&sBerryBlender->bgAffineSrc);
break;
case 16:
CreateSprite(&sSpriteTemplate_CountdownNumbers, 120, -16, 3);
sBerryBlender->mainState++;
break;
case 17:
// Wait here for the countdown
// State is progressed in SpriteCB_Start
break;
case 18:
sBerryBlender->mainState++;
break;
case 19:
SetLinkStandbyCallback();
sBerryBlender->mainState++;
break;
case 20:
if (IsLinkTaskFinished())
{
SetBerryBlenderLinkCallback();
sBerryBlender->mainState++;
}
break;
case 21:
sBerryBlender->speed = MIN_ARROW_SPEED;
sBerryBlender->gameFrameTime = 0;
SetMainCallback2(CB2_PlayBlender);
if (GetCurrentMapMusic() != MUS_CYCLING)
{
sBerryBlender->savedMusic = GetCurrentMapMusic();
}
PlayBGM(MUS_CYCLING);
break;
}
Blender_DummiedOutFunc(sBerryBlender->bg_X, sBerryBlender->bg_Y);
RunTasks();
AnimateSprites();
BuildOamBuffer();
RunTextPrinters();
UpdatePaletteFade();
}
static void InitBlenderBgs(void)
{
SetGpuReg(REG_OFFSET_DISPCNT, 0);
ResetSpriteData();
FreeAllSpritePalettes();
ResetTasks();
SetVBlankCallback(VBlankCB_BerryBlender);
ResetBgsAndClearDma3BusyFlags(0);
InitBgsFromTemplates(1, sBgTemplates, ARRAY_COUNT(sBgTemplates));
SetBgTilemapBuffer(1, sBerryBlender->tilemapBuffers[0]);
SetBgTilemapBuffer(2, sBerryBlender->tilemapBuffers[1]);
LoadUserWindowBorderGfx(0, 1, 0xD0);
LoadMessageBoxGfx(0, 0x14, 0xF0);
InitBerryBlenderWindows();
sBerryBlender->unk0 = 0;
sBerryBlender->speed = 0;
sBerryBlender->arrowPos = 0;
sBerryBlender->maxRPM = 0;
sBerryBlender->bg_X = 0;
sBerryBlender->bg_Y = 0;
}
static u8 GetArrowProximity(u16 arrowPos, u8 playerId)
{
u32 pos = (arrowPos / 256) + 24;
u8 arrowId = sBerryBlender->playerIdToArrowId[playerId];
u32 hitRangeStart = sArrowHitRangeStart[arrowId];
if (pos >= hitRangeStart && pos < hitRangeStart + 48)
{
if (pos >= hitRangeStart + 20 && pos < hitRangeStart + 28)
return PROXIMITY_BEST;
else
return PROXIMITY_GOOD;
}
return PROXIMITY_MISS;
}
static void SetOpponentsBerryData(u16 playerBerryItemId, u8 playersNum, struct BlenderBerry* playerBerry)
{
u16 opponentSetId = 0;
u16 opponentBerryId;
u16 berryMasterDiff;
u16 i;
if (playerBerryItemId == ITEM_ENIGMA_BERRY)
{
for (i = 0; i < FLAVOR_COUNT; i++)
{
if (playerBerry->flavors[opponentSetId] > playerBerry->flavors[i])
opponentSetId = i;
}
opponentSetId += NUM_NPC_BERRIES;
}
else
{
opponentSetId = ITEM_TO_BERRY(playerBerryItemId) - 1;
if (opponentSetId >= NUM_NPC_BERRIES)
opponentSetId = (opponentSetId % NUM_NPC_BERRIES) + NUM_NPC_BERRIES;
}
for (i = 0; i < playersNum - 1; i++)
{
opponentBerryId = sOpponentBerrySets[opponentSetId][i];
berryMasterDiff = ITEM_TO_BERRY(playerBerryItemId) - ITEM_TO_BERRY(ITEM_SPELON_BERRY);
if (!FlagGet(FLAG_HIDE_LILYCOVE_CONTEST_HALL_BLEND_MASTER) && gSpecialVar_0x8004 == 1)
{
opponentSetId %= ARRAY_COUNT(sBerryMasterBerries);
opponentBerryId = sBerryMasterBerries[opponentSetId];
// If the player's berry is any of the Berry Master's berries,
// then use the next lower set of berries
if (berryMasterDiff < ARRAY_COUNT(sBerryMasterBerries))
opponentBerryId -= ARRAY_COUNT(sBerryMasterBerries);
}
SetPlayerBerryData(i + 1, opponentBerryId + FIRST_BERRY_INDEX);
}
}
static void SetPlayerIdMaps(void)
{
s32 i, j;
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
sBerryBlender->playerIdToArrowId[i] = NO_PLAYER;
sBerryBlender->arrowIdToPlayerId[i] = sPlayerIdMap[sBerryBlender->numPlayers - 2][i];
}
for (j = 0; j < BLENDER_MAX_PLAYERS; j++)
{
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
if (sBerryBlender->arrowIdToPlayerId[i] == j)
sBerryBlender->playerIdToArrowId[j] = i;
}
}
}
static void PrintPlayerNames(void)
{
s32 i, xPos;
u32 playerId = 0;
u8 text[20];
if (gReceivedRemoteLinkPlayers)
playerId = GetMultiplayerId();
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
if (sBerryBlender->arrowIdToPlayerId[i] != NO_PLAYER)
{
sBerryBlender->playerArrowSpriteIds[sBerryBlender->arrowIdToPlayerId[i]] = sBerryBlender->playerArrowSpriteIds2[i];
StartSpriteAnim(&gSprites[sBerryBlender->playerArrowSpriteIds[sBerryBlender->arrowIdToPlayerId[i]]], i);
text[0] = EOS;
StringCopy(text, gLinkPlayers[sBerryBlender->arrowIdToPlayerId[i]].name);
xPos = GetStringCenterAlignXOffset(1, text, 0x38);
if (playerId == sBerryBlender->arrowIdToPlayerId[i])
Blender_AddTextPrinter(i, text, xPos, 1, 0, 2); // Highlight player's name in red
else
Blender_AddTextPrinter(i, text, xPos, 1, 0, 1);
PutWindowTilemap(i);
CopyWindowToVram(i, 3);
}
}
}
static void CB2_StartBlenderLocal(void)
{
s32 i, j;
switch (sBerryBlender->mainState)
{
case 0:
SetWirelessCommType0();
InitBlenderBgs();
SetPlayerBerryData(0, gSpecialVar_ItemId);
ConvertItemToBlenderBerry(&sBerryBlender->blendedBerries[0], gSpecialVar_ItemId);
SetOpponentsBerryData(gSpecialVar_ItemId, sBerryBlender->numPlayers, &sBerryBlender->blendedBerries[0]);
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
sBerryBlender->playerContinueResponses[i] = 0;
for (j = 0; j < NUM_SCORE_TYPES; j++)
{
sBerryBlender->scores[i][j] = 0;
}
}
sBerryBlender->playAgainState = 0;
sBerryBlender->loadGfxState = 0;
gLinkType = LINKTYPE_BERRY_BLENDER;
sBerryBlender->mainState++;
break;
case 1:
if (LoadBerryBlenderGfx())
{
sBerryBlender->mainState++;
UpdateBlenderCenter();
}
break;
case 2:
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
sBerryBlender->playerArrowSpriteIds2[i] = CreateSprite(&sSpriteTemplate_PlayerArrow, sPlayerArrowPos[i][0], sPlayerArrowPos[i][1], 1);
StartSpriteAnim(&gSprites[sBerryBlender->playerArrowSpriteIds2[i]], i + 8);
}
sBerryBlender->mainState++;
break;
case 3:
BeginNormalPaletteFade(0xFFFFFFFF, 0, 0x10, 0, RGB_BLACK);
sBerryBlender->mainState++;
sBerryBlender->framesToWait = 0;
break;
case 4:
if (++sBerryBlender->framesToWait == 2)
DrawBlenderBg();
if (!gPaletteFade.active)
sBerryBlender->mainState = 8;
break;
case 8:
sBerryBlender->mainState = 11;
sBerryBlender->playerToThrowBerry = 0;
break;
case 11:
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
// Throw 1 player's berry in
u32 playerId = sPlayerIdMap[sBerryBlender->numPlayers - 2][i];
if (sBerryBlender->playerToThrowBerry == playerId)
{
CreateBerrySprite(sBerryBlender->chosenItemId[sBerryBlender->playerToThrowBerry], i);
break;
}
}
sBerryBlender->framesToWait = 0;
sBerryBlender->mainState++;
sBerryBlender->playerToThrowBerry++;
break;
case 12:
if (++sBerryBlender->framesToWait > 60)
{
if (sBerryBlender->playerToThrowBerry >= sBerryBlender->numPlayers)
{
// Finished throwing berries in
sBerryBlender->arrowPos = sArrowStartPos[sArrowStartPosIds[sBerryBlender->numPlayers - 2]] - ARROW_FALL_ROTATION;
sBerryBlender->mainState++;
}
else
{
// Haven't finished throwing berries in, go back to prev step
sBerryBlender->mainState--;
}
sBerryBlender->framesToWait = 0;
}
break;
case 13:
sBerryBlender->mainState++;
SetPlayerIdMaps();
PlaySE(SE_FALL);
DrawBlenderCenter(&sBerryBlender->bgAffineSrc);
ShowBg(2);
break;
case 14:
SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG2_ON);
sBerryBlender->arrowPos += 0x200;
sBerryBlender->centerScale += 4;
if (sBerryBlender->centerScale > 255)
{
sBerryBlender->mainState++;
sBerryBlender->centerScale = 256;
sBerryBlender->arrowPos = sArrowStartPos[sArrowStartPosIds[sBerryBlender->numPlayers - 2]];
SetGpuRegBits(REG_OFFSET_BG2CNT, 2);
sBerryBlender->framesToWait = 0;
PlaySE(SE_TRUCK_DOOR);
PrintPlayerNames();
}
DrawBlenderCenter(&sBerryBlender->bgAffineSrc);
break;
case 15:
if (UpdateBlenderLandScreenShake())
{
sBerryBlender->mainState++;
}
DrawBlenderCenter(&sBerryBlender->bgAffineSrc);
break;
case 16:
CreateSprite(&sSpriteTemplate_CountdownNumbers, 120, -16, 3);
sBerryBlender->mainState++;
break;
case 17:
// Wait here for the countdown
// State is progressed in SpriteCB_Start
break;
case 18:
sBerryBlender->mainState++;
break;
case 19:
sBerryBlender->mainState++;
break;
case 20:
sBerryBlender->mainState++;
break;
case 21:
ResetLinkCmds();
sBerryBlender->speed = MIN_ARROW_SPEED;
sBerryBlender->gameFrameTime = 0;
sBerryBlender->perfectOpponents = FALSE;
sBerryBlender->slowdownTimer = 0;
SetMainCallback2(CB2_PlayBlender);
if (gSpecialVar_0x8004 == 1)
{
if (!FlagGet(FLAG_HIDE_LILYCOVE_CONTEST_HALL_BLEND_MASTER))
sBerryBlender->opponentTaskIds[0] = CreateTask(Task_HandleBerryMaster, 10);
else
sBerryBlender->opponentTaskIds[0] = CreateTask(sLocalOpponentTasks[0], 10);
}
if (gSpecialVar_0x8004 > 1)
{
for (i = 0; i < gSpecialVar_0x8004; i++)
sBerryBlender->opponentTaskIds[i] = CreateTask(sLocalOpponentTasks[i], 10 + i);
}
if (GetCurrentMapMusic() != MUS_CYCLING)
sBerryBlender->savedMusic = GetCurrentMapMusic();
PlayBGM(MUS_CYCLING);
PlaySE(SE_BERRY_BLENDER);
UpdateHitPitch();
break;
}
Blender_DummiedOutFunc(sBerryBlender->bg_X, sBerryBlender->bg_Y);
RunTasks();
AnimateSprites();
BuildOamBuffer();
RunTextPrinters();
UpdatePaletteFade();
}
static void ResetLinkCmds(void)
{
s32 i;
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
gSendCmd[BLENDER_COMM_INPUT_STATE] = 0;
gSendCmd[BLENDER_COMM_SCORE] = 0;
gRecvCmds[i][BLENDER_COMM_INPUT_STATE] = 0;
gRecvCmds[i][BLENDER_COMM_SCORE] = 0;
}
}
#define tTimer data[0]
#define tDelay data[1]
#define tPlayerId data[2]
static void Task_OpponentMiss(u8 taskId)
{
if(++gTasks[taskId].tTimer > gTasks[taskId].tDelay)
{
gRecvCmds[gTasks[taskId].tPlayerId][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_MISS;
DestroyTask(taskId);
}
}
static void CreateOpponentMissTask(u8 playerId, u8 delay)
{
u8 taskId = CreateTask(Task_OpponentMiss, 80);
gTasks[taskId].tDelay = delay;
gTasks[taskId].tPlayerId = playerId;
}
#undef tTimer
#undef tDelay
#undef tPlayerId
#define tDidInput data[0]
static void Task_HandleOpponent1(u8 taskId)
{
if (GetArrowProximity(sBerryBlender->arrowPos, 1) == PROXIMITY_BEST)
{
if (!gTasks[taskId].tDidInput)
{
if (!sBerryBlender->perfectOpponents)
{
u8 rand = Random() / 655;
if (sBerryBlender->speed < 500)
{
if (rand > 75)
gRecvCmds[1][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_BEST;
else
gRecvCmds[1][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_GOOD;
// BUG: Overrwrote above assignment. Opponent 1 can't get Best at low speed
gRecvCmds[1][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_GOOD;
}
else if (sBerryBlender->speed < 1500)
{
if (rand > 80)
{
gRecvCmds[1][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_BEST;
}
else
{
u8 value = rand - 21;
if (value < 60)
gRecvCmds[1][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_GOOD;
else if (rand < 10)
CreateOpponentMissTask(1, 5);
}
}
else if (rand <= 90)
{
u8 value = rand - 71;
if (value < 20)
gRecvCmds[1][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_GOOD;
else if (rand < 30)
CreateOpponentMissTask(1, 5);
}
else
{
gRecvCmds[1][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_BEST;
}
}
else
{
gRecvCmds[1][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_BEST;
}
gTasks[taskId].tDidInput = TRUE;
}
}
else
{
gTasks[taskId].tDidInput = FALSE;
}
}
static void Task_HandleOpponent2(u8 taskId)
{
u32 var1 = (sBerryBlender->arrowPos + 0x1800) & 0xFFFF;
u32 arrowId = sBerryBlender->playerIdToArrowId[2] & 0xFF;
if ((var1 >> 8) > sArrowHitRangeStart[arrowId] + 20 && (var1 >> 8) < sArrowHitRangeStart[arrowId] + 40)
{
if (!gTasks[taskId].tDidInput)
{
if (!sBerryBlender->perfectOpponents)
{
u8 rand = Random() / 655;
if (sBerryBlender->speed < 500)
{
if (rand > 66)
gRecvCmds[2][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_BEST;
else
gRecvCmds[2][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_GOOD;
}
else
{
u8 value;
if (rand > 65)
gRecvCmds[2][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_BEST;
value = rand - 41;
if (value < 25)
gRecvCmds[2][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_GOOD;
if (rand < 10)
CreateOpponentMissTask(2, 5);
}
gTasks[taskId].tDidInput = TRUE;
}
else
{
gRecvCmds[2][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_BEST;
gTasks[taskId].tDidInput = TRUE;
}
}
}
else
{
gTasks[taskId].tDidInput = FALSE;
}
}
static void Task_HandleOpponent3(u8 taskId)
{
u32 var1 = (sBerryBlender->arrowPos + 0x1800) & 0xFFFF;
u32 arrowId = sBerryBlender->playerIdToArrowId[3] & 0xFF;
if ((var1 >> 8) > sArrowHitRangeStart[arrowId] + 20 && (var1 >> 8) < sArrowHitRangeStart[arrowId] + 40)
{
if (gTasks[taskId].data[0] == 0)
{
if (!sBerryBlender->perfectOpponents)
{
u8 rand = (Random() / 655);
if (sBerryBlender->speed < 500)
{
if (rand > 88)
gRecvCmds[3][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_BEST;
else
gRecvCmds[3][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_GOOD;
}
else
{
if (rand > 60)
{
gRecvCmds[3][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_BEST;
}
else
{
s8 value = rand - 56; // makes me wonder what the original code was
u8 value2 = value;
if (value2 < 5)
gRecvCmds[3][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_GOOD;
}
if (rand < 5)
CreateOpponentMissTask(3, 5);
}
gTasks[taskId].tDidInput = TRUE;
}
else
{
gRecvCmds[3][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_BEST;
gTasks[taskId].tDidInput = TRUE;
}
}
}
else
{
gTasks[taskId].tDidInput = FALSE;
}
}
static void Task_HandleBerryMaster(u8 taskId)
{
if (GetArrowProximity(sBerryBlender->arrowPos, 1) == PROXIMITY_BEST)
{
if (!gTasks[taskId].tDidInput)
{
gRecvCmds[1][BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_BEST;
gTasks[taskId].tDidInput = TRUE;
}
}
else
{
gTasks[taskId].tDidInput = FALSE;
}
}
#undef tDidInput
static void CreateScoreSymbolSprite(u16 cmd, u8 arrowId)
{
u8 spriteId;
spriteId = CreateSprite(&sSpriteTemplate_ScoreSymbols,
sPlayerArrowPos[arrowId][0] - (10 * sPlayerArrowQuadrant[arrowId][0]),
sPlayerArrowPos[arrowId][1] - (10 * sPlayerArrowQuadrant[arrowId][1]),
1);
if (cmd == LINKCMD_BLENDER_SCORE_BEST)
{
StartSpriteAnim(&gSprites[spriteId], SCOREANIM_BEST_FLASH);
gSprites[spriteId].callback = SpriteCB_ScoreSymbolBest;
PlaySE(SE_ICE_STAIRS);
}
else if (cmd == LINKCMD_BLENDER_SCORE_GOOD)
{
StartSpriteAnim(&gSprites[spriteId], SCOREANIM_GOOD);
PlaySE(SE_SUCCESS);
}
else if (cmd == LINKCMD_BLENDER_SCORE_MISS)
{
StartSpriteAnim(&gSprites[spriteId], SCOREANIM_MISS);
PlaySE(SE_FAILURE);
}
CreateParticleSprites();
}
static void UpdateSpeedFromHit(u16 cmd)
{
UpdateHitPitch();
switch (cmd)
{
case LINKCMD_BLENDER_SCORE_BEST:
if (sBerryBlender->speed < 1500) {
sBerryBlender->speed += (384 / sNumPlayersToSpeedDivisor[sBerryBlender->numPlayers]);
}
else
{
sBerryBlender->speed += (128 / sNumPlayersToSpeedDivisor[sBerryBlender->numPlayers]);
ShakeBgCoordForHit(&sBerryBlender->bg_X, (sBerryBlender->speed / 100) - 10);
ShakeBgCoordForHit(&sBerryBlender->bg_Y, (sBerryBlender->speed / 100) - 10);
}
break;
case LINKCMD_BLENDER_SCORE_GOOD:
if (sBerryBlender->speed < 1500)
sBerryBlender->speed += (256 / sNumPlayersToSpeedDivisor[sBerryBlender->numPlayers]);
break;
case LINKCMD_BLENDER_SCORE_MISS:
sBerryBlender->speed -= (256 / sNumPlayersToSpeedDivisor[sBerryBlender->numPlayers]);
if (sBerryBlender->speed < MIN_ARROW_SPEED)
sBerryBlender->speed = MIN_ARROW_SPEED;
break;
}
}
// Return TRUE if the received command matches the corresponding Link or RFU command
static bool32 CheckRecvCmdMatches(u16 recvCmd, u16 linkCmd, u16 rfuCmd)
{
if (gReceivedRemoteLinkPlayers && gWirelessCommType)
{
if ((recvCmd & 0xFF00) == rfuCmd)
return TRUE;
}
else
{
if (recvCmd == linkCmd)
return TRUE;
}
return FALSE;
}
static void UpdateOpponentScores(void)
{
s32 i;
if (gSpecialVar_0x8004 != 0)
{
// Local game, "send" players score to itself
if (gSendCmd[BLENDER_COMM_SCORE] != 0)
{
gRecvCmds[0][BLENDER_COMM_SCORE] = gSendCmd[BLENDER_COMM_SCORE];
gRecvCmds[0][BLENDER_COMM_INPUT_STATE] = LINKCMD_BLENDER_SEND_KEYS;
gSendCmd[BLENDER_COMM_SCORE] = 0;
}
// Local game, simulate NPCs sending keys
// Their actual inputs are handled by Task_HandleOpponent
for (i = 1; i < BLENDER_MAX_PLAYERS; i++)
{
if (gRecvCmds[i][BLENDER_COMM_SCORE] != 0)
gRecvCmds[i][BLENDER_COMM_INPUT_STATE] = LINKCMD_BLENDER_SEND_KEYS;
}
}
for (i = 0; i < sBerryBlender->numPlayers; i++)
{
if (CheckRecvCmdMatches(gRecvCmds[i][BLENDER_COMM_INPUT_STATE], LINKCMD_BLENDER_SEND_KEYS, RFUCMD_BLENDER_SEND_KEYS))
{
u32 arrowId = sBerryBlender->playerIdToArrowId[i];
if (gRecvCmds[i][BLENDER_COMM_SCORE] == LINKCMD_BLENDER_SCORE_BEST)
{
UpdateSpeedFromHit(LINKCMD_BLENDER_SCORE_BEST);
sBerryBlender->progressBarValue += (sBerryBlender->speed / 55);
if (sBerryBlender->progressBarValue >= MAX_PROGRESS_BAR)
sBerryBlender->progressBarValue = MAX_PROGRESS_BAR;
CreateScoreSymbolSprite(LINKCMD_BLENDER_SCORE_BEST, arrowId);
sBerryBlender->scores[i][SCORE_BEST]++;
}
else if (gRecvCmds[i][BLENDER_COMM_SCORE] == LINKCMD_BLENDER_SCORE_GOOD)
{
UpdateSpeedFromHit(LINKCMD_BLENDER_SCORE_GOOD);
sBerryBlender->progressBarValue += (sBerryBlender->speed / 70);
CreateScoreSymbolSprite(LINKCMD_BLENDER_SCORE_GOOD, arrowId);
sBerryBlender->scores[i][SCORE_GOOD]++;
}
else if (gRecvCmds[i][BLENDER_COMM_SCORE] == LINKCMD_BLENDER_SCORE_MISS)
{
CreateScoreSymbolSprite(LINKCMD_BLENDER_SCORE_MISS, arrowId);
UpdateSpeedFromHit(LINKCMD_BLENDER_SCORE_MISS);
if (sBerryBlender->scores[i][SCORE_MISS] < 999)
sBerryBlender->scores[i][SCORE_MISS]++;
}
// BUG: Should [i][BLENDER_COMM_SCORE] below, not [BLENDER_COMM_SCORE][i]
// As a result the music tempo updates if any player misses, but only if 1 specific player hits
if (gRecvCmds[i][BLENDER_COMM_SCORE] == LINKCMD_BLENDER_SCORE_MISS
|| gRecvCmds[BLENDER_COMM_SCORE][i] == LINKCMD_BLENDER_SCORE_BEST
|| gRecvCmds[BLENDER_COMM_SCORE][i] == LINKCMD_BLENDER_SCORE_GOOD)
{
if (sBerryBlender->speed > 1500)
m4aMPlayTempoControl(&gMPlayInfo_BGM, ((sBerryBlender->speed - 750) / 20) + 256);
else
m4aMPlayTempoControl(&gMPlayInfo_BGM, 256);
}
}
}
if (gSpecialVar_0x8004 != 0)
{
for (i = 0; i < sBerryBlender->numPlayers; i++)
{
gRecvCmds[i][BLENDER_COMM_INPUT_STATE] = 0;
gRecvCmds[i][BLENDER_COMM_SCORE] = 0;
}
}
}
static void HandlePlayerInput(void)
{
u8 arrowId;
bool8 pressedA = FALSE;
u8 playerId = 0;
if (gReceivedRemoteLinkPlayers)
playerId = GetMultiplayerId();
arrowId = sBerryBlender->playerIdToArrowId[playerId];
if (sBerryBlender->gameEndState == 0)
{
if (gSaveBlock2Ptr->optionsButtonMode == OPTIONS_BUTTON_MODE_L_EQUALS_A && JOY_NEW(A_BUTTON))
{
if (JOY_HELD_RAW(A_BUTTON | L_BUTTON) != (A_BUTTON | L_BUTTON))
pressedA = TRUE;
}
else if (JOY_NEW(A_BUTTON))
{
pressedA = TRUE;
}
if (pressedA)
{
u8 proximity;
StartSpriteAnim(&gSprites[sBerryBlender->playerArrowSpriteIds[sBerryBlender->arrowIdToPlayerId[arrowId]]], arrowId + 4);
proximity = GetArrowProximity(sBerryBlender->arrowPos, playerId);
if (proximity == PROXIMITY_BEST)
gSendCmd[BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_BEST;
else if (proximity == PROXIMITY_GOOD)
gSendCmd[BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_GOOD;
else
gSendCmd[BLENDER_COMM_SCORE] = LINKCMD_BLENDER_SCORE_MISS;
}
}
if (++sBerryBlender->slowdownTimer > 5)
{
if (sBerryBlender->speed > MIN_ARROW_SPEED)
sBerryBlender->speed--;
sBerryBlender->slowdownTimer = 0;
}
if (gEnableContestDebugging && JOY_NEW(L_BUTTON))
sBerryBlender->perfectOpponents ^= 1;
}
static void CB2_PlayBlender(void)
{
UpdateBlenderCenter();
if (sBerryBlender->gameFrameTime < (99 * 60 * 60) + (59 * 60)) // game time can't be longer than 99 minutes and 59 seconds, can't print 3 digits
sBerryBlender->gameFrameTime++;
HandlePlayerInput();
SetLinkDebugValues((u16)(sBerryBlender->speed), sBerryBlender->progressBarValue);
UpdateOpponentScores();
TryUpdateProgressBar(sBerryBlender->progressBarValue, MAX_PROGRESS_BAR);
UpdateRPM(sBerryBlender->speed);
RestoreBgCoords();
ProcessLinkPlayerCmds();
if (sBerryBlender->gameEndState == 0 && sBerryBlender->maxProgressBarValue >= MAX_PROGRESS_BAR)
{
sBerryBlender->progressBarValue = MAX_PROGRESS_BAR;
sBerryBlender->gameEndState = 1;
SetMainCallback2(CB2_EndBlenderGame);
}
Blender_DummiedOutFunc(sBerryBlender->bg_X, sBerryBlender->bg_Y);
RunTasks();
AnimateSprites();
BuildOamBuffer();
RunTextPrinters();
UpdatePaletteFade();
}
static void Blender_DummiedOutFunc(s16 a0, s16 a1)
{
}
static bool8 AreBlenderBerriesSame(struct BlenderBerry* berries, u8 a, u8 b)
{
// First check to itemId is pointless (and wrong anyway?), always false when this is called
// Only used to determine if two enigma berries are equivalent
if (berries[a].itemId != berries[b].itemId
|| (StringCompare(berries[a].name, berries[b].name) == 0
&& (berries[a].flavors[FLAVOR_SPICY] == berries[b].flavors[FLAVOR_SPICY]
&& berries[a].flavors[FLAVOR_DRY] == berries[b].flavors[FLAVOR_DRY]
&& berries[a].flavors[FLAVOR_SWEET] == berries[b].flavors[FLAVOR_SWEET]
&& berries[a].flavors[FLAVOR_BITTER] == berries[b].flavors[FLAVOR_BITTER]
&& berries[a].flavors[FLAVOR_SOUR] == berries[b].flavors[FLAVOR_SOUR]
&& berries[a].flavors[FLAVOR_COUNT] == berries[b].flavors[FLAVOR_COUNT])))
return TRUE;
else
return FALSE;
}
static u32 CalculatePokeblockColor(struct BlenderBerry* berries, s16* _flavors, u8 numPlayers, u8 negativeFlavors)
{
s16 flavors[FLAVOR_COUNT + 1];
s32 i, j;
u8 numFlavors;
for (i = 0; i < FLAVOR_COUNT + 1; i++)
flavors[i] = _flavors[i];
j = 0;
for (i = 0; i < FLAVOR_COUNT; i++)
{
if (flavors[i] == 0)
j++;
}
// If all flavors are 0, or at least 3 were negative/0
// or if players used the same berry, color is black
if (j == 5 || negativeFlavors > 3)
return PBLOCK_CLR_BLACK;
for (i = 0; i < numPlayers; i++)
{
for (j = 0; j < numPlayers; j++)
{
if (berries[i].itemId == berries[j].itemId && i != j
&& (berries[i].itemId != ITEM_ENIGMA_BERRY || AreBlenderBerriesSame(berries, i, j)))
return PBLOCK_CLR_BLACK;
}
}
numFlavors = 0;
for (numFlavors = 0, i = 0; i < FLAVOR_COUNT; i++)
{
if (flavors[i] > 0)
numFlavors++;
}
// Check for special colors (White/Gray/Gold)
if (numFlavors > 3)
return PBLOCK_CLR_WHITE;
if (numFlavors == 3)
return PBLOCK_CLR_GRAY;
for (i = 0; i < FLAVOR_COUNT; i++)
{
if (flavors[i] > 50)
return PBLOCK_CLR_GOLD;
}
// Only 1 flavor present, return corresponding color
if (numFlavors == 1 && flavors[FLAVOR_SPICY] > 0)
return PBLOCK_CLR_RED;
if (numFlavors == 1 && flavors[FLAVOR_DRY] > 0)
return PBLOCK_CLR_BLUE;
if (numFlavors == 1 && flavors[FLAVOR_SWEET] > 0)
return PBLOCK_CLR_PINK;
if (numFlavors == 1 && flavors[FLAVOR_BITTER] > 0)
return PBLOCK_CLR_GREEN;
if (numFlavors == 1 && flavors[FLAVOR_SOUR] > 0)
return PBLOCK_CLR_YELLOW;
if (numFlavors == 2)
{
// Determine which 2 flavors are present
s32 idx = 0;
for (i = 0; i < FLAVOR_COUNT; i++)
{
if (flavors[i] > 0)
sPokeblockPresentFlavors[idx++] = i;
}
// Use the stronger flavor to determine color
// The weaker flavor is returned in the upper 16 bits, but this is ignored in the color assignment
if (flavors[sPokeblockPresentFlavors[0]] >= flavors[sPokeblockPresentFlavors[1]])
{
if (sPokeblockPresentFlavors[0] == FLAVOR_SPICY)
return (sPokeblockPresentFlavors[1] << 16) | PBLOCK_CLR_PURPLE;
if (sPokeblockPresentFlavors[0] == FLAVOR_DRY)
return (sPokeblockPresentFlavors[1] << 16) | PBLOCK_CLR_INDIGO;
if (sPokeblockPresentFlavors[0] == FLAVOR_SWEET)
return (sPokeblockPresentFlavors[1] << 16) | PBLOCK_CLR_BROWN;
if (sPokeblockPresentFlavors[0] == FLAVOR_BITTER)
return (sPokeblockPresentFlavors[1] << 16) | PBLOCK_CLR_LITE_BLUE;
if (sPokeblockPresentFlavors[0] == FLAVOR_SOUR)
return (sPokeblockPresentFlavors[1] << 16) | PBLOCK_CLR_OLIVE;
}
else
{
if (sPokeblockPresentFlavors[1] == FLAVOR_SPICY)
return (sPokeblockPresentFlavors[0] << 16) | PBLOCK_CLR_PURPLE;
if (sPokeblockPresentFlavors[1] == FLAVOR_DRY)
return (sPokeblockPresentFlavors[0] << 16) | PBLOCK_CLR_INDIGO;
if (sPokeblockPresentFlavors[1] == FLAVOR_SWEET)
return (sPokeblockPresentFlavors[0] << 16) | PBLOCK_CLR_BROWN;
if (sPokeblockPresentFlavors[1] == FLAVOR_BITTER)
return (sPokeblockPresentFlavors[0] << 16) | PBLOCK_CLR_LITE_BLUE;
if (sPokeblockPresentFlavors[1] == FLAVOR_SOUR)
return (sPokeblockPresentFlavors[0] << 16) | PBLOCK_CLR_OLIVE;
}
}
return PBLOCK_CLR_NONE;
}
static void Debug_SetMaxRPMStage(s16 value)
{
sDebug_MaxRPMStage = value;
}
// Unused
static s16 Debug_GetMaxRPMStage(void)
{
return sDebug_MaxRPMStage;
}
static void Debug_SetGameTimeStage(s16 value)
{
sDebug_GameTimeStage = value;
}
// Unued
static s16 Debug_GetGameTimeStage(void)
{
return sDebug_GameTimeStage;
}
static void CalculatePokeblock(struct BlenderBerry *berries, struct Pokeblock *pokeblock, u8 numPlayers, u8 *flavors, u16 maxRPM)
{
s32 i, j;
s32 multiuseVar, var2;
u8 numNegatives;
for (i = 0; i < FLAVOR_COUNT + 1; i++)
sPokeblockFlavors[i] = 0;
// Add up the flavor + feel of each players berry
for (i = 0; i < numPlayers; i++)
{
for (j = 0; j < FLAVOR_COUNT + 1; j++)
sPokeblockFlavors[j] += berries[i].flavors[j];
}
// Subtract each flavor total from the prev one
// The idea is to focus on only the flavors with the highest totals
// Bad way to do it though (order matters here)
multiuseVar = sPokeblockFlavors[0];
sPokeblockFlavors[FLAVOR_SPICY] -= sPokeblockFlavors[FLAVOR_DRY];
sPokeblockFlavors[FLAVOR_DRY] -= sPokeblockFlavors[FLAVOR_SWEET];
sPokeblockFlavors[FLAVOR_SWEET] -= sPokeblockFlavors[FLAVOR_BITTER];
sPokeblockFlavors[FLAVOR_BITTER] -= sPokeblockFlavors[FLAVOR_SOUR];
sPokeblockFlavors[FLAVOR_SOUR] -= multiuseVar;
// Count (and reset) the resulting negative flavors
multiuseVar = 0;
for (i = 0; i < FLAVOR_COUNT; i++)
{
if (sPokeblockFlavors[i] < 0)
{
sPokeblockFlavors[i] = 0;
multiuseVar++;
}
}
numNegatives = multiuseVar;
// Subtract the number of negative flavor totals from each positive total (without going below 0)
for (i = 0; i < FLAVOR_COUNT; i++)
{
if (sPokeblockFlavors[i] > 0)
{
if (sPokeblockFlavors[i] < multiuseVar)
sPokeblockFlavors[i] = 0;
else
sPokeblockFlavors[i] -= multiuseVar;
}
}
for (i = 0; i < FLAVOR_COUNT; i++)
sDebug_PokeblockFactorFlavors[i] = sPokeblockFlavors[i];
// Factor in max RPM and round
sDebug_PokeblockFactorRPM = multiuseVar = maxRPM / 333 + 100;
for (i = 0; i < FLAVOR_COUNT; i++)
{
s32 remainder;
s32 flavor = sPokeblockFlavors[i];
flavor = (flavor * multiuseVar) / 10;
remainder = flavor % 10;
flavor /= 10;
if (remainder > 4)
flavor++;
sPokeblockFlavors[i] = flavor;
}
for (i = 0; i < FLAVOR_COUNT; i++)
sDebug_PokeblockFactorFlavorsAfterRPM[i] = sPokeblockFlavors[i];
// Calculate color and feel of pokeblock
pokeblock->color = CalculatePokeblockColor(berries, &sPokeblockFlavors[0], numPlayers, numNegatives);
sPokeblockFlavors[FLAVOR_COUNT] = (sPokeblockFlavors[FLAVOR_COUNT] / numPlayers) - numPlayers;
if (sPokeblockFlavors[FLAVOR_COUNT] < 0)
sPokeblockFlavors[FLAVOR_COUNT] = 0;
if (pokeblock->color == PBLOCK_CLR_BLACK)
{
// Black pokeblocks get their flavors randomly reassigned
multiuseVar = Random() % ARRAY_COUNT(sBlackPokeblockFlavorFlags);
for (i = 0; i < FLAVOR_COUNT; i++)
{
if ((sBlackPokeblockFlavorFlags[multiuseVar] >> i) & 1)
sPokeblockFlavors[i] = 2;
else
sPokeblockFlavors[i] = 0;
}
}
for (i = 0; i < FLAVOR_COUNT + 1; i++)
{
if (sPokeblockFlavors[i] > 255)
sPokeblockFlavors[i] = 255;
}
pokeblock->spicy = sPokeblockFlavors[FLAVOR_SPICY];
pokeblock->dry = sPokeblockFlavors[FLAVOR_DRY];
pokeblock->sweet = sPokeblockFlavors[FLAVOR_SWEET];
pokeblock->bitter = sPokeblockFlavors[FLAVOR_BITTER];
pokeblock->sour = sPokeblockFlavors[FLAVOR_SOUR];
pokeblock->feel = sPokeblockFlavors[FLAVOR_COUNT];
for (i = 0; i < FLAVOR_COUNT + 1; i++)
flavors[i] = sPokeblockFlavors[i];
}
// Unused
static void Debug_CalculatePokeblock(struct BlenderBerry* berries, struct Pokeblock* pokeblock, u8 numPlayers, u8* flavors, u16 maxRPM)
{
CalculatePokeblock(berries, pokeblock, numPlayers, flavors, maxRPM);
}
static void Debug_SetStageVars(void)
{
u32 frames = (u16)(sBerryBlender->gameFrameTime);
u16 maxRPM = sBerryBlender->maxRPM;
s16 stage = 0;
if (frames < 900)
stage = 5;
else if ((u16)(frames - 900) < 600)
stage = 4;
else if ((u16)(frames - 1500) < 600)
stage = 3;
else if ((u16)(frames - 2100) < 900)
stage = 2;
else if ((u16)(frames - 3300) < 300)
stage = 1;
Debug_SetGameTimeStage(stage);
stage = 0;
if (maxRPM <= 64)
{
if (maxRPM >= 50 && maxRPM < 100)
stage = -1;
else if (maxRPM >= 100 && maxRPM < 150)
stage = -2;
else if (maxRPM >= 150 && maxRPM < 200)
stage = -3;
else if (maxRPM >= 200 && maxRPM < 250)
stage = -4;
else if (maxRPM >= 250 && maxRPM < 300)
stage = -5;
else if (maxRPM >= 350 && maxRPM < 400)
stage = -6;
else if (maxRPM >= 400 && maxRPM < 450)
stage = -7;
else if (maxRPM >= 500 && maxRPM < 550)
stage = -8;
else if (maxRPM >= 550 && maxRPM < 600)
stage = -9;
else if (maxRPM >= 600)
stage = -10;
}
Debug_SetMaxRPMStage(stage);
}
static void SendContinuePromptResponse(u16 *cmd)
{
if (gReceivedRemoteLinkPlayers && gWirelessCommType)
*cmd = RFUCMD_SEND_PACKET;
else
*cmd = LINKCMD_SEND_PACKET;
}
static void CB2_EndBlenderGame(void)
{
u8 i, j;
if (sBerryBlender->gameEndState < 3)
UpdateBlenderCenter();
GetMultiplayerId(); // unused return value
switch (sBerryBlender->gameEndState)
{
case 1:
m4aMPlayTempoControl(&gMPlayInfo_BGM, 256);
for (i = 0; i < gSpecialVar_0x8004; i++)
{
DestroyTask(sBerryBlender->opponentTaskIds[i]);
}
sBerryBlender->gameEndState++;
break;
case 2:
sBerryBlender->speed -= 32;
if (sBerryBlender->speed <= 0)
{
ClearLinkCallback();
sBerryBlender->speed = 0;
if (gReceivedRemoteLinkPlayers)
sBerryBlender->gameEndState++;
else
sBerryBlender->gameEndState = 5;
sBerryBlender->mainState = 0;
m4aMPlayStop(&gMPlayInfo_SE2);
}
UpdateHitPitch();
break;
case 3:
if (GetMultiplayerId() != 0)
{
sBerryBlender->gameEndState++;
}
else if (IsLinkTaskFinished())
{
if (gReceivedRemoteLinkPlayers && gWirelessCommType)
{
sBerryBlender->gameBlock.timeRPM.time = sBerryBlender->gameFrameTime;
sBerryBlender->gameBlock.timeRPM.maxRPM = sBerryBlender->maxRPM;
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
for (j = 0; j < NUM_SCORE_TYPES; j++)
sBerryBlender->gameBlock.scores[i][j] = sBerryBlender->scores[i][j];
}
if (SendBlock(0, &sBerryBlender->gameBlock, sizeof(sBerryBlender->gameBlock)))
sBerryBlender->gameEndState++;
}
else
{
sBerryBlender->smallBlock.time = sBerryBlender->gameFrameTime;
sBerryBlender->smallBlock.maxRPM = sBerryBlender->maxRPM;
if (SendBlock(0, &sBerryBlender->smallBlock, sizeof(sBerryBlender->smallBlock) + 32))
sBerryBlender->gameEndState++;
}
}
break;
case 4:
if (GetBlockReceivedStatus())
{
ResetBlockReceivedFlags();
sBerryBlender->gameEndState++;
if (gReceivedRemoteLinkPlayers && gWirelessCommType)
{
struct BlenderGameBlock *receivedBlock = (struct BlenderGameBlock*)(&gBlockRecvBuffer);
sBerryBlender->maxRPM = receivedBlock->timeRPM.maxRPM;
sBerryBlender->gameFrameTime = receivedBlock->timeRPM.time;
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
for (j = 0; j < NUM_SCORE_TYPES; j++)
sBerryBlender->scores[i][j] = receivedBlock->scores[i][j];
}
}
else
{
struct TimeAndRPM *receivedBlock = (struct TimeAndRPM*)(&gBlockRecvBuffer);
sBerryBlender->maxRPM = receivedBlock->maxRPM;
sBerryBlender->gameFrameTime = receivedBlock->time;
}
}
break;
case 5:
if (PrintBlendingRanking())
sBerryBlender->gameEndState++;
break;
case 6:
if (PrintBlendingResults())
{
if (gInGameOpponentsNo == 0)
IncrementGameStat(GAME_STAT_POKEBLOCKS_WITH_FRIENDS);
else
IncrementGameStat(GAME_STAT_POKEBLOCKS);
sBerryBlender->gameEndState++;
}
break;
case 7:
if (Blender_PrintText(&sBerryBlender->textState, sText_WouldLikeToBlendAnotherBerry, GetPlayerTextSpeedDelay()))
sBerryBlender->gameEndState++;
break;
case 9:
sBerryBlender->yesNoAnswer = 0;
CreateYesNoMenu(&sYesNoWindowTemplate_ContinuePlaying, 1, 0xD, 0);
sBerryBlender->gameEndState++;
break;
case 10:
switch (Menu_ProcessInputNoWrapClearOnChoose())
{
case 1:
case -1:
sBerryBlender->yesNoAnswer = 1;
sBerryBlender->gameEndState++;
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
if (sBerryBlender->arrowIdToPlayerId[i] != NO_PLAYER)
{
PutWindowTilemap(i);
CopyWindowToVram(i, 3);
}
}
break;
case 0:
sBerryBlender->yesNoAnswer = 0;
sBerryBlender->gameEndState++;
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
if (sBerryBlender->arrowIdToPlayerId[i] != NO_PLAYER)
{
PutWindowTilemap(i);
CopyWindowToVram(i, 3);
}
}
break;
}
break;
case 11:
SendContinuePromptResponse(&gSendCmd[BLENDER_COMM_INPUT_STATE]);
if (sBerryBlender->yesNoAnswer == 0)
{
if (IsBagPocketNonEmpty(POCKET_BERRIES) == FALSE)
{
// No berries
sBerryBlender->playAgainState = CANT_PLAY_NO_BERRIES;
gSendCmd[BLENDER_COMM_RESP] = LINKCMD_BLENDER_NO_BERRIES;
}
else if (GetFirstFreePokeblockSlot() == -1)
{
// No space for pokeblocks
sBerryBlender->playAgainState = CANT_PLAY_NO_PKBLCK_SPACE;
gSendCmd[BLENDER_COMM_RESP] = LINKCMD_BLENDER_NO_PBLOCK_SPACE;
}
else
{
sBerryBlender->playAgainState = PLAY_AGAIN_YES;
gSendCmd[BLENDER_COMM_RESP] = LINKCMD_BLENDER_PLAY_AGAIN;
}
sBerryBlender->gameEndState++;
}
else
{
sBerryBlender->playAgainState = PLAY_AGAIN_NO;
gSendCmd[BLENDER_COMM_RESP] = LINKCMD_CONT_BLOCK;
sBerryBlender->gameEndState++;
}
break;
case 12:
if (gInGameOpponentsNo)
{
SetMainCallback2(CB2_CheckPlayAgainLocal);
sBerryBlender->gameEndState = 0;
sBerryBlender->mainState = 0;
}
else
{
sBerryBlender->gameEndState++;
}
break;
case 8:
sBerryBlender->gameEndState++;
break;
case 13:
if (Blender_PrintText(&sBerryBlender->textState, sText_CommunicationStandby, GetPlayerTextSpeedDelay()))
{
SetMainCallback2(CB2_CheckPlayAgainLink);
sBerryBlender->gameEndState = 0;
sBerryBlender->mainState = 0;
}
break;
}
RestoreBgCoords();
UpdateRPM(sBerryBlender->speed);
ProcessLinkPlayerCmds();
Blender_DummiedOutFunc(sBerryBlender->bg_X, sBerryBlender->bg_Y);
RunTasks();
AnimateSprites();
BuildOamBuffer();
RunTextPrinters();
UpdatePaletteFade();
}
static bool8 LinkPlayAgainHandleSaving(void)
{
switch (sBerryBlender->linkPlayAgainState)
{
case 0:
SetLinkStandbyCallback();
sBerryBlender->linkPlayAgainState = 1;
sBerryBlender->framesToWait = 0;
break;
case 1:
if (IsLinkTaskFinished())
{
sBerryBlender->linkPlayAgainState++;
gSoftResetDisabled = TRUE;
}
break;
case 2:
FullSaveGame();
sBerryBlender->linkPlayAgainState++;
sBerryBlender->framesToWait = 0;
break;
case 3:
if (++sBerryBlender->framesToWait == 10)
{
SetLinkStandbyCallback();
sBerryBlender->linkPlayAgainState++;
}
break;
case 4:
if (IsLinkTaskFinished())
{
if (CheckSaveFile())
{
sBerryBlender->linkPlayAgainState = 5;
}
else
{
sBerryBlender->framesToWait = 0;
sBerryBlender->linkPlayAgainState = 3;
}
}
break;
case 5:
sBerryBlender->linkPlayAgainState++;
sBerryBlender->framesToWait = 0;
break;
case 6:
if (++sBerryBlender->framesToWait > 5)
{
gSoftResetDisabled = FALSE;
return TRUE;
}
break;
}
return FALSE;
}
static void CB2_CheckPlayAgainLink(void)
{
switch (sBerryBlender->gameEndState)
{
case 0:
if (sBerryBlender->playerContinueResponses[0] == LINKCMD_SEND_LINK_TYPE)
{
// Link leader says game will continue
sBerryBlender->gameEndState = 5;
}
else if (sBerryBlender->playerContinueResponses[0] == LINKCMD_BLENDER_STOP)
{
// Link leader says game will stop, if necessary print why
if (sBerryBlender->canceledPlayerCmd == LINKCMD_BLENDER_NO_BERRIES)
sBerryBlender->gameEndState = 2;
else if (sBerryBlender->canceledPlayerCmd == LINKCMD_BLENDER_NO_PBLOCK_SPACE)
sBerryBlender->gameEndState = 1;
else
sBerryBlender->gameEndState = 5;
}
break;
case 1:
sBerryBlender->gameEndState = 3;
StringCopy(gStringVar4, gLinkPlayers[sBerryBlender->canceledPlayerId].name);
StringAppend(gStringVar4, sText_ApostropheSPokeblockCaseIsFull);
break;
case 2:
sBerryBlender->gameEndState++;
StringCopy(gStringVar4, gLinkPlayers[sBerryBlender->canceledPlayerId].name);
StringAppend(gStringVar4, sText_HasNoBerriesToPut);
break;
case 3:
if (Blender_PrintText(&sBerryBlender->textState, gStringVar4, GetPlayerTextSpeedDelay()))
{
sBerryBlender->framesToWait = 0;
sBerryBlender->gameEndState++;
}
break;
case 4:
if (++sBerryBlender->framesToWait > 60)
sBerryBlender->gameEndState = 5;
break;
case 5:
Blender_PrintText(&sBerryBlender->textState, gText_SavingDontTurnOff2, 0);
SetLinkStandbyCallback();
sBerryBlender->gameEndState++;
break;
case 6:
if (IsLinkTaskFinished())
{
sBerryBlender->framesToWait = 0;
sBerryBlender->gameEndState++;
sBerryBlender->linkPlayAgainState = 0;
}
break;
case 7:
if (LinkPlayAgainHandleSaving())
{
PlaySE(SE_SAVE);
sBerryBlender->gameEndState++;
}
break;
case 8:
sBerryBlender->gameEndState++;
SetLinkStandbyCallback();
break;
case 9:
if (IsLinkTaskFinished())
{
BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 0x10, RGB_BLACK);
sBerryBlender->gameEndState++;
}
break;
case 10:
if (!gPaletteFade.active)
{
if (sBerryBlender->playerContinueResponses[0] == LINKCMD_SEND_LINK_TYPE)
{
FreeAllWindowBuffers();
UnsetBgTilemapBuffer(2);
UnsetBgTilemapBuffer(1);
FREE_AND_SET_NULL(sBerryBlender);
SetMainCallback2(DoBerryBlending);
}
else
{
sBerryBlender->framesToWait = 0;
sBerryBlender->gameEndState++;
}
}
break;
case 11:
if (++sBerryBlender->framesToWait > 30)
{
SetCloseLinkCallback();
sBerryBlender->gameEndState++;
}
break;
case 12:
if (!gReceivedRemoteLinkPlayers)
{
FREE_AND_SET_NULL(sBerryBlender);
SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic);
}
break;
}
ProcessLinkPlayerCmds();
Blender_DummiedOutFunc(sBerryBlender->bg_X, sBerryBlender->bg_Y);
RunTasks();
AnimateSprites();
BuildOamBuffer();
RunTextPrinters();
UpdatePaletteFade();
}
static void CB2_CheckPlayAgainLocal(void)
{
switch (sBerryBlender->gameEndState)
{
case 0:
if (sBerryBlender->playAgainState == PLAY_AGAIN_YES || sBerryBlender->playAgainState == PLAY_AGAIN_NO)
sBerryBlender->gameEndState = 9;
if (sBerryBlender->playAgainState == CANT_PLAY_NO_BERRIES)
sBerryBlender->gameEndState = 2;
if (sBerryBlender->playAgainState == CANT_PLAY_NO_PKBLCK_SPACE)
sBerryBlender->gameEndState = 1;
break;
case 1:
sBerryBlender->gameEndState = 3;
sBerryBlender->textState = 0;
StringCopy(gStringVar4, sText_YourPokeblockCaseIsFull);
break;
case 2:
sBerryBlender->gameEndState++;
sBerryBlender->textState = 0;
StringCopy(gStringVar4, sText_RunOutOfBerriesForBlending);
break;
case 3:
if (Blender_PrintText(&sBerryBlender->textState, gStringVar4, GetPlayerTextSpeedDelay()))
sBerryBlender->gameEndState = 9;
break;
case 9:
BeginFastPaletteFade(3);
sBerryBlender->gameEndState++;
break;
case 10:
if (!gPaletteFade.active)
{
if (sBerryBlender->playAgainState == PLAY_AGAIN_YES)
SetMainCallback2(DoBerryBlending);
else
SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic);
FreeAllWindowBuffers();
UnsetBgTilemapBuffer(2);
UnsetBgTilemapBuffer(1);
FREE_AND_SET_NULL(sBerryBlender);
}
break;
}
ProcessLinkPlayerCmds();
Blender_DummiedOutFunc(sBerryBlender->bg_X, sBerryBlender->bg_Y);
RunTasks();
AnimateSprites();
BuildOamBuffer();
RunTextPrinters();
UpdatePaletteFade();
}
static void ProcessLinkPlayerCmds(void)
{
if (gReceivedRemoteLinkPlayers)
{
if (CheckRecvCmdMatches(gRecvCmds[0][BLENDER_COMM_INPUT_STATE], LINKCMD_SEND_PACKET, RFUCMD_SEND_PACKET))
{
if (gRecvCmds[0][BLENDER_COMM_RESP] == LINKCMD_BLENDER_STOP)
{
// Link leader has indicated play is stopping, read signal to determine why
switch (gRecvCmds[0][BLENDER_COMM_STOP_TYPE])
{
case LINKCMD_CONT_BLOCK: // Someone selected "No" to continue playing
sBerryBlender->canceledPlayerCmd = LINKCMD_CONT_BLOCK;
sBerryBlender->canceledPlayerId = gRecvCmds[0][BLENDER_COMM_PLAYER_ID];
break;
case LINKCMD_BLENDER_NO_BERRIES:
sBerryBlender->canceledPlayerCmd = LINKCMD_BLENDER_NO_BERRIES;
sBerryBlender->canceledPlayerId = gRecvCmds[0][BLENDER_COMM_PLAYER_ID];
break;
case LINKCMD_BLENDER_NO_PBLOCK_SPACE:
sBerryBlender->canceledPlayerCmd = LINKCMD_BLENDER_NO_PBLOCK_SPACE;
sBerryBlender->canceledPlayerId = gRecvCmds[0][BLENDER_COMM_PLAYER_ID];
break;
}
sBerryBlender->playerContinueResponses[0] = LINKCMD_BLENDER_STOP;
}
else if (gRecvCmds[0][BLENDER_COMM_RESP] == LINKCMD_SEND_LINK_TYPE)
{
// Link leader has indicated play will continue
sBerryBlender->playerContinueResponses[0] = LINKCMD_SEND_LINK_TYPE;
}
}
// If player is link leader, check for responses to the "Continue playing" prompt (even if it's not up yet)
if (GetMultiplayerId() == 0
&& sBerryBlender->playerContinueResponses[0] != LINKCMD_BLENDER_STOP
&& sBerryBlender->playerContinueResponses[0] != LINKCMD_SEND_LINK_TYPE)
{
u8 i;
// Try to gather responses
for (i = 0; i < GetLinkPlayerCount(); i++)
{
if (CheckRecvCmdMatches(gRecvCmds[i][BLENDER_COMM_INPUT_STATE], LINKCMD_SEND_PACKET, RFUCMD_SEND_PACKET))
{
switch (gRecvCmds[i][BLENDER_COMM_RESP])
{
case LINKCMD_CONT_BLOCK: // Selected "No"
sBerryBlender->playerContinueResponses[i] = LINKCMD_CONT_BLOCK;
break;
case LINKCMD_BLENDER_PLAY_AGAIN: // Selected "Yes"
sBerryBlender->playerContinueResponses[i] = LINKCMD_BLENDER_PLAY_AGAIN;
break;
case LINKCMD_BLENDER_NO_BERRIES:
sBerryBlender->playerContinueResponses[i] = LINKCMD_BLENDER_NO_BERRIES;
break;
case LINKCMD_BLENDER_NO_PBLOCK_SPACE:
sBerryBlender->playerContinueResponses[i] = LINKCMD_BLENDER_NO_PBLOCK_SPACE;
break;
}
}
}
// Count players that have responded, stopping at first non-response
for (i = 0; i < GetLinkPlayerCount(); i++)
{
if (sBerryBlender->playerContinueResponses[i] == 0)
break;
}
// If all players responded, handle response
if (i == GetLinkPlayerCount())
{
// Count players that decided to play again, stopping at first negative response
for (i = 0; i < GetLinkPlayerCount(); i++)
{
if (sBerryBlender->playerContinueResponses[i] != LINKCMD_BLENDER_PLAY_AGAIN)
break;
}
// Schedule signal to other players about whether or not play will continue
SendContinuePromptResponse(&gSendCmd[BLENDER_COMM_INPUT_STATE]);
if (i == GetLinkPlayerCount())
{
// All players chose to continue playing
gSendCmd[BLENDER_COMM_RESP] = LINKCMD_SEND_LINK_TYPE;
}
else
{
// At least 1 player decided to stop playing, or can't continue playing
gSendCmd[BLENDER_COMM_RESP] = LINKCMD_BLENDER_STOP;
gSendCmd[BLENDER_COMM_STOP_TYPE] = sBerryBlender->playerContinueResponses[i];
gSendCmd[BLENDER_COMM_PLAYER_ID] = i;
}
}
}
}
}
static void DrawBlenderCenter(struct BgAffineSrcData *dest)
{
struct BgAffineSrcData affineSrc;
affineSrc.texX = 0x7800;
affineSrc.texY = 0x5000;
affineSrc.scrX = 0x78 - sBerryBlender->bg_X;
affineSrc.scrY = 0x50 - sBerryBlender->bg_Y;
affineSrc.sx = sBerryBlender->centerScale;
affineSrc.sy = sBerryBlender->centerScale;
affineSrc.alpha = sBerryBlender->arrowPos;
*dest = affineSrc;
}
u16 GetBlenderArrowPosition(void)
{
return sBerryBlender->arrowPos;
}
static void UpdateBlenderCenter(void)
{
u8 playerId = 0;
if (gReceivedRemoteLinkPlayers)
playerId = GetMultiplayerId();
if (gWirelessCommType && gReceivedRemoteLinkPlayers)
{
if (playerId == 0)
{
sBerryBlender->arrowPos += sBerryBlender->speed;
gSendCmd[BLENDER_COMM_PROGRESS_BAR] = sBerryBlender->progressBarValue;
gSendCmd[BLENDER_COMM_ARROW_POS] = sBerryBlender->arrowPos;
DrawBlenderCenter(&sBerryBlender->bgAffineSrc);
}
else
{
if ((gRecvCmds[0][BLENDER_COMM_INPUT_STATE] & 0xFF00) == RFUCMD_BLENDER_SEND_KEYS)
{
sBerryBlender->progressBarValue = gRecvCmds[0][BLENDER_COMM_PROGRESS_BAR];
sBerryBlender->arrowPos = gRecvCmds[0][BLENDER_COMM_ARROW_POS];
DrawBlenderCenter(&sBerryBlender->bgAffineSrc);
}
}
}
else
{
sBerryBlender->arrowPos += sBerryBlender->speed;
DrawBlenderCenter(&sBerryBlender->bgAffineSrc);
}
}
static void SetBgPos(void)
{
SetGpuReg(REG_OFFSET_BG1HOFS, sBerryBlender->bg_X);
SetGpuReg(REG_OFFSET_BG1VOFS, sBerryBlender->bg_Y);
SetGpuReg(REG_OFFSET_BG0HOFS, sBerryBlender->bg_X);
SetGpuReg(REG_OFFSET_BG0VOFS, sBerryBlender->bg_Y);
}
static void SpriteCB_Particle(struct Sprite* sprite)
{
sprite->data[2] += sprite->data[0];
sprite->data[3] += sprite->data[1];
sprite->pos2.x = sprite->data[2] / 8;
sprite->pos2.y = sprite->data[3] / 8;
if (sprite->animEnded)
DestroySprite(sprite);
}
static void CreateParticleSprites(void)
{
s32 limit = (Random() % 2) + 1;
s32 i;
for (i = 0; i < limit; i++)
{
u16 rand;
s32 x, y;
u8 spriteId;
rand = sBerryBlender->arrowPos + (Random() % 20);
x = gSineTable[(rand & 0xFF) + 64] / 4;
y = gSineTable[(rand & 0xFF)] / 4;
spriteId = CreateSprite(&sSpriteTemplate_Particles, x + 120, y + 80, 1);
gSprites[spriteId].data[0] = 16 - (Random() % 32);
gSprites[spriteId].data[1] = 16 - (Random() % 32);
gSprites[spriteId].callback = SpriteCB_Particle;
}
}
static void SpriteCB_ScoreSymbol(struct Sprite* sprite)
{
sprite->data[0]++;
sprite->pos2.y = -(sprite->data[0] / 3);
if (sprite->animEnded)
DestroySprite(sprite);
}
static void SpriteCB_ScoreSymbolBest(struct Sprite* sprite)
{
sprite->data[0]++;
sprite->pos2.y = -(sprite->data[0] * 2);
if (sprite->pos2.y < -12)
sprite->pos2.y = -12;
if (sprite->animEnded)
DestroySprite(sprite);
}
static void SetPlayerBerryData(u8 playerId, u16 itemId)
{
sBerryBlender->chosenItemId[playerId] = itemId;
ConvertItemToBlenderBerry(&sBerryBlender->blendedBerries[playerId], itemId);
}
#define sState data[0]
#define sYPos data[1]
#define sDelay data[2]
#define sAnimId data[3]
static void SpriteCB_CountdownNumber(struct Sprite* sprite)
{
switch (sprite->sState)
{
case 0:
sprite->sYPos += 8;
if (sprite->sYPos > DISPLAY_HEIGHT / 2 + 8)
{
sprite->sYPos = DISPLAY_HEIGHT / 2 + 8;
sprite->sState++;
PlaySE(SE_BALL_BOUNCE_1);
}
break;
case 1:
if (++sprite->sDelay > 20)
{
sprite->sState++;
sprite->sDelay = 0;
}
break;
case 2:
sprite->sYPos += 4;
if (sprite->sYPos > DISPLAY_HEIGHT + 16)
{
if (++sprite->sAnimId == 3)
{
DestroySprite(sprite);
CreateSprite(&sSpriteTemplate_Start, 120, -20, 2);
}
else
{
sprite->sState = 0;
sprite->sYPos = -16;
StartSpriteAnim(sprite, sprite->sAnimId);
}
}
break;
}
sprite->pos2.y = sprite->sYPos;
}
#undef sState
#undef sYPos
#undef sDelay
#undef sAnimId
static void SpriteCB_Start(struct Sprite* sprite)
{
switch (sprite->data[0])
{
case 0:
sprite->data[1] += 8;
if (sprite->data[1] > 92)
{
sprite->data[1] = 92;
sprite->data[0]++;
PlaySE(SE_PIN);
}
break;
case 1:
sprite->data[2] += 1;
if (sprite->data[2] > 20)
sprite->data[0]++;
break;
case 2:
sprite->data[1] += 4;
if (sprite->data[1] > DISPLAY_HEIGHT + 16)
{
sBerryBlender->mainState++;
DestroySprite(sprite);
}
break;
}
sprite->pos2.y = sprite->data[1];
}
static void TryUpdateProgressBar(u16 current, u16 limit)
{
// Progress bar doesn't move unless it's going up
if (sBerryBlender->maxProgressBarValue < current)
{
sBerryBlender->maxProgressBarValue += 2;
UpdateProgressBar(sBerryBlender->maxProgressBarValue, limit);
}
}
static void UpdateProgressBar(u16 value, u16 limit)
{
s32 amountFilled, maxFilledSegment, subSegmentsFilled, i;
u16 *vram;
vram = (u16*)(BG_SCREEN_ADDR(12));
amountFilled = (value * 64) / limit;
maxFilledSegment = amountFilled / 8;
// Set filled progress bar tiles in full segments
for (i = 0; i < maxFilledSegment; i++)
{
vram[11 + i] = PROGRESS_BAR_FILLED_TOP;
vram[43 + i] = PROGRESS_BAR_FILLED_BOTTOM;
}
// If progress bar between segments, fill with the corresponding partial segment tiles
subSegmentsFilled = amountFilled % 8;
if (subSegmentsFilled != 0)
{
vram[11 + i] = subSegmentsFilled + PROGRESS_BAR_EMPTY_TOP;
vram[43 + i] = subSegmentsFilled + PROGRESS_BAR_EMPTY_BOTTOM;
i++;
}
// Fill the remaining full segments with empty progress tiles
// Essentially unnecessary, given that it starts empty and progress only goes up
for (; i < 8; i++)
{
vram[11 + i] = PROGRESS_BAR_EMPTY_TOP;
vram[43 + i] = PROGRESS_BAR_EMPTY_BOTTOM;
}
}
static u32 ArrowSpeedToRPM(u16 speed)
{
return 60 * 60 * 100 * speed / MAX_ARROW_POS;
}
static void UpdateRPM(u16 speed)
{
u8 i;
u8 digits[5];
// Check if new max RPM has been reached
u32 currentRPM = ArrowSpeedToRPM(speed);
if (sBerryBlender->maxRPM < currentRPM)
sBerryBlender->maxRPM = currentRPM;
// Draw the current RPM number at the bottom of the screen
for (i = 0; i < 5; i++)
{
digits[i] = currentRPM % 10;
currentRPM /= 10;
}
*((u16*)(BG_SCREEN_ADDR(12) + 0x458)) = digits[4] + RPM_DIGIT;
*((u16*)(BG_SCREEN_ADDR(12) + 0x45A)) = digits[3] + RPM_DIGIT;
*((u16*)(BG_SCREEN_ADDR(12) + 0x45C)) = digits[2] + RPM_DIGIT;
*((u16*)(BG_SCREEN_ADDR(12) + 0x460)) = digits[1] + RPM_DIGIT;
*((u16*)(BG_SCREEN_ADDR(12) + 0x462)) = digits[0] + RPM_DIGIT;
}
// Passed a pointer to the bg x/y
// Used when hitting a Best at high RPM
static void ShakeBgCoordForHit(s16* coord, u16 speed)
{
if (*coord == 0)
*coord = (Random() % speed) - (speed / 2);
}
static void RestoreBgCoord(s16* coord)
{
if (*coord < 0)
(*coord)++;
if (*coord > 0)
(*coord)--;
}
// For "unshaking" the screen after ShakeBgCoordForHit is called
static void RestoreBgCoords(void)
{
RestoreBgCoord(&sBerryBlender->bg_X);
RestoreBgCoord(&sBerryBlender->bg_Y);
}
static void BlenderLandShakeBgCoord(s16* coord, u16 timer)
{
s32 strength;
if (timer < 10)
strength = 16;
else
strength = 8;
if (*coord == 0)
{
*coord = (Random() % strength) - (strength / 2);
}
else
{
if (*coord < 0)
(*coord)++;
if (*coord > 0)
(*coord)--;
}
}
// For shaking the screen when the blender lands after falling in at the start
static bool8 UpdateBlenderLandScreenShake(void)
{
if (sBerryBlender->framesToWait == 0)
{
sBerryBlender->bg_X = 0;
sBerryBlender->bg_Y = 0;
}
sBerryBlender->framesToWait++;
BlenderLandShakeBgCoord(&sBerryBlender->bg_X, sBerryBlender->framesToWait);
BlenderLandShakeBgCoord(&sBerryBlender->bg_Y, sBerryBlender->framesToWait);
if (sBerryBlender->framesToWait == 20)
{
sBerryBlender->bg_X = 0;
sBerryBlender->bg_Y = 0;
return TRUE;
}
return FALSE;
}
static void SpriteCB_PlayerArrow(struct Sprite* sprite)
{
sprite->pos2.x = -(sBerryBlender->bg_X);
sprite->pos2.y = -(sBerryBlender->bg_Y);
}
static void TryUpdateBerryBlenderRecord(void)
{
if (gSaveBlock1Ptr->berryBlenderRecords[sBerryBlender->numPlayers - 2] < sBerryBlender->maxRPM)
gSaveBlock1Ptr->berryBlenderRecords[sBerryBlender->numPlayers - 2] = sBerryBlender->maxRPM;
}
static bool8 PrintBlendingResults(void)
{
u16 i;
s32 xPos, yPos;
struct Pokeblock pokeblock;
u8 flavors[FLAVOR_COUNT + 1];
u8 text[40];
u16 berryIds[4]; // unused
switch (sBerryBlender->mainState)
{
case 0:
sBerryBlender->mainState++;
sBerryBlender->framesToWait = 17;
break;
case 1:
sBerryBlender->framesToWait -= 10;
if (sBerryBlender->framesToWait < 0)
{
sBerryBlender->framesToWait = 0;
sBerryBlender->mainState++;
}
break;
case 2:
if (++sBerryBlender->framesToWait > 20)
{
for (i = 0; i < NUM_SCORE_TYPES; i++)
DestroySprite(&gSprites[sBerryBlender->scoreIconIds[i]]);
sBerryBlender->framesToWait = 0;
sBerryBlender->mainState++;
}
break;
case 3:
{
u16 minutes, seconds;
u8 *txtPtr;
xPos = GetStringCenterAlignXOffset(1, sText_BlendingResults, 0xA8);
Blender_AddTextPrinter(5, sText_BlendingResults, xPos, 1, TEXT_SPEED_FF, 0);
if (sBerryBlender->numPlayers == BLENDER_MAX_PLAYERS)
yPos = 17;
else
yPos = 21;
for (i = 0; i < sBerryBlender->numPlayers; yPos += 16, i++)
{
u8 place = sBerryBlender->playerPlaces[i];
ConvertIntToDecimalStringN(sBerryBlender->stringVar, i + 1, STR_CONV_MODE_LEFT_ALIGN, 1);
StringAppend(sBerryBlender->stringVar, sText_Dot);
StringAppend(sBerryBlender->stringVar, gText_Space);
StringAppend(sBerryBlender->stringVar, gLinkPlayers[place].name);
Blender_AddTextPrinter(5, sBerryBlender->stringVar, 8, yPos, TEXT_SPEED_FF, 3);
StringCopy(sBerryBlender->stringVar, sBerryBlender->blendedBerries[place].name);
ConvertInternationalString(sBerryBlender->stringVar, gLinkPlayers[place].language);
StringAppend(sBerryBlender->stringVar, sText_SpaceBerry);
Blender_AddTextPrinter(5, sBerryBlender->stringVar, 0x54, yPos, TEXT_SPEED_FF, 3);
}
Blender_AddTextPrinter(5, sText_MaximumSpeed, 0, 0x51, TEXT_SPEED_FF, 3);
ConvertIntToDecimalStringN(sBerryBlender->stringVar, sBerryBlender->maxRPM / 100, STR_CONV_MODE_RIGHT_ALIGN, 3);
StringAppend(sBerryBlender->stringVar, sText_Dot);
ConvertIntToDecimalStringN(text, sBerryBlender->maxRPM % 100, STR_CONV_MODE_LEADING_ZEROS, 2);
StringAppend(sBerryBlender->stringVar, text);
StringAppend(sBerryBlender->stringVar, sText_RPM);
xPos = GetStringRightAlignXOffset(1, sBerryBlender->stringVar, 0xA8);
Blender_AddTextPrinter(5, sBerryBlender->stringVar, xPos, 0x51, TEXT_SPEED_FF, 3);
Blender_AddTextPrinter(5, sText_Time, 0, 0x61, TEXT_SPEED_FF, 3);
seconds = (sBerryBlender->gameFrameTime / 60) % 60;
minutes = (sBerryBlender->gameFrameTime / (60 * 60));
ConvertIntToDecimalStringN(sBerryBlender->stringVar, minutes, STR_CONV_MODE_LEADING_ZEROS, 2);
txtPtr = StringAppend(sBerryBlender->stringVar, sText_Min);
ConvertIntToDecimalStringN(txtPtr, seconds, STR_CONV_MODE_LEADING_ZEROS, 2);
StringAppend(sBerryBlender->stringVar, sText_Sec);
xPos = GetStringRightAlignXOffset(1, sBerryBlender->stringVar, 0xA8);
Blender_AddTextPrinter(5, sBerryBlender->stringVar, xPos, 0x61, TEXT_SPEED_FF, 3);
sBerryBlender->framesToWait = 0;
sBerryBlender->mainState++;
CopyWindowToVram(5, 2);
}
break;
case 4:
if (JOY_NEW(A_BUTTON))
sBerryBlender->mainState++;
break;
case 5:
ClearStdWindowAndFrameToTransparent(5, 1);
for (i = 0; i < BLENDER_MAX_PLAYERS; i++)
{
if (sBerryBlender->chosenItemId[i] != 0)
berryIds[i] = sBerryBlender->chosenItemId[i] - FIRST_BERRY_INDEX;
if (sBerryBlender->arrowIdToPlayerId[i] != NO_PLAYER)
{
PutWindowTilemap(i);
CopyWindowToVram(i, 3);
}
}
Debug_SetStageVars();
CalculatePokeblock(sBerryBlender->blendedBerries, &pokeblock, sBerryBlender->numPlayers, flavors, sBerryBlender->maxRPM);
PrintMadePokeblockString(&pokeblock, sBerryBlender->stringVar);
TryAddContestLinkTvShow(&pokeblock, &sBerryBlender->tvBlender);
CreateTask(Task_PlayPokeblockFanfare, 6);
IncrementDailyBerryBlender();
RemoveBagItem(gSpecialVar_ItemId, 1);
AddPokeblock(&pokeblock);
sBerryBlender->textState = 0;
sBerryBlender->mainState++;
break;
case 6:
if (Blender_PrintText(&sBerryBlender->textState, sBerryBlender->stringVar, GetPlayerTextSpeedDelay()))
{
TryUpdateBerryBlenderRecord();
return TRUE;
}
break;
}
return FALSE;
}
static void PrintMadePokeblockString(struct Pokeblock *pokeblock, u8 *dst)
{
u8 text[12];
u8 flavorLvl, feel;
dst[0] = EOS;
StringCopy(dst, gPokeblockNames[pokeblock->color]);
StringAppend(dst, sText_WasMade);
StringAppend(dst, sText_NewLine);
flavorLvl = GetHighestPokeblocksFlavorLevel(pokeblock);
feel = GetPokeblocksFeel(pokeblock);
StringAppend(dst, sText_TheLevelIs);
ConvertIntToDecimalStringN(text, flavorLvl, STR_CONV_MODE_LEFT_ALIGN, 3);
StringAppend(dst, text);
StringAppend(dst, sText_TheFeelIs);
ConvertIntToDecimalStringN(text, feel, STR_CONV_MODE_LEFT_ALIGN, 3);
StringAppend(dst, text);
StringAppend(dst, sText_Dot2);
StringAppend(dst, sText_NewParagraph);
}
static void SortBasedOnPoints(u8 *places, u8 playersNum, u32 *scores)
{
s32 i, j;
for (i = 0; i < playersNum; i++)
{
for (j = 0; j < playersNum; j++)
{
if (scores[places[i]] > scores[places[j]])
{
u8 temp;
SWAP(places[i], places[j], temp);
}
}
}
}
static void SortScores(void)
{
u8 playerId;
u8 i;
u8 places[BLENDER_MAX_PLAYERS];
u32 points[BLENDER_MAX_PLAYERS];
for (i = 0; i < sBerryBlender->numPlayers; i++)
places[i] = i;
for (i = 0; i < sBerryBlender->numPlayers; i++)
{
points[i] = 1000000 * sBerryBlender->scores[i][SCORE_BEST];
points[i] += 1000 * sBerryBlender->scores[i][SCORE_GOOD];
points[i] += 1000 - sBerryBlender->scores[i][SCORE_MISS];
}
SortBasedOnPoints(places, sBerryBlender->numPlayers, points);
for (i = 0; i < sBerryBlender->numPlayers; i++)
sBerryBlender->playerPlaces[i] = places[i];
if (!gReceivedRemoteLinkPlayers)
playerId = 0;
else
playerId = GetMultiplayerId();
for (i = 0; i < sBerryBlender->numPlayers; i++)
{
if (sBerryBlender->playerPlaces[i] == playerId)
sBerryBlender->ownRanking = i;
}
}
static bool8 PrintBlendingRanking(void)
{
u16 i;
s32 xPos, yPos;
switch (sBerryBlender->mainState)
{
case 0:
sBerryBlender->mainState++;
sBerryBlender->framesToWait = 255;
break;
case 1:
sBerryBlender->framesToWait -= 10;
if (sBerryBlender->framesToWait < 0)
{
sBerryBlender->framesToWait = 0;
sBerryBlender->mainState++;
}
break;
case 2:
if (++sBerryBlender->framesToWait > 20)
{
sBerryBlender->framesToWait = 0;
sBerryBlender->mainState++;
}
break;
case 3:
DrawStdFrameWithCustomTileAndPalette(5, 0, 1, 0xD);
xPos = GetStringCenterAlignXOffset(1, sText_Ranking, 168);
Blender_AddTextPrinter(5, sText_Ranking, xPos, 1, TEXT_SPEED_FF, 0);
sBerryBlender->scoreIconIds[SCORE_BEST] = CreateSprite(&sSpriteTemplate_ScoreSymbols, 128, 52, 0);
StartSpriteAnim(&gSprites[sBerryBlender->scoreIconIds[SCORE_BEST]], SCOREANIM_BEST_STATIC);
gSprites[sBerryBlender->scoreIconIds[SCORE_BEST]].callback = SpriteCallbackDummy;
sBerryBlender->scoreIconIds[SCORE_GOOD] = CreateSprite(&sSpriteTemplate_ScoreSymbols, 160, 52, 0);
// implicitly uses SCOREANIM_GOOD, no need to assign
gSprites[sBerryBlender->scoreIconIds[SCORE_GOOD]].callback = SpriteCallbackDummy;
sBerryBlender->scoreIconIds[SCORE_MISS] = CreateSprite(&sSpriteTemplate_ScoreSymbols, 192, 52, 0);
StartSpriteAnim(&gSprites[sBerryBlender->scoreIconIds[SCORE_MISS]], SCOREANIM_MISS);
gSprites[sBerryBlender->scoreIconIds[SCORE_MISS]].callback = SpriteCallbackDummy;
SortScores();
for (yPos = 41, i = 0; i < sBerryBlender->numPlayers; yPos += 16, i++)
{
u8 place = sBerryBlender->playerPlaces[i];
ConvertIntToDecimalStringN(sBerryBlender->stringVar, i + 1, STR_CONV_MODE_LEFT_ALIGN, 1);
StringAppend(sBerryBlender->stringVar, sText_Dot);
StringAppend(sBerryBlender->stringVar, gText_Space);
StringAppend(sBerryBlender->stringVar, gLinkPlayers[place].name);
Blender_AddTextPrinter(5, sBerryBlender->stringVar, 0, yPos, TEXT_SPEED_FF, 3);
ConvertIntToDecimalStringN(sBerryBlender->stringVar, sBerryBlender->scores[place][SCORE_BEST], STR_CONV_MODE_RIGHT_ALIGN, 3);
Blender_AddTextPrinter(5, sBerryBlender->stringVar, 78, yPos, TEXT_SPEED_FF, 3);
ConvertIntToDecimalStringN(sBerryBlender->stringVar, sBerryBlender->scores[place][SCORE_GOOD], STR_CONV_MODE_RIGHT_ALIGN, 3);
Blender_AddTextPrinter(5, sBerryBlender->stringVar, 78 + 32, yPos, TEXT_SPEED_FF, 3);
ConvertIntToDecimalStringN(sBerryBlender->stringVar, sBerryBlender->scores[place][SCORE_MISS], STR_CONV_MODE_RIGHT_ALIGN, 3);
Blender_AddTextPrinter(5, sBerryBlender->stringVar, 78 + 64, yPos, TEXT_SPEED_FF, 3);
}
PutWindowTilemap(5);
CopyWindowToVram(5, 3);
sBerryBlender->framesToWait = 0;
sBerryBlender->mainState++;
break;
case 4:
if (++sBerryBlender->framesToWait > 20)
sBerryBlender->mainState++;
break;
case 5:
if (JOY_NEW(A_BUTTON))
{
PlaySE(SE_SELECT);
sBerryBlender->mainState++;
}
break;
case 6:
sBerryBlender->mainState = 0;
return TRUE;
}
return FALSE;
}
void ShowBerryBlenderRecordWindow(void)
{
s32 i;
s32 xPos, yPos;
struct WindowTemplate winTemplate;
u8 text[32];
winTemplate = sBlenderRecordWindowTemplate;
gRecordsWindowId = AddWindow(&winTemplate);
DrawStdWindowFrame(gRecordsWindowId, 0);
FillWindowPixelBuffer(gRecordsWindowId, PIXEL_FILL(1));
xPos = GetStringCenterAlignXOffset(1, gText_BlenderMaxSpeedRecord, 144);
AddTextPrinterParameterized(gRecordsWindowId, 1, gText_BlenderMaxSpeedRecord, xPos, 1, 0, NULL);
AddTextPrinterParameterized(gRecordsWindowId, 1, gText_234Players, 4, 41, 0, NULL);
for (i = 0, yPos = 41; i < NUM_SCORE_TYPES; i++)
{
u8 *txtPtr;
u32 record;
record = gSaveBlock1Ptr->berryBlenderRecords[i];
txtPtr = ConvertIntToDecimalStringN(text, record / 100, STR_CONV_MODE_RIGHT_ALIGN, 3);
txtPtr = StringAppend(txtPtr, sText_Dot);
txtPtr = ConvertIntToDecimalStringN(txtPtr, record % 100, STR_CONV_MODE_LEADING_ZEROS, 2);
txtPtr = StringAppend(txtPtr, sText_RPM);
xPos = GetStringRightAlignXOffset(1, text, 140);
AddTextPrinterParameterized(gRecordsWindowId, 1, text, xPos, yPos + (i * 16), 0, NULL);
}
PutWindowTilemap(gRecordsWindowId);
CopyWindowToVram(gRecordsWindowId, 3);
}
static void Task_PlayPokeblockFanfare(u8 taskId)
{
if (gTasks[taskId].data[0] == 0)
{
PlayFanfare(MUS_LEVEL_UP);
gTasks[taskId].data[0]++;
}
if (IsFanfareTaskInactive())
{
PlayBGM(sBerryBlender->savedMusic);
DestroyTask(taskId);
}
}
static bool32 TryAddContestLinkTvShow(struct Pokeblock *pokeblock, struct TvBlenderStruct *tvBlender)
{
u8 flavorLevel = GetHighestPokeblocksFlavorLevel(pokeblock);
u16 sheen = (flavorLevel * 10) / GetPokeblocksFeel(pokeblock);
tvBlender->pokeblockSheen = sheen;
tvBlender->pokeblockColor = pokeblock->color;
tvBlender->name[0] = EOS;
if (gReceivedRemoteLinkPlayers)
{
if (sBerryBlender->ownRanking == 0 && sheen > 20)
{
// Player came first, try to put on air
StringCopy(tvBlender->name, gLinkPlayers[sBerryBlender->playerPlaces[sBerryBlender->numPlayers - 1]].name);
tvBlender->pokeblockFlavor = GetPokeblocksFlavor(pokeblock);
if (Put3CheersForPokeblocksOnTheAir(tvBlender->name, tvBlender->pokeblockFlavor,
tvBlender->pokeblockColor, tvBlender->pokeblockSheen,
gLinkPlayers[sBerryBlender->playerPlaces[sBerryBlender->numPlayers - 1]].language))
{
return TRUE;
}
return FALSE;
}
else if (sBerryBlender->ownRanking == sBerryBlender->numPlayers - 1 && sheen <= 20)
{
// Player came last, try to put on air
StringCopy(tvBlender->name, gLinkPlayers[sBerryBlender->playerPlaces[0]].name);
tvBlender->pokeblockFlavor = GetPokeblocksFlavor(pokeblock);
if (Put3CheersForPokeblocksOnTheAir(tvBlender->name, tvBlender->pokeblockFlavor,
tvBlender->pokeblockColor, tvBlender->pokeblockSheen,
gLinkPlayers[sBerryBlender->playerPlaces[0]].language))
{
return TRUE;
}
return FALSE;
}
}
return FALSE;
}
static void Blender_AddTextPrinter(u8 windowId, const u8 *string, u8 x, u8 y, s32 speed, s32 caseId)
{
u8 txtColor[3];
u32 letterSpacing = 0;
switch (caseId)
{
case 0:
case 3:
txtColor[0] = TEXT_COLOR_WHITE;
txtColor[1] = TEXT_COLOR_DARK_GREY;
txtColor[2] = TEXT_COLOR_LIGHT_GREY;
break;
case 1:
txtColor[0] = TEXT_COLOR_TRANSPARENT;
txtColor[1] = TEXT_COLOR_DARK_GREY;
txtColor[2] = TEXT_COLOR_LIGHT_GREY;
break;
case 2:
txtColor[0] = TEXT_COLOR_TRANSPARENT;
txtColor[1] = TEXT_COLOR_RED;
txtColor[2] = TEXT_COLOR_LIGHT_RED;
break;
}
if (caseId != 3)
{
FillWindowPixelBuffer(windowId, PIXEL_FILL(txtColor[0]));
}
AddTextPrinterParameterized4(windowId, 1, x, y, letterSpacing, 1, txtColor, speed, string);
}
static bool32 Blender_PrintText(s16 *textState, const u8 *string, s32 textSpeed)
{
switch (*textState)
{
case 0:
DrawDialogFrameWithCustomTileAndPalette(4, FALSE, 0x14, 0xF);
Blender_AddTextPrinter(4, string, 0, 1, textSpeed, 0);
PutWindowTilemap(4);
CopyWindowToVram(4, 3);
(*textState)++;
break;
case 1:
if (!IsTextPrinterActive(4))
{
*textState = 0;
return TRUE;
}
break;
}
return FALSE;
}