3 Domination as a plugin for netquake mods
4 by LordHavoc (lordhavoc@ghdigital.com)
6 How to add domination points to a mod:
7 1. Add this line to progs.src above world.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:
12 4. Add this line to the end of spawnfunc_worldspawn in world.qc:
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).
18 #define DOMPOINTFRAGS frags
20 .float enemy_playerid;
24 // pps: points per second
28 .float dom_pps_yellow;
35 void set_dom_state(void)
37 // BIG ugly hack to make stat sending work
38 self.dom_total_pps = total_pps;
39 self.dom_pps_red = pps_red;
40 self.dom_pps_blue = pps_blue;
43 self.dom_pps_yellow = pps_yellow;
47 self.dom_pps_pink = pps_pink;
51 void() dom_controlpoint_setup;
53 void LogDom(string mode, float team_before, entity actor)
56 if(!autocvar_sv_eventlog)
58 s = strcat(":dom:", mode);
59 s = strcat(s, ":", ftos(team_before));
60 s = strcat(s, ":", ftos(actor.playerid));
64 void() dom_spawnteams;
66 void dompoint_captured ()
69 local float old_delay, old_team, real_team;
71 // now that the delay has expired, switch to the latest team to lay claim to this point
77 LogDom("taken", self.team, self.dmg_inflictor);
78 self.dmg_inflictor = world;
80 self.goalentity = head;
81 self.model = head.mdl;
82 self.modelindex = head.dmg;
83 self.skin = head.skin;
85 //bprint(head.message);
88 //bprint(^3head.netname);
89 //bprint(head.netname);
90 //bprint(self.message);
93 float points, wait_time;
94 if (autocvar_g_domination_point_amt)
95 points = autocvar_g_domination_point_amt;
98 if (autocvar_g_domination_point_rate)
99 wait_time = autocvar_g_domination_point_rate;
101 wait_time = self.wait;
103 bprint("^3", head.netname, "^3", self.message);
105 bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
107 bprint(" ^7(", ftos(points), " point every ", ftos(wait_time), " seconds)\n");
109 if(self.enemy.playerid == self.enemy_playerid)
110 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
114 if (head.noise != "")
116 sound(self.enemy, CHAN_AUTO, head.noise, VOL_BASE, ATTN_NORM);
118 sound(self, CHAN_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
119 if (head.noise1 != "")
120 play2all(head.noise1);
122 //self.nextthink = time + autocvar_g_domination_point_rate;
123 //self.think = dompointthink;
125 self.delay = time + wait_time;
128 old_delay = self.delay;
129 old_team = self.team;
130 self.team = real_team;
134 self.delay = old_delay;
135 self.team = old_team;
139 // "fix" pps when slightly under 0 because of approximation errors
141 pps_red -= (points/wait_time);
142 if (pps_red < 0) pps_red = 0;
145 pps_blue -= (points/wait_time);
146 if (pps_blue < 0) pps_blue = 0;
149 pps_yellow -= (points/wait_time);
150 if (pps_yellow < 0) pps_yellow = 0;
153 pps_pink -= (points/wait_time);
154 if (pps_pink < 0) pps_pink = 0;
157 switch(self.goalentity.team)
159 // "fix" pps when slightly over total_pps because of approximation errors
161 pps_red += (points/wait_time);
162 if (pps_red > total_pps) pps_red = total_pps;
163 WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
166 pps_blue += (points/wait_time);
167 if (pps_blue > total_pps) pps_blue = total_pps;
168 WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
171 pps_yellow += (points/wait_time);
172 if (pps_yellow > total_pps) pps_yellow = total_pps;
173 WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
176 pps_pink += (points/wait_time);
177 if (pps_pink > total_pps) pps_pink = total_pps;
178 WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
181 FOR_EACH_PLAYER(self)
184 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
185 WaypointSprite_Ping(self.sprite);
190 void AnimateDomPoint()
192 if(self.pain_finished > time)
194 self.pain_finished = time + self.t_width;
195 if(self.nextthink > self.pain_finished)
196 self.nextthink = self.pain_finished;
198 self.frame = self.frame + 1;
199 if(self.frame > self.t_length)
207 self.nextthink = time + 0.1;
209 //self.frame = self.frame + 1;
210 //if(self.frame > 119)
216 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
219 if(autocvar_g_domination_point_rate)
220 self.delay = time + autocvar_g_domination_point_rate;
222 self.delay = time + self.wait;
224 // give credit to the team
225 // NOTE: this defaults to 0
226 if (self.goalentity.netname != "")
228 if(autocvar_g_domination_point_amt)
229 fragamt = autocvar_g_domination_point_amt;
231 fragamt = self.DOMPOINTFRAGS;
232 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
233 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
235 // give credit to the individual player, if he is still there
236 if (self.enemy.playerid == self.enemy_playerid)
238 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
239 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
249 if (other.classname != "player")
251 if (other.health < 1)
254 if(time < self.captime + 0.3)
257 // only valid teams can claim it
258 head = find(world, classname, "dom_team");
259 while (head && head.team != other.team)
260 head = find(head, classname, "dom_team");
261 if (!head || head.netname == "" || head == self.goalentity)
266 self.team = self.goalentity.team; // this stores the PREVIOUS team!
268 self.cnt = other.team;
269 self.owner = head; // team to switch to after the delay
270 self.dmg_inflictor = other;
273 // self.delay = time + cvar("g_domination_point_capturetime");
274 //self.nextthink = time + cvar("g_domination_point_capturetime");
275 //self.think = dompoint_captured;
277 // go to neutral team in the mean time
278 head = find(world, classname, "dom_team");
279 while (head && head.netname != "")
280 head = find(head, classname, "dom_team");
284 WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
285 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
286 WaypointSprite_Ping(self.sprite);
288 self.goalentity = head;
289 self.model = head.mdl;
290 self.modelindex = head.dmg;
291 self.skin = head.skin;
293 self.enemy = other; // individual player scoring
294 self.enemy_playerid = other.playerid;
298 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
299 Team declaration for Domination gameplay, this allows you to decide what team
300 names and control point models are used in your map.
302 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
303 can have netname set! The nameless team owns all control points at start.
307 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
309 Scoreboard color of the team (for example 4 is red and 13 is blue)
311 Model to use for control points owned by this team (for example
312 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
315 Skin of the model to use (for team skins on a single model)
317 Sound to play when this team captures a point.
318 (this is a localized sound, like a small alarm or other effect)
320 Narrator speech to play when this team captures a point.
321 (this is a global sound, like "Red team has captured a control point")
324 void spawnfunc_dom_team()
326 if(!g_domination || autocvar_g_domination_teams_override >= 2)
331 precache_model(self.model);
332 if (self.noise != "")
333 precache_sound(self.noise);
334 if (self.noise1 != "")
335 precache_sound(self.noise1);
336 self.classname = "dom_team";
337 setmodel(self, self.model); // precision not needed
338 self.mdl = self.model;
339 self.dmg = self.modelindex;
342 // this would have to be changed if used in quakeworld
344 self.team = self.cnt + 1; // WHY are these different anyway?
347 void dom_controlpoint_setup()
350 // find the spawnfunc_dom_team representing unclaimed points
351 head = find(world, classname, "dom_team");
352 while(head && head.netname != "")
353 head = find(head, classname, "dom_team");
355 objerror("no spawnfunc_dom_team with netname \"\" found\n");
357 // copy important properties from spawnfunc_dom_team entity
358 self.goalentity = head;
359 setmodel(self, head.mdl); // precision already set
360 self.skin = head.skin;
365 self.message = " has captured a control point";
367 if(self.DOMPOINTFRAGS <= 0)
368 self.DOMPOINTFRAGS = 1;
372 float points, waittime;
373 if (autocvar_g_domination_point_rate)
374 points = autocvar_g_domination_point_rate;
377 if (autocvar_g_domination_point_amt)
378 waittime = autocvar_g_domination_point_amt;
380 waittime = self.wait;
382 total_pps += points/waittime;
385 self.t_width = 0.02; // frame animation rate
387 self.t_length = 239; // maximum frame
389 self.think = dompointthink;
390 self.nextthink = time;
391 self.touch = dompointtouch;
392 self.solid = SOLID_TRIGGER;
393 self.flags = FL_ITEM;
394 setsize(self, '-32 -32 -32', '32 32 32');
395 setorigin(self, self.origin + '0 0 20');
398 waypoint_spawnforitem(self);
399 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite);
400 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
405 // player has joined game, get him on a team
407 /*void dom_player_join_team(entity pl)
410 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
411 float balance_teams, force_balance, balance_type;
413 balance_teams = autocvar_g_balance_teams;
414 balance_teams = autocvar_g_balance_teams_force;
416 c1 = c2 = c3 = c4 = -1;
419 // first find out what teams are allowed
420 head = find(world, classname, "dom_team");
423 if(head.netname != "")
425 //if(head.team == pl.team)
427 if(head.team == COLOR_TEAM1)
431 if(head.team == COLOR_TEAM2)
435 if(head.team == COLOR_TEAM3)
439 if(head.team == COLOR_TEAM4)
444 head = find(head, classname, "dom_team");
447 // make sure there are at least 2 teams to join
449 totalteams = totalteams + 1;
451 totalteams = totalteams + 1;
453 totalteams = totalteams + 1;
455 totalteams = totalteams + 1;
458 error("dom_player_join_team: Too few teams available for domination\n");
460 // whichever teams that are available are set to 0 instead of -1
462 // if we don't care what team he ends up on, put him on whatever team he entered as.
463 // if he's not on a valid team, then put him on the smallest team
464 if(!balance_teams && !force_balance)
466 if( c1 >= 0 && pl.team == COLOR_TEAM1)
467 selectedteam = pl.team;
468 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
469 selectedteam = pl.team;
470 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
471 selectedteam = pl.team;
472 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
473 selectedteam = pl.team;
478 SetPlayerColors(pl, selectedteam - 1);
481 // otherwise end up on the smallest team (handled below)
484 // now count how many players are on each team already
486 head = find(world, classname, "player");
489 //if(head.netname != "")
491 if(head.team == COLOR_TEAM1)
496 if(head.team == COLOR_TEAM2)
501 if(head.team == COLOR_TEAM3)
506 if(head.team == COLOR_TEAM4)
512 head = find(head, classname, "player");
515 // c1...c4 now have counts of each team
516 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
519 smallestteam_count = 999;
521 // 2 gives priority to what team you're already on, 1 goes in order
524 if(balance_type == 1)
526 if(c1 >= 0 && c1 < smallestteam_count)
529 smallestteam_count = c1;
531 if(c2 >= 0 && c2 < smallestteam_count)
534 smallestteam_count = c2;
536 if(c3 >= 0 && c3 < smallestteam_count)
539 smallestteam_count = c3;
541 if(c4 >= 0 && c4 < smallestteam_count)
544 smallestteam_count = c4;
549 if(c1 >= 0 && (c1 < smallestteam_count ||
550 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
553 smallestteam_count = c1;
555 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
556 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
559 smallestteam_count = c2;
561 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
562 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
565 smallestteam_count = c3;
567 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
568 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
571 smallestteam_count = c4;
575 if(smallestteam == 1)
577 selectedteam = COLOR_TEAM1 - 1;
579 if(smallestteam == 2)
581 selectedteam = COLOR_TEAM2 - 1;
583 if(smallestteam == 3)
585 selectedteam = COLOR_TEAM3 - 1;
587 if(smallestteam == 4)
589 selectedteam = COLOR_TEAM4 - 1;
592 SetPlayerColors(pl, selectedteam);
595 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
596 Control point for Domination gameplay.
598 void spawnfunc_dom_controlpoint()
605 self.think = dom_controlpoint_setup;
606 self.nextthink = time + 0.1;
607 self.reset = dom_controlpoint_setup;
612 //if(!self.glow_size)
613 // self.glow_size = cvar("g_domination_point_glow");
614 self.effects = self.effects | EF_LOWPRECISION;
615 if (autocvar_g_domination_point_fullbright)
616 self.effects |= EF_FULLBRIGHT;
619 // code from here on is just to support maps that don't have control point and team entities
620 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
622 local entity oldself;
625 self.classname = "dom_team";
626 self.netname = teamname;
627 self.cnt = teamcolor;
628 self.model = pointmodel;
629 self.skin = pointskin;
630 self.noise = capsound;
631 self.noise1 = capnarration;
632 self.message = capmessage;
634 // this code is identical to spawnfunc_dom_team
635 setmodel(self, self.model); // precision not needed
636 self.mdl = self.model;
637 self.dmg = self.modelindex;
640 // this would have to be changed if used in quakeworld
641 self.team = self.cnt + 1;
647 void dom_spawnpoint(vector org)
649 local entity oldself;
652 self.classname = "dom_controlpoint";
653 self.think = spawnfunc_dom_controlpoint;
654 self.nextthink = time;
655 setorigin(self, org);
656 spawnfunc_dom_controlpoint();
660 // spawn some default teams if the map is not set up for domination
661 void dom_spawnteams()
664 if(autocvar_g_domination_teams_override < 2)
665 numteams = autocvar_g_domination_default_teams;
667 numteams = autocvar_g_domination_teams_override;
668 // LordHavoc: edit this if you want to change defaults
669 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
670 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
672 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
674 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
675 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
678 void dom_delayedinit()
682 // if no teams are found, spawn defaults, if custom teams are set, use them
683 if (find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
685 // if no control points are found, spawn defaults
686 if (find(world, classname, "dom_controlpoint") == world)
688 // here follow default domination points for each map
690 if (world.model == "maps/e1m1.bsp")
692 dom_spawnpoint('0 0 0');
697 // if no supported map was found, make every deathmatch spawn a point
698 head = find(world, classname, "info_player_deathmatch");
701 dom_spawnpoint(head.origin);
702 head = find(head, classname, "info_player_deathmatch");
712 // we have to precache default models/sounds even if they might not be
713 // used because spawnfunc_worldspawn is executed before any other entities are read,
714 // so we don't even know yet if this map is set up for domination...
715 precache_model("models/domination/dom_red.md3");
716 precache_model("models/domination/dom_blue.md3");
717 precache_model("models/domination/dom_yellow.md3");
718 precache_model("models/domination/dom_pink.md3");
719 precache_model("models/domination/dom_unclaimed.md3");
720 precache_sound("domination/claim.wav");
721 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
723 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
724 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
725 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
726 if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
727 if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);