#include "quakedef.h" #define MAX_WEAPONS 64 //fixme: make dynamic. typedef enum { //one componant ff_death, ff_suicide, ff_bonusfrag, ff_tkbonus, ff_flagtouch, ff_flagcaps, ff_flagdrops, //two componant ff_frags, //must be the first of the two componant ff_fragedby, ff_tkills, ff_tkilledby, } fragfilemsgtypes_t; typedef struct statmessage_s { fragfilemsgtypes_t type; int wid; char *msgpart1; char *msgpart2; struct statmessage_s *next; } statmessage_t; typedef unsigned short stat; typedef struct { stat totaldeaths; stat totalsuicides; stat totalteamkills; stat totalkills; stat totaltouches; stat totalcaps; stat totaldrops; //I was going to keep track of kills with a certain gun - too much memory //track only your own and total weapon kills rather than per client struct wt_s { //these include you. stat kills; stat teamkills; stat suicides; stat ownkills; stat owndeaths; stat ownteamkills; stat ownteamdeaths; stat ownsuicides; char *fullname; char *abrev; char *codename; } weapontotals[MAX_WEAPONS]; struct ct_s { stat caps; //times they captured the flag stat drops; //times lost the flag stat grabs; //times grabbed flag stat owndeaths; //times you killed them stat ownkills; //times they killed you stat deaths; //times they died (including by you) stat kills; //times they killed (including by you) stat teamkills; //times they killed a team member. stat teamdeaths; //times they killed a team member. stat suisides; //times they were stupid. } clienttotals[MAX_CLIENTS]; statmessage_t *message; } fragstats_t; static fragstats_t fragstats; void Stats_Evaluate(fragfilemsgtypes_t mt, int wid, int p1, int p2) { qboolean u1; qboolean u2; if (mt == ff_frags || mt == ff_tkills) { int tmp = p1; p1 = p2; p2 = tmp; } u1 = (p1 == (cl.playernum[0]&127)); u2 = (p2 == (cl.playernum[0]&127)); switch(mt) { case ff_death: if (u1) { fragstats.weapontotals[wid].owndeaths++; fragstats.weapontotals[wid].ownkills++; } fragstats.weapontotals[wid].kills++; fragstats.clienttotals[p1].deaths++; fragstats.totaldeaths++; break; case ff_suicide: if (u1) { fragstats.weapontotals[wid].ownsuicides++; fragstats.weapontotals[wid].owndeaths++; fragstats.weapontotals[wid].ownkills++; } fragstats.weapontotals[wid].suicides++; fragstats.weapontotals[wid].kills++; fragstats.clienttotals[p1].suisides++; fragstats.clienttotals[p1].deaths++; fragstats.totalsuicides++; fragstats.totaldeaths++; break; case ff_bonusfrag: if (u1) fragstats.weapontotals[wid].ownkills++; fragstats.weapontotals[wid].kills++; fragstats.clienttotals[p1].kills++; fragstats.totalkills++; break; case ff_tkbonus: if (u1) fragstats.weapontotals[wid].ownkills++; fragstats.weapontotals[wid].kills++; fragstats.clienttotals[p1].kills++; fragstats.totalkills++; if (u1) fragstats.weapontotals[wid].ownteamkills++; fragstats.weapontotals[wid].teamkills++; fragstats.clienttotals[p1].teamkills++; fragstats.totalteamkills++; break; case ff_flagtouch: fragstats.clienttotals[p1].grabs++; fragstats.totaltouches++; break; case ff_flagcaps: fragstats.clienttotals[p1].caps++; fragstats.totalcaps++; break; case ff_flagdrops: fragstats.clienttotals[p1].drops++; fragstats.totaldrops++; break; //p1 died, p2 killed case ff_frags: case ff_fragedby: fragstats.weapontotals[wid].kills++; if (u1) fragstats.weapontotals[wid].owndeaths++; fragstats.clienttotals[p1].deaths++; fragstats.totaldeaths++; if (u2) fragstats.weapontotals[wid].ownkills++; fragstats.clienttotals[p2].kills++; fragstats.totalkills++; break; case ff_tkills: case ff_tkilledby: fragstats.weapontotals[wid].teamkills++; fragstats.weapontotals[wid].kills++; if (u1) { fragstats.weapontotals[wid].ownteamdeaths++; fragstats.weapontotals[wid].owndeaths++; } fragstats.clienttotals[p1].teamdeaths++; fragstats.clienttotals[p1].deaths++; fragstats.totaldeaths++; if (u2) { fragstats.weapontotals[wid].ownkills++; fragstats.weapontotals[wid].ownkills++; } fragstats.clienttotals[p2].teamkills++; fragstats.clienttotals[p2].kills++; fragstats.totalkills++; fragstats.totalteamkills++; break; } } static int Stats_FindWeapon(char *codename, qboolean create) { int i; if (!strcmp(codename, "NONE")) return 0; if (!strcmp(codename, "NULL")) return 0; if (!strcmp(codename, "NOWEAPON")) return 0; for (i = 1; i < MAX_WEAPONS; i++) { if (!fragstats.weapontotals[i].codename) { fragstats.weapontotals[i].codename = Z_Malloc(strlen(codename)+1); strcpy(fragstats.weapontotals[i].codename, codename); return i; } if (!stricmp(fragstats.weapontotals[i].codename, codename)) { if (create) return -2; return i; } } return -1; } static void Stats_StatMessage(fragfilemsgtypes_t type, int wid, char *token1, char *token2) { statmessage_t *ms; char *t; ms = Z_Malloc(sizeof(statmessage_t) + strlen(token1)+1 + (token2 && *token2?strlen(token2)+1:0)); t = (char *)(ms+1); ms->msgpart1 = t; strcpy(t, token1); if (token2 && *token2) { t += strlen(t)+1; ms->msgpart2 = t; strcpy(t, token2); } ms->type = type; ms->wid = wid; } static void Stats_Clear(void) { int i; statmessage_t *ms; while (fragstats.message) { ms = fragstats.message; fragstats.message = ms->next; Z_Free(ms); } for (i = 1; i < MAX_WEAPONS; i++) { if (fragstats.weapontotals[i].codename) Z_Free(fragstats.weapontotals[i].codename); if (fragstats.weapontotals[i].fullname) Z_Free(fragstats.weapontotals[i].fullname); if (fragstats.weapontotals[i].abrev) Z_Free(fragstats.weapontotals[i].abrev); } memset(&fragstats, 0, sizeof(fragstats)); } #define Z_Copy(tk) tz = Z_Malloc(strlen(tk)+1);strcpy(tz, tk) //remember the braces static void Stats_LoadFragFile(char *name) { char filename[MAX_QPATH]; char *file; char *end; char *tk, *tz; Stats_Clear(); strcpy(filename, name); COM_DefaultExtension(filename, ".dat"); file = COM_LoadTempFile(filename); if (!file) return; for (;;) { for (end = file; *end && *end != '\n'; end++) ; *end = '\0'; if (!*file) break; Cmd_TokenizeString(file); file = end+1; if (!Cmd_Argc()) continue; tk = Cmd_Argv(0); if (!stricmp(tk, "#fragfile")) { tk = Cmd_Argv(1); if (!stricmp(tk, "version")) {} else if (!stricmp(tk, "gamedir")) {} else Con_Printf("Unrecognised #meta \"%s\"\n", tk); } else if (!stricmp(tk, "#meta")) { tk = Cmd_Argv(1); if (!stricmp(tk, "title")) {} else if (!stricmp(tk, "description")) {} else if (!stricmp(tk, "author")) {} else if (!stricmp(tk, "email")) {} else if (!stricmp(tk, "webpage")) {} else {Con_Printf("Unrecognised #meta \"%s\"\n", tk);continue;} } else if (!stricmp(tk, "#define")) { tk = Cmd_Argv(1); if (!stricmp(tk, "weapon_class") || !stricmp(tk, "wc")) { int wid; tk = Cmd_Argv(2); wid = Stats_FindWeapon(tk, true); if (wid == -1) {Con_Printf("Too many weapon definitions. The max is %i\n", MAX_WEAPONS);continue;} else if (wid < -1) {Con_Printf("Weapon \"%s\" is already defined\n", tk);continue;} else { fragstats.weapontotals[wid].fullname = Z_Copy(Cmd_Argv(3)); fragstats.weapontotals[wid].abrev = Z_Copy(Cmd_Argv(4)); } } else if (!stricmp(tk, "obituary") || !stricmp(tk, "obit")) { int fftype; tk = Cmd_Argv(2); if (!stricmp(tk, "PLAYER_DEATH")) {fftype = ff_death;} else if (!stricmp(tk, "PLAYER_SUICIDE")) {fftype = ff_suicide;} else if (!stricmp(tk, "X_FRAGS_UNKNOWN")) {fftype = ff_bonusfrag;} else if (!stricmp(tk, "X_TEAMKILLS_UNKNOWN")) {fftype = ff_tkbonus;} else if (!stricmp(tk, "X_FRAGS_Y")) {fftype = ff_frags;} else if (!stricmp(tk, "X_FRAGGED_BY_Y")) {fftype = ff_fragedby;} else if (!stricmp(tk, "X_TEAMKILLS_Y")) {fftype = ff_tkills;} else if (!stricmp(tk, "X_TEAMKILLED_BY_Y")) {fftype = ff_tkilledby;} else {Con_Printf("Unrecognised obituary \"%s\"\n", tk);continue;} Stats_StatMessage(fftype, Stats_FindWeapon(Cmd_Argv(3), false), Cmd_Argv(4), Cmd_Argv(5)); } else if (!stricmp(tk, "flag_alert") || !stricmp(tk, "flag_msg")) { int fftype; tk = Cmd_Argv(2); if (!stricmp(tk, "X_TOUCHES_FLAG")) {fftype = ff_flagtouch;} else if (!stricmp(tk, "X_GETS_FLAG")) {fftype = ff_flagtouch;} else if (!stricmp(tk, "X_TAKES_FLAG")) {fftype = ff_flagtouch;} else if (!stricmp(tk, "X_CAPTURES_FLAG")) {fftype = ff_flagcaps;} else if (!stricmp(tk, "X_CAPS_FLAG")) {fftype = ff_flagcaps;} else if (!stricmp(tk, "X_SCORES")) {fftype = ff_flagcaps;} else if (!stricmp(tk, "X_DROPS_FLAG")) {fftype = ff_flagdrops;} else if (!stricmp(tk, "X_FUMBLES_FLAG")) {fftype = ff_flagdrops;} else if (!stricmp(tk, "X_LOSES_FLAG")) {fftype = ff_flagdrops;} else {Con_Printf("Unrecognised flag alert \"%s\"\n", tk);continue;} Stats_StatMessage(fftype, 0, Cmd_Argv(3), NULL); } else {Con_Printf("Unrecognised directive \"%s\"\n", tk);continue;} } else {Con_Printf("Unrecognised directive \"%s\"\n", tk);continue;} } } int qm_strcmp(char *s1, char *s2)//not like strcmp at all... { while(*s1) { if ((*s1++&0x7f)!=(*s2++&0x7f)) return 1; } return 0; } int qm_stricmp(char *s1, char *s2)//not like strcmp at all... { int c1,c2; while(*s1) { c1 = *s1++&0x7f; c2 = *s2++&0x7f; if (c1 >= 'A' && c1 <= 'Z') c1 = c1 - 'A' + 'a'; if (c2 >= 'A' && c2 <= 'Z') c2 = c2 - 'A' + 'a'; if (c1!=c2) return 1; } return 0; } static int Stats_ExtractName(char **line) { int i; int bm; int ml = 0; int l; bm = -1; for (i = 0; i < MAX_CLIENTS; i++) { if (!qm_strcmp(cl.players[i].name, *line)) { l = strlen(cl.players[i].name); if (l > ml) { bm = i; ml = l; } } } *line += ml; return bm; } void Stats_ParsePrintLine(char *line) { statmessage_t *ms; int p1; int p2; char *m2; p1 = Stats_ExtractName(&line); if (p1<0) //reject it. return; for (ms = fragstats.message; ms; ms = ms->next) { if (!qm_stricmp(ms->msgpart1, line)) { if (ms->type >= ff_frags) { //two players m2 = line + strlen(ms->msgpart1); p2 = Stats_ExtractName(&m2); if (!qm_stricmp(ms->msgpart2, m2)) { Stats_Evaluate(ms->type, ms->wid, p1, p2); return; //done. } } else { //one player Stats_Evaluate(ms->type, ms->wid, p1, p1); return; //done. } } } } void Stats_NewMap(void) { Stats_LoadFragFile("fragfile"); }