mirror of
https://github.com/yquake2/yquake2remaster.git
synced 2025-01-25 02:11:19 +00:00
61400d1ae8
The input system backend was once used in the client and the renderers, but for some years now it has been an integral part of the client only. Move it there.
683 lines
14 KiB
C
683 lines
14 KiB
C
/*
|
|
* Copyright (C) 1997-2001 Id Software, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*
|
|
* =======================================================================
|
|
*
|
|
* This file implements the camera, e.g the player's view
|
|
*
|
|
* =======================================================================
|
|
*/
|
|
|
|
#include "header/client.h"
|
|
#include "input/header/input.h"
|
|
|
|
/* development tools for weapons */
|
|
int gun_frame;
|
|
struct model_s *gun_model;
|
|
|
|
cvar_t *crosshair;
|
|
cvar_t *crosshair_3d;
|
|
cvar_t *crosshair_3d_glow;
|
|
|
|
cvar_t *crosshair_scale;
|
|
cvar_t *cl_testparticles;
|
|
cvar_t *cl_testentities;
|
|
cvar_t *cl_testlights;
|
|
cvar_t *cl_testblend;
|
|
cvar_t *crosshair_3d_glow_r;
|
|
cvar_t *crosshair_3d_glow_g;
|
|
cvar_t *crosshair_3d_glow_b;
|
|
|
|
cvar_t *cl_stats;
|
|
|
|
int r_numdlights;
|
|
dlight_t r_dlights[MAX_DLIGHTS];
|
|
|
|
int r_numentities;
|
|
entity_t r_entities[MAX_ENTITIES];
|
|
|
|
int r_numparticles;
|
|
particle_t r_particles[MAX_PARTICLES];
|
|
|
|
lightstyle_t r_lightstyles[MAX_LIGHTSTYLES];
|
|
|
|
char cl_weaponmodels[MAX_CLIENTWEAPONMODELS][MAX_QPATH];
|
|
int num_cl_weaponmodels;
|
|
|
|
void V_Render3dCrosshair(void);
|
|
|
|
/*
|
|
* Specifies the model that will be used as the world
|
|
*/
|
|
void
|
|
V_ClearScene(void)
|
|
{
|
|
r_numdlights = 0;
|
|
r_numentities = 0;
|
|
r_numparticles = 0;
|
|
}
|
|
|
|
void
|
|
V_AddEntity(entity_t *ent)
|
|
{
|
|
if (r_numentities >= MAX_ENTITIES)
|
|
{
|
|
return;
|
|
}
|
|
|
|
r_entities[r_numentities++] = *ent;
|
|
}
|
|
|
|
void
|
|
V_AddParticle(vec3_t org, unsigned int color, float alpha)
|
|
{
|
|
particle_t *p;
|
|
|
|
if (r_numparticles >= MAX_PARTICLES)
|
|
{
|
|
return;
|
|
}
|
|
|
|
p = &r_particles[r_numparticles++];
|
|
VectorCopy(org, p->origin);
|
|
p->color = color;
|
|
p->alpha = alpha;
|
|
}
|
|
|
|
void
|
|
V_AddLight(vec3_t org, float intensity, float r, float g, float b)
|
|
{
|
|
dlight_t *dl;
|
|
|
|
if (r_numdlights >= MAX_DLIGHTS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dl = &r_dlights[r_numdlights++];
|
|
VectorCopy(org, dl->origin);
|
|
dl->intensity = intensity;
|
|
dl->color[0] = r;
|
|
dl->color[1] = g;
|
|
dl->color[2] = b;
|
|
}
|
|
|
|
void
|
|
V_AddLightStyle(int style, float r, float g, float b)
|
|
{
|
|
lightstyle_t *ls;
|
|
|
|
if ((style < 0) || (style > MAX_LIGHTSTYLES))
|
|
{
|
|
Com_Error(ERR_DROP, "Bad light style %i", style);
|
|
}
|
|
|
|
ls = &r_lightstyles[style];
|
|
|
|
ls->white = r + g + b;
|
|
ls->rgb[0] = r;
|
|
ls->rgb[1] = g;
|
|
ls->rgb[2] = b;
|
|
}
|
|
|
|
/*
|
|
*If cl_testparticles is set, create 4096 particles in the view
|
|
*/
|
|
void
|
|
V_TestParticles(void)
|
|
{
|
|
particle_t *p;
|
|
int i, j;
|
|
float d, r, u;
|
|
|
|
r_numparticles = MAX_PARTICLES;
|
|
|
|
for (i = 0; i < r_numparticles; i++)
|
|
{
|
|
d = i * 0.25f;
|
|
r = 4 * ((i & 7) - 3.5f);
|
|
u = 4 * (((i >> 3) & 7) - 3.5f);
|
|
p = &r_particles[i];
|
|
|
|
for (j = 0; j < 3; j++)
|
|
{
|
|
p->origin[j] = cl.refdef.vieworg[j] + cl.v_forward[j] * d +
|
|
cl.v_right[j] * r + cl.v_up[j] * u;
|
|
}
|
|
|
|
p->color = 8;
|
|
p->alpha = cl_testparticles->value;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If cl_testentities is set, create 32 player models
|
|
*/
|
|
void
|
|
V_TestEntities(void)
|
|
{
|
|
int i, j;
|
|
float f, r;
|
|
entity_t *ent;
|
|
|
|
r_numentities = 32;
|
|
memset(r_entities, 0, sizeof(r_entities));
|
|
|
|
for (i = 0; i < r_numentities; i++)
|
|
{
|
|
ent = &r_entities[i];
|
|
|
|
r = 64 * ((i % 4) - 1.5);
|
|
f = 64 * (i / 4) + 128;
|
|
|
|
for (j = 0; j < 3; j++)
|
|
{
|
|
ent->origin[j] = cl.refdef.vieworg[j] + cl.v_forward[j] * f +
|
|
cl.v_right[j] * r;
|
|
}
|
|
|
|
ent->model = cl.baseclientinfo.model;
|
|
ent->skin = cl.baseclientinfo.skin;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If cl_testlights is set, create 32 lights models
|
|
*/
|
|
void
|
|
V_TestLights(void)
|
|
{
|
|
int i, j;
|
|
float f, r;
|
|
dlight_t *dl;
|
|
|
|
r_numdlights = 32;
|
|
memset(r_dlights, 0, sizeof(r_dlights));
|
|
|
|
for (i = 0; i < r_numdlights; i++)
|
|
{
|
|
dl = &r_dlights[i];
|
|
|
|
r = 64 * ((i % 4) - 1.5f);
|
|
f = 64 * (i / 4.0f) + 128;
|
|
|
|
for (j = 0; j < 3; j++)
|
|
{
|
|
dl->origin[j] = cl.refdef.vieworg[j] + cl.v_forward[j] * f +
|
|
cl.v_right[j] * r;
|
|
}
|
|
|
|
dl->color[0] = (float)(((i % 6) + 1) & 1);
|
|
dl->color[1] = (float)((((i % 6) + 1) & 2) >> 1);
|
|
dl->color[2] = (float)((((i % 6) + 1) & 4) >> 2);
|
|
dl->intensity = 200;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Call before entering a new level, or after changing dlls
|
|
*/
|
|
void
|
|
CL_PrepRefresh(void)
|
|
{
|
|
char mapname[32];
|
|
int i;
|
|
char name[MAX_QPATH];
|
|
float rotate;
|
|
vec3_t axis;
|
|
|
|
if (!cl.configstrings[CS_MODELS + 1][0])
|
|
{
|
|
return;
|
|
}
|
|
|
|
SCR_AddDirtyPoint(0, 0);
|
|
SCR_AddDirtyPoint(viddef.width - 1, viddef.height - 1);
|
|
|
|
/* let the refresher load the map */
|
|
strcpy(mapname, cl.configstrings[CS_MODELS + 1] + 5); /* skip "maps/" */
|
|
mapname[strlen(mapname) - 4] = 0; /* cut off ".bsp" */
|
|
|
|
/* register models, pics, and skins */
|
|
Com_Printf("Map: %s\r", mapname);
|
|
SCR_UpdateScreen();
|
|
R_BeginRegistration (mapname);
|
|
Com_Printf(" \r");
|
|
|
|
/* precache status bar pics */
|
|
Com_Printf("pics\r");
|
|
SCR_UpdateScreen();
|
|
SCR_TouchPics();
|
|
Com_Printf(" \r");
|
|
|
|
CL_RegisterTEntModels();
|
|
|
|
num_cl_weaponmodels = 1;
|
|
strcpy(cl_weaponmodels[0], "weapon.md2");
|
|
|
|
for (i = 1; i < MAX_MODELS && cl.configstrings[CS_MODELS + i][0]; i++)
|
|
{
|
|
strcpy(name, cl.configstrings[CS_MODELS + i]);
|
|
name[37] = 0; /* never go beyond one line */
|
|
|
|
if (name[0] != '*')
|
|
{
|
|
Com_Printf("%s\r", name);
|
|
}
|
|
|
|
SCR_UpdateScreen();
|
|
IN_Update();
|
|
|
|
if (name[0] == '#')
|
|
{
|
|
/* special player weapon model */
|
|
if (num_cl_weaponmodels < MAX_CLIENTWEAPONMODELS)
|
|
{
|
|
Q_strlcpy(cl_weaponmodels[num_cl_weaponmodels],
|
|
cl.configstrings[CS_MODELS + i] + 1,
|
|
sizeof(cl_weaponmodels[num_cl_weaponmodels]));
|
|
num_cl_weaponmodels++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cl.model_draw[i] = R_RegisterModel(cl.configstrings[CS_MODELS + i]);
|
|
|
|
if (name[0] == '*')
|
|
{
|
|
cl.model_clip[i] = CM_InlineModel(cl.configstrings[CS_MODELS + i]);
|
|
}
|
|
|
|
else
|
|
{
|
|
cl.model_clip[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if (name[0] != '*')
|
|
{
|
|
Com_Printf(" \r");
|
|
}
|
|
}
|
|
|
|
Com_Printf("images\r");
|
|
SCR_UpdateScreen();
|
|
|
|
for (i = 1; i < MAX_IMAGES && cl.configstrings[CS_IMAGES + i][0]; i++)
|
|
{
|
|
cl.image_precache[i] = Draw_FindPic(cl.configstrings[CS_IMAGES + i]);
|
|
IN_Update();
|
|
}
|
|
|
|
Com_Printf(" \r");
|
|
|
|
for (i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
if (!cl.configstrings[CS_PLAYERSKINS + i][0])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Com_Printf("client %i\r", i);
|
|
SCR_UpdateScreen();
|
|
IN_Update();
|
|
CL_ParseClientinfo(i);
|
|
Com_Printf(" \r");
|
|
}
|
|
|
|
CL_LoadClientinfo(&cl.baseclientinfo, "unnamed\\male/grunt");
|
|
|
|
/* set sky textures and speed */
|
|
Com_Printf("sky\r");
|
|
SCR_UpdateScreen();
|
|
rotate = (float)strtod(cl.configstrings[CS_SKYROTATE], (char **)NULL);
|
|
sscanf(cl.configstrings[CS_SKYAXIS], "%f %f %f", &axis[0], &axis[1], &axis[2]);
|
|
R_SetSky(cl.configstrings[CS_SKY], rotate, axis);
|
|
Com_Printf(" \r");
|
|
|
|
/* the renderer can now free unneeded stuff */
|
|
R_EndRegistration();
|
|
|
|
/* clear any lines of console text */
|
|
Con_ClearNotify();
|
|
|
|
SCR_UpdateScreen();
|
|
cl.refresh_prepped = true;
|
|
cl.force_refdef = true; /* make sure we have a valid refdef */
|
|
|
|
#if defined(OGG)
|
|
int track = (int)strtol(cl.configstrings[CS_CDTRACK], (char **)NULL, 10);
|
|
|
|
/* start the cd track */
|
|
if (Cvar_VariableValue("cd_shuffle"))
|
|
{
|
|
OGG_PlayTrack(track);
|
|
}
|
|
else
|
|
{
|
|
OGG_PlayTrack(track);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
float
|
|
CalcFov(float fov_x, float width, float height)
|
|
{
|
|
float a;
|
|
float x;
|
|
|
|
if ((fov_x < 1) || (fov_x > 179))
|
|
{
|
|
Com_Error(ERR_DROP, "Bad fov: %f", fov_x);
|
|
}
|
|
|
|
x = width / (float)tan(fov_x / 360 * M_PI);
|
|
|
|
a = (float)atan(height / x);
|
|
|
|
a = a * 360 / M_PI;
|
|
|
|
return a;
|
|
}
|
|
|
|
/* gun frame debugging functions */
|
|
void
|
|
V_Gun_Next_f(void)
|
|
{
|
|
gun_frame++;
|
|
Com_Printf("frame %i\n", gun_frame);
|
|
}
|
|
|
|
void
|
|
V_Gun_Prev_f(void)
|
|
{
|
|
gun_frame--;
|
|
|
|
if (gun_frame < 0)
|
|
{
|
|
gun_frame = 0;
|
|
}
|
|
|
|
Com_Printf("frame %i\n", gun_frame);
|
|
}
|
|
|
|
void
|
|
V_Gun_Model_f(void)
|
|
{
|
|
char name[MAX_QPATH];
|
|
|
|
if (Cmd_Argc() != 2)
|
|
{
|
|
gun_model = NULL;
|
|
return;
|
|
}
|
|
|
|
Com_sprintf(name, sizeof(name), "models/%s/tris.md2", Cmd_Argv(1));
|
|
gun_model = R_RegisterModel(name);
|
|
}
|
|
|
|
int
|
|
entitycmpfnc(const entity_t *a, const entity_t *b)
|
|
{
|
|
/* all other models are sorted by model then skin */
|
|
if (a->model == b->model)
|
|
{
|
|
return (a->skin == b->skin) ? 0 :
|
|
(a->skin > b->skin) ? 1 : -1;
|
|
}
|
|
else
|
|
{
|
|
return (a->model == b->model) ? 0 :
|
|
(a->model > b->model) ? 1 : -1;
|
|
}
|
|
}
|
|
|
|
void
|
|
V_RenderView(float stereo_separation)
|
|
{
|
|
if (cls.state != ca_active)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!cl.refresh_prepped)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cl_timedemo->value)
|
|
{
|
|
if (!cl.timedemo_start)
|
|
{
|
|
cl.timedemo_start = Sys_Milliseconds();
|
|
}
|
|
|
|
cl.timedemo_frames++;
|
|
}
|
|
|
|
/* an invalid frame will just use the exact previous refdef
|
|
we can't use the old frame if the video mode has changed, though... */
|
|
if (cl.frame.valid && (cl.force_refdef || !cl_paused->value))
|
|
{
|
|
cl.force_refdef = false;
|
|
|
|
V_ClearScene();
|
|
|
|
/* build a refresh entity list and calc cl.sim*
|
|
this also calls CL_CalcViewValues which loads
|
|
v_forward, etc. */
|
|
CL_AddEntities();
|
|
|
|
// before changing viewport we should trace the crosshair position
|
|
V_Render3dCrosshair();
|
|
|
|
if (cl_testparticles->value)
|
|
{
|
|
V_TestParticles();
|
|
}
|
|
|
|
if (cl_testentities->value)
|
|
{
|
|
V_TestEntities();
|
|
}
|
|
|
|
if (cl_testlights->value)
|
|
{
|
|
V_TestLights();
|
|
}
|
|
|
|
if (cl_testblend->value)
|
|
{
|
|
cl.refdef.blend[0] = 1;
|
|
cl.refdef.blend[1] = 0.5;
|
|
cl.refdef.blend[2] = 0.25;
|
|
cl.refdef.blend[3] = 0.5;
|
|
}
|
|
|
|
/* offset vieworg appropriately if
|
|
we're doing stereo separation */
|
|
|
|
if (stereo_separation != 0)
|
|
{
|
|
vec3_t tmp;
|
|
|
|
VectorScale(cl.v_right, stereo_separation, tmp);
|
|
VectorAdd(cl.refdef.vieworg, tmp, cl.refdef.vieworg);
|
|
}
|
|
|
|
/* never let it sit exactly on a node line, because a water plane can
|
|
dissapear when viewed with the eye exactly on it. the server protocol
|
|
only specifies to 1/8 pixel, so add 1/16 in each axis */
|
|
cl.refdef.vieworg[0] += 1.0 / 16;
|
|
cl.refdef.vieworg[1] += 1.0 / 16;
|
|
cl.refdef.vieworg[2] += 1.0 / 16;
|
|
|
|
cl.refdef.time = cl.time * 0.001f;
|
|
|
|
cl.refdef.areabits = cl.frame.areabits;
|
|
|
|
if (!cl_add_entities->value)
|
|
{
|
|
r_numentities = 0;
|
|
}
|
|
|
|
if (!cl_add_particles->value)
|
|
{
|
|
r_numparticles = 0;
|
|
}
|
|
|
|
if (!cl_add_lights->value)
|
|
{
|
|
r_numdlights = 0;
|
|
}
|
|
|
|
if (!cl_add_blend->value)
|
|
{
|
|
VectorClear(cl.refdef.blend);
|
|
}
|
|
|
|
cl.refdef.num_entities = r_numentities;
|
|
cl.refdef.entities = r_entities;
|
|
cl.refdef.num_particles = r_numparticles;
|
|
cl.refdef.particles = r_particles;
|
|
cl.refdef.num_dlights = r_numdlights;
|
|
cl.refdef.dlights = r_dlights;
|
|
cl.refdef.lightstyles = r_lightstyles;
|
|
|
|
cl.refdef.rdflags = cl.frame.playerstate.rdflags;
|
|
|
|
/* sort entities for better cache locality */
|
|
qsort(cl.refdef.entities, cl.refdef.num_entities,
|
|
sizeof(cl.refdef.entities[0]), (int (*)(const void *, const void *))
|
|
entitycmpfnc);
|
|
} else if (cl.frame.valid && cl_paused->value && gl1_stereo->value) {
|
|
// We need to adjust the refdef in stereo mode when paused.
|
|
vec3_t tmp;
|
|
CL_CalcViewValues();
|
|
VectorScale( cl.v_right, stereo_separation, tmp );
|
|
VectorAdd( cl.refdef.vieworg, tmp, cl.refdef.vieworg );
|
|
|
|
cl.refdef.vieworg[0] += 1.0/16;
|
|
cl.refdef.vieworg[1] += 1.0/16;
|
|
cl.refdef.vieworg[2] += 1.0/16;
|
|
|
|
cl.refdef.time = cl.time*0.001;
|
|
}
|
|
|
|
cl.refdef.x = scr_vrect.x;
|
|
cl.refdef.y = scr_vrect.y;
|
|
cl.refdef.width = scr_vrect.width;
|
|
cl.refdef.height = scr_vrect.height;
|
|
cl.refdef.fov_y = CalcFov(cl.refdef.fov_x, (float)cl.refdef.width,
|
|
(float)cl.refdef.height);
|
|
|
|
R_RenderFrame(&cl.refdef);
|
|
|
|
if (cl_stats->value)
|
|
{
|
|
Com_Printf("ent:%i lt:%i part:%i\n", r_numentities,
|
|
r_numdlights, r_numparticles);
|
|
}
|
|
|
|
if (log_stats->value && (log_stats_file != 0))
|
|
{
|
|
fprintf(log_stats_file, "%i,%i,%i,", r_numentities,
|
|
r_numdlights, r_numparticles);
|
|
}
|
|
|
|
SCR_AddDirtyPoint(scr_vrect.x, scr_vrect.y);
|
|
SCR_AddDirtyPoint(scr_vrect.x + scr_vrect.width - 1,
|
|
scr_vrect.y + scr_vrect.height - 1);
|
|
|
|
SCR_DrawCrosshair();
|
|
}
|
|
|
|
void
|
|
V_Render3dCrosshair(void)
|
|
{
|
|
trace_t crosshair_trace;
|
|
vec3_t end;
|
|
|
|
crosshair_3d = Cvar_Get("crosshair_3d", "0", CVAR_ARCHIVE);
|
|
crosshair_3d_glow = Cvar_Get("crosshair_3d_glow", "0", CVAR_ARCHIVE);
|
|
|
|
|
|
if(crosshair_3d->value || crosshair_3d_glow->value){
|
|
VectorMA(cl.refdef.vieworg,8192,cl.v_forward,end);
|
|
crosshair_trace = CL_PMTrace(cl.refdef.vieworg, vec3_origin, vec3_origin, end);
|
|
|
|
if(crosshair_3d_glow->value){
|
|
crosshair_3d_glow_r = Cvar_Get("crosshair_3d_glow_r", "5", CVAR_ARCHIVE);
|
|
crosshair_3d_glow_g = Cvar_Get("crosshair_3d_glow_g", "1", CVAR_ARCHIVE);
|
|
crosshair_3d_glow_b = Cvar_Get("crosshair_3d_glow_b", "4", CVAR_ARCHIVE);
|
|
|
|
V_AddLight(
|
|
crosshair_trace.endpos,
|
|
crosshair_3d_glow->value,
|
|
crosshair_3d_glow_r->value,
|
|
crosshair_3d_glow_g->value,
|
|
crosshair_3d_glow_b->value
|
|
);
|
|
}
|
|
|
|
if(crosshair_3d->value){
|
|
entity_t crosshair_ent = {0};
|
|
|
|
crosshair_ent.origin[0] = crosshair_trace.endpos[0];
|
|
crosshair_ent.origin[1] = crosshair_trace.endpos[1];
|
|
crosshair_ent.origin[2] = crosshair_trace.endpos[2];
|
|
|
|
crosshair_ent.model = R_RegisterModel("models/crosshair/tris.md2");
|
|
//crosshair_ent.skin = R_RegisterSkin("models/crosshair/skin.pcx");
|
|
|
|
AngleVectors2(crosshair_trace.plane.normal, crosshair_ent.angles);
|
|
crosshair_ent.flags = RF_DEPTHHACK | RF_FULLBRIGHT | RF_NOSHADOW;
|
|
|
|
V_AddEntity(&crosshair_ent);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
V_Viewpos_f(void)
|
|
{
|
|
Com_Printf("(%i %i %i) : %i\n", (int)cl.refdef.vieworg[0],
|
|
(int)cl.refdef.vieworg[1], (int)cl.refdef.vieworg[2],
|
|
(int)cl.refdef.viewangles[YAW]);
|
|
}
|
|
|
|
void
|
|
V_Init(void)
|
|
{
|
|
Cmd_AddCommand("gun_next", V_Gun_Next_f);
|
|
Cmd_AddCommand("gun_prev", V_Gun_Prev_f);
|
|
Cmd_AddCommand("gun_model", V_Gun_Model_f);
|
|
|
|
Cmd_AddCommand("viewpos", V_Viewpos_f);
|
|
|
|
crosshair = Cvar_Get("crosshair", "0", CVAR_ARCHIVE);
|
|
crosshair_scale = Cvar_Get("crosshair_scale", "-1", CVAR_ARCHIVE);
|
|
cl_testblend = Cvar_Get("cl_testblend", "0", 0);
|
|
cl_testparticles = Cvar_Get("cl_testparticles", "0", 0);
|
|
cl_testentities = Cvar_Get("cl_testentities", "0", 0);
|
|
cl_testlights = Cvar_Get("cl_testlights", "0", 0);
|
|
|
|
cl_stats = Cvar_Get("cl_stats", "0", 0);
|
|
}
|
|
|