Merge branch 'json-profiles-standings' into 'master'

JSON profiles and replay standings

See merge request KartKrew/Kart!1942
This commit is contained in:
SteelT 2024-02-27 01:52:19 +00:00
commit 2a8705a14d
8 changed files with 661 additions and 385 deletions

View file

@ -17,7 +17,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
f_finale.c
f_wipe.cpp
g_build_ticcmd.cpp
g_demo.c
g_demo.cpp
g_game.c
g_gamedata.cpp
g_input.c
@ -30,7 +30,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
hu_stuff.c
i_time.c
i_video_common.cpp
y_inter.c
y_inter.cpp
st_stuff.c
m_aatree.c
m_anigif.c
@ -145,7 +145,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
k_terrain.c
k_director.cpp
k_follower.c
k_profiles.c
k_profiles.cpp
k_specialstage.c
k_roulette.c
k_podium.cpp

View file

@ -11,6 +11,12 @@
/// \file g_demo.c
/// \brief Demo recording and playback
#include <algorithm>
#include <cstddef>
#include <tcb/span.hpp>
#include <nlohmann/json.hpp>
#include "doomdef.h"
#include "console.h"
#include "d_main.h"
@ -156,6 +162,7 @@ static ticcmd_t oldcmd[MAXPLAYERS];
// Below consts are only used for demo extrainfo sections
#define DW_STANDING 0x00
#define DW_STANDING2 0x01
// For time attack ghosts
#define GZT_XYZ 0x01
@ -266,7 +273,7 @@ void G_ReadDemoExtraData(void)
break;
case DXD_PST_LEFT:
CL_RemovePlayer(p, 0);
CL_RemovePlayer(p, static_cast<kickreason_t>(0));
break;
}
@ -353,9 +360,9 @@ void G_ReadDemoExtraData(void)
{
rng = READUINT32(demobuf.p);
if (P_GetRandSeed(i) != rng)
if (P_GetRandSeed(static_cast<pr_class_t>(i)) != rng)
{
P_SetRandSeed(i, rng);
P_SetRandSeed(static_cast<pr_class_t>(i), rng);
if (demosynced)
CONS_Alert(CONS_WARNING, "Demo playback has desynced (RNG class %d)!\n", i);
@ -433,7 +440,7 @@ void G_WriteDemoExtraData(void)
{
// Color
memset(name, 0, 16);
strncpy(name, skincolors[players[i].skincolor].name, 16);
strlcpy(name, skincolors[players[i].skincolor].name, 16);
M_Memcpy(demobuf.p,name,16);
demobuf.p += 16;
}
@ -441,7 +448,7 @@ void G_WriteDemoExtraData(void)
{
// Name
memset(name, 0, 16);
strncpy(name, player_names[i], 16);
strlcpy(name, player_names[i], 16);
M_Memcpy(demobuf.p,name,16);
demobuf.p += 16;
}
@ -452,7 +459,7 @@ void G_WriteDemoExtraData(void)
if (players[i].followerskin == -1)
strncpy(name, "None", 16);
else
strncpy(name, followers[players[i].followerskin].name, 16);
strlcpy(name, followers[players[i].followerskin].name, 16);
M_Memcpy(demobuf.p, name, 16);
demobuf.p += 16;
@ -463,7 +470,7 @@ void G_WriteDemoExtraData(void)
if (Followercolor_cons_t[j].value == players[i].followercolor)
break;
}
strncpy(name, Followercolor_cons_t[j].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match"
strlcpy(name, Followercolor_cons_t[j].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match"
M_Memcpy(demobuf.p,name,16);
demobuf.p += 16;
@ -494,7 +501,7 @@ void G_WriteDemoExtraData(void)
for (i = 0; i < PRNUMSYNCED; i++)
{
WRITEUINT32(demobuf.p, P_GetRandSeed(i));
WRITEUINT32(demobuf.p, P_GetRandSeed(static_cast<pr_class_t>(i)));
}
}
}
@ -706,7 +713,7 @@ void G_GhostAddHit(INT32 playernum, mobj_t *victim)
return;
ghostext[playernum].flags |= EZT_HIT;
ghostext[playernum].hits++;
ghostext[playernum].hitlist = Z_Realloc(ghostext[playernum].hitlist, ghostext[playernum].hits * sizeof(mobj_t *), PU_LEVEL, NULL);
ghostext[playernum].hitlist = static_cast<mobj_t**>(Z_Realloc(ghostext[playernum].hitlist, ghostext[playernum].hits * sizeof(mobj_t *), PU_LEVEL, NULL));
P_SetTarget(ghostext[playernum].hitlist + (ghostext[playernum].hits-1), victim);
}
@ -1400,7 +1407,7 @@ readghosttic:
}
}
if (xziptic & EZT_SPRITE)
g->mo->sprite = READUINT16(g->p);
g->mo->sprite = static_cast<spritenum_t>(READUINT16(g->p));
if (xziptic & EZT_ITEMDATA)
g->p += 1 + 1 + 4; // itemtype, itemamount, health
if (xziptic & EZT_STATDATA)
@ -1467,7 +1474,7 @@ readghosttic:
follow->sprite2 = READUINT8(g->p);
else
follow->sprite2 = 0;
follow->sprite = READUINT16(g->p);
follow->sprite = static_cast<spritenum_t>(READUINT16(g->p));
follow->frame = (READUINT8(g->p)) | (g->mo->frame & FF_TRANSMASK);
follow->angle = g->mo->angle;
follow->color = READUINT16(g->p);
@ -1582,7 +1589,7 @@ void G_StoreRewindInfo(void)
return;
timetolog = 8;
info = Z_Calloc(sizeof(rewindinfo_t), PU_STATIC, NULL);
info = static_cast<rewindinfo_t*>(Z_Calloc(sizeof(rewindinfo_t), PU_STATIC, NULL));
for (i = 0; i < MAXPLAYERS; i++)
{
@ -1907,7 +1914,7 @@ static UINT8 G_CheckDemoExtraFiles(savebuffer_t *info, boolean quick)
{
if (!toomany)
{
strlcpy(filename, (char *)info->p, min(P_SaveBufferRemaining(info) + 1, sizeof filename));
strlcpy(filename, (char *)info->p, std::min(P_SaveBufferRemaining(info) + 1, sizeof filename));
}
SKIPSTRINGN(info->p, P_SaveBufferRemaining(info));
@ -1981,7 +1988,7 @@ static void G_SaveDemoSkins(UINT8 **pp)
{
// Skinname, for first attempt at identification.
memset(skin, 0, 16);
strncpy(skin, skins[i].name, 16);
strlcpy(skin, skins[i].name, 16);
WRITEMEM((*pp), skin, 16);
// Backup information for second pass.
@ -2011,7 +2018,7 @@ static democharlist_t *G_LoadDemoSkins(savebuffer_t *info, UINT8 *worknumskins,
if (!(*worknumskins))
return NULL;
skinlist = Z_Calloc(sizeof(democharlist_t) * (*worknumskins), PU_STATIC, NULL);
skinlist = static_cast<democharlist_t*>(Z_Calloc(sizeof(democharlist_t) * (*worknumskins), PU_STATIC, NULL));
if (!skinlist)
{
I_Error("G_LoadDemoSkins: Insufficient memory to allocate list");
@ -2181,7 +2188,7 @@ void G_BeginRecording(void)
for (i = 0; i < PRNUMSYNCED; i++)
{
WRITEUINT32(demobuf.p, P_GetInitSeed(i));
WRITEUINT32(demobuf.p, P_GetInitSeed(static_cast<pr_class_t>(i)));
}
// Reserved for extrainfo location from start of file
@ -2232,7 +2239,7 @@ void G_BeginRecording(void)
// Name
memset(name, 0, 16);
strncpy(name, player_names[p], 16);
strlcpy(name, player_names[p], 16);
M_Memcpy(demobuf.p,name,16);
demobuf.p += 16;
@ -2324,36 +2331,26 @@ void G_BeginRecording(void)
}
}
void G_WriteStanding(UINT8 ranking, char *name, INT32 skinnum, UINT16 color, UINT32 val)
void srb2::write_current_demo_standings(const srb2::StandingsJson& standings)
{
char temp[16];
using namespace srb2;
using json = nlohmann::json;
if (demoinfo_p && *(UINT32 *)demoinfo_p == 0)
{
WRITEUINT8(demobuf.p, DEMOMARKER); // add the demo end marker
*(UINT32 *)demoinfo_p = demobuf.p - demobuf.buffer;
}
// TODO populate standings data
WRITEUINT8(demobuf.p, DW_STANDING);
WRITEUINT8(demobuf.p, ranking);
std::vector<uint8_t> ubjson = json::to_ubjson(standings);
uint32_t bytes = ubjson.size();
// Name
memset(temp, 0, 16);
strncpy(temp, name, 16);
M_Memcpy(demobuf.p,temp,16);
demobuf.p += 16;
WRITEUINT8(demobuf.p, DW_STANDING2);
// Skin
WRITEUINT8(demobuf.p, skinnum);
WRITEUINT32(demobuf.p, bytes);
WRITEMEM(demobuf.p, ubjson.data(), bytes);
}
// Color
memset(temp, 0, 16);
strncpy(temp, skincolors[color].name, 16);
M_Memcpy(demobuf.p,temp,16);
demobuf.p += 16;
// Score/time/whatever
WRITEUINT32(demobuf.p, val);
void srb2::write_current_demo_end_marker()
{
WRITEUINT8(demobuf.p, DEMOMARKER); // add the demo end marker
*(UINT32 *)demoinfo_p = demobuf.p - demobuf.buffer;
}
void G_SetDemoTime(UINT32 ptime, UINT32 plap)
@ -2508,6 +2505,52 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
return c;
}
static bool load_ubjson_standing(menudemo_t* pdemo, tcb::span<std::byte> slice, tcb::span<democharlist_t> demoskins)
{
using namespace srb2;
using json = nlohmann::json;
StandingsJson js;
try
{
js = json::from_ubjson(slice).template get<StandingsJson>();
}
catch (...)
{
return false;
}
size_t toread = std::min<size_t>(js.standings.size(), MAXPLAYERS);
for (size_t i = 0; i < toread; i++)
{
StandingJson& jsstanding = js.standings[i];
auto& memstanding = pdemo->standings[i];
memstanding.ranking = jsstanding.ranking;
strlcpy(memstanding.name, jsstanding.name.c_str(), 17);
if (jsstanding.demoskin >= demoskins.size())
{
memstanding.skin = demoskins[0].mapping;
}
else
{
memstanding.skin = demoskins[jsstanding.demoskin].mapping;
}
memstanding.color = SKINCOLOR_NONE;
for (size_t j = 0; j < numskincolors; j++)
{
skincolor_t& skincolor = skincolors[j];
if (jsstanding.skincolor == skincolor.name)
{
memstanding.color = j;
break;
}
}
memstanding.timeorscore = jsstanding.timeorscore;
}
return true;
}
void G_LoadDemoInfo(menudemo_t *pdemo)
{
savebuffer_t info = {0};
@ -2516,6 +2559,7 @@ void G_LoadDemoInfo(menudemo_t *pdemo)
UINT16 pdemoflags;
democharlist_t *skinlist = NULL;
UINT16 pdemoversion, count;
UINT16 legacystandingplayercount;
char mapname[MAXMAPLUMPNAME],gtname[MAXGAMETYPELENGTH];
INT32 i;
@ -2584,7 +2628,7 @@ void G_LoadDemoInfo(menudemo_t *pdemo)
goto badreplay;
}
info.p += 4; // "PLAY"
READSTRINGN(info.p, mapname, min(P_SaveBufferRemaining(&info), sizeof(mapname)));
READSTRINGN(info.p, mapname, std::min(P_SaveBufferRemaining(&info), sizeof(mapname)));
pdemo->map = G_MapNumber(mapname);
info.p += 16; // mapmd5
@ -2602,7 +2646,7 @@ void G_LoadDemoInfo(menudemo_t *pdemo)
goto badreplay;
}
READSTRINGN(info.p, gtname, min(P_SaveBufferRemaining(&info), sizeof(gtname))); // gametype
READSTRINGN(info.p, gtname, std::min(P_SaveBufferRemaining(&info), sizeof(gtname))); // gametype
pdemo->gametype = G_GetGametypeByName(gtname);
if (P_SaveBufferRemaining(&info) < 1)
@ -2668,44 +2712,95 @@ void G_LoadDemoInfo(menudemo_t *pdemo)
pdemo->gp = true;
// Read standings!
count = 0;
legacystandingplayercount = 0;
info.p = extrainfo_p;
while (P_SaveBufferRemaining(&info) >= 1+1+16+1+16+4 &&
READUINT8(info.p) == DW_STANDING) // Assume standings are always first in the extrainfo
while (P_SaveBufferRemaining(&info) > 1)
{
char temp[16];
UINT8 extrainfotag = READUINT8(info.p);
pdemo->standings[count].ranking = READUINT8(info.p);
// Name
M_Memcpy(pdemo->standings[count].name, info.p, 16);
info.p += 16;
// Skin
skinid = READUINT8(info.p);
if (skinid > worknumskins)
skinid = 0;
pdemo->standings[count].skin = skinlist[skinid].mapping;
// Color
M_Memcpy(temp,info.p,16);
info.p += 16;
for (i = 0; i < numskincolors; i++)
if (!stricmp(skincolors[i].name,temp)) // SRB2kart
switch (extrainfotag)
{
case DW_STANDING:
{
pdemo->standings[count].color = i;
// This is the only extrainfo tag that is not length prefixed. All others must be.
constexpr size_t kLegacyStandingSize = 1+16+1+16+4;
if (P_SaveBufferRemaining(&info) < kLegacyStandingSize)
{
goto corrupt;
}
if (legacystandingplayercount >= MAXPLAYERS)
{
info.p += kLegacyStandingSize;
break; // switch
}
char temp[16];
pdemo->standings[legacystandingplayercount].ranking = READUINT8(info.p);
// Name
M_Memcpy(pdemo->standings[legacystandingplayercount].name, info.p, 16);
info.p += 16;
// Skin
skinid = READUINT8(info.p);
if (skinid > worknumskins)
skinid = 0;
pdemo->standings[legacystandingplayercount].skin = skinlist[skinid].mapping;
// Color
M_Memcpy(temp,info.p,16);
info.p += 16;
for (i = 0; i < numskincolors; i++)
if (!stricmp(skincolors[i].name,temp)) // SRB2kart
{
pdemo->standings[legacystandingplayercount].color = i;
break;
}
// Score/time/whatever
pdemo->standings[legacystandingplayercount].timeorscore = READUINT32(info.p);
legacystandingplayercount++;
break;
}
// Score/time/whatever
pdemo->standings[count].timeorscore = READUINT32(info.p);
count++;
if (count >= MAXPLAYERS)
break; //@TODO still cycle through the rest of these if extra demo data is ever used
case DW_STANDING2:
{
if (P_SaveBufferRemaining(&info) < 4)
{
goto corrupt;
}
UINT32 size = READUINT32(info.p);
if (P_SaveBufferRemaining(&info) < size)
{
goto corrupt;
}
tcb::span<std::byte> slice = tcb::as_writable_bytes(tcb::span(info.p, size));
tcb::span<democharlist_t> demoskins {skinlist, worknumskins};
info.p += size;
if (!load_ubjson_standing(pdemo, slice, demoskins))
{
goto corrupt;
}
break;
}
default:
{
// Gracefully ignore other extrainfo tags by skipping their data
if (P_SaveBufferRemaining(&info) < 4)
{
goto corrupt;
}
UINT32 size = READUINT32(info.p);
if (P_SaveBufferRemaining(&info) < size)
{
goto corrupt;
}
info.p += size;
break;
}
}
}
if (P_SaveBufferRemaining(&info) == 0)
@ -2774,7 +2869,7 @@ void G_DoPlayDemo(const char *defdemoname)
if (defdemoname == NULL)
{
demobuf.p = demobuf.buffer;
pdemoname = ZZ_Alloc(1); // Easier than adding checks for this everywhere it's freed
pdemoname = static_cast<char*>(ZZ_Alloc(1)); // Easier than adding checks for this everywhere it's freed
}
else
{
@ -2787,7 +2882,7 @@ void G_DoPlayDemo(const char *defdemoname)
n--;
if (n != defdemoname)
n++;
pdemoname = ZZ_Alloc(strlen(n)+1);
pdemoname = static_cast<char*>(ZZ_Alloc(strlen(n)+1));
strcpy(pdemoname,n);
M_SetPlaybackMenuPointer();
@ -3065,7 +3160,7 @@ void G_DoPlayDemo(const char *defdemoname)
grandprixinfo.gp = true;
grandprixinfo.gamespeed = READUINT8(demobuf.p);
grandprixinfo.masterbots = READUINT8(demobuf.p) != 0;
grandprixinfo.eventmode = READUINT8(demobuf.p);
grandprixinfo.eventmode = static_cast<gpEvent_e>(READUINT8(demobuf.p));
}
// Sigh ... it's an empty demo.
@ -3236,7 +3331,7 @@ void G_DoPlayDemo(const char *defdemoname)
clientpowerlevels[p][gametype == GT_BATTLE ? PWRLV_BATTLE : PWRLV_RACE] = READUINT16(demobuf.p);
// Followitem
players[p].followitem = READUINT32(demobuf.p);
players[p].followitem = static_cast<mobjtype_t>(READUINT32(demobuf.p));
// GP
players[p].lives = READSINT8(demobuf.p);
@ -3262,7 +3357,7 @@ void G_DoPlayDemo(const char *defdemoname)
if (demo.attract == DEMO_ATTRACT_TITLE)
{
splitscreen = M_RandomKey(6)-1;
splitscreen = min(min(3, numslots-1), splitscreen); // Bias toward 1p and 4p views
splitscreen = std::min<int>(std::min(3, numslots-1), splitscreen); // Bias toward 1p and 4p views
for (p = 0; p <= splitscreen; p++)
G_ResetView(p+1, slots[M_RandomKey(numslots)], false);
@ -3272,7 +3367,7 @@ void G_DoPlayDemo(const char *defdemoname)
for (i = 0; i < PRNUMSYNCED; i++)
{
P_SetRandSeed(i, randseed[i]);
P_SetRandSeed(static_cast<pr_class_t>(i), randseed[i]);
}
G_InitNew((demoflags & DF_ENCORE) != 0, gamemap, true, true); // Doesn't matter whether you reset or not here, given changes to resetplayer.
@ -3484,7 +3579,7 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname)
}
gh = Z_Calloc(sizeof(demoghost), PU_LEVEL, NULL);
gh = static_cast<demoghost*>(Z_Calloc(sizeof(demoghost), PU_LEVEL, NULL));
gh->next = ghosts;
gh->buffer = buffer->buffer;
M_Memcpy(gh->checksum, md5, 16);
@ -3651,7 +3746,7 @@ staffbrief_t *G_GetStaffGhostBrief(UINT8 *buffer)
M_Memcpy(temp.name, p, 16);
ret = Z_Malloc(sizeof(staffbrief_t), PU_STATIC, NULL);
ret = static_cast<staffbrief_t*>(Z_Malloc(sizeof(staffbrief_t), PU_STATIC, NULL));
if (ret)
M_Memcpy(ret, &temp, sizeof(staffbrief_t));
@ -3732,7 +3827,7 @@ static void G_StopTimingDemo(void)
if (timedemo_csv)
{
FILE *f;
const char *csvpath = va("%s"PATHSEP"%s", srb2home, "timedemo.csv");
const char *csvpath = va("%s" PATHSEP "%s", srb2home, "timedemo.csv");
const char *header = "id,demoname,seconds,avgfps,leveltime,demotime,framecount,ticrate,rendermode,vidmode,vidwidth,vidheight,procbits\n";
const char *rowformat = "\"%s\",\"%s\",%f,%f,%u,%d,%u,%u,%u,%u,%u,%u,%u\n";
boolean headerrow = !FIL_FileExists(csvpath);
@ -3836,7 +3931,7 @@ boolean G_CheckDemoStatus(void)
if (!demo.recording)
return false;
if (modeattacking || demo.savemode != DSM_NOTSAVING)
if (modeattacking || demo.savemode != demovars_s::DSM_NOTSAVING)
{
if (demobuf.p)
{
@ -3923,13 +4018,13 @@ void G_SaveDemo(void)
#endif
if (FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer)) // finally output the file.
demo.savemode = DSM_SAVED;
demo.savemode = demovars_s::DSM_SAVED;
Z_Free(demobuf.buffer);
demo.recording = false;
if (!modeattacking)
{
if (demo.savemode == DSM_SAVED)
if (demo.savemode == demovars_s::DSM_SAVED)
{
CONS_Printf(M_GetText("Demo %s recorded\n"), demoname);
if (gamedata->eversavedreplay == false)
@ -3957,13 +4052,13 @@ boolean G_DemoTitleResponder(event_t *ev)
// Only ESC and non-keyboard keys abort connection
if (ch == KEY_ESCAPE)
{
demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING;
demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? demovars_s::DSM_WILLAUTOSAVE : demovars_s::DSM_NOTSAVING;
return true;
}
if (ch == KEY_ENTER || ch >= NUMKEYS)
{
demo.savemode = DSM_WILLSAVE;
demo.savemode = demovars_s::DSM_WILLSAVE;
return true;
}
@ -4001,7 +4096,7 @@ boolean G_CheckDemoTitleEntry(void)
if (!G_PlayerInputDown(0, gc_b, 0) && !G_PlayerInputDown(0, gc_x, 0))
return false;
demo.savemode = DSM_TITLEENTRY;
demo.savemode = demovars_s::DSM_TITLEENTRY;
return true;
}

View file

@ -19,6 +19,44 @@
#include "d_event.h"
#ifdef __cplusplus
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
// Modern json formats
namespace srb2
{
struct StandingJson
{
uint8_t ranking;
std::string name;
uint8_t demoskin;
std::string skincolor;
uint32_t timeorscore;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
StandingJson,
ranking,
name,
demoskin,
skincolor,
timeorscore
)
};
struct StandingsJson
{
std::vector<StandingJson> standings;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(StandingsJson, standings)
};
void write_current_demo_standings(const StandingsJson& standings);
void write_current_demo_end_marker();
} // namespace srb2
extern "C" {
#endif
@ -105,7 +143,6 @@ void G_RecordDemo(const char *name);
void G_BeginRecording(void);
// Only called by shutdown code.
void G_WriteStanding(UINT8 ranking, char *name, INT32 skinnum, UINT16 color, UINT32 val);
void G_SetDemoTime(UINT32 ptime, UINT32 plap);
UINT8 G_CmpDemoTime(char *oldname, char *newname);

View file

@ -166,3 +166,63 @@ void FileStream::close()
file_ = nullptr;
}
static int portable_fseek64(FILE* file, int64_t offset, int origin)
{
#ifdef _MSC_VER
return _fseeki64(file, offset, origin);
#elif __APPLE__
return fseeko(file, offset, origin);
#else
return fseeko64(file, offset, origin);
#endif
}
static int64_t portable_ftell64(FILE* file)
{
#ifdef _MSC_VER
return _ftelli64(file);
#elif __APPLE__
return ftello(file);
#else
return ftello64(file);
#endif
}
StreamSize FileStream::seek(SeekFrom seek_from, StreamOffset offset)
{
if (!file_)
{
throw std::domain_error("FileStream is empty");
}
int origin;
switch (seek_from)
{
case SeekFrom::kStart:
origin = SEEK_SET;
break;
case SeekFrom::kCurrent:
origin = SEEK_CUR;
break;
case SeekFrom::kEnd:
origin = SEEK_END;
break;
default:
throw std::invalid_argument("invalid SeekFrom");
}
if (portable_fseek64((FILE*)(file_), offset, origin) != 0)
{
int err = errno;
throw make_exception_from_errno(err);
}
StreamOffset newpos = portable_ftell64((FILE*)(file_));
if (newpos < 0)
{
int err = errno;
throw make_exception_from_errno(err);
}
return newpos;
}

View file

@ -183,9 +183,9 @@ void read(uint16_t& value, I& stream, Endian endian = Endian::kLE) {
}
template <typename I, typename std::enable_if_t<IsInputStreamV<I>>* = nullptr>
uint16_t read_uint16(I& stream) {
uint16_t read_uint16(I& stream, Endian endian = Endian::kLE) {
uint16_t ret;
read(ret, stream);
read(ret, stream, endian);
return ret;
}
@ -211,9 +211,9 @@ void read(int16_t& value, I& stream, Endian endian = Endian::kLE) {
}
template <typename I, typename std::enable_if_t<IsInputStreamV<I>>* = nullptr>
int16_t read_int16(I& stream) {
int16_t read_int16(I& stream, Endian endian = Endian::kLE) {
int16_t ret;
read(ret, stream);
read(ret, stream, endian);
return ret;
}
@ -235,9 +235,9 @@ void read(uint32_t& value, I& stream, Endian endian = Endian::kLE) {
}
template <typename I, typename std::enable_if_t<IsInputStreamV<I>>* = nullptr>
uint32_t read_uint32(I& stream) {
uint32_t read_uint32(I& stream, Endian endian = Endian::kLE) {
uint32_t ret;
read(ret, stream);
read(ret, stream, endian);
return ret;
}
@ -267,9 +267,9 @@ void read(int32_t& value, I& stream, Endian endian = Endian::kLE) {
}
template <typename I, typename std::enable_if_t<IsInputStreamV<I>>* = nullptr>
int32_t read_int32(I& stream) {
int32_t read_int32(I& stream, Endian endian = Endian::kLE) {
int32_t ret;
read(ret, stream);
read(ret, stream, endian);
return ret;
}
@ -295,9 +295,9 @@ void read(uint64_t& value, I& stream, Endian endian = Endian::kLE) {
}
template <typename I, typename std::enable_if_t<IsInputStreamV<I>>* = nullptr>
uint64_t read_uint64(I& stream) {
uint64_t read_uint64(I& stream, Endian endian = Endian::kLE) {
uint64_t ret;
read(ret, stream);
read(ret, stream, endian);
return ret;
}
@ -335,9 +335,9 @@ void read(int64_t& value, I& stream, Endian endian = Endian::kLE) {
}
template <typename I, typename std::enable_if_t<IsInputStreamV<I>>* = nullptr>
int64_t read_int64(I& stream) {
int64_t read_int64(I& stream, Endian endian = Endian::kLE) {
int64_t ret;
read(ret, stream);
read(ret, stream, endian);
return ret;
}
@ -354,9 +354,9 @@ void read(float& value, I& stream, Endian endian = Endian::kLE) {
}
template <typename I, typename std::enable_if_t<IsInputStreamV<I>>* = nullptr>
float read_float(I& stream) {
float read_float(I& stream, Endian endian = Endian::kLE) {
float ret;
read(ret, stream);
read(ret, stream, endian);
return ret;
}
@ -373,9 +373,9 @@ void read(double& value, I& stream, Endian endian = Endian::kLE) {
}
template <typename I, typename std::enable_if_t<IsInputStreamV<I>>* = nullptr>
double read_double(I& stream) {
double read_double(I& stream, Endian endian = Endian::kLE) {
double ret;
read(ret, stream);
read(ret, stream, endian);
return ret;
}
@ -616,9 +616,7 @@ public:
StreamSize read(tcb::span<std::byte> buffer);
StreamSize write(tcb::span<const std::byte> buffer);
// not bothering with seeking for now -- apparently 64-bit file positions is not available in ansi c
// StreamSize seek(SeekFrom seek_from, StreamOffset offset);
StreamSize seek(SeekFrom seek_from, StreamOffset offset);
void close();
};

View file

@ -10,6 +10,11 @@
/// \file k_profiles.c
/// \brief implements methods for profiles etc.
#include <algorithm>
#include <fmt/format.h>
#include "io/streams.hpp"
#include "doomtype.h"
#include "d_main.h" // pandf
#include "byteptr.h" // READ/WRITE macros
@ -34,11 +39,11 @@ INT32 PR_GetNumProfiles(void)
return numprofiles;
}
static void PR_GenerateProfileKeys(profile_t *new)
static void PR_GenerateProfileKeys(profile_t *newprofile)
{
static uint8_t seed[32];
csprng(seed, 32);
crypto_eddsa_key_pair(new->secret_key, new->public_key, seed);
crypto_eddsa_key_pair(newprofile->secret_key, newprofile->public_key, seed);
}
profile_t* PR_MakeProfile(
@ -49,52 +54,52 @@ profile_t* PR_MakeProfile(
INT32 controlarray[num_gamecontrols][MAXINPUTMAPPING],
boolean guest)
{
profile_t *new = Z_Calloc(sizeof(profile_t), PU_STATIC, NULL);
profile_t *newprofile = static_cast<profile_t*>(Z_Calloc(sizeof(profile_t), PU_STATIC, NULL));
new->version = PROFILEVER;
newprofile->version = PROFILEVER;
memset(new->secret_key, 0, sizeof(new->secret_key));
memset(new->public_key, 0, sizeof(new->public_key));
memset(newprofile->secret_key, 0, sizeof(newprofile->secret_key));
memset(newprofile->public_key, 0, sizeof(newprofile->public_key));
if (!guest)
{
PR_GenerateProfileKeys(new);
PR_GenerateProfileKeys(newprofile);
}
strcpy(new->profilename, prname);
new->profilename[sizeof new->profilename - 1] = '\0';
strcpy(newprofile->profilename, prname);
newprofile->profilename[sizeof newprofile->profilename - 1] = '\0';
strcpy(new->skinname, sname);
strcpy(new->playername, pname);
new->color = col;
strcpy(newprofile->skinname, sname);
strcpy(newprofile->playername, pname);
newprofile->color = col;
strcpy(new->follower, fname);
new->followercolor = fcol;
new->kickstartaccel = false;
new->autoroulette = false;
new->litesteer = true;
new->rumble = true;
strcpy(newprofile->follower, fname);
newprofile->followercolor = fcol;
newprofile->kickstartaccel = false;
newprofile->autoroulette = false;
newprofile->litesteer = true;
newprofile->rumble = true;
// Copy from gamecontrol directly as we'll be setting controls up directly in the profile.
memcpy(new->controls, controlarray, sizeof(new->controls));
memcpy(newprofile->controls, controlarray, sizeof(newprofile->controls));
new->wins = 0;
newprofile->wins = 0;
return new;
return newprofile;
}
profile_t* PR_MakeProfileFromPlayer(const char *prname, const char *pname, const char *sname, const UINT16 col, const char *fname, UINT16 fcol, UINT8 pnum)
{
// Generate profile using the player's gamecontrol, as we set them directly when making profiles from menus.
profile_t *new = PR_MakeProfile(prname, pname, sname, col, fname, fcol, gamecontrol[pnum], false);
profile_t *newprofile = PR_MakeProfile(prname, pname, sname, col, fname, fcol, gamecontrol[pnum], false);
// Player bound cvars:
new->kickstartaccel = cv_kickstartaccel[pnum].value;
new->autoroulette = cv_autoroulette[pnum].value;
new->litesteer = cv_litesteer[pnum].value;
new->rumble = cv_rumble[pnum].value;
newprofile->kickstartaccel = cv_kickstartaccel[pnum].value;
newprofile->autoroulette = cv_autoroulette[pnum].value;
newprofile->litesteer = cv_litesteer[pnum].value;
newprofile->rumble = cv_rumble[pnum].value;
return new;
return newprofile;
}
boolean PR_AddProfile(profile_t *p)
@ -236,10 +241,10 @@ void PR_InitNewProfile(void)
void PR_SaveProfiles(void)
{
size_t length = 0;
const size_t headerlen = strlen(PROFILEHEADER);
UINT8 i, j, k;
savebuffer_t save = {0};
namespace fs = std::filesystem;
using json = nlohmann::json;
using namespace srb2;
namespace io = srb2::io;
if (profilesList[PROFILE_GUEST] == NULL)
{
@ -247,65 +252,94 @@ void PR_SaveProfiles(void)
return;
}
if (P_SaveBufferAlloc(&save, sizeof(UINT32) + (numprofiles * sizeof(profile_t))) == false)
ProfilesJson ng{};
for (size_t i = 1; i < numprofiles; i++)
{
I_Error("No more free memory for saving profiles\n");
return;
}
ProfileJson jsonprof;
profile_t* cprof = profilesList[i];
// Add header.
WRITESTRINGN(save.p, PROFILEHEADER, headerlen);
WRITEUINT8(save.p, PROFILEVER);
WRITEUINT8(save.p, numprofiles);
for (i = 1; i < numprofiles; i++)
{
// Names and keys, all the string data up front
WRITESTRINGN(save.p, profilesList[i]->profilename, PROFILENAMELEN);
WRITEMEM(save.p, profilesList[i]->public_key, sizeof(((profile_t *)0)->public_key));
WRITEMEM(save.p, profilesList[i]->secret_key, sizeof(((profile_t *)0)->secret_key));
WRITESTRINGN(save.p, profilesList[i]->playername, MAXPLAYERNAME);
// Character and colour.
WRITESTRINGN(save.p, profilesList[i]->skinname, SKINNAMESIZE);
WRITEUINT16(save.p, profilesList[i]->color);
// Follower and colour.
WRITESTRINGN(save.p, profilesList[i]->follower, SKINNAMESIZE);
WRITEUINT16(save.p, profilesList[i]->followercolor);
WRITEUINT32(save.p, profilesList[i]->wins);
// Consvars.
WRITEUINT8(save.p, profilesList[i]->kickstartaccel);
WRITEUINT8(save.p, profilesList[i]->autoroulette);
WRITEUINT8(save.p, profilesList[i]->litesteer);
WRITEUINT8(save.p, profilesList[i]->rumble);
// Controls.
for (j = 0; j < num_gamecontrols; j++)
jsonprof.version = PROFILEVER;
jsonprof.profilename = std::string(cprof->profilename);
std::copy(std::begin(cprof->public_key), std::end(cprof->public_key), std::begin(jsonprof.publickey));
std::copy(std::begin(cprof->secret_key), std::end(cprof->secret_key), std::begin(jsonprof.secretkey));
jsonprof.playername = std::string(cprof->playername);
jsonprof.skinname = std::string(cprof->skinname);
jsonprof.colorname = std::string(skincolors[cprof->color].name);
jsonprof.followername = std::string(cprof->follower);
if (cprof->followercolor == FOLLOWERCOLOR_MATCH)
{
for (k = 0; k < MAXINPUTMAPPING; k++)
jsonprof.followercolorname = "Match";
}
else if (cprof->followercolor == FOLLOWERCOLOR_OPPOSITE)
{
jsonprof.followercolorname = "Opposite";
}
else if (cprof->followercolor == SKINCOLOR_NONE)
{
jsonprof.followercolorname = "Default";
}
else if (cprof->followercolor >= numskincolors)
{
jsonprof.followercolorname = std::string();
}
else
{
jsonprof.followercolorname = std::string(skincolors[cprof->followercolor].name);
}
jsonprof.records.wins = cprof->wins;
jsonprof.preferences.kickstartaccel = cprof->kickstartaccel;
jsonprof.preferences.autoroulette = cprof->autoroulette;
jsonprof.preferences.litesteer = cprof->litesteer;
jsonprof.preferences.rumble = cprof->rumble;
for (size_t j = 0; j < num_gamecontrols; j++)
{
for (size_t k = 0; k < MAXINPUTMAPPING; k++)
{
WRITEINT32(save.p, profilesList[i]->controls[j][k]);
jsonprof.controls[j][k] = cprof->controls[j][k];
}
}
ng.profiles.emplace_back(std::move(jsonprof));
}
length = save.p - save.buffer;
std::vector<uint8_t> ubjson = json::to_ubjson(ng);
if (!FIL_WriteFile(va(pandf, srb2home, PROFILESFILE), save.buffer, length))
std::string realpath = fmt::format("{}/{}", srb2home, PROFILESFILE);
std::string tmppath = fmt::format("{}.tmp", realpath);
try
{
io::FileStream file {tmppath, io::FileStreamMode::kWrite};
io::BufferedOutputStream<io::FileStream> bos {std::move(file)};
io::write(static_cast<uint32_t>(0x52494E47), bos, io::Endian::kBE); // "RING"
io::write(static_cast<uint32_t>(0x5052464C), bos, io::Endian::kBE); // "PRFL"
io::write(static_cast<uint8_t>(0), bos); // reserved1
io::write(static_cast<uint8_t>(0), bos); // reserved2
io::write(static_cast<uint8_t>(0), bos); // reserved3
io::write(static_cast<uint8_t>(0), bos); // reserved4
io::write_exact(bos, tcb::as_bytes(tcb::make_span(ubjson)));
bos.flush();
file = bos.stream();
file.close();
fs::rename(tmppath, realpath);
}
catch (...)
{
P_SaveBufferFree(&save);
I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?");
}
P_SaveBufferFree(&save);
}
void PR_LoadProfiles(void)
{
const size_t headerlen = strlen(PROFILEHEADER);
UINT8 i, j, k, version;
namespace fs = std::filesystem;
using namespace srb2;
namespace io = srb2::io;
using json = nlohmann::json;
profile_t *dprofile = PR_MakeProfile(
PROFILEDEFAULTNAME,
PROFILEDEFAULTPNAME,
@ -314,166 +348,132 @@ void PR_LoadProfiles(void)
gamecontroldefault,
true
);
savebuffer_t save = {0};
if (P_SaveBufferFromFile(&save, va(pandf, srb2home, PROFILESFILE)) == false)
std::string datapath {fmt::format("{}/{}", srb2home, PROFILESFILE)};
io::BufferedInputStream<io::FileStream> bis;
try
{
io::FileStream file {datapath, io::FileStreamMode::kRead};
bis = io::BufferedInputStream(std::move(file));
}
catch (const io::FileStreamException& ex)
{
// No profiles. Add the default one.
PR_AddProfile(dprofile);
return;
}
if (strncmp(PROFILEHEADER, (const char *)save.buffer, headerlen))
ProfilesJson js;
try
{
const char *gdfolder = "the Ring Racers folder";
if (strcmp(srb2home,"."))
gdfolder = srb2home;
uint32_t magic1;
uint32_t magic2;
uint8_t reserved1;
uint8_t reserved2;
uint8_t reserved3;
uint8_t reserved4;
magic1 = io::read_uint32(bis, io::Endian::kBE);
magic2 = io::read_uint32(bis, io::Endian::kBE);
reserved1 = io::read_uint8(bis);
reserved2 = io::read_uint8(bis);
reserved3 = io::read_uint8(bis);
reserved4 = io::read_uint8(bis);
P_SaveBufferFree(&save);
I_Error("Not a valid Profile file.\nDelete %s (maybe in %s) and try again.", PROFILESFILE, gdfolder);
}
save.p += headerlen;
if (magic1 != 0x52494E47 || magic2 != 0x5052464C || reserved1 != 0 || reserved2 != 0 || reserved3 != 0 || reserved4 != 0)
{
throw std::domain_error("Header is incompatible");
}
version = READUINT8(save.p);
if (version > PROFILEVER)
{
P_SaveBufferFree(&save);
I_Error("Existing %s is from the future! (expected %d, got %d)", PROFILESFILE, PROFILEVER, version);
std::vector<std::byte> remainder = io::read_to_vec(bis);
// safety: std::byte repr is always uint8_t 1-byte aligned
tcb::span<uint8_t> remainder_as_u8 = tcb::span((uint8_t*)remainder.data(), remainder.size());
json parsed = json::from_ubjson(remainder_as_u8);
js = parsed.template get<ProfilesJson>();
}
else if (version < PROFILEVER)
catch (...)
{
// We're converting - let'd create a backup.
FIL_WriteFile(va("%s" PATHSEP "%s.bak", srb2home, PROFILESFILE), save.buffer, save.size);
I_Error("Profiles file is corrupt");
return;
}
numprofiles = READUINT8(save.p);
if (numprofiles > MAXPROFILES)
numprofiles = MAXPROFILES;
for (i = 1; i < numprofiles; i++)
numprofiles = js.profiles.size() + 1; // 1 for guest
if (numprofiles > MAXPROFILES+1)
{
profilesList[i] = Z_Calloc(sizeof(profile_t), PU_STATIC, NULL);
numprofiles = MAXPROFILES+1;
}
// Version. (We always update this on successful forward step)
profilesList[i]->version = PROFILEVER;
for (size_t i = 1; i < numprofiles; i++)
{
auto& jsprof = js.profiles[i - 1];
profile_t* newprof = static_cast<profile_t*>(Z_Calloc(sizeof(profile_t), PU_STATIC, NULL));
profilesList[i] = newprof;
// Names and keys, all the identity stuff up front
READSTRINGN(save.p, profilesList[i]->profilename, PROFILENAMELEN);
newprof->version = jsprof.version;
strlcpy(newprof->profilename, jsprof.profilename.c_str(), sizeof(newprof->profilename));
memcpy(newprof->public_key, jsprof.publickey.data(), sizeof(newprof->public_key));
memcpy(newprof->secret_key, jsprof.secretkey.data(), sizeof(newprof->secret_key));
// Profile update 2-->3: Add profile keys.
if (version < 3)
strlcpy(newprof->playername, jsprof.playername.c_str(), sizeof(newprof->playername));
strlcpy(newprof->skinname, jsprof.skinname.c_str(), sizeof(newprof->skinname));
newprof->color = PROFILEDEFAULTCOLOR;
for (size_t c = 0; c < numskincolors; c++)
{
// Generate missing keys.
PR_GenerateProfileKeys(profilesList[i]);
}
else
{
READMEM(save.p, profilesList[i]->public_key, sizeof(((profile_t *)0)->public_key));
READMEM(save.p, profilesList[i]->secret_key, sizeof(((profile_t *)0)->secret_key));
}
READSTRINGN(save.p, profilesList[i]->playername, MAXPLAYERNAME);
// Character and colour.
READSTRINGN(save.p, profilesList[i]->skinname, SKINNAMESIZE);
profilesList[i]->color = READUINT16(save.p);
if (profilesList[i]->color == SKINCOLOR_NONE)
{
; // Valid, even outside the bounds
}
else if (profilesList[i]->color >= numskincolors
|| K_ColorUsable(profilesList[i]->color, false, false) == false)
{
profilesList[i]->color = PROFILEDEFAULTCOLOR;
}
// Follower and colour.
READSTRINGN(save.p, profilesList[i]->follower, SKINNAMESIZE);
profilesList[i]->followercolor = READUINT16(save.p);
if (profilesList[i]->followercolor == FOLLOWERCOLOR_MATCH
|| profilesList[i]->followercolor == FOLLOWERCOLOR_OPPOSITE
|| profilesList[i]->followercolor == SKINCOLOR_NONE)
{
; // Valid, even outside the bounds
}
else if (profilesList[i]->followercolor >= numskincolors
|| K_ColorUsable(profilesList[i]->followercolor, true, false) == false)
{
profilesList[i]->followercolor = PROFILEDEFAULTFOLLOWERCOLOR;
}
// Profile update 5-->6: PWR isn't in profile data anymore.
if (version < 6)
{
save.p += PWRLV_NUMTYPES*2;
profilesList[i]->wins = 0;
}
else
{
profilesList[i]->wins = READUINT32(save.p);
}
// Consvars.
profilesList[i]->kickstartaccel = (boolean)READUINT8(save.p);
// 6->7, add autoroulette
if (version < 7)
{
profilesList[i]->autoroulette = false;
}
else
{
profilesList[i]->autoroulette = (boolean)READUINT8(save.p);
}
// 7->8, add litesteer
if (version < 8)
{
profilesList[i]->litesteer = true;
}
else
{
profilesList[i]->litesteer = (boolean)READUINT8(save.p);
}
if (version < 4)
{
profilesList[i]->rumble = true;
}
else
{
profilesList[i]->rumble = (boolean)READUINT8(save.p);
}
// Controls.
for (j = 0; j < num_gamecontrols; j++)
{
#ifdef DEVELOP
// Profile update 1-->2: Add gc_rankings.
// Profile update 4-->5: Add gc_startlossless.
if ((j == gc_rankings && version < 2) ||
(j == gc_startlossless && version < 5))
if (jsprof.colorname == skincolors[c].name && K_ColorUsable(static_cast<skincolornum_t>(c), false, false))
{
for (k = 0; k < MAXINPUTMAPPING; k++)
newprof->color = c;
break;
}
}
strlcpy(newprof->follower, jsprof.followername.c_str(), sizeof(newprof->follower));
newprof->followercolor = PROFILEDEFAULTFOLLOWERCOLOR;
if (jsprof.followercolorname == "Match")
{
newprof->followercolor = FOLLOWERCOLOR_MATCH;
}
else if (jsprof.followercolorname == "Opposite")
{
newprof->followercolor = FOLLOWERCOLOR_OPPOSITE;
}
else if (jsprof.followercolorname == "Default")
{
newprof->followercolor = SKINCOLOR_NONE;
}
else if (!jsprof.followercolorname.empty())
{
for (size_t c = 0; c < numskincolors; c++)
{
if (jsprof.followercolorname == skincolors[c].name && K_ColorUsable(static_cast<skincolornum_t>(c), false, false))
{
profilesList[i]->controls[j][k] = gamecontroldefault[j][k];
newprof->followercolor = c;
break;
}
continue;
}
#endif
}
for (k = 0; k < MAXINPUTMAPPING; k++)
newprof->wins = jsprof.records.wins;
newprof->kickstartaccel = jsprof.preferences.kickstartaccel;
newprof->autoroulette = jsprof.preferences.autoroulette;
newprof->litesteer = jsprof.preferences.litesteer;
newprof->rumble = jsprof.preferences.rumble;
try
{
for (size_t j = 0; j < num_gamecontrols; j++)
{
profilesList[i]->controls[j][k] = READINT32(save.p);
for (size_t k = 0; k < MAXINPUTMAPPING; k++)
{
newprof->controls[j][k] = jsprof.controls.at(j).at(k);
}
}
}
catch (const std::out_of_range& ex)
{
I_Error("Profile '%s' controls are corrupt", jsprof.playername.c_str());
return;
}
}
// Add the the default profile directly to avoid letting anyone tamper with it.
profilesList[PROFILE_GUEST] = dprofile;
}
@ -638,7 +638,7 @@ char *GetPrettyRRID(const unsigned char *bin, boolean brief)
rrid_buf[i*2] = "0123456789ABCDEF"[bin[i] >> 4];
rrid_buf[i*2+1] = "0123456789ABCDEF"[bin[i] & 0x0F];
}
rrid_buf[len*2] = '\0';
return rrid_buf;

View file

@ -23,6 +23,82 @@
#include "k_follower.h" // followers
#ifdef __cplusplus
#include <array>
#include <cstdint>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
namespace srb2
{
struct ProfileRecordsJson
{
uint32_t wins;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfileRecordsJson, wins)
};
struct ProfilePreferencesJson
{
bool kickstartaccel;
bool autoroulette;
bool litesteer;
bool rumble;
tm test;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
ProfilePreferencesJson,
kickstartaccel,
autoroulette,
litesteer,
rumble
)
};
struct ProfileJson
{
uint32_t version;
std::string profilename;
std::string playername;
std::array<uint8_t, 32> publickey = {{}};
std::array<uint8_t, 64> secretkey = {{}};
std::string skinname;
std::string colorname;
std::string followername;
std::string followercolorname;
ProfileRecordsJson records;
ProfilePreferencesJson preferences;
std::array<std::array<int32_t, MAXINPUTMAPPING>, gamecontrols_e::num_gamecontrols> controls = {{{{}}}};
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
ProfileJson,
version,
profilename,
playername,
publickey,
secretkey,
skinname,
colorname,
followername,
followercolorname,
records,
preferences,
controls
)
};
struct ProfilesJson
{
std::vector<ProfileJson> profiles;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfilesJson, profiles)
};
} // namespace srb2
extern "C" {
#endif
@ -31,7 +107,7 @@ extern "C" {
#define SKINNAMESIZE 16
#define PROFILENAMELEN 6
#define PROFILEVER 8
#define PROFILEVER 1
#define MAXPROFILES 16
#define PROFILESFILE "ringprofiles.prf"
#define PROFILE_GUEST 0

View file

@ -9,6 +9,8 @@
/// \file y_inter.c
/// \brief Tally screens, or "Intermissions" as they were formally called in Doom
#include <algorithm>
#include "doomdef.h"
#include "doomstat.h"
#include "d_main.h"
@ -194,6 +196,8 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32))
data.isduel = (numplayersingame <= 2);
srb2::StandingsJson standings {};
for (j = 0; j < numplayersingame; j++)
{
for (i = 0; i < MAXPLAYERS; i++)
@ -238,13 +242,13 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32))
if (demo.recording)
{
G_WriteStanding(
data.pos[data.numplayers],
player_names[i],
data.character[data.numplayers],
data.color[data.numplayers],
data.val[data.numplayers]
);
srb2::StandingJson standing {};
standing.ranking = data.pos[data.numplayers];
standing.name = std::string(player_names[i]);
standing.demoskin = data.character[data.numplayers];
standing.skincolor = std::string(skincolors[data.color[data.numplayers]].name);
standing.timeorscore = data.val[data.numplayers];
standings.standings.emplace_back(std::move(standing));
}
if (data.val[data.numplayers] == (UINT32_MAX-1))
@ -282,6 +286,12 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32))
data.numplayers++;
}
if (demo.recording)
{
srb2::write_current_demo_end_marker();
srb2::write_current_demo_standings(standings);
}
if (getmainplayer == true)
{
// Okay, player scores have been set now - we can calculate GP-relevant material.
@ -308,7 +318,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32))
data.showrank = (rankforline >= GRADE_A);
data.linemeter =
(min(rankforline, GRADE_A)
(std::min(rankforline, GRADE_A)
* (2 * TICRATE)
) / GRADE_A;
@ -465,7 +475,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
INT32 hilicol = highlightflags;
patch_t *resbar = W_CachePatchName("R_RESBAR", PU_PATCH); // Results bars for players
patch_t *resbar = static_cast<patch_t*>(W_CachePatchName("R_RESBAR", PU_PATCH)); // Results bars for players
if (drawping || standings->rankingsmode != 0)
{
@ -550,7 +560,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
UINT8 *charcolormap = NULL;
if (standings->color[i] != SKINCOLOR_NONE)
{
charcolormap = R_GetTranslationColormap(standings->character[i], standings->color[i], GTC_CACHE);
charcolormap = R_GetTranslationColormap(standings->character[i], static_cast<skincolornum_t>(standings->color[i]), GTC_CACHE);
}
if (standings->isduel)
@ -558,8 +568,8 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
INT32 duelx = x + 22 + (datarightofcolumn ? inwardshim : -inwardshim);
INT32 duely = y - 80;
V_DrawScaledPatch(duelx, duely, 0, W_CachePatchName("DUELGRPH", PU_CACHE));
V_DrawScaledPatch(duelx + 8, duely + 9, V_TRANSLUCENT, W_CachePatchName("PREVBACK", PU_CACHE));
V_DrawScaledPatch(duelx, duely, 0, static_cast<patch_t*>(W_CachePatchName("DUELGRPH", PU_CACHE)));
V_DrawScaledPatch(duelx + 8, duely + 9, V_TRANSLUCENT, static_cast<patch_t*>(W_CachePatchName("PREVBACK", PU_CACHE)));
UINT8 spr2 = SPR2_STIN;
if (standings->pos[i] == 2)
@ -591,7 +601,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
if (j > splitscreen)
{
V_DrawScaledPatch(letterpos, duely, 0, W_CachePatchName(va("CHAR%s", (players[pnum].bot ? "CPU" : "EGGA")), PU_CACHE));
V_DrawScaledPatch(letterpos, duely, 0, static_cast<patch_t*>(W_CachePatchName(va("CHAR%s", (players[pnum].bot ? "CPU" : "EGGA")), PU_CACHE)));
}
else
{
@ -599,12 +609,12 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
UINT8 profilen = cv_lastprofile[j].value;
V_DrawScaledPatch(duelx, duely, 0, W_CachePatchName("FILEBACK", PU_CACHE));
V_DrawScaledPatch(duelx, duely, 0, static_cast<patch_t*>(W_CachePatchName("FILEBACK", PU_CACHE)));
if (datarightofcolumn && j == 0)
letterpos++; // A is one pixel thinner
V_DrawScaledPatch(letterpos, duely, 0, W_CachePatchName(va("CHARSEL%c", 'A' + j), PU_CACHE));
V_DrawScaledPatch(letterpos, duely, 0, static_cast<patch_t*>(W_CachePatchName(va("CHARSEL%c", 'A' + j), PU_CACHE)));
profile_t *pr = PR_GetProfile(profilen);
@ -628,13 +638,13 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
V_DrawMappedPatch(
x+14, y-5,
0,
W_CachePatchName("MINIDEAD", PU_CACHE),
R_GetTranslationColormap(TC_DEFAULT, standings->color[i], GTC_CACHE)
static_cast<patch_t*>(W_CachePatchName("MINIDEAD", PU_CACHE)),
R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(standings->color[i]), GTC_CACHE)
);
}
else
{
charcolormap = R_GetTranslationColormap(standings->character[i], standings->color[i], GTC_CACHE);
charcolormap = R_GetTranslationColormap(standings->character[i], static_cast<skincolornum_t>(standings->color[i]), GTC_CACHE);
V_DrawMappedPatch(x+14, y-5, 0, faceprefix[standings->character[i]][FACE_MINIMAP], charcolormap);
}
}
@ -742,27 +752,27 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
}
else if (standings->grade[pnum] != GRADE_INVALID)
{
patch_t *gradePtc = W_CachePatchName(va("R_INRNK%c", K_GetGradeChar(standings->grade[pnum])), PU_PATCH);
patch_t *gradePtc = static_cast<patch_t*>(W_CachePatchName(va("R_INRNK%c", K_GetGradeChar(static_cast<gp_rank_e>(standings->grade[pnum]))), PU_PATCH));
patch_t *gradeBG = NULL;
UINT16 gradeColor = SKINCOLOR_NONE;
UINT8 *gradeClm = NULL;
gradeColor = K_GetGradeColor(standings->grade[pnum]);
gradeColor = K_GetGradeColor(static_cast<gp_rank_e>(standings->grade[pnum]));
if (gradeColor != SKINCOLOR_NONE)
{
gradeClm = R_GetTranslationColormap(TC_DEFAULT, gradeColor, GTC_CACHE);
gradeClm = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(gradeColor), GTC_CACHE);
}
if (datarightofcolumn)
{
gradeBG = W_CachePatchName("R_INRNKR", PU_PATCH);
gradeBG = static_cast<patch_t*>(W_CachePatchName("R_INRNKR", PU_PATCH));
V_DrawMappedPatch(x + 118, y, 0, gradeBG, gradeClm);
V_DrawMappedPatch(x + 118 + 4, y - 1, 0, gradePtc, gradeClm);
}
else
{
gradeBG = W_CachePatchName("R_INRNKL", PU_PATCH);
gradeBG = static_cast<patch_t*>(W_CachePatchName("R_INRNKL", PU_PATCH));
V_DrawMappedPatch(x - 12, y, 0, gradeBG, gradeClm);
V_DrawMappedPatch(x - 12 + 3, y - 1, 0, gradePtc, gradeClm);
}
@ -828,27 +838,27 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
INT32 bufferspace = ((vid.width/vid.dupx) - BASEVIDWIDTH) / 2;
// Background pieces
patch_t *queuebg_flat = W_CachePatchName("R_RMBG1", PU_PATCH);
patch_t *queuebg_upwa = W_CachePatchName("R_RMBG2", PU_PATCH);
patch_t *queuebg_down = W_CachePatchName("R_RMBG3", PU_PATCH);
patch_t *queuebg_prize = W_CachePatchName("R_RMBG4", PU_PATCH);
patch_t *queuebg_flat = static_cast<patch_t*>(W_CachePatchName("R_RMBG1", PU_PATCH));
patch_t *queuebg_upwa = static_cast<patch_t*>(W_CachePatchName("R_RMBG2", PU_PATCH));
patch_t *queuebg_down = static_cast<patch_t*>(W_CachePatchName("R_RMBG3", PU_PATCH));
patch_t *queuebg_prize = static_cast<patch_t*>(W_CachePatchName("R_RMBG4", PU_PATCH));
// Progression lines
patch_t *line_upwa[BPP_MAX];
patch_t *line_down[BPP_MAX];
patch_t *line_flat[BPP_MAX];
line_upwa[BPP_AHEAD] = W_CachePatchName("R_RRMLN1", PU_PATCH);
line_upwa[BPP_DONE] = W_CachePatchName("R_RRMLN3", PU_PATCH);
line_upwa[BPP_SHADOW] = W_CachePatchName("R_RRMLS1", PU_PATCH);
line_upwa[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMLN1", PU_PATCH));
line_upwa[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMLN3", PU_PATCH));
line_upwa[BPP_SHADOW] = static_cast<patch_t*>(W_CachePatchName("R_RRMLS1", PU_PATCH));
line_down[BPP_AHEAD] = W_CachePatchName("R_RRMLN2", PU_PATCH);
line_down[BPP_DONE] = W_CachePatchName("R_RRMLN4", PU_PATCH);
line_down[BPP_SHADOW] = W_CachePatchName("R_RRMLS2", PU_PATCH);
line_down[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMLN2", PU_PATCH));
line_down[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMLN4", PU_PATCH));
line_down[BPP_SHADOW] = static_cast<patch_t*>(W_CachePatchName("R_RRMLS2", PU_PATCH));
line_flat[BPP_AHEAD] = W_CachePatchName("R_RRMLN5", PU_PATCH);
line_flat[BPP_DONE] = W_CachePatchName("R_RRMLN6", PU_PATCH);
line_flat[BPP_SHADOW] = W_CachePatchName("R_RRMLS3", PU_PATCH);
line_flat[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMLN5", PU_PATCH));
line_flat[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMLN6", PU_PATCH));
line_flat[BPP_SHADOW] = static_cast<patch_t*>(W_CachePatchName("R_RRMLS3", PU_PATCH));
// Progress markers
patch_t *level_dot[BPP_MAIN];
@ -856,17 +866,17 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
patch_t *capsu_dot[BPP_MAIN];
patch_t *prize_dot[BPP_MAIN];
level_dot[BPP_AHEAD] = W_CachePatchName("R_RRMRK2", PU_PATCH);
level_dot[BPP_DONE] = W_CachePatchName("R_RRMRK1", PU_PATCH);
level_dot[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK2", PU_PATCH));
level_dot[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK1", PU_PATCH));
bonus_dot[BPP_AHEAD] = W_CachePatchName("R_RRMRK7", PU_PATCH);
bonus_dot[BPP_DONE] = W_CachePatchName("R_RRMRK8", PU_PATCH);
bonus_dot[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK7", PU_PATCH));
bonus_dot[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK8", PU_PATCH));
capsu_dot[BPP_AHEAD] = W_CachePatchName("R_RRMRK3", PU_PATCH);
capsu_dot[BPP_DONE] = W_CachePatchName("R_RRMRK5", PU_PATCH);
capsu_dot[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK3", PU_PATCH));
capsu_dot[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK5", PU_PATCH));
prize_dot[BPP_AHEAD] = W_CachePatchName("R_RRMRK4", PU_PATCH);
prize_dot[BPP_DONE] = W_CachePatchName("R_RRMRK6", PU_PATCH);
prize_dot[BPP_AHEAD] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK4", PU_PATCH));
prize_dot[BPP_DONE] = static_cast<patch_t*>(W_CachePatchName("R_RRMRK6", PU_PATCH));
UINT8 *colormap = NULL, *oppositemap = NULL;
fixed_t playerx = 0, playery = 0;
@ -891,8 +901,8 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
pcolor = players[standings->mainplayer].skincolor;
}
colormap = R_GetTranslationColormap(TC_DEFAULT, pcolor, GTC_CACHE);
oppositemap = R_GetTranslationColormap(TC_DEFAULT, skincolors[pcolor].invcolor, GTC_CACHE);
colormap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(pcolor), GTC_CACHE);
oppositemap = R_GetTranslationColormap(TC_DEFAULT, static_cast<skincolornum_t>(skincolors[pcolor].invcolor), GTC_CACHE);
UINT8 workingqueuesize = roundqueue.size;
boolean upwa = false;
@ -924,7 +934,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
SINT8 deferxoffs = 0;
const INT32 desiredx2 = (290 + bufferspace);
spacetospecial = max(desiredx2 - widthofroundqueue - (24 - bufferspace), 16);
spacetospecial = std::max(desiredx2 - widthofroundqueue - (24 - bufferspace), 16);
if (roundqueue.position == roundqueue.size)
{
@ -1237,7 +1247,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
}
else
{
const fixed_t fillend = min((playerx / FRACUNIT) + 2, barend);
const fixed_t fillend = std::min((playerx / FRACUNIT) + 2, barend);
while (xiter < fillend)
{
@ -1353,8 +1363,8 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
if (playery != 0)
{
patch_t *rpmark[2];
rpmark[0] = W_CachePatchName("R_RPMARK", PU_PATCH);
rpmark[1] = W_CachePatchName("R_R2MARK", PU_PATCH);
rpmark[0] = static_cast<patch_t*>(W_CachePatchName("R_RPMARK", PU_PATCH));
rpmark[1] = static_cast<patch_t*>(W_CachePatchName("R_R2MARK", PU_PATCH));
// Change alignment
playerx -= (10 * FRACUNIT);
@ -1377,7 +1387,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
FRACUNIT,
baseflags,
faceprefix[pskin][FACE_RANK],
R_GetTranslationColormap(pskin, pcolor, GTC_CACHE)
R_GetTranslationColormap(pskin, static_cast<skincolornum_t>(pcolor), GTC_CACHE)
);
}
else
@ -1473,7 +1483,7 @@ void Y_DrawIntermissionHeader(fixed_t x, fixed_t y, boolean gotthrough, const ch
}
// Header bar
patch_t *rtpbr = W_CachePatchName((small ? "R_RTPB4" : "R_RTPBR"), PU_PATCH);
patch_t *rtpbr = static_cast<patch_t*>(W_CachePatchName((small ? "R_RTPB4" : "R_RTPBR"), PU_PATCH));
V_DrawFixedPatch((20 * frac) + x, (24 * frac) + y, FRACUNIT, small_flag, rtpbr, NULL);
fixed_t headerx, headery, headerwidth = 0;
@ -1517,7 +1527,7 @@ void Y_DrawIntermissionHeader(fixed_t x, fixed_t y, boolean gotthrough, const ch
if (gotthrough)
{
// GOT THROUGH ROUND
patch_t *gthro = W_CachePatchName((small ? "R_GTHR4" : "R_GTHRO"), PU_PATCH);
patch_t *gthro = static_cast<patch_t*>(W_CachePatchName((small ? "R_GTHR4" : "R_GTHRO"), PU_PATCH));
V_DrawFixedPatch((50 * frac) + x, (42 * frac) + y, FRACUNIT, small_flag, gthro, NULL);
}
@ -1594,15 +1604,15 @@ void Y_IntermissionDrawer(void)
fixed_t x;
// Checker scroll
patch_t *rbgchk = W_CachePatchName("R_RBGCHK", PU_PATCH);
patch_t *rbgchk = static_cast<patch_t*>(W_CachePatchName("R_RBGCHK", PU_PATCH));
// Scrolling marquee
patch_t *rrmq = W_CachePatchName("R_RRMQ", PU_PATCH);
patch_t *rrmq = static_cast<patch_t*>(W_CachePatchName("R_RRMQ", PU_PATCH));
fixed_t mqloop = SHORT(rrmq->width)*FRACUNIT;
fixed_t chkloop = SHORT(rbgchk->width)*FRACUNIT;
UINT8 *bgcolor = R_GetTranslationColormap(TC_INTERMISSION, 0, GTC_CACHE);
UINT8 *bgcolor = R_GetTranslationColormap(TC_INTERMISSION, static_cast<skincolornum_t>(0), GTC_CACHE);
// Draw the background
K_DrawMapThumbnail(0, 0, BASEVIDWIDTH<<FRACBITS, (data.encore ? V_FLIP : 0), prevmap, bgcolor);
@ -1681,11 +1691,11 @@ skiptallydrawer:
finalcounter:
{
if ((modeattacking == ATTACKING_NONE) && (demo.recording || demo.savemode == DSM_SAVED) && !demo.playback)
if ((modeattacking == ATTACKING_NONE) && (demo.recording || demo.savemode == demovars_s::DSM_SAVED) && !demo.playback)
{
switch (demo.savemode)
{
case DSM_NOTSAVING:
case demovars_s::DSM_NOTSAVING:
{
INT32 buttonx = BASEVIDWIDTH;
INT32 buttony = 2;
@ -1696,11 +1706,11 @@ finalcounter:
V_DrawRightAlignedThinString(buttonx - 2, buttony, highlightflags, "Save replay");
break;
}
case DSM_SAVED:
case demovars_s::DSM_SAVED:
V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, highlightflags, "Replay saved!");
break;
case DSM_TITLEENTRY:
case demovars_s::DSM_TITLEENTRY:
ST_DrawDemoTitleEntry();
break;
@ -1745,13 +1755,13 @@ void Y_Ticker(void)
if (demo.recording)
{
if (demo.savemode == DSM_NOTSAVING)
if (demo.savemode == demovars_s::DSM_NOTSAVING)
{
replayprompttic++;
G_CheckDemoTitleEntry();
}
if (demo.savemode == DSM_WILLSAVE || demo.savemode == DSM_WILLAUTOSAVE)
if (demo.savemode == demovars_s::DSM_WILLSAVE || demo.savemode == demovars_s::DSM_WILLAUTOSAVE)
G_SaveDemo();
}
@ -1983,7 +1993,7 @@ void Y_DetermineIntermissionType(void)
}
// set initially
intertype = gametypes[gametype]->intermission;
intertype = static_cast<intertype_t>(gametypes[gametype]->intermission);
// special cases
if (intertype == int_scoreortimeattack)
@ -2060,7 +2070,7 @@ void Y_StartIntermission(void)
else
{
// Minimum two seconds for match results, then two second slideover approx halfway through
sorttic = max((timer/2) - 2*TICRATE, 2*TICRATE);
sorttic = std::max((timer/2) - 2*TICRATE, 2*TICRATE);
}
// TODO: code's a mess, I'm just making it extra clear
@ -2147,8 +2157,8 @@ void Y_StartIntermission(void)
}
Automate_Run(AEV_INTERMISSIONSTART);
bgpatch = W_CachePatchName("MENUBG", PU_STATIC);
widebgpatch = W_CachePatchName("WEIRDRES", PU_STATIC);
bgpatch = static_cast<patch_t*>(W_CachePatchName("MENUBG", PU_STATIC));
widebgpatch = static_cast<patch_t*>(W_CachePatchName("WEIRDRES", PU_STATIC));
}
// ======