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