diff --git a/src/filesrch.c b/src/filesrch.c index 69a6c5c7..6827dc57 100644 --- a/src/filesrch.c +++ b/src/filesrch.c @@ -896,6 +896,8 @@ boolean preparefilemenu(boolean samedepth, boolean replayhut) strcpy(temp+len, PATHSEP); coredirmenu[folderpos++] = temp; } + else if (replayhut) // Reverse-alphabetical on just the files; acts as a fake "most recent first" with the current filename format + coredirmenu[sizecoredirmenu - 1 - pos++] = temp; else coredirmenu[numfolders + pos++] = temp; } diff --git a/src/g_game.c b/src/g_game.c index ca2f0208..09a3db52 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -6564,6 +6564,128 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) return c; } +void G_LoadDemoInfo(menudemo_t *pdemo) +{ + UINT8 *infobuffer, *info_p; + UINT8 version, subversion, pdemoflags; + UINT16 pdemoversion, cvarcount; + + if (!FIL_ReadFile(pdemo->filepath, &infobuffer)) + { + CONS_Alert(CONS_ERROR, M_GetText("Failed to read file '%s'.\n"), pdemo->filepath); + pdemo->type = MD_INVALID; + sprintf(pdemo->title, "INVALID REPLAY"); + + return; + } + + info_p = infobuffer; + + if (memcmp(info_p, DEMOHEADER, 12)) + { + CONS_Alert(CONS_ERROR, M_GetText("%s is not a SRB2Kart replay file.\n"), pdemo->filepath); + pdemo->type = MD_INVALID; + sprintf(pdemo->title, "INVALID REPLAY"); + Z_Free(infobuffer); + return; + } + + pdemo->type = MD_LOADED; + + info_p += 12; // DEMOHEADER + + version = READUINT8(info_p); + subversion = READUINT8(info_p); + pdemoversion = READUINT16(info_p); + + switch(pdemoversion) + { + case DEMOVERSION: // latest always supported + // demo title + M_Memcpy(pdemo->title, info_p, 64); + info_p += 64; + + break; +#ifdef DEMO_COMPAT_100 + case 0x0001: + pdemo->type = MD_OUTDATED; + sprintf(pdemo->title, "Legacy Replay"); + break; +#endif + // too old, cannot support. + default: + CONS_Alert(CONS_ERROR, M_GetText("%s is an incompatible replay format and cannot be played.\n"), pdemo->filepath); + pdemo->type = MD_INVALID; + sprintf(pdemo->title, "INVALID REPLAY"); + Z_Free(infobuffer); + return; + } + + if (version != VERSION || subversion != SUBVERSION) + pdemo->type = MD_OUTDATED; + + info_p += 16; // demo checksum + if (memcmp(info_p, "PLAY", 4)) + { + CONS_Alert(CONS_ERROR, M_GetText("%s is the wrong type of recording and cannot be played.\n"), pdemo->filepath); + pdemo->type = MD_INVALID; + sprintf(pdemo->title, "INVALID REPLAY"); + Z_Free(infobuffer); + return; + } + demo_p += 4; // "PLAY" + pdemo->map = READINT16(info_p); + demo_p += 16; // mapmd5 + + pdemoflags = READUINT8(info_p); + + // temp? + if (!(pdemoflags & DF_MULTIPLAYER)) + { + CONS_Alert(CONS_ERROR, M_GetText("%s is not a multiplayer replay and can't be listed on this menu fully yet.\n"), pdemo->filepath); + Z_Free(infobuffer); + return; + } + + pdemo->gametype = READUINT8(info_p); + + G_SkipDemoExtraFiles(&info_p); //@TODO see if this information is useful for display? + demo_p += 4; // RNG seed + + // Pared down version of CV_LoadNetVars to find the kart speed + cvarcount = READUINT16(info_p); + while (cvarcount--) + { + UINT16 netid; + char *svalue; + + netid = READUINT16(info_p); + svalue = (char *)info_p; + SKIPSTRING(info_p); + info_p++; // stealth + + if (netid == cv_kartspeed.netid) + { + for (cvarcount = 0; kartspeed_cons_t[cvarcount].value; cvarcount++) + { + if (!strcasecmp(kartspeed_cons_t[cvarcount].strvalue, svalue)) + { + pdemo->kartspeed = kartspeed_cons_t[cvarcount].value; + break; + } + } + + break; + } + } + + if (pdemoflags & DF_ENCORE) + pdemo->kartspeed |= DF_ENCORE; + + // I think that's everything we need? + free(infobuffer); +} + // // G_PlayDemo // diff --git a/src/g_game.h b/src/g_game.h index d97752b2..5b07d053 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -61,6 +61,25 @@ struct demovars_s { extern struct demovars_s demo; +typedef enum { + MD_NOTLOADED, + MD_LOADED, + MD_SUBDIR, + MD_OUTDATED, + MD_INVALID +} menudemotype_e; + +typedef struct menudemo_s { + char filepath[256]; + menudemotype_e type; + + char title[65]; // Null-terminated for string prints + UINT16 map; + UINT8 gametype; + UINT8 kartspeed; // Add OR DF_ENCORE for encore mode, idk +} menudemo_t; + + extern mobj_t *metalplayback; // gametic at level start @@ -147,6 +166,7 @@ void G_DeferedInitNew(boolean pencoremode, const char *mapname, INT32 pickedchar UINT8 ssplayers, boolean FLS); void G_DoLoadLevel(boolean resetplayer); +void G_LoadDemoInfo(menudemo_t *pdemo); void G_DeferedPlayDemo(const char *demo); // Can be called by the startup code or M_Responder, calls P_SetupLevel. diff --git a/src/m_menu.c b/src/m_menu.c index a6957f3a..a5f51f2a 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -338,6 +338,7 @@ static patch_t *addonsp[NUM_EXT+5]; // Replay hut static void M_ReplayHut(INT32 choice); +static void M_HandleReplayHutList(INT32 choice); static void M_DrawReplayHut(void); // Drawing functions @@ -532,9 +533,10 @@ static menuitem_t MISC_AddonsMenu[] = static menuitem_t MISC_ReplayHutMenu[] = { - {IT_SUBMENU |IT_STRING, NULL, "Replay Options...", NULL, 0}, + {IT_SUBMENU |IT_STRING, NULL, "Replay Options...", NULL, 0}, - {IT_KEYHANDLER|IT_NOTHING, NULL, "", NULL, 20}, // Dummy menuitem for the replay list + {IT_KEYHANDLER|IT_NOTHING, NULL, "", M_HandleReplayHutList, 20}, // Dummy menuitem for the replay list + {IT_NOTHING, NULL, "", NULL, 20}, // Dummy for handling wrapping to the top of the menu.. }; // --------------------------------- @@ -1602,7 +1604,7 @@ menu_t MISC_ReplayHutDef = MISC_ReplayHutMenu, M_DrawReplayHut, 30, 80, - 0, + (sizeof (MISC_ReplayHutMenu)/sizeof (menuitem_t)) - 2, // Start on the replay list NULL }; @@ -5008,9 +5010,35 @@ static void M_HandleAddons(INT32 choice) } // ---- REPLAY HUT ----- +menudemo_t *demolist; static INT16 replayOn = 0; +static void PrepReplayList(void) +{ + size_t i; + + if (demolist) + Z_Free(demolist); + + demolist = Z_Calloc(sizeof(menudemo_t) * sizedirmenu, PU_STATIC, NULL); + + for (i = 0; i < sizedirmenu; i++) + { + if (dirmenu[i][DIR_TYPE] == EXT_FOLDER) + { + demolist[i].type = MD_SUBDIR; + strncpy(demolist[i].title, dirmenu[i] + DIR_STRING, 64); + } + else + { + demolist[i].type = MD_NOTLOADED; + snprintf(demolist[i].filepath, 255, "%s%s", menupath, dirmenu[i] + DIR_STRING); + sprintf(demolist[i].title, "....."); + } + } +} + static void M_ReplayHut(INT32 choice) { (void)choice; @@ -5026,18 +5054,46 @@ static void M_ReplayHut(INT32 choice) else dir_on[menudepthleft] = 0; + PrepReplayList(); + M_SetupNextMenu(&MISC_ReplayHutDef); G_SetGamestate(GS_TIMEATTACK); S_ChangeMusicInternal("replst", true); } +static void M_HandleReplayHutList(INT32 choice) +{ + switch (choice) + { + case KEY_UPARROW: + if (replayOn) + replayOn--; + else + M_PrevOpt(); + + S_StartSound(NULL, sfx_menu1); + break; + + case KEY_DOWNARROW: + if (replayOn < (INT16)sizedirmenu-1) + replayOn++; + else + itemOn = 0; // Not M_NextOpt because that would take us to the extra dummy item + + S_StartSound(NULL, sfx_menu1); + break; + } +} + static void M_DrawReplayHut(void) { INT32 x, y, cursory = 0; INT16 i; + INT16 replaylistitem = currentMenu->numitems-2; + boolean processed_one_this_frame = false; - (void)cursory; + static UINT16 replayhutmenuy = 0; V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE)); M_DrawMenuTitle(); @@ -5046,25 +5102,41 @@ static void M_DrawReplayHut(void) x = currentMenu->x; y = currentMenu->y; - if (itemOn == currentMenu->numitems-1) + if (itemOn > replaylistitem) + { + itemOn = replaylistitem; + replayOn = sizedirmenu-1; + } + else if (itemOn < replaylistitem) + { + replayOn = 0; + } + + if (itemOn == replaylistitem) { INT32 maxy; // Scroll menu items if needed - cursory = y + currentMenu->menuitems[currentMenu->numitems-1].alphaKey + replayOn*10; - maxy = y + currentMenu->menuitems[currentMenu->numitems-1].alphaKey + sizedirmenu*10; + cursory = y + currentMenu->menuitems[replaylistitem].alphaKey + replayOn*10; + maxy = y + currentMenu->menuitems[replaylistitem].alphaKey + sizedirmenu*10; - if (cursory > maxy - 70) - cursory = maxy - 70; + if (cursory > maxy - 20) + cursory = maxy - 20; - if (cursory > 130) - y -= (cursory-130); + if (cursory - replayhutmenuy > 150) + replayhutmenuy += (cursory-150-replayhutmenuy)/2; + else if (cursory - replayhutmenuy < 110) + replayhutmenuy += (max(0, cursory-110)-replayhutmenuy)/2; } + else + replayhutmenuy /= 2; + + y -= replayhutmenuy; // Draw static menu items - for (i = 0; i < currentMenu->numitems-1; i++) + for (i = 0; i < replaylistitem; i++) { if (i == itemOn) - cursory = y; + cursory = y + currentMenu->menuitems[i].alphaKey; if ((currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING) V_DrawString(x, y + currentMenu->menuitems[i].alphaKey, 0, currentMenu->menuitems[i].text); @@ -5076,7 +5148,20 @@ static void M_DrawReplayHut(void) for (i = 0; i < (INT16)sizedirmenu; i++) { - V_DrawString(x, y+i*10, V_ALLOWLOWERCASE, dirmenu[i]+DIR_STRING); + INT32 localy = y+i*10; + if (localy >= 0 && localy < 200 && demolist[i].type == MD_NOTLOADED && !processed_one_this_frame) + { + processed_one_this_frame = true; + G_LoadDemoInfo(&demolist[i]); + } + + if (itemOn == replaylistitem && i == replayOn) + { + cursory = localy; + V_DrawString(x, localy, highlightflags|V_ALLOWLOWERCASE, demolist[i].title); + } + else + V_DrawString(x, localy, V_ALLOWLOWERCASE, demolist[i].title); } // Draw the cursor