1 /* vim: set tabstop=4: */
2 /*
3 Copyright (C) 1996-1997 Id Software, Inc.
4 Copyright (C) 2016      Spike
5 
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 
15 See the GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20 
21 */
22 
23 //provides a few convienience extensions, primarily builtins, but also autocvars.
24 //Also note the set+seta features.
25 
26 
27 #include "quakedef.h"
28 #include "q_ctype.h"
29 
PR_GetVMScale(void)30 static float PR_GetVMScale(void)
31 {
32 	//sigh, this is horrible (divides glwidth)
33 	float s = CLAMP (1.0, scr_sbarscale.value, (float)glwidth / 320.0);
34 	return s;
35 }
36 
37 //there's a few different aproaches to tempstrings...
38 //the lame way is to just have a single one (vanilla).
39 //the only slightly less lame way is to just cycle between 16 or so (most engines).
40 //one funky way is to allocate a single large buffer and just concatenate it for more tempstring space. don't forget to resize (dp).
41 //alternatively, just allocate them persistently and purge them only when there appear to be no more references to it (fte). makes strzone redundant.
42 
43 extern cvar_t sv_gameplayfix_setmodelrealbox, r_fteparticles;
44 cvar_t pr_checkextension = {"pr_checkextension", "1", CVAR_NONE};	//spike - enables qc extensions. if 0 then they're ALL BLOCKED! MWAHAHAHA! *cough* *splutter*
45 static int pr_ext_warned_particleeffectnum;	//so these only spam once per map
46 
47 static void *PR_FindExtGlobal(int type, const char *name);
48 void SV_CheckVelocity (edict_t *ent);
49 
50 typedef enum multicast_e
51 {
52 	MULTICAST_ALL_U,
53 	MULTICAST_PHS_U,
54 	MULTICAST_PVS_U,
55 	MULTICAST_ALL_R,
56 	MULTICAST_PHS_R,
57 	MULTICAST_PVS_R,
58 
59 	MULTICAST_ONE_U,
60 	MULTICAST_ONE_R,
61 	MULTICAST_INIT
62 } multicast_t;
63 static void SV_Multicast(multicast_t to, float *org, int msg_entity, unsigned int requireext2);
64 
65 #define Z_StrDup(s) strcpy(Z_Malloc(strlen(s)+1), s)
66 #define	RETURN_EDICT(e) (((int *)qcvm->globals)[OFS_RETURN] = EDICT_TO_PROG(e))
67 
PR_MakeTempString(const char * val)68 int PR_MakeTempString (const char *val)
69 {
70 	char *tmp = PR_GetTempString();
71 	q_strlcpy(tmp, val, STRINGTEMP_LENGTH);
72 	return PR_SetEngineString(tmp);
73 }
74 
75 #define ishex(c) ((c>='0' && c<= '9') || (c>='a' && c<='f') || (c>='A' && c<='F'))
dehex(char c)76 static int dehex(char c)
77 {
78 	if (c >= '0' && c <= '9')
79 		return c-'0';
80 	if (c >= 'A' && c <= 'F')
81 		return c-('A'-10);
82 	if (c >= 'a' && c <= 'f')
83 		return c-('a'-10);
84 	return 0;
85 }
86 //returns the next char...
87 struct markup_s
88 {
89 	const unsigned char *txt;
90 	vec4_t tint;	//predefined colour that applies to the entire string
91 	vec4_t colour;	//colour for the specific glyph in question
92 	unsigned char mask;
93 };
PR_Markup_Begin(struct markup_s * mu,const char * text,float * rgb,float alpha)94 void PR_Markup_Begin(struct markup_s *mu, const char *text, float *rgb, float alpha)
95 {
96 	if (*text == '\1' || *text == '\2')
97 	{
98 		mu->mask = 128;
99 		text++;
100 	}
101 	else
102 		mu->mask = 0;
103 	mu->txt = (const unsigned char *)text;
104 	VectorCopy(rgb, mu->tint);
105 	mu->tint[3] = alpha;
106 	VectorCopy(rgb, mu->colour);
107 	mu->colour[3] = alpha;
108 }
PR_Markup_Parse(struct markup_s * mu)109 int PR_Markup_Parse(struct markup_s *mu)
110 {
111 	static const vec4_t q3rgb[10] = {
112 		{0.00,0.00,0.00, 1.0},
113 		{1.00,0.33,0.33, 1.0},
114 		{0.00,1.00,0.33, 1.0},
115 		{1.00,1.00,0.33, 1.0},
116 		{0.33,0.33,1.00, 1.0},
117 		{0.33,1.00,1.00, 1.0},
118 		{1.00,0.33,1.00, 1.0},
119 		{1.00,1.00,1.00, 1.0},
120 		{1.00,1.00,1.00, 0.5},
121 		{0.50,0.50,0.50, 1.0}
122 	};
123 	unsigned int c;
124 	const float *f;
125 	while ((c = *mu->txt))
126 	{
127 		if (c == '^' && pr_checkextension.value)
128 		{	//parse markup like FTE/DP might.
129 			switch(mu->txt[1])
130 			{
131 			case '^':	//doubled up char for escaping.
132 				mu->txt++;
133 				break;
134 			case '0':	//black
135 			case '1':	//red
136 			case '2':	//green
137 			case '3':	//yellow
138 			case '4':	//blue
139 			case '5':	//cyan
140 			case '6':	//magenta
141 			case '7':	//white
142 			case '8':	//white+half-alpha
143 			case '9':	//grey
144 				f = q3rgb[mu->txt[1]-'0'];
145 				mu->colour[0] = mu->tint[0] * f[0];
146 				mu->colour[1] = mu->tint[1] * f[1];
147 				mu->colour[2] = mu->tint[2] * f[2];
148 				mu->colour[3] = mu->tint[3] * f[3];
149 				mu->txt+=2;
150 				continue;
151 			case 'h':	//toggle half-alpha
152 				if (mu->colour[3] != mu->tint[3] * 0.5)
153 					mu->colour[3] = mu->tint[3] * 0.5;
154 				else
155 					mu->colour[3] = mu->tint[3];
156 				mu->txt+=2;
157 				continue;
158 			case 'd':	//reset to defaults (fixme: should reset ^m without resetting \1)
159 				mu->colour[0] = mu->tint[0];
160 				mu->colour[1] = mu->tint[1];
161 				mu->colour[2] = mu->tint[2];
162 				mu->colour[3] = mu->tint[3];
163 				mu->mask = 0;
164 				mu->txt+=2;
165 				break;
166 			case 'b':	//blink
167 			case 's':	//modstack push
168 			case 'r':	//modstack restore
169 				mu->txt+=2;
170 				continue;
171 			case 'x':	//RGB 12-bit colour
172 				if (ishex(mu->txt[2]) && ishex(mu->txt[3]) && ishex(mu->txt[4]))
173 				{
174 					mu->colour[0] = mu->tint[0] * dehex(mu->txt[2])/15.0;
175 					mu->colour[1] = mu->tint[1] * dehex(mu->txt[3])/15.0;
176 					mu->colour[2] = mu->tint[2] * dehex(mu->txt[4])/15.0;
177 					mu->txt+=5;
178 					continue;
179 				}
180 				break;	//malformed
181 			case '[':	//start fte's ^[text\key\value\key\value^] links
182 			case ']':	//end link
183 				break;	//fixme... skip the keys, recolour properly, etc
184 		//				txt+=2;
185 		//				continue;
186 			case '&':
187 				if ((ishex(mu->txt[2])||mu->txt[2]=='-') && (ishex(mu->txt[3])||mu->txt[3]=='-'))
188 				{	//ignore fte's fore/back ansi colours
189 					mu->txt += 4;
190 					continue;
191 				}
192 				break;	//malformed
193 			case 'a':	//alternate charset (read: masked)...
194 			case 'm':	//toggle masking.
195 				mu->txt+=2;
196 				mu->mask ^= 128;
197 				continue;
198 			case 'U':	//ucs-2 unicode codepoint
199 				if (ishex(mu->txt[2]) && ishex(mu->txt[3]) && ishex(mu->txt[4]) && ishex(mu->txt[5]))
200 				{
201 					c = (dehex(mu->txt[2])<<12) | (dehex(mu->txt[3])<<8) | (dehex(mu->txt[4])<<4) | dehex(mu->txt[5]);
202 					mu->txt += 6;
203 
204 					if (c >= 0xe000 && c <= 0xe0ff)
205 						c &= 0xff;	//private-use 0xE0XX maps to quake's chars
206 					else if (c >= 0x20 && c <= 0x7f)
207 						c &= 0x7f;	//ascii is okay too.
208 					else
209 						c = '?'; //otherwise its some unicode char that we don't know how to handle.
210 					return c;
211 				}
212 				break; //malformed
213 			case '{':	//full unicode codepoint, for chars up to 0x10ffff
214 				mu->txt += 2;
215 				c = 0;	//no idea
216 				while(*mu->txt)
217 				{
218 					if (*mu->txt == '}')
219 					{
220 						mu->txt++;
221 						break;
222 					}
223 					if (!ishex(*mu->txt))
224 						break;
225 					c<<=4;
226 					c |= dehex(*mu->txt++);
227 				}
228 
229 				if (c >= 0xe000 && c <= 0xe0ff)
230 					c &= 0xff;	//private-use 0xE0XX maps to quake's chars
231 				else if (c >= 0x20 && c <= 0x7f)
232 					c &= 0x7f;	//ascii is okay too.
233 				//it would be nice to include a table to de-accent latin scripts, as well as translate cyrilic somehow, but not really necessary.
234 				else
235 					c = '?'; //otherwise its some unicode char that we don't know how to handle.
236 				return c;
237 			}
238 		}
239 
240 		//regular char
241 		mu->txt++;
242 		return c|mu->mask;
243 	}
244 	return 0;
245 }
246 
247 #define D(typestr,desc) typestr,desc
248 
249 //#define fixme
250 
251 //maths stuff
PF_Sin(void)252 static void PF_Sin(void)
253 {
254 	G_FLOAT(OFS_RETURN) = sin(G_FLOAT(OFS_PARM0));
255 }
PF_asin(void)256 static void PF_asin(void)
257 {
258 	G_FLOAT(OFS_RETURN) = asin(G_FLOAT(OFS_PARM0));
259 }
PF_Cos(void)260 static void PF_Cos(void)
261 {
262 	G_FLOAT(OFS_RETURN) = cos(G_FLOAT(OFS_PARM0));
263 }
PF_acos(void)264 static void PF_acos(void)
265 {
266 	G_FLOAT(OFS_RETURN) = acos(G_FLOAT(OFS_PARM0));
267 }
PF_tan(void)268 static void PF_tan(void)
269 {
270 	G_FLOAT(OFS_RETURN) = tan(G_FLOAT(OFS_PARM0));
271 }
PF_atan(void)272 static void PF_atan(void)
273 {
274 	G_FLOAT(OFS_RETURN) = atan(G_FLOAT(OFS_PARM0));
275 }
PF_atan2(void)276 static void PF_atan2(void)
277 {
278 	G_FLOAT(OFS_RETURN) = atan2(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1));
279 }
PF_Sqrt(void)280 static void PF_Sqrt(void)
281 {
282 	G_FLOAT(OFS_RETURN) = sqrt(G_FLOAT(OFS_PARM0));
283 }
PF_pow(void)284 static void PF_pow(void)
285 {
286 	G_FLOAT(OFS_RETURN) = pow(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1));
287 }
PF_Logarithm(void)288 static void PF_Logarithm(void)
289 {
290 	//log2(v) = ln(v)/ln(2)
291 	double r;
292 	r = log(G_FLOAT(OFS_PARM0));
293 	if (qcvm->argc > 1)
294 		r /= log(G_FLOAT(OFS_PARM1));
295 	G_FLOAT(OFS_RETURN) = r;
296 }
PF_mod(void)297 static void PF_mod(void)
298 {
299 	float a = G_FLOAT(OFS_PARM0);
300 	float n = G_FLOAT(OFS_PARM1);
301 
302 	if (n == 0)
303 	{
304 		Con_DWarning("PF_mod: mod by zero\n");
305 		G_FLOAT(OFS_RETURN) = 0;
306 	}
307 	else
308 	{
309 		//because QC is inherantly floaty, lets use floats.
310 		G_FLOAT(OFS_RETURN) = a - (n * (int)(a/n));
311 	}
312 }
PF_min(void)313 static void PF_min(void)
314 {
315 	float r = G_FLOAT(OFS_PARM0);
316 	int i;
317 	for (i = 1; i < qcvm->argc; i++)
318 	{
319 		if (r > G_FLOAT(OFS_PARM0 + i*3))
320 			r = G_FLOAT(OFS_PARM0 + i*3);
321 	}
322 	G_FLOAT(OFS_RETURN) = r;
323 }
PF_max(void)324 static void PF_max(void)
325 {
326 	float r = G_FLOAT(OFS_PARM0);
327 	int i;
328 	for (i = 1; i < qcvm->argc; i++)
329 	{
330 		if (r < G_FLOAT(OFS_PARM0 + i*3))
331 			r = G_FLOAT(OFS_PARM0 + i*3);
332 	}
333 	G_FLOAT(OFS_RETURN) = r;
334 }
PF_bound(void)335 static void PF_bound(void)
336 {
337 	float minval = G_FLOAT(OFS_PARM0);
338 	float curval = G_FLOAT(OFS_PARM1);
339 	float maxval = G_FLOAT(OFS_PARM2);
340 	if (curval > maxval)
341 		curval = maxval;
342 	if (curval < minval)
343 		curval = minval;
344 	G_FLOAT(OFS_RETURN) = curval;
345 }
PF_anglemod(void)346 static void PF_anglemod(void)
347 {
348 	float v = G_FLOAT(OFS_PARM0);
349 
350 	while (v >= 360)
351 		v = v - 360;
352 	while (v < 0)
353 		v = v + 360;
354 
355 	G_FLOAT(OFS_RETURN) = v;
356 }
PF_bitshift(void)357 static void PF_bitshift(void)
358 {
359 	int bitmask = G_FLOAT(OFS_PARM0);
360 	int shift = G_FLOAT(OFS_PARM1);
361 	if (shift < 0)
362 		bitmask >>= -shift;
363 	else
364 		bitmask <<= shift;
365 	G_FLOAT(OFS_RETURN) = bitmask;
366 }
PF_crossproduct(void)367 static void PF_crossproduct(void)
368 {
369 	CrossProduct(G_VECTOR(OFS_PARM0), G_VECTOR(OFS_PARM1), G_VECTOR(OFS_RETURN));
370 }
PF_vectorvectors(void)371 static void PF_vectorvectors(void)
372 {
373 	VectorCopy(G_VECTOR(OFS_PARM0), pr_global_struct->v_forward);
374 	VectorNormalize(pr_global_struct->v_forward);
375 	if (!pr_global_struct->v_forward[0] && !pr_global_struct->v_forward[1])
376 	{
377 		if (pr_global_struct->v_forward[2])
378 			pr_global_struct->v_right[1] = -1;
379 		else
380 			pr_global_struct->v_right[1] = 0;
381 		pr_global_struct->v_right[0] = pr_global_struct->v_right[2] = 0;
382 	}
383 	else
384 	{
385 		pr_global_struct->v_right[0] = pr_global_struct->v_forward[1];
386 		pr_global_struct->v_right[1] = -pr_global_struct->v_forward[0];
387 		pr_global_struct->v_right[2] = 0;
388 		VectorNormalize(pr_global_struct->v_right);
389 	}
390 	CrossProduct(pr_global_struct->v_right, pr_global_struct->v_forward, pr_global_struct->v_up);
391 }
PF_ext_vectoangles(void)392 static void PF_ext_vectoangles(void)
393 {	//alternative version of the original builtin, that can deal with roll angles too, by accepting an optional second argument for 'up'.
394 	float	*value1, *up;
395 
396 	value1 = G_VECTOR(OFS_PARM0);
397 	if (qcvm->argc >= 2)
398 		up = G_VECTOR(OFS_PARM1);
399 	else
400 		up = NULL;
401 
402 	VectorAngles(value1, up, G_VECTOR(OFS_RETURN));
403 	G_VECTOR(OFS_RETURN)[PITCH] *= -1;	//this builtin is for use with models. models have an inverted pitch. consistency with makevectors would never do!
404 }
405 
406 //string stuff
PF_strlen(void)407 static void PF_strlen(void)
408 {	//FIXME: doesn't try to handle utf-8
409 	const char *s = G_STRING(OFS_PARM0);
410 	G_FLOAT(OFS_RETURN) = strlen(s);
411 }
PF_strcat(void)412 static void PF_strcat(void)
413 {
414 	int		i;
415 	char *out = PR_GetTempString();
416 	size_t s;
417 
418 	out[0] = 0;
419 	s = 0;
420 	for (i = 0; i < qcvm->argc; i++)
421 	{
422 		s = q_strlcat(out, G_STRING((OFS_PARM0+i*3)), STRINGTEMP_LENGTH);
423 		if (s >= STRINGTEMP_LENGTH)
424 		{
425 			Con_Warning("PF_strcat: overflow (string truncated)\n");
426 			break;
427 		}
428 	}
429 
430 	G_INT(OFS_RETURN) = PR_SetEngineString(out);
431 }
PF_substring(void)432 static void PF_substring(void)
433 {
434 	int start, length, slen;
435 	const char *s;
436 	char *string;
437 
438 	s = G_STRING(OFS_PARM0);
439 	start = G_FLOAT(OFS_PARM1);
440 	length = G_FLOAT(OFS_PARM2);
441 
442 	slen = strlen(s);	//utf-8 should use chars, not bytes.
443 
444 	if (start < 0)
445 		start = slen+start;
446 	if (length < 0)
447 		length = slen-start+(length+1);
448 	if (start < 0)
449 	{
450 	//	length += start;
451 		start = 0;
452 	}
453 
454 	if (start >= slen || length<=0)
455 	{
456 		G_INT(OFS_RETURN) = PR_SetEngineString("");
457 		return;
458 	}
459 
460 	slen -= start;
461 	if (length > slen)
462 		length = slen;
463 	//utf-8 should switch to bytes now.
464 	s += start;
465 
466 	if (length >= STRINGTEMP_LENGTH)
467 	{
468 		length = STRINGTEMP_LENGTH-1;
469 		Con_Warning("PF_substring: truncation\n");
470 	}
471 
472 	string = PR_GetTempString();
473 	memcpy(string, s, length);
474 	string[length] = '\0';
475 	G_INT(OFS_RETURN) = PR_SetEngineString(string);
476 }
477 /*our zoned strings implementation is somewhat specific to quakespasm, so good luck porting*/
PF_strzone(void)478 static void PF_strzone(void)
479 {
480 	char *buf;
481 	size_t len = 0;
482 	const char *s[8];
483 	size_t l[8];
484 	int i;
485 	size_t id;
486 
487 	for (i = 0; i < qcvm->argc; i++)
488 	{
489 		s[i] = G_STRING(OFS_PARM0+i*3);
490 		l[i] = strlen(s[i]);
491 		len += l[i];
492 	}
493 	len++; /*for the null*/
494 
495 	buf = Z_Malloc(len);
496 	G_INT(OFS_RETURN) = PR_SetEngineString(buf);
497 	id = -1-G_INT(OFS_RETURN);
498 	if (id >= qcvm->knownzonesize)
499 	{
500 		qcvm->knownzonesize = (id+32)&~7;
501 		qcvm->knownzone = Z_Realloc(qcvm->knownzone, (qcvm->knownzonesize+7)>>3);
502 	}
503 	qcvm->knownzone[id>>3] |= 1u<<(id&7);
504 
505 	for (i = 0; i < qcvm->argc; i++)
506 	{
507 		memcpy(buf, s[i], l[i]);
508 		buf += l[i];
509 	}
510 	*buf = '\0';
511 }
PF_strunzone(void)512 static void PF_strunzone(void)
513 {
514 	size_t id;
515 	const char *foo = G_STRING(OFS_PARM0);
516 
517 	if (!G_INT(OFS_PARM0))
518 		return;	//don't bug out if they gave a null string
519 	id = -1-G_INT(OFS_PARM0);
520 	if (id < qcvm->knownzonesize && (qcvm->knownzone[id>>3] & (1u<<(id&7))))
521 	{
522 		qcvm->knownzone[id>>3] &= ~(1u<<(id&7));
523 		PR_ClearEngineString(G_INT(OFS_PARM0));
524 		Z_Free((void*)foo);
525 	}
526 	else
527 		Con_Warning("PF_strunzone: string wasn't strzoned\n");
528 }
PR_UnzoneAll(void)529 static void PR_UnzoneAll(void)
530 {	//called to clean up all zoned strings.
531 	while (qcvm->knownzonesize --> 0)
532 	{
533 		size_t id = qcvm->knownzonesize;
534 		if (qcvm->knownzone[id>>3] & (1u<<(id&7)))
535 		{
536 			string_t s = -1-(int)id;
537 			char *ptr = (char*)PR_GetString(s);
538 			PR_ClearEngineString(s);
539 			Z_Free(ptr);
540 		}
541 	}
542 	if (qcvm->knownzone)
543 		Z_Free(qcvm->knownzone);
544 	qcvm->knownzonesize = 0;
545 	qcvm->knownzone = NULL;
546 }
qc_isascii(unsigned int u)547 static qboolean qc_isascii(unsigned int u)
548 {
549 	if (u < 256)	//should be just \n and 32-127, but we don't actually support any actual unicode and we don't really want to make things worse.
550 		return true;
551 	return false;
552 }
PF_str2chr(void)553 static void PF_str2chr(void)
554 {
555 	const char *instr = G_STRING(OFS_PARM0);
556 	int ofs = (qcvm->argc>1)?G_FLOAT(OFS_PARM1):0;
557 
558 	if (ofs < 0)
559 		ofs = strlen(instr)+ofs;
560 
561 	if (ofs && (ofs < 0 || ofs > (int)strlen(instr)))
562 		G_FLOAT(OFS_RETURN) = '\0';
563 	else
564 		G_FLOAT(OFS_RETURN) = (unsigned char)instr[ofs];
565 }
PF_chr2str(void)566 static void PF_chr2str(void)
567 {
568 	char *ret = PR_GetTempString(), *out;
569 	int i;
570 	for (i = 0, out=ret; out-ret < STRINGTEMP_LENGTH-6 && i < qcvm->argc; i++)
571 	{
572 		unsigned int u = G_FLOAT(OFS_PARM0 + i*3);
573 		if (u >= 0xe000 && u < 0xe100)
574 			*out++ = (unsigned char)u;	//quake chars.
575 		else if (qc_isascii(u))
576 			*out++ = u;
577 		else
578 			*out++ = '?';	//no unicode support
579 	}
580 	*out = 0;
581 	G_INT(OFS_RETURN) = PR_SetEngineString(ret);
582 }
583 //part of PF_strconv
chrconv_number(int i,int base,int conv)584 static int chrconv_number(int i, int base, int conv)
585 {
586 	i -= base;
587 	switch (conv)
588 	{
589 	default:
590 	case 5:
591 	case 6:
592 	case 0:
593 		break;
594 	case 1:
595 		base = '0';
596 		break;
597 	case 2:
598 		base = '0'+128;
599 		break;
600 	case 3:
601 		base = '0'-30;
602 		break;
603 	case 4:
604 		base = '0'+128-30;
605 		break;
606 	}
607 	return i + base;
608 }
609 //part of PF_strconv
chrconv_punct(int i,int base,int conv)610 static int chrconv_punct(int i, int base, int conv)
611 {
612 	i -= base;
613 	switch (conv)
614 	{
615 	default:
616 	case 0:
617 		break;
618 	case 1:
619 		base = 0;
620 		break;
621 	case 2:
622 		base = 128;
623 		break;
624 	}
625 	return i + base;
626 }
627 //part of PF_strconv
chrchar_alpha(int i,int basec,int baset,int convc,int convt,int charnum)628 static int chrchar_alpha(int i, int basec, int baset, int convc, int convt, int charnum)
629 {
630 	//convert case and colour seperatly...
631 
632 	i -= baset + basec;
633 	switch (convt)
634 	{
635 	default:
636 	case 0:
637 		break;
638 	case 1:
639 		baset = 0;
640 		break;
641 	case 2:
642 		baset = 128;
643 		break;
644 
645 	case 5:
646 	case 6:
647 		baset = 128*((charnum&1) == (convt-5));
648 		break;
649 	}
650 
651 	switch (convc)
652 	{
653 	default:
654 	case 0:
655 		break;
656 	case 1:
657 		basec = 'a';
658 		break;
659 	case 2:
660 		basec = 'A';
661 		break;
662 	}
663 	return i + basec + baset;
664 }
665 //FTE_STRINGS
666 //bulk convert a string. change case or colouring.
PF_strconv(void)667 static void PF_strconv (void)
668 {
669 	int ccase = G_FLOAT(OFS_PARM0);		//0 same, 1 lower, 2 upper
670 	int redalpha = G_FLOAT(OFS_PARM1);	//0 same, 1 white, 2 red,  5 alternate, 6 alternate-alternate
671 	int rednum = G_FLOAT(OFS_PARM2);	//0 same, 1 white, 2 red, 3 redspecial, 4 whitespecial, 5 alternate, 6 alternate-alternate
672 	const unsigned char *string = (const unsigned char*)PF_VarString(3);
673 	int len = strlen((const char*)string);
674 	int i;
675 	unsigned char *resbuf = (unsigned char*)PR_GetTempString();
676 	unsigned char *result = resbuf;
677 
678 	//UTF-8-FIXME: cope with utf+^U etc
679 
680 	if (len >= STRINGTEMP_LENGTH)
681 		len = STRINGTEMP_LENGTH-1;
682 
683 	for (i = 0; i < len; i++, string++, result++)	//should this be done backwards?
684 	{
685 		if (*string >= '0' && *string <= '9')	//normal numbers...
686 			*result = chrconv_number(*string, '0', rednum);
687 		else if (*string >= '0'+128 && *string <= '9'+128)
688 			*result = chrconv_number(*string, '0'+128, rednum);
689 		else if (*string >= '0'+128-30 && *string <= '9'+128-30)
690 			*result = chrconv_number(*string, '0'+128-30, rednum);
691 		else if (*string >= '0'-30 && *string <= '9'-30)
692 			*result = chrconv_number(*string, '0'-30, rednum);
693 
694 		else if (*string >= 'a' && *string <= 'z')	//normal numbers...
695 			*result = chrchar_alpha(*string, 'a', 0, ccase, redalpha, i);
696 		else if (*string >= 'A' && *string <= 'Z')	//normal numbers...
697 			*result = chrchar_alpha(*string, 'A', 0, ccase, redalpha, i);
698 		else if (*string >= 'a'+128 && *string <= 'z'+128)	//normal numbers...
699 			*result = chrchar_alpha(*string, 'a', 128, ccase, redalpha, i);
700 		else if (*string >= 'A'+128 && *string <= 'Z'+128)	//normal numbers...
701 			*result = chrchar_alpha(*string, 'A', 128, ccase, redalpha, i);
702 
703 		else if ((*string & 127) < 16 || !redalpha)	//special chars..
704 			*result = *string;
705 		else if (*string < 128)
706 			*result = chrconv_punct(*string, 0, redalpha);
707 		else
708 			*result = chrconv_punct(*string, 128, redalpha);
709 	}
710 	*result = '\0';
711 
712 	G_INT(OFS_RETURN) = PR_SetEngineString((char*)resbuf);
713 }
PF_strpad(void)714 static void PF_strpad(void)
715 {
716 	char *destbuf = PR_GetTempString();
717 	char *dest = destbuf;
718 	int pad = G_FLOAT(OFS_PARM0);
719 	const char *src = PF_VarString(1);
720 
721 	//UTF-8-FIXME: pad is chars not bytes...
722 
723 	if (pad < 0)
724 	{	//pad left
725 		pad = -pad - strlen(src);
726 		if (pad>=STRINGTEMP_LENGTH)
727 			pad = STRINGTEMP_LENGTH-1;
728 		if (pad < 0)
729 			pad = 0;
730 
731 		q_strlcpy(dest+pad, src, STRINGTEMP_LENGTH-pad);
732 		while(pad)
733 		{
734 			dest[--pad] = ' ';
735 		}
736 	}
737 	else
738 	{	//pad right
739 		if (pad>=STRINGTEMP_LENGTH)
740 			pad = STRINGTEMP_LENGTH-1;
741 		pad -= strlen(src);
742 		if (pad < 0)
743 			pad = 0;
744 
745 		q_strlcpy(dest, src, STRINGTEMP_LENGTH);
746 		dest+=strlen(dest);
747 
748 		while(pad-->0)
749 			*dest++ = ' ';
750 		*dest = '\0';
751 	}
752 
753 	G_INT(OFS_RETURN) = PR_SetEngineString(destbuf);
754 }
PF_infoadd(void)755 static void PF_infoadd(void)
756 {
757 	const char *info = G_STRING(OFS_PARM0);
758 	const char *key = G_STRING(OFS_PARM1);
759 	const char *value = PF_VarString(2);
760 	char *destbuf = PR_GetTempString(), *o = destbuf, *e = destbuf + STRINGTEMP_LENGTH - 1;
761 
762 	size_t keylen = strlen(key);
763 	size_t valuelen = strlen(value);
764 	if (!*key)
765 	{	//error
766 		G_INT(OFS_RETURN) = G_INT(OFS_PARM0);
767 		return;
768 	}
769 
770 	//copy the string to the output, stripping the named key
771 	while(*info)
772 	{
773 		const char *l = info;
774 		if (*info++ != '\\')
775 			break;	//error / end-of-string
776 
777 		if (!strncmp(info, key, keylen) && info[keylen] == '\\')
778 		{
779 			//skip the key name
780 			info += keylen+1;
781 			//this is the old value for the key. skip over it
782 			while (*info && *info != '\\')
783 				info++;
784 		}
785 		else
786 		{
787 			//skip the key
788 			while (*info && *info != '\\')
789 				info++;
790 
791 			//validate that its a value now
792 			if (*info++ != '\\')
793 				break;	//error
794 			//skip the value
795 			while (*info && *info != '\\')
796 				info++;
797 
798 			//copy them over
799 			if (o + (info-l) >= e)
800 				break;	//exceeds maximum length
801 			while (l < info)
802 				*o++ = *l++;
803 		}
804 	}
805 
806 	if (*info)
807 		Con_Warning("PF_infoadd: invalid source info\n");
808 	else if (!*value)
809 		; //nothing needed
810 	else if (!*key || strchr(key, '\\') || strchr(value, '\\'))
811 		Con_Warning("PF_infoadd: invalid key/value\n");
812 	else if (o + 2 + keylen + valuelen >= e)
813 		Con_Warning("PF_infoadd: length exceeds max\n");
814 	else
815 	{
816 		*o++ = '\\';
817 		memcpy(o, key, keylen);
818 		o += keylen;
819 		*o++ = '\\';
820 		memcpy(o, value, valuelen);
821 		o += valuelen;
822 	}
823 
824 	*o = 0;
825 	G_INT(OFS_RETURN) = PR_SetEngineString(destbuf);
826 }
PF_infoget(void)827 static void PF_infoget(void)
828 {
829 	const char *info = G_STRING(OFS_PARM0);
830 	const char *key = G_STRING(OFS_PARM1);
831 	size_t keylen = strlen(key);
832 	while(*info)
833 	{
834 		if (*info++ != '\\')
835 			break;	//error / end-of-string
836 
837 		if (!strncmp(info, key, keylen) && info[keylen] == '\\')
838 		{
839 			char *destbuf = PR_GetTempString(), *o = destbuf, *e = destbuf + STRINGTEMP_LENGTH - 1;
840 
841 			//skip the key name
842 			info += keylen+1;
843 			//this is the old value for the key. copy it to the result
844 			while (*info && *info != '\\' && o < e)
845 				*o++ = *info++;
846 			*o++ = 0;
847 
848 			//success!
849 			G_INT(OFS_RETURN) = PR_SetEngineString(destbuf);
850 			return;
851 		}
852 		else
853 		{
854 			//skip the key
855 			while (*info && *info != '\\')
856 				info++;
857 
858 			//validate that its a value now
859 			if (*info++ != '\\')
860 				break;	//error
861 			//skip the value
862 			while (*info && *info != '\\')
863 				info++;
864 		}
865 	}
866 	G_INT(OFS_RETURN) = 0;
867 
868 }
PF_strncmp(void)869 static void PF_strncmp(void)
870 {
871 	const char *a = G_STRING(OFS_PARM0);
872 	const char *b = G_STRING(OFS_PARM1);
873 
874 	if (qcvm->argc > 2)
875 	{
876 		int len = G_FLOAT(OFS_PARM2);
877 		int aofs = qcvm->argc>3?G_FLOAT(OFS_PARM3):0;
878 		int bofs = qcvm->argc>4?G_FLOAT(OFS_PARM4):0;
879 		if (aofs < 0 || (aofs && aofs > (int)strlen(a)))
880 			aofs = strlen(a);
881 		if (bofs < 0 || (bofs && bofs > (int)strlen(b)))
882 			bofs = strlen(b);
883 		G_FLOAT(OFS_RETURN) = Q_strncmp(a + aofs, b, len);
884 	}
885 	else
886 		G_FLOAT(OFS_RETURN) = Q_strcmp(a, b);
887 }
PF_strncasecmp(void)888 static void PF_strncasecmp(void)
889 {
890 	const char *a = G_STRING(OFS_PARM0);
891 	const char *b = G_STRING(OFS_PARM1);
892 
893 	if (qcvm->argc > 2)
894 	{
895 		int len = G_FLOAT(OFS_PARM2);
896 		int aofs = qcvm->argc>3?G_FLOAT(OFS_PARM3):0;
897 		int bofs = qcvm->argc>4?G_FLOAT(OFS_PARM4):0;
898 		if (aofs < 0 || (aofs && aofs > (int)strlen(a)))
899 			aofs = strlen(a);
900 		if (bofs < 0 || (bofs && bofs > (int)strlen(b)))
901 			bofs = strlen(b);
902 		G_FLOAT(OFS_RETURN) = q_strncasecmp(a + aofs, b, len);
903 	}
904 	else
905 		G_FLOAT(OFS_RETURN) = q_strcasecmp(a, b);
906 }
PF_strstrofs(void)907 static void PF_strstrofs(void)
908 {
909 	const char *instr = G_STRING(OFS_PARM0);
910 	const char *match = G_STRING(OFS_PARM1);
911 	int firstofs = (qcvm->argc>2)?G_FLOAT(OFS_PARM2):0;
912 
913 	if (firstofs && (firstofs < 0 || firstofs > (int)strlen(instr)))
914 	{
915 		G_FLOAT(OFS_RETURN) = -1;
916 		return;
917 	}
918 
919 	match = strstr(instr+firstofs, match);
920 	if (!match)
921 		G_FLOAT(OFS_RETURN) = -1;
922 	else
923 		G_FLOAT(OFS_RETURN) = match - instr;
924 }
PF_strtrim(void)925 static void PF_strtrim(void)
926 {
927 	const char *str = G_STRING(OFS_PARM0);
928 	const char *end;
929 	char *news;
930 	size_t len;
931 
932 	//figure out the new start
933 	while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r')
934 		str++;
935 
936 	//figure out the new end.
937 	end = str + strlen(str);
938 	while(end > str && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n' || end[-1] == '\r'))
939 		end--;
940 
941 	//copy that substring into a tempstring.
942 	len = end - str;
943 	if (len >= STRINGTEMP_LENGTH)
944 		len = STRINGTEMP_LENGTH-1;
945 
946 	news = PR_GetTempString();
947 	memcpy(news, str, len);
948 	news[len] = 0;
949 
950 	G_INT(OFS_RETURN) = PR_SetEngineString(news);
951 }
PF_strreplace(void)952 static void PF_strreplace(void)
953 {
954 	char *resultbuf = PR_GetTempString();
955 	char *result = resultbuf;
956 	const char *search = G_STRING(OFS_PARM0);
957 	const char *replace = G_STRING(OFS_PARM1);
958 	const char *subject = G_STRING(OFS_PARM2);
959 	int searchlen = strlen(search);
960 	int replacelen = strlen(replace);
961 
962 	if (searchlen)
963 	{
964 		while (*subject && result < resultbuf + STRINGTEMP_LENGTH - replacelen - 2)
965 		{
966 			if (!strncmp(subject, search, searchlen))
967 			{
968 				subject += searchlen;
969 				memcpy(result, replace, replacelen);
970 				result += replacelen;
971 			}
972 			else
973 				*result++ = *subject++;
974 		}
975 		*result = 0;
976 		G_INT(OFS_RETURN) = PR_SetEngineString(resultbuf);
977 	}
978 	else
979 		G_INT(OFS_RETURN) = PR_SetEngineString(subject);
980 }
PF_strireplace(void)981 static void PF_strireplace(void)
982 {
983 	char *resultbuf = PR_GetTempString();
984 	char *result = resultbuf;
985 	const char *search = G_STRING(OFS_PARM0);
986 	const char *replace = G_STRING(OFS_PARM1);
987 	const char *subject = G_STRING(OFS_PARM2);
988 	int searchlen = strlen(search);
989 	int replacelen = strlen(replace);
990 
991 	if (searchlen)
992 	{
993 		while (*subject && result < resultbuf + sizeof(resultbuf) - replacelen - 2)
994 		{
995 			//UTF-8-FIXME: case insensitivity is awkward...
996 			if (!q_strncasecmp(subject, search, searchlen))
997 			{
998 				subject += searchlen;
999 				memcpy(result, replace, replacelen);
1000 				result += replacelen;
1001 			}
1002 			else
1003 				*result++ = *subject++;
1004 		}
1005 		*result = 0;
1006 		G_INT(OFS_RETURN) = PR_SetEngineString(resultbuf);
1007 	}
1008 	else
1009 		G_INT(OFS_RETURN) = PR_SetEngineString(subject);
1010 }
1011 
1012 
PF_sprintf_internal(const char * s,int firstarg,char * outbuf,int outbuflen)1013 static void PF_sprintf_internal (const char *s, int firstarg, char *outbuf, int outbuflen)
1014 {
1015 	const char *s0;
1016 	char *o = outbuf, *end = outbuf + outbuflen, *err;
1017 	int width, precision, thisarg, flags;
1018 	char formatbuf[16];
1019 	char *f;
1020 	int argpos = firstarg;
1021 	int isfloat;
1022 	static int dummyivec[3] = {0, 0, 0};
1023 	static float dummyvec[3] = {0, 0, 0};
1024 
1025 #define PRINTF_ALTERNATE 1
1026 #define PRINTF_ZEROPAD 2
1027 #define PRINTF_LEFT 4
1028 #define PRINTF_SPACEPOSITIVE 8
1029 #define PRINTF_SIGNPOSITIVE 16
1030 
1031 	formatbuf[0] = '%';
1032 
1033 #define GETARG_FLOAT(a) (((a)>=firstarg && (a)<qcvm->argc) ? (G_FLOAT(OFS_PARM0 + 3 * (a))) : 0)
1034 #define GETARG_VECTOR(a) (((a)>=firstarg && (a)<qcvm->argc) ? (G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyvec)
1035 #define GETARG_INT(a) (((a)>=firstarg && (a)<qcvm->argc) ? (G_INT(OFS_PARM0 + 3 * (a))) : 0)
1036 #define GETARG_INTVECTOR(a) (((a)>=firstarg && (a)<qcvm->argc) ? ((int*) G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyivec)
1037 #define GETARG_STRING(a) (((a)>=firstarg && (a)<qcvm->argc) ? (G_STRING(OFS_PARM0 + 3 * (a))) : "")
1038 
1039 	for(;;)
1040 	{
1041 		s0 = s;
1042 		switch(*s)
1043 		{
1044 			case 0:
1045 				goto finished;
1046 			case '%':
1047 				++s;
1048 
1049 				if(*s == '%')
1050 					goto verbatim;
1051 
1052 				// complete directive format:
1053 				// %3$*1$.*2$ld
1054 
1055 				width = -1;
1056 				precision = -1;
1057 				thisarg = -1;
1058 				flags = 0;
1059 				isfloat = -1;
1060 
1061 				// is number following?
1062 				if(*s >= '0' && *s <= '9')
1063 				{
1064 					width = strtol(s, &err, 10);
1065 					if(!err)
1066 					{
1067 						Con_Warning("PF_sprintf: bad format string: %s\n", s0);
1068 						goto finished;
1069 					}
1070 					if(*err == '$')
1071 					{
1072 						thisarg = width + (firstarg-1);
1073 						width = -1;
1074 						s = err + 1;
1075 					}
1076 					else
1077 					{
1078 						if(*s == '0')
1079 						{
1080 							flags |= PRINTF_ZEROPAD;
1081 							if(width == 0)
1082 								width = -1; // it was just a flag
1083 						}
1084 						s = err;
1085 					}
1086 				}
1087 
1088 				if(width < 0)
1089 				{
1090 					for(;;)
1091 					{
1092 						switch(*s)
1093 						{
1094 							case '#': flags |= PRINTF_ALTERNATE; break;
1095 							case '0': flags |= PRINTF_ZEROPAD; break;
1096 							case '-': flags |= PRINTF_LEFT; break;
1097 							case ' ': flags |= PRINTF_SPACEPOSITIVE; break;
1098 							case '+': flags |= PRINTF_SIGNPOSITIVE; break;
1099 							default:
1100 								goto noflags;
1101 						}
1102 						++s;
1103 					}
1104 noflags:
1105 					if(*s == '*')
1106 					{
1107 						++s;
1108 						if(*s >= '0' && *s <= '9')
1109 						{
1110 							width = strtol(s, &err, 10);
1111 							if(!err || *err != '$')
1112 							{
1113 								Con_Warning("PF_sprintf: invalid format string: %s\n", s0);
1114 								goto finished;
1115 							}
1116 							s = err + 1;
1117 						}
1118 						else
1119 							width = argpos++;
1120 						width = GETARG_FLOAT(width);
1121 						if(width < 0)
1122 						{
1123 							flags |= PRINTF_LEFT;
1124 							width = -width;
1125 						}
1126 					}
1127 					else if(*s >= '0' && *s <= '9')
1128 					{
1129 						width = strtol(s, &err, 10);
1130 						if(!err)
1131 						{
1132 							Con_Warning("PF_sprintf: invalid format string: %s\n", s0);
1133 							goto finished;
1134 						}
1135 						s = err;
1136 						if(width < 0)
1137 						{
1138 							flags |= PRINTF_LEFT;
1139 							width = -width;
1140 						}
1141 					}
1142 					// otherwise width stays -1
1143 				}
1144 
1145 				if(*s == '.')
1146 				{
1147 					++s;
1148 					if(*s == '*')
1149 					{
1150 						++s;
1151 						if(*s >= '0' && *s <= '9')
1152 						{
1153 							precision = strtol(s, &err, 10);
1154 							if(!err || *err != '$')
1155 							{
1156 								Con_Warning("PF_sprintf: invalid format string: %s\n", s0);
1157 								goto finished;
1158 							}
1159 							s = err + 1;
1160 						}
1161 						else
1162 							precision = argpos++;
1163 						precision = GETARG_FLOAT(precision);
1164 					}
1165 					else if(*s >= '0' && *s <= '9')
1166 					{
1167 						precision = strtol(s, &err, 10);
1168 						if(!err)
1169 						{
1170 							Con_Warning("PF_sprintf: invalid format string: %s\n", s0);
1171 							goto finished;
1172 						}
1173 						s = err;
1174 					}
1175 					else
1176 					{
1177 						Con_Warning("PF_sprintf: invalid format string: %s\n", s0);
1178 						goto finished;
1179 					}
1180 				}
1181 
1182 				for(;;)
1183 				{
1184 					switch(*s)
1185 					{
1186 						case 'h': isfloat = 1; break;
1187 						case 'l': isfloat = 0; break;
1188 						case 'L': isfloat = 0; break;
1189 						case 'j': break;
1190 						case 'z': break;
1191 						case 't': break;
1192 						default:
1193 							goto nolength;
1194 					}
1195 					++s;
1196 				}
1197 nolength:
1198 
1199 				// now s points to the final directive char and is no longer changed
1200 				if (*s == 'p' || *s == 'P')
1201 				{
1202 					//%p is slightly different from %x.
1203 					//always 8-bytes wide with 0 padding, always ints.
1204 					flags |= PRINTF_ZEROPAD;
1205 					if (width < 0) width = 8;
1206 					if (isfloat < 0) isfloat = 0;
1207 				}
1208 				else if (*s == 'i')
1209 				{
1210 					//%i defaults to ints, not floats.
1211 					if(isfloat < 0) isfloat = 0;
1212 				}
1213 
1214 				//assume floats, not ints.
1215 				if(isfloat < 0)
1216 					isfloat = 1;
1217 
1218 				if(thisarg < 0)
1219 					thisarg = argpos++;
1220 
1221 				if(o < end - 1)
1222 				{
1223 					f = &formatbuf[1];
1224 					if(*s != 's' && *s != 'c')
1225 						if(flags & PRINTF_ALTERNATE) *f++ = '#';
1226 					if(flags & PRINTF_ZEROPAD) *f++ = '0';
1227 					if(flags & PRINTF_LEFT) *f++ = '-';
1228 					if(flags & PRINTF_SPACEPOSITIVE) *f++ = ' ';
1229 					if(flags & PRINTF_SIGNPOSITIVE) *f++ = '+';
1230 					*f++ = '*';
1231 					if(precision >= 0)
1232 					{
1233 						*f++ = '.';
1234 						*f++ = '*';
1235 					}
1236 					if (*s == 'p')
1237 						*f++ = 'x';
1238 					else if (*s == 'P')
1239 						*f++ = 'X';
1240 					else if (*s == 'S')
1241 						*f++ = 's';
1242 					else
1243 						*f++ = *s;
1244 					*f++ = 0;
1245 
1246 					if(width < 0) // not set
1247 						width = 0;
1248 
1249 					switch(*s)
1250 					{
1251 						case 'd': case 'i':
1252 							if(precision < 0) // not set
1253 								q_snprintf(o, end - o, formatbuf, width, (isfloat ? (int) GETARG_FLOAT(thisarg) : (int) GETARG_INT(thisarg)));
1254 							else
1255 								q_snprintf(o, end - o, formatbuf, width, precision, (isfloat ? (int) GETARG_FLOAT(thisarg) : (int) GETARG_INT(thisarg)));
1256 							o += strlen(o);
1257 							break;
1258 						case 'o': case 'u': case 'x': case 'X': case 'p': case 'P':
1259 							if(precision < 0) // not set
1260 								q_snprintf(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)));
1261 							else
1262 								q_snprintf(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)));
1263 							o += strlen(o);
1264 							break;
1265 						case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
1266 							if(precision < 0) // not set
1267 								q_snprintf(o, end - o, formatbuf, width, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg)));
1268 							else
1269 								q_snprintf(o, end - o, formatbuf, width, precision, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg)));
1270 							o += strlen(o);
1271 							break;
1272 						case 'v': case 'V':
1273 							f[-2] += 'g' - 'v';
1274 							if(precision < 0) // not set
1275 								q_snprintf(o, end - o, va("%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf),
1276 									width, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]),
1277 									width, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]),
1278 									width, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2])
1279 								);
1280 							else
1281 								q_snprintf(o, end - o, va("%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf),
1282 									width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]),
1283 									width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]),
1284 									width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2])
1285 								);
1286 							o += strlen(o);
1287 							break;
1288 						case 'c':
1289 							//UTF-8-FIXME: figure it out yourself
1290 //							if(flags & PRINTF_ALTERNATE)
1291 							{
1292 								if(precision < 0) // not set
1293 									q_snprintf(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)));
1294 								else
1295 									q_snprintf(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)));
1296 								o += strlen(o);
1297 							}
1298 /*							else
1299 							{
1300 								unsigned int c = (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg));
1301 								char charbuf16[16];
1302 								const char *buf = u8_encodech(c, NULL, charbuf16);
1303 								if(!buf)
1304 									buf = "";
1305 								if(precision < 0) // not set
1306 									precision = end - o - 1;
1307 								o += u8_strpad(o, end - o, buf, (flags & PRINTF_LEFT) != 0, width, precision);
1308 							}
1309 */							break;
1310 						case 'S':
1311 							{	//tokenizable string
1312 								const char *quotedarg = GETARG_STRING(thisarg);
1313 
1314 								//try and escape it... hopefully it won't get truncated by precision limits...
1315 								char quotedbuf[65536];
1316 								size_t l;
1317 								l = strlen(quotedarg);
1318 								if (strchr(quotedarg, '\"') || strchr(quotedarg, '\n') || strchr(quotedarg, '\r') || l+3 >= sizeof(quotedbuf))
1319 								{	//our escapes suck...
1320 									Con_Warning("PF_sprintf: unable to safely escape arg: %s\n", s0);
1321 									quotedarg="";
1322 								}
1323 								quotedbuf[0] = '\"';
1324 								memcpy(quotedbuf+1, quotedarg, l);
1325 								quotedbuf[1+l] = '\"';
1326 								quotedbuf[1+l+1] = 0;
1327 								quotedarg = quotedbuf;
1328 
1329 								//UTF-8-FIXME: figure it out yourself
1330 //								if(flags & PRINTF_ALTERNATE)
1331 								{
1332 									if(precision < 0) // not set
1333 										q_snprintf(o, end - o, formatbuf, width, quotedarg);
1334 									else
1335 										q_snprintf(o, end - o, formatbuf, width, precision, quotedarg);
1336 									o += strlen(o);
1337 								}
1338 /*								else
1339 								{
1340 									if(precision < 0) // not set
1341 										precision = end - o - 1;
1342 									o += u8_strpad(o, end - o, quotedarg, (flags & PRINTF_LEFT) != 0, width, precision);
1343 								}
1344 */							}
1345 							break;
1346 						case 's':
1347 							//UTF-8-FIXME: figure it out yourself
1348 //							if(flags & PRINTF_ALTERNATE)
1349 							{
1350 								if(precision < 0) // not set
1351 									q_snprintf(o, end - o, formatbuf, width, GETARG_STRING(thisarg));
1352 								else
1353 									q_snprintf(o, end - o, formatbuf, width, precision, GETARG_STRING(thisarg));
1354 								o += strlen(o);
1355 							}
1356 /*							else
1357 							{
1358 								if(precision < 0) // not set
1359 									precision = end - o - 1;
1360 								o += u8_strpad(o, end - o, GETARG_STRING(thisarg), (flags & PRINTF_LEFT) != 0, width, precision);
1361 							}
1362 */							break;
1363 						default:
1364 							Con_Warning("PF_sprintf: invalid format string: %s\n", s0);
1365 							goto finished;
1366 					}
1367 				}
1368 				++s;
1369 				break;
1370 			default:
1371 verbatim:
1372 				if(o < end - 1)
1373 					*o++ = *s;
1374 				s++;
1375 				break;
1376 		}
1377 	}
1378 finished:
1379 	*o = 0;
1380 }
1381 
PF_sprintf(void)1382 static void PF_sprintf(void)
1383 {
1384 	char *outbuf = PR_GetTempString();
1385 	PF_sprintf_internal(G_STRING(OFS_PARM0), 1, outbuf, STRINGTEMP_LENGTH);
1386 	G_INT(OFS_RETURN) = PR_SetEngineString(outbuf);
1387 }
1388 
1389 //string tokenizing (gah)
1390 #define MAXQCTOKENS 64
1391 static struct {
1392 	char *token;
1393 	unsigned int start;
1394 	unsigned int end;
1395 } qctoken[MAXQCTOKENS];
1396 static unsigned int qctoken_count;
1397 
tokenize_flush(void)1398 static void tokenize_flush(void)
1399 {
1400 	while(qctoken_count > 0)
1401 	{
1402 		qctoken_count--;
1403 		free(qctoken[qctoken_count].token);
1404 	}
1405 	qctoken_count = 0;
1406 }
1407 
PF_ArgC(void)1408 static void PF_ArgC(void)
1409 {
1410 	G_FLOAT(OFS_RETURN) = qctoken_count;
1411 }
1412 
tokenizeqc(const char * str,qboolean dpfuckage)1413 static int tokenizeqc(const char *str, qboolean dpfuckage)
1414 {
1415 	//FIXME: if dpfuckage, then we should handle punctuation specially, as well as /*.
1416 	const char *start = str;
1417 	while(qctoken_count > 0)
1418 	{
1419 		qctoken_count--;
1420 		free(qctoken[qctoken_count].token);
1421 	}
1422 	qctoken_count = 0;
1423 	while (qctoken_count < MAXQCTOKENS)
1424 	{
1425 		/*skip whitespace here so the token's start is accurate*/
1426 		while (*str && *(const unsigned char*)str <= ' ')
1427 			str++;
1428 
1429 		if (!*str)
1430 			break;
1431 
1432 		qctoken[qctoken_count].start = str - start;
1433 //		if (dpfuckage)
1434 //			str = COM_ParseDPFuckage(str);
1435 //		else
1436 			str = COM_Parse(str);
1437 		if (!str)
1438 			break;
1439 
1440 		qctoken[qctoken_count].token = strdup(com_token);
1441 
1442 		qctoken[qctoken_count].end = str - start;
1443 		qctoken_count++;
1444 	}
1445 	return qctoken_count;
1446 }
1447 
1448 /*KRIMZON_SV_PARSECLIENTCOMMAND added these two - note that for compatibility with DP, this tokenize builtin is veeery vauge and doesn't match the console*/
PF_Tokenize(void)1449 static void PF_Tokenize(void)
1450 {
1451 	G_FLOAT(OFS_RETURN) = tokenizeqc(G_STRING(OFS_PARM0), true);
1452 }
1453 
PF_tokenize_console(void)1454 static void PF_tokenize_console(void)
1455 {
1456 	G_FLOAT(OFS_RETURN) = tokenizeqc(G_STRING(OFS_PARM0), false);
1457 }
1458 
PF_tokenizebyseparator(void)1459 static void PF_tokenizebyseparator(void)
1460 {
1461 	const char *str = G_STRING(OFS_PARM0);
1462 	const char *sep[7];
1463 	int seplen[7];
1464 	int seps = 0, s;
1465 	const char *start = str;
1466 	int tlen;
1467 	qboolean found = true;
1468 
1469 	while (seps < qcvm->argc - 1 && seps < 7)
1470 	{
1471 		sep[seps] = G_STRING(OFS_PARM1 + seps*3);
1472 		seplen[seps] = strlen(sep[seps]);
1473 		seps++;
1474 	}
1475 
1476 	tokenize_flush();
1477 
1478 	qctoken[qctoken_count].start = 0;
1479 	if (*str)
1480 	for(;;)
1481 	{
1482 		found = false;
1483 		/*see if its a separator*/
1484 		if (!*str)
1485 		{
1486 			qctoken[qctoken_count].end = str - start;
1487 			found = true;
1488 		}
1489 		else
1490 		{
1491 			for (s = 0; s < seps; s++)
1492 			{
1493 				if (!strncmp(str, sep[s], seplen[s]))
1494 				{
1495 					qctoken[qctoken_count].end = str - start;
1496 					str += seplen[s];
1497 					found = true;
1498 					break;
1499 				}
1500 			}
1501 		}
1502 		/*it was, split it out*/
1503 		if (found)
1504 		{
1505 			tlen = qctoken[qctoken_count].end - qctoken[qctoken_count].start;
1506 			qctoken[qctoken_count].token = malloc(tlen + 1);
1507 			memcpy(qctoken[qctoken_count].token, start + qctoken[qctoken_count].start, tlen);
1508 			qctoken[qctoken_count].token[tlen] = 0;
1509 
1510 			qctoken_count++;
1511 
1512 			if (*str && qctoken_count < MAXQCTOKENS)
1513 				qctoken[qctoken_count].start = str - start;
1514 			else
1515 				break;
1516 		}
1517 		str++;
1518 	}
1519 	G_FLOAT(OFS_RETURN) = qctoken_count;
1520 }
1521 
PF_argv_start_index(void)1522 static void PF_argv_start_index(void)
1523 {
1524 	int idx = G_FLOAT(OFS_PARM0);
1525 
1526 	/*negative indexes are relative to the end*/
1527 	if (idx < 0)
1528 		idx += qctoken_count;
1529 
1530 	if ((unsigned int)idx >= qctoken_count)
1531 		G_FLOAT(OFS_RETURN) = -1;
1532 	else
1533 		G_FLOAT(OFS_RETURN) = qctoken[idx].start;
1534 }
1535 
PF_argv_end_index(void)1536 static void PF_argv_end_index(void)
1537 {
1538 	int idx = G_FLOAT(OFS_PARM0);
1539 
1540 	/*negative indexes are relative to the end*/
1541 	if (idx < 0)
1542 		idx += qctoken_count;
1543 
1544 	if ((unsigned int)idx >= qctoken_count)
1545 		G_FLOAT(OFS_RETURN) = -1;
1546 	else
1547 		G_FLOAT(OFS_RETURN) = qctoken[idx].end;
1548 }
1549 
PF_ArgV(void)1550 static void PF_ArgV(void)
1551 {
1552 	int idx = G_FLOAT(OFS_PARM0);
1553 
1554 	/*negative indexes are relative to the end*/
1555 	if (idx < 0)
1556 		idx += qctoken_count;
1557 
1558 	if ((unsigned int)idx >= qctoken_count)
1559 		G_INT(OFS_RETURN) = 0;
1560 	else
1561 	{
1562 		char *ret = PR_GetTempString();
1563 		q_strlcpy(ret, qctoken[idx].token, STRINGTEMP_LENGTH);
1564 		G_INT(OFS_RETURN) = PR_SetEngineString(ret);
1565 	}
1566 }
1567 
1568 //conversions (mostly string)
PF_strtoupper(void)1569 static void PF_strtoupper(void)
1570 {
1571 	const char *in = G_STRING(OFS_PARM0);
1572 	char *out, *result = PR_GetTempString();
1573 	for (out = result; *in && out < result+STRINGTEMP_LENGTH-1;)
1574 		*out++ = q_toupper(*in++);
1575 	*out = 0;
1576 	G_INT(OFS_RETURN) = PR_SetEngineString(result);
1577 }
PF_strtolower(void)1578 static void PF_strtolower(void)
1579 {
1580 	const char *in = G_STRING(OFS_PARM0);
1581 	char *out, *result = PR_GetTempString();
1582 	for (out = result; *in && out < result+STRINGTEMP_LENGTH-1;)
1583 		*out++ = q_tolower(*in++);
1584 	*out = 0;
1585 	G_INT(OFS_RETURN) = PR_SetEngineString(result);
1586 }
1587 #include <time.h>
PF_strftime(void)1588 static void PF_strftime(void)
1589 {
1590 	const char *in = G_STRING(OFS_PARM1);
1591 	char *result = PR_GetTempString();
1592 
1593 	time_t curtime;
1594 	struct tm *tm;
1595 
1596 	curtime = time(NULL);
1597 
1598 	if (G_FLOAT(OFS_PARM0))
1599 		tm = localtime(&curtime);
1600 	else
1601 		tm = gmtime(&curtime);
1602 
1603 #ifdef _WIN32
1604 	//msvc sucks. this is a crappy workaround.
1605 	if (!strcmp(in, "%R"))
1606 		in = "%H:%M";
1607 	else if (!strcmp(in, "%F"))
1608 		in = "%Y-%m-%d";
1609 #endif
1610 
1611 	strftime(result, STRINGTEMP_LENGTH, in, tm);
1612 
1613 	G_INT(OFS_RETURN) = PR_SetEngineString(result);
1614 }
PF_stof(void)1615 static void PF_stof(void)
1616 {
1617 	G_FLOAT(OFS_RETURN) = atof(G_STRING(OFS_PARM0));
1618 }
PF_stov(void)1619 static void PF_stov(void)
1620 {
1621 	const char *s = G_STRING(OFS_PARM0);
1622 	s = COM_Parse(s);
1623 	G_VECTOR(OFS_RETURN)[0] = atof(com_token);
1624 	s = COM_Parse(s);
1625 	G_VECTOR(OFS_RETURN)[1] = atof(com_token);
1626 	s = COM_Parse(s);
1627 	G_VECTOR(OFS_RETURN)[2] = atof(com_token);
1628 }
PF_stoi(void)1629 static void PF_stoi(void)
1630 {
1631 	G_INT(OFS_RETURN) = atoi(G_STRING(OFS_PARM0));
1632 }
PF_itos(void)1633 static void PF_itos(void)
1634 {
1635 	char *result = PR_GetTempString();
1636 	q_snprintf(result, STRINGTEMP_LENGTH, "%i", G_INT(OFS_PARM0));
1637 	G_INT(OFS_RETURN) = PR_SetEngineString(result);
1638 }
PF_etos(void)1639 static void PF_etos(void)
1640 {	//yes, this is lame
1641 	char *result = PR_GetTempString();
1642 	q_snprintf(result, STRINGTEMP_LENGTH, "entity %i", G_EDICTNUM(OFS_PARM0));
1643 	G_INT(OFS_RETURN) = PR_SetEngineString(result);
1644 }
PF_stoh(void)1645 static void PF_stoh(void)
1646 {
1647 	G_INT(OFS_RETURN) = strtoul(G_STRING(OFS_PARM0), NULL, 16);
1648 }
PF_htos(void)1649 static void PF_htos(void)
1650 {
1651 	char *result = PR_GetTempString();
1652 	q_snprintf(result, STRINGTEMP_LENGTH, "%x", G_INT(OFS_PARM0));
1653 	G_INT(OFS_RETURN) = PR_SetEngineString(result);
1654 }
PF_ftoi(void)1655 static void PF_ftoi(void)
1656 {
1657 	G_INT(OFS_RETURN) = G_FLOAT(OFS_PARM0);
1658 }
PF_itof(void)1659 static void PF_itof(void)
1660 {
1661 	G_FLOAT(OFS_RETURN) = G_INT(OFS_PARM0);
1662 }
1663 
1664 //collision stuff
PF_tracebox(void)1665 static void PF_tracebox(void)
1666 {	//alternative version of traceline that just passes on two extra args. trivial really.
1667 	float	*v1, *mins, *maxs, *v2;
1668 	trace_t	trace;
1669 	int	nomonsters;
1670 	edict_t	*ent;
1671 
1672 	v1 = G_VECTOR(OFS_PARM0);
1673 	mins = G_VECTOR(OFS_PARM1);
1674 	maxs = G_VECTOR(OFS_PARM2);
1675 	v2 = G_VECTOR(OFS_PARM3);
1676 	nomonsters = G_FLOAT(OFS_PARM4);
1677 	ent = G_EDICT(OFS_PARM5);
1678 
1679 	/* FIXME FIXME FIXME: Why do we hit this with certain progs.dat ?? */
1680 	if (developer.value) {
1681 	  if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2]) ||
1682 	      IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2])) {
1683 	    Con_Warning ("NAN in traceline:\nv1(%f %f %f) v2(%f %f %f)\nentity %d\n",
1684 		      v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], NUM_FOR_EDICT(ent));
1685 	  }
1686 	}
1687 
1688 	if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2]))
1689 		v1[0] = v1[1] = v1[2] = 0;
1690 	if (IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2]))
1691 		v2[0] = v2[1] = v2[2] = 0;
1692 
1693 	trace = SV_Move (v1, mins, maxs, v2, nomonsters, ent);
1694 
1695 	pr_global_struct->trace_allsolid = trace.allsolid;
1696 	pr_global_struct->trace_startsolid = trace.startsolid;
1697 	pr_global_struct->trace_fraction = trace.fraction;
1698 	pr_global_struct->trace_inwater = trace.inwater;
1699 	pr_global_struct->trace_inopen = trace.inopen;
1700 	VectorCopy (trace.endpos, pr_global_struct->trace_endpos);
1701 	VectorCopy (trace.plane.normal, pr_global_struct->trace_plane_normal);
1702 	pr_global_struct->trace_plane_dist =  trace.plane.dist;
1703 	if (trace.ent)
1704 		pr_global_struct->trace_ent = EDICT_TO_PROG(trace.ent);
1705 	else
1706 		pr_global_struct->trace_ent = EDICT_TO_PROG(qcvm->edicts);
1707 }
PF_TraceToss(void)1708 static void PF_TraceToss(void)
1709 {
1710 	extern cvar_t sv_maxvelocity, sv_gravity;
1711 	int i;
1712 	float gravity;
1713 	vec3_t move, end;
1714 	trace_t trace;
1715 	eval_t	*val;
1716 
1717 	vec3_t origin, velocity;
1718 
1719 	edict_t *tossent, *ignore;
1720 	tossent = G_EDICT(OFS_PARM0);
1721 	if (tossent == qcvm->edicts)
1722 		Con_Warning("tracetoss: can not use world entity\n");
1723 	ignore = G_EDICT(OFS_PARM1);
1724 
1725 	val = GetEdictFieldValue(tossent, qcvm->extfields.gravity);
1726 	if (val && val->_float)
1727 		gravity = val->_float;
1728 	else
1729 		gravity = 1;
1730 	gravity *= sv_gravity.value * 0.05;
1731 
1732 	VectorCopy (tossent->v.origin, origin);
1733 	VectorCopy (tossent->v.velocity, velocity);
1734 
1735 	SV_CheckVelocity (tossent);
1736 
1737 	for (i = 0;i < 200;i++) // LordHavoc: sanity check; never trace more than 10 seconds
1738 	{
1739 		velocity[2] -= gravity;
1740 		VectorScale (velocity, 0.05, move);
1741 		VectorAdd (origin, move, end);
1742 		trace = SV_Move (origin, tossent->v.mins, tossent->v.maxs, end, MOVE_NORMAL, tossent);
1743 		VectorCopy (trace.endpos, origin);
1744 
1745 		if (trace.fraction < 1 && trace.ent && trace.ent != ignore)
1746 			break;
1747 
1748 		if (VectorLength(velocity) > sv_maxvelocity.value)
1749 		{
1750 //			Con_DPrintf("Slowing %s\n", PR_GetString(w->progs, tossent->v->classname));
1751 			VectorScale (velocity, sv_maxvelocity.value/VectorLength(velocity), velocity);
1752 		}
1753 	}
1754 
1755 	trace.fraction = 0; // not relevant
1756 
1757 	//and return those as globals.
1758 	pr_global_struct->trace_allsolid = trace.allsolid;
1759 	pr_global_struct->trace_startsolid = trace.startsolid;
1760 	pr_global_struct->trace_fraction = trace.fraction;
1761 	pr_global_struct->trace_inwater = trace.inwater;
1762 	pr_global_struct->trace_inopen = trace.inopen;
1763 	VectorCopy (trace.endpos, pr_global_struct->trace_endpos);
1764 	VectorCopy (trace.plane.normal, pr_global_struct->trace_plane_normal);
1765 	pr_global_struct->trace_plane_dist =  trace.plane.dist;
1766 	if (trace.ent)
1767 		pr_global_struct->trace_ent = EDICT_TO_PROG(trace.ent);
1768 	else
1769 		pr_global_struct->trace_ent = EDICT_TO_PROG(qcvm->edicts);
1770 }
1771 
1772 //model stuff
1773 void SetMinMaxSize (edict_t *e, float *minvec, float *maxvec, qboolean rotate);
PF_sv_setmodelindex(void)1774 static void PF_sv_setmodelindex(void)
1775 {
1776 	edict_t	*e			= G_EDICT(OFS_PARM0);
1777 	unsigned int newidx	= G_FLOAT(OFS_PARM1);
1778 	qmodel_t *mod = qcvm->GetModel(newidx);
1779 	e->v.model = (newidx<MAX_MODELS)?PR_SetEngineString(sv.model_precache[newidx]):0;
1780 	e->v.modelindex = newidx;
1781 
1782 	if (mod)
1783 	//johnfitz -- correct physics cullboxes for bmodels
1784 	{
1785 		if (mod->type == mod_brush || !sv_gameplayfix_setmodelrealbox.value)
1786 			SetMinMaxSize (e, mod->clipmins, mod->clipmaxs, true);
1787 		else
1788 			SetMinMaxSize (e, mod->mins, mod->maxs, true);
1789 	}
1790 	//johnfitz
1791 	else
1792 		SetMinMaxSize (e, vec3_origin, vec3_origin, true);
1793 }
PF_cl_setmodelindex(void)1794 static void PF_cl_setmodelindex(void)
1795 {
1796 	edict_t	*e		= G_EDICT(OFS_PARM0);
1797 	int newidx		= G_FLOAT(OFS_PARM1);
1798 	qmodel_t *mod = qcvm->GetModel(newidx);
1799 	e->v.model = mod?PR_SetEngineString(mod->name):0;	//FIXME: is this going to cause issues with vid_restart?
1800 	e->v.modelindex = newidx;
1801 
1802 	if (mod)
1803 	//johnfitz -- correct physics cullboxes for bmodels
1804 	{
1805 		if (mod->type == mod_brush || !sv_gameplayfix_setmodelrealbox.value)
1806 			SetMinMaxSize (e, mod->clipmins, mod->clipmaxs, true);
1807 		else
1808 			SetMinMaxSize (e, mod->mins, mod->maxs, true);
1809 	}
1810 	//johnfitz
1811 	else
1812 		SetMinMaxSize (e, vec3_origin, vec3_origin, true);
1813 }
1814 
PF_frameforname(void)1815 static void PF_frameforname(void)
1816 {
1817 	unsigned int modelindex	= G_FLOAT(OFS_PARM0);
1818 	const char *framename	= G_STRING(OFS_PARM1);
1819 	qmodel_t *mod = qcvm->GetModel(modelindex);
1820 	aliashdr_t *alias;
1821 
1822 	G_FLOAT(OFS_RETURN) = -1;
1823 	if (mod && mod->type == mod_alias && (alias = Mod_Extradata(mod)))
1824 	{
1825 		int i;
1826 		for (i = 0; i < alias->numframes; i++)
1827 		{
1828 			if (!strcmp(alias->frames[i].name, framename))
1829 			{
1830 				G_FLOAT(OFS_RETURN) = i;
1831 				break;
1832 			}
1833 		}
1834 	}
1835 }
PF_frametoname(void)1836 static void PF_frametoname(void)
1837 {
1838 	unsigned int modelindex	= G_FLOAT(OFS_PARM0);
1839 	unsigned int framenum	= G_FLOAT(OFS_PARM1);
1840 	qmodel_t *mod = qcvm->GetModel(modelindex);
1841 	aliashdr_t *alias;
1842 
1843 	if (mod && mod->type == mod_alias && (alias = Mod_Extradata(mod)) && framenum < (unsigned int)alias->numframes)
1844 		G_INT(OFS_RETURN) = PR_SetEngineString(alias->frames[framenum].name);
1845 	else
1846 		G_INT(OFS_RETURN) = 0;
1847 }
PF_frameduration(void)1848 static void PF_frameduration(void)
1849 {
1850 	unsigned int modelindex	= G_FLOAT(OFS_PARM0);
1851 	unsigned int framenum	= G_FLOAT(OFS_PARM1);
1852 	qmodel_t *mod = qcvm->GetModel(modelindex);
1853 	aliashdr_t *alias;
1854 
1855 	if (mod && mod->type == mod_alias && (alias = Mod_Extradata(mod)) && framenum < (unsigned int)alias->numframes)
1856 		G_FLOAT(OFS_RETURN) = alias->frames[framenum].numposes * alias->frames[framenum].interval;
1857 }
PF_getsurfacenumpoints(void)1858 static void PF_getsurfacenumpoints(void)
1859 {
1860 	edict_t	*ed				= G_EDICT(OFS_PARM0);
1861 	unsigned int surfidx	= G_FLOAT(OFS_PARM1);
1862 	unsigned int modelindex = ed->v.modelindex;
1863 	qmodel_t *mod = qcvm->GetModel(modelindex);
1864 
1865 	if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces)
1866 	{
1867 		surfidx += mod->firstmodelsurface;
1868 		G_FLOAT(OFS_RETURN) = mod->surfaces[surfidx].numedges;
1869 	}
1870 	else
1871 		G_FLOAT(OFS_RETURN) = 0;
1872 }
PF_getsurfacevertex(qmodel_t * mod,msurface_t * surf,unsigned int vert)1873 static mvertex_t *PF_getsurfacevertex(qmodel_t *mod, msurface_t *surf, unsigned int vert)
1874 {
1875 	signed int edge = mod->surfedges[vert+surf->firstedge];
1876 	if (edge >= 0)
1877 		return &mod->vertexes[mod->edges[edge].v[0]];
1878 	else
1879 		return &mod->vertexes[mod->edges[-edge].v[1]];
1880 }
PF_getsurfacepoint(void)1881 static void PF_getsurfacepoint(void)
1882 {
1883 	edict_t	*ed				= G_EDICT(OFS_PARM0);
1884 	unsigned int surfidx	= G_FLOAT(OFS_PARM1);
1885 	unsigned int point		= G_FLOAT(OFS_PARM2);
1886 	qmodel_t *mod = qcvm->GetModel(ed->v.modelindex);
1887 
1888 	if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces && point < (unsigned int)mod->surfaces[surfidx].numedges)
1889 	{
1890 		mvertex_t *v = PF_getsurfacevertex(mod, &mod->surfaces[surfidx+mod->firstmodelsurface], point);
1891 		VectorCopy(v->position, G_VECTOR(OFS_RETURN));
1892 	}
1893 	else
1894 	{
1895 		G_FLOAT(OFS_RETURN+0) = 0;
1896 		G_FLOAT(OFS_RETURN+1) = 0;
1897 		G_FLOAT(OFS_RETURN+2) = 0;
1898 	}
1899 }
PF_getsurfacenumtriangles(void)1900 static void PF_getsurfacenumtriangles(void)
1901 {	//for q3bsp compat (which this engine doesn't support, so its fairly simple)
1902 	edict_t	*ed				= G_EDICT(OFS_PARM0);
1903 	unsigned int surfidx	= G_FLOAT(OFS_PARM1);
1904 	qmodel_t *mod = qcvm->GetModel(ed->v.modelindex);
1905 
1906 	if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces)
1907 		G_FLOAT(OFS_RETURN) = (mod->surfaces[surfidx+mod->firstmodelsurface].numedges-2);	//q1bsp is only triangle fans
1908 	else
1909 		G_FLOAT(OFS_RETURN) = 0;
1910 }
PF_getsurfacetriangle(void)1911 static void PF_getsurfacetriangle(void)
1912 {	//for q3bsp compat (which this engine doesn't support, so its fairly simple)
1913 	edict_t	*ed				= G_EDICT(OFS_PARM0);
1914 	unsigned int surfidx	= G_FLOAT(OFS_PARM1);
1915 	unsigned int triangleidx= G_FLOAT(OFS_PARM2);
1916 	qmodel_t *mod = qcvm->GetModel(ed->v.modelindex);
1917 
1918 	if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces && triangleidx < (unsigned int)mod->surfaces[surfidx].numedges-2)
1919 	{
1920 		G_FLOAT(OFS_RETURN+0) = 0;
1921 		G_FLOAT(OFS_RETURN+1) = triangleidx+1;
1922 		G_FLOAT(OFS_RETURN+2) = triangleidx+2;
1923 	}
1924 	else
1925 	{
1926 		G_FLOAT(OFS_RETURN+0) = 0;
1927 		G_FLOAT(OFS_RETURN+1) = 0;
1928 		G_FLOAT(OFS_RETURN+2) = 0;
1929 	}
1930 }
PF_getsurfacenormal(void)1931 static void PF_getsurfacenormal(void)
1932 {
1933 	edict_t	*ed				= G_EDICT(OFS_PARM0);
1934 	unsigned int surfidx	= G_FLOAT(OFS_PARM1);
1935 	qmodel_t *mod = qcvm->GetModel(ed->v.modelindex);
1936 
1937 	if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces)
1938 	{
1939 		surfidx += mod->firstmodelsurface;
1940 		VectorCopy(mod->surfaces[surfidx].plane->normal, G_VECTOR(OFS_RETURN));
1941 		if (mod->surfaces[surfidx].flags & SURF_PLANEBACK)
1942 			VectorInverse(G_VECTOR(OFS_RETURN));
1943 	}
1944 	else
1945 		G_FLOAT(OFS_RETURN) = 0;
1946 }
PF_getsurfacetexture(void)1947 static void PF_getsurfacetexture(void)
1948 {
1949 	edict_t	*ed				= G_EDICT(OFS_PARM0);
1950 	unsigned int surfidx	= G_FLOAT(OFS_PARM1);
1951 	qmodel_t *mod = qcvm->GetModel(ed->v.modelindex);
1952 
1953 	if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces)
1954 	{
1955 		surfidx += mod->firstmodelsurface;
1956 		G_INT(OFS_RETURN) = PR_SetEngineString(mod->surfaces[surfidx].texinfo->texture->name);
1957 	}
1958 	else
1959 		G_INT(OFS_RETURN) = 0;
1960 }
1961 
1962 #define TriangleNormal(a,b,c,n) ( \
1963 	(n)[0] = ((a)[1] - (b)[1]) * ((c)[2] - (b)[2]) - ((a)[2] - (b)[2]) * ((c)[1] - (b)[1]), \
1964 	(n)[1] = ((a)[2] - (b)[2]) * ((c)[0] - (b)[0]) - ((a)[0] - (b)[0]) * ((c)[2] - (b)[2]), \
1965 	(n)[2] = ((a)[0] - (b)[0]) * ((c)[1] - (b)[1]) - ((a)[1] - (b)[1]) * ((c)[0] - (b)[0]) \
1966 	)
getsurface_clippointpoly(qmodel_t * model,msurface_t * surf,vec3_t point,vec3_t bestcpoint,float bestdist)1967 static float getsurface_clippointpoly(qmodel_t *model, msurface_t *surf, vec3_t point, vec3_t bestcpoint, float bestdist)
1968 {
1969 	int e, edge;
1970 	vec3_t edgedir, edgenormal, cpoint, temp;
1971 	mvertex_t *v1, *v2;
1972 	float dist = DotProduct(point, surf->plane->normal) - surf->plane->dist;
1973 	//don't care about SURF_PLANEBACK, the maths works out the same.
1974 
1975 	if (dist*dist < bestdist)
1976 	{	//within a specific range
1977 		//make sure it's within the poly
1978 		VectorMA(point, dist, surf->plane->normal, cpoint);
1979 		for (e = surf->firstedge+surf->numedges; e > surf->firstedge; edge++)
1980 		{
1981 			edge = model->surfedges[--e];
1982 			if (edge < 0)
1983 			{
1984 				v1 = &model->vertexes[model->edges[-edge].v[0]];
1985 				v2 = &model->vertexes[model->edges[-edge].v[1]];
1986 			}
1987 			else
1988 			{
1989 				v2 = &model->vertexes[model->edges[edge].v[0]];
1990 				v1 = &model->vertexes[model->edges[edge].v[1]];
1991 			}
1992 
1993 			VectorSubtract(v1->position, v2->position, edgedir);
1994 			CrossProduct(edgedir, surf->plane->normal, edgenormal);
1995 			if (!(surf->flags & SURF_PLANEBACK))
1996 			{
1997 				VectorSubtract(vec3_origin, edgenormal, edgenormal);
1998 			}
1999 			VectorNormalize(edgenormal);
2000 
2001 			dist = DotProduct(v1->position, edgenormal) - DotProduct(cpoint, edgenormal);
2002 			if (dist < 0)
2003 				VectorMA(cpoint, dist, edgenormal, cpoint);
2004 		}
2005 
2006 		VectorSubtract(cpoint, point, temp);
2007 		dist = DotProduct(temp, temp);
2008 		if (dist < bestdist)
2009 		{
2010 			bestdist = dist;
2011 			VectorCopy(cpoint, bestcpoint);
2012 		}
2013 	}
2014 	return bestdist;
2015 }
2016 
2017 // #438 float(entity e, vector p) getsurfacenearpoint (DP_QC_GETSURFACE)
PF_getsurfacenearpoint(void)2018 static void PF_getsurfacenearpoint(void)
2019 {
2020 	qmodel_t *model;
2021 	edict_t *ent;
2022 	msurface_t *surf;
2023 	int i;
2024 	float *point;
2025 
2026 	vec3_t cpoint = {0,0,0};
2027 	float bestdist, dist;
2028 	int bestsurf = -1;
2029 
2030 	ent = G_EDICT(OFS_PARM0);
2031 	point = G_VECTOR(OFS_PARM1);
2032 
2033 	G_FLOAT(OFS_RETURN) = -1;
2034 
2035 	model = qcvm->GetModel(ent->v.modelindex);
2036 
2037 	if (!model || model->type != mod_brush || model->needload)
2038 		return;
2039 
2040 	bestdist = 256;
2041 
2042 	//all polies, we can skip parts. special case.
2043 	surf = model->surfaces + model->firstmodelsurface;
2044 	for (i = 0; i < model->nummodelsurfaces; i++, surf++)
2045 	{
2046 		dist = getsurface_clippointpoly(model, surf, point, cpoint, bestdist);
2047 		if (dist < bestdist)
2048 		{
2049 			bestdist = dist;
2050 			bestsurf = i;
2051 		}
2052 	}
2053 	G_FLOAT(OFS_RETURN) = bestsurf;
2054 }
2055 
2056 // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint (DP_QC_GETSURFACE)
PF_getsurfaceclippedpoint(void)2057 static void PF_getsurfaceclippedpoint(void)
2058 {
2059 	qmodel_t *model;
2060 	edict_t *ent;
2061 	msurface_t *surf;
2062 	float *point;
2063 	int surfnum;
2064 
2065 	float *result = G_VECTOR(OFS_RETURN);
2066 
2067 	ent = G_EDICT(OFS_PARM0);
2068 	surfnum = G_FLOAT(OFS_PARM1);
2069 	point = G_VECTOR(OFS_PARM2);
2070 
2071 	VectorCopy(point, result);
2072 
2073 	model = qcvm->GetModel(ent->v.modelindex);
2074 
2075 	if (!model || model->type != mod_brush || model->needload)
2076 		return;
2077 	if (surfnum >= model->nummodelsurfaces)
2078 		return;
2079 
2080 	//all polies, we can skip parts. special case.
2081 	surf = model->surfaces + model->firstmodelsurface + surfnum;
2082 	getsurface_clippointpoly(model, surf, point, result, FLT_MAX);
2083 }
2084 
PF_getsurfacepointattribute(void)2085 static void PF_getsurfacepointattribute(void)
2086 {
2087 	edict_t	*ed				= G_EDICT(OFS_PARM0);
2088 	unsigned int surfidx	= G_FLOAT(OFS_PARM1);
2089 	unsigned int point		= G_FLOAT(OFS_PARM2);
2090 	unsigned int attribute	= G_FLOAT(OFS_PARM3);
2091 
2092 	qmodel_t *mod = qcvm->GetModel(ed->v.modelindex);
2093 
2094 	if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces && point < (unsigned int)mod->surfaces[mod->firstmodelsurface+surfidx].numedges)
2095 	{
2096 		msurface_t *fa = &mod->surfaces[surfidx+mod->firstmodelsurface];
2097 		mvertex_t *v = PF_getsurfacevertex(mod, fa, point);
2098 		switch(attribute)
2099 		{
2100 		default:
2101 			Con_Warning("PF_getsurfacepointattribute: attribute %u not supported\n", attribute);
2102 			G_FLOAT(OFS_RETURN+0) = 0;
2103 			G_FLOAT(OFS_RETURN+1) = 0;
2104 			G_FLOAT(OFS_RETURN+2) = 0;
2105 			break;
2106 		case 0:	//xyz coord
2107 			VectorCopy(v->position, G_VECTOR(OFS_RETURN));
2108 			break;
2109 		case 1:	//s dir
2110 		case 2:	//t dir
2111 			{
2112 				//figure out how similar to the normal it is, and negate any influence, so that its perpendicular
2113 				float sc = -DotProduct(fa->plane->normal, fa->texinfo->vecs[attribute-1]);
2114 				VectorMA(fa->texinfo->vecs[attribute-1], sc, fa->plane->normal, G_VECTOR(OFS_RETURN));
2115 				VectorNormalize(G_VECTOR(OFS_RETURN));
2116 			}
2117 			break;
2118 		case 3: //normal
2119 			VectorCopy(fa->plane->normal, G_VECTOR(OFS_RETURN));
2120 			if (fa->flags & SURF_PLANEBACK)
2121 				VectorInverse(G_VECTOR(OFS_RETURN));
2122 			break;
2123 		case 4: //st coord
2124 			G_FLOAT(OFS_RETURN+0) = (DotProduct(v->position, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3]) / fa->texinfo->texture->width;
2125 			G_FLOAT(OFS_RETURN+1) = (DotProduct(v->position, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3]) / fa->texinfo->texture->height;
2126 			G_FLOAT(OFS_RETURN+2) = 0;
2127 			break;
2128 		case 5: //lmst coord, not actually very useful
2129 			G_FLOAT(OFS_RETURN+0) = (DotProduct(v->position, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3] - fa->texturemins[0] + (fa->light_s+.5)) / LMBLOCK_WIDTH;
2130 			G_FLOAT(OFS_RETURN+1) = (DotProduct(v->position, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3] - fa->texturemins[1] + (fa->light_t+.5)) / LMBLOCK_HEIGHT;
2131 			G_FLOAT(OFS_RETURN+2) = 0;
2132 			break;
2133 		case 6: //colour
2134 			G_FLOAT(OFS_RETURN+0) = 1;
2135 			G_FLOAT(OFS_RETURN+1) = 1;
2136 			G_FLOAT(OFS_RETURN+2) = 1;
2137 			break;
2138 		}
2139 	}
2140 	else
2141 	{
2142 		G_FLOAT(OFS_RETURN+0) = 0;
2143 		G_FLOAT(OFS_RETURN+1) = 0;
2144 		G_FLOAT(OFS_RETURN+2) = 0;
2145 	}
2146 }
PF_sv_getlight(void)2147 static void PF_sv_getlight(void)
2148 {
2149 	qmodel_t *om = cl.worldmodel;
2150 	float *point = G_VECTOR(OFS_PARM0);
2151 
2152 	cl.worldmodel = qcvm->worldmodel;	//R_LightPoint is really clientside, so if its called from ssqc then try to make things work regardless
2153 										//FIXME: d_lightstylevalue isn't set on dedicated servers
2154 
2155 	//FIXME: seems like quakespasm doesn't do lits for model lighting, so we won't either.
2156 	G_FLOAT(OFS_RETURN+0) = G_FLOAT(OFS_RETURN+1) = G_FLOAT(OFS_RETURN+2) = R_LightPoint(point, NULL) / 255.0;
2157 
2158 	cl.worldmodel = om;
2159 }
2160 #define PF_cl_getlight PF_sv_getlight
2161 
2162 //server/client stuff
PF_checkcommand(void)2163 static void PF_checkcommand(void)
2164 {
2165 	const char *name = G_STRING(OFS_PARM0);
2166 	if (Cmd_Exists(name))
2167 		G_FLOAT(OFS_RETURN) = 1;
2168 	else if (Cmd_AliasExists(name))
2169 		G_FLOAT(OFS_RETURN) = 2;
2170 	else if (Cvar_FindVar(name))
2171 		G_FLOAT(OFS_RETURN) = 3;
2172 	else
2173 		G_FLOAT(OFS_RETURN) = 0;
2174 }
PF_clientcommand(void)2175 static void PF_clientcommand(void)
2176 {
2177 	edict_t	*ed				= G_EDICT(OFS_PARM0);
2178 	const char *str			= G_STRING(OFS_PARM1);
2179 	unsigned int i			= NUM_FOR_EDICT(ed)-1;
2180 	if (i < (unsigned int)svs.maxclients && svs.clients[i].active)
2181 	{
2182 		client_t *ohc = host_client;
2183 		host_client = &svs.clients[i];
2184 		Cmd_ExecuteString (str, src_client);
2185 		host_client = ohc;
2186 	}
2187 	else
2188 		Con_Printf("PF_clientcommand: not a client\n");
2189 }
PF_clienttype(void)2190 static void PF_clienttype(void)
2191 {
2192 	edict_t	*ed				= G_EDICT(OFS_PARM0);
2193 	unsigned int i			= NUM_FOR_EDICT(ed)-1;
2194 	if (i >= (unsigned int)svs.maxclients)
2195 	{
2196 		G_FLOAT(OFS_RETURN) = 3;	//CLIENTTYPE_NOTACLIENT
2197 		return;
2198 	}
2199 	if (svs.clients[i].active)
2200 	{
2201 		if (svs.clients[i].netconnection)
2202 			G_FLOAT(OFS_RETURN) = 1;//CLIENTTYPE_REAL;
2203 		else
2204 			G_FLOAT(OFS_RETURN) = 2;//CLIENTTYPE_BOT;
2205 	}
2206 	else
2207 		G_FLOAT(OFS_RETURN) = 0;//CLIENTTYPE_DISCONNECTED;
2208 }
PF_spawnclient(void)2209 static void PF_spawnclient(void)
2210 {
2211 	edict_t *ent;
2212 	unsigned int i;
2213 	if (svs.maxclients)
2214 	for (i = svs.maxclients; i --> 0; )
2215 	{
2216 		if (!svs.clients[i].active)
2217 		{
2218 			svs.clients[i].netconnection = NULL;	//botclients have no net connection, obviously.
2219 			SV_ConnectClient(i);
2220 			svs.clients[i].spawned = true;
2221 			ent = svs.clients[i].edict;
2222 			memset (&ent->v, 0, qcvm->progs->entityfields * 4);
2223 			ent->v.colormap = NUM_FOR_EDICT(ent);
2224 			ent->v.team = (svs.clients[i].colors & 15) + 1;
2225 			ent->v.netname = PR_SetEngineString(svs.clients[i].name);
2226 			RETURN_EDICT(ent);
2227 			return;
2228 		}
2229 	}
2230 	RETURN_EDICT(qcvm->edicts);
2231 }
PF_dropclient(void)2232 static void PF_dropclient(void)
2233 {
2234 	edict_t	*ed				= G_EDICT(OFS_PARM0);
2235 	unsigned int i			= NUM_FOR_EDICT(ed)-1;
2236 	if (i < (unsigned int)svs.maxclients && svs.clients[i].active)
2237 	{	//FIXME: should really set a flag or something, to avoid recursion issues.
2238 		client_t *ohc = host_client;
2239 		host_client = &svs.clients[i];
2240 		SV_DropClient (false);
2241 		host_client = ohc;
2242 	}
2243 }
2244 
2245 //console/cvar stuff
PF_print(void)2246 static void PF_print(void)
2247 {
2248 	int i;
2249 	for (i = 0; i < qcvm->argc; i++)
2250 		Con_Printf("%s", G_STRING(OFS_PARM0 + i*3));
2251 }
PF_cvar_string(void)2252 static void PF_cvar_string(void)
2253 {
2254 	const char *name = G_STRING(OFS_PARM0);
2255 	cvar_t *var = Cvar_FindVar(name);
2256 	if (var && var->string)
2257 	{
2258 		//cvars can easily change values.
2259 		//this would result in leaks/exploits/slowdowns if the qc spams calls to cvar_string+changes.
2260 		//so keep performance consistent, even if this is going to be slower.
2261 		char *temp = PR_GetTempString();
2262 		q_strlcpy(temp, var->string, STRINGTEMP_LENGTH);
2263 		G_INT(OFS_RETURN) = PR_SetEngineString(temp);
2264 	}
2265 	else if (!strcmp(name, "game"))
2266 	{	//game looks like a cvar in most other respects (and is a cvar in fte). let cvar_string work on it as a way to find out the current gamedir.
2267 		char *temp = PR_GetTempString();
2268 		q_strlcpy(temp, COM_GetGameNames(true), STRINGTEMP_LENGTH);
2269 		G_INT(OFS_RETURN) = PR_SetEngineString(temp);
2270 	}
2271 	else
2272 		G_INT(OFS_RETURN) = 0;
2273 }
PF_cvar_defstring(void)2274 static void PF_cvar_defstring(void)
2275 {
2276 	const char *name = G_STRING(OFS_PARM0);
2277 	cvar_t *var = Cvar_FindVar(name);
2278 	if (var && var->default_string)
2279 		G_INT(OFS_RETURN) = PR_SetEngineString(var->default_string);
2280 	else
2281 		G_INT(OFS_RETURN) = 0;
2282 }
PF_cvar_type(void)2283 static void PF_cvar_type(void)
2284 {
2285 	const char	*str = G_STRING(OFS_PARM0);
2286 	int ret = 0;
2287 	cvar_t *v;
2288 
2289 	v = Cvar_FindVar(str);
2290 	if (v)
2291 	{
2292 		ret |= 1; // CVAR_EXISTS
2293 		if(v->flags & CVAR_ARCHIVE)
2294 			ret |= 2; // CVAR_TYPE_SAVED
2295 //		if(v->flags & CVAR_NOTFROMSERVER)
2296 //			ret |= 4; // CVAR_TYPE_PRIVATE
2297 		if(!(v->flags & CVAR_USERDEFINED))
2298 			ret |= 8; // CVAR_TYPE_ENGINE
2299 //		if (v->description)
2300 //			ret |= 16; // CVAR_TYPE_HASDESCRIPTION
2301 	}
2302 	G_FLOAT(OFS_RETURN) = ret;
2303 }
PF_cvar_description(void)2304 static void PF_cvar_description(void)
2305 {	//quakespasm does not support cvar descriptions. we provide this stub to avoid crashes.
2306 	G_INT(OFS_RETURN) = 0;
2307 }
PF_registercvar(void)2308 static void PF_registercvar(void)
2309 {
2310 	const char *name = G_STRING(OFS_PARM0);
2311 	const char *value = (qcvm->argc>1)?G_STRING(OFS_PARM0):"";
2312 	Cvar_Create(name, value);
2313 }
2314 
2315 //temp entities + networking
PF_WriteString2(void)2316 static void PF_WriteString2(void)
2317 {	//writes a string without the null. a poor-man's strcat.
2318 	const char *string = G_STRING(OFS_PARM0);
2319 	SZ_Write (WriteDest(), string, Q_strlen(string));
2320 }
PF_WriteFloat(void)2321 static void PF_WriteFloat(void)
2322 {	//curiously, this was missing in vanilla.
2323 	MSG_WriteFloat(WriteDest(), G_FLOAT(OFS_PARM0));
2324 }
PF_sv_te_blooddp(void)2325 static void PF_sv_te_blooddp(void)
2326 {	//blood is common enough that we should emulate it for when engines do actually support it.
2327 	float *org = G_VECTOR(OFS_PARM0);
2328 	float *dir = G_VECTOR(OFS_PARM1);
2329 	float color = 73;
2330 	float count = G_FLOAT(OFS_PARM2);
2331 	SV_StartParticle (org, dir, color, count);
2332 }
PF_sv_te_bloodqw(void)2333 static void PF_sv_te_bloodqw(void)
2334 {	//qw tried to strip a lot.
2335 	float *org = G_VECTOR(OFS_PARM0);
2336 	float *dir = vec3_origin;
2337 	float color = 73;
2338 	float count = G_FLOAT(OFS_PARM1)*20;
2339 	SV_StartParticle (org, dir, color, count);
2340 }
PF_sv_te_lightningblood(void)2341 static void PF_sv_te_lightningblood(void)
2342 {	//a qw builtin, to replace particle.
2343 	float *org = G_VECTOR(OFS_PARM0);
2344 	vec3_t dir = {0, 0, -100};
2345 	float color = 20;
2346 	float count = 225;
2347 	SV_StartParticle (org, dir, color, count);
2348 }
PF_sv_te_spike(void)2349 static void PF_sv_te_spike(void)
2350 {
2351 	float *org = G_VECTOR(OFS_PARM0);
2352 	MSG_WriteByte(&sv.datagram, svc_temp_entity);
2353 	MSG_WriteByte(&sv.datagram, TE_SPIKE);
2354 	MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags);
2355 	MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags);
2356 	MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags);
2357 	SV_Multicast(MULTICAST_PVS_U, org, 0, 0);
2358 }
PF_cl_te_spike(void)2359 static void PF_cl_te_spike(void)
2360 {
2361 	float *pos = G_VECTOR(OFS_PARM0);
2362 
2363 	if (PScript_RunParticleEffectTypeString(pos, NULL, 1, "TE_SPIKE"))
2364 		R_RunParticleEffect (pos, vec3_origin, 0, 10);
2365 	if ( rand() % 5 )
2366 		S_StartSound (-1, 0, S_PrecacheSound ("weapons/tink1.wav"), pos, 1, 1);
2367 	else
2368 	{
2369 		int rnd = rand() & 3;
2370 		if (rnd == 1)
2371 			S_StartSound (-1, 0, S_PrecacheSound ("weapons/ric1.wav"), pos, 1, 1);
2372 		else if (rnd == 2)
2373 			S_StartSound (-1, 0, S_PrecacheSound ("weapons/ric2.wav"), pos, 1, 1);
2374 		else
2375 			S_StartSound (-1, 0, S_PrecacheSound ("weapons/ric3.wav"), pos, 1, 1);
2376 	}
2377 }
PF_sv_te_superspike(void)2378 static void PF_sv_te_superspike(void)
2379 {
2380 	float *org = G_VECTOR(OFS_PARM0);
2381 	MSG_WriteByte(&sv.datagram, svc_temp_entity);
2382 	MSG_WriteByte(&sv.datagram, TE_SUPERSPIKE);
2383 	MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags);
2384 	MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags);
2385 	MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags);
2386 	SV_Multicast(MULTICAST_PVS_U, org, 0, 0);
2387 }
PF_cl_te_superspike(void)2388 static void PF_cl_te_superspike(void)
2389 {
2390 	float *pos = G_VECTOR(OFS_PARM0);
2391 
2392 	if (PScript_RunParticleEffectTypeString(pos, NULL, 1, "TE_SUPERSPIKE"))
2393 		R_RunParticleEffect (pos, vec3_origin, 0, 20);
2394 
2395 	if ( rand() % 5 )
2396 		S_StartSound (-1, 0, S_PrecacheSound ("weapons/tink1.wav"), pos, 1, 1);
2397 	else
2398 	{
2399 		int rnd = rand() & 3;
2400 		if (rnd == 1)
2401 			S_StartSound (-1, 0, S_PrecacheSound ("weapons/ric1.wav"), pos, 1, 1);
2402 		else if (rnd == 2)
2403 			S_StartSound (-1, 0, S_PrecacheSound ("weapons/ric2.wav"), pos, 1, 1);
2404 		else
2405 			S_StartSound (-1, 0, S_PrecacheSound ("weapons/ric3.wav"), pos, 1, 1);
2406 	}
2407 }
PF_sv_te_gunshot(void)2408 static void PF_sv_te_gunshot(void)
2409 {
2410 	float *org = G_VECTOR(OFS_PARM0);
2411 	//float count = G_FLOAT(OFS_PARM1)*20;
2412 	MSG_WriteByte(&sv.datagram, svc_temp_entity);
2413 	MSG_WriteByte(&sv.datagram, TE_GUNSHOT);
2414 	MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags);
2415 	MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags);
2416 	MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags);
2417 	SV_Multicast(MULTICAST_PVS_U, org, 0, 0);
2418 }
PF_cl_te_gunshot(void)2419 static void PF_cl_te_gunshot(void)
2420 {
2421 	float *pos = G_VECTOR(OFS_PARM0);
2422 
2423 	int rnd = 20;
2424 	if (PScript_RunParticleEffectTypeString(pos, NULL, rnd, "TE_GUNSHOT"))
2425 		R_RunParticleEffect (pos, vec3_origin, 0, rnd);
2426 }
PF_sv_te_explosion(void)2427 static void PF_sv_te_explosion(void)
2428 {
2429 	float *org = G_VECTOR(OFS_PARM0);
2430 	MSG_WriteByte(&sv.datagram, svc_temp_entity);
2431 	MSG_WriteByte(&sv.datagram, TE_EXPLOSION);
2432 	MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags);
2433 	MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags);
2434 	MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags);
2435 	SV_Multicast(MULTICAST_PHS_U, org, 0, 0);
2436 }
PF_cl_te_explosion(void)2437 static void PF_cl_te_explosion(void)
2438 {
2439 	float *pos = G_VECTOR(OFS_PARM0);
2440 
2441 	dlight_t *dl;
2442 	if (PScript_RunParticleEffectTypeString(pos, NULL, 1, "TE_EXPLOSION"))
2443 		R_ParticleExplosion (pos);
2444 	dl = CL_AllocDlight (0);
2445 	VectorCopy (pos, dl->origin);
2446 	dl->radius = 350;
2447 	dl->die = cl.time + 0.5;
2448 	dl->decay = 300;
2449 	S_StartSound (-1, 0, S_PrecacheSound ("weapons/r_exp3.wav"), pos, 1, 1);
2450 }
PF_sv_te_tarexplosion(void)2451 static void PF_sv_te_tarexplosion(void)
2452 {
2453 	float *org = G_VECTOR(OFS_PARM0);
2454 	MSG_WriteByte(&sv.datagram, svc_temp_entity);
2455 	MSG_WriteByte(&sv.datagram, TE_TAREXPLOSION);
2456 	MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags);
2457 	MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags);
2458 	MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags);
2459 	SV_Multicast(MULTICAST_PHS_U, org, 0, 0);
2460 }
PF_cl_te_tarexplosion(void)2461 static void PF_cl_te_tarexplosion(void)
2462 {
2463 	float *pos = G_VECTOR(OFS_PARM0);
2464 
2465 	if (PScript_RunParticleEffectTypeString(pos, NULL, 1, "TE_TAREXPLOSION"))
2466 		R_BlobExplosion (pos);
2467 	S_StartSound (-1, 0, S_PrecacheSound ("weapons/r_exp3.wav"), pos, 1, 1);
2468 }
PF_sv_te_lightning1(void)2469 static void PF_sv_te_lightning1(void)
2470 {
2471 	edict_t *ed = G_EDICT(OFS_PARM0);
2472 	float *start = G_VECTOR(OFS_PARM1);
2473 	float *end = G_VECTOR(OFS_PARM2);
2474 	MSG_WriteByte(&sv.datagram, svc_temp_entity);
2475 	MSG_WriteByte(&sv.datagram, TE_LIGHTNING1);
2476 	MSG_WriteShort(&sv.datagram, NUM_FOR_EDICT(ed));
2477 	MSG_WriteCoord(&sv.datagram, start[0], sv.protocolflags);
2478 	MSG_WriteCoord(&sv.datagram, start[1], sv.protocolflags);
2479 	MSG_WriteCoord(&sv.datagram, start[2], sv.protocolflags);
2480 	MSG_WriteCoord(&sv.datagram, end[0], sv.protocolflags);
2481 	MSG_WriteCoord(&sv.datagram, end[1], sv.protocolflags);
2482 	MSG_WriteCoord(&sv.datagram, end[2], sv.protocolflags);
2483 	SV_Multicast(MULTICAST_PHS_U, start, 0, 0);
2484 }
PF_cl_te_lightning1(void)2485 static void PF_cl_te_lightning1(void)
2486 {
2487 	edict_t *ed = G_EDICT(OFS_PARM0);
2488 	float *start = G_VECTOR(OFS_PARM1);
2489 	float *end = G_VECTOR(OFS_PARM2);
2490 
2491 	CL_UpdateBeam (Mod_ForName("progs/bolt.mdl", true), "TE_LIGHTNING1", "TE_LIGHTNING1_END", -NUM_FOR_EDICT(ed), start, end);
2492 }
PF_sv_te_lightning2(void)2493 static void PF_sv_te_lightning2(void)
2494 {
2495 	edict_t *ed = G_EDICT(OFS_PARM0);
2496 	float *start = G_VECTOR(OFS_PARM1);
2497 	float *end = G_VECTOR(OFS_PARM2);
2498 	MSG_WriteByte(&sv.datagram, svc_temp_entity);
2499 	MSG_WriteByte(&sv.datagram, TE_LIGHTNING2);
2500 	MSG_WriteShort(&sv.datagram, NUM_FOR_EDICT(ed));
2501 	MSG_WriteCoord(&sv.datagram, start[0], sv.protocolflags);
2502 	MSG_WriteCoord(&sv.datagram, start[1], sv.protocolflags);
2503 	MSG_WriteCoord(&sv.datagram, start[2], sv.protocolflags);
2504 	MSG_WriteCoord(&sv.datagram, end[0], sv.protocolflags);
2505 	MSG_WriteCoord(&sv.datagram, end[1], sv.protocolflags);
2506 	MSG_WriteCoord(&sv.datagram, end[2], sv.protocolflags);
2507 	SV_Multicast(MULTICAST_PHS_U, start, 0, 0);
2508 }
PF_cl_te_lightning2(void)2509 static void PF_cl_te_lightning2(void)
2510 {
2511 	edict_t *ed = G_EDICT(OFS_PARM0);
2512 	float *start = G_VECTOR(OFS_PARM1);
2513 	float *end = G_VECTOR(OFS_PARM2);
2514 
2515 	CL_UpdateBeam (Mod_ForName("progs/bolt2.mdl", true), "TE_LIGHTNING2", "TE_LIGHTNING2_END", -NUM_FOR_EDICT(ed), start, end);
2516 }
PF_sv_te_wizspike(void)2517 static void PF_sv_te_wizspike(void)
2518 {
2519 	float *org = G_VECTOR(OFS_PARM0);
2520 	MSG_WriteByte(&sv.datagram, svc_temp_entity);
2521 	MSG_WriteByte(&sv.datagram, TE_WIZSPIKE);
2522 	MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags);
2523 	MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags);
2524 	MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags);
2525 	SV_Multicast(MULTICAST_PHS_U, org, 0, 0);
2526 }
PF_cl_te_wizspike(void)2527 static void PF_cl_te_wizspike(void)
2528 {
2529 	float *pos = G_VECTOR(OFS_PARM0);
2530 	S_StartSound (-1, 0, S_PrecacheSound ("wizard/hit.wav"), pos, 1, 1);
2531 }
PF_sv_te_knightspike(void)2532 static void PF_sv_te_knightspike(void)
2533 {
2534 	float *org = G_VECTOR(OFS_PARM0);
2535 	MSG_WriteByte(&sv.datagram, svc_temp_entity);
2536 	MSG_WriteByte(&sv.datagram, TE_KNIGHTSPIKE);
2537 	MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags);
2538 	MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags);
2539 	MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags);
2540 	SV_Multicast(MULTICAST_PHS_U, org, 0, 0);
2541 }
PF_cl_te_knightspike(void)2542 static void PF_cl_te_knightspike(void)
2543 {
2544 	float *pos = G_VECTOR(OFS_PARM0);
2545 	S_StartSound (-1, 0, S_PrecacheSound ("hknight/hit.wav"), pos, 1, 1);
2546 }
PF_sv_te_lightning3(void)2547 static void PF_sv_te_lightning3(void)
2548 {
2549 	edict_t *ed = G_EDICT(OFS_PARM0);
2550 	float *start = G_VECTOR(OFS_PARM1);
2551 	float *end = G_VECTOR(OFS_PARM2);
2552 	MSG_WriteByte(&sv.datagram, svc_temp_entity);
2553 	MSG_WriteByte(&sv.datagram, TE_LIGHTNING3);
2554 	MSG_WriteShort(&sv.datagram, NUM_FOR_EDICT(ed));
2555 	MSG_WriteCoord(&sv.datagram, start[0], sv.protocolflags);
2556 	MSG_WriteCoord(&sv.datagram, start[1], sv.protocolflags);
2557 	MSG_WriteCoord(&sv.datagram, start[2], sv.protocolflags);
2558 	MSG_WriteCoord(&sv.datagram, end[0], sv.protocolflags);
2559 	MSG_WriteCoord(&sv.datagram, end[1], sv.protocolflags);
2560 	MSG_WriteCoord(&sv.datagram, end[2], sv.protocolflags);
2561 	SV_Multicast(MULTICAST_PHS_U, start, 0, 0);
2562 }
PF_cl_te_lightning3(void)2563 static void PF_cl_te_lightning3(void)
2564 {
2565 	edict_t *ed = G_EDICT(OFS_PARM0);
2566 	float *start = G_VECTOR(OFS_PARM1);
2567 	float *end = G_VECTOR(OFS_PARM2);
2568 
2569 	CL_UpdateBeam (Mod_ForName("progs/bolt3.mdl", true), "TE_LIGHTNING3", "TE_LIGHTNING3_END", -NUM_FOR_EDICT(ed), start, end);
2570 }
PF_sv_te_lavasplash(void)2571 static void PF_sv_te_lavasplash(void)
2572 {
2573 	float *org = G_VECTOR(OFS_PARM0);
2574 	MSG_WriteByte(&sv.datagram, svc_temp_entity);
2575 	MSG_WriteByte(&sv.datagram, TE_LAVASPLASH);
2576 	MSG_WriteCoord(&sv.datagram, org[0], sv.protocolflags);
2577 	MSG_WriteCoord(&sv.datagram, org[1], sv.protocolflags);
2578 	MSG_WriteCoord(&sv.datagram, org[2], sv.protocolflags);
2579 	SV_Multicast(MULTICAST_PHS_U, org, 0, 0);
2580 }
PF_cl_te_lavasplash(void)2581 static void PF_cl_te_lavasplash(void)
2582 {
2583 }
PF_sv_te_teleport(void)2584 static void PF_sv_te_teleport(void)
2585 {
2586 	float *org = G_VECTOR(OFS_PARM0);
2587 	MSG_WriteByte(&sv.multicast, svc_temp_entity);
2588 	MSG_WriteByte(&sv.multicast, TE_TELEPORT);
2589 	MSG_WriteCoord(&sv.multicast, org[0], sv.protocolflags);
2590 	MSG_WriteCoord(&sv.multicast, org[1], sv.protocolflags);
2591 	MSG_WriteCoord(&sv.multicast, org[2], sv.protocolflags);
2592 	SV_Multicast(MULTICAST_PHS_U, org, 0, 0);
2593 }
PF_cl_te_teleport(void)2594 static void PF_cl_te_teleport(void)
2595 {
2596 }
PF_sv_te_explosion2(void)2597 static void PF_sv_te_explosion2(void)
2598 {
2599 	float *org = G_VECTOR(OFS_PARM0);
2600 	int palstart = G_FLOAT(OFS_PARM1);
2601 	int palcount = G_FLOAT(OFS_PARM1);
2602 	MSG_WriteByte(&sv.multicast, svc_temp_entity);
2603 	MSG_WriteByte(&sv.multicast, TE_EXPLOSION2);
2604 	MSG_WriteCoord(&sv.multicast, org[0], sv.protocolflags);
2605 	MSG_WriteCoord(&sv.multicast, org[1], sv.protocolflags);
2606 	MSG_WriteCoord(&sv.multicast, org[2], sv.protocolflags);
2607 	MSG_WriteByte(&sv.multicast, palstart);
2608 	MSG_WriteByte(&sv.multicast, palcount);
2609 	SV_Multicast(MULTICAST_PHS_U, org, 0, 0);
2610 }
PF_cl_te_explosion2(void)2611 static void PF_cl_te_explosion2(void)
2612 {
2613 	float *pos = G_VECTOR(OFS_PARM0);
2614 	dlight_t *dl;
2615 
2616 	dl = CL_AllocDlight (0);
2617 	VectorCopy (pos, dl->origin);
2618 	dl->radius = 350;
2619 	dl->die = cl.time + 0.5;
2620 	dl->decay = 300;
2621 	S_StartSound (-1, 0, S_PrecacheSound ("weapons/r_exp3.wav"), pos, 1, 1);
2622 }
PF_sv_te_beam(void)2623 static void PF_sv_te_beam(void)
2624 {
2625 	edict_t *ed = G_EDICT(OFS_PARM0);
2626 	float *start = G_VECTOR(OFS_PARM1);
2627 	float *end = G_VECTOR(OFS_PARM2);
2628 	MSG_WriteByte(&sv.multicast, svc_temp_entity);
2629 	MSG_WriteByte(&sv.multicast, TE_BEAM);
2630 	MSG_WriteShort(&sv.multicast, NUM_FOR_EDICT(ed));
2631 	MSG_WriteCoord(&sv.multicast, start[0], sv.protocolflags);
2632 	MSG_WriteCoord(&sv.multicast, start[1], sv.protocolflags);
2633 	MSG_WriteCoord(&sv.multicast, start[2], sv.protocolflags);
2634 	MSG_WriteCoord(&sv.multicast, end[0], sv.protocolflags);
2635 	MSG_WriteCoord(&sv.multicast, end[1], sv.protocolflags);
2636 	MSG_WriteCoord(&sv.multicast, end[2], sv.protocolflags);
2637 	SV_Multicast(MULTICAST_PHS_U, start, 0, 0);
2638 }
PF_cl_te_beam(void)2639 static void PF_cl_te_beam(void)
2640 {
2641 	edict_t *ed = G_EDICT(OFS_PARM0);
2642 	float *start = G_VECTOR(OFS_PARM1);
2643 	float *end = G_VECTOR(OFS_PARM2);
2644 
2645 	CL_UpdateBeam (Mod_ForName("progs/beam.mdl", true), "TE_BEAM", "TE_BEAM_END", -NUM_FOR_EDICT(ed), start, end);
2646 }
2647 #ifdef PSET_SCRIPT
PF_sv_te_particlerain(void)2648 static void PF_sv_te_particlerain(void)
2649 {
2650 	float *min = G_VECTOR(OFS_PARM0);
2651 	float *max = G_VECTOR(OFS_PARM1);
2652 	float *velocity = G_VECTOR(OFS_PARM2);
2653 	float count = G_FLOAT(OFS_PARM3);
2654 	float colour = G_FLOAT(OFS_PARM4);
2655 
2656 	if (count < 1)
2657 		return;
2658 	if (count > 65535)
2659 		count = 65535;
2660 
2661 	MSG_WriteByte(&sv.multicast, svc_temp_entity);
2662 	MSG_WriteByte(&sv.multicast, TEDP_PARTICLERAIN);
2663 	MSG_WriteCoord(&sv.multicast, min[0], sv.protocolflags);
2664 	MSG_WriteCoord(&sv.multicast, min[1], sv.protocolflags);
2665 	MSG_WriteCoord(&sv.multicast, min[2], sv.protocolflags);
2666 	MSG_WriteCoord(&sv.multicast, max[0], sv.protocolflags);
2667 	MSG_WriteCoord(&sv.multicast, max[1], sv.protocolflags);
2668 	MSG_WriteCoord(&sv.multicast, max[2], sv.protocolflags);
2669 	MSG_WriteCoord(&sv.multicast, velocity[0], sv.protocolflags);
2670 	MSG_WriteCoord(&sv.multicast, velocity[1], sv.protocolflags);
2671 	MSG_WriteCoord(&sv.multicast, velocity[2], sv.protocolflags);
2672 	MSG_WriteShort(&sv.multicast, count);
2673 	MSG_WriteByte(&sv.multicast, colour);
2674 
2675 	SV_Multicast (MULTICAST_ALL_U, NULL, 0, PEXT2_REPLACEMENTDELTAS);
2676 }
PF_sv_te_particlesnow(void)2677 static void PF_sv_te_particlesnow(void)
2678 {
2679 	float *min = G_VECTOR(OFS_PARM0);
2680 	float *max = G_VECTOR(OFS_PARM1);
2681 	float *velocity = G_VECTOR(OFS_PARM2);
2682 	float count = G_FLOAT(OFS_PARM3);
2683 	float colour = G_FLOAT(OFS_PARM4);
2684 
2685 	if (count < 1)
2686 		return;
2687 	if (count > 65535)
2688 		count = 65535;
2689 
2690 	MSG_WriteByte(&sv.multicast, svc_temp_entity);
2691 	MSG_WriteByte(&sv.multicast, TEDP_PARTICLESNOW);
2692 	MSG_WriteCoord(&sv.multicast, min[0], sv.protocolflags);
2693 	MSG_WriteCoord(&sv.multicast, min[1], sv.protocolflags);
2694 	MSG_WriteCoord(&sv.multicast, min[2], sv.protocolflags);
2695 	MSG_WriteCoord(&sv.multicast, max[0], sv.protocolflags);
2696 	MSG_WriteCoord(&sv.multicast, max[1], sv.protocolflags);
2697 	MSG_WriteCoord(&sv.multicast, max[2], sv.protocolflags);
2698 	MSG_WriteCoord(&sv.multicast, velocity[0], sv.protocolflags);
2699 	MSG_WriteCoord(&sv.multicast, velocity[1], sv.protocolflags);
2700 	MSG_WriteCoord(&sv.multicast, velocity[2], sv.protocolflags);
2701 	MSG_WriteShort(&sv.multicast, count);
2702 	MSG_WriteByte(&sv.multicast, colour);
2703 
2704 	SV_Multicast (MULTICAST_ALL_U, NULL, 0, PEXT2_REPLACEMENTDELTAS);
2705 }
2706 #else
2707 #define PF_sv_te_particlerain PF_void_stub
2708 #define PF_sv_te_particlesnow PF_void_stub
2709 #endif
2710 #define PF_sv_te_bloodshower PF_void_stub
2711 #define PF_sv_te_explosionrgb PF_void_stub
2712 #define PF_sv_te_particlecube PF_void_stub
2713 #define PF_sv_te_spark PF_void_stub
2714 #define PF_sv_te_gunshotquad PF_sv_te_gunshot
2715 #define PF_sv_te_spikequad PF_sv_te_spike
2716 #define PF_sv_te_superspikequad PF_sv_te_superspike
2717 #define PF_sv_te_explosionquad PF_sv_te_explosion
2718 #define PF_sv_te_smallflash PF_void_stub
2719 #define PF_sv_te_customflash PF_void_stub
2720 #define PF_sv_te_plasmaburn PF_sv_te_tarexplosion
2721 #define PF_sv_effect PF_void_stub
2722 
PF_sv_pointsound(void)2723 static void PF_sv_pointsound(void)
2724 {
2725 	float *origin = G_VECTOR(OFS_PARM0);
2726 	const char *sample = G_STRING(OFS_PARM1);
2727 	float volume = G_FLOAT(OFS_PARM2);
2728 	float attenuation = G_FLOAT(OFS_PARM3);
2729 	SV_StartSound (qcvm->edicts, origin, 0, sample, volume, attenuation);
2730 }
PF_cl_pointsound(void)2731 static void PF_cl_pointsound(void)
2732 {
2733 	float *origin = G_VECTOR(OFS_PARM0);
2734 	const char *sample = G_STRING(OFS_PARM1);
2735 	float volume = G_FLOAT(OFS_PARM2);
2736 	float attenuation = G_FLOAT(OFS_PARM3);
2737 	S_StartSound(0, 0, S_PrecacheSound(sample), origin, volume, attenuation);
2738 }
2739 //file stuff
2740 
PF_whichpack(void)2741 static void PF_whichpack(void)
2742 {
2743 	const char *fname = G_STRING(OFS_PARM0);	//uses native paths, as this isn't actually reading anything.
2744 	unsigned int path_id;
2745 	if (COM_FileExists(fname, &path_id))
2746 	{
2747 		//FIXME: quakespasm reports which gamedir the file is in, but paks are hidden.
2748 		//I'm too lazy to rewrite COM_FindFile, so I'm just going to hack something small to get the gamedir, just not the paks
2749 
2750 		searchpath_t *path;
2751 		for (path = com_searchpaths; path; path = path->next)
2752 			if (!path->pack && path->path_id == path_id)
2753 				break;	//okay, this one looks like one we can report
2754 
2755 		//sandbox it by stripping the basedir
2756 		fname = path->filename;
2757 		if (!strncmp(fname, com_basedir, strlen(com_basedir)))
2758 			fname += strlen(com_basedir);
2759 		else
2760 			fname = "?";	//no idea where this came from. something is screwing with us.
2761 		while (*fname == '/' || *fname == '\\')
2762 			fname++;	//small cleanup, just in case
2763 		G_INT(OFS_RETURN) = PR_SetEngineString(fname);
2764 	}
2765 	else
2766 		G_INT(OFS_RETURN) = 0;
2767 }
2768 
2769 //string buffers
2770 
2771 struct strbuf {
2772 	qcvm_t  *owningvm;
2773 	char **strings;
2774 	unsigned int used;
2775 	unsigned int allocated;
2776 };
2777 
2778 #define BUFSTRBASE 1
2779 #define NUMSTRINGBUFS 64u
2780 static struct strbuf strbuflist[NUMSTRINGBUFS];
2781 
PF_buf_shutdown(void)2782 static void PF_buf_shutdown(void)
2783 {
2784 	unsigned int i, bufno;
2785 
2786 	for (bufno = 0; bufno < NUMSTRINGBUFS; bufno++)
2787 	{
2788 		if (strbuflist[bufno].owningvm != qcvm)
2789 			continue;
2790 
2791 		for (i = 0; i < strbuflist[bufno].used; i++)
2792 			Z_Free(strbuflist[bufno].strings[i]);
2793 		Z_Free(strbuflist[bufno].strings);
2794 
2795 		strbuflist[bufno].owningvm = NULL;
2796 		strbuflist[bufno].strings = NULL;
2797 		strbuflist[bufno].used = 0;
2798 		strbuflist[bufno].allocated = 0;
2799 	}
2800 }
2801 
2802 // #440 float() buf_create (DP_QC_STRINGBUFFERS)
PF_buf_create(void)2803 static void PF_buf_create(void)
2804 {
2805 	unsigned int i;
2806 
2807 	const char *type = ((qcvm->argc>0)?G_STRING(OFS_PARM0):"string");
2808 //	unsigned int flags = ((pr_argc>1)?G_FLOAT(OFS_PARM1):1);
2809 
2810 	if (!q_strcasecmp(type, "string"))
2811 		;
2812 	else
2813 	{
2814 		G_FLOAT(OFS_RETURN) = -1;
2815 		return;
2816 	}
2817 
2818 	//flags&1 == saved. apparently.
2819 
2820 	for (i = 0; i < NUMSTRINGBUFS; i++)
2821 	{
2822 		if (!strbuflist[i].owningvm)
2823 		{
2824 			strbuflist[i].owningvm = qcvm;
2825 			strbuflist[i].used = 0;
2826 			strbuflist[i].allocated = 0;
2827 			strbuflist[i].strings = NULL;
2828 			G_FLOAT(OFS_RETURN) = i+BUFSTRBASE;
2829 			return;
2830 		}
2831 	}
2832 	G_FLOAT(OFS_RETURN) = -1;
2833 }
2834 // #441 void(float bufhandle) buf_del (DP_QC_STRINGBUFFERS)
PF_buf_del(void)2835 static void PF_buf_del(void)
2836 {
2837 	unsigned int i;
2838 	unsigned int bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE;
2839 
2840 	if (bufno >= NUMSTRINGBUFS)
2841 		return;
2842 	if (!strbuflist[bufno].owningvm)
2843 		return;
2844 
2845 	for (i = 0; i < strbuflist[bufno].used; i++)
2846 	{
2847 		if (strbuflist[bufno].strings[i])
2848 			Z_Free(strbuflist[bufno].strings[i]);
2849 	}
2850 	Z_Free(strbuflist[bufno].strings);
2851 
2852 	strbuflist[bufno].strings = NULL;
2853 	strbuflist[bufno].used = 0;
2854 	strbuflist[bufno].allocated = 0;
2855 
2856 	strbuflist[bufno].owningvm = NULL;
2857 }
2858 // #442 float(float bufhandle) buf_getsize (DP_QC_STRINGBUFFERS)
PF_buf_getsize(void)2859 static void PF_buf_getsize(void)
2860 {
2861 	int bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE;
2862 
2863 	if ((unsigned int)bufno >= NUMSTRINGBUFS)
2864 		return;
2865 	if (!strbuflist[bufno].owningvm)
2866 		return;
2867 
2868 	G_FLOAT(OFS_RETURN) = strbuflist[bufno].used;
2869 }
2870 // #443 void(float bufhandle_from, float bufhandle_to) buf_copy (DP_QC_STRINGBUFFERS)
PF_buf_copy(void)2871 static void PF_buf_copy(void)
2872 {
2873 	unsigned int buffrom = G_FLOAT(OFS_PARM0)-BUFSTRBASE;
2874 	unsigned int bufto = G_FLOAT(OFS_PARM1)-BUFSTRBASE;
2875 	unsigned int i;
2876 
2877 	if (bufto == buffrom)	//err...
2878 		return;
2879 	if (buffrom >= NUMSTRINGBUFS)
2880 		return;
2881 	if (!strbuflist[buffrom].owningvm)
2882 		return;
2883 	if (bufto >= NUMSTRINGBUFS)
2884 		return;
2885 	if (!strbuflist[bufto].owningvm)
2886 		return;
2887 
2888 	//obliterate any and all existing data.
2889 	for (i = 0; i < strbuflist[bufto].used; i++)
2890 		if (strbuflist[bufto].strings[i])
2891 			Z_Free(strbuflist[bufto].strings[i]);
2892 	Z_Free(strbuflist[bufto].strings);
2893 
2894 	//copy new data over.
2895 	strbuflist[bufto].used = strbuflist[bufto].allocated = strbuflist[buffrom].used;
2896 	strbuflist[bufto].strings = Z_Malloc(strbuflist[buffrom].used * sizeof(char*));
2897 	for (i = 0; i < strbuflist[buffrom].used; i++)
2898 		strbuflist[bufto].strings[i] = strbuflist[buffrom].strings[i]?Z_StrDup(strbuflist[buffrom].strings[i]):NULL;
2899 }
2900 static int PF_buf_sort_sortprefixlen;
PF_buf_sort_ascending(const void * a,const void * b)2901 static int PF_buf_sort_ascending(const void *a, const void *b)
2902 {
2903 	return strncmp(*(char*const*)a, *(char*const*)b, PF_buf_sort_sortprefixlen);
2904 }
PF_buf_sort_descending(const void * b,const void * a)2905 static int PF_buf_sort_descending(const void *b, const void *a)
2906 {
2907 	return strncmp(*(char*const*)a, *(char*const*)b, PF_buf_sort_sortprefixlen);
2908 }
2909 // #444 void(float bufhandle, float sortprefixlen, float backward) buf_sort (DP_QC_STRINGBUFFERS)
PF_buf_sort(void)2910 static void PF_buf_sort(void)
2911 {
2912 	int bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE;
2913 	int sortprefixlen = G_FLOAT(OFS_PARM1);
2914 	int backwards = G_FLOAT(OFS_PARM2);
2915 	unsigned int s,d;
2916 	char **strings;
2917 
2918 	if ((unsigned int)bufno >= NUMSTRINGBUFS)
2919 		return;
2920 	if (!strbuflist[bufno].owningvm)
2921 		return;
2922 
2923 	if (sortprefixlen <= 0)
2924 		sortprefixlen = 0x7fffffff;
2925 
2926 	//take out the nulls first, to avoid weird/crashy sorting
2927 	for (s = 0, d = 0, strings = strbuflist[bufno].strings; s < strbuflist[bufno].used; )
2928 	{
2929 		if (!strings[s])
2930 		{
2931 			s++;
2932 			continue;
2933 		}
2934 		strings[d++] = strings[s++];
2935 	}
2936 	strbuflist[bufno].used = d;
2937 
2938 	//no nulls now, sort it.
2939 	PF_buf_sort_sortprefixlen = sortprefixlen;	//eww, a global. burn in hell.
2940 	if (backwards)	//z first
2941 		qsort(strings, strbuflist[bufno].used, sizeof(char*), PF_buf_sort_descending);
2942 	else	//a first
2943 		qsort(strings, strbuflist[bufno].used, sizeof(char*), PF_buf_sort_ascending);
2944 }
2945 // #445 string(float bufhandle, string glue) buf_implode (DP_QC_STRINGBUFFERS)
PF_buf_implode(void)2946 static void PF_buf_implode(void)
2947 {
2948 	int bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE;
2949 	const char *glue = G_STRING(OFS_PARM1);
2950 	unsigned int gluelen = strlen(glue);
2951 	unsigned int retlen, l, i;
2952 	char **strings;
2953 	char *ret;
2954 
2955 	if ((unsigned int)bufno >= NUMSTRINGBUFS)
2956 		return;
2957 	if (!strbuflist[bufno].owningvm)
2958 		return;
2959 
2960 	//count neededlength
2961 	strings = strbuflist[bufno].strings;
2962 	/*
2963 	for (i = 0, retlen = 0; i < strbuflist[bufno].used; i++)
2964 	{
2965 		if (strings[i])
2966 		{
2967 			if (retlen)
2968 				retlen += gluelen;
2969 			retlen += strlen(strings[i]);
2970 		}
2971 	}
2972 	ret = malloc(retlen+1);*/
2973 
2974 	//generate the output
2975 	ret = PR_GetTempString();
2976 	for (i = 0, retlen = 0; i < strbuflist[bufno].used; i++)
2977 	{
2978 		if (strings[i])
2979 		{
2980 			if (retlen)
2981 			{
2982 				if (retlen+gluelen+1 > STRINGTEMP_LENGTH)
2983 				{
2984 					Con_Printf("PF_buf_implode: tempstring overflow\n");
2985 					break;
2986 				}
2987 				memcpy(ret+retlen, glue, gluelen);
2988 				retlen += gluelen;
2989 			}
2990 			l = strlen(strings[i]);
2991 			if (retlen+l+1 > STRINGTEMP_LENGTH)
2992 			{
2993 				Con_Printf("PF_buf_implode: tempstring overflow\n");
2994 				break;
2995 			}
2996 			memcpy(ret+retlen, strings[i], l);
2997 			retlen += l;
2998 		}
2999 	}
3000 
3001 	//add the null and return
3002 	ret[retlen] = 0;
3003 	G_INT(OFS_RETURN) = PR_SetEngineString(ret);
3004 }
3005 // #446 string(float bufhandle, float string_index) bufstr_get (DP_QC_STRINGBUFFERS)
PF_bufstr_get(void)3006 static void PF_bufstr_get(void)
3007 {
3008 	unsigned int bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE;
3009 	unsigned int index = G_FLOAT(OFS_PARM1);
3010 	char *ret;
3011 
3012 	if (bufno >= NUMSTRINGBUFS)
3013 	{
3014 		G_INT(OFS_RETURN) = 0;
3015 		return;
3016 	}
3017 	if (!strbuflist[bufno].owningvm)
3018 	{
3019 		G_INT(OFS_RETURN) = 0;
3020 		return;
3021 	}
3022 
3023 	if (index >= strbuflist[bufno].used)
3024 	{
3025 		G_INT(OFS_RETURN) = 0;
3026 		return;
3027 	}
3028 
3029 	if (strbuflist[bufno].strings[index])
3030 	{
3031 		ret = PR_GetTempString();
3032 		q_strlcpy(ret, strbuflist[bufno].strings[index], STRINGTEMP_LENGTH);
3033 		G_INT(OFS_RETURN) = PR_SetEngineString(ret);
3034 	}
3035 	else
3036 		G_INT(OFS_RETURN) = 0;
3037 }
3038 // #447 void(float bufhandle, float string_index, string str) bufstr_set (DP_QC_STRINGBUFFERS)
PF_bufstr_set(void)3039 static void PF_bufstr_set(void)
3040 {
3041 	unsigned int bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE;
3042 	unsigned int index = G_FLOAT(OFS_PARM1);
3043 	const char *string = G_STRING(OFS_PARM2);
3044 	unsigned int oldcount;
3045 
3046 	if ((unsigned int)bufno >= NUMSTRINGBUFS)
3047 		return;
3048 	if (!strbuflist[bufno].owningvm)
3049 		return;
3050 
3051 	if (index >= strbuflist[bufno].allocated)
3052 	{
3053 		oldcount = strbuflist[bufno].allocated;
3054 		strbuflist[bufno].allocated = (index + 256);
3055 		strbuflist[bufno].strings = Z_Realloc(strbuflist[bufno].strings, strbuflist[bufno].allocated*sizeof(char*));
3056 		memset(strbuflist[bufno].strings+oldcount, 0, (strbuflist[bufno].allocated - oldcount) * sizeof(char*));
3057 	}
3058 	if (strbuflist[bufno].strings[index])
3059 		Z_Free(strbuflist[bufno].strings[index]);
3060 	strbuflist[bufno].strings[index] = Z_Malloc(strlen(string)+1);
3061 	strcpy(strbuflist[bufno].strings[index], string);
3062 
3063 	if (index >= strbuflist[bufno].used)
3064 		strbuflist[bufno].used = index+1;
3065 }
3066 
PF_bufstr_add_internal(unsigned int bufno,const char * string,int appendonend)3067 static int PF_bufstr_add_internal(unsigned int bufno, const char *string, int appendonend)
3068 {
3069 	unsigned int index;
3070 	if (appendonend)
3071 	{
3072 		//add on end
3073 		index = strbuflist[bufno].used;
3074 	}
3075 	else
3076 	{
3077 		//find a hole
3078 		for (index = 0; index < strbuflist[bufno].used; index++)
3079 			if (!strbuflist[bufno].strings[index])
3080 				break;
3081 	}
3082 
3083 	//expand it if needed
3084 	if (index >= strbuflist[bufno].allocated)
3085 	{
3086 		unsigned int oldcount;
3087 		oldcount = strbuflist[bufno].allocated;
3088 		strbuflist[bufno].allocated = (index + 256);
3089 		strbuflist[bufno].strings = Z_Realloc(strbuflist[bufno].strings, strbuflist[bufno].allocated*sizeof(char*));
3090 		memset(strbuflist[bufno].strings+oldcount, 0, (strbuflist[bufno].allocated - oldcount) * sizeof(char*));
3091 	}
3092 
3093 	//add in the new string.
3094 	if (strbuflist[bufno].strings[index])
3095 		Z_Free(strbuflist[bufno].strings[index]);
3096 	strbuflist[bufno].strings[index] = Z_Malloc(strlen(string)+1);
3097 	strcpy(strbuflist[bufno].strings[index], string);
3098 
3099 	if (index >= strbuflist[bufno].used)
3100 		strbuflist[bufno].used = index+1;
3101 
3102 	return index;
3103 }
3104 
3105 // #448 float(float bufhandle, string str, float order) bufstr_add (DP_QC_STRINGBUFFERS)
PF_bufstr_add(void)3106 static void PF_bufstr_add(void)
3107 {
3108 	size_t bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE;
3109 	const char *string = G_STRING(OFS_PARM1);
3110 	qboolean ordered = G_FLOAT(OFS_PARM2);
3111 
3112 	if ((unsigned int)bufno >= NUMSTRINGBUFS)
3113 		return;
3114 	if (!strbuflist[bufno].owningvm)
3115 		return;
3116 
3117 	G_FLOAT(OFS_RETURN) = PF_bufstr_add_internal(bufno, string, ordered);
3118 }
3119 // #449 void(float bufhandle, float string_index) bufstr_free (DP_QC_STRINGBUFFERS)
PF_bufstr_free(void)3120 static void PF_bufstr_free(void)
3121 {
3122 	size_t bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE;
3123 	size_t index = G_FLOAT(OFS_PARM1);
3124 
3125 	if ((unsigned int)bufno >= NUMSTRINGBUFS)
3126 		return;
3127 	if (!strbuflist[bufno].owningvm)
3128 		return;
3129 
3130 	if (index >= strbuflist[bufno].used)
3131 		return;	//not valid anyway.
3132 
3133 	if (strbuflist[bufno].strings[index])
3134 		Z_Free(strbuflist[bufno].strings[index]);
3135 	strbuflist[bufno].strings[index] = NULL;
3136 }
3137 
PF_buf_cvarlist(void)3138 static void PF_buf_cvarlist(void)
3139 {
3140 	size_t bufno = G_FLOAT(OFS_PARM0)-BUFSTRBASE;
3141 	const char *pattern = G_STRING(OFS_PARM1);
3142 	const char *antipattern = G_STRING(OFS_PARM2);
3143 	unsigned int i;
3144 	cvar_t	*var;
3145 	int plen = strlen(pattern), alen = strlen(antipattern);
3146 	qboolean pwc = strchr(pattern, '*')||strchr(pattern, '?'),
3147 			 awc = strchr(antipattern, '*')||strchr(antipattern, '?');
3148 
3149 	if ((unsigned int)bufno >= NUMSTRINGBUFS)
3150 		return;
3151 	if (!strbuflist[bufno].owningvm)
3152 		return;
3153 
3154 	//obliterate any and all existing data.
3155 	for (i = 0; i < strbuflist[bufno].used; i++)
3156 		if (strbuflist[bufno].strings[i])
3157 			Z_Free(strbuflist[bufno].strings[i]);
3158 	if (strbuflist[bufno].strings)
3159 		Z_Free(strbuflist[bufno].strings);
3160 	strbuflist[bufno].used = strbuflist[bufno].allocated = 0;
3161 
3162 	for (var=Cvar_FindVarAfter ("", CVAR_NONE) ; var ; var=var->next)
3163 	{
3164 		if (plen && (pwc?!wildcmp(pattern, var->name):strncmp(var->name, pattern, plen)))
3165 			continue;
3166 		if (alen && (awc?wildcmp(antipattern, var->name):!strncmp(var->name, antipattern, alen)))
3167 			continue;
3168 
3169 		PF_bufstr_add_internal(bufno, var->name, true);
3170 	}
3171 
3172 	qsort(strbuflist[bufno].strings, strbuflist[bufno].used, sizeof(char*), PF_buf_sort_ascending);
3173 }
3174 
3175 //entity stuff
PF_WasFreed(void)3176 static void PF_WasFreed(void)
3177 {
3178 	edict_t *ed = G_EDICT(OFS_PARM0);
3179 	G_FLOAT(OFS_RETURN) = ed->free;
3180 }
PF_copyentity(void)3181 static void PF_copyentity(void)
3182 {
3183 	edict_t *src = G_EDICT(OFS_PARM0);
3184 	edict_t *dst = (qcvm->argc<2)?ED_Alloc():G_EDICT(OFS_PARM1);
3185 	if (src->free || dst->free)
3186 		Con_Printf("PF_copyentity: entity is free\n");
3187 	memcpy(&dst->v, &src->v, qcvm->edict_size - sizeof(entvars_t));
3188 	dst->alpha = src->alpha;
3189 	dst->sendinterval = src->sendinterval;
3190 	SV_LinkEdict(dst, false);
3191 
3192 	G_INT(OFS_RETURN) = EDICT_TO_PROG(dst);
3193 }
PF_edict_for_num(void)3194 static void PF_edict_for_num(void)
3195 {
3196 	G_INT(OFS_RETURN) = EDICT_TO_PROG(EDICT_NUM(G_FLOAT(OFS_PARM0)));
3197 }
PF_num_for_edict(void)3198 static void PF_num_for_edict(void)
3199 {
3200 	G_FLOAT(OFS_RETURN) = G_EDICTNUM(OFS_PARM0);
3201 }
PF_findchain(void)3202 static void PF_findchain(void)
3203 {
3204 	edict_t	*ent, *chain;
3205 	int	i, f;
3206 	const char *s, *t;
3207 	int cfld;
3208 
3209 	chain = (edict_t *)qcvm->edicts;
3210 
3211 	ent = NEXT_EDICT(qcvm->edicts);
3212 	f = G_INT(OFS_PARM0);
3213 	s = G_STRING(OFS_PARM1);
3214 	if (qcvm->argc > 2)
3215 		cfld = G_INT(OFS_PARM2);
3216 	else
3217 		cfld = &ent->v.chain - (int*)&ent->v;
3218 
3219 	for (i = 1; i < qcvm->num_edicts; i++, ent = NEXT_EDICT(ent))
3220 	{
3221 		if (ent->free)
3222 			continue;
3223 		t = E_STRING(ent,f);
3224 		if (strcmp(s, t))
3225 			continue;
3226 		((int*)&ent->v)[cfld] = EDICT_TO_PROG(chain);
3227 		chain = ent;
3228 	}
3229 
3230 	RETURN_EDICT(chain);
3231 }
PF_findfloat(void)3232 static void PF_findfloat(void)
3233 {
3234 	int		e;
3235 	int		f;
3236 	float	s, t;
3237 	edict_t	*ed;
3238 
3239 	e = G_EDICTNUM(OFS_PARM0);
3240 	f = G_INT(OFS_PARM1);
3241 	s = G_FLOAT(OFS_PARM2);
3242 
3243 	for (e++ ; e < qcvm->num_edicts ; e++)
3244 	{
3245 		ed = EDICT_NUM(e);
3246 		if (ed->free)
3247 			continue;
3248 		t = E_FLOAT(ed,f);
3249 		if (t == s)
3250 		{
3251 			RETURN_EDICT(ed);
3252 			return;
3253 		}
3254 	}
3255 
3256 	RETURN_EDICT(qcvm->edicts);
3257 }
PF_findchainfloat(void)3258 static void PF_findchainfloat(void)
3259 {
3260 	edict_t	*ent, *chain;
3261 	int	i, f;
3262 	float s, t;
3263 	int cfld;
3264 
3265 	chain = (edict_t *)qcvm->edicts;
3266 
3267 	ent = NEXT_EDICT(qcvm->edicts);
3268 	f = G_INT(OFS_PARM0);
3269 	s = G_FLOAT(OFS_PARM1);
3270 	if (qcvm->argc > 2)
3271 		cfld = G_INT(OFS_PARM2);
3272 	else
3273 		cfld = &ent->v.chain - (int*)&ent->v;
3274 
3275 	for (i = 1; i < qcvm->num_edicts; i++, ent = NEXT_EDICT(ent))
3276 	{
3277 		if (ent->free)
3278 			continue;
3279 		t = E_FLOAT(ent,f);
3280 		if (s != t)
3281 			continue;
3282 		((int*)&ent->v)[cfld] = EDICT_TO_PROG(chain);
3283 		chain = ent;
3284 	}
3285 
3286 	RETURN_EDICT(chain);
3287 }
PF_findflags(void)3288 static void PF_findflags(void)
3289 {
3290 	int		e;
3291 	int		f;
3292 	int		s, t;
3293 	edict_t	*ed;
3294 
3295 	e = G_EDICTNUM(OFS_PARM0);
3296 	f = G_INT(OFS_PARM1);
3297 	s = G_FLOAT(OFS_PARM2);
3298 
3299 	for (e++ ; e < qcvm->num_edicts ; e++)
3300 	{
3301 		ed = EDICT_NUM(e);
3302 		if (ed->free)
3303 			continue;
3304 		t = E_FLOAT(ed,f);
3305 		if (t & s)
3306 		{
3307 			RETURN_EDICT(ed);
3308 			return;
3309 		}
3310 	}
3311 
3312 	RETURN_EDICT(qcvm->edicts);
3313 }
PF_findchainflags(void)3314 static void PF_findchainflags(void)
3315 {
3316 	edict_t	*ent, *chain;
3317 	int	i, f;
3318 	int s, t;
3319 	int cfld;
3320 
3321 	chain = (edict_t *)qcvm->edicts;
3322 	ent = NEXT_EDICT(qcvm->edicts);
3323 
3324 	f = G_INT(OFS_PARM0);
3325 	s = G_FLOAT(OFS_PARM1);
3326 	if (qcvm->argc > 2)
3327 		cfld = G_INT(OFS_PARM2);
3328 	else
3329 		cfld = &ent->v.chain - (int*)&ent->v;
3330 
3331 	for (i = 1; i < qcvm->num_edicts; i++, ent = NEXT_EDICT(ent))
3332 	{
3333 		if (ent->free)
3334 			continue;
3335 		t = E_FLOAT(ent,f);
3336 		if (!(s & t))
3337 			continue;
3338 		((int*)&ent->v)[cfld] = EDICT_TO_PROG(chain);
3339 		chain = ent;
3340 	}
3341 
3342 	RETURN_EDICT(chain);
3343 }
PF_numentityfields(void)3344 static void PF_numentityfields(void)
3345 {
3346 	G_FLOAT(OFS_RETURN) = qcvm->progs->numfielddefs;
3347 }
PF_findentityfield(void)3348 static void PF_findentityfield(void)
3349 {
3350 	ddef_t *fld = ED_FindField(G_STRING(OFS_PARM0));
3351 	if (fld)
3352 		G_FLOAT(OFS_RETURN) = fld - qcvm->fielddefs;
3353 	else
3354 		G_FLOAT(OFS_RETURN) = 0;	//the first field is meant to be some dummy placeholder. or it could be modelindex...
3355 }
PF_entityfieldref(void)3356 static void PF_entityfieldref(void)
3357 {
3358 	unsigned int fldidx = G_FLOAT(OFS_PARM0);
3359 	if (fldidx >= (unsigned int)qcvm->progs->numfielddefs)
3360 		G_INT(OFS_RETURN) = 0;
3361 	else
3362 		G_INT(OFS_RETURN) = qcvm->fielddefs[fldidx].ofs;
3363 }
PF_entityfieldname(void)3364 static void PF_entityfieldname(void)
3365 {
3366 	unsigned int fldidx = G_FLOAT(OFS_PARM0);
3367 	if (fldidx < (unsigned int)qcvm->progs->numfielddefs)
3368 		G_INT(OFS_RETURN) = qcvm->fielddefs[fldidx].s_name;
3369 	else
3370 		G_INT(OFS_RETURN) = 0;
3371 }
PF_entityfieldtype(void)3372 static void PF_entityfieldtype(void)
3373 {
3374 	unsigned int fldidx = G_FLOAT(OFS_PARM0);
3375 	if (fldidx >= (unsigned int)qcvm->progs->numfielddefs)
3376 		G_FLOAT(OFS_RETURN) = ev_void;
3377 	else
3378 		G_FLOAT(OFS_RETURN) = qcvm->fielddefs[fldidx].type;
3379 }
PF_getentfldstr(void)3380 static void PF_getentfldstr(void)
3381 {
3382 	unsigned int fldidx = G_FLOAT(OFS_PARM0);
3383 	edict_t *ent = G_EDICT(OFS_PARM1);
3384 	if (fldidx < (unsigned int)qcvm->progs->numfielddefs)
3385 	{
3386 		char *ret = PR_GetTempString();
3387 		const char *val = PR_UglyValueString (qcvm->fielddefs[fldidx].type, (eval_t*)((float*)&ent->v + qcvm->fielddefs[fldidx].ofs));
3388 		q_strlcpy(ret, val, STRINGTEMP_LENGTH);
3389 		G_INT(OFS_RETURN) = PR_SetEngineString(ret);
3390 	}
3391 	else
3392 		G_INT(OFS_RETURN) = 0;
3393 }
PF_putentfldstr(void)3394 static void PF_putentfldstr(void)
3395 {
3396 	unsigned int fldidx = G_FLOAT(OFS_PARM0);
3397 	edict_t *ent = G_EDICT(OFS_PARM1);
3398 	const char *value = G_STRING(OFS_PARM2);
3399 	if (fldidx < (unsigned int)qcvm->progs->numfielddefs)
3400 		G_FLOAT(OFS_RETURN) = ED_ParseEpair ((void *)&ent->v, qcvm->fielddefs+fldidx, value, true);
3401 	else
3402 		G_FLOAT(OFS_RETURN) = false;
3403 }
3404 
PF_parseentitydata(void)3405 static void PF_parseentitydata(void)
3406 {
3407 	edict_t *ed = G_EDICT(OFS_PARM0);
3408 	const char *data = G_STRING(OFS_PARM1), *end;
3409 	unsigned int offset = (qcvm->argc>2)?G_FLOAT(OFS_PARM2):0;
3410 	if (offset)
3411 	{
3412 		unsigned int len = strlen(data);
3413 		if (offset > len)
3414 			offset = len;
3415 	}
3416 	if (!data[offset])
3417 		G_FLOAT(OFS_RETURN) = 0;
3418 	else
3419 	{
3420 		end = COM_Parse(data+offset);
3421 		if (!strcmp(com_token, "{"))
3422 		{
3423 			end = ED_ParseEdict(end, ed);
3424 			G_FLOAT(OFS_RETURN) = end - data;
3425 		}
3426 		else
3427 			G_FLOAT(OFS_RETURN) = 0;	//doesn't look like an ent to me.
3428 	}
3429 }
3430 
PF_callfunction(void)3431 static void PF_callfunction(void)
3432 {
3433 	dfunction_t *fnc;
3434 	const char *fname;
3435 	if (!qcvm->argc)
3436 		return;
3437 	qcvm->argc--;
3438 	fname = G_STRING(OFS_PARM0 + qcvm->argc*3);
3439 	fnc = ED_FindFunction(fname);
3440 	if (fnc && fnc->first_statement > 0)
3441 	{
3442 		PR_ExecuteProgram(fnc - qcvm->functions);
3443 	}
3444 }
PF_isfunction(void)3445 static void PF_isfunction(void)
3446 {
3447 	const char *fname = G_STRING(OFS_PARM0);
3448 	G_FLOAT(OFS_RETURN) = ED_FindFunction(fname)?true:false;
3449 }
3450 
3451 //other stuff
PF_gettime(void)3452 static void PF_gettime (void)
3453 {
3454 	int timer = (qcvm->argc > 0)?G_FLOAT(OFS_PARM0):0;
3455 	switch(timer)
3456 	{
3457 	default:
3458 		Con_DPrintf("PF_gettime: unsupported timer %i\n", timer);
3459 	case 0:		//cached time at start of frame
3460 		G_FLOAT(OFS_RETURN) = realtime;
3461 		break;
3462 	case 1:		//actual time
3463 		G_FLOAT(OFS_RETURN) = Sys_DoubleTime();
3464 		break;
3465 	//case 2:	//highres.. looks like time into the frame. no idea
3466 	//case 3:	//uptime
3467 	//case 4:	//cd track
3468 	//case 5:	//client simtime
3469 	}
3470 }
3471 #define STRINGIFY2(x) #x
3472 #define STRINGIFY(x) STRINGIFY2(x)
PF_infokey_internal(qboolean returnfloat)3473 static void PF_infokey_internal(qboolean returnfloat)
3474 {
3475 	unsigned int ent = G_EDICTNUM(OFS_PARM0);
3476 	const char *key = G_STRING(OFS_PARM1);
3477 	const char *r;
3478 	char buf[1024];
3479 	if (!ent)
3480 	{
3481 		if (!strcmp(key, "*version"))
3482 		{
3483 			q_snprintf(buf, sizeof(buf), ENGINE_NAME_AND_VER);
3484 			r = buf;
3485 		}
3486 		else
3487 			r = NULL;
3488 	}
3489 	else if (ent <= (unsigned int)svs.maxclients && svs.clients[ent-1].active)
3490 	{
3491 		client_t *client = &svs.clients[ent-1];
3492 		r = buf;
3493 		if (!strcmp(key, "ip"))
3494 			r = NET_QSocketGetTrueAddressString(client->netconnection);
3495 		else if (!strcmp(key, "ping"))
3496 		{
3497 			float total = 0;
3498 			unsigned int j;
3499 			for (j = 0; j < NUM_PING_TIMES; j++)
3500 				total+=client->ping_times[j];
3501 			total /= NUM_PING_TIMES;
3502 			q_snprintf(buf, sizeof(buf), "%f", total);
3503 		}
3504 		else if (!strcmp(key, "protocol"))
3505 		{
3506 			switch(sv.protocol)
3507 			{
3508 			case PROTOCOL_NETQUAKE:
3509 				r = "quake";
3510 				break;
3511 			case PROTOCOL_FITZQUAKE:
3512 				r = "fitz666";
3513 				break;
3514 			case PROTOCOL_RMQ:
3515 				r = "rmq999";
3516 				break;
3517 			default:
3518 				r = "";
3519 				break;
3520 			}
3521 		}
3522 		else if (!strcmp(key, "name"))
3523 			r = client->name;
3524 		else if (!strcmp(key, "topcolor"))
3525 			q_snprintf(buf, sizeof(buf), "%u", client->colors>>4);
3526 		else if (!strcmp(key, "bottomcolor"))
3527 			q_snprintf(buf, sizeof(buf), "%u", client->colors&15);
3528 		else if (!strcmp(key, "team"))	//nq doesn't really do teams. qw does though. yay compat?
3529 			q_snprintf(buf, sizeof(buf), "t%u", (client->colors&15)+1);
3530 		else if (!strcmp(key, "*VIP"))
3531 			r = "";
3532 		else if (!strcmp(key, "*spectator"))
3533 			r = "";
3534 		else if (!strcmp(key, "csqcactive"))
3535 			r = "0";
3536 		else
3537 			r = NULL;
3538 	}
3539 	else r = NULL;
3540 
3541 	if (returnfloat)
3542 	{
3543 		if (r)
3544 			G_FLOAT(OFS_RETURN) = atof(r);
3545 		else
3546 			G_FLOAT(OFS_RETURN) = 0;
3547 	}
3548 	else
3549 	{
3550 		if (r)
3551 		{
3552 			char *temp = PR_GetTempString();
3553 			q_strlcpy(temp, r, STRINGTEMP_LENGTH);
3554 			G_INT(OFS_RETURN) = PR_SetEngineString(temp);
3555 		}
3556 		else
3557 			G_INT(OFS_RETURN) = 0;
3558 	}
3559 }
PF_infokey_s(void)3560 static void PF_infokey_s(void)
3561 {
3562 	PF_infokey_internal(false);
3563 }
PF_infokey_f(void)3564 static void PF_infokey_f(void)
3565 {
3566 	PF_infokey_internal(true);
3567 }
3568 
PF_multicast_internal(qboolean reliable,byte * pvs,unsigned int requireext2)3569 static void PF_multicast_internal(qboolean reliable, byte *pvs, unsigned int requireext2)
3570 {
3571 	unsigned int i;
3572 	int cluster;
3573 	mleaf_t *playerleaf;
3574 	if (!pvs)
3575 	{
3576 		if (!requireext2)
3577 			SZ_Write((reliable?&sv.reliable_datagram:&sv.datagram), sv.multicast.data, sv.multicast.cursize);
3578 		else
3579 		{
3580 			for (i = 0; i < (unsigned int)svs.maxclients; i++)
3581 			{
3582 				if (!svs.clients[i].active)
3583 					continue;
3584 				if (!(svs.clients[i].protocol_pext2 & requireext2))
3585 					continue;
3586 				SZ_Write((reliable?&svs.clients[i].message:&svs.clients[i].datagram), sv.multicast.data, sv.multicast.cursize);
3587 			}
3588 		}
3589 	}
3590 	else
3591 	{
3592 		for (i = 0; i < (unsigned int)svs.maxclients; i++)
3593 		{
3594 			if (!svs.clients[i].active)
3595 				continue;
3596 
3597 			if (requireext2 && !(svs.clients[i].protocol_pext2 & requireext2))
3598 				continue;
3599 
3600 			//figure out which cluster (read: pvs index) to use.
3601 			playerleaf = Mod_PointInLeaf(svs.clients[i].edict->v.origin, qcvm->worldmodel);
3602 			cluster = playerleaf - qcvm->worldmodel->leafs;
3603 			cluster--;	//pvs is 1-based, leaf 0 is discarded.
3604 			if (cluster < 0 || (pvs[cluster>>3] & (1<<(cluster&7))))
3605 			{
3606 				//they can see it. add it in to whichever buffer is appropriate.
3607 				if (reliable)
3608 					SZ_Write(&svs.clients[i].message, sv.multicast.data, sv.multicast.cursize);
3609 				else
3610 					SZ_Write(&svs.clients[i].datagram, sv.multicast.data, sv.multicast.cursize);
3611 			}
3612 		}
3613 	}
3614 }
3615 //FIXME: shouldn't really be using pext2, but we don't track the earlier extensions, and it should be safe enough.
SV_Multicast(multicast_t to,float * org,int msg_entity,unsigned int requireext2)3616 static void SV_Multicast(multicast_t to, float *org, int msg_entity, unsigned int requireext2)
3617 {
3618 	unsigned int i;
3619 
3620 	if (to == MULTICAST_INIT && sv.state != ss_loading)
3621 	{
3622 		SZ_Write (&sv.signon, sv.multicast.data, sv.multicast.cursize);
3623 		to = MULTICAST_ALL_R;	//and send to players that are already on
3624 	}
3625 
3626 	switch(to)
3627 	{
3628 	case MULTICAST_INIT:
3629 		SZ_Write (&sv.signon, sv.multicast.data, sv.multicast.cursize);
3630 		break;
3631 	case MULTICAST_ALL_R:
3632 	case MULTICAST_ALL_U:
3633 		PF_multicast_internal(to==MULTICAST_ALL_R, NULL, requireext2);
3634 		break;
3635 	case MULTICAST_PHS_R:
3636 	case MULTICAST_PHS_U:
3637 		//we don't support phs, that would require lots of pvs decompression+merging stuff, and many q1bsps have a LOT of leafs.
3638 		PF_multicast_internal(to==MULTICAST_PHS_R, NULL/*Mod_LeafPHS(Mod_PointInLeaf(org, qcvm->worldmodel), qcvm->worldmodel)*/, requireext2);
3639 		break;
3640 	case MULTICAST_PVS_R:
3641 	case MULTICAST_PVS_U:
3642 		PF_multicast_internal(to==MULTICAST_PVS_R, Mod_LeafPVS(Mod_PointInLeaf(org, qcvm->worldmodel), qcvm->worldmodel), requireext2);
3643 		break;
3644 	case MULTICAST_ONE_R:
3645 	case MULTICAST_ONE_U:
3646 		i = msg_entity-1;
3647 		if (i >= (unsigned int)svs.maxclients)
3648 			break;
3649 		//a unicast, which ignores pvs.
3650 		//(unlike vanilla this allows unicast unreliables, so woo)
3651 		if (svs.clients[i].active)
3652 		{
3653 			SZ_Write(((to==MULTICAST_ONE_R)?&svs.clients[i].message:&svs.clients[i].datagram), sv.multicast.data, sv.multicast.cursize);
3654 		}
3655 		break;
3656 	default:
3657 		break;
3658 	}
3659 	SZ_Clear(&sv.multicast);
3660 }
PF_multicast(void)3661 static void PF_multicast(void)
3662 {
3663 	float *org = G_VECTOR(OFS_PARM0);
3664 	multicast_t to = G_FLOAT(OFS_PARM1);
3665 	SV_Multicast(to, org, NUM_FOR_EDICT(PROG_TO_EDICT(pr_global_struct->msg_entity)), 0);
3666 }
PF_randomvector(void)3667 static void PF_randomvector(void)
3668 {
3669 	vec3_t temp;
3670 	do
3671 	{
3672 		temp[0] = (rand() & 32767) * (2.0 / 32767.0) - 1.0;
3673 		temp[1] = (rand() & 32767) * (2.0 / 32767.0) - 1.0;
3674 		temp[2] = (rand() & 32767) * (2.0 / 32767.0) - 1.0;
3675 	} while (DotProduct(temp, temp) >= 1);
3676 	VectorCopy (temp, G_VECTOR(OFS_RETURN));
3677 }
3678 static void PF_checkextension(void);
3679 static void PF_checkbuiltin(void);
3680 static void PF_builtinsupported(void);
3681 
PF_uri_escape(void)3682 static void PF_uri_escape(void)
3683 {
3684 	static const char *hex = "0123456789ABCDEF";
3685 
3686 	char *result = PR_GetTempString();
3687 	char *o = result;
3688 	const unsigned char *s = (const unsigned char*)G_STRING(OFS_PARM0);
3689 	*result = 0;
3690 	while (*s && o < result+STRINGTEMP_LENGTH-4)
3691 	{
3692 		if ((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || (*s >= '0' && *s <= '9')
3693 				|| *s == '.' || *s == '-' || *s == '_')
3694 			*o++ = *s++;
3695 		else
3696 		{
3697 			*o++ = '%';
3698 			*o++ = hex[*s>>4];
3699 			*o++ = hex[*s&0xf];
3700 			s++;
3701 		}
3702 	}
3703 	*o = 0;
3704 	G_INT(OFS_RETURN) = PR_SetEngineString(result);
3705 }
PF_uri_unescape(void)3706 static void PF_uri_unescape(void)
3707 {
3708 	const char *s = G_STRING(OFS_PARM0), *i;
3709 	char *resultbuf = PR_GetTempString(), *o;
3710 	unsigned char hex;
3711 	i = s; o = resultbuf;
3712 	while (*i && o < resultbuf+STRINGTEMP_LENGTH-2)
3713 	{
3714 		if (*i == '%')
3715 		{
3716 			hex = 0;
3717 			if (i[1] >= 'A' && i[1] <= 'F')
3718 				hex += i[1]-'A'+10;
3719 			else if (i[1] >= 'a' && i[1] <= 'f')
3720 				hex += i[1]-'a'+10;
3721 			else if (i[1] >= '0' && i[1] <= '9')
3722 				hex += i[1]-'0';
3723 			else
3724 			{
3725 				*o++ = *i++;
3726 				continue;
3727 			}
3728 			hex <<= 4;
3729 			if (i[2] >= 'A' && i[2] <= 'F')
3730 				hex += i[2]-'A'+10;
3731 			else if (i[2] >= 'a' && i[2] <= 'f')
3732 				hex += i[2]-'a'+10;
3733 			else if (i[2] >= '0' && i[2] <= '9')
3734 				hex += i[2]-'0';
3735 			else
3736 			{
3737 				*o++ = *i++;
3738 				continue;
3739 			}
3740 			*o++ = hex;
3741 			i += 3;
3742 		}
3743 		else
3744 			*o++ = *i++;
3745 	}
3746 	*o = 0;
3747 	G_INT(OFS_RETURN) = PR_SetEngineString(resultbuf);
3748 }
PF_crc16(void)3749 static void PF_crc16(void)
3750 {
3751 	qboolean insens = G_FLOAT(OFS_PARM0);
3752 	const char *str = PF_VarString(1);
3753 	size_t len = strlen(str);
3754 
3755 	if (insens)
3756 	{
3757 		unsigned short	crc;
3758 
3759 		CRC_Init (&crc);
3760 		while (len--)
3761 			CRC_ProcessByte(&crc, q_tolower(*str++));
3762 		G_FLOAT(OFS_RETURN) = crc;
3763 	}
3764 	else
3765 		G_FLOAT(OFS_RETURN) = CRC_Block((const byte*)str, len);
3766 }
PF_digest_hex(void)3767 static void PF_digest_hex(void)
3768 {
3769 	const char *hashtype = G_STRING(OFS_PARM0);
3770 	const byte *data = (const byte*)PF_VarString(1);
3771 	size_t len = strlen((const char*)data);
3772 	static const char *hex = "0123456789ABCDEF";
3773 	char *resultbuf;
3774 	byte hashdata[20];
3775 
3776 	if (!strcmp(hashtype, "CRC16"))
3777 	{
3778 		int crc = CRC_Block((const byte*)data, len);
3779 		hashdata[0] = crc&0xff;
3780 		hashdata[1] = (crc>>8)&0xff;
3781 		len = 2;
3782 	}
3783 	else if (!strcmp(hashtype, "MD4"))
3784 	{
3785 		Com_BlockFullChecksum((void*)data, len, hashdata);
3786 		len = 16;
3787 	}
3788 	else
3789 	{
3790 		Con_Printf("PF_digest_hex: Unsupported digest %s\n", hashtype);
3791 		G_INT(OFS_RETURN) = 0;
3792 		return;
3793 	}
3794 
3795 	resultbuf = PR_GetTempString();
3796 	G_INT(OFS_RETURN) = PR_SetEngineString(resultbuf);
3797 	data = hashdata;
3798 	while (len --> 0)
3799 	{
3800 		*resultbuf++ = hex[*data>>4];
3801 		*resultbuf++ = hex[*data&0xf];
3802 		data++;
3803 	}
3804 	*resultbuf = 0;
3805 }
3806 
PF_strlennocol(void)3807 static void PF_strlennocol(void)
3808 {
3809 	int r = 0;
3810 	struct markup_s mu;
3811 
3812 	PR_Markup_Begin(&mu, G_STRING(OFS_PARM0), vec3_origin, 1);
3813 	while (PR_Markup_Parse(&mu))
3814 		r++;
3815 	G_FLOAT(OFS_RETURN) = r;
3816 }
PF_strdecolorize(void)3817 static void PF_strdecolorize(void)
3818 {
3819 	int l, c;
3820 	char *r = PR_GetTempString();
3821 	struct markup_s mu;
3822 
3823 	PR_Markup_Begin(&mu, G_STRING(OFS_PARM0), vec3_origin, 1);
3824 	for (l = 0; l < STRINGTEMP_LENGTH-1; l++)
3825 	{
3826 		c = PR_Markup_Parse(&mu);
3827 		if (!c)
3828 			break;
3829 		r[l] = c;
3830 	}
3831 	r[l] = 0;
3832 
3833 	G_INT(OFS_RETURN) = PR_SetEngineString(r);
3834 }
PF_setattachment(void)3835 static void PF_setattachment(void)
3836 {
3837 	edict_t *ent = G_EDICT(OFS_PARM0);
3838 	edict_t *tagent = G_EDICT(OFS_PARM1);
3839 	const char *tagname = G_STRING(OFS_PARM2);
3840 	eval_t *val;
3841 
3842 	if (*tagname)
3843 	{
3844 		//we don't support md3s, or any skeletal formats, so all tag names are logically invalid for us.
3845 		Con_DWarning("PF_setattachment: tag %s not found\n", tagname);
3846 	}
3847 
3848 	if ((val = GetEdictFieldValue(ent, qcvm->extfields.tag_entity)))
3849 		val->edict = EDICT_TO_PROG(tagent);
3850 	if ((val = GetEdictFieldValue(ent, qcvm->extfields.tag_index)))
3851 		val->_float = 0;
3852 }
3853 
PR_CustomStat(int idx,int type)3854 static struct svcustomstat_s *PR_CustomStat(int idx, int type)
3855 {
3856 	size_t i;
3857 	if (idx < 0 || idx >= MAX_CL_STATS)
3858 		return NULL;
3859 	switch(type)
3860 	{
3861 	case ev_ext_integer:
3862 	case ev_float:
3863 	case ev_vector:
3864 	case ev_entity:
3865 		break;
3866 	default:
3867 		return NULL;
3868 	}
3869 
3870 	for (i = 0; i < sv.numcustomstats; i++)
3871 	{
3872 		if (sv.customstats[i].idx == idx && (sv.customstats[i].type==ev_string) == (type==ev_string))
3873 			break;
3874 	}
3875 	if (i == sv.numcustomstats)
3876 		sv.numcustomstats++;
3877 	sv.customstats[i].idx = idx;
3878 	sv.customstats[i].type = type;
3879 	sv.customstats[i].fld = 0;
3880 	sv.customstats[i].ptr = NULL;
3881 	return &sv.customstats[i];
3882 }
PF_clientstat(void)3883 static void PF_clientstat(void)
3884 {
3885 	int idx = G_FLOAT(OFS_PARM0);
3886 	int type = G_FLOAT(OFS_PARM1);
3887 	int fldofs = G_INT(OFS_PARM2);
3888 	struct svcustomstat_s *stat = PR_CustomStat(idx, type);
3889 	if (!stat)
3890 		return;
3891 	stat->fld = fldofs;
3892 }
PF_isbackbuffered(void)3893 static void PF_isbackbuffered(void)
3894 {
3895 	unsigned int plnum = G_EDICTNUM(OFS_PARM0)-1;
3896 	G_FLOAT(OFS_RETURN) = true;	//assume the connection is clogged.
3897 	if (plnum > (unsigned int)svs.maxclients)
3898 		return;	//make error?
3899 	if (!svs.clients[plnum].active)
3900 		return; //empty slot
3901 	if (svs.clients[plnum].message.cursize > DATAGRAM_MTU)
3902 		return;
3903 	G_FLOAT(OFS_RETURN) = false;	//okay to spam with more reliables.
3904 }
3905 
3906 #ifdef PSET_SCRIPT
PF_SV_ForceParticlePrecache(const char * s)3907 int PF_SV_ForceParticlePrecache(const char *s)
3908 {
3909 	unsigned int i;
3910 	for (i = 1; i < MAX_PARTICLETYPES; i++)
3911 	{
3912 		if (!sv.particle_precache[i])
3913 		{
3914 			if (sv.state != ss_loading)
3915 			{
3916 				MSG_WriteByte(&sv.multicast, svcdp_precache);
3917 				MSG_WriteShort(&sv.multicast, i|0x4000);
3918 				MSG_WriteString(&sv.multicast, s);
3919 				SV_Multicast(MULTICAST_ALL_R, NULL, 0, PEXT2_REPLACEMENTDELTAS); //FIXME
3920 			}
3921 
3922 			sv.particle_precache[i] = strcpy(Hunk_Alloc(strlen(s)+1), s);	//weirdness to avoid issues with tempstrings
3923 			return i;
3924 		}
3925 		if (!strcmp(sv.particle_precache[i], s))
3926 			return i;
3927 	}
3928 	return 0;
3929 }
PF_sv_particleeffectnum(void)3930 static void PF_sv_particleeffectnum(void)
3931 {
3932 	const char	*s;
3933 	unsigned int i;
3934 	extern cvar_t r_particledesc;
3935 
3936 	s = G_STRING(OFS_PARM0);
3937 	G_FLOAT(OFS_RETURN) = 0;
3938 //	PR_CheckEmptyString (s);
3939 
3940 	if (!*s)
3941 		return;
3942 
3943 	if (!sv.particle_precache[1] && (!strncmp(s, "effectinfo.", 11) || strstr(r_particledesc.string, "effectinfo")))
3944 		COM_Effectinfo_Enumerate(PF_SV_ForceParticlePrecache);
3945 
3946 	for (i = 1; i < MAX_PARTICLETYPES; i++)
3947 	{
3948 		if (!sv.particle_precache[i])
3949 		{
3950 			if (sv.state != ss_loading)
3951 			{
3952 				if (pr_ext_warned_particleeffectnum++ < 3)
3953 					Con_Warning ("PF_sv_particleeffectnum(%s): Precache should only be done in spawn functions\n", s);
3954 
3955 				MSG_WriteByte(&sv.multicast, svcdp_precache);
3956 				MSG_WriteShort(&sv.multicast, i|0x4000);
3957 				MSG_WriteString(&sv.multicast, s);
3958 				SV_Multicast(MULTICAST_ALL_R, NULL, 0, PEXT2_REPLACEMENTDELTAS);
3959 			}
3960 
3961 			sv.particle_precache[i] = strcpy(Hunk_Alloc(strlen(s)+1), s);	//weirdness to avoid issues with tempstrings
3962 			G_FLOAT(OFS_RETURN) = i;
3963 			return;
3964 		}
3965 		if (!strcmp(sv.particle_precache[i], s))
3966 		{
3967 			if (sv.state != ss_loading && !pr_checkextension.value)
3968 			{
3969 				if (pr_ext_warned_particleeffectnum++ < 3)
3970 					Con_Warning ("PF_sv_particleeffectnum(%s): Precache should only be done in spawn functions\n", s);
3971 			}
3972 			G_FLOAT(OFS_RETURN) = i;
3973 			return;
3974 		}
3975 	}
3976 	PR_RunError ("PF_sv_particleeffectnum: overflow");
3977 }
PF_sv_trailparticles(void)3978 static void PF_sv_trailparticles(void)
3979 {
3980 	int efnum;
3981 	int ednum;
3982 	float *start = G_VECTOR(OFS_PARM2);
3983 	float *end = G_VECTOR(OFS_PARM3);
3984 
3985 	/*DP gets this wrong, lets try to be compatible*/
3986 	if ((unsigned int)G_INT(OFS_PARM1) >= MAX_EDICTS*(unsigned int)qcvm->edict_size)
3987 	{
3988 		ednum = G_EDICTNUM(OFS_PARM0);
3989 		efnum = G_FLOAT(OFS_PARM1);
3990 	}
3991 	else
3992 	{
3993 		efnum = G_FLOAT(OFS_PARM0);
3994 		ednum = G_EDICTNUM(OFS_PARM1);
3995 	}
3996 
3997 	if (efnum <= 0)
3998 		return;
3999 
4000 	MSG_WriteByte(&sv.multicast, svcdp_trailparticles);
4001 	MSG_WriteShort(&sv.multicast, ednum);
4002 	MSG_WriteShort(&sv.multicast, efnum);
4003 	MSG_WriteCoord(&sv.multicast, start[0], sv.protocolflags);
4004 	MSG_WriteCoord(&sv.multicast, start[1], sv.protocolflags);
4005 	MSG_WriteCoord(&sv.multicast, start[2], sv.protocolflags);
4006 	MSG_WriteCoord(&sv.multicast, end[0], sv.protocolflags);
4007 	MSG_WriteCoord(&sv.multicast, end[1], sv.protocolflags);
4008 	MSG_WriteCoord(&sv.multicast, end[2], sv.protocolflags);
4009 
4010 	SV_Multicast(MULTICAST_PHS_U, start, 0, PEXT2_REPLACEMENTDELTAS);
4011 }
PF_sv_pointparticles(void)4012 static void PF_sv_pointparticles(void)
4013 {
4014 	int efnum = G_FLOAT(OFS_PARM0);
4015 	float *org = G_VECTOR(OFS_PARM1);
4016 	float *vel = (qcvm->argc < 3)?vec3_origin:G_VECTOR(OFS_PARM2);
4017 	int count = (qcvm->argc < 4)?1:G_FLOAT(OFS_PARM3);
4018 
4019 	if (efnum <= 0)
4020 		return;
4021 	if (count > 65535)
4022 		count = 65535;
4023 	if (count < 1)
4024 		return;
4025 
4026 	if (count == 1 && !vel[0] && !vel[1] && !vel[2])
4027 	{
4028 		MSG_WriteByte(&sv.multicast, svcdp_pointparticles1);
4029 		MSG_WriteShort(&sv.multicast, efnum);
4030 		MSG_WriteCoord(&sv.multicast, org[0], sv.protocolflags);
4031 		MSG_WriteCoord(&sv.multicast, org[1], sv.protocolflags);
4032 		MSG_WriteCoord(&sv.multicast, org[2], sv.protocolflags);
4033 	}
4034 	else
4035 	{
4036 		MSG_WriteByte(&sv.multicast, svcdp_pointparticles);
4037 		MSG_WriteShort(&sv.multicast, efnum);
4038 		MSG_WriteCoord(&sv.multicast, org[0], sv.protocolflags);
4039 		MSG_WriteCoord(&sv.multicast, org[1], sv.protocolflags);
4040 		MSG_WriteCoord(&sv.multicast, org[2], sv.protocolflags);
4041 		MSG_WriteCoord(&sv.multicast, vel[0], sv.protocolflags);
4042 		MSG_WriteCoord(&sv.multicast, vel[1], sv.protocolflags);
4043 		MSG_WriteCoord(&sv.multicast, vel[2], sv.protocolflags);
4044 		MSG_WriteShort(&sv.multicast, count);
4045 	}
4046 
4047 	SV_Multicast(MULTICAST_PVS_U, org, 0, PEXT2_REPLACEMENTDELTAS);
4048 }
4049 
PF_CL_ForceParticlePrecache(const char * s)4050 int PF_CL_ForceParticlePrecache(const char *s)
4051 {
4052 	int i;
4053 
4054 	//check if an ssqc one already exists with that name
4055 	for (i = 1; i < MAX_PARTICLETYPES; i++)
4056 	{
4057 		if (!cl.particle_precache[i].name)
4058 			break;	//nope, no more known
4059 		if (!strcmp(cl.particle_precache[i].name, s))
4060 			return i;
4061 	}
4062 
4063 	//nope, check for a csqc one, and allocate if needed
4064 	for (i = 1; i < MAX_PARTICLETYPES; i++)
4065 	{
4066 		if (!cl.local_particle_precache[i].name)
4067 		{
4068 			cl.local_particle_precache[i].name = strcpy(Hunk_Alloc(strlen(s)+1), s);	//weirdness to avoid issues with tempstrings
4069 			cl.local_particle_precache[i].index = PScript_FindParticleType(cl.local_particle_precache[i].name);
4070 			return -i;
4071 		}
4072 		if (!strcmp(cl.local_particle_precache[i].name, s))
4073 			return -i;
4074 	}
4075 
4076 	//err... too many. bum.
4077 	return 0;
4078 }
PF_CL_GetParticle(int idx)4079 int PF_CL_GetParticle(int idx)
4080 {	//negatives are csqc-originated particles, positives are ssqc-originated, for consistency allowing networking of particles as identifiers
4081 	if (!idx)
4082 		return P_INVALID;
4083 	if (idx < 0)
4084 	{
4085 		idx = -idx;
4086 		if (idx >= MAX_PARTICLETYPES)
4087 			return P_INVALID;
4088 		return cl.local_particle_precache[idx].index;
4089 	}
4090 	else
4091 	{
4092 		if (idx >= MAX_PARTICLETYPES)
4093 			return P_INVALID;
4094 		return cl.particle_precache[idx].index;
4095 	}
4096 }
4097 
PF_cl_particleeffectnum(void)4098 static void PF_cl_particleeffectnum(void)
4099 {
4100 	const char	*s;
4101 
4102 	s = G_STRING(OFS_PARM0);
4103 	G_FLOAT(OFS_RETURN) = 0;
4104 //	PR_CheckEmptyString (s);
4105 
4106 	if (!*s)
4107 		return;
4108 
4109 	G_FLOAT(OFS_RETURN) = PF_CL_ForceParticlePrecache(s);
4110 	if (!G_FLOAT(OFS_RETURN))
4111 		PR_RunError ("PF_cl_particleeffectnum: overflow");
4112 }
PF_cl_trailparticles(void)4113 static void PF_cl_trailparticles(void)
4114 {
4115 	int efnum;
4116 	edict_t *ent;
4117 	float *start = G_VECTOR(OFS_PARM2);
4118 	float *end = G_VECTOR(OFS_PARM3);
4119 
4120 	if ((unsigned int)G_INT(OFS_PARM1) >= MAX_EDICTS*(unsigned int)qcvm->edict_size)
4121 	{	/*DP gets this wrong, lets try to be compatible*/
4122 		ent = G_EDICT(OFS_PARM0);
4123 		efnum = G_FLOAT(OFS_PARM1);
4124 	}
4125 	else
4126 	{
4127 		efnum = G_FLOAT(OFS_PARM0);
4128 		ent = G_EDICT(OFS_PARM1);
4129 	}
4130 
4131 	if (efnum <= 0)
4132 		return;
4133 	efnum = PF_CL_GetParticle(efnum);
4134 	PScript_ParticleTrail(start, end, efnum, host_frametime, -NUM_FOR_EDICT(ent), NULL, NULL/*&ent->trailstate*/);
4135 }
PF_cl_pointparticles(void)4136 static void PF_cl_pointparticles(void)
4137 {
4138 	int efnum = G_FLOAT(OFS_PARM0);
4139 	float *org = G_VECTOR(OFS_PARM1);
4140 	float *vel = (qcvm->argc < 3)?vec3_origin:G_VECTOR(OFS_PARM2);
4141 	int count = (qcvm->argc < 4)?1:G_FLOAT(OFS_PARM3);
4142 
4143 	if (efnum <= 0)
4144 		return;
4145 	if (count < 1)
4146 		return;
4147 	efnum = PF_CL_GetParticle(efnum);
4148 	PScript_RunParticleEffectState (org, vel, count, efnum, NULL);
4149 }
4150 #else
4151 #define PF_sv_particleeffectnum PF_void_stub
4152 #define PF_sv_trailparticles PF_void_stub
4153 #define PF_sv_pointparticles PF_void_stub
4154 #define PF_cl_particleeffectnum PF_void_stub
4155 #define PF_cl_trailparticles PF_void_stub
4156 #define PF_cl_pointparticles PF_void_stub
4157 #endif
4158 
4159 
PF_cl_getstat_int(void)4160 static void PF_cl_getstat_int(void)
4161 {
4162 	int stnum = G_FLOAT(OFS_PARM0);
4163 	if (stnum < 0 || stnum > countof(cl.stats))
4164 		G_INT(OFS_RETURN) = 0;
4165 	else
4166 		G_INT(OFS_RETURN) = cl.stats[stnum];
4167 }
PF_cl_getstat_float(void)4168 static void PF_cl_getstat_float(void)
4169 {
4170 	int stnum = G_FLOAT(OFS_PARM0);
4171 	if (stnum < 0 || stnum > countof(cl.stats))
4172 		G_FLOAT(OFS_RETURN) = 0;
4173 	else if (qcvm->argc > 1)
4174 	{
4175 		int firstbit = G_FLOAT(OFS_PARM1);
4176 		int bitcount = G_FLOAT(OFS_PARM2);
4177 		G_FLOAT(OFS_RETURN) = (cl.stats[stnum]>>firstbit) & ((1<<bitcount)-1);
4178 	}
4179 	else
4180 		G_FLOAT(OFS_RETURN) = cl.statsf[stnum];
4181 }
PF_cl_getstat_string(void)4182 static void PF_cl_getstat_string(void)
4183 {
4184 	int stnum = G_FLOAT(OFS_PARM0);
4185 	if (stnum < 0 || stnum > countof(cl.statss) || !cl.statss[stnum])
4186 		G_INT(OFS_RETURN) = 0;
4187 	else
4188 	{
4189 		char *result = PR_GetTempString();
4190 		q_strlcpy(result, cl.statss[stnum], STRINGTEMP_LENGTH);
4191 		G_INT(OFS_RETURN) = PR_SetEngineString(result);
4192 	}
4193 }
4194 
4195 static struct
4196 {
4197 	char name[MAX_QPATH];
4198 	unsigned int flags;
4199 	qpic_t *pic;
4200 } *qcpics;
4201 static size_t numqcpics;
4202 static size_t maxqcpics;
PR_ReloadPics(qboolean purge)4203 void PR_ReloadPics(qboolean purge)
4204 {
4205 	numqcpics = 0;
4206 
4207 	free(qcpics);
4208 	qcpics = NULL;
4209 	maxqcpics = 0;
4210 }
4211 #define PICFLAG_AUTO		0	//value used when no flags known
4212 #define PICFLAG_WAD			(1u<<0)	//name matches that of a wad lump
4213 #define PICFLAG_WRAP		(1u<<2)	//make sure npot stuff doesn't break wrapping.
4214 #define PICFLAG_MIPMAP		(1u<<3)	//disable use of scrap...
4215 #define PICFLAG_BLOCK		(1u<<9)	//wait until the texture is fully loaded.
4216 #define PICFLAG_NOLOAD		(1u<<31)
DrawQC_CachePic(const char * picname,unsigned int flags)4217 static qpic_t *DrawQC_CachePic(const char *picname, unsigned int flags)
4218 {	//okay, so this is silly. we've ended up with 3 different cache levels. qcpics, pics, and images.
4219 	size_t i;
4220 	unsigned int texflags;
4221 	for (i = 0; i < numqcpics; i++)
4222 	{	//binary search? something more sane?
4223 		if (!strcmp(picname, qcpics[i].name))
4224 		{
4225 			if (qcpics[i].pic)
4226 				return qcpics[i].pic;
4227 			break;
4228 		}
4229 	}
4230 
4231 	if (strlen(picname) >= MAX_QPATH)
4232 		return NULL;	//too long. get lost.
4233 
4234 	if (flags & PICFLAG_NOLOAD)
4235 		return NULL;	//its a query, not actually needed.
4236 
4237 	if (i+1 > maxqcpics)
4238 	{
4239 		maxqcpics = i + 32;
4240 		qcpics = realloc(qcpics, maxqcpics * sizeof(*qcpics));
4241 	}
4242 
4243 	strcpy(qcpics[i].name, picname);
4244 	qcpics[i].flags = flags;
4245 	qcpics[i].pic = NULL;
4246 
4247 	texflags = TEXPREF_ALPHA | TEXPREF_PAD | TEXPREF_NOPICMIP;
4248 	if (flags & PICFLAG_WRAP)
4249 		texflags &= ~TEXPREF_PAD;	//don't allow padding if its going to need to wrap (even if we don't enable clamp-to-edge normally). I just hope we have npot support.
4250 	if (flags & PICFLAG_MIPMAP)
4251 		texflags |= TEXPREF_MIPMAP;
4252 
4253 	//try to load it from a wad if applicable.
4254 	//the extra gfx/ crap is because DP insists on it for wad images. and its a nightmare to get things working in all engines if we don't accept that quirk too.
4255 	if (flags & PICFLAG_WAD)
4256 		qcpics[i].pic = Draw_PicFromWad2(picname + (strncmp(picname, "gfx/", 4)?0:4), texflags);
4257 	else if (!strncmp(picname, "gfx/", 4) && !strchr(picname+4, '.'))
4258 		qcpics[i].pic = Draw_PicFromWad2(picname+4, texflags);
4259 
4260 	//okay, not a wad pic, try and load a lmp/tga/etc
4261 	if (!qcpics[i].pic)
4262 		qcpics[i].pic = Draw_TryCachePic(picname, texflags);
4263 
4264 	if (i == numqcpics)
4265 		numqcpics++;
4266 
4267 	return qcpics[i].pic;
4268 }
4269 extern gltexture_t *char_texture;
DrawQC_CharacterQuad(float x,float y,int num,float w,float h,float * rgb,float alpha)4270 static void DrawQC_CharacterQuad (float x, float y, int num, float w, float h, float * rgb, float alpha)
4271 {
4272 	float size = 0.0625;
4273 	float frow = (num>>4)*size;
4274 	float fcol = (num&15)*size;
4275 	int i;
4276 	qboolean alpha_blend = alpha < 1.0f;
4277 	size = 0.0624;	//avoid rounding errors...
4278 
4279 	VkBuffer buffer;
4280 	VkDeviceSize buffer_offset;
4281 	basicvertex_t * vertices = (basicvertex_t*)R_VertexAllocate(6 * sizeof(basicvertex_t), &buffer, &buffer_offset);
4282 
4283 	basicvertex_t corner_verts[4];
4284 	memset(&corner_verts, 255, sizeof(corner_verts));
4285 
4286 	corner_verts[0].position[0] = x;
4287 	corner_verts[0].position[1] = y;
4288 	corner_verts[0].position[2] = 0.0f;
4289 	corner_verts[0].texcoord[0] = fcol;
4290 	corner_verts[0].texcoord[1] = frow;
4291 
4292 	corner_verts[1].position[0] = x+8;
4293 	corner_verts[1].position[1] = y;
4294 	corner_verts[1].position[2] = 0.0f;
4295 	corner_verts[1].texcoord[0] = fcol + size;
4296 	corner_verts[1].texcoord[1] = frow;
4297 
4298 	corner_verts[2].position[0] = x+8;
4299 	corner_verts[2].position[1] = y+8;
4300 	corner_verts[2].position[2] = 0.0f;
4301 	corner_verts[2].texcoord[0] = fcol + size;
4302 	corner_verts[2].texcoord[1] = frow + size;
4303 
4304 	corner_verts[3].position[0] = x;
4305 	corner_verts[3].position[1] = y+8;
4306 	corner_verts[3].position[2] = 0.0f;
4307 	corner_verts[3].texcoord[0] = fcol;
4308 	corner_verts[3].texcoord[1] = frow + size;
4309 
4310 	for (i = 0; i<4; ++i)
4311 	{
4312 		corner_verts[i].color[0] = rgb[0] * 255.0f;
4313 		corner_verts[i].color[1] = rgb[1] * 255.0f;
4314 		corner_verts[i].color[2] = rgb[2] * 255.0f;
4315 		corner_verts[i].color[3] = alpha * 255.0f;
4316 	}
4317 
4318 	vertices[0] = corner_verts[0];
4319 	vertices[1] = corner_verts[1];
4320 	vertices[2] = corner_verts[2];
4321 	vertices[3] = corner_verts[2];
4322 	vertices[4] = corner_verts[3];
4323 	vertices[5] = corner_verts[0];
4324 
4325 	vulkan_globals.vk_cmd_bind_vertex_buffers(vulkan_globals.command_buffer, 0, 1, &buffer, &buffer_offset);
4326 	if (alpha_blend)
4327 		R_BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.basic_blend_pipeline[render_pass_index]);
4328 	else
4329 		R_BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.basic_alphatest_pipeline[render_pass_index]);
4330 	vulkan_globals.vk_cmd_bind_descriptor_sets(vulkan_globals.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.basic_pipeline_layout.handle, 0, 1, &char_texture->descriptor_set, 0, NULL);
4331 	vulkan_globals.vk_cmd_draw(vulkan_globals.command_buffer, 6, 1, 0, 0);
4332 }
PF_cl_drawcharacter(void)4333 static void PF_cl_drawcharacter(void)
4334 {
4335 	extern gltexture_t *char_texture;
4336 
4337 	float *pos	= G_VECTOR(OFS_PARM0);
4338 	int charcode= (int)G_FLOAT (OFS_PARM1) & 0xff;
4339 	float *size	= G_VECTOR(OFS_PARM2);
4340 	float *rgb	= G_VECTOR(OFS_PARM3);
4341 	float alpha	= G_FLOAT (OFS_PARM4);
4342 
4343 	if (charcode == 32)
4344 		return; //don't waste time on spaces
4345 
4346 	DrawQC_CharacterQuad (pos[0], pos[1], charcode, size[0], size[1], rgb, alpha);
4347 }
4348 
PF_cl_drawrawstring(void)4349 static void PF_cl_drawrawstring(void)
4350 {
4351 	float *pos	= G_VECTOR(OFS_PARM0);
4352 	const char *text = G_STRING (OFS_PARM1);
4353 	float *size	= G_VECTOR(OFS_PARM2);
4354 	float *rgb	= G_VECTOR(OFS_PARM3);
4355 	float alpha	= G_FLOAT (OFS_PARM4);
4356 
4357 	float x = pos[0];
4358 	int c;
4359 
4360 	if (!*text)
4361 		return; //don't waste time on spaces
4362 
4363 	while ((c = *text++))
4364 	{
4365 		DrawQC_CharacterQuad (x, pos[1], c, size[0], size[1], rgb, alpha);
4366 		x += size[0];
4367 	}
4368 }
PF_cl_drawstring(void)4369 static void PF_cl_drawstring(void)
4370 {
4371 	float *pos	= G_VECTOR(OFS_PARM0);
4372 	const char *text = G_STRING (OFS_PARM1);
4373 	float *size	= G_VECTOR(OFS_PARM2);
4374 	float *rgb	= G_VECTOR(OFS_PARM3);
4375 	float alpha	= G_FLOAT (OFS_PARM4);
4376 
4377 	float x = pos[0];
4378 	struct markup_s mu;
4379 	int c;
4380 
4381 	if (!*text)
4382 		return; //don't waste time on spaces
4383 
4384 	PR_Markup_Begin(&mu, text, rgb, alpha);
4385 
4386 	while ((c = PR_Markup_Parse(&mu)))
4387 	{
4388 		DrawQC_CharacterQuad (x, pos[1], c, size[0], size[1], rgb, alpha);
4389 		x += size[0];
4390 	}
4391 }
PF_cl_stringwidth(void)4392 static void PF_cl_stringwidth(void)
4393 {
4394 	static const float defaultfontsize[] = {8,8};
4395 	const char *text = G_STRING (OFS_PARM0);
4396 	qboolean usecolours = G_FLOAT(OFS_PARM1);
4397 	const float *fontsize = (qcvm->argc>2)?G_VECTOR (OFS_PARM2):defaultfontsize;
4398 	struct markup_s mu;
4399 	int r = 0;
4400 
4401 	if (!usecolours)
4402 		r = strlen(text);
4403 	else
4404 	{
4405 		PR_Markup_Begin(&mu, text, vec3_origin, 1);
4406 		while (PR_Markup_Parse(&mu))
4407 		{
4408 			r += 1;
4409 		}
4410 	}
4411 
4412 	//primitive and lame, but hey.
4413 	G_FLOAT(OFS_RETURN) = fontsize[0] * r;
4414 }
4415 
PF_cl_drawsetclip(void)4416 static void PF_cl_drawsetclip(void)
4417 {
4418 	float s = PR_GetVMScale();
4419 
4420 	float x = G_FLOAT(OFS_PARM0)*s;
4421 	float y = G_FLOAT(OFS_PARM1)*s;
4422 	float w = G_FLOAT(OFS_PARM2)*s;
4423 	float h = G_FLOAT(OFS_PARM3)*s;
4424 
4425 	VkRect2D render_area;
4426 	render_area.offset.x = x;
4427 	render_area.offset.y = y;
4428 	render_area.extent.width = w;
4429 	render_area.extent.height = h;
4430 	vkCmdSetScissor(vulkan_globals.command_buffer, 0, 1, &render_area);
4431 }
PF_cl_drawresetclip(void)4432 static void PF_cl_drawresetclip(void)
4433 {
4434 	VkRect2D render_area;
4435 	render_area.offset.x = 0;
4436 	render_area.offset.y = 0;
4437 	render_area.extent.width = vid.width;
4438 	render_area.extent.height = vid.height;
4439 	vkCmdSetScissor(vulkan_globals.command_buffer, 0, 1, &render_area);
4440 }
4441 
PF_cl_precachepic(void)4442 static void PF_cl_precachepic(void)
4443 {
4444 	const char *name	= G_STRING(OFS_PARM0);
4445 	unsigned int flags = G_FLOAT(OFS_PARM1);
4446 
4447 	G_INT(OFS_RETURN) = G_INT(OFS_PARM0);	//return input string, for convienience
4448 
4449 	if (!DrawQC_CachePic(name, flags) && (flags & PICFLAG_BLOCK))
4450 		G_INT(OFS_RETURN) = 0;	//return input string, for convienience
4451 }
PF_cl_iscachedpic(void)4452 static void PF_cl_iscachedpic(void)
4453 {
4454 	const char *name	= G_STRING(OFS_PARM0);
4455 	if (DrawQC_CachePic(name, PICFLAG_NOLOAD))
4456 		G_FLOAT(OFS_RETURN) = true;
4457 	else
4458 		G_FLOAT(OFS_RETURN) = false;
4459 }
4460 
PF_cl_drawpic(void)4461 static void PF_cl_drawpic(void)
4462 {
4463 	float *pos	= G_VECTOR(OFS_PARM0);
4464 	qpic_t *pic	= DrawQC_CachePic(G_STRING(OFS_PARM1), PICFLAG_AUTO);
4465 	float *size	= G_VECTOR(OFS_PARM2);
4466 	float *rgb	= G_VECTOR(OFS_PARM3);
4467 	float alpha	= G_FLOAT (OFS_PARM4);
4468 
4469 	if (pic)
4470 		Draw_SubPic (pos[0], pos[1], size[0], size[1], pic, 0, 0, 1, 1, rgb, alpha);
4471 }
4472 
PF_cl_getimagesize(void)4473 static void PF_cl_getimagesize(void)
4474 {
4475 	qpic_t *pic	= DrawQC_CachePic(G_STRING(OFS_PARM0), PICFLAG_AUTO);
4476 	if (pic)
4477 		G_VECTORSET(OFS_RETURN, pic->width, pic->height, 0);
4478 	else
4479 		G_VECTORSET(OFS_RETURN, 0, 0, 0);
4480 }
4481 
PF_cl_drawsubpic(void)4482 static void PF_cl_drawsubpic(void)
4483 {
4484 	float *pos	= G_VECTOR(OFS_PARM0);
4485 	float *size	= G_VECTOR(OFS_PARM1);
4486 	qpic_t *pic	= DrawQC_CachePic(G_STRING(OFS_PARM2), PICFLAG_AUTO);
4487 	float *srcpos	= G_VECTOR(OFS_PARM3);
4488 	float *srcsize	= G_VECTOR(OFS_PARM4);
4489 	float *rgb	= G_VECTOR(OFS_PARM5);
4490 	float alpha	= G_FLOAT (OFS_PARM6);
4491 
4492 	if (pic)
4493 		Draw_SubPic (pos[0], pos[1], size[0], size[1], pic, srcpos[0], srcpos[1], srcsize[0], srcsize[1], rgb, alpha);
4494 }
4495 
PF_cl_drawfill(void)4496 static void PF_cl_drawfill(void)
4497 {
4498 	int i;
4499 	float *pos	= G_VECTOR(OFS_PARM0);
4500 	float *size	= G_VECTOR(OFS_PARM1);
4501 	float *rgb	= G_VECTOR(OFS_PARM2);
4502 	float alpha	= G_FLOAT (OFS_PARM3);
4503 
4504 	VkBuffer buffer;
4505 	VkDeviceSize buffer_offset;
4506 	basicvertex_t * vertices = (basicvertex_t*)R_VertexAllocate(6 * sizeof(basicvertex_t), &buffer, &buffer_offset);
4507 
4508 	basicvertex_t corner_verts[4];
4509 	memset(&corner_verts, 255, sizeof(corner_verts));
4510 
4511 	corner_verts[0].position[0] = pos[0];
4512 	corner_verts[0].position[1] = pos[1];
4513 	corner_verts[0].position[2] = 0.0f;
4514 
4515 	corner_verts[1].position[0] = pos[0]+size[0];
4516 	corner_verts[1].position[1] = pos[1];
4517 	corner_verts[1].position[2] = 0.0f;
4518 
4519 	corner_verts[2].position[0] = pos[0]+size[0];
4520 	corner_verts[2].position[1] = pos[1]+size[1];
4521 	corner_verts[2].position[2] = 0.0f;
4522 
4523 	corner_verts[3].position[0] = pos[0];
4524 	corner_verts[3].position[1] = pos[1]+size[1];
4525 	corner_verts[3].position[2] = 0.0f;
4526 
4527 	for (i = 0; i<4; ++i)
4528 	{
4529 		corner_verts[i].color[0] = rgb[0] * 255.0f;
4530 		corner_verts[i].color[1] = rgb[1] * 255.0f;
4531 		corner_verts[i].color[2] = rgb[2] * 255.0f;
4532 		corner_verts[i].color[3] = alpha * 255.0f;
4533 	}
4534 
4535 	vertices[0] = corner_verts[0];
4536 	vertices[1] = corner_verts[1];
4537 	vertices[2] = corner_verts[2];
4538 	vertices[3] = corner_verts[2];
4539 	vertices[4] = corner_verts[3];
4540 	vertices[5] = corner_verts[0];
4541 
4542 	vulkan_globals.vk_cmd_bind_vertex_buffers(vulkan_globals.command_buffer, 0, 1, &buffer, &buffer_offset);
4543 	R_BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.basic_notex_blend_pipeline[render_pass_index]);
4544 	vulkan_globals.vk_cmd_bind_descriptor_sets(vulkan_globals.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.basic_pipeline_layout.handle, 0, 1, &char_texture->descriptor_set, 0, NULL);
4545 	vulkan_globals.vk_cmd_draw(vulkan_globals.command_buffer, 6, 1, 0, 0);
4546 }
4547 
PF_cl_registercommand(void)4548 static void PF_cl_registercommand(void)
4549 {
4550 	const char *cmdname = G_STRING(OFS_PARM0);
4551 	Cmd_AddCommand(cmdname, NULL);
4552 }
PF_uri_get(void)4553 static void PF_uri_get(void){G_VECTORSET(OFS_RETURN, 0,0,0);}
4554 
PF_touchtriggers(void)4555 static void PF_touchtriggers (void)
4556 {
4557 	edict_t	*e;
4558 	float	*org;
4559 
4560 	e = (qcvm->argc > 0)?G_EDICT(OFS_PARM0):G_EDICT(pr_global_struct->self);
4561 	if (qcvm->argc > 1)
4562 	{
4563 		org = G_VECTOR(OFS_PARM1);
4564 		VectorCopy (org, e->v.origin);
4565 	}
4566 	SV_LinkEdict (e, true);
4567 }
4568 
PF_checkpvs(void)4569 static void PF_checkpvs (void)
4570 {
4571 	float *org = G_VECTOR(OFS_PARM0);
4572 	edict_t *ed = G_EDICT(OFS_PARM1);
4573 
4574 	mleaf_t *leaf = Mod_PointInLeaf (org, qcvm->worldmodel);
4575 	byte *pvs = Mod_LeafPVS (leaf, qcvm->worldmodel); //johnfitz -- worldmodel as a parameter
4576 	unsigned int i;
4577 
4578 	for (i=0 ; i < ed->num_leafs ; i++)
4579 	{
4580 		if (pvs[ed->leafnums[i] >> 3] & (1 << (ed->leafnums[i]&7) ))
4581 		{
4582 			G_FLOAT(OFS_RETURN) = true;
4583 			return;
4584 		}
4585 	}
4586 
4587 	G_FLOAT(OFS_RETURN) = false;
4588 }
4589 
4590 //A quick note on number ranges.
4591 //0: automatically assigned. more complicated, but no conflicts over numbers, just names...
4592 //   NOTE: #0 is potentially ambiguous - vanilla will interpret it as instruction 0 (which is normally reserved) rather than a builtin.
4593 //         if such functions were actually used, this would cause any 64bit engines that switched to unsigned types to crash due to an underflow.
4594 //         we do some sneaky hacks to avoid changes to the vm... because we're evil.
4595 //0-199: free for all.
4596 //200-299: fte's random crap
4597 //300-399: csqc's random crap
4598 //400+: dp's random crap
4599 static struct
4600 {
4601 	const char *name;
4602 	builtin_t ssqcfunc;
4603 	builtin_t csqcfunc;
4604 	int documentednumber;
4605 	const char *typestr;
4606 	const char *desc;
4607 	int number;
4608 } extensionbuiltins[] =
4609 #define PF_NoSSQC NULL
4610 #define PF_NoCSQC NULL
4611 #define PF_NoMenu NULL
4612 {
4613 	{"vectoangles2",				PF_ext_vectoangles,				PF_ext_vectoangles,				51,		 D("vector(vector fwd, optional vector up)", "Returns the angles (+x=UP) required to orient an entity to look in the given direction. The 'up' argument is required if you wish to set a roll angle, otherwise it will be limited to just monster-style turning.")},
4614 	{"sin",							PF_Sin,							PF_Sin,							60,		"float(float angle)"},	//60
4615 	{"cos",							PF_Cos,							PF_Cos,							61,		"float(float angle)"},	//61
4616 	{"sqrt",						PF_Sqrt,						PF_Sqrt,						62,		"float(float value)"},	//62
4617 	{"tracetoss",					PF_TraceToss,					PF_TraceToss,					64,		"void(entity ent, entity ignore)"},
4618 	{"etos",						PF_etos,						PF_etos,						65,		"string(entity ent)"},
4619 	{"etof",						PF_num_for_edict,				PF_num_for_edict,				0, 		"float(entity ent)"},
4620 	{"ftoe",						PF_edict_for_num,				PF_edict_for_num,				0, 		"entity(float ent)"},
4621 	{"infokey",						PF_infokey_s,					PF_NoCSQC,						80,		D("string(entity e, string key)", "If e is world, returns the field 'key' from either the serverinfo or the localinfo. If e is a player, returns the value of 'key' from the player's userinfo string. There are a few special exceptions, like 'ip' which is not technically part of the userinfo.")},	//80
4622 	{"infokeyf",					PF_infokey_f,					PF_NoCSQC,						0,		D("float(entity e, string key)", "Identical to regular infokey, but returns it as a float instead of creating new tempstrings.")},	//80
4623 	{"stof",						PF_stof,						PF_stof,						81,		"float(string)"},	//81
4624 	{"multicast",					PF_multicast,					PF_NoCSQC,						82,		D("#define unicast(pl,reli) do{msg_entity = pl; multicast('0 0 0', reli?MULITCAST_ONE_R:MULTICAST_ONE);}while(0)\n"
4625 																											"void(vector where, float set)", "Once the MSG_MULTICAST network message buffer has been filled with data, this builtin is used to dispatch it to the given target, filtering by pvs for reduced network bandwidth.")},	//82
4626 	{"tracebox",					PF_tracebox,					PF_tracebox,					90,		D("void(vector start, vector mins, vector maxs, vector end, float nomonsters, entity ent)", "Exactly like traceline, but a box instead of a uselessly thin point. Acceptable sizes are limited by bsp format, q1bsp has strict acceptable size values.")},
4627 	{"randomvec",					PF_randomvector,				PF_randomvector,				91,		D("vector()", "Returns a vector with random values. Each axis is independantly a value between -1 and 1 inclusive.")},
4628 	{"getlight",					PF_sv_getlight,					PF_cl_getlight,					92,		"vector(vector org)"},// (DP_QC_GETLIGHT),
4629 	{"registercvar",				PF_registercvar,				PF_registercvar,				93,		D("float(string cvarname, string defaultvalue)", "Creates a new cvar on the fly. If it does not already exist, it will be given the specified value. If it does exist, this is a no-op.\nThis builtin has the limitation that it does not apply to configs or commandlines. Such configs will need to use the set or seta command causing this builtin to be a noop.\nIn engines that support it, you will generally find the autocvar feature easier and more efficient to use.")},
4630 	{"min",							PF_min,							PF_min,							94,		D("float(float a, float b, ...)", "Returns the lowest value of its arguments.")},// (DP_QC_MINMAXBOUND)
4631 	{"max",							PF_max,							PF_max,							95,		D("float(float a, float b, ...)", "Returns the highest value of its arguments.")},// (DP_QC_MINMAXBOUND)
4632 	{"bound",						PF_bound,						PF_bound,						96,		D("float(float minimum, float val, float maximum)", "Returns val, unless minimum is higher, or maximum is less.")},// (DP_QC_MINMAXBOUND)
4633 	{"pow",							PF_pow,							PF_pow,							97,		"float(float value, float exp)"},
4634 	{"findfloat",					PF_findfloat,					PF_findfloat,					98,		D("#define findentity findfloat\nentity(entity start, .__variant fld, __variant match)", "Equivelent to the find builtin, but instead of comparing strings contents, this builtin compares the raw values. This builtin requires multiple calls in order to scan all entities - set start to the previous call's return value.\nworld is returned when there are no more entities.")},	// #98 (DP_QC_FINDFLOAT)
4635 	{"checkextension",				PF_checkextension,				PF_checkextension,				99,		D("float(string extname)", "Checks for an extension by its name (eg: checkextension(\"FRIK_FILE\") says that its okay to go ahead and use strcat).\nUse cvar(\"pr_checkextension\") to see if this builtin exists.")},	// #99	//darkplaces system - query a string to see if the mod supports X Y and Z.
4636 	{"checkbuiltin",				PF_checkbuiltin,				PF_checkbuiltin,				0,		D("float(__variant funcref)", "Checks to see if the specified builtin is supported/mapped. This is intended as a way to check for #0 functions, allowing for simple single-builtin functions.")},
4637 	{"builtin_find",				PF_builtinsupported,			PF_builtinsupported,			100,	D("float(string builtinname)", "Looks to see if the named builtin is valid, and returns the builtin number it exists at.")},	// #100	//per builtin system.
4638 	{"anglemod",					PF_anglemod,					PF_anglemod,					102,	"float(float value)"},	//telejano
4639 	{"strlen",						PF_strlen,						PF_strlen,						114,	"float(string s)"},	// (FRIK_FILE)
4640 	{"strcat",						PF_strcat,						PF_strcat,						115,	"string(string s1, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7, optional string s8)"},	// (FRIK_FILE)
4641 	{"substring",					PF_substring,					PF_substring,					116,	"string(string s, float start, float length)"},	// (FRIK_FILE)
4642 	{"stov",						PF_stov,						PF_stov,						117,	"vector(string s)"},	// (FRIK_FILE)
4643 	{"strzone",						PF_strzone,						PF_strzone,						118,	D("string(string s, ...)", "Create a semi-permanent copy of a string that only becomes invalid once strunzone is called on the string (instead of when the engine assumes your string has left scope).")},	// (FRIK_FILE)
4644 	{"strunzone",					PF_strunzone,					PF_strunzone,					119,	D("void(string s)", "Destroys a string that was allocated by strunzone. Further references to the string MAY crash the game.")},	// (FRIK_FILE)
4645 	{"tokenize_menuqc",				PF_Tokenize,					PF_Tokenize,					0,		"float(string s)"},
4646 	{"bitshift",					PF_bitshift,					PF_bitshift,					218,	"float(float number, float quantity)"},
4647 	{"te_lightningblood",			PF_sv_te_lightningblood,		NULL,							219,	"void(vector org)"},
4648 	{"strstrofs",					PF_strstrofs,					PF_strstrofs,					221,	D("float(string s1, string sub, optional float startidx)", "Returns the 0-based offset of sub within the s1 string, or -1 if sub is not in s1.\nIf startidx is set, this builtin will ignore matches before that 0-based offset.")},
4649 	{"str2chr",						PF_str2chr,						PF_str2chr,						222,	D("float(string str, float index)", "Retrieves the character value at offset 'index'.")},
4650 	{"chr2str",						PF_chr2str,						PF_chr2str,						223,	D("string(float chr, ...)", "The input floats are considered character values, and are concatenated.")},
4651 	{"strconv",						PF_strconv,						PF_strconv,						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.")},
4652 	{"strpad",						PF_strpad,						PF_strpad,						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
4653 	{"infoadd",						PF_infoadd,						PF_infoadd,						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.")},
4654 	{"infoget",						PF_infoget,						PF_infoget,						227,	D("string(infostring info, string key)", "Reads a named value from an infostring. The returned value is a tempstring")},
4655 	{"strncmp",						PF_strncmp,						PF_strncmp,						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.")},
4656 	{"strcasecmp",					PF_strncasecmp,					PF_strncasecmp,					229,	D("float(string s1, string s2)",  "Compares the two strings without case sensitivity.\nReturns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon.")},
4657 	{"strncasecmp",					PF_strncasecmp,					PF_strncasecmp,					230,	D("float(string s1, string s2, float len, optional float s1ofs, optional float s2ofs)", "Compares up to 'len' chars in the two strings without case sensitivity. s1ofs allows you to treat s2 as a substring to compare against, or should be 0.\nReturns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon.")},
4658 	{"strtrim",						PF_strtrim,						PF_strtrim,						0,		D("string(string s)", "Trims the whitespace from the start+end of the string.")},
4659 	{"clientstat",					PF_clientstat,					PF_NoCSQC,						232,	D("void(float num, float type, .__variant fld)", "Specifies what data to use in order to send various stats, in a client-specific way.\n'num' should be a value between 32 and 127, other values are reserved.\n'type' must be set to one of the EV_* constants, one of EV_FLOAT, EV_STRING, EV_INTEGER, EV_ENTITY.\nfld must be a reference to the field used, each player will be sent only their own copy of these fields.")},	//EXT_CSQC
4660 	{"isbackbuffered",				PF_isbackbuffered,				PF_NoCSQC,						234,	D("float(entity player)", "Returns if the given player's network buffer will take multiple network frames in order to clear. If this builtin returns non-zero, you should delay or reduce the amount of reliable (and also unreliable) data that you are sending to that client.")},
4661 	{"te_bloodqw",					PF_sv_te_bloodqw,				NULL,							239,	"void(vector org, float count)"},
4662 	{"checkpvs",					PF_checkpvs,					PF_checkpvs,					240,	"float(vector viewpos, entity entity)"},
4663 	{"mod",							PF_mod,							PF_mod,							245,	"float(float a, float n)"},
4664 	{"stoi",						PF_stoi,						PF_stoi,						259,	D("int(string)", "Converts the given string into a true integer. Base 8, 10, or 16 is determined based upon the format of the string.")},
4665 	{"itos",						PF_itos,						PF_itos,						260,	D("string(int)", "Converts the passed true integer into a base10 string.")},
4666 	{"stoh",						PF_stoh,						PF_stoh,						261,	D("int(string)", "Reads a base-16 string (with or without 0x prefix) as an integer. Bugs out if given a base 8 or base 10 string. :P")},
4667 	{"htos",						PF_htos,						PF_htos,						262,	D("string(int)", "Formats an integer as a base16 string, with leading 0s and no prefix. Always returns 8 characters.")},
4668 	{"ftoi",						PF_ftoi,						PF_ftoi,						0,		D("int(float)", "Converts the given float into a true integer without depending on extended qcvm instructions.")},
4669 	{"itof",						PF_itof,						PF_itof,						0,		D("float(int)", "Converts the given true integer into a float without depending on extended qcvm instructions.")},
4670 	{"crossproduct",				PF_crossproduct,				PF_crossproduct,				0,		D("#ifndef dotproduct\n#define dotproduct(v1,v2) ((vector)(v1)*(vector)(v2))\n#endif\nvector(vector v1, vector v2)", "Small helper function to calculate the crossproduct of two vectors.")},
4671 	{"frameforname",				PF_frameforname,				PF_frameforname,				276,	D("float(float modidx, string framename)", "Looks up a framegroup from a model by name, avoiding the need for hardcoding. Returns -1 on error.")},// (FTE_CSQC_SKELETONOBJECTS)
4672 	{"frameduration",				PF_frameduration,				PF_frameduration,				277,	D("float(float modidx, float framenum)", "Retrieves the duration (in seconds) of the specified framegroup.")},// (FTE_CSQC_SKELETONOBJECTS)
4673 	{"touchtriggers",				PF_touchtriggers,				PF_touchtriggers,				279,	D("void(optional entity ent, optional vector neworigin)", "Triggers a touch events between self and every SOLID_TRIGGER entity that it is in contact with. This should typically just be the triggers touch functions. Also optionally updates the origin of the moved entity.")},//
4674 	{"WriteFloat",					PF_WriteFloat,					PF_NoCSQC,						280,	"void(float buf, float fl)"},
4675 	{"frametoname",					PF_frametoname,					PF_frametoname,					284,	"string(float modidx, float framenum)"},
4676 	{"checkcommand",				PF_checkcommand,				PF_checkcommand,				294,	D("float(string name)", "Checks to see if the supplied name is a valid command, cvar, or alias. Returns 0 if it does not exist.")},
4677 	{"iscachedpic",					PF_NoSSQC,						PF_cl_iscachedpic,				316,	D("float(string name)", "Checks to see if the image is currently loaded. Engines might lie, or cache between maps.")},// (EXT_CSQC)
4678 	{"precache_pic",				PF_NoSSQC,						PF_cl_precachepic,				317,	D("string(string name, optional float flags)", "Forces the engine to load the named image. If trywad is specified, the specified name must any lack path and extension.")},// (EXT_CSQC)
4679 	{"drawgetimagesize",			PF_NoSSQC,						PF_cl_getimagesize,				318,	D("#define draw_getimagesize drawgetimagesize\nvector(string picname)", "Returns the dimensions of the named image. Images specified with .lmp should give the original .lmp's dimensions even if texture replacements use a different resolution.")},// (EXT_CSQC)
4680 	{"drawcharacter",				PF_NoSSQC,						PF_cl_drawcharacter,			320,	D("float(vector position, float character, vector size, vector rgb, float alpha, optional float drawflag)", "Draw the given quake character at the given position.\nIf flag&4, the function will consider the char to be a unicode char instead (or display as a ? if outside the 32-127 range).\nsize should normally be something like '8 8 0'.\nrgb should normally be '1 1 1'\nalpha normally 1.\nSoftware engines may assume the named defaults.\nNote that ALL text may be rescaled on the X axis due to variable width fonts. The X axis may even be ignored completely.")},// (EXT_CSQC, [EXT_CSQC_???])
4681 	{"drawrawstring",				PF_NoSSQC,						PF_cl_drawrawstring,			321,	D("float(vector position, string text, vector size, vector rgb, float alpha, optional float drawflag)", "Draws the specified string without using any markup at all, even in engines that support it.\nIf UTF-8 is globally enabled in the engine, then that encoding is used (without additional markup), otherwise it is raw quake chars.\nSoftware engines may assume a size of '8 8 0', rgb='1 1 1', alpha=1, flag&3=0, but it is not an error to draw out of the screen.")},// (EXT_CSQC, [EXT_CSQC_???])
4682 	{"drawpic",						PF_NoSSQC,						PF_cl_drawpic,					322,	D("float(vector position, string pic, vector size, vector rgb, float alpha, optional float drawflag)", "Draws an shader within the given 2d screen box. Software engines may omit support for rgb+alpha, but must support rescaling, and must clip to the screen without crashing.")},// (EXT_CSQC, [EXT_CSQC_???])
4683 	{"drawfill",					PF_NoSSQC,						PF_cl_drawfill,					323,	D("float(vector position, vector size, vector rgb, float alpha, optional float drawflag)", "Draws a solid block over the given 2d box, with given colour, alpha, and blend mode (specified via flags).\nflags&3=0 simple blend.\nflags&3=1 additive blend")},// (EXT_CSQC, [EXT_CSQC_???])
4684 	{"drawsetcliparea",				PF_NoSSQC,						PF_cl_drawsetclip,				324,	D("void(float x, float y, float width, float height)", "Specifies a 2d clipping region (aka: scissor test). 2d draw calls will all be clipped to this 2d box, the area outside will not be modified by any 2d draw call (even 2d polygons).")},// (EXT_CSQC_???)
4685 	{"drawresetcliparea",			PF_NoSSQC,						PF_cl_drawresetclip,			325,	D("void(void)", "Reverts the scissor/clip area to the whole screen.")},// (EXT_CSQC_???)
4686 	{"drawstring",					PF_NoSSQC,						PF_cl_drawstring,				326,	D("float(vector position, string text, vector size, vector rgb, float alpha, float drawflag)", "Draws a string, interpreting markup and recolouring as appropriate.")},// #326
4687 	{"stringwidth",					PF_NoSSQC,						PF_cl_stringwidth,				327,	D("float(string text, float usecolours, vector fontsize='8 8')", "Calculates the width of the screen in virtual pixels. If usecolours is 1, markup that does not affect the string width will be ignored. Will always be decoded as UTF-8 if UTF-8 is globally enabled.\nIf the char size is not specified, '8 8 0' will be assumed.")},// EXT_CSQC_'DARKPLACES'
4688 	{"drawsubpic",					PF_NoSSQC,						PF_cl_drawsubpic,				328,	D("void(vector pos, vector sz, string pic, vector srcpos, vector srcsz, vector rgb, float alpha, optional float drawflag)", "Draws a rescaled subsection of an image to the screen.")},// #328 EXT_CSQC_'DARKPLACES'
4689 	{"getstati",					PF_NoSSQC,						PF_cl_getstat_int,				330,	D("#define getstati_punf(stnum) (float)(__variant)getstati(stnum)\nint(float stnum)", "Retrieves the numerical value of the given EV_INTEGER or EV_ENTITY stat. Use getstati_punf if you wish to type-pun a float stat as an int to avoid truncation issues in DP.")},// (EXT_CSQC)
4690 	{"getstatf",					PF_NoSSQC,						PF_cl_getstat_float,			331,	D("#define getstatbits getstatf\nfloat(float stnum, optional float firstbit, optional float bitcount)", "Retrieves the numerical value of the given EV_FLOAT stat. If firstbit and bitcount are specified, retrieves the upper bits of the STAT_ITEMS stat (converted into a float, so there are no VM dependancies).")},// (EXT_CSQC)
4691 	{"getstats",					PF_NoSSQC,						PF_cl_getstat_string,			332,	D("string(float stnum)", "Retrieves the value of the given EV_STRING stat, as a tempstring.\nString stats use a separate pool of stats from numeric ones.\n")},
4692 	{"setmodelindex",				PF_sv_setmodelindex,			PF_cl_setmodelindex,			333,	D("void(entity e, float mdlindex)", "Sets a model by precache index instead of by name. Otherwise identical to setmodel.")},//
4693 	{"particleeffectnum",			PF_sv_particleeffectnum,		PF_cl_particleeffectnum,		335,	D("float(string effectname)", "Precaches the named particle effect. If your effect name is of the form 'foo.bar' then particles/foo.cfg will be loaded by the client if foo.bar was not already defined.\nDifferent engines will have different particle systems, this specifies the QC API only.")},// (EXT_CSQC)
4694 	{"trailparticles",				PF_sv_trailparticles,			PF_cl_trailparticles,			336,	D("void(float effectnum, entity ent, vector start, vector end)", "Draws the given effect between the two named points. If ent is not world, distances will be cached in the entity in order to avoid framerate dependancies. The entity is not otherwise used.")},// (EXT_CSQC),
4695 	{"pointparticles",				PF_sv_pointparticles,			PF_cl_pointparticles,			337,	D("void(float effectnum, vector origin, optional vector dir, optional float count)", "Spawn a load of particles from the given effect at the given point traveling or aiming along the direction specified. The number of particles are scaled by the count argument.")},// (EXT_CSQC)
4696 	{"print",						PF_print,						PF_print,						339,	D("void(string s, ...)", "Unconditionally print on the local system's console, even in ssqc (doesn't care about the value of the developer cvar).")},//(EXT_CSQC)
4697 	{"registercommand",				NULL,							PF_cl_registercommand,			352,	D("void(string cmdname)", "Register the given console command, for easy console use.\nConsole commands that are later used will invoke CSQC_ConsoleCommand.")},//(EXT_CSQC)
4698 	{"wasfreed",					PF_WasFreed,					PF_WasFreed,					353,	D("float(entity ent)", "Quickly check to see if the entity is currently free. This function is only valid during the two-second non-reuse window, after that it may give bad results. Try one second to make it more robust.")},//(EXT_CSQC) (should be availabe on server too)
4699 	{"copyentity",					PF_copyentity,					PF_copyentity,					400,	D("entity(entity from, optional entity to)", "Copies all fields from one entity to another.")},// (DP_QC_COPYENTITY)
4700 	{"findchain",					PF_findchain,					PF_findchain,					402,	"entity(.string field, string match, optional .entity chainfield)"},// (DP_QC_FINDCHAIN)
4701 	{"findchainfloat",				PF_findchainfloat,				PF_findchainfloat,				403,	"entity(.float fld, float match, optional .entity chainfield)"},// (DP_QC_FINDCHAINFLOAT)
4702 	{"te_blood",					PF_sv_te_blooddp,				NULL,							405,	"void(vector org, vector dir, float count)"},// #405 te_blood
4703 	{"te_particlerain",				PF_sv_te_particlerain,			NULL,							409,	"void(vector mincorner, vector maxcorner, vector vel, float howmany, float color)"},// (DP_TE_PARTICLERAIN)
4704 	{"te_particlesnow",				PF_sv_te_particlesnow,			NULL,							410,	"void(vector mincorner, vector maxcorner, vector vel, float howmany, float color)"},// (DP_TE_PARTICLESNOW)
4705 	{"te_gunshot",					PF_sv_te_gunshot,				PF_cl_te_gunshot,				418,	"void(vector org, optional float count)"},// #418 te_gunshot
4706 	{"te_spike",					PF_sv_te_spike,					PF_cl_te_spike,					419,	"void(vector org)"},// #419 te_spike
4707 	{"te_superspike",				PF_sv_te_superspike,			PF_cl_te_superspike,			420,	"void(vector org)"},// #420 te_superspike
4708 	{"te_explosion",				PF_sv_te_explosion,				PF_cl_te_explosion,				421,	"void(vector org)"},// #421 te_explosion
4709 	{"te_tarexplosion",				PF_sv_te_tarexplosion,			PF_cl_te_tarexplosion,			422,	"void(vector org)"},// #422 te_tarexplosion
4710 	{"te_wizspike",					PF_sv_te_wizspike,				PF_cl_te_wizspike,				423,	"void(vector org)"},// #423 te_wizspike
4711 	{"te_knightspike",				PF_sv_te_knightspike,			PF_cl_te_knightspike,			424,	"void(vector org)"},// #424 te_knightspike
4712 	{"te_lavasplash",				PF_sv_te_lavasplash,			PF_cl_te_lavasplash,			425,	"void(vector org)"},// #425 te_lavasplash
4713 	{"te_teleport",					PF_sv_te_teleport,				PF_cl_te_teleport,				426,	"void(vector org)"},// #426 te_teleport
4714 	{"te_explosion2",				PF_sv_te_explosion2,			PF_cl_te_explosion2,			427,	"void(vector org, float color, float colorlength)"},// #427 te_explosion2
4715 	{"te_lightning1",				PF_sv_te_lightning1,			PF_cl_te_lightning1,			428,	"void(entity own, vector start, vector end)"},// #428 te_lightning1
4716 	{"te_lightning2",				PF_sv_te_lightning2,			PF_cl_te_lightning2,			429,	"void(entity own, vector start, vector end)"},// #429 te_lightning2
4717 	{"te_lightning3",				PF_sv_te_lightning3,			PF_cl_te_lightning3,			430,	"void(entity own, vector start, vector end)"},// #430 te_lightning3
4718 	{"te_beam",						PF_sv_te_beam,					PF_cl_te_beam,					431,	"void(entity own, vector start, vector end)"},// #431 te_beam
4719 	{"vectorvectors",				PF_vectorvectors,				PF_vectorvectors,				432,	"void(vector dir)"},// (DP_QC_VECTORVECTORS)
4720 	{"getsurfacenumpoints",			PF_getsurfacenumpoints,			PF_getsurfacenumpoints,			434,	"float(entity e, float s)"},// (DP_QC_GETSURFACE)
4721 	{"getsurfacepoint",				PF_getsurfacepoint,				PF_getsurfacepoint,				435,	"vector(entity e, float s, float n)"},// (DP_QC_GETSURFACE)
4722 	{"getsurfacenormal",			PF_getsurfacenormal,			PF_getsurfacenormal,			436,	"vector(entity e, float s)"},// (DP_QC_GETSURFACE)
4723 	{"getsurfacetexture",			PF_getsurfacetexture,			PF_getsurfacetexture,			437,	"string(entity e, float s)"},// (DP_QC_GETSURFACE)
4724 	{"getsurfacenearpoint",			PF_getsurfacenearpoint,			PF_getsurfacenearpoint,			438,	"float(entity e, vector p)"},// (DP_QC_GETSURFACE)
4725 	{"getsurfaceclippedpoint",		PF_getsurfaceclippedpoint,		PF_getsurfaceclippedpoint,		439,	"vector(entity e, float s, vector p)"},// (DP_QC_GETSURFACE)
4726 	{"clientcommand",				PF_clientcommand,				PF_NoCSQC,						440,	"void(entity e, string s)"},// (KRIMZON_SV_PARSECLIENTCOMMAND)
4727 	{"tokenize",					PF_Tokenize,					PF_Tokenize,					441,	"float(string s)"},// (KRIMZON_SV_PARSECLIENTCOMMAND)
4728 	{"argv",						PF_ArgV,						PF_ArgV,						442,	"string(float n)"},// (KRIMZON_SV_PARSECLIENTCOMMAND
4729 	{"argc",						PF_ArgC,						PF_ArgC,						0,		"float()"},
4730 	{"setattachment",				PF_setattachment,				PF_setattachment,				443,	"void(entity e, entity tagentity, string tagname)", ""},// (DP_GFX_QUAKE3MODELTAGS)
4731 	{"cvar_string",					PF_cvar_string,					PF_cvar_string,					448,	 "string(string cvarname)"},//DP_QC_CVAR_STRING
4732 	{"findflags",					PF_findflags,					PF_findflags,					449,	"entity(entity start, .float fld, float match)"},//DP_QC_FINDFLAGS
4733 	{"findchainflags",				PF_findchainflags,				PF_findchainflags,				450,	"entity(.float fld, float match, optional .entity chainfield)"},//DP_QC_FINDCHAINFLAGS
4734 	{"dropclient",					PF_dropclient,					PF_NoCSQC,						453,	"void(entity player)"},//DP_SV_BOTCLIENT
4735 	{"spawnclient",					PF_spawnclient,					PF_NoCSQC,						454,	"entity()", "Spawns a dummy player entity.\nNote that such dummy players will be carried from one map to the next.\nWarning: DP_SV_CLIENTCOLORS DP_SV_CLIENTNAME are not implemented in quakespasm, so use KRIMZON_SV_PARSECLIENTCOMMAND's clientcommand builtin to change the bot's name/colours/skin/team/etc, in the same way that clients would ask."},//DP_SV_BOTCLIENT
4736 	{"clienttype",					PF_clienttype,					PF_NoCSQC,						455,	"float(entity client)"},//botclient
4737 	{"WriteUnterminatedString",		PF_WriteString2,				PF_NoCSQC,						456,	"void(float target, string str)"},	//writestring but without the null terminator. makes things a little nicer.
4738 	{"edict_num",					PF_edict_for_num,				PF_edict_for_num,				459,	"entity(float entnum)"},//DP_QC_EDICT_NUM
4739 	{"buf_create",					PF_buf_create,					PF_buf_create,					460,	"strbuf()"},//DP_QC_STRINGBUFFERS
4740 	{"buf_del",						PF_buf_del,						PF_buf_del,						461,	"void(strbuf bufhandle)"},//DP_QC_STRINGBUFFERS
4741 	{"buf_getsize",					PF_buf_getsize,					PF_buf_getsize,					462,	"float(strbuf bufhandle)"},//DP_QC_STRINGBUFFERS
4742 	{"buf_copy",					PF_buf_copy,					PF_buf_copy,					463,	"void(strbuf bufhandle_from, strbuf bufhandle_to)"},//DP_QC_STRINGBUFFERS
4743 	{"buf_sort",					PF_buf_sort,					PF_buf_sort,					464,	"void(strbuf bufhandle, float sortprefixlen, float backward)"},//DP_QC_STRINGBUFFERS
4744 	{"buf_implode",					PF_buf_implode,					PF_buf_implode,					465,	"string(strbuf bufhandle, string glue)"},//DP_QC_STRINGBUFFERS
4745 	{"bufstr_get",					PF_bufstr_get,					PF_bufstr_get,					466,	"string(strbuf bufhandle, float string_index)"},//DP_QC_STRINGBUFFERS
4746 	{"bufstr_set",					PF_bufstr_set,					PF_bufstr_set,					467,	"void(strbuf bufhandle, float string_index, string str)"},//DP_QC_STRINGBUFFERS
4747 	{"bufstr_add",					PF_bufstr_add,					PF_bufstr_add,					468,	"float(strbuf bufhandle, string str, float order)"},//DP_QC_STRINGBUFFERS
4748 	{"bufstr_free",					PF_bufstr_free,					PF_bufstr_free,					469,	"void(strbuf bufhandle, float string_index)"},//DP_QC_STRINGBUFFERS
4749 	{"asin",						PF_asin,						PF_asin,						471,	"float(float s)"},//DP_QC_ASINACOSATANATAN2TAN
4750 	{"acos",						PF_acos,						PF_acos,						472,	"float(float c)"},//DP_QC_ASINACOSATANATAN2TAN
4751 	{"atan",						PF_atan,						PF_atan,						473,	"float(float t)"},//DP_QC_ASINACOSATANATAN2TAN
4752 	{"atan2",						PF_atan2,						PF_atan2,						474,	"float(float c, float s)"},//DP_QC_ASINACOSATANATAN2TAN
4753 	{"tan",							PF_tan,							PF_tan,							475,	"float(float a)"},//DP_QC_ASINACOSATANATAN2TAN
4754 	{"strlennocol",					PF_strlennocol,					PF_strlennocol,					476,	D("float(string s)", "Returns the number of characters in the string after any colour codes or other markup has been parsed.")},//DP_QC_STRINGCOLORFUNCTIONS
4755 	{"strdecolorize",				PF_strdecolorize,				PF_strdecolorize,				477,	D("string(string s)", "Flattens any markup/colours, removing them from the string.")},//DP_QC_STRINGCOLORFUNCTIONS
4756 	{"strftime",					PF_strftime,					PF_strftime,					478,	"string(float uselocaltime, string format, ...)"},	//DP_QC_STRFTIME
4757 	{"tokenizebyseparator",			PF_tokenizebyseparator,			PF_tokenizebyseparator,			479,	"float(string s, string separator1, ...)"},	//DP_QC_TOKENIZEBYSEPARATOR
4758 	{"strtolower",					PF_strtolower,					PF_strtolower,					480,	"string(string s)"},	//DP_QC_STRING_CASE_FUNCTIONS
4759 	{"strtoupper",					PF_strtoupper,					PF_strtoupper,					481,	"string(string s)"},	//DP_QC_STRING_CASE_FUNCTIONS
4760 	{"cvar_defstring",				PF_cvar_defstring,				PF_cvar_defstring,				482,	"string(string s)"},	//DP_QC_CVAR_DEFSTRING
4761 	{"pointsound",					PF_sv_pointsound,				PF_cl_pointsound,				483,	"void(vector origin, string sample, float volume, float attenuation)"},//DP_SV_POINTSOUND
4762 	{"strreplace",					PF_strreplace,					PF_strreplace,					484,	"string(string search, string replace, string subject)"},//DP_QC_STRREPLACE
4763 	{"strireplace",					PF_strireplace,					PF_strireplace,					485,	"string(string search, string replace, string subject)"},//DP_QC_STRREPLACE
4764 	{"getsurfacepointattribute",	PF_getsurfacepointattribute,	PF_getsurfacepointattribute,	486,	"vector(entity e, float s, float n, float a)"},//DP_QC_GETSURFACEPOINTATTRIBUTE
4765 	{"crc16",						PF_crc16,						PF_crc16,						494,	"float(float caseinsensitive, string s, ...)"},//DP_QC_CRC16
4766 	{"cvar_type",					PF_cvar_type,					PF_cvar_type,					495,	"float(string name)"},//DP_QC_CVAR_TYPE
4767 	{"numentityfields",				PF_numentityfields,				PF_numentityfields,				496,	D("float()", "Gives the number of named entity fields. Note that this is not the size of an entity, but rather just the number of unique names (ie: vectors use 4 names rather than 3).")},//DP_QC_ENTITYDATA
4768 	{"findentityfield",				PF_findentityfield,				PF_findentityfield,				0,		D("float(string fieldname)", "Find a field index by name.")},
4769 	{"entityfieldref",				PF_entityfieldref,				PF_entityfieldref,				0,		D("typedef .__variant field_t;\nfield_t(float fieldnum)", "Returns a field value that can be directly used to read entity fields. Be sure to validate the type with entityfieldtype before using.")},//DP_QC_ENTITYDATA
4770 	{"entityfieldname",				PF_entityfieldname,				PF_entityfieldname,				497,	D("string(float fieldnum)", "Retrieves the name of the given entity field.")},//DP_QC_ENTITYDATA
4771 	{"entityfieldtype",				PF_entityfieldtype,				PF_entityfieldtype,				498,	D("float(float fieldnum)", "Provides information about the type of the field specified by the field num. Returns one of the EV_ values.")},//DP_QC_ENTITYDATA
4772 	{"getentityfieldstring",		PF_getentfldstr,				PF_getentfldstr,				499,	"string(float fieldnum, entity ent)"},//DP_QC_ENTITYDATA
4773 	{"putentityfieldstring",		PF_putentfldstr,				PF_putentfldstr,				500,	"float(float fieldnum, entity ent, string s)"},//DP_QC_ENTITYDATA
4774 	{"whichpack",					PF_whichpack,					PF_whichpack,					503,	D("string(string filename, optional float makereferenced)", "Returns the pak file name that contains the file specified. progs/player.mdl will generally return something like 'pak0.pak'. If makereferenced is true, clients will automatically be told that the returned package should be pre-downloaded and used, even if allow_download_refpackages is not set.")},//DP_QC_WHICHPACK
4775 	{"uri_escape",					PF_uri_escape,					PF_uri_escape,					510,	"string(string in)"},//DP_QC_URI_ESCAPE
4776 	{"uri_unescape",				PF_uri_unescape,				PF_uri_unescape,				511,	"string(string in)"},//DP_QC_URI_ESCAPE
4777 	{"num_for_edict",				PF_num_for_edict,				PF_num_for_edict,				512,	"float(entity ent)"},//DP_QC_NUM_FOR_EDICT
4778 	{"uri_get",						PF_uri_get,						PF_uri_get,						513,	"float(string uril, float id, optional string postmimetype, optional string postdata)", "stub."},//DP_QC_URI_GET
4779 	{"tokenize_console",			PF_tokenize_console,			PF_tokenize_console,			514,	D("float(string str)", "Tokenize a string exactly as the console's tokenizer would do so. The regular tokenize builtin became bastardized for convienient string parsing, which resulted in a large disparity that can be exploited to bypass checks implemented in a naive SV_ParseClientCommand function, therefore you can use this builtin to make sure it exactly matches.")},
4780 	{"argv_start_index",			PF_argv_start_index,			PF_argv_start_index,			515,	D("float(float idx)", "Returns the character index that the tokenized arg started at.")},
4781 	{"argv_end_index",				PF_argv_end_index,				PF_argv_end_index,				516,	D("float(float idx)", "Returns the character index that the tokenized arg stopped at.")},
4782 	{"buf_cvarlist",				PF_buf_cvarlist,				PF_buf_cvarlist,				517,	D("void(strbuf strbuf, string pattern, string antipattern)", "Populates the strbuf with a list of known cvar names.")},
4783 	{"cvar_description",			PF_cvar_description,			PF_cvar_description,			518,	D("string(string cvarname)", "Retrieves the description of a cvar, which might be useful for tooltips or help files. This may still not be useful.")},
4784 	{"gettime",						PF_gettime,						PF_gettime,						519,	"float(optional float timetype)"},
4785 	{"log",							PF_Logarithm,					PF_Logarithm,					532,	D("float(float v, optional float base)", "Determines the logarithm of the input value according to the specified base. This can be used to calculate how much something was shifted by.")},
4786 	{"callfunction",				PF_callfunction,				PF_callfunction,				605,	D("void(.../*, string funcname*/)", "Invokes the named function. The function name is always passed as the last parameter and must always be present. The others are passed to the named function as-is")},
4787 	{"isfunction",					PF_isfunction,					PF_isfunction,					607,	D("float(string s)", "Returns true if the named function exists and can be called with the callfunction builtin.")},
4788 	{"parseentitydata",				PF_parseentitydata,				PF_parseentitydata,				613,	D("float(entity e, string s, optional float offset)", "Reads a single entity's fields into an already-spawned entity. s should contain field pairs like in a saved game: {\"foo1\" \"bar\" \"foo2\" \"5\"}. Returns <=0 on failure, otherwise returns the offset in the string that was read to.")},
4789 	{"sprintf",						PF_sprintf,						PF_sprintf,						627,	"string(string fmt, ...)"},
4790 	{"getsurfacenumtriangles",		PF_getsurfacenumtriangles,		PF_getsurfacenumtriangles,		628,	"float(entity e, float s)"},
4791 	{"getsurfacetriangle",			PF_getsurfacetriangle,			PF_getsurfacetriangle,			629,	"vector(entity e, float s, float n)"},
4792 	{"digest_hex",					PF_digest_hex,					PF_digest_hex,					639,	"string(string digest, string data, ...)"},
4793 };
4794 
PR_Can_Particles(unsigned int prot,unsigned int pext1,unsigned int pext2)4795 qboolean PR_Can_Particles(unsigned int prot, unsigned int pext1, unsigned int pext2)
4796 {
4797 	if (r_fteparticles.value == 0)
4798 		return false;
4799 	if (pext2 || (pext1&PEXT1_CSQC))
4800 		return true;	//a bit different, but works
4801 	else
4802 		return false;	//sorry. don't report it as supported.
4803 }
PR_Can_Ent_Alpha(unsigned int prot,unsigned int pext1,unsigned int pext2)4804 qboolean PR_Can_Ent_Alpha(unsigned int prot, unsigned int pext1, unsigned int pext2)
4805 {
4806 	if (prot != PROTOCOL_NETQUAKE)
4807 		return true;	//most base protocols support it
4808 	else if (pext2 & PEXT2_REPLACEMENTDELTAS)
4809 		return true;	//as does fte's extensions
4810 	else
4811 		return false;	//sorry. don't report it as supported.
4812 }
PR_Can_Ent_ColorMod(unsigned int prot,unsigned int pext1,unsigned int pext2)4813 qboolean PR_Can_Ent_ColorMod(unsigned int prot, unsigned int pext1, unsigned int pext2)
4814 {
4815 	if (pext2 & PEXT2_REPLACEMENTDELTAS)
4816 		return true;	//as does fte's extensions
4817 	else
4818 		return false;	//sorry. don't report it as supported.
4819 }
PR_Can_Ent_Scale(unsigned int prot,unsigned int pext1,unsigned int pext2)4820 qboolean PR_Can_Ent_Scale(unsigned int prot, unsigned int pext1, unsigned int pext2)
4821 {
4822 	if (prot == PROTOCOL_RMQ)
4823 		return true;	//some base protocols support it
4824 	else if (pext2 & PEXT2_REPLACEMENTDELTAS)
4825 		return true;	//as does fte's extensions
4826 	else
4827 		return false;	//sorry. don't report it as supported.
4828 }
4829 static struct
4830 {
4831 	const char *name;
4832 	qboolean (*checkextsupported)(unsigned int prot, unsigned int pext1, unsigned int pext2);
4833 } qcextensions[] =
4834 {
4835 	{"DP_CON_SET"},
4836 	{"DP_CON_SETA"},
4837 	{"DP_EF_NOSHADOW"},
4838 	{"DP_ENT_ALPHA",			PR_Can_Ent_Alpha},	//already in quakespasm, supposedly.
4839 	{"DP_ENT_COLORMOD",			PR_Can_Ent_ColorMod},
4840 	{"DP_ENT_SCALE",			PR_Can_Ent_Scale},
4841 	{"DP_ENT_TRAILEFFECTNUM",	PR_Can_Particles},
4842 	{"DP_INPUTBUTTONS"},
4843 	{"DP_QC_ASINACOSATANATAN2TAN"},
4844 	{"DP_QC_COPYENTITY"},
4845 	{"DP_QC_CRC16"},
4846 	{"DP_QC_CVAR_DEFSTRING"},
4847 	{"DP_QC_CVAR_STRING"},
4848 	{"DP_QC_CVAR_TYPE"},
4849 	{"DP_QC_EDICT_NUM"},
4850 	{"DP_QC_ENTITYDATA"},
4851 	{"DP_QC_ETOS"},
4852 	{"DP_QC_FINDCHAIN"},
4853 	{"DP_QC_FINDCHAINFLAGS"},
4854 	{"DP_QC_FINDCHAINFLOAT"},
4855 	{"DP_QC_FINDFLAGS"},
4856 	{"DP_QC_FINDFLOAT"},
4857 	{"DP_QC_GETLIGHT"},
4858 	{"DP_QC_GETSURFACE"},
4859 	{"DP_QC_GETSURFACETRIANGLE"},
4860 	{"DP_QC_GETSURFACEPOINTATTRIBUTE"},
4861 	{"DP_QC_MINMAXBOUND"},
4862 	{"DP_QC_MULTIPLETEMPSTRINGS"},
4863 	{"DP_QC_RANDOMVEC"},
4864 	{"DP_QC_SINCOSSQRTPOW"},
4865 	{"DP_QC_SPRINTF"},
4866 	{"DP_QC_STRFTIME"},
4867 	{"DP_QC_STRING_CASE_FUNCTIONS"},
4868 	{"DP_QC_STRINGBUFFERS"},
4869 	{"DP_QC_STRINGCOLORFUNCTIONS"},
4870 	{"DP_QC_STRREPLACE"},
4871 	{"DP_QC_TOKENIZEBYSEPARATOR"},
4872 	{"DP_QC_TRACEBOX"},
4873 	{"DP_QC_TRACETOSS"},
4874 	{"DP_QC_TRACE_MOVETYPES"},
4875 	{"DP_QC_URI_ESCAPE"},
4876 	{"DP_QC_VECTOANGLES_WITH_ROLL"},
4877 	{"DP_QC_VECTORVECTORS"},
4878 	{"DP_QC_WHICHPACK"},
4879 	{"DP_REGISTERCVAR"},
4880 	{"DP_SV_BOTCLIENT"},
4881 	{"DP_SV_DROPCLIENT"},
4882 	{"DP_SV_POINTSOUND"},
4883 	{"DP_SV_PRINT"},
4884 	{"DP_SV_SPAWNFUNC_PREFIX"},
4885 	{"DP_SV_WRITEUNTERMINATEDSTRING"},
4886 #ifdef PSET_SCRIPT
4887 	{"DP_TE_PARTICLERAIN",		PR_Can_Particles},
4888 	{"DP_TE_PARTICLESNOW",		PR_Can_Particles},
4889 #endif
4890 	{"DP_TE_STANDARDEFFECTBUILTINS"},
4891 	{"EXT_BITSHIFT"},
4892 	{"FTE_ENT_SKIN_CONTENTS"},	//SOLID_BSP&&skin==CONTENTS_FOO changes CONTENTS_SOLID to CONTENTS_FOO, allowing you to swim in moving ents without qc hacks, as well as correcting view cshifts etc.
4893 #ifdef PSET_SCRIPT
4894 	{"FTE_PART_SCRIPT"},
4895 	{"FTE_PART_NAMESPACES"},
4896 #ifdef PSET_SCRIPT_EFFECTINFO
4897 	{"FTE_PART_NAMESPACE_EFFECTINFO"},
4898 #endif
4899 #endif
4900 	{"FTE_QC_CHECKCOMMAND"},
4901 	{"FTE_QC_CROSSPRODUCT"},
4902 	{"FTE_QC_INFOKEY"},
4903 	{"FTE_QC_INTCONV"},
4904 	{"FTE_QC_MULTICAST"},
4905 	{"FTE_STRINGS"},
4906 #ifdef PSET_SCRIPT
4907 	{"FTE_SV_POINTPARTICLES",	PR_Can_Particles},
4908 #endif
4909 	{"KRIMZON_SV_PARSECLIENTCOMMAND"},
4910 	{"ZQ_QC_STRINGS"},
4911 };
4912 
PF_checkextension(void)4913 static void PF_checkextension(void)
4914 {
4915 	const char *extname = G_STRING(OFS_PARM0);
4916 	unsigned int i;
4917 	cvar_t *v;
4918 	char *cvn;
4919 	for (i = 0; i < countof(qcextensions); i++)
4920 	{
4921 		if (!strcmp(extname, qcextensions[i].name))
4922 		{
4923 			if (qcextensions[i].checkextsupported)
4924 			{
4925 				unsigned int prot, pext1, pext2;
4926 				extern unsigned int sv_protocol;
4927 				extern unsigned int sv_protocol_pext1;
4928 				extern unsigned int sv_protocol_pext2;
4929 				extern cvar_t cl_nopext;
4930 				if (sv.active || qcvm == &sv.qcvm)
4931 				{	//server or client+server
4932 					prot = sv_protocol;
4933 					pext1 = sv_protocol_pext1;
4934 					pext2 = sv_protocol_pext2;
4935 
4936 					//if the server seems to be set up for singleplayer then filter by client settings. otherwise just assume the best.
4937 					if (!isDedicated && svs.maxclients == 1 && cl_nopext.value)
4938 						pext1 = pext2 = 0;
4939 				}
4940 				else if (cls.state == ca_connected)
4941 				{	//client only (or demo)
4942 					prot = cl.protocol;
4943 					pext1 = cl.protocol_pext1;
4944 					pext2 = cl.protocol_pext2;
4945 				}
4946 				else
4947 				{	//menuqc? ooer
4948 					prot = 0;
4949 					pext1 = 0;
4950 					pext2 = 0;
4951 				}
4952 				if (!qcextensions[i].checkextsupported(prot, pext1, pext2))
4953 				{
4954 					if (!pr_checkextension.value)
4955 						Con_Printf("Mod queried extension %s, but not enabled\n", extname);
4956 					G_FLOAT(OFS_RETURN) = false;
4957 					return;
4958 				}
4959 			}
4960 
4961 			cvn = va("pr_ext_%s", qcextensions[i].name);
4962 			for (i = 0; cvn[i]; i++)
4963 				if (cvn[i] >= 'A' && cvn[i] <= 'Z')
4964 					cvn[i] = 'a' + (cvn[i]-'A');
4965 			v = Cvar_Create(cvn, "1");
4966 			if (v && !v->value)
4967 			{
4968 				if (!pr_checkextension.value)
4969 					Con_Printf("Mod queried extension %s, but blocked by cvar\n", extname);
4970 				G_FLOAT(OFS_RETURN) = false;
4971 				return;
4972 			}
4973 			if (!pr_checkextension.value)
4974 				Con_Printf("Mod found extension %s\n", extname);
4975 			G_FLOAT(OFS_RETURN) = true;
4976 			return;
4977 		}
4978 	}
4979 	if (!pr_checkextension.value)
4980 		Con_DPrintf("Mod tried extension %s\n", extname);
4981 	G_FLOAT(OFS_RETURN) = false;
4982 }
4983 
PF_builtinsupported(void)4984 static void PF_builtinsupported(void)
4985 {
4986 	const char *biname = G_STRING(OFS_PARM0);
4987 	unsigned int i;
4988 	for (i = 0; i < sizeof(extensionbuiltins) / sizeof(extensionbuiltins[0]); i++)
4989 	{
4990 		if (!strcmp(extensionbuiltins[i].name, biname))
4991 		{
4992 			G_FLOAT(OFS_RETURN) = extensionbuiltins[i].number;
4993 		}
4994 	}
4995 	G_FLOAT(OFS_RETURN) = 0;
4996 }
4997 
4998 
PF_checkbuiltin(void)4999 static void PF_checkbuiltin (void)
5000 {
5001 	func_t funcref = G_INT(OFS_PARM0);
5002 	if ((unsigned int)funcref < (unsigned int)qcvm->progs->numfunctions)
5003 	{
5004 		dfunction_t *fnc = &qcvm->functions[(unsigned int)funcref];
5005 //		const char *funcname = PR_GetString(fnc->s_name);
5006 		int binum = -fnc->first_statement;
5007 		unsigned int i;
5008 
5009 		//qc defines the function at least. nothing weird there...
5010 		if (binum > 0 && binum < qcvm->numbuiltins)
5011 		{
5012 			if (qcvm->builtins[binum] == PF_Fixme)
5013 			{
5014 				G_FLOAT(OFS_RETURN) = false;	//the builtin with that number isn't defined.
5015 				for (i = 0; i < sizeof(extensionbuiltins) / sizeof(extensionbuiltins[0]); i++)
5016 				{
5017 					if (extensionbuiltins[i].number == binum)
5018 					{	//but it will be defined if its actually executed.
5019 						if (extensionbuiltins[i].desc && !strncmp(extensionbuiltins[i].desc, "stub.", 5))
5020 							G_FLOAT(OFS_RETURN) = false;	//pretend it won't work if it probably won't be useful
5021 						else if ((qcvm == &cl.qcvm && !extensionbuiltins[i].csqcfunc)
5022 								|| (qcvm == &sv.qcvm && !extensionbuiltins[i].ssqcfunc))
5023 							G_FLOAT(OFS_RETURN) = false;	//works, but not in this module
5024 						else
5025 							G_FLOAT(OFS_RETURN) = true;
5026 						break;
5027 					}
5028 				}
5029 			}
5030 			else
5031 			{
5032 				G_FLOAT(OFS_RETURN) = true;		//its defined, within the sane range, mapped, everything. all looks good.
5033 				//we should probably go through the available builtins and validate that the qc's name matches what would be expected
5034 				//this is really intended more for builtins defined as #0 though, in such cases, mismatched assumptions are impossible.
5035 			}
5036 		}
5037 		else
5038 			G_FLOAT(OFS_RETURN) = false;	//not a valid builtin (#0 builtins get remapped at load, even if the builtin is activated then)
5039 	}
5040 	else
5041 	{	//not valid somehow.
5042 		G_FLOAT(OFS_RETURN) = false;
5043 	}
5044 }
5045 
PF_Fixme(void)5046 void PF_Fixme (void)
5047 {
5048 	//interrogate the vm to try to figure out exactly which builtin they just tried to execute.
5049 	dstatement_t *st = &qcvm->statements[qcvm->xstatement];
5050 	eval_t *glob = (eval_t*)&qcvm->globals[st->a];
5051 	if ((unsigned int)glob->function < (unsigned int)qcvm->progs->numfunctions)
5052 	{
5053 		dfunction_t *fnc = &qcvm->functions[(unsigned int)glob->function];
5054 		const char *funcname = PR_GetString(fnc->s_name);
5055 		int binum = -fnc->first_statement;
5056 		unsigned int i;
5057 		if (binum >= 0)
5058 		{
5059 			//find an extension with the matching number
5060 			for (i = 0; i < sizeof(extensionbuiltins) / sizeof(extensionbuiltins[0]); i++)
5061 			{
5062 				int num = extensionbuiltins[i].number;
5063 				if (num == binum)
5064 				{	//set it up so we're faster next time
5065 					builtin_t bi = NULL;
5066 					if (qcvm == &sv.qcvm)
5067 						bi = extensionbuiltins[i].ssqcfunc;
5068 					else if (qcvm == &cl.qcvm)
5069 						bi = extensionbuiltins[i].csqcfunc;
5070 					if (!bi)
5071 						continue;
5072 
5073 					num = extensionbuiltins[i].documentednumber;
5074 					if (!pr_checkextension.value || (extensionbuiltins[i].desc && !strncmp(extensionbuiltins[i].desc, "stub.", 5)))
5075 						Con_Warning("Mod is using builtin #%u - %s\n", num, extensionbuiltins[i].name);
5076 					else
5077 						Con_DPrintf2("Mod uses builtin #%u - %s\n", num, extensionbuiltins[i].name);
5078 					qcvm->builtins[binum] = bi;
5079 					qcvm->builtins[binum]();
5080 					return;
5081 				}
5082 			}
5083 
5084 			PR_RunError ("unimplemented builtin #%i - %s", binum, funcname);
5085 		}
5086 	}
5087 	PR_RunError ("PF_Fixme: not a builtin...");
5088 }
5089 
5090 
5091 //called at map end
PR_ShutdownExtensions(void)5092 void PR_ShutdownExtensions(void)
5093 {
5094 	PR_UnzoneAll();
5095 	PF_buf_shutdown();
5096 	tokenize_flush();
5097 	pr_ext_warned_particleeffectnum = 0;
5098 }
5099 
PR_FindExtFunction(const char * entryname)5100 func_t PR_FindExtFunction(const char *entryname)
5101 {	//depends on 0 being an invalid function,
5102 	dfunction_t *func = ED_FindFunction(entryname);
5103 	if (func)
5104 		return func - qcvm->functions;
5105 	return 0;
5106 }
PR_FindExtGlobal(int type,const char * name)5107 static void *PR_FindExtGlobal(int type, const char *name)
5108 {
5109 	ddef_t *def = ED_FindGlobal(name);
5110 	if (def && (def->type&~DEF_SAVEGLOBAL) == type && def->ofs < qcvm->progs->numglobals)
5111 		return qcvm->globals + def->ofs;
5112 	return NULL;
5113 }
5114 
PR_InitExtensions(void)5115 void PR_InitExtensions(void)
5116 {
5117 	size_t i, g,m;
5118 	//this only needs to be done once. because we're evil.
5119 	//it should help slightly with the 'documentation' above at least.
5120 	g = m = sizeof(qcvm->builtins)/sizeof(qcvm->builtins[0]);
5121 	for (i = 0; i < sizeof(extensionbuiltins)/sizeof(extensionbuiltins[0]); i++)
5122 	{
5123 		if (extensionbuiltins[i].documentednumber)
5124 			extensionbuiltins[i].number = extensionbuiltins[i].documentednumber;
5125 		else
5126 			extensionbuiltins[i].number = --g;
5127 	}
5128 }
5129 
5130 //called at map start
PR_EnableExtensions(ddef_t * pr_globaldefs)5131 void PR_EnableExtensions(ddef_t *pr_globaldefs)
5132 {
5133 	unsigned int i, j;
5134 
5135 	for (i = qcvm->numbuiltins; i < countof(qcvm->builtins); i++)
5136 		qcvm->builtins[i] = PF_Fixme;
5137 	qcvm->numbuiltins = i;
5138 	if (!pr_checkextension.value && qcvm == &sv.qcvm)
5139 	{
5140 		Con_DPrintf("not enabling qc extensions\n");
5141 		return;
5142 	}
5143 
5144 #define QCEXTFUNC(n,t) qcvm->extfuncs.n = PR_FindExtFunction(#n);
5145 	QCEXTFUNCS_COMMON
5146 
5147 	//replace standard builtins with new replacement extended ones and selectively populate references to module-specific entrypoints.
5148 	if (qcvm == &cl.qcvm)
5149 	{	//csqc
5150 		for (i = 0; i < sizeof(extensionbuiltins) / sizeof(extensionbuiltins[0]); i++)
5151 		{
5152 			int num = (extensionbuiltins[i].documentednumber);
5153 			if (num && extensionbuiltins[i].csqcfunc && qcvm->builtins[num] != PF_Fixme)
5154 				qcvm->builtins[num] = extensionbuiltins[i].csqcfunc;
5155 		}
5156 
5157 		QCEXTFUNCS_GAME
5158 		QCEXTFUNCS_CS
5159 	}
5160 	else if (qcvm == &sv.qcvm)
5161 	{	//ssqc
5162 		for (i = 0; i < sizeof(extensionbuiltins) / sizeof(extensionbuiltins[0]); i++)
5163 		{
5164 			int num = (extensionbuiltins[i].documentednumber);
5165 			if (num && extensionbuiltins[i].ssqcfunc && qcvm->builtins[num] != PF_Fixme)
5166 				qcvm->builtins[num] = extensionbuiltins[i].ssqcfunc;
5167 		}
5168 
5169 		QCEXTFUNCS_GAME
5170 		QCEXTFUNCS_SV
5171 	}
5172 #undef QCEXTFUNC
5173 
5174 
5175 #define QCEXTGLOBAL_FLOAT(n) qcvm->extglobals.n = PR_FindExtGlobal(ev_float, #n);
5176 #define QCEXTGLOBAL_INT(n) qcvm->extglobals.n = PR_FindExtGlobal(ev_ext_integer, #n);
5177 #define QCEXTGLOBAL_VECTOR(n) qcvm->extglobals.n = PR_FindExtGlobal(ev_vector, #n);
5178 	QCEXTGLOBALS_COMMON
5179 	QCEXTGLOBALS_GAME
5180 	QCEXTGLOBALS_CSQC
5181 #undef QCEXTGLOBAL_FLOAT
5182 #undef QCEXTGLOBAL_INT
5183 #undef QCEXTGLOBAL_VECTOR
5184 
5185 	//any #0 functions are remapped to their builtins here, so we don't have to tweak the VM in an obscure potentially-breaking way.
5186 	for (i = 0; i < (unsigned int)qcvm->progs->numfunctions; i++)
5187 	{
5188 		if (qcvm->functions[i].first_statement == 0 && qcvm->functions[i].s_name && !qcvm->functions[i].parm_start && !qcvm->functions[i].locals)
5189 		{
5190 			const char *name = PR_GetString(qcvm->functions[i].s_name);
5191 			for (j = 0; j < sizeof(extensionbuiltins)/sizeof(extensionbuiltins[0]); j++)
5192 			{
5193 				if (!strcmp(extensionbuiltins[j].name, name))
5194 				{	//okay, map it
5195 					qcvm->functions[i].first_statement = -extensionbuiltins[j].number;
5196 					break;
5197 				}
5198 			}
5199 		}
5200 	}
5201 }
5202 
PR_DumpPlatform_f(void)5203 void PR_DumpPlatform_f(void)
5204 {
5205 	char	name[MAX_OSPATH];
5206 	FILE *f;
5207 	const char *outname = NULL;
5208 	unsigned int i, j;
5209 	enum
5210 	{
5211 		SS=1,
5212 		CS=2,
5213 		MN=4,
5214 	};
5215 	unsigned int targs = 0;
5216 	for (i = 1; i < (unsigned)Cmd_Argc(); )
5217 	{
5218 		const char *arg = Cmd_Argv(i++);
5219 		if (!strncmp(arg, "-O", 2))
5220 		{
5221 			if (arg[2])
5222 				outname = arg+2;
5223 			else
5224 				outname = Cmd_Argv(i++);
5225 		}
5226 		else if (!q_strcasecmp(arg, "-Tcs"))
5227 			targs |= CS;
5228 		else if (!q_strcasecmp(arg, "-Tss"))
5229 			targs |= SS;
5230 		else if (!q_strcasecmp(arg, "-Tmenu"))
5231 			targs |= MN;
5232 		else
5233 		{
5234 			Con_Printf("%s: Unknown argument\n", Cmd_Argv(0));
5235 			return;
5236 		}
5237 	}
5238 	if (!outname)
5239 		outname = ((targs==2)?"qscsextensions":"qsextensions");
5240 	if (!targs)
5241 		targs = SS|CS;
5242 
5243 	if (strstr(outname, ".."))
5244 		return;
5245 	q_snprintf (name, sizeof(name), "%s/src/%s", com_gamedir, outname);
5246 	COM_AddExtension (name, ".qc", sizeof(name));
5247 
5248 	f = fopen (name, "w");
5249 	if (!f)
5250 	{
5251 		Con_Printf("%s: Couldn't write %s\n", Cmd_Argv(0), name);
5252 		return;
5253 	}
5254 	Con_Printf("%s: Writing %s\n", Cmd_Argv(0), name);
5255 
5256 	fprintf(f,
5257 		"/*\n"
5258 		"Extensions file for "ENGINE_NAME_AND_VER"\n"
5259 		"This file is auto-generated by %s %s.\n"
5260 		"You will probably need to use FTEQCC to compile this.\n"
5261 		"*/\n"
5262 		,Cmd_Argv(0), Cmd_Args()?Cmd_Args():"with no args");
5263 
5264 	fprintf(f,
5265 		"\n\n//This file only supports csqc, so including this file in some other situation is a user error\n"
5266 		"#if defined(QUAKEWORLD) || defined(MENU)\n"
5267 		"#error Mixed up module defs\n"
5268 		"#endif\n"
5269 		);
5270 	if (targs & CS)
5271 	{
5272 		fprintf(f,
5273 			"#if !defined(CSQC) && !defined(SSQC) && !defined(MENU)\n"
5274 				"#define CSQC\n"
5275 				"#ifndef CSQC_SIMPLE\n"	//quakespasm's csqc implementation is simplified, and can do huds+menus, but that's about it.
5276 					"#define CSQC_SIMPLE\n"
5277 				"#endif\n"
5278 			"#endif\n"
5279 			);
5280 	}
5281 	else if (targs & SS)
5282 	{
5283 		fprintf(f,
5284 			"#if !defined(CSQC) && !defined(SSQC) && !defined(MENU)\n"
5285 				"#define SSQC\n"
5286 			"#endif\n"
5287 			);
5288 	}
5289 	else if (targs & MN)
5290 	{
5291 		fprintf(f,
5292 			"#if !defined(CSQC) && !defined(SSQC) && !defined(MENU)\n"
5293 				"#define MENU\n"
5294 			"#endif\n"
5295 			);
5296 	}
5297 	fprintf(f,
5298 		"#ifndef QSSDEP\n"
5299 			"#define QSSDEP(reason) __deprecated(reason)\n"
5300 		"#endif\n"
5301 	);
5302 
5303 	fprintf(f, "\n\n//List of advertised extensions\n");
5304 	for (i = 0; i < countof(qcextensions); i++)
5305 		fprintf(f, "//%s\n", qcextensions[i].name);
5306 
5307 	fprintf(f, "\n\n//Explicitly flag this stuff as probably-not-referenced, meaning fteqcc will shut up about it and silently strip what it can.\n");
5308 	fprintf(f, "#pragma noref 1\n");
5309 
5310 	if (targs & (SS|CS))	//qss uses the same system defs for both ssqc and csqc, as a simplification.
5311 	{	//I really hope that fteqcc's unused variable logic is up to scratch
5312 		fprintf(f, "#if defined(CSQC_SIMPLE) || defined(SSQC)\n");
5313 		fprintf(f, "entity		self,other,world;\n");
5314 		fprintf(f, "float		time,frametime,force_retouch;\n");
5315 		fprintf(f, "string		mapname;\n");
5316 		fprintf(f, "float		deathmatch,coop,teamplay,serverflags,total_secrets,total_monsters,found_secrets,killed_monsters,parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10, parm11, parm12, parm13, parm14, parm15, parm16;\n");
5317 		fprintf(f, "vector		v_forward, v_up, v_right;\n");
5318 		fprintf(f, "float		trace_allsolid,trace_startsolid,trace_fraction;\n");
5319 		fprintf(f, "vector		trace_endpos,trace_plane_normal;\n");
5320 		fprintf(f, "float		trace_plane_dist;\n");
5321 		fprintf(f, "entity		trace_ent;\n");
5322 		fprintf(f, "float		trace_inopen,trace_inwater;\n");
5323 		fprintf(f, "entity		msg_entity;\n");
5324 		fprintf(f, "void() 		main,StartFrame,PlayerPreThink,PlayerPostThink,ClientKill,ClientConnect,PutClientInServer,ClientDisconnect,SetNewParms,SetChangeParms;\n");
5325 		fprintf(f, "void		end_sys_globals;\n\n");
5326 		fprintf(f, ".float		modelindex;\n");
5327 		fprintf(f, ".vector		absmin, absmax;\n");
5328 		fprintf(f, ".float		ltime,movetype,solid;\n");
5329 		fprintf(f, ".vector		origin,oldorigin,velocity,angles,avelocity,punchangle;\n");
5330 		fprintf(f, ".string		classname,model;\n");
5331 		fprintf(f, ".float		frame,skin,effects;\n");
5332 		fprintf(f, ".vector		mins, maxs,size;\n");
5333 		fprintf(f, ".void()		touch,use,think,blocked;\n");
5334 		fprintf(f, ".float		nextthink;\n");
5335 		fprintf(f, ".entity		groundentity;\n");
5336 		fprintf(f, ".float		health,frags,weapon;\n");
5337 		fprintf(f, ".string		weaponmodel;\n");
5338 		fprintf(f, ".float		weaponframe,currentammo,ammo_shells,ammo_nails,ammo_rockets,ammo_cells,items,takedamage;\n");
5339 		fprintf(f, ".entity		chain;\n");
5340 		fprintf(f, ".float		deadflag;\n");
5341 		fprintf(f, ".vector		view_ofs;\n");
5342 		fprintf(f, ".float		button0,button1,button2,impulse,fixangle;\n");
5343 		fprintf(f, ".vector		v_angle;\n");
5344 		fprintf(f, ".float		idealpitch;\n");
5345 		fprintf(f, ".string		netname;\n");
5346 		fprintf(f, ".entity 	enemy;\n");
5347 		fprintf(f, ".float		flags,colormap,team,max_health,teleport_time,armortype,armorvalue,waterlevel,watertype,ideal_yaw,yaw_speed;\n");
5348 		fprintf(f, ".entity		aiment,goalentity;\n");
5349 		fprintf(f, ".float		spawnflags;\n");
5350 		fprintf(f, ".string		target,targetname;\n");
5351 		fprintf(f, ".float		dmg_take,dmg_save;\n");
5352 		fprintf(f, ".entity		dmg_inflictor,owner;\n");
5353 		fprintf(f, ".vector		movedir;\n");
5354 		fprintf(f, ".string		message;\n");
5355 		fprintf(f, ".float		sounds;\n");
5356 		fprintf(f, ".string		noise, noise1, noise2, noise3;\n");
5357 		fprintf(f, "void		end_sys_fields;\n");
5358 		fprintf(f, "#endif\n");
5359 	}
5360 	if (targs & MN)
5361 	{
5362 		fprintf(f, "#if defined(MENU)\n");
5363 		fprintf(f, "entity		self;\n");
5364 		fprintf(f, "void		end_sys_globals;\n\n");
5365 		fprintf(f, "void		end_sys_fields;\n");
5366 		fprintf(f, "#endif\n");
5367 	}
5368 
5369 	fprintf(f, "\n\n//Some custom types (that might be redefined as accessors by fteextensions.qc, although we don't define any methods here)\n");
5370 	fprintf(f, "#ifdef _ACCESSORS\n");
5371 	fprintf(f, "accessor strbuf:float;\n");
5372 	fprintf(f, "accessor searchhandle:float;\n");
5373 	fprintf(f, "accessor hashtable:float;\n");
5374 	fprintf(f, "accessor infostring:string;\n");
5375 	fprintf(f, "accessor filestream:float;\n");
5376 	fprintf(f, "#else\n");
5377 	fprintf(f, "#define strbuf float\n");
5378 	fprintf(f, "#define searchhandle float\n");
5379 	fprintf(f, "#define hashtable float\n");
5380 	fprintf(f, "#define infostring string\n");
5381 	fprintf(f, "#define filestream float\n");
5382 	fprintf(f, "#endif\n");
5383 
5384 
5385 	fprintf(f, "\n\n//Common entry points\n");
5386 #define QCEXTFUNC(n,t) fprintf(f, t " " #n "\n");
5387 	QCEXTFUNCS_COMMON
5388 	if (targs & (SS|CS))
5389 	{
5390 		QCEXTFUNCS_GAME
5391 	}
5392 
5393 	if (targs & SS)
5394 	{
5395 		fprintf(f, "\n\n//Serverside entry points\n");
5396 		QCEXTFUNCS_SV
5397 	}
5398 	if (targs & CS)
5399 	{
5400 		fprintf(f, "\n\n//CSQC entry points\n");
5401 		QCEXTFUNCS_CS
5402 	}
5403 #undef QCEXTFUNC
5404 
5405 #define QCEXTGLOBAL_INT(n) fprintf(f, "int " #n ";\n");
5406 #define QCEXTGLOBAL_FLOAT(n) fprintf(f, "float " #n ";\n");
5407 #define QCEXTGLOBAL_VECTOR(n) fprintf(f, "vector " #n ";\n");
5408 	QCEXTGLOBALS_COMMON
5409 	if (targs & (CS|SS))
5410 	{
5411 		QCEXTGLOBALS_GAME
5412 	}
5413 #undef QCEXTGLOBAL_INT
5414 #undef QCEXTGLOBAL_FLOAT
5415 #undef QCEXTGLOBAL_VECTOR
5416 
5417 	fprintf(f, "const float FALSE		= 0;\n");
5418 	fprintf(f, "const float TRUE		= 1;\n");
5419 
5420 	if (targs & (CS|SS))
5421 	{
5422 		fprintf(f, "const float STAT_HEALTH = 0;		/* Player's health. */\n");
5423 //		fprintf(f, "const float STAT_FRAGS = 1;			/* unused */\n");
5424 		fprintf(f, "const float STAT_WEAPONMODELI = 2;	/* This is the modelindex of the current viewmodel (renamed from the original name 'STAT_WEAPON' due to confusions). */\n");
5425 		fprintf(f, "const float STAT_AMMO = 3;			/* player.currentammo */\n");
5426 		fprintf(f, "const float STAT_ARMOR = 4;\n");
5427 		fprintf(f, "const float STAT_WEAPONFRAME = 5;\n");
5428 		fprintf(f, "const float STAT_SHELLS = 6;\n");
5429 		fprintf(f, "const float STAT_NAILS = 7;\n");
5430 		fprintf(f, "const float STAT_ROCKETS = 8;\n");
5431 		fprintf(f, "const float STAT_CELLS = 9;\n");
5432 		fprintf(f, "const float STAT_ACTIVEWEAPON = 10;	/* player.weapon */\n");
5433 		fprintf(f, "const float STAT_TOTALSECRETS = 11;\n");
5434 		fprintf(f, "const float STAT_TOTALMONSTERS = 12;\n");
5435 		fprintf(f, "const float STAT_FOUNDSECRETS = 13;\n");
5436 		fprintf(f, "const float STAT_KILLEDMONSTERS = 14;\n");
5437 		fprintf(f, "const float STAT_ITEMS = 15;		/* self.items | (self.items2<<23). In order to decode this stat properly, you need to use getstatbits(STAT_ITEMS,0,23) to read self.items, and getstatbits(STAT_ITEMS,23,11) to read self.items2 or getstatbits(STAT_ITEMS,28,4) to read the visible part of serverflags, whichever is applicable. */\n");
5438 		fprintf(f, "const float STAT_VIEWHEIGHT = 16;	/* player.view_ofs_z */\n");
5439 		fprintf(f, "const float STAT_VIEW2 = 20;		/* This stat contains the number of the entity in the server's .view2 field. */\n");
5440 		fprintf(f, "const float STAT_IDEALPITCH = 25;\n");
5441 		fprintf(f, "const float STAT_PUNCHANGLE_X = 26;\n");
5442 		fprintf(f, "const float STAT_PUNCHANGLE_Y = 27;\n");
5443 		fprintf(f, "const float STAT_PUNCHANGLE_Z = 28;\n");
5444 
5445 		fprintf(f, "const float SOLID_BBOX = %i;\n", SOLID_BBOX);
5446 		fprintf(f, "const float SOLID_BSP = %i;\n", SOLID_BSP);
5447 		fprintf(f, "const float SOLID_NOT = %i;\n", SOLID_NOT);
5448 		fprintf(f, "const float SOLID_SLIDEBOX = %i;\n", SOLID_SLIDEBOX);
5449 		fprintf(f, "const float SOLID_TRIGGER = %i;\n", SOLID_TRIGGER);
5450 
5451 		fprintf(f, "const float MOVETYPE_NONE = %i;\n", MOVETYPE_NONE);
5452 		fprintf(f, "const float MOVETYPE_WALK = %i;\n", MOVETYPE_WALK);
5453 		fprintf(f, "const float MOVETYPE_STEP = %i;\n", MOVETYPE_STEP);
5454 		fprintf(f, "const float MOVETYPE_FLY = %i;\n", MOVETYPE_FLY);
5455 		fprintf(f, "const float MOVETYPE_TOSS = %i;\n", MOVETYPE_TOSS);
5456 		fprintf(f, "const float MOVETYPE_PUSH = %i;\n", MOVETYPE_PUSH);
5457 		fprintf(f, "const float MOVETYPE_NOCLIP = %i;\n", MOVETYPE_NOCLIP);
5458 		fprintf(f, "const float MOVETYPE_FLYMISSILE = %i;\n", MOVETYPE_FLYMISSILE);
5459 		fprintf(f, "const float MOVETYPE_BOUNCE = %i;\n", MOVETYPE_BOUNCE);
5460 
5461 		fprintf(f, "const float CONTENT_EMPTY = %i;\n", CONTENTS_EMPTY);
5462 		fprintf(f, "const float CONTENT_SOLID = %i;\n", CONTENTS_SOLID);
5463 		fprintf(f, "const float CONTENT_WATER = %i;\n", CONTENTS_WATER);
5464 		fprintf(f, "const float CONTENT_SLIME = %i;\n", CONTENTS_SLIME);
5465 		fprintf(f, "const float CONTENT_LAVA = %i;\n", CONTENTS_LAVA);
5466 		fprintf(f, "const float CONTENT_SKY = %i;\n", CONTENTS_SKY);
5467 
5468 		fprintf(f, "__used var float physics_mode = 2;\n");
5469 
5470 		fprintf(f, "const float TE_SPIKE = %i;\n", TE_SPIKE);
5471 		fprintf(f, "const float TE_SUPERSPIKE = %i;\n", TE_SUPERSPIKE);
5472 		fprintf(f, "const float TE_GUNSHOT = %i;\n", TE_GUNSHOT);
5473 		fprintf(f, "const float TE_EXPLOSION = %i;\n", TE_EXPLOSION);
5474 		fprintf(f, "const float TE_TAREXPLOSION = %i;\n", TE_TAREXPLOSION);
5475 		fprintf(f, "const float TE_LIGHTNING1 = %i;\n", TE_LIGHTNING1);
5476 		fprintf(f, "const float TE_LIGHTNING2 = %i;\n", TE_LIGHTNING2);
5477 		fprintf(f, "const float TE_WIZSPIKE = %i;\n", TE_WIZSPIKE);
5478 		fprintf(f, "const float TE_KNIGHTSPIKE = %i;\n", TE_KNIGHTSPIKE);
5479 		fprintf(f, "const float TE_LIGHTNING3 = %i;\n", TE_LIGHTNING3);
5480 		fprintf(f, "const float TE_LAVASPLASH = %i;\n", TE_LAVASPLASH);
5481 		fprintf(f, "const float TE_TELEPORT = %i;\n", TE_TELEPORT);
5482 		fprintf(f, "const float TE_EXPLOSION2 = %i;\n", TE_EXPLOSION2);
5483 		fprintf(f, "const float TE_BEAM = %i;\n", TE_BEAM);
5484 
5485 
5486 		fprintf(f, "const float MF_ROCKET			= %#x;\n", EF_ROCKET);
5487 		fprintf(f, "const float MF_GRENADE			= %#x;\n", EF_GRENADE);
5488 		fprintf(f, "const float MF_GIB				= %#x;\n", EF_GIB);
5489 		fprintf(f, "const float MF_ROTATE			= %#x;\n", EF_ROTATE);
5490 		fprintf(f, "const float MF_TRACER			= %#x;\n", EF_TRACER);
5491 		fprintf(f, "const float MF_ZOMGIB			= %#x;\n", EF_ZOMGIB);
5492 		fprintf(f, "const float MF_TRACER2			= %#x;\n", EF_TRACER2);
5493 		fprintf(f, "const float MF_TRACER3			= %#x;\n", EF_TRACER3);
5494 
5495 
5496 		fprintf(f, "const float EF_BRIGHTFIELD = %i;\n", EF_BRIGHTFIELD);
5497 		fprintf(f, "const float EF_MUZZLEFLASH = %i;\n", EF_MUZZLEFLASH);
5498 		fprintf(f, "const float EF_BRIGHTLIGHT = %i;\n", EF_BRIGHTLIGHT);
5499 		fprintf(f, "const float EF_DIMLIGHT = %i;\n", EF_DIMLIGHT);
5500 
5501 		fprintf(f, "const float FL_FLY = %i;\n", FL_FLY);
5502 		fprintf(f, "const float FL_SWIM = %i;\n", FL_SWIM);
5503 		fprintf(f, "const float FL_CLIENT = %i;\n", FL_CLIENT);
5504 		fprintf(f, "const float FL_INWATER = %i;\n", FL_INWATER);
5505 		fprintf(f, "const float FL_MONSTER = %i;\n", FL_MONSTER);
5506 		fprintf(f, "const float FL_GODMODE = %i;\n", FL_GODMODE);
5507 		fprintf(f, "const float FL_NOTARGET = %i;\n", FL_NOTARGET);
5508 		fprintf(f, "const float FL_ITEM = %i;\n", FL_ITEM);
5509 		fprintf(f, "const float FL_ONGROUND = %i;\n", FL_ONGROUND);
5510 		fprintf(f, "const float FL_PARTIALGROUND = %i;\n", FL_PARTIALGROUND);
5511 		fprintf(f, "const float FL_WATERJUMP = %i;\n", FL_WATERJUMP);
5512 		fprintf(f, "const float FL_JUMPRELEASED = %i;\n", FL_JUMPRELEASED);
5513 
5514 		fprintf(f, "const float ATTN_NONE = %i;\n", 0);
5515 		fprintf(f, "const float ATTN_NORM = %i;\n", 1);
5516 		fprintf(f, "const float ATTN_IDLE = %i;\n", 2);
5517 		fprintf(f, "const float ATTN_STATIC = %i;\n", 3);
5518 
5519 		fprintf(f, "const float CHAN_AUTO = %i;\n", 0);
5520 		fprintf(f, "const float CHAN_WEAPON = %i;\n", 1);
5521 		fprintf(f, "const float CHAN_VOICE = %i;\n", 2);
5522 		fprintf(f, "const float CHAN_ITEM = %i;\n", 3);
5523 		fprintf(f, "const float CHAN_BODY = %i;\n", 4);
5524 	}
5525 
5526 	fprintf(f, "const float STAT_USER = 32;			/* Custom user stats start here (lower values are reserved for engine use). */\n");
5527 	//these can be used for both custom stats and for reflection
5528 	fprintf(f, "const float EV_VOID = %i;\n", ev_void);
5529 	fprintf(f, "const float EV_STRING = %i;\n", ev_string);
5530 	fprintf(f, "const float EV_FLOAT = %i;\n", ev_float);
5531 	fprintf(f, "const float EV_VECTOR = %i;\n", ev_vector);
5532 	fprintf(f, "const float EV_ENTITY = %i;\n", ev_entity);
5533 	fprintf(f, "const float EV_FIELD = %i;\n", ev_field);
5534 	fprintf(f, "const float EV_FUNCTION = %i;\n", ev_function);
5535 	fprintf(f, "const float EV_POINTER = %i;\n", ev_pointer);
5536 	fprintf(f, "const float EV_INTEGER = %i;\n", ev_ext_integer);
5537 
5538 #define QCEXTFIELD(n,t) fprintf(f, "%s %s;\n", t, #n);
5539 	//extra fields
5540 	fprintf(f, "\n\n//Supported Extension fields\n");
5541 	QCEXTFIELDS_ALL
5542 	if (targs & (SS|CS)) {QCEXTFIELDS_GAME}
5543 	if (targs & (SS)) {QCEXTFIELDS_SS}
5544 #undef QCEXTFIELD
5545 
5546 	if (targs & SS)
5547 	{
5548 		fprintf(f, ".float style;\n");		//not used by the engine, but is used by tools etc.
5549 		fprintf(f, ".float light_lev;\n");	//ditto.
5550 
5551 		//extra constants
5552 		fprintf(f, "\n\n//Supported Extension Constants\n");
5553 
5554 		fprintf(f, "const float CLIENTTYPE_DISCONNECT	= "STRINGIFY(0)";\n");
5555 		fprintf(f, "const float CLIENTTYPE_REAL			= "STRINGIFY(1)";\n");
5556 		fprintf(f, "const float CLIENTTYPE_BOT			= "STRINGIFY(2)";\n");
5557 		fprintf(f, "const float CLIENTTYPE_NOTCLIENT	= "STRINGIFY(3)";\n");
5558 
5559 		fprintf(f, "const float DAMAGE_AIM = %i;\n", DAMAGE_AIM);
5560 		fprintf(f, "const float DAMAGE_NO = %i;\n", DAMAGE_NO);
5561 		fprintf(f, "const float DAMAGE_YES = %i;\n", DAMAGE_YES);
5562 
5563 		fprintf(f, "const float MSG_BROADCAST = %i;\n", MSG_BROADCAST);
5564 		fprintf(f, "const float MSG_ONE = %i;\n", MSG_ONE);
5565 		fprintf(f, "const float MSG_ALL = %i;\n", MSG_ALL);
5566 		fprintf(f, "const float MSG_INIT = %i;\n", MSG_INIT);
5567 		fprintf(f, "const float MSG_MULTICAST = %i;\n", MSG_EXT_MULTICAST);
5568 		fprintf(f, "const float MSG_ENTITY = %i;\n", MSG_EXT_ENTITY);
5569 
5570 		fprintf(f, "const float MSG_MULTICAST	= %i;\n", 4);
5571 		fprintf(f, "const float MULTICAST_ALL	= %i;\n", MULTICAST_ALL_U);
5572 		fprintf(f, "const float MULTICAST_PVS	= %i;\n", MULTICAST_PVS_U);
5573 		fprintf(f, "const float MULTICAST_ONE	= %i;\n", MULTICAST_ONE_U);
5574 		fprintf(f, "const float MULTICAST_ALL_R	= %i;\n", MULTICAST_ALL_R);
5575 		fprintf(f, "const float MULTICAST_PVS_R	= %i;\n", MULTICAST_PVS_R);
5576 		fprintf(f, "const float MULTICAST_ONE_R	= %i;\n", MULTICAST_ONE_R);
5577 		fprintf(f, "const float MULTICAST_INIT	= %i;\n", MULTICAST_INIT);
5578 	}
5579 
5580 	fprintf(f, "const float FILE_READ		= "STRINGIFY(0)";\n");
5581 	fprintf(f, "const float FILE_APPEND		= "STRINGIFY(1)";\n");
5582 	fprintf(f, "const float FILE_WRITE		= "STRINGIFY(2)";\n");
5583 
5584 	//this is annoying. builtins from pr_cmds.c are not known here.
5585 	if (targs & (CS|SS))
5586 	{
5587 		const char *conflictprefix = "";//(targs&CS)?"":"//";
5588 		fprintf(f, "\n\n//Vanilla Builtin list (reduced, so as to avoid conflicts)\n");
5589 		fprintf(f, "void(vector) makevectors = #1;\n");
5590 		fprintf(f, "void(entity,vector) setorigin = #2;\n");
5591 		fprintf(f, "void(entity,string) setmodel = #3;\n");
5592 		fprintf(f, "void(entity,vector,vector) setsize = #4;\n");
5593 		fprintf(f, "float() random = #7;\n");
5594 		fprintf(f, "%svoid(entity e, float chan, string samp, float vol, float atten, optional float speedpct, optional float flags, optional float timeofs) sound = #8;\n", conflictprefix);
5595 		fprintf(f, "vector(vector) normalize = #9;\n");
5596 		fprintf(f, "void(string e) error = #10;\n");
5597 		fprintf(f, "void(string n) objerror = #11;\n");
5598 		fprintf(f, "float(vector) vlen = #12;\n");
5599 		fprintf(f, "float(vector fwd) vectoyaw = #13;\n");
5600 		fprintf(f, "entity() spawn = #14;\n");
5601 		fprintf(f, "void(entity e) remove = #15;\n");
5602 		fprintf(f, "void(vector v1, vector v2, float flags, entity ent) traceline = #16;\n");
5603 		if (targs&SS)
5604 			fprintf(f, "entity() checkclient = #17;\n");
5605 		fprintf(f, "entity(entity start, .string fld, string match) find = #18;\n");
5606 		fprintf(f, "string(string s) precache_sound = #19;\n");
5607 		fprintf(f, "string(string s) precache_model = #20;\n");
5608 		if (targs&SS)
5609 			fprintf(f, "%svoid(entity client, string s) stuffcmd = #21;\n", conflictprefix);
5610 		fprintf(f, "%sentity(vector org, float rad, optional .entity chainfield) findradius = #22;\n", conflictprefix);
5611 		if (targs&SS)
5612 		{
5613 			fprintf(f, "%svoid(string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7, optional string s8) bprint = #23;\n", conflictprefix);
5614 			fprintf(f, "%svoid(entity pl, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7) sprint = #24;\n", conflictprefix);
5615 		}
5616 		fprintf(f, "void(string,...) dprint = #25;\n");
5617 		fprintf(f, "string(float) ftos = #26;\n");
5618 		fprintf(f, "string(vector) vtos = #27;\n");
5619 		fprintf(f, "%sfloat(float yaw, float dist, optional float settraceglobals) walkmove = #32;\n", conflictprefix);
5620 		fprintf(f, "float() droptofloor = #34;\n");
5621 		fprintf(f, "%svoid(float lightstyle, string stylestring, optional vector rgb) lightstyle = #35;\n", conflictprefix);
5622 		fprintf(f, "float(float n) rint = #36;\n");
5623 		fprintf(f, "float(float n) floor = #37;\n");
5624 		fprintf(f, "float(float n) ceil = #38;\n");
5625 		fprintf(f, "float(entity e) checkbottom = #40;\n");
5626 		fprintf(f, "float(vector point) pointcontents = #41;\n");
5627 		fprintf(f, "float(float n) fabs = #43;\n");
5628 		if (targs&SS)
5629 			fprintf(f, "vector(entity e, float speed) aim = #44;\n");
5630 		fprintf(f, "float(string) cvar = #45;\n");
5631 		fprintf(f, "void(string,...) localcmd = #46;\n");
5632 		fprintf(f, "entity(entity) nextent = #47;\n");
5633 		fprintf(f, "void(vector o, vector d, float color, float count) particle = #48;\n");
5634 		fprintf(f, "void() changeyaw = #49;\n");
5635 		fprintf(f, "%svector(vector fwd, optional vector up) vectoangles = #51;\n", conflictprefix);
5636 		if (targs&SS)
5637 		{
5638 			fprintf(f, "void(float to, float val) WriteByte = #52;\n");
5639 			fprintf(f, "void(float to, float val) WriteChar = #53;\n");
5640 			fprintf(f, "void(float to, float val) WriteShort = #54;\n");
5641 			fprintf(f, "void(float to, float val) WriteLong = #55;\n");
5642 			fprintf(f, "void(float to, float val) WriteCoord = #56;\n");
5643 			fprintf(f, "void(float to, float val) WriteAngle = #57;\n");
5644 			fprintf(f, "void(float to, string val) WriteString = #58;\n");
5645 			fprintf(f, "void(float to, entity val) WriteEntity = #59;\n");
5646 		}
5647 		fprintf(f, "void(float step) movetogoal = #67;\n");
5648 		fprintf(f, "string(string s) precache_file = #68;\n");
5649 		fprintf(f, "void(entity e) makestatic = #69;\n");
5650 		if (targs&SS)
5651 			fprintf(f, "void(string mapname, optional string newmapstartspot) changelevel = #70;\n");
5652 		fprintf(f, "void(string var, string val) cvar_set = #72;\n");
5653 		if (targs&SS)
5654 			fprintf(f, "void(entity ent, string text, optional string text2, optional string text3, optional string text4, optional string text5, optional string text6, optional string text7) centerprint = #73;\n");
5655 		fprintf(f, "void (vector pos, string samp, float vol, float atten) ambientsound = #74;\n");
5656 		fprintf(f, "string(string str) precache_model2 = #75;\n");
5657 		fprintf(f, "string(string str) precache_sound2 = #76;\n");
5658 		fprintf(f, "string(string str) precache_file2 = #77;\n");
5659 		if (targs&SS)
5660 			fprintf(f, "void(entity player) setspawnparms = #78;\n");
5661 	}
5662 	if (targs & MN)
5663 	{
5664 		fprintf(f, "void(string e) error = #2;\n");
5665 		fprintf(f, "void(string n) objerror = #3;\n");
5666 		fprintf(f, "float(vector) vlen = #9;\n");
5667 		fprintf(f, "float(vector fwd) vectoyaw = #10;\n");
5668 		fprintf(f, "vector(vector fwd, optional vector up) vectoangles = #11;\n");
5669 		fprintf(f, "float() random = #12;\n");
5670 		fprintf(f, "void(string,...) localcmd = #13;\n");
5671 		fprintf(f, "float(string) cvar = #14;\n");
5672 		fprintf(f, "void(string var, string val) cvar_set = #15;\n");
5673 		fprintf(f, "void(string,...) dprint = #16;\n");
5674 		fprintf(f, "string(float) ftos = #17;\n");
5675 		fprintf(f, "float(float n) fabs = #18;\n");
5676 		fprintf(f, "string(vector) vtos = #19;\n");
5677 		fprintf(f, "entity() spawn = #22;\n");
5678 		fprintf(f, "void(entity e) remove = #23;\n");
5679 		fprintf(f, "entity(entity start, .string fld, string match) find = #24;\n");
5680 		fprintf(f, "string(string s) precache_file = #28;\n");
5681 		fprintf(f, "string(string s) precache_sound = #29;\n");
5682 		fprintf(f, "float(float n) rint = #34;\n");
5683 		fprintf(f, "float(float n) floor = #35;\n");
5684 		fprintf(f, "float(float n) ceil = #36;\n");
5685 		fprintf(f, "entity(entity) nextent = #37;\n");
5686 		fprintf(f, "float() clientstate = #62;\n");
5687 	}
5688 
5689 	for (j = 0; j < 2; j++)
5690 	{
5691 		if (j)
5692 			fprintf(f, "\n\n//Builtin Stubs List (these are present for simpler compatibility, but not properly supported in QuakeSpasm at this time).\n/*\n");
5693 		else
5694 			fprintf(f, "\n\n//Builtin list\n");
5695 		for (i = 0; i < sizeof(extensionbuiltins)/sizeof(extensionbuiltins[0]); i++)
5696 		{
5697 			if ((targs & CS) && extensionbuiltins[i].csqcfunc)
5698 				;
5699 			else if ((targs & SS) && extensionbuiltins[i].ssqcfunc)
5700 				;
5701 			else
5702 				continue;
5703 
5704 			if (j != (extensionbuiltins[i].desc?!strncmp(extensionbuiltins[i].desc, "stub.", 5):0))
5705 				continue;
5706 			fprintf(f, "%s %s = #%i;", extensionbuiltins[i].typestr, extensionbuiltins[i].name, extensionbuiltins[i].documentednumber);
5707 			if (extensionbuiltins[i].desc && !j)
5708 			{
5709 				const char *line = extensionbuiltins[i].desc;
5710 				const char *term;
5711 				fprintf(f, " /*");
5712 				while(*line)
5713 				{
5714 					fprintf(f, "\n\t\t");
5715 					term = line;
5716 					while(*term && *term != '\n')
5717 						term++;
5718 					fwrite(line, 1, term - line, f);
5719 					if (*term == '\n')
5720 					{
5721 						term++;
5722 					}
5723 					line = term;
5724 				}
5725 				fprintf(f, " */\n\n");
5726 			}
5727 			else
5728 				fprintf(f, "\n");
5729 		}
5730 		if (j)
5731 			fprintf(f, "*/\n");
5732 	}
5733 
5734 	fprintf(f, "\n\n//Reset this back to normal.\n");
5735 	fprintf(f, "#pragma noref 0\n");
5736 	fclose(f);
5737 }
5738