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 void() dom_controlpoint_setup;
22 void LogDom(string mode, float team_before, entity actor)
25 if(!cvar("sv_eventlog"))
27 s = strcat(":dom:", mode);
28 s = strcat(s, ":", ftos(team_before));
29 s = strcat(s, ":", ftos(actor.playerid));
30 GameLogEcho(s, FALSE);
33 void() dom_spawnteams;
35 void dompoint_captured ()
38 local float old_delay, old_team, real_team;
40 // now that the delay has expired, switch to the latest team to lay claim to this point
46 LogDom("taken", self.team, self.dmg_inflictor);
47 self.dmg_inflictor = world;
49 self.goalentity = head;
50 self.model = head.mdl;
51 self.modelindex = head.dmg;
52 self.skin = head.skin;
54 //bprint(head.message);
57 //bprint(^3head.netname);
58 //bprint(head.netname);
59 //bprint(self.message);
62 bprint("^3", head.netname, "^3", self.message, "\n");
65 sound(self, CHAN_BODY, head.noise, 1, ATTN_NORM);
66 if (head.noise1 != "")
67 sound(self, CHAN_VOICE, head.noise1, 1, ATTN_NONE);
69 //self.nextthink = time + cvar("g_domination_point_rate");
70 //self.think = dompointthink;
72 if(cvar("g_domination_point_rate"))
73 self.delay = time + cvar("g_domination_point_rate");
75 self.delay = time + self.wait;
78 old_delay = self.delay;
80 self.team = real_team;
84 self.delay = old_delay;
88 void AnimateDomPoint()
90 if(self.pain_finished > time)
92 self.pain_finished = time + self.t_width;
93 if(self.nextthink > self.pain_finished)
94 self.nextthink = self.pain_finished;
96 self.frame = self.frame + 1;
97 if(self.frame > self.t_length)
104 local float waittime;
105 local float teamfragamt;
106 local float individualfragamt;
108 self.nextthink = time + 0.1;
110 //self.frame = self.frame + 1;
111 //if(self.frame > 119)
117 if (gameover || self.delay > time || time < restart_countdown) // game has ended, don't keep giving points
120 waittime = cvar("g_domination_point_rate");
122 waittime = self.wait;
123 self.delay = time + waittime;
125 // give credit to the team
126 // NOTE: this defaults to 0
127 if (self.goalentity.netname)
129 teamfragamt = cvar("g_domination_point_teamamt");
131 teamfragamt = self.DOMPOINTFRAGS;
132 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_DOMPOINTS, teamfragamt);
135 // if the player left the game, changed teams or became spectator, we have to find another player on the same team to give credit to
136 if (!self.enemy.flags || self.enemy.team != self.goalentity.team || self.enemy.killcount == -666) // flags is zero on removed clients
139 FOR_EACH_PLAYER(head)
140 if (head.team == self.goalentity.team)
142 if(self.enemy == other) // search returned no matching player, reset dom point
144 dom_controlpoint_setup();
151 // give credit to the individual player
154 individualfragamt = cvar("g_domination_point_amt");
155 if(!individualfragamt)
156 individualfragamt = self.DOMPOINTFRAGS;
157 UpdateFrags(self.enemy, individualfragamt);
158 PlayerScore_Add(self.enemy, SP_DOM_DOMPOINTS, individualfragamt);
165 if (other.classname != "player")
167 if (other.health < 1)
170 // only valid teams can claim it
171 head = find(world, classname, "dom_team");
172 while (head && head.team != other.team)
173 head = find(head, classname, "dom_team");
174 if (!head || head.netname == "" || head == self.goalentity)
179 self.team = self.goalentity.team; // this stores the PREVIOUS team!
181 self.cnt = other.team;
182 self.aiment = head; // team to switch to after the delay
183 self.dmg_inflictor = other;
186 // self.delay = time + cvar("g_domination_point_capturetime");
187 //self.nextthink = time + cvar("g_domination_point_capturetime");
188 //self.think = dompoint_captured;
190 // go to neutral team in the mean time
191 head = find(world, classname, "dom_team");
192 while (head && head.netname != "")
193 head = find(head, classname, "dom_team");
197 self.goalentity = head;
198 self.model = head.mdl;
199 self.modelindex = head.dmg;
200 self.skin = head.skin;
202 self.enemy = other; // individual player scoring
206 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
207 Team declaration for Domination gameplay, this allows you to decide what team
208 names and control point models are used in your map.
210 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
211 can have netname set! The nameless team owns all control points at start.
215 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
217 Scoreboard color of the team (for example 4 is red and 13 is blue)
219 Model to use for control points owned by this team (for example
220 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
223 Skin of the model to use (for team skins on a single model)
225 Sound to play when this team captures a point.
226 (this is a localized sound, like a small alarm or other effect)
228 Narrator speech to play when this team captures a point.
229 (this is a global sound, like "Red team has captured a control point")
232 void spawnfunc_dom_team()
239 precache_model(self.model);
240 if (self.noise != "")
241 precache_sound(self.noise);
242 if (self.noise1 != "")
243 precache_sound(self.noise1);
244 self.classname = "dom_team";
245 setmodel(self, self.model); // precision not needed
246 self.mdl = self.model;
247 self.dmg = self.modelindex;
250 // this would have to be changed if used in quakeworld
251 self.team = self.cnt + 1;
254 void dom_controlpoint_setup()
257 // find the spawnfunc_dom_team representing unclaimed points
258 head = find(world, classname, "dom_team");
259 while(head && head.netname != "")
260 head = find(head, classname, "dom_team");
262 objerror("no spawnfunc_dom_team with netname \"\" found\n");
264 // copy important properties from spawnfunc_dom_team entity
265 self.goalentity = head;
266 setmodel(self, head.mdl); // precision already set
267 self.skin = head.skin;
272 self.message = " has captured a control point";
274 if(!self.DOMPOINTFRAGS)
275 self.DOMPOINTFRAGS = 1;
280 self.t_width = 0.1; // frame animation rate
282 self.t_length = 119; // maximum frame
284 self.think = dompointthink;
285 self.nextthink = time;
286 self.touch = dompointtouch;
287 self.solid = SOLID_TRIGGER;
288 self.flags = FL_ITEM;
289 setsize(self, '-32 -32 -32', '32 32 32');
290 setorigin(self, self.origin + '0 0 20');
293 waypoint_spawnforitem(self);
298 // player has joined game, get him on a team
300 /*void dom_player_join_team(entity pl)
303 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
304 float balance_teams, force_balance, balance_type;
306 balance_teams = cvar("g_balance_teams");
307 balance_teams = cvar("g_balance_teams_force");
309 c1 = c2 = c3 = c4 = -1;
312 // first find out what teams are allowed
313 head = find(world, classname, "dom_team");
316 if(head.netname != "")
318 //if(head.team == pl.team)
320 if(head.team == COLOR_TEAM1)
324 if(head.team == COLOR_TEAM2)
328 if(head.team == COLOR_TEAM3)
332 if(head.team == COLOR_TEAM4)
337 head = find(head, classname, "dom_team");
340 // make sure there are at least 2 teams to join
342 totalteams = totalteams + 1;
344 totalteams = totalteams + 1;
346 totalteams = totalteams + 1;
348 totalteams = totalteams + 1;
351 error("dom_player_join_team: Too few teams available for domination\n");
353 // whichever teams that are available are set to 0 instead of -1
355 // if we don't care what team he ends up on, put him on whatever team he entered as.
356 // if he's not on a valid team, then put him on the smallest team
357 if(!balance_teams && !force_balance)
359 if( c1 >= 0 && pl.team == COLOR_TEAM1)
360 selectedteam = pl.team;
361 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
362 selectedteam = pl.team;
363 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
364 selectedteam = pl.team;
365 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
366 selectedteam = pl.team;
371 SetPlayerColors(pl, selectedteam - 1);
374 // otherwise end up on the smallest team (handled below)
377 // now count how many players are on each team already
379 head = find(world, classname, "player");
382 //if(head.netname != "")
384 if(head.team == COLOR_TEAM1)
389 if(head.team == COLOR_TEAM2)
394 if(head.team == COLOR_TEAM3)
399 if(head.team == COLOR_TEAM4)
405 head = find(head, classname, "player");
408 // c1...c4 now have counts of each team
409 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
412 smallestteam_count = 999;
414 // 2 gives priority to what team you're already on, 1 goes in order
417 if(balance_type == 1)
419 if(c1 >= 0 && c1 < smallestteam_count)
422 smallestteam_count = c1;
424 if(c2 >= 0 && c2 < smallestteam_count)
427 smallestteam_count = c2;
429 if(c3 >= 0 && c3 < smallestteam_count)
432 smallestteam_count = c3;
434 if(c4 >= 0 && c4 < smallestteam_count)
437 smallestteam_count = c4;
442 if(c1 >= 0 && (c1 < smallestteam_count ||
443 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
446 smallestteam_count = c1;
448 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
449 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
452 smallestteam_count = c2;
454 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
455 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
458 smallestteam_count = c3;
460 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
461 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
464 smallestteam_count = c4;
468 if(smallestteam == 1)
470 selectedteam = COLOR_TEAM1 - 1;
472 if(smallestteam == 2)
474 selectedteam = COLOR_TEAM2 - 1;
476 if(smallestteam == 3)
478 selectedteam = COLOR_TEAM3 - 1;
480 if(smallestteam == 4)
482 selectedteam = COLOR_TEAM4 - 1;
485 SetPlayerColors(pl, selectedteam);
488 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
489 Control point for Domination gameplay.
491 void spawnfunc_dom_controlpoint()
498 self.think = dom_controlpoint_setup;
499 self.nextthink = time + 0.1;
504 //if(!self.glow_size)
505 // self.glow_size = cvar("g_domination_point_glow");
506 self.effects = self.effects | EF_FULLBRIGHT | EF_LOWPRECISION;
509 // code from here on is just to support maps that don't have control point and team entities
510 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
512 local entity oldself;
515 self.classname = "dom_team";
516 self.netname = teamname;
517 self.cnt = teamcolor;
518 self.model = pointmodel;
519 self.skin = pointskin;
520 self.noise = capsound;
521 self.noise1 = capnarration;
522 self.message = capmessage;
524 // this code is identical to spawnfunc_dom_team
525 setmodel(self, self.model); // precision not needed
526 self.mdl = self.model;
527 self.dmg = self.modelindex;
530 // this would have to be changed if used in quakeworld
531 self.team = self.cnt + 1;
537 void dom_spawnpoint(vector org)
539 local entity oldself;
542 self.classname = "dom_controlpoint";
543 self.think = spawnfunc_dom_controlpoint;
544 self.nextthink = time;
546 spawnfunc_dom_controlpoint();
550 // spawn some default teams if the map is not set up for domination
551 void dom_spawnteams()
555 numteams = cvar("g_domination_default_teams");
556 // LordHavoc: edit this if you want to change defaults
557 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
558 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
560 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
562 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
563 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
566 void dom_delayedinit()
570 self.think = SUB_Remove;
571 self.nextthink = time;
572 // if no teams are found, spawn defaults
573 if (find(world, classname, "dom_team") == world)
575 // if no control points are found, spawn defaults
576 if (find(world, classname, "dom_controlpoint") == world)
578 // here follow default domination points for each map
580 if (world.model == "maps/e1m1.bsp")
582 dom_spawnpoint('0 0 0');
587 // if no supported map was found, make every deathmatch spawn a point
588 head = find(world, classname, "info_player_deathmatch");
591 dom_spawnpoint(head.origin);
592 head = find(head, classname, "info_player_deathmatch");
601 // we have to precache default models/sounds even if they might not be
602 // used because spawnfunc_worldspawn is executed before any other entities are read,
603 // so we don't even know yet if this map is set up for domination...
604 precache_model("models/domination/dom_red.md3");
605 precache_model("models/domination/dom_blue.md3");
606 precache_model("models/domination/dom_yellow.md3");
607 precache_model("models/domination/dom_pink.md3");
608 precache_model("models/domination/dom_unclaimed.md3");
609 precache_sound("domination/claim.wav");
611 e.think = dom_delayedinit;
612 e.nextthink = time + 0.1;
614 // teamplay is always on in domination, defaults to hurt self but not teammates
615 //if(!cvar("teamplay"))
616 // cvar_set("teamplay", "3");