Player STAT Saving


Posted by [QBS]Quadrant (81.86.166.*) at 8:53 AM, 9/15/2003:

Hi Guys i thought someone might find this useful so im posting a STAT saving system and details of how to add it the code is forward and backward compatible ie you can add new things to save and the old files will still work or you can load a new file that has extra things on a old version...

Please Make a new file call it what you will load_save.c for example

And add the followinf functions:

//[QBS]===========================================
// This peice of code is designed to be used for
// player Persistance & global stats
// Coded By [QBS]Quadrant
//================================================

//load_save.c
#include "g_local.h" // talks to q2 engine
#include "load_save.h"
#include //[QBS] DISK MAKE/REMOVE DIR'S

static const char * PSUBDIR = "save/playerfiles";// subdir in your mod folder to save to


/* this version always zero-terminates, unlike the silly standard one */
// mysnprintf is coded by Blinky
int mysnprintf(char * buffer, size_t count, const char * fmt, ...)
{
va_list argptr;
int ret;
va_start(argptr, fmt);
ret = _vsnprintf(buffer, count, fmt, argptr);
va_end(argptr);
buffer[count-1]=0; /* Always put zero at end */
return ret;
}

//ok the below peices of code in this comment are to define the
//gamepath which is very useful,,


static void SetFilename(char * filename,int len, edict_t *ent)
{
char mod[16];
_mkdir ("your_mod/save/playerfiles");//[QBS] make the dir if we dont have it

if (gamepath->string && gamepath->string[0])
strcpy(mod, gamepath->string);
else
strcpy(mod, "baseq2");
mysnprintf(filename, len, "%s/%s/%s.DAT", mod, PSUBDIR,ent->client->pers.netname);
}

//=======================================================================================

void WRITE_PLAYER_STATS (edict_t *ent)// SAVE PLAYER
{
edict_t *attacker;
edict_t *self;
char filename[128];
FILE *FH;
attacker = ent;
self = ent;
ent = ent; //[QBS]what the hell lol :)

SetFilename(filename, sizeof(filename), ent);
// MISC
/*
ok what things should we have on player persistance
example

Deaths
Kills
Time played
Kill Percentage Worked out from kills to deaths
Player Password (to protect players character maybe)
*/

// DAT.player_password = ent->client->player_password;//example
DAT.version = file_version;
// EXP

DAT.score = ent->client->resp.score;// was used in testing
DAT.health = ent->health;

//[QBS] Player tracking anything you like can go here

DAT.player_deaths = ent->client->resp.player_deaths;
DAT.player_kills_given = ent->client->resp.player_kills_given;
//[QBS]end
/*
If you wanted you could have this post to a global system at this point to have global data for all servers running your mod
*/
if ((FH = fopen( filename, "wb")) == NULL)
{
// gi.cprintf(ent, PRINT_HIGH, "File is already in use\n");
return;
}
fwrite ( &DAT, sizeof(DAT), 1, FH );
fclose ( FH );
}

//======================================================================================

void READ_PLAYER_STATS (edict_t *ent)// LOAD

{
FILE *FH;
char filename[128];

//[QBS]attempt to make global
edict_t *attacker;
edict_t *self;

attacker = ent;
self = ent;
ent = ent; //what the hell lol :)
//[QBS]end


SetFilename(filename, sizeof(filename), ent);

if ((FH = fopen( filename, "rb")) == NULL)
{
// gi.cprintf(ent, PRINT_HIGH, "Player not found making new player.data file\n");
WRITE_PLAYER_STATS (ent);
return;
}
fread ( &DAT, sizeof(DAT), 1, FH );
fclose ( FH );

if (DAT.version != file_version)// delete if not needed

// gi.cprintf(ent, PRINT_HIGH, "Incorrect file version.\n");// delete if not needed
/*
if (DAT.player_password != ent->client->player_password)
{
gi.centerprintf (ent, "Incorrect Password:\n %c",ent->client->player_password);
return;
}
*/

ent->client->resp.score = DAT.score;
ent->health = DAT.health;
ent->client->player_password = DAT.player_password;

ent->client->resp.player_deaths = DAT.player_deaths;
ent->client->resp.player_kills_given = DAT.player_kills_given;//[QBS]end


//gi.cprintf(ent, PRINT_HIGH, "Player was loaded correctly.\n");
// ent->client->resp.loaded = 1;
}


//======================================================================================

ok next you need to make a header file for the above load_save.h etc

void WRITE_PLAYER_STATS (edict_t *ent);
void READ_PLAYER_STATS (edict_t *ent);

// ANYTHING YOU WISH TO SAVE MUST ALSO BE ADDED TO THE BELOW STRUCTURE
struct player_save
{
//MISC
int version;
// PASSWORD
// char pwd[32];
int player_password;
// EXPERIENCE
int health;
int player_deaths;
int player_kills_given;
int score;

}DAT;

//=======================================================================================

Ok the most important thing now is to call these functions at suitable places one mistake people make is they dont save the player often enough and then if there is a server crash people loose lots of points etc and get very unhappy so i save on every kill or death and on player disconnect...

ok to load the player we need to call READ_PLAYER_STATS (ent);

the best locations for this are as follows

If you are using a menu system in your mod to join a player or for a player to pick a class etc then i would call it from this menu..

I would also call this say in InitClientPersistant in p_client.c for none menu games or where for example you are switching from a spectator to active player..


ok next to save the player we need to call WRITE_PLAYER_STATS (ent);

This would be best done as follows

if you collect an item that is very important to the game or if you just gained some player experience in some way that is a lot etc that it would be nice to call the save function.

ok in player_die after the points have been added or deducted for the dead player and the attacker etc we would save the changes to both players as follows

if (attacker->client)// yes attacker was client
{
WRITE_PLAYER_STATS(attacker);//[QBS]
}
else//[QBS] must have been the world that killed them
{
}

if (!self->client) return;

WRITE_PLAYER_STATS(self);//[QBS] dead dude !! he's dead Jim !!! hehehe
//[QBS]1end


ok next at the bottom of ClientDisconnect we would call this function to save there last stats

WRITE_PLAYER_STATS (ent);//[QBS]

//=======================================================================================

Ok that ends this Tutorial i hope you guys will find it useful adn post feedback ok bye...