summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett Cox <garrettjcox@gmail.com>2026-07-31 23:47:26 -0500
committerGitHub <noreply@github.com>2026-07-31 23:47:26 -0500
commitf740fe0506483200142108b64fb2abf2eb2f735e (patch)
tree97e9cf92e1f05fa5abbb55c7bc3146412e329605
parent901fde33b6509403bea0ade78150837108ddb233 (diff)
Add new file setup screen, for selecting preset, rando/vanilla and seed (#1829)
-rw-r--r--mm/2s2h/BenGui/BenMenu.cpp6
-rw-r--r--mm/2s2h/Enhancements/Saving/FileSlot3.cpp11
-rw-r--r--mm/2s2h/Enhancements/Saving/NewFileSetup.cpp517
-rw-r--r--mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h8
-rw-r--r--mm/2s2h/PresetManager/PresetManager.cpp27
-rw-r--r--mm/2s2h/PresetManager/PresetManager.h4
-rw-r--r--mm/2s2h/Rando/Menu.cpp7
-rw-r--r--mm/2s2h/Rando/MiscBehavior/FileSelect.cpp21
-rw-r--r--mm/2s2h/Rando/MiscBehavior/MiscBehavior.h10
-rw-r--r--mm/2s2h/Rando/Spoiler/RefreshOptions.cpp7
-rw-r--r--mm/2s2h/Rando/Spoiler/Spoiler.h1
-rw-r--r--mm/assets/2s2h_assets.h28
-rw-r--r--mm/assets/custom/misc/title_static/gFileSelArrowLeftTex.ia8.pngbin0 -> 230 bytes
-rw-r--r--mm/assets/custom/misc/title_static/gFileSelArrowRightTex.ia8.pngbin0 -> 234 bytes
-rw-r--r--mm/assets/custom/misc/title_static/gFileSelGenerateNewTex.ia8.pngbin0 -> 816 bytes
-rw-r--r--mm/assets/custom/misc/title_static/gFileSelModeHeaderTex.ia8.pngbin0 -> 432 bytes
-rw-r--r--mm/assets/custom/misc/title_static/gFileSelNewFileTex.ia8.pngbin0 -> 544 bytes
-rw-r--r--mm/assets/custom/misc/title_static/gFileSelPresetHeaderTex.ia8.pngbin0 -> 436 bytes
-rw-r--r--mm/assets/custom/misc/title_static/gFileSelRandomizerTex.ia8.pngbin0 -> 704 bytes
-rw-r--r--mm/assets/custom/misc/title_static/gFileSelSeedHeaderTex.ia8.pngbin0 -> 349 bytes
-rw-r--r--mm/assets/custom/misc/title_static/gFileSelVanillaTex.ia8.pngbin0 -> 468 bytes
-rw-r--r--mm/src/overlays/gamestates/ovl_file_choose/z_file_choose_NES.c38
-rw-r--r--mm/src/overlays/gamestates/ovl_file_choose/z_file_select.h11
23 files changed, 672 insertions, 24 deletions
diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp
index 56dad0d58..bff7bafe6 100644
--- a/mm/2s2h/BenGui/BenMenu.cpp
+++ b/mm/2s2h/BenGui/BenMenu.cpp
@@ -1211,6 +1211,12 @@ void BenMenu::AddEnhancements() {
AddWidget(path, "3rd Save File Slot", WIDGET_CVAR_CHECKBOX)
.CVar("gEnhancements.Saving.FileSlot3")
.Options(CheckboxOptions().Tooltip("Adds a 3rd file slot that can be used for saves").DefaultValue(true));
+ AddWidget(path, "New File Setup Steps", WIDGET_CVAR_CHECKBOX)
+ .CVar("gEnhancements.Saving.NewFileSetup")
+ .Options(CheckboxOptions()
+ .Tooltip("After picking an empty file, asks whether it should be a randomizer file and lets "
+ "you apply one of your loaded presets before entering a name.")
+ .DefaultValue(true));
AddWidget(path, "Persistent Owl Saves", WIDGET_CVAR_CHECKBOX)
.CVar("gEnhancements.Saving.PersistentOwlSaves")
.Options(CheckboxOptions().Tooltip("Continuing a save will not remove the owl save. Playing Song of "
diff --git a/mm/2s2h/Enhancements/Saving/FileSlot3.cpp b/mm/2s2h/Enhancements/Saving/FileSlot3.cpp
index 14a92acf2..82f753c6b 100644
--- a/mm/2s2h/Enhancements/Saving/FileSlot3.cpp
+++ b/mm/2s2h/Enhancements/Saving/FileSlot3.cpp
@@ -11,8 +11,15 @@ extern FileSelectState* gFileSelectState;
#define CVAR CVarGetInteger(CVAR_NAME, true)
void RegisterFileSlot3() {
- // Reset the file select state to reload the save metadata
- if (gFileSelectState != NULL) {
+ static int lastValue = -1;
+ int value = CVAR;
+ bool changed = (lastValue != -1) && (lastValue != value);
+ lastValue = value;
+
+ // When we're adding the third file slot we need to refresh the entire file select state. We're only wanting
+ // to do this when the value changes, as this hook gets run when a preset is applied, and the user might
+ // have been in the middle of a file creation when they applied the preset.
+ if (changed && gFileSelectState != NULL) {
STOP_GAMESTATE(&gFileSelectState->state);
SET_NEXT_GAMESTATE(&gFileSelectState->state, FileSelect_Init, sizeof(FileSelectState));
}
diff --git a/mm/2s2h/Enhancements/Saving/NewFileSetup.cpp b/mm/2s2h/Enhancements/Saving/NewFileSetup.cpp
new file mode 100644
index 000000000..4f69381ca
--- /dev/null
+++ b/mm/2s2h/Enhancements/Saving/NewFileSetup.cpp
@@ -0,0 +1,517 @@
+#include <libultraship/bridge/consolevariablebridge.h>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include <fstream>
+#include <unordered_map>
+
+#include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h"
+#include "2s2h/GameInteractor/GameInteractor.h"
+#include "2s2h/PresetManager/PresetManager.h"
+#include "2s2h/ShipInit.hpp"
+#include "2s2h/ShipUtils.h"
+#include "2s2h/BenPort.h"
+#include "2s2h/Rando/MiscBehavior/MiscBehavior.h"
+#include "2s2h/Rando/Spoiler/Spoiler.h"
+
+extern "C" {
+#include "functions.h"
+#include "macros.h"
+#include "variables.h"
+#include "overlays/gamestates/ovl_file_choose/z_file_select.h"
+#include "misc/title_static/title_static.h"
+#include "2s2h_assets.h"
+
+extern FileSelectState* gFileSelectState;
+}
+
+#define CVAR_NAME "gEnhancements.Saving.NewFileSetup"
+#define CVAR CVarGetInteger(CVAR_NAME, 1)
+
+typedef enum {
+ NEW_FILE_SETUP_ROW_PRESET,
+ NEW_FILE_SETUP_ROW_MODE,
+ NEW_FILE_SETUP_ROW_SEED,
+ NEW_FILE_SETUP_ROW_MAX,
+} NewFileSetupRow;
+
+typedef struct {
+ const char* texture;
+ s16 width;
+ // Where the actual letters start inside the texture, so we can account for it when padding
+ s16 inkX;
+ s16 inkY;
+ s16 inkWidth;
+} SetupLabel;
+
+#define SETUP_CHAR_WIDTH 10
+#define SETUP_CHAR_HEIGHT 10
+#define SETUP_CHARS_PER_BATCH 8
+#define SETUP_LABEL_HEIGHT 16
+#define SETUP_TITLE_X -93
+#define SETUP_TITLE_Y 71
+#define SETUP_HEADING_X -97
+#define SETUP_SECTION_LIFT 4
+#define SETUP_SECTION_Y(row) (40 - ((row)*40) + ((row) > 0 ? SETUP_SECTION_LIFT : 0))
+#define SETUP_DIVIDER_Y(row) (SETUP_SECTION_Y(row) - 12)
+#define SETUP_VALUE_Y(row) (SETUP_SECTION_Y(row) - 17)
+#define SETUP_VALUE_X -95
+#define SETUP_VALUE_GAP 20
+#define SETUP_DIVIDER_LEFT -100
+#define SETUP_DIVIDER_WIDTH 204
+#define SETUP_DIVIDER_TEX_INK 204
+#define SETUP_ARROW_MARGIN 10
+#define SETUP_CYCLER_CENTER_X (SETUP_DIVIDER_LEFT + (SETUP_DIVIDER_WIDTH / 2))
+#define SETUP_PRESET_TEXT_WIDTH 160
+#define SETUP_SEED_ICONS 5
+#define SETUP_SEED_ICON_SIZE 16
+#define SETUP_SEED_ICON_GAP 4
+
+static s16 sRow = NEW_FILE_SETUP_ROW_PRESET;
+static s16 sAlpha = 0;
+static bool sIsRando = false;
+static s16 sPresetIndex = 0;
+static std::vector<std::string> sPresetNames;
+static std::string sPresetDisplay;
+static s16 sPresetDisplayWidth = 0;
+static s16 sSeedIndex = 0;
+static std::unordered_map<std::string, uint32_t> sSeedHashes;
+static SetupLabel sTitle = { gFileSelNewFileTex, 56, 4, 3, 48 };
+static SetupLabel sHeadings[NEW_FILE_SETUP_ROW_MAX] = {
+ { gFileSelPresetHeaderTex, 48, 3, 4, 41 },
+ { gFileSelModeHeaderTex, 40, 5, 4, 30 },
+ { gFileSelSeedHeaderTex, 32, 2, 4, 28 },
+};
+static SetupLabel sModeChoices[] = {
+ { gFileSelVanillaTex, 48, 5, 3, 38 },
+ { gFileSelRandomizerTex, 80, 5, 3, 69 },
+};
+static SetupLabel sGenerateNew = { gFileSelGenerateNewTex, 88, 3, 3, 82 };
+static SetupLabel sArrowLeft = { gFileSelArrowLeftTex, 16, 4, 4, 7 };
+static SetupLabel sArrowRight = { gFileSelArrowRightTex, 16, 4, 4, 7 };
+static s16 sCursorPrim[3] = { 255, 255, 255 };
+static s16 sCursorEnv[3] = { 0, 0, 0 };
+static s16 sCursorPulseDir = 1;
+static s16 sCursorTimer = 20;
+static const s16 sCursorPrimTargets[2][3] = { { 255, 255, 255 }, { 0, 255, 255 } };
+static const s16 sCursorEnvTargets[2][3] = { { 0, 0, 0 }, { 0, 150, 150 } };
+
+static bool RowIsVisible(s16 row) {
+ return (row != NEW_FILE_SETUP_ROW_SEED) || sIsRando;
+}
+
+static s16 NextVisibleRow(s16 row, s16 step) {
+ do {
+ row = (s16)((row + step + NEW_FILE_SETUP_ROW_MAX) % NEW_FILE_SETUP_ROW_MAX);
+ } while (!RowIsVisible(row));
+
+ return row;
+}
+
+static uint32_t SeedHashForSpoiler(const std::string& fileName) {
+ auto cached = sSeedHashes.find(fileName);
+ if (cached != sSeedHashes.end()) {
+ return cached->second;
+ }
+
+ uint32_t hash = 0;
+ try {
+ // performance optimization: only parse the top level of the spoiler file, since that's all we need
+ auto topLevelOnly = [](int depth, nlohmann::json::parse_event_t, nlohmann::json&) { return depth <= 1; };
+
+ std::ifstream stream(Ship::Context::GetPathRelativeToAppDirectory("randomizer/" + fileName, appShortName));
+ nlohmann::json spoiler = nlohmann::json::parse(stream, topLevelOnly);
+
+ if (spoiler.contains("finalSeed")) {
+ hash = spoiler["finalSeed"].get<uint32_t>();
+ }
+ } catch (...) { hash = 0; }
+
+ sSeedHashes[fileName] = hash;
+ return hash;
+}
+
+// Some sort of vtx black magic, archez would understand it
+static void SetQuadVtx(Vtx* vtx, s16 left, s16 top, s16 width, s16 height, s16 texWidth, s16 texHeight) {
+ vtx[0].v.ob[0] = vtx[2].v.ob[0] = left;
+ vtx[1].v.ob[0] = vtx[3].v.ob[0] = left + width;
+
+ vtx[0].v.ob[1] = vtx[1].v.ob[1] = top;
+ vtx[2].v.ob[1] = vtx[3].v.ob[1] = top - height;
+
+ vtx[0].v.tc[0] = vtx[0].v.tc[1] = vtx[1].v.tc[1] = vtx[2].v.tc[0] = 0;
+ vtx[1].v.tc[0] = vtx[3].v.tc[0] = texWidth << 5;
+ vtx[2].v.tc[1] = vtx[3].v.tc[1] = texHeight << 5;
+
+ for (s16 i = 0; i < 4; i++) {
+ vtx[i].v.ob[2] = 0;
+ vtx[i].v.flag = 0;
+ vtx[i].v.cn[0] = vtx[i].v.cn[1] = vtx[i].v.cn[2] = vtx[i].v.cn[3] = 255;
+ }
+}
+
+static void SetLabelCombine(GraphicsContext* gfxCtx) {
+ OPEN_DISPS(gfxCtx);
+
+ gDPPipeSync(POLY_OPA_DISP++);
+ gDPSetCombineLERP(POLY_OPA_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, PRIMITIVE,
+ ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0);
+
+ CLOSE_DISPS(gfxCtx);
+}
+
+// Cursor pulsing from white to blue like the rest of the menus
+static void UpdateCursorColor(void) {
+ for (s16 i = 0; i < 3; i++) {
+ s16 primStep = ABS_ALT(sCursorPrim[i] - sCursorPrimTargets[sCursorPulseDir][i]) / sCursorTimer;
+ s16 envStep = ABS_ALT(sCursorEnv[i] - sCursorEnvTargets[sCursorPulseDir][i]) / sCursorTimer;
+
+ sCursorPrim[i] += (sCursorPrim[i] >= sCursorPrimTargets[sCursorPulseDir][i]) ? -primStep : primStep;
+ sCursorEnv[i] += (sCursorEnv[i] >= sCursorEnvTargets[sCursorPulseDir][i]) ? -envStep : envStep;
+ }
+
+ if (--sCursorTimer == 0) {
+ for (s16 i = 0; i < 3; i++) {
+ sCursorPrim[i] = sCursorPrimTargets[sCursorPulseDir][i];
+ sCursorEnv[i] = sCursorEnvTargets[sCursorPulseDir][i];
+ }
+ sCursorTimer = 20;
+ sCursorPulseDir ^= 1;
+ }
+}
+
+static void SetValueColor(FileSelectState* fileSelect, bool isCurrent, bool rowSelected, u8 alpha) {
+ OPEN_DISPS(fileSelect->state.gfxCtx);
+
+ gDPPipeSync(POLY_OPA_DISP++);
+ if (!isCurrent) {
+ gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 120, 120, 120, alpha);
+ gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 255);
+ } else if (rowSelected) {
+ gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, sCursorPrim[0], sCursorPrim[1], sCursorPrim[2], alpha);
+ gDPSetEnvColor(POLY_OPA_DISP++, sCursorEnv[0], sCursorEnv[1], sCursorEnv[2], 255);
+ } else {
+ gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, alpha);
+ gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 255);
+ }
+
+ CLOSE_DISPS(fileSelect->state.gfxCtx);
+}
+
+static s16 GlyphAdvance(char c) {
+ return (s16)(Ship_GetCharFontWidthNES((u8)c) * SETUP_CHAR_WIDTH / (f32)FONT_CHAR_TEX_WIDTH);
+}
+
+static s16 TextWidth(const char* text) {
+ s16 width = 0;
+
+ for (const char* c = text; *c != '\0'; c++) {
+ width += GlyphAdvance(*c);
+ }
+
+ return width;
+}
+
+static void DrawText(FileSelectState* fileSelect, const char* text, s16 x, s16 top) {
+ s16 length = (s16)strlen(text);
+
+ if (length <= 0) {
+ return;
+ }
+
+ Vtx* vtx = (Vtx*)GRAPH_ALLOC(fileSelect->state.gfxCtx, length * 4 * sizeof(Vtx));
+
+ s16 pen = x;
+ for (s16 i = 0; i < length; i++) {
+ SetQuadVtx(&vtx[i * 4], pen, top, SETUP_CHAR_WIDTH, SETUP_CHAR_HEIGHT, FONT_CHAR_TEX_WIDTH,
+ FONT_CHAR_TEX_HEIGHT);
+ pen += GlyphAdvance(text[i]);
+ }
+
+ OPEN_DISPS(fileSelect->state.gfxCtx);
+
+ gDPPipeSync(POLY_OPA_DISP++);
+ gDPSetCombineLERP(POLY_OPA_DISP++, 0, 0, 0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, PRIMITIVE, TEXEL0, 0,
+ PRIMITIVE, 0);
+
+ for (s16 i = 0; i < length; i += SETUP_CHARS_PER_BATCH) {
+ s16 count = (s16)MIN(SETUP_CHARS_PER_BATCH, length - i);
+
+ gSPVertex(POLY_OPA_DISP++, (uintptr_t)&vtx[i * 4], count * 4, 0);
+
+ for (s16 j = 0; j < count; j++) {
+ FileSelect_DrawTexQuadI4(fileSelect->state.gfxCtx, Ship_GetCharFontTextureNES(text[i + j]), j * 4);
+ }
+ }
+
+ CLOSE_DISPS(fileSelect->state.gfxCtx);
+}
+
+static void DrawLabel(FileSelectState* fileSelect, const SetupLabel* label, s16 inkLeft, s16 inkTop) {
+ Vtx* vtx = (Vtx*)GRAPH_ALLOC(fileSelect->state.gfxCtx, 4 * sizeof(Vtx));
+
+ // Positioned by the lettering rather than the texture corner, so the padding around it never matters
+ SetQuadVtx(vtx, inkLeft - label->inkX, inkTop + label->inkY, label->width, SETUP_LABEL_HEIGHT, label->width,
+ SETUP_LABEL_HEIGHT);
+
+ OPEN_DISPS(fileSelect->state.gfxCtx);
+
+ gSPVertex(POLY_OPA_DISP++, (uintptr_t)vtx, 4, 0);
+ gDPLoadTextureBlock(POLY_OPA_DISP++, label->texture, G_IM_FMT_IA, G_IM_SIZ_8b, label->width, SETUP_LABEL_HEIGHT, 0,
+ G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD,
+ G_TX_NOLOD);
+ gSP1Quadrangle(POLY_OPA_DISP++, 0, 2, 3, 1, 0);
+
+ CLOSE_DISPS(fileSelect->state.gfxCtx);
+}
+
+// These match the look of the dividers in the option menu
+static void DrawDivider(FileSelectState* fileSelect, s16 top, u8 alpha) {
+ Vtx* vtx = (Vtx*)GRAPH_ALLOC(fileSelect->state.gfxCtx, 4 * sizeof(Vtx));
+
+ SetQuadVtx(vtx, SETUP_DIVIDER_LEFT, top, SETUP_DIVIDER_WIDTH, 2, SETUP_DIVIDER_TEX_INK, 2);
+
+ OPEN_DISPS(fileSelect->state.gfxCtx);
+
+ SetLabelCombine(fileSelect->state.gfxCtx);
+ gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0, 255, 255, alpha);
+ gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 0);
+ gSPVertex(POLY_OPA_DISP++, (uintptr_t)vtx, 4, 0);
+ gDPLoadTextureBlock_4b(POLY_OPA_DISP++, gFileSelOptionsDividerTex, G_IM_FMT_IA, 256, 2, 0,
+ G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD,
+ G_TX_NOLOD);
+ gSP1Quadrangle(POLY_OPA_DISP++, 0, 2, 3, 1, 0);
+
+ CLOSE_DISPS(fileSelect->state.gfxCtx);
+}
+
+static void DrawArrows(FileSelectState* fileSelect, s16 inkTop) {
+ DrawLabel(fileSelect, &sArrowLeft, SETUP_DIVIDER_LEFT + SETUP_ARROW_MARGIN, inkTop);
+ DrawLabel(fileSelect, &sArrowRight,
+ SETUP_DIVIDER_LEFT + SETUP_DIVIDER_WIDTH - SETUP_ARROW_MARGIN - sArrowRight.inkWidth, inkTop);
+}
+
+static void DrawSeedIcons(FileSelectState* fileSelect, uint32_t hash, s16 centerX, s16 centerY, u8 alpha) {
+ const s16 pitch = SETUP_SEED_ICON_SIZE + SETUP_SEED_ICON_GAP;
+ s16 left = centerX - (((SETUP_SEED_ICONS * pitch) - SETUP_SEED_ICON_GAP) / 2);
+
+ OPEN_DISPS(fileSelect->state.gfxCtx);
+
+ gDPPipeSync(POLY_OPA_DISP++);
+ gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM);
+ gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, alpha);
+
+ for (s16 i = 0; i < SETUP_SEED_ICONS; i++) {
+ Rando::MiscBehavior::Sprite* sprite = Rando::MiscBehavior::GetSeedTexture((u8)(hash % 100));
+ hash /= 100;
+
+ Vtx* vtx = (Vtx*)GRAPH_ALLOC(fileSelect->state.gfxCtx, 4 * sizeof(Vtx));
+ SetQuadVtx(vtx, left + (i * pitch), centerY + (SETUP_SEED_ICON_SIZE / 2), SETUP_SEED_ICON_SIZE,
+ SETUP_SEED_ICON_SIZE, sprite->width, sprite->height);
+
+ gSPVertex(POLY_OPA_DISP++, (uintptr_t)vtx, 4, 0);
+ gDPLoadTextureBlock(POLY_OPA_DISP++, sprite->tex, G_IM_FMT_RGBA, G_IM_SIZ_32b, sprite->width, sprite->height, 0,
+ G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMASK, G_TX_NOMASK,
+ G_TX_NOLOD, G_TX_NOLOD);
+ gSP1Quadrangle(POLY_OPA_DISP++, 0, 2, 3, 1, 0);
+ }
+
+ CLOSE_DISPS(fileSelect->state.gfxCtx);
+}
+
+static void UpdatePresetDisplay(void) {
+ sPresetDisplay = sPresetNames.empty() ? "" : sPresetNames[sPresetIndex];
+
+ while (sPresetDisplay.length() > 4 && TextWidth(sPresetDisplay.c_str()) > SETUP_PRESET_TEXT_WIDTH) {
+ sPresetDisplay = sPresetDisplay.substr(0, sPresetDisplay.length() - 4) + "...";
+ }
+
+ sPresetDisplayWidth = TextWidth(sPresetDisplay.c_str());
+}
+
+static void RefreshPresetList(void) {
+ sPresetNames.clear();
+ sPresetNames.push_back("None");
+
+ for (const auto& name : PresetManager_GetPresetNames()) {
+ sPresetNames.push_back(name);
+ }
+
+ sPresetIndex = 0;
+ UpdatePresetDisplay();
+}
+
+static void RefreshSeedList(void) {
+ Rando::Spoiler::RefreshOptions();
+ // The list is never empty in practice, but clamping guards against indexing it with -1 if it were
+ s32 last = (s32)Rando::Spoiler::spoilerOptions.size() - 1;
+ sSeedIndex = (last < 0) ? 0 : (s16)CLAMP(CVarGetInteger("gRando.SpoilerFileIndex", 0), 0, last);
+}
+
+static void ApplySelection(void) {
+ if ((sPresetIndex > 0) && (sPresetIndex < (s16)sPresetNames.size())) {
+ PresetManager_ApplyPresetByName(sPresetNames[sPresetIndex]);
+ }
+
+ CVarSetInteger("gRando.Enabled", sIsRando ? 1 : 0);
+
+ // OnFileCreate reads the pair to decide whether to generate a seed or replay a spoiler
+ Rando::Spoiler::SelectSpoiler(sIsRando ? sSeedIndex : 0);
+
+ CVarSave();
+}
+
+static void LeaveSetup(FileSelectState* fileSelect, s16 configMode) {
+ sAlpha = 0;
+ fileSelect->configMode = configMode;
+}
+
+extern "C" void FileSelect_RotateToNewFileSetup(GameState* thisx) {
+ FileSelectState* fileSelect = (FileSelectState*)thisx;
+
+ fileSelect->windowRot += 50.0f;
+
+ if (fileSelect->windowRot >= 314.0f) {
+ fileSelect->windowRot = 314.0f;
+ fileSelect->configMode = CM_2S2H_NEW_FILE_SETUP;
+ }
+}
+
+extern "C" void FileSelect_UpdateNewFileSetup(GameState* thisx) {
+ FileSelectState* fileSelect = (FileSelectState*)thisx;
+ Input* input = CONTROLLER1(&fileSelect->state);
+ s16 presetCount = (s16)sPresetNames.size();
+
+ sAlpha = (s16)MIN(sAlpha + 25, 255);
+
+ if (CHECK_BTN_ALL(input->press.button, BTN_B)) {
+ Audio_PlaySfx(NA_SE_SY_FSEL_CLOSE);
+ LeaveSetup(fileSelect, CM_2S2H_NEW_FILE_SETUP_TO_MAIN);
+ return;
+ }
+
+ if (CHECK_BTN_ALL(input->press.button, BTN_A) || CHECK_BTN_ALL(input->press.button, BTN_START)) {
+ Audio_PlaySfx(NA_SE_SY_FSEL_DECIDE_L);
+ ApplySelection();
+ LeaveSetup(fileSelect, CM_START_NAME_ENTRY);
+ return;
+ }
+
+ bool up = (fileSelect->stickAdjY > 30) || CHECK_BTN_ALL(input->press.button, BTN_DUP);
+ bool down = (fileSelect->stickAdjY < -30) || CHECK_BTN_ALL(input->press.button, BTN_DDOWN);
+
+ if (up || down) {
+ Audio_PlaySfx(NA_SE_SY_FSEL_CURSOR);
+ sRow = NextVisibleRow(sRow, up ? -1 : 1);
+ return;
+ }
+
+ bool left = (fileSelect->stickAdjX < -30) || CHECK_BTN_ALL(input->press.button, BTN_DLEFT);
+ bool right = (fileSelect->stickAdjX > 30) || CHECK_BTN_ALL(input->press.button, BTN_DRIGHT);
+
+ if (!left && !right) {
+ return;
+ }
+
+ Audio_PlaySfx(NA_SE_SY_FSEL_CURSOR);
+
+ if (sRow == NEW_FILE_SETUP_ROW_MODE) {
+ sIsRando = !sIsRando;
+ } else if (sRow == NEW_FILE_SETUP_ROW_SEED) {
+ s16 seedCount = (s16)Rando::Spoiler::spoilerOptions.size();
+ sSeedIndex = (s16)((sSeedIndex + (right ? 1 : seedCount - 1)) % seedCount);
+ } else {
+ sPresetIndex = (s16)((sPresetIndex + (right ? 1 : presetCount - 1)) % presetCount);
+ UpdatePresetDisplay();
+ }
+}
+
+extern "C" void FileSelect_DrawNewFileSetup(GameState* thisx) {
+ FileSelectState* fileSelect = (FileSelectState*)thisx;
+ u8 alpha = (u8)sAlpha;
+
+ if (alpha == 0) {
+ return;
+ }
+
+ UpdateCursorColor();
+
+ OPEN_DISPS(fileSelect->state.gfxCtx);
+
+ Gfx_SetupDL42_Opa(fileSelect->state.gfxCtx);
+ SetLabelCombine(fileSelect->state.gfxCtx);
+
+ // Title and section headings are plain white; only values take the selection colours
+ gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, alpha);
+ gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 255);
+ DrawLabel(fileSelect, &sTitle, SETUP_TITLE_X, SETUP_TITLE_Y);
+
+ for (s16 row = 0; row < NEW_FILE_SETUP_ROW_MAX; row++) {
+ if (!RowIsVisible(row)) {
+ continue;
+ }
+ gDPPipeSync(POLY_OPA_DISP++);
+ gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, alpha);
+ gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 255);
+ DrawLabel(fileSelect, &sHeadings[row], SETUP_HEADING_X, SETUP_SECTION_Y(row));
+ DrawDivider(fileSelect, SETUP_DIVIDER_Y(row), alpha);
+ }
+
+ // Mode: both values side by side
+ s16 valueX = SETUP_VALUE_X;
+ for (s16 i = 0; i < (s16)ARRAY_COUNT(sModeChoices); i++) {
+ SetValueColor(fileSelect, i == (sIsRando ? 1 : 0), sRow == NEW_FILE_SETUP_ROW_MODE, alpha);
+ DrawLabel(fileSelect, &sModeChoices[i], valueX, SETUP_VALUE_Y(NEW_FILE_SETUP_ROW_MODE));
+ valueX += sModeChoices[i].inkWidth + SETUP_VALUE_GAP;
+ }
+
+ // Seed: "Generate New" first, then every spoiler in the folder as its own icons
+ if (RowIsVisible(NEW_FILE_SETUP_ROW_SEED)) {
+ bool seedRowSelected = (sRow == NEW_FILE_SETUP_ROW_SEED);
+ s16 seedY = SETUP_VALUE_Y(NEW_FILE_SETUP_ROW_SEED);
+
+ SetValueColor(fileSelect, true, seedRowSelected, alpha);
+ DrawArrows(fileSelect, seedY);
+
+ if (sSeedIndex == 0) {
+ DrawLabel(fileSelect, &sGenerateNew, SETUP_CYCLER_CENTER_X - (sGenerateNew.inkWidth / 2), seedY);
+ } else if (sSeedIndex < (s16)Rando::Spoiler::spoilerOptions.size()) {
+ DrawSeedIcons(fileSelect, SeedHashForSpoiler(Rando::Spoiler::spoilerOptions[sSeedIndex]),
+ SETUP_CYCLER_CENTER_X, seedY - 4, alpha);
+ SetLabelCombine(fileSelect->state.gfxCtx);
+ }
+ }
+
+ // Preset: one at a time
+ bool presetRowSelected = (sRow == NEW_FILE_SETUP_ROW_PRESET);
+ s16 presetY = SETUP_VALUE_Y(NEW_FILE_SETUP_ROW_PRESET);
+
+ SetValueColor(fileSelect, true, presetRowSelected, alpha);
+ DrawArrows(fileSelect, presetY);
+
+ // Inherits the primitive SetValueColor set for this row, which is what tints the arrows too
+ DrawText(fileSelect, sPresetDisplay.c_str(), SETUP_CYCLER_CENTER_X - (sPresetDisplayWidth / 2), presetY - 1);
+
+ gDPPipeSync(POLY_OPA_DISP++);
+ gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIDECALA, G_CC_MODULATEIDECALA);
+
+ CLOSE_DISPS(fileSelect->state.gfxCtx);
+}
+
+void RegisterNewFileSetup() {
+ COND_VB_SHOULD(VB_FILE_SELECT_ROTATE_TO_NAME_ENTRY, CVAR, {
+ FileSelectState* fileSelect = va_arg(args, FileSelectState*);
+
+ sRow = NEW_FILE_SETUP_ROW_PRESET;
+ sAlpha = 0;
+ sIsRando = CVarGetInteger("gRando.Enabled", 0) != 0;
+ RefreshPresetList();
+ RefreshSeedList();
+
+ fileSelect->configMode = CM_2S2H_ROTATE_TO_NEW_FILE_SETUP;
+ *should = false;
+ });
+}
+
+static RegisterShipInitFunc initFunc(RegisterNewFileSetup, { CVAR_NAME });
diff --git a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h
index f2067542f..1fc58ca1d 100644
--- a/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h
+++ b/mm/2s2h/GameInteractor/GameInteractor_VanillaBehavior.h
@@ -554,6 +554,14 @@ typedef enum {
// true
// ```
// #### `args`
+ // - `*FileSelectState`
+ VB_FILE_SELECT_ROTATE_TO_NAME_ENTRY,
+
+ // #### `result`
+ // ```c
+ // true
+ // ```
+ // #### `args`
// - `*ItemId`
// - `s32` (slot)
// - `s32` (isDpad)
diff --git a/mm/2s2h/PresetManager/PresetManager.cpp b/mm/2s2h/PresetManager/PresetManager.cpp
index 44dea18be..9121944ce 100644
--- a/mm/2s2h/PresetManager/PresetManager.cpp
+++ b/mm/2s2h/PresetManager/PresetManager.cpp
@@ -1,5 +1,6 @@
#include "PresetManager.h"
#include <libultraship/bridge/consolevariablebridge.h>
+#include <algorithm>
#include <filesystem>
#include <fstream>
#include <set>
@@ -815,6 +816,32 @@ void PresetManager_CreatePreset(std::string presetName) {
} catch (...) { Notification::Emit({ .suffix = "Failed to create preset" }); }
}
+std::vector<std::string> PresetManager_GetPresetNames() {
+ std::vector<std::string> names;
+ names.reserve(presets.size());
+
+ for (const auto& [name, pair] : presets) {
+ names.push_back(name);
+ }
+
+ // presets is an unordered map, sort it
+ std::sort(names.begin(), names.end());
+
+ return names;
+}
+
+bool PresetManager_ApplyPresetByName(const std::string& name) {
+ auto it = presets.find(name);
+
+ if (it == presets.end()) {
+ return false;
+ }
+
+ PresetManager_ApplyPreset(it->second.first);
+
+ return true;
+}
+
bool PresetManager_HandleFileDropped(char* filePath) {
try {
std::ifstream fileStream(filePath);
diff --git a/mm/2s2h/PresetManager/PresetManager.h b/mm/2s2h/PresetManager/PresetManager.h
index 7b55c97f7..484dedf31 100644
--- a/mm/2s2h/PresetManager/PresetManager.h
+++ b/mm/2s2h/PresetManager/PresetManager.h
@@ -3,8 +3,12 @@
#define PRESET_MANAGER_H
#include <string>
+#include <vector>
bool PresetManager_HandleFileDropped(const std::string& filePath);
void PresetManager_Draw();
+std::vector<std::string> PresetManager_GetPresetNames();
+bool PresetManager_ApplyPresetByName(const std::string& name);
+
#endif // PRESET_MANAGER_H
diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp
index 5be6cbd1f..9a05a6ecf 100644
--- a/mm/2s2h/Rando/Menu.cpp
+++ b/mm/2s2h/Rando/Menu.cpp
@@ -372,12 +372,7 @@ static void DrawGeneralTab() {
UIWidgets::CVarCheckbox("Enable Rando (Randomizes new files upon creation)", "gRando.Enabled");
if (UIWidgets::CVarCombobox("Seed", "gRando.SpoilerFileIndex", Rando::Spoiler::spoilerOptions)) {
- if (CVarGetInteger("gRando.SpoilerFileIndex", 0) == 0) {
- CVarSetString("gRando.SpoilerFile", "");
- } else {
- CVarSetString("gRando.SpoilerFile",
- Rando::Spoiler::spoilerOptions[CVarGetInteger("gRando.SpoilerFileIndex", 0)].c_str());
- }
+ Rando::Spoiler::SelectSpoiler(CVarGetInteger("gRando.SpoilerFileIndex", 0));
}
if (CVarGetInteger("gRando.SpoilerFileIndex", 0) == 0) {
diff --git a/mm/2s2h/Rando/MiscBehavior/FileSelect.cpp b/mm/2s2h/Rando/MiscBehavior/FileSelect.cpp
index 32084a872..d7cda75f1 100644
--- a/mm/2s2h/Rando/MiscBehavior/FileSelect.cpp
+++ b/mm/2s2h/Rando/MiscBehavior/FileSelect.cpp
@@ -13,22 +13,13 @@ extern s16 sWindowContentColors[3];
extern FileSelectState* gFileSelectState;
}
-typedef struct {
- char tex[512];
- uint16_t width;
- uint16_t height;
- uint8_t im_fmt;
- uint8_t im_siz;
- uint8_t id;
-} Sprite;
-
// Image Icons
#include <array>
#include "assets/archives/icon_item_static/icon_item_static_yar.h"
#include "assets/archives/icon_item_24_static/icon_item_24_static_yar.h"
-inline std::array<Sprite, 100> gSeedTextures = { {
+inline std::array<Rando::MiscBehavior::Sprite, 100> gSeedTextures = { {
{ dgItemIconOcarinaOfTimeTex, 32, 32, G_IM_FMT_RGBA, G_IM_SIZ_32b, 0 },
{ dgItemIconBowTex, 32, 32, G_IM_FMT_RGBA, G_IM_SIZ_32b, 1 },
{ dgItemIconFireArrowTex, 32, 32, G_IM_FMT_RGBA, G_IM_SIZ_32b, 2 },
@@ -131,7 +122,7 @@ inline std::array<Sprite, 100> gSeedTextures = { {
{ dgQuestIconSmallKeyTex, 24, 24, G_IM_FMT_RGBA, G_IM_SIZ_32b, 99 },
} };
-Sprite* GetSeedTexture(const uint8_t index) {
+Rando::MiscBehavior::Sprite* Rando::MiscBehavior::GetSeedTexture(const uint8_t index) {
return &gSeedTextures[index];
}
@@ -248,7 +239,7 @@ void SetRandSaveTypeVtxData() {
}
}
-void SpriteLoad(Sprite* sprite) {
+void SpriteLoad(Rando::MiscBehavior::Sprite* sprite) {
OPEN_DISPS(gFileSelectState->state.gfxCtx);
/*
@@ -274,7 +265,7 @@ void SpriteLoad(Sprite* sprite) {
CLOSE_DISPS(gFileSelectState->state.gfxCtx);
}
-void SpriteDraw(Sprite* sprite, int left, int top, int width, int height) {
+void SpriteDraw(Rando::MiscBehavior::Sprite* sprite, int left, int top, int width, int height) {
int width_factor = (1 << 10) * sprite->width / width;
int height_factor = (1 << 10) * sprite->height / height;
@@ -305,8 +296,8 @@ void DrawSeedHashSprites() {
for (int i = 0; i < 5; i++) {
u8 hashPart = fileHash % 100;
fileHash /= 100;
- SpriteLoad(GetSeedTexture(hashPart));
- SpriteDraw(GetSeedTexture(hashPart), xStart + (40 * i), 10, 24, 24);
+ SpriteLoad(Rando::MiscBehavior::GetSeedTexture(hashPart));
+ SpriteDraw(Rando::MiscBehavior::GetSeedTexture(hashPart), xStart + (40 * i), 10, 24, 24);
}
}
}
diff --git a/mm/2s2h/Rando/MiscBehavior/MiscBehavior.h b/mm/2s2h/Rando/MiscBehavior/MiscBehavior.h
index 5d026bf9e..128674f3f 100644
--- a/mm/2s2h/Rando/MiscBehavior/MiscBehavior.h
+++ b/mm/2s2h/Rando/MiscBehavior/MiscBehavior.h
@@ -26,6 +26,16 @@ void SariasSongHint();
void BankSignHint();
void InitTycoonWallet();
+typedef struct {
+ char tex[512];
+ uint16_t width;
+ uint16_t height;
+ uint8_t im_fmt;
+ uint8_t im_siz;
+ uint8_t id;
+} Sprite;
+Sprite* GetSeedTexture(const uint8_t index);
+
} // namespace MiscBehavior
} // namespace Rando
diff --git a/mm/2s2h/Rando/Spoiler/RefreshOptions.cpp b/mm/2s2h/Rando/Spoiler/RefreshOptions.cpp
index 2ca62b82d..4c14f01e0 100644
--- a/mm/2s2h/Rando/Spoiler/RefreshOptions.cpp
+++ b/mm/2s2h/Rando/Spoiler/RefreshOptions.cpp
@@ -9,6 +9,13 @@ std::vector<std::string> Rando::Spoiler::spoilerOptions;
const std::filesystem::path randomizerFolderPath(Ship::Context::GetPathRelativeToAppDirectory("randomizer",
appShortName));
+void Rando::Spoiler::SelectSpoiler(s32 index) {
+ bool generateNew = (index <= 0) || (index >= (s32)Rando::Spoiler::spoilerOptions.size());
+
+ CVarSetInteger("gRando.SpoilerFileIndex", generateNew ? 0 : index);
+ CVarSetString("gRando.SpoilerFile", generateNew ? "" : Rando::Spoiler::spoilerOptions[index].c_str());
+}
+
// This function refreshes the list of spoiler files in the randomizer folder, this list is used in the Randomizer UI,
// and also includes an option to generate a new seed at the top of the list.
void Rando::Spoiler::RefreshOptions() {
diff --git a/mm/2s2h/Rando/Spoiler/Spoiler.h b/mm/2s2h/Rando/Spoiler/Spoiler.h
index 4cb145796..5939e54ba 100644
--- a/mm/2s2h/Rando/Spoiler/Spoiler.h
+++ b/mm/2s2h/Rando/Spoiler/Spoiler.h
@@ -11,6 +11,7 @@ namespace Spoiler {
extern std::vector<std::string> spoilerOptions;
void RefreshOptions();
+void SelectSpoiler(s32 index);
nlohmann::json GenerateFromSaveContext();
void SaveToFile(const std::string& fileName, nlohmann::json spoiler);
nlohmann::json LoadFromFile(const std::string& filePath);
diff --git a/mm/assets/2s2h_assets.h b/mm/assets/2s2h_assets.h
index 21905427e..d5d739f79 100644
--- a/mm/assets/2s2h_assets.h
+++ b/mm/assets/2s2h_assets.h
@@ -140,6 +140,34 @@ static const ALIGN_ASSET(2) char gFileSelCheatingDayTex[] = dgFileSelCheatingDay
#define dgFileSelRandIconTex "__OTR__misc/title_static/gFileSelRandIconTex"
static const ALIGN_ASSET(2) char gFileSelRandIconTex[] = dgFileSelRandIconTex;
+// File select new file setup labels, generated by tools/label_gen/genlabel.py
+#define dgFileSelNewFileTex "__OTR__misc/title_static/gFileSelNewFileTex"
+static const ALIGN_ASSET(2) char gFileSelNewFileTex[] = dgFileSelNewFileTex;
+
+#define dgFileSelModeHeaderTex "__OTR__misc/title_static/gFileSelModeHeaderTex"
+static const ALIGN_ASSET(2) char gFileSelModeHeaderTex[] = dgFileSelModeHeaderTex;
+
+#define dgFileSelSeedHeaderTex "__OTR__misc/title_static/gFileSelSeedHeaderTex"
+static const ALIGN_ASSET(2) char gFileSelSeedHeaderTex[] = dgFileSelSeedHeaderTex;
+
+#define dgFileSelPresetHeaderTex "__OTR__misc/title_static/gFileSelPresetHeaderTex"
+static const ALIGN_ASSET(2) char gFileSelPresetHeaderTex[] = dgFileSelPresetHeaderTex;
+
+#define dgFileSelGenerateNewTex "__OTR__misc/title_static/gFileSelGenerateNewTex"
+static const ALIGN_ASSET(2) char gFileSelGenerateNewTex[] = dgFileSelGenerateNewTex;
+
+#define dgFileSelVanillaTex "__OTR__misc/title_static/gFileSelVanillaTex"
+static const ALIGN_ASSET(2) char gFileSelVanillaTex[] = dgFileSelVanillaTex;
+
+#define dgFileSelRandomizerTex "__OTR__misc/title_static/gFileSelRandomizerTex"
+static const ALIGN_ASSET(2) char gFileSelRandomizerTex[] = dgFileSelRandomizerTex;
+
+#define dgFileSelArrowLeftTex "__OTR__misc/title_static/gFileSelArrowLeftTex"
+static const ALIGN_ASSET(2) char gFileSelArrowLeftTex[] = dgFileSelArrowLeftTex;
+
+#define dgFileSelArrowRightTex "__OTR__misc/title_static/gFileSelArrowRightTex"
+static const ALIGN_ASSET(2) char gFileSelArrowRightTex[] = dgFileSelArrowRightTex;
+
#define dgBoxChestCornerHealthTex "__OTR__objects/object_box/gBoxChestCornerHealthTex"
static const ALIGN_ASSET(2) char gBoxChestCornerHealthTex[] = dgBoxChestCornerHealthTex;
diff --git a/mm/assets/custom/misc/title_static/gFileSelArrowLeftTex.ia8.png b/mm/assets/custom/misc/title_static/gFileSelArrowLeftTex.ia8.png
new file mode 100644
index 000000000..690182903
--- /dev/null
+++ b/mm/assets/custom/misc/title_static/gFileSelArrowLeftTex.ia8.png
Binary files differ
diff --git a/mm/assets/custom/misc/title_static/gFileSelArrowRightTex.ia8.png b/mm/assets/custom/misc/title_static/gFileSelArrowRightTex.ia8.png
new file mode 100644
index 000000000..af6bd25d0
--- /dev/null
+++ b/mm/assets/custom/misc/title_static/gFileSelArrowRightTex.ia8.png
Binary files differ
diff --git a/mm/assets/custom/misc/title_static/gFileSelGenerateNewTex.ia8.png b/mm/assets/custom/misc/title_static/gFileSelGenerateNewTex.ia8.png
new file mode 100644
index 000000000..baec5d05e
--- /dev/null
+++ b/mm/assets/custom/misc/title_static/gFileSelGenerateNewTex.ia8.png
Binary files differ
diff --git a/mm/assets/custom/misc/title_static/gFileSelModeHeaderTex.ia8.png b/mm/assets/custom/misc/title_static/gFileSelModeHeaderTex.ia8.png
new file mode 100644
index 000000000..c27f67aad
--- /dev/null
+++ b/mm/assets/custom/misc/title_static/gFileSelModeHeaderTex.ia8.png
Binary files differ
diff --git a/mm/assets/custom/misc/title_static/gFileSelNewFileTex.ia8.png b/mm/assets/custom/misc/title_static/gFileSelNewFileTex.ia8.png
new file mode 100644
index 000000000..ac0959a54
--- /dev/null
+++ b/mm/assets/custom/misc/title_static/gFileSelNewFileTex.ia8.png
Binary files differ
diff --git a/mm/assets/custom/misc/title_static/gFileSelPresetHeaderTex.ia8.png b/mm/assets/custom/misc/title_static/gFileSelPresetHeaderTex.ia8.png
new file mode 100644
index 000000000..affafa431
--- /dev/null
+++ b/mm/assets/custom/misc/title_static/gFileSelPresetHeaderTex.ia8.png
Binary files differ
diff --git a/mm/assets/custom/misc/title_static/gFileSelRandomizerTex.ia8.png b/mm/assets/custom/misc/title_static/gFileSelRandomizerTex.ia8.png
new file mode 100644
index 000000000..f3f47db26
--- /dev/null
+++ b/mm/assets/custom/misc/title_static/gFileSelRandomizerTex.ia8.png
Binary files differ
diff --git a/mm/assets/custom/misc/title_static/gFileSelSeedHeaderTex.ia8.png b/mm/assets/custom/misc/title_static/gFileSelSeedHeaderTex.ia8.png
new file mode 100644
index 000000000..13169f9f6
--- /dev/null
+++ b/mm/assets/custom/misc/title_static/gFileSelSeedHeaderTex.ia8.png
Binary files differ
diff --git a/mm/assets/custom/misc/title_static/gFileSelVanillaTex.ia8.png b/mm/assets/custom/misc/title_static/gFileSelVanillaTex.ia8.png
new file mode 100644
index 000000000..4664b358d
--- /dev/null
+++ b/mm/assets/custom/misc/title_static/gFileSelVanillaTex.ia8.png
Binary files differ
diff --git a/mm/src/overlays/gamestates/ovl_file_choose/z_file_choose_NES.c b/mm/src/overlays/gamestates/ovl_file_choose/z_file_choose_NES.c
index 4a6ad1af0..cf7a65182 100644
--- a/mm/src/overlays/gamestates/ovl_file_choose/z_file_choose_NES.c
+++ b/mm/src/overlays/gamestates/ovl_file_choose/z_file_choose_NES.c
@@ -437,6 +437,10 @@ void FileSelect_UnusedCMDelay(GameState* thisx) {
void FileSelect_RotateToNameEntry(GameState* thisx) {
FileSelectState* this = (FileSelectState*)thisx;
+ if (!GameInteractor_Should(VB_FILE_SELECT_ROTATE_TO_NAME_ENTRY, true, this)) {
+ return;
+ }
+
this->windowRot += 50.0f;
if (this->windowRot >= 314.0f) {
@@ -527,6 +531,10 @@ void (*sConfigModeUpdateFuncs[])(GameState*) = {
FileSelect_RotateToMain, // CM_OPTIONS_TO_MAIN
// Possible Debug
FileSelect_UnusedCMDelay, // CM_UNUSED_DELAY
+ // 2S2H [Enhancement] New File Setup
+ FileSelect_RotateToNewFileSetup, // CM_2S2H_ROTATE_TO_NEW_FILE_SETUP
+ FileSelect_UpdateNewFileSetup, // CM_2S2H_NEW_FILE_SETUP
+ FileSelect_RotateToMain, // CM_2S2H_NEW_FILE_SETUP_TO_MAIN
};
s16 sCursorAlphaTargets[] = { 70, 200 };
@@ -1970,6 +1978,36 @@ void FileSelect_ConfigModeDraw(GameState* thisx) {
FileSelect_DrawOptions(&this->state);
}
+ // #region 2S2H [Enhancement]
+ if ((this->configMode >= CM_2S2H_ROTATE_TO_NEW_FILE_SETUP) &&
+ (this->configMode <= CM_2S2H_NEW_FILE_SETUP_TO_MAIN)) {
+ gDPPipeSync(POLY_OPA_DISP++);
+ gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM);
+ gDPSetPrimColorOverride(POLY_OPA_DISP++, 0, 0, this->windowColor[0], this->windowColor[1], this->windowColor[2],
+ this->windowAlpha, COSMETIC_ID("Menus.FileWindow"));
+ gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 0);
+
+ Matrix_Translate(0.0f, 0.0f, -93.6f, MTXMODE_NEW);
+ Matrix_Scale(0.78f, 0.78f, 0.78f, MTXMODE_APPLY);
+ Matrix_RotateXFApply((this->windowRot - 314.0f) / 100.0f);
+
+ MATRIX_FINALIZE_AND_LOAD(POLY_OPA_DISP++, this->state.gfxCtx);
+
+ gSPVertex(POLY_OPA_DISP++, &this->windowVtx[0], 32, 0);
+ gSPDisplayList(POLY_OPA_DISP++, gFileSelWindow1DL);
+
+ gSPVertex(POLY_OPA_DISP++, &this->windowVtx[32], 32, 0);
+ gSPDisplayList(POLY_OPA_DISP++, gFileSelWindow2DL);
+
+ gSPVertex(POLY_OPA_DISP++, &this->windowVtx[64], 16, 0);
+ gSPDisplayList(POLY_OPA_DISP++, gFileSelWindow3DL);
+
+ gDPPipeSync(POLY_OPA_DISP++);
+
+ FileSelect_DrawNewFileSetup(&this->state);
+ }
+ // #endregion
+
gDPPipeSync(POLY_OPA_DISP++);
FileSelect_SetView(this, 0.0f, 0.0f, 64.0f);
diff --git a/mm/src/overlays/gamestates/ovl_file_choose/z_file_select.h b/mm/src/overlays/gamestates/ovl_file_choose/z_file_select.h
index ad0f6be52..614e5b963 100644
--- a/mm/src/overlays/gamestates/ovl_file_choose/z_file_select.h
+++ b/mm/src/overlays/gamestates/ovl_file_choose/z_file_select.h
@@ -78,7 +78,11 @@ typedef enum {
/* 0x29 */ CM_OPTIONS_MENU,
/* 0x2A */ CM_OPTIONS_WAIT_FOR_FLASH_SAVE,
/* 0x2B */ CM_OPTIONS_TO_MAIN,
- /* 0x2C */ CM_UNUSED_DELAY
+ /* 0x2C */ CM_UNUSED_DELAY,
+ // 2S2H [Enhancement] New file setup, shown between picking an empty file and entering a name.
+ /* 0x2D */ CM_2S2H_ROTATE_TO_NEW_FILE_SETUP,
+ /* 0x2E */ CM_2S2H_NEW_FILE_SETUP,
+ /* 0x2F */ CM_2S2H_NEW_FILE_SETUP_TO_MAIN
} ConfigMode;
typedef enum {
@@ -326,6 +330,11 @@ void FileSelect_StartOptions(GameState* thisx);
void FileSelect_UpdateOptionsMenu(GameState* thisx);
void FileSelect_OptionsWaitForFlashSave(GameState* thisx);
+// 2S2H [Enhancement] source: mm/2s2h/Enhancements/Saving/NewFileSetup.cpp
+void FileSelect_RotateToNewFileSetup(GameState* thisx);
+void FileSelect_UpdateNewFileSetup(GameState* thisx);
+void FileSelect_DrawNewFileSetup(GameState* thisx);
+
extern u8 D_808141F0[];
extern s16 D_80814280[];