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