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