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(entity e, float connecting)
40 if(connecting) e.dom_total_pps = total_pps;
41 e.dom_pps_red = pps_red;
42 e.dom_pps_blue = pps_blue;
43 if(c3 >= 0) e.dom_pps_yellow = pps_yellow;
44 if(c4 >= 0) e.dom_pps_pink = pps_pink;
47 void() dom_controlpoint_setup;
49 void LogDom(string mode, float team_before, entity actor)
52 if(!cvar("sv_eventlog"))
54 s = strcat(":dom:", mode);
55 s = strcat(s, ":", ftos(team_before));
56 s = strcat(s, ":", ftos(actor.playerid));
60 void() dom_spawnteams;
62 void dompoint_captured ()
65 local float old_delay, old_team, real_team;
67 // now that the delay has expired, switch to the latest team to lay claim to this point
73 LogDom("taken", self.team, self.dmg_inflictor);
74 self.dmg_inflictor = world;
76 self.goalentity = head;
77 self.model = head.mdl;
78 self.modelindex = head.dmg;
79 self.skin = head.skin;
81 //bprint(head.message);
84 //bprint(^3head.netname);
85 //bprint(head.netname);
86 //bprint(self.message);
89 float points, wait_time;
90 if (g_domination_point_amt)
91 points = g_domination_point_amt;
94 if (g_domination_point_rate)
95 wait_time = g_domination_point_rate;
97 wait_time = self.wait;
99 bprint("^3", head.netname, "^3", self.message);
100 bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
102 if(self.enemy.playerid == self.enemy_playerid)
103 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
107 if (head.noise != "")
109 sound(self.enemy, CHAN_AUTO, head.noise, VOL_BASE, ATTN_NORM);
111 sound(self, CHAN_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
112 if (head.noise1 != "")
113 play2all(head.noise1);
115 //self.nextthink = time + cvar("g_domination_point_rate");
116 //self.think = dompointthink;
118 self.delay = time + wait_time;
121 old_delay = self.delay;
122 old_team = self.team;
123 self.team = real_team;
127 self.delay = old_delay;
128 self.team = old_team;
132 // "fix" pps when slightly under 0 because of approximation errors
134 pps_red -= (points/wait_time);
135 if (pps_red < 0) pps_red = 0;
138 pps_blue -= (points/wait_time);
139 if (pps_blue < 0) pps_blue = 0;
142 pps_yellow -= (points/wait_time);
143 if (pps_yellow < 0) pps_yellow = 0;
146 pps_pink -= (points/wait_time);
147 if (pps_pink < 0) pps_pink = 0;
150 switch(self.goalentity.team)
152 // "fix" pps when slightly over total_pps because of approximation errors
154 pps_red += (points/wait_time);
155 if (pps_red > total_pps) pps_red = total_pps;
156 WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
159 pps_blue += (points/wait_time);
160 if (pps_blue > total_pps) pps_blue = total_pps;
161 WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
164 pps_yellow += (points/wait_time);
165 if (pps_yellow > total_pps) pps_yellow = total_pps;
166 WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
169 pps_pink += (points/wait_time);
170 if (pps_pink > total_pps) pps_pink = total_pps;
171 WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
174 FOR_EACH_CLIENT(head)
175 set_dom_state(head, FALSE);
177 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
178 WaypointSprite_Ping(self.sprite);
183 void AnimateDomPoint()
185 if(self.pain_finished > time)
187 self.pain_finished = time + self.t_width;
188 if(self.nextthink > self.pain_finished)
189 self.nextthink = self.pain_finished;
191 self.frame = self.frame + 1;
192 if(self.frame > self.t_length)
200 self.nextthink = time + 0.1;
202 //self.frame = self.frame + 1;
203 //if(self.frame > 119)
209 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
212 if(g_domination_point_rate)
213 self.delay = time + g_domination_point_rate;
215 self.delay = time + self.wait;
217 // give credit to the team
218 // NOTE: this defaults to 0
219 if (self.goalentity.netname != "")
221 if(g_domination_point_amt)
222 fragamt = g_domination_point_amt;
224 fragamt = self.DOMPOINTFRAGS;
225 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
226 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
228 // give credit to the individual player, if he is still there
229 if (self.enemy.playerid == self.enemy_playerid)
231 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
232 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
242 if (other.classname != "player")
244 if (other.health < 1)
247 if(time < self.captime + 0.3)
250 // only valid teams can claim it
251 head = find(world, classname, "dom_team");
252 while (head && head.team != other.team)
253 head = find(head, classname, "dom_team");
254 if (!head || head.netname == "" || head == self.goalentity)
259 self.team = self.goalentity.team; // this stores the PREVIOUS team!
261 self.cnt = other.team;
262 self.owner = head; // team to switch to after the delay
263 self.dmg_inflictor = other;
266 // self.delay = time + cvar("g_domination_point_capturetime");
267 //self.nextthink = time + cvar("g_domination_point_capturetime");
268 //self.think = dompoint_captured;
270 // go to neutral team in the mean time
271 head = find(world, classname, "dom_team");
272 while (head && head.netname != "")
273 head = find(head, classname, "dom_team");
277 WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
278 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
279 WaypointSprite_Ping(self.sprite);
281 self.goalentity = head;
282 self.model = head.mdl;
283 self.modelindex = head.dmg;
284 self.skin = head.skin;
286 self.enemy = other; // individual player scoring
287 self.enemy_playerid = other.playerid;
291 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
292 Team declaration for Domination gameplay, this allows you to decide what team
293 names and control point models are used in your map.
295 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
296 can have netname set! The nameless team owns all control points at start.
300 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
302 Scoreboard color of the team (for example 4 is red and 13 is blue)
304 Model to use for control points owned by this team (for example
305 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
308 Skin of the model to use (for team skins on a single model)
310 Sound to play when this team captures a point.
311 (this is a localized sound, like a small alarm or other effect)
313 Narrator speech to play when this team captures a point.
314 (this is a global sound, like "Red team has captured a control point")
317 void spawnfunc_dom_team()
319 if(!g_domination || cvar("g_domination_teams_override") >= 2)
324 precache_model(self.model);
325 if (self.noise != "")
326 precache_sound(self.noise);
327 if (self.noise1 != "")
328 precache_sound(self.noise1);
329 self.classname = "dom_team";
330 setmodel(self, self.model); // precision not needed
331 self.mdl = self.model;
332 self.dmg = self.modelindex;
335 // this would have to be changed if used in quakeworld
337 self.team = self.cnt + 1; // WHY are these different anyway?
340 void dom_controlpoint_setup()
343 // find the spawnfunc_dom_team representing unclaimed points
344 head = find(world, classname, "dom_team");
345 while(head && head.netname != "")
346 head = find(head, classname, "dom_team");
348 objerror("no spawnfunc_dom_team with netname \"\" found\n");
350 // copy important properties from spawnfunc_dom_team entity
351 self.goalentity = head;
352 setmodel(self, head.mdl); // precision already set
353 self.skin = head.skin;
358 self.message = " has captured a control point";
360 if(!self.DOMPOINTFRAGS)
361 self.DOMPOINTFRAGS = 1;
366 self.t_width = 0.02; // frame animation rate
368 self.t_length = 239; // maximum frame
370 self.think = dompointthink;
371 self.nextthink = time;
372 self.touch = dompointtouch;
373 self.solid = SOLID_TRIGGER;
374 self.flags = FL_ITEM;
375 setsize(self, '-32 -32 -32', '32 32 32');
376 setorigin(self, self.origin + '0 0 20');
379 waypoint_spawnforitem(self);
380 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite);
381 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
386 // player has joined game, get him on a team
388 /*void dom_player_join_team(entity pl)
391 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
392 float balance_teams, force_balance, balance_type;
394 balance_teams = cvar("g_balance_teams");
395 balance_teams = cvar("g_balance_teams_force");
397 c1 = c2 = c3 = c4 = -1;
400 // first find out what teams are allowed
401 head = find(world, classname, "dom_team");
404 if(head.netname != "")
406 //if(head.team == pl.team)
408 if(head.team == COLOR_TEAM1)
412 if(head.team == COLOR_TEAM2)
416 if(head.team == COLOR_TEAM3)
420 if(head.team == COLOR_TEAM4)
425 head = find(head, classname, "dom_team");
428 // make sure there are at least 2 teams to join
430 totalteams = totalteams + 1;
432 totalteams = totalteams + 1;
434 totalteams = totalteams + 1;
436 totalteams = totalteams + 1;
439 error("dom_player_join_team: Too few teams available for domination\n");
441 // whichever teams that are available are set to 0 instead of -1
443 // if we don't care what team he ends up on, put him on whatever team he entered as.
444 // if he's not on a valid team, then put him on the smallest team
445 if(!balance_teams && !force_balance)
447 if( c1 >= 0 && pl.team == COLOR_TEAM1)
448 selectedteam = pl.team;
449 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
450 selectedteam = pl.team;
451 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
452 selectedteam = pl.team;
453 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
454 selectedteam = pl.team;
459 SetPlayerColors(pl, selectedteam - 1);
462 // otherwise end up on the smallest team (handled below)
465 // now count how many players are on each team already
467 head = find(world, classname, "player");
470 //if(head.netname != "")
472 if(head.team == COLOR_TEAM1)
477 if(head.team == COLOR_TEAM2)
482 if(head.team == COLOR_TEAM3)
487 if(head.team == COLOR_TEAM4)
493 head = find(head, classname, "player");
496 // c1...c4 now have counts of each team
497 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
500 smallestteam_count = 999;
502 // 2 gives priority to what team you're already on, 1 goes in order
505 if(balance_type == 1)
507 if(c1 >= 0 && c1 < smallestteam_count)
510 smallestteam_count = c1;
512 if(c2 >= 0 && c2 < smallestteam_count)
515 smallestteam_count = c2;
517 if(c3 >= 0 && c3 < smallestteam_count)
520 smallestteam_count = c3;
522 if(c4 >= 0 && c4 < smallestteam_count)
525 smallestteam_count = c4;
530 if(c1 >= 0 && (c1 < smallestteam_count ||
531 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
534 smallestteam_count = c1;
536 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
537 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
540 smallestteam_count = c2;
542 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
543 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
546 smallestteam_count = c3;
548 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
549 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
552 smallestteam_count = c4;
556 if(smallestteam == 1)
558 selectedteam = COLOR_TEAM1 - 1;
560 if(smallestteam == 2)
562 selectedteam = COLOR_TEAM2 - 1;
564 if(smallestteam == 3)
566 selectedteam = COLOR_TEAM3 - 1;
568 if(smallestteam == 4)
570 selectedteam = COLOR_TEAM4 - 1;
573 SetPlayerColors(pl, selectedteam);
576 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
577 Control point for Domination gameplay.
579 void spawnfunc_dom_controlpoint()
586 self.think = dom_controlpoint_setup;
587 self.nextthink = time + 0.1;
588 self.reset = dom_controlpoint_setup;
593 //if(!self.glow_size)
594 // self.glow_size = cvar("g_domination_point_glow");
595 self.effects = self.effects | EF_LOWPRECISION;
596 if (cvar("g_domination_point_fullbright"))
597 self.effects |= EF_FULLBRIGHT;
599 float points, waittime;
600 if (g_domination_point_rate)
601 points += g_domination_point_rate;
603 points += self.frags;
604 if (g_domination_point_amt)
605 waittime += g_domination_point_amt;
607 waittime += self.wait;
608 total_pps += points/waittime;
611 // code from here on is just to support maps that don't have control point and team entities
612 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
614 local entity oldself;
617 self.classname = "dom_team";
618 self.netname = teamname;
619 self.cnt = teamcolor;
620 self.model = pointmodel;
621 self.skin = pointskin;
622 self.noise = capsound;
623 self.noise1 = capnarration;
624 self.message = capmessage;
626 // this code is identical to spawnfunc_dom_team
627 setmodel(self, self.model); // precision not needed
628 self.mdl = self.model;
629 self.dmg = self.modelindex;
632 // this would have to be changed if used in quakeworld
633 self.team = self.cnt + 1;
639 void dom_spawnpoint(vector org)
641 local entity oldself;
644 self.classname = "dom_controlpoint";
645 self.think = spawnfunc_dom_controlpoint;
646 self.nextthink = time;
647 setorigin(self, org);
648 spawnfunc_dom_controlpoint();
652 // spawn some default teams if the map is not set up for domination
653 void dom_spawnteams()
656 if(cvar("g_domination_teams_override") < 2)
657 numteams = cvar("g_domination_default_teams");
659 numteams = cvar("g_domination_teams_override");
660 // LordHavoc: edit this if you want to change defaults
661 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
662 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
664 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
666 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
667 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
670 void dom_delayedinit()
674 // if no teams are found, spawn defaults, if custom teams are set, use them
675 if (find(world, classname, "dom_team") == world || cvar("g_domination_teams_override") >= 2)
677 // if no control points are found, spawn defaults
678 if (find(world, classname, "dom_controlpoint") == world)
680 // here follow default domination points for each map
682 if (world.model == "maps/e1m1.bsp")
684 dom_spawnpoint('0 0 0');
689 // if no supported map was found, make every deathmatch spawn a point
690 head = find(world, classname, "info_player_deathmatch");
693 dom_spawnpoint(head.origin);
694 head = find(head, classname, "info_player_deathmatch");
704 // we have to precache default models/sounds even if they might not be
705 // used because spawnfunc_worldspawn is executed before any other entities are read,
706 // so we don't even know yet if this map is set up for domination...
707 precache_model("models/domination/dom_red.md3");
708 precache_model("models/domination/dom_blue.md3");
709 precache_model("models/domination/dom_yellow.md3");
710 precache_model("models/domination/dom_pink.md3");
711 precache_model("models/domination/dom_unclaimed.md3");
712 precache_sound("domination/claim.wav");
713 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
715 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
716 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
717 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
718 if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
719 if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
721 g_domination_point_rate = cvar("g_domination_point_rate");
722 g_domination_point_amt = cvar("g_domination_point_amt");
724 // teamplay is always on in domination, defaults to hurt self but not teammates
726 // cvar_set("teamplay", "3");