]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/domination.qc
Merge branch 'master' into terencehill/centerprint_stuff
[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, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
113                 else
114                         sound(self, CH_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_REALCLIENT(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, RADARICON_DOMPOINT, '0 1 1');
396 };
397
398
399
400 // player has joined game, get him on a team
401 // depreciated
402 /*void dom_player_join_team(entity pl)
403 {
404         entity head;
405         float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
406         float balance_teams, force_balance, balance_type;
407
408         balance_teams = autocvar_g_balance_teams;
409         balance_teams = autocvar_g_balance_teams_force;
410
411         c1 = c2 = c3 = c4 = -1;
412         totalteams = 0;
413
414         // first find out what teams are allowed
415         head = find(world, classname, "dom_team");
416         while(head)
417         {
418                 if(head.netname != "")
419                 {
420                         //if(head.team == pl.team)
421                         //      selected = head;
422                         if(head.team == COLOR_TEAM1)
423                         {
424                                         c1 = 0;
425                         }
426                         if(head.team == COLOR_TEAM2)
427                         {
428                                         c2 = 0;
429                         }
430                         if(head.team == COLOR_TEAM3)
431                         {
432                                         c3 = 0;
433                         }
434                         if(head.team == COLOR_TEAM4)
435                         {
436                                         c4 = 0;
437                         }
438                 }
439                 head = find(head, classname, "dom_team");
440         }
441
442         // make sure there are at least 2 teams to join
443         if(c1 >= 0)
444                 totalteams = totalteams + 1;
445         if(c2 >= 0)
446                 totalteams = totalteams + 1;
447         if(c3 >= 0)
448                 totalteams = totalteams + 1;
449         if(c4 >= 0)
450                 totalteams = totalteams + 1;
451
452         if(totalteams <= 1)
453                 error("dom_player_join_team: Too few teams available for domination\n");
454
455         // whichever teams that are available are set to 0 instead of -1
456
457         // if we don't care what team he ends up on, put him on whatever team he entered as.
458         // if he's not on a valid team, then put him on the smallest team
459         if(!balance_teams && !force_balance)
460         {
461                 if(     c1 >= 0 && pl.team == COLOR_TEAM1)
462                         selectedteam = pl.team;
463                 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
464                         selectedteam = pl.team;
465                 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
466                         selectedteam = pl.team;
467                 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
468                         selectedteam = pl.team;
469                 else
470                         selectedteam = -1;
471                 if(selectedteam > 0)
472                 {
473                         SetPlayerColors(pl, selectedteam - 1);
474                         return;
475                 }
476                 // otherwise end up on the smallest team (handled below)
477         }
478
479         // now count how many players are on each team already
480
481         head = find(world, classname, "player");
482         while(head)
483         {
484                 //if(head.netname != "")
485                 {
486                         if(head.team == COLOR_TEAM1)
487                         {
488                                 if(c1 >= 0)
489                                         c1 = c1 + 1;
490                         }
491                         if(head.team == COLOR_TEAM2)
492                         {
493                                 if(c2 >= 0)
494                                         c2 = c2 + 1;
495                         }
496                         if(head.team == COLOR_TEAM3)
497                         {
498                                 if(c3 >= 0)
499                                         c3 = c3 + 1;
500                         }
501                         if(head.team == COLOR_TEAM4)
502                         {
503                                 if(c4 >= 0)
504                                         c4 = c4 + 1;
505                         }
506                 }
507                 head = find(head, classname, "player");
508         }
509
510         // c1...c4 now have counts of each team
511         // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
512
513         smallestteam = 0;
514         smallestteam_count = 999;
515
516         // 2 gives priority to what team you're already on, 1 goes in order
517         balance_type = 1;
518
519         if(balance_type == 1)
520         {
521                 if(c1 >= 0 && c1 < smallestteam_count)
522                 {
523                         smallestteam = 1;
524                         smallestteam_count = c1;
525                 }
526                 if(c2 >= 0 && c2 < smallestteam_count)
527                 {
528                         smallestteam = 2;
529                         smallestteam_count = c2;
530                 }
531                 if(c3 >= 0 && c3 < smallestteam_count)
532                 {
533                         smallestteam = 3;
534                         smallestteam_count = c3;
535                 }
536                 if(c4 >= 0 && c4 < smallestteam_count)
537                 {
538                         smallestteam = 4;
539                         smallestteam_count = c4;
540                 }
541         }
542         else
543         {
544                 if(c1 >= 0 && (c1 < smallestteam_count ||
545                                         (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
546                 {
547                         smallestteam = 1;
548                         smallestteam_count = c1;
549                 }
550                 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
551                                         (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
552                 {
553                         smallestteam = 2;
554                         smallestteam_count = c2;
555                 }
556                 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
557                                         (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
558                 {
559                         smallestteam = 3;
560                         smallestteam_count = c3;
561                 }
562                 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
563                                         (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
564                 {
565                         smallestteam = 4;
566                         smallestteam_count = c4;
567                 }
568         }
569
570         if(smallestteam == 1)
571         {
572                 selectedteam = COLOR_TEAM1 - 1;
573         }
574         if(smallestteam == 2)
575         {
576                 selectedteam = COLOR_TEAM2 - 1;
577         }
578         if(smallestteam == 3)
579         {
580                 selectedteam = COLOR_TEAM3 - 1;
581         }
582         if(smallestteam == 4)
583         {
584                 selectedteam = COLOR_TEAM4 - 1;
585         }
586
587         SetPlayerColors(pl, selectedteam);
588 }
589 */
590 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
591 Control point for Domination gameplay.
592 */
593 void spawnfunc_dom_controlpoint()
594 {
595         if(!g_domination)
596         {
597                 remove(self);
598                 return;
599         }
600         self.think = dom_controlpoint_setup;
601         self.nextthink = time + 0.1;
602         self.reset = dom_controlpoint_setup;
603
604         if(!self.scale)
605                 self.scale = 0.6;
606
607         //if(!self.glow_size)
608         //      self.glow_size = cvar("g_domination_point_glow");
609         self.effects = self.effects | EF_LOWPRECISION;
610         if (autocvar_g_domination_point_fullbright)
611                 self.effects |= EF_FULLBRIGHT;
612 };
613
614 // code from here on is just to support maps that don't have control point and team entities
615 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
616 {
617         local entity oldself;
618         oldself = self;
619         self = spawn();
620         self.classname = "dom_team";
621         self.netname = teamname;
622         self.cnt = teamcolor;
623         self.model = pointmodel;
624         self.skin = pointskin;
625         self.noise = capsound;
626         self.noise1 = capnarration;
627         self.message = capmessage;
628
629         // this code is identical to spawnfunc_dom_team
630         setmodel(self, self.model); // precision not needed
631         self.mdl = self.model;
632         self.dmg = self.modelindex;
633         self.model = "";
634         self.modelindex = 0;
635         // this would have to be changed if used in quakeworld
636         self.team = self.cnt + 1;
637
638         //eprint(self);
639         self = oldself;
640 };
641
642 void dom_spawnpoint(vector org)
643 {
644         local entity oldself;
645         oldself = self;
646         self = spawn();
647         self.classname = "dom_controlpoint";
648         self.think = spawnfunc_dom_controlpoint;
649         self.nextthink = time;
650         setorigin(self, org);
651         spawnfunc_dom_controlpoint();
652         self = oldself;
653 };
654
655 // spawn some default teams if the map is not set up for domination
656 void dom_spawnteams()
657 {
658         float numteams;
659         if(autocvar_g_domination_teams_override < 2)
660                 numteams = autocvar_g_domination_default_teams;
661         else
662                 numteams = autocvar_g_domination_teams_override;
663         // LordHavoc: edit this if you want to change defaults
664         dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
665         dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
666         if(numteams > 2)
667                 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
668         if(numteams > 3)
669                 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
670         dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
671 };
672
673 void dom_delayedinit()
674 {
675         local entity head;
676
677         // if no teams are found, spawn defaults, if custom teams are set, use them
678         if (find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
679                 dom_spawnteams();
680         // if no control points are found, spawn defaults
681         if (find(world, classname, "dom_controlpoint") == world)
682         {
683                 // TODO in a few months (maybe 2011/08): change this into error() and remove this very poor dom point selection
684                 backtrace("This map contains no dom_controlpoint entities. A very poor dom point placement will be chosen. Please fix the map.");
685
686                 // if no supported map was found, make every deathmatch spawn a point
687                 head = find(world, classname, "info_player_deathmatch");
688                 while (head)
689                 {
690                         dom_spawnpoint(head.origin);
691                         head = find(head, classname, "info_player_deathmatch");
692                 }
693         }
694
695         ScoreRules_dom();
696 };
697
698 void dom_init()
699 {
700         // we have to precache default models/sounds even if they might not be
701         // used because spawnfunc_worldspawn is executed before any other entities are read,
702         // so we don't even know yet if this map is set up for domination...
703         precache_model("models/domination/dom_red.md3");
704         precache_model("models/domination/dom_blue.md3");
705         precache_model("models/domination/dom_yellow.md3");
706         precache_model("models/domination/dom_pink.md3");
707         precache_model("models/domination/dom_unclaimed.md3");
708         precache_sound("domination/claim.wav");
709         InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
710
711         addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
712         addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
713         addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
714         if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
715         if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
716 };
717