mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-14 16:40:56 +00:00
- resurrected some old statistics code I had and made some minor enhancements to be of more use.
SVN r2821 (trunk)
This commit is contained in:
parent
051ae3224f
commit
54bdf38fef
10 changed files with 853 additions and 15 deletions
|
@ -728,8 +728,10 @@ add_executable( zdoom WIN32
|
||||||
s_sound.cpp
|
s_sound.cpp
|
||||||
sc_man.cpp
|
sc_man.cpp
|
||||||
st_stuff.cpp
|
st_stuff.cpp
|
||||||
|
statistics.cpp
|
||||||
stats.cpp
|
stats.cpp
|
||||||
stringtable.cpp
|
stringtable.cpp
|
||||||
|
strnatcmp.c
|
||||||
tables.cpp
|
tables.cpp
|
||||||
teaminfo.cpp
|
teaminfo.cpp
|
||||||
tempfiles.cpp
|
tempfiles.cpp
|
||||||
|
|
|
@ -116,6 +116,7 @@ void DrawHUD();
|
||||||
|
|
||||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||||
|
|
||||||
|
extern void ReadStatistics();
|
||||||
extern void M_RestoreMode ();
|
extern void M_RestoreMode ();
|
||||||
extern void M_SetDefaultMode ();
|
extern void M_SetDefaultMode ();
|
||||||
extern void R_ExecuteSetViewSize ();
|
extern void R_ExecuteSetViewSize ();
|
||||||
|
@ -2073,6 +2074,7 @@ void D_DoomMain (void)
|
||||||
// [RH] Parse through all loaded mapinfo lumps
|
// [RH] Parse through all loaded mapinfo lumps
|
||||||
Printf ("G_ParseMapInfo: Load map definitions.\n");
|
Printf ("G_ParseMapInfo: Load map definitions.\n");
|
||||||
G_ParseMapInfo (iwad_info->MapInfo);
|
G_ParseMapInfo (iwad_info->MapInfo);
|
||||||
|
ReadStatistics();
|
||||||
|
|
||||||
// [RH] Parse any SNDINFO lumps
|
// [RH] Parse any SNDINFO lumps
|
||||||
Printf ("S_InitData: Load sound definitions.\n");
|
Printf ("S_InitData: Load sound definitions.\n");
|
||||||
|
|
|
@ -104,6 +104,9 @@ void G_DoWorldDone (void);
|
||||||
void G_DoSaveGame (bool okForQuicksave, FString filename, const char *description);
|
void G_DoSaveGame (bool okForQuicksave, FString filename, const char *description);
|
||||||
void G_DoAutoSave ();
|
void G_DoAutoSave ();
|
||||||
|
|
||||||
|
void STAT_Write(FILE *file);
|
||||||
|
void STAT_Read(PNGHandle *png);
|
||||||
|
|
||||||
FIntCVar gameskill ("skill", 2, CVAR_SERVERINFO|CVAR_LATCH);
|
FIntCVar gameskill ("skill", 2, CVAR_SERVERINFO|CVAR_LATCH);
|
||||||
CVAR (Int, deathmatch, 0, CVAR_SERVERINFO|CVAR_LATCH);
|
CVAR (Int, deathmatch, 0, CVAR_SERVERINFO|CVAR_LATCH);
|
||||||
CVAR (Bool, chasedemo, false, 0);
|
CVAR (Bool, chasedemo, false, 0);
|
||||||
|
@ -1791,6 +1794,7 @@ void G_DoLoadGame ()
|
||||||
}
|
}
|
||||||
|
|
||||||
G_ReadSnapshots (png);
|
G_ReadSnapshots (png);
|
||||||
|
STAT_Read(png);
|
||||||
FRandom::StaticReadRNGState (png);
|
FRandom::StaticReadRNGState (png);
|
||||||
P_ReadACSDefereds (png);
|
P_ReadACSDefereds (png);
|
||||||
|
|
||||||
|
@ -2052,6 +2056,7 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio
|
||||||
}
|
}
|
||||||
|
|
||||||
G_WriteSnapshots (stdfile);
|
G_WriteSnapshots (stdfile);
|
||||||
|
STAT_Write(stdfile);
|
||||||
FRandom::StaticWriteRNGState (stdfile);
|
FRandom::StaticWriteRNGState (stdfile);
|
||||||
P_WriteACSDefereds (stdfile);
|
P_WriteACSDefereds (stdfile);
|
||||||
|
|
||||||
|
|
|
@ -83,18 +83,9 @@
|
||||||
|
|
||||||
#include "g_hub.h"
|
#include "g_hub.h"
|
||||||
|
|
||||||
|
void STAT_StartNewGame(const char *lev);
|
||||||
|
void STAT_ChangeLevel(const char *newl);
|
||||||
|
|
||||||
#ifndef STAT
|
|
||||||
#define STAT_NEW(map)
|
|
||||||
#define STAT_END(newl)
|
|
||||||
#define STAT_READ(png)
|
|
||||||
#define STAT_WRITE(f)
|
|
||||||
#else
|
|
||||||
void STAT_NEW(const char *lev);
|
|
||||||
void STAT_END(const char *newl);
|
|
||||||
void STAT_READ(PNGHandle *png);
|
|
||||||
void STAT_WRITE(FILE *f);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
EXTERN_CVAR (Float, sv_gravity)
|
EXTERN_CVAR (Float, sv_gravity)
|
||||||
EXTERN_CVAR (Float, sv_aircontrol)
|
EXTERN_CVAR (Float, sv_aircontrol)
|
||||||
|
@ -502,7 +493,7 @@ void G_InitNew (const char *mapname, bool bTitleLevel)
|
||||||
for (i = 0; i < MAXPLAYERS; i++)
|
for (i = 0; i < MAXPLAYERS; i++)
|
||||||
players[i].playerstate = PST_ENTER; // [BC]
|
players[i].playerstate = PST_ENTER; // [BC]
|
||||||
|
|
||||||
STAT_NEW(mapname);
|
STAT_StartNewGame(mapname);
|
||||||
}
|
}
|
||||||
|
|
||||||
usergame = !bTitleLevel; // will be set false if a demo
|
usergame = !bTitleLevel; // will be set false if a demo
|
||||||
|
@ -614,7 +605,7 @@ void G_ChangeLevel(const char *levelname, int position, int flags, int nextSkill
|
||||||
FBehavior::StaticStartTypedScripts (SCRIPT_Unloading, NULL, false, 0, true);
|
FBehavior::StaticStartTypedScripts (SCRIPT_Unloading, NULL, false, 0, true);
|
||||||
unloading = false;
|
unloading = false;
|
||||||
|
|
||||||
STAT_END(nextlevel);
|
STAT_ChangeLevel(nextlevel);
|
||||||
|
|
||||||
if (thiscluster && (thiscluster->flags & CLUSTER_HUB))
|
if (thiscluster && (thiscluster->flags & CLUSTER_HUB))
|
||||||
{
|
{
|
||||||
|
@ -1652,7 +1643,6 @@ void G_WriteSnapshots (FILE *file)
|
||||||
{
|
{
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
|
||||||
STAT_WRITE(file);
|
|
||||||
for (i = 0; i < wadlevelinfos.Size(); i++)
|
for (i = 0; i < wadlevelinfos.Size(); i++)
|
||||||
{
|
{
|
||||||
if (wadlevelinfos[i].snapshot)
|
if (wadlevelinfos[i].snapshot)
|
||||||
|
@ -1803,7 +1793,6 @@ void G_ReadSnapshots (PNGHandle *png)
|
||||||
arc << pnum;
|
arc << pnum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
STAT_READ(png);
|
|
||||||
png->File->ResetFilePtr();
|
png->File->ResetFilePtr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -202,6 +202,9 @@ enum ELevelFlags
|
||||||
LEVEL2_POLYGRIND = 0x02000000, // Polyobjects grind corpses to gibs.
|
LEVEL2_POLYGRIND = 0x02000000, // Polyobjects grind corpses to gibs.
|
||||||
LEVEL2_RESETINVENTORY = 0x04000000, // Resets player inventory when starting this level (unless in a hub)
|
LEVEL2_RESETINVENTORY = 0x04000000, // Resets player inventory when starting this level (unless in a hub)
|
||||||
LEVEL2_RESETHEALTH = 0x08000000, // Resets player health when starting this level (unless in a hub)
|
LEVEL2_RESETHEALTH = 0x08000000, // Resets player health when starting this level (unless in a hub)
|
||||||
|
|
||||||
|
LEVEL2_NOSTATISTICS = 0x10000000, // This level should not have statistics collected
|
||||||
|
LEVEL2_ENDGAME = 0x20000000, // This is an epilogue level that cannot be quit.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1377,6 +1377,8 @@ MapFlagHandlers[] =
|
||||||
{ "no_grinding_polyobj", MITYPE_CLRFLAG2, LEVEL2_POLYGRIND, 0 },
|
{ "no_grinding_polyobj", MITYPE_CLRFLAG2, LEVEL2_POLYGRIND, 0 },
|
||||||
{ "resetinventory", MITYPE_SETFLAG2, LEVEL2_RESETINVENTORY, 0 },
|
{ "resetinventory", MITYPE_SETFLAG2, LEVEL2_RESETINVENTORY, 0 },
|
||||||
{ "resethealth", MITYPE_SETFLAG2, LEVEL2_RESETHEALTH, 0 },
|
{ "resethealth", MITYPE_SETFLAG2, LEVEL2_RESETHEALTH, 0 },
|
||||||
|
{ "endofgame", MITYPE_SETFLAG2, LEVEL2_ENDGAME, 0 },
|
||||||
|
{ "nostatistics", MITYPE_SETFLAG2, LEVEL2_NOSTATISTICS, 0 },
|
||||||
{ "unfreezesingleplayerconversations",MITYPE_SETFLAG2, LEVEL2_CONV_SINGLE_UNFREEZE, 0 },
|
{ "unfreezesingleplayerconversations",MITYPE_SETFLAG2, LEVEL2_CONV_SINGLE_UNFREEZE, 0 },
|
||||||
{ "nobotnodes", MITYPE_IGNORE, 0, 0 }, // Skulltag option: nobotnodes
|
{ "nobotnodes", MITYPE_IGNORE, 0, 0 }, // Skulltag option: nobotnodes
|
||||||
{ "compat_shorttex", MITYPE_COMPATFLAG, COMPATF_SHORTTEX},
|
{ "compat_shorttex", MITYPE_COMPATFLAG, COMPATF_SHORTTEX},
|
||||||
|
|
606
src/statistics.cpp
Normal file
606
src/statistics.cpp
Normal file
|
@ -0,0 +1,606 @@
|
||||||
|
/*
|
||||||
|
**
|
||||||
|
** 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 "gi.h"
|
||||||
|
#include "g_level.h"
|
||||||
|
#include "gstrings.h"
|
||||||
|
#include "doomstat.h"
|
||||||
|
#include "configfile.h"
|
||||||
|
#include "c_dispatch.h"
|
||||||
|
#include "c_console.h"
|
||||||
|
#include "d_gui.h"
|
||||||
|
#include "d_dehacked.h"
|
||||||
|
#include "g_game.h"
|
||||||
|
#include "m_png.h"
|
||||||
|
#include "m_misc.h"
|
||||||
|
#include "doomerrors.h"
|
||||||
|
#include "w_wad.h"
|
||||||
|
#include "hu_stuff.h"
|
||||||
|
#include "p_local.h"
|
||||||
|
#include "m_png.h"
|
||||||
|
#include "p_setup.h"
|
||||||
|
#include "s_Sound.h"
|
||||||
|
#include "wi_stuff.h"
|
||||||
|
#include "sc_man.h"
|
||||||
|
#include "cmdlib.h"
|
||||||
|
#include "p_terrain.h"
|
||||||
|
#include "decallib.h"
|
||||||
|
#include "a_doomglobal.h"
|
||||||
|
#include "autosegs.h"
|
||||||
|
#include "i_cd.h"
|
||||||
|
#include "stats.h"
|
||||||
|
#include "a_sharedglobal.h"
|
||||||
|
#include "v_text.h"
|
||||||
|
#include "r_sky.h"
|
||||||
|
#include "p_lnspec.h"
|
||||||
|
#include "m_crc32.h"
|
||||||
|
|
||||||
|
CVAR(Int, savestatistics, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||||||
|
CVAR(String, statfile, "zdoomstat.txt", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Global statistics data
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
// This struct is used to track statistics data in game
|
||||||
|
struct OneLevel
|
||||||
|
{
|
||||||
|
int totalkills, killcount;
|
||||||
|
int totalsecrets, secretcount;
|
||||||
|
int leveltime;
|
||||||
|
char levelname[9];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Current game's statistics
|
||||||
|
static TArray<OneLevel> LevelData;
|
||||||
|
static FEpisode *StartEpisode;
|
||||||
|
|
||||||
|
// The statistics for one level
|
||||||
|
struct FLevelStatistics
|
||||||
|
{
|
||||||
|
char info[30];
|
||||||
|
short skill;
|
||||||
|
short playerclass;
|
||||||
|
char name[12];
|
||||||
|
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;
|
||||||
|
|
||||||
|
extern TArray<level_info_t> wadlevelinfos;
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Initializes statistics data from external file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static void ParseStatistics(const char *fn, TArray<FStatistics> &statlist)
|
||||||
|
{
|
||||||
|
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, 12);
|
||||||
|
sc.MustGetString();
|
||||||
|
strncpy(session.info, sc.String, 30);
|
||||||
|
|
||||||
|
int h,m,s;
|
||||||
|
sc.MustGetString();
|
||||||
|
sscanf(sc.String, "%d:%d:%d", &h, &m, &s);
|
||||||
|
session.timeneeded= ((((h*60)+m)*60)+s)*TICRATE;
|
||||||
|
|
||||||
|
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, 12);
|
||||||
|
sc.MustGetString();
|
||||||
|
strncpy(lstats.info, sc.String, 30);
|
||||||
|
|
||||||
|
int h,m,s;
|
||||||
|
sc.MustGetString();
|
||||||
|
sscanf(sc.String, "%d:%d:%d", &h, &m, &s);
|
||||||
|
lstats.timeneeded= ((((h*60)+m)*60)+s)*TICRATE;
|
||||||
|
|
||||||
|
lstats.skill = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(CRecoverableError &)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
//
|
||||||
|
// Reads the statistics file
|
||||||
|
//
|
||||||
|
// ====================================================================
|
||||||
|
|
||||||
|
void ReadStatistics()
|
||||||
|
{
|
||||||
|
ParseStatistics(statfile, EpisodeStatistics);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
//
|
||||||
|
// Saves the statistics file
|
||||||
|
// Sorting helpers.
|
||||||
|
//
|
||||||
|
// ====================================================================
|
||||||
|
|
||||||
|
int STACK_ARGS 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 STACK_ARGS compare_level_names(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
FLevelStatistics *A = (FLevelStatistics*)a;
|
||||||
|
FLevelStatistics *B = (FLevelStatistics*)b;
|
||||||
|
|
||||||
|
return strnatcasecmp(A->name, B->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int STACK_ARGS 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*TICRATE); }
|
||||||
|
inline int minutes(int v) { return (v % (60*60*TICRATE)) / (60*TICRATE); }
|
||||||
|
inline int seconds(int v) { return (v % (60*TICRATE))/TICRATE; }
|
||||||
|
|
||||||
|
static void SaveStatistics(const char *fn, TArray<FStatistics> &statlist)
|
||||||
|
{
|
||||||
|
unsigned int j;
|
||||||
|
|
||||||
|
FILE * f = fopen(fn, "wt");
|
||||||
|
if (f==NULL) 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);
|
||||||
|
|
||||||
|
fprintf(f, "%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)
|
||||||
|
{
|
||||||
|
fprintf(f,"\t%2i. %10s \"%-22s\" %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)
|
||||||
|
{
|
||||||
|
fprintf(f,"\t{\n");
|
||||||
|
|
||||||
|
qsort(&ls[0], ls.Size(), sizeof(ls[0]), compare_level_names);
|
||||||
|
|
||||||
|
for(unsigned k=0;k<ls.Size ();k++)
|
||||||
|
{
|
||||||
|
fprintf(f, "\t\t%-8s \"%-22s\" %02d:%02d:%02d\n", ls[k].name, ls[k].info,
|
||||||
|
hours(ls[k].timeneeded), minutes(ls[k].timeneeded), seconds(ls[k].timeneeded));
|
||||||
|
}
|
||||||
|
fprintf(f,"\t}\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(f,"}\n\n");
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
//
|
||||||
|
// 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)
|
||||||
|
mysnprintf(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=G_SkillProperty(SKILLP_ACSReturn);
|
||||||
|
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 *mapname)
|
||||||
|
{
|
||||||
|
LevelData.Clear();
|
||||||
|
if (!deathmatch && !multiplayer)
|
||||||
|
{
|
||||||
|
for(unsigned int j=0;j<AllEpisodes.Size();j++)
|
||||||
|
{
|
||||||
|
if (!AllEpisodes[j].mEpisodeMap.CompareNoCase(mapname))
|
||||||
|
{
|
||||||
|
StartEpisode = &AllEpisodes[j];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StartEpisode = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Store the current level's statistics
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static void StoreLevelStats()
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
if (!(level.flags2&LEVEL2_NOSTATISTICS)) // don't consider maps that were excluded from statistics
|
||||||
|
{
|
||||||
|
for(i=0;i<LevelData.Size();i++)
|
||||||
|
{
|
||||||
|
if (!stricmp(LevelData[i].levelname, level.mapname)) break;
|
||||||
|
}
|
||||||
|
if (i==LevelData.Size())
|
||||||
|
{
|
||||||
|
LevelData.Reserve(1);
|
||||||
|
strcpy(LevelData[i].levelname, level.mapname);
|
||||||
|
}
|
||||||
|
LevelData[i].totalkills = level.total_monsters;
|
||||||
|
LevelData[i].killcount = level.killed_monsters;
|
||||||
|
LevelData[i].totalsecrets = level.total_secrets;
|
||||||
|
LevelData[i].secretcount = level.found_secrets;
|
||||||
|
LevelData[i].leveltime = level.maptime;
|
||||||
|
|
||||||
|
// Check for living monsters. On some maps it can happen
|
||||||
|
// that the counter misses some.
|
||||||
|
TThinkerIterator<AActor> it;
|
||||||
|
AActor *ac;
|
||||||
|
int mc = 0;
|
||||||
|
|
||||||
|
while ((ac = it.Next()))
|
||||||
|
{
|
||||||
|
if ((ac->flags & MF_COUNTKILL) && ac->health > 0) mc++;
|
||||||
|
}
|
||||||
|
if (mc == 0) LevelData[i].killcount = LevelData[i].totalkills;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// STAT_ChangeLevel: called when the level changes or the current statistics are
|
||||||
|
// requested
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void STAT_ChangeLevel(const char *newl)
|
||||||
|
{
|
||||||
|
// record the current level's stats.
|
||||||
|
StoreLevelStats();
|
||||||
|
|
||||||
|
level_info_t *thisinfo = level.info;
|
||||||
|
level_info_t *nextinfo = NULL;
|
||||||
|
|
||||||
|
if (strncmp(newl, "enDSeQ", 6))
|
||||||
|
{
|
||||||
|
level_info_t *l = FindLevelInfo (newl);
|
||||||
|
nextinfo = l->CheckLevelRedirect ();
|
||||||
|
if (nextinfo == NULL) nextinfo = l;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savestatistics == 1)
|
||||||
|
{
|
||||||
|
if ((nextinfo == NULL || (nextinfo->flags2 & LEVEL2_ENDGAME)) && StartEpisode != NULL)
|
||||||
|
{
|
||||||
|
// we reached the end of this episode
|
||||||
|
int wad = 0;
|
||||||
|
MapData * map = P_OpenMapData(StartEpisode->mEpisodeMap);
|
||||||
|
if (map != NULL)
|
||||||
|
{
|
||||||
|
wad = Wads.GetLumpFile(map->lumpnum);
|
||||||
|
delete map;
|
||||||
|
}
|
||||||
|
const char * name = Wads.GetWadName(wad);
|
||||||
|
FString section = ExtractFileBase(name) + "." + StartEpisode->mEpisodeMap;
|
||||||
|
section.ToUpper();
|
||||||
|
|
||||||
|
const char *ep_name = StartEpisode->mEpisodeName;
|
||||||
|
if (*ep_name == '$') ep_name = GStrings[ep_name+1];
|
||||||
|
FStatistics *sl = GetStatisticsList(EpisodeStatistics, section, ep_name);
|
||||||
|
|
||||||
|
int statvals[4] = {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;
|
||||||
|
}
|
||||||
|
|
||||||
|
infostring.Format("%4d/%4d, %3d/%3d, %2d", statvals[0], statvals[1], statvals[2], statvals[3], validlevels);
|
||||||
|
FSessionStatistics *es = StatisticsEntry(sl, infostring, level.totaltime);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (savestatistics == 2) // todo: handle single level statistics.
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// saves statistics info to savegames
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static void SerializeStatistics(FArchive &arc)
|
||||||
|
{
|
||||||
|
FString startlevel;
|
||||||
|
int i = LevelData.Size();
|
||||||
|
|
||||||
|
arc << i;
|
||||||
|
|
||||||
|
if (arc.IsLoading())
|
||||||
|
{
|
||||||
|
arc << startlevel;
|
||||||
|
StartEpisode = NULL;
|
||||||
|
for(unsigned int j=0;j<AllEpisodes.Size();j++)
|
||||||
|
{
|
||||||
|
if (!AllEpisodes[j].mEpisodeMap.CompareNoCase(startlevel))
|
||||||
|
{
|
||||||
|
StartEpisode = &AllEpisodes[j];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LevelData.Resize(i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (StartEpisode != NULL) startlevel = StartEpisode->mEpisodeMap;
|
||||||
|
arc << startlevel;
|
||||||
|
}
|
||||||
|
for(int j = 0; j < i; j++)
|
||||||
|
{
|
||||||
|
OneLevel &l = LevelData[j];
|
||||||
|
|
||||||
|
arc << l.totalkills
|
||||||
|
<< l.killcount
|
||||||
|
<< l.totalsecrets
|
||||||
|
<< l.secretcount
|
||||||
|
<< l.leveltime;
|
||||||
|
|
||||||
|
if (arc.IsStoring()) arc.WriteName(l.levelname);
|
||||||
|
else strcpy(l.levelname, arc.ReadName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define STAT_ID MAKE_ID('s','T','a','t')
|
||||||
|
|
||||||
|
void STAT_Write(FILE *file)
|
||||||
|
{
|
||||||
|
FPNGChunkArchive arc (file, STAT_ID);
|
||||||
|
SerializeStatistics(arc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void STAT_Read(PNGHandle *png)
|
||||||
|
{
|
||||||
|
DWORD chunkLen = (DWORD)M_FindPNGChunk (png, STAT_ID);
|
||||||
|
if (chunkLen != 0)
|
||||||
|
{
|
||||||
|
FPNGChunkArchive arc (png->File->GetFile(), STAT_ID, chunkLen);
|
||||||
|
SerializeStatistics(arc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// 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, l->killcount, l->totalkills, l->secretcount, l->totalsecrets,
|
||||||
|
l->leveltime/(60*TICRATE), (l->leveltime/TICRATE)%60);
|
||||||
|
}
|
||||||
|
return compose;
|
||||||
|
}
|
||||||
|
|
||||||
|
CCMD(printstats)
|
||||||
|
{
|
||||||
|
StoreLevelStats(); // Get the current level's current results.
|
||||||
|
Printf("%s", GetStatString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CCMD(finishgame)
|
||||||
|
{
|
||||||
|
// This CCMD simulates an end-of-game action and exists to end mods that never exit their last level.
|
||||||
|
G_SetForEndGame (level.nextmap);
|
||||||
|
G_ExitLevel (0, false);
|
||||||
|
}
|
||||||
|
|
178
src/strnatcmp.c
Normal file
178
src/strnatcmp.c
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
/* -*- 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 <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.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
src/strnatcmp.h
Normal file
39
src/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
|
12
zdoom.vcproj
12
zdoom.vcproj
|
@ -1000,6 +1000,10 @@
|
||||||
RelativePath=".\src\st_stuff.cpp"
|
RelativePath=".\src\st_stuff.cpp"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\src\statistics.cpp"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath=".\src\stats.cpp"
|
RelativePath=".\src\stats.cpp"
|
||||||
>
|
>
|
||||||
|
@ -1008,6 +1012,10 @@
|
||||||
RelativePath=".\src\stringtable.cpp"
|
RelativePath=".\src\stringtable.cpp"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\src\strnatcmp.c"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath=".\src\tables.cpp"
|
RelativePath=".\src\tables.cpp"
|
||||||
>
|
>
|
||||||
|
@ -1533,6 +1541,10 @@
|
||||||
RelativePath=".\src\stringtable.h"
|
RelativePath=".\src\stringtable.h"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\src\strnatcmp.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath=".\src\Tables.h"
|
RelativePath=".\src\Tables.h"
|
||||||
>
|
>
|
||||||
|
|
Loading…
Reference in a new issue