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