diff --git a/engine/client/cl_ents.c b/engine/client/cl_ents.c index 69cf6f07..3a9d270b 100644 --- a/engine/client/cl_ents.c +++ b/engine/client/cl_ents.c @@ -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 { diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 2bc954e0..74f0c7a0 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -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: diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index e6f0ea68..aa2903a9 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -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]); diff --git a/engine/client/cl_pred.c b/engine/client/cl_pred.c index eee200b6..ab2d99fc 100644 --- a/engine/client/cl_pred.c +++ b/engine/client/cl_pred.c @@ -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; } } diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index 19aa07c7..a9783ad3 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -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; diff --git a/engine/client/clq2_ents.c b/engine/client/clq2_ents.c index eea86fb4..bd31714c 100644 --- a/engine/client/clq2_ents.c +++ b/engine/client/clq2_ents.c @@ -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! { diff --git a/engine/client/console.c b/engine/client/console.c index fe171d1b..309e1463 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -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]); diff --git a/engine/client/m_download.c b/engine/client/m_download.c index d6478597..905e8450 100644 --- a/engine/client/m_download.c +++ b/engine/client/m_download.c @@ -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++) { diff --git a/engine/client/m_options.c b/engine/client/m_options.c index d5a917ed..ec1b4b43 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -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; diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index 3a15d992..1ecfc7ab 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -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 diff --git a/engine/client/pr_skelobj.c b/engine/client/pr_skelobj.c index e4b4c2a0..7a41c057 100644 --- a/engine/client/pr_skelobj.c +++ b/engine/client/pr_skelobj.c @@ -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; diff --git a/engine/client/render.h b/engine/client/render.h index 8b3a04d8..70f47e79 100644 --- a/engine/client/render.h +++ b/engine/client/render.h @@ -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 { diff --git a/engine/client/sbar.c b/engine/client/sbar.c index b4e50ed0..118c6c62 100644 --- a/engine/client/sbar.c +++ b/engine/client/sbar.c @@ -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); diff --git a/engine/client/sys_linux.c b/engine/client/sys_linux.c index c4851921..2d4ba6f5 100644 --- a/engine/client/sys_linux.c +++ b/engine/client/sys_linux.c @@ -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; diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index c63be440..5be1d9ba 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -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 diff --git a/engine/common/com_mesh.c b/engine/common/com_mesh.c index 8cc961eb..e9ddfee6 100644 --- a/engine/common/com_mesh.c +++ b/engine/common/com_mesh.c @@ -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 diff --git a/engine/common/com_phys_ode.c b/engine/common/com_phys_ode.c index 511749bf..12ffda2c 100644 --- a/engine/common/com_phys_ode.c +++ b/engine/common/com_phys_ode.c @@ -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); diff --git a/engine/common/common.h b/engine/common/common.h index b75ccfca..7be87b98 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -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)) diff --git a/engine/common/cvar.c b/engine/common/cvar.c index efc7b7a9..a0ed9ecc 100644 --- a/engine/common/cvar.c +++ b/engine/common/cvar.c @@ -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 diff --git a/engine/common/fs.c b/engine/common/fs.c index 3d8b1033..3c9c9826 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -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)}, diff --git a/engine/common/mathlib.c b/engine/common/mathlib.c index 3d67a0db..ea96523d 100644 --- a/engine/common/mathlib.c +++ b/engine/common/mathlib.c @@ -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) { diff --git a/engine/common/mathlib.h b/engine/common/mathlib.h index 89a4c595..0845d2ca 100644 --- a/engine/common/mathlib.h +++ b/engine/common/mathlib.h @@ -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); diff --git a/engine/common/net_ssl_gnutls.c b/engine/common/net_ssl_gnutls.c index 7f1e9039..77f7469a 100644 --- a/engine/common/net_ssl_gnutls.c +++ b/engine/common/net_ssl_gnutls.c @@ -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; diff --git a/engine/common/net_ssl_winsspi.c b/engine/common/net_ssl_winsspi.c index 99e8eaa6..2d3c9f0b 100644 --- a/engine/common/net_ssl_winsspi.c +++ b/engine/common/net_ssl_winsspi.c @@ -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; diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index 5fdf3fde..4cc5929f 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -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; } diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c index 9701969a..a8f0f9be 100644 --- a/engine/common/pr_bgcmd.c +++ b/engine/common/pr_bgcmd.c @@ -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]); diff --git a/engine/common/pr_common.h b/engine/common/pr_common.h index acca5884..ce120db3 100644 --- a/engine/common/pr_common.h +++ b/engine/common/pr_common.h @@ -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); diff --git a/engine/gl/gl_heightmap.c b/engine/gl/gl_heightmap.c index bf2d52d4..e181dcb1 100644 --- a/engine/gl/gl_heightmap.c +++ b/engine/gl/gl_heightmap.c @@ -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_prespawning, //sent before initial inserts + 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,7 +4317,15 @@ qboolean Heightmap_Trace(struct model_s *model, int hulloverride, const framesta hmtrace.absmins[1] > brushes->maxs[1] || hmtrace.absmins[2] > brushes->maxs[2]) continue; - face = Heightmap_Trace_Brush(&hmtrace, brushes->planes, brushes->numplanes, brushes); + 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) { trace->brush_id = brushes->id; @@ -5898,77 +5974,137 @@ 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) - { - int x, y; - index_t r1, r2; - - for (y = 0, r1 = numverts, r2 = 0; y < br->patch->ypoints; y++) + if (br->selected) + continue; + if (br->patch) + { //this one's a patch + if (br->patch->tex == bt && lmnum == -1) { - 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]); + int x, y; + index_t r1, r2; - Vector2Copy(br->patch->verts[r2].tc, arrays->lmcoord[r1]); + if (br->patch->tessvert && !br->selected) + { //tessellated version of the patch. + + //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->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; + 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 + { //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; } } - for (y = 0, r1 = numverts, r2 = r1 + br->patch->xpoints; y < br->patch->ypoints-1; y++) - { - for (x = 0; x < br->patch->xpoints-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++; - } - numverts += br->patch->ypoints*br->patch->xpoints; } - for (j = 0; j < br->numplanes; j++) - { - if (br->faces[j].tex == bt && !br->selected && br->faces[j].lightmap == lmnum) + 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++) { - size_t k, o; - float s,t; - - for (k = 0, o = numverts; k < br->faces[j].numpoints; k++, o++) + if (br->faces[j].tex == bt && !br->selected && br->faces[j].lightmap == lmnum) { - VectorCopy(br->faces[j].points[k], arrays->coord[o]); - VectorCopy(br->planes[j], arrays->normal[o]); - VectorCopy(br->faces[j].stdir[0], arrays->svector[o]); - VectorCopy(br->faces[j].stdir[1], arrays->tvector[o]); - Vector4Set(arrays->rgba[o], 1.0, 1.0, 1.0, 1.0); + size_t k, o; + float s,t; - //compute the texcoord planes - s = (DotProduct(arrays->svector[o], arrays->coord[o]) + br->faces[j].stdir[0][3]); - t = (DotProduct(arrays->tvector[o], arrays->coord[o]) + br->faces[j].stdir[1][3]); - arrays->texcoord[o][0] = s * scale[0]; - arrays->texcoord[o][1] = t * scale[1]; + for (k = 0, o = numverts; k < br->faces[j].numpoints; k++, o++) + { + VectorCopy(br->faces[j].points[k], arrays->coord[o]); + VectorCopy(br->planes[j], arrays->normal[o]); + VectorCopy(br->faces[j].stdir[0], arrays->svector[o]); + VectorCopy(br->faces[j].stdir[1], arrays->tvector[o]); + Vector4Set(arrays->rgba[o], 1.0, 1.0, 1.0, 1.0); - //maths, maths, and more maths. - arrays->lmcoord[o][0] = (br->faces[j].lmbase[0]+0.5 + s/br->faces[j].lmscale-br->faces[j].lmbias[0]) * hm->brushlmscale; - arrays->lmcoord[o][1] = (br->faces[j].lmbase[1]+0.5 + t/br->faces[j].lmscale-br->faces[j].lmbias[1]) * hm->brushlmscale; + //compute the texcoord planes + s = (DotProduct(arrays->svector[o], arrays->coord[o]) + br->faces[j].stdir[0][3]); + t = (DotProduct(arrays->tvector[o], arrays->coord[o]) + br->faces[j].stdir[1][3]); + arrays->texcoord[o][0] = s * scale[0]; + arrays->texcoord[o][1] = t * scale[1]; + + //maths, maths, and more maths. + arrays->lmcoord[o][0] = (br->faces[j].lmbase[0]+0.5 + s/br->faces[j].lmscale-br->faces[j].lmbias[0]) * hm->brushlmscale; + arrays->lmcoord[o][1] = (br->faces[j].lmbase[1]+0.5 + t/br->faces[j].lmscale-br->faces[j].lmbias[1]) * hm->brushlmscale; + } + for (k = 2; k < br->faces[j].numpoints; k++) + { //triangle fans + arrays->index[numindicies++] = numverts + 0; + arrays->index[numindicies++] = numverts + k-1; + arrays->index[numindicies++] = numverts + k-0; + } + numverts += br->faces[j].numpoints; } - for (k = 2; k < br->faces[j].numpoints; k++) - { //triangle fans - arrays->index[numindicies++] = numverts + 0; - arrays->index[numindicies++] = numverts + k-1; - arrays->index[numindicies++] = numverts + k-0; - } - numverts += br->faces[j].numpoints; } } } @@ -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)); - 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 (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); - MSG_WriteByte(msg, hmcmd_brush_insert); - Brush_Serialise(msg, best); + 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,17 +6878,33 @@ 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)); - brush.numplanes = 128; - brush.planes = alloca(sizeof(*brush.planes) * brush.numplanes); - brush.faces = alloca(sizeof(*brush.faces) * brush.numplanes); - if (!Brush_Deserialise(hm, &brush)) + if (cmd == hmcmd_patch_insert) { - Con_Printf("SV_Parse_BrushEdit: %s sent an unparsable brush\n", host_client->name); - return false; + 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); + if (!Brush_Deserialise(hm, &brush)) + { + Con_Printf("SV_Parse_BrushEdit: %s sent an unparsable brush\n", host_client->name); + return false; + } } if (!authorise) { @@ -6660,8 +6920,11 @@ qboolean SV_Parse_BrushEdit(void) MSG_WriteByte(&sv.multicast, svcfte_brushedit); MSG_WriteShort(&sv.multicast, modelindex); - MSG_WriteByte(&sv.multicast, hmcmd_brush_insert); - Brush_Serialise(&sv.multicast, &brush); + 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,15 +6964,26 @@ qboolean SV_Parse_BrushEdit(void) typedef struct { - int shadername; - vec3_t planenormal; - float planedist; - vec3_t sdir; - float sbias; - vec3_t tdir; - float tbias; + string_t shadername; + vec3_t planenormal; + float planedist; + vec3_t sdir; + float sbias; + vec3_t tdir; + 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) { @@ -6966,10 +7474,18 @@ void QCBUILTIN PF_brush_selected(pubprogfuncs_t *prinst, struct globalvars_s *pr { if (br->selected != state) { - for (i = 0; i < br->numplanes; i++) + if (br->patch) { - br->faces[i].tex->rebuild = true; - br->faces[i].relight = true; + 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 (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]); + 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", - br->patch->tex?br->patch->tex->shadername:"", - br->patch->xpoints/*width*/, - br->patch->ypoints/*height*/, - 0.0/*rotation*/, - 1.0/*xscale*/, - 1.0/*yscale*/); - for (y = 0; y < br->patch->ypoints; y++) + 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->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*/); + } + 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;} diff --git a/engine/gl/gl_model.c b/engine/gl/gl_model.c index 3705e31c..5c172d05 100644 --- a/engine/gl/gl_model.c +++ b/engine/gl/gl_model.c @@ -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); diff --git a/engine/gl/gl_terrain.h b/engine/gl/gl_terrain.h index fce0f941..4073f050 100644 --- a/engine/gl/gl_terrain.h +++ b/engine/gl/gl_terrain.h @@ -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 { diff --git a/engine/http/httpclient.c b/engine/http/httpclient.c index 48c23da8..91a35b8b 100644 --- a/engine/http/httpclient.c +++ b/engine/http/httpclient.c @@ -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) diff --git a/engine/http/iweb.h b/engine/http/iweb.h index 21f1d2dd..ad1207a4 100644 --- a/engine/http/iweb.h +++ b/engine/http/iweb.h @@ -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 diff --git a/engine/qclib/qcc_pr_comp.c b/engine/qclib/qcc_pr_comp.c index 183a5e45..323019b7 100644 --- a/engine/qclib/qcc_pr_comp.c +++ b/engine/qclib/qcc_pr_comp.c @@ -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_PR_ParseError(ERR_INTERNAL, "QCC_PR_ParseInitializerTemp changed type\n"); - return def; + 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"); + 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) diff --git a/engine/qclib/qccmain.c b/engine/qclib/qccmain.c index 8de1ae3e..297b2703 100644 --- a/engine/qclib/qccmain.c +++ b/engine/qclib/qccmain.c @@ -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; diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index eaa2975c..5d4e9116 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -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 diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 33105db8..6803dc61 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -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); diff --git a/engine/server/sv_move.c b/engine/server/sv_move.c index 7bb46067..9c9698b5 100644 --- a/engine/server/sv_move.c +++ b/engine/server/sv_move.c @@ -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); diff --git a/engine/server/sv_phys.c b/engine/server/sv_phys.c index 5ae6c076..917cf3c3 100644 --- a/engine/server/sv_phys.c +++ b/engine/server/sv_phys.c @@ -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++) { diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 0d57f024..d9ab020f 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -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; @@ -68,7 +70,7 @@ cvar_t sv_cheatspeedchecktime = CVARD("sv_cheatspeedchecktime", "30", "The inter #endif cvar_t sv_playermodelchecks = CVAR("sv_playermodelchecks", "0"); cvar_t sv_ping_ignorepl = CVARD("sv_ping_ignorepl", "0", "If 1, ping times reported for players will ignore the effects of packetloss on ping times. 0 is slightly more honest, but less useful for connection diagnosis."); -cvar_t sv_protocol_nq = CVARD("sv_protocol_nq", "", "Specifies the default protocol to use for new NQ clients. This is only relevent for clients that do not report their supported protocols. Supported values are\n0 = autodetect\n15 = vanilla\n666 = fitzquake\n999 = rmq protocol\nThe sv_bigcoords cvar forces upgrades as required."); +cvar_t sv_protocol_nq = CVARD("sv_protocol_nq", "", "Specifies the default protocol to use for new NQ clients. This is only relevent for clients that do not report their supported protocols. Supported values are\n0 = autodetect\n15 = vanilla\n666 = fitzquake\n999 = rmq protocol\nThe sv_bigcoords cvar forces upgrades as required."); cvar_t sv_minpitch = CVARAFD("minpitch", "", "sv_minpitch", CVAR_SERVERINFO, "Assumed to be -70"); cvar_t sv_maxpitch = CVARAFD("maxpitch", "", "sv_maxpitch", CVAR_SERVERINFO, "Assumed to be 80"); @@ -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();