fteqw/engine/client/fragstats.c
Spoike 2201b920c8 fix colormod
added frag message filter, and dedicated frag tracker.
added 'windowed consoles' for social-type stuff without depending upon csqc mods for it.
added in_deviceids command which allows listing/renumbering device ids.
slider widgets now support inverted ranges, so gamma selection isn't so weird.
fix top/bottom colour selection bug.
software banding feature is now part of the 'software' preset (now that it supports mipmaps).
support for loading .maps, and editing their brushes etc (with appropriate qc mod). 'map mymap.map' to use. expect problems with missing wads and replacement textures overriding them and messing up texture scales.
snd_inactive is now default.
fix threading issue with wavs, no more error from 0-sample-but-otherwise-valid wavs.
added -makeinstaller option to embed a manifest inside the exe (and icon).
the resulting program will insist on installing the game if its run from outside a valid basedir.
framegroup support for q1mdl.
textures are now loaded on multiple worker threads, for reduced load times. moo har har.
netgraph shows packet+byte rates too.
added r_lightstylescale, pretty similar to contrast, but doesn't impose any framerate cost, but may have overbrighting issues.
r_softwarebanding now works on q2bsp too.
fixed crepuscular lights.
gzip transfer encoding is performed while downloading, instead of inducing stalls.
FINALLY fix ezquake download compat issue (dimman found the issue).

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4851 fc73d0e0-1445-4013-8a0c-d673dee63da5
2015-04-14 23:12:17 +00:00

746 lines
18 KiB
C

#include "quakedef.h"
#define MAX_WEAPONS 64 //fixme: make dynamic.
typedef enum {
//one componant
ff_death,
ff_tkdeath,
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 *image;
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 died to a team member.
stat suisides; //times they were stupid.
} clienttotals[MAX_CLIENTS];
qboolean readcaps;
qboolean readkills;
statmessage_t *message;
} fragstats_t;
cvar_t r_tracker_frags = CVARD("r_tracker_frags", "0", "0: like vanilla quake\n1: shows only your kills/deaths\n2: shows all kills\n");
static fragstats_t fragstats;
int Stats_GetKills(int playernum)
{
return fragstats.clienttotals[playernum].kills;
}
int Stats_GetTKills(int playernum)
{
return fragstats.clienttotals[playernum].teamkills;
}
int Stats_GetDeaths(int playernum)
{
return fragstats.clienttotals[playernum].deaths;
}
int Stats_GetTouches(int playernum)
{
return fragstats.clienttotals[playernum].grabs;
}
int Stats_GetCaptures(int playernum)
{
return fragstats.clienttotals[playernum].caps;
}
qboolean Stats_HaveFlags(void)
{
return fragstats.readcaps;
}
qboolean Stats_HaveKills(void)
{
return fragstats.readkills;
}
void VARGS Stats_Message(char *msg, ...);
static char *Stats_GenTrackerImageString(char *in)
{ //images are of the form "foo \sg\ bar \q\"
//which should eg be remapped to: "foo ^Ue200 bar foo ^Ue201"
char res[256];
char image[MAX_QPATH];
char *outi;
char *out;
int i;
if (!in || !*in)
return NULL;
for (out = res; *in && out < res+sizeof(res)-10; )
{
if (*in == '\\')
{
in++;
for (outi = image; *in && outi < image+sizeof(image)-10; )
{
if (*in == '\\')
break;
*outi++ = *in++;
}
*outi = 0;
in++;
i = Font_RegisterTrackerImage(va("tracker/%s", image));
if (i)
{
char hexchars[16] = "0123456789abcdef";
*out++ = '^';
*out++ = 'U';
*out++ = hexchars[(i>>12)&15];
*out++ = hexchars[(i>>8)&15];
*out++ = hexchars[(i>>4)&15];
*out++ = hexchars[(i>>0)&15];
}
else
{
//just copy the short name over, not much else we can do.
for(outi = image; out < res+sizeof(res)-10 && *outi; )
*out++ = *outi++;
}
}
else
*out++ = *in++;
}
*out = 0;
return Z_StrDup(res);
}
void Stats_FragMessage(int p1, int wid, int p2, qboolean teamkill)
{
static const char *nonplayers[] = {
"BUG",
"(teamkill)",
"(suicide)",
"(death)",
"(unknown)",
"(fixme)",
"(fixme)"
};
char message[512];
console_t *tracker;
struct wt_s *w = &fragstats.weapontotals[wid];
const char *p1n = (p1 < 0)?nonplayers[-p1]:cl.players[p1].name;
const char *p2n = (p2 < 0)?nonplayers[-p2]:cl.players[p2].name;
int localplayer = (cl.spectator && cl.playerview[0].cam_locked)?cl.playerview[0].cam_spec_track:cl.playerview[0].playernum;
#define YOU_GOOD S_COLOR_GREEN
#define YOU_BAD S_COLOR_BLUE
#define TEAM_GOOD S_COLOR_GREEN
#define TEAM_BAD S_COLOR_RED
#define TEAM_VBAD S_COLOR_BLUE
#define TEAM_NEUTRAL S_COLOR_WHITE //enemy team thing that does not directly affect us
#define ENEMY_GOOD S_COLOR_RED
#define ENEMY_BAD S_COLOR_GREEN
#define ENEMY_NEUTRAL S_COLOR_WHITE
char *p1c = S_COLOR_WHITE;
char *p2c = S_COLOR_WHITE;
if (!r_tracker_frags.ival)
return;
if (r_tracker_frags.ival < 2)
if (p1 != localplayer && p2 != localplayer)
return;
if (teamkill)
{//team kills/suicides are always considered bad.
if (p1 == localplayer)
p1c = YOU_BAD;
else if (cl.teamplay && !strcmp(cl.players[p1].team, cl.players[localplayer].team))
p1c = TEAM_VBAD;
else
p1c = TEAM_NEUTRAL;
p2c = p1c;
}
else if (p1 == p2)
p1c = p2c = YOU_BAD;
else if (cl.teamplay && p1 >= 0 && p2 >= 0 && !strcmp(cl.players[p1].team, cl.players[p2].team))
p1c = p2c = TEAM_VBAD;
else
{
if (p2 >= 0)
{
//us/teammate killing is good - unless it was a teammate.
if (p2 == localplayer)
p2c = YOU_GOOD;
else if (cl.teamplay && !strcmp(cl.players[p2].team, cl.players[localplayer].team))
p2c = TEAM_GOOD;
else
p2c = ENEMY_GOOD;
}
if (p1 >= 0)
{
//us/teammate dying is bad.
if (p1 == localplayer)
p1c = YOU_BAD;
else if (cl.teamplay && !strcmp(cl.players[p1].team, cl.players[localplayer].team))
p1c = TEAM_BAD;
else
p1c = p2c;
}
}
Q_snprintfz(message, sizeof(message), "%s%s ^7%s %s%s\n", p1c, p1n, w->image?w->image:w->abrev, p2c, p2n);
tracker = Con_FindConsole("tracker");
if (!tracker)
{
tracker = Con_Create("tracker", CONF_HIDDEN|CONF_NOTIFY|CONF_NOTIFY_RIGHT|CONF_NOTIFY_BOTTOM);
//this stuff should be configurable
tracker->notif_l = tracker->maxlines = 8;
tracker->notif_x = 0.5;
tracker->notif_y = 0.333;
tracker->notif_w = 1-tracker->notif_x;
tracker->notif_t = 4;
tracker->notif_fade = 1;
}
Con_PrintCon(tracker, message, tracker->parseflags);
}
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.playerview[0].playernum));
u2 = (p2 == (cl.playerview[0].playernum));
//messages are killed weapon killer
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++;
Stats_FragMessage(p1, wid, -3, true);
if (u1)
Stats_Message("You died\n%s deaths: %i\n", fragstats.weapontotals[wid].fullname, fragstats.weapontotals[wid].owndeaths);
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++;
Stats_FragMessage(p1, wid, -2, true);
if (u1)
Stats_Message("You killed your own dumb self\n%s suicides: %i (%i)\n", fragstats.weapontotals[wid].fullname, fragstats.weapontotals[wid].ownsuicides, fragstats.weapontotals[wid].suicides);
break;
case ff_bonusfrag:
if (u1)
fragstats.weapontotals[wid].ownkills++;
fragstats.weapontotals[wid].kills++;
fragstats.clienttotals[p1].kills++;
fragstats.totalkills++;
Stats_FragMessage(-4, wid, p1, false);
if (u1)
Stats_Message("You killed someone\n%s kills: %i\n", fragstats.weapontotals[wid].fullname, fragstats.weapontotals[wid].ownkills);
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++;
Stats_FragMessage(-1, wid, p1, true);
if (u1)
Stats_Message("You killed your teammate\n%s teamkills: %i\n", fragstats.weapontotals[wid].fullname, fragstats.weapontotals[wid].ownteamkills);
break;
case ff_flagtouch:
fragstats.clienttotals[p1].grabs++;
fragstats.totaltouches++;
if (u1)
{
Stats_Message("You grabbed the flag\nflag grabs: %i (%i)\n", fragstats.clienttotals[p1].grabs, fragstats.totaltouches);
}
break;
case ff_flagcaps:
fragstats.clienttotals[p1].caps++;
fragstats.totalcaps++;
if (u1)
{
Stats_Message("You captured the flag\nflag captures: %i (%i)\n", fragstats.clienttotals[p1].caps, fragstats.totalcaps);
}
break;
case ff_flagdrops:
fragstats.clienttotals[p1].drops++;
fragstats.totaldrops++;
if (u1)
{
Stats_Message("You dropped the flag\nflag drops: %i (%i)\n", fragstats.clienttotals[p1].drops, fragstats.totaldrops);
}
break;
//p1 died, p2 killed
case ff_frags:
case ff_fragedby:
fragstats.weapontotals[wid].kills++;
fragstats.clienttotals[p1].deaths++;
fragstats.totaldeaths++;
if (u1)
{
fragstats.weapontotals[wid].owndeaths++;
Stats_Message("%s killed you\n%s deaths: %i (%i/%i)\n", cl.players[p2].name, fragstats.weapontotals[wid].fullname, fragstats.clienttotals[p2].owndeaths, fragstats.weapontotals[wid].owndeaths, fragstats.totaldeaths);
}
fragstats.clienttotals[p2].kills++;
fragstats.totalkills++;
if (u2)
{
fragstats.weapontotals[wid].ownkills++;
Stats_Message("You killed %s\n%s kills: %i (%i/%i)\n", cl.players[p1].name, fragstats.weapontotals[wid].fullname, fragstats.clienttotals[p2].kills, fragstats.weapontotals[wid].kills, fragstats.totalkills);
}
Stats_FragMessage(p1, wid, p2, false);
break;
case ff_tkdeath:
//killed by a teammate, but we don't know who
//kinda useless, but this is all some mods give us
fragstats.weapontotals[wid].teamkills++;
fragstats.weapontotals[wid].kills++;
fragstats.totalkills++; //its a kill, but we don't know who from
fragstats.totalteamkills++;
if (u1)
fragstats.weapontotals[wid].owndeaths++;
fragstats.clienttotals[p1].teamdeaths++;
fragstats.clienttotals[p1].deaths++;
fragstats.totaldeaths++;
Stats_FragMessage(p1, wid, -1, true);
if (u1)
Stats_Message("Your teammate killed you\n%s deaths: %i\n", fragstats.weapontotals[wid].fullname, fragstats.weapontotals[wid].owndeaths);
break;
case ff_tkills:
case ff_tkilledby:
//p1 killed by p2 (kills is already inverted)
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++;
Stats_FragMessage(p1, wid, p2, false);
if (u1)
Stats_Message("%s killed you\n%s deaths: %i (%i/%i)\n", cl.players[p2].name, fragstats.weapontotals[wid].fullname, fragstats.clienttotals[p2].owndeaths, fragstats.weapontotals[wid].owndeaths, fragstats.totaldeaths);
if (u2)
Stats_Message("You killed %s\n%s kills: %i (%i/%i)\n", cl.players[p1].name, fragstats.weapontotals[wid].fullname, fragstats.clienttotals[p2].kills, fragstats.weapontotals[wid].kills, fragstats.totalkills);
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;
ms->next = fragstats.message;
fragstats.message = ms;
//we have a message type, save the fact that we have it.
if (type == ff_flagtouch || type == ff_flagcaps || type == ff_flagdrops)
fragstats.readcaps = true;
if (type == ff_frags || type == ff_fragedby)
fragstats.readkills = true;
}
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);
if (fragstats.weapontotals[i].image) Z_Free(fragstats.weapontotals[i].image);
}
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;
char oend;
Stats_Clear();
strcpy(filename, name);
COM_DefaultExtension(filename, ".dat", sizeof(filename));
file = COM_LoadTempFile(filename, NULL);
if (!file || !*file)
{
Con_DPrintf("Couldn't load %s\n", filename);
return;
}
else
Con_DPrintf("Loaded %s\n", filename);
oend = 1;
for (;*file;)
{
if (!oend)
break;
for (end = file; *end && *end != '\n'; end++)
;
oend = *end;
*end = '\0';
Cmd_TokenizeString(file, true, false);
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));
fragstats.weapontotals[wid].image = Stats_GenTrackerImageString(Cmd_Argv(5));
}
}
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_TEAMKILLED_UNKNOWN")) {fftype = ff_tkdeath;}
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 < cl.allocated_client_slots; 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;
}
qboolean Stats_ParsePrintLine(char *line)
{
statmessage_t *ms;
int p1;
int p2;
char *m2;
p1 = Stats_ExtractName(&line);
if (p1<0) //reject it.
{
return false;
}
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 (!ms->msgpart2)
continue;
if (!qm_stricmp(ms->msgpart2, m2))
{
Stats_Evaluate(ms->type, ms->wid, p1, p2);
return true; //done.
}
}
else
{ //one player
Stats_Evaluate(ms->type, ms->wid, p1, p1);
return true; //done.
}
}
}
return false;
}
void Stats_NewMap(void)
{
Stats_LoadFragFile("fragfile");
}