#include "qwsvdef.h" #ifndef CLIENTONLY #if defined(_WIN32) && !defined(MINGW) #define inline //_inline //fix for stupid VC #elif defined(CLANG) #define inline // fix for stupid clang #endif #ifdef SVRANKING typedef struct { int ident; int version; int usedslots; int leader; int freeslot; } rankfileheader_t; //endian #define swaplong LittleLong #define swapfloat LittleFloat rankfileheader_t rankfileheader; FILE *rankfile; cvar_t rank_autoadd = SCVAR("rank_autoadd", "1"); cvar_t rank_needlogin = SCVAR("rank_needlogin", "0"); cvar_t rank_filename = SCVAR("rank_filename", ""); char rank_cvargroup[] = "server rankings"; #define RANKFILE_VERSION ((NUM_RANK_SPAWN_PARMS==32)?0:0x00000001) #define RANKFILE_IDENT *(int*)"RANK" void inline READ_PLAYERSTATS(int x, rankstats_t *os) { int i; fseek(rankfile, sizeof(rankfileheader_t)+sizeof(rankheader_t)+((x-1)*sizeof(rankinfo_t)), SEEK_SET); fread(os, sizeof(rankstats_t), 1, rankfile); 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); } void inline WRITE_PLAYERSTATS(int x, rankstats_t *os) { rankstats_t ns; int i; fseek(rankfile, sizeof(rankfileheader_t)+sizeof(rankheader_t)+((x-1)*sizeof(rankinfo_t)), SEEK_SET); 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); fwrite(&ns, sizeof(rankstats_t), 1, rankfile); } void inline READ_PLAYERHEADER(int x, rankheader_t *oh) { fseek(rankfile, sizeof(rankfileheader_t)+((x-1)*sizeof(rankinfo_t)), SEEK_SET); fread(oh, sizeof(rankheader_t), 1, rankfile); 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); } void inline WRITE_PLAYERHEADER(int x, rankheader_t *oh) { rankheader_t nh; fseek(rankfile, sizeof(rankfileheader_t)+((x-1)*sizeof(rankinfo_t)), SEEK_SET); 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); fwrite(&nh, sizeof(rankheader_t), 1, rankfile); } void inline READ_PLAYERINFO(int x, rankinfo_t *inf) { READ_PLAYERHEADER(x, &inf->h); READ_PLAYERSTATS(x, &inf->s); } void inline 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); fseek(rankfile, 0, SEEK_SET); fwrite(&nh, sizeof(rankfileheader_t), 1, rankfile); } //#define WRITEHEADER() {fseek(rankfile, 0, SEEK_SET);fwrite(&rankfileheader, sizeof(rankfileheader_t), 1, rankfile);} #define NAMECMP(saved, against) Q_strncasecmp(saved, against, 31) qboolean Rank_OpenRankings(void) { char syspath[MAX_OSPATH]; qboolean created; if (!rankfile) { if (!*rank_filename.string) { return false; } if (!FS_NativePath(rank_filename.string, FS_GAMEONLY, syspath, sizeof(syspath))) return false; rankfile = fopen(syspath, "r+b"); if (!rankfile) //hmm... try creating { rankfile = fopen(syspath, "w+b"); created = true; } else created = false; if (!rankfile) return false; //couldn't open file. memset(&rankfileheader, 0, sizeof(rankfileheader)); fseek(rankfile, 0, SEEK_SET); fread(&rankfileheader, sizeof(rankfileheader_t), 1, rankfile); 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 " DISTRIBUTION "\n", rankfileheader.version, RANKFILE_VERSION); fclose(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); fflush(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. fflush(rankfile); return; } if (!rh.next) { LINKAFTER(nid, id, &nh); //Bum. We got to the end of the list and we are the WORST player! fflush(rankfile); return; } nid = rh.next; } } int Rank_GetPlayerID(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); fflush(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); fflush(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)); if (Cmd_Argc() < 2) { Con_Printf("%s: [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, name, sizeof(name)); 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); fflush(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; WRITE_PLAYERSTATS(id, &rs); Rank_SetPlayerStats(id, &rs); fflush(rankfile); } void Rank_SetPass_f (void) { rankheader_t rh; char *name = Cmd_Argv(1); int newpass = atoi(Cmd_Argv(2)); int id; if (Cmd_Argc() != 3) { Con_Printf("setpass \n"); return; } if (!Rank_OpenRankings()) { Con_Printf("Failed to open rankings file.\n"); return; } SV_FixupName(name, name, sizeof(name)); 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; int id; if (!Rank_OpenRankings()) { Con_Printf("Failed to open rankings file.\n"); return 0; } SV_FixupName(name, name, sizeof(name)); 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; FILE *outfile; if (!Rank_OpenRankings()) { Con_Printf("Failed to open rankings file.\n"); return; } outfile = fopen("list.txt", "wb"); fprintf(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); fprintf(outfile, "%5i: %32s, %5i %5i\r\n", num, ri.h.name, ri.s.kills, ri.s.deaths); num++; id = ri.h.next; } fclose(outfile); } void Rank_Remove_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(); fflush(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 = 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 (strstr(ri.h.name, match)) { 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 ; istate != 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) { fclose(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"); fflush(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_Remove_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); } void Rank_Flush (void) //new game dir? { if (rankfile) { Rank_Refresh_f(); if (!rankfile) return; fclose(rankfile); rankfile=NULL; } } #endif #endif