]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_domination.qc
75d0d7dc3ca21808ba3b43664c641b8a86c75741
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator / gamemode_domination.qc
1 #ifndef GAMEMODE_DOMINATION_H
2 #define GAMEMODE_DOMINATION_H
3
4 // score rule declarations
5 const float ST_DOM_TICKS = 1;
6 const float SP_DOM_TICKS = 4;
7 const float SP_DOM_TAKES = 5;
8 const float ST_DOM_CAPS = 1;
9 const float SP_DOM_CAPS = 4;
10
11 // pps: points per second
12 .float dom_total_pps;
13 .float dom_pps_red;
14 .float dom_pps_blue;
15 .float dom_pps_yellow;
16 .float dom_pps_pink;
17 float total_pps;
18 float pps_red;
19 float pps_blue;
20 float pps_yellow;
21 float pps_pink;
22
23 // capture declarations
24 .float enemy_playerid;
25 .entity sprite;
26 .float captime;
27
28 // misc globals
29 float domination_roundbased;
30 float domination_teams;
31 #endif
32
33 #ifdef IMPLEMENTATION
34
35 #include "../../teamplay.qh"
36
37 bool g_domination;
38
39 int autocvar_g_domination_default_teams;
40 bool autocvar_g_domination_disable_frags;
41 int autocvar_g_domination_point_amt;
42 bool autocvar_g_domination_point_fullbright;
43 int autocvar_g_domination_point_leadlimit;
44 bool autocvar_g_domination_roundbased;
45 int autocvar_g_domination_roundbased_point_limit;
46 float autocvar_g_domination_round_timelimit;
47 float autocvar_g_domination_warmup;
48 #define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
49 float autocvar_g_domination_point_rate;
50 int autocvar_g_domination_teams_override;
51
52 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
53 {
54         if(autocvar_sv_eventlog)
55                 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
56 }
57
58 void set_dom_state(entity e)
59 {
60         e.dom_total_pps = total_pps;
61         e.dom_pps_red = pps_red;
62         e.dom_pps_blue = pps_blue;
63         if(domination_teams >= 3)
64                 e.dom_pps_yellow = pps_yellow;
65         if(domination_teams >= 4)
66                 e.dom_pps_pink = pps_pink;
67 }
68
69 void dompoint_captured ()
70 {SELFPARAM();
71         entity head;
72         float old_delay, old_team, real_team;
73
74         // now that the delay has expired, switch to the latest team to lay claim to this point
75         head = self.owner;
76
77         real_team = self.cnt;
78         self.cnt = -1;
79
80         dom_EventLog("taken", self.team, self.dmg_inflictor);
81         self.dmg_inflictor = world;
82
83         self.goalentity = head;
84         self.model = head.mdl;
85         self.modelindex = head.dmg;
86         self.skin = head.skin;
87
88         float points, wait_time;
89         if (autocvar_g_domination_point_amt)
90                 points = autocvar_g_domination_point_amt;
91         else
92                 points = self.frags;
93         if (autocvar_g_domination_point_rate)
94                 wait_time = autocvar_g_domination_point_rate;
95         else
96                 wait_time = self.wait;
97
98         if(domination_roundbased)
99                 bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
100         else
101                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
102
103         if(self.enemy.playerid == self.enemy_playerid)
104                 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
105         else
106                 self.enemy = world;
107
108         if (head.noise != "")
109                 if(self.enemy)
110                         _sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
111                 else
112                         _sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
113         if (head.noise1 != "")
114                 play2all(head.noise1);
115
116         self.delay = time + wait_time;
117
118         // do trigger work
119         old_delay = self.delay;
120         old_team = self.team;
121         self.team = real_team;
122         self.delay = 0;
123         activator = self;
124         SUB_UseTargets ();
125         self.delay = old_delay;
126         self.team = old_team;
127
128         entity msg = WP_DomNeut;
129         switch(self.team)
130         {
131                 case NUM_TEAM_1: msg = WP_DomRed; break;
132                 case NUM_TEAM_2: msg = WP_DomBlue; break;
133                 case NUM_TEAM_3: msg = WP_DomYellow; break;
134                 case NUM_TEAM_4: msg = WP_DomPink; break;
135         }
136
137         WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null);
138
139         total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
140         for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
141         {
142                 if (autocvar_g_domination_point_amt)
143                         points = autocvar_g_domination_point_amt;
144                 else
145                         points = head.frags;
146                 if (autocvar_g_domination_point_rate)
147                         wait_time = autocvar_g_domination_point_rate;
148                 else
149                         wait_time = head.wait;
150                 switch(head.goalentity.team)
151                 {
152                         case NUM_TEAM_1:
153                                 pps_red += points/wait_time;
154                                 break;
155                         case NUM_TEAM_2:
156                                 pps_blue += points/wait_time;
157                                 break;
158                         case NUM_TEAM_3:
159                                 pps_yellow += points/wait_time;
160                                 break;
161                         case NUM_TEAM_4:
162                                 pps_pink += points/wait_time;
163                                 break;
164                 }
165                 total_pps += points/wait_time;
166         }
167
168         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
169         WaypointSprite_Ping(self.sprite);
170
171         self.captime = time;
172
173         FOR_EACH_REALCLIENT(head)
174                 set_dom_state(head);
175 }
176
177 void AnimateDomPoint()
178 {SELFPARAM();
179         if(self.pain_finished > time)
180                 return;
181         self.pain_finished = time + self.t_width;
182         if(self.nextthink > self.pain_finished)
183                 self.nextthink = self.pain_finished;
184
185         self.frame = self.frame + 1;
186         if(self.frame > self.t_length)
187                 self.frame = 0;
188 }
189
190 void dompointthink()
191 {SELFPARAM();
192         float fragamt;
193
194         self.nextthink = time + 0.1;
195
196         //self.frame = self.frame + 1;
197         //if(self.frame > 119)
198         //      self.frame = 0;
199         AnimateDomPoint();
200
201         // give points
202
203         if (gameover || self.delay > time || time < game_starttime)     // game has ended, don't keep giving points
204                 return;
205
206         if(autocvar_g_domination_point_rate)
207                 self.delay = time + autocvar_g_domination_point_rate;
208         else
209                 self.delay = time + self.wait;
210
211         // give credit to the team
212         // NOTE: this defaults to 0
213         if (!domination_roundbased)
214         if (self.goalentity.netname != "")
215         {
216                 if(autocvar_g_domination_point_amt)
217                         fragamt = autocvar_g_domination_point_amt;
218                 else
219                         fragamt = self.frags;
220                 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
221                 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
222
223                 // give credit to the individual player, if he is still there
224                 if (self.enemy.playerid == self.enemy_playerid)
225                 {
226                         PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
227                         PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
228                 }
229                 else
230                         self.enemy = world;
231         }
232 }
233
234 void dompointtouch()
235 {SELFPARAM();
236         entity head;
237         if (!IS_PLAYER(other))
238                 return;
239         if (other.health < 1)
240                 return;
241
242         if(round_handler_IsActive() && !round_handler_IsRoundStarted())
243                 return;
244
245         if(time < self.captime + 0.3)
246                 return;
247
248         // only valid teams can claim it
249         head = find(world, classname, "dom_team");
250         while (head && head.team != other.team)
251                 head = find(head, classname, "dom_team");
252         if (!head || head.netname == "" || head == self.goalentity)
253                 return;
254
255         // delay capture
256
257         self.team = self.goalentity.team; // this stores the PREVIOUS team!
258
259         self.cnt = other.team;
260         self.owner = head; // team to switch to after the delay
261         self.dmg_inflictor = other;
262
263         // self.state = 1;
264         // self.delay = time + cvar("g_domination_point_capturetime");
265         //self.nextthink = time + cvar("g_domination_point_capturetime");
266         //self.think = dompoint_captured;
267
268         // go to neutral team in the mean time
269         head = find(world, classname, "dom_team");
270         while (head && head.netname != "")
271                 head = find(head, classname, "dom_team");
272         if(head == world)
273                 return;
274
275         WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null);
276         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
277         WaypointSprite_Ping(self.sprite);
278
279         self.goalentity = head;
280         self.model = head.mdl;
281         self.modelindex = head.dmg;
282         self.skin = head.skin;
283
284         self.enemy = other; // individual player scoring
285         self.enemy_playerid = other.playerid;
286         dompoint_captured();
287 }
288
289 void dom_controlpoint_setup()
290 {SELFPARAM();
291         entity head;
292         // find the spawnfunc_dom_team representing unclaimed points
293         head = find(world, classname, "dom_team");
294         while(head && head.netname != "")
295                 head = find(head, classname, "dom_team");
296         if (!head)
297                 objerror("no spawnfunc_dom_team with netname \"\" found\n");
298
299         // copy important properties from spawnfunc_dom_team entity
300         self.goalentity = head;
301         _setmodel(self, head.mdl); // precision already set
302         self.skin = head.skin;
303
304         self.cnt = -1;
305
306         if(self.message == "")
307                 self.message = " has captured a control point";
308
309         if(self.frags <= 0)
310                 self.frags = 1;
311         if(self.wait <= 0)
312                 self.wait = 5;
313
314         float points, waittime;
315         if (autocvar_g_domination_point_amt)
316                 points = autocvar_g_domination_point_amt;
317         else
318                 points = self.frags;
319         if (autocvar_g_domination_point_rate)
320                 waittime = autocvar_g_domination_point_rate;
321         else
322                 waittime = self.wait;
323
324         total_pps += points/waittime;
325
326         if(!self.t_width)
327                 self.t_width = 0.02; // frame animation rate
328         if(!self.t_length)
329                 self.t_length = 239; // maximum frame
330
331         self.think = dompointthink;
332         self.nextthink = time;
333         self.touch = dompointtouch;
334         self.solid = SOLID_TRIGGER;
335         self.flags = FL_ITEM;
336         setsize(self, '-32 -32 -32', '32 32 32');
337         setorigin(self, self.origin + '0 0 20');
338         droptofloor();
339
340         waypoint_spawnforitem(self);
341         WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT);
342 }
343
344 float total_controlpoints;
345 void Domination_count_controlpoints()
346 {
347         entity e;
348         total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
349         for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
350         {
351                 ++total_controlpoints;
352                 redowned += (e.goalentity.team == NUM_TEAM_1);
353                 blueowned += (e.goalentity.team == NUM_TEAM_2);
354                 yellowowned += (e.goalentity.team == NUM_TEAM_3);
355                 pinkowned += (e.goalentity.team == NUM_TEAM_4);
356         }
357 }
358
359 float Domination_GetWinnerTeam()
360 {
361         float winner_team = 0;
362         if(redowned == total_controlpoints)
363                 winner_team = NUM_TEAM_1;
364         if(blueowned == total_controlpoints)
365         {
366                 if(winner_team) return 0;
367                 winner_team = NUM_TEAM_2;
368         }
369         if(yellowowned == total_controlpoints)
370         {
371                 if(winner_team) return 0;
372                 winner_team = NUM_TEAM_3;
373         }
374         if(pinkowned == total_controlpoints)
375         {
376                 if(winner_team) return 0;
377                 winner_team = NUM_TEAM_4;
378         }
379         if(winner_team)
380                 return winner_team;
381         return -1; // no control points left?
382 }
383
384 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
385 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
386 float Domination_CheckWinner()
387 {
388         if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
389         {
390                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
391                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
392                 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
393                 return 1;
394         }
395
396         Domination_count_controlpoints();
397
398         float winner_team = Domination_GetWinnerTeam();
399
400         if(winner_team == -1)
401                 return 0;
402
403         if(winner_team > 0)
404         {
405                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
406                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
407                 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
408         }
409         else if(winner_team == -1)
410         {
411                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
412                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
413         }
414
415         round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
416
417         return 1;
418 }
419
420 float Domination_CheckPlayers()
421 {
422         return 1;
423 }
424
425 void Domination_RoundStart()
426 {
427         entity e;
428         FOR_EACH_PLAYER(e)
429                 e.player_blocked = 0;
430 }
431
432 //go to best items, or control points you don't own
433 void havocbot_role_dom()
434 {SELFPARAM();
435         if(self.deadflag != DEAD_NO)
436                 return;
437
438         if (self.bot_strategytime < time)
439         {
440                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
441                 navigation_goalrating_start();
442                 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
443                 havocbot_goalrating_items(8000, self.origin, 8000);
444                 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
445                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
446                 navigation_goalrating_end();
447         }
448 }
449
450 MUTATOR_HOOKFUNCTION(dom, GetTeamCount)
451 {
452         // fallback?
453         ret_float = domination_teams;
454         ret_string = "dom_team";
455
456         entity head = find(world, classname, ret_string);
457         while(head)
458         {
459                 if(head.netname != "")
460                 {
461                         switch(head.team)
462                         {
463                                 case NUM_TEAM_1: c1 = 0; break;
464                                 case NUM_TEAM_2: c2 = 0; break;
465                                 case NUM_TEAM_3: c3 = 0; break;
466                                 case NUM_TEAM_4: c4 = 0; break;
467                         }
468                 }
469
470                 head = find(head, classname, ret_string);
471         }
472
473         ret_string = string_null;
474
475         return true;
476 }
477
478 MUTATOR_HOOKFUNCTION(dom, reset_map_players)
479 {SELFPARAM();
480         total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
481         entity e;
482         FOR_EACH_PLAYER(e)
483         {
484                 setself(e);
485                 PutClientInServer();
486                 self.player_blocked = 1;
487                 if(IS_REAL_CLIENT(self))
488                         set_dom_state(self);
489         }
490         return 1;
491 }
492
493 MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
494 {SELFPARAM();
495         if(domination_roundbased)
496         if(!round_handler_IsRoundStarted())
497                 self.player_blocked = 1;
498         else
499                 self.player_blocked = 0;
500         return false;
501 }
502
503 MUTATOR_HOOKFUNCTION(dom, ClientConnect)
504 {SELFPARAM();
505         set_dom_state(self);
506         return false;
507 }
508
509 MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
510 {SELFPARAM();
511         self.havocbot_role = havocbot_role_dom;
512         return true;
513 }
514
515 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
516 Control point for Domination gameplay.
517 */
518 spawnfunc(dom_controlpoint)
519 {
520         if(!g_domination)
521         {
522                 remove(self);
523                 return;
524         }
525         self.think = dom_controlpoint_setup;
526         self.nextthink = time + 0.1;
527         self.reset = dom_controlpoint_setup;
528
529         if(!self.scale)
530                 self.scale = 0.6;
531
532         self.effects = self.effects | EF_LOWPRECISION;
533         if (autocvar_g_domination_point_fullbright)
534                 self.effects |= EF_FULLBRIGHT;
535 }
536
537 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
538 Team declaration for Domination gameplay, this allows you to decide what team
539 names and control point models are used in your map.
540
541 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
542 can have netname set!  The nameless team owns all control points at start.
543
544 Keys:
545 "netname"
546  Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
547 "cnt"
548  Scoreboard color of the team (for example 4 is red and 13 is blue)
549 "model"
550  Model to use for control points owned by this team (for example
551  "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
552  keycard)
553 "skin"
554  Skin of the model to use (for team skins on a single model)
555 "noise"
556  Sound to play when this team captures a point.
557  (this is a localized sound, like a small alarm or other effect)
558 "noise1"
559  Narrator speech to play when this team captures a point.
560  (this is a global sound, like "Red team has captured a control point")
561 */
562
563 spawnfunc(dom_team)
564 {
565         if(!g_domination || autocvar_g_domination_teams_override >= 2)
566         {
567                 remove(self);
568                 return;
569         }
570         precache_model(self.model);
571         if (self.noise != "")
572                 precache_sound(self.noise);
573         if (self.noise1 != "")
574                 precache_sound(self.noise1);
575         self.classname = "dom_team";
576         _setmodel(self, self.model); // precision not needed
577         self.mdl = self.model;
578         self.dmg = self.modelindex;
579         self.model = "";
580         self.modelindex = 0;
581         // this would have to be changed if used in quakeworld
582         if(self.cnt)
583                 self.team = self.cnt + 1; // WHY are these different anyway?
584 }
585
586 // scoreboard setup
587 void ScoreRules_dom(float teams)
588 {
589         if(domination_roundbased)
590         {
591                 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
592                 ScoreInfo_SetLabel_TeamScore  (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
593                 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
594                 ScoreRules_basics_end();
595         }
596         else
597         {
598                 float sp_domticks, sp_score;
599                 sp_score = sp_domticks = 0;
600                 if(autocvar_g_domination_disable_frags)
601                         sp_domticks = SFL_SORT_PRIO_PRIMARY;
602                 else
603                         sp_score = SFL_SORT_PRIO_PRIMARY;
604                 ScoreRules_basics(teams, sp_score, sp_score, true);
605                 ScoreInfo_SetLabel_TeamScore  (ST_DOM_TICKS,    "ticks",     sp_domticks);
606                 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS,    "ticks",     sp_domticks);
607                 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES,    "takes",     0);
608                 ScoreRules_basics_end();
609         }
610 }
611
612 // code from here on is just to support maps that don't have control point and team entities
613 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
614 {SELFPARAM();
615         setself(spawn());
616         self.classname = "dom_team";
617         self.netname = teamname;
618         self.cnt = teamcolor;
619         self.model = pointmodel;
620         self.skin = pointskin;
621         self.noise = capsound;
622         self.noise1 = capnarration;
623         self.message = capmessage;
624
625         // this code is identical to spawnfunc_dom_team
626         _setmodel(self, self.model); // precision not needed
627         self.mdl = self.model;
628         self.dmg = self.modelindex;
629         self.model = "";
630         self.modelindex = 0;
631         // this would have to be changed if used in quakeworld
632         self.team = self.cnt + 1;
633
634         //eprint(self);
635         setself(this);
636 }
637
638 void _spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); }
639 void dom_spawnpoint(vector org)
640 {SELFPARAM();
641         setself(spawn());
642         self.classname = "dom_controlpoint";
643         self.think = _spawnfunc_dom_controlpoint;
644         self.nextthink = time;
645         setorigin(self, org);
646         spawnfunc_dom_controlpoint(this);
647         setself(this);
648 }
649
650 // spawn some default teams if the map is not set up for domination
651 void dom_spawnteams(float teams)
652 {
653         dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND(DOM_CLAIM), "", "Red team has captured a control point");
654         dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND(DOM_CLAIM), "", "Blue team has captured a control point");
655         if(teams >= 3)
656                 dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND(DOM_CLAIM), "", "Yellow team has captured a control point");
657         if(teams >= 4)
658                 dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND(DOM_CLAIM), "", "Pink team has captured a control point");
659         dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
660 }
661
662 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
663 {
664         // if no teams are found, spawn defaults
665         if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
666         {
667                 LOG_INFO("No ""dom_team"" entities found on this map, creating them anyway.\n");
668                 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
669                 dom_spawnteams(domination_teams);
670         }
671
672         CheckAllowedTeams(world);
673         domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
674
675         addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
676         addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
677         addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
678         if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
679         if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
680
681         domination_roundbased = autocvar_g_domination_roundbased;
682
683         ScoreRules_dom(domination_teams);
684
685         if(domination_roundbased)
686         {
687                 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
688                 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
689         }
690 }
691
692 void dom_Initialize()
693 {
694         g_domination = true;
695         InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
696 }
697
698
699 REGISTER_MUTATOR(dom, IS_GAMETYPE(DOMINATION))
700 {
701         int fraglimit_override = autocvar_g_domination_point_limit;
702         if(autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
703                 fraglimit_override = autocvar_g_domination_roundbased_point_limit;
704
705         ActivateTeamplay();
706         SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, -1, -1);
707         have_team_spawns = -1; // request team spawns
708
709         MUTATOR_ONADD
710         {
711                 if(time > 1) // game loads at time 1
712                         error("This is a game type and it cannot be added at runtime.");
713                 dom_Initialize();
714         }
715
716         MUTATOR_ONREMOVE
717         {
718                 LOG_INFO("This is a game type and it cannot be removed at runtime.");
719                 return -1;
720         }
721
722         return 0;
723 }
724 #endif