4 #include "../client/defs.qh"
5 #include "constants.qh"
6 #include "../client/mutators/events.qh"
8 #include "notifications.qh"
9 #include "deathtypes.qh"
12 #include "constants.qh"
13 #include "../server/autocvars.qh"
14 #include "../server/defs.qh"
15 #include "../server/mutators/events.qh"
16 #include "notifications.qh"
17 #include "deathtypes.qh"
21 string wordwrap_buffer;
23 void wordwrap_buffer_put(string s)
25 wordwrap_buffer = strcat(wordwrap_buffer, s);
28 string wordwrap(string s, float l)
32 wordwrap_cb(s, l, wordwrap_buffer_put);
40 void wordwrap_buffer_sprint(string s)
42 wordwrap_buffer = strcat(wordwrap_buffer, s);
45 sprint(self, wordwrap_buffer);
50 void wordwrap_sprint(string s, float l)
53 wordwrap_cb(s, l, wordwrap_buffer_sprint);
54 if(wordwrap_buffer != "")
55 sprint(self, strcat(wordwrap_buffer, "\n"));
63 string draw_UseSkinFor(string pic)
65 if(substring(pic, 0, 1) == "/")
66 return substring(pic, 1, strlen(pic)-1);
68 return strcat(draw_currentSkin, "/", pic);
72 void wordwrap_cb(string s, float l, void(string) callback)
75 float lleft, i, j, wlen;
79 for (i = 0;i < strlen(s);++i)
81 if (substring(s, i, 2) == "\\n")
87 else if (substring(s, i, 1) == "\n")
92 else if (substring(s, i, 1) == " ")
102 for (j = i+1;j < strlen(s);++j)
103 // ^^ this skips over the first character of a word, which
104 // is ALWAYS part of the word
105 // this is safe since if i+1 == strlen(s), i will become
106 // strlen(s)-1 at the end of this block and the function
107 // will terminate. A space can't be the first character we
108 // read here, and neither can a \n be the start, since these
109 // two cases have been handled above.
111 c = substring(s, j, 1);
118 // we need to keep this tempstring alive even if substring is
119 // called repeatedly, so call strcat even though we're not
129 callback(substring(s, i, wlen));
130 lleft = lleft - wlen;
137 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
166 // converts a number to a string with the indicated number of decimals
167 // works for up to 10 decimals!
168 string ftos_decimals(float number, float decimals)
170 // inhibit stupid negative zero
173 // we have sprintf...
174 return sprintf("%.*f", decimals, number);
177 vector colormapPaletteColor(float c, bool isPants)
181 case 0: return '1.000000 1.000000 1.000000';
182 case 1: return '1.000000 0.333333 0.000000';
183 case 2: return '0.000000 1.000000 0.501961';
184 case 3: return '0.000000 1.000000 0.000000';
185 case 4: return '1.000000 0.000000 0.000000';
186 case 5: return '0.000000 0.666667 1.000000';
187 case 6: return '0.000000 1.000000 1.000000';
188 case 7: return '0.501961 1.000000 0.000000';
189 case 8: return '0.501961 0.000000 1.000000';
190 case 9: return '1.000000 0.000000 1.000000';
191 case 10: return '1.000000 0.000000 0.501961';
192 case 11: return '0.000000 0.000000 1.000000';
193 case 12: return '1.000000 1.000000 0.000000';
194 case 13: return '0.000000 0.333333 1.000000';
195 case 14: return '1.000000 0.666667 0.000000';
199 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
200 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
201 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
204 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
205 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
206 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
207 default: return '0.000 0.000 0.000';
211 // Databases (hash tables)
212 const float DB_BUCKETS = 8192;
213 void db_save(float db, string pFilename)
216 fh = fopen(pFilename, FILE_WRITE);
219 LOG_INFO(strcat("^1Can't write DB to ", pFilename));
223 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
224 for(i = 0; i < n; ++i)
225 fputs(fh, strcat(bufstr_get(db, i), "\n"));
234 int db_load(string pFilename)
236 float db, fh, i, j, n;
241 fh = fopen(pFilename, FILE_READ);
245 if(stof(l) == DB_BUCKETS)
248 while((l = fgets(fh)))
251 bufstr_set(db, i, l);
257 // different count of buckets, or a dump?
258 // need to reorganize the database then (SLOW)
260 // note: we also parse the first line (l) in case the DB file is
261 // missing the bucket count
264 n = tokenizebyseparator(l, "\\");
265 for(j = 2; j < n; j += 2)
266 db_put(db, argv(j-1), uri_unescape(argv(j)));
268 while((l = fgets(fh)));
274 void db_dump(float db, string pFilename)
276 float fh, i, j, n, m;
277 fh = fopen(pFilename, FILE_WRITE);
279 error(strcat("Can't dump DB to ", pFilename));
282 for(i = 0; i < n; ++i)
284 m = tokenizebyseparator(bufstr_get(db, i), "\\");
285 for(j = 2; j < m; j += 2)
286 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
291 void db_close(float db)
296 string db_get(float db, string pKey)
299 h = crc16(false, pKey) % DB_BUCKETS;
300 return uri_unescape(infoget(bufstr_get(db, h), pKey));
303 void db_put(float db, string pKey, string pValue)
306 h = crc16(false, pKey) % DB_BUCKETS;
307 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
313 LOG_INFO("LOAD...\n");
314 db = db_load("foo.db");
315 LOG_INFO("LOADED. FILL...\n");
316 for(i = 0; i < DB_BUCKETS; ++i)
317 db_put(db, ftos(random()), "X");
318 LOG_INFO("FILLED. SAVE...\n");
319 db_save(db, "foo.db");
320 LOG_INFO("SAVED. CLOSE...\n");
322 LOG_INFO("CLOSED.\n");
325 // Multiline text file buffers
326 int buf_load(string pFilename)
333 fh = fopen(pFilename, FILE_READ);
340 while((l = fgets(fh)))
342 bufstr_set(buf, i, l);
349 void buf_save(float buf, string pFilename)
352 fh = fopen(pFilename, FILE_WRITE);
354 error(strcat("Can't write buf to ", pFilename));
355 n = buf_getsize(buf);
356 for(i = 0; i < n; ++i)
357 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
361 string format_time(float seconds)
363 float days, hours, minutes;
364 seconds = floor(seconds + 0.5);
365 days = floor(seconds / 864000);
366 seconds -= days * 864000;
367 hours = floor(seconds / 36000);
368 seconds -= hours * 36000;
369 minutes = floor(seconds / 600);
370 seconds -= minutes * 600;
372 return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
374 return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
377 string mmsss(float tenths)
381 tenths = floor(tenths + 0.5);
382 minutes = floor(tenths / 600);
383 tenths -= minutes * 600;
384 s = ftos(1000 + tenths);
385 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
388 string mmssss(float hundredths)
392 hundredths = floor(hundredths + 0.5);
393 minutes = floor(hundredths / 6000);
394 hundredths -= minutes * 6000;
395 s = ftos(10000 + hundredths);
396 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
399 string ScoreString(int pFlags, float pValue)
404 pValue = floor(pValue + 0.5); // round
406 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
408 else if(pFlags & SFL_RANK)
410 valstr = ftos(pValue);
412 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
413 valstr = strcat(valstr, "th");
414 else if(substring(valstr, l - 1, 1) == "1")
415 valstr = strcat(valstr, "st");
416 else if(substring(valstr, l - 1, 1) == "2")
417 valstr = strcat(valstr, "nd");
418 else if(substring(valstr, l - 1, 1) == "3")
419 valstr = strcat(valstr, "rd");
421 valstr = strcat(valstr, "th");
423 else if(pFlags & SFL_TIME)
424 valstr = TIME_ENCODED_TOSTRING(pValue);
426 valstr = ftos(pValue);
431 // compressed vector format:
432 // like MD3, just even shorter
433 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
434 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
435 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
436 // length = 2^(length_encoded/8) / 8
437 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
438 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
439 // the special value 0 indicates the zero vector
441 float lengthLogTable[128];
443 float invertLengthLog(float x)
447 if(x >= lengthLogTable[127])
449 if(x <= lengthLogTable[0])
457 m = floor((l + r) / 2);
458 if(lengthLogTable[m] < x)
464 // now: r is >=, l is <
465 float lerr = (x - lengthLogTable[l]);
466 float rerr = (lengthLogTable[r] - x);
472 vector decompressShortVector(int data)
477 float p = (data & 0xF000) / 0x1000;
478 float y = (data & 0x0F80) / 0x80;
479 int len = (data & 0x007F);
481 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
494 y = .19634954084936207740 * y;
495 p = .19634954084936207740 * p - 1.57079632679489661922;
496 out.x = cos(y) * cos(p);
497 out.y = sin(y) * cos(p);
501 //print("decompressed: ", vtos(out), "\n");
503 return out * lengthLogTable[len];
506 float compressShortVector(vector vec)
512 //print("compress: ", vtos(vec), "\n");
513 ang = vectoangles(vec);
517 if(ang.x < -90 && ang.x > +90)
518 error("BOGUS vectoangles");
519 //print("angles: ", vtos(ang), "\n");
521 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
530 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
531 len = invertLengthLog(vlen(vec));
533 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
535 return (p * 0x1000) + (y * 0x80) + len;
538 void compressShortVector_init()
541 float f = pow(2, 1/8);
543 for(i = 0; i < 128; ++i)
545 lengthLogTable[i] = l;
549 if(cvar("developer"))
551 LOG_INFO("Verifying vector compression table...\n");
552 for(i = 0x0F00; i < 0xFFFF; ++i)
553 if(i != compressShortVector(decompressShortVector(i)))
555 LOG_INFO("BROKEN vector compression: ", ftos(i));
556 LOG_INFO(" -> ", vtos(decompressShortVector(i)));
557 LOG_INFO(" -> ", ftos(compressShortVector(decompressShortVector(i))));
566 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
568 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
569 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
570 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
571 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
572 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
573 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
574 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
575 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
576 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
577 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
578 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
579 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
584 string fixPriorityList(string order, float from, float to, float subtract, float complete)
589 n = tokenize_console(order);
591 for(i = 0; i < n; ++i)
596 if(w >= from && w <= to)
597 neworder = strcat(neworder, ftos(w), " ");
601 if(w >= from && w <= to)
602 neworder = strcat(neworder, ftos(w), " ");
609 n = tokenize_console(neworder);
610 for(w = to; w >= from; --w)
612 for(i = 0; i < n; ++i)
613 if(stof(argv(i)) == w)
615 if(i == n) // not found
616 neworder = strcat(neworder, ftos(w), " ");
620 return substring(neworder, 0, strlen(neworder) - 1);
623 string mapPriorityList(string order, string(string) mapfunc)
628 n = tokenize_console(order);
630 for(i = 0; i < n; ++i)
631 neworder = strcat(neworder, mapfunc(argv(i)), " ");
633 return substring(neworder, 0, strlen(neworder) - 1);
636 string swapInPriorityList(string order, float i, float j)
641 n = tokenize_console(order);
643 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
646 for(w = 0; w < n; ++w)
649 s = strcat(s, argv(j), " ");
651 s = strcat(s, argv(i), " ");
653 s = strcat(s, argv(w), " ");
655 return substring(s, 0, strlen(s) - 1);
661 float cvar_value_issafe(string s)
663 if(strstrofs(s, "\"", 0) >= 0)
665 if(strstrofs(s, "\\", 0) >= 0)
667 if(strstrofs(s, ";", 0) >= 0)
669 if(strstrofs(s, "$", 0) >= 0)
671 if(strstrofs(s, "\r", 0) >= 0)
673 if(strstrofs(s, "\n", 0) >= 0)
679 void get_mi_min_max(float mode)
684 strunzone(mi_shortname);
685 mi_shortname = mapname;
686 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
687 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
688 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
689 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
690 mi_shortname = strzone(mi_shortname);
702 MapInfo_Get_ByName(mi_shortname, 0, 0);
703 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
705 mi_min = MapInfo_Map_mins;
706 mi_max = MapInfo_Map_maxs;
714 tracebox('1 0 0' * mi.x,
715 '0 1 0' * mi.y + '0 0 1' * mi.z,
716 '0 1 0' * ma.y + '0 0 1' * ma.z,
720 if(!trace_startsolid)
721 mi_min.x = trace_endpos.x;
723 tracebox('0 1 0' * mi.y,
724 '1 0 0' * mi.x + '0 0 1' * mi.z,
725 '1 0 0' * ma.x + '0 0 1' * ma.z,
729 if(!trace_startsolid)
730 mi_min.y = trace_endpos.y;
732 tracebox('0 0 1' * mi.z,
733 '1 0 0' * mi.x + '0 1 0' * mi.y,
734 '1 0 0' * ma.x + '0 1 0' * ma.y,
738 if(!trace_startsolid)
739 mi_min.z = trace_endpos.z;
741 tracebox('1 0 0' * ma.x,
742 '0 1 0' * mi.y + '0 0 1' * mi.z,
743 '0 1 0' * ma.y + '0 0 1' * ma.z,
747 if(!trace_startsolid)
748 mi_max.x = trace_endpos.x;
750 tracebox('0 1 0' * ma.y,
751 '1 0 0' * mi.x + '0 0 1' * mi.z,
752 '1 0 0' * ma.x + '0 0 1' * ma.z,
756 if(!trace_startsolid)
757 mi_max.y = trace_endpos.y;
759 tracebox('0 0 1' * ma.z,
760 '1 0 0' * mi.x + '0 1 0' * mi.y,
761 '1 0 0' * ma.x + '0 1 0' * ma.y,
765 if(!trace_startsolid)
766 mi_max.z = trace_endpos.z;
771 void get_mi_min_max_texcoords(float mode)
775 get_mi_min_max(mode);
780 // extend mi_picmax to get a square aspect ratio
781 // center the map in that area
782 extend = mi_picmax - mi_picmin;
783 if(extend.y > extend.x)
785 mi_picmin.x -= (extend.y - extend.x) * 0.5;
786 mi_picmax.x += (extend.y - extend.x) * 0.5;
790 mi_picmin.y -= (extend.x - extend.y) * 0.5;
791 mi_picmax.y += (extend.x - extend.y) * 0.5;
794 // add another some percent
795 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
799 // calculate the texcoords
800 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
801 // first the two corners of the origin
802 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
803 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
804 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
805 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
806 // then the other corners
807 mi_pictexcoord1_x = mi_pictexcoord0_x;
808 mi_pictexcoord1_y = mi_pictexcoord2_y;
809 mi_pictexcoord3_x = mi_pictexcoord2_x;
810 mi_pictexcoord3_y = mi_pictexcoord0_y;
814 float cvar_settemp(string tmp_cvar, string tmp_value)
816 float created_saved_value;
819 created_saved_value = 0;
821 if (!(tmp_cvar || tmp_value))
823 LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !\n");
827 if(!cvar_type(tmp_cvar))
829 LOG_INFOF("Error: cvar %s doesn't exist!\n", tmp_cvar);
833 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
834 if(e.netname == tmp_cvar)
835 created_saved_value = -1; // skip creation
837 if(created_saved_value != -1)
839 // creating a new entity to keep track of this cvar
841 e.classname = "saved_cvar_value";
842 e.netname = strzone(tmp_cvar);
843 e.message = strzone(cvar_string(tmp_cvar));
844 created_saved_value = 1;
847 // update the cvar to the value given
848 cvar_set(tmp_cvar, tmp_value);
850 return created_saved_value;
853 float cvar_settemp_restore()
857 while((e = find(e, classname, "saved_cvar_value")))
859 if(cvar_type(e.netname))
861 cvar_set(e.netname, e.message);
866 LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
872 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
879 return (rgb.y - rgb.z) / (ma - mi);
881 return (rgb.y - rgb.z) / (ma - mi) + 6;
884 return (rgb.z - rgb.x) / (ma - mi) + 2;
885 else // if(ma == rgb_z)
886 return (rgb.x - rgb.y) / (ma - mi) + 4;
889 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
893 hue -= 6 * floor(hue / 6);
895 //else if(ma == rgb_x)
896 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
900 rgb.y = hue * (ma - mi) + mi;
903 //else if(ma == rgb_y)
904 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
907 rgb.x = (2 - hue) * (ma - mi) + mi;
915 rgb.z = (hue - 2) * (ma - mi) + mi;
917 //else // if(ma == rgb_z)
918 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
922 rgb.y = (4 - hue) * (ma - mi) + mi;
927 rgb.x = (hue - 4) * (ma - mi) + mi;
931 //else if(ma == rgb_x)
932 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
937 rgb.z = (6 - hue) * (ma - mi) + mi;
943 vector rgb_to_hsv(vector rgb)
948 mi = min(rgb.x, rgb.y, rgb.z);
949 ma = max(rgb.x, rgb.y, rgb.z);
951 hsv.x = rgb_mi_ma_to_hue(rgb, mi, ma);
962 vector hsv_to_rgb(vector hsv)
964 return hue_mi_ma_to_rgb(hsv.x, hsv.z * (1 - hsv.y), hsv.z);
967 vector rgb_to_hsl(vector rgb)
972 mi = min(rgb.x, rgb.y, rgb.z);
973 ma = max(rgb.x, rgb.y, rgb.z);
975 hsl.x = rgb_mi_ma_to_hue(rgb, mi, ma);
977 hsl.z = 0.5 * (mi + ma);
980 else if(hsl.z <= 0.5)
981 hsl.y = (ma - mi) / (2*hsl.z);
982 else // if(hsl_z > 0.5)
983 hsl.y = (ma - mi) / (2 - 2*hsl.z);
988 vector hsl_to_rgb(vector hsl)
990 float mi, ma, maminusmi;
993 maminusmi = hsl.y * 2 * hsl.z;
995 maminusmi = hsl.y * (2 - 2 * hsl.z);
997 // hsl_z = 0.5 * mi + 0.5 * ma
998 // maminusmi = - mi + ma
999 mi = hsl.z - 0.5 * maminusmi;
1000 ma = hsl.z + 0.5 * maminusmi;
1002 return hue_mi_ma_to_rgb(hsl.x, mi, ma);
1005 string rgb_to_hexcolor(vector rgb)
1010 DEC_TO_HEXDIGIT(floor(rgb.x * 15 + 0.5)),
1011 DEC_TO_HEXDIGIT(floor(rgb.y * 15 + 0.5)),
1012 DEC_TO_HEXDIGIT(floor(rgb.z * 15 + 0.5))
1016 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1019 // The following function is SLOW.
1020 // For your safety and for the protection of those around you...
1021 // DO NOT CALL THIS AT HOME.
1022 // No really, don't.
1023 if(w(theText, theSize) <= maxWidth)
1024 return strlen(theText); // yeah!
1026 // binary search for right place to cut string
1028 float left, right, middle; // this always works
1030 right = strlen(theText); // this always fails
1033 middle = floor((left + right) / 2);
1034 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1039 while(left < right - 1);
1041 if(w("^7", theSize) == 0) // detect color codes support in the width function
1043 // NOTE: when color codes are involved, this binary search is,
1044 // mathematically, BROKEN. However, it is obviously guaranteed to
1045 // terminate, as the range still halves each time - but nevertheless, it is
1046 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1047 // range, and "right" is outside).
1049 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1050 // and decrease left on the basis of the chars detected of the truncated tag
1051 // Even if the ^xrgb tag is not complete/correct, left is decreased
1052 // (sometimes too much but with a correct result)
1053 // it fixes also ^[0-9]
1054 while(left >= 1 && substring(theText, left-1, 1) == "^")
1057 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1059 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1061 ch = str2chr(theText, left-1);
1062 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1065 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1067 ch = str2chr(theText, left-2);
1068 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1070 ch = str2chr(theText, left-1);
1071 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1080 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1083 // The following function is SLOW.
1084 // For your safety and for the protection of those around you...
1085 // DO NOT CALL THIS AT HOME.
1086 // No really, don't.
1087 if(w(theText) <= maxWidth)
1088 return strlen(theText); // yeah!
1090 // binary search for right place to cut string
1092 float left, right, middle; // this always works
1094 right = strlen(theText); // this always fails
1097 middle = floor((left + right) / 2);
1098 if(w(substring(theText, 0, middle)) <= maxWidth)
1103 while(left < right - 1);
1105 if(w("^7") == 0) // detect color codes support in the width function
1107 // NOTE: when color codes are involved, this binary search is,
1108 // mathematically, BROKEN. However, it is obviously guaranteed to
1109 // terminate, as the range still halves each time - but nevertheless, it is
1110 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1111 // range, and "right" is outside).
1113 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1114 // and decrease left on the basis of the chars detected of the truncated tag
1115 // Even if the ^xrgb tag is not complete/correct, left is decreased
1116 // (sometimes too much but with a correct result)
1117 // it fixes also ^[0-9]
1118 while(left >= 1 && substring(theText, left-1, 1) == "^")
1121 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1123 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1125 ch = str2chr(theText, left-1);
1126 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1129 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1131 ch = str2chr(theText, left-2);
1132 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1134 ch = str2chr(theText, left-1);
1135 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1144 string find_last_color_code(string s)
1146 int start = strstrofs(s, "^", 0);
1147 if (start == -1) // no caret found
1149 int len = strlen(s)-1;
1151 for(i = len; i >= start; --i)
1153 if(substring(s, i, 1) != "^")
1157 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1160 // check if carets aren't all escaped
1164 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1165 return substring(s, i, 2);
1168 if(substring(s, i+1, 1) == "x")
1169 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1170 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1171 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1172 return substring(s, i, 5);
1174 i -= carets; // this also skips one char before the carets
1180 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1186 s = getWrappedLine_remaining;
1190 getWrappedLine_remaining = string_null;
1191 return s; // the line has no size ANYWAY, nothing would be displayed.
1194 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1195 if(cantake > 0 && cantake < strlen(s))
1198 while(take > 0 && substring(s, take, 1) != " ")
1202 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1203 if(getWrappedLine_remaining == "")
1204 getWrappedLine_remaining = string_null;
1205 else if (tw("^7", theFontSize) == 0)
1206 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1207 return substring(s, 0, cantake);
1211 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1212 if(getWrappedLine_remaining == "")
1213 getWrappedLine_remaining = string_null;
1214 else if (tw("^7", theFontSize) == 0)
1215 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1216 return substring(s, 0, take);
1221 getWrappedLine_remaining = string_null;
1226 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1232 s = getWrappedLine_remaining;
1236 getWrappedLine_remaining = string_null;
1237 return s; // the line has no size ANYWAY, nothing would be displayed.
1240 cantake = textLengthUpToLength(s, w, tw);
1241 if(cantake > 0 && cantake < strlen(s))
1244 while(take > 0 && substring(s, take, 1) != " ")
1248 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1249 if(getWrappedLine_remaining == "")
1250 getWrappedLine_remaining = string_null;
1251 else if (tw("^7") == 0)
1252 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1253 return substring(s, 0, cantake);
1257 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1258 if(getWrappedLine_remaining == "")
1259 getWrappedLine_remaining = string_null;
1260 else if (tw("^7") == 0)
1261 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1262 return substring(s, 0, take);
1267 getWrappedLine_remaining = string_null;
1272 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1274 if(tw(theText, theFontSize) <= maxWidth)
1277 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1280 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1282 if(tw(theText) <= maxWidth)
1285 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1288 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1290 string subpattern, subpattern2, subpattern3, subpattern4;
1291 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1293 subpattern2 = ",teams,";
1295 subpattern2 = ",noteams,";
1297 subpattern3 = ",teamspawns,";
1299 subpattern3 = ",noteamspawns,";
1300 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1301 subpattern4 = ",race,";
1303 subpattern4 = string_null;
1305 if(substring(pattern, 0, 1) == "-")
1307 pattern = substring(pattern, 1, strlen(pattern) - 1);
1308 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1310 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1312 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1314 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1319 if(substring(pattern, 0, 1) == "+")
1320 pattern = substring(pattern, 1, strlen(pattern) - 1);
1321 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1322 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1323 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1327 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1334 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1350 // actually, every number solves the equation!
1361 if(a > 0) // put the smaller solution first
1363 v.x = ((-b)-D) / (2*a);
1364 v.y = ((-b)+D) / (2*a);
1368 v.x = (-b+D) / (2*a);
1369 v.y = (-b-D) / (2*a);
1375 // complex solutions!
1388 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1392 // make origin and speed relative
1397 // now solve for ret, ret normalized:
1398 // eorg + t * evel == t * ret * spd
1399 // or, rather, solve for t:
1400 // |eorg + t * evel| == t * spd
1401 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1402 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1403 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1404 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1405 // q = (eorg * eorg) / (evel * evel - spd * spd)
1406 if(!solution.z) // no real solution
1409 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1410 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1411 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1412 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1413 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1414 // spd < |evel| * sin angle(evel, eorg)
1417 else if(solution.x > 0)
1419 // both solutions > 0: take the smaller one
1420 // happens if p < 0 and q > 0
1421 ret = normalize(eorg + solution.x * evel);
1423 else if(solution.y > 0)
1425 // one solution > 0: take the larger one
1426 // happens if q < 0 or q == 0 and p < 0
1427 ret = normalize(eorg + solution.y * evel);
1431 // no solution > 0: reject
1432 // happens if p > 0 and q >= 0
1433 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1434 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1439 // "Enemy is moving away from me at more than spd"
1443 // NOTE: we always got a solution if spd > |evel|
1445 if(newton_style == 2)
1446 ret = normalize(ret * spd + myvel);
1451 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1456 if(newton_style == 2)
1458 // true Newtonian projectiles with automatic aim adjustment
1460 // solve: |outspeed * mydir - myvel| = spd
1461 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1462 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1466 // myvel^2 - (mydir * myvel)^2 > spd^2
1467 // velocity without mydir component > spd
1468 // fire at smallest possible spd that works?
1469 // |(mydir * myvel) * myvel - myvel| = spd
1471 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1475 outspeed = solution.y; // the larger one
1478 //outspeed = 0; // slowest possible shot
1479 outspeed = solution.x; // the real part (that is, the average!)
1480 //dprint("impossible shot, adjusting\n");
1483 outspeed = bound(spd * mi, outspeed, spd * ma);
1484 return mydir * outspeed;
1488 return myvel + spd * mydir;
1491 float compressShotOrigin(vector v)
1495 y = rint(v.y * 4) + 128;
1496 z = rint(v.z * 4) + 128;
1497 if(x > 255 || x < 0)
1499 LOG_INFO("shot origin ", vtos(v), " x out of bounds\n");
1500 x = bound(0, x, 255);
1502 if(y > 255 || y < 0)
1504 LOG_INFO("shot origin ", vtos(v), " y out of bounds\n");
1505 y = bound(0, y, 255);
1507 if(z > 255 || z < 0)
1509 LOG_INFO("shot origin ", vtos(v), " z out of bounds\n");
1510 z = bound(0, z, 255);
1512 return x * 0x10000 + y * 0x100 + z;
1514 vector decompressShotOrigin(int f)
1517 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1518 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1519 v.z = ((f & 0xFF) - 128) / 4;
1524 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1526 // NOTE: we'll always choose the SMALLER value...
1527 float healthdamage, armordamage, armorideal;
1528 if (deathtype == DEATH_DROWN) // Why should armor help here...
1531 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1532 armordamage = a + (h - 1); // damage we can take if we could use more armor
1533 armorideal = healthdamage * armorblock;
1535 if(armordamage < healthdamage)
1548 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1551 if (deathtype == DEATH_DROWN) // Why should armor help here...
1553 v.y = bound(0, damage * armorblock, a); // save
1554 v.x = bound(0, damage - v.y, damage); // take
1560 string getcurrentmod()
1564 m = cvar_string("fs_gamedir");
1565 n = tokenize_console(m);
1572 // from the GNU Scientific Library
1573 float gsl_ran_gaussian_lastvalue;
1574 float gsl_ran_gaussian_lastvalue_set;
1575 float gsl_ran_gaussian(float sigma)
1578 if(gsl_ran_gaussian_lastvalue_set)
1580 gsl_ran_gaussian_lastvalue_set = 0;
1581 return sigma * gsl_ran_gaussian_lastvalue;
1585 a = random() * 2 * M_PI;
1586 b = sqrt(-2 * log(random()));
1587 gsl_ran_gaussian_lastvalue = cos(a) * b;
1588 gsl_ran_gaussian_lastvalue_set = 1;
1589 return sigma * sin(a) * b;
1593 float matchacl(string acl, string str)
1600 t = car(acl); acl = cdr(acl);
1603 if(substring(t, 0, 1) == "-")
1606 t = substring(t, 1, strlen(t) - 1);
1608 else if(substring(t, 0, 1) == "+")
1609 t = substring(t, 1, strlen(t) - 1);
1611 if(substring(t, -1, 1) == "*")
1613 t = substring(t, 0, strlen(t) - 1);
1614 s = substring(str, 0, strlen(t));
1627 string get_model_datafilename(string m, float sk, string fil)
1632 m = "models/player/*_";
1634 m = strcat(m, ftos(sk));
1637 return strcat(m, ".", fil);
1640 float get_model_parameters(string m, float sk)
1642 get_model_parameters_modelname = string_null;
1643 get_model_parameters_modelskin = -1;
1644 get_model_parameters_name = string_null;
1645 get_model_parameters_species = -1;
1646 get_model_parameters_sex = string_null;
1647 get_model_parameters_weight = -1;
1648 get_model_parameters_age = -1;
1649 get_model_parameters_desc = string_null;
1650 get_model_parameters_bone_upperbody = string_null;
1651 get_model_parameters_bone_weapon = string_null;
1652 for(int i = 0; i < MAX_AIM_BONES; ++i)
1654 get_model_parameters_bone_aim[i] = string_null;
1655 get_model_parameters_bone_aimweight[i] = 0;
1657 get_model_parameters_fixbone = 0;
1660 MUTATOR_CALLHOOK(ClearModelParams);
1666 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1667 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1671 if(substring(m, -4, -1) != ".txt")
1673 if(substring(m, -6, 1) != "_")
1675 sk = stof(substring(m, -5, 1));
1676 m = substring(m, 0, -7);
1679 string fn = get_model_datafilename(m, sk, "txt");
1680 int fh = fopen(fn, FILE_READ);
1684 fn = get_model_datafilename(m, sk, "txt");
1685 fh = fopen(fn, FILE_READ);
1690 get_model_parameters_modelname = m;
1691 get_model_parameters_modelskin = sk;
1693 while((s = fgets(fh)))
1696 break; // next lines will be description
1700 get_model_parameters_name = s;
1704 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1705 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1706 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1707 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1708 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1709 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1710 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1713 get_model_parameters_sex = s;
1715 get_model_parameters_weight = stof(s);
1717 get_model_parameters_age = stof(s);
1718 if(c == "description")
1719 get_model_parameters_description = s;
1720 if(c == "bone_upperbody")
1721 get_model_parameters_bone_upperbody = s;
1722 if(c == "bone_weapon")
1723 get_model_parameters_bone_weapon = s;
1725 MUTATOR_CALLHOOK(GetModelParams, c, s);
1727 for(int i = 0; i < MAX_AIM_BONES; ++i)
1728 if(c == strcat("bone_aim", ftos(i)))
1730 get_model_parameters_bone_aimweight[i] = stof(car(s));
1731 get_model_parameters_bone_aim[i] = cdr(s);
1734 get_model_parameters_fixbone = stof(s);
1737 while((s = fgets(fh)))
1739 if(get_model_parameters_desc)
1740 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1742 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1750 float vercmp_recursive(string v1, string v2)
1756 dot1 = strstrofs(v1, ".", 0);
1757 dot2 = strstrofs(v2, ".", 0);
1761 s1 = substring(v1, 0, dot1);
1765 s2 = substring(v2, 0, dot2);
1767 r = stof(s1) - stof(s2);
1771 r = strcasecmp(s1, s2);
1784 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
1787 float vercmp(string v1, string v2)
1789 if(strcasecmp(v1, v2) == 0) // early out check
1798 return vercmp_recursive(v1, v2);
1801 // x-encoding (encoding as zero length invisible string)
1802 const string XENCODE_2 = "xX";
1803 const string XENCODE_22 = "0123456789abcdefABCDEF";
1804 string xencode(int f)
1807 d = f % 22; f = floor(f / 22);
1808 c = f % 22; f = floor(f / 22);
1809 b = f % 22; f = floor(f / 22);
1810 a = f % 2; // f = floor(f / 2);
1813 substring(XENCODE_2, a, 1),
1814 substring(XENCODE_22, b, 1),
1815 substring(XENCODE_22, c, 1),
1816 substring(XENCODE_22, d, 1)
1819 float xdecode(string s)
1822 if(substring(s, 0, 1) != "^")
1826 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1827 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1828 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1829 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1830 if(a < 0 || b < 0 || c < 0 || d < 0)
1832 return ((a * 22 + b) * 22 + c) * 22 + d;
1836 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1838 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1841 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1845 entity ReadCSQCEntity()
1847 int f = ReadShort();
1850 return findfloat(world, entnum, f);
1854 float shutdown_running;
1859 void CSQC_Shutdown()
1865 if(shutdown_running)
1867 LOG_INFO("Recursive shutdown detected! Only restoring cvars...\n");
1871 shutdown_running = 1;
1874 cvar_settemp_restore(); // this must be done LAST, but in any case
1877 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
1878 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
1879 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
1880 // this will use the value:
1882 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
1883 // accuracy at x is 1/derivative, i.e.
1884 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
1886 void WriteApproxPastTime(float dst, float t)
1888 float dt = time - t;
1890 // warning: this is approximate; do not resend when you don't have to!
1891 // be careful with sendflags here!
1892 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
1895 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
1898 dt = rint(bound(0, dt, 255));
1904 float ReadApproxPastTime()
1906 float dt = ReadByte();
1908 // map from range...PPROXPASTTIME_MAX / 256
1909 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
1911 return servertime - dt;
1916 .float skeleton_bones_index;
1917 void Skeleton_SetBones(entity e)
1919 // set skeleton_bones to the total number of bones on the model
1920 if(e.skeleton_bones_index == e.modelindex)
1921 return; // same model, nothing to update
1924 skelindex = skel_create(e.modelindex);
1925 e.skeleton_bones = skel_get_numbones(skelindex);
1926 skel_delete(skelindex);
1927 e.skeleton_bones_index = e.modelindex;
1931 string to_execute_next_frame;
1932 void execute_next_frame()
1934 if(to_execute_next_frame)
1936 localcmd("\n", to_execute_next_frame, "\n");
1937 strunzone(to_execute_next_frame);
1938 to_execute_next_frame = string_null;
1941 void queue_to_execute_next_frame(string s)
1943 if(to_execute_next_frame)
1945 s = strcat(s, "\n", to_execute_next_frame);
1946 strunzone(to_execute_next_frame);
1948 to_execute_next_frame = strzone(s);
1951 .float FindConnectedComponent_processing;
1952 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1954 entity queue_start, queue_end;
1956 // we build a queue of to-be-processed entities.
1957 // queue_start is the next entity to be checked for neighbors
1958 // queue_end is the last entity added
1960 if(e.FindConnectedComponent_processing)
1961 error("recursion or broken cleanup");
1963 // start with a 1-element queue
1964 queue_start = queue_end = e;
1965 queue_end.(fld) = world;
1966 queue_end.FindConnectedComponent_processing = 1;
1968 // for each queued item:
1969 for (; queue_start; queue_start = queue_start.(fld))
1971 // find all neighbors of queue_start
1973 for(t = world; (t = nxt(t, queue_start, pass)); )
1975 if(t.FindConnectedComponent_processing)
1977 if(iscon(t, queue_start, pass))
1979 // it is connected? ADD IT. It will look for neighbors soon too.
1980 queue_end.(fld) = t;
1982 queue_end.(fld) = world;
1983 queue_end.FindConnectedComponent_processing = 1;
1989 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1990 queue_start.FindConnectedComponent_processing = 0;
1994 vector animfixfps(entity e, vector a, vector b)
1996 // multi-frame anim: keep as-is
2000 dur = frameduration(e.modelindex, a.x);
2004 dur = frameduration(e.modelindex, a.x);
2014 void dedicated_print(string input) // print(), but only print if the server is not local
2016 if(server_is_dedicated) { LOG_INFO(input); }
2021 float Announcer_PickNumber(float type, float num)
2029 case 10: return ANNCE_NUM_GAMESTART_10;
2030 case 9: return ANNCE_NUM_GAMESTART_9;
2031 case 8: return ANNCE_NUM_GAMESTART_8;
2032 case 7: return ANNCE_NUM_GAMESTART_7;
2033 case 6: return ANNCE_NUM_GAMESTART_6;
2034 case 5: return ANNCE_NUM_GAMESTART_5;
2035 case 4: return ANNCE_NUM_GAMESTART_4;
2036 case 3: return ANNCE_NUM_GAMESTART_3;
2037 case 2: return ANNCE_NUM_GAMESTART_2;
2038 case 1: return ANNCE_NUM_GAMESTART_1;
2046 case 10: return ANNCE_NUM_IDLE_10;
2047 case 9: return ANNCE_NUM_IDLE_9;
2048 case 8: return ANNCE_NUM_IDLE_8;
2049 case 7: return ANNCE_NUM_IDLE_7;
2050 case 6: return ANNCE_NUM_IDLE_6;
2051 case 5: return ANNCE_NUM_IDLE_5;
2052 case 4: return ANNCE_NUM_IDLE_4;
2053 case 3: return ANNCE_NUM_IDLE_3;
2054 case 2: return ANNCE_NUM_IDLE_2;
2055 case 1: return ANNCE_NUM_IDLE_1;
2063 case 10: return ANNCE_NUM_KILL_10;
2064 case 9: return ANNCE_NUM_KILL_9;
2065 case 8: return ANNCE_NUM_KILL_8;
2066 case 7: return ANNCE_NUM_KILL_7;
2067 case 6: return ANNCE_NUM_KILL_6;
2068 case 5: return ANNCE_NUM_KILL_5;
2069 case 4: return ANNCE_NUM_KILL_4;
2070 case 3: return ANNCE_NUM_KILL_3;
2071 case 2: return ANNCE_NUM_KILL_2;
2072 case 1: return ANNCE_NUM_KILL_1;
2080 case 10: return ANNCE_NUM_RESPAWN_10;
2081 case 9: return ANNCE_NUM_RESPAWN_9;
2082 case 8: return ANNCE_NUM_RESPAWN_8;
2083 case 7: return ANNCE_NUM_RESPAWN_7;
2084 case 6: return ANNCE_NUM_RESPAWN_6;
2085 case 5: return ANNCE_NUM_RESPAWN_5;
2086 case 4: return ANNCE_NUM_RESPAWN_4;
2087 case 3: return ANNCE_NUM_RESPAWN_3;
2088 case 2: return ANNCE_NUM_RESPAWN_2;
2089 case 1: return ANNCE_NUM_RESPAWN_1;
2093 case CNT_ROUNDSTART:
2097 case 10: return ANNCE_NUM_ROUNDSTART_10;
2098 case 9: return ANNCE_NUM_ROUNDSTART_9;
2099 case 8: return ANNCE_NUM_ROUNDSTART_8;
2100 case 7: return ANNCE_NUM_ROUNDSTART_7;
2101 case 6: return ANNCE_NUM_ROUNDSTART_6;
2102 case 5: return ANNCE_NUM_ROUNDSTART_5;
2103 case 4: return ANNCE_NUM_ROUNDSTART_4;
2104 case 3: return ANNCE_NUM_ROUNDSTART_3;
2105 case 2: return ANNCE_NUM_ROUNDSTART_2;
2106 case 1: return ANNCE_NUM_ROUNDSTART_1;
2114 case 10: return ANNCE_NUM_10;
2115 case 9: return ANNCE_NUM_9;
2116 case 8: return ANNCE_NUM_8;
2117 case 7: return ANNCE_NUM_7;
2118 case 6: return ANNCE_NUM_6;
2119 case 5: return ANNCE_NUM_5;
2120 case 4: return ANNCE_NUM_4;
2121 case 3: return ANNCE_NUM_3;
2122 case 2: return ANNCE_NUM_2;
2123 case 1: return ANNCE_NUM_1;
2128 return NOTIF_ABORT; // abort sending if none of these numbers were right
2133 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2135 switch(nativecontents)
2140 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2142 return DPCONTENTS_WATER;
2144 return DPCONTENTS_SLIME;
2146 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2148 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2153 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2155 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2156 return CONTENT_SOLID;
2157 if(supercontents & DPCONTENTS_SKY)
2159 if(supercontents & DPCONTENTS_LAVA)
2160 return CONTENT_LAVA;
2161 if(supercontents & DPCONTENTS_SLIME)
2162 return CONTENT_SLIME;
2163 if(supercontents & DPCONTENTS_WATER)
2164 return CONTENT_WATER;
2165 return CONTENT_EMPTY;