]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/domination.qc
Merge remote-tracking branch 'origin/terencehill/newpanelhud' into fruitiex/panelhud
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / domination.qc
1
2 /*
3 Domination as a plugin for netquake mods
4 by LordHavoc (lordhavoc@ghdigital.com)
5
6 How to add domination points to a mod:
7 1. Add this line to progs.src above world.qc:
8 domination.qc
9 2. Comment out all lines in ClientObituary in client.qc that begin with targ.frags  or attacker.frags.
10 3. Add this above spawnfunc_worldspawn in world.qc:
11 void() dom_init;
12 4. Add this line to the end of spawnfunc_worldspawn in world.qc:
13 dom_init();
14
15 Note: The only teams who can use dom control points are identified by spawnfunc_dom_team entities (if none exist these default to red and blue and use only quake models/sounds).
16 */
17
18 #define DOMPOINTFRAGS frags
19
20 .float enemy_playerid;
21 .entity sprite;
22 .float captime;
23
24 // pps: points per second
25 .float dom_total_pps;
26 .float dom_pps_red;
27 .float dom_pps_blue;
28 .float dom_pps_yellow;
29 .float dom_pps_pink;
30 float total_pps;
31 float pps_red;
32 float pps_blue;
33 float pps_yellow;
34 float pps_pink;
35 void set_dom_state(entity e)
36 {
37         // BIG ugly hack to make stat sending work
38         e.dom_total_pps = total_pps;
39         e.dom_pps_red = pps_red;
40         e.dom_pps_blue = pps_blue;
41         if(c3 >= 0)
42                 e.dom_pps_yellow = pps_yellow;
43         if(c4 >= 0)
44                 e.dom_pps_pink = pps_pink;
45 }
46
47 void() dom_controlpoint_setup;
48
49 void LogDom(string mode, float team_before, entity actor)
50 {
51         string s;
52         if(!autocvar_sv_eventlog)
53                 return;
54         s = strcat(":dom:", mode);
55         s = strcat(s, ":", ftos(team_before));
56         s = strcat(s, ":", ftos(actor.playerid));
57         GameLogEcho(s);
58 }
59
60 void() dom_spawnteams;
61
62 void dompoint_captured ()
63 {
64         local entity head;
65         local float old_delay, old_team, real_team;
66
67         // now that the delay has expired, switch to the latest team to lay claim to this point
68         head = self.owner;
69
70         real_team = self.cnt;
71         self.cnt = -1;
72
73         LogDom("taken", self.team, self.dmg_inflictor);
74         self.dmg_inflictor = world;
75
76         self.goalentity = head;
77         self.model = head.mdl;
78         self.modelindex = head.dmg;
79         self.skin = head.skin;
80
81         //bprint(head.message);
82         //bprint("\n");
83
84         //bprint(^3head.netname);
85         //bprint(head.netname);
86         //bprint(self.message);
87         //bprint("\n");
88
89         float points, wait_time;
90         if (autocvar_g_domination_point_amt)
91                 points = autocvar_g_domination_point_amt;
92         else
93                 points = self.frags;
94         if (autocvar_g_domination_point_rate)
95                 wait_time = autocvar_g_domination_point_rate;
96         else
97                 wait_time = self.wait;
98
99         bprint("^3", head.netname, "^3", self.message);
100         if (points != 1)
101                 bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
102         else
103                 bprint(" ^7(", ftos(points), " point every ", ftos(wait_time), " seconds)\n");
104
105         if(self.enemy.playerid == self.enemy_playerid)
106                 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
107         else
108                 self.enemy = world;
109
110         if (head.noise != "")
111                 if(self.enemy)
112                         sound(self.enemy, CHAN_AUTO, head.noise, VOL_BASE, ATTN_NORM);
113                 else
114                         sound(self, CHAN_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
115         if (head.noise1 != "")
116                 play2all(head.noise1);
117
118         //self.nextthink = time + autocvar_g_domination_point_rate;
119         //self.think = dompointthink;
120
121         self.delay = time + wait_time;
122
123         // do trigger work
124         old_delay = self.delay;
125         old_team = self.team;
126         self.team = real_team;
127         self.delay = 0;
128         activator = self;
129         SUB_UseTargets ();
130         self.delay = old_delay;
131         self.team = old_team;
132
133         switch(self.team)
134         {
135                 // "fix" pps when slightly under 0 because of approximation errors
136                 case COLOR_TEAM1:
137                         pps_red -= (points/wait_time);
138                         if (pps_red < 0) pps_red = 0;
139                         break;
140                 case COLOR_TEAM2:
141                         pps_blue -= (points/wait_time);
142                         if (pps_blue < 0) pps_blue = 0;
143                         break;
144                 case COLOR_TEAM3:
145                         pps_yellow -= (points/wait_time);
146                         if (pps_yellow < 0) pps_yellow = 0;
147                         break;
148                 case COLOR_TEAM4:
149                         pps_pink -= (points/wait_time);
150                         if (pps_pink < 0) pps_pink = 0;
151         }
152
153         switch(self.goalentity.team)
154         {
155                 // "fix" pps when slightly over total_pps because of approximation errors
156                 case COLOR_TEAM1:
157                         pps_red += (points/wait_time);
158                         if (pps_red > total_pps) pps_red = total_pps;
159                         WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
160                         break;
161                 case COLOR_TEAM2:
162                         pps_blue += (points/wait_time);
163                         if (pps_blue > total_pps) pps_blue = total_pps;
164                         WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
165                         break;
166                 case COLOR_TEAM3:
167                         pps_yellow += (points/wait_time);
168                         if (pps_yellow > total_pps) pps_yellow = total_pps;
169                         WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
170                         break;
171                 case COLOR_TEAM4:
172                         pps_pink += (points/wait_time);
173                         if (pps_pink > total_pps) pps_pink = total_pps;
174                         WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
175         }
176
177         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
178         WaypointSprite_Ping(self.sprite);
179
180         self.captime = time;
181
182         FOR_EACH_PLAYER(head)
183                 set_dom_state(head);
184 };
185
186 void AnimateDomPoint()
187 {
188         if(self.pain_finished > time)
189                 return;
190         self.pain_finished = time + self.t_width;
191         if(self.nextthink > self.pain_finished)
192                 self.nextthink = self.pain_finished;
193
194         self.frame = self.frame + 1;
195         if(self.frame > self.t_length)
196                 self.frame = 0;
197 }
198
199 void dompointthink()
200 {
201         local float fragamt;
202
203         self.nextthink = time + 0.1;
204
205         //self.frame = self.frame + 1;
206         //if(self.frame > 119)
207         //      self.frame = 0;
208         AnimateDomPoint();
209
210         // give points
211
212         if (gameover || self.delay > time || time < game_starttime)     // game has ended, don't keep giving points
213                 return;
214
215         if(autocvar_g_domination_point_rate)
216                 self.delay = time + autocvar_g_domination_point_rate;
217         else
218                 self.delay = time + self.wait;
219
220         // give credit to the team
221         // NOTE: this defaults to 0
222         if (self.goalentity.netname != "")
223         {
224                 if(autocvar_g_domination_point_amt)
225                         fragamt = autocvar_g_domination_point_amt;
226                 else
227                         fragamt = self.DOMPOINTFRAGS;
228                 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
229                 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
230
231                 // give credit to the individual player, if he is still there
232                 if (self.enemy.playerid == self.enemy_playerid)
233                 {
234                         PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
235                         PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
236                 }
237                 else
238                         self.enemy = world;
239         }
240 }
241
242 void dompointtouch()
243 {
244         local entity head;
245         if (other.classname != "player")
246                 return;
247         if (other.health < 1)
248                 return;
249
250         if(time < self.captime + 0.3)
251                 return;
252
253         // only valid teams can claim it
254         head = find(world, classname, "dom_team");
255         while (head && head.team != other.team)
256                 head = find(head, classname, "dom_team");
257         if (!head || head.netname == "" || head == self.goalentity)
258                 return;
259
260         // delay capture
261
262         self.team = self.goalentity.team; // this stores the PREVIOUS team!
263
264         self.cnt = other.team;
265         self.owner = head; // team to switch to after the delay
266         self.dmg_inflictor = other;
267
268         // self.state = 1;
269         // self.delay = time + cvar("g_domination_point_capturetime");
270         //self.nextthink = time + cvar("g_domination_point_capturetime");
271         //self.think = dompoint_captured;
272
273         // go to neutral team in the mean time
274         head = find(world, classname, "dom_team");
275         while (head && head.netname != "")
276                 head = find(head, classname, "dom_team");
277         if(head == world)
278                 return;
279
280         WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
281         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
282         WaypointSprite_Ping(self.sprite);
283
284         self.goalentity = head;
285         self.model = head.mdl;
286         self.modelindex = head.dmg;
287         self.skin = head.skin;
288
289         self.enemy = other; // individual player scoring
290         self.enemy_playerid = other.playerid;
291         dompoint_captured();
292 };
293
294 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
295 Team declaration for Domination gameplay, this allows you to decide what team
296 names and control point models are used in your map.
297
298 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
299 can have netname set!  The nameless team owns all control points at start.
300
301 Keys:
302 "netname"
303  Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
304 "cnt"
305  Scoreboard color of the team (for example 4 is red and 13 is blue)
306 "model"
307  Model to use for control points owned by this team (for example
308  "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
309  keycard)
310 "skin"
311  Skin of the model to use (for team skins on a single model)
312 "noise"
313  Sound to play when this team captures a point.
314  (this is a localized sound, like a small alarm or other effect)
315 "noise1"
316  Narrator speech to play when this team captures a point.
317  (this is a global sound, like "Red team has captured a control point")
318 */
319
320 void spawnfunc_dom_team()
321 {
322         if(!g_domination || autocvar_g_domination_teams_override >= 2)
323         {
324                 remove(self);
325                 return;
326         }
327         precache_model(self.model);
328         if (self.noise != "")
329                 precache_sound(self.noise);
330         if (self.noise1 != "")
331                 precache_sound(self.noise1);
332         self.classname = "dom_team";
333         setmodel(self, self.model); // precision not needed
334         self.mdl = self.model;
335         self.dmg = self.modelindex;
336         self.model = "";
337         self.modelindex = 0;
338         // this would have to be changed if used in quakeworld
339         if(self.cnt)
340                 self.team = self.cnt + 1; // WHY are these different anyway?
341 };
342
343 void dom_controlpoint_setup()
344 {
345         local entity head;
346         // find the spawnfunc_dom_team representing unclaimed points
347         head = find(world, classname, "dom_team");
348         while(head && head.netname != "")
349                 head = find(head, classname, "dom_team");
350         if (!head)
351                 objerror("no spawnfunc_dom_team with netname \"\" found\n");
352
353         // copy important properties from spawnfunc_dom_team entity
354         self.goalentity = head;
355         setmodel(self, head.mdl); // precision already set
356         self.skin = head.skin;
357
358         self.cnt = -1;
359
360         if(!self.message)
361                 self.message = " has captured a control point";
362
363         if(self.DOMPOINTFRAGS <= 0)
364                 self.DOMPOINTFRAGS = 1;
365         if(self.wait <= 0)
366                 self.wait = 5;
367
368         float points, waittime;
369         if (autocvar_g_domination_point_rate)
370                 points = autocvar_g_domination_point_rate;
371         else
372                 points = self.frags;
373         if (autocvar_g_domination_point_amt)
374                 waittime = autocvar_g_domination_point_amt;
375         else
376                 waittime = self.wait;
377
378         total_pps += points/waittime;
379
380         if(!self.t_width)
381                 self.t_width = 0.02; // frame animation rate
382         if(!self.t_length)
383                 self.t_length = 239; // maximum frame
384
385         self.think = dompointthink;
386         self.nextthink = time;
387         self.touch = dompointtouch;
388         self.solid = SOLID_TRIGGER;
389         self.flags = FL_ITEM;
390         setsize(self, '-32 -32 -32', '32 32 32');
391         setorigin(self, self.origin + '0 0 20');
392         droptofloor();
393
394         waypoint_spawnforitem(self);
395         WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite);
396         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
397 };
398
399
400
401 // player has joined game, get him on a team
402 // depreciated
403 /*void dom_player_join_team(entity pl)
404 {
405         entity head;
406         float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
407         float balance_teams, force_balance, balance_type;
408
409         balance_teams = autocvar_g_balance_teams;
410         balance_teams = autocvar_g_balance_teams_force;
411
412         c1 = c2 = c3 = c4 = -1;
413         totalteams = 0;
414
415         // first find out what teams are allowed
416         head = find(world, classname, "dom_team");
417         while(head)
418         {
419                 if(head.netname != "")
420                 {
421                         //if(head.team == pl.team)
422                         //      selected = head;
423                         if(head.team == COLOR_TEAM1)
424                         {
425                                         c1 = 0;
426                         }
427                         if(head.team == COLOR_TEAM2)
428                         {
429                                         c2 = 0;
430                         }
431                         if(head.team == COLOR_TEAM3)
432                         {
433                                         c3 = 0;
434                         }
435                         if(head.team == COLOR_TEAM4)
436                         {
437                                         c4 = 0;
438                         }
439                 }
440                 head = find(head, classname, "dom_team");
441         }
442
443         // make sure there are at least 2 teams to join
444         if(c1 >= 0)
445                 totalteams = totalteams + 1;
446         if(c2 >= 0)
447                 totalteams = totalteams + 1;
448         if(c3 >= 0)
449                 totalteams = totalteams + 1;
450         if(c4 >= 0)
451                 totalteams = totalteams + 1;
452
453         if(totalteams <= 1)
454                 error("dom_player_join_team: Too few teams available for domination\n");
455
456         // whichever teams that are available are set to 0 instead of -1
457
458         // if we don't care what team he ends up on, put him on whatever team he entered as.
459         // if he's not on a valid team, then put him on the smallest team
460         if(!balance_teams && !force_balance)
461         {
462                 if(     c1 >= 0 && pl.team == COLOR_TEAM1)
463                         selectedteam = pl.team;
464                 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
465                         selectedteam = pl.team;
466                 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
467                         selectedteam = pl.team;
468                 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
469                         selectedteam = pl.team;
470                 else
471                         selectedteam = -1;
472                 if(selectedteam > 0)
473                 {
474                         SetPlayerColors(pl, selectedteam - 1);
475                         return;
476                 }
477                 // otherwise end up on the smallest team (handled below)
478         }
479
480         // now count how many players are on each team already
481
482         head = find(world, classname, "player");
483         while(head)
484         {
485                 //if(head.netname != "")
486                 {
487                         if(head.team == COLOR_TEAM1)
488                         {
489                                 if(c1 >= 0)
490                                         c1 = c1 + 1;
491                         }
492                         if(head.team == COLOR_TEAM2)
493                         {
494                                 if(c2 >= 0)
495                                         c2 = c2 + 1;
496                         }
497                         if(head.team == COLOR_TEAM3)
498                         {
499                                 if(c3 >= 0)
500                                         c3 = c3 + 1;
501                         }
502                         if(head.team == COLOR_TEAM4)
503                         {
504                                 if(c4 >= 0)
505                                         c4 = c4 + 1;
506                         }
507                 }
508                 head = find(head, classname, "player");
509         }
510
511         // c1...c4 now have counts of each team
512         // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
513
514         smallestteam = 0;
515         smallestteam_count = 999;
516
517         // 2 gives priority to what team you're already on, 1 goes in order
518         balance_type = 1;
519
520         if(balance_type == 1)
521         {
522                 if(c1 >= 0 && c1 < smallestteam_count)
523                 {
524                         smallestteam = 1;
525                         smallestteam_count = c1;
526                 }
527                 if(c2 >= 0 && c2 < smallestteam_count)
528                 {
529                         smallestteam = 2;
530                         smallestteam_count = c2;
531                 }
532                 if(c3 >= 0 && c3 < smallestteam_count)
533                 {
534                         smallestteam = 3;
535                         smallestteam_count = c3;
536                 }
537                 if(c4 >= 0 && c4 < smallestteam_count)
538                 {
539                         smallestteam = 4;
540                         smallestteam_count = c4;
541                 }
542         }
543         else
544         {
545                 if(c1 >= 0 && (c1 < smallestteam_count ||
546                                         (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
547                 {
548                         smallestteam = 1;
549                         smallestteam_count = c1;
550                 }
551                 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
552                                         (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
553                 {
554                         smallestteam = 2;
555                         smallestteam_count = c2;
556                 }
557                 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
558                                         (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
559                 {
560                         smallestteam = 3;
561                         smallestteam_count = c3;
562                 }
563                 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
564                                         (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
565                 {
566                         smallestteam = 4;
567                         smallestteam_count = c4;
568                 }
569         }
570
571         if(smallestteam == 1)
572         {
573                 selectedteam = COLOR_TEAM1 - 1;
574         }
575         if(smallestteam == 2)
576         {
577                 selectedteam = COLOR_TEAM2 - 1;
578         }
579         if(smallestteam == 3)
580         {
581                 selectedteam = COLOR_TEAM3 - 1;
582         }
583         if(smallestteam == 4)
584         {
585                 selectedteam = COLOR_TEAM4 - 1;
586         }
587
588         SetPlayerColors(pl, selectedteam);
589 }
590 */
591 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
592 Control point for Domination gameplay.
593 */
594 void spawnfunc_dom_controlpoint()
595 {
596         if(!g_domination)
597         {
598                 remove(self);
599                 return;
600         }
601         self.think = dom_controlpoint_setup;
602         self.nextthink = time + 0.1;
603         self.reset = dom_controlpoint_setup;
604
605         if(!self.scale)
606                 self.scale = 0.6;
607
608         //if(!self.glow_size)
609         //      self.glow_size = cvar("g_domination_point_glow");
610         self.effects = self.effects | EF_LOWPRECISION;
611         if (autocvar_g_domination_point_fullbright)
612                 self.effects |= EF_FULLBRIGHT;
613 };
614
615 // code from here on is just to support maps that don't have control point and team entities
616 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
617 {
618         local entity oldself;
619         oldself = self;
620         self = spawn();
621         self.classname = "dom_team";
622         self.netname = teamname;
623         self.cnt = teamcolor;
624         self.model = pointmodel;
625         self.skin = pointskin;
626         self.noise = capsound;
627         self.noise1 = capnarration;
628         self.message = capmessage;
629
630         // this code is identical to spawnfunc_dom_team
631         setmodel(self, self.model); // precision not needed
632         self.mdl = self.model;
633         self.dmg = self.modelindex;
634         self.model = "";
635         self.modelindex = 0;
636         // this would have to be changed if used in quakeworld
637         self.team = self.cnt + 1;
638
639         //eprint(self);
640         self = oldself;
641 };
642
643 void dom_spawnpoint(vector org)
644 {
645         local entity oldself;
646         oldself = self;
647         self = spawn();
648         self.classname = "dom_controlpoint";
649         self.think = spawnfunc_dom_controlpoint;
650         self.nextthink = time;
651         setorigin(self, org);
652         spawnfunc_dom_controlpoint();
653         self = oldself;
654 };
655
656 // spawn some default teams if the map is not set up for domination
657 void dom_spawnteams()
658 {
659         float numteams;
660         if(autocvar_g_domination_teams_override < 2)
661                 numteams = autocvar_g_domination_default_teams;
662         else
663                 numteams = autocvar_g_domination_teams_override;
664         // LordHavoc: edit this if you want to change defaults
665         dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
666         dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
667         if(numteams > 2)
668                 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
669         if(numteams > 3)
670                 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
671         dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
672 };
673
674 void dom_delayedinit()
675 {
676         local entity head;
677
678         // if no teams are found, spawn defaults, if custom teams are set, use them
679         if (find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
680                 dom_spawnteams();
681         // if no control points are found, spawn defaults
682         if (find(world, classname, "dom_controlpoint") == world)
683         {
684                 // here follow default domination points for each map
685                 /*
686                 if (world.model == "maps/e1m1.bsp")
687                 {
688                         dom_spawnpoint('0 0 0');
689                 }
690                 else
691                 */
692                 {
693                         // if no supported map was found, make every deathmatch spawn a point
694                         head = find(world, classname, "info_player_deathmatch");
695                         while (head)
696                         {
697                                 dom_spawnpoint(head.origin);
698                                 head = find(head, classname, "info_player_deathmatch");
699                         }
700                 }
701         }
702
703         ScoreRules_dom();
704 };
705
706 void dom_init()
707 {
708         // we have to precache default models/sounds even if they might not be
709         // used because spawnfunc_worldspawn is executed before any other entities are read,
710         // so we don't even know yet if this map is set up for domination...
711         precache_model("models/domination/dom_red.md3");
712         precache_model("models/domination/dom_blue.md3");
713         precache_model("models/domination/dom_yellow.md3");
714         precache_model("models/domination/dom_pink.md3");
715         precache_model("models/domination/dom_unclaimed.md3");
716         precache_sound("domination/claim.wav");
717         InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
718
719         addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
720         addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
721         addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
722         if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
723         if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
724 };
725