]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_world.qc
use the button macros everywhere
[divverent/nexuiz.git] / data / qcsrc / server / g_world.qc
1 float SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS = 1;
2 string redirection_target;
3 float world_initialized;
4
5 string GetMapname();
6 string GetGametype();
7 void GotoNextMap();
8 void ShuffleMaplist()
9 float() DoNextMapOverride;
10
11 .string fog;
12
13 void SetDefaultAlpha()
14 {
15         if(cvar("g_running_guns"))
16         {
17                 default_player_alpha = -1;
18                 default_weapon_alpha = +1;
19         }
20         else if(g_cloaked)
21         {
22                 default_player_alpha = cvar("g_balance_cloaked_alpha");
23                 default_weapon_alpha = default_player_alpha;
24         }
25         else
26         {
27                 default_player_alpha = cvar("g_player_alpha");
28                 if(default_player_alpha <= 0)
29                         default_player_alpha = 1;
30                 default_weapon_alpha = default_player_alpha;
31         }
32 }
33
34 void fteqcc_testbugs()
35 {
36         float a, b;
37
38         if(!cvar("developer_fteqccbugs"))
39                 return;
40
41         dprint("*** fteqcc test: checking for bugs...\n");
42
43         a = 1;
44         b = 5;
45         if(sqrt(a) - sqrt(b - a) == 0)
46                 dprint("*** fteqcc test: found same-function-twice bug\n");
47         else
48                 dprint("*** fteqcc test: same-function-twice bug got FINALLY FIXED! HOORAY!\n");
49
50         world.frags = -10;
51         world.enemy = world;
52         world.enemy.frags += 10;
53         if(world.frags > 0.2 || world.frags < -0.2) // don't error out if it's just roundoff errors
54                 dprint("*** fteqcc test: found += bug\n");
55         else
56                 dprint("*** fteqcc test: += bug got FINALLY FIXED! HOORAY!\n");
57         world.frags = 0;
58 }
59
60 /**
61  * Takes care of pausing and unpausing the game.
62  * Centerprints the information about an upcoming or active timeout to all active
63  * players. Also plays reminder sounds.
64  */
65 void timeoutHandler_Think() {
66         local string timeStr;
67         local entity plr;
68         if (timeoutStatus == 1) {
69                 if (remainingLeadTime > 0) {
70                         //centerprint the information to every player
71                         timeStr = getTimeoutText(0);
72                         FOR_EACH_REALCLIENT(plr) {
73                                 if(plr.classname == "player") {
74                                         centerprint_atprio(plr, CENTERPRIO_SPAM, timeStr);
75                                 }
76                         }
77                         remainingLeadTime -= 1;
78                         //think again in 1 second:
79                         self.nextthink = time + 1;
80                 }
81                 else {
82                         //now pause the game:
83                         timeoutStatus = 2;
84                         cvar_set("slowmo", ftos(TIMEOUT_SLOWMO_VALUE));
85                         //copy .v_angle to .lastV_angle for every player in order to fix their view during pause (see PlayerPreThink)
86                         FOR_EACH_REALPLAYER(plr) {
87                                 plr.lastV_angle = plr.v_angle;
88                         }
89                         self.nextthink = time;
90                 }
91         }
92         else if (timeoutStatus == 2) {
93                 if (remainingTimeoutTime > 0) {
94                         timeStr = getTimeoutText(0);
95                         FOR_EACH_REALCLIENT(plr) {
96                                 if(plr.classname == "player") {
97                                         centerprint_atprio(plr, CENTERPRIO_SPAM, timeStr);
98                                 }
99                         }
100                         if(remainingTimeoutTime == cvar("sv_timeout_resumetime")) { //play a warning sound when only <sv_timeout_resumetime> seconds are left
101                                 sound(world, CHAN_AUTO, "announcer/robotic/prepareforbattle.wav", 1, ATTN_NONE);
102                         }
103                         remainingTimeoutTime -= 1;
104                         self.nextthink = time + TIMEOUT_SLOWMO_VALUE;
105                 }
106                 else {
107                         //unpause the game again
108                         remainingTimeoutTime = timeoutStatus = 0;
109                         cvar_set("slowmo", ftos(orig_slowmo));
110                         //and unlock the fixed view again once there is no timeout active anymore
111                         FOR_EACH_REALPLAYER(plr) {
112                                 plr.fixangle = FALSE;
113                         }
114                         //get rid of the countdown message
115                         FOR_EACH_REALCLIENT(plr) {
116                                 if(plr.classname == "player") {
117                                         centerprint_atprio(plr, CENTERPRIO_SPAM, "");
118                                 }
119                         }
120                         remove(self);
121                         return;
122                 }
123                 
124         }
125         else if (timeoutStatus == 0) { //if a player called the resumegame command (which set timeoutStatus to 0 already)
126                 FOR_EACH_REALCLIENT(plr) {
127                         if(plr.classname == "player") {
128                                 centerprint_atprio(plr, CENTERPRIO_SPAM, "");
129                         }
130                 }
131                 remove(self);
132                 return;
133         }
134 }
135
136 float GotoFirstMap()
137 {
138         if(cvar("_sv_init"))
139         {
140                 cvar_set("_sv_init", "0");
141                 if(cvar("g_maplist_shuffle"))
142                         ShuffleMaplist();
143 #ifdef MAPINFO
144                 tokenizebyseparator(cvar_string("g_maplist"), " ");
145 #else
146                 tokenize(cvar_string("g_maplist"));
147                 if(argv(0) != GetMapname())
148 #endif
149                 {
150                         cvar_set("nextmap", argv(0));
151
152 #ifdef MAPINFO
153                         MapInfo_Enumerate();
154                         MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), 0);
155 #endif
156
157                         if(!DoNextMapOverride())
158                                 GotoNextMap();
159
160                         return 1;
161                 }
162         }
163         return 0;
164 }
165
166 float world_already_spawned;
167 void spawnfunc_worldspawn (void)
168 {
169         float fd, l;
170         string s;
171
172         dprint_load(); // load dprint status from cvar
173
174         if(world_already_spawned)
175                 error("world already spawned - you may have EXACTLY ONE worldspawn!");
176         world_already_spawned = TRUE;
177
178         if(GotoFirstMap())
179                 return;
180
181 #ifdef MAPINFO
182         MapInfo_LoadMapSettings(mapname);
183 #endif
184
185         if(sv_cheats)
186                 ServerProgsDB = db_create();
187         else
188                 ServerProgsDB = db_load("server.db");
189
190         /*
191         TODO sound pack system
192         // initialize sound pack system
193         soundpack = cvar_string("g_soundpack");
194         if(soundpack != "")
195                 soundpack = strcat(soundpack, "/");
196         soundpack = strzone(soundpack);
197         */
198
199         // 0 normal
200         lightstyle(0, "m");
201
202         // 1 FLICKER (first variety)
203         lightstyle(1, "mmnmmommommnonmmonqnmmo");
204
205         // 2 SLOW STRONG PULSE
206         lightstyle(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
207
208         // 3 CANDLE (first variety)
209         lightstyle(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
210
211         // 4 FAST STROBE
212         lightstyle(4, "mamamamamama");
213
214         // 5 GENTLE PULSE 1
215         lightstyle(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj");
216
217         // 6 FLICKER (second variety)
218         lightstyle(6, "nmonqnmomnmomomno");
219
220         // 7 CANDLE (second variety)
221         lightstyle(7, "mmmaaaabcdefgmmmmaaaammmaamm");
222
223         // 8 CANDLE (third variety)
224         lightstyle(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
225
226         // 9 SLOW STROBE (fourth variety)
227         lightstyle(9, "aaaaaaaazzzzzzzz");
228
229         // 10 FLUORESCENT FLICKER
230         lightstyle(10, "mmamammmmammamamaaamammma");
231
232         // 11 SLOW PULSE NOT FADE TO BLACK
233         lightstyle(11, "abcdefghijklmnopqrrqponmlkjihgfedcba");
234
235         // styles 32-62 are assigned by the spawnfunc_light program for switchable lights
236
237         // 63 testing
238         lightstyle(63, "a");
239
240         // for setting by mapinfo
241         q3acompat_machineshotgunswap = cvar("sv_q3acompat_machineshotgunswap");
242         cvar_set("sv_q3acompat_machineshotgunswap", "0");
243
244         player_count = 0;
245         lms_lowest_lives = 0;
246         lms_next_place = 0;
247
248         bot_waypoints_for_items = cvar("g_waypoints_for_items");
249         if(bot_waypoints_for_items == 1)
250                 if(self.spawnflags & SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS)
251                         bot_waypoints_for_items = 0;
252
253         if(cvar("g_campaign"))
254                 CampaignPreInit();
255
256         InitGameplayMode();
257         readlevelcvars();
258         precache();
259
260         WaypointSprite_Init();
261
262         //if (g_domination)
263         //      dom_init();
264
265         local entity head;
266         head = nextent(world);
267         maxclients = 0;
268         while(head)
269         {
270                 maxclients++;
271                 head = nextent(head);
272         }
273
274         GameLogInit(); // prepare everything
275         if(cvar("sv_eventlog"))
276         {
277                 s = strcat(cvar_string("sv_eventlog_files_counter"), ".");
278                 s = strcat(s, ftos(random()));
279 #ifdef MAPINFO
280                 GameLogEcho(strcat(":gamestart:", GetGametype(), "_", GetMapname(), ":", s), FALSE);
281 #else
282                 GameLogEcho(strcat(":gamestart:", GetMapname(), ":", s), FALSE);
283 #endif
284                 s = ":gameinfo:mutators:LIST";
285                 if(cvar("g_grappling_hook"))
286                         s = strcat(s, ":grappling_hook");
287                 if(!cvar("g_use_ammunition"))
288                         s = strcat(s, ":no_use_ammunition");
289                 if(!cvar("g_pickup_items"))
290                         s = strcat(s, ":no_pickup_items");
291                 if(cvar("g_instagib"))
292                         s = strcat(s, ":instagib");
293                 if(cvar("g_rocketarena"))
294                         s = strcat(s, ":rockerarena");
295                 if(cvar("g_nixnex"))
296                         s = strcat(s, ":nixnex");
297                 if(cvar("g_vampire"))
298                         s = strcat(s, ":vampire");
299                 if(cvar("g_laserguided_missile"))
300                         s = strcat(s, ":laserguided_missile");
301                 if(cvar("g_norecoil"))
302                         s = strcat(s, ":norecoil");
303                 if(cvar("g_midair"))
304                         s = strcat(s, ":midair");
305                 if(cvar("g_minstagib"))
306                         s = strcat(s, ":minstagib");
307                 GameLogEcho(s, FALSE);
308                 GameLogEcho(":gameinfo:end", FALSE);
309         }
310
311         cvar_set("nextmap", "");
312
313         SetDefaultAlpha();
314
315         if(cvar("g_campaign"))
316                 CampaignPostInit();
317
318         fteqcc_testbugs();
319
320         Ban_LoadBans();
321
322         //initialise globals related to sv_timeout
323         sys_ticrate = cvar("sys_ticrate");
324         orig_slowmo = cvar("slowmo");
325
326 #ifdef MAPINFO
327         MapInfo_Enumerate();
328         MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), 1);
329 #endif
330
331         //if tourney is used map starts in warmup mode. if this mode shall stay unlimited, reset timelimit, but save the original one
332         if(g_tourney && cvar("g_tourney_warmup_unlimited_time")) {
333                 timelimit_orig = cvar("timelimit");
334                 cvar_set("timelimit", "0");
335         }
336
337         fd = fopen(strcat("maps/", mapname, ".cfg"), FILE_READ);
338         if(fd != -1)
339         {
340                 while((s = fgets(fd)))
341                 {
342                         l = tokenize(s);
343                         if(l < 2)
344                                 continue;
345                         if(argv(0) != "fog")
346                                 continue;
347                         s = "";
348                         while(l >= 2)
349                         {
350                                 --l;
351                                 s = strcat(argv(l), " ", s);
352                         }
353                         s = substring(s, 0, strlen(s) - 1);
354                         print("Found ^1DEPRECATED^7 fog command in .cfg file: ", s, "\n");
355                         world.fog = strzone(s);
356                 }
357                 fclose(fd);
358         }
359
360         world_initialized = 1;
361 }
362
363 void spawnfunc_light (void)
364 {
365         //makestatic (self); // Who the f___ did that?
366         remove(self);
367 }
368
369 float( string pFilename ) TryFile =
370 {
371         local float lHandle;
372         dprint("TryFile(\"", pFilename, "\")\n");
373         lHandle = fopen( pFilename, FILE_READ );
374         if( lHandle != -1 ) {
375                 fclose( lHandle );
376                 return TRUE;
377         } else {
378                 return FALSE;
379         }
380 };
381
382 string GetGametype()
383 {
384         if (game == GAME_DEATHMATCH)
385                 return "dm";
386         else if (game == GAME_TEAM_DEATHMATCH)
387                 return "tdm";
388         else if (game == GAME_DOMINATION)
389                 return "dom";
390         else if (game == GAME_CTF)
391                 return "ctf";
392         else if (game == GAME_RUNEMATCH)
393                 return "rune";
394         else if (game == GAME_LMS)
395                 return "lms";
396         else if (game == GAME_KEYHUNT)
397                 return "kh";
398         else if (game == GAME_ONSLAUGHT)
399                 return "ons";
400         else if (game == GAME_ASSAULT)
401                 return "as";
402         return "dm";
403 }
404
405 float IsSameGametype(string mapcfgname)
406 {
407 #ifdef MAPINFO
408         return TRUE; // can't change game type by map name here
409 #else
410         string gt;
411         gt = GetGametype();
412         if(substring(mapcfgname, 0, strlen(gt) + 1) == strcat(gt, "_"))
413                 return TRUE;
414         return FALSE;
415 #endif
416 }
417
418 string getmapname_stored;
419 string GetMapname()
420 {
421 #ifdef MAPINFO
422         return mapname;
423 #else
424         if(getmapname_stored == "")
425                 getmapname_stored = strzone(strcat(GetGametype(), "_", mapname));
426         return getmapname_stored;
427 #endif
428 }
429
430 float Map_Count, Map_Current;
431 string Map_Current_Name;
432
433 // NOTE: this now expects the map list to be already tokenize()d and the count in Map_Count
434 float GetMaplistPosition()
435 {
436         float pos, idx;
437         string map;
438
439         map = GetMapname();
440         idx = cvar("g_maplist_index");
441
442         if(idx >= 0)
443                 if(idx < Map_Count)
444                         if(map == argv(idx))
445                                 return idx;
446
447         for(pos = 0; pos < Map_Count; ++pos)
448                 if(map == argv(pos))
449                         return pos;
450
451         // resume normal maplist rotation if current map is not in g_maplist
452         return idx;
453 }
454
455 float MapHasRightSize(string map)
456 {
457         float fh;
458         if(currentbots || cvar("bot_number") || player_count < cvar("minplayers"))
459         if(cvar("g_maplist_check_waypoints"))
460         {
461                 dprint("checkwp "); dprint(map);
462                 fh = fopen(strcat("maps/", map, ".waypoints"), FILE_READ);
463                 if(fh < 0)
464                 {
465                         dprint(": no waypoints\n");
466                         return FALSE;
467                 }
468                 dprint(": has waypoints\n");
469                 fclose(fh);
470         }
471
472         // open map size restriction file
473         dprint("opensize "); dprint(map);
474         fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ);
475         if(fh >= 0)
476         {
477                 float mapmin, mapmax;
478                 dprint(": ok, ");
479                 mapmin = stof(fgets(fh));
480                 mapmax = stof(fgets(fh));
481                 fclose(fh);
482                 if(player_count < mapmin)
483                 {
484                         dprint("not enough\n");
485                         return FALSE;
486                 }
487                 if(player_count > mapmax)
488                 {
489                         dprint("too many\n");
490                         return FALSE;
491                 }
492                 dprint("right size\n");
493                 return TRUE;
494         }
495         dprint(": not found\n");
496         return TRUE;
497 }
498
499 string Map_Filename(float position)
500 {
501 #ifdef MAPINFO
502         return strcat("maps/", argv(position), ".bsp");
503 #else
504         return strcat("maps/", argv(position), ".mapcfg");
505 #endif
506 }
507
508 string strwords(string s, float w)
509 {
510         float endpos;
511         for(endpos = 0; w && endpos >= 0; --w)
512                 endpos = strstrofs(s, " ", endpos + 1);
513         if(endpos < 0)
514                 return s;
515         else
516                 return substring(s, 0, endpos);
517 }
518
519 float strhasword(string s, string w)
520 {
521         return strstrofs(strcat(" ", s, " "), strcat(" ", w, " "), 0) >= 0;
522 }
523
524 void Map_MarkAsRecent(string m)
525 {
526         cvar_set("g_maplist_mostrecent", strwords(strcat(m, " ", cvar_string("g_maplist_mostrecent")), cvar("g_maplist_mostrecent_count")));
527 }
528
529 float Map_IsRecent(string m)
530 {
531         return strhasword(cvar_string("g_maplist_mostrecent"), m);
532 }
533
534 float(float position, float pass) Map_Check =
535 {
536         string filename;
537         string map_next;
538         map_next = argv(position);
539         if(pass <= 1)
540         {
541                 if(map_next == Map_Current_Name) // same map again in first pass?
542                         return 0;
543                 if(Map_IsRecent(map_next))
544                         return 0;
545         }
546         filename = Map_Filename(position);
547 #ifdef MAPINFO
548         if(MapInfo_CheckMap(map_next))
549 #else
550         if(TryFile(filename))
551 #endif
552         {
553                 if(pass == 2)
554                         return 1;
555                 if(MapHasRightSize(map_next))
556                         return 1;
557                 return 0;
558         }
559         else
560                 dprint( "Couldn't select '", filename, "'..\n" );
561
562         return 0;
563 }
564
565 void(string nextmapname) Map_Goto_SetStr =
566 {
567         if(getmapname_stored != "")
568                 strunzone(getmapname_stored);
569         if(nextmapname == "")
570                 getmapname_stored = "";
571         else
572                 getmapname_stored = strzone(nextmapname);
573 }
574
575 void(float position) Map_Goto_SetFloat =
576 {
577         cvar_set("g_maplist_index", ftos(position));
578         Map_Goto_SetStr(argv(position));
579 }
580
581 void() GameResetCfg =
582 {
583 #ifdef MAPINFO
584         // settings persist, except...
585         if(cvar("g_campaign"))
586                 localcmd("\nexec mutator_reset.cfg\n");
587         localcmd("\nsettemp_restore\n");
588 #else
589         // if an exit cfg is defined by exiting map, exec it.
590         string exit_cfg;
591         exit_cfg = cvar_string("exit_cfg");
592         if(exit_cfg != "")
593                 localcmd(strcat("exec \"", exit_cfg, "\"\n"));
594
595         localcmd("exec game_reset.cfg\n");
596 #endif
597 };
598
599 void() Map_Goto =
600 {
601         Map_MarkAsRecent(getmapname_stored);
602         GameResetCfg();
603 #ifdef MAPINFO
604         MapInfo_LoadMap(getmapname_stored);
605 #else
606         localcmd(strcat("exec \"maps/", getmapname_stored ,".mapcfg\"\n"));
607 #endif
608 }
609
610 // return codes of map selectors:
611 //   -1 = temporary failure (that is, try some method that is guaranteed to succeed)
612 //   -2 = permanent failure
613 float() MaplistMethod_Iterate = // usual method
614 {
615         float pass, i;
616
617         for(pass = 1; pass <= 2; ++pass)
618         {
619                 for(i = 1; i < Map_Count; ++i)
620                 {
621                         float mapindex;
622                         mapindex = mod(i + Map_Current, Map_Count);
623                         if(Map_Check(mapindex, pass))
624                                 return mapindex;
625                 }
626         }
627         return -1;
628 }
629
630 float() MaplistMethod_Repeat = // fallback method
631 {
632         if(Map_Check(Map_Current, 2))
633                 return Map_Current;
634         return -2;
635 }
636
637 float() MaplistMethod_Random = // random map selection
638 {
639         float i, imax;
640
641         imax = 42;
642
643         for(i = 0; i <= imax; ++i)
644         {
645                 float mapindex;
646                 mapindex = mod(Map_Current + ceil(random() * (Map_Count - 1)), Map_Count); // any OTHER map
647                 if(Map_Check(mapindex, 1))
648                         return mapindex;
649         }
650         return -1;
651 }
652
653 float(float exponent) MaplistMethod_Shuffle = // more clever shuffling
654 // the exponent sets a bias on the map selection:
655 // the higher the exponent, the less likely "shortly repeated" same maps are
656 {
657         float i, j, imax, insertpos;
658
659         imax = 42;
660
661         for(i = 0; i <= imax; ++i)
662         {
663                 string newlist;
664
665                 // now reinsert this at another position
666                 insertpos = pow(random(), 1 / exponent);       // ]0, 1]
667                 insertpos = insertpos * (Map_Count - 1);       // ]0, Map_Count - 1]
668                 insertpos = ceil(insertpos) + 1;               // {2, 3, 4, ..., Map_Count}
669                 dprint("SHUFFLE: insert pos = ", ftos(insertpos), "\n");
670
671                 // insert the current map there
672                 newlist = "";
673 #ifdef MAPINFO
674                 for(j = 1; j < insertpos; ++j)                 // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above
675                         newlist = strcat(newlist, " ", argv(j));
676                 newlist = strcat(newlist, " ", argv(0));       // now insert the just selected map
677                 for(j = insertpos; j < Map_Count; ++j)         // i == Map_Count: no loop, has just been inserted as last
678                         newlist = strcat(newlist, " ", argv(j));
679                 newlist = substring(newlist, 1, strlen(newlist) - 1);
680 #else
681                 for(j = 1; j < insertpos; ++j)                 // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above
682                         newlist = strcat(newlist, "'", argv(j), "'");
683                 newlist = strcat(newlist, "'", argv(0), "'");  // now insert the just selected map
684                 for(j = insertpos; j < Map_Count; ++j)         // i == Map_Count: no loop, has just been inserted as last
685                         newlist = strcat(newlist, "'", argv(j), "'");
686 #endif
687                 cvar_set("g_maplist", newlist);
688 #ifdef MAPINFO
689                 Map_Count = tokenizebyseparator(cvar_string("g_maplist"), " ");
690 #else
691                 Map_Count = tokenize(newlist);
692 #endif
693
694                 // NOTE: the selected map has just been inserted at (insertpos-1)th position
695                 Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working
696                 if(Map_Check(Map_Current, 1))
697                         return Map_Current;
698         }
699         return -1;
700 }
701
702 void() Maplist_Init =
703 {
704         string temp;
705         temp = cvar_string("g_maplist");
706 #ifdef MAPINFO
707         Map_Count = tokenizebyseparator(cvar_string("g_maplist"), " ");
708 #else
709         Map_Count = tokenize(temp);
710 #endif
711         if(Map_Count == 0)
712         {
713                 bprint( "Maplist is empty!  Resetting it to default map list.\n" );
714 #ifdef MAPINFO
715                 cvar_set("g_maplist", temp = MapInfo_ListAllowedMaps());
716                 localcmd("\nmenu_cmd sync\n");
717                 Map_Count = tokenizebyseparator(temp, " ");
718 #else
719                 cvar_set("g_maplist", temp = cvar_string("g_maplist_defaultlist"));
720                 Map_Count = tokenize(temp);
721 #endif
722         }
723         if(Map_Count == 0)
724                 error("empty maplist, cannot select a new map");
725         Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1);
726
727         Map_Current_Name = strzone(argv(Map_Current)); // will be automatically freed on exit thanks to DP
728         // this may or may not be correct, but who cares, in the worst case a map
729         // isn't chosen in the first pass that should have been
730 }
731
732 string() GetNextMap =
733 {
734         float nextMap;
735
736         Maplist_Init();
737         nextMap = -1;
738
739         if(nextMap == -1)
740                 if(cvar("g_maplist_shuffle") > 0)
741                         nextMap = MaplistMethod_Shuffle(cvar("g_maplist_shuffle") + 1);
742
743         if(nextMap == -1)
744                 if(cvar("g_maplist_selectrandom"))
745                         nextMap = MaplistMethod_Random();
746
747         if(nextMap == -1)
748                 nextMap = MaplistMethod_Iterate();
749
750         if(nextMap == -1)
751                 nextMap = MaplistMethod_Repeat();
752
753         if(nextMap >= 0)
754         {
755                 Map_Goto_SetFloat(nextMap);
756                 return getmapname_stored;
757         }
758
759         return "";
760 };
761
762 float() DoNextMapOverride =
763 {
764         if(cvar("g_campaign"))
765         {
766                 CampaignPostIntermission();
767                 alreadychangedlevel = TRUE;
768                 return TRUE;
769         }
770         if(cvar("quit_when_empty"))
771         {
772                 if(player_count <= currentbots)
773                 {
774                         localcmd("quit\n");
775                         alreadychangedlevel = TRUE;
776                         return TRUE;
777                 }
778         }
779         if(cvar_string("quit_and_redirect") != "")
780         {
781                 redirection_target = strzone(cvar_string("quit_and_redirect"));
782                 alreadychangedlevel = TRUE;
783                 return TRUE;
784         }
785         if (cvar("samelevel")) // if samelevel is set, stay on same level
786         {
787                 // this does not work because it tries to exec maps/nexdm01.mapcfg (which doesn't exist, it should be trying maps/dm_nexdm01.mapcfg for example)
788                 //localcmd(strcat("exec \"maps/", mapname, ".mapcfg\"\n"));
789                 // so instead just restart the current map using the restart command (DOES NOT WORK PROPERLY WITH exit_cfg STUFF)
790                 localcmd("restart\n");
791                 //changelevel (mapname);
792                 alreadychangedlevel = TRUE;
793                 return TRUE;
794         }
795         if(cvar_string("nextmap") != "")
796 #ifdef MAPINFO
797                 if(MapInfo_CheckMap(cvar_string("nextmap")))
798 #else
799                 if(TryFile(strcat("maps/", cvar_string("nextmap"), ".mapcfg")))
800 #endif
801                 {
802                         Map_Goto_SetStr(cvar_string("nextmap"));
803                         Map_Goto();
804                         alreadychangedlevel = TRUE;
805                         return TRUE;
806                 }
807         if(cvar("lastlevel"))
808         {
809                 GameResetCfg();
810                 localcmd("set lastlevel 0\ntogglemenu\n");
811                 alreadychangedlevel = TRUE;
812                 return TRUE;
813         }
814         return FALSE;
815 };
816
817 void() GotoNextMap =
818 {
819         //local string nextmap;
820         //local float n, nummaps;
821         //local string s;
822         if (alreadychangedlevel)
823                 return;
824         alreadychangedlevel = TRUE;
825
826         {
827                 string nextMap;
828                 float allowReset;
829
830                 for(allowReset = 1; allowReset >= 0; --allowReset)
831                 {
832                         nextMap = GetNextMap();
833                         if(nextMap != "")
834                                 break;
835
836                         if(allowReset)
837                         {
838                                 bprint( "Maplist contains no single playable map!  Resetting it to default map list.\n" );
839 #ifdef MAPINFO
840                                 cvar_set("g_maplist", MapInfo_ListAllowedMaps());
841                                 localcmd("\nmenu_cmd sync\n");
842 #else
843                                 cvar_set("g_maplist", cvar_string("g_maplist_defaultlist"));
844 #endif
845                         }
846                         else
847                         {
848                                 error("Everything is broken - not even the default map list works. Please report this to the developers.");
849                         }
850                 }
851                 Map_Goto();
852         }
853 };
854
855
856 /*
857 ============
858 IntermissionThink
859
860 When the player presses attack or jump, change to the next level
861 ============
862 */
863 .float autoscreenshot;
864 void() MapVote_Start;
865 void() MapVote_Think;
866 float mapvote_initialized;
867 void() IntermissionThink =
868 {
869         FixIntermissionClient(self);
870
871         if(cvar("sv_autoscreenshot"))
872         if(self.autoscreenshot > 0)
873         if(time > self.autoscreenshot)
874         {
875                 self.autoscreenshot = -1;
876                 if(clienttype(self) == CLIENTTYPE_REAL)
877                         stuffcmd(self, "\nscreenshot\necho \"^5A screenshot has been taken at request of the server.\"\n");
878                 return;
879         }
880
881         if (time < intermission_exittime)
882                 return;
883
884         if(!mapvote_initialized)
885                 if (time < intermission_exittime + 10 && !self.BUTTON_ATCK && !self.BUTTON_JUMP && !self.BUTTON_ATCK2 && !self.BUTTON_HOOK && !self.BUTTON_USE)
886                         return;
887
888         MapVote_Start();
889 };
890
891 /*
892 ============
893 FindIntermission
894
895 Returns the entity to view from
896 ============
897 */
898 /*
899 entity() FindIntermission =
900 {
901         local   entity spot;
902         local   float cyc;
903
904 // look for info_intermission first
905         spot = find (world, classname, "info_intermission");
906         if (spot)
907         {       // pick a random one
908                 cyc = random() * 4;
909                 while (cyc > 1)
910                 {
911                         spot = find (spot, classname, "info_intermission");
912                         if (!spot)
913                                 spot = find (spot, classname, "info_intermission");
914                         cyc = cyc - 1;
915                 }
916                 return spot;
917         }
918
919 // then look for the start position
920         spot = find (world, classname, "info_player_start");
921         if (spot)
922                 return spot;
923
924 // testinfo_player_start is only found in regioned levels
925         spot = find (world, classname, "testplayerstart");
926         if (spot)
927                 return spot;
928
929 // then look for the start position
930         spot = find (world, classname, "info_player_deathmatch");
931         if (spot)
932                 return spot;
933
934         //objerror ("FindIntermission: no spot");
935         return world;
936 };
937 */
938
939 /*
940 ===============================================================================
941
942 RULES
943
944 ===============================================================================
945 */
946
947 void(float final) DumpStats =
948 {
949         local float file;
950         local string s;
951         local float to_console;
952         local float to_eventlog;
953         local float to_file;
954
955         to_console = cvar("sv_logscores_console");
956         to_eventlog = cvar("sv_eventlog");
957         to_file = cvar("sv_logscores_file");
958
959         if(!final)
960         {
961                 to_console = TRUE; // always print printstats replies
962                 to_eventlog = FALSE; // but never print them to the event log
963         }
964
965         if(to_eventlog)
966                 if(cvar("sv_eventlog_console"))
967                         to_console = FALSE; // otherwise we get the output twice
968
969         if(final)
970                 s = ":scores:";
971         else
972                 s = ":status:";
973 #ifdef MAPINFO
974         s = strcat(s, GetGametype(), "_", GetMapname(), ":", ftos(rint(time)));
975 #else
976         s = strcat(s, GetMapname(), ":", ftos(rint(time)));
977 #endif
978
979         if(to_console)
980                 ServerConsoleEcho(s, FALSE);
981         if(to_eventlog)
982                 GameLogEcho(s, FALSE);
983         if(to_file)
984         {
985                 file = fopen(cvar_string("sv_logscores_filename"), FILE_APPEND);
986                 if(file == -1)
987                         to_file = FALSE;
988                 else
989                         fputs(file, strcat(s, "\n"));
990         }
991
992         FOR_EACH_CLIENT(other)
993         {
994                 if ((clienttype(other) == CLIENTTYPE_REAL) || (clienttype(other) == CLIENTTYPE_BOT && cvar("sv_logscores_bots")))
995                 {
996                         s = strcat(":player:", ftos(other.frags), ":");
997                         s = strcat(s, ftos(other.deaths), ":");
998                         s = strcat(s, ftos(rint(time - other.jointime)), ":");
999                         s = strcat(s, ftos(other.team), ":");
1000
1001                         if(to_console)
1002                                 ServerConsoleEcho(strcat(s, other.netname), TRUE);
1003                         if(to_eventlog)
1004                                 GameLogEcho(strcat(s, ftos(other.playerid), ":", other.netname), TRUE);
1005                         if(to_file)
1006                                 fputs(file, strcat(s, other.netname, "\n"));
1007                 }
1008         }
1009
1010         if(to_console)
1011                 ServerConsoleEcho(":end", FALSE);
1012         if(to_eventlog)
1013                 GameLogEcho(":end", FALSE);
1014         if(to_file)
1015         {
1016                 fputs(file, ":end\n");
1017                 fclose(file);
1018         }
1019 }
1020
1021 void FixIntermissionClient(entity e)
1022 {
1023         string s;
1024         if(!e.autoscreenshot) // initial call
1025         {
1026                 e.angles = e.v_angle;
1027                 e.angles_x = -e.angles_x;
1028                 e.autoscreenshot = time + 0.8;  // used for autoscreenshot
1029                 e.health = -2342;
1030                 // first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not)
1031                 e.solid = SOLID_NOT;
1032                 e.movetype = MOVETYPE_NONE;
1033                 e.takedamage = DAMAGE_NO;
1034                 if(e.weaponentity)
1035                         e.weaponentity.effects = EF_NODRAW;
1036                 if(clienttype(e) == CLIENTTYPE_REAL)
1037                 {
1038                         stuffcmd(e, "\nscr_printspeed 1000000\n");
1039                         s = cvar_string("sv_intermission_cdtrack");
1040                         if(s != "")
1041                                 stuffcmd(e, strcat("\ncd loop ", s, "\n"));
1042                         msg_entity = e;
1043                         WriteByte(MSG_ONE, SVC_INTERMISSION);
1044                 }
1045         }
1046
1047         //e.velocity = '0 0 0';
1048         //e.fixangle = TRUE;
1049
1050         // TODO halt weapon animation
1051 }
1052
1053
1054 /*
1055 go to the next level for deathmatch
1056 only called if a time or frag limit has expired
1057 */
1058 void() NextLevel =
1059 {
1060         float minTotalFrags;
1061         float maxTotalFrags;
1062         float score;
1063         float f;
1064
1065         gameover = TRUE;
1066
1067         intermission_running = 1;
1068
1069 // enforce a wait time before allowing changelevel
1070         if(player_count > 0)
1071                 intermission_exittime = time + cvar("sv_mapchange_delay");
1072         else
1073                 intermission_exittime = -1;
1074
1075         /*
1076         WriteByte (MSG_ALL, SVC_CDTRACK);
1077         WriteByte (MSG_ALL, 3);
1078         WriteByte (MSG_ALL, 3);
1079         // done in FixIntermission
1080         */
1081
1082         //pos = FindIntermission ();
1083
1084         VoteReset();
1085
1086         DumpStats(TRUE);
1087
1088         if(cvar("sv_eventlog"))
1089                 GameLogEcho(":gameover", FALSE);
1090
1091         GameLogClose();
1092
1093         FOR_EACH_CLIENT(other)
1094         {
1095                 FixIntermissionClient(other);
1096
1097                 if(other.winning)
1098                         bprint(other.netname, " ^7wins.\n");
1099         }
1100
1101         minTotalFrags = 0;
1102         maxTotalFrags = 0;
1103         FOR_EACH_PLAYER(other)
1104         {
1105                 if(maxTotalFrags < other.totalfrags)
1106                         maxTotalFrags = other.totalfrags;
1107                 if(minTotalFrags > other.totalfrags)
1108                         minTotalFrags = other.totalfrags;
1109         }
1110
1111         if(!currentbots)
1112         {
1113                 FOR_EACH_PLAYER(other)
1114                 {
1115                         score = (other.totalfrags - minTotalFrags) / max(maxTotalFrags - minTotalFrags, 1);
1116                         f = bound(0, other.play_time / max(time, 1), 1);
1117                         // store some statistics?
1118                 }
1119         }
1120
1121         if(cvar("g_campaign"))
1122                 CampaignPreIntermission();
1123
1124         // WriteByte (MSG_ALL, SVC_INTERMISSION);
1125 };
1126
1127 /*
1128 ============
1129 CheckRules_Player
1130
1131 Exit deathmatch games upon conditions
1132 ============
1133 */
1134 .float fogtime;
1135 void() CheckRules_Player =
1136 {
1137         if (gameover)   // someone else quit the game already
1138                 return;
1139
1140         if(self.deadflag == DEAD_NO)
1141                 self.play_time += frametime;
1142
1143         if(sv_foginterval)
1144         if(world.fog)
1145         if(time > self.fogtime)
1146         {
1147                 stuffcmd(self, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
1148                 self.fogtime = time + sv_foginterval;
1149         }
1150
1151         // fixme: don't check players; instead check spawnfunc_dom_team and spawnfunc_ctf_team entities
1152         //   (div0: and that in CheckRules_World please)
1153 };
1154
1155 float checkrules_oneminutewarning;
1156 float checkrules_leaderfrags;
1157 float tdm_max_score, tdm_old_score;
1158
1159 float checkrules_equality;
1160 float checkrules_overtimewarning;
1161 float checkrules_overtimeend;
1162
1163 void() InitiateOvertime =
1164 {
1165         if(!checkrules_overtimeend)
1166                 checkrules_overtimeend = time + 60 * cvar("timelimit_maxovertime");
1167 }
1168
1169 float WINNING_NO = 0; // no winner, but time limits may terminate the game
1170 float WINNING_YES = 1; // winner found
1171 float WINNING_NEVER = 2; // no winner, enter overtime if time limit is reached
1172 float WINNING_STARTOVERTIME = 3; // no winner, enter overtime NOW
1173
1174 float(float fraglimitreached, float equality) GetWinningCode =
1175 {
1176         if(equality)
1177                 if(fraglimitreached)
1178                         return WINNING_STARTOVERTIME;
1179                 else
1180                         return WINNING_NEVER;
1181         else
1182                 if(fraglimitreached)
1183                         return WINNING_YES;
1184                 else
1185                         return WINNING_NO;
1186 }
1187
1188 // set the .winning flag for exactly those players with a given field value
1189 void(.float field, float value) SetWinners =
1190 {
1191         entity head;
1192         FOR_EACH_PLAYER(head)
1193                 head.winning = (head.field == value);
1194 }
1195
1196 // set the .winning flag for those players with a given field value
1197 void(.float field, float value) AddWinners =
1198 {
1199         entity head;
1200         FOR_EACH_PLAYER(head)
1201                 if(head.field == value)
1202                         head.winning = 1;
1203 }
1204
1205 // clear frags for all players but the team given (when they ran out of spawnpoints)
1206 void(.float field, float value) ClearFragsForEveryoneBut =
1207 {
1208         entity head;
1209         FOR_EACH_PLAYER(head)
1210                 if(head.field != value)
1211                         head.frags = max(head.frags, 0);
1212 }
1213
1214 // clear the .winning flags
1215 void(void) ClearWinners =
1216 {
1217         entity head;
1218         FOR_EACH_PLAYER(head)
1219                 head.winning = 0;
1220 }
1221
1222 // Onslaught winning condition:
1223 // game terminates if only one team has a working generator (or none)
1224 float() WinningCondition_Onslaught =
1225 {
1226         entity head;
1227         local float t1, t2, t3, t4;
1228         // first check if the game has ended
1229         t1 = t2 = t3 = t4 = 0;
1230         head = find(world, classname, "onslaught_generator");
1231         while (head)
1232         {
1233                 if (head.health > 0)
1234                 {
1235                         if (head.team == COLOR_TEAM1) t1 = 1;
1236                         if (head.team == COLOR_TEAM2) t2 = 1;
1237                         if (head.team == COLOR_TEAM3) t3 = 1;
1238                         if (head.team == COLOR_TEAM4) t4 = 1;
1239                 }
1240                 head = find(head, classname, "onslaught_generator");
1241         }
1242         if (t1 + t2 + t3 + t4 < 2)
1243         {
1244                 // game over, only one team remains (or none)
1245                 ClearWinners();
1246                 if (t1) SetWinners(team, COLOR_TEAM1);
1247                 if (t2) SetWinners(team, COLOR_TEAM2);
1248                 if (t3) SetWinners(team, COLOR_TEAM3);
1249                 if (t4) SetWinners(team, COLOR_TEAM4);
1250                 dprint("Have a winner, ending game.\n");
1251                 return WINNING_YES;
1252         }
1253
1254         // Two or more teams remain
1255         return WINNING_NO;
1256 }
1257
1258 float() LMS_NewPlayerLives =
1259 {
1260         float fl;
1261         fl = cvar("fraglimit");
1262         if(fl == 0)
1263                 fl = 999;
1264
1265         // first player has left the game for dying too much? Nobody else can get in.
1266         if(lms_lowest_lives < 1)
1267                 return FALSE;
1268
1269         if(!cvar("g_lms_join_anytime"))
1270                 if(lms_lowest_lives < fl - cvar("g_lms_last_join"))
1271                         return FALSE;
1272
1273         return bound(1, lms_lowest_lives, fl);
1274 }
1275
1276 // Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives)
1277 // they win. Otherwise the defending team wins once the timelimit passes.
1278 void assault_new_round();
1279 float() WinningCondition_Assault =
1280 {
1281         local float status;
1282         status = WINNING_NO;
1283
1284         // as the timelimit has not yet passed just assume the defending team will win
1285         if(assault_attacker_team == COLOR_TEAM1)
1286         {
1287                 SetWinners(team, COLOR_TEAM2);
1288         }
1289         else
1290         {
1291                 SetWinners(team, COLOR_TEAM1);
1292         }
1293
1294         local entity ent;
1295         ent = find(world, classname, "target_assault_roundend");
1296         if(ent)
1297         {
1298                 if(ent.winning) // round end has been triggered by attacking team
1299                 {
1300                         SetWinners(team, assault_attacker_team);
1301                         if(assault_attacker_team == COLOR_TEAM1)
1302                         {
1303                                 team1_score = team1_score + 50;
1304                         }
1305                         else
1306                         {
1307                                 team2_score = team2_score + 50;
1308                         }
1309
1310                         if(ent.cnt == 1) // this was the second round
1311                         {
1312                                 status = WINNING_YES;
1313                         }
1314                         else
1315                         {
1316                                 cvar_set("timelimit", ftos((2*time)/60));
1317                                 assault_new_round();
1318                         }
1319                 }
1320         }
1321
1322         return status;
1323
1324 }
1325
1326
1327 // LMS winning condition: game terminates if and only if there's at most one
1328 // one player who's living lives. Top two scores being equal cancels the time
1329 // limit.
1330 float() WinningCondition_LMS =
1331 {
1332         entity head;
1333         float have_player;
1334         float have_players;
1335         float l;
1336
1337         have_player = FALSE;
1338         have_players = FALSE;
1339         l = LMS_NewPlayerLives();
1340
1341         head = find(world, classname, "player");
1342         if(head)
1343                 have_player = TRUE;
1344         head = find(head, classname, "player");
1345         if(head)
1346                 have_players = TRUE;
1347
1348         if(have_player)
1349         {
1350                 // we have at least one player
1351                 if(have_players)
1352                 {
1353                         // two or more active players - continue with the game
1354                 }
1355                 else
1356                 {
1357                         // exactly one player?
1358                         if(l)
1359                         {
1360                                 // but no game has taken place yet
1361                         }
1362                         else
1363                         {
1364                                 // a winner!
1365                                 ClearWinners(); SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
1366                                 dprint("Have a winner, ending game.\n");
1367                                 return WINNING_YES;
1368                         }
1369                 }
1370         }
1371         else
1372         {
1373                 // nobody is playing at all...
1374                 if(l)
1375                 {
1376                         // wait for players...
1377                 }
1378                 else
1379                 {
1380                         // SNAFU (maybe a draw game?)
1381                         ClearWinners();
1382                         dprint("No players, ending game.\n");
1383                         return WINNING_YES;
1384                 }
1385         }
1386
1387         // When we get here, we have at least two players who are actually LIVING,
1388         // or one player who is still waiting for a victim to join the server. Now
1389         // check if the top two players have equal score.
1390
1391         checkrules_leaderfrags = 0;
1392         checkrules_equality = FALSE;
1393         FOR_EACH_PLAYER(head)
1394         {
1395                 if(head.frags > checkrules_leaderfrags)
1396                 {
1397                         checkrules_leaderfrags = head.frags;
1398                         checkrules_equality = FALSE;
1399                 }
1400                 else if(head.frags > 0 && head.frags == checkrules_leaderfrags)
1401                         checkrules_equality = TRUE;
1402         }
1403
1404         SetWinners(frags, checkrules_leaderfrags);
1405
1406         // The top two players have the same amount of lives? No timelimit then,
1407         // enter overtime...
1408
1409         if(checkrules_equality)
1410                 return WINNING_NEVER;
1411
1412         // Top two have different scores? Way to go for our beloved TIMELIMIT!
1413         return WINNING_NO;
1414 }
1415
1416 // DM winning condition: game terminates if a player reached the fraglimit,
1417 // unless the first two players have the same score. The latter case also
1418 // breaks the time limit.
1419 float(float fraglimit) WinningCondition_MaxIndividualScore =
1420 {
1421         float checkrules_oldleaderfrags;
1422         entity head;
1423
1424         checkrules_oldleaderfrags = checkrules_leaderfrags;
1425         checkrules_leaderfrags = 0;
1426         checkrules_equality = FALSE;
1427         FOR_EACH_PLAYER(head)
1428         {
1429                 if(head.frags > checkrules_leaderfrags)
1430                 {
1431                         checkrules_leaderfrags = head.frags;
1432                         checkrules_equality = FALSE;
1433                 }
1434                 else if(head.frags > 0 && head.frags == checkrules_leaderfrags)
1435                         checkrules_equality = TRUE;
1436         }
1437
1438         if(checkrules_leaderfrags > 0)
1439                 SetWinners(frags, checkrules_leaderfrags);
1440         else
1441                 ClearWinners();
1442
1443         if (!g_runematch)
1444                 if (checkrules_leaderfrags != checkrules_oldleaderfrags)
1445                 {
1446                         if (checkrules_leaderfrags == fraglimit - 1)
1447                                 sound(world, CHAN_AUTO, "announcer/robotic/1fragleft.wav", 1, ATTN_NONE);
1448                         else if (checkrules_leaderfrags == fraglimit - 2)
1449                                 sound(world, CHAN_AUTO, "announcer/robotic/2fragsleft.wav", 1, ATTN_NONE);
1450                         else if (checkrules_leaderfrags == fraglimit - 3)
1451                                 sound(world, CHAN_AUTO, "announcer/robotic/3fragsleft.wav", 1, ATTN_NONE);
1452                 }
1453
1454         return GetWinningCode(fraglimit && checkrules_leaderfrags >= fraglimit, checkrules_equality);
1455 }
1456
1457 float(float fraglimit) WinningConditionBase_Teamplay =
1458 {
1459         tdm_old_score = tdm_max_score;
1460         tdm_max_score = max4(team1_score, team2_score, team3_score, team4_score);
1461
1462         checkrules_equality =
1463         (
1464                 (tdm_max_score > 0)
1465                 &&
1466                 (
1467                           (team1_score == tdm_max_score)
1468                         + (team2_score == tdm_max_score)
1469                         + (team3_score == tdm_max_score)
1470                         + (team4_score == tdm_max_score)
1471                         >= 2));
1472
1473         ClearWinners();
1474         if(tdm_max_score > 0)
1475         {
1476                 if(team1_score == tdm_max_score)
1477                         AddWinners(team, COLOR_TEAM1);
1478                 if(team2_score == tdm_max_score)
1479                         AddWinners(team, COLOR_TEAM2);
1480                 if(team3_score == tdm_max_score)
1481                         AddWinners(team, COLOR_TEAM3);
1482                 if(team4_score == tdm_max_score)
1483                         AddWinners(team, COLOR_TEAM4);
1484         }
1485
1486         if(!g_runematch && !g_domination)
1487                 if(tdm_max_score != tdm_old_score)
1488                 {
1489                         if(tdm_max_score == fraglimit - 1)
1490                                 sound(world, CHAN_AUTO, "announcer/robotic/1fragleft.wav", 1, ATTN_NONE);
1491                         else if(tdm_max_score == fraglimit - 2)
1492                                 sound(world, CHAN_AUTO, "announcer/robotic/2fragsleft.wav", 1, ATTN_NONE);
1493                         else if(tdm_max_score == fraglimit - 3)
1494                                 sound(world, CHAN_AUTO, "announcer/robotic/3fragsleft.wav", 1, ATTN_NONE);
1495                 }
1496
1497         return GetWinningCode(fraglimit && tdm_max_score >= fraglimit, checkrules_equality);
1498 }
1499
1500 // TDM winning condition: game terminates if a team's score sum reached the
1501 // fraglimit, unless the first two teams have the same total score. The latter
1502 // case also breaks the time limit.
1503 float(float fraglimit) WinningCondition_MaxTeamSum =
1504 {
1505         entity head;
1506
1507         team1_score = team2_score = team3_score = team4_score = 0;
1508
1509         FOR_EACH_PLAYER(head)
1510         {
1511                 if(head.team == COLOR_TEAM1)
1512                         team1_score += head.frags;
1513                 else if(head.team == COLOR_TEAM2)
1514                         team2_score += head.frags;
1515                 else if(head.team == COLOR_TEAM3)
1516                         team3_score += head.frags;
1517                 else if(head.team == COLOR_TEAM4)
1518                         team4_score += head.frags;
1519         }
1520
1521         return WinningConditionBase_Teamplay(fraglimit);
1522 }
1523
1524 // DOM/CTF winning condition: game terminates if the max of a team's players'
1525 // score reached the fraglimit, unless the first two teams have the same
1526 // maximum score. The latter case also breaks the time limit.
1527 float(float fraglimit) WinningCondition_MaxTeamMax =
1528 {
1529         entity head;
1530
1531         team1_score = team2_score = team3_score = team4_score = 0;
1532
1533         FOR_EACH_PLAYER(head)
1534         {
1535                 if(head.team == COLOR_TEAM1)
1536                 {
1537                         if(head.frags > team1_score)
1538                                 team1_score = head.frags;
1539                 }
1540                 else if(head.team == COLOR_TEAM2)
1541                 {
1542                         if(head.frags > team2_score)
1543                                 team2_score = head.frags;
1544                 }
1545                 else if(head.team == COLOR_TEAM3)
1546                 {
1547                         if(head.frags > team3_score)
1548                                 team3_score = head.frags;
1549                 }
1550                 else if(head.team == COLOR_TEAM4)
1551                 {
1552                         if(head.frags > team4_score)
1553                                 team4_score = head.frags;
1554                 }
1555         }
1556
1557         return WinningConditionBase_Teamplay(fraglimit);
1558 }
1559
1560 void print_to(entity e, string s)
1561 {
1562         if(e)
1563                 sprint(e, strcat(s, "\n"));
1564         else
1565                 ServerConsoleEcho(s, TRUE);
1566 }
1567
1568 void PrintScoreboardFor(entity e, string name, string colorcode, float whichteam)
1569 {
1570         entity head;
1571         float v;
1572         float teamvalue;
1573         float fragtotal;
1574         string s;
1575         float found;
1576         found = FALSE;
1577         teamvalue = 0;
1578         FOR_EACH_PLAYER(head)
1579         {
1580                 if(!whichteam || head.team == whichteam)
1581                 {
1582                         if(name != "")
1583                                 if(!found)
1584                                         print_to(e, strcat(" ", colorcode, name, ":"));
1585                         found = TRUE;
1586                         fragtotal = fragtotal + head.frags;
1587                         s = ftos(head.frags);
1588                         s = strcat(s, "/", ftos(head.deaths));
1589                         s = strcat(s, " @ ", ftos(head.ping));
1590                         if(clienttype(head) == CLIENTTYPE_BOT)
1591                                 s = strcat(s, "botms");
1592                         else
1593                                 s = strcat(s, "ms");
1594                         v = PlayerValue(head);
1595                         teamvalue += v;
1596                         s = strcat(s, " / ", ftos(v));
1597                         print_to(e, strcat("  ", colorcode, head.netname, colorcode, " (", s, ")"));
1598                 }
1599         }
1600         if(whichteam && found)
1601         {
1602                 s = ftos(fragtotal);
1603                 s = strcat(s, " / ", ftos(teamvalue));
1604                 print_to(e, strcat(colorcode, "  (total: ", s, ")"));
1605         }
1606 }
1607
1608 void PrintScoreboard(entity e)
1609 {
1610         print_to(e, strcat("Time:      ", ftos(time / 60)));
1611         print_to(e, strcat("Timelimit: ", ftos(cvar("timelimit"))));
1612         print_to(e, strcat("Fraglimit: ", ftos(cvar("fraglimit"))));
1613         print_to(e, "Scoreboard:");
1614         if(teams_matter)
1615         {
1616                 PrintScoreboardFor(e, "Red", "^1", COLOR_TEAM1);
1617                 PrintScoreboardFor(e, "Blue", "^4", COLOR_TEAM2);
1618                 PrintScoreboardFor(e, "Yellow", "^3", COLOR_TEAM3);
1619                 PrintScoreboardFor(e, "Pink", "^6", COLOR_TEAM4);
1620         }
1621         else
1622         {
1623                 PrintScoreboardFor(e, "", "^7", 0);
1624         }
1625         print_to(e, ".");
1626 }
1627
1628 void ShuffleMaplist()
1629 {
1630         string result;
1631         float start;
1632         float litems;
1633         float selected;
1634         float i;
1635
1636         result = cvar_string("g_maplist");
1637 #ifdef MAPINFO
1638         litems = tokenizebyseparator(result, " ");
1639 #else
1640         litems = tokenize(result);
1641 #endif
1642
1643         for(start = 0; start < litems - 1; ++start)
1644         {
1645                 result = "";
1646
1647                 // select a random item
1648                 selected = ceil(random() * (litems - start) + start) - 1;
1649
1650                 // shift this item to the place start
1651 #ifdef MAPINFO
1652                 for(i = 0; i < start; ++i)
1653                         result = strcat(result, " ", argv(i));
1654                 result = strcat(result, " ", argv(selected));
1655                 for(i = start; i < litems; ++i)
1656                         if(i != selected)
1657                                 result = strcat(result, " ", argv(i));
1658                 result = substring(result, 1, strlen(result) - 1);
1659
1660                 litems = tokenizebyseparator(result, " ");
1661 #else
1662                 for(i = 0; i < start; ++i)
1663                         result = strcat(result, "'", argv(i), "'");
1664                 result = strcat(result, "'", argv(selected), "'");
1665                 for(i = start; i < litems; ++i)
1666                         if(i != selected)
1667                                 result = strcat(result, "'", argv(i), "'");
1668
1669                 litems = tokenize(result);
1670 #endif
1671
1672                 //dprint(result, "\n");
1673         }
1674
1675         cvar_set("g_maplist", result);
1676 }
1677
1678 float WinningCondition_RanOutOfSpawns()
1679 {
1680         entity head;
1681
1682         if(!have_team_spawns)
1683                 return WINNING_NO;
1684
1685         if(!some_spawn_has_been_used)
1686                 return WINNING_NO;
1687
1688         team1_score = team2_score = team3_score = team4_score = 0;
1689
1690         FOR_EACH_PLAYER(head) if(head.deadflag == DEAD_NO)
1691         {
1692                 if(head.team == COLOR_TEAM1)
1693                         team1_score = 1;
1694                 else if(head.team == COLOR_TEAM2)
1695                         team2_score = 1;
1696                 else if(head.team == COLOR_TEAM3)
1697                         team3_score = 1;
1698                 else if(head.team == COLOR_TEAM4)
1699                         team4_score = 1;
1700         }
1701
1702         for(head = world; (head = find(head, classname, "info_player_deathmatch")) != world; )
1703         {
1704                 if(head.team == COLOR_TEAM1)
1705                         team1_score = 1;
1706                 else if(head.team == COLOR_TEAM2)
1707                         team2_score = 1;
1708                 else if(head.team == COLOR_TEAM3)
1709                         team3_score = 1;
1710                 else if(head.team == COLOR_TEAM4)
1711                         team4_score = 1;
1712         }
1713
1714         ClearWinners();
1715         if(team1_score + team2_score + team3_score + team4_score == 0)
1716         {
1717                 checkrules_equality = TRUE;
1718                 return WINNING_YES;
1719         }
1720         else if(team1_score + team2_score + team3_score + team4_score == 1)
1721         {
1722                 if(team1_score)
1723                 {
1724                         AddWinners(team, COLOR_TEAM1);
1725                         ClearFragsForEveryoneBut(team, COLOR_TEAM1);
1726                 }
1727                 if(team2_score)
1728                 {
1729                         AddWinners(team, COLOR_TEAM2);
1730                         ClearFragsForEveryoneBut(team, COLOR_TEAM2);
1731                 }
1732                 if(team3_score)
1733                 {
1734                         AddWinners(team, COLOR_TEAM3);
1735                         ClearFragsForEveryoneBut(team, COLOR_TEAM3);
1736                 }
1737                 if(team4_score)
1738                 {
1739                         AddWinners(team, COLOR_TEAM4);
1740                         ClearFragsForEveryoneBut(team, COLOR_TEAM4);
1741                 }
1742                 return WINNING_YES;
1743         }
1744         else
1745                 return WINNING_NO;
1746 }
1747
1748 /*
1749 ============
1750 CheckRules_World
1751
1752 Exit deathmatch games upon conditions
1753 ============
1754 */
1755 void() CheckRules_World =
1756 {
1757         local float status;
1758         local float timelimit;
1759         local float fraglimit;
1760
1761         VoteThink();
1762         MapVote_Think();
1763
1764         SetDefaultAlpha();
1765
1766         /*
1767         MapVote_Think should now do that part
1768         if (intermission_running)
1769                 if (time >= intermission_exittime + 60)
1770                 {
1771                         if(!DoNextMapOverride())
1772                                 GotoNextMap();
1773                         return;
1774                 }
1775         */
1776
1777         if (gameover)   // someone else quit the game already
1778         {
1779                 if(player_count == 0) // Nobody there? Then let's go to the next map
1780                         MapVote_Start();
1781                         // this will actually check the player count in the next frame
1782                         // again, but this shouldn't hurt
1783                 return;
1784         }
1785
1786         timelimit = cvar("timelimit") * 60;
1787         fraglimit = cvar("fraglimit");
1788
1789         if(checkrules_overtimeend)
1790         {
1791                 if(!checkrules_overtimewarning)
1792                 {
1793                         checkrules_overtimewarning = TRUE;
1794                         //sound(world, CHAN_AUTO, "announcer/robotic/1minuteremains.wav", 1, ATTN_NONE);
1795                         bcenterprint("^3Now playing ^1OVERTIME^3!\n\n^3Keep fragging until we have a ^1winner^3!");
1796                 }
1797         }
1798         else
1799         {
1800                 if (timelimit && time >= timelimit)
1801                         InitiateOvertime();
1802         }
1803
1804         if (checkrules_overtimeend && time >= checkrules_overtimeend)
1805         {
1806                 NextLevel();
1807                 return;
1808         }
1809
1810         if (!checkrules_oneminutewarning && timelimit > 0 && time > timelimit - 60)
1811         {
1812                 checkrules_oneminutewarning = TRUE;
1813                 sound(world, CHAN_AUTO, "announcer/robotic/1minuteremains.wav", 1, ATTN_NONE);
1814         }
1815
1816         status = WinningCondition_RanOutOfSpawns();
1817         if(status == WINNING_YES)
1818         {
1819                 bprint("Hey! Someone ran out of spawns!\n");
1820         }
1821         else if(g_assault)
1822         {
1823                 status = WinningCondition_Assault();
1824         }
1825         else if(g_lms)
1826         {
1827                 status = WinningCondition_LMS();
1828         }
1829         else if (g_onslaught)
1830         {
1831                 status = WinningCondition_Onslaught();
1832         }
1833         else
1834         {
1835                 if(teams_matter)
1836                 {
1837                         if(g_tdm || g_runematch || g_ctf || g_domination || g_keyhunt)
1838                                 status = WinningCondition_MaxTeamSum(fraglimit);
1839                         //else if()
1840                         //      status = WinningCondition_MaxTeamMax(fraglimit);
1841                         else
1842                         {
1843                                 dprint("div0: How can this happen?\n");
1844                                 status = WinningCondition_MaxTeamMax(fraglimit);
1845                         }
1846                 }
1847                 else
1848                         status = WinningCondition_MaxIndividualScore(fraglimit);
1849         }
1850
1851         if(status == WINNING_STARTOVERTIME)
1852         {
1853                 status = WINNING_NEVER;
1854                 InitiateOvertime();
1855         }
1856
1857         if(status == WINNING_NEVER)
1858                 // equality cases! Nobody wins if the overtime ends in a draw.
1859                 ClearWinners();
1860
1861         if(checkrules_overtimeend)
1862                 if(status != WINNING_NEVER || time >= checkrules_overtimeend)
1863                         status = WINNING_YES;
1864
1865         if(status == WINNING_YES)
1866                 NextLevel();
1867 };
1868
1869 float randsel_value;
1870 float randsel_priority;
1871 float randsel_count;
1872 void RandSel_Init()
1873 {
1874         randsel_value = -1;
1875         randsel_priority = -1;
1876         randsel_count = -1;
1877 }
1878 void RandSel_Add(float priority, float value)
1879 {
1880         if(priority > randsel_priority)
1881         {
1882                 randsel_priority = priority;
1883                 randsel_value = value;
1884                 randsel_count = 1;
1885         }
1886         else if(priority == randsel_priority)
1887         {
1888                 randsel_count += 1;
1889                 if(ceil(random() * randsel_count) == 1)
1890                         randsel_value = value;
1891         }
1892 }
1893
1894 float mapvote_nextthink;
1895 float mapvote_initialized;
1896 float mapvote_keeptwotime;
1897 float mapvote_timeout;
1898 string mapvote_message;
1899
1900 #define MAPVOTE_COUNT 10
1901 float mapvote_count;
1902 float mapvote_count_real;
1903 string mapvote_maps[MAPVOTE_COUNT];
1904 float mapvote_maps_suggested[MAPVOTE_COUNT];
1905 string mapvote_suggestions[MAPVOTE_COUNT];
1906 float mapvote_suggestion_ptr;
1907 float mapvote_maxlen;
1908 float mapvote_voters;
1909 float mapvote_votes[MAPVOTE_COUNT];
1910 float mapvote_run;
1911 float mapvote_detail;
1912 float mapvote_abstain;
1913 .float mapvote;
1914
1915 void MapVote_ClearAllVotes()
1916 {
1917         FOR_EACH_CLIENT(other)
1918                 other.mapvote = 0;
1919 }
1920
1921 string MapVote_Suggest(string m)
1922 {
1923         float i;
1924         if(m == "")
1925                 return "That's not how to use this command.";
1926         if(!cvar("g_maplist_votable_suggestions"))
1927                 return "Suggestions are not accepted on this server.";
1928         if(mapvote_initialized)
1929                 return "Can't suggest - voting is already in progress!";
1930 #ifdef MAPINFO
1931         m = MapInfo_FixName(m);
1932         if(!m)
1933                 return "The map you suggested is not available on this server.";
1934 #else
1935         if(!cvar("g_maplist_votable_suggestions_change_gametype"))
1936                 if(!IsSameGametype(m))
1937                         return "This server does not allow changing the game type by map suggestions.";
1938 #endif
1939         if(!cvar("g_maplist_votable_override_mostrecent"))
1940                 if(Map_IsRecent(m))
1941                         return "This server does not allow for recent maps to be played again. Please be patient for some rounds.";
1942
1943 #ifdef MAPINFO
1944         if(!MapInfo_CheckMap(m))
1945                 return "The map you suggested does not support the current game mode.";
1946 #else
1947         if(!TryFile(strcat("maps/", m, ".mapcfg")))
1948                 return "The map you suggested is not available on this server.";
1949 #endif
1950         for(i = 0; i < mapvote_suggestion_ptr; ++i)
1951                 if(mapvote_suggestions[i] == m)
1952                         return "This map was already suggested.";
1953         if(mapvote_suggestion_ptr >= MAPVOTE_COUNT)
1954         {
1955                 i = ceil(random() * mapvote_suggestion_ptr) - 1;
1956         }
1957         else
1958         {
1959                 i = mapvote_suggestion_ptr;
1960                 mapvote_suggestion_ptr += 1;
1961         }
1962         if(mapvote_suggestions[i] != "")
1963                 strunzone(mapvote_suggestions[i]);
1964         mapvote_suggestions[i] = strzone(m);
1965         if(cvar("sv_eventlog"))
1966                 GameLogEcho(strcat(":vote:suggested:", m, ":", ftos(self.playerid)), TRUE);
1967         return strcat("Suggestion of ", m, " accepted.");
1968 }
1969
1970 void MapVote_AddVotable(string nextMap, float isSuggestion)
1971 {
1972         float j;
1973         if(nextMap == "")
1974                 return;
1975         for(j = 0; j < mapvote_count; ++j)
1976                 if(mapvote_maps[j] == nextMap)
1977                         return;
1978         if(strlen(nextMap) > mapvote_maxlen)
1979                 mapvote_maxlen = strlen(nextMap);
1980         mapvote_maps[mapvote_count] = strzone(nextMap);
1981         mapvote_maps_suggested[mapvote_count] = isSuggestion;
1982         mapvote_count += 1;
1983 }
1984
1985 void MapVote_Init()
1986 {
1987         float i;
1988         float nmax, smax;
1989
1990         MapVote_ClearAllVotes();
1991
1992         mapvote_count = 0;
1993         mapvote_detail = !cvar("g_maplist_votable_nodetail");
1994         mapvote_abstain = cvar("g_maplist_votable_abstain");
1995
1996         if(mapvote_abstain)
1997                 nmax = min(MAPVOTE_COUNT - 1, cvar("g_maplist_votable"));
1998         else
1999                 nmax = min(MAPVOTE_COUNT, cvar("g_maplist_votable"));
2000         smax = min(nmax, cvar("g_maplist_votable_suggestions"));
2001
2002         for(i = 0; i < 100 && mapvote_count < smax; ++i)
2003                 MapVote_AddVotable(mapvote_suggestions[ceil(random() * mapvote_suggestion_ptr) - 1], TRUE);
2004
2005         for(i = 0; i < 100 && mapvote_count < nmax; ++i)
2006                 MapVote_AddVotable(GetNextMap(), FALSE);
2007
2008         if(mapvote_count == 0)
2009         {
2010                 bprint( "Maplist contains no single playable map!  Resetting it to default map list.\n" );
2011 #ifdef MAPINFO
2012                 cvar_set("g_maplist", MapInfo_ListAllowedMaps());
2013                 localcmd("\nmenu_cmd sync\n");
2014 #else
2015                 cvar_set("g_maplist", cvar_string("g_maplist_defaultlist"));
2016 #endif
2017                 for(i = 0; i < 100 && mapvote_count < nmax; ++i)
2018                         MapVote_AddVotable(GetNextMap(), FALSE);
2019         }
2020
2021         mapvote_count_real = mapvote_count;
2022         if(mapvote_abstain)
2023                 MapVote_AddVotable("don't care", 0);
2024
2025         //dprint("mapvote count is ", ftos(mapvote_count), "\n");
2026
2027         mapvote_keeptwotime = time + cvar("g_maplist_votable_keeptwotime");
2028         mapvote_timeout = time + cvar("g_maplist_votable_timeout");
2029         if(mapvote_count_real < 3 || mapvote_keeptwotime <= time)
2030                 mapvote_keeptwotime = 0;
2031         mapvote_message = "Choose a map and press its key!";
2032 }
2033 float MapVote_Finished(float mappos)
2034 {
2035         string result;
2036         float i;
2037         float didntvote;
2038
2039         if(cvar("sv_eventlog"))
2040         {
2041                 result = strcat(":vote:finished:", mapvote_maps[mappos]);
2042                 result = strcat(result, ":", ftos(mapvote_votes[mappos]), "::");
2043                 didntvote = mapvote_voters;
2044                 for(i = 0; i < mapvote_count; ++i)
2045                         if(mapvote_maps[i] != "")
2046                         {
2047                                 didntvote -= mapvote_votes[i];
2048                                 if(i != mappos)
2049                                 {
2050                                         result = strcat(result, ":", mapvote_maps[i]);
2051                                         result = strcat(result, ":", ftos(mapvote_votes[i]));
2052                                 }
2053                         }
2054                 result = strcat(result, ":didn't vote:", ftos(didntvote));
2055
2056                 GameLogEcho(result, FALSE);
2057                 if(mapvote_maps_suggested[mappos])
2058                         GameLogEcho(strcat(":vote:suggestion_accepted:", mapvote_maps[mappos]), FALSE);
2059         }
2060
2061         FOR_EACH_REALCLIENT(other)
2062                 FixClientCvars(other);
2063
2064         Map_Goto_SetStr(mapvote_maps[mappos]);
2065         Map_Goto();
2066         alreadychangedlevel = TRUE;
2067         return TRUE;
2068 }
2069 void MapVote_CheckRules_1()
2070 {
2071         float i;
2072
2073         for(i = 0; i < mapvote_count; ++i) if(mapvote_maps[i] != "")
2074         {
2075                 //dprint("Map ", ftos(i), ": "); dprint(mapvote_maps[i], "\n");
2076                 mapvote_votes[i] = 0;
2077         }
2078
2079         mapvote_voters = 0;
2080         FOR_EACH_REALCLIENT(other)
2081         {
2082                 ++mapvote_voters;
2083                 if(other.mapvote)
2084                 {
2085                         i = other.mapvote - 1;
2086                         //dprint("Player ", other.netname, " vote = ", ftos(other.mapvote - 1), "\n");
2087                         mapvote_votes[i] = mapvote_votes[i] + 1;
2088                 }
2089         }
2090 }
2091
2092 float MapVote_CheckRules_2()
2093 {
2094         float i;
2095         float firstPlace, secondPlace;
2096         float firstPlaceVotes, secondPlaceVotes;
2097         float mapvote_voters_real;
2098         string result;
2099
2100         mapvote_voters_real = mapvote_voters;
2101         if(mapvote_abstain)
2102                 mapvote_voters_real -= mapvote_votes[mapvote_count - 1];
2103
2104         RandSel_Init();
2105         for(i = 0; i < mapvote_count_real; ++i) if(mapvote_maps[i] != "")
2106                 RandSel_Add(mapvote_votes[i], i);
2107         firstPlace = randsel_value;
2108         firstPlaceVotes = randsel_priority;
2109         //dprint("First place: ", ftos(firstPlace), "\n");
2110         //dprint("First place votes: ", ftos(firstPlaceVotes), "\n");
2111
2112         RandSel_Init();
2113         for(i = 0; i < mapvote_count_real; ++i) if(mapvote_maps[i] != "")
2114                 if(i != firstPlace)
2115                         RandSel_Add(mapvote_votes[i], i);
2116         secondPlace = randsel_value;
2117         secondPlaceVotes = randsel_priority;
2118         //dprint("Second place: ", ftos(secondPlace), "\n");
2119         //dprint("Second place votes: ", ftos(secondPlaceVotes), "\n");
2120
2121         if(firstPlace == -1)
2122                 error("No first place in map vote... WTF?");
2123
2124         if(secondPlace == -1 || time > mapvote_timeout || (mapvote_voters_real - firstPlaceVotes) < firstPlaceVotes)
2125                 return MapVote_Finished(firstPlace);
2126
2127         if(mapvote_keeptwotime)
2128                 if(time > mapvote_keeptwotime || (mapvote_voters_real - firstPlaceVotes - secondPlaceVotes) < secondPlaceVotes)
2129                 {
2130                         float didntvote;
2131                         mapvote_message = "Now decide between the TOP TWO!";
2132                         mapvote_keeptwotime = 0;
2133                         result = strcat(":vote:keeptwo:", mapvote_maps[firstPlace]);
2134                         result = strcat(result, ":", ftos(firstPlaceVotes));
2135                         result = strcat(result, ":", mapvote_maps[secondPlace]);
2136                         result = strcat(result, ":", ftos(secondPlaceVotes), "::");
2137                         didntvote = mapvote_voters;
2138                         for(i = 0; i < mapvote_count; ++i)
2139                                 if(mapvote_maps[i] != "")
2140                                 {
2141                                         didntvote -= mapvote_votes[i];
2142                                         if(i != firstPlace)
2143                                                 if(i != secondPlace)
2144                                                 {
2145                                                         result = strcat(result, ":", mapvote_maps[i]);
2146                                                         result = strcat(result, ":", ftos(mapvote_votes[i]));
2147                                                         if(i < mapvote_count_real)
2148                                                         {
2149                                                                 strunzone(mapvote_maps[i]);
2150                                                                 mapvote_maps[i] = "";
2151                                                         }
2152                                                 }
2153                                 }
2154                         result = strcat(result, ":didn't vote:", ftos(didntvote));
2155                         if(cvar("sv_eventlog"))
2156                                 GameLogEcho(result, FALSE);
2157                 }
2158
2159         return FALSE;
2160 }
2161 void MapVote_Tick()
2162 {
2163         string msgstr;
2164         string tmp;
2165         float i;
2166         float keeptwo;
2167         float totalvotes;
2168
2169         keeptwo = mapvote_keeptwotime;
2170         MapVote_CheckRules_1(); // count
2171         if(MapVote_CheckRules_2()) // decide
2172                 return;
2173
2174         totalvotes = 0;
2175         FOR_EACH_REALCLIENT(other)
2176         {
2177                 // hide scoreboard again
2178                 if(other.health != 2342)
2179                 {
2180                         other.health = 2342;
2181                         other.impulse = 0;
2182                         if(clienttype(other) == CLIENTTYPE_REAL)
2183                         {
2184                                 stuffcmd(other, "\nin_bind 7 1 \"impulse 1\"; in_bind 7 2 \"impulse 2\"; in_bind 7 3 \"impulse 3\"; in_bind 7 4 \"impulse 4\"; in_bind 7 5 \"impulse 5\"; in_bind 7 6 \"impulse 6\"; in_bind 7 7 \"impulse 7\"; in_bind 7 8 \"impulse 8\"; in_bind 7 9 \"impulse 9\"; in_bind 7 0 \"impulse 10\"; in_bind 7 KP_1 \"impulse 1\"; in_bind 7 KP_2 \"impulse 2\"; in_bind 7 KP_3 \"impulse 3\"; in_bind 7 KP_4 \"impulse 4\"; in_bind 7 KP_5 \"impulse 5\"; in_bind 7 KP_6 \"impulse 6\"; in_bind 7 KP_7 \"impulse 7\"; in_bind 7 KP_8 \"impulse 8\"; in_bind 7 KP_9 \"impulse 9\"; in_bind 7 KP_0 \"impulse 10\"; in_bindmap 7 0\n");
2185                                 msg_entity = other;
2186                                 WriteByte(MSG_ONE, SVC_FINALE);
2187                                 WriteString(MSG_ONE, "");
2188                         }
2189                 }
2190
2191                 // notify about keep-two
2192                 if(keeptwo != 0 && mapvote_keeptwotime == 0)
2193                         play2(other, "misc/invshot.wav");
2194
2195                 // clear possibly invalid votes
2196                 if(mapvote_maps[other.mapvote - 1] == "")
2197                         other.mapvote = 0;
2198                 // use impulses as new vote
2199                 if(other.impulse >= 1 && other.impulse <= mapvote_count)
2200                         if(mapvote_maps[other.impulse - 1] != "")
2201                                 other.mapvote = other.impulse;
2202                 other.impulse = 0;
2203
2204                 if(other.mapvote)
2205                         ++totalvotes;
2206         }
2207
2208         MapVote_CheckRules_1(); // just count
2209
2210         FOR_EACH_REALCLIENT(other)
2211         {
2212                 // display voting screen
2213                 msgstr = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
2214                 msgstr = substring(msgstr, 0, strlen(msgstr) - mapvote_count);
2215                 if(mapvote_abstain)
2216                         msgstr = substring(msgstr, 1, strlen(msgstr) - 1);
2217                 msgstr = strcat(msgstr, mapvote_message);
2218                 msgstr = strcat(msgstr, "\n\n");
2219                 for(i = 0; i < mapvote_count; ++i)
2220                         if(mapvote_maps[i] == "")
2221                                 msgstr = strcat(msgstr, "\n");
2222                         else
2223                         {
2224                                 tmp = mapvote_maps[i];
2225                                 tmp = strpad(mapvote_maxlen, tmp);
2226                                 tmp = strcat(ftos(mod(i + 1, 10)), ": ", tmp);
2227                                 if(mapvote_detail)
2228                                 {
2229                                         tmp = strcat(tmp, " ^2(", ftos(mapvote_votes[i]), " vote");
2230                                         if(mapvote_votes[i] != 1)
2231                                                 tmp = strcat(tmp, "s");
2232                                         tmp = strcat(tmp, ")");
2233                                         tmp = strpad(mapvote_maxlen + 15, tmp);
2234                                 }
2235                                 if(mapvote_abstain)
2236                                         if(i == mapvote_count - 1)
2237                                                 msgstr = strcat(msgstr, "\n");
2238                                 if(other.mapvote == i + 1)
2239                                         msgstr = strcat(msgstr, "^3> ", tmp, "\n");
2240                                 else
2241                                         msgstr = strcat(msgstr, "^7  ", tmp, "\n");
2242                         }
2243
2244                 msgstr = strcat(msgstr, "\n\n^2", ftos(totalvotes), " vote");
2245                 if(totalvotes != 1)
2246                         msgstr = strcat(msgstr, "s");
2247                 msgstr = strcat(msgstr, " cast");
2248                 i = ceil(mapvote_timeout - time);
2249                 msgstr = strcat(msgstr, "\n", ftos(i), " second");
2250                 if(i != 1)
2251                         msgstr = strcat(msgstr, "s");
2252                 msgstr = strcat(msgstr, " left");
2253
2254                 centerprint_atprio(other, CENTERPRIO_MAPVOTE, msgstr);
2255         }
2256 }
2257 void MapVote_Start()
2258 {
2259         if(mapvote_run)
2260                 return;
2261
2262 #ifdef MAPINFO
2263         MapInfo_Enumerate();
2264         if(MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), 1))
2265 #endif
2266                 mapvote_run = TRUE;
2267 }
2268 void MapVote_Think()
2269 {
2270         if(!mapvote_run)
2271                 return;
2272
2273         if(alreadychangedlevel)
2274                 return;
2275
2276         if(time < mapvote_nextthink)
2277                 return;
2278         //dprint("tick\n");
2279
2280         mapvote_nextthink = time + 0.5;
2281
2282         if(!mapvote_initialized)
2283         {
2284                 mapvote_initialized = TRUE;
2285                 if(DoNextMapOverride())
2286                         return;
2287                 if(!cvar("g_maplist_votable") || player_count <= 0)
2288                 {
2289                         GotoNextMap();
2290                         return;
2291                 }
2292                 MapVote_Init();
2293         }
2294
2295         MapVote_Tick();
2296 };
2297
2298 string GotoMap(string m)
2299 {
2300 #ifdef MAPINFO
2301         if(!MapInfo_CheckMap(m))
2302 #else
2303         if(!TryFile(strcat("maps/", m, ".mapcfg")))
2304 #endif
2305                 return "The map you chose is not available on this server.";
2306         cvar_set("nextmap", m);
2307         cvar_set("timelimit", "-1");
2308         if(mapvote_initialized || alreadychangedlevel)
2309         {
2310                 if(DoNextMapOverride())
2311                         return "Map switch initiated.";
2312                 else
2313                         return "Hm... no. For some reason I like THIS map more.";
2314         }
2315         else
2316                 return "Map switch will happen after scoreboard.";
2317 }
2318
2319
2320 void EndFrame()
2321 {
2322         FOR_EACH_REALCLIENT(self)
2323         {
2324                 if(self.classname == "spectator")
2325                 {
2326                         if(self.enemy.hitsound)
2327                                 play2(self, "misc/hit.wav");
2328                 }
2329                 else
2330                 {
2331                         if(self.hitsound)
2332                                 play2(self, "misc/hit.wav");
2333                 }
2334         }
2335         FOR_EACH_CLIENT(self)
2336                 self.hitsound = FALSE;
2337 }
2338
2339
2340 /*
2341  * RedirectionThink:
2342  * returns TRUE if redirecting
2343  */
2344 float redirection_timeout;
2345 float redirection_nextthink;
2346 float RedirectionThink()
2347 {
2348         float clients_found;
2349
2350         if(redirection_target == "")
2351                 return FALSE;
2352
2353         if(!redirection_timeout)
2354         {
2355                 cvar_set("sv_public", "-2");
2356                 redirection_timeout = time + 0.6; // this will only try twice... should be able to keep more clients
2357                 if(redirection_target == "self")
2358                         bprint("^3SERVER NOTICE:^7 restarting the server\n");
2359                 else
2360                         bprint("^3SERVER NOTICE:^7 redirecting everyone to ", redirection_target, "\n");
2361         }
2362
2363         if(time < redirection_nextthink)
2364                 return TRUE;
2365
2366         redirection_nextthink = time + 1;
2367
2368         clients_found = 0;
2369         FOR_EACH_REALCLIENT(self)
2370         {
2371                 ServerConsoleEcho(strcat("Redirecting: sending connect command to ", self.netname), FALSE);
2372                 if(redirection_target == "self")
2373                         stuffcmd(self, "\ndisconnect; reconnect\n");
2374                 else
2375                         stuffcmd(self, strcat("\ndisconnect; connect ", redirection_target, "\n"));
2376                 ++clients_found;
2377         }
2378
2379         ServerConsoleEcho(strcat("Redirecting: ", ftos(clients_found), " clients left."), FALSE);
2380
2381         if(time > redirection_timeout || clients_found == 0)
2382                 localcmd("\nwait; wait; wait; quit\n");
2383
2384         return TRUE;
2385 }
2386
2387 void SV_Shutdown()
2388 {
2389         if(world_initialized)
2390         {
2391                 world_initialized = 0;
2392                 print("Saving persistent data...\n");
2393                 Ban_SaveBans();
2394                 if(!sv_cheats)
2395                         db_save(ServerProgsDB, "server.db");
2396                 print("done!\n");
2397                 // tell the bot system the game is ending now
2398                 bot_endgame();
2399         }
2400         else
2401         {
2402                 print("NOTE: crashed before even initializing the world, not saving persistent data\n");
2403         }
2404 }