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(entity e)
37 e.dom_total_pps = total_pps;
38 e.dom_pps_red = pps_red;
39 e.dom_pps_blue = pps_blue;
41 e.dom_pps_yellow = pps_yellow;
43 e.dom_pps_pink = pps_pink;
46 void() dom_controlpoint_setup;
48 void LogDom(string mode, float team_before, entity actor)
51 if(!autocvar_sv_eventlog)
53 s = strcat(":dom:", mode);
54 s = strcat(s, ":", ftos(team_before));
55 s = strcat(s, ":", ftos(actor.playerid));
59 void() dom_spawnteams;
61 void dompoint_captured ()
64 local float old_delay, old_team, real_team;
66 // now that the delay has expired, switch to the latest team to lay claim to this point
72 LogDom("taken", self.team, self.dmg_inflictor);
73 self.dmg_inflictor = world;
75 self.goalentity = head;
76 self.model = head.mdl;
77 self.modelindex = head.dmg;
78 self.skin = head.skin;
80 //bprint(head.message);
83 //bprint(^3head.netname);
84 //bprint(head.netname);
85 //bprint(self.message);
88 float points, wait_time;
89 if (autocvar_g_domination_point_amt)
90 points = autocvar_g_domination_point_amt;
93 if (autocvar_g_domination_point_rate)
94 wait_time = autocvar_g_domination_point_rate;
96 wait_time = self.wait;
98 bprint("^3", head.netname, "^3", self.message);
100 bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
102 bprint(" ^7(", ftos(points), " point every ", ftos(wait_time), " seconds)\n");
104 if(self.enemy.playerid == self.enemy_playerid)
105 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
109 if (head.noise != "")
111 sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
113 sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
114 if (head.noise1 != "")
115 play2all(head.noise1);
117 //self.nextthink = time + autocvar_g_domination_point_rate;
118 //self.think = dompointthink;
120 self.delay = time + wait_time;
123 old_delay = self.delay;
124 old_team = self.team;
125 self.team = real_team;
129 self.delay = old_delay;
130 self.team = old_team;
132 switch(self.goalentity.team)
135 WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
138 WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
141 WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
144 WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
147 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
148 for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
150 if (autocvar_g_domination_point_amt)
151 points = autocvar_g_domination_point_amt;
154 if (autocvar_g_domination_point_rate)
155 wait_time = autocvar_g_domination_point_rate;
157 wait_time = head.wait;
158 switch(head.goalentity.team)
161 pps_red += points/wait_time;
164 pps_blue += points/wait_time;
167 pps_yellow += points/wait_time;
170 pps_pink += points/wait_time;
172 total_pps += points/wait_time;
175 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
176 WaypointSprite_Ping(self.sprite);
180 FOR_EACH_REALCLIENT(head)
184 void AnimateDomPoint()
186 if(self.pain_finished > time)
188 self.pain_finished = time + self.t_width;
189 if(self.nextthink > self.pain_finished)
190 self.nextthink = self.pain_finished;
192 self.frame = self.frame + 1;
193 if(self.frame > self.t_length)
201 self.nextthink = time + 0.1;
203 //self.frame = self.frame + 1;
204 //if(self.frame > 119)
210 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
213 if(autocvar_g_domination_point_rate)
214 self.delay = time + autocvar_g_domination_point_rate;
216 self.delay = time + self.wait;
218 // give credit to the team
219 // NOTE: this defaults to 0
220 if (self.goalentity.netname != "")
222 if(autocvar_g_domination_point_amt)
223 fragamt = autocvar_g_domination_point_amt;
225 fragamt = self.DOMPOINTFRAGS;
226 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
227 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
229 // give credit to the individual player, if he is still there
230 if (self.enemy.playerid == self.enemy_playerid)
232 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
233 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
243 if (other.classname != "player")
245 if (other.health < 1)
248 if(time < self.captime + 0.3)
251 // only valid teams can claim it
252 head = find(world, classname, "dom_team");
253 while (head && head.team != other.team)
254 head = find(head, classname, "dom_team");
255 if (!head || head.netname == "" || head == self.goalentity)
260 self.team = self.goalentity.team; // this stores the PREVIOUS team!
262 self.cnt = other.team;
263 self.owner = head; // team to switch to after the delay
264 self.dmg_inflictor = other;
267 // self.delay = time + cvar("g_domination_point_capturetime");
268 //self.nextthink = time + cvar("g_domination_point_capturetime");
269 //self.think = dompoint_captured;
271 // go to neutral team in the mean time
272 head = find(world, classname, "dom_team");
273 while (head && head.netname != "")
274 head = find(head, classname, "dom_team");
278 WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
279 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
280 WaypointSprite_Ping(self.sprite);
282 self.goalentity = head;
283 self.model = head.mdl;
284 self.modelindex = head.dmg;
285 self.skin = head.skin;
287 self.enemy = other; // individual player scoring
288 self.enemy_playerid = other.playerid;
292 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
293 Team declaration for Domination gameplay, this allows you to decide what team
294 names and control point models are used in your map.
296 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
297 can have netname set! The nameless team owns all control points at start.
301 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
303 Scoreboard color of the team (for example 4 is red and 13 is blue)
305 Model to use for control points owned by this team (for example
306 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
309 Skin of the model to use (for team skins on a single model)
311 Sound to play when this team captures a point.
312 (this is a localized sound, like a small alarm or other effect)
314 Narrator speech to play when this team captures a point.
315 (this is a global sound, like "Red team has captured a control point")
318 void spawnfunc_dom_team()
320 if(!g_domination || autocvar_g_domination_teams_override >= 2)
325 precache_model(self.model);
326 if (self.noise != "")
327 precache_sound(self.noise);
328 if (self.noise1 != "")
329 precache_sound(self.noise1);
330 self.classname = "dom_team";
331 setmodel(self, self.model); // precision not needed
332 self.mdl = self.model;
333 self.dmg = self.modelindex;
336 // this would have to be changed if used in quakeworld
338 self.team = self.cnt + 1; // WHY are these different anyway?
341 void dom_controlpoint_setup()
344 // find the spawnfunc_dom_team representing unclaimed points
345 head = find(world, classname, "dom_team");
346 while(head && head.netname != "")
347 head = find(head, classname, "dom_team");
349 objerror("no spawnfunc_dom_team with netname \"\" found\n");
351 // copy important properties from spawnfunc_dom_team entity
352 self.goalentity = head;
353 setmodel(self, head.mdl); // precision already set
354 self.skin = head.skin;
359 self.message = " has captured a control point";
361 if(self.DOMPOINTFRAGS <= 0)
362 self.DOMPOINTFRAGS = 1;
366 float points, waittime;
367 if (autocvar_g_domination_point_amt)
368 points = autocvar_g_domination_point_amt;
371 if (autocvar_g_domination_point_rate)
372 waittime = autocvar_g_domination_point_rate;
374 waittime = self.wait;
376 total_pps += points/waittime;
379 self.t_width = 0.02; // frame animation rate
381 self.t_length = 239; // maximum frame
383 self.think = dompointthink;
384 self.nextthink = time;
385 self.touch = dompointtouch;
386 self.solid = SOLID_TRIGGER;
387 self.flags = FL_ITEM;
388 setsize(self, '-32 -32 -32', '32 32 32');
389 setorigin(self, self.origin + '0 0 20');
392 waypoint_spawnforitem(self);
393 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT, '0 1 1');
398 // player has joined game, get him on a team
400 /*void dom_player_join_team(entity pl)
403 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
404 float balance_teams, force_balance, balance_type;
406 balance_teams = autocvar_g_balance_teams;
407 balance_teams = autocvar_g_balance_teams_force;
409 c1 = c2 = c3 = c4 = -1;
412 // first find out what teams are allowed
413 head = find(world, classname, "dom_team");
416 if(head.netname != "")
418 //if(head.team == pl.team)
420 if(head.team == COLOR_TEAM1)
424 if(head.team == COLOR_TEAM2)
428 if(head.team == COLOR_TEAM3)
432 if(head.team == COLOR_TEAM4)
437 head = find(head, classname, "dom_team");
440 // make sure there are at least 2 teams to join
442 totalteams = totalteams + 1;
444 totalteams = totalteams + 1;
446 totalteams = totalteams + 1;
448 totalteams = totalteams + 1;
451 error("dom_player_join_team: Too few teams available for domination\n");
453 // whichever teams that are available are set to 0 instead of -1
455 // if we don't care what team he ends up on, put him on whatever team he entered as.
456 // if he's not on a valid team, then put him on the smallest team
457 if(!balance_teams && !force_balance)
459 if( c1 >= 0 && pl.team == COLOR_TEAM1)
460 selectedteam = pl.team;
461 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
462 selectedteam = pl.team;
463 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
464 selectedteam = pl.team;
465 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
466 selectedteam = pl.team;
471 SetPlayerColors(pl, selectedteam - 1);
474 // otherwise end up on the smallest team (handled below)
477 // now count how many players are on each team already
479 head = find(world, classname, "player");
482 //if(head.netname != "")
484 if(head.team == COLOR_TEAM1)
489 if(head.team == COLOR_TEAM2)
494 if(head.team == COLOR_TEAM3)
499 if(head.team == COLOR_TEAM4)
505 head = find(head, classname, "player");
508 // c1...c4 now have counts of each team
509 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
512 smallestteam_count = 999;
514 // 2 gives priority to what team you're already on, 1 goes in order
517 if(balance_type == 1)
519 if(c1 >= 0 && c1 < smallestteam_count)
522 smallestteam_count = c1;
524 if(c2 >= 0 && c2 < smallestteam_count)
527 smallestteam_count = c2;
529 if(c3 >= 0 && c3 < smallestteam_count)
532 smallestteam_count = c3;
534 if(c4 >= 0 && c4 < smallestteam_count)
537 smallestteam_count = c4;
542 if(c1 >= 0 && (c1 < smallestteam_count ||
543 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
546 smallestteam_count = c1;
548 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
549 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
552 smallestteam_count = c2;
554 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
555 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
558 smallestteam_count = c3;
560 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
561 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
564 smallestteam_count = c4;
568 if(smallestteam == 1)
570 selectedteam = COLOR_TEAM1 - 1;
572 if(smallestteam == 2)
574 selectedteam = COLOR_TEAM2 - 1;
576 if(smallestteam == 3)
578 selectedteam = COLOR_TEAM3 - 1;
580 if(smallestteam == 4)
582 selectedteam = COLOR_TEAM4 - 1;
585 SetPlayerColors(pl, selectedteam);
588 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
589 Control point for Domination gameplay.
591 void spawnfunc_dom_controlpoint()
598 self.think = dom_controlpoint_setup;
599 self.nextthink = time + 0.1;
600 self.reset = dom_controlpoint_setup;
605 //if(!self.glow_size)
606 // self.glow_size = cvar("g_domination_point_glow");
607 self.effects = self.effects | EF_LOWPRECISION;
608 if (autocvar_g_domination_point_fullbright)
609 self.effects |= EF_FULLBRIGHT;
612 // code from here on is just to support maps that don't have control point and team entities
613 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
615 local entity oldself;
618 self.classname = "dom_team";
619 self.netname = teamname;
620 self.cnt = teamcolor;
621 self.model = pointmodel;
622 self.skin = pointskin;
623 self.noise = capsound;
624 self.noise1 = capnarration;
625 self.message = capmessage;
627 // this code is identical to spawnfunc_dom_team
628 setmodel(self, self.model); // precision not needed
629 self.mdl = self.model;
630 self.dmg = self.modelindex;
633 // this would have to be changed if used in quakeworld
634 self.team = self.cnt + 1;
640 void dom_spawnpoint(vector org)
642 local entity oldself;
645 self.classname = "dom_controlpoint";
646 self.think = spawnfunc_dom_controlpoint;
647 self.nextthink = time;
648 setorigin(self, org);
649 spawnfunc_dom_controlpoint();
653 // spawn some default teams if the map is not set up for domination
654 void dom_spawnteams()
657 if(autocvar_g_domination_teams_override < 2)
658 numteams = autocvar_g_domination_default_teams;
660 numteams = autocvar_g_domination_teams_override;
661 // LordHavoc: edit this if you want to change defaults
662 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
663 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
665 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
667 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
668 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
671 void dom_delayedinit()
675 // if no teams are found, spawn defaults, if custom teams are set, use them
676 if (find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
678 // if no control points are found, spawn defaults
679 if (find(world, classname, "dom_controlpoint") == world)
681 // TODO in a few months (maybe 2011/08): change this into error() and remove this very poor dom point selection
682 backtrace("This map contains no dom_controlpoint entities. A very poor dom point placement will be chosen. Please fix the map.");
684 // if no supported map was found, make every deathmatch spawn a point
685 head = find(world, classname, "info_player_deathmatch");
688 dom_spawnpoint(head.origin);
689 head = find(head, classname, "info_player_deathmatch");
698 // we have to precache default models/sounds even if they might not be
699 // used because spawnfunc_worldspawn is executed before any other entities are read,
700 // so we don't even know yet if this map is set up for domination...
701 precache_model("models/domination/dom_red.md3");
702 precache_model("models/domination/dom_blue.md3");
703 precache_model("models/domination/dom_yellow.md3");
704 precache_model("models/domination/dom_pink.md3");
705 precache_model("models/domination/dom_unclaimed.md3");
706 precache_sound("domination/claim.wav");
707 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
709 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
710 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
711 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
712 if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
713 if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);