mirror of
https://github.com/WiIIiam278/breaking-bad-ds.git
synced 2025-06-19 01:05:33 -04:00

* Fix wrong line endings * Fixup launch tasks * Fixup nflib lib dir * Fixup debugger, improve dialogue save tracking * Fixup typo * Improve stability of game end logic * Dialogue text fixes * Fix SFX bug on Hank's mineral screen * Bump to 1.0.6 * Add SFX to cracking minigame
1032 lines
30 KiB
C++
1032 lines
30 KiB
C++
#include "menu.h"
|
|
|
|
Menu::Menu()
|
|
{
|
|
state = MENU_LOADING;
|
|
highlightedItem = NONE;
|
|
}
|
|
|
|
int Menu::Load()
|
|
{
|
|
skybox = NE_ModelCreate(NE_Static);
|
|
skyboxMaterial = NE_MaterialCreate();
|
|
|
|
logo = NE_ModelCreate(NE_Static);
|
|
logoMaterial = NE_MaterialCreate();
|
|
|
|
text = NE_ModelCreate(NE_Static);
|
|
textMaterial = NE_MaterialCreate();
|
|
|
|
// Load assets from the filesystem
|
|
if (NE_ModelLoadStaticMeshFAT(skybox, "model/logo_skybox.dl") == 0 || NE_ModelLoadStaticMeshFAT(logo, "model/logo.dl") == 0 || NE_ModelLoadStaticMeshFAT(text, "model/logo_text.dl") == 0)
|
|
{
|
|
consoleDemoInit();
|
|
printf("Couldn't load logo mesh/text plane...");
|
|
return -1;
|
|
}
|
|
if (NE_MaterialTexLoadFAT(skyboxMaterial, NE_A1RGB5, 256, 256, NE_TEXGEN_TEXCOORD, "model/logo_skybox_tex.bin") == 0 || NE_MaterialTexLoadFAT(logoMaterial, NE_A1RGB5, 128, 128, NE_TEXGEN_TEXCOORD, "model/logo_tex.bin") == 0 || NE_MaterialTexLoadFAT(textMaterial, NE_A1RGB5, 128, 128, NE_TEXGEN_TEXCOORD, "model/logo_text_tex.bin") == 0)
|
|
{
|
|
consoleDemoInit();
|
|
printf("Couldn't load logo/text textures...");
|
|
return -1;
|
|
}
|
|
|
|
// Assign material to the model
|
|
NE_ModelSetMaterial(skybox, skyboxMaterial);
|
|
NE_ModelSetMaterial(logo, logoMaterial);
|
|
NE_MaterialSetPropierties(logoMaterial,
|
|
RGB15(24, 24, 24), // Diffuse
|
|
RGB15(16, 16, 16), // Ambient
|
|
RGB15(8, 8, 8), // Specular
|
|
NE_White, // Emission
|
|
false, false); // Vertex color, use shininess table
|
|
NE_ModelSetMaterial(text, textMaterial);
|
|
NE_MaterialSetPropierties(textMaterial,
|
|
RGB15(24, 24, 24), // Diffuse
|
|
RGB15(16, 16, 16), // Ambient
|
|
RGB15(8, 8, 8), // Specular
|
|
NE_White, // Emission
|
|
false, false); // Vertex color, use shininess table
|
|
NE_ModelSetMaterial(skybox, skyboxMaterial);
|
|
NE_MaterialSetPropierties(skyboxMaterial,
|
|
RGB15(0, 0, 0), // Diffuse
|
|
RGB15(12, 12, 12), // Ambient
|
|
RGB15(8, 8, 8), // Specular
|
|
NE_White, // Emission
|
|
false, false); // Vertex color, use shininess table
|
|
|
|
// Set model rotation and scale
|
|
int scale = 8250;
|
|
NE_ModelScaleI(skybox, scale * 8, scale * 8, scale * 8);
|
|
NE_ModelScaleI(logo, scale, scale, scale);
|
|
NE_ModelScaleI(text, scale, scale, scale);
|
|
|
|
// Position models
|
|
x = TARGET_X - 10;
|
|
textX = TARGET_X - 16;
|
|
NE_ModelSetCoordI(skybox, 0, 0, 0);
|
|
NE_ModelSetCoordI(logo, 0, 0, 0);
|
|
NE_ModelSetCoordI(text, 0, 0, 0);
|
|
NE_ModelTranslate(skybox, SKYPOS_POS[0], SKYPOS_POS[1], SKYPOS_POS[2]);
|
|
NE_ModelTranslate(logo, x, Y, Z);
|
|
NE_ModelTranslate(text, textX, Y + 0.1, Z + 4.55);
|
|
|
|
// Mark meshes for removal when deleted
|
|
NE_ModelFreeMeshWhenDeleted(skybox);
|
|
NE_ModelFreeMeshWhenDeleted(logo);
|
|
NE_ModelFreeMeshWhenDeleted(text);
|
|
|
|
// Prepare sprites
|
|
LoadInterfaceSprites();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Menu::LoadInterfaceSprites()
|
|
{
|
|
NF_LoadSpriteGfx(MENU_MISC_SPRITE_NAME, 1, 64, 32);
|
|
NF_LoadSpritePal(MENU_MISC_SPRITE_NAME, 1);
|
|
NF_VramSpriteGfx(1, 1, 0, false);
|
|
NF_VramSpritePal(1, 1, 0);
|
|
|
|
// Create, position & scale sprite
|
|
NF_CreateSprite(1, START_SPRITE, 0, 0, 64, 64);
|
|
NF_EnableSpriteRotScale(1, START_SPRITE, START_SPRITE, true);
|
|
NF_SpriteRotScale(1, START_SPRITE, 0, 283, 283);
|
|
|
|
// Load UI sprites
|
|
NF_LoadSpriteGfx(MENU_BUTTONS_SPRITE_NAME, BUTTON_GFX_ID, MENU_SPRITE_SIZE[0], MENU_SPRITE_SIZE[1]);
|
|
NF_LoadSpritePal(MENU_BUTTONS_SPRITE_NAME, BUTTON_GFX_ID);
|
|
NF_VramSpriteGfx(1, BUTTON_GFX_ID, BUTTON_GFX_ID, false);
|
|
NF_VramSpritePal(1, BUTTON_GFX_ID, BUTTON_GFX_ID);
|
|
|
|
NF_LoadSpriteGfx(MENU_TEXT_SPRITE_NAME, TEXT_GFX_ID, MENU_SPRITE_SIZE[0], MENU_SPRITE_SIZE[1]);
|
|
NF_LoadSpritePal(MENU_TEXT_SPRITE_NAME, TEXT_GFX_ID);
|
|
NF_VramSpriteGfx(1, TEXT_GFX_ID, TEXT_GFX_ID, false);
|
|
NF_VramSpritePal(1, TEXT_GFX_ID, TEXT_GFX_ID);
|
|
}
|
|
|
|
void Menu::UnloadInterfaceSprites()
|
|
{
|
|
NF_DeleteSprite(1, START_SPRITE);
|
|
NF_UnloadSpriteGfx(1);
|
|
NF_UnloadSpritePal(1);
|
|
NF_FreeSpriteGfx(1, 0);
|
|
NF_UnloadSpriteGfx(BUTTON_GFX_ID);
|
|
NF_UnloadSpritePal(BUTTON_GFX_ID);
|
|
NF_FreeSpriteGfx(1, BUTTON_GFX_ID);
|
|
NF_UnloadSpriteGfx(TEXT_GFX_ID);
|
|
NF_UnloadSpritePal(TEXT_GFX_ID);
|
|
NF_FreeSpriteGfx(1, TEXT_GFX_ID);
|
|
}
|
|
|
|
void Menu::ShowLayout()
|
|
{
|
|
if (currentLayout != nullptr)
|
|
{
|
|
if (currentLayout->buttonCount > 0)
|
|
{
|
|
for (int i = 0; i < currentLayout->buttonCount; i++)
|
|
{
|
|
NF_DeleteSprite(1, TEXT_SPRITES[i]);
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
NF_DeleteSprite(1, BUTTON_SPRITES_BASE + (i * 4) + j);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (currentLayout->titleSprite != -1)
|
|
{
|
|
NF_DeleteSprite(1, TITLE_SPRITE);
|
|
}
|
|
}
|
|
|
|
Layout *layout = GetLayoutForState(state);
|
|
if (layout == nullptr)
|
|
{
|
|
currentLayout = nullptr;
|
|
return;
|
|
}
|
|
|
|
menuCaret[0] = menuCaret[1] = 0;
|
|
if (layout->allowCaretNavigation && layout->buttonCount > 1)
|
|
{
|
|
highlightedItem = layout->caretMap[0][0];
|
|
}
|
|
|
|
if (layout->buttonCount > 0)
|
|
{
|
|
for (int b = 0; b < layout->buttonCount; b++)
|
|
{
|
|
int spriteOffset = b * 4;
|
|
Button button = layout->buttons[b];
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
int xOffset = (i == 1 || i == 3) ? 64 : 0;
|
|
int yOffset = (i == 2 || i == 3) ? 32 : 0;
|
|
int spriteId = BUTTON_SPRITES_BASE + (spriteOffset + i);
|
|
NF_CreateSprite(1, spriteId, BUTTON_GFX_ID, BUTTON_GFX_ID, layout->buttonCoords[b][0] + xOffset, layout->buttonCoords[b][1] + yOffset);
|
|
NF_HflipSprite(1, spriteId, i % 2 != 0);
|
|
NF_VflipSprite(1, spriteId, i > 1);
|
|
NF_SpriteFrame(1, spriteId, button.buttonSprite);
|
|
}
|
|
NF_CreateSprite(1, TEXT_SPRITES[b], TEXT_GFX_ID, TEXT_GFX_ID, layout->buttonCoords[b][0] + 32, layout->buttonCoords[b][1] + 16);
|
|
NF_SpriteFrame(1, TEXT_SPRITES[b], button.textSprite);
|
|
}
|
|
}
|
|
|
|
if (layout->titleSprite != -1)
|
|
{
|
|
NF_CreateSprite(1, TITLE_SPRITE, TEXT_GFX_ID, TEXT_GFX_ID, layout->titleCoords[0], layout->titleCoords[1]);
|
|
NF_SpriteFrame(1, TITLE_SPRITE, layout->titleSprite);
|
|
}
|
|
|
|
currentLayout = layout;
|
|
}
|
|
|
|
void Menu::UpdateLayout(volatile int frame)
|
|
{
|
|
const Layout *layout = GetLayoutForState(state);
|
|
if (layout == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int b = 0; b < layout->buttonCount; b++)
|
|
{
|
|
int spriteOffset = b * 4;
|
|
Button button = layout->buttons[b];
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
int spriteId = BUTTON_SPRITES_BASE + (spriteOffset + i);
|
|
NF_SpriteFrame(1, spriteId, button.buttonSprite + (highlightedItem == button.selection ? 4 : 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Menu::ShowBackground()
|
|
{
|
|
if ((state == MENU_LOGO || state == MENU_LOADING))
|
|
{
|
|
if (currentBackground != 0)
|
|
{
|
|
NF_DeleteTiledBg(1, MENU_BG_ID);
|
|
NF_UnloadTiledBg(currentBgName);
|
|
}
|
|
currentBackground = 0;
|
|
}
|
|
else
|
|
{
|
|
Layout *layout = GetLayoutForState(state);
|
|
if (layout == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (currentBackground != layout->backgroundId)
|
|
{
|
|
if (currentBackground != 0)
|
|
{
|
|
NF_DeleteTiledBg(1, MENU_BG_ID);
|
|
NF_UnloadTiledBg(currentBgName);
|
|
}
|
|
currentBackground = layout->backgroundId;
|
|
currentBgName = layout->backgroundName;
|
|
NF_LoadTiledBg(currentBgName, currentBgName, 256, 256);
|
|
NF_CreateTiledBg(1, MENU_BG_ID, currentBgName);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Menu::SetState(MenuState newState, volatile int frame, Sound *sound)
|
|
{
|
|
Layout *newLayout = GetLayoutForState(newState);
|
|
bool transitionNeeded = false;
|
|
if ((newLayout != nullptr) && (transitionNeeded = (newLayout->backgroundId != currentBackground)))
|
|
{
|
|
Transition(false, 0, TS_BOTTOM, frame);
|
|
}
|
|
|
|
if (newState == MENU_LOADING)
|
|
{
|
|
sound->StopBGM();
|
|
}
|
|
else if (state == MENU_LOGO && newState != MENU_LOGO)
|
|
{
|
|
sound->PlayBGM(BGM_TITLE_LOOP, true);
|
|
}
|
|
else if (newState == MENU_LOGO)
|
|
{
|
|
sound->PlayBGM(BGM_TITLE_INTRO, false);
|
|
}
|
|
|
|
if (state == MENU_MUSIC_PLAYER && newState == MENU_MAIN)
|
|
{
|
|
sound->PlayBGM(BGM_TITLE_LOOP, true);
|
|
currentSoundTestTrack = 1;
|
|
}
|
|
|
|
if ((state == MENU_MP_HOST_ROOM || state == MENU_MP_JOIN_ROOM) && (newState != MENU_MP_HOST_ROOM && newState != MENU_MP_JOIN_ROOM))
|
|
{
|
|
ShowMultiplayerStatus(false);
|
|
}
|
|
else if (newState == MENU_MP_HOST_ROOM)
|
|
{
|
|
StartMultiplayer(true);
|
|
}
|
|
else if (newState == MENU_MP_JOIN_ROOM)
|
|
{
|
|
StartMultiplayer(false);
|
|
}
|
|
|
|
if (newState == MENU_MINERALS && state != MENU_MINERALS)
|
|
{
|
|
ShowMinerals(true);
|
|
}
|
|
else if (state == MENU_MINERALS && newState != MENU_MINERALS)
|
|
{
|
|
ShowMinerals(false);
|
|
}
|
|
|
|
if (state != MENU_LOGO && newState != MENU_LOGO)
|
|
{
|
|
sound->PlaySFX(SFX_MENU_DRUM);
|
|
rumble(state % 2 == 0);
|
|
}
|
|
state = newState;
|
|
ShowLayout();
|
|
ShowBackground();
|
|
|
|
if (transitionNeeded)
|
|
{
|
|
Transition(true, 15, TS_BOTTOM, frame);
|
|
}
|
|
}
|
|
|
|
void Menu::PositionLogo()
|
|
{
|
|
// Position logo
|
|
NE_ModelSetCoordI(logo, 0, 0, 0);
|
|
NE_ModelSetCoordI(text, 0, 0, 0);
|
|
NE_ModelTranslate(logo, TARGET_X, Y, Z);
|
|
NE_ModelTranslate(text, TARGET_TEXT_X, Y + 0.1, Z + 4.55);
|
|
}
|
|
|
|
void Menu::StartMultiplayer(bool mpCreatingRoom)
|
|
{
|
|
this->mpCreatingRoom = mpCreatingRoom;
|
|
joinMultiplayer(mpCreatingRoom);
|
|
}
|
|
|
|
void Menu::UpdateMultiplayer()
|
|
{
|
|
tickMultiplayer();
|
|
mpCurrentStatus = getMultiplayerStatus();
|
|
ShowMultiplayerStatus(true);
|
|
}
|
|
|
|
void Menu::ShowMultiplayerStatus(bool showSprite)
|
|
{
|
|
if (!showSprite && mpShowingStatus)
|
|
{
|
|
NF_DeleteSprite(1, MP_STATUS_SPRITE);
|
|
}
|
|
else if (showSprite)
|
|
{
|
|
if (!mpShowingStatus && mpCurrentStatus > -1)
|
|
{
|
|
NF_CreateSprite(1, MP_STATUS_SPRITE, TEXT_GFX_ID, TEXT_GFX_ID, (SCREEN_WIDTH / 2) - 32, (SCREEN_HEIGHT / 2) - 16);
|
|
}
|
|
|
|
int statusSpriteId;
|
|
switch (mpCurrentStatus)
|
|
{
|
|
case 0:
|
|
statusSpriteId = 12;
|
|
break;
|
|
case 1:
|
|
statusSpriteId = 11;
|
|
break;
|
|
case 2:
|
|
statusSpriteId = 14;
|
|
break;
|
|
case 3:
|
|
statusSpriteId = 13;
|
|
break;
|
|
default:
|
|
statusSpriteId = 15;
|
|
break;
|
|
}
|
|
|
|
NF_SpriteFrame(1, MP_STATUS_SPRITE, statusSpriteId);
|
|
}
|
|
mpShowingStatus = showSprite;
|
|
}
|
|
|
|
void Menu::ShowMinerals(bool showSprites)
|
|
{
|
|
int row = 0;
|
|
int col = 0;
|
|
for (int i = 0; i < MINERAL_COUNT; i++)
|
|
{
|
|
int spriteId = MINERAL_SPRITE_BASE + i;
|
|
row = i / MINERALS_PER_ROW;
|
|
col = i % MINERALS_PER_ROW;
|
|
int pos[2] = {MINERAL_GRID_BASE_POS[0] + ((32 + MINERAL_GAP) * col), MINERAL_GRID_BASE_POS[1] + ((32 + MINERAL_GAP) * row)};
|
|
|
|
if (showSprites)
|
|
{
|
|
NF_CreateSprite(1, spriteId, 0, 0, pos[0], pos[1]);
|
|
NF_SpriteFrame(1, spriteId, 4);
|
|
NF_ShowSprite(1, spriteId, !globalSave.minerals[i]);
|
|
}
|
|
else
|
|
{
|
|
NF_DeleteSprite(1, MINERAL_SPRITE_BASE + i);
|
|
}
|
|
}
|
|
|
|
if (showSprites)
|
|
{
|
|
NF_CreateSprite(1, MINERAL_CURSOR_SPRITE, 0, 0, MINERAL_GRID_BASE_POS[0], MINERAL_GRID_BASE_POS[1]);
|
|
NF_SpriteFrame(1, MINERAL_CURSOR_SPRITE, 3);
|
|
|
|
NF_CreateSprite(1, HANK_EYES_SPRITE, 0, 0, HANK_EYES_POS[0], HANK_EYES_POS[1]);
|
|
NF_SpriteFrame(1, HANK_EYES_SPRITE, 5);
|
|
NF_ShowSprite(1, HANK_EYES_SPRITE, false);
|
|
}
|
|
else
|
|
{
|
|
NF_DeleteSprite(1, MINERAL_CURSOR_SPRITE);
|
|
NF_DeleteSprite(1, HANK_EYES_SPRITE);
|
|
}
|
|
}
|
|
|
|
char* Menu::GetCharacterName(Character character) {
|
|
switch (character) {
|
|
case CHAR_WALT:
|
|
return "Walt";
|
|
case CHAR_JESSE:
|
|
return "Jesse";
|
|
case CHAR_YEPPERS:
|
|
return "Kusuri";
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
void Menu::UpdateMinerals(volatile int frame, Sound *sound)
|
|
{
|
|
int row = 0;
|
|
int col = 0;
|
|
NF_ShowSprite(1, MINERAL_CURSOR_SPRITE, currentlySelectedMineral != -1);
|
|
|
|
bool touching = false;
|
|
touchPosition touch;
|
|
if (keysDown() & KEY_TOUCH)
|
|
{
|
|
touchRead(&touch);
|
|
touching = touch.px >= MINERAL_GRID_BASE_POS[0] && touch.px <= MINERAL_GRID_BASE_POS[0] + ((32 + MINERAL_GAP) * MINERALS_PER_ROW)
|
|
&& touch.py > MINERAL_GRID_BASE_POS[1] && touch.py < MINERAL_GRID_BASE_POS[1] + ((32 + MINERAL_GAP) * (MINERAL_COUNT / MINERALS_PER_ROW));
|
|
|
|
if (!touching)
|
|
{
|
|
currentlySelectedMineral = -1;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < MINERAL_COUNT; i++)
|
|
{
|
|
int spriteId = MINERAL_SPRITE_BASE + i;
|
|
NF_ShowSprite(1, spriteId, !globalSave.minerals[i]);
|
|
|
|
row = i / MINERALS_PER_ROW;
|
|
col = i % MINERALS_PER_ROW;
|
|
int pos[2] = {MINERAL_GRID_BASE_POS[0] + ((32 + MINERAL_GAP) * col), MINERAL_GRID_BASE_POS[1] + ((32 + MINERAL_GAP) * row)};
|
|
|
|
if (touching)
|
|
{
|
|
bool touchingMineral = touch.px >= pos[0] && touch.px <= pos[0] + 32 && touch.py >= pos[1] && touch.py <= pos[1] + 32;
|
|
|
|
if (touchingMineral && currentlySelectedMineral != i) {
|
|
sound->PlaySFX(SFX_MENU_SELECT);
|
|
}
|
|
|
|
currentlySelectedMineral = touchingMineral ? i : currentlySelectedMineral;
|
|
}
|
|
|
|
if (currentlySelectedMineral == i)
|
|
{
|
|
NF_MoveSprite(1, MINERAL_CURSOR_SPRITE, pos[0], pos[1]);
|
|
}
|
|
}
|
|
|
|
// Hank blink animation
|
|
int blinkRange = (hankLastBlink - frame);
|
|
bool isBlinking = false;
|
|
if (blinkRange < 10 && blinkRange > 0)
|
|
{
|
|
isBlinking = true;
|
|
}
|
|
if (blinkRange <= 0)
|
|
{
|
|
hankLastBlink = frame + (rand() % 100) + 100;
|
|
}
|
|
NF_ShowSprite(1, HANK_EYES_SPRITE, isBlinking);
|
|
}
|
|
|
|
void Menu::Update(volatile int frame, Sound *sound)
|
|
{
|
|
NF_ClearTextLayer(1, 0);
|
|
|
|
switch (state)
|
|
{
|
|
case MENU_LOADING:
|
|
NF_ShowSprite(1, START_SPRITE, false);
|
|
break;
|
|
|
|
case MENU_LOGO:
|
|
if (x < TARGET_X)
|
|
{
|
|
x += X_SPEED;
|
|
NE_ModelTranslate(logo, X_SPEED, 0, 0);
|
|
}
|
|
if (textX < TARGET_TEXT_X)
|
|
{
|
|
textX += X_SPEED;
|
|
NE_ModelTranslate(text, X_SPEED, 0, 0);
|
|
}
|
|
|
|
NF_SpriteFrame(1, START_SPRITE, (frame > 345) ? 1 : 0);
|
|
NE_ModelRotate(skybox, (frame % 4 == 0 ? 1 : 0), 0, 0);
|
|
|
|
if (frame >= 690)
|
|
{
|
|
SetState(MENU_TITLE, frame, sound);
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case MENU_TITLE:
|
|
if (frame % 30 == 0)
|
|
{
|
|
showStartSprite = !showStartSprite;
|
|
}
|
|
if (!canSave()) {
|
|
NF_WriteText(1, 0, 0, 0, "Cannot save game!");
|
|
}
|
|
NF_WriteText(1, 0, 26, 0, "v1.0.6");
|
|
break;
|
|
|
|
case MENU_RUMBLE:
|
|
if (!isDSiMode()) {
|
|
NF_WriteText(1, 0, 2, 7, "A DS Rumble Pak is required.");
|
|
NF_WriteText(1, 0, 2, 8, "to enable this feature. If a");
|
|
NF_WriteText(1, 0, 2, 9, "Rumble Pak is inserted into");
|
|
NF_WriteText(1, 0, 2, 10, "SLOT-2, you should now feel.");
|
|
NF_WriteText(1, 0, 2, 11, "the rumble effect.");
|
|
|
|
NF_WriteText(1, 0, 2, 14, "Remove the Rumble Pak from");
|
|
NF_WriteText(1, 0, 2, 15, "SLOT-2 to disable rumble.");
|
|
|
|
rumble(frame % 2 == 0);
|
|
}
|
|
else {
|
|
NF_WriteText(1, 0, 2, 9, "Rumble support is disabled.");
|
|
NF_WriteText(1, 0, 2, 11, "You do not have a system");
|
|
NF_WriteText(1, 0, 2, 12, "that supports the rumble");
|
|
NF_WriteText(1, 0, 2, 13, "feature of this game.");
|
|
}
|
|
break;
|
|
|
|
case MENU_MINERALS:
|
|
if (currentlySelectedMineral == -1)
|
|
{
|
|
NF_WriteText(1, 0, 2, 1, "Walt! If you happen upon any");
|
|
NF_WriteText(1, 0, 2, 3, "minerals, I'll appraise 'em!");
|
|
}
|
|
else
|
|
{
|
|
const Mineral *mineral = &MINERALS[currentlySelectedMineral];
|
|
if (globalSave.minerals[currentlySelectedMineral])
|
|
{
|
|
NF_WriteText(1, 0, 1, 1, mineral->name);
|
|
NF_WriteText(1, 0, 1, 3, mineral->description);
|
|
}
|
|
else
|
|
{
|
|
NF_WriteText(1, 0, 1, 1, "???");
|
|
if (mineral->isSecret)
|
|
{
|
|
NF_WriteText(1, 0, 1, 3, "?????????");
|
|
}
|
|
else
|
|
{
|
|
NF_WriteText(1, 0, 1, 3, mineral->description);
|
|
}
|
|
}
|
|
}
|
|
UpdateMinerals(frame, sound);
|
|
break;
|
|
|
|
case MENU_MUSIC_PLAYER:
|
|
NF_WriteText(1, 0, 2, 9, "Currently playing:");
|
|
char trackNoAndTitle[50];
|
|
sprintf(trackNoAndTitle, "%d. %s", currentSoundTestTrack + 1, BGMS[currentSoundTestTrack].name);
|
|
NF_WriteText(1, 0, 2, 10, trackNoAndTitle);
|
|
NF_WriteText(1, 0, 2, 11, sound->GetBgmTrackProgressString());
|
|
NF_WriteText(1, 0, 2, 13, "LEFT/RIGHT to change track.");
|
|
break;
|
|
|
|
case MENU_MP_HOST_ROOM:
|
|
case MENU_MP_JOIN_ROOM:
|
|
UpdateMultiplayer();
|
|
break;
|
|
|
|
case MENU_CUSTOM_GAME:
|
|
NF_WriteText(1, 0, 2, 2, "- Custom Cook -");
|
|
|
|
// Option config
|
|
char editorText[50];
|
|
for (int i = 0; i < CUSTOM_GAME_OPTION_COUNT; i++)
|
|
{
|
|
switch (i) {
|
|
case 0:
|
|
if (customGameValues[i] == 5) {
|
|
sprintf(
|
|
editorText,
|
|
"%s%s: Final Cook", customGameCursor == i ? "> " : "",
|
|
customGameIds[i]
|
|
);
|
|
}
|
|
else
|
|
{
|
|
sprintf(
|
|
editorText,
|
|
"%s%s: %i", customGameCursor == i ? "> " : "",
|
|
customGameIds[i],
|
|
customGameValues[i] + 1
|
|
);
|
|
}
|
|
break;
|
|
case 1:
|
|
sprintf(
|
|
editorText,
|
|
"%s%s: $%i", customGameCursor == i ? "> " : "",
|
|
customGameIds[i],
|
|
customGameValues[i]
|
|
);
|
|
break;
|
|
case 2:
|
|
sprintf(
|
|
editorText,
|
|
"%s%s: %s", customGameCursor == i ? "> " : "",
|
|
customGameIds[i],
|
|
GetCharacterName(static_cast<Character>(customGameValues[i]))
|
|
);
|
|
break;
|
|
default:
|
|
sprintf(
|
|
editorText,
|
|
"%s%s: %s", customGameCursor == i ? "> " : "",
|
|
customGameIds[i],
|
|
customGameValues[i] ? "On" : "Off"
|
|
);
|
|
break;
|
|
}
|
|
|
|
NF_WriteText(1, 0, 3 - (customGameCursor == i ? 2 : 0), 5 + (i * 2), editorText);
|
|
}
|
|
|
|
NF_WriteText(1, 0, 2, 20, "UP/DOWN: Select");
|
|
NF_WriteText(1, 0, 2, 21, "LEFT/RIGHT: Edit");
|
|
break;
|
|
}
|
|
|
|
if (GetLayoutForState(state) != nullptr)
|
|
{
|
|
UpdateLayout(frame);
|
|
NF_ShowSprite(1, START_SPRITE, state == MENU_TITLE && showStartSprite);
|
|
NF_SpriteFrame(1, START_SPRITE, 2);
|
|
NE_ModelRotate(skybox, 0, (frame % 8 == 0 ? 1 : 0), 0);
|
|
}
|
|
}
|
|
|
|
MenuSelection Menu::HandleInput(volatile int frame, Sound *sound)
|
|
{
|
|
touchPosition touch;
|
|
switch (state)
|
|
{
|
|
case MENU_LOGO:
|
|
if (keysDown() & KEY_TOUCH || keysDown() & KEY_A || keysDown() & KEY_START)
|
|
{
|
|
return SKIP_LOGO;
|
|
}
|
|
return NONE;
|
|
|
|
case MENU_TITLE:
|
|
if (keysDown() & KEY_TOUCH || keysDown() & KEY_A || keysDown() & KEY_START)
|
|
{
|
|
SetState(MENU_MAIN, frame, sound);
|
|
}
|
|
return NONE;
|
|
|
|
case MENU_MUSIC_PLAYER:
|
|
if (keysDown() & KEY_RIGHT || keysDown() & KEY_LEFT)
|
|
{
|
|
if (keysDown() & KEY_RIGHT)
|
|
{
|
|
currentSoundTestTrack++;
|
|
sound->PlaySFX(SFX_MENU_SELECT);
|
|
|
|
if (currentSoundTestTrack >= BGM_COUNT)
|
|
{
|
|
currentSoundTestTrack = 0;
|
|
}
|
|
}
|
|
else if (keysDown() & KEY_LEFT)
|
|
{
|
|
currentSoundTestTrack--;
|
|
sound->PlaySFX(SFX_MENU_SELECT);
|
|
|
|
if (currentSoundTestTrack < 0)
|
|
{
|
|
currentSoundTestTrack = BGM_COUNT - 1;
|
|
}
|
|
}
|
|
|
|
sound->PlayBGM(static_cast<TrackId>(currentSoundTestTrack), true);
|
|
rumble(currentSoundTestTrack % 2 == 0);
|
|
}
|
|
return HandleLayoutInput(frame, sound, touch);
|
|
|
|
case MENU_MP_HOST_ROOM:
|
|
if (mpCurrentStatus == MP_HOST_READY)
|
|
{
|
|
if (keysDown() & KEY_A)
|
|
{
|
|
sound->PlaySFX(SFX_MENU_DRUM);
|
|
return START_MP_GAME;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case MENU_MP_JOIN_ROOM:
|
|
if (mpCurrentStatus == MP_CLIENT_READY && (getOpponent()->timeLeft > -1))
|
|
{
|
|
sound->PlaySFX(SFX_MENU_DRUM);
|
|
return START_MP_GAME;
|
|
}
|
|
break;
|
|
|
|
case MENU_CUSTOM_GAME:
|
|
if (keysDown() & KEY_RIGHT || keysDown() & KEY_LEFT
|
|
|| keysDown() & KEY_DOWN || keysDown() & KEY_UP)
|
|
{
|
|
// Adjust cursor
|
|
if (keysDown() & KEY_UP)
|
|
{
|
|
customGameCursor--;
|
|
sound->PlaySFX(SFX_MENU_SELECT);
|
|
}
|
|
else if (keysDown() & KEY_DOWN)
|
|
{
|
|
customGameCursor++;
|
|
sound->PlaySFX(SFX_MENU_SELECT);
|
|
}
|
|
if (customGameCursor < 0)
|
|
{
|
|
customGameCursor = CUSTOM_GAME_OPTION_COUNT - 1;
|
|
}
|
|
else if (customGameCursor >= CUSTOM_GAME_OPTION_COUNT)
|
|
{
|
|
customGameCursor = 0;
|
|
}
|
|
|
|
// Adjust value
|
|
int valueMin = CUSTOM_GAME_MENU_MAP[customGameCursor][0];
|
|
int valueMax = CUSTOM_GAME_MENU_MAP[customGameCursor][1];
|
|
int valueInc = CUSTOM_GAME_MENU_MAP[customGameCursor][2];
|
|
if (keysDown() & KEY_RIGHT)
|
|
{
|
|
customGameValues[customGameCursor] += valueInc;
|
|
sound->PlaySFX(SFX_SUCCESS_BELL);
|
|
}
|
|
else if (keysDown() & KEY_LEFT)
|
|
{
|
|
customGameValues[customGameCursor] -= valueInc;
|
|
sound->PlaySFX(SFX_SUCCESS_BELL);
|
|
}
|
|
if (customGameValues[customGameCursor] < valueMin)
|
|
{
|
|
customGameValues[customGameCursor] = valueMax;
|
|
}
|
|
else if (customGameValues[customGameCursor] > valueMax)
|
|
{
|
|
customGameValues[customGameCursor] = valueMin;
|
|
}
|
|
}
|
|
return HandleLayoutInput(frame, sound, touch);
|
|
}
|
|
|
|
return HandleLayoutInput(frame, sound, touch);
|
|
}
|
|
|
|
MenuSelection Menu::HandleLayoutInput(volatile int frame, Sound *sound, touchPosition touch)
|
|
{
|
|
const Layout *layout = GetLayoutForState(state);
|
|
if (layout == nullptr)
|
|
{
|
|
return NONE;
|
|
}
|
|
|
|
if (keysDown() & KEY_TOUCH)
|
|
{
|
|
touchRead(&touch);
|
|
|
|
for (int b = 0; b < layout->buttonCount; b++)
|
|
{
|
|
if (IsTouchInBox(layout->buttonCoords[b], layout->buttons[b].dimensions, touch))
|
|
{
|
|
return HandleClick(layout->buttons[b].selection, frame, sound);
|
|
}
|
|
}
|
|
highlightedItem = NONE;
|
|
return NONE;
|
|
}
|
|
|
|
if (keysDown() & KEY_B)
|
|
{
|
|
SetState(layout->backState, frame, sound);
|
|
return NONE;
|
|
}
|
|
|
|
if (keysDown() & KEY_A || keysDown() & KEY_START)
|
|
{
|
|
return HandleClick(highlightedItem, frame, sound);
|
|
}
|
|
|
|
if (!layout->allowCaretNavigation)
|
|
{
|
|
return NONE;
|
|
}
|
|
|
|
if (keysDown() & KEY_UP || keysDown() & KEY_DOWN || keysDown() & KEY_LEFT || keysDown() & KEY_RIGHT)
|
|
{
|
|
int dCaret[2] = {0, 0};
|
|
if (keysDown() & KEY_UP)
|
|
{
|
|
dCaret[0]--;
|
|
}
|
|
else if (keysDown() & KEY_DOWN)
|
|
{
|
|
dCaret[0]++;
|
|
}
|
|
else if (keysDown() & KEY_LEFT)
|
|
{
|
|
dCaret[1]--;
|
|
}
|
|
else if (keysDown() & KEY_RIGHT)
|
|
{
|
|
dCaret[1]++;
|
|
}
|
|
|
|
if (menuCaret[0] + dCaret[0] < 0)
|
|
{
|
|
dCaret[0] = 2;
|
|
}
|
|
else if (menuCaret[0] + dCaret[0] > 2)
|
|
{
|
|
dCaret[0] = 0;
|
|
}
|
|
if (menuCaret[1] + dCaret[1] < 0)
|
|
{
|
|
dCaret[1] = 1;
|
|
}
|
|
else if (menuCaret[1] + dCaret[1] > 1)
|
|
{
|
|
dCaret[1] = 0;
|
|
}
|
|
|
|
MenuSelection selection = layout->caretMap[menuCaret[0] + dCaret[0]][menuCaret[1] + dCaret[1]];
|
|
if (selection != NONE)
|
|
{
|
|
menuCaret[0] += dCaret[0];
|
|
menuCaret[1] += dCaret[1];
|
|
highlightedItem = selection;
|
|
sound->PlaySFX(SFX_MENU_SELECT);
|
|
}
|
|
}
|
|
|
|
return NONE;
|
|
}
|
|
|
|
MenuSelection Menu::HandleClick(MenuSelection clicked, volatile int frame, Sound *sound)
|
|
{
|
|
switch (clicked)
|
|
{
|
|
case OPEN_GAME_MENU:
|
|
if (CheckSelection(clicked))
|
|
{
|
|
SetState(MENU_GAME_SELECT, frame, sound);
|
|
return OPEN_GAME_MENU;
|
|
}
|
|
return NONE;
|
|
case TOGGLE_RUMBLE:
|
|
if (CheckSelection(clicked))
|
|
{
|
|
SetState(MENU_RUMBLE, frame, sound);
|
|
return TOGGLE_RUMBLE;
|
|
}
|
|
return NONE;
|
|
case BACK_TO_TITLE:
|
|
if (CheckSelection(clicked))
|
|
{
|
|
SetState(MENU_TITLE, frame, sound);
|
|
return BACK_TO_TITLE;
|
|
}
|
|
return NONE;
|
|
case BACK_TO_MAIN_MENU:
|
|
if (CheckSelection(clicked))
|
|
{
|
|
SetState(MENU_MAIN, frame, sound);
|
|
return BACK_TO_MAIN_MENU;
|
|
}
|
|
return NONE;
|
|
case OPEN_ROOM:
|
|
if (CheckSelection(clicked))
|
|
{
|
|
SetState(MENU_MP_HOST_ROOM, frame, sound);
|
|
return OPEN_ROOM;
|
|
}
|
|
return NONE;
|
|
case SEARCH_FOR_ROOMS:
|
|
if (CheckSelection(clicked))
|
|
{
|
|
SetState(MENU_MP_JOIN_ROOM, frame, sound);
|
|
return SEARCH_FOR_ROOMS;
|
|
}
|
|
return NONE;
|
|
case OPEN_EXTRAS_MENU:
|
|
if (CheckSelection(clicked))
|
|
{
|
|
SetState(MENU_EXTRAS, frame, sound);
|
|
return OPEN_EXTRAS_MENU;
|
|
}
|
|
return NONE;
|
|
case OPEN_SOUND_TEST_MENU:
|
|
if (CheckSelection(clicked))
|
|
{
|
|
SetState(MENU_MUSIC_PLAYER, frame, sound);
|
|
return OPEN_SOUND_TEST_MENU;
|
|
}
|
|
return NONE;
|
|
case OPEN_MINERALS_MENU:
|
|
if (CheckSelection(clicked))
|
|
{
|
|
SetState(MENU_MINERALS, frame, sound);
|
|
return OPEN_MINERALS_MENU;
|
|
}
|
|
return NONE;
|
|
case BACK_TO_GAME_MENU:
|
|
if (CheckSelection(clicked))
|
|
{
|
|
if (state == MENU_MP_HOST_ROOM || state == MENU_MP_JOIN_ROOM)
|
|
{
|
|
if (mpCurrentStatus == MP_HOST_READY || mpCurrentStatus == MP_CLIENT_READY)
|
|
{
|
|
rumble(rand() % 2 == 0);
|
|
return NONE;
|
|
}
|
|
SetState(MENU_GAME_SELECT, frame, sound);
|
|
mpCurrentStatus = -1;
|
|
mpCreatingRoom = false;
|
|
disableMultiplayer();
|
|
return BACK_TO_GAME_MENU;
|
|
}
|
|
SetState(MENU_GAME_SELECT, frame, sound);
|
|
return BACK_TO_GAME_MENU;
|
|
}
|
|
return NONE;
|
|
case BACK_TO_EXTRAS_MENU:
|
|
if (CheckSelection(clicked))
|
|
{
|
|
SetState(MENU_EXTRAS, frame, sound);
|
|
return BACK_TO_EXTRAS_MENU;
|
|
}
|
|
return NONE;
|
|
case START_STORY_MODE:
|
|
if (CheckSelection(clicked)) {
|
|
if ((keysHeld() & KEY_SELECT) && (keysHeld() & KEY_R)) {
|
|
SetState(MENU_CUSTOM_GAME, frame, sound);
|
|
return OPEN_CUSTOM_GAME_MENU;
|
|
}
|
|
return clicked;
|
|
}
|
|
return NONE;
|
|
default:
|
|
if (CheckSelection(clicked))
|
|
{
|
|
return clicked;
|
|
}
|
|
return NONE;
|
|
}
|
|
return NONE;
|
|
}
|
|
|
|
bool Menu::IsTouchInBox(const int coords[2], const int boxDimensions[2], touchPosition touch)
|
|
{
|
|
int xOffset = (128 - boxDimensions[0]) / 2;
|
|
int yOffset = (64 - boxDimensions[1]) / 2;
|
|
int boxPos[2] = {coords[0] + xOffset, coords[1] + yOffset};
|
|
|
|
return (touch.px >= boxPos[0] && touch.px <= boxPos[0] + boxDimensions[0] &&
|
|
touch.py >= boxPos[1] && touch.py <= boxPos[1] + boxDimensions[1]);
|
|
}
|
|
|
|
MenuSelection Menu::CheckSelection(MenuSelection tappedBox)
|
|
{
|
|
if (tappedBox == highlightedItem)
|
|
{
|
|
return highlightedItem;
|
|
}
|
|
highlightedItem = tappedBox;
|
|
return NONE;
|
|
}
|
|
|
|
void Menu::Draw(volatile int frame)
|
|
{
|
|
if (state != MENU_LOADING)
|
|
{
|
|
NE_ModelDraw(skybox);
|
|
NE_PolyFormat(31, 8, NE_LIGHT_0, NE_CULL_NONE, NE_FOG_ENABLE);
|
|
NE_ModelDraw(logo);
|
|
NE_ModelDraw(text);
|
|
}
|
|
}
|
|
|
|
int* Menu::GetCustomGameValues()
|
|
{
|
|
return customGameValues;
|
|
}
|
|
|
|
void Menu::Unload(volatile int frame, Sound *sound)
|
|
{
|
|
SetState(MENU_LOADING, frame, sound);
|
|
ShowLayout();
|
|
ShowBackground();
|
|
ShowMultiplayerStatus(false);
|
|
|
|
// Free the model and material
|
|
NE_ModelDelete(logo);
|
|
NE_ModelDelete(text);
|
|
NE_ModelDelete(skybox);
|
|
|
|
NE_MaterialDelete(logoMaterial);
|
|
NE_MaterialDelete(textMaterial);
|
|
NE_MaterialDelete(skyboxMaterial);
|
|
|
|
// Unload sprites
|
|
UnloadInterfaceSprites();
|
|
NF_VramSpriteGfxDefrag(1);
|
|
} |