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 g_domination_point_amt;
21 float g_domination_point_rate;
23 .float enemy_playerid;
27 // pps: points per second
31 .float dom_pps_yellow;
38 void set_dom_state(void)
40 // BIG ugly hack to make stat sending work
41 self.dom_total_pps = total_pps;
42 self.dom_pps_red = pps_red;
43 self.dom_pps_blue = pps_blue;
46 self.dom_pps_yellow = pps_yellow;
50 self.dom_pps_pink = pps_pink;
54 void() dom_controlpoint_setup;
56 void LogDom(string mode, float team_before, entity actor)
59 if(!cvar("sv_eventlog"))
61 s = strcat(":dom:", mode);
62 s = strcat(s, ":", ftos(team_before));
63 s = strcat(s, ":", ftos(actor.playerid));
67 void() dom_spawnteams;
69 void dompoint_captured ()
72 local float old_delay, old_team, real_team;
74 // now that the delay has expired, switch to the latest team to lay claim to this point
80 LogDom("taken", self.team, self.dmg_inflictor);
81 self.dmg_inflictor = world;
83 self.goalentity = head;
84 self.model = head.mdl;
85 self.modelindex = head.dmg;
86 self.skin = head.skin;
88 //bprint(head.message);
91 //bprint(^3head.netname);
92 //bprint(head.netname);
93 //bprint(self.message);
96 float points, wait_time;
97 if (g_domination_point_amt)
98 points = g_domination_point_amt;
101 if (g_domination_point_rate)
102 wait_time = g_domination_point_rate;
104 wait_time = self.wait;
106 bprint("^3", head.netname, "^3", self.message);
108 bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
110 bprint(" ^7(", ftos(points), " point every ", ftos(wait_time), " seconds)\n");
112 if(self.enemy.playerid == self.enemy_playerid)
113 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
117 if (head.noise != "")
119 sound(self.enemy, CHAN_AUTO, head.noise, VOL_BASE, ATTN_NORM);
121 sound(self, CHAN_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
122 if (head.noise1 != "")
123 play2all(head.noise1);
125 //self.nextthink = time + cvar("g_domination_point_rate");
126 //self.think = dompointthink;
128 self.delay = time + wait_time;
131 old_delay = self.delay;
132 old_team = self.team;
133 self.team = real_team;
137 self.delay = old_delay;
138 self.team = old_team;
142 // "fix" pps when slightly under 0 because of approximation errors
144 pps_red -= (points/wait_time);
145 if (pps_red < 0) pps_red = 0;
148 pps_blue -= (points/wait_time);
149 if (pps_blue < 0) pps_blue = 0;
152 pps_yellow -= (points/wait_time);
153 if (pps_yellow < 0) pps_yellow = 0;
156 pps_pink -= (points/wait_time);
157 if (pps_pink < 0) pps_pink = 0;
160 switch(self.goalentity.team)
162 // "fix" pps when slightly over total_pps because of approximation errors
164 pps_red += (points/wait_time);
165 if (pps_red > total_pps) pps_red = total_pps;
166 WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
169 pps_blue += (points/wait_time);
170 if (pps_blue > total_pps) pps_blue = total_pps;
171 WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
174 pps_yellow += (points/wait_time);
175 if (pps_yellow > total_pps) pps_yellow = total_pps;
176 WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
179 pps_pink += (points/wait_time);
180 if (pps_pink > total_pps) pps_pink = total_pps;
181 WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
184 FOR_EACH_PLAYER(self)
187 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
188 WaypointSprite_Ping(self.sprite);
193 void AnimateDomPoint()
195 if(self.pain_finished > time)
197 self.pain_finished = time + self.t_width;
198 if(self.nextthink > self.pain_finished)
199 self.nextthink = self.pain_finished;
201 self.frame = self.frame + 1;
202 if(self.frame > self.t_length)
210 self.nextthink = time + 0.1;
212 //self.frame = self.frame + 1;
213 //if(self.frame > 119)
219 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
222 if(g_domination_point_rate)
223 self.delay = time + g_domination_point_rate;
225 self.delay = time + self.wait;
227 // give credit to the team
228 // NOTE: this defaults to 0
229 if (self.goalentity.netname != "")
231 if(g_domination_point_amt)
232 fragamt = g_domination_point_amt;
234 fragamt = self.DOMPOINTFRAGS;
235 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
236 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
238 // give credit to the individual player, if he is still there
239 if (self.enemy.playerid == self.enemy_playerid)
241 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
242 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
252 if (other.classname != "player")
254 if (other.health < 1)
257 if(time < self.captime + 0.3)
260 // only valid teams can claim it
261 head = find(world, classname, "dom_team");
262 while (head && head.team != other.team)
263 head = find(head, classname, "dom_team");
264 if (!head || head.netname == "" || head == self.goalentity)
269 self.team = self.goalentity.team; // this stores the PREVIOUS team!
271 self.cnt = other.team;
272 self.owner = head; // team to switch to after the delay
273 self.dmg_inflictor = other;
276 // self.delay = time + cvar("g_domination_point_capturetime");
277 //self.nextthink = time + cvar("g_domination_point_capturetime");
278 //self.think = dompoint_captured;
280 // go to neutral team in the mean time
281 head = find(world, classname, "dom_team");
282 while (head && head.netname != "")
283 head = find(head, classname, "dom_team");
287 WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
288 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
289 WaypointSprite_Ping(self.sprite);
291 self.goalentity = head;
292 self.model = head.mdl;
293 self.modelindex = head.dmg;
294 self.skin = head.skin;
296 self.enemy = other; // individual player scoring
297 self.enemy_playerid = other.playerid;
301 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
302 Team declaration for Domination gameplay, this allows you to decide what team
303 names and control point models are used in your map.
305 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
306 can have netname set! The nameless team owns all control points at start.
310 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
312 Scoreboard color of the team (for example 4 is red and 13 is blue)
314 Model to use for control points owned by this team (for example
315 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
318 Skin of the model to use (for team skins on a single model)
320 Sound to play when this team captures a point.
321 (this is a localized sound, like a small alarm or other effect)
323 Narrator speech to play when this team captures a point.
324 (this is a global sound, like "Red team has captured a control point")
327 void spawnfunc_dom_team()
329 if(!g_domination || cvar("g_domination_teams_override") >= 2)
334 precache_model(self.model);
335 if (self.noise != "")
336 precache_sound(self.noise);
337 if (self.noise1 != "")
338 precache_sound(self.noise1);
339 self.classname = "dom_team";
340 setmodel(self, self.model); // precision not needed
341 self.mdl = self.model;
342 self.dmg = self.modelindex;
345 // this would have to be changed if used in quakeworld
347 self.team = self.cnt + 1; // WHY are these different anyway?
350 void dom_controlpoint_setup()
353 // find the spawnfunc_dom_team representing unclaimed points
354 head = find(world, classname, "dom_team");
355 while(head && head.netname != "")
356 head = find(head, classname, "dom_team");
358 objerror("no spawnfunc_dom_team with netname \"\" found\n");
360 // copy important properties from spawnfunc_dom_team entity
361 self.goalentity = head;
362 setmodel(self, head.mdl); // precision already set
363 self.skin = head.skin;
368 self.message = " has captured a control point";
370 if(self.DOMPOINTFRAGS <= 0)
371 self.DOMPOINTFRAGS = 1;
375 float points, waittime;
376 if (g_domination_point_rate)
377 points = g_domination_point_rate;
380 if (g_domination_point_amt)
381 waittime = g_domination_point_amt;
383 waittime = self.wait;
385 total_pps += points/waittime;
388 self.t_width = 0.02; // frame animation rate
390 self.t_length = 239; // maximum frame
392 self.think = dompointthink;
393 self.nextthink = time;
394 self.touch = dompointtouch;
395 self.solid = SOLID_TRIGGER;
396 self.flags = FL_ITEM;
397 setsize(self, '-32 -32 -32', '32 32 32');
398 setorigin(self, self.origin + '0 0 20');
401 waypoint_spawnforitem(self);
402 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite);
403 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
408 // player has joined game, get him on a team
410 /*void dom_player_join_team(entity pl)
413 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
414 float balance_teams, force_balance, balance_type;
416 balance_teams = cvar("g_balance_teams");
417 balance_teams = cvar("g_balance_teams_force");
419 c1 = c2 = c3 = c4 = -1;
422 // first find out what teams are allowed
423 head = find(world, classname, "dom_team");
426 if(head.netname != "")
428 //if(head.team == pl.team)
430 if(head.team == COLOR_TEAM1)
434 if(head.team == COLOR_TEAM2)
438 if(head.team == COLOR_TEAM3)
442 if(head.team == COLOR_TEAM4)
447 head = find(head, classname, "dom_team");
450 // make sure there are at least 2 teams to join
452 totalteams = totalteams + 1;
454 totalteams = totalteams + 1;
456 totalteams = totalteams + 1;
458 totalteams = totalteams + 1;
461 error("dom_player_join_team: Too few teams available for domination\n");
463 // whichever teams that are available are set to 0 instead of -1
465 // if we don't care what team he ends up on, put him on whatever team he entered as.
466 // if he's not on a valid team, then put him on the smallest team
467 if(!balance_teams && !force_balance)
469 if( c1 >= 0 && pl.team == COLOR_TEAM1)
470 selectedteam = pl.team;
471 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
472 selectedteam = pl.team;
473 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
474 selectedteam = pl.team;
475 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
476 selectedteam = pl.team;
481 SetPlayerColors(pl, selectedteam - 1);
484 // otherwise end up on the smallest team (handled below)
487 // now count how many players are on each team already
489 head = find(world, classname, "player");
492 //if(head.netname != "")
494 if(head.team == COLOR_TEAM1)
499 if(head.team == COLOR_TEAM2)
504 if(head.team == COLOR_TEAM3)
509 if(head.team == COLOR_TEAM4)
515 head = find(head, classname, "player");
518 // c1...c4 now have counts of each team
519 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
522 smallestteam_count = 999;
524 // 2 gives priority to what team you're already on, 1 goes in order
527 if(balance_type == 1)
529 if(c1 >= 0 && c1 < smallestteam_count)
532 smallestteam_count = c1;
534 if(c2 >= 0 && c2 < smallestteam_count)
537 smallestteam_count = c2;
539 if(c3 >= 0 && c3 < smallestteam_count)
542 smallestteam_count = c3;
544 if(c4 >= 0 && c4 < smallestteam_count)
547 smallestteam_count = c4;
552 if(c1 >= 0 && (c1 < smallestteam_count ||
553 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
556 smallestteam_count = c1;
558 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
559 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
562 smallestteam_count = c2;
564 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
565 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
568 smallestteam_count = c3;
570 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
571 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
574 smallestteam_count = c4;
578 if(smallestteam == 1)
580 selectedteam = COLOR_TEAM1 - 1;
582 if(smallestteam == 2)
584 selectedteam = COLOR_TEAM2 - 1;
586 if(smallestteam == 3)
588 selectedteam = COLOR_TEAM3 - 1;
590 if(smallestteam == 4)
592 selectedteam = COLOR_TEAM4 - 1;
595 SetPlayerColors(pl, selectedteam);
598 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
599 Control point for Domination gameplay.
601 void spawnfunc_dom_controlpoint()
608 self.think = dom_controlpoint_setup;
609 self.nextthink = time + 0.1;
610 self.reset = dom_controlpoint_setup;
615 //if(!self.glow_size)
616 // self.glow_size = cvar("g_domination_point_glow");
617 self.effects = self.effects | EF_LOWPRECISION;
618 if (cvar("g_domination_point_fullbright"))
619 self.effects |= EF_FULLBRIGHT;
622 // code from here on is just to support maps that don't have control point and team entities
623 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
625 local entity oldself;
628 self.classname = "dom_team";
629 self.netname = teamname;
630 self.cnt = teamcolor;
631 self.model = pointmodel;
632 self.skin = pointskin;
633 self.noise = capsound;
634 self.noise1 = capnarration;
635 self.message = capmessage;
637 // this code is identical to spawnfunc_dom_team
638 setmodel(self, self.model); // precision not needed
639 self.mdl = self.model;
640 self.dmg = self.modelindex;
643 // this would have to be changed if used in quakeworld
644 self.team = self.cnt + 1;
650 void dom_spawnpoint(vector org)
652 local entity oldself;
655 self.classname = "dom_controlpoint";
656 self.think = spawnfunc_dom_controlpoint;
657 self.nextthink = time;
658 setorigin(self, org);
659 spawnfunc_dom_controlpoint();
663 // spawn some default teams if the map is not set up for domination
664 void dom_spawnteams()
667 if(cvar("g_domination_teams_override") < 2)
668 numteams = cvar("g_domination_default_teams");
670 numteams = cvar("g_domination_teams_override");
671 // LordHavoc: edit this if you want to change defaults
672 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
673 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
675 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
677 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
678 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
681 void dom_delayedinit()
685 // if no teams are found, spawn defaults, if custom teams are set, use them
686 if (find(world, classname, "dom_team") == world || cvar("g_domination_teams_override") >= 2)
688 // if no control points are found, spawn defaults
689 if (find(world, classname, "dom_controlpoint") == world)
691 // here follow default domination points for each map
693 if (world.model == "maps/e1m1.bsp")
695 dom_spawnpoint('0 0 0');
700 // if no supported map was found, make every deathmatch spawn a point
701 head = find(world, classname, "info_player_deathmatch");
704 dom_spawnpoint(head.origin);
705 head = find(head, classname, "info_player_deathmatch");
715 // we have to precache default models/sounds even if they might not be
716 // used because spawnfunc_worldspawn is executed before any other entities are read,
717 // so we don't even know yet if this map is set up for domination...
718 precache_model("models/domination/dom_red.md3");
719 precache_model("models/domination/dom_blue.md3");
720 precache_model("models/domination/dom_yellow.md3");
721 precache_model("models/domination/dom_pink.md3");
722 precache_model("models/domination/dom_unclaimed.md3");
723 precache_sound("domination/claim.wav");
724 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
726 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
727 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
728 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
729 if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
730 if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
732 g_domination_point_rate = cvar("g_domination_point_rate");
733 g_domination_point_amt = cvar("g_domination_point_amt");
735 // teamplay is always on in domination, defaults to hurt self but not teammates
737 // cvar_set("teamplay", "3");