- transitioned all JSON-based savegame code to FSerializer and removed sjson.

Now everything is in place to transition the rest of the savegame code as well.
This commit is contained in:
Christoph Oelckers 2020-02-23 14:03:03 +01:00
parent 1b6b43291b
commit 985e441d80
21 changed files with 212 additions and 2821 deletions

View file

@ -653,7 +653,6 @@ set (PCH_SOURCES
glbackend/hw_draw2d.cpp
thirdparty/src/base64.cpp
thirdparty/src/sjson.cpp
thirdparty/src/fix16.cpp
thirdparty/src/fix16_str.cpp
thirdparty/src/md4.cpp

View file

@ -61,6 +61,7 @@ void CompositeSavegameWriter::AddCompressedElement(const char* filename, FCompre
subbuffers.Push(buffer);
buffer = {};
subfiles.Push(nullptr);
isCompressed.Push(true);
}
FCompressedBuffer CompositeSavegameWriter::CompressElement(BufferWriter *bw, bool compress)

View file

@ -44,7 +44,6 @@
#include "v_draw.h"
#include "files.h"
#include "resourcefile.h"
#include "sjson.h"
#include "savegamehelp.h"
#include "i_specialpaths.h"
#include "../../platform/win32/i_findfile.h" // This is a temporary direct path. Needs to be fixed when stuff gets cleaned up.

View file

@ -44,7 +44,6 @@
#include "v_draw.h"
#include "files.h"
#include "resourcefile.h"
#include "sjson.h"
#include "cmdlib.h"
#include "files.h"
#include "savegamehelp.h"
@ -52,6 +51,7 @@
#include "c_dispatch.h"
#include "i_system.h"
#include "build.h"
#include "serializer.h"
FSavegameManager savegameManager;
@ -378,50 +378,49 @@ unsigned FSavegameManager::ExtractSaveData(int index)
// this should not happen because the file has already been verified.
return index;
}
auto fr = info->NewReader();
auto data = fr.ReadPadded(1);
fr.Close();
sjson_context* ctx = sjson_create_context(0, 0, NULL);
if (ctx)
void* data = info->Get();
FSerializer arc;
if (!arc.OpenReader((const char*)data, info->LumpSize))
{
sjson_node* root = sjson_decode(ctx, (const char*)data.Data());
return index;
}
FString comment, fcomment, ncomment, mtime;
FString comment = sjson_get_string(root, "Creation Time", "");
FString fcomment = sjson_get_string(root, "Map Label", "");
FString ncomment = sjson_get_string(root, "Map Name", "");
FString mtime = sjson_get_string(root, "Map Time", "");
comment.AppendFormat("\n%s - %s\n%s", fcomment.GetChars(), ncomment.GetChars(), mtime.GetChars());
SaveCommentString = comment;
arc("Creation Time", comment)
("Map Label", fcomment)
("Map Name", ncomment)
("Map Time", mtime);
// Extract pic (todo: let the renderer write a proper PNG file instead of a raw canvas dunp of the software renderer - and make it work for all games.)
FResourceLump *pic = resf->FindLump("savepic.png");
if (pic != nullptr)
comment.AppendFormat("\n%s - %s\n%s", fcomment.GetChars(), ncomment.GetChars(), mtime.GetChars());
SaveCommentString = comment;
FResourceLump *pic = resf->FindLump("savepic.png");
if (pic != nullptr)
{
FileReader picreader;
picreader.OpenMemoryArray([=](TArray<uint8_t> &array)
{
FileReader picreader;
picreader.OpenMemoryArray([=](TArray<uint8_t> &array)
auto cache = pic->Lock();
array.Resize(pic->LumpSize);
memcpy(&array[0], cache, pic->LumpSize);
pic->Unlock();
return true;
});
PNGHandle *png = M_VerifyPNG(picreader);
if (png != nullptr)
{
SavePic = PNGTexture_CreateFromFile(png, node->Filename);
delete png;
if (SavePic && SavePic->GetWidth() == 1 && SavePic->GetHeight() == 1)
{
auto cache = pic->Lock();
array.Resize(pic->LumpSize);
memcpy(&array[0], cache, pic->LumpSize);
pic->Unlock();
return true;
});
PNGHandle *png = M_VerifyPNG(picreader);
if (png != nullptr)
{
SavePic = PNGTexture_CreateFromFile(png, node->Filename);
delete png;
if (SavePic && SavePic->GetWidth() == 1 && SavePic->GetHeight() == 1)
{
delete SavePic;
SavePic = nullptr;
SavePicData.Clear();
}
delete SavePic;
SavePic = nullptr;
SavePicData.Clear();
}
}
sjson_destroy_context(ctx);
}
delete resf;
}

View file

@ -50,10 +50,9 @@
#include "c_dispatch.h"
#include "gamecontrol.h"
#include "filereadermusicinterface.h"
#include "savegamehelp.h"
#include "sjson.h"
#include "v_text.h"
#include "mapinfo.h"
#include "serializer.h"
MusPlayingInfo mus_playing;
MusicAliasMap MusicAliases;
@ -707,61 +706,27 @@ void Mus_SetPaused(bool on)
else S_ResumeMusic();
}
void MUS_Save()
void Mus_Serialize(FSerializer &arc)
{
FString music = mus_playing.name;
if (music.IsEmpty()) music = mus_playing.LastSong;
sjson_context* ctx = sjson_create_context(0, 0, NULL);
if (!ctx)
if (arc.BeginObject("music"))
{
return;
if (arc.isWriting())
{
FString music = mus_playing.name;
if (music.IsEmpty()) music = mus_playing.LastSong;
arc.AddString("music", music);
}
else arc("music", mus_playing.LastSong);
arc("baseorder", mus_playing.baseorder)
("loop", mus_playing.loop)
.EndObject();
// this is to prevent scripts from resetting the music after it has been loaded from the savegame.
if (arc.isReading()) mus_blocked = true;
// Actual music resuming cannot be performed here, it must be done in the game code.
}
sjson_node* root = sjson_mkobject(ctx);
sjson_put_string(ctx, root, "music", music);
sjson_put_int(ctx, root, "baseorder", mus_playing.baseorder);
sjson_put_bool(ctx, root, "loop", mus_playing.loop);
char* encoded = sjson_stringify(ctx, root, " ");
FileWriter* fil = WriteSavegameChunk("music.json");
if (!fil)
{
sjson_destroy_context(ctx);
return;
}
fil->Write(encoded, strlen(encoded));
sjson_free_string(ctx, encoded);
sjson_destroy_context(ctx);
}
bool MUS_Restore()
{
auto fil = ReadSavegameChunk("music.json");
if (!fil.isOpen())
{
return false;
}
auto text = fil.ReadPadded(1);
fil.Close();
if (text.Size() == 0)
{
return false;
}
sjson_context* ctx = sjson_create_context(0, 0, NULL);
sjson_node* root = sjson_decode(ctx, (const char*)text.Data());
mus_playing.LastSong = sjson_get_string(root, "music", "");
mus_playing.baseorder = sjson_get_int(root, "baseorder", 0);
mus_playing.loop = sjson_get_bool(root, "loop", true);
sjson_destroy_context(ctx);
mus_blocked = true; // this is to prevent scripts from resetting the music after it has been loaded from the savegame.
return true;
}
void Mus_ResumeSaved()

View file

@ -71,9 +71,6 @@ extern MusPlayingInfo mus_playing;
extern float relative_volume, saved_relative_volume;
void MUS_Save();
bool MUS_Restore();
// Note for later when the OPL player is ported.
// DN3D and related games use "d3dtimbr.tmb"

View file

@ -12,3 +12,5 @@ void Mus_Fade(double seconds);
void Mus_SetPaused(bool on);
void Mus_ResumeSaved();
FString G_SetupFilenameBasedMusic(const char* fileName, const char *defaultfn);
class FSerializer;
void Mus_Serialize(FSerializer& arc);

View file

@ -12,6 +12,8 @@ enum
MAXQUOTES = 16384,
};
class FSerializer;
class Quotes
{
FString quotes[MAXQUOTES];
@ -55,11 +57,9 @@ public:
}
void AppendQuote(int dst, int src, int len = -1);
void AppendExQuote(int dst, int src, int len = -1);
void FormatQuote(int dst, const char* fmt, ...);
void Substitute(int num, const char* text, const char* replc);
void ReadFromSavegame();
void WriteToSavegame();
void Serialize(FSerializer &arc);
};
extern Quotes quoteMgr;

View file

@ -37,7 +37,8 @@
#include "quotemgr.h"
#include "savegamehelp.h"
#include "sjson.h"
#include "serializer.h"
#include "printf.h"
void Quotes::MakeStringLabel(FString &quote)
@ -72,15 +73,6 @@ void Quotes::AppendQuote(int dst, int src, int len)
else quotes[dst] += FString(GStrings.localize(quotes[src]), len);
}
void Quotes::AppendExQuote(int dst, int src, int len)
{
// This needs to apply the localization because the combined string is not localizable anymore.
if (quotes[dst][0] == '$') quotes[dst] = GStrings.localize(quotes[dst]);
if (len < 0) quotes[dst] << GStrings.localize(exquotes[src]);
else quotes[dst] += FString(GStrings.localize(exquotes[src]), len);
}
void Quotes::FormatQuote(int dst, const char* fmt, ...)
{
va_list ap;
@ -95,88 +87,20 @@ void Quotes::Substitute(int dst, const char* text, const char* replc)
}
void Quotes::ReadFromSavegame()
void Quotes::Serialize(FSerializer &arc)
{
for (auto& q : quotes) q = "";
for (auto& q : exquotes) q = "";
auto fil = ReadSavegameChunk("quotes.json");
if (!fil.isOpen())
// This only saves the regular quotes. The ExQuotes array is immutable once initialized.
if (arc.BeginObject("quotes"))
{
return;
}
auto text = fil.ReadPadded(1);
fil.Close();
if (text.Size() == 0)
{
return;
}
sjson_context* ctx = sjson_create_context(0, 0, NULL);
sjson_node* root = sjson_decode(ctx, (const char*)text.Data());
auto qs = sjson_find_member(root, "quotes");
auto xs = sjson_find_member(root, "exquotes");
sjson_node* q;
sjson_foreach(q, qs)
{
int index = (int)strtoll(q->key, nullptr, 10);
quotes[index] = q->string_;
}
sjson_foreach(q, xs)
{
int index = (int)strtoll(q->key, nullptr, 10);
exquotes[index] = q->string_;
}
sjson_destroy_context(ctx);
}
void Quotes::WriteToSavegame()
{
sjson_context* ctx = sjson_create_context(0, 0, NULL);
if (!ctx)
{
return;
}
sjson_node* root = sjson_mkobject(ctx);
sjson_node* qs = sjson_mkobject(ctx);
sjson_node* xs = sjson_mkobject(ctx);
for (unsigned i = 0; i < MAXQUOTES; i++)
{
if (quotes[i].IsNotEmpty())
for (int i = 0; i < MAXQUOTES; i++)
{
char buff[10];
snprintf(buff, 10, "%d", i);
sjson_append_member(ctx, qs, buff, sjson_mkstring(ctx, quotes[i]));
}
if (exquotes[i].IsNotEmpty())
{
char buff[10];
snprintf(buff, 10, "%d", i);
sjson_append_member(ctx, xs, buff, sjson_mkstring(ctx, exquotes[i]));
char buf[10];
mysnprintf(buf, 10, "%d", i);
FString nulstr;
arc(buf, quotes[i], nulstr);
}
arc.EndObject();
}
sjson_append_member(ctx, root, "quotes", qs);
sjson_append_member(ctx, root, "exquotes", xs);
char* encoded = sjson_stringify(ctx, root, " ");
FileWriter* fil = WriteSavegameChunk("quotes.json");
if (!fil)
{
sjson_destroy_context(ctx);
return;
}
fil->Write(encoded, strlen(encoded));
sjson_free_string(ctx, encoded);
sjson_destroy_context(ctx);
return;
}
Quotes quoteMgr;

View file

@ -35,7 +35,6 @@
#include "compositesaveame.h"
#include "savegamehelp.h"
#include "sjson.h"
#include "baselayer.h"
#include "gstrings.h"
#include "i_specialpaths.h"
@ -43,7 +42,6 @@
#include "filesystem/filesystem.h"
#include "statistics.h"
#include "secrets.h"
#include "s_music.h"
#include "quotemgr.h"
#include "mapinfo.h"
#include "v_video.h"
@ -51,6 +49,7 @@
#include "m_argv.h"
#include "serializer.h"
#include "version.h"
#include "z_music.h"
static CompositeSavegameWriter savewriter;
static FResourceFile *savereader;
@ -59,6 +58,20 @@ void SaveEngineState();
CVAR(String, cl_savedir, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
//=============================================================================
//
//
//
//=============================================================================
static void SerializeSession(FSerializer& arc)
{
SerializeStatistics(arc);
SECRET_Serialize(arc);
Mus_Serialize(arc);
quoteMgr.Serialize(arc);
}
//=============================================================================
//
// This is for keeping my sanity while working with the horrible mess
@ -80,12 +93,21 @@ bool OpenSaveGameForRead(const char *name)
if (savereader != nullptr)
{
FResourceLump* info = savereader->FindLump("session.json");
if (info == nullptr)
{
return false;
}
FSerializer arc;
void* data = info->Get();
if (!arc.OpenReader((const char*)data, info->LumpSize))
{
return false;
}
// Load system-side data from savegames.
ReadStatistics();
SECRET_Load();
MUS_Restore();
quoteMgr.ReadFromSavegame();
SerializeSession(arc);
LoadEngineState();
auto file = ReadSavegameChunk("info.json");
@ -152,7 +174,6 @@ bool OpenSaveGameForWrite(const char* filename, const char *name)
FSerializer savegameengine; // saved play state.
savegameinfo.OpenWriter(true);
savegamesession.OpenWriter(save_formatted);
savegameengine.OpenWriter(save_formatted);
char buf[100];
@ -192,10 +213,11 @@ bool OpenSaveGameForWrite(const char* filename, const char *name)
// Handle system-side modules that need to persist data in savegames here, in a central place.
SaveStatistics();
SECRET_Save();
MUS_Save();
quoteMgr.WriteToSavegame();
savegamesession.OpenWriter(save_formatted);
SerializeSession(savegamesession);
buff = savegamesession.GetCompressedOutput();
AddCompressedSavegameChunk("session.json", buff);
SaveEngineState();
auto picfile = WriteSavegameChunk("savepic.png");
screen->WriteSavePic(picfile, 240, 180);
@ -270,84 +292,75 @@ static bool G_CheckSaveGameWads (const char *gamegrp, const char *mapgrp, bool p
int G_ValidateSavegame(FileReader &fr, FString *savetitle, bool formenu)
{
#if 0
FSerializer arc(nullptr);
if (!arc.OpenReader((const char*)data, info->LumpSize))
auto data = fr.Read();
FSerializer arc;
if (!arc.OpenReader((const char*)data.Data(), data.Size()))
{
LoadGameError("TXT_FAILEDTOREADSG");
return;
return -2;
}
#endif
auto data = fr.ReadPadded(1);
sjson_context* ctx = sjson_create_context(0, 0, NULL);
if (ctx)
int savever;
FString engine, gamegrp, mapgrp, title, filename;
arc("Save Version", savever)
("Engine", engine)
("Game Resource", gamegrp)
("Map Resource", mapgrp)
("Title", title)
("Map File", filename);
auto savesig = gi->GetSaveSig();
if (savetitle) *savetitle = title;
if (engine.Compare(savesig.savesig) != 0 || savever > savesig.currentsavever)
{
sjson_node* root = sjson_decode(ctx, (const char*)data.Data());
int savever = sjson_get_int(root, "Save Version", -1);
FString engine = sjson_get_string(root, "Engine", "");
FString gamegrp = sjson_get_string(root, "Game Resource", "");
FString mapgrp = sjson_get_string(root, "Map Resource", "");
FString title = sjson_get_string(root, "Title", "");
FString filename = sjson_get_string(root, "Map File", "");
auto savesig = gi->GetSaveSig();
sjson_destroy_context(ctx);
if (savetitle) *savetitle = title;
if (engine.Compare(savesig.savesig) != 0 || savever > savesig.currentsavever)
// different engine or newer version:
// not our business. Leave it alone.
return 0;
}
MapRecord *curLevel = nullptr;
if (strncmp(filename, "file://", 7) != 0)
{
for (auto& mr : mapList)
{
// different engine or newer version:
// not our business. Leave it alone.
if (mr.fileName.Compare(filename) == 0)
{
curLevel = &mr;
}
}
}
else
{
curLevel = &userMapRecord;
if (!formenu)
{
userMapRecord.name = "";
userMapRecord.SetFileName(filename);
}
}
if (!curLevel) return 0;
if (!formenu) currentLevel = curLevel;
if (savever < savesig.minsavever)
{
// old, incompatible savegame. List as not usable.
return -1;
}
else
{
auto ggfn = ExtractFileBase(fileSystem.GetResourceFileName(1), true);
if (gamegrp.CompareNoCase(ggfn) == 0)
{
return G_CheckSaveGameWads(gamegrp, mapgrp, false) ? 1 : -2;
}
else
{
// different game. Skip this.
return 0;
}
MapRecord *curLevel = nullptr;
if (strncmp(filename, "file://", 7) != 0)
{
for (auto& mr : mapList)
{
if (mr.fileName.Compare(filename) == 0)
{
curLevel = &mr;
}
}
}
else
{
curLevel = &userMapRecord;
if (!formenu)
{
userMapRecord.name = "";
userMapRecord.SetFileName(filename);
}
}
if (!curLevel) return 0;
if (!formenu) currentLevel = curLevel;
if (savever < savesig.minsavever)
{
// old, incompatible savegame. List as not usable.
return -1;
}
else
{
auto ggfn = ExtractFileBase(fileSystem.GetResourceFileName(1), true);
if (gamegrp.CompareNoCase(ggfn) == 0)
{
return G_CheckSaveGameWads(gamegrp, mapgrp, false) ? 1 : -2;
}
else
{
// different game. Skip this.
return 0;
}
}
}
return 0;
}

View file

@ -6,8 +6,7 @@
#include "c_cvars.h"
#include "v_font.h"
#include "v_draw.h"
#include "sjson.h"
#include "savegamehelp.h"
#include "serializer.h"
#include "mapinfo.h"
// Unlike in GZDoom we have to maintain this list here, because we got different game frontents that all store this info differently.
@ -109,54 +108,13 @@ CCMD(secret)
}
}
void SECRET_Save()
void SECRET_Serialize(FSerializer &arc)
{
sjson_context* ctx = sjson_create_context(0, 0, NULL);
if (!ctx)
if (arc.BeginObject("secrets"))
{
return;
arc("secrets", discovered_secrets)
.EndObject();
}
sjson_node* root = sjson_mkobject(ctx);
sjson_put_ints(ctx, root, "secrets", discovered_secrets.Data(), discovered_secrets.Size());
char* encoded = sjson_stringify(ctx, root, " ");
FileWriter* fil = WriteSavegameChunk("secrets.json");
if (!fil)
{
sjson_destroy_context(ctx);
return;
}
fil->Write(encoded, strlen(encoded));
sjson_free_string(ctx, encoded);
sjson_destroy_context(ctx);
}
bool SECRET_Load()
{
auto fil = ReadSavegameChunk("secrets.json");
if (!fil.isOpen())
{
return false;
}
auto text = fil.ReadPadded(1);
fil.Close();
if (text.Size() == 0)
{
return false;
}
sjson_context* ctx = sjson_create_context(0, 0, NULL);
sjson_node* root = sjson_decode(ctx, (const char*)text.Data());
discovered_secrets.Resize(1000); // Retarted interface alert
int realsize = sjson_get_ints(discovered_secrets.Data(), 1000, root, "secrets");
discovered_secrets.Resize(realsize);
sjson_destroy_context(ctx);
return true;
}
void SECRET_SetMapName(const char *filename, const char *_maptitle)

View file

@ -1,8 +1,8 @@
#pragma once
#include "files.h"
void SECRET_Save();
bool SECRET_Load();
class FSerializer;
void SECRET_Serialize(FSerializer &arc);
void SECRET_SetMapName(const char *filename, const char *maptitle);
void SECRET_Trigger(int num);

View file

@ -51,6 +51,8 @@
#include "printf.h"
#include "s_soundinternal.h"
bool save_full = false;
//==========================================================================
//
// This will double-encode already existing UTF-8 content.

View file

@ -50,6 +50,7 @@
#include "s_music.h"
#include "z_music.h"
#include "gamecvars.h"
#include "gamecontrol.h"
#include <zmusic.h>
EXTERN_CVAR (Float, snd_sfxvolume)
@ -70,8 +71,6 @@ CVAR(String, snd_backend, DEF_BACKEND, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR (Bool, snd_pitched, false, CVAR_ARCHIVE)
SoundRenderer *GSnd;
bool nosound;
bool nosfx;
void I_CloseSound ();
@ -248,12 +247,9 @@ public:
void I_InitSound ()
{
FModule_SetProgDir(progdir);
/* Get command line options: */
nosound = !!Args->CheckParm ("-nosound");
nosfx = !!Args->CheckParm ("-nosfx");
GSnd = NULL;
if (nosound)
if (userConfig.nosound)
{
GSnd = new NullSoundRenderer;
return;

View file

@ -159,8 +159,6 @@ public:
};
extern SoundRenderer *GSnd;
extern bool nosfx;
extern bool nosound;
void I_InitSound ();
void I_CloseSound();

View file

@ -43,6 +43,7 @@
#include "name.h"
#include "filesystem.h"
#include "cmdlib.h"
#include "gamecontrol.h"
enum
@ -382,7 +383,7 @@ FSoundChan *SoundEngine::StartSound(int type, const void *source,
FVector3 pos, vel;
FRolloffInfo *rolloff;
if (sound_id <= 0 || volume <= 0 || nosfx || nosound )
if (sound_id <= 0 || volume <= 0 || userConfig.nosound )
return NULL;
// prevent crashes.

View file

@ -45,8 +45,7 @@
#include "c_cvars.h"
#include "sc_man.h"
#include "baselayer.h"
#include "savegamehelp.h"
#include "sjson.h"
#include "serializer.h"
#include "gstrings.h"
#include "version.h"
@ -468,112 +467,31 @@ void STAT_Cancel()
//
//==========================================================================
void SaveOneLevel(sjson_context *ctx, sjson_node *lev, OneLevel& l)
FSerializer& Serialize(FSerializer& arc, const char* key, OneLevel& l, OneLevel* def)
{
sjson_put_int(ctx, lev, "totalkills", l.totalkills);
sjson_put_int(ctx, lev, "kills", l.killcount);
sjson_put_int(ctx, lev, "totalsecrets", l.totalsecrets);
sjson_put_int(ctx, lev, "secrets", l.secretcount);
sjson_put_int(ctx, lev, "leveltime", l.leveltime);
sjson_put_string(ctx, lev, "levelname", l.Levelname);
if (arc.BeginObject(key))
{
arc("totalkills", l.totalkills)
("killcount", l.killcount)
("totalsecrets", l.totalsecrets)
("secretcount", l.secretcount)
("leveltime", l.leveltime)
("levelname", l.Levelname)
.EndObject();
}
return arc;
}
void ReadOneLevel(sjson_node *lev, OneLevel& l)
void SerializeStatistics(FSerializer &arc)
{
l.totalkills = sjson_get_int(lev, "totalkills", 0);
l.killcount = sjson_get_int(lev, "kills", 0);
l.totalsecrets = sjson_get_int(lev, "totalsecrets", 0);
l.secretcount = sjson_get_int(lev, "secrets", 0);
l.leveltime = sjson_get_int(lev, "leveltime", 0);
l.Levelname = sjson_get_string(lev, "levelname", "");
}
void SaveStatistics()
{
sjson_context* ctx = sjson_create_context(0, 0, NULL);
if (!ctx)
if (arc.BeginObject("statistics"))
{
return;
arc("levelname", LevelName)
("episode", StartEpisode)
("skill", StartSkill)
("levels", LevelData)
.EndObject();
}
sjson_node* root = sjson_mkobject(ctx);
sjson_put_string(ctx, root, "levelname", LevelName);
sjson_put_string(ctx, root, "episode", StartEpisode);
sjson_put_int(ctx, root, "skill", StartSkill);
sjson_node* levels = sjson_mkarray(ctx);
for (auto& lev : LevelData)
{
sjson_node* levj = sjson_mkobject(ctx);
SaveOneLevel(ctx, levj, lev);
sjson_append_element(levels, levj);
}
sjson_append_member(ctx, root, "levels", levels);
char errmsg[256];
if (!sjson_check(root, errmsg))
{
buildprint(errmsg, "\n");
sjson_destroy_context(ctx);
return;
}
char* encoded = sjson_stringify(ctx, root, " ");
FileWriter* fil = WriteSavegameChunk("statistics.json");
if (!fil)
{
sjson_destroy_context(ctx);
return;
}
fil->Write(encoded, strlen(encoded));
sjson_free_string(ctx, encoded);
sjson_destroy_context(ctx);
return;
}
bool ReadStatistics()
{
auto fil = ReadSavegameChunk("statistics.json");
if (!fil.isOpen())
{
return false;
}
auto text = fil.ReadPadded(1);
fil.Close();
if (text.Size() == 0)
{
return false;
}
sjson_context* ctx = sjson_create_context(0, 0, NULL);
sjson_node* root = sjson_decode(ctx, (const char*)text.Data());
LevelName = sjson_get_string(root, "levelname", "");
StartEpisode = sjson_get_string(root, "episode", "");
StartSkill = sjson_get_int(root, "skill", -1);
sjson_node* levels = sjson_find_member(root, "levels");
if (LevelName.Len() == 0 || StartEpisode.Len() == 0 || StartSkill == -1 || levels == nullptr)
{
sjson_destroy_context(ctx);
return true; // do not error out on this.
}
int numlevels = sjson_child_count(levels);
LevelData.Resize(numlevels);
int i = 0;
for (auto& lev : LevelData)
{
ReadOneLevel(sjson_find_element(levels, i++), lev);
}
sjson_destroy_context(ctx);
return true;
}

View file

@ -6,6 +6,6 @@ void STAT_NewLevel(const char* mapname);
void STAT_Update(bool endofgame);
void STAT_Cancel();
class FSerializer;
void InitStatistics();
void SaveStatistics();
bool ReadStatistics();
void SerializeStatistics(FSerializer &);

View file

@ -66,17 +66,17 @@ const char *GetVersionString();
#define SAVESIG_SW GAMENAME ".ShadowWarrior"
#define SAVESIG_PS GAMENAME ".Exhumed"
#define MINSAVEVER_DN3D 3
#define MINSAVEVER_BLD 3
#define MINSAVEVER_RR 3
#define MINSAVEVER_SW 2
#define MINSAVEVER_PS 2
#define MINSAVEVER_DN3D 4
#define MINSAVEVER_BLD 4
#define MINSAVEVER_RR 4
#define MINSAVEVER_SW 3
#define MINSAVEVER_PS 3
#define SAVEVER_DN3D 3
#define SAVEVER_BLD 3
#define SAVEVER_RR 3
#define SAVEVER_SW 2
#define SAVEVER_PS 2
#define SAVEVER_DN3D 4
#define SAVEVER_BLD 4
#define SAVEVER_RR 4
#define SAVEVER_SW 3
#define SAVEVER_PS 3
#if defined(__APPLE__) || defined(_WIN32)
#define GAME_DIR GAMENAME

File diff suppressed because it is too large Load diff

View file

@ -1,5 +0,0 @@
#include "compat.h"
#define SJSON_IMPLEMENT
#include "sjson.h"