]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_world.qc
only shuffle ONCE; add nexuiz-dinput.bat
[divverent/nexuiz.git] / data / qcsrc / server / g_world.qc
1 string GetMapname();
2 void GotoNextMap();
3 void HandleMaplistShuffleCommands();
4
5 void SetDefaultAlpha()
6 {
7         if(cvar("g_running_guns"))
8         {
9                 default_player_alpha = -1;
10                 default_weapon_alpha = +1;
11         }
12         else if(cvar("g_cloaked"))
13         {
14                 default_player_alpha = cvar("g_balance_cloaked_alpha");
15                 default_weapon_alpha = default_player_alpha;
16         }
17         else
18         {
19                 default_player_alpha = cvar("g_player_alpha");
20                 if(default_player_alpha <= 0)
21                         default_player_alpha = 1;
22                 default_weapon_alpha = default_player_alpha;
23         }
24 }
25
26 void fteqcc_testbugs()
27 {
28         float a, b;
29
30         if(!cvar("developer_fteqccbugs"))
31                 return;
32
33         dprint("*** fteqcc test: checking for bugs...\n");
34
35         a = 1;
36         b = 5;
37         if(sqrt(a) - sqrt(b - a) == 0)
38                 dprint("*** fteqcc test: found same-function-twice bug\n");
39         else
40                 dprint("*** fteqcc test: same-function-twice bug got FINALLY FIXED! HOORAY!\n");
41
42         world.frags = -10;
43         world.enemy = world;
44         world.enemy.frags += 10;
45         if(world.frags > 0.2 || world.frags < -0.2) // don't error out if it's just roundoff errors
46                 dprint("*** fteqcc test: found += bug\n");
47         else
48                 dprint("*** fteqcc test: += bug got FINALLY FIXED! HOORAY!\n");
49         world.frags = 0;
50 }
51
52 void GotoFirstMap()
53 {
54         if(cvar("_sv_init"))
55         {
56                 cvar_set("_sv_init", "0");
57                 HandleMaplistShuffleCommands();
58                 tokenize(cvar_string("g_maplist"));
59                 if(argv(0) != GetMapname())
60                 {
61                         cvar_set("nextmap", argv(0));
62                         GotoNextMap();
63                 }
64         }
65 }
66
67 float world_already_spawned;
68 void worldspawn (void)
69 {
70         if(world_already_spawned)
71                 error("world already spawned - you may have EXACTLY ONE worldspawn!");
72         world_already_spawned = TRUE;
73         // Precache all player models
74         // Workaround for "invisible players"
75         precache_model("models/player/carni.zym");
76         precache_model("models/player/crash.zym");
77         precache_model("models/player/grunt.zym");
78         precache_model("models/player/headhunter.zym");
79         precache_model("models/player/insurrectionist.zym");
80         precache_model("models/player/jeandarc.zym");
81         precache_model("models/player/lurk.zym");
82         precache_model("models/player/lycanthrope.zym");
83         precache_model("models/player/marine.zym");
84         precache_model("models/player/nexus.zym");
85         precache_model("models/player/pyria.zym");
86         precache_model("models/player/shock.zym");
87         precache_model("models/player/skadi.zym");
88         precache_model("models/player/specop.zym");
89         precache_model("models/player/visitant.zym");
90
91         //precache_model ("progs/beam.mdl");
92         precache_model ("models/bullet.mdl");
93         precache_model ("models/casing_bronze.mdl");
94         precache_model ("models/casing_shell.mdl");
95         precache_model ("models/casing_steel.mdl");
96         precache_model ("models/ebomb.mdl");
97         precache_model ("models/elaser.mdl");
98         precache_model ("models/flash.md3");
99         precache_model ("models/gibs/bloodyskull.md3");
100         precache_model ("models/gibs/chunk.mdl");
101         precache_model ("models/gibs/eye.md3");
102         precache_model ("models/gibs/gib1.md3");
103         //precache_model ("models/gibs/gib2.md3");
104         //precache_model ("models/gibs/gib3.md3");
105         //precache_model ("models/gibs/gib4.md3");
106         precache_model ("models/gibs/gib5.md3");
107         //precache_model ("models/gibs/gib6.md3");
108         precache_model ("models/gibs/gib1.mdl");
109         precache_model ("models/gibs/gib2.mdl");
110         precache_model ("models/gibs/gib3.mdl");
111         precache_model ("models/grenademodel.md3");
112         precache_model ("models/hagarmissile.mdl");
113         precache_model ("models/items/a_bullets.mdl");
114         precache_model ("models/items/a_cells.md3");
115         precache_model ("models/items/a_rockets.md3");
116         precache_model ("models/items/a_shells.md3");
117         precache_model ("models/items/g_a1.md3");
118         precache_model ("models/items/g_a25.md3");
119         precache_model ("models/items/g_h1.md3");
120         precache_model ("models/items/g_h25.md3");
121         precache_model ("models/items/g_h100.md3");
122         precache_model ("models/items/g_invincible.md3");
123         precache_model ("models/items/g_strength.md3");
124         precache_model ("models/laser.mdl");
125         precache_model ("models/misc/chatbubble.spr");
126         precache_model ("models/misc/teambubble.spr");
127         precache_model ("models/nexflash.md3");
128         precache_model ("models/plasma.mdl");
129         precache_model ("models/plasmatrail.mdl");
130         precache_model ("models/rocket.md3");
131         //precache_model ("models/sprites/grenexpl.spr");
132         precache_model ("models/runematch/rune.mdl");
133         precache_model ("models/runematch/curse.mdl");
134         //precache_model ("models/sprites/hagar.spr");
135         //precache_model ("models/sprites/muzzleflash.spr32");
136         //precache_model ("models/sprites/electrocombo.spr32");
137         //precache_model ("models/sprites/plasmahitwall.spr32");
138         //precache_model ("models/sprites/plasmashot.spr32");
139         //precache_model ("models/sprites/rockexpl.spr");
140         precache_model ("models/tracer.mdl");
141         precache_model ("models/uziflash.md3");
142         precache_model ("models/weapons/g_crylink.md3");
143         precache_model ("models/weapons/g_electro.md3");
144         precache_model ("models/weapons/g_gl.md3");
145         precache_model ("models/weapons/g_hagar.md3");
146         precache_model ("models/weapons/g_nex.md3");
147         precache_model ("models/weapons/g_rl.md3");
148         precache_model ("models/weapons/g_shotgun.md3");
149         precache_model ("models/weapons/g_uzi.md3");
150         precache_model ("models/weapons/v_crylink.md3");
151         precache_model ("models/weapons/v_electro.md3");
152         precache_model ("models/weapons/v_gl.md3");
153         precache_model ("models/weapons/v_hagar.md3");
154         precache_model ("models/weapons/v_laser.md3");
155         precache_model ("models/weapons/v_nex.md3");
156         precache_model ("models/weapons/v_rl.md3");
157         precache_model ("models/weapons/v_shotgun.md3");
158         precache_model ("models/weapons/v_uzi.md3");
159         precache_model ("models/weapons/w_crylink.zym");
160         precache_model ("models/weapons/w_electro.zym");
161         precache_model ("models/weapons/w_gl.zym");
162         precache_model ("models/weapons/w_hagar.zym");
163         precache_model ("models/weapons/w_laser.zym");
164         precache_model ("models/weapons/w_nex.zym");
165         precache_model ("models/weapons/w_rl.zym");
166         precache_model ("models/weapons/w_shotgun.zym");
167         precache_model ("models/weapons/w_uzi.zym");
168
169         // laser for laser-guided weapons
170         precache_model ("models/laser_dot.mdl");
171
172         precache_sound ("misc/null.wav");
173         precache_sound ("misc/armor1.wav");
174         precache_sound ("misc/armor25.wav");
175         precache_sound ("misc/armorimpact.wav");
176         precache_sound ("misc/bodyimpact1.wav");
177         precache_sound ("misc/bodyimpact2.wav");
178         precache_sound ("misc/gib.wav");
179         precache_sound ("misc/gib_splat01.wav");
180         precache_sound ("misc/gib_splat02.wav");
181         precache_sound ("misc/gib_splat03.wav");
182         precache_sound ("misc/gib_splat04.wav");
183         //precache_sound ("misc/h2ohit.wav");
184         precache_sound ("misc/hit.wav");
185         precache_sound ("misc/footstep01.wav");
186         precache_sound ("misc/footstep02.wav");
187         precache_sound ("misc/footstep03.wav");
188         precache_sound ("misc/footstep04.wav");
189         precache_sound ("misc/footstep05.wav");
190         precache_sound ("misc/footstep06.wav");
191         precache_sound ("misc/hitground1.ogg");
192         precache_sound ("misc/hitground2.ogg");
193         precache_sound ("misc/hitground3.ogg");
194         precache_sound ("misc/hitground4.ogg");
195         precache_sound ("misc/itempickup.ogg");
196         precache_sound ("misc/itemrespawn.ogg");
197         precache_sound ("misc/jumppad.ogg");
198         precache_sound ("misc/mediumhealth.ogg");
199         precache_sound ("misc/megahealth.ogg");
200         precache_sound ("misc/minihealth.ogg");
201         precache_sound ("misc/powerup.ogg");
202         precache_sound ("misc/powerup_shield.ogg");
203         precache_sound ("misc/talk.wav");
204         precache_sound ("misc/teleport.ogg");
205         precache_sound ("plats/medplat1.wav");
206         precache_sound ("plats/medplat2.wav");
207         precache_sound ("player/lava.wav");
208         precache_sound ("player/slime.wav");
209         precache_sound ("weapons/crylink_fire.ogg");
210         precache_sound ("weapons/electro_bounce.ogg");
211         precache_sound ("weapons/electro_fire.ogg");
212         precache_sound ("weapons/electro_fire2.ogg");
213         precache_sound ("weapons/electro_fly.wav");
214         precache_sound ("weapons/electro_impact.ogg");
215         precache_sound ("weapons/electro_impact_combo.ogg");
216         //precache_sound ("weapons/grenade_bounce.ogg");
217         precache_sound ("weapons/grenade_bounce1.ogg");
218         precache_sound ("weapons/grenade_bounce2.ogg");
219         precache_sound ("weapons/grenade_bounce3.ogg");
220         precache_sound ("weapons/grenade_fire.ogg");
221         precache_sound ("weapons/grenade_impact.ogg");
222         precache_sound ("weapons/hagar_fire.ogg");
223         precache_sound ("weapons/hagexp1.ogg");
224         precache_sound ("weapons/hagexp2.ogg");
225         precache_sound ("weapons/hagexp3.ogg");
226         precache_sound ("weapons/hook_fire.ogg");
227         precache_sound ("weapons/hook_impact.ogg");
228         precache_sound ("weapons/lasergun_fire.ogg");
229         precache_sound ("weapons/laserimpact.ogg");
230         precache_sound ("weapons/nexfire.ogg");
231         precache_sound ("weapons/neximpact.ogg");
232         precache_sound ("weapons/ric1.ogg");
233         precache_sound ("weapons/ric2.ogg");
234         precache_sound ("weapons/ric3.ogg");
235         precache_sound ("weapons/rocket_fire.ogg");
236         precache_sound ("weapons/rocket_fly.wav");
237         precache_sound ("weapons/rocket_impact.ogg");
238         precache_sound ("weapons/rocket_det.ogg");
239         precache_sound ("weapons/shotgun_fire.ogg");
240         precache_sound ("weapons/tink1.ogg");
241         precache_sound ("weapons/uzi_fire.ogg");
242         precache_sound ("weapons/weapon_switch.ogg");
243         precache_sound ("weapons/weaponpickup.ogg");
244         precache_sound ("weapons/strength_fire.ogg");
245
246         //precache_sound ("announce/male/kill10.ogg");
247         //precache_sound ("announce/male/kill15.ogg");
248         //precache_sound ("announce/male/kill20.ogg");
249         //precache_sound ("announce/male/kill25.ogg");
250         //precache_sound ("announce/male/kill3.ogg");
251         //precache_sound ("announce/male/kill30.ogg");
252         //precache_sound ("announce/male/kill4.ogg");
253         //precache_sound ("announce/male/kill5.ogg");
254         //precache_sound ("announce/male/kill6.ogg");
255         //precache_sound ("announce/male/mapkill1.ogg");
256         //precache_sound ("announce/robotic/last_second_save.ogg");
257         //precache_sound ("announce/robotic/narrowly_averted.ogg");
258         //precache_sound ("minstagib/mockery.ogg");
259
260         // announcer sounds - male
261         precache_sound ("announcer/male/03kills.ogg");
262         precache_sound ("announcer/male/05kills.ogg");
263         precache_sound ("announcer/male/10kills.ogg");
264         precache_sound ("announcer/male/15kills.ogg");
265         precache_sound ("announcer/male/20kills.ogg");
266         precache_sound ("announcer/male/25kills.ogg");
267         precache_sound ("announcer/male/30kills.ogg");
268         precache_sound ("announcer/male/botlike.ogg");
269         precache_sound ("announcer/male/electrobitch.ogg");
270         precache_sound ("announcer/male/welcome.ogg");
271         precache_sound ("announcer/male/yoda.ogg");
272
273         // announcer sounds - robotic
274         precache_sound ("announcer/robotic/1fragleft.ogg");
275         precache_sound ("announcer/robotic/1minuteremains.ogg");
276         precache_sound ("announcer/robotic/2fragsleft.ogg");
277         precache_sound ("announcer/robotic/3fragsleft.ogg");
278         precache_sound ("announcer/robotic/lastsecond.ogg");
279         precache_sound ("announcer/robotic/narrowly.ogg");
280         precache_sound ("announcer/robotic/1.ogg");
281         precache_sound ("announcer/robotic/2.ogg");
282         precache_sound ("announcer/robotic/3.ogg");
283         precache_sound ("announcer/robotic/4.ogg");
284         precache_sound ("announcer/robotic/5.ogg");
285         precache_sound ("announcer/robotic/6.ogg");
286         precache_sound ("announcer/robotic/7.ogg");
287         precache_sound ("announcer/robotic/8.ogg");
288         precache_sound ("announcer/robotic/9.ogg");
289         precache_sound ("announcer/robotic/10.ogg");
290
291         // plays music for the level if there is any
292         if (self.noise)
293         {
294                 precache_sound (self.noise);
295                 ambientsound ('0 0 0', self.noise, 1.00, ATTN_NONE);
296         }
297
298                 // 0 normal
299         lightstyle(0, "m");
300
301         // 1 FLICKER (first variety)
302         lightstyle(1, "mmnmmommommnonmmonqnmmo");
303
304         // 2 SLOW STRONG PULSE
305         lightstyle(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
306
307         // 3 CANDLE (first variety)
308         lightstyle(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
309
310         // 4 FAST STROBE
311         lightstyle(4, "mamamamamama");
312
313         // 5 GENTLE PULSE 1
314         lightstyle(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj");
315
316         // 6 FLICKER (second variety)
317         lightstyle(6, "nmonqnmomnmomomno");
318
319         // 7 CANDLE (second variety)
320         lightstyle(7, "mmmaaaabcdefgmmmmaaaammmaamm");
321
322         // 8 CANDLE (third variety)
323         lightstyle(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
324
325         // 9 SLOW STROBE (fourth variety)
326         lightstyle(9, "aaaaaaaazzzzzzzz");
327
328         // 10 FLUORESCENT FLICKER
329         lightstyle(10, "mmamammmmammamamaaamammma");
330
331         // 11 SLOW PULSE NOT FADE TO BLACK
332         lightstyle(11, "abcdefghijklmnopqrrqponmlkjihgfedcba");
333
334         // styles 32-62 are assigned by the light program for switchable lights
335
336         // 63 testing
337         lightstyle(63, "a");
338
339         registercvar("_g_maplist_shufflenow", "0");
340         registercvar("_g_maplist_have_shuffled", "0");
341
342         player_count = 0;
343         lms_lowest_lives = 0;
344         lms_next_place = 0;
345
346         GotoFirstMap();
347
348         if(cvar("g_campaign"))
349                 CampaignPreInit();
350
351         InitGameplayMode();
352         //if (cvar("g_domination"))
353         //      dom_init();
354
355         local entity head;
356         head = nextent(world);
357         maxclients = 0;
358         while(head)
359         {
360                 maxclients++;
361                 head = nextent(head);
362         }
363
364         GameLogInit(); // prepare everything
365         if(cvar("sv_eventlog"))
366         {
367                 local string s;
368                 GameLogEcho(":logversion:2", FALSE);
369                 s = strcat(cvar_string("sv_eventlog_files_counter"), ".");
370                 s = strcat(s, ftos(random()));
371                 GameLogEcho(strcat(":gamestart:", GetMapname(), ":", s), FALSE);
372                 s = ":gameinfo:mutators:LIST";
373                 if(cvar("g_grappling_hook"))
374                         s = strcat(s, ":grappling_hook");
375                 if(!cvar("g_use_ammunition"))
376                         s = strcat(s, ":no_use_ammunition");
377                 if(!cvar("g_pickup_items"))
378                         s = strcat(s, ":no_pickup_items");
379                 if(cvar("g_instagib"))
380                         s = strcat(s, ":instagib");
381                 if(cvar("g_rocketarena"))
382                         s = strcat(s, ":rockerarena");
383                 if(cvar("g_nixnex"))
384                         s = strcat(s, ":nixnex");
385                 if(cvar("g_vampire"))
386                         s = strcat(s, ":vampire");
387                 if(cvar("g_laserguided_missile"))
388                         s = strcat(s, ":laserguided_missile");
389                 if(cvar("g_norecoil"))
390                         s = strcat(s, ":norecoil");
391                 if(cvar("g_midair"))
392                         s = strcat(s, ":midair");
393                 if(cvar("g_minstagib"))
394                         s = strcat(s, ":minstagib");
395                 GameLogEcho(s, FALSE);
396                 GameLogEcho(":gameinfo:end", FALSE);
397         }
398
399         cvar_set("nextmap", "");
400
401         SetDefaultAlpha();
402
403         if(cvar("g_campaign"))
404                 CampaignPostInit();
405
406         // fteqcc_testbugs();
407 }
408
409 void light (void)
410 {
411         makestatic (self);
412 }
413
414 float( string pFilename ) TryFile =
415 {
416         local float lHandle;
417         dprint("TryFile(\"", pFilename, "\")\n");
418         lHandle = fopen( pFilename, FILE_READ );
419         if( lHandle != -1 ) {
420                 fclose( lHandle );
421                 return TRUE;
422         } else {
423                 return FALSE;
424         }
425 };
426
427 string GetGametype()
428 {
429         if (game == GAME_DEATHMATCH)
430                 return "dm";
431         else if (game == GAME_TEAM_DEATHMATCH)
432                 return "tdm";
433         else if (game == GAME_DOMINATION)
434                 return "dom";
435         else if (game == GAME_CTF)
436                 return "ctf";
437         else if (game == GAME_RUNEMATCH)
438                 return "rune";
439         else if (game == GAME_LMS)
440                 return "lms";
441         return "dm";
442 }
443
444 string GetMapname()
445 {
446         return strcat(GetGametype(), "_", mapname);
447 }
448
449 float Map_Count, Map_Current;
450 string Map_Current_Name;
451
452 // NOTE: this now expects the map list to be already tokenize()d and the count in Map_Count
453 float GetMaplistPosition()
454 {
455         float pos;
456         string map;
457
458         map = GetMapname();
459         for(pos = 0; pos < Map_Count; ++pos)
460                 if(map == argv(pos))
461                         return pos;
462
463         // resume normal maplist rotation if current map is not in g_maplist
464         return cvar("g_maplist_index");
465 }
466
467 float MapHasRightSize(string map)
468 {
469         // open map size restriction file
470         float fh;
471         dprint("opensize "); dprint(map);
472         fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ);
473         if(fh >= 0)
474         {
475                 float mapmin, mapmax;
476                 dprint(": ok, ");
477                 mapmin = stof(fgets(fh));
478                 mapmax = stof(fgets(fh));
479                 fclose(fh);
480                 if(player_count < mapmin)
481                 {
482                         dprint("not enough\n");
483                         return FALSE;
484                 }
485                 if(player_count > mapmax)
486                 {
487                         dprint("too many\n");
488                         return FALSE;
489                 }
490                 dprint("right size\n");
491                 return TRUE;
492         }
493         dprint(": not found\n");
494         return TRUE;
495 }
496
497 string Map_Filename(float position)
498 {
499         return strcat("maps/", argv(position), ".mapcfg");
500 }
501
502 float(float position, float pass) Map_Check =
503 {
504         string filename;
505         string map_next;
506         map_next = argv(position);
507         if(pass <= 1)
508                 if(map_next == Map_Current_Name) // same map again in first pass?
509                         return 0;
510         filename = Map_Filename(position);
511         if(TryFile(filename))
512         {
513                 if(pass == 2)
514                         return 1;
515                 if(MapHasRightSize(argv(position)))
516                         return 1;
517                 return 0;
518         }
519         else
520                 dprint( "Couldn't find '", filename, "'..\n" );
521
522         return 0;
523 }
524
525 void(float position) Map_Goto =
526 {
527         cvar_set("g_maplist_index", ftos(position));
528         localcmd(strcat("exec \"", Map_Filename(position) ,"\"\n"));
529 }
530
531 // return codes of map selectors:
532 //   -1 = temporary failure (that is, try some method that is guaranteed to succeed)
533 //   -2 = permanent failure
534 float() MaplistMethod_Iterate = // usual method
535 {
536         float pass, i;
537
538         for(pass = 1; pass <= 2; ++pass)
539         {
540                 for(i = 1; i < Map_Count; ++i)
541                 {
542                         float mapindex;
543                         mapindex = math_mod(i + Map_Current, Map_Count);
544                         if(Map_Check(mapindex, pass))
545                                 return mapindex;
546                 }
547         }
548         return -1;
549 }
550
551 float() MaplistMethod_Repeat = // fallback method
552 {
553         if(Map_Check(Map_Current, 2))
554                 return Map_Current;
555         return -2;
556 }
557
558 float() MaplistMethod_Random = // random map selection
559 {
560         float i, imax;
561
562         imax = 42;
563
564         for(i = 0; i <= imax; ++i)
565         {
566                 float mapindex;
567                 mapindex = math_mod(Map_Current + ceil(random() * (Map_Count - 1)), Map_Count); // any OTHER map
568                 if(Map_Check(mapindex, 1))
569                         return mapindex;
570         }
571         return -1;
572 }
573
574 float(float exponent) MaplistMethod_Shuffle = // more clever shuffling
575 // the exponent sets a bias on the map selection:
576 // the higher the exponent, the 
577 {
578         float i, j, imax, insertpos;
579
580         imax = 42;
581
582         if(Map_Count <= 1)
583                 return 0; // only one map, then always play this one
584
585         for(i = 0; i <= imax; ++i)
586         {
587                 string newlist;
588
589                 // now reinsert this at another position
590                 insertpos = pow(random(), 1 / exponent);       // ]0, 1]
591                 insertpos = insertpos * (Map_Count - 1);       // ]0, Map_Count - 1]
592                 insertpos = ceil(insertpos) + 1;               // {2, 3, 4, ..., Map_Count}
593                 dprint("SHUFFLE: insert pos = ", ftos(insertpos), "\n");
594
595                 // insert the current map there
596                 newlist = "";
597                 for(j = 1; j < insertpos; ++j)                 // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above
598                         newlist = strcat(newlist, "'", argv(j), "'");
599                 newlist = strcat(newlist, "'", argv(0), "'");  // now insert the just selected map
600                 for(j = insertpos; j < Map_Count; ++j)         // i == Map_Count: no loop, has just been inserted as last
601                         newlist = strcat(newlist, "'", argv(j), "'");
602                 cvar_set("g_maplist", newlist);
603                 Map_Count = tokenize(newlist);
604
605                 // NOTE: the selected map has just been inserted at (insertpos-1)th position
606                 Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working
607                 if(Map_Check(Map_Current, 1))
608                         return Map_Current;
609         }
610         return -1;
611 }
612
613 void() Maplist_Init =
614 {
615         string temp;
616         temp = cvar_string("g_maplist");
617         Map_Count = tokenize(temp);
618         if(Map_Count == 0)
619         {
620                 bprint( "Maplist is empty!  Resetting it to default map list.\n" );
621                 cvar_set("g_maplist", temp = cvar_string("g_maplist_defaultlist"));
622                 Map_Count = tokenize(temp);
623         }
624         if(Map_Count == 0)
625                 error("empty maplist, cannot select a new map");
626         Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1);
627
628         Map_Current_Name = strzone(argv(Map_Current)); // will be automatically freed on exit thanks to DP
629         // this may or may not be correct, but who cares, in the worst case a map
630         // isn't chosen in the first pass that should have been
631 }
632
633 void() GotoNextMap =
634 {
635         //local string nextmap;
636         //local float n, nummaps;
637         //local string s;
638         string exit_cfg;
639         if (alreadychangedlevel)
640                 return;
641         alreadychangedlevel = TRUE;
642
643         if(cvar("g_campaign"))
644         {
645                 CampaignPostIntermission();
646                 return;
647         }
648
649         if(cvar("quit_when_empty"))
650         {
651                 if(player_count <= currentbots)
652                 {
653                         localcmd("quit\n");
654                         return;
655                 }
656         }
657
658         if (cvar("samelevel")) // if samelevel is set, stay on same level
659         {
660                 // 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)
661                 //localcmd(strcat("exec \"maps/", mapname, ".mapcfg\"\n"));
662                 // so instead just restart the current map using the restart command (DOES NOT WORK PROPERLY WITH exit_cfg STUFF)
663                 localcmd("restart\n");
664                 //changelevel (mapname);
665                 return;
666         }
667
668         // if an exit cfg is defined by exiting map, exec it.
669         exit_cfg = cvar_string("exit_cfg");
670         if(exit_cfg != "")
671                 localcmd(strcat("exec \"", exit_cfg, "\"\n"));
672
673         localcmd("exec game_reset.cfg\n");
674
675
676         if (cvar("lastlevel"))
677         {
678                 localcmd(strcat("set lastlevel 0\n"));
679                 localcmd(strcat("togglemenu\n"));
680         }
681         else
682         {
683                 float nextMap;
684                 float allowReset;
685
686                 // cvar "nextmap" always gets priority
687                 if(TryFile(strcat("maps/", cvar_string("nextmap"), ".mapcfg")))
688                 {
689                         localcmd(strcat("exec \"maps/", cvar_string("nextmap"), ".mapcfg\"\n"));
690                         return;
691                 }
692
693                 for(allowReset = 1; allowReset >= 0; --allowReset)
694                 {
695                         Maplist_Init();
696                         nextMap = -1;
697
698                         if(nextMap == -1)
699                                 if(cvar("g_maplist_shuffle") > 0)
700                                         nextMap = MaplistMethod_Shuffle(cvar("g_maplist_shuffle") + 1);
701
702                         if(nextMap == -1)
703                                 if(cvar("g_maplist_selectrandom"))
704                                         nextMap = MaplistMethod_Random();
705
706                         if(nextMap == -1)
707                                 nextMap = MaplistMethod_Iterate();
708
709                         if(nextMap == -1)
710                                 nextMap = MaplistMethod_Repeat();
711
712                         if(nextMap >= 0)
713                         {
714                                 Map_Goto(nextMap);
715                                 break;
716                         }
717                         else // PERMANENT FAILURE
718                         {
719                                 if(allowReset)
720                                 {
721                                         bprint( "Maplist contains no single playable map!  Resetting it to default map list.\n" );
722                                         cvar_set("g_maplist", cvar_string("g_maplist_defaultlist"));
723                                 }
724                                 else
725                                 {
726                                         error("Everything is broken - not even the default map list works. Please report this to the developers.");
727                                 }
728                         }
729                 }
730         }
731 };
732
733
734 /*
735 ============
736 IntermissionThink
737
738 When the player presses attack or jump, change to the next level
739 ============
740 */
741 .float autoscreenshot;
742 void() IntermissionThink =
743 {
744         if(cvar("sv_autoscreenshot"))
745         if(self.autoscreenshot)
746         if(time > self.autoscreenshot)
747         {
748                 self.autoscreenshot = FALSE;
749                 if(clienttype(self) == CLIENTTYPE_REAL)
750                         stuffcmd(self, "\nscreenshot\necho \"^5A screenshot has been taken at request of the server.\"\n");
751                 return;
752         }
753
754         if (time < intermission_exittime)
755                 return;
756
757         if (time < intermission_exittime + 10 && !self.button0 && !self.button2 && !self.button3 && !self.button6 && !self.buttonuse)
758                 return;
759
760         GotoNextMap ();
761 };
762
763 /*
764 ============
765 FindIntermission
766
767 Returns the entity to view from
768 ============
769 */
770 /*
771 entity() FindIntermission =
772 {
773         local   entity spot;
774         local   float cyc;
775
776 // look for info_intermission first
777         spot = find (world, classname, "info_intermission");
778         if (spot)
779         {       // pick a random one
780                 cyc = random() * 4;
781                 while (cyc > 1)
782                 {
783                         spot = find (spot, classname, "info_intermission");
784                         if (!spot)
785                                 spot = find (spot, classname, "info_intermission");
786                         cyc = cyc - 1;
787                 }
788                 return spot;
789         }
790
791 // then look for the start position
792         spot = find (world, classname, "info_player_start");
793         if (spot)
794                 return spot;
795
796 // testinfo_player_start is only found in regioned levels
797         spot = find (world, classname, "testplayerstart");
798         if (spot)
799                 return spot;
800
801 // then look for the start position
802         spot = find (world, classname, "info_player_deathmatch");
803         if (spot)
804                 return spot;
805
806         //objerror ("FindIntermission: no spot");
807         return world;
808 };
809 */
810
811 /*
812 ===============================================================================
813
814 RULES
815
816 ===============================================================================
817 */
818
819 void() DumpStats =
820 {
821         local float file;
822         local string s;
823
824         if(cvar("_printstats"))
825                 cvar_set("_printstats", "0");
826         else if(!gameover)
827                 return;
828
829         if(gameover)
830                 s = ":scores:";
831         else
832                 s = ":status:";
833
834         s = strcat(s, GetMapname(), ":", ftos(rint(time)));
835
836         if(cvar("sv_eventlog") && gameover)
837                 GameLogEcho(s, FALSE);
838         else if(cvar("sv_logscores_console"))
839                 ServerConsoleEcho(s, FALSE);
840         if(cvar("sv_logscores_file"))
841         {
842                 file = fopen(cvar_string("sv_logscores_filename"), FILE_APPEND);
843                 fputs(file, strcat(s, "\n"));
844         }
845
846         other = findchainflags(flags, FL_CLIENT);
847         while (other)
848         {
849                 if ((clienttype(other) == CLIENTTYPE_REAL) || (clienttype(other) == CLIENTTYPE_BOT && cvar("sv_logscores_bots")))
850                 {
851                         s = strcat(":player:", ftos(other.frags), ":");
852                         s = strcat(s, ftos(other.deaths), ":");
853                         s = strcat(s, ftos(rint(time - other.jointime)), ":");
854                         s = strcat(s, ftos(other.team), ":");
855
856                         if(cvar("sv_logscores_file"))
857                                 fputs(file, strcat(s, other.netname, "\n"));
858                         if(cvar("sv_eventlog") && gameover)
859                                 GameLogEcho(strcat(s, ftos(other.playerid), ":", other.netname), TRUE);
860                         else if(cvar("sv_logscores_console"))
861                                 ServerConsoleEcho(strcat(s, other.netname), TRUE);
862                 }
863                 other = other.chain;
864         }
865
866         if(cvar("sv_eventlog") && gameover)
867                 GameLogEcho(":end", FALSE);
868         else if(cvar("sv_logscores_console"))
869                 ServerConsoleEcho(":end", FALSE);
870         if(cvar("sv_logscores_file"))
871         {
872                 fputs(file, ":end\n");
873                 fclose(file);
874         }
875 }
876
877
878 /*
879 go to the next level for deathmatch
880 only called if a time or frag limit has expired
881 */
882 void() NextLevel =
883 {
884         gameover = TRUE;
885
886         intermission_running = 1;
887
888 // enforce a wait time before allowing changelevel
889         if(player_count > 0)
890                 intermission_exittime = time + cvar("sv_mapchange_delay");
891         else
892                 intermission_exittime = -60;
893
894         WriteByte (MSG_ALL, SVC_CDTRACK);
895         WriteByte (MSG_ALL, 3);
896         WriteByte (MSG_ALL, 3);
897
898         //pos = FindIntermission ();
899
900         VoteReset();
901
902         DumpStats();
903
904         if(cvar("sv_eventlog"))
905                 GameLogEcho(":gameover", FALSE);
906
907         GameLogClose();
908
909         other = findchainflags(flags, FL_CLIENT);
910         while (other != world)
911         {
912                 //other.nextthink = time + 0.5;
913                 other.takedamage = DAMAGE_NO;
914                 other.solid = SOLID_NOT;
915                 other.movetype = MOVETYPE_NONE;
916                 other.angles = other.v_angle;
917                 other.angles_x = other.angles_x * -1;
918                 other.autoscreenshot = time + 0.8;      // used for autoscreenshot
919
920                 self = other;
921
922                 if(other.winning)
923                         bprint(strcat(other.netname, " ^7wins.\n"));
924
925                 /*
926                 if (pos != world);
927                 {
928                         other.modelindex = 0;
929                         other.weaponentity = world; // remove weapon model
930                         other.view_ofs = '0 0 0';
931                         other.angles = other.v_angle = pos.mangle;
932                         if (!other.angles)
933                         {
934                                 other.angles = other.v_angle = pos.angles;
935                                 other.v_angle_x = other.v_angle_x * -1;
936                         }
937                         other.fixangle = TRUE;          // turn this way immediately
938                         setorigin (other, pos.origin);
939                 }
940                 */
941                 other = other.chain;
942         }
943
944         if(cvar("g_campaign"))
945                 CampaignPreIntermission();
946
947         WriteByte (MSG_ALL, SVC_INTERMISSION);
948 };
949
950 /*
951 ============
952 CheckRules_Player
953
954 Exit deathmatch games upon conditions
955 ============
956 */
957 void() CheckRules_Player =
958 {
959         if (gameover)   // someone else quit the game already
960                 return;
961
962         // fixme: don't check players; instead check dom_team and ctf_team entities
963         //   (div0: and that in CheckRules_World please)
964 };
965
966 float checkrules_oneminutewarning;
967 float checkrules_leaderfrags;
968 float tdm_max_score, tdm_old_score;
969
970 float checkrules_equality;
971 float checkrules_overtimewarning;
972 float checkrules_overtimeend;
973
974 void() InitiateOvertime =
975 {
976         if(!checkrules_overtimeend)
977                 checkrules_overtimeend = time + 60 * cvar("timelimit_maxovertime");
978 }
979
980 float WINNING_NO = 0; // no winner, but time limits may terminate the game
981 float WINNING_YES = 1; // winner found
982 float WINNING_NEVER = 2; // no winner, enter overtime if time limit is reached
983 float WINNING_STARTOVERTIME = 3; // no winner, enter overtime NOW
984
985 float(float fraglimitreached, float equality) GetWinningCode =
986 {
987         if(equality)
988                 if(fraglimitreached)
989                         return WINNING_STARTOVERTIME;
990                 else
991                         return WINNING_NEVER;
992         else
993                 if(fraglimitreached)
994                         return WINNING_YES;
995                 else
996                         return WINNING_NO;
997 }
998
999 // set the .winning flag for exactly those players with a given field value
1000 void(.float field, float value) SetWinners =
1001 {
1002         entity head;
1003         head = findchain(classname, "player");
1004         while (head)
1005         {
1006                 head.winning = (head.field == value);
1007                 head = head.chain;
1008         }
1009 }
1010
1011 // set the .winning flag for those players with a given field value
1012 void(.float field, float value) AddWinners =
1013 {
1014         entity head;
1015         head = findchain(classname, "player");
1016         while (head)
1017         {
1018                 if(head.field == value)
1019                         head.winning = 1;
1020                 head = head.chain;
1021         }
1022 }
1023
1024 // clear the .winning flags
1025 void(void) ClearWinners =
1026 {
1027         entity head;
1028         head = findchain(classname, "player");
1029         while (head)
1030         {
1031                 head.winning = 0;
1032                 head = head.chain;
1033         }
1034 }
1035
1036 float() LMS_NewPlayerLives =
1037 {
1038         float fl;
1039         fl = cvar("fraglimit");
1040         if(fl == 0)
1041                 fl = 999;
1042
1043         // first player has left the game for dying too much? Nobody else can get in.
1044         if(lms_lowest_lives < 1)
1045                 return FALSE;
1046
1047         if(!cvar("g_lms_join_anytime"))
1048                 if(lms_lowest_lives < fl - cvar("g_lms_last_join"))
1049                         return FALSE;
1050
1051         return bound(1, lms_lowest_lives, fl);
1052 }
1053
1054 // LMS winning condition: game terminates if and only if there's at most one
1055 // one player who's living lives. Top two scores being equal cancels the time
1056 // limit.
1057 float() WinningCondition_LMS =
1058 {
1059         entity head;
1060         float have_player;
1061         float have_players;
1062         float l;
1063
1064         have_player = FALSE;
1065         have_players = FALSE;
1066         l = LMS_NewPlayerLives();
1067
1068         head = find(world, classname, "player");
1069         if(head)
1070                 have_player = TRUE;
1071         head = find(head, classname, "player");
1072         if(head)
1073                 have_players = TRUE;
1074
1075         if(have_player)
1076         {
1077                 // we have at least one player
1078                 if(have_players)
1079                 {
1080                         // two or more active players - continue with the game
1081                 }
1082                 else
1083                 {
1084                         // exactly one player?
1085                         if(l)
1086                         {
1087                                 // but no game has taken place yet
1088                         }
1089                         else
1090                         {
1091                                 // a winner!
1092                                 ClearWinners(); SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
1093                                 dprint("Have a winner, ending game.\n");
1094                                 return WINNING_YES;
1095                         }
1096                 }
1097         }
1098         else
1099         {
1100                 // nobody is playing at all...
1101                 if(l)
1102                 {
1103                         // wait for players...
1104                 }
1105                 else
1106                 {
1107                         // SNAFU (maybe a draw game?)
1108                         ClearWinners();
1109                         dprint("No players, ending game.\n");
1110                         return WINNING_YES;
1111                 }
1112         }
1113
1114         // When we get here, we have at least two players who are actually LIVING,
1115         // or one player who is still waiting for a victim to join the server. Now
1116         // check if the top two players have equal score.
1117
1118         checkrules_leaderfrags = 0;
1119         head = findchain(classname, "player");
1120         checkrules_equality = FALSE;
1121         while (head)
1122         {
1123                 if(head.frags > checkrules_leaderfrags)
1124                 {
1125                         checkrules_leaderfrags = head.frags;
1126                         checkrules_equality = FALSE;
1127                 }
1128                 else if(head.frags > 0 && head.frags == checkrules_leaderfrags)
1129                         checkrules_equality = TRUE;
1130                 head = head.chain;
1131         }
1132
1133         SetWinners(frags, checkrules_leaderfrags);
1134
1135         // The top two players have the same amount of lives? No timelimit then,
1136         // enter overtime...
1137
1138         if(checkrules_equality)
1139                 return WINNING_NEVER;
1140
1141         // Top two have different scores? Way to go for our beloved TIMELIMIT!
1142         return WINNING_NO;
1143 }
1144
1145 // DM winning condition: game terminates if a player reached the fraglimit,
1146 // unless the first two players have the same score. The latter case also
1147 // breaks the time limit.
1148 float(float fraglimit) WinningCondition_MaxIndividualScore =
1149 {
1150         float checkrules_oldleaderfrags;
1151         entity head;
1152
1153         checkrules_oldleaderfrags = checkrules_leaderfrags;
1154         checkrules_leaderfrags = 0;
1155         head = findchain(classname, "player");
1156         checkrules_equality = FALSE;
1157         while (head)
1158         {
1159                 if(head.frags > checkrules_leaderfrags)
1160                 {
1161                         checkrules_leaderfrags = head.frags;
1162                         checkrules_equality = FALSE;
1163                 }
1164                 else if(head.frags > 0 && head.frags == checkrules_leaderfrags)
1165                         checkrules_equality = TRUE;
1166                 head = head.chain;
1167         }
1168
1169         if(checkrules_leaderfrags > 0)
1170                 SetWinners(frags, checkrules_leaderfrags);
1171         else
1172                 ClearWinners();
1173
1174         if (!cvar("g_runematch"))
1175                 if (checkrules_leaderfrags != checkrules_oldleaderfrags)
1176                 {
1177                         if (checkrules_leaderfrags == fraglimit - 1)
1178                                 sound(world, CHAN_AUTO, "announcer/robotic/1fragleft.ogg", 1, ATTN_NONE);
1179                         else if (checkrules_leaderfrags == fraglimit - 2)
1180                                 sound(world, CHAN_AUTO, "announcer/robotic/2fragsleft.ogg", 1, ATTN_NONE);
1181                         else if (checkrules_leaderfrags == fraglimit - 3)
1182                                 sound(world, CHAN_AUTO, "announcer/robotic/3fragsleft.ogg", 1, ATTN_NONE);
1183                 }
1184
1185         return GetWinningCode(fraglimit && checkrules_leaderfrags >= fraglimit, checkrules_equality);
1186 }
1187
1188 float(float fraglimit) WinningConditionBase_Teamplay =
1189 {
1190         tdm_old_score = tdm_max_score;
1191         tdm_max_score = max(team1_score, team2_score, team3_score, team4_score);
1192
1193         checkrules_equality =
1194         (
1195                 (tdm_max_score > 0)
1196                 &&
1197                 (
1198                           (team1_score == tdm_max_score)
1199                         + (team2_score == tdm_max_score)
1200                         + (team3_score == tdm_max_score)
1201                         + (team4_score == tdm_max_score)
1202                         >= 2));
1203
1204         ClearWinners();
1205         if(tdm_max_score > 0)
1206         {
1207                 if(team1_score == tdm_max_score)
1208                         AddWinners(team, COLOR_TEAM1);
1209                 if(team2_score == tdm_max_score)
1210                         AddWinners(team, COLOR_TEAM2);
1211                 if(team3_score == tdm_max_score)
1212                         AddWinners(team, COLOR_TEAM3);
1213                 if(team4_score == tdm_max_score)
1214                         AddWinners(team, COLOR_TEAM4);
1215         }
1216
1217         if(!cvar("g_runematch") && !cvar("g_domination"))
1218                 if(tdm_max_score != tdm_old_score)
1219                 {
1220                         if(tdm_max_score == fraglimit - 1)
1221                                 sound(world, CHAN_AUTO, "announcer/robotic/1fragleft.ogg", 1, ATTN_NONE);
1222                         else if(tdm_max_score == fraglimit - 2)
1223                                 sound(world, CHAN_AUTO, "announcer/robotic/2fragsleft.ogg", 1, ATTN_NONE);
1224                         else if(tdm_max_score == fraglimit - 3)
1225                                 sound(world, CHAN_AUTO, "announcer/robotic/3fragsleft.ogg", 1, ATTN_NONE);
1226                 }
1227
1228         return GetWinningCode(fraglimit && tdm_max_score >= fraglimit, checkrules_equality);
1229 }
1230
1231 // TDM winning condition: game terminates if a team's score sum reached the
1232 // fraglimit, unless the first two teams have the same total score. The latter
1233 // case also breaks the time limit.
1234 float(float fraglimit) WinningCondition_MaxTeamSum =
1235 {
1236         entity head;
1237
1238         team1_score = team2_score = team3_score = team4_score = 0;
1239
1240         head = findchain(classname, "player");
1241         while (head)
1242         {
1243                 if(head.team == COLOR_TEAM1)
1244                         team1_score += head.frags;
1245                 else if(head.team == COLOR_TEAM2)
1246                         team2_score += head.frags;
1247                 else if(head.team == COLOR_TEAM3)
1248                         team3_score += head.frags;
1249                 else if(head.team == COLOR_TEAM4)
1250                         team4_score += head.frags;
1251                 head = head.chain;
1252         }
1253
1254         return WinningConditionBase_Teamplay(fraglimit);
1255 }
1256
1257 // DOM/CTF winning condition: game terminates if the max of a team's players'
1258 // score reached the fraglimit, unless the first two teams have the same
1259 // maximum score. The latter case also breaks the time limit.
1260 float(float fraglimit) WinningCondition_MaxTeamMax =
1261 {
1262         entity head;
1263
1264         team1_score = team2_score = team3_score = team4_score = 0;
1265
1266         head = findchain(classname, "player");
1267         while (head)
1268         {
1269                 if(head.team == COLOR_TEAM1)
1270                 {
1271                         if(head.frags > team1_score)
1272                                 team1_score = head.frags;
1273                 }
1274                 else if(head.team == COLOR_TEAM2)
1275                 {
1276                         if(head.frags > team2_score)
1277                                 team2_score = head.frags;
1278                 }
1279                 else if(head.team == COLOR_TEAM3)
1280                 {
1281                         if(head.frags > team3_score)
1282                                 team3_score = head.frags;
1283                 }
1284                 else if(head.team == COLOR_TEAM4)
1285                 {
1286                         if(head.frags > team4_score)
1287                                 team4_score = head.frags;
1288                 }
1289                 head = head.chain;
1290         }
1291
1292         return WinningConditionBase_Teamplay(fraglimit);
1293 }
1294
1295 void PrintScoreboardFor(string name, string colorcode, float whichteam)
1296 {
1297         entity head;
1298         float fragtotal;
1299         string s;
1300         float found;
1301         found = FALSE;
1302         head = find(world, classname, "player");
1303         while(head)
1304         {
1305                 if(!whichteam || head.team == whichteam)
1306                 {
1307                         if(name != "")
1308                                 if(!found)
1309                                         ServerConsoleEcho(strcat(" ", colorcode, name, ":"), FALSE);
1310                         found = TRUE;
1311                         fragtotal = fragtotal + head.frags;
1312                         s = ftos(head.frags);
1313                         s = strcat(s, "/", ftos(head.deaths));
1314                         s = strcat(s, " @ ", ftos(head.ping));
1315                         if(clienttype(head) == CLIENTTYPE_BOT)
1316                                 s = strcat(s, "botms");
1317                         else
1318                                 s = strcat(s, "ms");
1319                         ServerConsoleEcho(strcat("  ", colorcode, head.netname, colorcode, " (", s, ")"), TRUE);
1320                 }
1321                 head = find(head, classname, "player");
1322         }
1323         if(whichteam && found)
1324                 ServerConsoleEcho(strcat(colorcode, "  (total: ", ftos(fragtotal), ")"), FALSE);
1325 }
1326
1327 void PrintScoreboard()
1328 {
1329         ServerConsoleEcho("Scoreboard:", FALSE);
1330         if(teams_matter)
1331         {
1332                 PrintScoreboardFor("Red", "^1", COLOR_TEAM1);
1333                 PrintScoreboardFor("Blue", "^4", COLOR_TEAM2);
1334                 PrintScoreboardFor("Pink", "^6", COLOR_TEAM3);
1335                 PrintScoreboardFor("Yellow", "^3", COLOR_TEAM4);
1336         }
1337         else
1338         {
1339                 PrintScoreboardFor("", "^7", 0);
1340         }
1341         ServerConsoleEcho(".", FALSE);
1342 }
1343
1344 void RemoveFromMaplist(string m)
1345 {
1346         string result;
1347         float litems;
1348         float i;
1349         float found;
1350
1351         litems = tokenize(cvar_string("g_maplist"));
1352         found = 0;
1353         result = "";
1354         for(i = 0; i < litems; ++i)
1355         {
1356                 m = strcat(m);
1357                 result = strcat(result);
1358                 if(argv(i) == m)
1359                         found += 1;
1360                 else
1361                         result = strcat(result, "'", argv(i), "'");
1362         }
1363         if(found)
1364                 cvar_set("g_maplist", result);
1365         ServerConsoleEcho(strcat("Removed ", ftos(found), " items."), FALSE);
1366 }
1367
1368 void AddToMaplist(string m)
1369 {
1370         string result;
1371         float found;
1372         float litems;
1373         float i;
1374         float ipos;
1375         float inserted;
1376
1377         if(!TryFile(strcat("maps/", m, ".mapcfg")))
1378         {
1379                 ServerConsoleEcho("Map not found.", FALSE);
1380                 return;
1381         }
1382
1383         litems = tokenize(cvar_string("g_maplist"));
1384         if(cvar("g_maplist_shuffle"))
1385                 ipos = ceil(random() * (litems + 1)) - 1;
1386         else
1387                 ipos = litems;
1388         found = 0;
1389         inserted = 0;
1390         for(i = 0; i < litems; ++i)
1391         {
1392                 m = strcat(m);
1393                 if(i == ipos)
1394                 {
1395                         result = strcat(result, "'", m, "'");
1396                         inserted = 1;
1397                 }
1398                 result = strcat(result, "'", argv(i), "'");
1399                 if(argv(i) == m)
1400                         found += 1;
1401         }
1402         if(!inserted)
1403                 result = strcat(result, "'", m, "'");
1404         if(!found)
1405         {
1406                 cvar_set("g_maplist", result);
1407                 ServerConsoleEcho("Map added.", FALSE);
1408         }
1409         else
1410                 ServerConsoleEcho("Map already in list.", FALSE);
1411 }
1412
1413 void ShuffleMaplist()
1414 {
1415         string result;
1416         float start;
1417         float litems;
1418         float selected;
1419         float i;
1420
1421         result = cvar_string("g_maplist");
1422         litems = tokenize(result);
1423
1424         for(start = 0; start < litems - 1; ++start)
1425         {
1426                 result = "";
1427
1428                 // select a random item
1429                 selected = ceil(random() * (litems - start) + start) - 1;
1430
1431                 // shift this item to the place start
1432                 for(i = 0; i < start; ++i)
1433                         result = strcat(result, "'", argv(i), "'");
1434                 result = strcat(result, "'", argv(selected), "'");
1435                 for(i = start; i < litems; ++i)
1436                         if(i != selected)
1437                                 result = strcat(result, "'", argv(i), "'");
1438
1439                 litems = tokenize(result);
1440
1441                 //dprint(result, "\n");
1442         }
1443
1444         cvar_set("g_maplist", result);
1445 }
1446
1447 void() HandleMaplistShuffleCommands =
1448 {
1449         // automatically shuffle when setting g_maplist_shuffle
1450         if(cvar_string("_g_maplist_add") != "")
1451         {
1452                 AddToMaplist(cvar_string("_g_maplist_add"));
1453                 cvar_set("_g_maplist_add", "");
1454         }
1455         if(cvar_string("_g_maplist_remove") != "")
1456         {
1457                 RemoveFromMaplist(cvar_string("_g_maplist_remove"));
1458                 cvar_set("_g_maplist_remove", "");
1459         }
1460         if(cvar("_g_maplist_shufflenow") || (cvar("g_maplist_shuffle") && !cvar("_g_maplist_have_shuffled")))
1461         {
1462                 ShuffleMaplist();
1463                 cvar_set("_g_maplist_shufflenow", "0");
1464                 cvar_set("_g_maplist_have_shuffled", "1");
1465                 ServerConsoleEcho("Shuffled map list.", FALSE);
1466         }
1467         if(cvar("_g_maplist_have_shuffled"))
1468                 if(!cvar("g_maplist_shuffle"))
1469                         cvar_set("_g_maplist_have_shuffled", "0");
1470 }
1471
1472 /*
1473 ============
1474 CheckRules_World
1475
1476 Exit deathmatch games upon conditions
1477 ============
1478 */
1479 void() CheckRules_World =
1480 {
1481         local float status;
1482         local float timelimit;
1483         local float fraglimit;
1484
1485         VoteThink();
1486
1487         SetDefaultAlpha();
1488
1489         if (intermission_running)
1490                 if (time >= intermission_exittime + 60)
1491                 {
1492                         GotoNextMap();
1493                         return;
1494                 }
1495
1496         if (gameover)   // someone else quit the game already
1497                 return;
1498
1499         DumpStats();
1500
1501         if(cvar("_scoreboard"))
1502         {
1503                 cvar_set("_scoreboard", "0");
1504                 PrintScoreboard();
1505         }
1506
1507         HandleMaplistShuffleCommands();
1508
1509         timelimit = cvar("timelimit") * 60;
1510         fraglimit = cvar("fraglimit");
1511
1512         if (timelimit && time >= timelimit)
1513                 InitiateOvertime();
1514
1515         if (checkrules_overtimeend && time >= checkrules_overtimeend)
1516         {
1517                 NextLevel();
1518                 return;
1519         }
1520
1521         if(!checkrules_overtimewarning && checkrules_overtimeend)
1522         {
1523                 checkrules_overtimewarning = TRUE;
1524                 //sound(world, CHAN_AUTO, "announcer/robotic/1minuteremains.ogg", 1, ATTN_NONE);
1525                 bcenterprint("^3Now playing ^1OVERTIME^3!\n\n^3Keep fragging until we have a ^1winner^3!");
1526         }
1527
1528         if (!checkrules_oneminutewarning && timelimit > 0 && time > timelimit - 60)
1529         {
1530                 checkrules_oneminutewarning = TRUE;
1531                 sound(world, CHAN_AUTO, "announcer/robotic/1minuteremains.ogg", 1, ATTN_NONE);
1532         }
1533
1534         status = WINNING_NO;
1535         if(cvar("g_lms"))
1536         {
1537                 status = WinningCondition_LMS();
1538         }
1539         else
1540         {
1541                 if(teams_matter)
1542                 {
1543                         if(cvar("g_tdm") || cvar("g_runematch") || cvar("g_ctf") || cvar("g_domination"))
1544                                 status = WinningCondition_MaxTeamSum(fraglimit);
1545                         //else if()
1546                         //      status = WinningCondition_MaxTeamMax(fraglimit);
1547                         else
1548                         {
1549                                 dprint("div0: How can this happen?\n");
1550                                 status = WinningCondition_MaxTeamMax(fraglimit);
1551                         }
1552                 }
1553                 else
1554                         status = WinningCondition_MaxIndividualScore(fraglimit);
1555         }
1556
1557         if(status == WINNING_STARTOVERTIME)
1558         {
1559                 status = WINNING_NEVER;
1560                 InitiateOvertime();
1561         }
1562
1563         if(status == WINNING_NEVER)
1564                 // equality cases! Nobody wins if the overtime ends in a draw.
1565                 ClearWinners();
1566
1567         if(checkrules_overtimeend)
1568                 if(status != WINNING_NEVER)
1569                         status = WINNING_YES;
1570
1571         if(status == WINNING_YES)
1572                 NextLevel();
1573 };