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, RADARICON_DOMPOINT, '0 1 1');
400 // player has joined game, get him on a team
402 /*void dom_player_join_team(entity pl)
405 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
406 float balance_teams, force_balance, balance_type;
408 balance_teams = autocvar_g_balance_teams;
409 balance_teams = autocvar_g_balance_teams_force;
411 c1 = c2 = c3 = c4 = -1;
414 // first find out what teams are allowed
415 head = find(world, classname, "dom_team");
418 if(head.netname != "")
420 //if(head.team == pl.team)
422 if(head.team == COLOR_TEAM1)
426 if(head.team == COLOR_TEAM2)
430 if(head.team == COLOR_TEAM3)
434 if(head.team == COLOR_TEAM4)
439 head = find(head, classname, "dom_team");
442 // make sure there are at least 2 teams to join
444 totalteams = totalteams + 1;
446 totalteams = totalteams + 1;
448 totalteams = totalteams + 1;
450 totalteams = totalteams + 1;
453 error("dom_player_join_team: Too few teams available for domination\n");
455 // whichever teams that are available are set to 0 instead of -1
457 // if we don't care what team he ends up on, put him on whatever team he entered as.
458 // if he's not on a valid team, then put him on the smallest team
459 if(!balance_teams && !force_balance)
461 if( c1 >= 0 && pl.team == COLOR_TEAM1)
462 selectedteam = pl.team;
463 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
464 selectedteam = pl.team;
465 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
466 selectedteam = pl.team;
467 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
468 selectedteam = pl.team;
473 SetPlayerColors(pl, selectedteam - 1);
476 // otherwise end up on the smallest team (handled below)
479 // now count how many players are on each team already
481 head = find(world, classname, "player");
484 //if(head.netname != "")
486 if(head.team == COLOR_TEAM1)
491 if(head.team == COLOR_TEAM2)
496 if(head.team == COLOR_TEAM3)
501 if(head.team == COLOR_TEAM4)
507 head = find(head, classname, "player");
510 // c1...c4 now have counts of each team
511 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
514 smallestteam_count = 999;
516 // 2 gives priority to what team you're already on, 1 goes in order
519 if(balance_type == 1)
521 if(c1 >= 0 && c1 < smallestteam_count)
524 smallestteam_count = c1;
526 if(c2 >= 0 && c2 < smallestteam_count)
529 smallestteam_count = c2;
531 if(c3 >= 0 && c3 < smallestteam_count)
534 smallestteam_count = c3;
536 if(c4 >= 0 && c4 < smallestteam_count)
539 smallestteam_count = c4;
544 if(c1 >= 0 && (c1 < smallestteam_count ||
545 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
548 smallestteam_count = c1;
550 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
551 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
554 smallestteam_count = c2;
556 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
557 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
560 smallestteam_count = c3;
562 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
563 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
566 smallestteam_count = c4;
570 if(smallestteam == 1)
572 selectedteam = COLOR_TEAM1 - 1;
574 if(smallestteam == 2)
576 selectedteam = COLOR_TEAM2 - 1;
578 if(smallestteam == 3)
580 selectedteam = COLOR_TEAM3 - 1;
582 if(smallestteam == 4)
584 selectedteam = COLOR_TEAM4 - 1;
587 SetPlayerColors(pl, selectedteam);
590 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
591 Control point for Domination gameplay.
593 void spawnfunc_dom_controlpoint()
600 self.think = dom_controlpoint_setup;
601 self.nextthink = time + 0.1;
602 self.reset = dom_controlpoint_setup;
607 //if(!self.glow_size)
608 // self.glow_size = cvar("g_domination_point_glow");
609 self.effects = self.effects | EF_LOWPRECISION;
610 if (autocvar_g_domination_point_fullbright)
611 self.effects |= EF_FULLBRIGHT;
614 // code from here on is just to support maps that don't have control point and team entities
615 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
617 local entity oldself;
620 self.classname = "dom_team";
621 self.netname = teamname;
622 self.cnt = teamcolor;
623 self.model = pointmodel;
624 self.skin = pointskin;
625 self.noise = capsound;
626 self.noise1 = capnarration;
627 self.message = capmessage;
629 // this code is identical to spawnfunc_dom_team
630 setmodel(self, self.model); // precision not needed
631 self.mdl = self.model;
632 self.dmg = self.modelindex;
635 // this would have to be changed if used in quakeworld
636 self.team = self.cnt + 1;
642 void dom_spawnpoint(vector org)
644 local entity oldself;
647 self.classname = "dom_controlpoint";
648 self.think = spawnfunc_dom_controlpoint;
649 self.nextthink = time;
650 setorigin(self, org);
651 spawnfunc_dom_controlpoint();
655 // spawn some default teams if the map is not set up for domination
656 void dom_spawnteams()
659 if(autocvar_g_domination_teams_override < 2)
660 numteams = autocvar_g_domination_default_teams;
662 numteams = autocvar_g_domination_teams_override;
663 // LordHavoc: edit this if you want to change defaults
664 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
665 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
667 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
669 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
670 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
673 void dom_delayedinit()
677 // if no teams are found, spawn defaults, if custom teams are set, use them
678 if (find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
680 // if no control points are found, spawn defaults
681 if (find(world, classname, "dom_controlpoint") == world)
683 // TODO in a few months (maybe 2011/08): change this into error() and remove this very poor dom point selection
684 backtrace("This map contains no dom_controlpoint entities. A very poor dom point placement will be chosen. Please fix the map.");
686 // if no supported map was found, make every deathmatch spawn a point
687 head = find(world, classname, "info_player_deathmatch");
690 dom_spawnpoint(head.origin);
691 head = find(head, classname, "info_player_deathmatch");
700 // we have to precache default models/sounds even if they might not be
701 // used because spawnfunc_worldspawn is executed before any other entities are read,
702 // so we don't even know yet if this map is set up for domination...
703 precache_model("models/domination/dom_red.md3");
704 precache_model("models/domination/dom_blue.md3");
705 precache_model("models/domination/dom_yellow.md3");
706 precache_model("models/domination/dom_pink.md3");
707 precache_model("models/domination/dom_unclaimed.md3");
708 precache_sound("domination/claim.wav");
709 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
711 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
712 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
713 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
714 if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
715 if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);