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 void() dom_controlpoint_setup;
26 void LogDom(string mode, float team_before, entity actor)
29 if(!cvar("sv_eventlog"))
31 s = strcat(":dom:", mode);
32 s = strcat(s, ":", ftos(team_before));
33 s = strcat(s, ":", ftos(actor.playerid));
37 void() dom_spawnteams;
39 void dompoint_captured ()
42 local float old_delay, old_team, real_team;
44 // now that the delay has expired, switch to the latest team to lay claim to this point
50 LogDom("taken", self.team, self.dmg_inflictor);
51 self.dmg_inflictor = world;
53 self.goalentity = head;
54 self.model = head.mdl;
55 self.modelindex = head.dmg;
56 self.skin = head.skin;
58 //bprint(head.message);
61 //bprint(^3head.netname);
62 //bprint(head.netname);
63 //bprint(self.message);
66 bprint("^3", head.netname, "^3", self.message, "\n");
67 if(self.enemy.playerid == self.enemy_playerid)
68 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
74 sound(self.enemy, CHAN_AUTO, head.noise, VOL_BASE, ATTN_NORM);
76 sound(self, CHAN_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
77 if (head.noise1 != "")
78 play2all(head.noise1);
80 //self.nextthink = time + cvar("g_domination_point_rate");
81 //self.think = dompointthink;
83 if(cvar("g_domination_point_rate"))
84 self.delay = time + cvar("g_domination_point_rate");
86 self.delay = time + self.wait;
89 old_delay = self.delay;
91 self.team = real_team;
95 self.delay = old_delay;
98 switch(self.goalentity.team)
101 WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
104 WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
107 WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
110 WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
113 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
114 WaypointSprite_Ping(self.sprite);
119 void AnimateDomPoint()
121 if(self.pain_finished > time)
123 self.pain_finished = time + self.t_width;
124 if(self.nextthink > self.pain_finished)
125 self.nextthink = self.pain_finished;
127 self.frame = self.frame + 1;
128 if(self.frame > self.t_length)
134 local float waittime;
137 self.nextthink = time + 0.1;
139 //self.frame = self.frame + 1;
140 //if(self.frame > 119)
146 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
149 waittime = cvar("g_domination_point_rate");
151 waittime = self.wait;
152 self.delay = time + waittime;
154 // give credit to the team
155 // NOTE: this defaults to 0
156 if (self.goalentity.netname != "")
158 fragamt = cvar("g_domination_point_amt");
160 fragamt = self.DOMPOINTFRAGS;
161 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
162 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
164 // give credit to the individual player, if he is still there
165 if (self.enemy.playerid == self.enemy_playerid)
167 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
168 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
178 if (other.classname != "player")
180 if (other.health < 1)
183 if(time < self.captime + 0.3)
186 // only valid teams can claim it
187 head = find(world, classname, "dom_team");
188 while (head && head.team != other.team)
189 head = find(head, classname, "dom_team");
190 if (!head || head.netname == "" || head == self.goalentity)
195 self.team = self.goalentity.team; // this stores the PREVIOUS team!
197 self.cnt = other.team;
198 self.owner = head; // team to switch to after the delay
199 self.dmg_inflictor = other;
202 // self.delay = time + cvar("g_domination_point_capturetime");
203 //self.nextthink = time + cvar("g_domination_point_capturetime");
204 //self.think = dompoint_captured;
206 // go to neutral team in the mean time
207 head = find(world, classname, "dom_team");
208 while (head && head.netname != "")
209 head = find(head, classname, "dom_team");
213 WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
214 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
215 WaypointSprite_Ping(self.sprite);
217 self.goalentity = head;
218 self.model = head.mdl;
219 self.modelindex = head.dmg;
220 self.skin = head.skin;
222 self.enemy = other; // individual player scoring
223 self.enemy_playerid = other.playerid;
227 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
228 Team declaration for Domination gameplay, this allows you to decide what team
229 names and control point models are used in your map.
231 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
232 can have netname set! The nameless team owns all control points at start.
236 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
238 Scoreboard color of the team (for example 4 is red and 13 is blue)
240 Model to use for control points owned by this team (for example
241 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
244 Skin of the model to use (for team skins on a single model)
246 Sound to play when this team captures a point.
247 (this is a localized sound, like a small alarm or other effect)
249 Narrator speech to play when this team captures a point.
250 (this is a global sound, like "Red team has captured a control point")
253 void spawnfunc_dom_team()
255 if(!g_domination || cvar("g_domination_teams_override") >= 2)
260 precache_model(self.model);
261 if (self.noise != "")
262 precache_sound(self.noise);
263 if (self.noise1 != "")
264 precache_sound(self.noise1);
265 self.classname = "dom_team";
266 setmodel(self, self.model); // precision not needed
267 self.mdl = self.model;
268 self.dmg = self.modelindex;
271 // this would have to be changed if used in quakeworld
273 self.team = self.cnt + 1; // WHY are these different anyway?
276 void dom_controlpoint_setup()
279 // find the spawnfunc_dom_team representing unclaimed points
280 head = find(world, classname, "dom_team");
281 while(head && head.netname != "")
282 head = find(head, classname, "dom_team");
284 objerror("no spawnfunc_dom_team with netname \"\" found\n");
286 // copy important properties from spawnfunc_dom_team entity
287 self.goalentity = head;
288 setmodel(self, head.mdl); // precision already set
289 self.skin = head.skin;
294 self.message = " has captured a control point";
296 if(!self.DOMPOINTFRAGS)
297 self.DOMPOINTFRAGS = 1;
302 self.t_width = 0.02; // frame animation rate
304 self.t_length = 239; // maximum frame
306 self.think = dompointthink;
307 self.nextthink = time;
308 self.touch = dompointtouch;
309 self.solid = SOLID_TRIGGER;
310 self.flags = FL_ITEM;
311 setsize(self, '-32 -32 -32', '32 32 32');
312 setorigin(self, self.origin + '0 0 20');
315 waypoint_spawnforitem(self);
316 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite);
317 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
322 // player has joined game, get him on a team
324 /*void dom_player_join_team(entity pl)
327 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
328 float balance_teams, force_balance, balance_type;
330 balance_teams = cvar("g_balance_teams");
331 balance_teams = cvar("g_balance_teams_force");
333 c1 = c2 = c3 = c4 = -1;
336 // first find out what teams are allowed
337 head = find(world, classname, "dom_team");
340 if(head.netname != "")
342 //if(head.team == pl.team)
344 if(head.team == COLOR_TEAM1)
348 if(head.team == COLOR_TEAM2)
352 if(head.team == COLOR_TEAM3)
356 if(head.team == COLOR_TEAM4)
361 head = find(head, classname, "dom_team");
364 // make sure there are at least 2 teams to join
366 totalteams = totalteams + 1;
368 totalteams = totalteams + 1;
370 totalteams = totalteams + 1;
372 totalteams = totalteams + 1;
375 error("dom_player_join_team: Too few teams available for domination\n");
377 // whichever teams that are available are set to 0 instead of -1
379 // if we don't care what team he ends up on, put him on whatever team he entered as.
380 // if he's not on a valid team, then put him on the smallest team
381 if(!balance_teams && !force_balance)
383 if( c1 >= 0 && pl.team == COLOR_TEAM1)
384 selectedteam = pl.team;
385 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
386 selectedteam = pl.team;
387 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
388 selectedteam = pl.team;
389 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
390 selectedteam = pl.team;
395 SetPlayerColors(pl, selectedteam - 1);
398 // otherwise end up on the smallest team (handled below)
401 // now count how many players are on each team already
403 head = find(world, classname, "player");
406 //if(head.netname != "")
408 if(head.team == COLOR_TEAM1)
413 if(head.team == COLOR_TEAM2)
418 if(head.team == COLOR_TEAM3)
423 if(head.team == COLOR_TEAM4)
429 head = find(head, classname, "player");
432 // c1...c4 now have counts of each team
433 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
436 smallestteam_count = 999;
438 // 2 gives priority to what team you're already on, 1 goes in order
441 if(balance_type == 1)
443 if(c1 >= 0 && c1 < smallestteam_count)
446 smallestteam_count = c1;
448 if(c2 >= 0 && c2 < smallestteam_count)
451 smallestteam_count = c2;
453 if(c3 >= 0 && c3 < smallestteam_count)
456 smallestteam_count = c3;
458 if(c4 >= 0 && c4 < smallestteam_count)
461 smallestteam_count = c4;
466 if(c1 >= 0 && (c1 < smallestteam_count ||
467 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
470 smallestteam_count = c1;
472 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
473 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
476 smallestteam_count = c2;
478 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
479 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
482 smallestteam_count = c3;
484 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
485 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
488 smallestteam_count = c4;
492 if(smallestteam == 1)
494 selectedteam = COLOR_TEAM1 - 1;
496 if(smallestteam == 2)
498 selectedteam = COLOR_TEAM2 - 1;
500 if(smallestteam == 3)
502 selectedteam = COLOR_TEAM3 - 1;
504 if(smallestteam == 4)
506 selectedteam = COLOR_TEAM4 - 1;
509 SetPlayerColors(pl, selectedteam);
512 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
513 Control point for Domination gameplay.
515 void spawnfunc_dom_controlpoint()
522 self.think = dom_controlpoint_setup;
523 self.nextthink = time + 0.1;
524 self.reset = dom_controlpoint_setup;
529 //if(!self.glow_size)
530 // self.glow_size = cvar("g_domination_point_glow");
531 self.effects = self.effects | EF_LOWPRECISION;
532 if (cvar("g_domination_point_fullbright"))
533 self.effects |= EF_FULLBRIGHT;
536 // code from here on is just to support maps that don't have control point and team entities
537 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
539 local entity oldself;
542 self.classname = "dom_team";
543 self.netname = teamname;
544 self.cnt = teamcolor;
545 self.model = pointmodel;
546 self.skin = pointskin;
547 self.noise = capsound;
548 self.noise1 = capnarration;
549 self.message = capmessage;
551 // this code is identical to spawnfunc_dom_team
552 setmodel(self, self.model); // precision not needed
553 self.mdl = self.model;
554 self.dmg = self.modelindex;
557 // this would have to be changed if used in quakeworld
558 self.team = self.cnt + 1;
564 void dom_spawnpoint(vector org)
566 local entity oldself;
569 self.classname = "dom_controlpoint";
570 self.think = spawnfunc_dom_controlpoint;
571 self.nextthink = time;
572 setorigin(self, org);
573 spawnfunc_dom_controlpoint();
577 // spawn some default teams if the map is not set up for domination
578 void dom_spawnteams()
581 if(cvar("g_domination_teams_override") < 2)
582 numteams = cvar("g_domination_default_teams");
584 numteams = cvar("g_domination_teams_override");
585 // LordHavoc: edit this if you want to change defaults
586 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
587 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
589 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
591 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
592 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
595 void dom_delayedinit()
599 // if no teams are found, spawn defaults, if custom teams are set, use them
600 if (find(world, classname, "dom_team") == world || cvar("g_domination_teams_override") >= 2)
602 // if no control points are found, spawn defaults
603 if (find(world, classname, "dom_controlpoint") == world)
605 // here follow default domination points for each map
607 if (world.model == "maps/e1m1.bsp")
609 dom_spawnpoint('0 0 0');
614 // if no supported map was found, make every deathmatch spawn a point
615 head = find(world, classname, "info_player_deathmatch");
618 dom_spawnpoint(head.origin);
619 head = find(head, classname, "info_player_deathmatch");
629 // we have to precache default models/sounds even if they might not be
630 // used because spawnfunc_worldspawn is executed before any other entities are read,
631 // so we don't even know yet if this map is set up for domination...
632 precache_model("models/domination/dom_red.md3");
633 precache_model("models/domination/dom_blue.md3");
634 precache_model("models/domination/dom_yellow.md3");
635 precache_model("models/domination/dom_pink.md3");
636 precache_model("models/domination/dom_unclaimed.md3");
637 precache_sound("domination/claim.wav");
638 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
640 // teamplay is always on in domination, defaults to hurt self but not teammates
642 // cvar_set("teamplay", "3");