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