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