Rewrite replay standings extrainfo

This commit is contained in:
Eidolon 2024-02-20 18:23:38 -06:00
parent 995af69eab
commit 32715fbe66
3 changed files with 200 additions and 62 deletions

View file

@ -12,6 +12,10 @@
/// \brief Demo recording and playback
#include <algorithm>
#include <cstddef>
#include <tcb/span.hpp>
#include <nlohmann/json.hpp>
#include "doomdef.h"
#include "console.h"
@ -158,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
@ -2326,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)
@ -2510,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};
@ -2518,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;
@ -2670,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)

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

@ -196,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++)
@ -240,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))
@ -284,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.