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 total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
133 for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
135 if (autocvar_g_domination_point_amt)
136 points = autocvar_g_domination_point_amt;
139 if (autocvar_g_domination_point_rate)
140 wait_time = autocvar_g_domination_point_rate;
142 wait_time = head.wait;
143 switch(head.goalentity.team)
146 pps_red += points/wait_time;
149 pps_blue += points/wait_time;
152 pps_yellow += points/wait_time;
155 pps_pink += points/wait_time;
157 total_pps += points/wait_time;
160 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
161 WaypointSprite_Ping(self.sprite);
165 FOR_EACH_REALCLIENT(head)
169 void AnimateDomPoint()
171 if(self.pain_finished > time)
173 self.pain_finished = time + self.t_width;
174 if(self.nextthink > self.pain_finished)
175 self.nextthink = self.pain_finished;
177 self.frame = self.frame + 1;
178 if(self.frame > self.t_length)
186 self.nextthink = time + 0.1;
188 //self.frame = self.frame + 1;
189 //if(self.frame > 119)
195 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
198 if(autocvar_g_domination_point_rate)
199 self.delay = time + autocvar_g_domination_point_rate;
201 self.delay = time + self.wait;
203 // give credit to the team
204 // NOTE: this defaults to 0
205 if (self.goalentity.netname != "")
207 if(autocvar_g_domination_point_amt)
208 fragamt = autocvar_g_domination_point_amt;
210 fragamt = self.DOMPOINTFRAGS;
211 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
212 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
214 // give credit to the individual player, if he is still there
215 if (self.enemy.playerid == self.enemy_playerid)
217 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
218 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
228 if (other.classname != "player")
230 if (other.health < 1)
233 if(time < self.captime + 0.3)
236 // only valid teams can claim it
237 head = find(world, classname, "dom_team");
238 while (head && head.team != other.team)
239 head = find(head, classname, "dom_team");
240 if (!head || head.netname == "" || head == self.goalentity)
245 self.team = self.goalentity.team; // this stores the PREVIOUS team!
247 self.cnt = other.team;
248 self.owner = head; // team to switch to after the delay
249 self.dmg_inflictor = other;
252 // self.delay = time + cvar("g_domination_point_capturetime");
253 //self.nextthink = time + cvar("g_domination_point_capturetime");
254 //self.think = dompoint_captured;
256 // go to neutral team in the mean time
257 head = find(world, classname, "dom_team");
258 while (head && head.netname != "")
259 head = find(head, classname, "dom_team");
263 WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
264 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
265 WaypointSprite_Ping(self.sprite);
267 self.goalentity = head;
268 self.model = head.mdl;
269 self.modelindex = head.dmg;
270 self.skin = head.skin;
272 self.enemy = other; // individual player scoring
273 self.enemy_playerid = other.playerid;
277 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
278 Team declaration for Domination gameplay, this allows you to decide what team
279 names and control point models are used in your map.
281 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
282 can have netname set! The nameless team owns all control points at start.
286 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
288 Scoreboard color of the team (for example 4 is red and 13 is blue)
290 Model to use for control points owned by this team (for example
291 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
294 Skin of the model to use (for team skins on a single model)
296 Sound to play when this team captures a point.
297 (this is a localized sound, like a small alarm or other effect)
299 Narrator speech to play when this team captures a point.
300 (this is a global sound, like "Red team has captured a control point")
303 void spawnfunc_dom_team()
305 if(!g_domination || autocvar_g_domination_teams_override >= 2)
310 precache_model(self.model);
311 if (self.noise != "")
312 precache_sound(self.noise);
313 if (self.noise1 != "")
314 precache_sound(self.noise1);
315 self.classname = "dom_team";
316 setmodel(self, self.model); // precision not needed
317 self.mdl = self.model;
318 self.dmg = self.modelindex;
321 // this would have to be changed if used in quakeworld
323 self.team = self.cnt + 1; // WHY are these different anyway?
326 void dom_controlpoint_setup()
329 // find the spawnfunc_dom_team representing unclaimed points
330 head = find(world, classname, "dom_team");
331 while(head && head.netname != "")
332 head = find(head, classname, "dom_team");
334 objerror("no spawnfunc_dom_team with netname \"\" found\n");
336 // copy important properties from spawnfunc_dom_team entity
337 self.goalentity = head;
338 setmodel(self, head.mdl); // precision already set
339 self.skin = head.skin;
344 self.message = " has captured a control point";
346 if(self.DOMPOINTFRAGS <= 0)
347 self.DOMPOINTFRAGS = 1;
351 float points, waittime;
352 if (autocvar_g_domination_point_amt)
353 points = autocvar_g_domination_point_amt;
356 if (autocvar_g_domination_point_rate)
357 waittime = autocvar_g_domination_point_rate;
359 waittime = self.wait;
361 total_pps += points/waittime;
364 self.t_width = 0.02; // frame animation rate
366 self.t_length = 239; // maximum frame
368 self.think = dompointthink;
369 self.nextthink = time;
370 self.touch = dompointtouch;
371 self.solid = SOLID_TRIGGER;
372 self.flags = FL_ITEM;
373 setsize(self, '-32 -32 -32', '32 32 32');
374 setorigin(self, self.origin + '0 0 20');
377 waypoint_spawnforitem(self);
378 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT, '0 1 1');
383 // player has joined game, get him on a team
385 /*void dom_player_join_team(entity pl)
388 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
389 float balance_teams, force_balance, balance_type;
391 balance_teams = autocvar_g_balance_teams;
392 balance_teams = autocvar_g_balance_teams_force;
394 c1 = c2 = c3 = c4 = -1;
397 // first find out what teams are allowed
398 head = find(world, classname, "dom_team");
401 if(head.netname != "")
403 //if(head.team == pl.team)
405 if(head.team == COLOR_TEAM1)
409 if(head.team == COLOR_TEAM2)
413 if(head.team == COLOR_TEAM3)
417 if(head.team == COLOR_TEAM4)
422 head = find(head, classname, "dom_team");
425 // make sure there are at least 2 teams to join
427 totalteams = totalteams + 1;
429 totalteams = totalteams + 1;
431 totalteams = totalteams + 1;
433 totalteams = totalteams + 1;
436 error("dom_player_join_team: Too few teams available for domination\n");
438 // whichever teams that are available are set to 0 instead of -1
440 // if we don't care what team he ends up on, put him on whatever team he entered as.
441 // if he's not on a valid team, then put him on the smallest team
442 if(!balance_teams && !force_balance)
444 if( c1 >= 0 && pl.team == COLOR_TEAM1)
445 selectedteam = pl.team;
446 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
447 selectedteam = pl.team;
448 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
449 selectedteam = pl.team;
450 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
451 selectedteam = pl.team;
456 SetPlayerColors(pl, selectedteam - 1);
459 // otherwise end up on the smallest team (handled below)
462 // now count how many players are on each team already
464 head = find(world, classname, "player");
467 //if(head.netname != "")
469 if(head.team == COLOR_TEAM1)
474 if(head.team == COLOR_TEAM2)
479 if(head.team == COLOR_TEAM3)
484 if(head.team == COLOR_TEAM4)
490 head = find(head, classname, "player");
493 // c1...c4 now have counts of each team
494 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
497 smallestteam_count = 999;
499 // 2 gives priority to what team you're already on, 1 goes in order
502 if(balance_type == 1)
504 if(c1 >= 0 && c1 < smallestteam_count)
507 smallestteam_count = c1;
509 if(c2 >= 0 && c2 < smallestteam_count)
512 smallestteam_count = c2;
514 if(c3 >= 0 && c3 < smallestteam_count)
517 smallestteam_count = c3;
519 if(c4 >= 0 && c4 < smallestteam_count)
522 smallestteam_count = c4;
527 if(c1 >= 0 && (c1 < smallestteam_count ||
528 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
531 smallestteam_count = c1;
533 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
534 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
537 smallestteam_count = c2;
539 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
540 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
543 smallestteam_count = c3;
545 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
546 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
549 smallestteam_count = c4;
553 if(smallestteam == 1)
555 selectedteam = COLOR_TEAM1 - 1;
557 if(smallestteam == 2)
559 selectedteam = COLOR_TEAM2 - 1;
561 if(smallestteam == 3)
563 selectedteam = COLOR_TEAM3 - 1;
565 if(smallestteam == 4)
567 selectedteam = COLOR_TEAM4 - 1;
570 SetPlayerColors(pl, selectedteam);
573 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
574 Control point for Domination gameplay.
576 void spawnfunc_dom_controlpoint()
583 self.think = dom_controlpoint_setup;
584 self.nextthink = time + 0.1;
585 self.reset = dom_controlpoint_setup;
590 //if(!self.glow_size)
591 // self.glow_size = cvar("g_domination_point_glow");
592 self.effects = self.effects | EF_LOWPRECISION;
593 if (autocvar_g_domination_point_fullbright)
594 self.effects |= EF_FULLBRIGHT;
597 // code from here on is just to support maps that don't have control point and team entities
598 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
600 local entity oldself;
603 self.classname = "dom_team";
604 self.netname = teamname;
605 self.cnt = teamcolor;
606 self.model = pointmodel;
607 self.skin = pointskin;
608 self.noise = capsound;
609 self.noise1 = capnarration;
610 self.message = capmessage;
612 // this code is identical to spawnfunc_dom_team
613 setmodel(self, self.model); // precision not needed
614 self.mdl = self.model;
615 self.dmg = self.modelindex;
618 // this would have to be changed if used in quakeworld
619 self.team = self.cnt + 1;
625 void dom_spawnpoint(vector org)
627 local entity oldself;
630 self.classname = "dom_controlpoint";
631 self.think = spawnfunc_dom_controlpoint;
632 self.nextthink = time;
633 setorigin(self, org);
634 spawnfunc_dom_controlpoint();
638 // spawn some default teams if the map is not set up for domination
639 void dom_spawnteams()
642 if(autocvar_g_domination_teams_override < 2)
643 numteams = autocvar_g_domination_default_teams;
645 numteams = autocvar_g_domination_teams_override;
646 // LordHavoc: edit this if you want to change defaults
647 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
648 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
650 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
652 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
653 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
656 void dom_delayedinit()
660 // if no teams are found, spawn defaults, if custom teams are set, use them
661 if (find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
663 // if no control points are found, spawn defaults
664 if (find(world, classname, "dom_controlpoint") == world)
666 // TODO in a few months (maybe 2011/08): change this into error() and remove this very poor dom point selection
667 backtrace("This map contains no dom_controlpoint entities. A very poor dom point placement will be chosen. Please fix the map.");
669 // if no supported map was found, make every deathmatch spawn a point
670 head = find(world, classname, "info_player_deathmatch");
673 dom_spawnpoint(head.origin);
674 head = find(head, classname, "info_player_deathmatch");
683 // we have to precache default models/sounds even if they might not be
684 // used because spawnfunc_worldspawn is executed before any other entities are read,
685 // so we don't even know yet if this map is set up for domination...
686 precache_model("models/domination/dom_red.md3");
687 precache_model("models/domination/dom_blue.md3");
688 precache_model("models/domination/dom_yellow.md3");
689 precache_model("models/domination/dom_pink.md3");
690 precache_model("models/domination/dom_unclaimed.md3");
691 precache_sound("domination/claim.wav");
692 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
694 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
695 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
696 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
697 if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
698 if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);