game mode "keyhunt", still in testing
[divverent/nexuiz.git] / data / qcsrc / server / miscfunctions.qc
1 void() info_player_deathmatch; // needed for the other spawnpoints
2
3 void move_out_of_solid_expand(entity e, vector by)
4 {
5         float eps = 0.0625;
6         tracebox(e.origin, e.mins - '1 1 1' * eps, e.maxs + '1 1 1' * eps, e.origin + by, MOVE_WORLDONLY, e);
7         if(trace_startsolid)
8                 return;
9         if(trace_fraction < 1)
10         {
11                 // hit something
12                 // adjust origin in the other direction...
13                 e.origin = e.origin - by * (1 - trace_fraction);
14         }
15 }
16
17 void move_out_of_solid(entity e)
18 {
19         vector o, m0, m1;
20
21         o = e.origin;
22         traceline(o, o, MOVE_WORLDONLY, e);
23         if(trace_startsolid)
24         {
25                 dprint("origin is in solid too! (", vtos(o), ")");
26                 return;
27         }
28
29         tracebox(o, e.mins, e.maxs, o, MOVE_WORLDONLY, e);
30         if(!trace_startsolid)
31                 return;
32
33         m0 = e.mins;
34         m1 = e.maxs;
35         e.mins = '0 0 0';
36         e.maxs = '0 0 0';
37         move_out_of_solid_expand(e, '1 0 0' * m0_x); e.mins_x = m0_x;
38         move_out_of_solid_expand(e, '1 0 0' * m1_x); e.maxs_x = m1_x;
39         move_out_of_solid_expand(e, '0 1 0' * m0_y); e.mins_y = m0_y;
40         move_out_of_solid_expand(e, '0 1 0' * m1_y); e.maxs_y = m1_y;
41         move_out_of_solid_expand(e, '0 0 1' * m0_z); e.mins_z = m0_z;
42         move_out_of_solid_expand(e, '0 0 1' * m1_z); e.maxs_z = m1_z;
43         setorigin(e, e.origin);
44
45         tracebox(e.origin, e.mins, e.maxs, e.origin, MOVE_WORLDONLY, e);
46         if(trace_startsolid)
47         {
48                 dprint("could not get out of solid (", vtos(o), ")");
49                 return;
50         }
51 }
52
53 // converts a number to a string with the indicated number of decimals
54 // works for up to 10 decimals!
55 string ftos_decimals(float number, float decimals)
56 {
57         string result;
58         string tmp;
59         float len;
60
61         // if negative, cut off the sign first
62         if(number < 0)
63                 return strcat("-", ftos_decimals(-number, decimals));
64         // it now is always positive!
65
66         // 3.516 -> 352
67         number = floor(number * pow(10, decimals) + 0.5);
68
69         // 352 -> "352"
70         result = ftos(number);
71         len = strlen(result);
72         // does it have a decimal point (should not happen)? If there is one, it is always at len-7)
73                 // if ftos had fucked it up, which should never happen: "34278.000000"
74         if(len >= 7)
75                 if(substring(result, len - 7, 1) == ".")
76                 {
77                         dprint("ftos(integer) has comma? Can't be. Affected result: ", result, "\n");
78                         result = substring(result, 0, len - 7);
79                         len -= 7;
80                 }
81                 // "34278"
82         // is it too short? If yes, insert leading zeroes
83         if(len <= decimals)
84         {
85                 result = strcat(substring("0000000000", 0, decimals - len + 1), result);
86                 len = decimals + 1;
87         }
88         // and now... INSERT THE POINT!
89         tmp = substring(result, len - decimals, decimals);
90         result = strcat(substring(result, 0, len - decimals), ".", tmp);
91         return result;
92 }
93
94 #define FOR_EACH_CLIENT(v) for(v = world; (v = findflags(v, flags, FL_CLIENT)) != world; )
95 #define FOR_EACH_REALCLIENT(v) FOR_EACH_CLIENT(v) if(clienttype(v) == CLIENTTYPE_REAL)
96 string STR_PLAYER = "player";
97 #define FOR_EACH_PLAYER(v) for(v = world; (v = find(v, classname, STR_PLAYER)) != world; )
98 #define FOR_EACH_REALPLAYER(v) FOR_EACH_PLAYER(v) if(clienttype(v) == CLIENTTYPE_REAL)
99
100 // change that to actually calling strcat when running on an engine without
101 // unlimited tempstrings:
102 // string strcat1(string s) = #115; // FRIK_FILE
103 #define strcat1(s) (s)
104
105 float logfile_open;
106 float logfile;
107
108 void(string s) bcenterprint
109 {
110         // TODO replace by MSG_ALL (would show it to spectators too, though)?
111         entity head;
112         FOR_EACH_PLAYER(head)
113                 if(clienttype(head) == CLIENTTYPE_REAL)
114                         centerprint(head, s);
115 }
116
117 void(string s, float check_dangerous) ServerConsoleEcho =
118 {
119         local string ch;
120         if (checkextension("DP_SV_PRINT"))
121                 print(s, "\n");
122         else
123         {
124                 localcmd("echo \"");
125                 if(check_dangerous)
126                 {
127                         while(strlen(s))
128                         {
129                                 ch = substring(s, 0, 1);
130                                 if(ch != "\"" && ch != "\r" && ch != "\n")
131                                         localcmd(ch);
132                                 s = substring(s, 1, strlen(s) - 1);
133                         }
134                 }
135                 else
136                 {
137                         localcmd(s);
138                 }
139                 localcmd("\"\n");
140         }
141 }
142
143 void(string s, float check_dangerous) GameLogEcho =
144 {
145         string fn;
146         float matches;
147
148         if(cvar("sv_eventlog_files"))
149         {
150                 if(!logfile_open)
151                 {
152                         logfile_open = TRUE;
153                         matches = cvar("sv_eventlog_files_counter") + 1;
154                         cvar_set("sv_eventlog_files_counter", ftos(matches));
155                         fn = ftos(matches);
156                         if(strlen(fn) < 8)
157                                 fn = strcat(substring("00000000", 0, 8 - strlen(fn)), fn);
158                         fn = strcat(cvar_string("sv_eventlog_files_nameprefix"), fn, cvar_string("sv_eventlog_files_namesuffix"));
159                         logfile = fopen(fn, FILE_APPEND);
160                 }
161                 if(logfile >= 0)
162                         fputs(logfile, strcat(s, "\n"));
163         }
164         if(cvar("sv_eventlog_console"))
165         {
166                 ServerConsoleEcho(s, check_dangerous);
167         }
168 }
169
170 void() GameLogInit =
171 {
172         logfile_open = 0;
173         // will be opened later
174 }
175
176 void() GameLogClose =
177 {
178         if(logfile_open && logfile >= 0)
179         {
180                 fclose(logfile);
181                 logfile = -1;
182         }
183 }
184
185 float math_mod(float a, float b)
186 {
187         return a - (floor(a / b) * b);
188 }
189
190 void relocate_spawnpoint()
191 {
192         // nudge off the floor
193         setorigin(self, self.origin + '0 0 1');
194
195         tracebox(self.origin, PL_MIN, PL_MAX, self.origin, TRUE, self);
196         if (trace_startsolid)
197         {
198                 objerror("player spawn point in solid, mapper sucks!\n");
199                 return;
200         }
201
202         if(cvar("g_spawnpoints_autodrop"))
203         {
204                 setsize(self, PL_MIN, PL_MAX);
205                 droptofloor();
206         }
207 }
208
209 // NOTE: DO NOT USE THIS FUNCTION TOO OFTEN.
210 // IT WILL MOST PROBABLY DESTROY _ALL_ OTHER TEMP
211 // STRINGS AND TAKE QUITE LONG. haystack and needle MUST
212 // BE CONSTANT OR strzoneD!
213 float(string haystack, string needle, float offset) strstr =
214 {
215         float len, endpos;
216         string found;
217         len = strlen(needle);
218         endpos = strlen(haystack) - len;
219         while(offset <= endpos)
220         {
221                 found = substring(haystack, offset, len);
222                 if(found == needle)
223                         return offset;
224                 offset = offset + 1;
225         }
226         return -1;
227 }
228
229 float NUM_NEAREST_ENTITIES = 4;
230 entity nearest_entity[NUM_NEAREST_ENTITIES];
231 float nearest_length[NUM_NEAREST_ENTITIES];
232 entity(vector point, .string field, string value, vector axismod) findnearest =
233 {
234         entity localhead;
235         float i;
236         float j;
237         float len;
238         vector dist;
239
240         float num_nearest;
241         num_nearest = 0;
242
243         localhead = find(world, field, value);
244         while(localhead)
245         {
246                 if((localhead.items == IT_KEY1 || localhead.items == IT_KEY2) && localhead.target == "###item###")
247                         dist = localhead.oldorigin;
248                 else
249                         dist = localhead.origin;
250                 dist = dist - point;
251                 dist = dist_x * axismod_x * '1 0 0' + dist_y * axismod_y * '0 1 0' + dist_z * axismod_z * '0 0 1';
252                 len = vlen(dist);
253
254                 for(i = 0; i < num_nearest; ++i)
255                 {
256                         if(len < nearest_length[i])
257                                 break;
258                 }
259
260                 // now i tells us where to insert at
261                 //   INSERTION SORT! YOU'VE SEEN IT! RUN!
262                 if(i < NUM_NEAREST_ENTITIES)
263                 {
264                         for(j = NUM_NEAREST_ENTITIES - 1; j >= i; --j)
265                         {
266                                 nearest_length[j + 1] = nearest_length[j];
267                                 nearest_entity[j + 1] = nearest_entity[j];
268                         }
269                         nearest_length[i] = len;
270                         nearest_entity[i] = localhead;
271                         if(num_nearest < NUM_NEAREST_ENTITIES)
272                                 num_nearest = num_nearest + 1;
273                 }
274
275                 localhead = find(localhead, field, value);
276         }
277
278         // now use the first one from our list that we can see
279         for(i = 0; i < num_nearest; ++i)
280         {
281                 traceline(point, nearest_entity[i].origin, TRUE, world);
282                 if(trace_fraction == 1)
283                 {
284                         if(i != 0)
285                         {
286                                 dprint("Nearest point (");
287                                 dprint(nearest_entity[0].netname);
288                                 dprint(") is not visible, using a visible one.\n");
289                         }
290                         return nearest_entity[i];
291                 }
292         }
293
294         if(num_nearest == 0)
295                 return world;
296
297         dprint("Not seeing any location point, using nearest as fallback.\n");
298         /* DEBUGGING CODE:
299         dprint("Candidates were: ");
300         for(j = 0; j < num_nearest; ++j)
301         {
302                 if(j != 0)
303                         dprint(", ");
304                 dprint(nearest_entity[j].netname);
305         }
306         dprint("\n");
307         */
308
309         return nearest_entity[0];
310 }
311
312 void() target_location =
313 {
314         self.classname = "target_location";
315         // location name in netname
316         // eventually support: count, teamgame selectors, line of sight?
317 };
318
319 void() info_location =
320 {
321         self.classname = "target_location";
322         self.message = self.netname;
323 };
324
325 string NearestLocation(vector p)
326 {
327         entity loc;
328         string ret;
329         ret = "somewhere";
330         loc = findnearest(p, classname, "target_location", '1 1 1');
331         if(loc)
332         {
333                 ret = loc.message;
334         }
335         else
336         {
337                 loc = findnearest(p, target, "###item###", '1 1 4');
338                 if(loc)
339                         ret = loc.netname;
340         }
341         return ret;
342 }
343
344 string(string msg) formatmessage =
345 {
346         float p;
347         float n;
348         string msg_save;
349         string escape;
350         string replacement;
351         msg_save = strzone(msg);
352         p = 0;
353         n = 7;
354         while(1)
355         {
356                 if(n < 1)
357                         break; // too many replacements
358                 n = n - 1;
359                 p = strstr(msg_save, "%", p); // NOTE: this destroys msg as it's a tempstring!
360                 if(p < 0)
361                         break;
362                 replacement = substring(msg_save, p, 2);
363                 escape = substring(msg_save, p + 1, 1);
364                 if(escape == "%")
365                         replacement = "%";
366                 else if(escape == "a")
367                         replacement = ftos(floor(self.armorvalue));
368                 else if(escape == "h")
369                         replacement = ftos(floor(self.health));
370                 else if(escape == "l")
371                         replacement = NearestLocation(self.origin);
372                 else if(escape == "y")
373                         replacement = NearestLocation(self.cursor_trace_endpos);
374                 else if(escape == "d")
375                         replacement = NearestLocation(self.death_origin);
376                 else if(escape == "w")
377                 {
378                         float wep;
379                         wep = self.weapon;
380                         if(!wep)
381                                 wep = self.switchweapon;
382                         if(!wep)
383                                 wep = self.cnt;
384                         replacement = W_Name(wep);
385                 }
386                 else if(escape == "W")
387                 {
388                         if(self.items & IT_SHELLS) replacement = "shells";
389                         else if(self.items & IT_NAILS) replacement = "bullets";
390                         else if(self.items & IT_ROCKETS) replacement = "rockets";
391                         else if(self.items & IT_CELLS) replacement = "cells";
392                         else replacement = "batteries"; // ;)
393                 }
394                 else if(escape == "x")
395                 {
396                         replacement = self.cursor_trace_ent.netname;
397                         if(!replacement || !self.cursor_trace_ent)
398                                 replacement = "nothing";
399                 }
400                 else if(escape == "p")
401                 {
402                         if(self.last_selected_player)
403                                 replacement = self.last_selected_player.netname;
404                         else
405                                 replacement = "(nobody)";
406                 }
407                 msg = strcat(substring(msg_save, 0, p), replacement);
408                 msg = strcat(msg, substring(msg_save, p+2, strlen(msg_save) - (p+2)));
409                 strunzone(msg_save);
410                 msg_save = strzone(msg);
411                 p = p + 2;
412         }
413         msg = strcat(msg_save, "");
414         strunzone(msg_save);
415         return msg;
416 }
417
418 /*
419 =============
420 GetCvars
421 =============
422 Called with:
423   0:  sends the request
424   >0: receives a cvar from name=argv(f) value=argv(f+1)
425 */
426 void GetCvars_handleString(float f, .string field, string name)
427 {
428         if(f < 0)
429         {
430                 if(self.field)
431                         strunzone(self.field);
432         }
433         else if(f > 0)
434         {
435                 if(argv(f) == name)
436                 {
437                         if(self.field)
438                                 strunzone(self.field);
439                         self.field = strzone(argv(f + 1));
440                 }
441         }
442         else
443                 stuffcmd(self, strcat("sendcvar ", name, "\n"));
444 }
445 void GetCvars_handleFloat(float f, .float field, string name)
446 {
447         if(f < 0)
448         {
449         }
450         else if(f > 0)
451         {
452                 if(argv(f) == name)
453                         self.field = stof(argv(f + 1));
454         }
455         else
456                 stuffcmd(self, strcat("sendcvar ", name, "\n"));
457 }
458 void GetCvars(float f)
459 {
460         GetCvars_handleFloat(f, autoswitch, "cl_autoswitch");
461         GetCvars_handleFloat(f, cvar_cl_hidewaypoints, "cl_hidewaypoints");
462         GetCvars_handleFloat(f, cvar_cl_zoomfactor, "cl_zoomfactor");
463         GetCvars_handleFloat(f, cvar_cl_zoomspeed, "cl_zoomspeed");
464         GetCvars_handleFloat(f, cvar_cl_playerdetailreduction, "cl_playerdetailreduction");
465         GetCvars_handleFloat(f, cvar_cl_nogibs, "cl_nogibs");
466         GetCvars_handleFloat(f, cvar_scr_centertime, "scr_centertime");
467         GetCvars_handleFloat(f, cvar_cl_shownames, "cl_shownames");
468         GetCvars_handleString(f, cvar_g_nexuizversion, "g_nexuizversion");
469 }
470
471 float fexists(string f)
472 {
473         float fh;
474         fh = fopen(f, FILE_READ);
475         if(fh < 0)
476                 return FALSE;
477         fclose(fh);
478         return TRUE;
479 }
480
481 void backtrace(string msg)
482 {
483         float dev;
484         dev = cvar("developer");
485         cvar_set("developer", "1");
486         dprint("\n");
487         dprint("--- CUT HERE ---\nWARNING: ");
488         dprint(msg);
489         dprint("\n");
490         remove(world); // isn't there any better way to cause a backtrace?
491         dprint("\n--- CUT UNTIL HERE ---\n");
492         cvar_set("developer", ftos(dev));
493 }
494
495 void DistributeFragsAmongTeam(entity p, float targetteam, float factor)
496 {
497         float f;
498         float d;
499         float nTeam;
500         entity head;
501
502         if(!teams_matter)
503                 return;
504
505         //if(p.frags < 0)
506         //{
507         //      p.frags = 0; // do not harm the new team!
508         //      return; // won't distribute negative scores
509         //}
510
511         if(p.frags == -666)
512                 return;
513
514         f = ceil(factor * p.frags);
515         p.frags = p.frags - f;
516
517         nTeam = 0;
518         FOR_EACH_PLAYER(head)
519                 if(head != p)
520                         if(head.team == targetteam)
521                                 nTeam = nTeam + 1;
522
523         if(nTeam == 0)
524                 return;
525
526         FOR_EACH_PLAYER(head)
527                 if(head != p)
528                         if(head.team == targetteam)
529                         {
530                                 d = floor(f / nTeam);
531                                 head.frags = head.frags + d;
532                                 f = f - d;
533                                 nTeam = nTeam - 1;
534                         }
535
536         if(nTeam != 0)
537                 error("nPlayers in team changed!");
538         if(f != 0)
539                 error(strcat("There were ", ftos(f), " frags left. BAD!"));
540 }
541
542 string Team_ColorCode(float teamid)
543 {
544         if(teamid == COLOR_TEAM1)
545                 return "^1";
546         else if(teamid == COLOR_TEAM2)
547                 return "^4";
548         else if(teamid == COLOR_TEAM3)
549                 return "^6";
550         else if(teamid == COLOR_TEAM4)
551                 return "^3";
552         else
553                 return "^7";
554 }
555
556 /*
557 string decolorize(string s)
558 {
559         string out;
560         out = "";
561         while(s != "")
562         {
563                 float n;
564                 string ch1, ch2;
565                 n = 1;
566                 ch1 = substring(s, 0, 1);
567                 ch2 = substring(s, 1, 1);
568                 if(ch1 == "^")
569                 {
570                         n = 2;
571                         if(ch2 == "^")
572                                 out = strcat(out, "^^");
573                         else if(ch2 == "0")
574                                 out = strcat1(out);
575                         else if(ch2 == "1")
576                                 out = strcat1(out);
577                         else if(ch2 == "2")
578                                 out = strcat1(out);
579                         else if(ch2 == "3")
580                                 out = strcat1(out);
581                         else if(ch2 == "4")
582                                 out = strcat1(out);
583                         else if(ch2 == "5")
584                                 out = strcat1(out);
585                         else if(ch2 == "6")
586                                 out = strcat1(out);
587                         else if(ch2 == "7")
588                                 out = strcat1(out);
589                         else if(ch2 == "8")
590                                 out = strcat1(out);
591                         else if(ch2 == "9")
592                                 out = strcat1(out);
593                         else
594                         {
595                                 n = 1;
596                                 out = strcat(out, "^^");
597                         }
598                         s = substring(s, n, strlen(s) - n);
599                 }
600                 else
601                 {
602                         s = substring(s, 1, strlen(s) - 1);
603                         out = strcat(out, ch1);
604                 }
605         }
606         return out;
607 }
608 #define strdecolorize(s) decolorize(s)
609 #define strlennocol(s) strlen(decolorize(s))
610 */
611
612 #define CENTERPRIO_POINT 1
613 #define CENTERPRIO_SPAM 2
614 #define CENTERPRIO_REBALANCE 2
615 #define CENTERPRIO_VOTE 4
616 #define CENTERPRIO_NORMAL 5
617 #define CENTERPRIO_MAPVOTE 9
618 #define CENTERPRIO_ADMIN 99
619 .float centerprint_priority;
620 .float centerprint_expires;
621 void centerprint_atprio(entity e, float prio, string s)
622 {
623         if(intermission_running)
624                 if(prio < CENTERPRIO_MAPVOTE)
625                         return;
626         if(time > e.centerprint_expires)
627                 e.centerprint_priority = 0;
628         if(prio >= e.centerprint_priority)
629         {
630                 e.centerprint_priority = prio;
631                 e.centerprint_expires = time + e.cvar_scr_centertime;
632                 centerprint_builtin(e, s);
633         }
634 }
635 void centerprint_expire(entity e, float prio)
636 {
637         if(prio == e.centerprint_priority)
638         {
639                 e.centerprint_priority = 0;
640                 centerprint_builtin(e, "");
641         }
642 }
643 void centerprint(entity e, string s)
644 {
645         centerprint_atprio(e, CENTERPRIO_NORMAL, s);
646 }
647
648 void VoteNag();
649
650 // decolorizes and team colors the player name when needed
651 string playername(entity p)
652 {
653         string t;
654         if(teams_matter && !intermission_running && p.classname == "player")
655         {
656                 t = Team_ColorCode(p.team);
657                 return strcat(t, strdecolorize(p.netname));
658         }
659         else
660                 return p.netname;
661 }
662
663 // requires that m2>m1 in all coordinates, and that m4>m3
664 float(vector m1, vector m2, vector m3, vector m4) boxesoverlap = {return m2_x >= m3_x && m1_x <= m4_x && m2_y >= m3_y && m1_y <= m4_y && m2_z >= m3_z && m1_z <= m4_z;};
665
666 // requires the same, but is a stronger condition
667 float(vector smins, vector smaxs, vector bmins, vector bmaxs) boxinsidebox = {return smins_x >= bmins_x && smaxs_x <= bmaxs_x && smins_y >= bmins_y && smaxs_y <= bmaxs_y && smins_z >= bmins_z && smaxs_z <= bmaxs_z;};
668
669 float g_pickup_shells;
670 float g_pickup_shells_max;
671 float g_pickup_nails;
672 float g_pickup_nails_max;
673 float g_pickup_rockets;
674 float g_pickup_rockets_max;
675 float g_pickup_cells;
676 float g_pickup_cells_max;
677 float g_pickup_armorshard;
678 float g_pickup_armorshard_max;
679 float g_pickup_armor;
680 float g_pickup_armor_max;
681 float g_pickup_healthshard;
682 float g_pickup_healthshard_max;
683 float g_pickup_health;
684 float g_pickup_health_max;
685 float g_pickup_healthmega;
686 float g_pickup_healthmega_max;
687
688 void readlevelcvars(void)
689 {
690         g_pickup_shells                    = cvar("g_pickup_shells");
691         g_pickup_shells_max                = cvar("g_pickup_shells_max");
692         g_pickup_nails                     = cvar("g_pickup_nails");
693         g_pickup_nails_max                 = cvar("g_pickup_nails_max");
694         g_pickup_rockets                   = cvar("g_pickup_rockets");
695         g_pickup_rockets_max               = cvar("g_pickup_rockets_max");
696         g_pickup_cells                     = cvar("g_pickup_cells");
697         g_pickup_cells_max                 = cvar("g_pickup_cells_max");
698         g_pickup_armorshard                = cvar("g_pickup_armorshard");
699         g_pickup_armorshard_max            = cvar("g_pickup_armorshard_max");
700         g_pickup_armor                     = cvar("g_pickup_armor");
701         g_pickup_armor_max                 = cvar("g_pickup_armor_max");
702         g_pickup_healthshard               = cvar("g_pickup_healthshard");
703         g_pickup_healthshard_max           = cvar("g_pickup_healthshard_max");
704         g_pickup_health                    = cvar("g_pickup_health");
705         g_pickup_health_max                = cvar("g_pickup_health_max");
706         g_pickup_healthmega                = cvar("g_pickup_healthmega");
707         g_pickup_healthmega_max            = cvar("g_pickup_healthmega_max");
708 }
709
710 /*
711 // TODO sound pack system
712 string soundpack;
713
714 string precache_sound_builtin (string s) = #19;
715 void(entity e, float chan, string samp, float vol, float atten) sound_builtin = #8;
716 string precache_sound(string s)
717 {
718         return precache_sound_builtin(strcat(soundpack, s));
719 }
720 void play2(entity e, string filename)
721 {
722         stuffcmd(e, strcat("play2 ", soundpack, filename, "\n"));
723 }
724 void sound(entity e, float chan, string samp, float vol, float atten)
725 {
726         sound_builtin(e, chan, strcat(soundpack, samp), vol, atten);
727 }
728 */
729
730 string precache_sound (string s) = #19;
731 void(entity e, float chan, string samp, float vol, float atten) sound = #8;
732 void play2(entity e, string filename)
733 {
734         stuffcmd(e, strcat("play2 ", filename, "\n"));
735 }