+ Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i but it has no model\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity));
+ }
+}
+
+/////////////////////////////////////////
+// DP_MD3_TAGINFO extension coded by VorteX
+
+int SV_GetTagIndex (prvm_edict_t *e, const char *tagname)
+{
+ int i;
+ model_t *model;
+
+ i = e->fields.server->modelindex;
+ if (i < 1 || i >= MAX_MODELS)
+ return -1;
+ model = sv.models[i];
+
+ return Mod_Alias_GetTagIndexForName(model, e->fields.server->skin, tagname);
+};
+
+// Warnings/errors code:
+// 0 - normal (everything all-right)
+// 1 - world entity
+// 2 - free entity
+// 3 - null or non-precached model
+// 4 - no tags with requested index
+// 5 - runaway loop at attachment chain
+extern cvar_t cl_bob;
+extern cvar_t cl_bobcycle;
+extern cvar_t cl_bobup;
+int SV_GetTagMatrix (matrix4x4_t *out, prvm_edict_t *ent, int tagindex)
+{
+ prvm_eval_t *val;
+ int modelindex, reqframe, attachloop;
+ matrix4x4_t entitymatrix, tagmatrix, attachmatrix;
+ prvm_edict_t *attachent;
+ model_t *model;
+
+ Matrix4x4_CreateIdentity(out); // warnings and errors return identical matrix
+
+ if (ent == prog->edicts)
+ return 1;
+ if (ent->priv.server->free)
+ return 2;
+
+ modelindex = (int)ent->fields.server->modelindex;
+ if (modelindex <= 0 || modelindex > MAX_MODELS)
+ return 3;
+
+ model = sv.models[modelindex];
+
+ if (ent->fields.server->frame >= 0 && ent->fields.server->frame < model->numframes && model->animscenes)
+ reqframe = model->animscenes[(int)ent->fields.server->frame].firstframe;
+ else
+ reqframe = 0; // if model has wrong frame, engine automatically switches to model first frame
+
+ // get initial tag matrix
+ if (tagindex)
+ {
+ int ret = Mod_Alias_GetTagMatrix(model, reqframe, tagindex - 1, &tagmatrix);
+ if (ret)
+ return ret;
+ }
+ else
+ Matrix4x4_CreateIdentity(&tagmatrix);
+
+ if ((val = PRVM_GETEDICTFIELDVALUE(ent, eval_tag_entity)) && val->edict)
+ { // DP_GFX_QUAKE3MODELTAGS, scan all chain and stop on unattached entity
+ attachloop = 0;
+ do
+ {
+ attachent = PRVM_EDICT_NUM(val->edict); // to this it entity our entity is attached
+ val = PRVM_GETEDICTFIELDVALUE(ent, eval_tag_index);
+ if (val->_float >= 1 && attachent->fields.server->modelindex >= 1 && attachent->fields.server->modelindex < MAX_MODELS && (model = sv.models[(int)attachent->fields.server->modelindex]) && model->animscenes && attachent->fields.server->frame >= 0 && attachent->fields.server->frame < model->numframes)
+ Mod_Alias_GetTagMatrix(model, model->animscenes[(int)attachent->fields.server->frame].firstframe, val->_float - 1, &attachmatrix);
+ else
+ Matrix4x4_CreateIdentity(&attachmatrix);
+
+ // apply transformation by child entity matrix
+ val = PRVM_GETEDICTFIELDVALUE(ent, eval_scale);
+ if (val->_float == 0)
+ val->_float = 1;
+ Matrix4x4_CreateFromQuakeEntity(&entitymatrix, ent->fields.server->origin[0], ent->fields.server->origin[1], ent->fields.server->origin[2], -ent->fields.server->angles[0], ent->fields.server->angles[1], ent->fields.server->angles[2], val->_float);
+ Matrix4x4_Concat(out, &entitymatrix, &tagmatrix);
+ out->m[0][3] = entitymatrix.m[0][3] + val->_float*(entitymatrix.m[0][0]*tagmatrix.m[0][3] + entitymatrix.m[0][1]*tagmatrix.m[1][3] + entitymatrix.m[0][2]*tagmatrix.m[2][3]);
+ out->m[1][3] = entitymatrix.m[1][3] + val->_float*(entitymatrix.m[1][0]*tagmatrix.m[0][3] + entitymatrix.m[1][1]*tagmatrix.m[1][3] + entitymatrix.m[1][2]*tagmatrix.m[2][3]);
+ out->m[2][3] = entitymatrix.m[2][3] + val->_float*(entitymatrix.m[2][0]*tagmatrix.m[0][3] + entitymatrix.m[2][1]*tagmatrix.m[1][3] + entitymatrix.m[2][2]*tagmatrix.m[2][3]);
+ Matrix4x4_Copy(&tagmatrix, out);
+
+ // finally transformate by matrix of tag on parent entity
+ Matrix4x4_Concat(out, &attachmatrix, &tagmatrix);
+ out->m[0][3] = attachmatrix.m[0][3] + attachmatrix.m[0][0]*tagmatrix.m[0][3] + attachmatrix.m[0][1]*tagmatrix.m[1][3] + attachmatrix.m[0][2]*tagmatrix.m[2][3];
+ out->m[1][3] = attachmatrix.m[1][3] + attachmatrix.m[1][0]*tagmatrix.m[0][3] + attachmatrix.m[1][1]*tagmatrix.m[1][3] + attachmatrix.m[1][2]*tagmatrix.m[2][3];
+ out->m[2][3] = attachmatrix.m[2][3] + attachmatrix.m[2][0]*tagmatrix.m[0][3] + attachmatrix.m[2][1]*tagmatrix.m[1][3] + attachmatrix.m[2][2]*tagmatrix.m[2][3];
+ Matrix4x4_Copy(&tagmatrix, out);
+
+ ent = attachent;
+ attachloop += 1;
+ if (attachloop > 255) // prevent runaway looping
+ return 5;
+ }
+ while ((val = PRVM_GETEDICTFIELDVALUE(ent, eval_tag_entity)) && val->edict);
+ }
+
+ // normal or RENDER_VIEWMODEL entity (or main parent entity on attach chain)
+ val = PRVM_GETEDICTFIELDVALUE(ent, eval_scale);
+ if (val->_float == 0)
+ val->_float = 1;
+ // Alias models have inverse pitch, bmodels can't have tags, so don't check for modeltype...
+ Matrix4x4_CreateFromQuakeEntity(&entitymatrix, ent->fields.server->origin[0], ent->fields.server->origin[1], ent->fields.server->origin[2], -ent->fields.server->angles[0], ent->fields.server->angles[1], ent->fields.server->angles[2], val->_float);
+ Matrix4x4_Concat(out, &entitymatrix, &tagmatrix);
+ out->m[0][3] = entitymatrix.m[0][3] + val->_float*(entitymatrix.m[0][0]*tagmatrix.m[0][3] + entitymatrix.m[0][1]*tagmatrix.m[1][3] + entitymatrix.m[0][2]*tagmatrix.m[2][3]);
+ out->m[1][3] = entitymatrix.m[1][3] + val->_float*(entitymatrix.m[1][0]*tagmatrix.m[0][3] + entitymatrix.m[1][1]*tagmatrix.m[1][3] + entitymatrix.m[1][2]*tagmatrix.m[2][3]);
+ out->m[2][3] = entitymatrix.m[2][3] + val->_float*(entitymatrix.m[2][0]*tagmatrix.m[0][3] + entitymatrix.m[2][1]*tagmatrix.m[1][3] + entitymatrix.m[2][2]*tagmatrix.m[2][3]);
+
+ if ((val = PRVM_GETEDICTFIELDVALUE(ent, eval_viewmodelforclient)) && val->edict)
+ {// RENDER_VIEWMODEL magic
+ Matrix4x4_Copy(&tagmatrix, out);
+ ent = PRVM_EDICT_NUM(val->edict);
+
+ val = PRVM_GETEDICTFIELDVALUE(ent, eval_scale);
+ if (val->_float == 0)
+ val->_float = 1;
+
+ Matrix4x4_CreateFromQuakeEntity(&entitymatrix, ent->fields.server->origin[0], ent->fields.server->origin[1], ent->fields.server->origin[2] + ent->fields.server->view_ofs[2], ent->fields.server->v_angle[0], ent->fields.server->v_angle[1], ent->fields.server->v_angle[2], val->_float);
+ Matrix4x4_Concat(out, &entitymatrix, &tagmatrix);
+ out->m[0][3] = entitymatrix.m[0][3] + val->_float*(entitymatrix.m[0][0]*tagmatrix.m[0][3] + entitymatrix.m[0][1]*tagmatrix.m[1][3] + entitymatrix.m[0][2]*tagmatrix.m[2][3]);
+ out->m[1][3] = entitymatrix.m[1][3] + val->_float*(entitymatrix.m[1][0]*tagmatrix.m[0][3] + entitymatrix.m[1][1]*tagmatrix.m[1][3] + entitymatrix.m[1][2]*tagmatrix.m[2][3]);
+ out->m[2][3] = entitymatrix.m[2][3] + val->_float*(entitymatrix.m[2][0]*tagmatrix.m[0][3] + entitymatrix.m[2][1]*tagmatrix.m[1][3] + entitymatrix.m[2][2]*tagmatrix.m[2][3]);
+
+ /*
+ // Cl_bob, ported from rendering code
+ if (ent->fields.server->health > 0 && cl_bob.value && cl_bobcycle.value)
+ {
+ double bob, cycle;
+ // LordHavoc: this code is *weird*, but not replacable (I think it
+ // should be done in QC on the server, but oh well, quake is quake)
+ // LordHavoc: figured out bobup: the time at which the sin is at 180
+ // degrees (which allows lengthening or squishing the peak or valley)
+ cycle = sv.time/cl_bobcycle.value;
+ cycle -= (int)cycle;
+ if (cycle < cl_bobup.value)
+ cycle = sin(M_PI * cycle / cl_bobup.value);
+ else
+ cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value));
+ // bob is proportional to velocity in the xy plane
+ // (don't count Z, or jumping messes it up)
+ bob = sqrt(ent->fields.server->velocity[0]*ent->fields.server->velocity[0] + ent->fields.server->velocity[1]*ent->fields.server->velocity[1])*cl_bob.value;
+ bob = bob*0.3 + bob*0.7*cycle;
+ out->m[2][3] += bound(-7, bob, 4);
+ }
+ */
+ }
+ return 0;
+}
+
+//float(entity ent, string tagname) gettagindex;
+
+void PF_gettagindex (void)
+{
+ prvm_edict_t *ent = PRVM_G_EDICT(OFS_PARM0);
+ const char *tag_name = PRVM_G_STRING(OFS_PARM1);
+ int modelindex, tag_index;
+
+ if (ent == prog->edicts)
+ PF_WARNING("gettagindex: can't affect world entity\n");
+ if (ent->priv.server->free)
+ PF_WARNING("gettagindex: can't affect free entity\n");
+
+ modelindex = (int)ent->fields.server->modelindex;
+ tag_index = 0;
+ if (modelindex <= 0 || modelindex > MAX_MODELS)
+ Con_DPrintf("gettagindex(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(ent));
+ else
+ {
+ tag_index = SV_GetTagIndex(ent, tag_name);
+ if (tag_index == 0)
+ Con_DPrintf("gettagindex(entity #%i): tag \"%s\" not found\n", PRVM_NUM_FOR_EDICT(ent), tag_name);
+ }
+ PRVM_G_FLOAT(OFS_RETURN) = tag_index;
+};
+
+//vector(entity ent, float tagindex) gettaginfo;
+void PF_gettaginfo (void)
+{
+ prvm_edict_t *e = PRVM_G_EDICT(OFS_PARM0);
+ int tagindex = (int)PRVM_G_FLOAT(OFS_PARM1);
+ matrix4x4_t tag_matrix;
+ int returncode;
+
+ returncode = SV_GetTagMatrix(&tag_matrix, e, tagindex);
+ Matrix4x4_ToVectors(&tag_matrix, prog->globals.server->v_forward, prog->globals.server->v_right, prog->globals.server->v_up, PRVM_G_VECTOR(OFS_RETURN));
+
+ switch(returncode)
+ {
+ case 1:
+ PF_WARNING("gettagindex: can't affect world entity\n");
+ break;
+ case 2:
+ PF_WARNING("gettagindex: can't affect free entity\n");
+ break;
+ case 3:
+ Con_DPrintf("SV_GetTagMatrix(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(e));
+ break;
+ case 4:
+ Con_DPrintf("SV_GetTagMatrix(entity #%i): model has no tag with requested index %i\n", PRVM_NUM_FOR_EDICT(e), tagindex);
+ break;
+ case 5:
+ Con_DPrintf("SV_GetTagMatrix(entity #%i): runaway loop at attachment chain\n", PRVM_NUM_FOR_EDICT(e));
+ break;
+ }
+}
+
+
+/////////////////////////////////////////
+// DP_QC_FS_SEARCH extension
+
+// qc fs search handling
+#define MAX_SEARCHES 128
+
+fssearch_t *pr_fssearchlist[MAX_SEARCHES];
+
+void PR_Search_Init(void)
+{
+ memset(pr_fssearchlist,0,sizeof(pr_fssearchlist));
+}
+
+void PR_Search_Reset(void)
+{
+ int i;
+ // reset the fssearch list
+ for(i = 0; i < MAX_SEARCHES; i++)
+ if(pr_fssearchlist[i])
+ FS_FreeSearch(pr_fssearchlist[i]);
+ memset(pr_fssearchlist,0,sizeof(pr_fssearchlist));
+}
+
+/*
+=========
+PF_search_begin
+
+float search_begin(string pattern, float caseinsensitive, float quiet)
+=========
+*/
+void PF_search_begin(void)
+{
+ int handle;
+ const char *pattern;
+ int caseinsens, quiet;
+
+ pattern = PRVM_G_STRING(OFS_PARM0);
+ if (!pattern || pattern[0] <= ' ')
+ PF_ERROR("PF_search_begin: Bad string");
+
+ caseinsens = PRVM_G_FLOAT(OFS_PARM1);
+ quiet = PRVM_G_FLOAT(OFS_PARM2);
+
+ for(handle = 0; handle < MAX_SEARCHES; handle++)
+ if(!pr_fssearchlist[handle])
+ break;
+
+ if(handle >= MAX_SEARCHES)
+ {
+ Con_Printf("PR_search_begin: ran out of search handles (%i)\n", MAX_SEARCHES);
+ PRVM_G_FLOAT(OFS_RETURN) = -2;
+ return;
+ }
+
+ if(!(pr_fssearchlist[handle] = FS_Search(pattern,caseinsens, quiet)))
+ PRVM_G_FLOAT(OFS_RETURN) = -1;
+ else
+ PRVM_G_FLOAT(OFS_RETURN) = handle;
+}
+
+/*
+=========
+VM_search_end
+
+void search_end(float handle)
+=========
+*/
+void PF_search_end(void)
+{
+ int handle;
+
+ handle = PRVM_G_FLOAT(OFS_PARM0);
+
+ if(handle < 0 || handle >= MAX_SEARCHES)
+ {
+ Con_Printf("PF_search_end: invalid handle %i\n", handle);
+ return;
+ }
+ if(pr_fssearchlist[handle] == NULL)
+ {
+ Con_Printf("PF_search_end: no such handle %i\n", handle);
+ return;
+ }
+
+ FS_FreeSearch(pr_fssearchlist[handle]);
+ pr_fssearchlist[handle] = NULL;
+}
+
+/*
+=========
+VM_search_getsize
+
+float search_getsize(float handle)
+=========
+*/
+void PF_search_getsize(void)
+{
+ int handle;
+
+ handle = PRVM_G_FLOAT(OFS_PARM0);
+
+ if(handle < 0 || handle >= MAX_SEARCHES)
+ {
+ Con_Printf("PF_search_getsize: invalid handle %i\n", handle);
+ return;
+ }
+ if(pr_fssearchlist[handle] == NULL)
+ {
+ Con_Printf("PF_search_getsize: no such handle %i\n", handle);
+ return;
+ }
+
+ PRVM_G_FLOAT(OFS_RETURN) = pr_fssearchlist[handle]->numfilenames;
+}
+
+/*
+=========
+VM_search_getfilename
+
+string search_getfilename(float handle, float num)
+=========
+*/
+void PF_search_getfilename(void)
+{
+ int handle, filenum;
+ char *tmp;
+
+ handle = PRVM_G_FLOAT(OFS_PARM0);
+ filenum = PRVM_G_FLOAT(OFS_PARM1);
+
+ if(handle < 0 || handle >= MAX_SEARCHES)
+ {
+ Con_Printf("PF_search_getfilename: invalid handle %i\n", handle);
+ return;
+ }
+ if(pr_fssearchlist[handle] == NULL)
+ {
+ Con_Printf("PF_search_getfilename: no such handle %i\n", handle);
+ return;
+ }
+ if(filenum < 0 || filenum >= pr_fssearchlist[handle]->numfilenames)
+ {
+ Con_Printf("PF_search_getfilename: invalid filenum %i\n", filenum);
+ return;
+ }
+
+ tmp = PR_GetTempString();
+ strcpy(tmp, pr_fssearchlist[handle]->filenames[filenum]);
+
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetEngineString(tmp);
+}
+
+void PF_cvar_string (void)
+{
+ const char *str;
+ cvar_t *var;
+ char *tmp;
+
+ str = PRVM_G_STRING(OFS_PARM0);
+ var = Cvar_FindVar (str);
+ if (var)
+ {
+ tmp = PR_GetTempString();
+ strcpy(tmp, var->string);