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 // BIG ugly hack to make stat sending work
38 e.dom_total_pps = total_pps;
39 e.dom_pps_red = pps_red;
40 e.dom_pps_blue = pps_blue;
42 e.dom_pps_yellow = pps_yellow;
44 e.dom_pps_pink = pps_pink;
47 void() dom_controlpoint_setup;
49 void LogDom(string mode, float team_before, entity actor)
52 if(!autocvar_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 (autocvar_g_domination_point_amt)
91 points = autocvar_g_domination_point_amt;
94 if (autocvar_g_domination_point_rate)
95 wait_time = autocvar_g_domination_point_rate;
97 wait_time = self.wait;
99 bprint("^3", head.netname, "^3", self.message);
101 bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
103 bprint(" ^7(", ftos(points), " point every ", ftos(wait_time), " seconds)\n");
105 if(self.enemy.playerid == self.enemy_playerid)
106 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
110 if (head.noise != "")
112 sound(self.enemy, CHAN_AUTO, head.noise, VOL_BASE, ATTN_NORM);
114 sound(self, CHAN_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
115 if (head.noise1 != "")
116 play2all(head.noise1);
118 //self.nextthink = time + autocvar_g_domination_point_rate;
119 //self.think = dompointthink;
121 self.delay = time + wait_time;
124 old_delay = self.delay;
125 old_team = self.team;
126 self.team = real_team;
130 self.delay = old_delay;
131 self.team = old_team;
135 // "fix" pps when slightly under 0 because of approximation errors
137 pps_red -= (points/wait_time);
138 if (pps_red < 0) pps_red = 0;
141 pps_blue -= (points/wait_time);
142 if (pps_blue < 0) pps_blue = 0;
145 pps_yellow -= (points/wait_time);
146 if (pps_yellow < 0) pps_yellow = 0;
149 pps_pink -= (points/wait_time);
150 if (pps_pink < 0) pps_pink = 0;
153 switch(self.goalentity.team)
155 // "fix" pps when slightly over total_pps because of approximation errors
157 pps_red += (points/wait_time);
158 if (pps_red > total_pps) pps_red = total_pps;
159 WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
162 pps_blue += (points/wait_time);
163 if (pps_blue > total_pps) pps_blue = total_pps;
164 WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
167 pps_yellow += (points/wait_time);
168 if (pps_yellow > total_pps) pps_yellow = total_pps;
169 WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
172 pps_pink += (points/wait_time);
173 if (pps_pink > total_pps) pps_pink = total_pps;
174 WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
177 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
178 WaypointSprite_Ping(self.sprite);
182 FOR_EACH_REALCLIENT(head)
186 void AnimateDomPoint()
188 if(self.pain_finished > time)
190 self.pain_finished = time + self.t_width;
191 if(self.nextthink > self.pain_finished)
192 self.nextthink = self.pain_finished;
194 self.frame = self.frame + 1;
195 if(self.frame > self.t_length)
203 self.nextthink = time + 0.1;
205 //self.frame = self.frame + 1;
206 //if(self.frame > 119)
212 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
215 if(autocvar_g_domination_point_rate)
216 self.delay = time + autocvar_g_domination_point_rate;
218 self.delay = time + self.wait;
220 // give credit to the team
221 // NOTE: this defaults to 0
222 if (self.goalentity.netname != "")
224 if(autocvar_g_domination_point_amt)
225 fragamt = autocvar_g_domination_point_amt;
227 fragamt = self.DOMPOINTFRAGS;
228 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
229 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
231 // give credit to the individual player, if he is still there
232 if (self.enemy.playerid == self.enemy_playerid)
234 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
235 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
245 if (other.classname != "player")
247 if (other.health < 1)
250 if(time < self.captime + 0.3)
253 // only valid teams can claim it
254 head = find(world, classname, "dom_team");
255 while (head && head.team != other.team)
256 head = find(head, classname, "dom_team");
257 if (!head || head.netname == "" || head == self.goalentity)
262 self.team = self.goalentity.team; // this stores the PREVIOUS team!
264 self.cnt = other.team;
265 self.owner = head; // team to switch to after the delay
266 self.dmg_inflictor = other;
269 // self.delay = time + cvar("g_domination_point_capturetime");
270 //self.nextthink = time + cvar("g_domination_point_capturetime");
271 //self.think = dompoint_captured;
273 // go to neutral team in the mean time
274 head = find(world, classname, "dom_team");
275 while (head && head.netname != "")
276 head = find(head, classname, "dom_team");
280 WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
281 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
282 WaypointSprite_Ping(self.sprite);
284 self.goalentity = head;
285 self.model = head.mdl;
286 self.modelindex = head.dmg;
287 self.skin = head.skin;
289 self.enemy = other; // individual player scoring
290 self.enemy_playerid = other.playerid;
294 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
295 Team declaration for Domination gameplay, this allows you to decide what team
296 names and control point models are used in your map.
298 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
299 can have netname set! The nameless team owns all control points at start.
303 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
305 Scoreboard color of the team (for example 4 is red and 13 is blue)
307 Model to use for control points owned by this team (for example
308 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
311 Skin of the model to use (for team skins on a single model)
313 Sound to play when this team captures a point.
314 (this is a localized sound, like a small alarm or other effect)
316 Narrator speech to play when this team captures a point.
317 (this is a global sound, like "Red team has captured a control point")
320 void spawnfunc_dom_team()
322 if(!g_domination || autocvar_g_domination_teams_override >= 2)
327 precache_model(self.model);
328 if (self.noise != "")
329 precache_sound(self.noise);
330 if (self.noise1 != "")
331 precache_sound(self.noise1);
332 self.classname = "dom_team";
333 setmodel(self, self.model); // precision not needed
334 self.mdl = self.model;
335 self.dmg = self.modelindex;
338 // this would have to be changed if used in quakeworld
340 self.team = self.cnt + 1; // WHY are these different anyway?
343 void dom_controlpoint_setup()
346 // find the spawnfunc_dom_team representing unclaimed points
347 head = find(world, classname, "dom_team");
348 while(head && head.netname != "")
349 head = find(head, classname, "dom_team");
351 objerror("no spawnfunc_dom_team with netname \"\" found\n");
353 // copy important properties from spawnfunc_dom_team entity
354 self.goalentity = head;
355 setmodel(self, head.mdl); // precision already set
356 self.skin = head.skin;
361 self.message = " has captured a control point";
363 if(self.DOMPOINTFRAGS <= 0)
364 self.DOMPOINTFRAGS = 1;
368 float points, waittime;
369 if (autocvar_g_domination_point_rate)
370 points = autocvar_g_domination_point_rate;
373 if (autocvar_g_domination_point_amt)
374 waittime = autocvar_g_domination_point_amt;
376 waittime = self.wait;
378 total_pps += points/waittime;
381 self.t_width = 0.02; // frame animation rate
383 self.t_length = 239; // maximum frame
385 self.think = dompointthink;
386 self.nextthink = time;
387 self.touch = dompointtouch;
388 self.solid = SOLID_TRIGGER;
389 self.flags = FL_ITEM;
390 setsize(self, '-32 -32 -32', '32 32 32');
391 setorigin(self, self.origin + '0 0 20');
394 waypoint_spawnforitem(self);
395 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite);
396 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
401 // player has joined game, get him on a team
403 /*void dom_player_join_team(entity pl)
406 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
407 float balance_teams, force_balance, balance_type;
409 balance_teams = autocvar_g_balance_teams;
410 balance_teams = autocvar_g_balance_teams_force;
412 c1 = c2 = c3 = c4 = -1;
415 // first find out what teams are allowed
416 head = find(world, classname, "dom_team");
419 if(head.netname != "")
421 //if(head.team == pl.team)
423 if(head.team == COLOR_TEAM1)
427 if(head.team == COLOR_TEAM2)
431 if(head.team == COLOR_TEAM3)
435 if(head.team == COLOR_TEAM4)
440 head = find(head, classname, "dom_team");
443 // make sure there are at least 2 teams to join
445 totalteams = totalteams + 1;
447 totalteams = totalteams + 1;
449 totalteams = totalteams + 1;
451 totalteams = totalteams + 1;
454 error("dom_player_join_team: Too few teams available for domination\n");
456 // whichever teams that are available are set to 0 instead of -1
458 // if we don't care what team he ends up on, put him on whatever team he entered as.
459 // if he's not on a valid team, then put him on the smallest team
460 if(!balance_teams && !force_balance)
462 if( c1 >= 0 && pl.team == COLOR_TEAM1)
463 selectedteam = pl.team;
464 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
465 selectedteam = pl.team;
466 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
467 selectedteam = pl.team;
468 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
469 selectedteam = pl.team;
474 SetPlayerColors(pl, selectedteam - 1);
477 // otherwise end up on the smallest team (handled below)
480 // now count how many players are on each team already
482 head = find(world, classname, "player");
485 //if(head.netname != "")
487 if(head.team == COLOR_TEAM1)
492 if(head.team == COLOR_TEAM2)
497 if(head.team == COLOR_TEAM3)
502 if(head.team == COLOR_TEAM4)
508 head = find(head, classname, "player");
511 // c1...c4 now have counts of each team
512 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
515 smallestteam_count = 999;
517 // 2 gives priority to what team you're already on, 1 goes in order
520 if(balance_type == 1)
522 if(c1 >= 0 && c1 < smallestteam_count)
525 smallestteam_count = c1;
527 if(c2 >= 0 && c2 < smallestteam_count)
530 smallestteam_count = c2;
532 if(c3 >= 0 && c3 < smallestteam_count)
535 smallestteam_count = c3;
537 if(c4 >= 0 && c4 < smallestteam_count)
540 smallestteam_count = c4;
545 if(c1 >= 0 && (c1 < smallestteam_count ||
546 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
549 smallestteam_count = c1;
551 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
552 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
555 smallestteam_count = c2;
557 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
558 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
561 smallestteam_count = c3;
563 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
564 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
567 smallestteam_count = c4;
571 if(smallestteam == 1)
573 selectedteam = COLOR_TEAM1 - 1;
575 if(smallestteam == 2)
577 selectedteam = COLOR_TEAM2 - 1;
579 if(smallestteam == 3)
581 selectedteam = COLOR_TEAM3 - 1;
583 if(smallestteam == 4)
585 selectedteam = COLOR_TEAM4 - 1;
588 SetPlayerColors(pl, selectedteam);
591 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
592 Control point for Domination gameplay.
594 void spawnfunc_dom_controlpoint()
601 self.think = dom_controlpoint_setup;
602 self.nextthink = time + 0.1;
603 self.reset = dom_controlpoint_setup;
608 //if(!self.glow_size)
609 // self.glow_size = cvar("g_domination_point_glow");
610 self.effects = self.effects | EF_LOWPRECISION;
611 if (autocvar_g_domination_point_fullbright)
612 self.effects |= EF_FULLBRIGHT;
615 // code from here on is just to support maps that don't have control point and team entities
616 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
618 local entity oldself;
621 self.classname = "dom_team";
622 self.netname = teamname;
623 self.cnt = teamcolor;
624 self.model = pointmodel;
625 self.skin = pointskin;
626 self.noise = capsound;
627 self.noise1 = capnarration;
628 self.message = capmessage;
630 // this code is identical to spawnfunc_dom_team
631 setmodel(self, self.model); // precision not needed
632 self.mdl = self.model;
633 self.dmg = self.modelindex;
636 // this would have to be changed if used in quakeworld
637 self.team = self.cnt + 1;
643 void dom_spawnpoint(vector org)
645 local entity oldself;
648 self.classname = "dom_controlpoint";
649 self.think = spawnfunc_dom_controlpoint;
650 self.nextthink = time;
651 setorigin(self, org);
652 spawnfunc_dom_controlpoint();
656 // spawn some default teams if the map is not set up for domination
657 void dom_spawnteams()
660 if(autocvar_g_domination_teams_override < 2)
661 numteams = autocvar_g_domination_default_teams;
663 numteams = autocvar_g_domination_teams_override;
664 // LordHavoc: edit this if you want to change defaults
665 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
666 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
668 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
670 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
671 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
674 void dom_delayedinit()
678 // if no teams are found, spawn defaults, if custom teams are set, use them
679 if (find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
681 // if no control points are found, spawn defaults
682 if (find(world, classname, "dom_controlpoint") == world)
684 // TODO in a few months (maybe 2011/08): change this into error() and remove this very poor dom point selection
685 backtrace("This map contains no dom_controlpoint entities. A very poor dom point placement will be chosen. Please fix the map.");
687 // if no supported map was found, make every deathmatch spawn a point
688 head = find(world, classname, "info_player_deathmatch");
691 dom_spawnpoint(head.origin);
692 head = find(head, classname, "info_player_deathmatch");
701 // we have to precache default models/sounds even if they might not be
702 // used because spawnfunc_worldspawn is executed before any other entities are read,
703 // so we don't even know yet if this map is set up for domination...
704 precache_model("models/domination/dom_red.md3");
705 precache_model("models/domination/dom_blue.md3");
706 precache_model("models/domination/dom_yellow.md3");
707 precache_model("models/domination/dom_pink.md3");
708 precache_model("models/domination/dom_unclaimed.md3");
709 precache_sound("domination/claim.wav");
710 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
712 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
713 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
714 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
715 if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
716 if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);