]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/gamec/g_world.c
Fixed g_campaign team balance
[divverent/nexuiz.git] / data / qcsrc / server / gamec / g_world.c
1 string GetMapname();
2
3 void SetDefaultAlpha()
4 {
5         if(cvar("g_cloaked"))
6         {
7                 default_player_alpha = cvar("g_balance_cloaked_alpha");
8         }
9         else
10         {
11                 default_player_alpha = cvar("g_player_alpha");
12                 if(default_player_alpha <= 0)
13                         default_player_alpha = 1;
14         }
15 }
16
17 void worldspawn (void)
18 {
19
20         // Precache all player models
21         // Workaround for "invisible players"
22         precache_model("models/player/carni.zym");
23         precache_model("models/player/crash.zym");
24         precache_model("models/player/grunt.zym");
25         precache_model("models/player/headhunter.zym");
26         precache_model("models/player/insurrectionist.zym");
27         precache_model("models/player/jeandarc.zym");
28         precache_model("models/player/lurk.zym");
29         precache_model("models/player/lycanthrope.zym");
30         precache_model("models/player/marine.zym");
31         precache_model("models/player/nexus.zym");
32         precache_model("models/player/pyria.zym");
33         precache_model("models/player/shock.zym");
34         precache_model("models/player/skadi.zym");
35         precache_model("models/player/specop.zym");
36         precache_model("models/player/visitant.zym");
37
38         //precache_model ("progs/beam.mdl");
39         precache_model ("models/bullet.mdl");
40         precache_model ("models/casing_bronze.mdl");
41         precache_model ("models/casing_shell.mdl");
42         precache_model ("models/casing_steel.mdl");
43         precache_model ("models/ebomb.mdl");
44         precache_model ("models/elaser.mdl");
45         precache_model ("models/flash.md3");
46         precache_model ("models/gibs/bloodyskull.md3");
47         precache_model ("models/gibs/chunk.mdl");
48         precache_model ("models/gibs/eye.md3");
49         precache_model ("models/gibs/gib1.md3");
50         //precache_model ("models/gibs/gib2.md3");
51         //precache_model ("models/gibs/gib3.md3");
52         //precache_model ("models/gibs/gib4.md3");
53         precache_model ("models/gibs/gib5.md3");
54         //precache_model ("models/gibs/gib6.md3");
55         precache_model ("models/gibs/gib1.mdl");
56         precache_model ("models/gibs/gib2.mdl");
57         precache_model ("models/gibs/gib3.mdl");
58         precache_model ("models/grenademodel.md3");
59         precache_model ("models/hagarmissile.mdl");
60         precache_model ("models/items/a_bullets.mdl");
61         precache_model ("models/items/a_cells.md3");
62         precache_model ("models/items/a_rockets.md3");
63         precache_model ("models/items/a_shells.md3");
64         precache_model ("models/items/g_a1.md3");
65         precache_model ("models/items/g_a25.md3");
66         precache_model ("models/items/g_h1.md3");
67         precache_model ("models/items/g_h25.md3");
68         precache_model ("models/items/g_h100.md3");
69         precache_model ("models/items/g_invincible.md3");
70         precache_model ("models/items/g_strength.md3");
71         precache_model ("models/laser.mdl");
72         precache_model ("models/misc/chatbubble.spr");
73         precache_model ("models/misc/teambubble.spr");
74         precache_model ("models/nexflash.md3");
75         precache_model ("models/plasma.mdl");
76         precache_model ("models/plasmatrail.mdl");
77         precache_model ("models/rocket.md3");
78         //precache_model ("models/sprites/grenexpl.spr");
79         precache_model ("models/runematch/rune.mdl");
80         precache_model ("models/runematch/curse.mdl");
81         //precache_model ("models/sprites/hagar.spr");
82         //precache_model ("models/sprites/muzzleflash.spr32");
83         //precache_model ("models/sprites/electrocombo.spr32");
84         //precache_model ("models/sprites/plasmahitwall.spr32");
85         //precache_model ("models/sprites/plasmashot.spr32");
86         //precache_model ("models/sprites/rockexpl.spr");
87         precache_model ("models/tracer.mdl");
88         precache_model ("models/uziflash.md3");
89         precache_model ("models/weapons/g_crylink.md3");
90         precache_model ("models/weapons/g_electro.md3");
91         precache_model ("models/weapons/g_gl.md3");
92         precache_model ("models/weapons/g_hagar.md3");
93         precache_model ("models/weapons/g_nex.md3");
94         precache_model ("models/weapons/g_rl.md3");
95         precache_model ("models/weapons/g_shotgun.md3");
96         precache_model ("models/weapons/g_uzi.md3");
97         precache_model ("models/weapons/v_crylink.md3");
98         precache_model ("models/weapons/v_electro.md3");
99         precache_model ("models/weapons/v_gl.md3");
100         precache_model ("models/weapons/v_hagar.md3");
101         precache_model ("models/weapons/v_laser.md3");
102         precache_model ("models/weapons/v_nex.md3");
103         precache_model ("models/weapons/v_rl.md3");
104         precache_model ("models/weapons/v_shotgun.md3");
105         precache_model ("models/weapons/v_uzi.md3");
106         precache_model ("models/weapons/w_crylink.zym");
107         precache_model ("models/weapons/w_electro.zym");
108         precache_model ("models/weapons/w_gl.zym");
109         precache_model ("models/weapons/w_hagar.zym");
110         precache_model ("models/weapons/w_laser.zym");
111         precache_model ("models/weapons/w_nex.zym");
112         precache_model ("models/weapons/w_rl.zym");
113         precache_model ("models/weapons/w_shotgun.zym");
114         precache_model ("models/weapons/w_uzi.zym");
115
116         // laser for laser-guided weapons
117         precache_model ("models/laser_dot.mdl");
118
119         precache_sound ("misc/null.wav");
120         precache_sound ("misc/armor1.wav");
121         precache_sound ("misc/armor25.wav");
122         precache_sound ("misc/armorimpact.wav");
123         precache_sound ("misc/bodyimpact1.wav");
124         precache_sound ("misc/bodyimpact2.wav");
125         precache_sound ("misc/gib.wav");
126         precache_sound ("misc/gib_splat01.wav");
127         precache_sound ("misc/gib_splat02.wav");
128         precache_sound ("misc/gib_splat03.wav");
129         precache_sound ("misc/gib_splat04.wav");
130         //precache_sound ("misc/h2ohit.wav");
131         precache_sound ("misc/hit.wav");
132         precache_sound ("misc/footstep01.wav");
133         precache_sound ("misc/footstep02.wav");
134         precache_sound ("misc/footstep03.wav");
135         precache_sound ("misc/footstep04.wav");
136         precache_sound ("misc/footstep05.wav");
137         precache_sound ("misc/footstep06.wav");
138         precache_sound ("misc/hitground1.ogg");
139         precache_sound ("misc/hitground2.ogg");
140         precache_sound ("misc/hitground3.ogg");
141         precache_sound ("misc/hitground4.ogg");
142         precache_sound ("misc/itempickup.ogg");
143         precache_sound ("misc/itemrespawn.ogg");
144         precache_sound ("misc/jumppad.ogg");
145         precache_sound ("misc/mediumhealth.ogg");
146         precache_sound ("misc/megahealth.ogg");
147         precache_sound ("misc/minihealth.ogg");
148         precache_sound ("misc/powerup.ogg");
149         precache_sound ("misc/powerup_shield.ogg");
150         precache_sound ("misc/talk.wav");
151         precache_sound ("misc/teleport.ogg");
152         precache_sound ("plats/medplat1.wav");
153         precache_sound ("plats/medplat2.wav");
154         precache_sound ("player/lava.wav");
155         precache_sound ("player/slime.wav");
156         precache_sound ("weapons/crylink_fire.ogg");
157         precache_sound ("weapons/electro_bounce.ogg");
158         precache_sound ("weapons/electro_fire.ogg");
159         precache_sound ("weapons/electro_fire2.ogg");
160         precache_sound ("weapons/electro_fly.wav");
161         precache_sound ("weapons/electro_impact.ogg");
162         precache_sound ("weapons/electro_impact_combo.ogg");
163         //precache_sound ("weapons/grenade_bounce.ogg");
164         precache_sound ("weapons/grenade_bounce1.ogg");
165         precache_sound ("weapons/grenade_bounce2.ogg");
166         precache_sound ("weapons/grenade_bounce3.ogg");
167         precache_sound ("weapons/grenade_fire.ogg");
168         precache_sound ("weapons/grenade_impact.ogg");
169         precache_sound ("weapons/hagar_fire.ogg");
170         precache_sound ("weapons/hagexp1.ogg");
171         precache_sound ("weapons/hagexp2.ogg");
172         precache_sound ("weapons/hagexp3.ogg");
173         precache_sound ("weapons/hook_fire.ogg");
174         precache_sound ("weapons/hook_impact.ogg");
175         precache_sound ("weapons/lasergun_fire.ogg");
176         precache_sound ("weapons/laserimpact.ogg");
177         precache_sound ("weapons/nexfire.ogg");
178         precache_sound ("weapons/neximpact.ogg");
179         precache_sound ("weapons/ric1.ogg");
180         precache_sound ("weapons/ric2.ogg");
181         precache_sound ("weapons/ric3.ogg");
182         precache_sound ("weapons/rocket_fire.ogg");
183         precache_sound ("weapons/rocket_fly.wav");
184         precache_sound ("weapons/rocket_impact.ogg");
185         precache_sound ("weapons/rocket_det.ogg");
186         precache_sound ("weapons/shotgun_fire.ogg");
187         precache_sound ("weapons/tink1.ogg");
188         precache_sound ("weapons/uzi_fire.ogg");
189         precache_sound ("weapons/weapon_switch.ogg");
190         precache_sound ("weapons/weaponpickup.ogg");
191         precache_sound ("weapons/strength_fire.ogg");
192
193         //precache_sound ("announce/male/kill10.ogg");
194         //precache_sound ("announce/male/kill15.ogg");
195         //precache_sound ("announce/male/kill20.ogg");
196         //precache_sound ("announce/male/kill25.ogg");
197         //precache_sound ("announce/male/kill3.ogg");
198         //precache_sound ("announce/male/kill30.ogg");
199         //precache_sound ("announce/male/kill4.ogg");
200         //precache_sound ("announce/male/kill5.ogg");
201         //precache_sound ("announce/male/kill6.ogg");
202         //precache_sound ("announce/male/mapkill1.ogg");
203         //precache_sound ("announce/robotic/last_second_save.ogg");
204         //precache_sound ("announce/robotic/narrowly_averted.ogg");
205         //precache_sound ("minstagib/mockery.ogg");
206
207         // announcer sounds - male
208         precache_sound ("announcer/male/03kills.ogg");
209         precache_sound ("announcer/male/05kills.ogg");
210         precache_sound ("announcer/male/10kills.ogg");
211         precache_sound ("announcer/male/15kills.ogg");
212         precache_sound ("announcer/male/20kills.ogg");
213         precache_sound ("announcer/male/25kills.ogg");
214         precache_sound ("announcer/male/30kills.ogg");
215         precache_sound ("announcer/male/botlike.ogg");
216         precache_sound ("announcer/male/electrobitch.ogg");
217         precache_sound ("announcer/male/welcome.ogg");
218         precache_sound ("announcer/male/yoda.ogg");
219
220         // announcer sounds - robotic
221         precache_sound ("announcer/robotic/1fragleft.ogg");
222         precache_sound ("announcer/robotic/1minuteremains.ogg");
223         precache_sound ("announcer/robotic/2fragsleft.ogg");
224         precache_sound ("announcer/robotic/3fragsleft.ogg");
225         precache_sound ("announcer/robotic/lastsecond.ogg");
226         precache_sound ("announcer/robotic/narrowly.ogg");
227
228         // plays music for the level if there is any
229         if (self.noise)
230         {
231                 precache_sound (self.noise);
232                 ambientsound ('0 0 0', self.noise, 1.00, ATTN_NONE);
233         }
234
235                 // 0 normal
236         lightstyle(0, "m");
237
238         // 1 FLICKER (first variety)
239         lightstyle(1, "mmnmmommommnonmmonqnmmo");
240
241         // 2 SLOW STRONG PULSE
242         lightstyle(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
243
244         // 3 CANDLE (first variety)
245         lightstyle(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
246
247         // 4 FAST STROBE
248         lightstyle(4, "mamamamamama");
249
250         // 5 GENTLE PULSE 1
251         lightstyle(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj");
252
253         // 6 FLICKER (second variety)
254         lightstyle(6, "nmonqnmomnmomomno");
255
256         // 7 CANDLE (second variety)
257         lightstyle(7, "mmmaaaabcdefgmmmmaaaammmaamm");
258
259         // 8 CANDLE (third variety)
260         lightstyle(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
261
262         // 9 SLOW STROBE (fourth variety)
263         lightstyle(9, "aaaaaaaazzzzzzzz");
264
265         // 10 FLUORESCENT FLICKER
266         lightstyle(10, "mmamammmmammamamaaamammma");
267
268         // 11 SLOW PULSE NOT FADE TO BLACK
269         lightstyle(11, "abcdefghijklmnopqrrqponmlkjihgfedcba");
270
271         // styles 32-62 are assigned by the light program for switchable lights
272
273         // 63 testing
274         lightstyle(63, "a");
275
276         player_count = 0;
277         lms_dead_count = 0;
278         lms_lowest_lives = 0;
279         lms_next_place = 0;
280
281         if(cvar("g_campaign"))
282                 CampaignPreInit();
283
284         InitGameplayMode();
285         //if (cvar("g_domination"))
286         //      dom_init();
287
288         local entity head;
289         head = nextent(world);
290         maxclients = 0;
291         while(head)
292         {
293                 maxclients++;
294                 head = nextent(head);
295         }
296
297         GameLogInit(); // prepare everything
298         if(cvar("sv_eventlog"))
299         {
300                 local string s;
301                 GameLogEcho(":logversion:2", FALSE);
302                 s = strcat(cvar_string("sv_eventlog_files_counter"), ".");
303                 s = strcat(s, ftos(random()));
304                 GameLogEcho(strcat(":gamestart:", GetMapname(), ":", s), FALSE);
305                 s = ":gameinfo:mutators:LIST";
306                 if(cvar("g_grappling_hook"))
307                         s = strcat(s, ":grappling_hook");
308                 if(!cvar("g_use_ammunition"))
309                         s = strcat(s, ":no_use_ammunition");
310                 if(!cvar("g_pickup_items"))
311                         s = strcat(s, ":no_pickup_items");
312                 if(cvar("g_instagib"))
313                         s = strcat(s, ":instagib");
314                 if(cvar("g_rocketarena"))
315                         s = strcat(s, ":rockerarena");
316                 if(cvar("g_nixnex"))
317                         s = strcat(s, ":nixnex");
318                 if(cvar("g_vampire"))
319                         s = strcat(s, ":vampire");
320                 if(cvar("g_laserguided_missile"))
321                         s = strcat(s, ":laserguided_missile");
322                 if(cvar("g_norecoil"))
323                         s = strcat(s, ":norecoil");
324                 if(cvar("g_midair"))
325                         s = strcat(s, ":midair");
326                 if(cvar("g_minstagib"))
327                         s = strcat(s, ":minstagib");
328                 GameLogEcho(s, FALSE);
329                 GameLogEcho(":gameinfo:end", FALSE);
330         }
331
332         cvar_set("nextmap", "");
333
334         SetDefaultAlpha();
335
336         if(cvar("g_campaign"))
337                 CampaignPostInit();
338 }
339
340 void light (void)
341 {
342         makestatic (self);
343 }
344
345 float( string pFilename ) TryFile =
346 {
347         local float lHandle;
348         dprint("TryFile(\"", pFilename, "\")\n");
349         lHandle = fopen( pFilename, FILE_READ );
350         if( lHandle != -1 ) {
351                 fclose( lHandle );
352                 return TRUE;
353         } else {
354                 return FALSE;
355         }
356 };
357
358 string GetMapname()
359 {
360         if (game == GAME_DEATHMATCH)
361                 return strcat("dm_", mapname);
362         else if (game == GAME_TEAM_DEATHMATCH)
363                 return strcat("tdm_", mapname);
364         else if (game == GAME_DOMINATION)
365                 return strcat("dom_", mapname);
366         else if (game == GAME_CTF)
367                 return strcat("ctf_", mapname);
368         else if (game == GAME_RUNEMATCH)
369                 return strcat("rune_", mapname);
370         else if (game == GAME_LMS)
371                 return strcat("lms_", mapname);
372         return strcat("dm_", mapname);
373 }
374
375 float GetMaplistPosition()
376 {
377         float pos, f;
378         string map, s;
379
380         map = strzone(GetMapname());
381         pos = 0;
382         f = tokenize(cvar_string("g_maplist"));
383
384         while(pos < f)
385         {
386                 s = strzone(argv(pos));
387                 if(s == map)
388                 {
389                         strunzone(s);
390                         strunzone(map);
391                         return pos;
392                 }
393                 strunzone(s);
394                 pos++;
395         }
396
397         strunzone(map);
398         // resume normal maplist rotation if current map is not in g_maplist
399         return cvar("g_maplist_index");
400 }
401
402 void() GotoNextMap =
403 {
404         //local string nextmap;
405         //local float n, nummaps;
406         //local string s;
407         string exit_cfg;
408         if (alreadychangedlevel)
409                 return;
410         alreadychangedlevel = TRUE;
411
412         if(cvar("g_campaign"))
413         {
414                 CampaignPostIntermission();
415                 return;
416         }
417
418         if (cvar("samelevel")) // if samelevel is set, stay on same level
419         {
420                 // 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)
421                 //localcmd(strcat("exec \"maps/", mapname, ".mapcfg\"\n"));
422                 // so instead just restart the current map using the restart command (DOES NOT WORK PROPERLY WITH exit_cfg STUFF)
423                 localcmd("restart\n");
424                 //changelevel (mapname);
425                 return;
426         }
427
428         // if an exit cfg is defined by exiting map, exec it.
429         exit_cfg = cvar_string("exit_cfg");
430         if(exit_cfg != "")
431                 localcmd(strcat("exec \"", exit_cfg, "\"\n"));
432
433         ResetGameCvars();
434
435
436         if (cvar("lastlevel"))
437         {
438                 localcmd(strcat("set lastlevel 0\n"));
439                 localcmd(strcat("togglemenu\n"));
440         }
441         else
442         {
443                 // method 0
444                 local float lCurrent;
445                 local float lSize;
446                 local float lOldCurrent;
447                 local float lBeforeCurrent;
448                 local float pass;
449
450                 if(TryFile(strcat("maps/", cvar_string("nextmap"), ".mapcfg")))
451                 {
452                         localcmd(strcat("exec \"maps/", cvar_string("nextmap"), ".mapcfg\"\n"));
453                         return;
454                 }
455
456                 pass = 0;
457                 while (pass < 2)
458                 {
459                         pass = pass + 1;
460                         local string temp;
461                         temp = cvar_string( "g_maplist" );
462                         if(temp == "")
463                         {
464                                 bprint( "Maplist is empty!  Resetting it to default map list.\n" );
465                                 cvar_set("g_maplist", cvar_string("g_maplist_defaultlist"));
466                                 temp = cvar_string( "g_maplist" );
467                         }
468                         temp = strzone(temp);
469                         dprint("g_maplist is ", temp, "\n");
470                         lCurrent = max(0, GetMaplistPosition());
471                         lSize = tokenize( temp );
472                         lOldCurrent = lCurrent;
473                         dprint(ftos(lOldCurrent), " / ", ftos(lSize), " (start)\n");
474
475                         // if we want a random map selection...
476                         if(cvar("g_maplist_selectrandom") && lSize > 1)
477                         {
478                                 lBeforeCurrent = lCurrent - 1;
479                                 if(lBeforeCurrent < 0)
480                                         lBeforeCurrent = lSize - 1;
481                                 lCurrent = ceil(random() * (lSize - 1)) - 1; // random in 0..lsize-2
482                                 if(lCurrent >= lBeforeCurrent)
483                                         lCurrent += 1;
484                                 // choose any map except for the current one
485                         }
486
487                         while( 1 ) {
488                                 local string lFilename;
489
490                                 lCurrent = lCurrent + 1;
491                                 dprint(ftos(lCurrent), " / ", ftos(lSize), "\n");
492                                 if( lCurrent >= lSize ) {
493                                         lCurrent = 0;
494                                 }
495                                 if( lOldCurrent == lCurrent ) {
496                                         // we couldn't find a valid map at all
497                                         if (pass == 1)
498                                         {
499                                                 bprint( "Maplist is bad/messed up. Not one good mapcfg can be found in it!  Resetting it to default map list.\n" );
500                                                 cvar_set("g_maplist", cvar_string("g_maplist_defaultlist"));
501                                                 // let the loop restart with the default list now
502                                         }
503                                         else
504                                         {
505                                                 bprint( "Both g_maplist and g_maplist_defaultlist are messed up!  Complain to developers!\n" );
506                                                 localcmd( "disconnect\n" );
507                                         }
508                                         break;
509                                 }
510
511                                 cvar_set( "g_maplist_index", ftos( lCurrent ) );
512
513                                 lFilename = strcat( "maps/", argv( lCurrent ), ".mapcfg" );
514                                 if( TryFile( lFilename ) ) {
515                                         localcmd(strcat("exec \"", lFilename ,"\"\n"));
516                                         pass = 2; // exit the outer loop
517                                         break;
518                                 } else {
519                                         dprint( "Couldn't find '", lFilename, "'..\n" );
520                                 }
521                                 //changelevel( argv( lCurrent ) );
522                         }
523                         strunzone(temp);
524                 }
525
526                 /*
527                 // method 1
528
529                 //local entity pos;
530                 local float fh;
531                 local string line;
532
533                 // restart current map if no cycle is found
534                 nextmap = strzone(mapname);
535                 fh = fopen("maplist.cfg", FILE_READ);
536                 if (fh >= 0)
537                 {
538                         while (1)
539                         {
540                                 line = fgets(fh);
541                                 if (nextmap == mapname)
542                                 {
543                                         strunzone(nextmap);
544                                         nextmap = strzone(line);
545                                 }
546                                 if (!line)
547                                         break;
548                                 if (line == mapname)
549                                 {
550                                         line = fgets(fh);
551                                         if (!line)
552                                                 break;
553                                         strunzone(nextmap);
554                                         nextmap = strzone(line);
555                                         break;
556                                 }
557                         }
558                         fclose(fh);
559                 }
560                 changelevel (nextmap);
561                 strunzone(nextmap);*/
562
563                 // method 3
564                 /*
565                 s = cvar_string("g_maplist");
566                 nummaps = tokenize(s);
567                 // if no map list, restart current one
568                 nextmap = mapname;
569                 if (nummaps >= 1)
570                 {
571                         n = 0;
572                         while (n < nummaps)
573                         {
574                                 if (argv(n) == mapname)
575                                         break;
576                                 n = n + 1;
577                         }
578                         n = n + 1;
579                         if (n >= nummaps)
580                                 n = 0;
581                         nextmap = argv(n);
582                 }
583                 changelevel (nextmap);
584                 */
585         }
586 };
587
588
589 /*
590 ============
591 IntermissionThink
592
593 When the player presses attack or jump, change to the next level
594 ============
595 */
596 void() IntermissionThink =
597 {
598         if (time < intermission_exittime)
599                 return;
600
601         if (time < intermission_exittime + 10 && !self.button0 && !self.button1 && !self.button2 && !self.button3)
602                 return;
603
604         GotoNextMap ();
605 };
606
607 /*
608 ============
609 FindIntermission
610
611 Returns the entity to view from
612 ============
613 */
614 /*
615 entity() FindIntermission =
616 {
617         local   entity spot;
618         local   float cyc;
619
620 // look for info_intermission first
621         spot = find (world, classname, "info_intermission");
622         if (spot)
623         {       // pick a random one
624                 cyc = random() * 4;
625                 while (cyc > 1)
626                 {
627                         spot = find (spot, classname, "info_intermission");
628                         if (!spot)
629                                 spot = find (spot, classname, "info_intermission");
630                         cyc = cyc - 1;
631                 }
632                 return spot;
633         }
634
635 // then look for the start position
636         spot = find (world, classname, "info_player_start");
637         if (spot)
638                 return spot;
639
640 // testinfo_player_start is only found in regioned levels
641         spot = find (world, classname, "testplayerstart");
642         if (spot)
643                 return spot;
644
645 // then look for the start position
646         spot = find (world, classname, "info_player_deathmatch");
647         if (spot)
648                 return spot;
649
650         //objerror ("FindIntermission: no spot");
651         return world;
652 };
653 */
654
655 /*
656 ===============================================================================
657
658 RULES
659
660 ===============================================================================
661 */
662
663 void() DumpStats =
664 {
665         local float file;
666         local string s;
667
668         if(cvar("_printstats"))
669                 cvar_set("_printstats", "0");
670         else if(!gameover)
671                 return;
672
673         if(gameover)
674                 s = ":scores:";
675         else
676                 s = ":status:";
677
678         s = strcat(s, GetMapname(), ":", ftos(rint(time)));
679
680         if(cvar("sv_eventlog") && gameover)
681                 GameLogEcho(s, FALSE);
682         else if(cvar("sv_logscores_console"))
683                 ServerConsoleEcho(s, FALSE);
684         if(cvar("sv_logscores_file"))
685         {
686                 file = fopen(cvar_string("sv_logscores_filename"), FILE_APPEND);
687                 fputs(file, strcat(s, "\n"));
688         }
689
690         other = findchainflags(flags, FL_CLIENT);
691         while (other)
692         {
693                 if ((clienttype(other) == CLIENTTYPE_REAL) || (clienttype(other) == CLIENTTYPE_BOT && cvar("sv_logscores_bots")))
694                 {
695                         s = strcat(":player:", ftos(other.frags), ":");
696                         s = strcat(s, ftos(other.deaths), ":");
697                         s = strcat(s, ftos(rint(time - other.jointime)), ":");
698                         s = strcat(s, ftos(other.team), ":");
699
700                         if(cvar("sv_logscores_file"))
701                                 fputs(file, strcat(s, other.netname, "\n"));
702                         if(cvar("sv_eventlog") && gameover)
703                                 GameLogEcho(strcat(s, ftos(other.playerid), ":", other.netname), TRUE);
704                         else if(cvar("sv_logscores_console"))
705                                 ServerConsoleEcho(strcat(s, other.netname), TRUE);
706                 }
707                 other = other.chain;
708         }
709
710         if(cvar("sv_eventlog") && gameover)
711                 GameLogEcho(":end", FALSE);
712         else if(cvar("sv_logscores_console"))
713                 ServerConsoleEcho(":end", FALSE);
714         if(cvar("sv_logscores_file"))
715         {
716                 fputs(file, ":end\n");
717                 fclose(file);
718         }
719 }
720
721
722 /*
723 go to the next level for deathmatch
724 only called if a time or frag limit has expired
725 */
726 void() NextLevel =
727 {
728         gameover = TRUE;
729
730         intermission_running = 1;
731
732 // enforce a wait time before allowing changelevel
733         intermission_exittime = time + cvar("sv_mapchange_delay");
734
735         WriteByte (MSG_ALL, SVC_CDTRACK);
736         WriteByte (MSG_ALL, 3);
737         WriteByte (MSG_ALL, 3);
738
739         //pos = FindIntermission ();
740
741         VoteReset();
742
743         DumpStats();
744
745         if(cvar("sv_eventlog"))
746                 GameLogEcho(":gameover", FALSE);
747
748         GameLogClose();
749
750         other = findchainflags(flags, FL_CLIENT);
751         while (other != world)
752         {
753                 //other.nextthink = time + 0.5;
754                 other.takedamage = DAMAGE_NO;
755                 other.solid = SOLID_NOT;
756                 other.movetype = MOVETYPE_NONE;
757                 other.angles = other.v_angle;
758                 other.angles_x = other.angles_x * -1;
759
760                 self = other;
761                 weapon_action(other.weapon, WR_IDLE);
762
763                 if(clienttype(other) == CLIENTTYPE_REAL)
764                 if(cvar("sv_autoscreenshot"))
765                         stuffcmd(other, "screenshot\necho \"^5A screenshot has been taken at request of the server.\"\n");
766
767                 if(other.winning)
768                         bprint(strcat(other.netname, " wins.\n"));
769
770                 /*
771                 if (pos != world);
772                 {
773                         other.modelindex = 0;
774                         other.weaponentity = world; // remove weapon model
775                         other.view_ofs = '0 0 0';
776                         other.angles = other.v_angle = pos.mangle;
777                         if (!other.angles)
778                         {
779                                 other.angles = other.v_angle = pos.angles;
780                                 other.v_angle_x = other.v_angle_x * -1;
781                         }
782                         other.fixangle = TRUE;          // turn this way immediately
783                         setorigin (other, pos.origin);
784                 }
785                 */
786                 other = other.chain;
787         }
788
789         if(cvar("g_campaign"))
790                 CampaignPreIntermission();
791
792         WriteByte (MSG_ALL, SVC_INTERMISSION);
793 };
794
795 /*
796 ============
797 CheckRules_Player
798
799 Exit deathmatch games upon conditions
800 ============
801 */
802 void() CheckRules_Player =
803 {
804         if (gameover)   // someone else quit the game already
805                 return;
806
807         // fixme: don't check players; instead check dom_team and ctf_team entities
808         //   (div0: and that in CheckRules_World please)
809 };
810
811 float checkrules_oneminutewarning;
812 float checkrules_leaderfrags;
813 float tdm_max_score, tdm_old_score;
814
815 float checkrules_equality;
816 float checkrules_overtimewarning;
817 float checkrules_overtimeend;
818
819 void() InitiateOvertime =
820 {
821         if(!checkrules_overtimeend)
822                 checkrules_overtimeend = time + 60 * cvar("timelimit_maxovertime");
823 }
824
825 float WINNING_NO = 0; // no winner, but time limits may terminate the game
826 float WINNING_YES = 1; // winner found
827 float WINNING_NEVER = 2; // no winner, enter overtime if time limit is reached
828 float WINNING_STARTOVERTIME = 3; // no winner, enter overtime NOW
829
830 float(float fraglimitreached, float equality) GetWinningCode =
831 {
832         if(equality)
833                 if(fraglimitreached)
834                         return WINNING_STARTOVERTIME;
835                 else
836                         return WINNING_NEVER;
837         else
838                 if(fraglimitreached)
839                         return WINNING_YES;
840                 else
841                         return WINNING_NO;
842 }
843
844 // set the .winning flag for exactly those players with a given field value
845 void(.float field, float value) SetWinners =
846 {
847         entity head;
848         head = findchain(classname, "player");
849         while (head)
850         {
851                 head.winning = (head.field == value);
852                 head = head.chain;
853         }
854 }
855
856 // set the .winning flag for those players with a given field value
857 void(.float field, float value) AddWinners =
858 {
859         entity head;
860         head = findchain(classname, "player");
861         while (head)
862         {
863                 if(head.field == value)
864                         head.winning = 1;
865                 head = head.chain;
866         }
867 }
868
869 // clear the .winning flags
870 void(void) ClearWinners =
871 {
872         entity head;
873         head = findchain(classname, "player");
874         while (head)
875         {
876                 head.winning = 0;
877                 head = head.chain;
878         }
879 }
880
881 // LMS winning condition: game terminates if and only if there's at most one
882 // one player who's living lives. Top two scores being equal cancels the time
883 // limit.
884 float() WinningCondition_LMS =
885 {
886         entity head;
887
888         if(lms_dead_count < 0)
889                 lms_dead_count = 0;
890
891         if(player_count > 1 && lms_dead_count >= player_count - 1)
892                 return WINNING_YES; // He's the last man standing!
893
894         if((player_count == 1 && lms_dead_count == 1))
895                 return WINNING_YES; // All dead... (n:n is handled by the test above)
896
897         // When we get here, we have at least two players who are actually LIVING,
898         // or one player who is still waiting for a victim to join the server. Now
899         // check if the top two players have equal score.
900
901         checkrules_leaderfrags = 0;
902         head = findchain(classname, "player");
903         checkrules_equality = FALSE;
904         while (head)
905         {
906                 if(head.frags > checkrules_leaderfrags)
907                         checkrules_leaderfrags = head.frags;
908                 else if(head.frags > 0 && head.frags == checkrules_leaderfrags)
909                         checkrules_equality = TRUE;
910                 head = head.chain;
911         }
912
913         SetWinners(frags, checkrules_leaderfrags);
914
915         // The top two players have the same amount of lives? No timelimit then,
916         // enter overtime...
917
918         if(checkrules_equality)
919                 return WINNING_NEVER;
920
921         // Top two have different scores? Way to go for our beloved TIMELIMIT!
922         return WINNING_NO;
923 }
924
925 // DM winning condition: game terminates if a player reached the fraglimit,
926 // unless the first two players have the same score. The latter case also
927 // breaks the time limit.
928 float(float fraglimit) WinningCondition_MaxIndividualScore =
929 {
930         float checkrules_oldleaderfrags;
931         entity head;
932
933         checkrules_oldleaderfrags = checkrules_leaderfrags;
934         checkrules_leaderfrags = 0;
935         head = findchain(classname, "player");
936         checkrules_equality = FALSE;
937         while (head)
938         {
939                 if(head.frags > checkrules_leaderfrags)
940                         checkrules_leaderfrags = head.frags;
941                 else if(head.frags > 0 && head.frags == checkrules_leaderfrags)
942                         checkrules_equality = TRUE;
943                 head = head.chain;
944         }
945
946         SetWinners(frags, checkrules_leaderfrags);
947
948         if (!cvar("g_runematch"))
949                 if (checkrules_leaderfrags != checkrules_oldleaderfrags)
950                 {
951                         if (checkrules_leaderfrags == fraglimit - 1)
952                                 sound(world, CHAN_AUTO, "announcer/robotic/1fragleft.ogg", 1, ATTN_NONE);
953                         else if (checkrules_leaderfrags == fraglimit - 2)
954                                 sound(world, CHAN_AUTO, "announcer/robotic/2fragsleft.ogg", 1, ATTN_NONE);
955                         else if (checkrules_leaderfrags == fraglimit - 3)
956                                 sound(world, CHAN_AUTO, "announcer/robotic/3fragsleft.ogg", 1, ATTN_NONE);
957                 }
958
959         return GetWinningCode(fraglimit && checkrules_leaderfrags >= fraglimit, checkrules_equality);
960 }
961
962 float(float fraglimit) WinningConditionBase_Teamplay =
963 {
964         tdm_old_score = tdm_max_score;
965         tdm_max_score = max(team1_score, team2_score, team3_score, team4_score);
966
967         checkrules_equality =
968         (
969                 (tdm_max_score > 0)
970                 &&
971                 (
972                           (team1_score == tdm_max_score)
973                         + (team2_score == tdm_max_score)
974                         + (team3_score == tdm_max_score)
975                         + (team4_score == tdm_max_score)
976                         >= 2));
977
978         ClearWinners();
979         if(team1_score == tdm_max_score)
980                 AddWinners(team, COLOR_TEAM1);
981         if(team2_score == tdm_max_score)
982                 AddWinners(team, COLOR_TEAM2);
983         if(team3_score == tdm_max_score)
984                 AddWinners(team, COLOR_TEAM3);
985         if(team4_score == tdm_max_score)
986                 AddWinners(team, COLOR_TEAM4);
987
988         if(!cvar("g_runematch") && !cvar("g_domination"))
989                 if(tdm_max_score != tdm_old_score)
990                 {
991                         if(tdm_max_score == fraglimit - 1)
992                                 sound(world, CHAN_AUTO, "announcer/robotic/1fragleft.ogg", 1, ATTN_NONE);
993                         else if(tdm_max_score == fraglimit - 2)
994                                 sound(world, CHAN_AUTO, "announcer/robotic/2fragsleft.ogg", 1, ATTN_NONE);
995                         else if(tdm_max_score == fraglimit - 3)
996                                 sound(world, CHAN_AUTO, "announcer/robotic/3fragsleft.ogg", 1, ATTN_NONE);
997                 }
998
999         return GetWinningCode(fraglimit && tdm_max_score >= fraglimit, checkrules_equality);
1000 }
1001
1002 // TDM winning condition: game terminates if a team's score sum reached the
1003 // fraglimit, unless the first two teams have the same total score. The latter
1004 // case also breaks the time limit.
1005 float(float fraglimit) WinningCondition_MaxTeamSum =
1006 {
1007         entity head;
1008
1009         team1_score = team2_score = team3_score = team4_score = 0;
1010
1011         head = findchain(classname, "player");
1012         while (head)
1013         {
1014                 if(head.team == COLOR_TEAM1)
1015                         team1_score += head.frags;
1016                 else if(head.team == COLOR_TEAM2)
1017                         team2_score += head.frags;
1018                 else if(head.team == COLOR_TEAM3)
1019                         team3_score += head.frags;
1020                 else if(head.team == COLOR_TEAM4)
1021                         team4_score += head.frags;
1022                 head = head.chain;
1023         }
1024
1025         return WinningConditionBase_Teamplay(fraglimit);
1026 }
1027
1028 // DOM/CTF winning condition: game terminates if the max of a team's players'
1029 // score reached the fraglimit, unless the first two teams have the same
1030 // maximum score. The latter case also breaks the time limit.
1031 float(float fraglimit) WinningCondition_MaxTeamMax =
1032 {
1033         entity head;
1034
1035         team1_score = team2_score = team3_score = team4_score = 0;
1036
1037         head = findchain(classname, "player");
1038         while (head)
1039         {
1040                 if(head.team == COLOR_TEAM1)
1041                 {
1042                         if(head.frags > team1_score)
1043                                 team1_score = head.frags;
1044                 }
1045                 else if(head.team == COLOR_TEAM2)
1046                 {
1047                         if(head.frags > team2_score)
1048                                 team2_score = head.frags;
1049                 }
1050                 else if(head.team == COLOR_TEAM3)
1051                 {
1052                         if(head.frags > team3_score)
1053                                 team3_score = head.frags;
1054                 }
1055                 else if(head.team == COLOR_TEAM4)
1056                 {
1057                         if(head.frags > team4_score)
1058                                 team4_score = head.frags;
1059                 }
1060                 head = head.chain;
1061         }
1062
1063         return WinningConditionBase_Teamplay(fraglimit);
1064 }
1065
1066
1067 /*
1068 ============
1069 CheckRules_World
1070
1071 Exit deathmatch games upon conditions
1072 ============
1073 */
1074 void() CheckRules_World =
1075 {
1076         local float status;
1077         local float timelimit;
1078         local float fraglimit;
1079
1080         VoteThink();
1081
1082         SetDefaultAlpha();
1083
1084         if (intermission_running)
1085                 if (time >= intermission_exittime + 60)
1086                 {
1087                         GotoNextMap();
1088                         return;
1089                 }
1090
1091         if (gameover)   // someone else quit the game already
1092                 return;
1093
1094         DumpStats();
1095
1096         timelimit = cvar("timelimit") * 60;
1097         fraglimit = cvar("fraglimit");
1098
1099         if (timelimit && time >= timelimit)
1100                 InitiateOvertime();
1101
1102         if (checkrules_overtimeend && time >= checkrules_overtimeend)
1103         {
1104                 NextLevel();
1105                 return;
1106         }
1107
1108         if(!checkrules_overtimewarning && checkrules_overtimeend)
1109         {
1110                 checkrules_overtimewarning = TRUE;
1111                 //sound(world, CHAN_AUTO, "announcer/robotic/1minuteremains.ogg", 1, ATTN_NONE);
1112                 bcenterprint("^3Now playing ^1OVERTIME^3!\n\n^3Keep fragging until we have a ^1winner^3!");
1113         }
1114
1115         if (!checkrules_oneminutewarning && timelimit && time > timelimit - 60)
1116         {
1117                 checkrules_oneminutewarning = TRUE;
1118                 sound(world, CHAN_AUTO, "announcer/robotic/1minuteremains.ogg", 1, ATTN_NONE);
1119         }
1120
1121         status = WINNING_NO;
1122         if(cvar("g_lms"))
1123         {
1124                 status = WinningCondition_LMS();
1125         }
1126         else
1127         {
1128                 if(teams_matter)
1129                 {
1130                         if(cvar("g_tdm") || cvar("g_runematch"))
1131                                 status = WinningCondition_MaxTeamSum(fraglimit);
1132                         else if(cvar("g_ctf") || cvar("g_domination"))
1133                                 status = WinningCondition_MaxTeamMax(fraglimit);
1134                         else
1135                         {
1136                                 dprint("div0: How can this happen?\n");
1137                                 status = WinningCondition_MaxTeamMax(fraglimit);
1138                         }
1139                 }
1140                 else
1141                         status = WinningCondition_MaxIndividualScore(fraglimit);
1142         }
1143
1144         if(status == WINNING_STARTOVERTIME)
1145         {
1146                 status = WINNING_NEVER;
1147                 InitiateOvertime();
1148         }
1149
1150         if(checkrules_overtimeend)
1151                 if(status != WINNING_NEVER)
1152                         status = WINNING_YES;
1153
1154         if(status == WINNING_YES)
1155                 NextLevel();
1156 };