+// #510 string(string input, ...) uri_escape (DP_QC_URI_ESCAPE)
+// does URI escaping on a string (replace evil stuff by %AB escapes)
+void VM_uri_escape (void)
+{
+ char src[VM_STRINGTEMP_LENGTH];
+ char dest[VM_STRINGTEMP_LENGTH];
+ char *p, *q;
+ static const char *hex = "0123456789ABCDEF";
+
+ VM_SAFEPARMCOUNTRANGE(1, 8, VM_uri_escape);
+ VM_VarString(0, src, sizeof(src));
+
+ for(p = src, q = dest; *p && q < dest + sizeof(dest) - 3; ++p)
+ {
+ if((*p >= 'A' && *p <= 'Z')
+ || (*p >= 'a' && *p <= 'z')
+ || (*p >= '0' && *p <= '9')
+ || (*p == '-') || (*p == '_') || (*p == '.')
+ || (*p == '!') || (*p == '~')
+ || (*p == '\'') || (*p == '(') || (*p == ')'))
+ *q++ = *p;
+ else
+ {
+ *q++ = '%';
+ *q++ = hex[(*(unsigned char *)p >> 4) & 0xF];
+ *q++ = hex[ *(unsigned char *)p & 0xF];
+ }
+ }
+ *q++ = 0;
+
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(dest);
+}
+
+// #510 string(string input, ...) uri_unescape (DP_QC_URI_ESCAPE)
+// does URI unescaping on a string (get back the evil stuff)
+void VM_uri_unescape (void)
+{
+ char src[VM_STRINGTEMP_LENGTH];
+ char dest[VM_STRINGTEMP_LENGTH];
+ char *p, *q;
+ int hi, lo;
+
+ VM_SAFEPARMCOUNTRANGE(1, 8, VM_uri_unescape);
+ VM_VarString(0, src, sizeof(src));
+
+ for(p = src, q = dest; *p; ) // no need to check size, because unescape can't expand
+ {
+ if(*p == '%')
+ {
+ if(p[1] >= '0' && p[1] <= '9')
+ hi = p[1] - '0';
+ else if(p[1] >= 'a' && p[1] <= 'f')
+ hi = p[1] - 'a' + 10;
+ else if(p[1] >= 'A' && p[1] <= 'F')
+ hi = p[1] - 'A' + 10;
+ else
+ goto nohex;
+ if(p[2] >= '0' && p[2] <= '9')
+ lo = p[2] - '0';
+ else if(p[2] >= 'a' && p[2] <= 'f')
+ lo = p[2] - 'a' + 10;
+ else if(p[2] >= 'A' && p[2] <= 'F')
+ lo = p[2] - 'A' + 10;
+ else
+ goto nohex;
+ if(hi != 0 || lo != 0) // don't unescape NUL bytes
+ *q++ = (char) (hi * 0x10 + lo);
+ p += 3;
+ continue;
+ }
+
+nohex:
+ // otherwise:
+ *q++ = *p++;
+ }
+ *q++ = 0;
+
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(dest);
+}
+
+// #502 string(string filename) whichpack (DP_QC_WHICHPACK)
+// returns the name of the pack containing a file, or "" if it is not in any pack (but local or non-existant)
+void VM_whichpack (void)
+{
+ const char *fn, *pack;
+
+ VM_SAFEPARMCOUNT(1, VM_whichpack);
+ fn = PRVM_G_STRING(OFS_PARM0);
+ pack = FS_WhichPack(fn);
+
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(pack ? pack : "");
+}
+
+typedef struct
+{
+ int prognr;
+ double starttime;
+ float id;
+ char buffer[MAX_INPUTLINE];
+ unsigned char *postdata; // free when uri_to_prog_t is freed
+ size_t postlen;
+ char *sigdata; // free when uri_to_prog_t is freed
+ size_t siglen;
+}
+uri_to_prog_t;
+
+static void uri_to_string_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata)
+{
+ uri_to_prog_t *handle = (uri_to_prog_t *) cbdata;
+
+ if(!PRVM_ProgLoaded(handle->prognr))
+ {
+ // curl reply came too late... so just drop it
+ if(handle->postdata)
+ Z_Free(handle->postdata);
+ if(handle->sigdata)
+ Z_Free(handle->sigdata);
+ Z_Free(handle);
+ return;
+ }
+
+ PRVM_SetProg(handle->prognr);
+ PRVM_Begin;
+ if((prog->starttime == handle->starttime) && (PRVM_allfunction(URI_Get_Callback)))
+ {
+ if(length_received >= sizeof(handle->buffer))
+ length_received = sizeof(handle->buffer) - 1;
+ handle->buffer[length_received] = 0;
+
+ PRVM_G_FLOAT(OFS_PARM0) = handle->id;
+ PRVM_G_FLOAT(OFS_PARM1) = status;
+ PRVM_G_INT(OFS_PARM2) = PRVM_SetTempString(handle->buffer);
+ PRVM_ExecuteProgram(PRVM_allfunction(URI_Get_Callback), "QC function URI_Get_Callback is missing");
+ }
+ PRVM_End;
+
+ if(handle->postdata)
+ Z_Free(handle->postdata);
+ if(handle->sigdata)
+ Z_Free(handle->sigdata);
+ Z_Free(handle);
+}
+
+// uri_get() gets content from an URL and calls a callback "uri_get_callback" with it set as string; an unique ID of the transfer is returned
+// returns 1 on success, and then calls the callback with the ID, 0 or the HTTP status code, and the received data in a string
+void VM_uri_get (void)
+{
+ const char *url;
+ float id;
+ qboolean ret;
+ uri_to_prog_t *handle;
+ const char *posttype = NULL;
+ const char *postseparator = NULL;
+ int poststringbuffer = -1;
+ int postkeyid = -1;
+ const char *query_string = NULL;
+ size_t lq;
+
+ if(!PRVM_allfunction(URI_Get_Callback))
+ PRVM_ERROR("uri_get called by %s without URI_Get_Callback defined", PRVM_NAME);
+
+ VM_SAFEPARMCOUNTRANGE(2, 6, VM_uri_get);
+
+ url = PRVM_G_STRING(OFS_PARM0);
+ id = PRVM_G_FLOAT(OFS_PARM1);
+ if(prog->argc >= 3)
+ posttype = PRVM_G_STRING(OFS_PARM2);
+ if(prog->argc >= 4)
+ postseparator = PRVM_G_STRING(OFS_PARM3);
+ if(prog->argc >= 5)
+ poststringbuffer = PRVM_G_FLOAT(OFS_PARM4);
+ if(prog->argc >= 6)
+ postkeyid = PRVM_G_FLOAT(OFS_PARM5);
+ handle = (uri_to_prog_t *) Z_Malloc(sizeof(*handle)); // this can't be the prog's mem pool, as curl may call the callback later!
+
+ query_string = strchr(url, '?');
+ if(query_string)
+ ++query_string;
+ lq = query_string ? strlen(query_string) : 0;
+
+ handle->prognr = PRVM_GetProgNr();
+ handle->starttime = prog->starttime;
+ handle->id = id;
+ if(postseparator)
+ {
+ size_t l = strlen(postseparator);
+ if(poststringbuffer >= 0)
+ {
+ size_t ltotal;
+ int i;
+ // "implode"
+ prvm_stringbuffer_t *stringbuffer;
+ stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, poststringbuffer);
+ if(!stringbuffer)
+ {
+ VM_Warning("uri_get: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), PRVM_NAME);
+ return;
+ }
+ ltotal = 0;
+ for(i = 0;i < stringbuffer->num_strings;i++)
+ {
+ if(i > 0)
+ ltotal += l;
+ if(stringbuffer->strings[i])
+ ltotal += strlen(stringbuffer->strings[i]);
+ }
+ handle->postdata = (unsigned char *)Z_Malloc(ltotal + 1 + lq);
+ handle->postlen = ltotal;
+ ltotal = 0;
+ for(i = 0;i < stringbuffer->num_strings;i++)
+ {
+ if(i > 0)
+ {
+ memcpy(handle->postdata + ltotal, postseparator, l);
+ ltotal += l;
+ }
+ if(stringbuffer->strings[i])
+ {
+ memcpy(handle->postdata + ltotal, stringbuffer->strings[i], strlen(stringbuffer->strings[i]));
+ ltotal += strlen(stringbuffer->strings[i]);
+ }
+ }
+ if(ltotal != handle->postlen)
+ PRVM_ERROR ("%s: string buffer content size mismatch, possible overrun", PRVM_NAME);
+ }
+ else
+ {
+ handle->postdata = (unsigned char *)Z_Malloc(l + 1 + lq);
+ handle->postlen = l;
+ memcpy(handle->postdata, postseparator, l);
+ }
+ handle->postdata[handle->postlen] = 0;
+ if(query_string)
+ memcpy(handle->postdata + handle->postlen + 1, query_string, lq);
+ if(postkeyid >= 0)
+ {
+ // POST: we sign postdata \0 query string
+ size_t ll;
+ handle->sigdata = (char *)Z_Malloc(8192);
+ strlcpy(handle->sigdata, "X-D0-Blind-ID-Detached-Signature: ", 8192);
+ l = strlen(handle->sigdata);
+ handle->siglen = Crypto_SignDataDetached(handle->postdata, handle->postlen + 1 + lq, postkeyid, handle->sigdata + l, 8192 - l);
+ if(!handle->siglen)
+ {
+ Z_Free(handle->sigdata);
+ handle->sigdata = NULL;
+ goto out1;
+ }
+ ll = base64_encode((unsigned char *) (handle->sigdata + l), handle->siglen, 8192 - l - 1);
+ if(!ll)
+ {
+ Z_Free(handle->sigdata);
+ handle->sigdata = NULL;
+ goto out1;
+ }
+ handle->siglen = l + ll;
+ handle->sigdata[handle->siglen] = 0;
+ }
+out1:
+ ret = Curl_Begin_ToMemory_POST(url, handle->sigdata, 0, posttype, handle->postdata, handle->postlen, (unsigned char *) handle->buffer, sizeof(handle->buffer), uri_to_string_callback, handle);
+ }
+ else
+ {
+ if(postkeyid >= 0 && query_string)
+ {
+ // GET: we sign JUST the query string
+ size_t l, ll;
+ handle->sigdata = (char *)Z_Malloc(8192);
+ strlcpy(handle->sigdata, "X-D0-Blind-ID-Detached-Signature: ", 8192);
+ l = strlen(handle->sigdata);
+ handle->siglen = Crypto_SignDataDetached(query_string, lq, postkeyid, handle->sigdata + l, 8192 - l);
+ if(!handle->siglen)
+ {
+ Z_Free(handle->sigdata);
+ handle->sigdata = NULL;
+ goto out2;
+ }
+ ll = base64_encode((unsigned char *) (handle->sigdata + l), handle->siglen, 8192 - l - 1);
+ if(!ll)
+ {
+ Z_Free(handle->sigdata);
+ handle->sigdata = NULL;
+ goto out2;
+ }
+ handle->siglen = l + ll;
+ handle->sigdata[handle->siglen] = 0;
+ }
+out2:
+ handle->postdata = NULL;
+ handle->postlen = 0;
+ ret = Curl_Begin_ToMemory(url, 0, (unsigned char *) handle->buffer, sizeof(handle->buffer), uri_to_string_callback, handle);
+ }
+ if(ret)
+ {
+ PRVM_G_INT(OFS_RETURN) = 1;
+ }
+ else
+ {
+ if(handle->postdata)
+ Z_Free(handle->postdata);
+ if(handle->sigdata)
+ Z_Free(handle->sigdata);
+ Z_Free(handle);
+ PRVM_G_INT(OFS_RETURN) = 0;
+ }
+}
+
+void VM_netaddress_resolve (void)
+{
+ const char *ip;
+ char normalized[128];
+ int port;
+ lhnetaddress_t addr;
+
+ VM_SAFEPARMCOUNTRANGE(1, 2, VM_netaddress_resolve);
+
+ ip = PRVM_G_STRING(OFS_PARM0);
+ port = 0;
+ if(prog->argc > 1)
+ port = (int) PRVM_G_FLOAT(OFS_PARM1);
+
+ if(LHNETADDRESS_FromString(&addr, ip, port) && LHNETADDRESS_ToString(&addr, normalized, sizeof(normalized), prog->argc > 1))
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(normalized);
+ else
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString("");
+}
+
+//string(void) getextresponse = #624; // returns the next extResponse packet that was sent to this client
+void VM_CL_getextresponse (void)
+{
+ VM_SAFEPARMCOUNT(0,VM_argv);
+
+ if (cl_net_extresponse_count <= 0)
+ PRVM_G_INT(OFS_RETURN) = OFS_NULL;
+ else
+ {
+ int first;
+ --cl_net_extresponse_count;
+ first = (cl_net_extresponse_last + NET_EXTRESPONSE_MAX - cl_net_extresponse_count) % NET_EXTRESPONSE_MAX;
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(cl_net_extresponse[first]);
+ }
+}
+
+void VM_SV_getextresponse (void)
+{
+ VM_SAFEPARMCOUNT(0,VM_argv);
+
+ if (sv_net_extresponse_count <= 0)
+ PRVM_G_INT(OFS_RETURN) = OFS_NULL;
+ else
+ {
+ int first;
+ --sv_net_extresponse_count;
+ first = (sv_net_extresponse_last + NET_EXTRESPONSE_MAX - sv_net_extresponse_count) % NET_EXTRESPONSE_MAX;
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(sv_net_extresponse[first]);
+ }
+}
+
+/*
+=========
+Common functions between menu.dat and clsprogs
+=========
+*/
+
+//#349 float() isdemo
+void VM_CL_isdemo (void)
+{
+ VM_SAFEPARMCOUNT(0, VM_CL_isdemo);
+ PRVM_G_FLOAT(OFS_RETURN) = cls.demoplayback;
+}
+
+//#355 float() videoplaying
+void VM_CL_videoplaying (void)
+{
+ VM_SAFEPARMCOUNT(0, VM_CL_videoplaying);
+ PRVM_G_FLOAT(OFS_RETURN) = cl_videoplaying;
+}
+
+/*
+=========
+VM_M_callfunction
+
+ callfunction(...,string function_name)
+Extension: pass
+=========
+*/
+mfunction_t *PRVM_ED_FindFunction (const char *name);
+void VM_callfunction(void)
+{
+ mfunction_t *func;
+ const char *s;
+
+ VM_SAFEPARMCOUNTRANGE(1, 8, VM_callfunction);
+
+ s = PRVM_G_STRING(OFS_PARM0+(prog->argc - 1)*3);
+
+ VM_CheckEmptyString(s);
+
+ func = PRVM_ED_FindFunction(s);
+
+ if(!func)
+ PRVM_ERROR("VM_callfunciton: function %s not found !", s);
+ else if (func->first_statement < 0)
+ {
+ // negative statements are built in functions
+ int builtinnumber = -func->first_statement;
+ prog->xfunction->builtinsprofile++;
+ if (builtinnumber < prog->numbuiltins && prog->builtins[builtinnumber])
+ prog->builtins[builtinnumber]();
+ else
+ PRVM_ERROR("No such builtin #%i in %s; most likely cause: outdated engine build. Try updating!", builtinnumber, PRVM_NAME);
+ }
+ else if(func - prog->functions > 0)
+ {
+ prog->argc--;
+ PRVM_ExecuteProgram(func - prog->functions,"");
+ prog->argc++;
+ }
+}
+
+/*
+=========
+VM_isfunction
+
+float isfunction(string function_name)
+=========
+*/
+mfunction_t *PRVM_ED_FindFunction (const char *name);
+void VM_isfunction(void)
+{
+ mfunction_t *func;
+ const char *s;
+
+ VM_SAFEPARMCOUNT(1, VM_isfunction);
+
+ s = PRVM_G_STRING(OFS_PARM0);
+
+ VM_CheckEmptyString(s);
+
+ func = PRVM_ED_FindFunction(s);
+
+ if(!func)
+ PRVM_G_FLOAT(OFS_RETURN) = false;
+ else
+ PRVM_G_FLOAT(OFS_RETURN) = true;
+}
+
+/*
+=========
+VM_sprintf
+
+string sprintf(string format, ...)
+=========
+*/
+
+void VM_sprintf(void)
+{
+ const char *s, *s0;
+ char outbuf[MAX_INPUTLINE];
+ char *o = outbuf, *end = outbuf + sizeof(outbuf), *err;
+ int argpos = 1;
+ int width, precision, thisarg, flags;
+ char formatbuf[16];
+ char *f;
+ int isfloat;
+ static int dummyivec[3] = {0, 0, 0};
+ static float dummyvec[3] = {0, 0, 0};
+
+#define PRINTF_ALTERNATE 1
+#define PRINTF_ZEROPAD 2
+#define PRINTF_LEFT 4
+#define PRINTF_SPACEPOSITIVE 8
+#define PRINTF_SIGNPOSITIVE 16
+
+ formatbuf[0] = '%';
+
+ s = PRVM_G_STRING(OFS_PARM0);
+
+#define GETARG_FLOAT(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_FLOAT(OFS_PARM0 + 3 * (a))) : 0)
+#define GETARG_VECTOR(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyvec)
+#define GETARG_INT(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_INT(OFS_PARM0 + 3 * (a))) : 0)
+#define GETARG_INTVECTOR(a) (((a)>=1 && (a)<prog->argc) ? ((int*) PRVM_G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyivec)
+#define GETARG_STRING(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_STRING(OFS_PARM0 + 3 * (a))) : "")
+
+ for(;;)
+ {
+ s0 = s;
+ switch(*s)
+ {
+ case 0:
+ goto finished;
+ case '%':
+ ++s;
+
+ if(*s == '%')
+ goto verbatim;
+
+ // complete directive format:
+ // %3$*1$.*2$ld
+
+ width = -1;
+ precision = -1;
+ thisarg = -1;
+ flags = 0;
+ isfloat = -1;
+
+ // is number following?
+ if(*s >= '0' && *s <= '9')
+ {
+ width = strtol(s, &err, 10);
+ if(!err)
+ {
+ VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+ goto finished;
+ }
+ if(*err == '$')
+ {
+ thisarg = width;
+ width = -1;
+ s = err + 1;
+ }
+ else
+ {
+ if(*s == '0')
+ {
+ flags |= PRINTF_ZEROPAD;
+ if(width == 0)
+ width = -1; // it was just a flag
+ }
+ s = err;
+ }
+ }
+
+ if(width < 0)
+ {
+ for(;;)
+ {
+ switch(*s)
+ {
+ case '#': flags |= PRINTF_ALTERNATE; break;
+ case '0': flags |= PRINTF_ZEROPAD; break;
+ case '-': flags |= PRINTF_LEFT; break;
+ case ' ': flags |= PRINTF_SPACEPOSITIVE; break;
+ case '+': flags |= PRINTF_SIGNPOSITIVE; break;
+ default:
+ goto noflags;
+ }
+ ++s;
+ }
+noflags:
+ if(*s == '*')
+ {
+ ++s;
+ if(*s >= '0' && *s <= '9')
+ {
+ width = strtol(s, &err, 10);
+ if(!err || *err != '$')
+ {
+ VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+ goto finished;
+ }
+ s = err + 1;
+ }
+ else
+ width = argpos++;
+ width = GETARG_FLOAT(width);
+ if(width < 0)
+ {
+ flags |= PRINTF_LEFT;
+ width = -width;
+ }
+ }
+ else if(*s >= '0' && *s <= '9')
+ {
+ width = strtol(s, &err, 10);
+ if(!err)
+ {
+ VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+ goto finished;
+ }
+ s = err;
+ if(width < 0)
+ {
+ flags |= PRINTF_LEFT;
+ width = -width;
+ }
+ }
+ // otherwise width stays -1
+ }
+
+ if(*s == '.')
+ {
+ ++s;
+ if(*s == '*')
+ {
+ ++s;
+ if(*s >= '0' && *s <= '9')
+ {
+ precision = strtol(s, &err, 10);
+ if(!err || *err != '$')
+ {
+ VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+ goto finished;
+ }
+ s = err + 1;
+ }
+ else
+ precision = argpos++;
+ precision = GETARG_FLOAT(precision);
+ }
+ else if(*s >= '0' && *s <= '9')
+ {
+ precision = strtol(s, &err, 10);
+ if(!err)
+ {
+ VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+ goto finished;
+ }
+ s = err;
+ }
+ else
+ {
+ VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+ goto finished;
+ }
+ }
+
+ for(;;)
+ {
+ switch(*s)
+ {
+ case 'h': isfloat = 1; break;
+ case 'l': isfloat = 0; break;
+ case 'L': isfloat = 0; break;
+ case 'j': break;
+ case 'z': break;
+ case 't': break;
+ default:
+ goto nolength;
+ }
+ ++s;
+ }
+nolength:
+
+ // now s points to the final directive char and is no longer changed
+ if(isfloat < 0)
+ {
+ if(*s == 'i')
+ isfloat = 0;
+ else
+ isfloat = 1;
+ }
+
+ if(thisarg < 0)
+ thisarg = argpos++;
+
+ if(o < end - 1)
+ {
+ f = &formatbuf[1];
+ if(*s != 's' && *s != 'c')
+ if(flags & PRINTF_ALTERNATE) *f++ = '#';
+ if(flags & PRINTF_ZEROPAD) *f++ = '0';
+ if(flags & PRINTF_LEFT) *f++ = '-';
+ if(flags & PRINTF_SPACEPOSITIVE) *f++ = ' ';
+ if(flags & PRINTF_SIGNPOSITIVE) *f++ = '+';
+ *f++ = '*';
+ if(precision >= 0)
+ {
+ *f++ = '.';
+ *f++ = '*';
+ }
+ *f++ = *s;
+ *f++ = 0;
+
+ if(width < 0) // not set
+ width = 0;
+
+ switch(*s)
+ {
+ case 'd': case 'i':
+ if(precision < 0) // not set
+ o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? (int) GETARG_FLOAT(thisarg) : (int) GETARG_INT(thisarg)));
+ else
+ o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (int) GETARG_FLOAT(thisarg) : (int) GETARG_INT(thisarg)));
+ break;
+ case 'o': case 'u': case 'x': case 'X':
+ if(precision < 0) // not set
+ o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)));
+ else
+ o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)));
+ break;
+ case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
+ if(precision < 0) // not set
+ o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg)));
+ else
+ o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg)));
+ break;
+ case 'v': case 'V':
+ f[-2] += 'g' - 'v';
+ if(precision < 0) // not set
+ o += dpsnprintf(o, end - o, va("%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf),
+ width, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]),
+ width, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]),
+ width, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2])
+ );
+ else
+ o += dpsnprintf(o, end - o, va("%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf),
+ width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]),
+ width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]),
+ width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2])
+ );
+ break;
+ case 'c':
+ if(flags & PRINTF_ALTERNATE)
+ {
+ if(precision < 0) // not set
+ o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)));
+ else
+ o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)));
+ }
+ else
+ {
+ unsigned int c = (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg));
+ const char *buf = u8_encodech(c, NULL);
+ if(!buf)
+ buf = "";
+ if(precision < 0) // not set
+ precision = end - o - 1;
+ o += u8_strpad(o, end - o, buf, (flags & PRINTF_LEFT) != 0, width, precision);
+ }
+ break;
+ case 's':
+ if(flags & PRINTF_ALTERNATE)
+ {
+ if(precision < 0) // not set
+ o += dpsnprintf(o, end - o, formatbuf, width, GETARG_STRING(thisarg));
+ else
+ o += dpsnprintf(o, end - o, formatbuf, width, precision, GETARG_STRING(thisarg));
+ }
+ else
+ {
+ if(precision < 0) // not set
+ precision = end - o - 1;
+ o += u8_strpad(o, end - o, GETARG_STRING(thisarg), (flags & PRINTF_LEFT) != 0, width, precision);
+ }
+ break;
+ default:
+ VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+ goto finished;
+ }
+ }
+ ++s;
+ break;
+ default:
+verbatim:
+ if(o < end - 1)
+ *o++ = *s++;
+ break;
+ }
+ }
+finished:
+ *o = 0;
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(outbuf);
+}
+
+
+// surface querying
+
+static dp_model_t *getmodel(prvm_edict_t *ed)
+{
+ switch(PRVM_GetProgNr())
+ {
+ case PRVM_SERVERPROG:
+ return SV_GetModelFromEdict(ed);
+ case PRVM_CLIENTPROG:
+ return CL_GetModelFromEdict(ed);
+ default:
+ return NULL;
+ }
+}
+
+typedef struct
+{
+ unsigned int progid;
+ dp_model_t *model;
+ frameblend_t frameblend[MAX_FRAMEBLENDS];
+ skeleton_t *skeleton_p;
+ skeleton_t skeleton;
+ float *data_vertex3f;
+ float *data_svector3f;
+ float *data_tvector3f;
+ float *data_normal3f;
+ int max_vertices;
+ float *buf_vertex3f;
+ float *buf_svector3f;
+ float *buf_tvector3f;
+ float *buf_normal3f;
+}
+animatemodel_cache_t;
+static animatemodel_cache_t animatemodel_cache;
+
+void animatemodel(dp_model_t *model, prvm_edict_t *ed)
+{
+ skeleton_t *skeleton;
+ int skeletonindex = -1;
+ qboolean need = false;
+ if(!(model->surfmesh.isanimated && model->AnimateVertices))
+ {
+ animatemodel_cache.data_vertex3f = model->surfmesh.data_vertex3f;
+ animatemodel_cache.data_svector3f = model->surfmesh.data_svector3f;
+ animatemodel_cache.data_tvector3f = model->surfmesh.data_tvector3f;
+ animatemodel_cache.data_normal3f = model->surfmesh.data_normal3f;
+ return;
+ }
+ if(animatemodel_cache.progid != prog->id)
+ memset(&animatemodel_cache, 0, sizeof(animatemodel_cache));
+ need |= (animatemodel_cache.model != model);
+ VM_GenerateFrameGroupBlend(ed->priv.server->framegroupblend, ed);
+ VM_FrameBlendFromFrameGroupBlend(ed->priv.server->frameblend, ed->priv.server->framegroupblend, model);
+ need |= (memcmp(&animatemodel_cache.frameblend, &ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend))) != 0;
+ skeletonindex = (int)PRVM_gameedictfloat(ed, skeletonindex) - 1;
+ if (!(skeletonindex >= 0 && skeletonindex < MAX_EDICTS && (skeleton = prog->skeletons[skeletonindex]) && skeleton->model->num_bones == ed->priv.server->skeleton.model->num_bones))
+ skeleton = NULL;
+ need |= (animatemodel_cache.skeleton_p != skeleton);
+ if(skeleton)
+ need |= (memcmp(&animatemodel_cache.skeleton, skeleton, sizeof(ed->priv.server->skeleton))) != 0;
+ if(!need)
+ return;
+ if(model->surfmesh.num_vertices > animatemodel_cache.max_vertices)
+ {
+ animatemodel_cache.max_vertices = model->surfmesh.num_vertices * 2;
+ if(animatemodel_cache.buf_vertex3f) Mem_Free(animatemodel_cache.buf_vertex3f);
+ if(animatemodel_cache.buf_svector3f) Mem_Free(animatemodel_cache.buf_svector3f);
+ if(animatemodel_cache.buf_tvector3f) Mem_Free(animatemodel_cache.buf_tvector3f);
+ if(animatemodel_cache.buf_normal3f) Mem_Free(animatemodel_cache.buf_normal3f);
+ animatemodel_cache.buf_vertex3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices);
+ animatemodel_cache.buf_svector3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices);
+ animatemodel_cache.buf_tvector3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices);
+ animatemodel_cache.buf_normal3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices);
+ }
+ animatemodel_cache.data_vertex3f = animatemodel_cache.buf_vertex3f;
+ animatemodel_cache.data_svector3f = animatemodel_cache.buf_svector3f;
+ animatemodel_cache.data_tvector3f = animatemodel_cache.buf_tvector3f;
+ animatemodel_cache.data_normal3f = animatemodel_cache.buf_normal3f;
+ VM_UpdateEdictSkeleton(ed, model, ed->priv.server->frameblend);
+ model->AnimateVertices(model, ed->priv.server->frameblend, &ed->priv.server->skeleton, animatemodel_cache.data_vertex3f, animatemodel_cache.data_normal3f, animatemodel_cache.data_svector3f, animatemodel_cache.data_tvector3f);
+ animatemodel_cache.progid = prog->id;
+ animatemodel_cache.model = model;
+ memcpy(&animatemodel_cache.frameblend, &ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend));
+ animatemodel_cache.skeleton_p = skeleton;
+ if(skeleton)
+ memcpy(&animatemodel_cache.skeleton, skeleton, sizeof(ed->priv.server->skeleton));
+}
+
+static void getmatrix(prvm_edict_t *ed, matrix4x4_t *out)
+{
+ switch(PRVM_GetProgNr())
+ {
+ case PRVM_SERVERPROG:
+ SV_GetEntityMatrix(ed, out, false);
+ break;
+ case PRVM_CLIENTPROG:
+ CL_GetEntityMatrix(ed, out, false);
+ break;
+ default:
+ *out = identitymatrix;
+ break;
+ }
+}
+
+static void applytransform_forward(const vec3_t in, prvm_edict_t *ed, vec3_t out)
+{
+ matrix4x4_t m;
+ getmatrix(ed, &m);
+ Matrix4x4_Transform(&m, in, out);
+}
+
+static void applytransform_forward_direction(const vec3_t in, prvm_edict_t *ed, vec3_t out)
+{
+ matrix4x4_t m;
+ getmatrix(ed, &m);
+ Matrix4x4_Transform3x3(&m, in, out);
+}
+
+static void applytransform_inverted(const vec3_t in, prvm_edict_t *ed, vec3_t out)
+{
+ matrix4x4_t m, n;
+ getmatrix(ed, &m);
+ Matrix4x4_Invert_Full(&n, &m);
+ Matrix4x4_Transform3x3(&n, in, out);
+}
+
+static void applytransform_forward_normal(const vec3_t in, prvm_edict_t *ed, vec3_t out)
+{
+ matrix4x4_t m;
+ float p[4];
+ getmatrix(ed, &m);
+ Matrix4x4_TransformPositivePlane(&m, in[0], in[1], in[2], 0, p);
+ VectorCopy(p, out);
+}
+
+static void clippointtosurface(prvm_edict_t *ed, dp_model_t *model, msurface_t *surface, vec3_t p, vec3_t out)
+{
+ int i, j, k;
+ float *v[3], facenormal[3], edgenormal[3], sidenormal[3], temp[3], offsetdist, dist, bestdist;
+ const int *e;
+ animatemodel(model, ed);
+ bestdist = 1000000000;
+ VectorCopy(p, out);
+ for (i = 0, e = (model->surfmesh.data_element3i + 3 * surface->num_firsttriangle);i < surface->num_triangles;i++, e += 3)
+ {
+ // clip original point to each triangle of the surface and find the
+ // triangle that is closest
+ v[0] = animatemodel_cache.data_vertex3f + e[0] * 3;
+ v[1] = animatemodel_cache.data_vertex3f + e[1] * 3;
+ v[2] = animatemodel_cache.data_vertex3f + e[2] * 3;
+ TriangleNormal(v[0], v[1], v[2], facenormal);
+ VectorNormalize(facenormal);
+ offsetdist = DotProduct(v[0], facenormal) - DotProduct(p, facenormal);
+ VectorMA(p, offsetdist, facenormal, temp);
+ for (j = 0, k = 2;j < 3;k = j, j++)
+ {
+ VectorSubtract(v[k], v[j], edgenormal);
+ CrossProduct(edgenormal, facenormal, sidenormal);
+ VectorNormalize(sidenormal);
+ offsetdist = DotProduct(v[k], sidenormal) - DotProduct(temp, sidenormal);
+ if (offsetdist < 0)
+ VectorMA(temp, offsetdist, sidenormal, temp);
+ }
+ dist = VectorDistance2(temp, p);
+ if (bestdist > dist)
+ {
+ bestdist = dist;
+ VectorCopy(temp, out);
+ }
+ }
+}
+
+static msurface_t *getsurface(dp_model_t *model, int surfacenum)
+{
+ if (surfacenum < 0 || surfacenum >= model->nummodelsurfaces)
+ return NULL;
+ return model->data_surfaces + surfacenum + model->firstmodelsurface;
+}
+
+
+//PF_getsurfacenumpoints, // #434 float(entity e, float s) getsurfacenumpoints = #434;
+void VM_getsurfacenumpoints(void)
+{
+ dp_model_t *model;
+ msurface_t *surface;
+ VM_SAFEPARMCOUNT(2, VM_getsurfacenumpoints);
+ // return 0 if no such surface
+ if (!(model = getmodel(PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
+ {
+ PRVM_G_FLOAT(OFS_RETURN) = 0;
+ return;
+ }
+
+ // note: this (incorrectly) assumes it is a simple polygon
+ PRVM_G_FLOAT(OFS_RETURN) = surface->num_vertices;
+}
+//PF_getsurfacepoint, // #435 vector(entity e, float s, float n) getsurfacepoint = #435;
+void VM_getsurfacepoint(void)
+{
+ prvm_edict_t *ed;
+ dp_model_t *model;
+ msurface_t *surface;
+ int pointnum;
+ VM_SAFEPARMCOUNT(3, VM_getsurfacepoint);
+ VectorClear(PRVM_G_VECTOR(OFS_RETURN));
+ ed = PRVM_G_EDICT(OFS_PARM0);
+ if (!(model = getmodel(ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
+ return;
+ // note: this (incorrectly) assumes it is a simple polygon
+ pointnum = (int)PRVM_G_FLOAT(OFS_PARM2);
+ if (pointnum < 0 || pointnum >= surface->num_vertices)
+ return;
+ animatemodel(model, ed);
+ applytransform_forward(&(animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN));
+}
+//PF_getsurfacepointattribute, // #486 vector(entity e, float s, float n, float a) getsurfacepointattribute = #486;
+// float SPA_POSITION = 0;
+// float SPA_S_AXIS = 1;
+// float SPA_T_AXIS = 2;
+// float SPA_R_AXIS = 3; // same as SPA_NORMAL
+// float SPA_TEXCOORDS0 = 4;
+// float SPA_LIGHTMAP0_TEXCOORDS = 5;
+// float SPA_LIGHTMAP0_COLOR = 6;
+void VM_getsurfacepointattribute(void)
+{
+ prvm_edict_t *ed;
+ dp_model_t *model;
+ msurface_t *surface;
+ int pointnum;
+ int attributetype;
+
+ VM_SAFEPARMCOUNT(4, VM_getsurfacepoint);
+ VectorClear(PRVM_G_VECTOR(OFS_RETURN));
+ ed = PRVM_G_EDICT(OFS_PARM0);
+ if (!(model = getmodel(ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
+ return;
+ pointnum = (int)PRVM_G_FLOAT(OFS_PARM2);
+ if (pointnum < 0 || pointnum >= surface->num_vertices)
+ return;
+ attributetype = (int) PRVM_G_FLOAT(OFS_PARM3);
+
+ animatemodel(model, ed);
+
+ switch( attributetype ) {
+ // float SPA_POSITION = 0;
+ case 0:
+ applytransform_forward(&(animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN));
+ break;
+ // float SPA_S_AXIS = 1;
+ case 1:
+ applytransform_forward_direction(&(animatemodel_cache.data_svector3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN));
+ break;
+ // float SPA_T_AXIS = 2;
+ case 2:
+ applytransform_forward_direction(&(animatemodel_cache.data_tvector3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN));
+ break;
+ // float SPA_R_AXIS = 3; // same as SPA_NORMAL
+ case 3:
+ applytransform_forward_direction(&(animatemodel_cache.data_normal3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN));
+ break;
+ // float SPA_TEXCOORDS0 = 4;
+ case 4: {
+ float *ret = PRVM_G_VECTOR(OFS_RETURN);
+ float *texcoord = &(model->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[pointnum * 2];
+ ret[0] = texcoord[0];
+ ret[1] = texcoord[1];
+ ret[2] = 0.0f;
+ break;
+ }
+ // float SPA_LIGHTMAP0_TEXCOORDS = 5;
+ case 5: {
+ float *ret = PRVM_G_VECTOR(OFS_RETURN);
+ float *texcoord = &(model->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[pointnum * 2];
+ ret[0] = texcoord[0];
+ ret[1] = texcoord[1];
+ ret[2] = 0.0f;
+ break;
+ }
+ // float SPA_LIGHTMAP0_COLOR = 6;
+ case 6:
+ // ignore alpha for now..
+ VectorCopy( &(model->surfmesh.data_lightmapcolor4f + 4 * surface->num_firstvertex)[pointnum * 4], PRVM_G_VECTOR(OFS_RETURN));
+ break;
+ default:
+ VectorSet( PRVM_G_VECTOR(OFS_RETURN), 0.0f, 0.0f, 0.0f );
+ break;
+ }
+}
+//PF_getsurfacenormal, // #436 vector(entity e, float s) getsurfacenormal = #436;
+void VM_getsurfacenormal(void)
+{
+ dp_model_t *model;
+ msurface_t *surface;
+ vec3_t normal;
+ VM_SAFEPARMCOUNT(2, VM_getsurfacenormal);
+ VectorClear(PRVM_G_VECTOR(OFS_RETURN));
+ if (!(model = getmodel(PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
+ return;
+ // note: this only returns the first triangle, so it doesn't work very
+ // well for curved surfaces or arbitrary meshes
+ animatemodel(model, PRVM_G_EDICT(OFS_PARM0));
+ TriangleNormal((animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex), (animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex) + 3, (animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex) + 6, normal);
+ applytransform_forward_normal(normal, PRVM_G_EDICT(OFS_PARM0), PRVM_G_VECTOR(OFS_RETURN));
+ VectorNormalize(PRVM_G_VECTOR(OFS_RETURN));
+}
+//PF_getsurfacetexture, // #437 string(entity e, float s) getsurfacetexture = #437;
+void VM_getsurfacetexture(void)
+{
+ dp_model_t *model;
+ msurface_t *surface;
+ VM_SAFEPARMCOUNT(2, VM_getsurfacetexture);
+ PRVM_G_INT(OFS_RETURN) = OFS_NULL;
+ if (!(model = getmodel(PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
+ return;
+ PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(surface->texture->name);
+}
+//PF_getsurfacenearpoint, // #438 float(entity e, vector p) getsurfacenearpoint = #438;
+void VM_getsurfacenearpoint(void)
+{
+ int surfacenum, best;
+ vec3_t clipped, p;
+ vec_t dist, bestdist;
+ prvm_edict_t *ed;
+ dp_model_t *model;
+ msurface_t *surface;
+ vec_t *point;
+ VM_SAFEPARMCOUNT(2, VM_getsurfacenearpoint);
+ PRVM_G_FLOAT(OFS_RETURN) = -1;
+ ed = PRVM_G_EDICT(OFS_PARM0);
+ point = PRVM_G_VECTOR(OFS_PARM1);
+
+ if (!ed || ed->priv.server->free)
+ return;
+ model = getmodel(ed);
+ if (!model || !model->num_surfaces)
+ return;
+
+ animatemodel(model, ed);
+
+ applytransform_inverted(point, ed, p);
+ best = -1;
+ bestdist = 1000000000;
+ for (surfacenum = 0;surfacenum < model->nummodelsurfaces;surfacenum++)
+ {
+ surface = model->data_surfaces + surfacenum + model->firstmodelsurface;
+ // first see if the nearest point on the surface's box is closer than the previous match
+ clipped[0] = bound(surface->mins[0], p[0], surface->maxs[0]) - p[0];
+ clipped[1] = bound(surface->mins[1], p[1], surface->maxs[1]) - p[1];
+ clipped[2] = bound(surface->mins[2], p[2], surface->maxs[2]) - p[2];
+ dist = VectorLength2(clipped);
+ if (dist < bestdist)
+ {
+ // it is, check the nearest point on the actual geometry
+ clippointtosurface(ed, model, surface, p, clipped);
+ VectorSubtract(clipped, p, clipped);
+ dist += VectorLength2(clipped);
+ if (dist < bestdist)
+ {
+ // that's closer too, store it as the best match
+ best = surfacenum;
+ bestdist = dist;
+ }
+ }
+ }
+ PRVM_G_FLOAT(OFS_RETURN) = best;
+}
+//PF_getsurfaceclippedpoint, // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint = #439;
+void VM_getsurfaceclippedpoint(void)
+{
+ prvm_edict_t *ed;
+ dp_model_t *model;
+ msurface_t *surface;
+ vec3_t p, out;
+ VM_SAFEPARMCOUNT(3, VM_te_getsurfaceclippedpoint);
+ VectorClear(PRVM_G_VECTOR(OFS_RETURN));
+ ed = PRVM_G_EDICT(OFS_PARM0);
+ if (!(model = getmodel(ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
+ return;
+ animatemodel(model, ed);
+ applytransform_inverted(PRVM_G_VECTOR(OFS_PARM2), ed, p);
+ clippointtosurface(ed, model, surface, p, out);
+ VectorAdd(out, PRVM_serveredictvector(ed, origin), PRVM_G_VECTOR(OFS_RETURN));
+}
+
+//PF_getsurfacenumtriangles, // #??? float(entity e, float s) getsurfacenumtriangles = #???;
+void VM_getsurfacenumtriangles(void)
+{
+ dp_model_t *model;
+ msurface_t *surface;
+ VM_SAFEPARMCOUNT(2, VM_SV_getsurfacenumtriangles);
+ // return 0 if no such surface
+ if (!(model = getmodel(PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
+ {
+ PRVM_G_FLOAT(OFS_RETURN) = 0;
+ return;
+ }
+
+ // note: this (incorrectly) assumes it is a simple polygon
+ PRVM_G_FLOAT(OFS_RETURN) = surface->num_triangles;
+}
+//PF_getsurfacetriangle, // #??? vector(entity e, float s, float n) getsurfacetriangle = #???;
+void VM_getsurfacetriangle(void)
+{
+ const vec3_t d = {-1, -1, -1};
+ prvm_edict_t *ed;
+ dp_model_t *model;
+ msurface_t *surface;
+ int trinum;
+ VM_SAFEPARMCOUNT(3, VM_SV_getsurfacetriangle);
+ VectorClear(PRVM_G_VECTOR(OFS_RETURN));
+ ed = PRVM_G_EDICT(OFS_PARM0);
+ if (!(model = getmodel(ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
+ return;
+ trinum = (int)PRVM_G_FLOAT(OFS_PARM2);
+ if (trinum < 0 || trinum >= surface->num_triangles)
+ return;
+ // FIXME: implement rotation/scaling
+ VectorMA(&(model->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[trinum * 3], surface->num_firstvertex, d, PRVM_G_VECTOR(OFS_RETURN));
+}
+
+//
+// physics builtins
+//
+
+void World_Physics_ApplyCmd(prvm_edict_t *ed, edict_odefunc_t *f);
+
+#define VM_physics_ApplyCmd(ed,f) if (!ed->priv.server->ode_body) VM_physics_newstackfunction(ed, f); else World_Physics_ApplyCmd(ed, f)
+
+edict_odefunc_t *VM_physics_newstackfunction(prvm_edict_t *ed, edict_odefunc_t *f)
+{
+ edict_odefunc_t *newfunc, *func;
+
+ newfunc = (edict_odefunc_t *)Mem_Alloc(prog->progs_mempool, sizeof(edict_odefunc_t));
+ memcpy(newfunc, f, sizeof(edict_odefunc_t));
+ newfunc->next = NULL;
+ if (!ed->priv.server->ode_func)
+ ed->priv.server->ode_func = newfunc;
+ else
+ {
+ for (func = ed->priv.server->ode_func; func->next; func = func->next);
+ func->next = newfunc;
+ }
+ return newfunc;
+}
+
+// void(entity e, float physics_enabled) physics_enable = #;
+void VM_physics_enable(void)
+{
+ prvm_edict_t *ed;
+ edict_odefunc_t f;
+
+ VM_SAFEPARMCOUNT(2, VM_physics_enable);
+ ed = PRVM_G_EDICT(OFS_PARM0);
+ if (!ed)
+ {
+ if (developer.integer > 0)
+ VM_Warning("VM_physics_enable: null entity!\n");
+ return;
+ }
+ // entity should have MOVETYPE_PHYSICS already set, this can damage memory (making leaked allocation) so warn about this even if non-developer
+ if (PRVM_serveredictfloat(ed, movetype) != MOVETYPE_PHYSICS)
+ {
+ VM_Warning("VM_physics_enable: entity is not MOVETYPE_PHYSICS!\n");
+ return;
+ }
+ f.type = PRVM_G_FLOAT(OFS_PARM1) == 0 ? ODEFUNC_DISABLE : ODEFUNC_ENABLE;
+ VM_physics_ApplyCmd(ed, &f);
+}
+
+// void(entity e, vector force, vector relative_ofs) physics_addforce = #;
+void VM_physics_addforce(void)
+{
+ prvm_edict_t *ed;
+ edict_odefunc_t f;
+
+ VM_SAFEPARMCOUNT(3, VM_physics_addforce);
+ ed = PRVM_G_EDICT(OFS_PARM0);
+ if (!ed)
+ {
+ if (developer.integer > 0)
+ VM_Warning("VM_physics_addforce: null entity!\n");
+ return;
+ }
+ // entity should have MOVETYPE_PHYSICS already set, this can damage memory (making leaked allocation) so warn about this even if non-developer
+ if (PRVM_serveredictfloat(ed, movetype) != MOVETYPE_PHYSICS)
+ {
+ VM_Warning("VM_physics_addforce: entity is not MOVETYPE_PHYSICS!\n");
+ return;
+ }
+ f.type = ODEFUNC_RELFORCEATPOS;
+ VectorCopy(PRVM_G_VECTOR(OFS_PARM1), f.v1);
+ VectorSubtract(PRVM_serveredictvector(ed, origin), PRVM_G_VECTOR(OFS_PARM2), f.v2);
+ VM_physics_ApplyCmd(ed, &f);
+}
+
+// void(entity e, vector torque) physics_addtorque = #;
+void VM_physics_addtorque(void)
+{
+ prvm_edict_t *ed;
+ edict_odefunc_t f;
+
+ VM_SAFEPARMCOUNT(2, VM_physics_addtorque);
+ ed = PRVM_G_EDICT(OFS_PARM0);
+ if (!ed)
+ {
+ if (developer.integer > 0)
+ VM_Warning("VM_physics_addtorque: null entity!\n");
+ return;
+ }
+ // entity should have MOVETYPE_PHYSICS already set, this can damage memory (making leaked allocation) so warn about this even if non-developer
+ if (PRVM_serveredictfloat(ed, movetype) != MOVETYPE_PHYSICS)
+ {
+ VM_Warning("VM_physics_addtorque: entity is not MOVETYPE_PHYSICS!\n");
+ return;
+ }
+ f.type = ODEFUNC_RELTORQUE;
+ VectorCopy(PRVM_G_VECTOR(OFS_PARM1), f.v1);
+ VM_physics_ApplyCmd(ed, &f);
+}