]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/common/util.qc
menu: add a color picker for player names
[divverent/nexuiz.git] / data / qcsrc / common / util.qc
1 string wordwrap_buffer;
2
3 void wordwrap_buffer_put(string s)
4 {
5         wordwrap_buffer = strcat(wordwrap_buffer, s);
6 }
7
8 string wordwrap(string s, float l)
9 {
10         string r;
11         wordwrap_buffer = "";
12         wordwrap_cb(s, l, wordwrap_buffer_put);
13         r = wordwrap_buffer;
14         wordwrap_buffer = "";
15         return r;
16 }
17
18 #ifndef MENUQC
19 #ifndef CSQC
20 void wordwrap_buffer_sprint(string s)
21 {
22         wordwrap_buffer = strcat(wordwrap_buffer, s);
23         if(s == "\n")
24         {
25                 sprint(self, wordwrap_buffer);
26                 wordwrap_buffer = "";
27         }
28 }
29
30 void wordwrap_sprint(string s, float l)
31 {
32         wordwrap_buffer = "";
33         wordwrap_cb(s, l, wordwrap_buffer_sprint);
34         if(wordwrap_buffer != "")
35                 sprint(self, strcat(wordwrap_buffer, "\n"));
36         wordwrap_buffer = "";
37         return;
38 }
39 #endif
40 #endif
41
42 string unescape(string in)
43 {
44         local float i, len;
45         local string str, s;
46
47         // but it doesn't seem to be necessary in my tests at least
48         in = strzone(in);
49
50         len = strlen(in);
51         str = "";
52         for(i = 0; i < len; ++i)
53         {
54                 s = substring(in, i, 1);
55                 if(s == "\\")
56                 {
57                         s = substring(in, i+1, 1);
58                         if(s == "n")
59                                 str = strcat(str, "\n");
60                         else if(s == "\\")
61                                 str = strcat(str, "\\");
62                         else
63                                 str = strcat(str, substring(in, i, 2));
64                         ++i;
65                 } else
66                         str = strcat(str, s);
67         }
68
69         strunzone(in);
70         return str;
71 }
72
73 void wordwrap_cb(string s, float l, void(string) callback)
74 {
75         local string c;
76         local float lleft, i, j, wlen;
77
78         s = strzone(s);
79         lleft = l;
80         for (i = 0;i < strlen(s);++i)
81         {
82                 if (substring(s, i, 2) == "\\n")
83                 {
84                         callback("\n");
85                         lleft = l;
86                         ++i;
87                 }
88                 else if (substring(s, i, 1) == "\n")
89                 {
90                         callback("\n");
91                         lleft = l;
92                 }
93                 else if (substring(s, i, 1) == " ")
94                 {
95                         if (lleft > 0)
96                         {
97                                 callback(" ");
98                                 lleft = lleft - 1;
99                         }
100                 }
101                 else
102                 {
103                         for (j = i+1;j < strlen(s);++j)
104                                 //    ^^ this skips over the first character of a word, which
105                                 //       is ALWAYS part of the word
106                                 //       this is safe since if i+1 == strlen(s), i will become
107                                 //       strlen(s)-1 at the end of this block and the function
108                                 //       will terminate. A space can't be the first character we
109                                 //       read here, and neither can a \n be the start, since these
110                                 //       two cases have been handled above.
111                         {
112                                 c = substring(s, j, 1);
113                                 if (c == " ")
114                                         break;
115                                 if (c == "\\")
116                                         break;
117                                 if (c == "\n")
118                                         break;
119                                 // we need to keep this tempstring alive even if substring is
120                                 // called repeatedly, so call strcat even though we're not
121                                 // doing anything
122                                 callback("");
123                         }
124                         wlen = j - i;
125                         if (lleft < wlen)
126                         {
127                                 callback("\n");
128                                 lleft = l;
129                         }
130                         callback(substring(s, i, wlen));
131                         lleft = lleft - wlen;
132                         i = j - 1;
133                 }
134         }
135         strunzone(s);
136 }
137
138 float dist_point_line(vector p, vector l0, vector ldir)
139 {
140         ldir = normalize(ldir);
141         
142         // remove the component in line direction
143         p = p - (p * ldir) * ldir;
144
145         // vlen of the remaining vector
146         return vlen(p);
147 }
148
149 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
150 {
151         entity e;
152         e = start;
153         funcPre(pass, e);
154         while(e.downleft)
155         {
156                 e = e.downleft;
157                 funcPre(pass, e);
158         }
159         funcPost(pass, e);
160         while(e != start)
161         {
162                 if(e.right)
163                 {
164                         e = e.right;
165                         funcPre(pass, e);
166                         while(e.downleft)
167                         {
168                                 e = e.downleft;
169                                 funcPre(pass, e);
170                         }
171                 }
172                 else
173                         e = e.up;
174                 funcPost(pass, e);
175         }
176 }
177
178 float median(float a, float b, float c)
179 {
180         if(a < c)
181                 return bound(a, b, c);
182         return bound(c, b, a);
183 }
184
185 // converts a number to a string with the indicated number of decimals
186 // works for up to 10 decimals!
187 string ftos_decimals(float number, float decimals)
188 {
189         string result;
190         string tmp;
191         float len;
192
193         // if negative, cut off the sign first
194         if(number < 0)
195                 return strcat("-", ftos_decimals(-number, decimals));
196         // it now is always positive!
197
198         // 3.516 -> 352
199         number = floor(number * pow(10, decimals) + 0.5);
200
201         // 352 -> "352"
202         result = ftos(number);
203         len = strlen(result);
204         // does it have a decimal point (should not happen)? If there is one, it is always at len-7)
205                 // if ftos had messed it up, which should never happen: "34278.000000"
206         if(len >= 7)
207                 if(substring(result, len - 7, 1) == ".")
208                 {
209                         dprint("ftos(integer) has comma? Can't be. Affected result: ", result, "\n");
210                         result = substring(result, 0, len - 7);
211                         len -= 7;
212                 }
213                 // "34278"
214         if(decimals == 0)
215                 return result; // don't insert a point for zero decimals
216         // is it too short? If yes, insert leading zeroes
217         if(len <= decimals)
218         {
219                 result = strcat(substring("0000000000", 0, decimals - len + 1), result);
220                 len = decimals + 1;
221         }
222         // and now... INSERT THE POINT!
223         tmp = substring(result, len - decimals, decimals);
224         result = strcat(substring(result, 0, len - decimals), ".", tmp);
225         return result;
226 }
227
228 float time;
229 vector colormapPaletteColor(float c, float isPants)
230 {
231         switch(c)
232         {
233                 case  0: return '0.800000 0.800000 0.800000';
234                 case  1: return '0.600000 0.400000 0.000000';
235                 case  2: return '0.000000 1.000000 0.501961';
236                 case  3: return '0.000000 1.000000 0.000000';
237                 case  4: return '1.000000 0.000000 0.000000';
238                 case  5: return '0.000000 0.501961 1.000000';
239                 case  6: return '0.000000 1.000000 1.000000';
240                 case  7: return '0.501961 1.000000 0.000000';
241                 case  8: return '0.501961 0.000000 1.000000';
242                 case  9: return '1.000000 0.000000 1.000000';
243                 case 10: return '1.000000 0.000000 0.501961';
244                 case 11: return '0.600000 0.600000 0.600000';
245                 case 12: return '1.000000 1.000000 0.000000';
246                 case 13: return '0.000000 0.000000 1.000000';
247                 case 14: return '1.000000 0.501961 0.000000';
248                 case 15:
249                         if(isPants)
250                                 return
251                                           '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
252                                         + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
253                                         + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
254                         else
255                                 return
256                                           '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
257                                         + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
258                                         + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
259                 default: return '0.000 0.000 0.000';
260         }
261 }
262
263 // unzone the string, and return it as tempstring. Safe to be called on string_null
264 string fstrunzone(string s)
265 {
266         string sc;
267         if not(s)
268                 return s;
269         sc = strcat(s, "");
270         strunzone(s);
271         return sc;
272 }
273
274 // Databases (hash tables)
275 #define DB_BUCKETS 8192
276 void db_save(float db, string pFilename)
277 {
278         float fh, i, n;
279         fh = fopen(pFilename, FILE_WRITE);
280         if(fh < 0) 
281         {
282                 print(strcat("^1Can't write DB to ", pFilename));
283                 return;
284         }
285         n = buf_getsize(db);
286         fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
287         for(i = 0; i < n; ++i)
288                 fputs(fh, strcat(bufstr_get(db, i), "\n"));
289         fclose(fh);
290 }
291
292 float db_create()
293 {
294         return buf_create();
295 }
296
297 float db_load(string pFilename)
298 {
299         float db, fh, i, j, n;
300         string l;
301         db = buf_create();
302         if(db < 0)
303                 return -1;
304         fh = fopen(pFilename, FILE_READ);
305         if(fh < 0)
306                 return db;
307         if(stof(fgets(fh)) == DB_BUCKETS)
308         {
309                 i = 0;
310                 while((l = fgets(fh)))
311                 {
312                         if(l != "")
313                                 bufstr_set(db, i, l);
314                         ++i;
315                 }
316         }
317         else
318         {
319                 // different count of buckets?
320                 // need to reorganize the database then (SLOW)
321                 while((l = fgets(fh)))
322                 {
323                         n = tokenizebyseparator(l, "\\");
324                         for(j = 2; j < n; j += 2)
325                                 db_put(db, argv(j-1), uri_unescape(argv(j)));
326                 }
327         }
328         fclose(fh);
329         return db;
330 }
331
332 void db_dump(float db, string pFilename)
333 {
334         float fh, i, j, n, m;
335         fh = fopen(pFilename, FILE_WRITE);
336         if(fh < 0)
337                 error(strcat("Can't dump DB to ", pFilename));
338         n = buf_getsize(db);
339         fputs(fh, "0\n");
340         for(i = 0; i < n; ++i)
341         {
342                 m = tokenizebyseparator(bufstr_get(db, i), "\\");
343                 for(j = 2; j < m; j += 2)
344                         fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
345         }
346         fclose(fh);
347 }
348
349 void db_close(float db)
350 {
351         buf_del(db);
352 }
353
354 string db_get(float db, string pKey)
355 {
356         float h;
357         h = mod(crc16(FALSE, pKey), DB_BUCKETS);
358         return uri_unescape(infoget(bufstr_get(db, h), pKey));
359 }
360
361 void db_put(float db, string pKey, string pValue)
362 {
363         float h;
364         h = mod(crc16(FALSE, pKey), DB_BUCKETS);
365         bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
366 }
367
368 void db_test()
369 {
370         float db, i;
371         print("LOAD...\n");
372         db = db_load("foo.db");
373         print("LOADED. FILL...\n");
374         for(i = 0; i < DB_BUCKETS; ++i)
375                 db_put(db, ftos(random()), "X");
376         print("FILLED. SAVE...\n");
377         db_save(db, "foo.db");
378         print("SAVED. CLOSE...\n");
379         db_close(db);
380         print("CLOSED.\n");
381 }
382
383 // Multiline text file buffers
384 float buf_load(string pFilename)
385 {
386         float buf, fh, i;
387         string l;
388         buf = buf_create();
389         if(buf < 0)
390                 return -1;
391         fh = fopen(pFilename, FILE_READ);
392         if(fh < 0)
393                 return buf;
394         i = 0;
395         while((l = fgets(fh)))
396         {
397                 bufstr_set(buf, i, l);
398                 ++i;
399         }
400         fclose(fh);
401         return buf;
402 }
403
404 void buf_save(float buf, string pFilename)
405 {
406         float fh, i, n;
407         fh = fopen(pFilename, FILE_WRITE);
408         if(fh < 0)
409                 error(strcat("Can't write buf to ", pFilename));
410         n = buf_getsize(buf);
411         for(i = 0; i < n; ++i)
412                 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
413         fclose(fh);
414 }
415
416 string GametypeNameFromType(float g)
417 {
418         if      (g == GAME_DEATHMATCH) return "dm";
419         else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
420         else if (g == GAME_DOMINATION) return "dom";
421         else if (g == GAME_CTF) return "ctf";
422         else if (g == GAME_RUNEMATCH) return "rune";
423         else if (g == GAME_LMS) return "lms";
424         else if (g == GAME_ARENA) return "arena";
425         else if (g == GAME_KEYHUNT) return "kh";
426         else if (g == GAME_ONSLAUGHT) return "ons";
427         else if (g == GAME_ASSAULT) return "as";
428         else if (g == GAME_RACE) return "race";
429         return "dm";
430 }
431
432 string mmsss(float tenths)
433 {
434         float minutes;
435         string s;
436         tenths = floor(tenths + 0.5);
437         minutes = floor(tenths / 600);
438         tenths -= minutes * 600;
439         s = ftos(1000 + tenths);
440         return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
441 }
442
443 string ScoreString(float pFlags, float pValue)
444 {
445         string valstr;
446         float l;
447
448         pValue = floor(pValue + 0.5); // round
449
450         if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
451                 valstr = "";
452         else if(pFlags & SFL_RANK)
453         {
454                 valstr = ftos(pValue);
455                 l = strlen(valstr);
456                 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
457                         valstr = strcat(valstr, "th");
458                 else if(substring(valstr, l - 1, 1) == "1")
459                         valstr = strcat(valstr, "st");
460                 else if(substring(valstr, l - 1, 1) == "2")
461                         valstr = strcat(valstr, "nd");
462                 else if(substring(valstr, l - 1, 1) == "3")
463                         valstr = strcat(valstr, "rd");
464                 else
465                         valstr = strcat(valstr, "th");
466         }
467         else if(pFlags & SFL_TIME)
468                 valstr = mmsss(pValue);
469         else
470                 valstr = ftos(pValue);
471         
472         return valstr;
473 }
474
475 vector cross(vector a, vector b)
476 {
477         return
478                 '1 0 0' * (a_y * b_z - a_z * b_y)
479         +       '0 1 0' * (a_z * b_x - a_x * b_z)
480         +       '0 0 1' * (a_x * b_y - a_y * b_x);
481 }
482
483 // compressed vector format:
484 // like MD3, just even shorter
485 //   4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
486 //   5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
487 //   7 bit length (logarithmic encoding), 1/8 .. about 7844
488 //     length = 2^(length_encoded/8) / 8
489 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
490 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
491 // the special value 0 indicates the zero vector
492
493 float lengthLogTable[128];
494
495 float invertLengthLog(float x)
496 {
497         float l, r, m, lerr, rerr;
498
499         if(x >= lengthLogTable[127])
500                 return 127;
501         if(x <= lengthLogTable[0])
502                 return 0;
503
504         l = 0;
505         r = 127;
506
507         while(r - l > 1)
508         {
509                 m = floor((l + r) / 2);
510                 if(lengthLogTable[m] < x)
511                         l = m;
512                 else
513                         r = m;
514         }
515
516         // now: r is >=, l is <
517         lerr = (x - lengthLogTable[l]);
518         rerr = (lengthLogTable[r] - x);
519         if(lerr < rerr)
520                 return l;
521         return r;
522 }
523
524 vector decompressShortVector(float data)
525 {
526         vector out;
527         float pitch, yaw, len;
528         if(data == 0)
529                 return '0 0 0';
530         pitch = (data & 0xF000) / 0x1000;
531         yaw =   (data & 0x0F80) / 0x80;
532         len =   (data & 0x007F);
533
534         //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
535
536         if(pitch == 0)
537         {
538                 out_x = 0;
539                 out_y = 0;
540                 if(yaw == 31)
541                         out_z = -1;
542                 else
543                         out_z = +1;
544         }
545         else
546         {
547                 yaw   = .19634954084936207740 * yaw;
548                 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
549                 out_x = cos(yaw) *  cos(pitch);
550                 out_y = sin(yaw) *  cos(pitch);
551                 out_z =            -sin(pitch);
552         }
553
554         //print("decompressed: ", vtos(out), "\n");
555
556         return out * lengthLogTable[len];
557 }
558
559 float compressShortVector(vector vec)
560 {
561         vector ang;
562         float pitch, yaw, len;
563         if(vlen(vec) == 0)
564                 return 0;
565         //print("compress: ", vtos(vec), "\n");
566         ang = vectoangles(vec);
567         ang_x = -ang_x;
568         if(ang_x < -90)
569                 ang_x += 360;
570         if(ang_x < -90 && ang_x > +90)
571                 error("BOGUS vectoangles");
572         //print("angles: ", vtos(ang), "\n");
573
574         pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
575         if(pitch == 0)
576         {
577                 if(vec_z < 0)
578                         yaw = 31;
579                 else
580                         yaw = 30;
581         }
582         else
583                 yaw = floor(0.5 + ang_y * 32 / 360)          & 31; // 0..360 to 0..32
584         len = invertLengthLog(vlen(vec));
585
586         //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
587
588         return (pitch * 0x1000) + (yaw * 0x80) + len;
589 }
590
591 void compressShortVector_init()
592 {
593         float l, f, i;
594         l = 1;
595         f = pow(2, 1/8);
596         for(i = 0; i < 128; ++i)
597         {
598                 lengthLogTable[i] = l;
599                 l *= f;
600         }
601
602         if(cvar("developer"))
603         {
604                 print("Verifying vector compression table...\n");
605                 for(i = 0x0F00; i < 0xFFFF; ++i)
606                         if(i != compressShortVector(decompressShortVector(i)))
607                         {
608                                 print("BROKEN vector compression: ", ftos(i));
609                                 print(" -> ", vtos(decompressShortVector(i)));
610                                 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
611                                 print("\n");
612                                 error("b0rk");
613                         }
614                 print("Done.\n");
615         }
616 }
617
618 #ifndef MENUQC
619 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
620 {
621         traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
622         traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
623         traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
624         traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
625         traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
626         traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
627         traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
628         traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
629         traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
630         traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
631         traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
632         traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
633         return 1;
634 }
635
636 void fixedmakevectors(vector a)
637 {
638         // a makevectors that actually inverts vectoangles
639         a_x = -a_x;
640         makevectors(a);
641 }
642 #endif
643
644 string fixPriorityList(string order, float from, float to, float subtract, float complete)
645 {
646         string neworder;
647         float i, n, w;
648
649         n = tokenize_sane(order);
650         for(i = 0; i < n; ++i)
651         {
652                 w = stof(argv(i));
653                 if(w == floor(w))
654                 {
655                         if(w >= from && w <= to)
656                                 neworder = strcat(neworder, ftos(w), " ");
657                         else
658                         {
659                                 w -= subtract;
660                                 if(w >= from && w <= to)
661                                         neworder = strcat(neworder, ftos(w), " ");
662                         }
663                 }
664         }
665
666         if(complete)
667         {
668                 n = tokenize_sane(neworder);
669                 for(w = to; w >= from; --w)
670                 {
671                         for(i = 0; i < n; ++i)
672                                 if(stof(argv(i)) == w)
673                                         break;
674                         if(i == n) // not found
675                                 neworder = strcat(neworder, ftos(w), " ");
676                 }
677         }
678         
679         return substring(neworder, 0, strlen(neworder) - 1);
680 }
681
682 string swapInPriorityList(string order, float i, float j)
683 {
684         string s;
685         float w, n;
686
687         n = tokenize_sane(order);
688
689         if(i >= 0 && i < n && j >= 0 && j < n && i != j)
690         {
691                 s = "";
692                 for(w = 0; w < n; ++w)
693                 {
694                         if(w == i)
695                                 s = strcat(s, argv(j), " ");
696                         else if(w == j)
697                                 s = strcat(s, argv(i), " ");
698                         else
699                                 s = strcat(s, argv(w), " ");
700                 }
701                 return substring(s, 0, strlen(s) - 1);
702         }
703         
704         return order;
705 }
706
707 float cvar_value_issafe(string s)
708 {
709         if(strstrofs(s, "\"", 0) >= 0)
710                 return 0;
711         if(strstrofs(s, "\\", 0) >= 0)
712                 return 0;
713         if(strstrofs(s, ";", 0) >= 0)
714                 return 0;
715         if(strstrofs(s, "$", 0) >= 0)
716                 return 0;
717         if(strstrofs(s, "\r", 0) >= 0)
718                 return 0;
719         if(strstrofs(s, "\n", 0) >= 0)
720                 return 0;
721         return 1;
722 }
723
724 #ifndef MENUQC
725 void get_mi_min_max(float mode)
726 {
727         vector mi, ma;
728
729         if(mi_shortname)
730                 strunzone(mi_shortname);
731         mi_shortname = mapname;
732         if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
733                 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
734         if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
735                 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
736         mi_shortname = strzone(mi_shortname);
737
738 #ifdef CSQC
739         mi = world.mins;
740         ma = world.maxs;
741 #else
742         mi = world.absmin;
743         ma = world.absmax;
744 #endif
745
746         mi_min = mi;
747         mi_max = ma;
748         MapInfo_Get_ByName(mi_shortname, 0, 0);
749         if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
750         {
751                 mi_min = MapInfo_Map_mins;
752                 mi_max = MapInfo_Map_maxs;
753         }
754         else
755         {
756                 // not specified
757                 if(mode)
758                 {
759                         // be clever
760                         tracebox('1 0 0' * mi_x,
761                                          '0 1 0' * mi_y + '0 0 1' * mi_z,
762                                          '0 1 0' * ma_y + '0 0 1' * ma_z,
763                                          '1 0 0' * ma_x,
764                                          MOVE_WORLDONLY,
765                                          world);
766                         if(!trace_startsolid)
767                                 mi_min_x = trace_endpos_x;
768
769                         tracebox('0 1 0' * mi_y,
770                                          '1 0 0' * mi_x + '0 0 1' * mi_z,
771                                          '1 0 0' * ma_x + '0 0 1' * ma_z,
772                                          '0 1 0' * ma_y,
773                                          MOVE_WORLDONLY,
774                                          world);
775                         if(!trace_startsolid)
776                                 mi_min_y = trace_endpos_y;
777
778                         tracebox('0 0 1' * mi_z,
779                                          '1 0 0' * mi_x + '0 1 0' * mi_y,
780                                          '1 0 0' * ma_x + '0 1 0' * ma_y,
781                                          '0 0 1' * ma_z,
782                                          MOVE_WORLDONLY,
783                                          world);
784                         if(!trace_startsolid)
785                                 mi_min_z = trace_endpos_z;
786
787                         tracebox('1 0 0' * ma_x,
788                                          '0 1 0' * mi_y + '0 0 1' * mi_z,
789                                          '0 1 0' * ma_y + '0 0 1' * ma_z,
790                                          '1 0 0' * mi_x,
791                                          MOVE_WORLDONLY,
792                                          world);
793                         if(!trace_startsolid)
794                                 mi_max_x = trace_endpos_x;
795
796                         tracebox('0 1 0' * ma_y,
797                                          '1 0 0' * mi_x + '0 0 1' * mi_z,
798                                          '1 0 0' * ma_x + '0 0 1' * ma_z,
799                                          '0 1 0' * mi_y,
800                                          MOVE_WORLDONLY,
801                                          world);
802                         if(!trace_startsolid)
803                                 mi_max_y = trace_endpos_y;
804
805                         tracebox('0 0 1' * ma_z,
806                                          '1 0 0' * mi_x + '0 1 0' * mi_y,
807                                          '1 0 0' * ma_x + '0 1 0' * ma_y,
808                                          '0 0 1' * mi_z,
809                                          MOVE_WORLDONLY,
810                                          world);
811                         if(!trace_startsolid)
812                                 mi_max_z = trace_endpos_z;
813                 }
814         }
815 }
816
817 void get_mi_min_max_texcoords(float mode)
818 {
819         vector extend;
820
821         get_mi_min_max(mode);
822
823         mi_picmin = mi_min;
824         mi_picmax = mi_max;
825
826         // extend mi_picmax to get a square aspect ratio
827         // center the map in that area
828         extend = mi_picmax - mi_picmin;
829         if(extend_y > extend_x)
830         {
831                 mi_picmin_x -= (extend_y - extend_x) * 0.5;
832                 mi_picmax_x += (extend_y - extend_x) * 0.5;
833         }
834         else
835         {
836                 mi_picmin_y -= (extend_x - extend_y) * 0.5;
837                 mi_picmax_y += (extend_x - extend_y) * 0.5;
838         }
839
840         // add another some percent
841         extend = (mi_picmax - mi_picmin) * (1 / 64.0);
842         mi_picmin -= extend;
843         mi_picmax += extend;
844
845         // calculate the texcoords
846         mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
847         // first the two corners of the origin
848         mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
849         mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
850         mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
851         mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
852         // then the other corners
853         mi_pictexcoord1_x = mi_pictexcoord0_x;
854         mi_pictexcoord1_y = mi_pictexcoord2_y;
855         mi_pictexcoord3_x = mi_pictexcoord2_x;
856         mi_pictexcoord3_y = mi_pictexcoord0_y;
857 }
858 #endif
859
860 #ifdef CSQC
861 void cvar_settemp(string pKey, string pValue)
862 {
863         error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
864 }
865 void cvar_settemp_restore()
866 {
867         error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
868 }
869 #else
870 void cvar_settemp(string pKey, string pValue)
871 {
872         cvar_set("settemp_list", strcat("1 ", pKey, " ", cvar_string("settemp_var"), " ", cvar_string("settemp_list")));
873 #ifdef MENUQC
874         registercvar(cvar_string("settemp_var"), "", 0);
875 #else
876         registercvar(cvar_string("settemp_var"), "");
877 #endif
878         cvar_set(cvar_string("settemp_var"), cvar_string(pKey));
879         cvar_set("settemp_var", strcat(cvar_string("settemp_var"), "x"));
880         cvar_set(pKey, pValue);
881 }
882
883 void cvar_settemp_restore()
884 {
885         // undo what cvar_settemp did
886         float n, i;
887         n = tokenize_sane(cvar_string("settemp_list"));
888         for(i = 0; i < n - 3; i += 3)
889                 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
890         cvar_set("settemp_list", "0");
891 }
892 #endif
893
894 float almost_equals(float a, float b)
895 {
896         float eps;
897         eps = (max(a, -a) + max(b, -b)) * 0.001;
898         if(a - b < eps && b - a < eps)
899                 return TRUE;
900         return FALSE;
901 }
902
903 float almost_in_bounds(float a, float b, float c)
904 {
905         float eps;
906         eps = (max(a, -a) + max(c, -c)) * 0.001;
907         return b == median(a - eps, b, c + eps);
908 }
909
910
911
912 #ifdef MENUQC
913 float (string s) _tokenize_builtin = #58;
914 string (float argnum) _argv_builtin = #59;
915 float (string s, string sep) _tokenizebyseparator_builtin = #479;
916 #else
917 float (string s) _tokenize_builtin = #441;
918 string (float argnum) _argv_builtin = #442;
919 float (string s, string sep) _tokenizebyseparator_builtin = #479;
920 #endif
921
922 float MAX_TOKENS = 256;
923 string _argv_sane_buffer[MAX_TOKENS];
924 float _argv_sane_startpos[MAX_TOKENS];
925 float _argv_sane_endpos[MAX_TOKENS];
926 float _argc_sane;
927
928 string _argv_sane(float i)
929 {
930         // Perl-ish -1 for the last argument
931         if(i < 0)
932                 i = _argc_sane + i;
933         return strcat("", _argv_sane_buffer[i]); // force tempstring
934 }
935
936 float _argv_start_index_sane(float i)
937 {
938         // Perl-ish -1 for the last argument
939         if(i < 0)
940                 i = _argc_sane + i;
941         return _argv_sane_startpos[i];
942 }
943
944 float _argv_end_index_sane(float i)
945 {
946         // Perl-ish -1 for the last argument
947         if(i < 0)
948                 i = _argc_sane + i;
949         return _argv_sane_endpos[i];
950 }
951
952 string TOKENIZE_SANE_WHITESPACE_CHARS = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
953 string TOKENIZE_SANE_COMMENT_BREAKERS = "\x0d\x0a";
954 // change this if DP changes its type to "char"!
955
956 float _tokenize_sane(string s)
957 {
958         // This MUST match COM_ParseToken_Console!
959         string com_token, tmp;
960         float data;
961         float end;
962         float i;
963
964         _argc_sane = 0;
965         data = 0;
966         end = strlen(s);
967
968         for(i = 0; i < MAX_TOKENS; ++i)
969         {
970                 if(_argv_sane_buffer[i])
971                         strunzone(_argv_sane_buffer[i]);
972                 _argv_sane_buffer[i] = string_null;
973                 _argv_sane_startpos[i] = 0;
974         }
975
976         for(;;)
977         {
978                 // skip whitespace
979                 for(; data < end && strstrofs(TOKENIZE_SANE_WHITESPACE_CHARS, substring(s, data, 1), 0) >= 0; ++data)
980                         ;
981                 if(data == end)
982                         break;
983
984                 if(substring(s, data, 2) == "//")
985                 {
986                         // comment
987
988                         // Any call to the tokenizer ALREADY assumes it's a single line, so we can safely abort if we see a comment.
989                         /*
990                         data += 2;
991                         while(data < end && strstrofs(TOKENIZE_SANE_COMMENT_BREAKERS, substring(s, data, 1), 0) >= 0)
992                                 ++data;
993                         continue; // go to skipwhite again
994                         */
995
996                         // I'd like to simply put a "break" here, but then fteqcc says this function has unreachable code
997                         return _argc_sane;
998                 }
999                 else if(substring(s, data, 1) == "\"")
1000                 {
1001                         // quoted string
1002                         com_token = "";
1003                         _argv_sane_startpos[_argc_sane] = data;
1004                         for(++data; data < end && substring(s, data, 1) != "\""; ++data)
1005                         {
1006                                 // allow escaped " and \ case
1007                                 tmp = substring(s, data, 2);
1008                                 if(tmp == "\\\"" || tmp == "\\\\")
1009                                         ++data;
1010                                 com_token = strcat(com_token, substring(s, data, 1));
1011                         }
1012                         if(substring(s, data, 1) == "\"")
1013                                 ++data;
1014                         _argv_sane_endpos[_argc_sane] = data;
1015                         _argv_sane_buffer[_argc_sane] = strzone(com_token);
1016                         ++_argc_sane;
1017                 }
1018                 else
1019                 {
1020                         // regular word
1021                         com_token = "";
1022                         _argv_sane_startpos[_argc_sane] = data;
1023                         for(; data < end && strstrofs(TOKENIZE_SANE_WHITESPACE_CHARS, substring(s, data, 1), 0) < 0; ++data)
1024                                 com_token = strcat(com_token, substring(s, data, 1));
1025                         _argv_sane_endpos[_argc_sane] = data;
1026                         _argv_sane_buffer[_argc_sane] = strzone(com_token);
1027                         ++_argc_sane;
1028                 }
1029         }
1030
1031         return _argc_sane;
1032 }
1033
1034
1035
1036 var void(void) func_null;
1037
1038 // "sane" tokenizer
1039 // matching the console 1:1
1040
1041 float tokenize_sane(string s)
1042 {
1043         argv = _argv_sane;
1044         argv_start_index = _argv_start_index_sane;
1045         argv_end_index = _argv_end_index_sane;
1046         return _tokenize_sane(s);
1047 }
1048
1049 float tokenize_insane(string s)
1050 {
1051         argv = _argv_builtin;
1052         argv_start_index = func_null;
1053         argv_end_index = func_null;
1054         return _tokenize_builtin(s);
1055 }
1056
1057 float tokenizebyseparator(string s, string sep)
1058 {
1059         argv = _argv_builtin;
1060         argv_start_index = func_null;
1061         argv_end_index = func_null;
1062         return _tokenizebyseparator_builtin(s, sep);
1063 }
1064
1065 float power2of(float e)
1066 {
1067         return pow(2, e);
1068 }
1069 float log2of(float x)
1070 {
1071         // NOTE: generated code
1072         if(x > 2048)
1073                 if(x > 131072)
1074                         if(x > 1048576)
1075                                 if(x > 4194304)
1076                                         return 23;
1077                                 else
1078                                         if(x > 2097152)
1079                                                 return 22;
1080                                         else
1081                                                 return 21;
1082                         else
1083                                 if(x > 524288)
1084                                         return 20;
1085                                 else
1086                                         if(x > 262144)
1087                                                 return 19;
1088                                         else
1089                                                 return 18;
1090                 else
1091                         if(x > 16384)
1092                                 if(x > 65536)
1093                                         return 17;
1094                                 else
1095                                         if(x > 32768)
1096                                                 return 16;
1097                                         else
1098                                                 return 15;
1099                         else
1100                                 if(x > 8192)
1101                                         return 14;
1102                                 else
1103                                         if(x > 4096)
1104                                                 return 13;
1105                                         else
1106                                                 return 12;
1107         else
1108                 if(x > 32)
1109                         if(x > 256)
1110                                 if(x > 1024)
1111                                         return 11;
1112                                 else
1113                                         if(x > 512)
1114                                                 return 10;
1115                                         else
1116                                                 return 9;
1117                         else
1118                                 if(x > 128)
1119                                         return 8;
1120                                 else
1121                                         if(x > 64)
1122                                                 return 7;
1123                                         else
1124                                                 return 6;
1125                 else
1126                         if(x > 4)
1127                                 if(x > 16)
1128                                         return 5;
1129                                 else
1130                                         if(x > 8)
1131                                                 return 4;
1132                                         else
1133                                                 return 3;
1134                         else
1135                                 if(x > 2)
1136                                         return 2;
1137                                 else
1138                                         if(x > 1)
1139                                                 return 1;
1140                                         else
1141                                                 return 0;
1142 }
1143
1144 vector rgb_to_hsl(vector rgb)
1145 {
1146         float mi, ma;
1147         vector hsl;
1148
1149         mi = min3(rgb_x, rgb_y, rgb_z);
1150         ma = max3(rgb_x, rgb_y, rgb_z);
1151
1152         if(mi == ma)
1153                 hsl_x = 0;
1154         else if(ma == rgb_x)
1155                 hsl_x = (rgb_y - rgb_z) / (ma - mi);
1156         else if(ma == rgb_y)
1157                 hsl_x = (rgb_z - rgb_x) / (ma - mi) + 2;
1158         else // if(ma == rgb_z)
1159                 hsl_x = (rgb_x - rgb_y) / (ma - mi) + 4;
1160         if(hsl_x < 0)
1161                 hsl_x += 6;
1162         
1163         hsl_z = 0.5 * (mi + ma);
1164
1165         if(mi == ma)
1166                 hsl_y = 0;
1167         else if(hsl_z <= 0.5)
1168                 hsl_y = (ma - mi) / (2*hsl_z);
1169         else // if(hsl_z > 0.5)
1170                 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1171         
1172         return hsl;
1173 }
1174
1175 vector hsl_to_rgb(vector hsl)
1176 {
1177         float mi, ma, maminusmi, h;
1178         vector rgb;
1179
1180         if(hsl_z <= 0.5)
1181                 maminusmi = hsl_y * 2 * hsl_z;
1182         else
1183                 maminusmi = hsl_y * (2 - 2 * hsl_z);
1184         
1185         // hsl_z     = 0.5 * mi + 0.5 * ma
1186         // maminusmi =     - mi +       ma
1187         mi = hsl_z - 0.5 * maminusmi;
1188         ma = hsl_z + 0.5 * maminusmi;
1189
1190         h = hsl_x - 6 * floor(hsl_x / 6);
1191
1192         //else if(ma == rgb_x)
1193         //      h = 60 * (rgb_y - rgb_z) / (ma - mi);
1194         if(h <= 1)
1195         {
1196                 rgb_x = ma;
1197                 rgb_y = h * (ma - mi) + mi;
1198                 rgb_z = mi;
1199         }
1200         //else if(ma == rgb_y)
1201         //      h = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1202         else if(h <= 2)
1203         {
1204                 rgb_x = (2 - h) * (ma - mi) + mi;
1205                 rgb_y = ma;
1206                 rgb_z = mi;
1207         }
1208         else if(h <= 3)
1209         {
1210                 rgb_x = mi;
1211                 rgb_y = ma;
1212                 rgb_z = (h - 2) * (ma - mi) + mi;
1213         }
1214         //else // if(ma == rgb_z)
1215         //      h = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1216         else if(h <= 4)
1217         {
1218                 rgb_x = mi;
1219                 rgb_y = (4 - h) * (ma - mi) + mi;
1220                 rgb_z = ma;
1221         }
1222         else if(h <= 5)
1223         {
1224                 rgb_x = (h - 4) * (ma - mi) + mi;
1225                 rgb_y = mi;
1226                 rgb_z = ma;
1227         }
1228         //else if(ma == rgb_x)
1229         //      h = 60 * (rgb_y - rgb_z) / (ma - mi);
1230         else // if(h <= 6)
1231         {
1232                 rgb_x = ma;
1233                 rgb_y = mi;
1234                 rgb_z = (6 - h) * (ma - mi) + mi;
1235         }
1236
1237         return rgb;
1238 }
1239
1240 string rgb_to_hexcolor(vector rgb)
1241 {
1242         return
1243                 strcat(
1244                         "^x",
1245                         DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1246                         DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1247                         DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1248                 );
1249 }