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 g_domination_point_amt;
21 float g_domination_point_rate;
23 .float enemy_playerid;
27 // pps: points per second
31 .float dom_pps_yellow;
38 void set_dom_state(entity e, float connecting)
40 if(connecting) e.dom_total_pps = total_pps;
41 e.dom_pps_red = pps_red;
42 e.dom_pps_blue = pps_blue;
43 if(c3 >= 0) e.dom_pps_yellow = pps_yellow;
44 if(c4 >= 0) e.dom_pps_pink = pps_pink;
47 void() dom_controlpoint_setup;
49 void LogDom(string mode, float team_before, entity actor)
52 if(!cvar("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 (g_domination_point_amt)
91 points = g_domination_point_amt;
94 if (g_domination_point_rate)
95 wait_time = 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 + cvar("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 FOR_EACH_CLIENT(head)
178 set_dom_state(head, FALSE);
180 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
181 WaypointSprite_Ping(self.sprite);
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(g_domination_point_rate)
216 self.delay = time + 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(g_domination_point_amt)
225 fragamt = 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 || cvar("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)
364 self.DOMPOINTFRAGS = 1;
369 self.t_width = 0.02; // frame animation rate
371 self.t_length = 239; // maximum frame
373 self.think = dompointthink;
374 self.nextthink = time;
375 self.touch = dompointtouch;
376 self.solid = SOLID_TRIGGER;
377 self.flags = FL_ITEM;
378 setsize(self, '-32 -32 -32', '32 32 32');
379 setorigin(self, self.origin + '0 0 20');
382 waypoint_spawnforitem(self);
383 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite);
384 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
389 // player has joined game, get him on a team
391 /*void dom_player_join_team(entity pl)
394 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
395 float balance_teams, force_balance, balance_type;
397 balance_teams = cvar("g_balance_teams");
398 balance_teams = cvar("g_balance_teams_force");
400 c1 = c2 = c3 = c4 = -1;
403 // first find out what teams are allowed
404 head = find(world, classname, "dom_team");
407 if(head.netname != "")
409 //if(head.team == pl.team)
411 if(head.team == COLOR_TEAM1)
415 if(head.team == COLOR_TEAM2)
419 if(head.team == COLOR_TEAM3)
423 if(head.team == COLOR_TEAM4)
428 head = find(head, classname, "dom_team");
431 // make sure there are at least 2 teams to join
433 totalteams = totalteams + 1;
435 totalteams = totalteams + 1;
437 totalteams = totalteams + 1;
439 totalteams = totalteams + 1;
442 error("dom_player_join_team: Too few teams available for domination\n");
444 // whichever teams that are available are set to 0 instead of -1
446 // if we don't care what team he ends up on, put him on whatever team he entered as.
447 // if he's not on a valid team, then put him on the smallest team
448 if(!balance_teams && !force_balance)
450 if( c1 >= 0 && pl.team == COLOR_TEAM1)
451 selectedteam = pl.team;
452 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
453 selectedteam = pl.team;
454 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
455 selectedteam = pl.team;
456 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
457 selectedteam = pl.team;
462 SetPlayerColors(pl, selectedteam - 1);
465 // otherwise end up on the smallest team (handled below)
468 // now count how many players are on each team already
470 head = find(world, classname, "player");
473 //if(head.netname != "")
475 if(head.team == COLOR_TEAM1)
480 if(head.team == COLOR_TEAM2)
485 if(head.team == COLOR_TEAM3)
490 if(head.team == COLOR_TEAM4)
496 head = find(head, classname, "player");
499 // c1...c4 now have counts of each team
500 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
503 smallestteam_count = 999;
505 // 2 gives priority to what team you're already on, 1 goes in order
508 if(balance_type == 1)
510 if(c1 >= 0 && c1 < smallestteam_count)
513 smallestteam_count = c1;
515 if(c2 >= 0 && c2 < smallestteam_count)
518 smallestteam_count = c2;
520 if(c3 >= 0 && c3 < smallestteam_count)
523 smallestteam_count = c3;
525 if(c4 >= 0 && c4 < smallestteam_count)
528 smallestteam_count = c4;
533 if(c1 >= 0 && (c1 < smallestteam_count ||
534 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
537 smallestteam_count = c1;
539 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
540 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
543 smallestteam_count = c2;
545 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
546 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
549 smallestteam_count = c3;
551 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
552 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
555 smallestteam_count = c4;
559 if(smallestteam == 1)
561 selectedteam = COLOR_TEAM1 - 1;
563 if(smallestteam == 2)
565 selectedteam = COLOR_TEAM2 - 1;
567 if(smallestteam == 3)
569 selectedteam = COLOR_TEAM3 - 1;
571 if(smallestteam == 4)
573 selectedteam = COLOR_TEAM4 - 1;
576 SetPlayerColors(pl, selectedteam);
579 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
580 Control point for Domination gameplay.
582 void spawnfunc_dom_controlpoint()
589 self.think = dom_controlpoint_setup;
590 self.nextthink = time + 0.1;
591 self.reset = dom_controlpoint_setup;
596 //if(!self.glow_size)
597 // self.glow_size = cvar("g_domination_point_glow");
598 self.effects = self.effects | EF_LOWPRECISION;
599 if (cvar("g_domination_point_fullbright"))
600 self.effects |= EF_FULLBRIGHT;
602 float points, waittime;
603 if (g_domination_point_rate)
604 points += g_domination_point_rate;
606 points += self.frags;
607 if (g_domination_point_amt)
608 waittime += g_domination_point_amt;
610 waittime += self.wait;
611 total_pps += points/waittime;
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(cvar("g_domination_teams_override") < 2)
660 numteams = cvar("g_domination_default_teams");
662 numteams = cvar("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 || cvar("g_domination_teams_override") >= 2)
680 // if no control points are found, spawn defaults
681 if (find(world, classname, "dom_controlpoint") == world)
683 // here follow default domination points for each map
685 if (world.model == "maps/e1m1.bsp")
687 dom_spawnpoint('0 0 0');
692 // if no supported map was found, make every deathmatch spawn a point
693 head = find(world, classname, "info_player_deathmatch");
696 dom_spawnpoint(head.origin);
697 head = find(head, classname, "info_player_deathmatch");
707 // we have to precache default models/sounds even if they might not be
708 // used because spawnfunc_worldspawn is executed before any other entities are read,
709 // so we don't even know yet if this map is set up for domination...
710 precache_model("models/domination/dom_red.md3");
711 precache_model("models/domination/dom_blue.md3");
712 precache_model("models/domination/dom_yellow.md3");
713 precache_model("models/domination/dom_pink.md3");
714 precache_model("models/domination/dom_unclaimed.md3");
715 precache_sound("domination/claim.wav");
716 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
718 addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
719 addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
720 addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
721 if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
722 if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
724 g_domination_point_rate = cvar("g_domination_point_rate");
725 g_domination_point_amt = cvar("g_domination_point_amt");
727 // teamplay is always on in domination, defaults to hurt self but not teammates
729 // cvar_set("teamplay", "3");