source-engine-2018-hl2_src/utils/tfstats/plrpersist.cpp

420 lines
10 KiB
C++
Raw Normal View History

2020-04-23 00:56:21 +08:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
//
//------------------------------------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#pragma warning (disable:4786)
#include "PlrPersist.h"
#include "TextFile.h"
#include <map>
#include <string>
using namespace std;
//------------------------------------------------------------------------------------------------------
// Function: CPlrPersist::generate
// Purpose: fills in the fields of this with the data in the given CPlayer object
// Input: cp - the player object to get data from
//------------------------------------------------------------------------------------------------------
void CPlrPersist::generate(CPlayer& cp)
{
kills=deaths=timeon=0;
valid=true;
WONID=cp.WONID;
matches=1;
lastplayed=cp.logofftime;
//do perteam stuff
CTimeIndexedList<int>::iterator teamiter=cp.teams.begin();
for (teamiter;teamiter!=cp.teams.end();++teamiter)
{
int tdt=teamiter->data;
kills+=cp.perteam[teamiter->data].kills;
deaths+=cp.perteam[teamiter->data].deaths;
timeon+=cp.perteam[teamiter->data].timeon;
map<string,int>::iterator it;
it=cp.perteam[teamiter->data].weaponKills.begin();
for (it;it!=cp.perteam[teamiter->data].weaponKills.end();++it)
{
string name=it->first;
int kills=it->second;
weapmap[name]+=kills;
}
}
CTimeIndexedList<player_class>::iterator clsit=cp.allclassesplayed.begin();
for (clsit;clsit!=cp.allclassesplayed.end();++clsit)
{
string classname=plrClassNames[clsit->data];
classmap[classname]+=cp.allclassesplayed.howLong(clsit->data);
}
CTimeIndexedList<string>::iterator nameiter;
for (nameiter=cp.aliases.begin();nameiter!=cp.aliases.end();++nameiter)
{
nickmap[nameiter->data]+=cp.aliases.howLong(nameiter->data);
}
pair<time_t,time_t> startstop;
startstop.first=cp.logontime;
startstop.second=cp.logofftime;
playtimes.push_back(startstop);
}
//------------------------------------------------------------------------------------------------------
// Function: CPlrPersist::merge
// Purpose: merges the stats of another CPlrPersist object into this one.
// This is the key operation of this class. This is how player stats are kept up
// to date over time. If the two data files have playtimes that overlap, they
// are not merged (unless the mergeOverlaps flag is true)
// Input: other - the CPlrPersist object that we want to merge into this one
// mergeOverlaps - if true, overlapping playtimes are ignored.
//------------------------------------------------------------------------------------------------------
void CPlrPersist::merge(CPlrPersist& other,bool mergeOverlaps)
{
if (!other.valid)
return; //don't modify
if (WONID!=other.WONID)
{
g_pApp->warning("merging stats for two different WONIDs (%lu, %lu)",WONID,other.WONID);
}
else
{
//do playtimes first to see if overlaps occur
list<pair<time_t,time_t> >::iterator itOther=other.playtimes.begin();
for (itOther;itOther!=other.playtimes.end();++itOther)
{
list<pair<time_t,time_t> >::iterator overlap=timesOverlap(itOther->first,itOther->second);
time_t overlapSecond=overlap->second;
time_t overlapFirst=overlap->first;
if (mergeOverlaps || overlap==playtimes.end())
playtimes.push_back(*itOther);
else
{
g_pApp->warning("not merging stats for WON ID# %lu, playtime ranges overlap\n\t((%lu-%lu) overlaps with (%lu-%lu))",WONID,itOther->first,itOther->second,overlap->first,overlap->second);
return;
}
}
}
matches+=other.matches;
kills+=other.kills;
deaths+=other.deaths;
timeon+=other.timeon;
if (other.lastplayed > lastplayed)
lastplayed=other.lastplayed;
//do names
map<string,int>::iterator it;
it=other.nickmap.begin();
for (it;it!=other.nickmap.end();++it)
{
string name=it->first;
int time=it->second;
nickmap[name]+=time;
}
//do weapons
it=other.weapmap.begin();
for (it;it!=other.weapmap.end();++it)
{
string name=it->first;
int kills=it->second;
weapmap[name]+=kills;
}
//do classes
it=other.classmap.begin();
for (it;it!=other.classmap.end();++it)
{
string name=it->first;
int time=it->second;
classmap[name]+=time;
}
}
//------------------------------------------------------------------------------------------------------
// Function: CPlrPersist::read
// Purpose: fills in the fields of this by reading data out of a file
// Input: f - the file from which to read the data
//------------------------------------------------------------------------------------------------------
void CPlrPersist::read(CTextFile& f)
{
if (!f.isValid())
{
kills=deaths=timeon=0; WONID=-1;
valid=false;
return;
}
if(WONID==-1)
{
//parse it out of f;
string s=f.fileName();
char buf[100];
int startpos=s.find_last_of(g_pApp->os->pathSeperator());
int endpos=s.find_last_of(".");
if (endpos == -1)
return;
if (startpos==-1)
startpos=0;
s.copy(buf,(endpos-startpos),startpos);
buf[endpos-startpos]=0;
WONID=strtoul(buf,NULL,10);
if (!WONID)
{
WONID=-1;
valid=false;
return;
}
}
valid=false;
if (!f.eof()) kills=f.readInt(); else return;
if (!f.eof()) deaths=f.readInt(); else return;
if (!f.eof()) timeon=f.readULong(); else return;
if (!f.eof()) matches=f.readInt(); else return;
if (!f.eof()) lastplayed=f.readULong(); else return;
string next;
if (!f.eof())
{
f.discard("names");
next= f.peekNextString();
while ( next!="endnames")
{
string name=f.readString();
int timeon=f.readInt();
nickmap[name]=timeon;
next=f.peekNextString();
}
f.discard("endnames");
} else return;
if (!f.eof())
{
f.discard("weapons");
next= f.peekNextString();
while (next!="endweapons")
{
string name=f.readString();
int kills=f.readInt();
weapmap[name]=kills;
next=f.peekNextString();
}
f.discard("endweapons");
} else return;
if (!f.eof())
{
f.discard("classes");
next= f.peekNextString();
while (next!="endclasses")
{
string name=f.readString();
int timeused=f.readInt();
classmap[name]=timeused;
next=f.peekNextString();
}
f.discard("endclasses");
} else return;
if (!f.eof())
{
f.discard("playtimes");
next= f.peekNextString();
while (next!="endplaytimes")
{
pair<time_t,time_t> startstop;
startstop.first=f.readULong();
startstop.second=f.readULong();
playtimes.push_back(startstop);
next=f.peekNextString();
}
f.discard("endplaytimes");
} else return;
valid=true;
}
//------------------------------------------------------------------------------------------------------
// Function: CPlrPersist::read
// Purpose: converts the WONID to a file name (<wonid>.tfs) and passes execution
// off to the above read function.
// Input: WONID - the WONID of the player whose datafile we want to read
//------------------------------------------------------------------------------------------------------
void CPlrPersist::read(unsigned long WONID)
{
string file=g_pApp->playerDirectory;
char buf[100];
file+=g_pApp->os->ultoa(WONID,buf,10);
file+=".tfs";
this->WONID=WONID;
CTextFile f(file.c_str());
read(f);
}
void CPlrPersist::write()
{
string file=g_pApp->playerDirectory;
char buf[100];
file+=g_pApp->os->ultoa(WONID,buf,10);
file+=".tfs";
FILE* fout=fopen(file.c_str(),"wt");
fprintf(fout,"%li //kills\n",kills);
fprintf(fout,"%li //deaths\n",deaths);
fprintf(fout,"%lu //timeon\n",timeon);
fprintf(fout,"%li //matches played\n",matches);
fprintf(fout,"%lu //last played\n",lastplayed);
map<string,int>::iterator it;
fprintf(fout,"names\n");
it=nickmap.begin();
for (it;it!=nickmap.end();++it)
{
string name=it->first;
int time=it->second;
fprintf(fout,"\t\"%s\" %li //has used the name \"%s\" for %02li:%02li:%02li\n",name.c_str(),time,name.c_str(),Util::time_t2hours(time),Util::time_t2mins(time),Util::time_t2secs(time));
}
fprintf(fout,"endnames\n");
fprintf(fout,"weapons\n");
it=weapmap.begin();
for (it;it!=weapmap.end();++it)
{
string name=it->first;
int kills=it->second;
fprintf(fout,"\t\"%s\" %li //has killed %li people with \"%s\"\n",name.c_str(),kills,kills,name.c_str());
}
fprintf(fout,"endweapons\n");
fprintf(fout,"classes\n");
it=classmap.begin();
for (it;it!=classmap.end();++it)
{
string name=it->first;
int time=it->second;
fprintf(fout,"\t\"%s\" %li //has played as a \"%s\" for %02li:%02li:%02li\n",name.c_str(),time,name.c_str(),Util::time_t2hours(time),Util::time_t2mins(time),Util::time_t2secs(time));
}
fprintf(fout,"endclasses\n");
fprintf(fout,"playtimes\n");
list<pair<time_t,time_t> >::iterator it2=playtimes.begin();
for (it2;it2!=playtimes.end();++it2)
{
char buf[500];
time_t t1=it2->first;
time_t t2=it2->second;
bool doesOverlap;
list<pair<time_t,time_t> >::iterator overlap=timesOverlap(it2->first,it2->second,false);
doesOverlap= overlap!=playtimes.end();
fprintf(fout,"\t%lu %lu //played from %s.",it2->first,it2->second,Util::makeDurationString(it2->first,it2->second,buf," to "));
if (doesOverlap)
fprintf(fout,"Warning! overlaps with time range (%lu-%lu)",overlap->first,overlap->second);
fprintf(fout,"\n");
}
fprintf(fout,"endplaytimes\n");
fclose(fout);
}
list<pair<time_t,time_t> >::iterator CPlrPersist::timesOverlap(time_t start, time_t end,bool testself)
{
list<pair<time_t,time_t> >::iterator it;
it=playtimes.begin();
for (it;it!=playtimes.end();++it)
{
time_t itFirst=it->first;
time_t itSecond=it->second;
if (start == it->first && end == it->second)
{
if (testself)
break;
}
//if start is in current range
else if (start >= it->first && start <= it->second)
break;
//if end is in current range
else if (end >= it->first && end <= it->second)
break;
//if the start is before this range and end is after
else if (start <= it->first && end >= it->second)
break;
}
return it;
}
string CPlrPersist::faveString(map<string,int>& theMap)
{
string retstr;
time_t max=0;
map<string,int>::iterator it=theMap.begin();
for (it;it!=theMap.end();++it)
{
if (it->second > max)
{
max=it->second;
retstr=it->first;
}
}
return retstr;
}
string CPlrPersist::faveName()
{
return faveString(nickmap);
}
string CPlrPersist::faveWeap()
{
string s=faveString(weapmap);
faveweapkills=weapmap[s];
return s;
}
string CPlrPersist::faveClass()
{
return faveString(classmap);
}
double CPlrPersist::rank()
{
return ((double)((double)kills - (double)deaths) * 1000.0) / (double)timeon;
}