Merge branch 'save-replay-virt-keyboard' into 'master'

Polish replay saving

Closes #1000 and #1091

See merge request KartKrew/Kart!1976
This commit is contained in:
James R. 2024-03-04 00:52:19 +00:00
commit e97c107c1e
13 changed files with 139 additions and 181 deletions

View file

@ -278,12 +278,6 @@ void D_ProcessEvents(void)
HandleGamepadDeviceEvents(ev);
if (demo.savemode == demovars_s::DSM_TITLEENTRY)
{
if (G_DemoTitleResponder(ev))
continue;
}
// console input
#ifdef HAVE_THREADS
I_lock_mutex(&con_mutex);

View file

@ -2878,7 +2878,7 @@ static void Got_Mapcmd(const UINT8 **cp, INT32 playernum)
if (demo.playback && !demo.timing)
precache = false;
demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING;
demo.willsave = (cv_recordmultiplayerdemos.value == 2);
demo.savebutton = 0;
G_InitNew(pencoremode, mapnumber, presetplayer, skipprecutscene);

View file

@ -65,6 +65,30 @@
#include "k_credits.h"
#include "k_grandprix.h"
static menuitem_t TitleEntry[] =
{
{IT_NOTHING | IT_SPACE, "Save Replay", NULL,
NULL, {NULL}, 0, 0},
};
static menu_t TitleEntryDef = {
sizeof (TitleEntry) / sizeof (menuitem_t),
NULL,
0,
TitleEntry,
0, 0,
0, 0,
MBF_SOUNDLESS,
NULL,
0, 0,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
boolean nodrawers; // for comparative timing purposes
boolean noblit; // for comparative timing purposes
tic_t demostarttime; // for comparative timing purposes
@ -1752,6 +1776,9 @@ void G_ConfirmRewind(tic_t rewindtime)
//
void G_RecordDemo(const char *name)
{
if (demo.recording)
G_CheckDemoStatus();
extern consvar_t cv_netdemosize;
INT32 maxsize;
@ -3968,7 +3995,7 @@ boolean G_CheckDemoStatus(void)
if (!demo.recording)
return false;
if (modeattacking || demo.savemode != demovars_s::DSM_NOTSAVING)
if (modeattacking || demo.willsave)
{
if (demobuf.p)
{
@ -3992,6 +4019,9 @@ void G_SaveDemo(void)
UINT8 i;
#endif
if (currentMenu == &TitleEntryDef)
M_ClearMenus(true);
// Ensure extrainfo pointer is always available, even if no info is present.
if (demoinfo_p && *(UINT32 *)demoinfo_p == 0)
{
@ -4054,14 +4084,13 @@ void G_SaveDemo(void)
md5_buffer((char *)p+16, (demobuf.buffer + length) - (p+16), p);
#endif
if (FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer)) // finally output the file.
demo.savemode = demovars_s::DSM_SAVED;
bool saved = FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer); // finally output the file.
Z_Free(demobuf.buffer);
demo.recording = false;
if (!modeattacking)
{
if (demo.savemode == demovars_s::DSM_SAVED)
if (saved)
{
CONS_Printf(M_GetText("Demo %s recorded\n"), demoname);
if (gamedata->eversavedreplay == false)
@ -4076,55 +4105,6 @@ void G_SaveDemo(void)
}
}
boolean G_DemoTitleResponder(event_t *ev)
{
size_t len;
INT32 ch;
if (ev->type != ev_keydown)
return false;
ch = (INT32)ev->data1;
// Only ESC and non-keyboard keys abort connection
if (ch == KEY_ESCAPE)
{
demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? demovars_s::DSM_WILLAUTOSAVE : demovars_s::DSM_NOTSAVING;
return true;
}
if (ch == KEY_ENTER || ch >= NUMKEYS)
{
demo.savemode = demovars_s::DSM_WILLSAVE;
return true;
}
if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && fontv[HU_FONT].font[ch-HU_FONTSTART])
|| ch == ' ') // Allow spaces, of course
{
len = strlen(demo.titlename);
if (len < 64)
{
demo.titlename[len+1] = 0;
demo.titlename[len] = CON_ShiftChar(ch);
}
}
else if (ch == KEY_BACKSPACE)
{
if (shiftdown)
memset(demo.titlename, 0, sizeof(demo.titlename));
else
{
len = strlen(demo.titlename);
if (len > 0)
demo.titlename[len-1] = 0;
}
}
return true;
}
boolean G_CheckDemoTitleEntry(void)
{
if (menuactive || chat_on)
@ -4133,7 +4113,18 @@ boolean G_CheckDemoTitleEntry(void)
if (!G_PlayerInputDown(0, gc_b, 0) && !G_PlayerInputDown(0, gc_x, 0))
return false;
demo.savemode = demovars_s::DSM_TITLEENTRY;
demo.willsave = true;
M_OpenVirtualKeyboard(
false,
sizeof demo.titlename,
[](const char* replace) -> const char*
{
if (replace)
strlcpy(demo.titlename, replace, sizeof demo.titlename);
return demo.titlename;
},
&TitleEntryDef
);
return true;
}

View file

@ -90,13 +90,7 @@ struct demovars_s {
boolean netgame; // multiplayer netgame
tic_t savebutton; // Used to determine when the local player can choose to save the replay while the race is still going
enum {
DSM_NOTSAVING,
DSM_WILLAUTOSAVE,
DSM_TITLEENTRY,
DSM_WILLSAVE,
DSM_SAVED
} savemode;
boolean willsave;
boolean freecam;
@ -232,8 +226,6 @@ void G_DeferedPlayDemo(const char *demo);
void G_SaveDemo(void);
boolean G_DemoTitleResponder(event_t *ev);
boolean G_CheckDemoTitleEntry(void);
typedef enum

View file

@ -4549,7 +4549,7 @@ void G_AfterIntermission(void)
M_PlaybackQuit(0);
return;
}
else if (demo.recording && (modeattacking || demo.savemode != DSM_NOTSAVING))
else if (demo.recording && (modeattacking || demo.willsave))
G_SaveDemo();
if (modeattacking) // End the run.

View file

@ -542,6 +542,7 @@ extern INT32 menuKey; // keyboard key pressed for menu
extern INT16 virtualKeyboard[5][NUMVIRTUALKEYSINROW];
extern INT16 shift_virtualKeyboard[5][NUMVIRTUALKEYSINROW];
typedef const char *(*vkb_query_fn_t)(const char *replace);
extern struct menutyping_s
{
boolean active; // Active
@ -554,7 +555,10 @@ extern struct menutyping_s
boolean keyboardcapslock;
boolean keyboardshift;
char cache[MAXSTRINGLENGTH]; // cached string
vkb_query_fn_t queryfn; // callback on open and close
menu_t *dummymenu;
size_t cachelen;
char *cache; // cached string
} menutyping;
// While typing, we'll have a fade strongly darken the screen to overlay the typing menu instead
@ -682,7 +686,8 @@ void M_PlayMenuJam(void);
boolean M_ConsiderSealedSwapAlert(void);
void M_OpenVirtualKeyboard(boolean gamepad);
void M_OpenVirtualKeyboard(boolean gamepad, size_t cachelen, vkb_query_fn_t queryfn, menu_t *dummymenu);
void M_AbortVirtualKeyboard(void);
void M_MenuTypingInput(INT32 key);
void M_QuitResponse(INT32 ch);

View file

@ -495,6 +495,21 @@ static void M_DrawMenuTooltips(void)
}
}
static const char *M_MenuTypingCroppedString(void)
{
static char buf[36];
const char *p = menutyping.cache;
size_t n = strlen(p);
if (n > sizeof buf)
{
p += n - sizeof buf;
n = sizeof buf;
}
memcpy(buf, p, n);
buf[n] = '\0';
return buf;
}
// Draws the typing submenu
static void M_DrawMenuTyping(void)
{
@ -534,7 +549,7 @@ static void M_DrawMenuTyping(void)
V_DrawFill(x + 4, y + 4 + 5, 1, 8+6, 121);
V_DrawFill(x + 5 + boxwidth - 8, y + 4 + 5, 1, 8+6, 121);
INT32 textwidth = M_DrawCaretString(x + 8, y + 12, menutyping.cache, true);
INT32 textwidth = M_DrawCaretString(x + 8, y + 12, M_MenuTypingCroppedString(), true);
if (skullAnimCounter < 4
&& menutyping.menutypingclose == false
&& menutyping.menutypingfade == (menutyping.keyboardtyping ? 9 : 18))

View file

@ -188,6 +188,14 @@ static void M_ChangeCvar(INT32 choice)
M_ChangeCvarDirect(choice, currentMenu->menuitems[itemOn].itemaction.cvar);
}
static const char *M_QueryCvarAction(const char *replace)
{
consvar_t *cvar = currentMenu->menuitems[itemOn].itemaction.cvar;
if (replace)
CV_Set(cvar, replace);
return cvar->string;
}
boolean M_NextOpt(void)
{
INT16 oldItemOn = itemOn; // prevent infinite loop
@ -831,7 +839,7 @@ void M_ClearMenus(boolean callexitmenufunc)
D_StartTitle();
}
menutyping.active = false;
M_AbortVirtualKeyboard();
menumessage.active = false;
menuactive = false;
@ -918,6 +926,8 @@ void M_SetupNextMenu(menu_t *menudef, boolean notransition)
void M_GoBack(INT32 choice)
{
const INT16 behaviourflags = currentMenu->behaviourflags;
(void)choice;
noFurtherInput = true;
@ -940,7 +950,8 @@ void M_GoBack(INT32 choice)
else // No returning to the title screen.
M_QuitSRB2(-1);
S_StartSound(NULL, sfx_s3k5b);
if (!(behaviourflags & MBF_SOUNDLESS))
S_StartSound(NULL, sfx_s3k5b);
}
//
@ -1124,7 +1135,8 @@ static void M_HandleMenuInput(void)
// If we're hovering over a IT_CV_STRING option, pressing A/X opens the typing submenu
if (M_MenuConfirmPressed(pid))
{
M_OpenVirtualKeyboard(thisMenuKey == -1); // If we entered this menu by pressing a menu Key, default to keyboard typing, otherwise use controller.
// If we entered this menu by pressing a menu Key, default to keyboard typing, otherwise use controller.
M_OpenVirtualKeyboard(thisMenuKey == -1, MAXSTRINGLENGTH, M_QueryCvarAction, NULL);
return;
}
else if (M_MenuExtraPressed(pid))

View file

@ -5,6 +5,7 @@
#include "../../s_sound.h"
#include "../../console.h" // CON_ShiftChar
#include "../../i_system.h" // I_Clipboard funcs
#include "../../z_zone.h"
// Typing "sub"-menu
struct menutyping_s menutyping;
@ -91,9 +92,9 @@ boolean M_ChangeStringCvar(INT32 choice)
const char *paste = I_ClipboardPaste();
if (paste == NULL || paste[0] == '\0')
;
else if (len < MAXSTRINGLENGTH - 1)
else if (len < menutyping.cachelen)
{
strlcat(menutyping.cache, paste, MAXSTRINGLENGTH);
strlcat(menutyping.cache, paste, menutyping.cachelen + 1);
S_StartSound(NULL, sfx_tmxbdn); // Tails
}
@ -146,7 +147,7 @@ boolean M_ChangeStringCvar(INT32 choice)
if (choice >= 32 && choice <= 127)
{
len = strlen(menutyping.cache);
if (len < MAXSTRINGLENGTH - 1)
if (len < menutyping.cachelen)
{
menutyping.cache[len++] = (char)choice;
menutyping.cache[len] = 0;
@ -180,7 +181,19 @@ static void M_ToggleVirtualShift(void)
static void M_CloseVirtualKeyboard(void)
{
menutyping.menutypingclose = true; // close menu.
CV_Set(currentMenu->menuitems[itemOn].itemaction.cvar, menutyping.cache);
menutyping.queryfn(menutyping.cache);
}
void M_AbortVirtualKeyboard(void)
{
if (!menutyping.active)
return;
menutyping.active = false;
Z_Free(menutyping.cache);
if (currentMenu == menutyping.dummymenu)
M_GoBack(0);
}
static boolean M_IsTypingKey(INT32 key)
@ -202,7 +215,7 @@ void M_MenuTypingInput(INT32 key)
// Closing
menutyping.menutypingfade--;
if (!menutyping.menutypingfade)
menutyping.active = false;
M_AbortVirtualKeyboard();
return; // prevent inputs while closing the menu.
}
@ -405,11 +418,28 @@ void M_MenuTypingInput(INT32 key)
}
}
void M_OpenVirtualKeyboard(boolean gamepad)
void M_OpenVirtualKeyboard(boolean gamepad, size_t cachelen, vkb_query_fn_t queryfn, menu_t *dummymenu)
{
menutyping.keyboardtyping = !gamepad;
menutyping.active = true;
menutyping.menutypingclose = false;
strlcpy(menutyping.cache, currentMenu->menuitems[itemOn].itemaction.cvar->string, MAXSTRINGLENGTH);
menutyping.queryfn = queryfn;
menutyping.dummymenu = dummymenu;
menutyping.cachelen = cachelen;
Z_Malloc(cachelen + 1, PU_STATIC, &menutyping.cache);
strlcpy(menutyping.cache, queryfn(NULL), cachelen + 1);
if (dummymenu)
{
if (!menuactive)
{
M_StartControlPanel();
dummymenu->prevMenu = NULL;
}
else
dummymenu->prevMenu = currentMenu;
M_SetupNextMenu(dummymenu, true);
}
}

View file

@ -1178,9 +1178,8 @@ void P_Ticker(boolean run)
{
G_WriteAllGhostTics();
if (cv_recordmultiplayerdemos.value && (demo.savemode == DSM_NOTSAVING || demo.savemode == DSM_WILLAUTOSAVE))
if (demo.savebutton && demo.savebutton + 3*TICRATE < leveltime)
G_CheckDemoTitleEntry();
if (cv_recordmultiplayerdemos.value && demo.savebutton && demo.savebutton + 3*TICRATE < leveltime)
G_CheckDemoTitleEntry();
}
else if (demo.playback) // Use Ghost data for consistency checks.
{

View file

@ -1303,36 +1303,6 @@ static void ST_overlayDrawer(void)
K_DrawMidVote();
}
void ST_DrawDemoTitleEntry(void)
{
static UINT8 anim = 0;
char *nametodraw;
anim++;
anim %= 8;
nametodraw = demo.titlename;
while (V_StringWidth(nametodraw, 0) > MAXSTRINGLENGTH*8 - 8)
nametodraw++;
#define x (BASEVIDWIDTH/2 - 139)
#define y (BASEVIDHEIGHT/2)
M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
V_DrawString(x + 8, y + 12, 0, nametodraw);
if (anim < 4)
V_DrawCharacter(x + 8 + V_StringWidth(nametodraw, 0), y + 12,
'_' | 0x80, false);
M_DrawTextBox(x + 30, y - 24, 26, 1);
V_DrawString(x + 38, y - 16, 0, "Enter the name of the replay.");
M_DrawTextBox(x + 50, y + 20, 20, 1);
V_DrawThinString(x + 58, y + 28, 0, "Escape - Cancel");
V_DrawRightAlignedThinString(x + 220, y + 28, 0, "Enter - Confirm");
#undef x
#undef y
}
// MayonakaStatic: draw Midnight Channel's TV-like borders
static void ST_MayonakaStatic(void)
{
@ -1487,6 +1457,15 @@ void ST_DrawServerSplash(boolean timelimited)
}
}
void ST_DrawSaveReplayHint(INT32 flags)
{
V_DrawRightAlignedThinString(
BASEVIDWIDTH - 2, 2,
flags|V_YELLOWMAP,
demo.willsave ? "Replay will be saved. \xAB" "Change title" : "\xAB" "or " "\xAE" "Save replay"
);
}
static fixed_t ST_CalculateFadeIn(player_t *player)
{
const tic_t length = TICRATE/4;
@ -1626,26 +1605,6 @@ void ST_Drawer(void)
INT32 flags = V_SNAPTOTOP | V_SNAPTORIGHT |
(Easing_Linear(min(t, fadeLength) * FRACUNIT / fadeLength, 9, 0) << V_ALPHASHIFT);
switch (demo.savemode)
{
case DSM_NOTSAVING:
V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, flags|V_YELLOWMAP, "\xAB" "or " "\xAE" "Save replay");
break;
case DSM_WILLAUTOSAVE:
V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, flags|V_YELLOWMAP, "Replay will be saved. \xAB" "Change title");
break;
case DSM_WILLSAVE:
V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, flags|V_YELLOWMAP, "Replay will be saved.");
break;
case DSM_TITLEENTRY:
ST_DrawDemoTitleEntry();
break;
default: // Don't render anything
break;
}
ST_DrawSaveReplayHint(flags);
}
}

View file

@ -33,9 +33,6 @@ extern "C" {
// Called by main loop.
void ST_Ticker(boolean run);
// Called when naming a replay.
void ST_DrawDemoTitleEntry(void);
#ifdef HAVE_DISCORDRPC
// Called when you have Discord asks
void ST_AskToJoinEnvelope(void);
@ -79,6 +76,7 @@ extern tic_t lt_exitticker, lt_endtime;
extern tic_t lt_fade;
void ST_DrawServerSplash(boolean timelimited);
void ST_DrawSaveReplayHint(INT32 flags);
// return if player a is in the same team as player b
boolean ST_SameTeam(player_t *a, player_t *b);

View file

@ -80,7 +80,6 @@ static INT32 powertype = PWRLV_DISABLED;
static INT32 intertic;
static INT32 endtic = -1;
static INT32 sorttic = -1;
static INT32 replayprompttic;
static fixed_t mqscroll = 0;
static fixed_t chkscroll = 0;
@ -1690,35 +1689,8 @@ skiptallydrawer:
}
finalcounter:
{
if ((modeattacking == ATTACKING_NONE) && (demo.recording || demo.savemode == demovars_s::DSM_SAVED) && !demo.playback)
{
switch (demo.savemode)
{
case demovars_s::DSM_NOTSAVING:
{
INT32 buttonx = BASEVIDWIDTH;
INT32 buttony = 2;
K_drawButtonAnim(buttonx - 76, buttony, 0, kp_button_b[1], replayprompttic);
V_DrawRightAlignedThinString(buttonx - 55, buttony, highlightflags, "or");
K_drawButtonAnim(buttonx - 55, buttony, 0, kp_button_x[1], replayprompttic);
V_DrawRightAlignedThinString(buttonx - 2, buttony, highlightflags, "Save replay");
break;
}
case demovars_s::DSM_SAVED:
V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, highlightflags, "Replay saved!");
break;
case demovars_s::DSM_TITLEENTRY:
ST_DrawDemoTitleEntry();
break;
default: // Don't render any text here
break;
}
}
}
if ((modeattacking == ATTACKING_NONE) && demo.recording)
ST_DrawSaveReplayHint(0);
if (Y_CanSkipIntermission())
{
@ -1754,16 +1726,7 @@ void Y_Ticker(void)
return;
if (demo.recording)
{
if (demo.savemode == demovars_s::DSM_NOTSAVING)
{
replayprompttic++;
G_CheckDemoTitleEntry();
}
if (demo.savemode == demovars_s::DSM_WILLSAVE || demo.savemode == demovars_s::DSM_WILLAUTOSAVE)
G_SaveDemo();
}
G_CheckDemoTitleEntry();
// Check for pause or menu up in single player
if (paused || P_AutoPause())