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