mirror of
https://github.com/DrBeef/Raze.git
synced 2024-11-15 08:52:00 +00:00
- added GZDoom's statistics code
Not hooked up yet.
This commit is contained in:
parent
0bdec4ea2e
commit
74ed8fd1d9
9 changed files with 788 additions and 0 deletions
|
@ -652,6 +652,7 @@ set( FASTMATH_SOURCES
|
|||
|
||||
# The rest is only here because it is C, not C++
|
||||
glad/src/glad.c
|
||||
common/utility/strnatcmp.c
|
||||
|
||||
# Another bit of cruft just to make S(hit)DL happy...
|
||||
sdlappicon.cpp
|
||||
|
@ -743,6 +744,7 @@ set (PCH_SOURCES
|
|||
common/initfs.cpp
|
||||
common/openaudio.cpp
|
||||
common/optionmenu/optionmenu.cpp
|
||||
common/statistics.cpp
|
||||
|
||||
common/2d/v_2ddrawer.cpp
|
||||
common/2d/v_draw.cpp
|
||||
|
|
|
@ -161,6 +161,13 @@ void wm_setapptitle(const char *name);
|
|||
|
||||
#include "print.h"
|
||||
|
||||
struct GameStats
|
||||
{
|
||||
int kill, tkill;
|
||||
int secret, tsecret;
|
||||
int timesecnd;
|
||||
};
|
||||
|
||||
struct GameInterface
|
||||
{
|
||||
virtual ~GameInterface() {}
|
||||
|
@ -171,6 +178,7 @@ struct GameInterface
|
|||
virtual void set_hud_scale(int size) = 0;
|
||||
virtual bool mouseInactiveConditional(bool condition) { return condition; }
|
||||
virtual FString statFPS() { return "FPS display not available"; }
|
||||
virtual GameStats getStats() { return {}; }
|
||||
};
|
||||
|
||||
extern GameInterface* gi;
|
||||
|
|
545
source/common/statistics.cpp
Normal file
545
source/common/statistics.cpp
Normal file
|
@ -0,0 +1,545 @@
|
|||
/*
|
||||
**
|
||||
** statistics.cpp
|
||||
** Save game statistics to a file
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2010 Christoph Oelckers
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions
|
||||
** are met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** 3. The name of the author may not be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "strnatcmp.h"
|
||||
#include "c_dispatch.h"
|
||||
#include "m_png.h"
|
||||
#include "filesystem.h"
|
||||
#include "cmdlib.h"
|
||||
#include "stats.h"
|
||||
#include "c_cvars.h"
|
||||
#include "sc_man.h"
|
||||
#include "baselayer.h"
|
||||
|
||||
CVAR(Int, savestatistics, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||||
CVAR(String, statfile, "demolitionstat.txt", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Global statistics data
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
// This struct is used to track statistics data in game
|
||||
struct OneLevel
|
||||
{
|
||||
int totalkills = 0, killcount = 0;
|
||||
int totalsecrets = 0, secretcount = 0;
|
||||
int leveltime = 0;
|
||||
FString Levelname;
|
||||
};
|
||||
|
||||
// Current game's statistics
|
||||
static TArray<OneLevel> LevelData;
|
||||
static char StartEpisode[MAX_PATH];
|
||||
static int StartSkill;
|
||||
static char LevelName[MAX_PATH];
|
||||
|
||||
// The statistics for one level
|
||||
struct FLevelStatistics
|
||||
{
|
||||
char info[60];
|
||||
short skill;
|
||||
short playerclass;
|
||||
char name[24];
|
||||
int timeneeded;
|
||||
};
|
||||
|
||||
// Statistics for one episode playthrough
|
||||
struct FSessionStatistics : public FLevelStatistics
|
||||
{
|
||||
TArray<FLevelStatistics> levelstats;
|
||||
};
|
||||
|
||||
// Collected statistics for one episode
|
||||
struct FStatistics
|
||||
{
|
||||
TArray<FSessionStatistics> stats;
|
||||
FString epi_name;
|
||||
FString epi_header;
|
||||
};
|
||||
|
||||
// All statistics ever collected
|
||||
static TArray<FStatistics> EpisodeStatistics;
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Initializes statistics data from external file
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static void ParseStatistics(const char *fn, TArray<FStatistics> &statlist)
|
||||
{
|
||||
statlist.Clear();
|
||||
try
|
||||
{
|
||||
FScanner sc;
|
||||
sc.OpenFile(fn);
|
||||
|
||||
while (sc.GetString())
|
||||
{
|
||||
FStatistics &ep_entry = statlist[statlist.Reserve(1)];
|
||||
|
||||
ep_entry.epi_header = sc.String;
|
||||
sc.MustGetString();
|
||||
ep_entry.epi_name = sc.String;
|
||||
|
||||
sc.MustGetStringName("{");
|
||||
while (!sc.CheckString("}"))
|
||||
{
|
||||
FSessionStatistics &session = ep_entry.stats[ep_entry.stats.Reserve(1)];
|
||||
|
||||
sc.MustGetString();
|
||||
sc.MustGetString();
|
||||
strncpy(session.name, sc.String, 24);
|
||||
sc.MustGetString();
|
||||
strncpy(session.info, sc.String, 60);
|
||||
|
||||
int h,m,s;
|
||||
sc.MustGetString();
|
||||
sscanf(sc.String, "%d:%d:%d", &h, &m, &s);
|
||||
session.timeneeded= ((((h*60)+m)*60)+s);
|
||||
|
||||
sc.MustGetNumber();
|
||||
session.skill=sc.Number;
|
||||
if (sc.CheckString("{"))
|
||||
{
|
||||
while (!sc.CheckString("}"))
|
||||
{
|
||||
FLevelStatistics &lstats = session.levelstats[session.levelstats.Reserve(1)];
|
||||
|
||||
sc.MustGetString();
|
||||
strncpy(lstats.name, sc.String, 24);
|
||||
sc.MustGetString();
|
||||
strncpy(lstats.info, sc.String, 60);
|
||||
|
||||
int h,m,s;
|
||||
sc.MustGetString();
|
||||
sscanf(sc.String, "%d:%d:%d", &h, &m, &s);
|
||||
lstats.timeneeded= ((((h*60)+m)*60)+s);
|
||||
|
||||
lstats.skill = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(std::runtime_error &)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// Reads the statistics file
|
||||
//
|
||||
// ====================================================================
|
||||
|
||||
void ReadStatistics()
|
||||
{
|
||||
ParseStatistics(statfile, EpisodeStatistics);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// Saves the statistics file
|
||||
// Sorting helpers.
|
||||
//
|
||||
// ====================================================================
|
||||
|
||||
int compare_episode_names(const void *a, const void *b)
|
||||
{
|
||||
FStatistics *A = (FStatistics*)a;
|
||||
FStatistics *B = (FStatistics*)b;
|
||||
|
||||
return strnatcasecmp(A->epi_header, B->epi_header);
|
||||
}
|
||||
|
||||
int compare_level_names(const void *a, const void *b)
|
||||
{
|
||||
FLevelStatistics *A = (FLevelStatistics*)a;
|
||||
FLevelStatistics *B = (FLevelStatistics*)b;
|
||||
|
||||
return strnatcasecmp(A->name, B->name);
|
||||
}
|
||||
|
||||
int compare_dates(const void *a, const void *b)
|
||||
{
|
||||
FLevelStatistics *A = (FLevelStatistics*)a;
|
||||
FLevelStatistics *B = (FLevelStatistics*)b;
|
||||
char *p;
|
||||
|
||||
int aday = strtol(A->name, &p, 10);
|
||||
int amonth = strtol(p+1, &p, 10);
|
||||
int ayear = strtol(p+1, &p, 10);
|
||||
int av = aday + 100 * amonth + 2000*ayear;
|
||||
|
||||
int bday = strtol(B->name, &p, 10);
|
||||
int bmonth = strtol(p+1, &p, 10);
|
||||
int byear = strtol(p+1, &p, 10);
|
||||
int bv = bday + 100 * bmonth + 2000*byear;
|
||||
|
||||
return av-bv;
|
||||
}
|
||||
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// Main save routine
|
||||
//
|
||||
// ====================================================================
|
||||
|
||||
inline int hours(int v) { return v / (60*60); }
|
||||
inline int minutes(int v) { return (v % (60*60)) / (60); }
|
||||
inline int seconds(int v) { return (v % (60)); }
|
||||
|
||||
static void SaveStatistics(const char *fn, TArray<FStatistics> &statlist)
|
||||
{
|
||||
unsigned int j;
|
||||
|
||||
FileWriter *fw = FileWriter::Open(fn);
|
||||
if (fw == nullptr) return;
|
||||
|
||||
qsort(&statlist[0], statlist.Size(), sizeof(statlist[0]), compare_episode_names);
|
||||
for(unsigned i=0;i<statlist.Size ();i++)
|
||||
{
|
||||
FStatistics &ep_stats = statlist[i];
|
||||
|
||||
qsort(&ep_stats.stats[0], ep_stats.stats.Size(), sizeof(ep_stats.stats[0]), compare_dates);
|
||||
|
||||
fw->Printf("%s \"%s\"\n{\n", ep_stats.epi_header.GetChars(), ep_stats.epi_name.GetChars());
|
||||
for(j=0;j<ep_stats.stats.Size();j++)
|
||||
{
|
||||
FSessionStatistics *sst = &ep_stats.stats[j];
|
||||
if (sst->info[0]>0)
|
||||
{
|
||||
fw->Printf("\t%2i. %10s \"%-33s\" %02d:%02d:%02d %i\n", j+1, sst->name, sst->info,
|
||||
hours(sst->timeneeded), minutes(sst->timeneeded), seconds(sst->timeneeded), sst->skill);
|
||||
|
||||
TArray<FLevelStatistics> &ls = sst->levelstats;
|
||||
if (ls.Size() > 0)
|
||||
{
|
||||
fw->Printf("\t{\n");
|
||||
|
||||
qsort(&ls[0], ls.Size(), sizeof(ls[0]), compare_level_names);
|
||||
|
||||
for(unsigned k=0;k<ls.Size ();k++)
|
||||
{
|
||||
fw->Printf("\t\t%-8s \"%-33s\" %02d:%02d:%02d\n", ls[k].name, ls[k].info,
|
||||
hours(ls[k].timeneeded), minutes(ls[k].timeneeded), seconds(ls[k].timeneeded));
|
||||
}
|
||||
fw->Printf("\t}\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
fw->Printf("}\n\n");
|
||||
}
|
||||
delete fw;
|
||||
}
|
||||
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// Gets list for current episode
|
||||
//
|
||||
// ====================================================================
|
||||
static FStatistics *GetStatisticsList(TArray<FStatistics> &statlist, const char *section, const char *fullname)
|
||||
{
|
||||
for(unsigned int i=0;i<statlist.Size();i++)
|
||||
{
|
||||
if (!stricmp(section, statlist[i].epi_header))
|
||||
{
|
||||
return &statlist[i];
|
||||
}
|
||||
}
|
||||
FStatistics * stats = &statlist[statlist.Reserve(1)];
|
||||
stats->epi_header = section;
|
||||
stats->epi_name = fullname;
|
||||
return stats;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// Adds a statistics entry
|
||||
//
|
||||
// ====================================================================
|
||||
static FSessionStatistics *StatisticsEntry(FStatistics *stats, const char *text, int playtime)
|
||||
{
|
||||
FSessionStatistics s;
|
||||
time_t clock;
|
||||
struct tm *lt;
|
||||
|
||||
time (&clock);
|
||||
lt = localtime (&clock);
|
||||
|
||||
if (lt != NULL)
|
||||
snprintf(s.name, countof(s.name), "%02d.%02d.%04d",lt->tm_mday, lt->tm_mon+1, lt->tm_year+1900);
|
||||
else
|
||||
strcpy(s.name,"00.00.0000");
|
||||
|
||||
s.skill=StartSkill;
|
||||
strcpy(s.info, text);
|
||||
s.timeneeded=playtime;
|
||||
|
||||
stats->stats.Push(s);
|
||||
return &stats->stats[stats->stats.Size()-1];
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// Adds a statistics entry
|
||||
//
|
||||
// ====================================================================
|
||||
static void LevelStatEntry(FSessionStatistics *es, const char *level, const char *text, int playtime)
|
||||
{
|
||||
FLevelStatistics s;
|
||||
time_t clock;
|
||||
struct tm *lt;
|
||||
|
||||
time (&clock);
|
||||
lt = localtime (&clock);
|
||||
|
||||
strcpy(s.name, level);
|
||||
strcpy(s.info, text);
|
||||
s.timeneeded=playtime;
|
||||
es->levelstats.Push(s);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// STAT_StartNewGame: called when a new game starts. Sets the current episode
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void STAT_StartNewGame(const char *episode, int skill)
|
||||
{
|
||||
strncpy(StartEpisode, episode, MAX_PATH);
|
||||
StartSkill = skill;
|
||||
LevelData.Clear();
|
||||
}
|
||||
|
||||
void STAT_NewLevel(const char* mapname)
|
||||
{
|
||||
strncpy(LevelName, mapname, MAX_PATH);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Store the current level's statistics
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static void StoreLevelStats()
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for(i=0;i<LevelData.Size();i++)
|
||||
{
|
||||
if (!LevelData[i].Levelname.CompareNoCase(LevelName)) break;
|
||||
}
|
||||
if (i==LevelData.Size())
|
||||
{
|
||||
LevelData.Reserve(1);
|
||||
LevelData[i].Levelname = LevelName; // should never happen
|
||||
}
|
||||
auto stat = gi->getStats();
|
||||
LevelData[i].totalkills = stat.tkill;
|
||||
LevelData[i].killcount = stat.kill;
|
||||
LevelData[i].totalsecrets = stat.tsecret;
|
||||
LevelData[i].secretcount = stat.secret;
|
||||
LevelData[i].leveltime = stat.timesecnd;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// STAT_ChangeLevel: called when the level changes or the current statistics are
|
||||
// requested
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void STAT_Update(bool endofgame)
|
||||
{
|
||||
const char* fn = "?";
|
||||
// record the current level's stats.
|
||||
StoreLevelStats();
|
||||
|
||||
if (savestatistics == 1 && endofgame)
|
||||
{
|
||||
if (LevelData.Size() == 0)
|
||||
{
|
||||
auto lump = fileSystem.FindFile(LevelName);
|
||||
if (lump >= 0)
|
||||
{
|
||||
int file = fileSystem.GetFileContainer(lump);
|
||||
fn = fileSystem.GetResourceFileName(file);
|
||||
}
|
||||
}
|
||||
FString section = ExtractFileBase(fn) + "." + LevelData[0].Levelname;
|
||||
section.ToUpper();
|
||||
FStatistics* sl = GetStatisticsList(EpisodeStatistics, section, StartEpisode);
|
||||
|
||||
int statvals[] = { 0,0,0,0, 0 };
|
||||
FString infostring;
|
||||
int validlevels = LevelData.Size();
|
||||
for (unsigned i = 0; i < LevelData.Size(); i++)
|
||||
{
|
||||
statvals[0] += LevelData[i].killcount;
|
||||
statvals[1] += LevelData[i].totalkills;
|
||||
statvals[2] += LevelData[i].secretcount;
|
||||
statvals[3] += LevelData[i].totalsecrets;
|
||||
statvals[4] += LevelData[i].leveltime;
|
||||
}
|
||||
|
||||
infostring.Format("%4d/%4d, %3d/%3d, %2d", statvals[0], statvals[1], statvals[2], statvals[3], validlevels);
|
||||
FSessionStatistics* es = StatisticsEntry(sl, infostring, statvals[4]);
|
||||
|
||||
for (unsigned i = 0; i < LevelData.Size(); i++)
|
||||
{
|
||||
FString lsection = LevelData[i].Levelname;
|
||||
lsection.ToUpper();
|
||||
infostring.Format("%4d/%4d, %3d/%3d", LevelData[i].killcount, LevelData[i].totalkills, LevelData[i].secretcount, LevelData[i].totalsecrets);
|
||||
LevelStatEntry(es, lsection, infostring, LevelData[i].leveltime);
|
||||
}
|
||||
SaveStatistics(statfile, EpisodeStatistics);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// saves statistics info to savegames
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void SaveOneLevel(FileWriter& fil, OneLevel& l)
|
||||
{
|
||||
fil.Write(&l.totalkills, 4);
|
||||
fil.Write(&l.killcount, 4);
|
||||
fil.Write(&l.totalsecrets, 4);
|
||||
fil.Write(&l.secretcount, 4);
|
||||
fil.Write(&l.leveltime, 4);
|
||||
uint8_t siz = l.Levelname.Len();
|
||||
fil.Write(&siz, 1);
|
||||
fil.Write(l.Levelname.GetChars(), siz);
|
||||
}
|
||||
|
||||
void ReadOneLevel(FileReader& fil, OneLevel& l)
|
||||
{
|
||||
fil.Read(&l.totalkills, 4);
|
||||
fil.Read(&l.killcount, 4);
|
||||
fil.Read(&l.totalsecrets, 4);
|
||||
fil.Read(&l.secretcount, 4);
|
||||
fil.Read(&l.leveltime, 4);
|
||||
uint8_t siz;
|
||||
fil.Read(&siz, 1);
|
||||
auto p = l.Levelname.LockNewBuffer(siz);
|
||||
fil.Read(p, siz);
|
||||
l.Levelname.UnlockBuffer();
|
||||
}
|
||||
|
||||
void SaveStatistics(FileWriter& fil)
|
||||
{
|
||||
fil.Write("STAT", 4);
|
||||
fil.Write(LevelName, MAX_PATH);
|
||||
fil.Write(&StartEpisode, MAX_PATH);
|
||||
fil.Write(&StartSkill, 4);
|
||||
int p = LevelData.Size();
|
||||
fil.Write(&p, 4);
|
||||
for (auto& lev : LevelData)
|
||||
{
|
||||
SaveOneLevel(fil, lev);
|
||||
}
|
||||
fil.Write("TATS", 4);
|
||||
}
|
||||
|
||||
bool ReadStatistics(FileReader& fil)
|
||||
{
|
||||
char id[4];
|
||||
|
||||
fil.Read(id, 4);
|
||||
if (memcmp(id, "STAT", 4)) return false;
|
||||
fil.Read(LevelName, MAX_PATH);
|
||||
fil.Read(&StartEpisode, MAX_PATH);
|
||||
fil.Read(&StartSkill, 4);
|
||||
int p;
|
||||
fil.Read(&p, 4);
|
||||
LevelData.Resize(p);
|
||||
for (auto& lev : LevelData)
|
||||
{
|
||||
ReadOneLevel(fil, lev);
|
||||
}
|
||||
fil.Read(id, 4);
|
||||
if (memcmp(id, "TATS", 4)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// show statistics
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FString GetStatString()
|
||||
{
|
||||
FString compose;
|
||||
for(unsigned i = 0; i < LevelData.Size(); i++)
|
||||
{
|
||||
OneLevel *l = &LevelData[i];
|
||||
compose.AppendFormat("Level %s - Kills: %d/%d - Secrets: %d/%d - Time: %d:%02d\n",
|
||||
l->Levelname.GetChars(), l->killcount, l->totalkills, l->secretcount, l->totalsecrets,
|
||||
l->leveltime/(60), (l->leveltime)%60);
|
||||
}
|
||||
return compose;
|
||||
}
|
||||
|
||||
CCMD(printstats)
|
||||
{
|
||||
StoreLevelStats(); // Refresh the current level's results.
|
||||
Printf("%s", GetStatString().GetChars());
|
||||
}
|
||||
|
||||
ADD_STAT(statistics)
|
||||
{
|
||||
StoreLevelStats(); // Refresh the current level's results.
|
||||
return GetStatString();
|
||||
}
|
||||
|
176
source/common/utility/strnatcmp.c
Normal file
176
source/common/utility/strnatcmp.c
Normal file
|
@ -0,0 +1,176 @@
|
|||
/* -*- mode: c; c-file-style: "k&r" -*-
|
||||
|
||||
strnatcmp.c -- Perform 'natural order' comparisons of strings in C.
|
||||
Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
|
||||
/* partial change history:
|
||||
*
|
||||
* 2004-10-10 mbp: Lift out character type dependencies into macros.
|
||||
*
|
||||
* Eric Sosman pointed out that ctype functions take a parameter whose
|
||||
* value must be that of an unsigned int, even on platforms that have
|
||||
* negative chars in their default char type.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "strnatcmp.h"
|
||||
|
||||
|
||||
/* These are defined as macros to make it easier to adapt this code to
|
||||
* different characters types or comparison functions. */
|
||||
static __inline int
|
||||
nat_isdigit(nat_char a)
|
||||
{
|
||||
return isdigit((unsigned char) a);
|
||||
}
|
||||
|
||||
|
||||
static __inline int
|
||||
nat_isspace(nat_char a)
|
||||
{
|
||||
return isspace((unsigned char) a);
|
||||
}
|
||||
|
||||
|
||||
static __inline nat_char
|
||||
nat_toupper(nat_char a)
|
||||
{
|
||||
return toupper((unsigned char) a);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int
|
||||
compare_right(nat_char const *a, nat_char const *b)
|
||||
{
|
||||
int bias = 0;
|
||||
|
||||
/* The longest run of digits wins. That aside, the greatest
|
||||
value wins, but we can't know that it will until we've scanned
|
||||
both numbers to know that they have the same magnitude, so we
|
||||
remember it in BIAS. */
|
||||
for (;; a++, b++) {
|
||||
if (!nat_isdigit(*a) && !nat_isdigit(*b))
|
||||
return bias;
|
||||
else if (!nat_isdigit(*a))
|
||||
return -1;
|
||||
else if (!nat_isdigit(*b))
|
||||
return +1;
|
||||
else if (*a < *b) {
|
||||
if (!bias)
|
||||
bias = -1;
|
||||
} else if (*a > *b) {
|
||||
if (!bias)
|
||||
bias = +1;
|
||||
} else if (!*a && !*b)
|
||||
return bias;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
compare_left(nat_char const *a, nat_char const *b)
|
||||
{
|
||||
/* Compare two left-aligned numbers: the first to have a
|
||||
different value wins. */
|
||||
for (;; a++, b++) {
|
||||
if (!nat_isdigit(*a) && !nat_isdigit(*b))
|
||||
return 0;
|
||||
else if (!nat_isdigit(*a))
|
||||
return -1;
|
||||
else if (!nat_isdigit(*b))
|
||||
return +1;
|
||||
else if (*a < *b)
|
||||
return -1;
|
||||
else if (*a > *b)
|
||||
return +1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int strnatcmp0(nat_char const *a, nat_char const *b, int fold_case)
|
||||
{
|
||||
int ai, bi;
|
||||
nat_char ca, cb;
|
||||
int fractional, result;
|
||||
|
||||
assert(a && b);
|
||||
ai = bi = 0;
|
||||
while (1) {
|
||||
ca = a[ai]; cb = b[bi];
|
||||
|
||||
/* skip over leading spaces or zeros */
|
||||
while (nat_isspace(ca))
|
||||
ca = a[++ai];
|
||||
|
||||
while (nat_isspace(cb))
|
||||
cb = b[++bi];
|
||||
|
||||
/* process run of digits */
|
||||
if (nat_isdigit(ca) && nat_isdigit(cb)) {
|
||||
fractional = (ca == '0' || cb == '0');
|
||||
|
||||
if (fractional) {
|
||||
if ((result = compare_left(a+ai, b+bi)) != 0)
|
||||
return result;
|
||||
} else {
|
||||
if ((result = compare_right(a+ai, b+bi)) != 0)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ca && !cb) {
|
||||
/* The strings compare the same. Perhaps the caller
|
||||
will want to call strcmp to break the tie. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fold_case) {
|
||||
ca = nat_toupper(ca);
|
||||
cb = nat_toupper(cb);
|
||||
}
|
||||
|
||||
if (ca < cb)
|
||||
return -1;
|
||||
else if (ca > cb)
|
||||
return +1;
|
||||
|
||||
++ai; ++bi;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int strnatcmp(nat_char const *a, nat_char const *b) {
|
||||
return strnatcmp0(a, b, 0);
|
||||
}
|
||||
|
||||
|
||||
/* Compare, recognizing numeric string and ignoring case. */
|
||||
int strnatcasecmp(nat_char const *a, nat_char const *b) {
|
||||
return strnatcmp0(a, b, 1);
|
||||
}
|
39
source/common/utility/strnatcmp.h
Normal file
39
source/common/utility/strnatcmp.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/* -*- mode: c; c-file-style: "k&r" -*-
|
||||
|
||||
strnatcmp.c -- Perform 'natural order' comparisons of strings in C.
|
||||
Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/* CUSTOMIZATION SECTION
|
||||
*
|
||||
* You can change this typedef, but must then also change the inline
|
||||
* functions in strnatcmp.c */
|
||||
typedef char nat_char;
|
||||
|
||||
int strnatcmp(nat_char const *a, nat_char const *b);
|
||||
int strnatcasecmp(nat_char const *a, nat_char const *b);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -153,6 +153,7 @@ struct GameInterface : ::GameInterface
|
|||
void set_hud_scale(int size) override;
|
||||
bool mouseInactiveConditional(bool condition) override;
|
||||
FString statFPS() override;
|
||||
GameStats getStats() override;
|
||||
};
|
||||
|
||||
END_DUKE_NS
|
||||
|
|
|
@ -1180,6 +1180,15 @@ void G_DisplayRest(int32_t smoothratio)
|
|||
VM_OnEvent(EVENT_DISPLAYEND, g_player[screenpeek].ps->i, screenpeek);
|
||||
}
|
||||
|
||||
GameStats GameInterface::getStats()
|
||||
{
|
||||
GameStats stats;
|
||||
DukePlayer_t* p = g_player[myconnectindex].ps;
|
||||
return { p->actors_killed, p->max_actors_killed, p->secret_rooms, p->max_secret_rooms, p->player_par / REALGAMETICSPERSEC };
|
||||
}
|
||||
|
||||
|
||||
|
||||
void G_FadePalette(int32_t r, int32_t g, int32_t b, int32_t e)
|
||||
{
|
||||
if (ud.screenfade == 0)
|
||||
|
|
|
@ -157,6 +157,7 @@ struct GameInterface : ::GameInterface
|
|||
void set_hud_scale(int size) override;
|
||||
bool mouseInactiveConditional(bool condition) override;
|
||||
FString statFPS() override;
|
||||
GameStats getStats() override;
|
||||
};
|
||||
|
||||
END_RR_NS
|
||||
|
|
|
@ -751,6 +751,13 @@ FString GameInterface::statFPS()
|
|||
return output;
|
||||
}
|
||||
|
||||
GameStats GameInterface::getStats()
|
||||
{
|
||||
GameStats stats;
|
||||
DukePlayer_t* p = g_player[myconnectindex].ps;
|
||||
return { p->actors_killed, p->max_actors_killed, p->secret_rooms, p->max_secret_rooms, p->player_par / REALGAMETICSPERSEC };
|
||||
}
|
||||
|
||||
#undef FPS_COLOR
|
||||
|
||||
void G_DisplayRest(int32_t smoothratio)
|
||||
|
|
Loading…
Reference in a new issue