diff --git a/src/menu/confirmation_dialog.c b/src/menu/confirmation_dialog.c new file mode 100644 index 0000000..8892432 --- /dev/null +++ b/src/menu/confirmation_dialog.c @@ -0,0 +1,223 @@ +#include "./confirmation_dialog.h" + +#include "../font/dejavusans.h" +#include "../controls/controller.h" +#include "../audio/soundplayer.h" +#include "./translations.h" + +#include "../build/assets/materials/ui.h" +#include "../build/src/audio/clips.h" +#include "../build/src/audio/subtitles.h" + +#define DIALOG_LEFT 40 +#define DIALOG_WIDTH (SCREEN_WD - (DIALOG_LEFT * 2)) +#define DIALOG_PADDING 8 + +#define TEXT_MARGIN 4 + +#define CONTENT_LEFT (DIALOG_LEFT + DIALOG_PADDING) +#define CONTENT_WIDTH (DIALOG_WIDTH - (DIALOG_PADDING * 2)) +#define CONTENT_CENTER_X (CONTENT_LEFT + (CONTENT_WIDTH / 2)) +#define CONTENT_MARGIN 20 + +#define BUTTON_HEIGHT 16 +#define BUTTON_MARGIN 8 + +static struct Coloru8 gDialogColor = {164, 164, 164, 255}; + +void confirmationDialogInit(struct ConfirmationDialog* confirmationDialog) { + confirmationDialog->menuOutline = menuBuildBorder(0, 0, 0, 0); + confirmationDialog->titleText = NULL; + confirmationDialog->messageText = NULL; + confirmationDialog->selectedButton = NULL; + confirmationDialog->closeCallback = NULL; + confirmationDialog->callbackData = NULL; + confirmationDialog->isShown = 0; + + confirmationDialog->confirmButton = menuBuildButton( + &gDejaVuSansFont, "", + 0, + 0, + BUTTON_HEIGHT, + 0 + ); + confirmationDialog->cancelButton = menuBuildButton( + &gDejaVuSansFont, "", + 0, + 0, + BUTTON_HEIGHT, + 0 + ); +} + +static void confirmationDialogLayout(struct ConfirmationDialog* confirmationDialog) { + int dialogHeight = CONTENT_MARGIN + confirmationDialog->messageText->height + BUTTON_MARGIN + BUTTON_HEIGHT + DIALOG_PADDING; + int dialogTop = (SCREEN_HT - dialogHeight) / 2; + + menuRerenderBorder( + DIALOG_LEFT, + dialogTop, + DIALOG_WIDTH, + dialogHeight, + confirmationDialog->menuOutline + ); + prerenderedTextRelocate( + confirmationDialog->titleText, + CONTENT_LEFT, + dialogTop + TEXT_MARGIN + ); + prerenderedTextRelocate( + confirmationDialog->messageText, + CONTENT_LEFT, + dialogTop + CONTENT_MARGIN + ); + + int buttonsWidth = confirmationDialog->confirmButton.w + confirmationDialog->cancelButton.w + BUTTON_MARGIN; + int buttonsX = CONTENT_CENTER_X - (buttonsWidth / 2); + int buttonsY = confirmationDialog->messageText->y + confirmationDialog->messageText->height + BUTTON_MARGIN; + + menuRelocateButton( + &confirmationDialog->confirmButton, + buttonsX, + buttonsY, + 0 + ); + menuRelocateButton( + &confirmationDialog->cancelButton, + buttonsX + confirmationDialog->confirmButton.w + BUTTON_MARGIN, + buttonsY, + 0 + ); +} + +void confirmationDialogShow(struct ConfirmationDialog* confirmationDialog, struct ConfirmationDialogParams* params) { + if (confirmationDialog->titleText) { + prerenderedTextFree(confirmationDialog->titleText); + } + if (confirmationDialog->messageText) { + prerenderedTextFree(confirmationDialog->messageText); + } + + confirmationDialog->titleText = menuBuildPrerenderedText( + &gDejaVuSansFont, params->title, + 0, + 0, + SCREEN_WD + ); + confirmationDialog->messageText = menuBuildPrerenderedText( + &gDejaVuSansFont, params->message, + 0, + 0, + CONTENT_WIDTH + ); + + menuRebuildButtonText( + &confirmationDialog->confirmButton, + &gDejaVuSansFont, + params->confirmLabel, + 0 + ); + menuRebuildButtonText( + &confirmationDialog->cancelButton, + &gDejaVuSansFont, + params->cancelLabel, + 0 + ); + + confirmationDialogLayout(confirmationDialog); + + confirmationDialog->selectedButton = &confirmationDialog->cancelButton; + confirmationDialog->closeCallback = params->closeCallback; + confirmationDialog->callbackData = params->callbackData; + confirmationDialog->isShown = 1; +} + +static void confirmationDialogClose(struct ConfirmationDialog* confirmationDialog, int isConfirmed) { + ConfirmationDialogCallback callback = confirmationDialog->closeCallback; + if (callback) { + callback(confirmationDialog->callbackData, isConfirmed); + } + + confirmationDialog->isShown = 0; +} + +enum InputCapture confirmationDialogUpdate(struct ConfirmationDialog* confirmationDialog) { + if (controllerGetButtonDown(0, B_BUTTON)) { + confirmationDialogClose(confirmationDialog, 0); + } else if (controllerGetButtonDown(0, A_BUTTON)) { + if (confirmationDialog->selectedButton == &confirmationDialog->confirmButton) { + confirmationDialogClose(confirmationDialog, 1); + } else { + confirmationDialogClose(confirmationDialog, 0); + } + + soundPlayerPlay(SOUNDS_BUTTONCLICKRELEASE, 1.0f, 0.5f, NULL, NULL, SoundTypeAll); + } + + int controllerDir = controllerGetDirectionDown(0); + if (controllerDir & (ControllerDirectionLeft | ControllerDirectionRight)) { + if (confirmationDialog->selectedButton == &confirmationDialog->confirmButton) { + confirmationDialog->selectedButton = &confirmationDialog->cancelButton; + } else { + confirmationDialog->selectedButton = &confirmationDialog->confirmButton; + } + soundPlayerPlay(SOUNDS_BUTTONROLLOVER, 1.0f, 0.5f, NULL, NULL, SoundTypeAll); + } + + return InputCaptureGrab; +} + +static void renderButtonText(struct ConfirmationDialog* confirmationDialog, struct MenuButton* button, struct PrerenderedTextBatch* batch) { + struct Coloru8* color = confirmationDialog->selectedButton == button ? &gColorBlack : &gColorWhite; + prerenderedBatchAdd(batch, button->text, color); +} + +void confirmationDialogRender(struct ConfirmationDialog* confirmationDialog, struct RenderState* renderState) { + gSPDisplayList(renderState->dl++, ui_material_list[DEFAULT_UI_INDEX]); + gDPSetScissor(renderState->dl++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WD, SCREEN_HT); + + gSPDisplayList(renderState->dl++, ui_material_list[SOLID_TRANSPARENT_OVERLAY_INDEX]); + gDPFillRectangle(renderState->dl++, 0, 0, SCREEN_WD, SCREEN_HT); + gSPDisplayList(renderState->dl++, ui_material_revert_list[SOLID_TRANSPARENT_OVERLAY_INDEX]); + + gSPDisplayList(renderState->dl++, ui_material_list[ROUNDED_CORNERS_INDEX]); + gDPPipeSync(renderState->dl++); + gDPSetEnvColor(renderState->dl++, gDialogColor.r, gDialogColor.g, gDialogColor.b, gDialogColor.a); + gSPDisplayList(renderState->dl++, confirmationDialog->menuOutline); + gSPDisplayList(renderState->dl++, ui_material_revert_list[ROUNDED_CORNERS_INDEX]); + + gSPDisplayList(renderState->dl++, ui_material_list[SOLID_ENV_INDEX]); + + if (confirmationDialog->selectedButton != NULL) { + gDPPipeSync(renderState->dl++); + gDPSetEnvColor(renderState->dl++, gSelectionOrange.r, gSelectionOrange.g, gSelectionOrange.b, gSelectionOrange.a); + gDPFillRectangle( + renderState->dl++, + confirmationDialog->selectedButton->x, + confirmationDialog->selectedButton->y, + confirmationDialog->selectedButton->x + confirmationDialog->selectedButton->w, + confirmationDialog->selectedButton->y + confirmationDialog->selectedButton->h + ); + } + + gSPDisplayList(renderState->dl++, confirmationDialog->confirmButton.outline); + gSPDisplayList(renderState->dl++, confirmationDialog->cancelButton.outline); + + gSPDisplayList(renderState->dl++, ui_material_revert_list[SOLID_ENV_INDEX]); + + struct PrerenderedTextBatch* batch = prerenderedBatchStart(); + + if (confirmationDialog->titleText) { + prerenderedBatchAdd(batch, confirmationDialog->titleText, NULL); + } + if (confirmationDialog->messageText) { + prerenderedBatchAdd(batch, confirmationDialog->messageText, NULL); + } + + renderButtonText(confirmationDialog, &confirmationDialog->confirmButton, batch); + renderButtonText(confirmationDialog, &confirmationDialog->cancelButton, batch); + + renderState->dl = prerenderedBatchFinish(batch, gDejaVuSansImages, renderState->dl); + + gDPSetScissor(renderState->dl++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WD, SCREEN_HT); +} diff --git a/src/menu/confirmation_dialog.h b/src/menu/confirmation_dialog.h new file mode 100644 index 0000000..02ef167 --- /dev/null +++ b/src/menu/confirmation_dialog.h @@ -0,0 +1,35 @@ +#ifndef __MENU_CONFIRMATION_DIALOG_H__ +#define __MENU_CONFIRMATION_DIALOG_H__ + +#include "./menu.h" +#include "../graphics/graphics.h" + +typedef void (*ConfirmationDialogCallback)(void* data, int isConfirmed); + +struct ConfirmationDialogParams { + char* title; + char* message; + char* confirmLabel; + char* cancelLabel; + ConfirmationDialogCallback closeCallback; + void* callbackData; +}; + +struct ConfirmationDialog { + Gfx* menuOutline; + struct PrerenderedText* titleText; + struct PrerenderedText* messageText; + struct MenuButton confirmButton; + struct MenuButton cancelButton; + struct MenuButton* selectedButton; + ConfirmationDialogCallback closeCallback; + void* callbackData; + int isShown; +}; + +void confirmationDialogInit(struct ConfirmationDialog* confirmationDialog); +void confirmationDialogShow(struct ConfirmationDialog* confirmationDialog, struct ConfirmationDialogParams* params); +enum InputCapture confirmationDialogUpdate(struct ConfirmationDialog* confirmationDialog); +void confirmationDialogRender(struct ConfirmationDialog* confirmationDialog, struct RenderState* renderState); + +#endif diff --git a/src/menu/controls.c b/src/menu/controls.c index 99b49f7..88e8ac4 100644 --- a/src/menu/controls.c +++ b/src/menu/controls.c @@ -159,7 +159,7 @@ int controlsMeasureIcons(enum ControllerAction action) { return result; } -Gfx* controlsRenderIcons(Gfx* dl, enum ControllerAction action, int x, int y) { +Gfx* controlsRenderActionIcons(Gfx* dl, enum ControllerAction action, int x, int y) { struct ControllerSourceWithController sources[MAX_SOURCES_PER_ACTION]; int sourceCount = controllerSourcesForAction(action, sources, MAX_SOURCES_PER_ACTION); @@ -192,12 +192,31 @@ Gfx* controlsRenderIcons(Gfx* dl, enum ControllerAction action, int x, int y) { return dl; } +void controlsRenderButtonIcon(enum ControllerActionSource source, int x, int y, struct RenderState* renderState) { + struct ControllerIcon icon; + + source = controllerSourceMapAction(source); + if (!IS_VALID_SOURCE(source)) { + return; + } + + icon = gControllerButtonIcons[(int)gControllerActionToButtonIcon[(int)source]]; + gSPTextureRectangle( + renderState->dl++, + x << 2, y << 2, + (x + icon.w) << 2, (y + icon.h) << 2, + G_TX_RENDERTILE, + icon.x << 5, icon.y << 5, + 0x400, 0x400 + ); +} + void controlsLayoutRow(struct ControlsMenuRow* row, struct ControlActionDataRow* data, int x, int y) { struct PrerenderedText* copy = prerenderedTextCopy(row->actionText); menuFreePrerenderedDeferred(row->actionText); row->actionText = copy; prerenderedTextRelocate(row->actionText, x + ROW_PADDING, y); - Gfx* dl = controlsRenderIcons(row->sourceIcons, data->action, CONTROLS_X + CONTROLS_WIDTH - ROW_PADDING * 2, y); + Gfx* dl = controlsRenderActionIcons(row->sourceIcons, data->action, CONTROLS_X + CONTROLS_WIDTH - ROW_PADDING * 2, y); gSPEndDisplayList(dl++); row->y = y; } @@ -521,7 +540,7 @@ void controlsRenderPrompt(enum ControllerAction action, char* message, float opa gSPDisplayList(renderState->dl++, ui_material_list[BUTTON_ICONS_INDEX]); gDPSetEnvColor(renderState->dl++, 232, 206, 80, opacityAsInt); - renderState->dl = controlsRenderIcons(renderState->dl, action, textPositionX - CONTROL_PROMPT_PADDING, textPositionY); + renderState->dl = controlsRenderActionIcons(renderState->dl, action, textPositionX - CONTROL_PROMPT_PADDING, textPositionY); gSPDisplayList(renderState->dl++, ui_material_revert_list[BUTTON_ICONS_INDEX]); stackMallocFree(fontRender); diff --git a/src/menu/controls.h b/src/menu/controls.h index 380954e..e96a544 100644 --- a/src/menu/controls.h +++ b/src/menu/controls.h @@ -46,5 +46,6 @@ void controlsMenuRender(struct ControlsMenu* controlsMenu, struct RenderState* r void controlsRenderPrompt(enum ControllerAction action, char* message, float opacity, struct RenderState* renderState); void controlsRenderSubtitle(char* message, float textOpacity, float backgroundOpacity, struct RenderState* renderState, enum SubtitleType subtitleType); +void controlsRenderButtonIcon(enum ControllerActionSource source, int x, int y, struct RenderState* renderState); #endif \ No newline at end of file diff --git a/src/menu/load_game.c b/src/menu/load_game.c index 9dfe78e..2f0b997 100644 --- a/src/menu/load_game.c +++ b/src/menu/load_game.c @@ -25,28 +25,64 @@ void loadGamePopulate(struct LoadGameMenu* loadGame) { savefileInfo[i].testchamberDisplayNumber = saveSlots[i].testChamber; savefileInfo[i].savefileName = saveSlots[i].saveSlot == 0 ? translationsGet(GAMEUI_AUTOSAVE) : NULL; savefileInfo[i].screenshot = (u16*)SCREEN_SHOT_SRAM(saveSlots[i].saveSlot); + savefileInfo[i].isFree = 0; } - savefileUseList(loadGame->savefileList, translationsGet(GAMEUI_LOADGAME), savefileInfo, numberOfSaves); + savefileUseList( + loadGame->savefileList, + translationsGet(GAMEUI_LOADGAME), + translationsGet(GAMEUI_LOAD), + savefileInfo, + numberOfSaves + ); +} + +static void loadGameConfirmDeletionClosed(struct LoadGameMenu* loadGame, int isConfirmed) { + if (isConfirmed) { + short selectedSaveIndex = loadGame->savefileList->selectedSave; + struct SavefileInfo* selectedSave = &loadGame->savefileList->savefileInfo[selectedSaveIndex]; + + savefileDeleteGame(selectedSave->slotIndex); + loadGamePopulate(loadGame); + + if (selectedSaveIndex >= loadGame->savefileList->numberOfSaves) { + --selectedSaveIndex; + } + loadGame->savefileList->selectedSave = selectedSaveIndex; + } } enum InputCapture loadGameUpdate(struct LoadGameMenu* loadGame) { - if (controllerGetButtonDown(0, A_BUTTON) && loadGame->savefileList->numberOfSaves) { - Checkpoint* save = stackMalloc(MAX_CHECKPOINT_SIZE); - int testChamber; - int testSubject; - savefileLoadGame(savefileGetSlot(loadGame->savefileList), save, &testChamber, &testSubject); - gCurrentTestSubject = testSubject; - - levelQueueLoad(getLevelIndexFromChamberDisplayNumber(testChamber), NULL, NULL); - checkpointUse(save); - - stackMallocFree(save); - - soundPlayerPlay(SOUNDS_BUTTONCLICKRELEASE, 1.0f, 0.5f, NULL, NULL, SoundTypeAll); + enum InputCapture capture = savefileListUpdate(loadGame->savefileList); + if (capture != InputCapturePass) { + return capture; } - return savefileListUpdate(loadGame->savefileList); + if (loadGame->savefileList->numberOfSaves) { + if (controllerGetButtonDown(0, A_BUTTON)) { + Checkpoint* save = stackMalloc(MAX_CHECKPOINT_SIZE); + int testChamber; + int testSubject; + savefileLoadGame(savefileGetSlot(loadGame->savefileList), save, &testChamber, &testSubject); + gCurrentTestSubject = testSubject; + + levelQueueLoad(getLevelIndexFromChamberDisplayNumber(testChamber), NULL, NULL); + checkpointUse(save); + + stackMallocFree(save); + + soundPlayerPlay(SOUNDS_BUTTONCLICKRELEASE, 1.0f, 0.5f, NULL, NULL, SoundTypeAll); + } else if (controllerGetButtonDown(0, Z_TRIG)) { + savefileListConfirmDeletion( + loadGame->savefileList, + (ConfirmationDialogCallback)&loadGameConfirmDeletionClosed, + loadGame + ); + soundPlayerPlay(SOUNDS_BUTTONCLICKRELEASE, 1.0f, 0.5f, NULL, NULL, SoundTypeAll); + } + } + + return InputCapturePass; } void loadGameRender(struct LoadGameMenu* loadGame, struct RenderState* renderState, struct GraphicsTask* task) { diff --git a/src/menu/menu.c b/src/menu/menu.c index ae7842e..6f83f4b 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -16,10 +16,7 @@ struct PrerenderedText* menuBuildPrerenderedText(struct Font* font, char* messag return result; } -Gfx* menuBuildBorder(int x, int y, int width, int height) { - Gfx* result = malloc(sizeof(Gfx) * 7 * 3 + 1); - Gfx* dl = result; - +Gfx* menuRerenderBorder(int x, int y, int width, int height, Gfx* dl) { gSPTextureRectangle( dl++, x << 2, y << 2, @@ -83,6 +80,13 @@ Gfx* menuBuildBorder(int x, int y, int width, int height) { 0x400, 0x400 ); + return dl; +} + +Gfx* menuBuildBorder(int x, int y, int width, int height) { + Gfx* result = malloc(sizeof(Gfx) * 7 * 3 + 1); + Gfx* dl = menuRerenderBorder(x, y, width, height, result); + gSPEndDisplayList(dl++); return result; @@ -193,6 +197,18 @@ void menuRebuildButtonText(struct MenuButton* button, struct Font* font, char* m menuRenderOutline(button->x, button->y, button->w, button->h, 0, button->outline); } +void menuRelocateButton(struct MenuButton* button, int x, int y, int rightAlign) { + if (rightAlign) { + x -= button->w; + } + + menuRenderOutline(x, y, button->w, button->h, 0, button->outline); + prerenderedTextRelocate(button->text, x + BUTTON_LEFT_PADDING, y + BUTTON_TOP_PADDING); + + button->x = x; + button->y = y; +} + void menuSetRenderColor(struct RenderState* renderState, int isSelected, struct Coloru8* selected, struct Coloru8* defaultColor) { if (isSelected) { gDPSetEnvColor(renderState->dl++, selected->r, selected->g, selected->b, selected->a); diff --git a/src/menu/menu.h b/src/menu/menu.h index 75f0157..b5fb7a5 100644 --- a/src/menu/menu.h +++ b/src/menu/menu.h @@ -47,6 +47,7 @@ extern struct Coloru8 gBorderDark; struct PrerenderedText* menuBuildPrerenderedText(struct Font* font, char* message, int x, int y, int maxWidth); +Gfx* menuRerenderBorder(int x, int y, int width, int height, Gfx* dl); Gfx* menuBuildBorder(int x, int y, int width, int height); Gfx* menuBuildHorizontalLine(int x, int y, int width); Gfx* menuRerenderSolidBorder(int x, int y, int w, int h, int nx, int ny, int nw, int nh, Gfx* dl); @@ -56,6 +57,7 @@ Gfx* menuBuildOutline(int x, int y, int width, int height, int invert); struct MenuButton menuBuildButton(struct Font* font, char* message, int x, int y, int height, int rightAlign); void menuSetRenderColor(struct RenderState* renderState, int isSelected, struct Coloru8* selected, struct Coloru8* defaultColor); void menuRebuildButtonText(struct MenuButton* button, struct Font* font, char* message, int rightAlign); +void menuRelocateButton(struct MenuButton* button, int x, int y, int rightAlign); struct MenuCheckbox menuBuildCheckbox(struct Font* font, char* message, int x, int y); Gfx* menuCheckboxRender(struct MenuCheckbox* checkbox, Gfx* dl); diff --git a/src/menu/save_game_menu.c b/src/menu/save_game_menu.c index d99b4b4..c4bb3ee 100644 --- a/src/menu/save_game_menu.c +++ b/src/menu/save_game_menu.c @@ -28,6 +28,7 @@ void saveGamePopulate(struct SaveGameMenu* saveGame, int includeNew) { savefileInfo[i].testchamberDisplayNumber = saveSlots[i].testChamber; savefileInfo[i].savefileName = NULL; savefileInfo[i].screenshot = (u16*)SCREEN_SHOT_SRAM(saveSlots[i].saveSlot); + savefileInfo[i].isFree = 0; if (suggestedSlot == saveSlots[i].saveSlot) { startSelection = i; @@ -41,6 +42,7 @@ void saveGamePopulate(struct SaveGameMenu* saveGame, int includeNew) { savefileInfo[numberOfSaves].savefileName = translationsGet(GAMEUI_NEWSAVEGAME); savefileInfo[numberOfSaves].testchamberDisplayNumber = getChamberDisplayNumberFromLevelIndex(gCurrentLevelIndex, gScene.player.body.currentRoom); savefileInfo[numberOfSaves].screenshot = gScreenGrabBuffer; + savefileInfo[numberOfSaves].isFree = 1; if (suggestedSlot == 0) { startSelection = numberOfSaves; @@ -49,7 +51,13 @@ void saveGamePopulate(struct SaveGameMenu* saveGame, int includeNew) { ++numberOfSaves; } - savefileUseList(saveGame->savefileList, translationsGet(GAMEUI_SAVEGAME), savefileInfo, numberOfSaves); + savefileUseList( + saveGame->savefileList, + translationsGet(GAMEUI_SAVEGAME), + translationsGet(GAMEUI_SAVE), + savefileInfo, + numberOfSaves + ); if (startSelection == -1) { saveGame->savefileList->selectedSave = numberOfSaves - 1; @@ -58,20 +66,83 @@ void saveGamePopulate(struct SaveGameMenu* saveGame, int includeNew) { } } -enum InputCapture saveGameUpdate(struct SaveGameMenu* saveGame) { - if (controllerGetButtonDown(0, A_BUTTON) && saveGame->savefileList->numberOfSaves) { - Checkpoint* save = stackMalloc(MAX_CHECKPOINT_SIZE); - if (checkpointSaveInto(&gScene, save)) { - savefileSaveGame(save, gScreenGrabBuffer, getChamberDisplayNumberFromLevelIndex(gCurrentLevelIndex, gScene.player.body.currentRoom), gCurrentTestSubject, savefileGetSlot(saveGame->savefileList)); - saveGamePopulate(saveGame, 0); - soundPlayerPlay(SOUNDS_BUTTONCLICKRELEASE, 1.0f, 0.5f, NULL, NULL, SoundTypeAll); - }else{ - soundPlayerPlay(SOUNDS_WPN_DENYSELECT, 1.0f, 0.5f, NULL, NULL, SoundTypeAll); +static void saveGameSaveInSelectedSlot(struct SaveGameMenu* saveGame) { + Checkpoint* save = stackMalloc(MAX_CHECKPOINT_SIZE); + if (checkpointSaveInto(&gScene, save)) { + savefileSaveGame( + save, + gScreenGrabBuffer, + getChamberDisplayNumberFromLevelIndex(gCurrentLevelIndex, gScene.player.body.currentRoom), + gCurrentTestSubject, + savefileGetSlot(saveGame->savefileList) + ); + saveGamePopulate(saveGame, 1); + + soundPlayerPlay(SOUNDS_BUTTONCLICKRELEASE, 1.0f, 0.5f, NULL, NULL, SoundTypeAll); + } else { + soundPlayerPlay(SOUNDS_WPN_DENYSELECT, 1.0f, 0.5f, NULL, NULL, SoundTypeAll); + } + stackMallocFree(save); +} + +static void saveGameConfirmDeletionClosed(struct SaveGameMenu* saveGame, int isConfirmed) { + if (isConfirmed) { + short selectedSaveIndex = saveGame->savefileList->selectedSave; + struct SavefileInfo* selectedSave = &saveGame->savefileList->savefileInfo[selectedSaveIndex]; + + savefileDeleteGame(selectedSave->slotIndex); + saveGamePopulate(saveGame, 1); + + if (selectedSaveIndex >= saveGame->savefileList->numberOfSaves) { + --selectedSaveIndex; } - stackMallocFree(save); + saveGame->savefileList->selectedSave = selectedSaveIndex; + } +} + +static void saveGameConfirmOverwriteClosed(void* saveGame, int isConfirmed) { + if (isConfirmed) { + saveGameSaveInSelectedSlot(saveGame); + } +} + +enum InputCapture saveGameUpdate(struct SaveGameMenu* saveGame) { + enum InputCapture capture = savefileListUpdate(saveGame->savefileList); + if (capture != InputCapturePass) { + return capture; } - return savefileListUpdate(saveGame->savefileList); + if (saveGame->savefileList->numberOfSaves) { + if (controllerGetButtonDown(0, A_BUTTON)) { + short selectedSaveIndex = saveGame->savefileList->selectedSave; + struct SavefileInfo* selectedSave = &saveGame->savefileList->savefileInfo[selectedSaveIndex]; + + if (selectedSave->isFree) { + saveGameSaveInSelectedSlot(saveGame); + } else { + savefileListConfirmOverwrite( + saveGame->savefileList, + (ConfirmationDialogCallback)&saveGameConfirmOverwriteClosed, + saveGame + ); + soundPlayerPlay(SOUNDS_BUTTONCLICKRELEASE, 1.0f, 0.5f, NULL, NULL, SoundTypeAll); + } + } else if (controllerGetButtonDown(0, Z_TRIG)) { + short selectedSaveIndex = saveGame->savefileList->selectedSave; + struct SavefileInfo* selectedSave = &saveGame->savefileList->savefileInfo[selectedSaveIndex]; + + if (!selectedSave->isFree) { + savefileListConfirmDeletion( + saveGame->savefileList, + (ConfirmationDialogCallback)&saveGameConfirmDeletionClosed, + saveGame + ); + soundPlayerPlay(SOUNDS_BUTTONCLICKRELEASE, 1.0f, 0.5f, NULL, NULL, SoundTypeAll); + } + } + } + + return InputCapturePass; } void saveGameRender(struct SaveGameMenu* saveGame, struct RenderState* renderState, struct GraphicsTask* task) { diff --git a/src/menu/savefile_list.c b/src/menu/savefile_list.c index b068483..f4b996c 100644 --- a/src/menu/savefile_list.c +++ b/src/menu/savefile_list.c @@ -6,6 +6,8 @@ #include "../graphics/image.h" #include "../audio/soundplayer.h" #include "./text_manipulation.h" +#include "./controls.h" +#include "./translations.h" #include #include "../build/assets/materials/ui.h" @@ -72,15 +74,19 @@ void savefileListSlotInit(struct SavefileListSlot* savefileListSlot, int x, int } #define LOAD_GAME_LEFT 40 -#define LOAD_GAME_TOP 20 +#define LOAD_GAME_TOP 15 -#define FILE_OFFSET_X 16 -#define FILE_OFFSET_Y 28 +#define FILE_OFFSET_X 16 +#define FILE_OFFSET_Y 28 + +#define CONTROLS_HEIGHT 10 +#define CONTROL_TEXT_PADDING 12 +#define CONTROL_TEXT_MARGIN 2 #define CONTENT_X (LOAD_GAME_LEFT + 8) #define CONTENT_Y (LOAD_GAME_TOP + FILE_OFFSET_Y - 8) #define CONTENT_WIDTH (SCREEN_WD - CONTENT_X * 2) -#define CONTENT_HEIGHT (SCREEN_HT - CONTENT_Y - LOAD_GAME_TOP - 8) +#define CONTENT_HEIGHT (SCREEN_HT - CONTENT_Y - LOAD_GAME_TOP - CONTROLS_HEIGHT - 8) #define SCROLLED_ROW_Y(rowIndex, scrollOffset) (LOAD_GAME_TOP + (rowIndex) * ROW_HEIGHT + FILE_OFFSET_Y + (scrollOffset)) @@ -117,6 +123,8 @@ void savefileListMenuSetScroll(struct SavefileListMenu* savefileList, int amount void savefileListMenuInit(struct SavefileListMenu* savefileList) { savefileList->menuOutline = menuBuildBorder(LOAD_GAME_LEFT, LOAD_GAME_TOP, SCREEN_WD - LOAD_GAME_LEFT * 2, SCREEN_HT - LOAD_GAME_TOP * 2); savefileList->savefileListTitleText = NULL; + savefileList->deleteText = NULL; + savefileList->confirmText = NULL; savefileList->numberOfSaves = 3; savefileList->scrollOffset = 0; @@ -128,14 +136,46 @@ void savefileListMenuInit(struct SavefileListMenu* savefileList) { LOAD_GAME_TOP + i * ROW_HEIGHT + FILE_OFFSET_Y ); } + + confirmationDialogInit(&savefileList->confirmationDialog); } -void savefileUseList(struct SavefileListMenu* savefileList, char* title, struct SavefileInfo* savefileInfo, int slotCount) { +void savefileUseList(struct SavefileListMenu* savefileList, char* title, char* confirmLabel, struct SavefileInfo* savefileInfo, int slotCount) { if (savefileList->savefileListTitleText) { prerenderedTextFree(savefileList->savefileListTitleText); } + if (savefileList->deleteText) { + prerenderedTextFree(savefileList->deleteText); + } + if (savefileList->confirmText) { + prerenderedTextFree(savefileList->confirmText); + } - savefileList->savefileListTitleText = menuBuildPrerenderedText(&gDejaVuSansFont, title, 48, LOAD_GAME_TOP + 4, SCREEN_WD); + savefileList->savefileListTitleText = menuBuildPrerenderedText( + &gDejaVuSansFont, + title, + CONTENT_X, + LOAD_GAME_TOP + 4, + SCREEN_WD + ); + savefileList->deleteText = menuBuildPrerenderedText(&gDejaVuSansFont, + translationsGet(GAMEUI_DELETE), + CONTENT_X + CONTROL_TEXT_PADDING, + CONTENT_Y + CONTENT_HEIGHT + CONTROL_TEXT_MARGIN, + 120 + ); + savefileList->confirmText = menuBuildPrerenderedText( + &gDejaVuSansFont, + confirmLabel, + 0, + CONTENT_Y + CONTENT_HEIGHT + CONTROL_TEXT_MARGIN, + 120 + ); + prerenderedTextRelocate( + savefileList->confirmText, + CONTENT_X + CONTENT_WIDTH - savefileList->confirmText->width - 1, + savefileList->confirmText->y + ); for (int i = 0; i < slotCount; ++i) { savefileList->savefileInfo[i] = savefileInfo[i]; @@ -148,6 +188,10 @@ void savefileUseList(struct SavefileListMenu* savefileList, char* title, struct } enum InputCapture savefileListUpdate(struct SavefileListMenu* savefileList) { + if (savefileList->confirmationDialog.isShown) { + return confirmationDialogUpdate(&savefileList->confirmationDialog); + } + if (controllerGetButtonDown(0, B_BUTTON)) { return InputCaptureExit; } @@ -185,6 +229,40 @@ enum InputCapture savefileListUpdate(struct SavefileListMenu* savefileList) { return InputCapturePass; } +static void savefileListRenderControls(struct SavefileListMenu* savefileList, struct RenderState* renderState) { + if (savefileList->numberOfSaves == 0) { + return; + } + + struct SavefileInfo* selectedSave = &savefileList->savefileInfo[savefileList->selectedSave]; + struct PrerenderedTextBatch* batch = prerenderedBatchStart(); + + gSPDisplayList(renderState->dl++, ui_material_list[BUTTON_ICONS_INDEX]); + + if (savefileList->confirmText) { + controlsRenderButtonIcon( + ControllerActionSourceAButton, + savefileList->confirmText->x - CONTROL_TEXT_PADDING - 2, + savefileList->confirmText->y, + renderState + ); + prerenderedBatchAdd(batch, savefileList->confirmText, NULL); + } + if (savefileList->deleteText && !selectedSave->isFree) { + controlsRenderButtonIcon( + ControllerActionSourceZTrig, + savefileList->deleteText->x - CONTROL_TEXT_PADDING - 1, + savefileList->deleteText->y, + renderState + ); + prerenderedBatchAdd(batch, savefileList->deleteText, NULL); + } + + gSPDisplayList(renderState->dl++, ui_material_revert_list[BUTTON_ICONS_INDEX]); + + renderState->dl = prerenderedBatchFinish(batch, gDejaVuSansImages, renderState->dl); +} + void savefileListRender(struct SavefileListMenu* savefileList, struct RenderState* renderState, struct GraphicsTask* task) { gSPDisplayList(renderState->dl++, ui_material_list[DEFAULT_UI_INDEX]); @@ -229,6 +307,8 @@ void savefileListRender(struct SavefileListMenu* savefileList, struct RenderStat renderState->dl = prerenderedBatchFinish(batch, gDejaVuSansImages, renderState->dl); + savefileListRenderControls(savefileList, renderState); + gDPPipeSync(renderState->dl++); gDPSetScissor(renderState->dl++, G_SC_NON_INTERLACE, CONTENT_X, CONTENT_Y, CONTENT_X + CONTENT_WIDTH, CONTENT_Y + CONTENT_HEIGHT); @@ -252,6 +332,7 @@ void savefileListRender(struct SavefileListMenu* savefileList, struct RenderStat renderState->dl = prerenderedBatchFinish(batch, gDejaVuSansImages, renderState->dl); + gSPDisplayList(renderState->dl++, ui_material_revert_list[DEJAVU_SANS_0_INDEX]); gSPDisplayList(renderState->dl++, ui_material_list[IMAGE_COPY_INDEX]); @@ -289,10 +370,40 @@ void savefileListRender(struct SavefileListMenu* savefileList, struct RenderStat gSPDisplayList(renderState->dl++, ui_material_revert_list[IMAGE_COPY_INDEX]); + if (savefileList->confirmationDialog.isShown) { + confirmationDialogRender(&savefileList->confirmationDialog, renderState); + } + gDPPipeSync(renderState->dl++); gDPSetScissor(renderState->dl++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WD, SCREEN_HT); } int savefileGetSlot(struct SavefileListMenu* savefileList) { return savefileList->savefileInfo[savefileList->selectedSave].slotIndex; -} \ No newline at end of file +} + +void savefileListConfirmDeletion(struct SavefileListMenu* savefileList, ConfirmationDialogCallback callback, void* callbackData) { + struct ConfirmationDialogParams dialogParams = { + translationsGet(GAMEUI_CONFIRMDELETESAVEGAME_TITLE), + translationsGet(GAMEUI_CONFIRMDELETESAVEGAME_INFO), + translationsGet(GAMEUI_CONFIRMDELETESAVEGAME_OK), + translationsGet(GAMEUI_CANCEL), + callback, + callbackData + }; + + confirmationDialogShow(&savefileList->confirmationDialog, &dialogParams); +} + +void savefileListConfirmOverwrite(struct SavefileListMenu* savefileList, ConfirmationDialogCallback callback, void* callbackData) { + struct ConfirmationDialogParams dialogParams = { + translationsGet(GAMEUI_CONFIRMOVERWRITESAVEGAME_TITLE), + translationsGet(GAMEUI_CONFIRMOVERWRITESAVEGAME_INFO), + translationsGet(GAMEUI_CONFIRMOVERWRITESAVEGAME_OK), + translationsGet(GAMEUI_CANCEL), + callback, + callbackData + }; + + confirmationDialogShow(&savefileList->confirmationDialog, &dialogParams); +} diff --git a/src/menu/savefile_list.h b/src/menu/savefile_list.h index 941f527..917f567 100644 --- a/src/menu/savefile_list.h +++ b/src/menu/savefile_list.h @@ -6,12 +6,14 @@ #include "../graphics/graphics.h" #include "../savefile/savefile.h" #include "./new_game_menu.h" +#include "./confirmation_dialog.h" struct SavefileInfo { short slotIndex; short testchamberDisplayNumber; char* savefileName; u16* screenshot; + int isFree; }; struct SavefileListSlot { @@ -28,6 +30,9 @@ struct SavefileListSlot { struct SavefileListMenu { Gfx* menuOutline; struct PrerenderedText* savefileListTitleText; + struct PrerenderedText* deleteText; + struct PrerenderedText* confirmText; + struct ConfirmationDialog confirmationDialog; struct SavefileInfo savefileInfo[MAX_SAVE_SLOTS]; struct SavefileListSlot slots[MAX_VISIBLE_SLOTS]; short numberOfSaves; @@ -37,9 +42,11 @@ struct SavefileListMenu { }; void savefileListMenuInit(struct SavefileListMenu* savefileList); -void savefileUseList(struct SavefileListMenu* savefileList, char* title, struct SavefileInfo* savefileInfo, int slotCount); +void savefileUseList(struct SavefileListMenu* savefileList, char* title, char* confirmLabel, struct SavefileInfo* savefileInfo, int slotCount); enum InputCapture savefileListUpdate(struct SavefileListMenu* savefileList); void savefileListRender(struct SavefileListMenu* savefileList, struct RenderState* renderState, struct GraphicsTask* task); int savefileGetSlot(struct SavefileListMenu* savefileList); +void savefileListConfirmDeletion(struct SavefileListMenu* savefileList, ConfirmationDialogCallback callback, void* callbackData); +void savefileListConfirmOverwrite(struct SavefileListMenu* savefileList, ConfirmationDialogCallback callback, void* callbackData); #endif \ No newline at end of file diff --git a/src/savefile/savefile.c b/src/savefile/savefile.c index 09d2378..52f2e93 100755 --- a/src/savefile/savefile.c +++ b/src/savefile/savefile.c @@ -85,9 +85,7 @@ void savefileNew() { gSaveData.header.flags = 0; for (int i = 0; i < MAX_SAVE_SLOTS; ++i) { - gSaveData.saveSlotMetadata[i].testChamber = NO_TEST_CHAMBER; - gSaveData.saveSlotMetadata[i].testSubjectNumber = 0xFF; - gSaveData.saveSlotMetadata[i].saveSlotOrder = 0xFF; + savefileDeleteGame(i); } controllerSetDefaultSource(); @@ -152,6 +150,25 @@ void savefileSave() { savefileSramSave((void*)SRAM_ADDR, &gSaveData, sizeof(gSaveData)); } +void savefileDeleteGame(int slotIndex) { + unsigned char prevSortOrder = gSaveData.saveSlotMetadata[slotIndex].saveSlotOrder; + + // shift existing slot sort orders + for (int i = 0; i < MAX_SAVE_SLOTS; ++i) { + unsigned char currSlotOrder = gSaveData.saveSlotMetadata[i].saveSlotOrder; + + if (currSlotOrder > prevSortOrder && currSlotOrder < 0xFF) { + --gSaveData.saveSlotMetadata[i].saveSlotOrder; + } + } + + gSaveData.saveSlotMetadata[slotIndex].testChamber = NO_TEST_CHAMBER; + gSaveData.saveSlotMetadata[slotIndex].testSubjectNumber = 0xFF; + gSaveData.saveSlotMetadata[slotIndex].saveSlotOrder = 0xFF; + + savefileSave(); +} + #define SAVE_SLOT_SRAM_ADDRESS(index) (SRAM_ADDR + (1 + (index)) * SAVE_SLOT_SIZE) void savefileSaveGame(Checkpoint checkpoint, u16* screenshot, int testChamberDisplayNumber, int subjectNumber, int slotIndex) { diff --git a/src/savefile/savefile.h b/src/savefile/savefile.h index 40fe271..ad8a375 100755 --- a/src/savefile/savefile.h +++ b/src/savefile/savefile.h @@ -90,6 +90,8 @@ extern int gCurrentTestSubject; void savefileLoad(); void savefileSave(); +void savefileDeleteGame(int slotIndex); + void savefileSaveGame(Checkpoint checkpoint, u16* screenshot, int testChamberIndex, int subjectNumber, int slotIndex); int savefileListSaves(struct SaveSlotInfo* slots, int includeAuto); int savefileNextTestSubject(); diff --git a/tools/level_scripts/subtitle_generate.py b/tools/level_scripts/subtitle_generate.py index cb7173c..18e928b 100644 --- a/tools/level_scripts/subtitle_generate.py +++ b/tools/level_scripts/subtitle_generate.py @@ -9,11 +9,20 @@ hl_gameui_whitelist = { "GAMEUI_ASPECTWIDE", "GAMEUI_AUDIO", "GAMEUI_AUTOSAVE", + "GAMEUI_CANCEL", "GAMEUI_CAPTIONING", "GAMEUI_CHAPTER", + "GAMEUI_CONFIRMDELETESAVEGAME_INFO", + "GAMEUI_CONFIRMDELETESAVEGAME_OK", + "GAMEUI_CONFIRMDELETESAVEGAME_TITLE", + "GAMEUI_CONFIRMOVERWRITESAVEGAME_INFO", + "GAMEUI_CONFIRMOVERWRITESAVEGAME_OK", + "GAMEUI_CONFIRMOVERWRITESAVEGAME_TITLE", + "GAMEUI_DELETE", "GAMEUI_GAMEMENU_QUIT", "GAMEUI_GAMEMENU_RESUMEGAME", "GAMEUI_JOYSTICKINVERTED", + "GAMEUI_LOAD", "GAMEUI_LOADGAME", "GAMEUI_MUSICVOLUME", "GAMEUI_NEWGAME", @@ -23,6 +32,7 @@ hl_gameui_whitelist = { "GAMEUI_PORTAL", "GAMEUI_PORTALDEPTHLABEL", "GAMEUI_PORTALFUNNEL", + "GAMEUI_SAVE", "GAMEUI_SAVEGAME", "GAMEUI_SOUNDEFFECTVOLUME", "GAMEUI_SUBTITLES", @@ -139,7 +149,7 @@ def get_caption_keys_values_language(lines): keyval[1] = keyval[1].replace("[$WIN32]", "") key = keyval[0].replace('"', "").replace(".", "_").replace("-", "_").replace('\\', "_").replace('#', "").upper() - val = keyval[1].replace('"', "").replace("\n", "").replace("\\", "").strip() + val = keyval[1].replace('"', "").replace("\n", "").replace("\\n", " ").replace("\\", "").strip() val = re.sub(r'\','',val) val = re.sub(r'\','',val) val = val.replace("", "")