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