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)
371 self.DOMPOINTFRAGS = 1;
376 self.t_width = 0.02; // frame animation rate
378 self.t_length = 239; // maximum frame
380 self.think = dompointthink;
381 self.nextthink = time;
382 self.touch = dompointtouch;
383 self.solid = SOLID_TRIGGER;
384 self.flags = FL_ITEM;
385 setsize(self, '-32 -32 -32', '32 32 32');
386 setorigin(self, self.origin + '0 0 20');
389 waypoint_spawnforitem(self);
390 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite);
391 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
396 // player has joined game, get him on a team
398 /*void dom_player_join_team(entity pl)
401 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
402 float balance_teams, force_balance, balance_type;
404 balance_teams = cvar("g_balance_teams");
405 balance_teams = cvar("g_balance_teams_force");
407 c1 = c2 = c3 = c4 = -1;
410 // first find out what teams are allowed
411 head = find(world, classname, "dom_team");
414 if(head.netname != "")
416 //if(head.team == pl.team)
418 if(head.team == COLOR_TEAM1)
422 if(head.team == COLOR_TEAM2)
426 if(head.team == COLOR_TEAM3)
430 if(head.team == COLOR_TEAM4)
435 head = find(head, classname, "dom_team");
438 // make sure there are at least 2 teams to join
440 totalteams = totalteams + 1;
442 totalteams = totalteams + 1;
444 totalteams = totalteams + 1;
446 totalteams = totalteams + 1;
449 error("dom_player_join_team: Too few teams available for domination\n");
451 // whichever teams that are available are set to 0 instead of -1
453 // if we don't care what team he ends up on, put him on whatever team he entered as.
454 // if he's not on a valid team, then put him on the smallest team
455 if(!balance_teams && !force_balance)
457 if( c1 >= 0 && pl.team == COLOR_TEAM1)
458 selectedteam = pl.team;
459 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
460 selectedteam = pl.team;
461 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
462 selectedteam = pl.team;
463 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
464 selectedteam = pl.team;
469 SetPlayerColors(pl, selectedteam - 1);
472 // otherwise end up on the smallest team (handled below)
475 // now count how many players are on each team already
477 head = find(world, classname, "player");
480 //if(head.netname != "")
482 if(head.team == COLOR_TEAM1)
487 if(head.team == COLOR_TEAM2)
492 if(head.team == COLOR_TEAM3)
497 if(head.team == COLOR_TEAM4)
503 head = find(head, classname, "player");
506 // c1...c4 now have counts of each team
507 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
510 smallestteam_count = 999;
512 // 2 gives priority to what team you're already on, 1 goes in order
515 if(balance_type == 1)
517 if(c1 >= 0 && c1 < smallestteam_count)
520 smallestteam_count = c1;
522 if(c2 >= 0 && c2 < smallestteam_count)
525 smallestteam_count = c2;
527 if(c3 >= 0 && c3 < smallestteam_count)
530 smallestteam_count = c3;
532 if(c4 >= 0 && c4 < smallestteam_count)
535 smallestteam_count = c4;
540 if(c1 >= 0 && (c1 < smallestteam_count ||
541 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
544 smallestteam_count = c1;
546 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
547 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
550 smallestteam_count = c2;
552 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
553 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
556 smallestteam_count = c3;
558 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
559 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
562 smallestteam_count = c4;
566 if(smallestteam == 1)
568 selectedteam = COLOR_TEAM1 - 1;
570 if(smallestteam == 2)
572 selectedteam = COLOR_TEAM2 - 1;
574 if(smallestteam == 3)
576 selectedteam = COLOR_TEAM3 - 1;
578 if(smallestteam == 4)
580 selectedteam = COLOR_TEAM4 - 1;
583 SetPlayerColors(pl, selectedteam);
586 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
587 Control point for Domination gameplay.
589 void spawnfunc_dom_controlpoint()
596 self.think = dom_controlpoint_setup;
597 self.nextthink = time + 0.1;
598 self.reset = dom_controlpoint_setup;
603 //if(!self.glow_size)
604 // self.glow_size = cvar("g_domination_point_glow");
605 self.effects = self.effects | EF_LOWPRECISION;
606 if (cvar("g_domination_point_fullbright"))
607 self.effects |= EF_FULLBRIGHT;
609 float points, waittime;
610 if (g_domination_point_rate)
611 points = g_domination_point_rate;
614 if(points == 0) // default
617 if (g_domination_point_amt)
618 waittime = g_domination_point_amt;
620 waittime = self.wait;
621 if(waittime == 0) // default
623 total_pps += points/waittime;
626 // code from here on is just to support maps that don't have control point and team entities
627 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
629 local entity oldself;
632 self.classname = "dom_team";
633 self.netname = teamname;
634 self.cnt = teamcolor;
635 self.model = pointmodel;
636 self.skin = pointskin;
637 self.noise = capsound;
638 self.noise1 = capnarration;
639 self.message = capmessage;
641 // this code is identical to spawnfunc_dom_team
642 setmodel(self, self.model); // precision not needed
643 self.mdl = self.model;
644 self.dmg = self.modelindex;
647 // this would have to be changed if used in quakeworld
648 self.team = self.cnt + 1;
654 void dom_spawnpoint(vector org)
656 local entity oldself;
659 self.classname = "dom_controlpoint";
660 self.think = spawnfunc_dom_controlpoint;
661 self.nextthink = time;
662 setorigin(self, org);
663 spawnfunc_dom_controlpoint();
667 // spawn some default teams if the map is not set up for domination
668 void dom_spawnteams()
671 if(cvar("g_domination_teams_override") < 2)
672 numteams = cvar("g_domination_default_teams");
674 numteams = cvar("g_domination_teams_override");
675 // LordHavoc: edit this if you want to change defaults
676 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
677 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
679 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
681 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
682 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
685 void dom_delayedinit()
689 // if no teams are found, spawn defaults, if custom teams are set, use them
690 if (find(world, classname, "dom_team") == world || cvar("g_domination_teams_override") >= 2)
692 // if no control points are found, spawn defaults
693 if (find(world, classname, "dom_controlpoint") == world)
695 // here follow default domination points for each map
697 if (world.model == "maps/e1m1.bsp")
699 dom_spawnpoint('0 0 0');
704 // if no supported map was found, make every deathmatch spawn a point
705 head = find(world, classname, "info_player_deathmatch");
708 dom_spawnpoint(head.origin);
709 head = find(head, classname, "info_player_deathmatch");
719 // we have to precache default models/sounds even if they might not be
720 // used because spawnfunc_worldspawn is executed before any other entities are read,
721 // so we don't even know yet if this map is set up for domination...
722 precache_model("models/domination/dom_red.md3");
723 precache_model("models/domination/dom_blue.md3");
724 precache_model("models/domination/dom_yellow.md3");
725 precache_model("models/domination/dom_pink.md3");
726 precache_model("models/domination/dom_unclaimed.md3");
727 precache_sound("domination/claim.wav");
728 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
730 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
731 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
732 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
733 if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
734 if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
736 g_domination_point_rate = cvar("g_domination_point_rate");
737 g_domination_point_amt = cvar("g_domination_point_amt");
739 // teamplay is always on in domination, defaults to hurt self but not teammates
741 // cvar_set("teamplay", "3");