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