1#include "util.qh"
2
3#if defined(CSQC)
4    #include "../client/defs.qh"
5    #include "constants.qh"
6	#include "../client/mutators/events.qh"
7    #include "mapinfo.qh"
8    #include "notifications/all.qh"
9    #include <common/deathtypes/all.qh>
10#elif defined(MENUQC)
11#elif defined(SVQC)
12    #include "constants.qh"
13    #include "../server/autocvars.qh"
14    #include "../server/defs.qh"
15	#include "../server/mutators/events.qh"
16    #include "notifications/all.qh"
17    #include <common/deathtypes/all.qh>
18    #include "mapinfo.qh"
19#endif
20
21#ifdef GAMEQC
22/*
23* Get "real" origin, in worldspace, even if ent is attached to something else.
24*/
25vector real_origin(entity ent)
26{
27	entity e;
28	vector v = ((ent.absmin + ent.absmax) * 0.5);
29
30	e = ent.tag_entity;
31	while(e)
32	{
33		v = v + ((e.absmin + e.absmax) * 0.5);
34		e = e.tag_entity;
35	}
36
37	return v;
38}
39#endif
40
41string wordwrap_buffer;
42
43void wordwrap_buffer_put(string s)
44{
45	wordwrap_buffer = strcat(wordwrap_buffer, s);
46}
47
48string wordwrap(string s, float l)
49{
50	string r;
51	wordwrap_buffer = "";
52	wordwrap_cb(s, l, wordwrap_buffer_put);
53	r = wordwrap_buffer;
54	wordwrap_buffer = "";
55	return r;
56}
57
58#ifdef SVQC
59entity _wordwrap_buffer_sprint_ent;
60void wordwrap_buffer_sprint(string s)
61{
62	wordwrap_buffer = strcat(wordwrap_buffer, s);
63	if(s == "\n")
64	{
65		sprint(_wordwrap_buffer_sprint_ent, wordwrap_buffer);
66		wordwrap_buffer = "";
67	}
68}
69
70void wordwrap_sprint(entity to, string s, float l)
71{
72	wordwrap_buffer = "";
73	_wordwrap_buffer_sprint_ent = to;
74	wordwrap_cb(s, l, wordwrap_buffer_sprint);
75	_wordwrap_buffer_sprint_ent = NULL;
76	if(wordwrap_buffer != "")
77		sprint(to, strcat(wordwrap_buffer, "\n"));
78	wordwrap_buffer = "";
79	return;
80}
81#endif
82
83#ifndef SVQC
84string draw_UseSkinFor(string pic)
85{
86	if(substring(pic, 0, 1) == "/")
87		return substring(pic, 1, strlen(pic)-1);
88	else
89		return strcat(draw_currentSkin, "/", pic);
90}
91#endif
92
93void wordwrap_cb(string s, float l, void(string) callback)
94{
95	string c;
96	float lleft, i, j, wlen;
97
98	s = strzone(s);
99	lleft = l;
100	for (i = 0;i < strlen(s);++i)
101	{
102		if (substring(s, i, 2) == "\\n")
103		{
104			callback("\n");
105			lleft = l;
106			++i;
107		}
108		else if (substring(s, i, 1) == "\n")
109		{
110			callback("\n");
111			lleft = l;
112		}
113		else if (substring(s, i, 1) == " ")
114		{
115			if (lleft > 0)
116			{
117				callback(" ");
118				lleft = lleft - 1;
119			}
120		}
121		else
122		{
123			for (j = i+1;j < strlen(s);++j)
124				//    ^^ this skips over the first character of a word, which
125				//       is ALWAYS part of the word
126				//       this is safe since if i+1 == strlen(s), i will become
127				//       strlen(s)-1 at the end of this block and the function
128				//       will terminate. A space can't be the first character we
129				//       read here, and neither can a \n be the start, since these
130				//       two cases have been handled above.
131			{
132				c = substring(s, j, 1);
133				if (c == " ")
134					break;
135				if (c == "\\")
136					break;
137				if (c == "\n")
138					break;
139				// we need to keep this tempstring alive even if substring is
140				// called repeatedly, so call strcat even though we're not
141				// doing anything
142				callback("");
143			}
144			wlen = j - i;
145			if (lleft < wlen)
146			{
147				callback("\n");
148				lleft = l;
149			}
150			callback(substring(s, i, wlen));
151			lleft = lleft - wlen;
152			i = j - 1;
153		}
154	}
155	strunzone(s);
156}
157
158void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
159{
160	entity e;
161	e = start;
162	funcPre(pass, e);
163	while (e.(downleft))
164	{
165		e = e.(downleft);
166		funcPre(pass, e);
167	}
168	funcPost(pass, e);
169	while(e != start)
170	{
171		if (e.(right))
172		{
173			e = e.(right);
174			funcPre(pass, e);
175			while (e.(downleft))
176			{
177				e = e.(downleft);
178				funcPre(pass, e);
179			}
180		}
181		else
182			e = e.(up);
183		funcPost(pass, e);
184	}
185}
186
187string ScoreString(int pFlags, float pValue)
188{
189	string valstr;
190	float l;
191
192	pValue = floor(pValue + 0.5); // round
193
194	if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
195		valstr = "";
196	else if(pFlags & SFL_RANK)
197	{
198		valstr = ftos(pValue);
199		l = strlen(valstr);
200		if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
201			valstr = strcat(valstr, "th");
202		else if(substring(valstr, l - 1, 1) == "1")
203			valstr = strcat(valstr, "st");
204		else if(substring(valstr, l - 1, 1) == "2")
205			valstr = strcat(valstr, "nd");
206		else if(substring(valstr, l - 1, 1) == "3")
207			valstr = strcat(valstr, "rd");
208		else
209			valstr = strcat(valstr, "th");
210	}
211	else if(pFlags & SFL_TIME)
212		valstr = TIME_ENCODED_TOSTRING(pValue);
213	else
214		valstr = ftos(pValue);
215
216	return valstr;
217}
218
219// compressed vector format:
220// like MD3, just even shorter
221//   4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
222//   5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
223//   7 bit length (logarithmic encoding), 1/8 .. about 7844
224//     length = 2^(length_encoded/8) / 8
225// if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
226// thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
227// the special value 0 indicates the zero vector
228
229float lengthLogTable[128];
230
231float invertLengthLog(float dist)
232{
233	int l, r, m;
234
235	if(dist >= lengthLogTable[127])
236		return 127;
237	if(dist <= lengthLogTable[0])
238		return 0;
239
240	l = 0;
241	r = 127;
242
243	while(r - l > 1)
244	{
245		m = floor((l + r) / 2);
246		if(lengthLogTable[m] < dist)
247			l = m;
248		else
249			r = m;
250	}
251
252	// now: r is >=, l is <
253	float lerr = (dist - lengthLogTable[l]);
254	float rerr = (lengthLogTable[r] - dist);
255	if(lerr < rerr)
256		return l;
257	return r;
258}
259
260vector decompressShortVector(int data)
261{
262	vector out;
263	if(data == 0)
264		return '0 0 0';
265	float p	= (data & 0xF000) / 0x1000;
266	float q	= (data & 0x0F80) / 0x80;
267	int len = (data & 0x007F);
268
269	//print("\ndecompress: p ", ftos(p)); print("q ", ftos(q)); print("len ", ftos(len), "\n");
270
271	if(p == 0)
272	{
273		out.x = 0;
274		out.y = 0;
275		if(q == 31)
276			out.z = -1;
277		else
278			out.z = +1;
279	}
280	else
281	{
282		q   = .19634954084936207740 * q;
283		p = .19634954084936207740 * p - 1.57079632679489661922;
284		out.x = cos(q) *  cos(p);
285		out.y = sin(q) *  cos(p);
286		out.z =          -sin(p);
287	}
288
289	//print("decompressed: ", vtos(out), "\n");
290
291	return out * lengthLogTable[len];
292}
293
294float compressShortVector(vector vec)
295{
296	vector ang;
297	float p, y, len;
298	if(vec == '0 0 0')
299		return 0;
300	//print("compress: ", vtos(vec), "\n");
301	ang = vectoangles(vec);
302	ang.x = -ang.x;
303	if(ang.x < -90)
304		ang.x += 360;
305	if(ang.x < -90 && ang.x > +90)
306		error("BOGUS vectoangles");
307	//print("angles: ", vtos(ang), "\n");
308
309	p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
310	if(p == 0)
311	{
312		if(vec.z < 0)
313			y = 31;
314		else
315			y = 30;
316	}
317	else
318		y = floor(0.5 + ang.y * 32 / 360)          & 31; // 0..360 to 0..32
319	len = invertLengthLog(vlen(vec));
320
321	//print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
322
323	return (p * 0x1000) + (y * 0x80) + len;
324}
325
326STATIC_INIT(compressShortVector)
327{
328	float l = 1;
329	float f = (2 ** (1/8));
330	int i;
331	for(i = 0; i < 128; ++i)
332	{
333		lengthLogTable[i] = l;
334		l *= f;
335	}
336
337	if(cvar("developer"))
338	{
339		LOG_INFO("Verifying vector compression table...\n");
340		for(i = 0x0F00; i < 0xFFFF; ++i)
341			if(i != compressShortVector(decompressShortVector(i)))
342			{
343				LOG_INFO("BROKEN vector compression: ", ftos(i));
344				LOG_INFO(" -> ", vtos(decompressShortVector(i)));
345				LOG_INFO(" -> ", ftos(compressShortVector(decompressShortVector(i))));
346				LOG_INFO("\n");
347				error("b0rk");
348			}
349		LOG_INFO("Done.\n");
350	}
351}
352
353#ifdef GAMEQC
354float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
355{
356	traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
357	traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
358	traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
359	traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
360	traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
361	traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
362	traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
363	traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
364	traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
365	traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
366	traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
367	traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
368	return 1;
369}
370#endif
371
372string fixPriorityList(string order, float from, float to, float subtract, float complete)
373{
374	string neworder;
375	float i, n, w;
376
377	n = tokenize_console(order);
378	neworder = "";
379	for(i = 0; i < n; ++i)
380	{
381		w = stof(argv(i));
382		if(w == floor(w))
383		{
384			if(w >= from && w <= to)
385				neworder = strcat(neworder, ftos(w), " ");
386			else
387			{
388				w -= subtract;
389				if(w >= from && w <= to)
390					neworder = strcat(neworder, ftos(w), " ");
391			}
392		}
393	}
394
395	if(complete)
396	{
397		n = tokenize_console(neworder);
398		for(w = to; w >= from; --w)
399		{
400			int wflags = Weapons_from(w).spawnflags;
401			if((wflags & WEP_FLAG_HIDDEN) && (wflags & WEP_FLAG_MUTATORBLOCKED) && !(wflags & WEP_FLAG_NORMAL))
402				continue;
403			for(i = 0; i < n; ++i)
404				if(stof(argv(i)) == w)
405					break;
406			if(i == n) // not found
407				neworder = strcat(neworder, ftos(w), " ");
408		}
409	}
410
411	return substring(neworder, 0, strlen(neworder) - 1);
412}
413
414string mapPriorityList(string order, string(string) mapfunc)
415{
416	string neworder;
417	float i, n;
418
419	n = tokenize_console(order);
420	neworder = "";
421	for(i = 0; i < n; ++i)
422		neworder = strcat(neworder, mapfunc(argv(i)), " ");
423
424	return substring(neworder, 0, strlen(neworder) - 1);
425}
426
427string swapInPriorityList(string order, float i, float j)
428{
429	string s;
430	float w, n;
431
432	n = tokenize_console(order);
433
434	if(i >= 0 && i < n && j >= 0 && j < n && i != j)
435	{
436		s = "";
437		for(w = 0; w < n; ++w)
438		{
439			if(w == i)
440				s = strcat(s, argv(j), " ");
441			else if(w == j)
442				s = strcat(s, argv(i), " ");
443			else
444				s = strcat(s, argv(w), " ");
445		}
446		return substring(s, 0, strlen(s) - 1);
447	}
448
449	return order;
450}
451
452#ifdef GAMEQC
453void get_mi_min_max(float mode)
454{
455	vector mi, ma;
456
457	if(mi_shortname)
458		strunzone(mi_shortname);
459	mi_shortname = mapname;
460	if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
461		mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
462	if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
463		mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
464	mi_shortname = strzone(mi_shortname);
465
466#ifdef CSQC
467	mi = world.mins;
468	ma = world.maxs;
469#else
470	mi = world.absmin;
471	ma = world.absmax;
472#endif
473
474	mi_min = mi;
475	mi_max = ma;
476	MapInfo_Get_ByName(mi_shortname, 0, NULL);
477	if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
478	{
479		mi_min = MapInfo_Map_mins;
480		mi_max = MapInfo_Map_maxs;
481	}
482	else
483	{
484		// not specified
485		if(mode)
486		{
487			// be clever
488			tracebox('1 0 0' * mi.x,
489					 '0 1 0' * mi.y + '0 0 1' * mi.z,
490					 '0 1 0' * ma.y + '0 0 1' * ma.z,
491					 '1 0 0' * ma.x,
492					 MOVE_WORLDONLY,
493					 NULL);
494			if(!trace_startsolid)
495				mi_min.x = trace_endpos.x;
496
497			tracebox('0 1 0' * mi.y,
498					 '1 0 0' * mi.x + '0 0 1' * mi.z,
499					 '1 0 0' * ma.x + '0 0 1' * ma.z,
500					 '0 1 0' * ma.y,
501					 MOVE_WORLDONLY,
502					 NULL);
503			if(!trace_startsolid)
504				mi_min.y = trace_endpos.y;
505
506			tracebox('0 0 1' * mi.z,
507					 '1 0 0' * mi.x + '0 1 0' * mi.y,
508					 '1 0 0' * ma.x + '0 1 0' * ma.y,
509					 '0 0 1' * ma.z,
510					 MOVE_WORLDONLY,
511					 NULL);
512			if(!trace_startsolid)
513				mi_min.z = trace_endpos.z;
514
515			tracebox('1 0 0' * ma.x,
516					 '0 1 0' * mi.y + '0 0 1' * mi.z,
517					 '0 1 0' * ma.y + '0 0 1' * ma.z,
518					 '1 0 0' * mi.x,
519					 MOVE_WORLDONLY,
520					 NULL);
521			if(!trace_startsolid)
522				mi_max.x = trace_endpos.x;
523
524			tracebox('0 1 0' * ma.y,
525					 '1 0 0' * mi.x + '0 0 1' * mi.z,
526					 '1 0 0' * ma.x + '0 0 1' * ma.z,
527					 '0 1 0' * mi.y,
528					 MOVE_WORLDONLY,
529					 NULL);
530			if(!trace_startsolid)
531				mi_max.y = trace_endpos.y;
532
533			tracebox('0 0 1' * ma.z,
534					 '1 0 0' * mi.x + '0 1 0' * mi.y,
535					 '1 0 0' * ma.x + '0 1 0' * ma.y,
536					 '0 0 1' * mi.z,
537					 MOVE_WORLDONLY,
538					 NULL);
539			if(!trace_startsolid)
540				mi_max.z = trace_endpos.z;
541		}
542	}
543}
544
545void get_mi_min_max_texcoords(float mode)
546{
547	vector extend;
548
549	get_mi_min_max(mode);
550
551	mi_picmin = mi_min;
552	mi_picmax = mi_max;
553
554	// extend mi_picmax to get a square aspect ratio
555	// center the map in that area
556	extend = mi_picmax - mi_picmin;
557	if(extend.y > extend.x)
558	{
559		mi_picmin.x -= (extend.y - extend.x) * 0.5;
560		mi_picmax.x += (extend.y - extend.x) * 0.5;
561	}
562	else
563	{
564		mi_picmin.y -= (extend.x - extend.y) * 0.5;
565		mi_picmax.y += (extend.x - extend.y) * 0.5;
566	}
567
568	// add another some percent
569	extend = (mi_picmax - mi_picmin) * (1 / 64.0);
570	mi_picmin -= extend;
571	mi_picmax += extend;
572
573	// calculate the texcoords
574	mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
575	// first the two corners of the origin
576	mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
577	mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
578	mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
579	mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
580	// then the other corners
581	mi_pictexcoord1_x = mi_pictexcoord0_x;
582	mi_pictexcoord1_y = mi_pictexcoord2_y;
583	mi_pictexcoord3_x = mi_pictexcoord2_x;
584	mi_pictexcoord3_y = mi_pictexcoord0_y;
585}
586#endif
587
588float cvar_settemp(string tmp_cvar, string tmp_value)
589{
590	float created_saved_value;
591
592	created_saved_value = 0;
593
594	if (!(tmp_cvar || tmp_value))
595	{
596		LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !");
597		return 0;
598	}
599
600	if(!cvar_type(tmp_cvar))
601	{
602		LOG_INFOF("Error: cvar %s doesn't exist!\n", tmp_cvar);
603		return 0;
604	}
605
606	IL_EACH(g_saved_cvars, it.netname == tmp_cvar,
607	{
608		created_saved_value = -1; // skip creation
609		break; // no need to continue
610	});
611
612	if(created_saved_value != -1)
613	{
614		// creating a new entity to keep track of this cvar
615		entity e = new_pure(saved_cvar_value);
616		IL_PUSH(g_saved_cvars, e);
617		e.netname = strzone(tmp_cvar);
618		e.message = strzone(cvar_string(tmp_cvar));
619		created_saved_value = 1;
620	}
621
622	// update the cvar to the value given
623	cvar_set(tmp_cvar, tmp_value);
624
625	return created_saved_value;
626}
627
628int cvar_settemp_restore()
629{
630	int j = 0;
631	// FIXME this new-style loop fails!
632#if 0
633	FOREACH_ENTITY_CLASS("saved_cvar_value", true,
634	{
635		if(cvar_type(it.netname))
636		{
637			cvar_set(it.netname, it.message);
638			strunzone(it.netname);
639			strunzone(it.message);
640			delete(it);
641			++j;
642		}
643		else
644			LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", it.netname);
645	});
646
647#else
648	entity e = NULL;
649	while((e = find(e, classname, "saved_cvar_value")))
650	{
651		if(cvar_type(e.netname))
652		{
653			cvar_set(e.netname, e.message);
654			delete(e);
655			++j;
656		}
657		else
658			print(sprintf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname));
659	}
660#endif
661
662	return j;
663}
664
665bool isCaretEscaped(string theText, float pos)
666{
667	int i = 0;
668	while(pos - i >= 1 && substring(theText, pos - i - 1, 1) == "^")
669		++i;
670	return (i & 1);
671}
672
673int skipIncompleteTag(string theText, float pos, int len)
674{
675	int i = 0, ch = 0;
676	int tag_start = -1;
677
678	if(substring(theText, pos - 1, 1) == "^")
679	{
680		if(isCaretEscaped(theText, pos - 1) || pos >= len)
681			return 0;
682
683		ch = str2chr(theText, pos);
684		if(ch >= '0' && ch <= '9')
685			return 1; // ^[0-9] color code found
686		else if (ch == 'x')
687			tag_start = pos - 1; // ^x tag found
688		else
689			return 0;
690	}
691	else
692	{
693		for(i = 2; pos - i >= 0 && i <= 4; ++i)
694		{
695			if(substring(theText, pos - i, 2) == "^x")
696			{
697				tag_start = pos - i; // ^x tag found
698				break;
699			}
700		}
701	}
702
703	if(tag_start >= 0)
704	{
705		if(tag_start + 5 < len)
706		if(IS_HEXDIGIT(substring(theText, tag_start + 2, 1)))
707		if(IS_HEXDIGIT(substring(theText, tag_start + 3, 1)))
708		if(IS_HEXDIGIT(substring(theText, tag_start + 4, 1)))
709		{
710			if(!isCaretEscaped(theText, tag_start))
711				return 5 - (pos - tag_start); // ^xRGB color code found
712		}
713	}
714	return 0;
715}
716
717float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
718{
719	// STOP.
720	// The following function is SLOW.
721	// For your safety and for the protection of those around you...
722	// DO NOT CALL THIS AT HOME.
723	// No really, don't.
724	if(w(theText, theSize) <= maxWidth)
725		return strlen(theText); // yeah!
726
727	bool colors = (w("^7", theSize) == 0);
728
729	// binary search for right place to cut string
730	int len, left, right, middle;
731	left = 0;
732	right = len = strlen(theText);
733	int ofs = 0;
734	do
735	{
736		middle = floor((left + right) / 2);
737		if(colors)
738			ofs = skipIncompleteTag(theText, middle, len);
739		if(w(substring(theText, 0, middle + ofs), theSize) <= maxWidth)
740			left = middle + ofs;
741		else
742			right = middle;
743	}
744	while(left < right - 1);
745
746	return left;
747}
748
749float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
750{
751	// STOP.
752	// The following function is SLOW.
753	// For your safety and for the protection of those around you...
754	// DO NOT CALL THIS AT HOME.
755	// No really, don't.
756	if(w(theText) <= maxWidth)
757		return strlen(theText); // yeah!
758
759	bool colors = (w("^7") == 0);
760
761	// binary search for right place to cut string
762	int len, left, right, middle;
763	left = 0;
764	right = len = strlen(theText);
765	int ofs = 0;
766	do
767	{
768		middle = floor((left + right) / 2);
769		if(colors)
770			ofs = skipIncompleteTag(theText, middle, len);
771		if(w(substring(theText, 0, middle + ofs)) <= maxWidth)
772			left = middle + ofs;
773		else
774			right = middle;
775	}
776	while(left < right - 1);
777
778	return left;
779}
780
781string find_last_color_code(string s)
782{
783	int start = strstrofs(s, "^", 0);
784	if (start == -1) // no caret found
785		return "";
786	int len = strlen(s)-1;
787	int i;
788	for(i = len; i >= start; --i)
789	{
790		if(substring(s, i, 1) != "^")
791			continue;
792
793		int carets = 1;
794		while (i-carets >= start && substring(s, i-carets, 1) == "^")
795			++carets;
796
797		// check if carets aren't all escaped
798		if (carets & 1)
799		{
800			if(i+1 <= len)
801			if(IS_DIGIT(substring(s, i+1, 1)))
802				return substring(s, i, 2);
803
804			if(i+4 <= len)
805			if(substring(s, i+1, 1) == "x")
806			if(IS_HEXDIGIT(substring(s, i + 2, 1)))
807			if(IS_HEXDIGIT(substring(s, i + 3, 1)))
808			if(IS_HEXDIGIT(substring(s, i + 4, 1)))
809				return substring(s, i, 5);
810		}
811		i -= carets; // this also skips one char before the carets
812	}
813
814	return "";
815}
816
817string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
818{
819	float cantake;
820	float take;
821	string s;
822
823	s = getWrappedLine_remaining;
824
825	if(w <= 0)
826	{
827		getWrappedLine_remaining = string_null;
828		return s; // the line has no size ANYWAY, nothing would be displayed.
829	}
830
831	cantake = textLengthUpToWidth(s, w, theFontSize, tw);
832	if(cantake > 0 && cantake < strlen(s))
833	{
834		take = cantake - 1;
835		while(take > 0 && substring(s, take, 1) != " ")
836			--take;
837		if(take == 0)
838		{
839			getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
840			if(getWrappedLine_remaining == "")
841				getWrappedLine_remaining = string_null;
842			else if (tw("^7", theFontSize) == 0)
843				getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
844			return substring(s, 0, cantake);
845		}
846		else
847		{
848			getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
849			if(getWrappedLine_remaining == "")
850				getWrappedLine_remaining = string_null;
851			else if (tw("^7", theFontSize) == 0)
852				getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
853			return substring(s, 0, take);
854		}
855	}
856	else
857	{
858		getWrappedLine_remaining = string_null;
859		return s;
860	}
861}
862
863string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
864{
865	float cantake;
866	float take;
867	string s;
868
869	s = getWrappedLine_remaining;
870
871	if(w <= 0)
872	{
873		getWrappedLine_remaining = string_null;
874		return s; // the line has no size ANYWAY, nothing would be displayed.
875	}
876
877	cantake = textLengthUpToLength(s, w, tw);
878	if(cantake > 0 && cantake < strlen(s))
879	{
880		take = cantake - 1;
881		while(take > 0 && substring(s, take, 1) != " ")
882			--take;
883		if(take == 0)
884		{
885			getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
886			if(getWrappedLine_remaining == "")
887				getWrappedLine_remaining = string_null;
888			else if (tw("^7") == 0)
889				getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
890			return substring(s, 0, cantake);
891		}
892		else
893		{
894			getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
895			if(getWrappedLine_remaining == "")
896				getWrappedLine_remaining = string_null;
897			else if (tw("^7") == 0)
898				getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
899			return substring(s, 0, take);
900		}
901	}
902	else
903	{
904		getWrappedLine_remaining = string_null;
905		return s;
906	}
907}
908
909string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
910{
911	if(tw(theText, theFontSize) <= maxWidth)
912		return theText;
913	else
914		return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
915}
916
917string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
918{
919	if(tw(theText) <= maxWidth)
920		return theText;
921	else
922		return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
923}
924
925float isGametypeInFilter(Gametype gt, float tp, float ts, string pattern)
926{
927	string subpattern, subpattern2, subpattern3, subpattern4;
928	subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
929	if(tp)
930		subpattern2 = ",teams,";
931	else
932		subpattern2 = ",noteams,";
933	if(ts)
934		subpattern3 = ",teamspawns,";
935	else
936		subpattern3 = ",noteamspawns,";
937	if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
938		subpattern4 = ",race,";
939	else
940		subpattern4 = string_null;
941
942	if(substring(pattern, 0, 1) == "-")
943	{
944		pattern = substring(pattern, 1, strlen(pattern) - 1);
945		if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
946			return 0;
947		if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
948			return 0;
949		if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
950			return 0;
951		if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
952			return 0;
953	}
954	else
955	{
956		if(substring(pattern, 0, 1) == "+")
957			pattern = substring(pattern, 1, strlen(pattern) - 1);
958		if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
959		if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
960		if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
961		{
962			if (!subpattern4)
963				return 0;
964			if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
965				return 0;
966		}
967	}
968	return 1;
969}
970
971vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
972{
973	vector ret;
974
975	// make origin and speed relative
976	eorg -= myorg;
977	if(newton_style)
978		evel -= myvel;
979
980	// now solve for ret, ret normalized:
981	//   eorg + t * evel == t * ret * spd
982	// or, rather, solve for t:
983	//   |eorg + t * evel| == t * spd
984	//   eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
985	//   t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
986	vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
987	// p = 2 * (eorg * evel) / (evel * evel - spd * spd)
988	// q = (eorg * eorg) / (evel * evel - spd * spd)
989	if(!solution.z) // no real solution
990	{
991		// happens if D < 0
992		// (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
993		// (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
994		// spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
995		// spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
996		// spd^2 < evel^2 * sin^2 angle(evel, eorg)
997		// spd < |evel| * sin angle(evel, eorg)
998		return '0 0 0';
999	}
1000	else if(solution.x > 0)
1001	{
1002		// both solutions > 0: take the smaller one
1003		// happens if p < 0 and q > 0
1004		ret = normalize(eorg + solution.x * evel);
1005	}
1006	else if(solution.y > 0)
1007	{
1008		// one solution > 0: take the larger one
1009		// happens if q < 0 or q == 0 and p < 0
1010		ret = normalize(eorg + solution.y * evel);
1011	}
1012	else
1013	{
1014		// no solution > 0: reject
1015		// happens if p > 0 and q >= 0
1016		// 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1017		// (eorg * eorg) / (evel * evel - spd * spd) >= 0
1018		//
1019		// |evel| >= spd
1020		// eorg * evel > 0
1021		//
1022		// "Enemy is moving away from me at more than spd"
1023		return '0 0 0';
1024	}
1025
1026	// NOTE: we always got a solution if spd > |evel|
1027
1028	if(newton_style == 2)
1029		ret = normalize(ret * spd + myvel);
1030
1031	return ret;
1032}
1033
1034vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1035{
1036	if(!newton_style)
1037		return spd * mydir;
1038
1039	if(newton_style == 2)
1040	{
1041		// true Newtonian projectiles with automatic aim adjustment
1042		//
1043		// solve: |outspeed * mydir - myvel| = spd
1044		// outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1045		// outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1046		// PLUS SIGN!
1047		// not defined?
1048		// then...
1049		// myvel^2 - (mydir * myvel)^2 > spd^2
1050		// velocity without mydir component > spd
1051		// fire at smallest possible spd that works?
1052		// |(mydir * myvel) * myvel - myvel| = spd
1053
1054		vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1055
1056		float outspeed;
1057		if(solution.z)
1058			outspeed = solution.y; // the larger one
1059		else
1060		{
1061			//outspeed = 0; // slowest possible shot
1062			outspeed = solution.x; // the real part (that is, the average!)
1063			//dprint("impossible shot, adjusting\n");
1064		}
1065
1066		outspeed = bound(spd * mi, outspeed, spd * ma);
1067		return mydir * outspeed;
1068	}
1069
1070	// real Newtonian
1071	return myvel + spd * mydir;
1072}
1073
1074float compressShotOrigin(vector v)
1075{
1076	float rx = rint(v.x * 2);
1077	float ry = rint(v.y * 4) + 128;
1078	float rz = rint(v.z * 4) + 128;
1079	if(rx > 255 || rx < 0)
1080	{
1081		LOG_DEBUG("shot origin ", vtos(v), " x out of bounds\n");
1082		rx = bound(0, rx, 255);
1083	}
1084	if(ry > 255 || ry < 0)
1085	{
1086		LOG_DEBUG("shot origin ", vtos(v), " y out of bounds\n");
1087		ry = bound(0, ry, 255);
1088	}
1089	if(rz > 255 || rz < 0)
1090	{
1091		LOG_DEBUG("shot origin ", vtos(v), " z out of bounds\n");
1092		rz = bound(0, rz, 255);
1093	}
1094	return rx * 0x10000 + ry * 0x100 + rz;
1095}
1096vector decompressShotOrigin(int f)
1097{
1098	vector v;
1099	v.x = ((f & 0xFF0000) / 0x10000) / 2;
1100	v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1101	v.z = ((f & 0xFF) - 128) / 4;
1102	return v;
1103}
1104
1105#ifdef GAMEQC
1106vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1107{
1108	// NOTE: we'll always choose the SMALLER value...
1109	float healthdamage, armordamage, armorideal;
1110	if (DEATH_IS(deathtype, DEATH_DROWN))  // Why should armor help here...
1111		armorblock = 0;
1112	vector v;
1113	healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1114	armordamage = a + (h - 1); // damage we can take if we could use more armor
1115	armorideal = healthdamage * armorblock;
1116	v.y = armorideal;
1117	if(armordamage < healthdamage)
1118	{
1119		v.x = armordamage;
1120		v.z = 1;
1121	}
1122	else
1123	{
1124		v.x = healthdamage;
1125		v.z = 0;
1126	}
1127	return v;
1128}
1129
1130vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1131{
1132	vector v;
1133	if (DEATH_IS(deathtype, DEATH_DROWN))  // Why should armor help here...
1134		armorblock = 0;
1135	v.y = bound(0, damage * armorblock, a); // save
1136	v.x = bound(0, damage - v.y, damage); // take
1137	v.z = 0;
1138	return v;
1139}
1140#endif
1141
1142string getcurrentmod()
1143{
1144	float n;
1145	string m;
1146	m = cvar_string("fs_gamedir");
1147	n = tokenize_console(m);
1148	if(n == 0)
1149		return "data";
1150	else
1151		return argv(n - 1);
1152}
1153
1154float matchacl(string acl, string str)
1155{
1156	string t, s;
1157	float r, d;
1158	r = 0;
1159	while(acl)
1160	{
1161		t = car(acl); acl = cdr(acl);
1162
1163		d = 1;
1164		if(substring(t, 0, 1) == "-")
1165		{
1166			d = -1;
1167			t = substring(t, 1, strlen(t) - 1);
1168		}
1169		else if(substring(t, 0, 1) == "+")
1170			t = substring(t, 1, strlen(t) - 1);
1171
1172		if(substring(t, -1, 1) == "*")
1173		{
1174			t = substring(t, 0, strlen(t) - 1);
1175			s = substring(str, 0, strlen(t));
1176		}
1177		else
1178			s = str;
1179
1180		if(s == t)
1181		{
1182			r = d;
1183		}
1184	}
1185	return r;
1186}
1187
1188string get_model_datafilename(string m, float sk, string fil)
1189{
1190	if(m)
1191		m = strcat(m, "_");
1192	else
1193		m = "models/player/*_";
1194	if(sk >= 0)
1195		m = strcat(m, ftos(sk));
1196	else
1197		m = strcat(m, "*");
1198	return strcat(m, ".", fil);
1199}
1200
1201float get_model_parameters(string m, float sk)
1202{
1203	get_model_parameters_modelname = string_null;
1204	get_model_parameters_modelskin = -1;
1205	get_model_parameters_name = string_null;
1206	get_model_parameters_species = -1;
1207	get_model_parameters_sex = string_null;
1208	get_model_parameters_weight = -1;
1209	get_model_parameters_age = -1;
1210	get_model_parameters_desc = string_null;
1211	get_model_parameters_bone_upperbody = string_null;
1212	get_model_parameters_bone_weapon = string_null;
1213	for(int i = 0; i < MAX_AIM_BONES; ++i)
1214	{
1215		get_model_parameters_bone_aim[i] = string_null;
1216		get_model_parameters_bone_aimweight[i] = 0;
1217	}
1218	get_model_parameters_fixbone = 0;
1219	get_model_parameters_hidden = false;
1220
1221#ifdef GAMEQC
1222	MUTATOR_CALLHOOK(ClearModelParams);
1223#endif
1224
1225	if (!m)
1226		return 1;
1227
1228	if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1229		m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1230
1231	if(sk < 0)
1232	{
1233		if(substring(m, -4, -1) != ".txt")
1234			return 0;
1235		if(substring(m, -6, 1) != "_")
1236			return 0;
1237		sk = stof(substring(m, -5, 1));
1238		m = substring(m, 0, -7);
1239	}
1240
1241	string fn = get_model_datafilename(m, sk, "txt");
1242	int fh = fopen(fn, FILE_READ);
1243	if(fh < 0)
1244	{
1245		sk = 0;
1246		fn = get_model_datafilename(m, sk, "txt");
1247		fh = fopen(fn, FILE_READ);
1248		if(fh < 0)
1249			return 0;
1250	}
1251
1252	get_model_parameters_modelname = m;
1253	get_model_parameters_modelskin = sk;
1254	string s, c;
1255	while((s = fgets(fh)))
1256	{
1257		if(s == "")
1258			break; // next lines will be description
1259		c = car(s);
1260		s = cdr(s);
1261		if(c == "name")
1262			get_model_parameters_name = s;
1263		if(c == "species")
1264			switch(s)
1265			{
1266				case "human":       get_model_parameters_species = SPECIES_HUMAN;       break;
1267				case "alien":       get_model_parameters_species = SPECIES_ALIEN;       break;
1268				case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1269				case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1270				case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1271				case "animal":      get_model_parameters_species = SPECIES_ANIMAL;      break;
1272				case "reserved":    get_model_parameters_species = SPECIES_RESERVED;    break;
1273			}
1274		if(c == "sex")
1275			get_model_parameters_sex = s;
1276		if(c == "weight")
1277			get_model_parameters_weight = stof(s);
1278		if(c == "age")
1279			get_model_parameters_age = stof(s);
1280		if(c == "description")
1281			get_model_parameters_description = s;
1282		if(c == "bone_upperbody")
1283			get_model_parameters_bone_upperbody = s;
1284		if(c == "bone_weapon")
1285			get_model_parameters_bone_weapon = s;
1286	#ifdef GAMEQC
1287		MUTATOR_CALLHOOK(GetModelParams, c, s);
1288	#endif
1289		for(int i = 0; i < MAX_AIM_BONES; ++i)
1290			if(c == strcat("bone_aim", ftos(i)))
1291			{
1292				get_model_parameters_bone_aimweight[i] = stof(car(s));
1293				get_model_parameters_bone_aim[i] = cdr(s);
1294			}
1295		if(c == "fixbone")
1296			get_model_parameters_fixbone = stof(s);
1297		if(c == "hidden")
1298			get_model_parameters_hidden = stob(s);
1299	}
1300
1301	while((s = fgets(fh)))
1302	{
1303		if(get_model_parameters_desc)
1304			get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1305		if(s != "")
1306			get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1307	}
1308
1309	fclose(fh);
1310
1311	return 1;
1312}
1313
1314// x-encoding (encoding as zero length invisible string)
1315const string XENCODE_2  = "xX";
1316const string XENCODE_22 = "0123456789abcdefABCDEF";
1317string xencode(int f)
1318{
1319	float a, b, c, d;
1320	d = f % 22; f = floor(f / 22);
1321	c = f % 22; f = floor(f / 22);
1322	b = f % 22; f = floor(f / 22);
1323	a = f %  2; // f = floor(f /  2);
1324	return strcat(
1325		"^",
1326		substring(XENCODE_2,  a, 1),
1327		substring(XENCODE_22, b, 1),
1328		substring(XENCODE_22, c, 1),
1329		substring(XENCODE_22, d, 1)
1330	);
1331}
1332float xdecode(string s)
1333{
1334	float a, b, c, d;
1335	if(substring(s, 0, 1) != "^")
1336		return -1;
1337	if(strlen(s) < 5)
1338		return -1;
1339	a = strstrofs(XENCODE_2,  substring(s, 1, 1), 0);
1340	b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1341	c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1342	d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1343	if(a < 0 || b < 0 || c < 0 || d < 0)
1344		return -1;
1345	return ((a * 22 + b) * 22 + c) * 22 + d;
1346}
1347
1348/*
1349string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1350{
1351	if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1352		return input;
1353	else
1354		return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1355}*/
1356
1357float shutdown_running;
1358#ifdef SVQC
1359void SV_Shutdown()
1360#endif
1361#ifdef CSQC
1362void CSQC_Shutdown()
1363#endif
1364#ifdef MENUQC
1365void m_shutdown()
1366#endif
1367{
1368	if(shutdown_running)
1369	{
1370		LOG_INFO("Recursive shutdown detected! Only restoring cvars...\n");
1371	}
1372	else
1373	{
1374		shutdown_running = 1;
1375		Shutdown();
1376		shutdownhooks();
1377	}
1378	cvar_settemp_restore(); // this must be done LAST, but in any case
1379}
1380
1381#ifdef GAMEQC
1382.float skeleton_bones_index;
1383void Skeleton_SetBones(entity e)
1384{
1385	// set skeleton_bones to the total number of bones on the model
1386	if(e.skeleton_bones_index == e.modelindex)
1387		return; // same model, nothing to update
1388
1389	float skelindex;
1390	skelindex = skel_create(e.modelindex);
1391	e.skeleton_bones = skel_get_numbones(skelindex);
1392	skel_delete(skelindex);
1393	e.skeleton_bones_index = e.modelindex;
1394}
1395#endif
1396
1397string to_execute_next_frame;
1398void execute_next_frame()
1399{
1400	if(to_execute_next_frame)
1401	{
1402		localcmd("\n", to_execute_next_frame, "\n");
1403		strunzone(to_execute_next_frame);
1404		to_execute_next_frame = string_null;
1405	}
1406}
1407void queue_to_execute_next_frame(string s)
1408{
1409	if(to_execute_next_frame)
1410	{
1411		s = strcat(s, "\n", to_execute_next_frame);
1412		strunzone(to_execute_next_frame);
1413	}
1414	to_execute_next_frame = strzone(s);
1415}
1416
1417.float FindConnectedComponent_processing;
1418void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1419{
1420	entity queue_start, queue_end;
1421
1422	// we build a queue of to-be-processed entities.
1423	// queue_start is the next entity to be checked for neighbors
1424	// queue_end is the last entity added
1425
1426	if(e.FindConnectedComponent_processing)
1427		error("recursion or broken cleanup");
1428
1429	// start with a 1-element queue
1430	queue_start = queue_end = e;
1431	queue_end.(fld) = NULL;
1432	queue_end.FindConnectedComponent_processing = 1;
1433
1434	// for each queued item:
1435	for (; queue_start; queue_start = queue_start.(fld))
1436	{
1437		// find all neighbors of queue_start
1438		entity t;
1439		for(t = NULL; (t = nxt(t, queue_start, pass)); )
1440		{
1441			if(t.FindConnectedComponent_processing)
1442				continue;
1443			if(iscon(t, queue_start, pass))
1444			{
1445				// it is connected? ADD IT. It will look for neighbors soon too.
1446				queue_end.(fld) = t;
1447				queue_end = t;
1448				queue_end.(fld) = NULL;
1449				queue_end.FindConnectedComponent_processing = 1;
1450			}
1451		}
1452	}
1453
1454	// unmark
1455	for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1456		queue_start.FindConnectedComponent_processing = 0;
1457}
1458
1459#ifdef GAMEQC
1460vector animfixfps(entity e, vector a, vector b)
1461{
1462	// multi-frame anim: keep as-is
1463	if(a.y == 1)
1464	{
1465		float dur = frameduration(e.modelindex, a.x);
1466		if (dur <= 0 && b.y)
1467		{
1468			a = b;
1469			dur = frameduration(e.modelindex, a.x);
1470		}
1471		if (dur > 0)
1472			a.z = 1.0 / dur;
1473	}
1474	return a;
1475}
1476#endif
1477
1478#ifdef GAMEQC
1479Notification Announcer_PickNumber(int type, int num)
1480{
1481    return = NULL;
1482	switch (type)
1483	{
1484		case CNT_GAMESTART:
1485		{
1486			switch(num)
1487			{
1488				case 10: return ANNCE_NUM_GAMESTART_10;
1489				case 9:  return ANNCE_NUM_GAMESTART_9;
1490				case 8:  return ANNCE_NUM_GAMESTART_8;
1491				case 7:  return ANNCE_NUM_GAMESTART_7;
1492				case 6:  return ANNCE_NUM_GAMESTART_6;
1493				case 5:  return ANNCE_NUM_GAMESTART_5;
1494				case 4:  return ANNCE_NUM_GAMESTART_4;
1495				case 3:  return ANNCE_NUM_GAMESTART_3;
1496				case 2:  return ANNCE_NUM_GAMESTART_2;
1497				case 1:  return ANNCE_NUM_GAMESTART_1;
1498			}
1499			break;
1500		}
1501		case CNT_IDLE:
1502		{
1503			switch(num)
1504			{
1505				case 10: return ANNCE_NUM_IDLE_10;
1506				case 9:  return ANNCE_NUM_IDLE_9;
1507				case 8:  return ANNCE_NUM_IDLE_8;
1508				case 7:  return ANNCE_NUM_IDLE_7;
1509				case 6:  return ANNCE_NUM_IDLE_6;
1510				case 5:  return ANNCE_NUM_IDLE_5;
1511				case 4:  return ANNCE_NUM_IDLE_4;
1512				case 3:  return ANNCE_NUM_IDLE_3;
1513				case 2:  return ANNCE_NUM_IDLE_2;
1514				case 1:  return ANNCE_NUM_IDLE_1;
1515			}
1516			break;
1517		}
1518		case CNT_KILL:
1519		{
1520			switch(num)
1521			{
1522				case 10: return ANNCE_NUM_KILL_10;
1523				case 9:  return ANNCE_NUM_KILL_9;
1524				case 8:  return ANNCE_NUM_KILL_8;
1525				case 7:  return ANNCE_NUM_KILL_7;
1526				case 6:  return ANNCE_NUM_KILL_6;
1527				case 5:  return ANNCE_NUM_KILL_5;
1528				case 4:  return ANNCE_NUM_KILL_4;
1529				case 3:  return ANNCE_NUM_KILL_3;
1530				case 2:  return ANNCE_NUM_KILL_2;
1531				case 1:  return ANNCE_NUM_KILL_1;
1532			}
1533			break;
1534		}
1535		case CNT_RESPAWN:
1536		{
1537			switch(num)
1538			{
1539				case 10: return ANNCE_NUM_RESPAWN_10;
1540				case 9:  return ANNCE_NUM_RESPAWN_9;
1541				case 8:  return ANNCE_NUM_RESPAWN_8;
1542				case 7:  return ANNCE_NUM_RESPAWN_7;
1543				case 6:  return ANNCE_NUM_RESPAWN_6;
1544				case 5:  return ANNCE_NUM_RESPAWN_5;
1545				case 4:  return ANNCE_NUM_RESPAWN_4;
1546				case 3:  return ANNCE_NUM_RESPAWN_3;
1547				case 2:  return ANNCE_NUM_RESPAWN_2;
1548				case 1:  return ANNCE_NUM_RESPAWN_1;
1549			}
1550			break;
1551		}
1552		case CNT_ROUNDSTART:
1553		{
1554			switch(num)
1555			{
1556				case 10: return ANNCE_NUM_ROUNDSTART_10;
1557				case 9:  return ANNCE_NUM_ROUNDSTART_9;
1558				case 8:  return ANNCE_NUM_ROUNDSTART_8;
1559				case 7:  return ANNCE_NUM_ROUNDSTART_7;
1560				case 6:  return ANNCE_NUM_ROUNDSTART_6;
1561				case 5:  return ANNCE_NUM_ROUNDSTART_5;
1562				case 4:  return ANNCE_NUM_ROUNDSTART_4;
1563				case 3:  return ANNCE_NUM_ROUNDSTART_3;
1564				case 2:  return ANNCE_NUM_ROUNDSTART_2;
1565				case 1:  return ANNCE_NUM_ROUNDSTART_1;
1566			}
1567			break;
1568		}
1569		default:
1570		{
1571			switch(num)
1572			{
1573				case 10: return ANNCE_NUM_10;
1574				case 9:  return ANNCE_NUM_9;
1575				case 8:  return ANNCE_NUM_8;
1576				case 7:  return ANNCE_NUM_7;
1577				case 6:  return ANNCE_NUM_6;
1578				case 5:  return ANNCE_NUM_5;
1579				case 4:  return ANNCE_NUM_4;
1580				case 3:  return ANNCE_NUM_3;
1581				case 2:  return ANNCE_NUM_2;
1582				case 1:  return ANNCE_NUM_1;
1583			}
1584			break;
1585		}
1586	}
1587}
1588#endif
1589
1590#ifdef GAMEQC
1591int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1592{
1593	switch(nativecontents)
1594	{
1595		case CONTENT_EMPTY:
1596			return 0;
1597		case CONTENT_SOLID:
1598			return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1599		case CONTENT_WATER:
1600			return DPCONTENTS_WATER;
1601		case CONTENT_SLIME:
1602			return DPCONTENTS_SLIME;
1603		case CONTENT_LAVA:
1604			return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1605		case CONTENT_SKY:
1606			return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1607	}
1608	return 0;
1609}
1610
1611int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1612{
1613	if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1614		return CONTENT_SOLID;
1615	if(supercontents & DPCONTENTS_SKY)
1616		return CONTENT_SKY;
1617	if(supercontents & DPCONTENTS_LAVA)
1618		return CONTENT_LAVA;
1619	if(supercontents & DPCONTENTS_SLIME)
1620		return CONTENT_SLIME;
1621	if(supercontents & DPCONTENTS_WATER)
1622		return CONTENT_WATER;
1623	return CONTENT_EMPTY;
1624}
1625#endif
1626