]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_world.qc
remove all TEs that contain a permanent state.
[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;
1892
1893 void MapVote_ClearAllVotes()
1894 {
1895         FOR_EACH_CLIENT(other)
1896                 other.mapvote = 0;
1897 }
1898
1899 string MapVote_Suggest(string m)
1900 {
1901         float i;
1902         if(m == "")
1903                 return "That's not how to use this command.";
1904         if(!cvar("g_maplist_votable_suggestions"))
1905                 return "Suggestions are not accepted on this server.";
1906         if(mapvote_initialized)
1907                 return "Can't suggest - voting is already in progress!";
1908         m = MapInfo_FixName(m);
1909         if(!m)
1910                 return "The map you suggested is not available on this server.";
1911         if(!cvar("g_maplist_votable_override_mostrecent"))
1912                 if(Map_IsRecent(m))
1913                         return "This server does not allow for recent maps to be played again. Please be patient for some rounds.";
1914
1915         if(!MapInfo_CheckMap(m))
1916                 return "The map you suggested does not support the current game mode.";
1917         for(i = 0; i < mapvote_suggestion_ptr; ++i)
1918                 if(mapvote_suggestions[i] == m)
1919                         return "This map was already suggested.";
1920         if(mapvote_suggestion_ptr >= MAPVOTE_COUNT)
1921         {
1922                 i = floor(random() * mapvote_suggestion_ptr);
1923         }
1924         else
1925         {
1926                 i = mapvote_suggestion_ptr;
1927                 mapvote_suggestion_ptr += 1;
1928         }
1929         if(mapvote_suggestions[i] != "")
1930                 strunzone(mapvote_suggestions[i]);
1931         mapvote_suggestions[i] = strzone(m);
1932         if(cvar("sv_eventlog"))
1933                 GameLogEcho(strcat(":vote:suggested:", m, ":", ftos(self.playerid)));
1934         return strcat("Suggestion of ", m, " accepted.");
1935 }
1936
1937 void MapVote_AddVotable(string nextMap, float isSuggestion)
1938 {
1939         float j;
1940         if(nextMap == "")
1941                 return;
1942         for(j = 0; j < mapvote_count; ++j)
1943                 if(mapvote_maps[j] == nextMap)
1944                         return;
1945         if(strlen(nextMap) > mapvote_maxlen)
1946                 mapvote_maxlen = strlen(nextMap);
1947         mapvote_maps[mapvote_count] = strzone(nextMap);
1948         mapvote_maps_suggested[mapvote_count] = isSuggestion;
1949         mapvote_count += 1;
1950 }
1951
1952 void MapVote_Spawn();
1953 void MapVote_Init()
1954 {
1955         float i;
1956         float nmax, smax;
1957
1958         MapVote_ClearAllVotes();
1959
1960         mapvote_count = 0;
1961         mapvote_detail = !cvar("g_maplist_votable_nodetail");
1962         mapvote_abstain = cvar("g_maplist_votable_abstain");
1963
1964         if(mapvote_abstain)
1965                 nmax = min(MAPVOTE_COUNT - 1, cvar("g_maplist_votable"));
1966         else
1967                 nmax = min(MAPVOTE_COUNT, cvar("g_maplist_votable"));
1968         smax = min3(nmax, cvar("g_maplist_votable_suggestions"), mapvote_suggestion_ptr);
1969
1970         if(mapvote_suggestion_ptr)
1971                 for(i = 0; i < 100 && mapvote_count < smax; ++i)
1972                         MapVote_AddVotable(mapvote_suggestions[floor(random() * mapvote_suggestion_ptr)], TRUE);
1973
1974         for(i = 0; i < 100 && mapvote_count < nmax; ++i)
1975                 MapVote_AddVotable(GetNextMap(), FALSE);
1976
1977         if(mapvote_count == 0)
1978         {
1979                 bprint( "Maplist contains no single playable map!  Resetting it to default map list.\n" );
1980                 cvar_set("g_maplist", MapInfo_ListAllowedMaps(0, MAPINFO_FLAG_HIDDEN));
1981                 if(cvar("g_maplist_shuffle"))
1982                         ShuffleMaplist();
1983                 localcmd("\nmenu_cmd sync\n");
1984                 for(i = 0; i < 100 && mapvote_count < nmax; ++i)
1985                         MapVote_AddVotable(GetNextMap(), FALSE);
1986         }
1987
1988         mapvote_count_real = mapvote_count;
1989         if(mapvote_abstain)
1990                 MapVote_AddVotable("don't care", 0);
1991
1992         //dprint("mapvote count is ", ftos(mapvote_count), "\n");
1993
1994         mapvote_keeptwotime = time + cvar("g_maplist_votable_keeptwotime");
1995         mapvote_timeout = time + cvar("g_maplist_votable_timeout");
1996         if(mapvote_count_real < 3 || mapvote_keeptwotime <= time)
1997                 mapvote_keeptwotime = 0;
1998         mapvote_message = "Choose a map and press its key!";
1999
2000         mapvote_screenshot_dir = cvar_string("g_maplist_votable_screenshot_dir");
2001         if(mapvote_screenshot_dir == "")
2002                 mapvote_screenshot_dir = "maps";
2003         mapvote_screenshot_dir = strzone(mapvote_screenshot_dir);
2004
2005         MapVote_Spawn();
2006 }
2007
2008 void MapVote_SendPicture(float id)
2009 {
2010         msg_entity = self;
2011         WriteByte(MSG_ONE, SVC_TEMPENTITY);
2012         WriteByte(MSG_ONE, TE_CSQC_PICTURE);
2013         WriteByte(MSG_ONE, id);
2014         WritePicture(MSG_ONE, strcat(mapvote_screenshot_dir, "/", mapvote_maps[id]), 3072);
2015 }
2016
2017 float GameCommand_MapVote(string cmd)
2018 {
2019         if(!intermission_running)
2020                 return FALSE;
2021
2022         if(cmd == "mv_getpic")
2023         {
2024                 MapVote_SendPicture(stof(argv(1)));
2025                 return TRUE;
2026         }
2027
2028         return FALSE;
2029 }
2030
2031 float MapVote_GetMapMask()
2032 {
2033         float mask, i, power;
2034         mask = 0;
2035         for(i = 0, power = 1; i < mapvote_count; ++i, power *= 2)
2036                 if(mapvote_maps[i] != "")
2037                         mask |= power;
2038         return mask;
2039 }
2040
2041 entity mapvote_ent;
2042 float MapVote_SendEntity(entity to, float sf)
2043 {
2044         string mapfile, pakfile;
2045         float i, o;
2046
2047         WriteByte(MSG_ENTITY, ENT_CLIENT_MAPVOTE);
2048         WriteByte(MSG_ENTITY, sf);
2049
2050         if(sf & 1)
2051         {
2052                 // flag 1 == initialization
2053                 WriteString(MSG_ENTITY, mapvote_screenshot_dir);
2054                 WriteByte(MSG_ENTITY, mapvote_count);
2055                 WriteByte(MSG_ENTITY, mapvote_abstain);
2056                 WriteByte(MSG_ENTITY, mapvote_detail);
2057                 WriteCoord(MSG_ENTITY, mapvote_timeout);
2058                 if(mapvote_count <= 8)
2059                         WriteByte(MSG_ENTITY, MapVote_GetMapMask());
2060                 else
2061                         WriteShort(MSG_ENTITY, MapVote_GetMapMask());
2062                 for(i = 0; i < mapvote_count; ++i)
2063                         if(mapvote_maps[i] != "")
2064                         {
2065                                 WriteString(MSG_ENTITY, mapvote_maps[i]);
2066                                 mapfile = strcat(mapvote_screenshot_dir, "/", mapvote_maps[i]);
2067                                 pakfile = whichpack(strcat(mapfile, ".tga"));
2068                                 if(pakfile == "")
2069                                         pakfile = whichpack(strcat(mapfile, ".jpg"));
2070                                 if(pakfile == "")
2071                                         pakfile = whichpack(strcat(mapfile, ".png"));
2072                                 print("pakfile is ", pakfile, "\n");
2073                                 for(o = strstr(pakfile, "/", 0)+1; o > 0; o = strstr(pakfile, "/", 0)+1)
2074                                         pakfile = substring(pakfile, o, 999);
2075                                 WriteString(MSG_ENTITY, pakfile);
2076                         }
2077         }
2078
2079         if(sf & 2)
2080         {
2081                 // flag 2 == update of mask
2082                 if(mapvote_count <= 8)
2083                         WriteByte(MSG_ENTITY, MapVote_GetMapMask());
2084                 else
2085                         WriteShort(MSG_ENTITY, MapVote_GetMapMask());
2086         }
2087
2088         if(sf & 4)
2089         {
2090                 if(mapvote_detail)
2091                         for(i = 0; i < mapvote_count; ++i)
2092                                 if(mapvote_maps[i] != "")
2093                                         WriteByte(MSG_ENTITY, mapvote_votes[i]);
2094
2095                 WriteByte(MSG_ENTITY, to.mapvote);
2096         }
2097
2098         return TRUE;
2099 }
2100
2101 void MapVote_Spawn()
2102 {
2103         mapvote_ent = spawn();
2104         mapvote_ent.classname = "ent_client_mapvote";
2105         mapvote_ent.SendEntity = MapVote_SendEntity;
2106         setmodel(mapvote_ent, "null");
2107         mapvote_ent.effects = EF_NODEPTHTEST;
2108 }
2109
2110 void MapVote_TouchMask()
2111 {
2112         mapvote_ent.SendFlags |= 2;
2113 }
2114
2115 void MapVote_TouchVotes(entity voter)
2116 {
2117         mapvote_ent.SendFlags |= 4;
2118 }
2119
2120 float MapVote_Finished(float mappos)
2121 {
2122         string result;
2123         float i;
2124         float didntvote;
2125
2126         if(cvar("sv_eventlog"))
2127         {
2128                 result = strcat(":vote:finished:", mapvote_maps[mappos]);
2129                 result = strcat(result, ":", ftos(mapvote_votes[mappos]), "::");
2130                 didntvote = mapvote_voters;
2131                 for(i = 0; i < mapvote_count; ++i)
2132                         if(mapvote_maps[i] != "")
2133                         {
2134                                 didntvote -= mapvote_votes[i];
2135                                 if(i != mappos)
2136                                 {
2137                                         result = strcat(result, ":", mapvote_maps[i]);
2138                                         result = strcat(result, ":", ftos(mapvote_votes[i]));
2139                                 }
2140                         }
2141                 result = strcat(result, ":didn't vote:", ftos(didntvote));
2142
2143                 GameLogEcho(result);
2144                 if(mapvote_maps_suggested[mappos])
2145                         GameLogEcho(strcat(":vote:suggestion_accepted:", mapvote_maps[mappos]));
2146         }
2147
2148         FOR_EACH_REALCLIENT(other)
2149                 FixClientCvars(other);
2150
2151         Map_Goto_SetStr(mapvote_maps[mappos]);
2152         Map_Goto();
2153         alreadychangedlevel = TRUE;
2154         return TRUE;
2155 }
2156 void MapVote_CheckRules_1()
2157 {
2158         float i;
2159
2160         for(i = 0; i < mapvote_count; ++i) if(mapvote_maps[i] != "")
2161         {
2162                 //dprint("Map ", ftos(i), ": "); dprint(mapvote_maps[i], "\n");
2163                 mapvote_votes[i] = 0;
2164         }
2165
2166         mapvote_voters = 0;
2167         FOR_EACH_REALCLIENT(other)
2168         {
2169                 ++mapvote_voters;
2170                 if(other.mapvote)
2171                 {
2172                         i = other.mapvote - 1;
2173                         //dprint("Player ", other.netname, " vote = ", ftos(other.mapvote - 1), "\n");
2174                         mapvote_votes[i] = mapvote_votes[i] + 1;
2175                 }
2176         }
2177 }
2178
2179 float MapVote_CheckRules_2()
2180 {
2181         float i;
2182         float firstPlace, secondPlace;
2183         float firstPlaceVotes, secondPlaceVotes;
2184         float mapvote_voters_real;
2185         string result;
2186
2187         mapvote_voters_real = mapvote_voters;
2188         if(mapvote_abstain)
2189                 mapvote_voters_real -= mapvote_votes[mapvote_count - 1];
2190
2191         RandomSelection_Init();
2192         for(i = 0; i < mapvote_count_real; ++i) if(mapvote_maps[i] != "")
2193                 RandomSelection_Add(world, i, 1, mapvote_votes[i]);
2194         firstPlace = RandomSelection_chosen_float;
2195         firstPlaceVotes = RandomSelection_best_priority;
2196         //dprint("First place: ", ftos(firstPlace), "\n");
2197         //dprint("First place votes: ", ftos(firstPlaceVotes), "\n");
2198
2199         RandomSelection_Init();
2200         for(i = 0; i < mapvote_count_real; ++i) if(mapvote_maps[i] != "")
2201                 if(i != firstPlace)
2202                         RandomSelection_Add(world, i, 1, mapvote_votes[i]);
2203         secondPlace = RandomSelection_chosen_float;
2204         secondPlaceVotes = RandomSelection_best_priority;
2205         //dprint("Second place: ", ftos(secondPlace), "\n");
2206         //dprint("Second place votes: ", ftos(secondPlaceVotes), "\n");
2207
2208         if(firstPlace == -1)
2209                 error("No first place in map vote... WTF?");
2210
2211         if(secondPlace == -1 || time > mapvote_timeout || (mapvote_voters_real - firstPlaceVotes) < firstPlaceVotes)
2212                 return MapVote_Finished(firstPlace);
2213
2214         if(mapvote_keeptwotime)
2215                 if(time > mapvote_keeptwotime || (mapvote_voters_real - firstPlaceVotes - secondPlaceVotes) < secondPlaceVotes)
2216                 {
2217                         float didntvote;
2218                         MapVote_TouchMask();
2219                         mapvote_message = "Now decide between the TOP TWO!";
2220                         mapvote_keeptwotime = 0;
2221                         result = strcat(":vote:keeptwo:", mapvote_maps[firstPlace]);
2222                         result = strcat(result, ":", ftos(firstPlaceVotes));
2223                         result = strcat(result, ":", mapvote_maps[secondPlace]);
2224                         result = strcat(result, ":", ftos(secondPlaceVotes), "::");
2225                         didntvote = mapvote_voters;
2226                         for(i = 0; i < mapvote_count; ++i)
2227                                 if(mapvote_maps[i] != "")
2228                                 {
2229                                         didntvote -= mapvote_votes[i];
2230                                         if(i != firstPlace)
2231                                                 if(i != secondPlace)
2232                                                 {
2233                                                         result = strcat(result, ":", mapvote_maps[i]);
2234                                                         result = strcat(result, ":", ftos(mapvote_votes[i]));
2235                                                         if(i < mapvote_count_real)
2236                                                         {
2237                                                                 strunzone(mapvote_maps[i]);
2238                                                                 mapvote_maps[i] = "";
2239                                                         }
2240                                                 }
2241                                 }
2242                         result = strcat(result, ":didn't vote:", ftos(didntvote));
2243                         if(cvar("sv_eventlog"))
2244                                 GameLogEcho(result);
2245                 }
2246
2247         return FALSE;
2248 }
2249 void MapVote_Tick()
2250 {
2251         float keeptwo;
2252         float totalvotes;
2253
2254         keeptwo = mapvote_keeptwotime;
2255         MapVote_CheckRules_1(); // count
2256         if(MapVote_CheckRules_2()) // decide
2257                 return;
2258
2259         totalvotes = 0;
2260         FOR_EACH_REALCLIENT(other)
2261         {
2262                 // hide scoreboard again
2263                 if(other.health != 2342)
2264                 {
2265                         other.health = 2342;
2266                         other.impulse = 0;
2267                         if(clienttype(other) == CLIENTTYPE_REAL)
2268                         {
2269                                 msg_entity = other;
2270                                 WriteByte(MSG_ONE, SVC_FINALE);
2271                                 WriteString(MSG_ONE, "");
2272                         }
2273                 }
2274
2275                 // notify about keep-two
2276                 if(keeptwo != 0 && mapvote_keeptwotime == 0)
2277                         play2(other, "misc/invshot.wav");
2278
2279                 // clear possibly invalid votes
2280                 if(mapvote_maps[other.mapvote - 1] == "")
2281                         other.mapvote = 0;
2282                 // use impulses as new vote
2283                 if(other.impulse >= 1 && other.impulse <= mapvote_count)
2284                         if(mapvote_maps[other.impulse - 1] != "")
2285                         {
2286                                 other.mapvote = other.impulse;
2287                                 MapVote_TouchVotes(other);
2288                         }
2289                 other.impulse = 0;
2290
2291                 if(other.mapvote)
2292                         ++totalvotes;
2293         }
2294
2295         MapVote_CheckRules_1(); // just count
2296 }
2297 void MapVote_Start()
2298 {
2299         if(mapvote_run)
2300                 return;
2301
2302         MapInfo_Enumerate();
2303         if(MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), 0, (g_maplist_allow_hidden ? MAPINFO_FLAG_HIDDEN : 0), 1))
2304                 mapvote_run = TRUE;
2305 }
2306 void MapVote_Think()
2307 {
2308         if(!mapvote_run)
2309                 return;
2310
2311         if(alreadychangedlevel)
2312                 return;
2313
2314         if(time < mapvote_nextthink)
2315                 return;
2316         //dprint("tick\n");
2317
2318         mapvote_nextthink = time + 0.5;
2319
2320         if(!mapvote_initialized)
2321         {
2322                 mapvote_initialized = TRUE;
2323                 if(DoNextMapOverride())
2324                         return;
2325                 if(!cvar("g_maplist_votable") || player_count <= 0)
2326                 {
2327                         GotoNextMap();
2328                         return;
2329                 }
2330                 MapVote_Init();
2331         }
2332
2333         MapVote_Tick();
2334 };
2335
2336 string GotoMap(string m)
2337 {
2338         if(!MapInfo_CheckMap(m))
2339                 return "The map you chose is not available on this server.";
2340         cvar_set("nextmap", m);
2341         cvar_set("timelimit", "-1");
2342         if(mapvote_initialized || alreadychangedlevel)
2343         {
2344                 if(DoNextMapOverride())
2345                         return "Map switch initiated.";
2346                 else
2347                         return "Hm... no. For some reason I like THIS map more.";
2348         }
2349         else
2350                 return "Map switch will happen after scoreboard.";
2351 }
2352
2353
2354 void EndFrame()
2355 {
2356         FOR_EACH_REALCLIENT(self)
2357         {
2358                 if(self.classname == "spectator")
2359                 {
2360                         if(self.enemy.typehitsound)
2361                                 play2(self, "misc/typehit.wav");
2362                         else if(self.enemy.hitsound && self.cvar_cl_hitsound)
2363                                 play2(self, "misc/hit.wav");
2364                 }
2365                 else
2366                 {
2367                         if(self.typehitsound)
2368                                 play2(self, "misc/typehit.wav");
2369                         else if(self.hitsound && self.cvar_cl_hitsound)
2370                                 play2(self, "misc/hit.wav");
2371                 }
2372         }
2373         FOR_EACH_CLIENT(self)
2374         {
2375                 self.hitsound = FALSE;
2376                 self.typehitsound = FALSE;
2377         }
2378 }
2379
2380
2381 /*
2382  * RedirectionThink:
2383  * returns TRUE if redirecting
2384  */
2385 float redirection_timeout;
2386 float redirection_nextthink;
2387 float RedirectionThink()
2388 {
2389         float clients_found;
2390
2391         if(redirection_target == "")
2392                 return FALSE;
2393
2394         if(!redirection_timeout)
2395         {
2396                 cvar_set("sv_public", "-2");
2397                 redirection_timeout = time + 0.6; // this will only try twice... should be able to keep more clients
2398                 if(redirection_target == "self")
2399                         bprint("^3SERVER NOTICE:^7 restarting the server\n");
2400                 else
2401                         bprint("^3SERVER NOTICE:^7 redirecting everyone to ", redirection_target, "\n");
2402         }
2403
2404         if(time < redirection_nextthink)
2405                 return TRUE;
2406
2407         redirection_nextthink = time + 1;
2408
2409         clients_found = 0;
2410         FOR_EACH_REALCLIENT(self)
2411         {
2412                 print("Redirecting: sending connect command to ", self.netname, "\n");
2413                 if(redirection_target == "self")
2414                         stuffcmd(self, "\ndisconnect; reconnect\n");
2415                 else
2416                         stuffcmd(self, strcat("\ndisconnect; connect ", redirection_target, "\n"));
2417                 ++clients_found;
2418         }
2419
2420         print("Redirecting: ", ftos(clients_found), " clients left.\n");
2421
2422         if(time > redirection_timeout || clients_found == 0)
2423                 localcmd("\nwait; wait; wait; quit\n");
2424
2425         return TRUE;
2426 }
2427
2428 void RestoreGame()
2429 {
2430         // Loaded from a save game
2431         // some things then break, so let's work around them...
2432
2433         // Progs DB (capture records)
2434         if(sv_cheats)
2435                 ServerProgsDB = db_create();
2436         else
2437                 ServerProgsDB = db_load("server.db");
2438
2439         // Mapinfo
2440         MapInfo_Shutdown();
2441         MapInfo_Enumerate();
2442         MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), 0, (g_maplist_allow_hidden ? MAPINFO_FLAG_HIDDEN : 0), 1);
2443 }
2444
2445 void SV_Shutdown()
2446 {
2447         if(world_initialized > 0)
2448         {
2449                 world_initialized = 0;
2450                 print("Saving persistent data...\n");
2451                 Ban_SaveBans();
2452                 if(!sv_cheats)
2453                         db_save(ServerProgsDB, "server.db");
2454                 if(cvar("developer"))
2455                         db_save(TemporaryDB, "server-temp.db");
2456                 db_close(ServerProgsDB);
2457                 db_close(TemporaryDB);
2458                 print("done!\n");
2459                 // tell the bot system the game is ending now
2460                 bot_endgame();
2461
2462                 MapInfo_Shutdown();
2463         }
2464         else if(world_initialized == 0)
2465         {
2466                 print("NOTE: crashed before even initializing the world, not saving persistent data\n");
2467         }
2468 }