cvar_t cl_stainmaps_clearonload = {CVAR_SAVE, "cl_stainmaps_clearonload", "1","clear stainmaps on map restart"};
cvar_t cl_beams_polygons = {CVAR_SAVE, "cl_beams_polygons", "1","use beam polygons instead of models"};
-cvar_t cl_beams_relative = {CVAR_SAVE, "cl_beams_relative", "1","beams are relative to owner (smooth sweeps)"};
-cvar_t cl_beams_lightatend = {CVAR_SAVE, "cl_beams_lightatend", "0","make a light at the end of the beam"};
+cvar_t cl_beams_quakepositionhack = {CVAR_SAVE, "cl_beams_quakepositionhack", "1", "makes your lightning gun appear to fire from your waist (as in Quake and QuakeWorld)"};
+cvar_t cl_beams_instantaimhack = {CVAR_SAVE, "cl_beams_instantaimhack", "1", "makes your lightning gun aiming update instantly"};
+cvar_t cl_beams_lightatend = {CVAR_SAVE, "cl_beams_lightatend", "0", "make a light at the end of the beam"};
cvar_t cl_noplayershadow = {CVAR_SAVE, "cl_noplayershadow", "0","hide player shadow"};
}
}
+void CL_Beam_CalculatePositions(const beam_t *b, vec3_t start, vec3_t end)
+{
+ VectorCopy(b->start, start);
+ VectorCopy(b->end, end);
+
+ // if coming from the player, update the start position
+ if (b->entity == cl.viewentity)
+ {
+ if (cl_beams_quakepositionhack.integer && !chase_active.integer)
+ {
+ // LordHavoc: this is a stupid hack from Quake that makes your
+ // lightning appear to come from your waist and cover less of your
+ // view
+ // in Quake this hack was applied to all players (causing the
+ // infamous crotch-lightning), but in darkplaces and QuakeWorld it
+ // only applies to your own lightning, and only in first person
+ Matrix4x4_OriginFromMatrix(&cl.entities[cl.viewentity].render.matrix, start);
+ }
+ if (cl_beams_instantaimhack.integer)
+ {
+ vec3_t dir, localend;
+ vec_t len;
+ // LordHavoc: this updates the beam direction to match your
+ // viewangles
+ VectorSubtract(end, start, dir);
+ len = VectorLength(dir);
+ VectorNormalize(dir);
+ VectorSet(localend, len, 0, 0);
+ Matrix4x4_Transform(&r_view.matrix, localend, end);
+ }
+ }
+}
+
void CL_RelinkBeams(void)
{
int i;
beam_t *b;
- vec3_t dist, org;
+ vec3_t dist, org, start, end;
float d;
entity_t *ent;
float yaw, pitch;
continue;
}
- // if coming from the player, update the start position
- //if (b->entity == cl.viewentity)
- // Matrix4x4_OriginFromMatrix(&cl.entities[cl.viewentity].render.matrix, b->start);
- if (cl_beams_relative.integer >= 1 && b->entity && (b->entity == cl.viewentity || cl_beams_relative.integer >= 2) && cl.entities[b->entity].state_current.active && b->relativestartvalid)
- {
- entity_render_t *r = &cl.entities[b->entity].render;
- //Matrix4x4_OriginFromMatrix(&r->matrix, origin);
- //Matrix4x4_CreateFromQuakeEntity(&matrix, r->origin[0], r->origin[1], r->origin[2] + 16, r->angles[0], r->angles[1], r->angles[2], 1);
- Matrix4x4_Transform(&r->matrix, b->relativestart, b->start);
- Matrix4x4_Transform(&r->matrix, b->relativeend, b->end);
- }
+ CL_Beam_CalculatePositions(b, start, end);
if (b->lightning)
{
if (cl_beams_lightatend.integer)
{
// FIXME: create a matrix from the beam start/end orientation
- Matrix4x4_CreateTranslate(&tempmatrix, b->end[0], b->end[1], b->end[2]);
+ Matrix4x4_CreateTranslate(&tempmatrix, end[0], end[1], end[2]);
CL_AllocDlight (NULL, &tempmatrix, 200, 0.3, 0.7, 1, 0, 0, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
}
if (cl_beams_polygons.integer)
}
// calculate pitch and yaw
- VectorSubtract (b->end, b->start, dist);
-
+ // (this is similar to the QuakeC builtin function vectoangles)
+ VectorSubtract(end, start, dist);
if (dist[1] == 0 && dist[0] == 0)
{
yaw = 0;
}
// add new entities for the lightning
- VectorCopy (b->start, org);
+ VectorCopy (start, org);
d = VectorNormalizeLength(dist);
while (d > 0)
{
Cvar_RegisterVariable(&cl_stainmaps);
Cvar_RegisterVariable(&cl_stainmaps_clearonload);
Cvar_RegisterVariable(&cl_beams_polygons);
- Cvar_RegisterVariable(&cl_beams_relative);
+ Cvar_RegisterVariable(&cl_beams_quakepositionhack);
+ Cvar_RegisterVariable(&cl_beams_instantaimhack);
Cvar_RegisterVariable(&cl_beams_lightatend);
Cvar_RegisterVariable(&cl_noplayershadow);
CL_Effect(org, modelindex, startframe, framecount, framerate);
}
-void CL_ParseBeam (model_t *m, int lightning)
+void CL_NewBeam (int ent, vec3_t start, vec3_t end, model_t *m, int lightning)
{
- int i, ent;
- vec3_t start, end;
+ int i;
beam_t *b = NULL;
- ent = (unsigned short) MSG_ReadShort ();
- MSG_ReadVector(start, cls.protocol);
- MSG_ReadVector(end, cls.protocol);
-
if (ent >= MAX_EDICTS)
{
- Con_Printf("CL_ParseBeam: invalid entity number %i\n", ent);
+ Con_Printf("CL_NewBeam: invalid entity number %i\n", ent);
ent = 0;
}
// if the entity was not found then just replace an unused beam
if (i == cl.max_beams)
for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++)
- if (!b->model || b->endtime < cl.time)
+ if (!b->model)
break;
if (i < cl.max_beams)
{
b->entity = ent;
b->lightning = lightning;
b->model = m;
- b->endtime = cl.time + 0.2;
+ b->endtime = cl.mtime[0] + 0.2;
VectorCopy (start, b->start);
VectorCopy (end, b->end);
- b->relativestartvalid = 0;
- if (ent && cl.entities[ent].state_current.active)
- {
- entity_state_t *p;
- matrix4x4_t matrix, imatrix;
- if (ent == cl.viewentity && cl.movement)
- p = &cl.entities[b->entity].state_previous;
- else
- p = &cl.entities[b->entity].state_current;
- // not really valid yet, we need to get the orientation now
- // (ParseBeam flagged this because it is received before
- // entities are received, by now they have been received)
- // note: because players create lightning in their think
- // function (which occurs before movement), they actually
- // have some lag in it's location, so compare to the
- // previous player state, not the latest
- Matrix4x4_CreateFromQuakeEntity(&matrix, p->origin[0], p->origin[1], p->origin[2], -p->angles[0], p->angles[1], p->angles[2], 1);
- Matrix4x4_Invert_Simple(&imatrix, &matrix);
- Matrix4x4_Transform(&imatrix, b->start, b->relativestart);
- Matrix4x4_Transform(&imatrix, b->end, b->relativeend);
- b->relativestartvalid = 1;
- }
}
else
Con_Print("beam list overflow!\n");
}
+void CL_ParseBeam (model_t *m, int lightning)
+{
+ int ent;
+ vec3_t start, end;
+
+ ent = (unsigned short) MSG_ReadShort ();
+ MSG_ReadVector(start, cls.protocol);
+ MSG_ReadVector(end, cls.protocol);
+
+ if (ent >= MAX_EDICTS)
+ {
+ Con_Printf("CL_ParseBeam: invalid entity number %i\n", ent);
+ ent = 0;
+ }
+
+ CL_NewBeam(ent, start, end, m, lightning);
+}
+
void CL_ParseTempEntity(void)
{
int type;
struct model_s *model;
float endtime;
vec3_t start, end;
- // if this beam is owned by an entity, this is the beam start relative to
- // that entity's matrix for per frame start updates
- vec3_t relativestart;
- vec3_t relativeend;
- // indicates whether relativestart is valid
- int relativestartvalid;
}
beam_t;
void CL_BoundingBoxForEntity(entity_render_t *ent);
-extern cvar_t cl_beams_polygons;
-extern cvar_t cl_beams_relative;
-extern cvar_t cl_beams_lightatend;
-
//
// cl_input
//
void CL_MoveLerpEntityStates(entity_t *ent);
void CL_LerpUpdate(entity_t *e);
void CL_ParseTEnt (void);
+void CL_NewBeam (int ent, vec3_t start, vec3_t end, model_t *m, int lightning);
void CL_RelinkBeams (void);
+void CL_Beam_CalculatePositions (const beam_t *b, vec3_t start, vec3_t end);
void CL_ClearTempEntities (void);
entity_t *CL_NewTempEntity (void);
PRVM_G_FLOAT(OFS_RETURN) = i;
}
-void CSQC_ParseBeam (int ent, vec3_t start, vec3_t end, model_t *m, int lightning)
-{
- int i;
- beam_t *b;
-
- // override any beam with the same entity
- for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++)
- {
- if (b->entity == ent && ent)
- {
- //b->entity = ent;
- b->lightning = lightning;
- b->relativestartvalid = (ent && cl.csqcentities[ent].state_current.active) ? 2 : 0;
- b->model = m;
- b->endtime = cl.time + 0.2;
- VectorCopy (start, b->start);
- VectorCopy (end, b->end);
- return;
- }
- }
-
- // find a free beam
- for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++)
- {
- if (!b->model || b->endtime < cl.time)
- {
- b->entity = ent;
- b->lightning = lightning;
- b->relativestartvalid = (ent && cl.csqcentities[ent].state_current.active) ? 2 : 0;
- b->model = m;
- b->endtime = cl.time + 0.2;
- VectorCopy (start, b->start);
- VectorCopy (end, b->end);
- return;
- }
- }
- Con_Print("beam list overflow!\n");
-}
-
// #336 void(entity ent, float effectnum, vector start, vector end[, float color]) trailparticles (EXT_CSQC)
void VM_CL_trailparticles (void)
{
}
-static void VM_CL_NewBeam (int ent, float *start, float *end, model_t *m, qboolean lightning)
-{
- beam_t *b;
- int i;
-
- if (ent >= cl.max_csqcentities)
- CL_ExpandCSQCEntities(ent);
-
- // override any beam with the same entity
- for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++)
- {
- if (b->entity == ent && ent)
- {
- //b->entity = ent;
- b->lightning = lightning;
- b->relativestartvalid = (ent && cl.csqcentities[ent].state_current.active) ? 2 : 0;
- b->model = m;
- b->endtime = cl.time + 0.2;
- VectorCopy (start, b->start);
- VectorCopy (end, b->end);
- return;
- }
- }
-
- // find a free beam
- for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++)
- {
- if (!b->model || b->endtime < cl.time)
- {
- b->entity = ent;
- b->lightning = lightning;
- b->relativestartvalid = (ent && cl.csqcentities[ent].state_current.active) ? 2 : 0;
- b->model = m;
- b->endtime = cl.time + 0.2;
- VectorCopy (start, b->start);
- VectorCopy (end, b->end);
- return;
- }
- }
- Con_Print("beam list overflow!\n");
-}
-
// #428 void(entity own, vector start, vector end) te_lightning1 (DP_TE_STANDARDEFFECTBUILTINS)
void VM_CL_te_lightning1 (void)
{
VM_SAFEPARMCOUNT(3, VM_CL_te_lightning1);
- VM_CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), cl.model_bolt, true);
+ CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), cl.model_bolt, true);
}
// #429 void(entity own, vector start, vector end) te_lightning2 (DP_TE_STANDARDEFFECTBUILTINS)
void VM_CL_te_lightning2 (void)
{
VM_SAFEPARMCOUNT(3, VM_CL_te_lightning2);
- VM_CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), cl.model_bolt2, true);
+ CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), cl.model_bolt2, true);
}
// #430 void(entity own, vector start, vector end) te_lightning3 (DP_TE_STANDARDEFFECTBUILTINS)
void VM_CL_te_lightning3 (void)
{
VM_SAFEPARMCOUNT(3, VM_CL_te_lightning3);
- VM_CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), cl.model_bolt3, false);
+ CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), cl.model_bolt3, false);
}
// #431 void(entity own, vector start, vector end) te_beam (DP_TE_STANDARDEFFECTBUILTINS)
void VM_CL_te_beam (void)
{
VM_SAFEPARMCOUNT(3, VM_CL_te_beam);
- VM_CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), cl.model_beam, false);
+ CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), PRVM_G_VECTOR(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM2), cl.model_beam, false);
}
// #433 void(vector org) te_plasmaburn (DP_TE_PLASMABURN)
}
}
-#define OPTIONS_EFFECTS_ITEMS 35
+#define OPTIONS_EFFECTS_ITEMS 36
static int options_effects_cursor;
extern cvar_t r_explosionclip;
extern cvar_t r_coronas;
extern cvar_t gl_flashblend;
-extern cvar_t cl_beams_polygon;
-extern cvar_t cl_beams_relative;
+extern cvar_t cl_beams_polygons;
+extern cvar_t cl_beams_quakepositionhack;
+extern cvar_t cl_beams_instantaimhack;
extern cvar_t cl_beams_lightatend;
extern cvar_t r_lightningbeam_thickness;
extern cvar_t r_lightningbeam_scroll;
else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_blood_alpha, bound(0.2, cl_particles_blood_alpha.value + dir * 0.1, 1));
else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_blood_bloodhack, !cl_particles_blood_bloodhack.integer);
else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_polygons, !cl_beams_polygons.integer);
- else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_relative, !cl_beams_relative.integer);
+ else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_instantaimhack, !cl_beams_instantaimhack.integer);
+ else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_quakepositionhack, !cl_beams_quakepositionhack.integer);
else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_lightatend, !cl_beams_lightatend.integer);
else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_thickness, bound(1, r_lightningbeam_thickness.integer + dir, 10));
else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_scroll, bound(0, r_lightningbeam_scroll.integer + dir, 10));
M_Options_PrintCheckbox(" Blood", true, cl_particles_blood.integer);
M_Options_PrintSlider( " Blood Opacity", true, cl_particles_blood_alpha.value, 0.2, 1);
M_Options_PrintCheckbox("Force New Blood Effect", true, cl_particles_blood_bloodhack.integer);
- M_Options_PrintCheckbox(" Lightning Polygons", true, cl_beams_polygons.integer);
- M_Options_PrintCheckbox("Lightning Smooth Sweep", true, cl_beams_relative.integer);
+ M_Options_PrintCheckbox(" Polygon Lightning", true, cl_beams_polygons.integer);
+ M_Options_PrintCheckbox("Smooth Sweep Lightning", true, cl_beams_instantaimhack.integer);
+ M_Options_PrintCheckbox(" Waist-level Lightning", true, cl_beams_quakepositionhack.integer);
M_Options_PrintCheckbox(" Lightning End Light", true, cl_beams_lightatend.integer);
M_Options_PrintSlider( " Lightning Thickness", cl_beams_polygons.integer, r_lightningbeam_thickness.integer, 1, 10);
M_Options_PrintSlider( " Lightning Scroll", cl_beams_polygons.integer, r_lightningbeam_scroll.integer, 0, 10);
for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
{
const beam_t *b = cl.beams + surfacelist[surfacelistindex];
- vec3_t beamdir, right, up, offset;
+ vec3_t beamdir, right, up, offset, start, end;
float length, t1, t2;
+
+ CL_Beam_CalculatePositions(b, start, end);
// calculate beam direction (beamdir) vector and beam length
// get difference vector
- VectorSubtract(b->end, b->start, beamdir);
+ VectorSubtract(end, start, beamdir);
// find length of difference vector
length = sqrt(DotProduct(beamdir, beamdir));
// calculate scale to make beamdir a unit vector (normalized)
// calculate up vector such that it points toward viewer, and rotates around the beamdir
// get direction from start of beam to viewer
- VectorSubtract(r_view.origin, b->start, up);
+ VectorSubtract(r_view.origin, start, up);
// remove the portion of the vector that moves along the beam
// (this leaves only a vector pointing directly away from the beam)
t1 = -DotProduct(up, beamdir);
VectorNormalize(up);
// calculate T coordinate scrolling (start and end texcoord along the beam)
- t1 = r_refdef.time * -r_lightningbeam_scroll.value;// + beamrepeatscale * DotProduct(b->start, beamdir);
+ t1 = r_refdef.time * -r_lightningbeam_scroll.value;// + beamrepeatscale * DotProduct(start, beamdir);
t1 = t1 - (int) t1;
t2 = t1 + beamrepeatscale * length;
// polygon 1, verts 0-3
VectorScale(right, r_lightningbeam_thickness.value, offset);
- R_CalcLightningBeamPolygonVertex3f(vertex3f + 0, b->start, b->end, offset);
+ R_CalcLightningBeamPolygonVertex3f(vertex3f + 0, start, end, offset);
// polygon 2, verts 4-7
VectorAdd(right, up, offset);
VectorScale(offset, r_lightningbeam_thickness.value * 0.70710681f, offset);
- R_CalcLightningBeamPolygonVertex3f(vertex3f + 12, b->start, b->end, offset);
+ R_CalcLightningBeamPolygonVertex3f(vertex3f + 12, start, end, offset);
// polygon 3, verts 8-11
VectorSubtract(right, up, offset);
VectorScale(offset, r_lightningbeam_thickness.value * 0.70710681f, offset);
- R_CalcLightningBeamPolygonVertex3f(vertex3f + 24, b->start, b->end, offset);
+ R_CalcLightningBeamPolygonVertex3f(vertex3f + 24, start, end, offset);
R_CalcLightningBeamPolygonTexCoord2f(texcoord2f + 0, t1, t2);
R_CalcLightningBeamPolygonTexCoord2f(texcoord2f + 8, t1 + 0.33, t2 + 0.33);
R_CalcLightningBeamPolygonTexCoord2f(texcoord2f + 16, t1 + 0.66, t2 + 0.66);
}
}
+extern cvar_t cl_beams_polygons;
void R_DrawLightningBeams(void)
{
int i;
beam_t *b;
- vec3_t org;
if (!cl_beams_polygons.integer)
return;
beamrepeatscale = 1.0f / r_lightningbeam_repeatdistance.value;
for (i = 0, b = cl.beams;i < cl.num_beams;i++, b++)
{
- if (b->model && b->endtime >= r_refdef.time && b->lightning)
+ if (b->model && b->lightning)
{
- VectorAdd(b->start, b->end, org);
- VectorScale(org, 0.5f, org);
+ vec3_t org, start, end, dir;
+ vec_t dist;
+ CL_Beam_CalculatePositions(b, start, end);
+ // calculate the nearest point on the line (beam) for depth sorting
+ VectorSubtract(end, start, dir);
+ dist = (DotProduct(r_view.origin, dir) - DotProduct(start, dir)) / (DotProduct(end, dir) - DotProduct(start, dir));
+ dist = bound(0, dist, 1);
+ VectorLerp(start, dist, end, org);
+ // now we have the nearest point on the line, so sort with it
R_MeshQueue_AddTransparent(org, R_DrawLightningBeam_TransparentCallback, NULL, i, NULL);
}
}
0 bug darkplaces server: the lava+func_trains room of r1m5 is leaving items floating in the air - r1m5 is Towers of Wrath, in episode of Dissolution of Eternity, aka rogue (maichal)
0 bug darkplaces video: generate 1024 color gamma ramps for glx on Quadro, right now hardware gamma is being disabled on these cards because they use 1024 color ramps, not 256 (div0)
0 bug darkplaces wgl client: during video mode setup, sometimes another application's window becomes permanently top most, not darkplaces' fullscreen window, why? (tZork)
+0 bug darkplaces wgl client: hardware gamma is being retried every frame for unknown reasons, this is SEVERELY impacting framerates on win2k/xp (Jago)
0 bug darkplaces windows sound: freezing on exit sometimes when freeing sound buffer during sound shutdown (Black)
0 bug dpmod: allow selection of weapons with secondary ammo but no primary ammo, and switch away if trying to fire primary ammo you don't have (romi)
0 bug dpmod: chthon stops attacking in coop if shot enough
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
0 change darkplaces client: get image sizes from .lmp files if present
0 change darkplaces client: hardcode sbar image sizes so they can be replaced with higher quality images
+0 change darkplaces client: modify cl_particles_quake to make all the engine dlights be white and look as much like quake as possible (Jago)
0 change darkplaces client: particles shouldn't be using contents checks to decide whether to die, they should use movement traces
0 change darkplaces client: restrict wateralpha and such cvars according to what is permitted in qw serverinfo?
+0 change darkplaces client: turn off coronas on dlights (Jago)
0 change darkplaces extensions: edit FRIK_FILE documentation to mention that fgets uses its own separate buffer, so only one fgets can be done at a time without uzing strzone, but that darkplaces uses standard tempstrings for fgets (it doesn't - change it!) and mention DP_QC_MULTIPLETEMPSTRINGS (FrikaC)
0 change darkplaces extensions: edit FRIK_FILE documentation to mention that strcat uses its own separate buffer, and that a = strcat(a, b);a = strcat(a, c); works correctly despite this, also mention that in DP strcat uses standard tempstrings, and mention DP_QC_MULTIPLETEMPSTRINGS (FrikaC)
0 change darkplaces general: make r_speeds 2 show timings for other subsystems such as client, sound, server, network (Carni)
2 feature darkplaces loader: add support for fuhquake naming of map textures (textures/start/quake.tga style)
2 feature darkplaces menu: add some basic graphics/effects options profiles so that people can choose profiles like "Classic", "Modern", "Excessive", "Realistic", or any other profiles that make sense, may also need to reorganize the graphics/effects options menus to be a bit less confusing (Tron)
2 feature darkplaces menu: implement menu_clearkeyconfig and menu_keyconfig and the corresponding menu (diGGer)
+2 feature darkplaces menu: new game custom level/mod menu, which allows you to choose a mod and browse through maps and choose starting skill, by default it would have the current mod selected and start selected (Jago)
2 feature darkplaces model: add model_exportobj console command to allow exporting a specified model as .obj (Randy)
2 feature darkplaces physics: DP_SV_QCPHYSICS extension, calls SV_PhysicsQC function, which replaces the entire SV_Physics C function, calling all thinks and physics and everything (Urre)
2 feature darkplaces protocol: .float bulge; field which makes an entity larger by moving vertices along their normals, well known as the fatboy mutator in Unreal Tournament (Wazat)