breaking-bad-ds/source/menu.cpp
William cd9753f5f6
Fix line endings, bug, text & sound fixes (#8)
* 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
2023-11-22 01:04:19 +00:00

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);
}