484e8bbfc2
reworked network addresses to separate address family and connection type. this should make banning people more reliable, as well as simplifying a whole load of logic (no need to check for ipv4 AND ipv6). tcpconnect will keep trying to connect even if the connection wasn't instant, instead of giving up instantly. rewrote tcp connections quite a bit. sv_port_tcp now handles qtv+qizmo+http+ws+rtcbroker+tls equivalents. qtv_streamport is now a legacy cvar and now acts equivalently to sv_port_tcp (but still separate). rewrote screenshot and video capture code to use strides. this solves image-is-upside down issues with vulkan. ignore alt key in browser port. oh no! no more red text! oh no! no more alt-being-wrongly-down-and-being-unable-to-type-anything-without-forcing-alt-released! reworked audio decoder interface. now has clearly defined success/unavailable/end-of-file results. this should solve a whole load of issues with audio streaming. fixed various openal audio streaming issues too. openal also got some workarounds for emscripten's poor emulation. fixed ogg decoder to retain sync properly if seeked. updated menu_media a bit. now reads vorbis comments/id3v1 tags to get proper track names. also saves the playlist so you don't have to manually repopulate the list so it might actually be usable now (after how many years?) r_stains now defaults to 0, and is no longer enabled by presets. use decals if you want that sort of thing. added fs_noreexec cvar, so configs will not be reexeced on gamedir change. this also means defaults won't be reapplied, etc. added 'nvvk' renderer on windows, using nvidia's vulkan-inside-opengl gl extension. mostly just to see how much slower it is. fixed up the ftp server quite a lot. more complete, more compliant, and should do ipv6 properly to-boot. file transfers also threaded. fixed potential crash inside runclientphys. experimental sv_antilag=3 setting. totally untested. the aim is to avoid missing due to lagged knockbacks. may be expensive for the server. browser port's websockets support fixed. experimental support for webrtc ('works for me', requires a broker server). updated avplug(renamed to ffmpeg so people know what it is) to use ffmpeg 3.2.4 properly, with its new encoder api. should be much more robust... also added experimental audio decoder for game music etc (currently doesn't resample, so playback rates are screwed, disabled by cvar). git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5097 fc73d0e0-1445-4013-8a0c-d673dee63da5
952 lines
20 KiB
C
952 lines
20 KiB
C
#include "quakedef.h"
|
|
|
|
#ifndef CLIENTONLY
|
|
//FIXME: this is shitty old code.
|
|
//possible improvements: using a hash table for player names for faster logons
|
|
//threading logins
|
|
//using a real database...
|
|
|
|
#ifdef SVRANKING
|
|
|
|
typedef struct {
|
|
int ident;
|
|
int version;
|
|
int usedslots;
|
|
int leader;
|
|
int freeslot;
|
|
} rankfileheader_t;
|
|
|
|
//endian
|
|
#define NOENDIAN
|
|
#ifdef NOENDIAN
|
|
#define swaplong(l) l
|
|
#define swapfloat(f) f
|
|
#else
|
|
#define swaplong LittleLong
|
|
#define swapfloat LittleFloat
|
|
#endif
|
|
|
|
rankfileheader_t rankfileheader;
|
|
vfsfile_t *rankfile;
|
|
|
|
cvar_t rank_autoadd = CVARD("rank_autoadd", "1", "Automatically register players into the ranking system");
|
|
cvar_t rank_needlogin = CVARD("rank_needlogin", "0", "If set to 1, prohibits players from joining if they're not yet registered. This will require an external mechanism to register users, presumably via rcon.");
|
|
cvar_t rank_filename = CVARD("rank_filename", "", "Specifies which file to use as a rankings database. Enables the ranking system if set.");
|
|
cvar_t rank_parms_first = CVARD("rank_parms_first", "0", "Mod setting: first parm saved");
|
|
cvar_t rank_parms_last = CVARD("rank_parms_last", "31", "Mod setting: the index of the last parm to be saved. Clamped to 32.");
|
|
char rank_cvargroup[] = "server rankings";
|
|
|
|
#define RANKFILE_VERSION ((NUM_RANK_SPAWN_PARMS==32)?0:0x00000001)
|
|
#define RANKFILE_IDENT (('R'<<0)|('A'<<8)|('N'<<16)|('K'<<24))
|
|
|
|
static void READ_PLAYERSTATS(int x, rankstats_t *os)
|
|
{
|
|
#ifndef NOENDIAN
|
|
int i;
|
|
#endif
|
|
size_t result;
|
|
|
|
VFS_SEEK(rankfile, sizeof(rankfileheader_t)+sizeof(rankheader_t)+((x-1)*sizeof(rankinfo_t)));
|
|
result = VFS_READ(rankfile, os, sizeof(rankstats_t));
|
|
|
|
if (result != sizeof(rankstats_t))
|
|
Con_Printf("READ_PLAYERSTATS() fread: expected %lu, result was %u\n",(long unsigned int)sizeof(rankstats_t),(unsigned int)result);
|
|
|
|
#ifndef NOENDIAN
|
|
os->kills = swaplong(os->kills);
|
|
os->deaths = swaplong(os->deaths);
|
|
for (i = 0; i < NUM_RANK_SPAWN_PARMS; i++)
|
|
os->parm[i] = swapfloat(os->parm[i]);
|
|
os->timeonserver = swapfloat(os->timeonserver);
|
|
// os->flags1 = (os->flags1);
|
|
// os->trustlevel = (os->trustlevel);
|
|
// os->pad2 = (os->pad2);
|
|
// os->pad3 = (os->pad3);
|
|
#endif
|
|
}
|
|
|
|
static void WRITE_PLAYERSTATS(int x, rankstats_t *os)
|
|
{
|
|
#ifdef NOENDIAN
|
|
VFS_SEEK(rankfile, sizeof(rankfileheader_t)+sizeof(rankheader_t)+((x-1)*sizeof(rankinfo_t)));
|
|
VFS_WRITE(rankfile, os, sizeof(rankstats_t));
|
|
#else
|
|
rankstats_t ns;
|
|
int i;
|
|
ns.kills = swaplong(os->kills);
|
|
ns.deaths = swaplong(os->deaths);
|
|
for (i = 0; i < NUM_RANK_SPAWN_PARMS; i++)
|
|
ns.parm[i] = swapfloat(os->parm[i]);
|
|
ns.timeonserver = swapfloat(os->timeonserver);
|
|
ns.flags1 = (os->flags1);
|
|
ns.trustlevel = (os->trustlevel);
|
|
ns.pad2 = (os->pad2);
|
|
ns.pad3 = (os->pad3);
|
|
|
|
VFS_SEEK(rankfile, sizeof(rankfileheader_t)+sizeof(rankheader_t)+((x-1)*sizeof(rankinfo_t)));
|
|
VFS_WRITE(rankfile, &ns, sizeof(rankstats_t));
|
|
#endif
|
|
}
|
|
|
|
static void READ_PLAYERHEADER(int x, rankheader_t *oh)
|
|
{
|
|
size_t result;
|
|
|
|
VFS_SEEK(rankfile, sizeof(rankfileheader_t)+((x-1)*sizeof(rankinfo_t)));
|
|
|
|
result = VFS_READ(rankfile, oh, sizeof(rankheader_t));
|
|
|
|
if (result != sizeof(rankheader_t))
|
|
Con_Printf("READ_PLAYERHEADER() fread: expected %lu, result was %u\n",(long unsigned int)sizeof(rankheader_t),(unsigned int)result);
|
|
|
|
#ifndef NOENDIAN
|
|
oh->prev = swaplong(oh->prev); //score is held for convineance.
|
|
oh->next = swaplong(oh->next);
|
|
// strcpy(oh->name, oh->name);
|
|
oh->pwd = swaplong(oh->pwd);
|
|
oh->score = swapfloat(oh->score);
|
|
#endif
|
|
}
|
|
|
|
static void WRITE_PLAYERHEADER(int x, rankheader_t *oh)
|
|
{
|
|
#ifdef NOENDIAN
|
|
VFS_SEEK(rankfile, sizeof(rankfileheader_t)+((x-1)*sizeof(rankinfo_t)));
|
|
VFS_WRITE(rankfile, &oh, sizeof(rankheader_t));
|
|
#else
|
|
rankheader_t nh;
|
|
nh.prev = swaplong(oh->prev); //score is held for convineance.
|
|
nh.next = swaplong(oh->next);
|
|
Q_strncpyz(nh.name, oh->name, sizeof(nh.name));
|
|
nh.pwd = swaplong(oh->pwd);
|
|
nh.score = swapfloat(oh->score);
|
|
|
|
VFS_SEEK(rankfile, sizeof(rankfileheader_t)+((x-1)*sizeof(rankinfo_t)));
|
|
VFS_WRITE(rankfile, &nh, sizeof(rankheader_t));
|
|
#endif
|
|
}
|
|
|
|
static void READ_PLAYERINFO(int x, rankinfo_t *inf)
|
|
{
|
|
READ_PLAYERHEADER(x, &inf->h);
|
|
READ_PLAYERSTATS(x, &inf->s);
|
|
}
|
|
|
|
static void WRITEHEADER(void)
|
|
{
|
|
rankfileheader_t nh;
|
|
|
|
nh.ident = RANKFILE_IDENT;
|
|
nh.version = swaplong(RANKFILE_VERSION);
|
|
nh.usedslots = swaplong(rankfileheader.usedslots);
|
|
nh.leader = swaplong(rankfileheader.leader);
|
|
nh.freeslot = swaplong(rankfileheader.freeslot);
|
|
|
|
VFS_SEEK(rankfile, 0);
|
|
VFS_WRITE(rankfile, &nh, sizeof(rankfileheader_t));
|
|
}
|
|
//#define WRITEHEADER() {fseek(rankfile, 0, SEEK_SET);fwrite(&rankfileheader, 1, sizeof(rankfileheader_t), rankfile);}
|
|
|
|
#define NAMECMP(saved, against) Q_strncasecmp(saved, against, 31)
|
|
|
|
qboolean Rank_OpenRankings(void)
|
|
{
|
|
char syspath[MAX_OSPATH];
|
|
size_t result;
|
|
qboolean created;
|
|
if (!rankfile)
|
|
{
|
|
if (!*rank_filename.string)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!FS_NativePath(rank_filename.string, FS_GAMEONLY, syspath, sizeof(syspath)))
|
|
return false;
|
|
|
|
rankfile = FS_OpenVFS(rank_filename.string, "r+b", FS_GAMEONLY);
|
|
if (!rankfile) //hmm... try creating
|
|
{
|
|
rankfile = FS_OpenVFS(rank_filename.string, "w+b", FS_GAMEONLY);
|
|
created = true;
|
|
}
|
|
else
|
|
created = false;
|
|
if (!rankfile)
|
|
return false; //couldn't open file.
|
|
|
|
memset(&rankfileheader, 0, sizeof(rankfileheader));
|
|
|
|
VFS_SEEK(rankfile, 0);
|
|
result = VFS_READ(rankfile, &rankfileheader, sizeof(rankfileheader_t));
|
|
|
|
if (result != sizeof(rankfileheader_t))
|
|
Con_Printf("Rank_OpenRankings() fread: expected %lu, result was %u (%s)\n",(long unsigned int)sizeof(rankfileheader_t),(unsigned int)result, rank_filename.string);
|
|
|
|
rankfileheader.version = swaplong(rankfileheader.version);
|
|
rankfileheader.usedslots = swaplong(rankfileheader.usedslots);
|
|
rankfileheader.leader = swaplong(rankfileheader.leader);
|
|
rankfileheader.freeslot = swaplong(rankfileheader.freeslot);
|
|
|
|
if (!created && (rankfileheader.version != RANKFILE_VERSION || rankfileheader.ident != RANKFILE_IDENT))
|
|
{
|
|
Con_Printf("Rank file is version %i not %i\nEither delete the file or use an equivelent version of " FULLENGINENAME "\n", rankfileheader.version, RANKFILE_VERSION);
|
|
VFS_CLOSE(rankfile);
|
|
rankfile = NULL;
|
|
|
|
return false;
|
|
}
|
|
|
|
return true; //success.
|
|
}
|
|
return true; //already open
|
|
}
|
|
|
|
void LINKUN(int id)
|
|
{
|
|
int idnext, idprev;
|
|
rankheader_t hnext = {0}, hprev = {0}, info;
|
|
|
|
READ_PLAYERHEADER(id, &info);
|
|
|
|
idnext = info.next;
|
|
if (idnext)
|
|
READ_PLAYERHEADER(idnext, &hnext);
|
|
idprev = info.prev;
|
|
if (idprev)
|
|
READ_PLAYERHEADER(idprev, &hprev);
|
|
|
|
if (idnext)
|
|
{
|
|
hnext.prev = idprev;
|
|
WRITE_PLAYERHEADER(idnext, &hnext);
|
|
}
|
|
if (idprev)
|
|
{
|
|
hprev.next = idnext;
|
|
WRITE_PLAYERHEADER(idprev, &hprev);
|
|
}
|
|
else if (rankfileheader.leader == id) //ensure header is accurate
|
|
{
|
|
rankfileheader.leader = info.next;
|
|
WRITEHEADER();
|
|
}
|
|
else if (rankfileheader.freeslot == id)
|
|
{
|
|
rankfileheader.freeslot = info.next;
|
|
WRITEHEADER();
|
|
}
|
|
|
|
info.next = 0;
|
|
info.prev = 0;
|
|
WRITE_PLAYERHEADER(id, &info);
|
|
}
|
|
|
|
void LINKBEFORE(int bef, int id, rankheader_t *info)
|
|
{
|
|
int idnext, idprev;
|
|
rankheader_t hnext, hprev = {0};
|
|
|
|
if (!bef)
|
|
Sys_Error("Cannot link before no entry\n");
|
|
|
|
idnext = bef;
|
|
READ_PLAYERHEADER(idnext, &hnext);
|
|
idprev = hnext.prev;
|
|
if (idprev)
|
|
READ_PLAYERHEADER(idprev, &hprev);
|
|
|
|
//now we know the before and after entries.
|
|
|
|
hnext.prev = id;
|
|
WRITE_PLAYERHEADER(idnext, &hnext);
|
|
|
|
if (idprev)
|
|
{
|
|
hprev.next = id;
|
|
WRITE_PLAYERHEADER(idprev, &hprev);
|
|
}
|
|
else if (rankfileheader.leader == bef)
|
|
{
|
|
rankfileheader.leader = id;
|
|
WRITEHEADER();
|
|
}
|
|
else if (rankfileheader.freeslot == bef)
|
|
{
|
|
rankfileheader.freeslot = id;
|
|
WRITEHEADER();
|
|
}
|
|
info->next = idnext;
|
|
info->prev = idprev;
|
|
WRITE_PLAYERHEADER(id, info);
|
|
}
|
|
|
|
void LINKAFTER(int aft, int id, rankheader_t *info)
|
|
{
|
|
int idnext, idprev;
|
|
rankheader_t hnext = {0}, hprev = {0};
|
|
|
|
idprev = aft;
|
|
if (idprev)
|
|
{
|
|
READ_PLAYERHEADER(idprev, &hprev);
|
|
idnext = hprev.next;
|
|
}
|
|
else
|
|
idnext = rankfileheader.leader;
|
|
|
|
if (idnext)
|
|
READ_PLAYERHEADER(idnext, &hnext);
|
|
|
|
//now we know the before and after entries.
|
|
|
|
if (idnext)
|
|
{
|
|
hnext.prev = id;
|
|
WRITE_PLAYERHEADER(idnext, &hnext);
|
|
}
|
|
|
|
if (idprev)
|
|
{
|
|
hprev.next = id;
|
|
WRITE_PLAYERHEADER(idprev, &hprev);
|
|
}
|
|
else if (rankfileheader.leader == idnext)
|
|
{
|
|
rankfileheader.leader = id;
|
|
WRITEHEADER();
|
|
}
|
|
else if (rankfileheader.freeslot == idnext)
|
|
{
|
|
rankfileheader.freeslot = id;
|
|
WRITEHEADER();
|
|
}
|
|
info->next = idnext;
|
|
info->prev = idprev;
|
|
WRITE_PLAYERHEADER(id, info);
|
|
}
|
|
|
|
rankstats_t *Rank_GetPlayerStats(int id, rankstats_t *buffer) //returns the players persistant stats.
|
|
{
|
|
if (!Rank_OpenRankings())
|
|
return NULL;
|
|
if (!id)
|
|
{
|
|
Con_Printf("WARNING: Rank_GetPlayerStats with id 0\n");
|
|
memset(buffer, 0, sizeof(rankstats_t));
|
|
return NULL;
|
|
}
|
|
READ_PLAYERSTATS(id, buffer);
|
|
|
|
return buffer;
|
|
}
|
|
rankinfo_t *Rank_GetPlayerInfo(int id, rankinfo_t *buffer) //return stats + rankings.
|
|
{
|
|
if (!id)
|
|
{
|
|
Con_Printf("WARNING: Rank_GetPlayerInfo with id 0\n");
|
|
memset(buffer, 0, sizeof(rankinfo_t));
|
|
return NULL;
|
|
}
|
|
|
|
if (!Rank_OpenRankings())
|
|
return NULL;
|
|
|
|
READ_PLAYERINFO(id, buffer);
|
|
|
|
return buffer;
|
|
}
|
|
void Rank_SetPlayerStats(int id, rankstats_t *stats)
|
|
{
|
|
//rewrite to seek in a proper direction.
|
|
int nid;
|
|
rankheader_t rh, nh;
|
|
|
|
if (!id)
|
|
{
|
|
Con_Printf("WARNING: Rank_SetPlayerStats with id 0\n");
|
|
return;
|
|
}
|
|
|
|
//write
|
|
WRITE_PLAYERSTATS(id, stats);
|
|
|
|
//now re-sort.
|
|
READ_PLAYERHEADER(id, &nh);
|
|
nh.score = (stats->kills+1)/((float)stats->deaths+1);
|
|
//WRITE_PLAYERHEADER(id, &nh); //saved on link.
|
|
|
|
LINKUN(id);
|
|
|
|
nid = rankfileheader.leader;
|
|
if (!nid) //Hmm. First player!
|
|
{
|
|
LINKAFTER(0, id, &nh);
|
|
VFS_FLUSH(rankfile);
|
|
return;
|
|
}
|
|
while(nid)
|
|
{
|
|
READ_PLAYERHEADER(nid, &rh);
|
|
if (rh.score < nh.score)
|
|
{
|
|
LINKAFTER(rh.prev, id, &nh);
|
|
//LINKBEFORE(nid, id, &nh); //we are doing better than this guy.
|
|
VFS_FLUSH(rankfile);
|
|
return;
|
|
}
|
|
if (!rh.next)
|
|
{
|
|
LINKAFTER(nid, id, &nh); //Bum. We got to the end of the list and we are the WORST player!
|
|
VFS_FLUSH(rankfile);
|
|
return;
|
|
}
|
|
nid = rh.next;
|
|
}
|
|
}
|
|
|
|
int Rank_GetPlayerID(char *guid, const char *name, int pwd, qboolean allowadd, qboolean requirepasswordtobeset)
|
|
{
|
|
rankstats_t rs;
|
|
rankheader_t rh;
|
|
int id;
|
|
|
|
if (requirepasswordtobeset)
|
|
if (!pwd)
|
|
return 0;
|
|
|
|
if (!Rank_OpenRankings())
|
|
return 0;
|
|
|
|
id = rankfileheader.leader; //assumtion. A leader is more likly to be logging in than a begginer.
|
|
while(id)
|
|
{
|
|
READ_PLAYERHEADER(id, &rh);
|
|
if (!NAMECMP(rh.name, name))
|
|
{
|
|
if (rh.pwd == pwd || !rh.pwd)
|
|
{
|
|
if (!rh.pwd && requirepasswordtobeset)
|
|
return 0;
|
|
return id;
|
|
}
|
|
return 0;
|
|
}
|
|
id = rh.next;
|
|
}
|
|
|
|
if (!rank_autoadd.value || !allowadd)
|
|
return 0;
|
|
|
|
id = rankfileheader.freeslot;
|
|
if (id)
|
|
{
|
|
READ_PLAYERHEADER(id, &rh);
|
|
rankfileheader.freeslot = rh.next;
|
|
WRITEHEADER();
|
|
|
|
memset(&rh, 0, sizeof(rh));
|
|
Q_strncpyz(rh.name, name, sizeof(rh.name));
|
|
rh.pwd = pwd;
|
|
rh.prev = 0;
|
|
rh.next = rankfileheader.usedslots;
|
|
rankfileheader.usedslots = id;
|
|
WRITEHEADER();
|
|
|
|
WRITE_PLAYERHEADER(id, &rh);
|
|
|
|
memset(&rs, 0, sizeof(rs));
|
|
rs.trustlevel = 1;
|
|
Rank_SetPlayerStats(id, &rs);
|
|
|
|
VFS_FLUSH(rankfile);
|
|
return id;
|
|
}
|
|
|
|
id = ++rankfileheader.usedslots;
|
|
WRITEHEADER();
|
|
memset(&rh, 0, sizeof(rh));
|
|
Q_strncpyz(rh.name, name, sizeof(rh.name));
|
|
rh.prev = 0;
|
|
rh.pwd = pwd;
|
|
rh.next = 0;
|
|
WRITE_PLAYERHEADER(id, &rh);
|
|
|
|
memset(&rs, 0, sizeof(rs));
|
|
rs.trustlevel = 1;
|
|
WRITE_PLAYERSTATS(id, &rs);
|
|
|
|
Rank_SetPlayerStats(id, &rs);
|
|
|
|
VFS_FLUSH(rankfile);
|
|
return id;
|
|
}
|
|
|
|
void Rank_AddUser_f (void)
|
|
{
|
|
rankstats_t rs;
|
|
rankheader_t rh;
|
|
int id;
|
|
|
|
char *name = Cmd_Argv(1);
|
|
int pwd = atoi(Cmd_Argv(2));
|
|
int userlevel = atoi(Cmd_Argv(3));
|
|
char fixed[80];
|
|
|
|
if (Cmd_Argc() < 2)
|
|
{
|
|
Con_Printf("%s: <name> [pwd] [rights]\n", Cmd_Argv(0));
|
|
return;
|
|
}
|
|
//2
|
|
if (Cmd_Argc() >= 4)
|
|
{
|
|
if (userlevel >= Cmd_ExecLevel)
|
|
{
|
|
Con_Printf("You cannot add a user of equal or higher rank.\n");
|
|
return;
|
|
}
|
|
else if (userlevel < RESTRICT_MIN)
|
|
userlevel = RESTRICT_MIN;
|
|
}
|
|
if (Cmd_Argc() > 4)
|
|
{
|
|
Con_Printf("Too many arguments\n");
|
|
return;
|
|
}
|
|
|
|
SV_FixupName(name, fixed, sizeof(fixed));
|
|
name = fixed;
|
|
|
|
if (!Rank_OpenRankings())
|
|
{
|
|
Con_Printf("Failed to open rankings file.\n");
|
|
return;
|
|
}
|
|
|
|
id = rankfileheader.leader;
|
|
while(id)
|
|
{
|
|
READ_PLAYERHEADER(id, &rh);
|
|
if (!NAMECMP(rh.name, name))
|
|
{
|
|
Con_Printf("User %s already exists\n", name);
|
|
return;
|
|
}
|
|
id = rh.next;
|
|
}
|
|
|
|
id = rankfileheader.freeslot;
|
|
if (id)
|
|
{
|
|
READ_PLAYERHEADER(id, &rh);
|
|
rankfileheader.freeslot = rh.next;
|
|
WRITEHEADER();
|
|
|
|
memset(&rh, 0, sizeof(rh));
|
|
Q_strncpyz(rh.name, name, sizeof(rh.name));
|
|
rh.pwd = pwd;
|
|
rh.prev = 0;
|
|
rh.next = rankfileheader.usedslots;
|
|
rankfileheader.usedslots = id;
|
|
WRITEHEADER();
|
|
|
|
WRITE_PLAYERHEADER(id, &rh);
|
|
|
|
memset(&rs, 0, sizeof(rs));
|
|
rs.trustlevel = userlevel;
|
|
Rank_SetPlayerStats(id, &rs);
|
|
|
|
VFS_FLUSH(rankfile);
|
|
return;
|
|
}
|
|
|
|
id = ++rankfileheader.usedslots;
|
|
WRITEHEADER();
|
|
memset(&rh, 0, sizeof(rh));
|
|
Q_strncpyz(rh.name, name, sizeof(rh.name));
|
|
rh.prev = 0;
|
|
rh.pwd = pwd;
|
|
rh.next = 0;
|
|
WRITE_PLAYERHEADER(id, &rh);
|
|
|
|
memset(&rs, 0, sizeof(rs));
|
|
rs.trustlevel = userlevel;
|
|
#if NUM_RANK_SPAWN_PARMS>32
|
|
rs.created = rs.lastseen = time(NULL);
|
|
#endif
|
|
WRITE_PLAYERSTATS(id, &rs);
|
|
|
|
Rank_SetPlayerStats(id, &rs);
|
|
|
|
VFS_FLUSH(rankfile);
|
|
}
|
|
|
|
void Rank_SetPass_f (void)
|
|
{
|
|
rankheader_t rh;
|
|
char *name = Cmd_Argv(1);
|
|
int newpass = atoi(Cmd_Argv(2));
|
|
char fixed[80];
|
|
|
|
int id;
|
|
|
|
if (Cmd_Argc() != 3)
|
|
{
|
|
Con_Printf("setpass <name> <newpass>\n");
|
|
return;
|
|
}
|
|
|
|
if (!Rank_OpenRankings())
|
|
{
|
|
Con_Printf("Failed to open rankings file.\n");
|
|
return;
|
|
}
|
|
|
|
SV_FixupName(name, fixed, sizeof(fixed));
|
|
name = fixed;
|
|
|
|
id = rankfileheader.leader;
|
|
while(id)
|
|
{
|
|
READ_PLAYERHEADER(id, &rh);
|
|
if (!NAMECMP(rh.name, name))
|
|
{
|
|
Con_Printf("Changing passcode of user %s.\n", rh.name);
|
|
rh.pwd = newpass;
|
|
WRITE_PLAYERHEADER(id, &rh);
|
|
return;
|
|
}
|
|
id = rh.next;
|
|
}
|
|
}
|
|
|
|
int Rank_GetPass (char *name)
|
|
{
|
|
rankheader_t rh;
|
|
char fixed[80];
|
|
|
|
int id;
|
|
|
|
if (!Rank_OpenRankings())
|
|
{
|
|
Con_Printf("Failed to open rankings file.\n");
|
|
return 0;
|
|
}
|
|
|
|
SV_FixupName(name, fixed, sizeof(fixed));
|
|
name = fixed;
|
|
|
|
id = rankfileheader.leader;
|
|
while(id)
|
|
{
|
|
READ_PLAYERHEADER(id, &rh);
|
|
if (!NAMECMP(rh.name, name))
|
|
{
|
|
return rh.pwd;
|
|
}
|
|
id = rh.next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int Rank_Enumerate (unsigned int first, unsigned int last, void (*callback) (const rankinfo_t *ri)) //leader first.
|
|
{
|
|
rankinfo_t ri;
|
|
int id;
|
|
int num;
|
|
|
|
if (!Rank_OpenRankings())
|
|
{
|
|
Con_Printf("Failed to open rankings file.\n");
|
|
return 0;
|
|
}
|
|
|
|
id = rankfileheader.leader; //start at the leaders
|
|
num=1;
|
|
while(id)
|
|
{
|
|
READ_PLAYERINFO(id, &ri);
|
|
|
|
if (num >= last)
|
|
return num - first;
|
|
if (num >= first)
|
|
callback(&ri);
|
|
|
|
num++;
|
|
id = ri.h.next;
|
|
}
|
|
return num - first;
|
|
}
|
|
|
|
void Rank_RankingList_f (void)
|
|
{
|
|
rankinfo_t ri;
|
|
int id;
|
|
int num;
|
|
|
|
vfsfile_t *outfile;
|
|
|
|
if (!Rank_OpenRankings())
|
|
{
|
|
Con_Printf("Failed to open rankings file.\n");
|
|
return;
|
|
}
|
|
|
|
outfile = FS_OpenVFS("list.txt", "wb", FS_GAMEONLY);
|
|
if (!outfile)
|
|
{
|
|
Con_Printf("Couldn't open list.txt\n");
|
|
return;
|
|
}
|
|
|
|
VFS_PRINTF(outfile, "%5s: %32s, %5s %5s\r\n", "", "Name", "Kills", "Deaths");
|
|
|
|
id = rankfileheader.leader; //start at the leaders
|
|
num=1;
|
|
while(id)
|
|
{
|
|
READ_PLAYERINFO(id, &ri);
|
|
|
|
VFS_PRINTF(outfile, "%5i: %32s, %5i %5i\r\n", num, ri.h.name, ri.s.kills, ri.s.deaths);
|
|
|
|
num++;
|
|
id = ri.h.next;
|
|
}
|
|
|
|
VFS_CLOSE(outfile);
|
|
}
|
|
|
|
void Rank_RemoveID_f (void)
|
|
{
|
|
rankinfo_t ri;
|
|
int id;
|
|
int num;
|
|
int remnum;
|
|
|
|
if (Cmd_Argc() < 2)
|
|
{
|
|
Con_Printf("Removes a ranking entry.\nUse ranklist to find the entry number.");
|
|
return;
|
|
}
|
|
remnum = atoi(Cmd_Argv(1));
|
|
|
|
if (!Rank_OpenRankings())
|
|
{
|
|
Con_Printf("Failed to open rankings file.\n");
|
|
return;
|
|
}
|
|
|
|
id = rankfileheader.leader; //start at the leaders
|
|
num=1;
|
|
while(id)
|
|
{
|
|
READ_PLAYERINFO(id, &ri);
|
|
|
|
if (num == remnum)
|
|
{
|
|
LINKUN(id);
|
|
ri.h.next = rankfileheader.freeslot;
|
|
ri.h.prev = 0;
|
|
rankfileheader.freeslot = id;
|
|
WRITE_PLAYERHEADER(id, &ri.h);
|
|
WRITEHEADER();
|
|
VFS_FLUSH(rankfile);
|
|
|
|
Con_Printf("Client %s removed from rankings\n", ri.h.name);
|
|
return;
|
|
}
|
|
num++;
|
|
id = ri.h.next;
|
|
}
|
|
|
|
Con_Printf("Client %i not found\n", remnum);
|
|
}
|
|
|
|
void Rank_ListTop10_f (void)
|
|
{
|
|
rankinfo_t ri;
|
|
int id;
|
|
int num;
|
|
extern redirect_t sv_redirected;
|
|
|
|
if (!Rank_OpenRankings())
|
|
{
|
|
Con_Printf("Failed to open rankings file.\n");
|
|
return;
|
|
}
|
|
|
|
id = rankfileheader.leader; //start at the leaders
|
|
num=1;
|
|
while(id)
|
|
{
|
|
READ_PLAYERINFO(id, &ri);
|
|
|
|
if (sv_redirected)
|
|
Con_Printf("%2i: %5i %5i %s\n", num, ri.s.kills, ri.s.deaths, ri.h.name);
|
|
else
|
|
Con_Printf("%2i: %32s, %5i %5i\n", num, ri.h.name, ri.s.kills, ri.s.deaths);
|
|
if (num >= 10)
|
|
break;
|
|
|
|
num++;
|
|
id = ri.h.next;
|
|
}
|
|
if (num < 10)
|
|
Con_Printf("END\n");
|
|
}
|
|
|
|
void Rank_Find_f (void)
|
|
{
|
|
rankinfo_t ri;
|
|
int id;
|
|
|
|
char *match = Q_strlwr(Cmd_Argv(1));
|
|
|
|
if (!Rank_OpenRankings())
|
|
{
|
|
Con_Printf("Failed to open rankings file.\n");
|
|
return;
|
|
}
|
|
|
|
id = rankfileheader.leader; //start at the leaders
|
|
|
|
while(id)
|
|
{
|
|
READ_PLAYERINFO(id, &ri);
|
|
|
|
if (wildcmp(match, Q_strlwr(ri.h.name)))
|
|
{
|
|
Con_Printf("%i %s\n", id, ri.h.name);
|
|
}
|
|
|
|
id = ri.h.next;
|
|
}
|
|
}
|
|
|
|
void Rank_Refresh_f(void)
|
|
{
|
|
int i;
|
|
if (!Rank_OpenRankings())
|
|
{
|
|
Con_Printf("Failed to open rankings file.\n");
|
|
return;
|
|
}
|
|
|
|
for (i=0, host_client = svs.clients ; i<svs.allocated_client_slots ; i++, host_client++)
|
|
{
|
|
if (host_client->state != cs_spawned)
|
|
continue;
|
|
|
|
if (host_client->rankid)
|
|
{
|
|
rankstats_t rs = {0};
|
|
Rank_GetPlayerStats(host_client->rankid, &rs);
|
|
rs.timeonserver += realtime - host_client->stats_started;
|
|
host_client->stats_started = realtime;
|
|
rs.kills += host_client->kills;
|
|
rs.deaths += host_client->deaths;
|
|
host_client->kills=0;
|
|
host_client->deaths=0;
|
|
Rank_SetPlayerStats(host_client->rankid, &rs);
|
|
}
|
|
}
|
|
if (rankfile)
|
|
{
|
|
VFS_CLOSE(rankfile);
|
|
rankfile = NULL;
|
|
}
|
|
}
|
|
|
|
void Rank_RCon_f(void)
|
|
{
|
|
int gofor, num, id;
|
|
int newlevel;
|
|
rankstats_t rs = {0};
|
|
rankinfo_t ri;
|
|
|
|
if (!Rank_OpenRankings())
|
|
{
|
|
Con_Printf("Failed to open rankings file.\n");
|
|
return;
|
|
}
|
|
|
|
gofor = atoi(Cmd_Argv(1));
|
|
newlevel = atoi(Cmd_Argv(2));
|
|
if (newlevel >= Cmd_ExecLevel)
|
|
{
|
|
Con_Printf("You cannot promote a user to the same level as you\n");
|
|
return;
|
|
}
|
|
else if (newlevel < RESTRICT_MIN)
|
|
newlevel = RESTRICT_MIN;
|
|
|
|
//get user id
|
|
id = rankfileheader.leader; //start at the leaders
|
|
num = 1;
|
|
while(id)
|
|
{
|
|
READ_PLAYERINFO(id, &ri);
|
|
|
|
if (num == gofor)
|
|
{
|
|
//save new level
|
|
Rank_GetPlayerStats(id, &rs);
|
|
|
|
if (rs.trustlevel >= Cmd_ExecLevel)
|
|
{
|
|
Con_Printf("You cannot demote a higher or equal user.\n");
|
|
return;
|
|
}
|
|
rs.trustlevel = newlevel;
|
|
Rank_SetPlayerStats(id, &rs);
|
|
|
|
if (!ri.h.pwd && newlevel > 1)
|
|
Con_Printf("WARNING: user has no password set\n");
|
|
|
|
VFS_FLUSH(rankfile);
|
|
return;
|
|
}
|
|
|
|
num++;
|
|
id = ri.h.next;
|
|
}
|
|
Con_Printf("Couldn't find ranked user %i\n", gofor);
|
|
}
|
|
|
|
|
|
void Rank_RegisterCommands(void)
|
|
{
|
|
Cmd_AddCommand("ranklist", Rank_RankingList_f);
|
|
Cmd_AddCommand("ranktopten", Rank_ListTop10_f);
|
|
Cmd_AddCommand("rankfind", Rank_Find_f);
|
|
Cmd_AddCommand("rankremove", Rank_RemoveID_f);
|
|
Cmd_AddCommand("rankrefresh", Rank_Refresh_f);
|
|
|
|
Cmd_AddCommand("rankrconlevel", Rank_RCon_f);
|
|
|
|
Cmd_AddCommand("rankadd", Rank_AddUser_f);
|
|
Cmd_AddCommand("adduser", Rank_AddUser_f);
|
|
Cmd_AddCommand("setpass", Rank_SetPass_f);
|
|
|
|
Cvar_Register(&rank_autoadd, rank_cvargroup);
|
|
Cvar_Register(&rank_needlogin, rank_cvargroup);
|
|
Cvar_Register(&rank_filename, rank_cvargroup);
|
|
|
|
Cvar_Register(&rank_parms_first, rank_cvargroup);
|
|
Cvar_Register(&rank_parms_last, rank_cvargroup);
|
|
}
|
|
void Rank_Flush (void) //new game dir?
|
|
{
|
|
if (rankfile)
|
|
{
|
|
Rank_Refresh_f();
|
|
if (!rankfile)
|
|
return;
|
|
VFS_CLOSE(rankfile);
|
|
rankfile=NULL;
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|