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