forked from fte/fteqw
1
0
Fork 0

Add r_meshroll, like r_meshpitch (hexen2 requires it set to -1 - they made the bug worse).

HTTP client code now tracks the reason for failure better (so we can distinguish between dns, no response, disconnects, and tls cert issues).
Downloads menu shows reasons for failure for its various sources.
make hexen2's +infoplaque button work even when tab isn't held! Yes! usability! who'd a thought it?
Try to clean up some client-only build issues.
Added 'librequake' as a recognised game name (mostly so that we can reuse the quake-specific engine compat settings.
.map support now supports patches enough to query+create(+network)+select them. Collisions are basically defective though.
qcc: Remove char as default keyword.
qcc: Fix some recent regressions.
sv: Add 'sv_protocol csqc' setting to force csqc protocols on clients that disabled handshakes, for mods that NEED csqc support. other options include 'fte1' and 'fte2', if you want to force extra stuff. Engines that don't support the selected protocols will crash out so only set these if your mod/map always requires it.
sv: default to qw physics whenever a mod defines the SV_RunClientCommand, even if its an nq mod.


git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5775 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2020-10-06 03:17:28 +00:00
parent 524fdb3dfd
commit 432bc96456
39 changed files with 1100 additions and 298 deletions

View File

@ -2013,11 +2013,7 @@ void CL_RotateAroundTag(entity_t *ent, int entnum, int parenttagent, int parentt
else
model = NULL;
if (model && model->type == mod_alias)
{
ang[0]*=r_meshpitch.value;
AngleVectors(ang, axis[0], axis[1], axis[2]);
ang[0]*=r_meshpitch.value;
}
AngleVectorsMesh(ang, axis[0], axis[1], axis[2]);
else
AngleVectors(ang, axis[0], axis[1], axis[2]);
VectorInverse(axis[1]);
@ -2160,10 +2156,8 @@ entity_t *V_AddEntity(entity_t *in)
*ent = *in;
ent->angles[0]*=r_meshpitch.value;
AngleVectors(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
AngleVectorsMesh(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
VectorInverse(ent->axis[1]);
ent->angles[0]*=r_meshpitch.value;
return ent;
}
@ -2190,10 +2184,8 @@ void VQ2_AddLerpEntity(entity_t *in) //a convienience function
ent->framestate.g[FS_REG].lerpfrac = back;
ent->angles[0]*=r_meshpitch.value;
AngleVectors(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
AngleVectorsMesh(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
VectorInverse(ent->axis[1]);
ent->angles[0]*=r_meshpitch.value;
}
*/
int V_AddLight (int entsource, vec3_t org, float quant, float r, float g, float b)
@ -3333,10 +3325,8 @@ void CL_LinkStaticEntities(void *pvs, int *areas)
//figure out the correct axis for the model
if (clmodel && clmodel->type == mod_alias && (cls.protocol == CP_QUAKEWORLD || cls.protocol == CP_NETQUAKE))
{ //q2 is fixed, but q1 pitches the wrong way
stat->state.angles[0]*=r_meshpitch.value;
AngleVectors(stat->state.angles, stat->ent.axis[0], stat->ent.axis[1], stat->ent.axis[2]);
stat->state.angles[0]*=r_meshpitch.value;
{ //q2 is fixed, but q1 pitches the wrong way, and hexen2 rolls the wrong way too.
AngleVectorsMesh(stat->state.angles, stat->ent.axis[0], stat->ent.axis[1], stat->ent.axis[2]);
}
else
AngleVectors(stat->state.angles, stat->ent.axis[0], stat->ent.axis[1], stat->ent.axis[2]);
@ -4072,8 +4062,9 @@ void CL_LinkPacketEntities (void)
{
VectorCopy(le->angles, angles);
//if (model && model->type == mod_alias)
angles[0]*=r_meshpitch.value; //pflags matches alias models.
AngleVectors(angles, dl->axis[0], dl->axis[1], dl->axis[2]);
AngleVectorsMesh(angles, dl->axis[0], dl->axis[1], dl->axis[2]); //pflags matches alias models.
//else
// AngleVectors(angles, dl->axis[0], dl->axis[1], dl->axis[2]);
VectorInverse(dl->axis[1]);
R_LoadNumberedLightTexture(dl, state->skinnum);
}
@ -4333,10 +4324,8 @@ void CL_LinkPacketEntities (void)
}
}
if (model && model->type == mod_alias)
angles[0]*=r_meshpitch.value; //carmack screwed up when he added alias models - they pitch the wrong way.
VectorCopy(angles, ent->angles);
AngleVectors(angles, ent->axis[0], ent->axis[1], ent->axis[2]);
AngleVectorsMesh(angles, ent->axis[0], ent->axis[1], ent->axis[2]);
VectorInverse(ent->axis[1]);
/*if this entity is in a player's slot...*/
@ -4578,10 +4567,8 @@ void CL_LinkProjectiles (void)
VectorCopy (pr->origin, ent->origin);
VectorCopy (pr->angles, ent->angles);
ent->angles[0]*=r_meshpitch.value;
AngleVectors(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
AngleVectorsMesh(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
VectorInverse(ent->axis[1]);
ent->angles[0]*=r_meshpitch.value;
}
}
@ -5110,8 +5097,7 @@ void CL_AddFlagModels (entity_t *ent, int team)
newent->angles[2] -= 45;
VectorCopy(newent->angles, angles);
angles[0]*=r_meshpitch.value;
AngleVectors(angles, newent->axis[0], newent->axis[1], newent->axis[2]);
AngleVectorsMesh(angles, newent->axis[0], newent->axis[1], newent->axis[2]);
VectorInverse(newent->axis[1]);
}
@ -5369,7 +5355,10 @@ void CL_LinkPlayers (void)
}
if (model && model->type == mod_alias)
{
angles[0]*=r_meshpitch.value; //carmack screwed up when he added alias models - they pitch the wrong way.
angles[2]*=r_meshroll.value; //hexen2 screwed it up even more - they roll the wrong way.
}
VectorCopy(angles, ent->angles);
AngleVectors(angles, ent->axis[0], ent->axis[1], ent->axis[2]);
VectorInverse(ent->axis[1]);
@ -5707,6 +5696,7 @@ void CL_SetSolidEntities (void)
pent->model = mod;
VectorCopy (state->angles, pent->angles);
pent->angles[0]*=r_meshpitch.value;
pent->angles[2]*=r_meshroll.value;
}
else
{

View File

@ -4679,7 +4679,7 @@ void CL_Status_f(void)
for (i = 0; i < csqc_world.num_edicts; i++)
{
e = EDICT_NUM_PB(csqc_world.progs, i);
if (e && e->ereftype == ER_FREE && sv.time - e->freetime > 0.5)
if (e && e->ereftype == ER_FREE && Sys_DoubleTime() - e->freetime > 0.5)
continue; //free, and older than the zombie time
count++;
}
@ -5972,6 +5972,7 @@ void CL_UpdateHeadAngles(void)
R_ConcatRotations(headchange, tmp, tmp2);
VectorAngles(tmp2[0], tmp2[2], pv->viewangles);
pv->viewangles[0] *= r_meshpitch.value;
pv->viewangles[2] *= r_meshroll.value;
//fall through
default:

View File

@ -4803,11 +4803,7 @@ static void CL_ParseStaticProt (int baselinetype)
VectorCopy (es.origin, ent->origin);
VectorCopy (es.angles, ent->angles);
if (ent->model && ent->model->type == mod_alias)
{
es.angles[0]*=r_meshpitch.value;
AngleVectors(es.angles, ent->axis[0], ent->axis[1], ent->axis[2]);
es.angles[0]*=r_meshpitch.value;
}
AngleVectorsMesh(es.angles, ent->axis[0], ent->axis[1], ent->axis[2]);
else
AngleVectors(es.angles, ent->axis[0], ent->axis[1], ent->axis[2]);
VectorInverse(ent->axis[1]);

View File

@ -1425,6 +1425,7 @@ void CL_PredictMovePNum (int seat)
if (pv->pmovetype != PM_6DOF)
le->angles[0] *= 0.333;
le->angles[0] *= r_meshpitch.value;
le->angles[2] *= r_meshroll.value;
}
}

View File

@ -2496,6 +2496,7 @@ void CL_SpawnSpriteEffect(vec3_t org, vec3_t dir, vec3_t orientationup, model_t
else
ex->angles[1] = 0;
ex->angles[0]*=r_meshpitch.value;
ex->angles[2]*=r_meshroll.value;
}
@ -3205,6 +3206,7 @@ void CL_UpdateExplosions (void)
VectorCopy (ex->angles, ent->angles);
ent->skinnum = ex->skinnum;
ent->angles[0]*=r_meshpitch.value;
ent->angles[2]*=r_meshroll.value;
AngleVectors(ent->angles, ent->axis[0], ent->axis[1], ent->axis[2]);
VectorInverse(ent->axis[1]);
ent->model = ex->model;

View File

@ -1998,7 +1998,8 @@ static void CLQ2_AddPacketEntities (q2frame_t *frame)
}
}
ent.angles[0]*=r_meshpitch.value; //q2 has it fixed.
ent.angles[0]*=r_meshpitch.value; //q2 has it fixed. consistency...
ent.angles[2]*=r_meshroll.value; //h2 doesn't. consistency...
if (s1->number == cl.playerview[0].playernum+1) //woo! this is us!
{

View File

@ -2680,9 +2680,7 @@ static void Con_DrawModelPreview(model_t *model, float x, float y, float w, floa
ent.scale = 1;
ent.angles[1] = realtime*90;//mods->yaw;
// ent.angles[0] = realtime*23.4;//mods->pitch;
ent.angles[0]*=r_meshpitch.value;
AngleVectors(ent.angles, ent.axis[0], ent.axis[1], ent.axis[2]);
ent.angles[0]*=r_meshpitch.value;
AngleVectorsMesh(ent.angles, ent.axis[0], ent.axis[1], ent.axis[2]);
VectorInverse(ent.axis[1]);
//ent.origin[2] -= (ent.model->maxs[2]-ent.model->mins[2]) * 0.5 + ent.model->mins[2];
@ -2725,9 +2723,7 @@ static void Con_DrawModelPreview(model_t *model, float x, float y, float w, floa
if (ent.model->camerabone>0 && Mod_GetTag(ent.model, ent.model->camerabone, &ent.framestate, transforms))
{
VectorClear(ent.origin);
ent.angles[0]*=r_meshpitch.value;
AngleVectors(ent.angles, ent.axis[0], ent.axis[1], ent.axis[2]);
ent.angles[0]*=r_meshpitch.value;
AngleVectorsMesh(ent.angles, ent.axis[0], ent.axis[1], ent.axis[2]);
VectorInverse(ent.axis[1]);
scale = 1;
{
@ -2749,9 +2745,7 @@ static void Con_DrawModelPreview(model_t *model, float x, float y, float w, floa
else
{
ent.angles[1] = realtime*90;//mods->yaw;
ent.angles[0]*=r_meshpitch.value;
AngleVectors(ent.angles, ent.axis[0], ent.axis[1], ent.axis[2]);
ent.angles[0]*=r_meshpitch.value;
AngleVectorsMesh(ent.angles, ent.axis[0], ent.axis[1], ent.axis[2]);
VectorScale(ent.axis[0], scale, ent.axis[0]);
VectorScale(ent.axis[1], -scale, ent.axis[1]);
VectorScale(ent.axis[2], scale, ent.axis[2]);

View File

@ -223,7 +223,11 @@ static struct
{
SRCSTAT_UNKNOWN, //we don't know whether the user wants to allow or block this source.
SRCSTAT_DISABLED, //do NOT query this source
SRCSTAT_FAILED, //tried but failed. FIXME: add some reasons.
SRCSTAT_FAILED_DNS, //tried but failed, unresolvable
SRCSTAT_FAILED_NORESP, //tried but failed, no response.
SRCSTAT_FAILED_EOF, //tried but failed, abrupt termination.
SRCSTAT_FAILED_MITM, //tried but failed. misc cert problems.
SRCSTAT_FAILED_HTTP, //tried but failed, misc http failure
SRCSTAT_PENDING, //waiting for response (or queued). don't show package list yet.
SRCSTAT_OBTAINED, //we got a response.
} status;
@ -2081,7 +2085,7 @@ static void PM_ListDownloaded(struct dl_download *dl)
size_t listidx = dl->user_num;
size_t sz = 0;
char *f = NULL;
if (dl->file)
if (dl->file && dl->status != DL_FAILED)
{
sz = VFS_GETLEN(dl->file);
f = BZ_Malloc(sz+1);
@ -2115,10 +2119,20 @@ static void PM_ListDownloaded(struct dl_download *dl)
{
downloadablelist[listidx].status = SRCSTAT_OBTAINED;
PM_ParsePackageList(f, 0, dl->url, downloadablelist[listidx].prefix);
BZ_Free(f);
}
else if (dl->replycode == HTTP_DNSFAILURE)
downloadablelist[listidx].status = SRCSTAT_FAILED_DNS;
else if (dl->replycode == HTTP_NORESPONSE)
downloadablelist[listidx].status = SRCSTAT_FAILED_NORESP;
else if (dl->replycode == HTTP_EOF)
downloadablelist[listidx].status = SRCSTAT_FAILED_EOF;
else if (dl->replycode == HTTP_MITM || dl->replycode == HTTP_UNTRUSTED)
downloadablelist[listidx].status = SRCSTAT_FAILED_MITM;
else if (dl->replycode && dl->replycode < 900)
downloadablelist[listidx].status = SRCSTAT_FAILED_HTTP;
else
downloadablelist[listidx].status = SRCSTAT_FAILED;
downloadablelist[listidx].status = SRCSTAT_FAILED_EOF;
BZ_Free(f);
if (!doautoupdate && !domanifestinstall)
return; //don't spam this.
@ -2252,7 +2266,7 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry)
if (allowphonehome<=0)
{
downloadablelist[i].status = SRCSTAT_FAILED;
downloadablelist[i].status = SRCSTAT_FAILED_DNS;
continue;
}
downloadablelist[i].curdl = HTTP_CL_Get(va("%s%s"DOWNLOADABLESARGS, downloadablelist[i].url, strchr(downloadablelist[i].url,'?')?"&":"?"), NULL, PM_ListDownloaded);
@ -2267,7 +2281,7 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry)
else
{
Con_Printf("Could not contact updates server - %s\n", downloadablelist[i].url);
downloadablelist[i].status = SRCSTAT_FAILED;
downloadablelist[i].status = SRCSTAT_FAILED_DNS;
}
}
@ -3030,7 +3044,7 @@ int PM_IsApplying(qboolean listsonly)
int count = 0;
#ifdef WEBCLIENT
package_t *p;
int i;
// int i;
if (!listsonly)
{
for (p = availablepackages; p ; p=p->next)
@ -3039,11 +3053,11 @@ int PM_IsApplying(qboolean listsonly)
count++;
}
}
for (i = 0; i < numdownloadablelists; i++)
/*for (i = 0; i < numdownloadablelists; i++)
{
if (downloadablelist[i].curdl)
count++;
}
}*/
#endif
return count;
}
@ -4541,10 +4555,33 @@ static void MD_Source_Draw (int x, int y, struct menucustom_s *c, struct emenu_s
switch(downloadablelist[c->dint].status)
{
case SRCSTAT_OBTAINED:
case SRCSTAT_PENDING:
Draw_FunStringWidth (x, y, "^&02 ", 48, 2, false); //green
break;
case SRCSTAT_FAILED:
case SRCSTAT_PENDING:
Draw_FunStringWidth (x, y, "^&0E ", 48, 2, false); //yellow
Draw_FunStringWidth (x, y, "??", 48, 2, false); //this should be fast... so if they get a chance to see the ?? then there's something bad happening, and the ?? is appropriate.
break;
case SRCSTAT_FAILED_DNS:
Draw_FunStringWidth (x, y, "^&0E ", 48, 2, false); //yellow
Draw_FunStringWidth (x, y, "DNS", 48, 2, false);
break;
case SRCSTAT_FAILED_NORESP:
Draw_FunStringWidth (x, y, "^&0E ", 48, 2, false); //yellow
Draw_FunStringWidth (x, y, "NR", 48, 2, false);
break;
case SRCSTAT_FAILED_EOF:
Draw_FunStringWidth (x, y, "^&0E ", 48, 2, false); //yellow
Draw_FunStringWidth (x, y, "EOF", 48, 2, false);
break;
case SRCSTAT_FAILED_MITM:
Draw_FunStringWidth (x, y, "^&04 ", 48, 2, false); //red
Draw_FunStringWidth (x, y, "^bMITM", 48, 2, false);
break;
case SRCSTAT_FAILED_HTTP:
Draw_FunStringWidth (x, y, "^&0E ", 48, 2, false); //yellow
Draw_FunStringWidth (x, y, "404", 48, 2, false);
break;
default:
case SRCSTAT_UNKNOWN:
Draw_FunStringWidth (x, y, "^&0E ", 48, 2, false); //yellow
break;
@ -4564,7 +4601,7 @@ static qboolean MD_Source_Key (struct menucustom_s *c, struct emenu_s *m, int ke
{
case SRCSTAT_OBTAINED:
case SRCSTAT_PENDING:
case SRCSTAT_FAILED:
default: //various failures
case SRCSTAT_UNKNOWN:
downloadablelist[c->dint].status = SRCSTAT_DISABLED;
break;
@ -4865,18 +4902,18 @@ static void MD_Download_UpdateStatus(struct emenu_s *m)
if (!info->populated)
{
for (i = 0; i < numdownloadablelists; i++)
y = 48;
/*for (i = 0; i < numdownloadablelists; i++)
{
if (downloadablelist[i].status == SRCSTAT_PENDING)
{
Draw_FunStringWidth(0, vid.height - 8, "Querying for package list", vid.width, 2, false);
Draw_FunStringWidth(0, y, "Querying for package list", vid.width, 2, false);
return;
}
}
}*/
info->populated = true;
MC_AddFrameStart(m, 48);
y = 48;
#ifdef WEBCLIENT
for (i = 0; i < numdownloadablelists; i++)
{

View File

@ -2338,6 +2338,7 @@ qboolean M_Apply_SP_Cheats_H2 (union menuoption_s *op,struct emenu_s *menu,int k
if (key != K_ENTER && key != K_KP_ENTER && key != K_GP_START && key != K_MOUSE1)
return false;
#ifdef HAVE_SERVER
switch(info->skillcombo->selectedoption)
{
case 0:
@ -2356,6 +2357,7 @@ qboolean M_Apply_SP_Cheats_H2 (union menuoption_s *op,struct emenu_s *menu,int k
if ((unsigned)info->mapcombo->selectedoption < countof(maplist_h2))
Cbuf_AddText(va("map %s\n", maplist_h2[info->mapcombo->selectedoption]), RESTRICT_LOCAL);
#endif
M_RemoveMenu(menu);
Cbuf_AddText("menu_spcheats\n", RESTRICT_LOCAL);
@ -2404,8 +2406,8 @@ void M_Menu_Singleplayer_Cheats_Hexen2 (void)
y+=8;
#ifdef HAVE_SERVER
info->skillcombo = MC_AddCombo(menu,16,170, y, "Difficulty", skilloptions, currentskill); y+=8;
#endif
info->mapcombo = MC_AddCombo(menu,16,170, y, "Map", mapoptions_h2, currentmap); y+=8;
#endif
#ifdef HAVE_SERVER
MC_AddCheckBox(menu, 16, 170, y, "Cheats", &sv_cheats,0); y+=8;
#endif
@ -3264,14 +3266,16 @@ static void M_ModelViewerDraw(int x, int y, struct menucustom_s *c, struct emenu
// ent.angles[1] = realtime*45;//mods->yaw;
// ent.angles[0] = realtime*23.4;//mods->pitch;
ent.angles[0]*=r_meshpitch.value;
AngleVectors(ent.angles, ent.axis[0], ent.axis[1], ent.axis[2]);
ent.angles[0]*=r_meshpitch.value;
VectorInverse(ent.axis[1]);
ent.model = Mod_ForName(mods->modelname, MLV_WARN);
if (!ent.model)
return; //panic!
if (ent.model->type == mod_alias) //should we even bother with this here?
AngleVectorsMesh(ent.angles, ent.axis[0], ent.axis[1], ent.axis[2]);
else
AngleVectors(ent.angles, ent.axis[0], ent.axis[1], ent.axis[2]);
VectorInverse(ent.axis[1]);
ent.scale = max(max(fabs(ent.model->maxs[0]-ent.model->mins[0]), fabs(ent.model->maxs[1]-ent.model->mins[1])), fabs(ent.model->maxs[2]-ent.model->mins[2]));
ent.scale = ent.scale?64.0/ent.scale:1;
// ent.scale = 1;

View File

@ -6827,6 +6827,11 @@ static struct {
{"brush_getfacepoints", PF_brush_getfacepoints, 0},
{"brush_calcfacepoints", PF_brush_calcfacepoints,0},
{"brush_findinvolume", PF_brush_findinvolume, 0},
{"patch_getcp", PF_patch_getcp, 0},
{"patch_getmesh", PF_patch_getmesh, 0},
{"patch_create", PF_patch_create, 0},
// {"patch_calculate", PF_patch_calculate, 0},
#endif
#ifdef ENGINE_ROUTING

View File

@ -223,7 +223,10 @@ static void bonemat_fromentity(world_t *w, wedict_t *ed, float *trans)
a[1] = ed->v->angles[1];
a[2] = ed->v->angles[2];
if (!mod || mod->type == mod_alias)
{
a[0] *= r_meshpitch.value;
a[2] *= r_meshroll.value;
}
AngleVectors(a, d[0], d[1], d[2]);
bonemat_fromqcvectors(trans, d[0], d[1], d[2], ed->v->origin);
}
@ -1684,7 +1687,7 @@ void QCBUILTIN PF_skel_ragedit(pubprogfuncs_t *prinst, struct globalvars_s *pr_g
//fixme: respond to renderflags&USEAXIS? scale?
a[0] = wed->v->angles[0] * r_meshpitch.value; /*mod_alias bug*/
a[1] = wed->v->angles[1];
a[2] = wed->v->angles[2];
a[2] = wed->v->angles[2] * r_meshroll.value; /*hexen2 bug*/
AngleVectors(a, d[0], d[1], d[2]);
bonemat_fromqcvectors(emat, d[0], d[1], d[2], wed->v->origin);
skelidx = wed->xv->skeletonindex;

View File

@ -716,6 +716,7 @@ extern cvar_t gl_playermip;
extern cvar_t r_lightmap_saturation;
extern cvar_t r_meshpitch;
extern cvar_t r_meshroll; //gah!
extern cvar_t vid_hardwaregamma;
enum {

View File

@ -2397,18 +2397,6 @@ static void Sbar_Hexen2DrawExtra (playerview_t *pv)
"Demoness"
};
if (pv->sb_hexen2_infoplaque)
{
int i;
Con_Printf("Objectives:\n");
for (i = 0; i < 64; i++)
{
if (pv->stats[STAT_H2_OBJECTIVE1 + i/32] & (1<<(i&31)))
Con_Printf("%s\n", T_GetInfoString(i));
}
pv->sb_hexen2_infoplaque = false;
}
if (!pv->sb_hexen2_extra_info)
{
sbar_rect.y -= 46-SBAR_HEIGHT;
@ -2877,6 +2865,19 @@ void Sbar_Draw (playerview_t *pv)
if (sbar_hexen2)
{
//hexen2 hud
if (pv->sb_hexen2_infoplaque)
{
int i;
Con_Printf("Objectives:\n");
for (i = 0; i < 64; i++)
{
if (pv->stats[STAT_H2_OBJECTIVE1 + i/32] & (1<<(i&31)))
Con_Printf("%s\n", T_GetInfoString(i));
}
pv->sb_hexen2_infoplaque = false;
}
if (sb_lines > 24 || pv->sb_hexen2_extra_info)
{
Sbar_Hexen2DrawExtra(pv);

View File

@ -1310,6 +1310,7 @@ int main (int c, const char **v)
newtime = Sys_DoubleTime ();
time = newtime - oldtime;
#ifdef HAVE_SERVER
if (isDedicated)
{
sleeptime = SV_Frame();
@ -1317,6 +1318,7 @@ int main (int c, const char **v)
NET_Sleep(sleeptime, noconinput?false:true);
}
else
#endif
{
sleeptime = Host_Frame(time);
oldtime = newtime;

View File

@ -366,6 +366,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#undef AVAIL_WASAPI //wasapi is available in the vista sdk, while that's compatible with earlier versions, its not really expected until 2008
#endif
#if !defined(HAVE_SERVER) && !defined(SV_MASTER)
#undef HAVE_HTTPSV
#endif
#ifdef NO_MULTITHREAD
#undef MULTITHREAD
#endif

View File

@ -38,6 +38,7 @@ cvar_t r_meshpitch = CVARCD ("r_meshpitch", "1", r_meshpitch_callback, "Sp
#else
cvar_t r_meshpitch = CVARCD ("r_meshpitch", "-1", r_meshpitch_callback, "Specifies the direction of the pitch angle on mesh models formats, Quake compatibility requires -1.");
#endif
cvar_t r_meshroll = CVARCD ("r_meshroll", "1", r_meshpitch_callback, "Specifies the direction of the roll angle on mesh models formats, also affects gamecode, so do not change from its default.");
cvar_t dpcompat_skinfiles = CVARD ("dpcompat_skinfiles", "0", "When set, uses a nodraw shader for any unmentioned surfaces.");
#ifdef HAVE_CLIENT

View File

@ -64,6 +64,7 @@ static int VectorCompare (const vec3_t v1, const vec3_t v2)
static rbeplugfuncs_t *rbefuncs;
cvar_t r_meshpitch;
cvar_t r_meshroll;
//============================================================================
// physics engine support
@ -2738,6 +2739,7 @@ static void QDECL World_ODE_Start(world_t *world)
world->rbe = &ctx->pub;
r_meshpitch.value = cvarfuncs->GetFloat("r_meshpitch");
r_meshroll.value = cvarfuncs->GetFloat("r_meshroll");
VectorAvg(world->worldmodel->mins, world->worldmodel->maxs, center);
VectorSubtract(world->worldmodel->maxs, center, extents);

View File

@ -580,6 +580,14 @@ typedef struct vfsfile_s
#endif
} vfsfile_t;
#define VFS_ERROR_TRYLATER 0 //nothing to write/read yet.
#define VFS_ERROR_UNSPECIFIED -1 //no reason given
#define VFS_ERROR_NORESPONSE -2 //no reason given
#define VFS_ERROR_EOF -3 //no reason given
#define VFS_ERROR_DNSFAILURE -4 //weird one, but oh well
#define VFS_ERROR_WRONGCERT -5 //server gave a certificate with the wrong name
#define VFS_ERROR_UNTRUSTED -6 //server gave a certificate with the right name, but we don't have a full chain of trust
#define VFS_CLOSE(vf) ((vf)->Close(vf))
#define VFS_TELL(vf) ((vf)->Tell(vf))
#define VFS_GETLEN(vf) ((vf)->GetLen(vf))

View File

@ -369,18 +369,21 @@ showhelp:
// print cvar list header
if (!(listflags & CLF_RAW) && !num)
Con_TPrintf("CVar list:\n");
Con_TPrintf("^aCVar list:\n");
// print group header
if (!(listflags & CLF_RAW) && !gnum)
Con_Printf("%s --\n", grp->name);
Con_Printf("^a%s --\n", grp->name);
// print restriction level
if (listflags & CLF_LEVEL)
Con_Printf("(%i) ", cmd->restriction);
// print cvar name
Con_Printf("%s", cmd->name);
if (!cmd->defaultstr || !strcmp(cmd->string, cmd->defaultstr))
Con_Printf(S_COLOR_GREEN "%s", cmd->name);
else
Con_Printf(S_COLOR_RED "%s", cmd->name);
total++;
// print current value

View File

@ -3650,7 +3650,7 @@ void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths)
/*quake requires a few settings for compatibility*/
#define QRPCOMPAT "set cl_cursor_scale 0.2\nset cl_cursor_bias_x 7.5\nset cl_cursor_bias_y 0.8\n"
#define QUAKESPASMSUCKS "set mod_h2holey_bugged 1\n"
#define QCFG "set v_gammainverted 1\nset con_stayhidden 0\nset com_parseutf8 0\nset allow_download_pakcontents 1\nset allow_download_refpackages 0\nset sv_bigcoords \"\"\nmap_autoopenportals 1\n" "sv_port "STRINGIFY(PORT_QWSERVER)" "STRINGIFY(PORT_NQSERVER)"\n" ZFIXHACK EZQUAKECOMPETITIVE QRPCOMPAT QUAKESPASMSUCKS
#define QCFG "set v_gammainverted 1\nset con_stayhidden 0\nset com_parseutf8 0\nset allow_download_pakcontents 1\nset allow_download_refpackages 0\nset r_meshpitch -1\nset sv_bigcoords \"\"\nmap_autoopenportals 1\n" "sv_port "STRINGIFY(PORT_QWSERVER)" "STRINGIFY(PORT_NQSERVER)"\n" ZFIXHACK EZQUAKECOMPETITIVE QRPCOMPAT QUAKESPASMSUCKS
/*NetQuake reconfiguration, to make certain people feel more at home...*/
#define NQCFG "//disablehomedir 1\n//mainconfig ftenq\ncfg_save_auto 1\n" QCFG "set sv_nqplayerphysics 1\nset cl_loopbackprotocol auto\ncl_sbar 1\nset plug_sbar 0\nset sv_port "STRINGIFY(PORT_NQSERVER)"\ncl_defaultport "STRINGIFY(PORT_NQSERVER)"\nset m_preset_chosen 1\nset vid_wait 1\nset cl_demoreel 1\n"
#define SPASMCFG NQCFG "fps_preset builtin_spasm\nset cl_demoreel 0\ncl_sbar 2\nset gl_load24bit 1\n"
@ -3666,7 +3666,7 @@ void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths)
/*some modern non-compat settings*/
#define DMFCFG "set com_parseutf8 1\npm_airstep 1\nsv_demoExtensions 1\n"
/*set some stuff so our regular qw client appears more like hexen2. sv_mintic is required to 'fix' the ravenstaff so that its projectiles don't impact upon each other*/
#define HEX2CFG "set com_parseutf8 -1\nset gl_font gfx/hexen2\nset in_builtinkeymap 0\nset_calc cl_playerclass int (random * 5) + 1\nset cl_forwardspeed 200\nset cl_backspeed 200\ncl_sidespeed 225\nset sv_maxspeed 640\ncl_run 0\nset watervis 1\nset r_lavaalpha 1\nset r_lavastyle -2\nset r_wateralpha 0.5\nset sv_pupglow 1\ngl_shaftlight 0.5\nsv_mintic 0.015\nset mod_warnmodels 0\nset cl_model_bobbing 1\nsv_sound_watersplash \"misc/hith2o.wav\"\nsv_sound_land \"fx/thngland.wav\"\nset sv_walkpitch 0\n"
#define HEX2CFG "set v_gammainverted 1\nset com_parseutf8 -1\nset gl_font gfx/hexen2\nset in_builtinkeymap 0\nset_calc cl_playerclass int (random * 5) + 1\nset cl_forwardspeed 200\nset cl_backspeed 200\ncl_sidespeed 225\nset sv_maxspeed 640\ncl_run 0\nset watervis 1\nset r_lavaalpha 1\nset r_lavastyle -2\nset r_wateralpha 0.5\nset sv_pupglow 1\ngl_shaftlight 0.5\nsv_mintic 0.015\nset r_meshpitch -1\nset r_meshroll -1\nset mod_warnmodels 0\nset cl_model_bobbing 1\nsv_sound_watersplash \"misc/hith2o.wav\"\nsv_sound_land \"fx/thngland.wav\"\nset sv_walkpitch 0\n"
/*yay q2!*/
#define Q2CFG "set v_gammainverted 1\nset com_parseutf8 0\ncom_nogamedirnativecode 0\nset sv_bigcoords 0\nsv_port "STRINGIFY(PORT_Q2SERVER)"\n"
/*Q3's ui doesn't like empty model/headmodel/handicap cvars, even if the gamecode copes*/
@ -3750,6 +3750,7 @@ const gamemode_info_t gamemode_info[] = {
{"-quoth", "quoth", "FTE-Quake", {"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "quoth", "*fte"}, "Quake: Quoth", UPDATEURL(Q1)},
{"-nehahra", "nehahra", "FTE-Quake", {"id1/pak0.pak","id1/quake.rc"},NEHCFG, {"id1", "qw", "nehahra", "*fte"}, "Quake: Seal Of Nehahra", UPDATEURL(Q1)},
//various quake-based standalone mods.
{"-librequake", "librequake","LibreQuake", {"lq1/pak0.pak","lq1/gfx.pk3","lq1/quake.rc"},QCFG, {"lq1"}, "LibreQuake", UPDATEURL(LQ)},
// {"-nexuiz", "nexuiz", "Nexuiz", {"nexuiz.exe"}, NEXCFG, {"data", "*ftedata"},"Nexuiz"},
// {"-xonotic", "xonotic", "Xonotic", {"data/xonotic-data.pk3dir",
// "data/xonotic-*data*.pk3"}, XONCFG, {"data", "*ftedata"},"Xonotic", UPDATEURL(Xonotic)},

View File

@ -335,7 +335,10 @@ void QDECL VectorAngles(const float *forward, const float *up, float *result, qb
yaw *= 180 / M_PI;
roll *= 180 / M_PI;
if (meshpitch)
{
pitch *= r_meshpitch.value;
roll *= r_meshroll.value;
}
if (pitch < 0)
pitch += 360;
if (yaw < 0)
@ -382,6 +385,14 @@ void QDECL AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3
up[2] = cr*cp;
}
}
void AngleVectorsMesh (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up)
{
vec3_t ang;
ang[0] = angles[0] * r_meshpitch.value;
ang[1] = angles[1];
ang[2] = angles[2] * r_meshroll.value;
AngleVectors (ang, forward, right, up);
}
int VectorCompare (const vec3_t v1, const vec3_t v2)
{

View File

@ -129,6 +129,7 @@ typedef struct {
void AddPointToBounds (const vec3_t v, vec3_t mins, vec3_t maxs);
float anglemod (float a);
void QDECL AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);
void QDECL AngleVectorsMesh (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);
void QDECL VectorAngles (const float *forward, const float *up, float *angles, qboolean meshpitch); //up may be NULL
void VARGS BOPS_Error (void);
int VARGS BoxOnPlaneSide (const vec3_t emins, const vec3_t emaxs, const struct mplane_s *plane);

View File

@ -430,6 +430,9 @@ typedef struct
qboolean handshaking;
qboolean datagram;
int pullerror; //adding these two because actual networking errors are not getting represented properly, at least with regard to timeouts.
int pusherror;
qboolean challenging; //not sure this is actually needed, but hey.
void *cbctx;
neterr_t(*cbpush)(void *cbctx, const qbyte *data, size_t datasize);
@ -658,7 +661,7 @@ static int SSL_DoHandshake(gnutlsfile_t *file)
if (!file->session)
{
//Sys_Printf("null session\n");
return -1;
return VFS_ERROR_UNSPECIFIED;
}
err = qgnutls_handshake (file->session);
@ -676,9 +679,18 @@ static int SSL_DoHandshake(gnutlsfile_t *file)
qgnutls_perror (err);
}
SSL_Close(&file->funcs);
// Con_Printf("%s: abort\n", file->certname);
return -1;
switch(err)
{
case GNUTLS_E_CERTIFICATE_ERROR: err = VFS_ERROR_UNTRUSTED; break;
case GNUTLS_E_PREMATURE_TERMINATION: err = VFS_ERROR_EOF; break;
case GNUTLS_E_PUSH_ERROR: err = file->pusherror; break;
case GNUTLS_E_PULL_ERROR: err = file->pullerror; break;
default: err = VFS_ERROR_UNSPECIFIED; break;
}
SSL_Close(&file->funcs);
return err;
}
file->handshaking = false;
return 1;
@ -697,7 +709,7 @@ static int QDECL SSL_Read(struct vfsfile_s *f, void *buffer, int bytestoread)
}
if (!bytestoread) //gnutls doesn't like this.
return -1;
return VFS_ERROR_UNSPECIFIED; //caller is expecting data that we can never return, or something.
read = qgnutls_record_recv(file->session, buffer, bytestoread);
if (read < 0)
@ -705,7 +717,7 @@ static int QDECL SSL_Read(struct vfsfile_s *f, void *buffer, int bytestoread)
if (read == GNUTLS_E_PREMATURE_TERMINATION)
{
Con_Printf("TLS Premature Termination from %s\n", file->certname);
return -1;
return VFS_ERROR_EOF;
}
else if (read == GNUTLS_E_REHANDSHAKE)
{
@ -721,7 +733,7 @@ static int QDECL SSL_Read(struct vfsfile_s *f, void *buffer, int bytestoread)
}
}
else if (read == 0)
return -1; //closed by remote connection.
return VFS_ERROR_EOF; //closed by remote connection.
return read;
}
static int QDECL SSL_Write(struct vfsfile_s *f, const void *buffer, int bytestowrite)
@ -744,11 +756,11 @@ static int QDECL SSL_Write(struct vfsfile_s *f, const void *buffer, int bytestow
else
{
Con_DPrintf("TLS Send Error %i (%i bytes)\n", written, bytestowrite);
return -1;
return VFS_ERROR_UNSPECIFIED;
}
}
else if (written == 0)
return -1; //closed by remote connection.
return VFS_ERROR_EOF; //closed by remote connection.
return written;
}
static qboolean QDECL SSL_Seek (struct vfsfile_s *file, qofs_t pos)
@ -775,7 +787,17 @@ static ssize_t SSL_Push(gnutls_transport_ptr_t p, const void *data, size_t size)
int done = VFS_WRITE(file->stream, data, size);
if (done <= 0)
{
qgnutls_transport_set_errno(file->session, (done==0)?EAGAIN:ECONNRESET);
int eno;
file->pusherror = done;
switch(done)
{
case VFS_ERROR_EOF: return 0;
case VFS_ERROR_DNSFAILURE:
case VFS_ERROR_NORESPONSE: eno = ECONNRESET; break;
case VFS_ERROR_TRYLATER: eno = EAGAIN; break;
default: eno = ECONNRESET; break;
}
qgnutls_transport_set_errno(file->session, eno);
return -1;
}
return done;
@ -787,8 +809,17 @@ static ssize_t SSL_Pull(gnutls_transport_ptr_t p, void *data, size_t size)
int done = VFS_READ(file->stream, data, size);
if (done <= 0)
{
//use ECONNRESET instead of returning eof.
qgnutls_transport_set_errno(file->session, (done==0)?EAGAIN:ECONNRESET);
int eno;
file->pullerror = done;
switch(done)
{
case VFS_ERROR_EOF: return 0;
case VFS_ERROR_DNSFAILURE:
case VFS_ERROR_NORESPONSE: eno = ECONNRESET; break;
case VFS_ERROR_TRYLATER: eno = EAGAIN; break;
default: eno = ECONNRESET; break;
}
qgnutls_transport_set_errno(file->session, eno);
return -1;
}
return done;

View File

@ -229,7 +229,7 @@ static int SSPI_CheckNewInCrypt(sslfile_t *f)
{
int newd;
if (!f->stream)
return -1;
return VFS_ERROR_EOF;
newd = VFS_READ(f->stream, f->incrypt.data+f->incrypt.avail, f->incrypt.datasize - f->incrypt.avail);
if (newd < 0)
return newd;

View File

@ -3986,9 +3986,7 @@ typedef struct ftenet_tcp_stream_s {
enum
{
#if defined(HAVE_SERVER) || defined(SV_MASTER)
TCPC_UNKNOWN, //waiting to see what they send us.
#endif
//TCPC_QTV, //included for completeness. qtv handles the sockets itself, we just parse initial handshake and then pass it over (as either a tcp or tls vfsfile_t)
TCPC_QIZMO, //'qizmo\n' handshake, followed by packets prefixed with a 16bit packet length.
#ifdef HAVE_HTTPSV
@ -3996,8 +3994,8 @@ typedef struct ftenet_tcp_stream_s {
TCPC_WEBSOCKETB, //binary encoded data (subprotocol = 'binary')
TCPC_WEBSOCKETNQ, //raw nq msg buffers with no encapsulation or handshake
TCPC_HTTPCLIENT, //we're sending a file to this victim.
TCPC_WEBRTC_HOST, //for brokering webrtc connections, doesn't carry any actual game data itself.
TCPC_WEBRTC_CLIENT, //for brokering webrtc connections, doesn't carry any actual game data itself.
TCPC_WEBRTC_HOST //for brokering webrtc connections, doesn't carry any actual game data itself.
#endif
} clienttype;
qbyte inbuffer[MAX_OVERALLMSGLEN];
@ -5129,6 +5127,7 @@ static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_s
if (st->dlfile)
VFS_CLOSE(st->dlfile);
#ifdef HAVE_HTTPSV
if (st->clienttype == TCPC_WEBRTC_CLIENT)
{ //notify its server
ftenet_tcp_stream_t *o;
@ -5165,6 +5164,7 @@ static qboolean FTENET_TCP_KillStream(ftenet_tcp_connection_t *con, ftenet_tcp_s
SVM_RemoveBrokerGame(st->webrtc.resource);
#endif
}
#endif
return false;
}
@ -8823,8 +8823,8 @@ typedef struct {
SOCKET sock;
qboolean conpending;
qboolean readaborted; //some kind of error. don't spam
qboolean writeaborted; //some kind of error. don't spam
int readaborted; //some kind of error. don't spam
int writeaborted; //some kind of error. don't spam
char readbuffer[65536];
int readbuffered;
@ -8871,19 +8871,24 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea
int e = neterrno();
if (e != NET_EWOULDBLOCK && e != NET_EINTR)
{
tf->readaborted = VFS_ERROR_UNSPECIFIED;
switch(e)
{
case NET_ENOTCONN:
Con_Printf("connection to \"%s\" failed\n", tf->peer);
tf->readaborted = VFS_ERROR_NORESPONSE;
break;
case NET_ECONNABORTED:
Con_DPrintf("connection to \"%s\" aborted\n", tf->peer);
tf->readaborted = VFS_ERROR_NORESPONSE;
break;
case NET_ETIMEDOUT:
Con_Printf("connection to \"%s\" timed out\n", tf->peer);
tf->readaborted = VFS_ERROR_NORESPONSE;
break;
case NET_ECONNREFUSED:
Con_DPrintf("connection to \"%s\" refused\n", tf->peer);
tf->readaborted = VFS_ERROR_NORESPONSE;
break;
case NET_ECONNRESET:
Con_DPrintf("connection to \"%s\" reset\n", tf->peer);
@ -8891,14 +8896,13 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea
default:
Con_Printf("tcp socket error %i (%s)\n", e, tf->peer);
}
tf->readaborted = true;
}
//fixme: figure out wouldblock or error
}
else if (len == 0 && trying != 0)
{
//peer disconnected
tf->readaborted = true;
tf->readaborted = VFS_ERROR_EOF;
}
else
{
@ -8910,7 +8914,7 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea
if (bytestoread > tf->readbuffered)
bytestoread = tf->readbuffered;
if (bytestoread < 0)
return -1; //caller error...
return VFS_ERROR_UNSPECIFIED; //caller error...
if (bytestoread > 0)
{
@ -8919,14 +8923,7 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea
memmove(tf->readbuffer, tf->readbuffer+bytestoread, tf->readbuffered);
return bytestoread;
}
else
{
if (tf->readaborted)
{
return -1; //signal an error
}
return 0; //signal nothing available
}
else return tf->readaborted;
}
int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestoread)
{
@ -8934,7 +8931,7 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt
int len;
if (tf->writeaborted)
return -1;
return VFS_ERROR_UNSPECIFIED; //a previous write failed.
if (tf->conpending)
{
@ -8955,6 +8952,7 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt
len = send(tf->sock, buffer, bytestoread, 0);
if (len == -1 || len == 0)
{
int reason = VFS_ERROR_UNSPECIFIED;
int e = (len==0)?NET_ECONNABORTED:neterrno();
switch(e)
{
@ -8963,16 +8961,17 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt
return 0; //nothing available yet.
case NET_ETIMEDOUT:
Con_Printf("connection to \"%s\" timed out\n", tf->peer);
return -1; //don't bother trying to read if we never connected.
return VFS_ERROR_NORESPONSE; //don't bother trying to read if we never connected.
case NET_ECONNABORTED:
Con_Printf("connection to \"%s\" aborted\n", tf->peer);
reason = len?VFS_ERROR_NORESPONSE:VFS_ERROR_EOF;
break;
case NET_ENOTCONN:
#ifdef __unix__
case EPIPE:
#endif
Con_Printf("connection to \"%s\" failed\n", tf->peer);
return -1; //don't bother trying to read if we never connected.
return VFS_ERROR_NORESPONSE; //don't bother trying to read if we never connected.
default:
Sys_Printf("tcp socket error %i (%s)\n", e, tf->peer);
break;
@ -8981,7 +8980,7 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt
// instead let the read handling kill it if there's nothing new to be read
VFSTCP_ReadBytes(file, NULL, 0);
tf->writeaborted = true;
return -1;
return reason;
}
return len;
}

View File

@ -6334,9 +6334,7 @@ void QCBUILTIN PF_rotatevectorsbyangles (pubprogfuncs_t *prinst, struct globalva
float *ang = G_VECTOR(OFS_PARM0);
vec3_t src[3], trans[3], res[3];
ang[0]*=r_meshpitch.value;
AngleVectors(ang, trans[0], trans[1], trans[2]);
ang[0]*=r_meshpitch.value;
AngleVectorsMesh(ang, trans[0], trans[1], trans[2]);
VectorInverse(trans[1]);
VectorCopy(w->g.v_forward, src[0]);

View File

@ -341,6 +341,9 @@ void QCBUILTIN PF_brush_selected(pubprogfuncs_t *prinst, struct globalvars_s *pr
void QCBUILTIN PF_brush_getfacepoints(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_brush_calcfacepoints(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_brush_findinvolume(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_patch_getcp(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_patch_getmesh(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
void QCBUILTIN PF_patch_create(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
#endif
void QCBUILTIN PF_touchtriggers(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);

View File

@ -7,18 +7,19 @@ See gl_terrain.h for terminology, networking notes, etc.
#ifdef TERRAIN
#include "glquake.h"
#include "shader.h"
#include "com_mesh.h"
#include "pr_common.h"
#include "gl_terrain.h"
static plugterrainfuncs_t terrainfuncs;
struct patchvert_s
typedef struct
{
vec3_t v;
vec2_t tc;
vec4_t rgba;
};
} qcpatchvert_t;
cvar_t mod_terrain_networked = CVARD("mod_terrain_networked", "0", "Terrain edits are networked. Clients will download sections on demand, and servers will notify clients of changes.");
@ -33,10 +34,11 @@ cvar_t mod_terrain_brushtexscale = CVARD("mod_map_texscale", "1", "Defines the s
enum
{
hmcmd_brush_delete,
hmcmd_brush_insert,
hmcmd_brush_delete, //brush OR patch destruction
hmcmd_brush_insert, //brush creation
hmcmd_prespawning, //sent before initial inserts
hmcmd_prespawned, //sent just after initial inserts
hmcmd_patch_insert, //patch creation
hmcmd_ent_edit = 0x40,
hmcmd_ent_remove
@ -3726,6 +3728,72 @@ static int Heightmap_Trace_Brush(hmtrace_t *tr, vec4_t *planes, int numplanes, b
return false;
}
static qboolean Heightmap_Trace_Quad(hmtrace_t *tr, const float *v0, const float *v1, const float *v2, const float *v3)
{
//super lame shite. be lazy and just use a bbox
static vec4_t n[6] = {
{-1, 0, 0, 0},
{ 0,-1, 0, 0},
{ 0, 0,-1, 0},
{ 1, 0, 0, 0},
{ 0, 1, 0, 0},
{ 0, 0, 1, 0},
};
vec3_t d[2];
const float epsilon = 1.0/64;
VectorCopy(v0, d[0]);
VectorCopy(v0, d[1]);
AddPointToBounds(v1, d[0], d[1]);
AddPointToBounds(v2, d[0], d[1]);
AddPointToBounds(v3, d[0], d[1]);
//I'm implementing this primarily for selecting patches.
//decals are often infinitely thin things.
//so expand them by a tiny amount in the hopes that traces will hit patches before the wall they're coplanar with.
n[0][3] =-d[0][0]+epsilon;
n[1][3] =-d[0][1]+epsilon;
n[2][3] =-d[0][2]+epsilon;
n[3][3] = d[1][0]+epsilon;
n[4][3] = d[1][1]+epsilon;
n[5][3] = d[1][2]+epsilon;
return Heightmap_Trace_Brush(tr, n, 6, NULL) != 0;
}
static qboolean Heightmap_Trace_Patch(hmtrace_t *tr, brushes_t *brushinfo)
{
const struct patchdata_s *patch = brushinfo->patch;
unsigned int w, h, x, y;
qboolean ret = false;
if (!patch->tessvert)
{
const struct patchcpvert_s *r1 = patch->cp, *r2;
w = patch->numcp[0];
h = patch->numcp[1];
for (y = 0, r2 = r1 + w; y < h-1; y++)
{
for (x = 0; x < w-1; x++, r1++, r2++)
ret |= Heightmap_Trace_Quad(tr, r1[0].v, r1[1].v, r2[0].v, r1[1].v);
r1++; r2++;
}
}
else
{
const struct patchtessvert_s *r1 = patch->tessvert, *r2;
w = patch->tesssize[0];
h = patch->tesssize[1];
for (y = 0, r2 = r1 + w; y < h-1; y++)
{
for (x = 0; x < w-1; x++, r1++, r2++)
ret |= Heightmap_Trace_Quad(tr, r1[0].v, r1[1].v, r2[0].v, r1[1].v);
r1++; r2++;
}
}
return ret;
}
//sx,sy are the tile coord
//note that tile SECTHEIGHTSIZE-1 does not exist, as the last sample overlaps the first sample of the next section
static void Heightmap_Trace_Square(hmtrace_t *tr, int tx, int ty)
@ -4249,6 +4317,14 @@ qboolean Heightmap_Trace(struct model_s *model, int hulloverride, const framesta
hmtrace.absmins[1] > brushes->maxs[1] ||
hmtrace.absmins[2] > brushes->maxs[2])
continue;
if (brushes->patch)
{
if (Heightmap_Trace_Patch(&hmtrace, brushes))
face = -1;
else
face = 0;
}
else
face = Heightmap_Trace_Brush(&hmtrace, brushes->planes, brushes->numplanes, brushes);
if (face)
{
@ -5898,32 +5974,38 @@ void Terr_Brush_Draw(heightmap_t *hm, batch_t **batches, entity_t *e)
{
for (numverts = 0, numindicies = 0; i < hm->numbrushes; i++, br++)
{
//if a single batch has too many verts, cut it off before it overflows our maximum batch size, and hope we don't get a really really complex brush.
if (numverts >= 0xf000 || numindicies >= 0xf000)
break;
if (br->patch && br->patch->tex == bt && lmnum == -1)
if (br->selected)
continue;
if (br->patch)
{ //this one's a patch
if (br->patch->tex == bt && lmnum == -1)
{
int x, y;
index_t r1, r2;
for (y = 0, r1 = numverts, r2 = 0; y < br->patch->ypoints; y++)
{
for (x = 0; x < br->patch->xpoints; x++, r1++, r2++)
{
VectorCopy(br->patch->verts[r2].v, arrays->coord[r1]);
Vector2Copy(br->patch->verts[r2].tc, arrays->texcoord[r1]);
VectorCopy(br->patch->verts[r2].norm, arrays->normal[r1]);
VectorCopy(br->patch->verts[r2].sdir, arrays->svector[r1]);
VectorCopy(br->patch->verts[r2].tdir, arrays->tvector[r1]);
Vector4Copy(br->patch->verts[r2].rgba, arrays->rgba[r1]);
if (br->patch->tessvert && !br->selected)
{ //tessellated version of the patch.
Vector2Copy(br->patch->verts[r2].tc, arrays->lmcoord[r1]);
}
}
for (y = 0, r1 = numverts, r2 = r1 + br->patch->xpoints; y < br->patch->ypoints-1; y++)
//make sure we don't overflow anything.
size_t newverts = br->patch->tesssize[0]*br->patch->tesssize[1], newindexes = (br->patch->tesssize[0]-1)*(br->patch->tesssize[1]-1)*6;
if (numverts+newverts >= 0xffff || numindicies+newindexes >= 0xffff)
break;
for (y = 0, r1 = numverts, r2 = 0; y < br->patch->tesssize[1]; y++)
{
for (x = 0; x < br->patch->xpoints-1; x++, r1++, r2++)
for (x = 0; x < br->patch->tesssize[0]; x++, r1++, r2++)
{
VectorCopy(br->patch->tessvert[r2].v, arrays->coord[r1]);
Vector2Copy(br->patch->tessvert[r2].tc, arrays->texcoord[r1]);
Vector4Copy(br->patch->tessvert[r2].rgba, arrays->rgba[r1]);
//lame
Vector2Copy(br->patch->tessvert[r2].tc, arrays->lmcoord[r1]);
}
}
for (y = 0, r1 = numverts, r2 = r1 + br->patch->tesssize[0]; y < br->patch->tesssize[1]-1; y++)
{
for (x = 0; x < br->patch->tesssize[0]-1; x++, r1++, r2++)
{
arrays->index[numindicies++] = r1;
arrays->index[numindicies++] = r1+1;
@ -5935,8 +6017,61 @@ void Terr_Brush_Draw(heightmap_t *hm, batch_t **batches, entity_t *e)
}
r1++; r2++;
}
numverts += br->patch->ypoints*br->patch->xpoints;
Mod_AccumulateTextureVectors(arrays->coord, arrays->texcoord, arrays->normal, arrays->svector, arrays->tvector, arrays->index+numindicies-newindexes, newindexes, true);
Mod_NormaliseTextureVectors(arrays->normal+numverts, arrays->svector+numverts, arrays->tvector+numverts, newverts, true);
numverts += newverts;
}
else
{ //control-point representation.
//make sure we don't overflow anything.
size_t newverts = br->patch->numcp[0]*br->patch->numcp[1], newindexes = (br->patch->numcp[0]-1)*(br->patch->numcp[1]-1)*6;
if (numverts+newverts >= 0xffff || numindicies+newindexes >= 0xffff)
break;
for (y = 0, r1 = numverts, r2 = 0; y < br->patch->numcp[1]; y++)
{
for (x = 0; x < br->patch->numcp[0]; x++, r1++, r2++)
{
VectorCopy(br->patch->cp[r2].v, arrays->coord[r1]);
Vector2Copy(br->patch->cp[r2].tc, arrays->texcoord[r1]);
Vector4Copy(br->patch->cp[r2].rgba, arrays->rgba[r1]);
//lame
Vector2Copy(br->patch->cp[r2].tc, arrays->lmcoord[r1]);
}
}
for (y = 0, r1 = numverts, r2 = r1 + br->patch->numcp[0]; y < br->patch->numcp[1]-1; y++)
{
for (x = 0; x < br->patch->numcp[0]-1; x++, r1++, r2++)
{
arrays->index[numindicies++] = r1;
arrays->index[numindicies++] = r1+1;
arrays->index[numindicies++] = r2;
arrays->index[numindicies++] = r1+1;
arrays->index[numindicies++] = r2+1;
arrays->index[numindicies++] = r2;
}
r1++; r2++;
}
Mod_AccumulateTextureVectors(arrays->coord, arrays->texcoord, arrays->normal, arrays->svector, arrays->tvector, arrays->index+numindicies-newindexes, newindexes, true);
Mod_NormaliseTextureVectors(arrays->normal+numverts, arrays->svector+numverts, arrays->tvector+numverts, newverts, true);
numverts += newverts;
}
}
}
else
{ //regular brush
//make sure we don't overflow anything.
size_t newverts = 0, newindexes = 0;
for (j = 0; j < br->numplanes; j++)
if (br->faces[j].tex == bt && !br->selected && br->faces[j].lightmap == lmnum)
newverts += br->faces[j].numpoints, newindexes += (br->faces[j].numpoints-2)*3;
if (numverts+newverts >= 0xffff || numindicies+newindexes >= 0xffff)
break;
for (j = 0; j < br->numplanes; j++)
{
if (br->faces[j].tex == bt && !br->selected && br->faces[j].lightmap == lmnum)
@ -5972,6 +6107,7 @@ void Terr_Brush_Draw(heightmap_t *hm, batch_t **batches, entity_t *e)
}
}
}
}
if (numverts || numindicies)
{
@ -6101,11 +6237,11 @@ static brushes_t *Terr_Brush_Insert(model_t *model, heightmap_t *hm, brushes_t *
out->planes = NULL;
out->faces = NULL;
out->numplanes = 0;
ClearBounds(out->mins, out->maxs);
if (brush->numplanes)
{
out->planes = BZ_Malloc((sizeof(*out->planes)+sizeof(*out->faces)) * brush->numplanes);
out->faces = (void*)(out->planes+brush->numplanes);
ClearBounds(out->mins, out->maxs);
for (iface = 0, oface = 0; iface < brush->numplanes; iface++)
{
for (j = 0; j < oface; j++)
@ -6215,13 +6351,14 @@ static brushes_t *Terr_Brush_Insert(model_t *model, heightmap_t *hm, brushes_t *
if (brush->patch)
{
out->patch = BZ_Malloc(sizeof(*out->patch)-sizeof(out->patch->verts) + sizeof(*out->patch->verts)*brush->patch->xpoints*brush->patch->ypoints);
memcpy(out->patch, brush->patch, sizeof(*out->patch)-sizeof(out->patch->verts) + sizeof(*out->patch->verts)*brush->patch->xpoints*brush->patch->ypoints);
out->patch = BZ_Malloc(sizeof(*out->patch)-sizeof(out->patch->cp) + sizeof(*out->patch->cp)*brush->patch->numcp[0]*brush->patch->numcp[1]);
memcpy(out->patch, brush->patch, sizeof(*out->patch)-sizeof(out->patch->cp) + sizeof(*out->patch->cp)*brush->patch->numcp[0]*brush->patch->numcp[1]);
numpoints = out->patch->xpoints*out->patch->ypoints;
numpoints = out->patch->numcp[0]*out->patch->numcp[1];
//FIXME: lightmap...
for (j = 0; j < numpoints; j++)
AddPointToBounds(out->patch->verts[j].v, out->mins, out->maxs);
AddPointToBounds(out->patch->cp[j].v, out->mins, out->maxs);
out->patch->tex->rebuild = true;
}
@ -6275,29 +6412,32 @@ static brushes_t *Terr_Brush_Insert(model_t *model, heightmap_t *hm, brushes_t *
}
static brushes_t *Terr_Patch_Insert(model_t *model, heightmap_t *hm, brushtex_t *patch_tex, int patch_w, int patch_h, struct patchvert_s *patch_v, int stride)
static brushes_t *Terr_Patch_Insert(model_t *model, heightmap_t *hm, brushtex_t *patch_tex, unsigned patch_w, unsigned patch_h, unsigned subdiv_w, unsigned subdiv_h, qcpatchvert_t *patch_v, int stride)
{
int x, y;
brushes_t brush;
//finish the brush
brush.contents = 0;
brush.contents = FTECONTENTS_SOLID;
brush.numplanes = 0;
brush.planes = NULL;
brush.faces = NULL;
brush.id = 0;
brush.patch = alloca(sizeof(*brush.patch)-sizeof(brush.patch->verts) + sizeof(*brush.patch->verts)*patch_w*patch_h);
brush.patch = alloca(sizeof(*brush.patch)-sizeof(brush.patch->cp) + sizeof(*brush.patch->cp)*patch_w*patch_h);
brush.patch->tex = patch_tex;
brush.patch->xpoints = patch_w;
brush.patch->ypoints = patch_h;
brush.patch->numcp[0] = patch_w;
brush.patch->numcp[1] = patch_h;
brush.patch->subdiv[0] = subdiv_w;
brush.patch->subdiv[1] = subdiv_h;
brush.patch->tessvert = NULL;
for (y = 0; y < patch_h; y++)
{
for (x = 0; x < patch_w; x++)
{
VectorCopy(patch_v[x].v, brush.patch->verts[x + y*patch_w].v);
Vector2Copy(patch_v[x].tc, brush.patch->verts[x + y*patch_w].tc);
Vector4Copy(patch_v[x].rgba, brush.patch->verts[x + y*patch_w].rgba);
VectorCopy(patch_v[x].v, brush.patch->cp[x + y*patch_w].v);
Vector2Copy(patch_v[x].tc, brush.patch->cp[x + y*patch_w].tc);
Vector4Copy(patch_v[x].rgba, brush.patch->cp[x + y*patch_w].rgba);
//brush.patch->verts[x + y*patch_w].norm
//brush.patch->verts[x + y*patch_w].sdir
//brush.patch->verts[x + y*patch_w].tdir
@ -6396,6 +6536,7 @@ static qboolean Brush_Deserialise(heightmap_t *hm, brushes_t *br)
br->planes[i][2] = MSG_ReadFloat();
br->planes[i][3] = MSG_ReadFloat();
//FIXME: can we optimise this part? a flag to say whether its needed?
br->faces[i].stdir[0][0] = MSG_ReadFloat();
br->faces[i].stdir[0][1] = MSG_ReadFloat();
br->faces[i].stdir[0][2] = MSG_ReadFloat();
@ -6409,6 +6550,89 @@ static qboolean Brush_Deserialise(heightmap_t *hm, brushes_t *br)
return true;
}
static void Patch_Serialise(sizebuf_t *sb, brushes_t *br)
{
qbyte flags = 0;
unsigned int i, m = br->patch->numcp[0]*br->patch->numcp[1];
for (i = 0; i < m; i++)
{
if (br->patch->cp[i].rgba[0] != 1)
flags |= 1;
if (br->patch->cp[i].rgba[1] != 1)
flags |= 2;
if (br->patch->cp[i].rgba[2] != 1)
flags |= 4;
if (br->patch->cp[i].rgba[3] != 1)
flags |= 8;
}
MSG_WriteLong(sb, br->id);
MSG_WriteByte(sb, flags);
MSG_WriteLong(sb, br->contents);
MSG_WriteString(sb, br->patch->tex->shadername);
MSG_WriteShort(sb, br->patch->numcp[0]);
MSG_WriteShort(sb, br->patch->numcp[1]);
MSG_WriteShort(sb, br->patch->subdiv[0]);
MSG_WriteShort(sb, br->patch->subdiv[1]);
for (i = 0; i < m; i++)
{
MSG_WriteFloat(sb, br->patch->cp[i].v[0]);
MSG_WriteFloat(sb, br->patch->cp[i].v[1]);
MSG_WriteFloat(sb, br->patch->cp[i].v[2]);
MSG_WriteFloat(sb, br->patch->cp[i].tc[0]);
MSG_WriteFloat(sb, br->patch->cp[i].tc[1]);
if (flags&1)
MSG_WriteFloat(sb, br->patch->cp[i].rgba[0]);
if (flags&2)
MSG_WriteFloat(sb, br->patch->cp[i].rgba[1]);
if (flags&4)
MSG_WriteFloat(sb, br->patch->cp[i].rgba[2]);
if (flags&8)
MSG_WriteFloat(sb, br->patch->cp[i].rgba[3]);
}
}
static qboolean Patch_Deserialise(heightmap_t *hm, brushes_t *br)
{
struct patchcpvert_s vert;
qboolean flags;
unsigned int i, maxverts = br->patch->numcp[0]*br->patch->numcp[1];
br->id = MSG_ReadLong();
flags = MSG_ReadByte();
br->contents = MSG_ReadLong();
//FIXME: as a server, we probably want to reject the brush if we exceed some texnum/memory limitation, so clients can't just spam new textures endlessly.
br->patch->tex = Terr_Brush_FindTexture(hm, MSG_ReadString());
br->patch->numcp[0] = MSG_ReadShort();
br->patch->numcp[1] = MSG_ReadShort();
br->patch->subdiv[0] = MSG_ReadShort();
br->patch->subdiv[1] = MSG_ReadShort();
for (i = 0; i < br->patch->numcp[0]*br->patch->numcp[1]; i++)
{
vert.v[0] = MSG_ReadFloat();
vert.v[1] = MSG_ReadFloat();
vert.v[2] = MSG_ReadFloat();
vert.tc[0] = MSG_ReadFloat();
vert.tc[1] = MSG_ReadFloat();
vert.rgba[0] = (flags&1)?MSG_ReadFloat():1;
vert.rgba[1] = (flags&2)?MSG_ReadFloat():1;
vert.rgba[2] = (flags&4)?MSG_ReadFloat():1;
vert.rgba[3] = (flags&8)?MSG_ReadFloat():1;
if (i < maxverts)
br->patch->cp[i] = vert;
}
return i <= maxverts;
}
#ifndef SERVERONLY
heightmap_t *CL_BrushEdit_ForceContext(model_t *mod)
{
@ -6454,18 +6678,30 @@ void CL_Parse_BrushEdit(void)
return; //ignore if we're the server, we should already have it anyway.
Terr_Brush_DeleteId(hm, id);
}
else if (cmd == hmcmd_brush_insert) //1=create/replace
else if (cmd == hmcmd_brush_insert || cmd == hmcmd_patch_insert) //1=create/replace
{
brushes_t brush;
hm = CL_BrushEdit_ForceContext(mod); //do this early, to ensure that the textures are correct
memset(&brush, 0, sizeof(brush));
if (cmd == hmcmd_patch_insert)
{
const unsigned int maxpoints = 64*64;
brush.patch = alloca(sizeof(*brush.patch) + sizeof(*brush.patch->cp)*(maxpoints-countof(brush.patch->cp)));
brush.patch->numcp[0] = 1;
brush.patch->numcp[1] = maxpoints;
if (!Patch_Deserialise(hm, &brush))
Host_EndGame("CL_Parse_BrushEdit: unparsable patch\n");
}
else
{
brush.numplanes = 128;
brush.planes = alloca(sizeof(*brush.planes) * brush.numplanes);
brush.faces = alloca(sizeof(*brush.faces) * brush.numplanes);
if (!Brush_Deserialise(hm, &brush))
Host_EndGame("CL_Parse_BrushEdit: unparsable brush\n");
}
if (ignore)
return; //ignore if we're the server, we should already have it anyway (but might need it for demos, hence why its still sent).
if (brush.id)
@ -6600,8 +6836,16 @@ qboolean SV_Prespawn_Brushes(sizebuf_t *msg, unsigned int *modelindex, unsigned
{
MSG_WriteByte(msg, svcfte_brushedit);
MSG_WriteShort(msg, *modelindex);
if (best->patch)
{
MSG_WriteByte(msg, hmcmd_patch_insert);
Patch_Serialise(msg, best);
}
else
{
MSG_WriteByte(msg, hmcmd_brush_insert);
Brush_Serialise(msg, best);
}
*lastid = bestid;
return true;
}
@ -6634,10 +6878,25 @@ qboolean SV_Parse_BrushEdit(void)
SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0);
return true;
}
else if (cmd == hmcmd_brush_insert)
else if (cmd == hmcmd_brush_insert || cmd == hmcmd_patch_insert)
{
brushes_t brush;
memset(&brush, 0, sizeof(brush));
if (cmd == hmcmd_patch_insert)
{
const unsigned int maxpoints = 64*64;
brush.patch = alloca(sizeof(*brush.patch) + sizeof(*brush.patch->cp)*(maxpoints-countof(brush.patch->cp)));
memset(brush.patch, 0, sizeof(*brush.patch));
brush.patch->numcp[0] = maxpoints;
brush.patch->numcp[1] = 1;
if (!Patch_Deserialise(hm, &brush))
{
Con_Printf("SV_Parse_BrushEdit: %s sent an unparsable patch\n", host_client->name);
return false;
}
}
else
{
brush.numplanes = 128;
brush.planes = alloca(sizeof(*brush.planes) * brush.numplanes);
brush.faces = alloca(sizeof(*brush.faces) * brush.numplanes);
@ -6646,6 +6905,7 @@ qboolean SV_Parse_BrushEdit(void)
Con_Printf("SV_Parse_BrushEdit: %s sent an unparsable brush\n", host_client->name);
return false;
}
}
if (!authorise)
{
SV_PrintToClient(host_client, PRINT_MEDIUM, "Brush editing ignored: you are not a mapper\n");
@ -6660,7 +6920,10 @@ qboolean SV_Parse_BrushEdit(void)
MSG_WriteByte(&sv.multicast, svcfte_brushedit);
MSG_WriteShort(&sv.multicast, modelindex);
MSG_WriteByte(&sv.multicast, hmcmd_brush_insert);
MSG_WriteByte(&sv.multicast, cmd);
if (cmd == hmcmd_patch_insert)
Patch_Serialise(&sv.multicast, &brush);
else
Brush_Serialise(&sv.multicast, &brush);
SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0);
return true;
@ -6701,7 +6964,7 @@ qboolean SV_Parse_BrushEdit(void)
typedef struct
{
int shadername;
string_t shadername;
vec3_t planenormal;
float planedist;
vec3_t sdir;
@ -6710,6 +6973,17 @@ typedef struct
float tbias;
} qcbrushface_t;
typedef struct
{
string_t shadername;
unsigned int contents;
unsigned int cp_width;
unsigned int cp_height;
unsigned int subdiv_x;
unsigned int subdiv_y;
vec3_t texinfo;
} qcpatchinfo_t;
static void *validateqcpointer(pubprogfuncs_t *prinst, size_t qcptr, size_t elementsize, size_t elementcount, qboolean allownull)
{
//make sure that the sizes can't overflow
@ -6731,6 +7005,124 @@ static void *validateqcpointer(pubprogfuncs_t *prinst, size_t qcptr, size_t elem
}
return prinst->stringtable + qcptr;
}
// {"patch_getcp", PF_patch_getcp, 0, 0, 0, 0, D(qcpatchvert "int(float modelidx, int patchid, patchvert_t *out_controlverts, int maxcp, __out patchinfo_t out_info)", "Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error.")},
void QCBUILTIN PF_patch_getcp(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
world_t *vmw = prinst->parms->user;
model_t *mod = vmw->Get_CModel(vmw, G_FLOAT(OFS_PARM0));
heightmap_t *hm = mod?mod->terrain:NULL;
unsigned int patchid = G_INT(OFS_PARM1);
unsigned int maxverts = G_INT(OFS_PARM3);
qcpatchvert_t *out_verts = validateqcpointer(prinst, G_INT(OFS_PARM2), sizeof(*out_verts), maxverts, true);
qcpatchinfo_t *out_info = validateqcpointer(prinst, G_INT(OFS_PARM4), sizeof(*out_info), 1, true);
unsigned int i, j;
brushes_t *br;
//assume the worst.
G_INT(OFS_RETURN) = 0;
if (out_info)
memset(out_info, 0, sizeof(*out_info));
if (!hm)
return;
for (i = 0; i < hm->numbrushes; i++)
{
br = &hm->wbrushes[i];
if (br->id == patchid)
{
if (!br->patch)
return;
if (out_info)
{
out_info->contents = br->contents;
out_info->cp_width = br->patch->numcp[0];
out_info->cp_height = br->patch->numcp[1];
out_info->subdiv_x = br->patch->subdiv[0];
out_info->subdiv_y = br->patch->subdiv[1];
out_info->shadername = PR_TempString(prinst, br->patch->tex->shadername);
}
if (!out_verts)
G_INT(OFS_RETURN) = br->patch->numcp[0]*br->patch->numcp[1];
else
{
maxverts = min(br->numplanes, maxverts);
for (j = 0; j < br->patch->numcp[0]*br->patch->numcp[1]; j++)
{
VectorCopy(br->patch->cp[j].v, out_verts->v);
Vector2Copy(br->patch->cp[j].tc, out_verts->tc);
Vector4Copy(br->patch->cp[j].rgba, out_verts->rgba);
out_verts++;
}
G_INT(OFS_RETURN) = j;
}
return;
}
}
}
// {"patch_getmesh", PF_patch_getmesh, 0, 0, 0, 0, D("int(float modelidx, int patchid, patchvert_t *out_verts, int maxverts, __out patchinfo_t out_info)", "Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error.")},
void QCBUILTIN PF_patch_getmesh(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
world_t *vmw = prinst->parms->user;
model_t *mod = vmw->Get_CModel(vmw, G_FLOAT(OFS_PARM0));
heightmap_t *hm = mod?mod->terrain:NULL;
unsigned int patchid = G_INT(OFS_PARM1);
unsigned int maxverts = G_INT(OFS_PARM3);
qcpatchvert_t *out_verts = validateqcpointer(prinst, G_INT(OFS_PARM2), sizeof(*out_verts), maxverts, true);
qcpatchinfo_t *out_info = validateqcpointer(prinst, G_INT(OFS_PARM4), sizeof(*out_info), 1, true);
unsigned int i, j;
brushes_t *br;
//assume the worst.
G_INT(OFS_RETURN) = 0;
if (out_info)
memset(out_info, 0, sizeof(*out_info));
if (!hm)
return;
for (i = 0; i < hm->numbrushes; i++)
{
br = &hm->wbrushes[i];
if (br->id == patchid)
{
if (!br->patch)
return;
if (out_info)
{
out_info->contents = br->contents;
out_info->cp_width = br->patch->numcp[0];
out_info->cp_height = br->patch->numcp[1];
out_info->subdiv_x = br->patch->subdiv[0];
out_info->subdiv_y = br->patch->subdiv[1];
out_info->shadername = PR_TempString(prinst, br->patch->tex->shadername);
}
if (!out_verts)
G_INT(OFS_RETURN) = br->patch->tesssize[0]*br->patch->tesssize[1];
else
{
maxverts = min(br->numplanes, maxverts);
for (j = 0; j < br->patch->tesssize[0]*br->patch->tesssize[1]; j++)
{
VectorCopy(br->patch->tessvert[j].v, out_verts->v);
Vector2Copy(br->patch->tessvert[j].tc, out_verts->tc);
Vector4Copy(br->patch->tessvert[j].rgba, out_verts->rgba);
out_verts++;
}
G_INT(OFS_RETURN) = j;
}
return;
}
}
}
// {"brush_get", PF_brush_get, 0, 0, 0, 0, D(qcbrushface "int(float modelidx, int brushid, brushface_t *out_faces, int maxfaces, int *out_contents)", "Queries a brush's information. You must pre-allocate the face array for the builtin to write to. Return value is the number of faces retrieved, 0 on error.")},
void QCBUILTIN PF_brush_get(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
@ -6757,6 +7149,8 @@ void QCBUILTIN PF_brush_get(pubprogfuncs_t *prinst, struct globalvars_s *pr_glob
br = &hm->wbrushes[i];
if (br->id == brushid)
{
if (br->patch)
return;
if (out_contents)
*out_contents = br->contents;
if (!out_faces)
@ -6784,7 +7178,7 @@ void QCBUILTIN PF_brush_get(pubprogfuncs_t *prinst, struct globalvars_s *pr_glob
}
}
}
// {"brush_create", PF_brush_create, 0, 0, 0, 0, D("int(float modelidx, brushface_t *in_faces, int numfaces, int contents)", "Inserts a new brush into the model. Return value is the new brush's id.")},
// {"brush_create", PF_brush_create, 0, 0, 0, 0, D("int(float modelidx, brushface_t *in_faces, int numfaces, int contents, optional int prevbrushid=0)", "Inserts a new brush into the model. Return value is the new brush's id.")},
void QCBUILTIN PF_brush_create(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
world_t *vmw = prinst->parms->user;
@ -6901,6 +7295,120 @@ void QCBUILTIN PF_brush_create(pubprogfuncs_t *prinst, struct globalvars_s *pr_g
}
}
}
//{"patch_create", PF_patch_create, 0, 0, 0, 0, D("int(float modelidx, int oldpatchid, patchvert_t *in_controlverts, patchinfo_t in_info)", "Inserts a new patch into the model. Return value is the new patch's id.")},
void QCBUILTIN PF_patch_create(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
world_t *vmw = prinst->parms->user;
int modelindex = G_FLOAT(OFS_PARM0);
model_t *mod = vmw->Get_CModel(vmw, modelindex);
heightmap_t *hm = mod?mod->terrain:NULL;
unsigned int brushid = G_INT(OFS_PARM1); //to simplify edits
qcpatchinfo_t *info = (qcpatchinfo_t*)&G_INT(OFS_PARM3);
unsigned int totalcp = info->cp_width*info->cp_width;
qcpatchvert_t *in_cverts = validateqcpointer(prinst, G_INT(OFS_PARM2), sizeof(*in_cverts), totalcp, false);
unsigned int i;
brushes_t brush, *nb;
G_INT(OFS_RETURN) = 0;
if (!hm)
{
if (mod && mod->loadstate == MLS_LOADING)
COM_WorkerPartialSync(mod, &mod->loadstate, MLS_LOADING);
if (mod && mod->loadstate == MLS_LOADED)
{
char basename[MAX_QPATH];
COM_FileBase(mod->name, basename, sizeof(basename));
mod->terrain = Mod_LoadTerrainInfo(mod, basename, true);
hm = mod->terrain;
if (!hm)
return;
Terr_FinishTerrain(mod);
}
else
return;
}
//if we're creating one that already exists, then assume that its a move.
if (brushid && Terr_Brush_DeleteId(hm, brushid))
{
#ifndef CLIENTONLY
if (sv.state && modelindex > 0)
{
MSG_WriteByte(&sv.multicast, svcfte_brushedit);
MSG_WriteShort(&sv.multicast, modelindex);
MSG_WriteByte(&sv.multicast, hmcmd_brush_delete);
MSG_WriteLong(&sv.multicast, brushid);
SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0);
}
else
#endif
#ifndef SERVERONLY
if (cls.state && modelindex > 0)
{
MSG_WriteByte(&cls.netchan.message, clcfte_brushedit);
MSG_WriteShort(&cls.netchan.message, modelindex);
MSG_WriteByte(&cls.netchan.message, hmcmd_brush_delete);
MSG_WriteLong(&cls.netchan.message, brushid);
}
#else
{
}
#endif
}
brush.patch = alloca(sizeof(*brush.patch) + sizeof(brush.patch->cp[0])*(totalcp-countof(brush.patch->cp)));
memset (brush.patch, 0, sizeof(*brush.patch) - sizeof(brush.patch->cp));
brush.patch->numcp[0] = info->cp_width;
brush.patch->numcp[1] = info->cp_height;
brush.patch->subdiv[0] = info->subdiv_x;
brush.patch->subdiv[1] = info->subdiv_y;
brush.patch->tex = Terr_Brush_FindTexture(hm, PR_GetString(prinst, info->shadername));
for (i = 0; i < totalcp; i++)
{
VectorCopy(in_cverts[i].v, brush.patch->cp[i].v);
Vector2Copy(in_cverts[i].tc, brush.patch->cp[i].tc);
Vector4Copy(in_cverts[i].rgba, brush.patch->cp[i].rgba);
}
//now emit it
brush.id = 0;
brush.contents = info->contents;
brush.numplanes = 0;
brush.planes = NULL;
if (info->cp_width > 1 && info->cp_width > 1)
{
nb = Terr_Brush_Insert(mod, hm, &brush);
if (nb)
{
G_INT(OFS_RETURN) = nb->id;
#ifndef CLIENTONLY
if (sv.state && modelindex > 0)
{
MSG_WriteByte(&sv.multicast, svcfte_brushedit);
MSG_WriteShort(&sv.multicast, modelindex);
MSG_WriteByte(&sv.multicast, hmcmd_brush_insert);
Brush_Serialise(&sv.multicast, nb);
SV_MulticastProtExt(vec3_origin, MULTICAST_ALL_R, ~0, 0, 0);
return;
}
#endif
#ifndef SERVERONLY
if (cls.state && modelindex > 0)
{
MSG_WriteByte(&cls.netchan.message, clcfte_brushedit);
MSG_WriteShort(&cls.netchan.message, modelindex);
MSG_WriteByte(&cls.netchan.message, hmcmd_brush_insert);
Brush_Serialise(&cls.netchan.message, nb);
return;
}
#endif
}
}
}
// {"brush_delete", PF_brush_delete, 0, 0, 0, 0, D("void(float modelidx, int brushid)", "Destroys the specified brush.")},
void QCBUILTIN PF_brush_delete(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
@ -6965,12 +7473,20 @@ void QCBUILTIN PF_brush_selected(pubprogfuncs_t *prinst, struct globalvars_s *pr
if (state >= 0)
{
if (br->selected != state)
{
if (br->patch)
{
br->patch->tex->rebuild = true;
// br->patch->relight = true;
}
else
{
for (i = 0; i < br->numplanes; i++)
{
br->faces[i].tex->rebuild = true;
br->faces[i].relight = true;
}
}
br->selected = state;
}
}
@ -7073,11 +7589,32 @@ void QCBUILTIN PF_brush_getfacepoints(pubprogfuncs_t *prinst, struct globalvars_
else
{
faceid--;
if (br->patch)
{
int w = br->patch->numcp[0];
int h = br->patch->numcp[1];
int x = faceid % (w-1);
int y = faceid / (w-1);
if (x >= w-1 || y >= h-1)
break;
if (maxpoints >= 1)
VectorCopy(br->patch->cp[(x+0)+(y+0)*w].v, out_verts[0]);
if (maxpoints >= 2)
VectorCopy(br->patch->cp[(x+1)+(y+0)*w].v, out_verts[1]);
if (maxpoints >= 3)
VectorCopy(br->patch->cp[(x+1)+(y+1)*w].v, out_verts[2]);
if (maxpoints >= 3)
VectorCopy(br->patch->cp[(x+0)+(y+1)*w].v, out_verts[3]);
p = min(4, maxpoints);
}
else
{
if (faceid >= br->numplanes)
break;
maxpoints = min(maxpoints, br->faces[faceid].numpoints);
for (p = 0; p < maxpoints; p++)
VectorCopy(br->faces[faceid].points[p], out_verts[p]);
}
G_INT(OFS_RETURN) = p;
}
break;
@ -7147,40 +7684,56 @@ void Terr_WriteBrushInfo(vfsfile_t *file, brushes_t *br)
if (br->patch)
{
qboolean hasrgba = false;
for (y = 0; y < br->patch->ypoints*br->patch->xpoints; y++)
for (y = 0; y < br->patch->numcp[1]*br->patch->numcp[0]; y++)
{
if (br->patch->verts[y].rgba[0] != 1.0 || br->patch->verts[y].rgba[1] != 1.0 || br->patch->verts[y].rgba[2] != 1.0 || br->patch->verts[y].rgba[3] != 1.0)
if (br->patch->cp[y].rgba[0] != 1.0 || br->patch->cp[y].rgba[1] != 1.0 || br->patch->cp[y].rgba[2] != 1.0 || br->patch->cp[y].rgba[3] != 1.0)
break;
}
hasrgba = (y < br->patch->ypoints*br->patch->xpoints);
hasrgba = (y < br->patch->numcp[1]*br->patch->numcp[0]);
VFS_PRINTF(file, "\n\tpatchDef%s\n\t{\n\t\t\"%s\"\n\t\t( %u %u %.9g %.9g %.9g )\n\t\t(\n",
hasrgba?"WS":"2",
if (br->patch->subdiv[0]>=0 && br->patch->subdiv[1]>=0)
{
VFS_PRINTF(file, "\n\tpatchDef3%s\n\t{\n\t\t\"%s\"\n\t\t( %u %u %u %u %.9g %.9g %.9g )\n\t\t(\n",
hasrgba?"WS":"",
br->patch->tex?br->patch->tex->shadername:"",
br->patch->xpoints/*width*/,
br->patch->ypoints/*height*/,
br->patch->numcp[0]/*width*/,
br->patch->numcp[1]/*height*/,
br->patch->subdiv[0]/*width*/,
br->patch->subdiv[1]/*height*/,
0.0/*rotation*/,
1.0/*xscale*/,
1.0/*yscale*/);
for (y = 0; y < br->patch->ypoints; y++)
}
else
{
VFS_PRINTF(file, "\n\tpatchDef2%s\n\t{\n\t\t\"%s\"\n\t\t( %u %u %.9g %.9g %.9g )\n\t\t(\n",
hasrgba?"WS":"",
br->patch->tex?br->patch->tex->shadername:"",
br->patch->numcp[0]/*width*/,
br->patch->numcp[1]/*height*/,
0.0/*rotation*/,
1.0/*xscale*/,
1.0/*yscale*/);
}
for (y = 0; y < br->patch->numcp[1]; y++)
{
VFS_PRINTF(file, "\t\t\t(\n");
for (x = 0; x < br->patch->xpoints; x++)
for (x = 0; x < br->patch->numcp[0]; x++)
{
const char *fmt;
if (hasrgba)
fmt = "\t\t\t\t( %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g )\n";
else
fmt = "\t\t\t\t( %.9g %.9g %.9g %.9g %.9g )\n"; //q3 compat.
VFS_PRINTF(file, fmt, br->patch->verts[x + y*br->patch->xpoints].v[0],
br->patch->verts[x + y*br->patch->xpoints].v[1],
br->patch->verts[x + y*br->patch->xpoints].v[2],
br->patch->verts[x + y*br->patch->xpoints].tc[0],
br->patch->verts[x + y*br->patch->xpoints].tc[1],
br->patch->verts[x + y*br->patch->xpoints].rgba[0],
br->patch->verts[x + y*br->patch->xpoints].rgba[1],
br->patch->verts[x + y*br->patch->xpoints].rgba[2],
br->patch->verts[x + y*br->patch->xpoints].rgba[3]);
VFS_PRINTF(file, fmt, br->patch->cp[x + y*br->patch->numcp[0]].v[0],
br->patch->cp[x + y*br->patch->numcp[0]].v[1],
br->patch->cp[x + y*br->patch->numcp[0]].v[2],
br->patch->cp[x + y*br->patch->numcp[0]].tc[0],
br->patch->cp[x + y*br->patch->numcp[0]].tc[1],
br->patch->cp[x + y*br->patch->numcp[0]].rgba[0],
br->patch->cp[x + y*br->patch->numcp[0]].rgba[1],
br->patch->cp[x + y*br->patch->numcp[0]].rgba[2],
br->patch->cp[x + y*br->patch->numcp[0]].rgba[3]);
}
VFS_PRINTF(file, "\t\t\t)\n");
}
@ -7209,7 +7762,7 @@ void Terr_WriteBrushInfo(vfsfile_t *file, brushes_t *br)
);
//write the name - if it contains markup or control chars, or other weird glyphs then be sure to quote it.
//we could always quote it, but that can and will screw up some editor somewhere...
//we could unconditionally quote it, but that can and will screw up some editor somewhere (like trenchbroom...)
for (s = texname = br->faces[i].tex?br->faces[i].tex->shadername:""; *s; s++)
{
if (*s <= 32 || *s >= 127 || *s == '\\' || *s == '(' || *s == '[' || *s == '{' || *s == ')' || *s == ']' || *s == '}')
@ -7409,8 +7962,8 @@ qboolean Terr_ReformEntitiesLump(model_t *mod, heightmap_t *hm, char *entities)
//patch info
brushtex_t *patch_tex=NULL;
int patch_w=0, patch_h=0;
struct patchvert_s patch_v[64][64];
int patchsz[2], patchsubdiv[2];
qcpatchvert_t patch_v[64][64];
#ifdef RUNTIMELIGHTING
hm->entsdirty = true;
@ -7447,7 +8000,7 @@ qboolean Terr_ReformEntitiesLump(model_t *mod, heightmap_t *hm, char *entities)
Terr_Brush_Insert(submod, subhm, &brush);
}
else if (patch_tex)
Terr_Patch_Insert(submod, subhm, patch_tex, patch_w, patch_h, patch_v[0], countof(patch_v[0]));
Terr_Patch_Insert(submod, subhm, patch_tex, patchsz[0], patchsz[1], patchsubdiv[0], patchsubdiv[1], patch_v[0], countof(patch_v[0]));
subhm->brushesedited = oe;
}
numplanes = 0;
@ -7532,11 +8085,14 @@ qboolean Terr_ReformEntitiesLump(model_t *mod, heightmap_t *hm, char *entities)
continue;
}
}
else if (inbrush && (!strcmp(token, "patchDef2") || !strcmp(token, "patchDef3") || !strcmp(token, "patchDefWS")))
else if (inbrush && (!strcmp(token, "patchDef2") || !strcmp(token, "patchDef3") ||
!strcmp(token, "patchDef2WS") || !strcmp(token, "patchDef3WS")))
{
int x, y;
qboolean patchdef3 = !strcmp(token, "patchDef3"); //fancy alternative with rgba colours per control point
qboolean parsergba = !strcmp(token, "patchDefWS"); //fancy alternative with rgba colours per control point
qboolean patchdef3 = !!strchr(token, '3'); //explict tessellation info (doom3-like)
qboolean parsergba = !!strstr(token, "WS"); //fancy alternative with rgba colours per control point
patchsz[0] = patchsz[1] = 0;
patchsubdiv[0] = patchsubdiv[1] = -1;
if (numplanes || patch_tex)
{
Con_Printf(CON_ERROR "%s: mixed patch+planes\n", mod->name);
@ -7554,14 +8110,14 @@ qboolean Terr_ReformEntitiesLump(model_t *mod, heightmap_t *hm, char *entities)
/*patch_w = atof(token);*/
entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL);
/*patch_h = atof(token);*/
entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL);
if (patchdef3)
{
/*xsubdiv = atof(token);*/
entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL);
/*ysubdiv = atof(token);*/
patchsubdiv[0] = atof(token);
entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL);
patchsubdiv[1] = atof(token);
}
entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL);
/*rotation = atof(token);*/
entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL);
/*xscale = atof(token);*/
@ -7573,7 +8129,6 @@ qboolean Terr_ReformEntitiesLump(model_t *mod, heightmap_t *hm, char *entities)
if (strcmp(token, "(")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;}
entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL);
y = 0;
patch_w = patch_h = 0;
while (!strcmp(token, "("))
{
entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL);
@ -7622,14 +8177,14 @@ qboolean Terr_ReformEntitiesLump(model_t *mod, heightmap_t *hm, char *entities)
if (x < countof(patch_v[y])-1)
x++;
}
if (patch_w < x)
patch_w = x;
if (patchsz[0] < x)
patchsz[0] = x;
if (strcmp(token, ")")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;}
entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL);
if (y < countof(patch_v)-1)
y++;
}
patch_h = y;
patchsz[1] = y;
if (strcmp(token, ")")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;}
entities = COM_ParseTokenOut(entities, brushpunct, token, sizeof(token), NULL);
if (strcmp(token, "}")) {Con_Printf(CON_ERROR "%s: invalid patch\n", mod->name);return false;}

View File

@ -618,6 +618,7 @@ void Mod_Init (qboolean initial)
Cvar_Register(&mod_loadentfiles_dir, NULL);
Cvar_Register(&temp_lit2support, NULL);
Cvar_Register (&r_meshpitch, "Gamecode");
Cvar_Register (&r_meshroll, "Gamecode");
Cmd_AddCommandD("sv_saveentfile", Mod_SaveEntFile_f, "Dumps a copy of the map's entities to disk, so that it can be edited and used as a replacement for slightly customised maps.");
Cmd_AddCommandD("mod_showent", Mod_ShowEnt_f, "Allows you to quickly search through a map's entities.");
Cmd_AddCommand("version_modelformats", Mod_PrintFormats_f);

View File

@ -271,18 +271,27 @@ typedef struct
struct patchdata_s
{ //unlit, always...
brushtex_t *tex;
unsigned short xpoints;
unsigned short ypoints;
struct
unsigned short numcp[2];
short subdiv[2]; //<0=regular q3 patch, 0=cp-only, >0=fixed-tessellation.
unsigned short tesssize[2];
struct patchtessvert_s
{
vec3_t v;
vec2_t tc;
vec4_t rgba;
// vec3_t norm;
// vec3_t sdir;
// vec3_t tdir;
} *tessvert; //x+(y*tesssize[0])
vec3_t norm;
vec3_t sdir;
vec3_t tdir;
} verts[1]; //x+(y*xpoints)
//control points
struct patchcpvert_s
{
vec3_t v;
vec2_t tc;
vec4_t rgba;
} cp[1]; //x+(y*numcp[0]) extends past end of patchdata_s
} *patch; //if this is NULL, then its a regular brush. otherwise its a patch.
struct brushface_s
{

View File

@ -510,6 +510,26 @@ static void ExpandBuffer(struct http_dl_ctx_s *con, int quant)
con->bufferlen = newlen;
}
static int VFSError_To_HTTP(int vfserr)
{
switch(vfserr)
{
case VFS_ERROR_TRYLATER:
return 0;
default:
case VFS_ERROR_UNSPECIFIED:
return 0; //don't know, no reason given.
case VFS_ERROR_EOF:
return HTTP_EOF;
case VFS_ERROR_DNSFAILURE:
return HTTP_DNSFAILURE;
case VFS_ERROR_WRONGCERT:
return HTTP_MITM;
case VFS_ERROR_UNTRUSTED:
return HTTP_UNTRUSTED;
}
}
static qboolean HTTP_DL_Work(struct dl_download *dl)
{
struct http_dl_ctx_s *con = dl->ctx;
@ -549,7 +569,11 @@ static qboolean HTTP_DL_Work(struct dl_download *dl)
if (!ammount)
return true;
if (ammount < 0)
{
dl->status = DL_FAILED;
dl->replycode = VFSError_To_HTTP(ammount);
return false;
}
#else
ammount = send(con->sock, con->buffer, con->bufferused, 0);
@ -579,7 +603,11 @@ static qboolean HTTP_DL_Work(struct dl_download *dl)
if (!ammount)
return true;
if (ammount < 0)
{
dl->status = DL_FAILED;
dl->replycode = VFSError_To_HTTP(ammount);
return false;
}
#else
ammount = recv(con->sock, con->buffer+con->bufferused, con->bufferlen-con->bufferused-15, 0);
if (!ammount)

View File

@ -148,6 +148,14 @@ struct dl_download *DL_Create(const char *url);
qboolean DL_CreateThread(struct dl_download *dl, vfsfile_t *file, void (*NotifyFunction)(struct dl_download *dl));
void DL_Close(struct dl_download *dl);
void DL_DeThread(void);
//internal 'http' error codes.
#define HTTP_DNSFAILURE 900 //no ip known
#define HTTP_NORESPONSE 901 //tcp failure
#define HTTP_EOF 902 //unexpected eof
#define HTTP_MITM 903 //wrong cert
#define HTTP_UNTRUSTED 904 //unverifiable cert
#endif
#endif

View File

@ -1904,6 +1904,9 @@ Emits a primitive statement, returning the var it places it's value in
*/
static int QCC_ShouldConvert(QCC_type_t *from, etype_t wanted)
{
if (from->type == ev_boolean)
from = from->parentclass;
/*no conversion needed*/
if (from->type == wanted)
return 0;
@ -2007,7 +2010,7 @@ static QCC_sref_t QCC_SupplyConversion(QCC_sref_t var, etype_t wanted, pbool fa
QCC_PR_ParsePrintSRef(WARN_LAXCAST, var);
}
else
QCC_PR_ParseErrorPrintSRef(ERR_TYPEMISMATCH, var, "Implicit type mismatch. Needed %s, got %s.", basictypenames[wanted], basictypenames[var.cast->type]);
QCC_PR_ParseErrorPrintSRef(ERR_TYPEMISMATCH, var, "Implicit type mismatch. Needed %s%s%s, got %s%s%s.", col_type,basictypenames[wanted],col_none, col_type,basictypenames[var.cast->type],col_none);
}
return var;
}
@ -7070,11 +7073,11 @@ QCC_sref_t QCC_PR_GenerateFunctionCallRef (QCC_sref_t newself, QCC_sref_t func,
QCC_ForceUnFreeDef(fparm.sym);
}
fparm.ofs = ofs;
if (!fparm.ofs)
{
// if (!fparm.ofs)
// {
args[parm].firststatement = numstatements;
args[parm].ref = fparm;
}
// }
parm++;
if (ofs+asz == arglist[i]->cast->size)
@ -9187,7 +9190,7 @@ fieldarrayindex:
if (arraysize && makearraypointers)
{
QCC_PR_ParseWarning(0, "Is this still needed?");
// QCC_PR_ParseWarning(0, "Is this still needed?"); //yes, when passing the head of an array to a function that takes a pointer
r = QCC_PR_GenerateAddressOf(retbuf, r);
}
}
@ -9200,6 +9203,13 @@ QCC_sref_t QCC_PR_GenerateVector(QCC_sref_t x, QCC_sref_t y, QCC_sref_t z)
{
QCC_sref_t d;
if (x.cast->type != ev_float && x.cast->type != ev_integer)
x = QCC_EvaluateCast(x, type_float, true);
if (y.cast->type != ev_float && y.cast->type != ev_integer)
y = QCC_EvaluateCast(y, type_float, true);
if (z.cast->type != ev_float && z.cast->type != ev_integer)
z = QCC_EvaluateCast(z, type_float, true);
if ((x.cast->type != ev_float && x.cast->type != ev_integer) ||
(y.cast->type != ev_float && y.cast->type != ev_integer) ||
(z.cast->type != ev_float && z.cast->type != ev_integer))
@ -14800,7 +14810,7 @@ static pbool QCC_CheckUninitialised(int firststatement, int laststatement)
err = QCC_CheckOneUninitialised(firststatement, laststatement, local, local->ofs, local->ofs + local->type->size * (local->arraysize?local->arraysize:1));
if (err > 0)
{
QCC_PR_Warning(WARN_UNINITIALIZED, s_filen, statements[err].linenum, "Potentially uninitialised variable %s", local->name);
QCC_PR_Warning(WARN_UNINITIALIZED, s_filen, statements[err].linenum, "Potentially uninitialised variable %s%s%s", col_symbol,local->name,col_none);
result = true;
// break;
}
@ -17271,10 +17281,16 @@ QCC_sref_t QCC_PR_ParseInitializerType_Internal(int arraysize, QCC_def_t *basede
QCC_sref_t QCC_PR_ParseInitializerTemp(QCC_type_t *type)
{
QCC_sref_t def = QCC_GetTemp(type);
def = QCC_PR_ParseInitializerType_Internal(0, NULL, def, 0);
if (def.cast != type)
QCC_sref_t imm;
imm = QCC_PR_ParseInitializerType_Internal(0, NULL, def, 0);
if (imm.cast)
{
if (imm.cast != type)
QCC_PR_ParseError(ERR_INTERNAL, "QCC_PR_ParseInitializerTemp changed type\n");
return def;
QCC_FreeTemp(def);
return imm; //just use the immediate.
}
return def; //use our silly temp.
}
//returns true where its a const/static initialiser. false if non-const/final initialiser
pbool QCC_PR_ParseInitializerType(int arraysize, QCC_def_t *basedef, QCC_sref_t def, unsigned int flags)

View File

@ -343,11 +343,11 @@ compiler_flag_t compiler_flag[] = {
{&keyword_goto, defaultkeyword, "goto", "Keyword: goto", "Disables the 'goto' keyword."},
{&keyword_int, typekeyword, "int", "Keyword: int", "Disables the 'int' keyword."},
{&keyword_integer, typekeyword, "integer", "Keyword: integer", "Disables the 'integer' keyword."},
{&keyword_double, defaultkeyword, "double", "Keyword: double", "Disables the 'double' keyword."},
{&keyword_long, defaultkeyword, "long", "Keyword: long", "Disables the 'long' keyword."},
{&keyword_short, defaultkeyword, "short", "Keyword: short", "Disables the 'short' keyword."},
{&keyword_char, defaultkeyword, "char", "Keyword: char", "Disables the 'char' keyword."},
{&keyword_signed, defaultkeyword, "signed", "Keyword: signed", "Disables the 'signed' keyword."},
{&keyword_double, nondefaultkeyword, "double", "Keyword: double", "Disables the 'double' keyword."},
{&keyword_long, nondefaultkeyword, "long", "Keyword: long", "Disables the 'long' keyword."},
{&keyword_short, nondefaultkeyword, "short", "Keyword: short", "Disables the 'short' keyword."},
{&keyword_char, nondefaultkeyword, "char", "Keyword: char", "Disables the 'char' keyword."},
{&keyword_signed, nondefaultkeyword, "signed", "Keyword: signed", "Disables the 'signed' keyword."},
{&keyword_unsigned, defaultkeyword, "unsigned", "Keyword: unsigned", "Disables the 'unsigned' keyword."},
{&keyword_noref, defaultkeyword, "noref", "Keyword: noref", "Disables the 'noref' keyword."}, //nowhere else references this, don't warn about it.
{&keyword_unused, nondefaultkeyword, "unused", "Keyword: unused", "Disables the 'unused' keyword. 'unused' means that the variable is unused, you're aware that its unused, and you'd rather not know about all the warnings this results in."},
@ -4335,6 +4335,7 @@ static void QCC_PR_CommandLinePrecompilerOptions (void)
keyword_vector = keyword_entity = keyword_float = keyword_string = false; //not to be confused with actual types, but rather the absence of the keyword local.
keyword_integer = keyword_enumflags = false;
keyword_float = keyword_int = keyword_typedef = keyword_struct = keyword_union = keyword_enum = true;
keyword_double = keyword_long = keyword_short = keyword_char = keyword_signed = keyword_unsigned = true;
keyword_thinktime = keyword_until = keyword_loop = false;
keyword_integer = true;

View File

@ -3006,7 +3006,8 @@ void PF_setmodel_Internal (pubprogfuncs_t *prinst, edict_t *e, const char *m)
// if it is an inline model, get the size information for it
if (m && (m[0] == '*' || (*m&&progstype == PROG_H2)))
{
mod = Mod_ForName (Mod_FixName(m, sv.modelname), MLV_WARN);
mod = SVPR_GetCModel(&sv.world, i);
// mod = Mod_ForName (Mod_FixName(m, sv.modelname), MLV_WARN);
if (mod)
{
while(mod->loadstate == MLS_LOADING)
@ -3014,7 +3015,15 @@ void PF_setmodel_Internal (pubprogfuncs_t *prinst, edict_t *e, const char *m)
VectorCopy (mod->mins, e->v->mins);
VectorCopy (mod->maxs, e->v->maxs);
VectorSubtract (mod->maxs, mod->mins, e->v->size);
#ifdef HEXEN2
if (progstype == PROG_H2 && mod->type == mod_alias && !sv_gameplayfix_setmodelrealbox.ival)
{ //hexen2 expands its mdls by 10
vec3_t hexen2expansion = {10,10,10};
VectorSubtract (mod->mins, hexen2expansion, e->v->mins);
VectorAdd (mod->maxs, hexen2expansion, e->v->maxs);
}
#endif
VectorSubtract (e->v->maxs, e->v->mins, e->v->size);
World_LinkEdict (&sv.world, (wedict_t*)e, false);
}
@ -10985,7 +10994,7 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs
{"chr2str", PF_chr2str, 0, 0, 0, 223, D("string(float chr, ...)", "The input floats are considered character values, and are concatenated.")},
{"strconv", PF_strconv, 0, 0, 0, 224, D("string(float ccase, float redalpha, float redchars, string str, ...)", "Converts quake chars in the input string amongst different representations.\nccase specifies the new case for letters.\n 0: not changed.\n 1: forced to lower case.\n 2: forced to upper case.\nredalpha and redchars switch between colour ranges.\n 0: no change.\n 1: Forced white.\n 2: Forced red.\n 3: Forced gold(low) (numbers only).\n 4: Forced gold (high) (numbers only).\n 5+6: Forced to white and red alternately.\nYou should not use this builtin in combination with UTF-8.")},
{"strpad", PF_strpad, 0, 0, 0, 225, D("string(float pad, string str1, ...)", "Pads the string with spaces, to ensure its a specific length (so long as a fixed-width font is used, anyway). If pad is negative, the spaces are added on the left. If positive the padding is on the right.")}, //will be moved
{"infoadd", PF_infoadd, 0, 0, 0, 226, D("string(infostring old, string key, string value)", "Returns a new tempstring infostring with the named value changed (or added if it was previously unspecified). Key and value may not contain the \\ character.")},
{"infoadd", PF_infoadd, 0, 0, 0, 226, D("infostring(infostring old, string key, string value)", "Returns a new tempstring infostring with the named value changed (or added if it was previously unspecified). Key and value may not contain the \\ character.")},
{"infoget", PF_infoget, 0, 0, 0, 227, D("string(infostring info, string key)", "Reads a named value from an infostring. The returned value is a tempstring")},
// {"strcmp", PF_strncmp, 0, 0, 0, 228, D("float(string s1, string s2)", "Compares the two strings exactly. s1ofs allows you to treat s2 as a substring to compare against, or should be 0.\nReturns 0 if the two strings are equal, a negative value if s1 appears numerically lower, and positive if s1 appears numerically higher.")},
{"strncmp", PF_strncmp, 0, 0, 0, 228, D("#define strcmp strncmp\nfloat(string s1, string s2, optional float len, optional float s1ofs, optional float s2ofs)", "Compares up to 'len' chars in the two strings. s1ofs allows you to treat s2 as a substring to compare against, or should be 0.\nReturns 0 if the two strings are equal, a negative value if s1 appears numerically lower, and positive if s1 appears numerically higher.")},
@ -11105,6 +11114,27 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs
{"brush_findinvolume",PF_brush_findinvolume,0, 0, 0, 0, D("int(float modelid, vector *planes, float *dists, int numplanes, int *out_brushes, int *out_faces, int maxresults)", "Allows you to easily obtain a list of brushes+faces within the given bounding region. If out_faces is not null, the same brush might be listed twice.")},
// {"brush_editplane", PF_brush_editplane, 0, 0, 0, 0, D("float(float modelid, int brushid, int faceid, in brushface *face)", "Changes a surface's texture info.")},
// {"brush_transformselected",PF_brush_transformselected,0,0,0, 0, D("int(float modelid, int brushid, float *matrix)", "Transforms selected brushes by the given transform")},
#define qcpatchvert \
"typedef struct\n{\n" \
"\tstring shadername;\n" \
"\tint contents;\n" \
"\tint cpwidth;\n" \
"\tint cpheight;\n" \
"\tint tesswidth;\n" \
"\tint tessheight;\n" \
"\tvector texinfo;/*scalex,y,rot*/\n" \
"} patchinfo_t;\n" \
"typedef struct\n{\n" \
"\tvector xyz;\n" \
"\tvector rgb; float a;\n" \
"\tfloat s, t;\n" \
"} patchvert_t;\n" \
"#define patch_delete(modelidx,patchidx) brush_delete(modelidx,patchidx)\n"
{"patch_getcp", PF_patch_getcp, 0, 0, 0, 0, D(qcpatchvert "int(float modelidx, int patchid, patchvert_t *out_controlverts, int maxcp, patchinfo_t *out_info)", "Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error.")},
{"patch_getmesh", PF_patch_getmesh, 0, 0, 0, 0, D("int(float modelidx, int patchid, patchvert_t *out_verts, int maxverts, __out patchinfo_t out_info)", "Queries a patch's information. You must pre-allocate the face array for the builtin to write to. Return value is the total number of control verts that were retrieved, 0 on error.")},
{"patch_create", PF_patch_create, 0, 0, 0, 0, D("int(float modelidx, int oldpatchid, patchvert_t *in_controlverts, patchinfo_t in_info)", "Inserts a new patch into the model. Return value is the new patch's id.")},
// {"patch_calculate", PF_patch_calculate, 0, 0, 0, 0, D("int(patchvert_t *in_controlverts, patchvert_t *out_renderverts, int maxout, __inout patchinfo_t inout_info)", "Calculates the geometry of a hyperthetical patch.")},
#endif
#ifdef ENGINE_ROUTING

View File

@ -163,7 +163,7 @@ cvar_t sv_csqc_progname = CVARAF("sv_csqc_progname", "csprogs.dat", /*dp*/"csqc_
cvar_t pausable = CVAR("pausable", "");
cvar_t sv_banproxies = CVARD("sv_banproxies", "0", "If enabled, anyone connecting via known proxy software will be refused entry. This should aid with blocking aimbots, but is only reliable for certain public proxies.");
cvar_t sv_specprint = CVARD("sv_specprint", "3", "Bitfield that controls which player events spectators see when tracking that player.\n&1: spectators will see centerprints.\n&2: spectators will see sprints (pickup messages etc).\n&4: spectators will receive console commands, this is potentially risky.\nIndividual spectators can use 'setinfo sp foo' to limit this setting.");
cvar_t sv_protocol = CVARD("sv_protocol", "", "Specifies which protocol extensions to force. recognised values: csqc");
//
// game rules mirrored in svs.info
@ -1945,6 +1945,56 @@ void SV_ClientProtocolExtensionsChanged(client_t *client)
extern cvar_t pr_maxedicts;
client_t *seat;
extern cvar_t sv_protocol;
char *s = sv_protocol.string;
while ((s = COM_Parse(s)))
{
if (!strcasecmp(com_token, "fte2"))
{ //fancy stuff
client->fteprotocolextensions
|= PEXT_CSQC /*mods break without*/
| PEXT_CHUNKEDDOWNLOADS /*much faster downloads+redirects*/
;
client->fteprotocolextensions2
|= PEXT2_PRYDONCURSOR /*mods might break without*/
// | PEXT2_VOICECHAT /*entirely optional*/
| PEXT2_SETANGLEDELTA /*mostly just nice to have*/
| PEXT2_REPLACEMENTDELTAS /*carries quite a bit of extra info*/
| PEXT2_MAXPLAYERS /*not supporting the extra players is bad*/
| PEXT2_PREDINFO /*fixes some repdelta issues (especially for nq)*/
| PEXT2_NEWSIZEENCODING /*more accurate sizes, for awkward mods*/
| PEXT2_INFOBLOBS /*allows mods to send infoblobs to csqc (for avatar images or whatever)*/
;
}
if (!strcasecmp(com_token, "fte1"))
{ //older stuff. most of this was replaced by replacementdeltas.
client->fteprotocolextensions
|= PEXT_SETVIEW
| PEXT_SCALE
| PEXT_TRANS
| PEXT_ACCURATETIMINGS
| PEXT_SOUNDDBL
| PEXT_MODELDBL
| PEXT_ENTITYDBL
| PEXT_ENTITYDBL2
| PEXT_FLOATCOORDS
| PEXT_COLOURMOD
| PEXT_SPAWNSTATIC2
| PEXT_256PACKETENTITIES
| PEXT_SETATTACHMENT
| PEXT_CHUNKEDDOWNLOADS
| PEXT_CSQC
| PEXT_DPFLAGS
;
}
if (!strcasecmp(com_token, "csqc"))
{ //JUST csqc.
client->fteprotocolextensions
|= PEXT_CSQC
;
}
}
client->fteprotocolextensions &= Net_PextMask(PROTOCOL_VERSION_FTE1, ISNQCLIENT(client));
client->fteprotocolextensions2 &= Net_PextMask(PROTOCOL_VERSION_FTE2, ISNQCLIENT(client));
client->ezprotocolextensions1 &= Net_PextMask(PROTOCOL_VERSION_EZQUAKE1, ISNQCLIENT(client)) & EZPEXT1_SERVERADVERTISE;
@ -5408,6 +5458,7 @@ void SV_InitLocal (void)
if (isDedicated)
sv_public.enginevalue = "1";
Cvar_Register (&sv_protocol, cvargroup_servercontrol);
Cvar_Register (&sv_guidhash, cvargroup_servercontrol);
Cvar_Register (&sv_serverip, cvargroup_servercontrol);
Cvar_Register (&sv_public, cvargroup_servercontrol);

View File

@ -323,8 +323,7 @@ float World_changeyaw (wedict_t *ent)
vec3_t vang;
/*calc current view matrix relative to the surface*/
ent->v->angles[PITCH] *= r_meshpitch.value;
AngleVectors(ent->v->angles, view[0], view[1], view[2]);
AngleVectorsMesh(ent->v->angles, view[0], view[1], view[2]);
VectorNegate(view[1], view[1]);
World_GetEntGravityAxis(ent, surf);

View File

@ -151,6 +151,7 @@ SV_CheckVelocity
void WPhys_CheckVelocity (world_t *w, wedict_t *ent)
{
int i;
#ifdef HAVE_SERVER
extern cvar_t sv_nqplayerphysics;
if (sv_nqplayerphysics.ival)
@ -175,6 +176,7 @@ void WPhys_CheckVelocity (world_t *w, wedict_t *ent)
}
}
else
#endif
{ //bound radially (for sanity)
for (i=0 ; i<3 ; i++)
{

View File

@ -38,7 +38,9 @@ void QDECL SV_NQPhysicsUpdate(cvar_t *var, char *oldvalue)
{
if (!strcmp(var->string, "auto") || !strcmp(var->string, ""))
{ //prediction requires nq physics, so use it by default in multiplayer.
if (progstype <= PROG_QW || (!isDedicated && sv.allocated_client_slots > 1))
if ( progstype <= PROG_QW || //none or qw use qw physics by default
(!isDedicated && sv.allocated_client_slots > 1) || //multiplayer dedicated servers use qw physics for nq mods too. server admins are expected to be able to spend a little more time to configure things properly.
(svprogfuncs&&PR_FindFunction(svprogfuncs, "SV_RunClientCommand", PR_ANY))) //mods that use explicit custom player physics/pred ALWAYS want qw physics (just hope noone forces it off)
var->ival = 0;
else
var->ival = 1;
@ -1224,7 +1226,7 @@ void SV_SendClientPrespawnInfo(client_t *client)
if (client->fteprotocolextensions & PEXT_SOUNDDBL)
maxclientsupportedsounds = MAX_PRECACHE_SOUNDS;
#endif
#ifdef PEXT_SOUNDDBL
#ifdef PEXT2_REPLACEMENTDELTAS
if (client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)
maxclientsupportedsounds = MAX_PRECACHE_SOUNDS;
#endif
@ -6154,13 +6156,13 @@ void SV_Pext_f(void)
}
}
SV_ClientProtocolExtensionsChanged(host_client);
if (!host_client->supportedprotocols && Cmd_Argc() == 1)
Con_DPrintf("%s reports no extended capabilities.\n", host_client->name);
else
Con_DPrintf("%s now using pext: %x, %x, %x\n", host_client->name, host_client->fteprotocolextensions, host_client->fteprotocolextensions2, host_client->ezprotocolextensions1);
SV_ClientProtocolExtensionsChanged(host_client);
#ifdef NQPROT
if (ISNQCLIENT(host_client))
SVNQ_New_f();