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