cvar_t cl_deathnoviewmodel = {0, "cl_deathnoviewmodel", "1", "hides gun model when dead"};
+cvar_t cl_locs_enable = {CVAR_SAVE, "locs_enable", "1", "enables replacement of certain % codes in chat messages: %l (location), %d (last death location), %h (health), %a (armor), %x (rockets), %c (cells), %r (rocket launcher status), %p (powerup status), %w (weapon status), %t (current time in level)"};
+cvar_t cl_locs_show = {0, "locs_show", "0", "shows defined locations for editing purposes"};
+
client_static_t cls;
client_state_t cl;
World_PrintAreaStats(&cl.world, "client");
}
+cl_locnode_t *CL_Locs_FindNearest(const vec3_t point)
+{
+ int i;
+ cl_locnode_t *loc;
+ cl_locnode_t *best;
+ vec3_t nearestpoint;
+ vec_t dist, bestdist;
+ best = NULL;
+ bestdist = 0;
+ for (loc = cl.locnodes;loc;loc = loc->next)
+ {
+ for (i = 0;i < 3;i++)
+ nearestpoint[i] = bound(loc->mins[i], point[i], loc->maxs[i]);
+ dist = VectorDistance2(nearestpoint, point);
+ if (bestdist > dist || !best)
+ {
+ bestdist = dist;
+ best = loc;
+ if (bestdist < 1)
+ break;
+ }
+ }
+ return best;
+}
+
+void CL_Locs_FindLocationName(char *buffer, size_t buffersize, vec3_t point)
+{
+ cl_locnode_t *loc;
+ loc = CL_Locs_FindNearest(point);
+ if (loc)
+ strlcpy(buffer, loc->name, buffersize);
+ else
+ dpsnprintf(buffer, buffersize, "LOC=%.0f:%.0f:%.0f", point[0], point[1], point[2]);
+}
+
+void CL_Locs_FreeNode(cl_locnode_t *node)
+{
+ cl_locnode_t **pointer, **next;
+ for (pointer = &cl.locnodes;*pointer;pointer = next)
+ {
+ next = &(*pointer)->next;
+ if (*pointer == node)
+ {
+ *pointer = node->next;
+ Mem_Free(node);
+ }
+ }
+ Con_Printf("CL_Locs_FreeNode: no such node! (%p)\n", node);
+}
+
+void CL_Locs_AddNode(vec3_t mins, vec3_t maxs, const char *namestart, const char *nameend)
+{
+ cl_locnode_t *node, **pointer;
+ int namelen = (int)(nameend - namestart);
+ node = Mem_Alloc(cls.levelmempool, sizeof(cl_locnode_t) + namelen + 1);
+ VectorSet(node->mins, min(mins[0], maxs[0]), min(mins[1], maxs[1]), min(mins[2], maxs[2]));
+ VectorSet(node->maxs, max(mins[0], maxs[0]), max(mins[1], maxs[1]), max(mins[2], maxs[2]));
+ node->name = (char *)(node + 1);
+ memcpy(node->name, namestart, namelen);
+ node->name[namelen] = 0;
+ // link it into the tail of the list to preserve the order
+ for (pointer = &cl.locnodes;*pointer;pointer = &(*pointer)->next)
+ ;
+ *pointer = node;
+}
+
+void CL_Locs_Add_f(void)
+{
+ vec3_t mins, maxs;
+ if (Cmd_Argc() != 5 && Cmd_Argc() != 8)
+ {
+ Con_Printf("usage: %s x y z[ x y z] name\n", Cmd_Argv(0));
+ return;
+ }
+ mins[0] = atof(Cmd_Argv(1));
+ mins[1] = atof(Cmd_Argv(2));
+ mins[2] = atof(Cmd_Argv(3));
+ if (Cmd_Argc() == 8)
+ {
+ maxs[0] = atof(Cmd_Argv(4));
+ maxs[1] = atof(Cmd_Argv(5));
+ maxs[2] = atof(Cmd_Argv(6));
+ CL_Locs_AddNode(mins, maxs, Cmd_Argv(7), Cmd_Argv(7) + strlen(Cmd_Argv(7)));
+ }
+ else
+ CL_Locs_AddNode(mins, mins, Cmd_Argv(4), Cmd_Argv(4) + strlen(Cmd_Argv(4)));
+}
+
+void CL_Locs_RemoveNearest_f(void)
+{
+ cl_locnode_t *loc;
+ loc = CL_Locs_FindNearest(r_view.origin);
+ if (loc)
+ CL_Locs_FreeNode(loc);
+ else
+ Con_Printf("no loc point or box found for your location\n");
+}
+
+void CL_Locs_Clear_f(void)
+{
+ while (cl.locnodes)
+ CL_Locs_FreeNode(cl.locnodes);
+}
+
+void CL_Locs_Save_f(void)
+{
+ cl_locnode_t *loc;
+ qfile_t *outfile;
+ char locfilename[MAX_QPATH];
+ if (!cl.locnodes)
+ {
+ Con_Printf("No loc points/boxes exist!\n");
+ return;
+ }
+ if (cls.state != ca_connected || !cl.worldmodel)
+ {
+ Con_Printf("No level loaded!\n");
+ return;
+ }
+ FS_StripExtension(cl.worldmodel->name, locfilename, sizeof(locfilename));
+ strlcat(locfilename, ".loc", sizeof(locfilename));
+
+ outfile = FS_Open(locfilename, "w", false, false);
+ if (!outfile)
+ return;
+ // if any boxes are used then this is a proquake-format loc file, which
+ // allows comments, so add some relevant information at the start
+ for (loc = cl.locnodes;loc;loc = loc->next)
+ if (!VectorCompare(loc->mins, loc->maxs))
+ break;
+ if (loc)
+ {
+ FS_Printf(outfile, "// %s %s saved by %s\n// x,y,z,x,y,z,\"name\"\n\n", locfilename, Sys_TimeString("%Y-%m-%d"), engineversion);
+ for (loc = cl.locnodes;loc;loc = loc->next)
+ if (VectorCompare(loc->mins, loc->maxs))
+ break;
+ if (loc)
+ Con_Printf("Warning: writing loc file containing a mixture of qizmo-style points and proquake-style boxes may not work in qizmo or proquake!\n");
+ }
+ for (loc = cl.locnodes;loc;loc = loc->next)
+ {
+ if (VectorCompare(loc->mins, loc->maxs))
+ FS_Printf(outfile, "%.0f %.0f %.0f %s\n", loc->mins[0]*8, loc->mins[1]*8, loc->mins[2]*8, loc->name);
+ else
+ FS_Printf(outfile, "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,\"%s\"\n", loc->mins[0], loc->mins[1], loc->mins[2], loc->maxs[0], loc->maxs[1], loc->maxs[2], loc->name);
+ }
+ FS_Close(outfile);
+}
+
+void CL_Locs_Reload_f(void)
+{
+ int i, linenumber, limit;
+ char *filedata, *text, *textend, *linestart, *linetext, *lineend;
+ fs_offset_t filesize;
+ vec3_t mins, maxs;
+ char locfilename[MAX_QPATH];
+
+ if (cls.state != ca_connected || !cl.worldmodel)
+ {
+ Con_Printf("No level loaded!\n");
+ return;
+ }
+ FS_StripExtension(cl.worldmodel->name, locfilename, sizeof(locfilename));
+ strlcat(locfilename, ".loc", sizeof(locfilename));
+
+ CL_Locs_Clear_f();
+
+ filedata = (char *)FS_LoadFile(locfilename, cls.levelmempool, false, &filesize);
+ if (!filedata)
+ return;
+ text = filedata;
+ textend = filedata + filesize;
+ for (linenumber = 1;text < textend;linenumber++)
+ {
+ linestart = text;
+ for (;text < textend && *text != '\r' && *text != '\n';text++)
+ ;
+ lineend = text;
+ if (text + 1 < textend && *text == '\r' && text[1] == '\n')
+ text++;
+ if (text < textend)
+ text++;
+ // trim trailing whitespace
+ while (lineend > linestart && lineend[-1] <= ' ')
+ lineend--;
+ // trim leading whitespace
+ while (linestart < lineend && *linestart <= ' ')
+ linestart++;
+ // check if this is a comment
+ if (linestart + 2 <= lineend && !strncmp(linestart, "//", 2))
+ continue;
+ linetext = linestart;
+ limit = 3;
+ for (i = 0;i < limit;i++)
+ {
+ if (linetext >= lineend)
+ break;
+ // note: a missing number is interpreted as 0
+ if (i < 3)
+ mins[i] = atof(linetext);
+ else
+ maxs[i - 3] = atof(linetext);
+ // now advance past the number
+ while (linetext < lineend && *linetext > ' ' && *linetext != ',')
+ linetext++;
+ // advance through whitespace
+ if (linetext < lineend)
+ {
+ if (*linetext <= ' ')
+ linetext++;
+ else if (*linetext == ',')
+ {
+ linetext++;
+ limit = 6;
+ }
+ }
+ }
+ // if this is a quoted name, remove the quotes
+ if (linetext < lineend && *linetext == '"')
+ {
+ lineend--;
+ linetext++;
+ }
+ // valid line parsed
+ if (i == 3 || i == 6)
+ {
+ // if a point was parsed, it needs to be scaled down by 8 (since
+ // point-based loc files were invented by a proxy which dealt
+ // directly with quake protocol coordinates, which are *8), turn
+ // it into a box
+ if (i == 3)
+ {
+ VectorScale(mins, (1.0 / 8.0), mins);
+ VectorCopy(mins, maxs);
+ }
+ // add the point or box to the list
+ CL_Locs_AddNode(mins, maxs, linetext, lineend);
+ }
+ }
+}
+
/*
===========
CL_Shutdown
Cmd_AddCommand("timerefresh", CL_TimeRefresh_f, "turn quickly and print rendering statistcs");
+ Cvar_RegisterVariable(&cl_locs_enable);
+ Cvar_RegisterVariable(&cl_locs_show);
+ Cmd_AddCommand("locs_add", CL_Locs_Add_f, "add a point or box location (usage: x y z[ x y z] \"name\", if two sets of xyz are supplied it is a box, otherwise point)");
+ Cmd_AddCommand("locs_removenearest", CL_Locs_RemoveNearest_f, "remove the nearest point or box (note: you need to be very near a box to remove it)");
+ Cmd_AddCommand("locs_clear", CL_Locs_Clear_f, "remove all loc points/boxes");
+ Cmd_AddCommand("locs_reload", CL_Locs_Reload_f, "reload .loc file for this map");
+ Cmd_AddCommand("locs_save", CL_Locs_Save_f, "save .loc file for this map containing currently defined points and boxes");
+
CL_Parse_Init();
CL_Particles_Init();
CL_Screen_Init();
return false;
}
+extern void CL_Locs_Reload_f(void);
static void QW_CL_ProcessUserInfo(int slot);
static void QW_CL_RequestNextDownload(void)
{
cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
CL_UpdateRenderEntity(&cl.entities[0].render);
+ CL_Locs_Reload_f();
R_Modules_NewMap();
// TODO: add pmodel/emodel player.mdl/eyes.mdl CRCs to userinfo
// we now have the worldmodel so we can set up the game world
cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
CL_UpdateRenderEntity(&cl.entities[0].render);
+ CL_Locs_Reload_f();
R_Modules_NewMap();
// check memory integrity
Mem_CheckSentinelsGlobal();
// the worldmodel failed, but we need to set up anyway
cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
CL_UpdateRenderEntity(&cl.entities[0].render);
+ CL_Locs_Reload_f();
R_Modules_NewMap();
// check memory integrity
Mem_CheckSentinelsGlobal();
// we now have the worldmodel so we can set up the game world
cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
CL_UpdateRenderEntity(&cl.entities[0].render);
+ CL_Locs_Reload_f();
R_Modules_NewMap();
// check memory integrity
Mem_CheckSentinelsGlobal();
void R_TimeReport_Frame(void)
{
int i, j, lines, y;
+ cl_locnode_t *loc;
if (r_speeds_string[0])
{
speedstringcount = 0;
r_speeds_string[0] = 0;
r_timereport_active = false;
+ // put the location name in the r_speeds display as it greatly helps
+ // when creating loc files
+ loc = CL_Locs_FindNearest(cl.movement_origin);
+ if (loc)
+ sprintf(r_speeds_string + strlen(r_speeds_string), "Location: %s\n", loc->name);
sprintf(r_speeds_string + strlen(r_speeds_string), "org:'%+8.2f %+8.2f %+8.2f' dir:'%+2.3f %+2.3f %+2.3f'\n", r_view.origin[0], r_view.origin[1], r_view.origin[2], r_view.forward[0], r_view.forward[1], r_view.forward[2]);
sprintf(r_speeds_string + strlen(r_speeds_string), "%5i entities%6i surfaces%6i triangles%5i leafs%5i portals%6i particles\n", r_refdef.stats.entities, r_refdef.stats.entities_surfaces, r_refdef.stats.entities_triangles, r_refdef.stats.world_leafs, r_refdef.stats.world_portals, r_refdef.stats.particles);
sprintf(r_speeds_string + strlen(r_speeds_string), "%4i lights%4i clears%4i scissored%7i light%7i shadow%7i dynamic\n", r_refdef.stats.lights, r_refdef.stats.lights_clears, r_refdef.stats.lights_scissored, r_refdef.stats.lights_lighttriangles, r_refdef.stats.lights_shadowtriangles, r_refdef.stats.lights_dynamicshadowtriangles);
}
cl_parsingtextmode_t;
+typedef struct cl_locnode_s
+{
+ struct cl_locnode_s *next;
+ char *name;
+ vec3_t mins, maxs;
+}
+cl_locnode_t;
+
//
// the client_state_t structure is wiped completely at every
// server signon
// collision culling data
world_t world;
+
+ // loc file stuff (points and boxes describing locations in the level)
+ cl_locnode_t *locnodes;
+ // this is updated to cl.movement_origin whenever health is < 1
+ // used by %d print in say/say_team messages if cl_locs_enable is on
+ vec3_t lastdeathorigin;
}
client_state_t;
extern cvar_t cl_prydoncursor;
+extern cvar_t cl_locs_enable;
+
extern client_state_t cl;
extern void CL_AllocLightFlash (entity_render_t *ent, matrix4x4_t *matrix, float radius, float red, float green, float blue, float decay, float lifetime, int cubemapnum, int style, int shadowenable, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags);
+cl_locnode_t *CL_Locs_FindNearest(const vec3_t point);
+void CL_Locs_FindLocationName(char *buffer, size_t buffersize, vec3_t point);
+
//=============================================================================
//
*/
void Cmd_ForwardStringToServer (const char *s)
{
+ char temp[128];
if (cls.state != ca_connected)
{
Con_Printf("Can't \"%s\", not connected\n", s);
MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
else
MSG_WriteByte(&cls.netcon->message, clc_stringcmd);
- SZ_Write(&cls.netcon->message, (const unsigned char *)s, (int)strlen(s) + 1);
+ if ((!strncmp(s, "say ", 4) || !strncmp(s, "say_team ", 9)) && cl_locs_enable.integer)
+ {
+ // say/say_team commands can replace % character codes with status info
+ while (*s)
+ {
+ if (*s == '%' && s[1])
+ {
+ // handle proquake message macros
+ temp[0] = 0;
+ switch (s[1])
+ {
+ case 'l': // current location
+ CL_Locs_FindLocationName(temp, sizeof(temp), cl.movement_origin);
+ break;
+ case 'h': // current health
+ dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_HEALTH]);
+ break;
+ case 'a': // current armor
+ dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_ARMOR]);
+ break;
+ case 'x': // current rockets
+ dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_ROCKETS]);
+ break;
+ case 'c': // current cells
+ dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_CELLS]);
+ break;
+ // silly proquake macros
+ case 'd': // loc at last death
+ CL_Locs_FindLocationName(temp, sizeof(temp), cl.lastdeathorigin);
+ break;
+ case 't': // current time
+ dpsnprintf(temp, sizeof(temp), "%.0f:%.0f", floor(cl.time / 60), cl.time - floor(cl.time / 60) * 60);
+ break;
+ case 'r': // rocket launcher status ("I have RL", "I need rockets", "I need RL")
+ if (!(cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER))
+ dpsnprintf(temp, sizeof(temp), "I need RL");
+ else if (!cl.stats[STAT_ROCKETS])
+ dpsnprintf(temp, sizeof(temp), "I need rockets");
+ else
+ dpsnprintf(temp, sizeof(temp), "I have RL");
+ break;
+ case 'p': // powerup status (outputs "quad" "pent" and "eyes" according to status)
+ if (cl.stats[STAT_ITEMS] & IT_QUAD)
+ {
+ if (temp[0])
+ strlcat(temp, " ", sizeof(temp));
+ strlcat(temp, "quad", sizeof(temp));
+ }
+ if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY)
+ {
+ if (temp[0])
+ strlcat(temp, " ", sizeof(temp));
+ strlcat(temp, "pent", sizeof(temp));
+ }
+ if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY)
+ {
+ if (temp[0])
+ strlcat(temp, " ", sizeof(temp));
+ strlcat(temp, "eyes", sizeof(temp));
+ }
+ break;
+ case 'w': // weapon status (outputs "SSG:NG:SNG:GL:RL:LG" with the text between : characters omitted if you lack the weapon)
+ if (cl.stats[STAT_ITEMS] & IT_SUPER_SHOTGUN)
+ strlcat(temp, "SSG", sizeof(temp));
+ strlcat(temp, ":", sizeof(temp));
+ if (cl.stats[STAT_ITEMS] & IT_NAILGUN)
+ strlcat(temp, "NG", sizeof(temp));
+ strlcat(temp, ":", sizeof(temp));
+ if (cl.stats[STAT_ITEMS] & IT_SUPER_NAILGUN)
+ strlcat(temp, "SNG", sizeof(temp));
+ strlcat(temp, ":", sizeof(temp));
+ if (cl.stats[STAT_ITEMS] & IT_GRENADE_LAUNCHER)
+ strlcat(temp, "GL", sizeof(temp));
+ strlcat(temp, ":", sizeof(temp));
+ if (cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER)
+ strlcat(temp, "RL", sizeof(temp));
+ strlcat(temp, ":", sizeof(temp));
+ if (cl.stats[STAT_ITEMS] & IT_LIGHTNING)
+ strlcat(temp, "LG", sizeof(temp));
+ break;
+ default:
+ // not a recognized macro, print it as-is...
+ temp[0] = s[0];
+ temp[1] = s[1];
+ temp[2] = 0;
+ break;
+ }
+ // write the resulting text
+ SZ_Write(&cls.netcon->message, (unsigned char *)temp, strlen(temp));
+ s += 2;
+ continue;
+ }
+ MSG_WriteByte(&cls.netcon->message, *s);
+ s++;
+ }
+ MSG_WriteByte(&cls.netcon->message, 0);
+ }
+ else // any other command is passed on as-is
+ SZ_Write(&cls.netcon->message, (const unsigned char *)s, (int)strlen(s) + 1);
}
/*
extern void R_DrawLightningBeams (void);
extern void VM_CL_AddPolygonsToMeshQueue (void);
extern void R_DrawPortals (void);
+extern cvar_t cl_locs_show;
+static void R_DrawLocs(void);
void R_RenderScene(void)
{
// don't let sound skip if going slow
}
VM_CL_AddPolygonsToMeshQueue();
+ if (cl_locs_show.integer)
+ {
+ R_DrawLocs();
+ if (r_timereport_active)
+ R_TimeReport("showlocs");
+ }
+
if (r_drawportals.integer)
{
R_DrawPortals();
}
}
+float locboxvertex3f[6*4*3] =
+{
+ 1,0,1, 1,0,0, 1,1,0, 1,1,1,
+ 0,1,1, 0,1,0, 0,0,0, 0,0,1,
+ 1,1,1, 1,1,0, 0,1,0, 0,1,1,
+ 0,0,1, 0,0,0, 1,0,0, 1,0,1,
+ 0,0,1, 1,0,1, 1,1,1, 0,1,1,
+ 1,0,0, 0,0,0, 0,1,0, 1,1,0
+};
+
+int locboxelement3i[6*2*3] =
+{
+ 0, 1, 2, 0, 2, 3,
+ 4, 5, 6, 4, 6, 7,
+ 8, 9,10, 8,10,11,
+ 12,13,14, 12,14,15,
+ 16,17,18, 16,18,19,
+ 20,21,22, 20,22,23
+};
+
+void R_DrawLoc_Callback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
+{
+ int i, j;
+ cl_locnode_t *loc = (cl_locnode_t *)ent;
+ vec3_t mins, size;
+ float vertex3f[6*4*3];
+ CHECKGLERROR
+ GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ GL_DepthMask(false);
+ GL_DepthTest(true);
+ GL_CullFace(GL_NONE);
+ R_Mesh_Matrix(&identitymatrix);
+
+ R_Mesh_VertexPointer(vertex3f);
+ R_Mesh_ColorPointer(NULL);
+ R_Mesh_ResetTextureState();
+
+ i = surfacelist[0];
+ GL_Color(((i & 0x0007) >> 0) * (1.0f / 7.0f) * r_view.colorscale,
+ ((i & 0x0038) >> 3) * (1.0f / 7.0f) * r_view.colorscale,
+ ((i & 0x01C0) >> 6) * (1.0f / 7.0f) * r_view.colorscale,
+ surfacelist[0] < 0 ? 0.5f : 0.125f);
+
+ if (VectorCompare(loc->mins, loc->maxs))
+ {
+ VectorSet(size, 2, 2, 2);
+ VectorMA(loc->mins, -0.5f, size, mins);
+ }
+ else
+ {
+ VectorCopy(loc->mins, mins);
+ VectorSubtract(loc->maxs, loc->mins, size);
+ }
+
+ for (i = 0;i < 6*4*3;)
+ for (j = 0;j < 3;j++, i++)
+ vertex3f[i] = mins[j] + size[j] * locboxvertex3f[i];
+
+ R_Mesh_Draw(0, 6*4, 6*2, locboxelement3i);
+}
+
+void R_DrawLocs(void)
+{
+ int index;
+ cl_locnode_t *loc, *nearestloc;
+ vec3_t center;
+ nearestloc = CL_Locs_FindNearest(cl.movement_origin);
+ for (loc = cl.locnodes, index = 0;loc;loc = loc->next, index++)
+ {
+ VectorLerp(loc->mins, 0.5f, loc->maxs, center);
+ R_MeshQueue_AddTransparent(center, R_DrawLoc_Callback, (entity_render_t *)loc, loc == nearestloc ? -1 : index, NULL);
+ }
+}
+
void R_DrawCollisionBrushes(entity_render_t *ent)
{
int i;
0 bug hmap2 -vis: test that bitlongs code works properly on big endian systems such as Mac (LordHavoc)
0 bug hmap2: figure out why there is a subtle difference between bmodel lighting and wall lighting, something to do with nudges causing different attenuation? (Urre)
0 bug hmap2: handle \" properly in hmap2 cmdlib.c COM_Parse (sort)
+0 bug hmap2: the plane-distance based projection size for polygons doesn't work in certain wedge cases where two sides of a wedge brush are very near origin but the entirety of the wedge brush is much larger
0 bug hmap: strip .map extension from filename if present
0 change darkplaces client: disable all possible 'cheat' things unless -developer is given on commandline, this includes r_show*, r_test, gl_lightmaps, r_fullbright, and would require changing -developer to only set developer to 1 rather than 100, as 100 is too annoying
0 change darkplaces client: implement inversion of non-uniform scaling in Matrix4x4_Invert_Simple or replace it with a full featured matrix inverter