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;
37 void() dom_controlpoint_setup;
39 void LogDom(string mode, float team_before, entity actor)
42 if(!cvar("sv_eventlog"))
44 s = strcat(":dom:", mode);
45 s = strcat(s, ":", ftos(team_before));
46 s = strcat(s, ":", ftos(actor.playerid));
50 void() dom_spawnteams;
52 void dompoint_captured ()
55 local float old_delay, old_team, real_team;
57 // now that the delay has expired, switch to the latest team to lay claim to this point
63 LogDom("taken", self.team, self.dmg_inflictor);
64 self.dmg_inflictor = world;
66 self.goalentity = head;
67 self.model = head.mdl;
68 self.modelindex = head.dmg;
69 self.skin = head.skin;
71 //bprint(head.message);
74 //bprint(^3head.netname);
75 //bprint(head.netname);
76 //bprint(self.message);
79 float points, wait_time;
80 if (g_domination_point_amt)
81 points = g_domination_point_amt;
84 if (g_domination_point_rate)
85 wait_time = g_domination_point_rate;
87 wait_time = self.wait;
89 bprint("^3", head.netname, "^3", self.message);
90 bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
92 if(self.enemy.playerid == self.enemy_playerid)
93 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
99 sound(self.enemy, CHAN_AUTO, head.noise, VOL_BASE, ATTN_NORM);
101 sound(self, CHAN_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
102 if (head.noise1 != "")
103 play2all(head.noise1);
105 //self.nextthink = time + cvar("g_domination_point_rate");
106 //self.think = dompointthink;
108 self.delay = time + wait_time;
111 old_delay = self.delay;
112 old_team = self.team;
113 self.team = real_team;
117 self.delay = old_delay;
118 self.team = old_team;
123 pps_red -= (points/wait_time);
126 pps_blue -= (points/wait_time);
129 pps_yellow -= (points/wait_time);
132 pps_pink -= (points/wait_time);
135 switch(self.goalentity.team)
138 pps_red += (points/wait_time);
139 WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
142 pps_blue += (points/wait_time);
143 WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
146 pps_yellow += (points/wait_time);
147 WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
150 pps_pink += (points/wait_time);
151 WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
155 FOR_EACH_CLIENT(player)
157 player.dom_pps_red = pps_red;
158 player.dom_pps_blue = pps_blue;
159 player.dom_pps_yellow = pps_yellow;
160 player.dom_pps_pink = pps_pink;
163 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
164 WaypointSprite_Ping(self.sprite);
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(g_domination_point_rate)
199 self.delay = time + 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(g_domination_point_amt)
208 fragamt = 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 || cvar("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)
347 self.DOMPOINTFRAGS = 1;
352 self.t_width = 0.02; // frame animation rate
354 self.t_length = 239; // maximum frame
356 self.think = dompointthink;
357 self.nextthink = time;
358 self.touch = dompointtouch;
359 self.solid = SOLID_TRIGGER;
360 self.flags = FL_ITEM;
361 setsize(self, '-32 -32 -32', '32 32 32');
362 setorigin(self, self.origin + '0 0 20');
365 waypoint_spawnforitem(self);
366 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite);
367 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
372 // player has joined game, get him on a team
374 /*void dom_player_join_team(entity pl)
377 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
378 float balance_teams, force_balance, balance_type;
380 balance_teams = cvar("g_balance_teams");
381 balance_teams = cvar("g_balance_teams_force");
383 c1 = c2 = c3 = c4 = -1;
386 // first find out what teams are allowed
387 head = find(world, classname, "dom_team");
390 if(head.netname != "")
392 //if(head.team == pl.team)
394 if(head.team == COLOR_TEAM1)
398 if(head.team == COLOR_TEAM2)
402 if(head.team == COLOR_TEAM3)
406 if(head.team == COLOR_TEAM4)
411 head = find(head, classname, "dom_team");
414 // make sure there are at least 2 teams to join
416 totalteams = totalteams + 1;
418 totalteams = totalteams + 1;
420 totalteams = totalteams + 1;
422 totalteams = totalteams + 1;
425 error("dom_player_join_team: Too few teams available for domination\n");
427 // whichever teams that are available are set to 0 instead of -1
429 // if we don't care what team he ends up on, put him on whatever team he entered as.
430 // if he's not on a valid team, then put him on the smallest team
431 if(!balance_teams && !force_balance)
433 if( c1 >= 0 && pl.team == COLOR_TEAM1)
434 selectedteam = pl.team;
435 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
436 selectedteam = pl.team;
437 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
438 selectedteam = pl.team;
439 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
440 selectedteam = pl.team;
445 SetPlayerColors(pl, selectedteam - 1);
448 // otherwise end up on the smallest team (handled below)
451 // now count how many players are on each team already
453 head = find(world, classname, "player");
456 //if(head.netname != "")
458 if(head.team == COLOR_TEAM1)
463 if(head.team == COLOR_TEAM2)
468 if(head.team == COLOR_TEAM3)
473 if(head.team == COLOR_TEAM4)
479 head = find(head, classname, "player");
482 // c1...c4 now have counts of each team
483 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
486 smallestteam_count = 999;
488 // 2 gives priority to what team you're already on, 1 goes in order
491 if(balance_type == 1)
493 if(c1 >= 0 && c1 < smallestteam_count)
496 smallestteam_count = c1;
498 if(c2 >= 0 && c2 < smallestteam_count)
501 smallestteam_count = c2;
503 if(c3 >= 0 && c3 < smallestteam_count)
506 smallestteam_count = c3;
508 if(c4 >= 0 && c4 < smallestteam_count)
511 smallestteam_count = c4;
516 if(c1 >= 0 && (c1 < smallestteam_count ||
517 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
520 smallestteam_count = c1;
522 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
523 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
526 smallestteam_count = c2;
528 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
529 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
532 smallestteam_count = c3;
534 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
535 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
538 smallestteam_count = c4;
542 if(smallestteam == 1)
544 selectedteam = COLOR_TEAM1 - 1;
546 if(smallestteam == 2)
548 selectedteam = COLOR_TEAM2 - 1;
550 if(smallestteam == 3)
552 selectedteam = COLOR_TEAM3 - 1;
554 if(smallestteam == 4)
556 selectedteam = COLOR_TEAM4 - 1;
559 SetPlayerColors(pl, selectedteam);
562 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
563 Control point for Domination gameplay.
565 void spawnfunc_dom_controlpoint()
572 self.think = dom_controlpoint_setup;
573 self.nextthink = time + 0.1;
574 self.reset = dom_controlpoint_setup;
579 //if(!self.glow_size)
580 // self.glow_size = cvar("g_domination_point_glow");
581 self.effects = self.effects | EF_LOWPRECISION;
582 if (cvar("g_domination_point_fullbright"))
583 self.effects |= EF_FULLBRIGHT;
585 float points, waittime;
586 if (g_domination_point_rate)
587 points += g_domination_point_rate;
589 points += self.frags;
590 if (g_domination_point_amt)
591 waittime += g_domination_point_amt;
593 waittime += self.wait;
594 total_pps += points/waittime;
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(cvar("g_domination_teams_override") < 2)
643 numteams = cvar("g_domination_default_teams");
645 numteams = cvar("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 || cvar("g_domination_teams_override") >= 2)
663 // if no control points are found, spawn defaults
664 if (find(world, classname, "dom_controlpoint") == world)
666 // here follow default domination points for each map
668 if (world.model == "maps/e1m1.bsp")
670 dom_spawnpoint('0 0 0');
675 // if no supported map was found, make every deathmatch spawn a point
676 head = find(world, classname, "info_player_deathmatch");
679 dom_spawnpoint(head.origin);
680 head = find(head, classname, "info_player_deathmatch");
690 // we have to precache default models/sounds even if they might not be
691 // used because spawnfunc_worldspawn is executed before any other entities are read,
692 // so we don't even know yet if this map is set up for domination...
693 precache_model("models/domination/dom_red.md3");
694 precache_model("models/domination/dom_blue.md3");
695 precache_model("models/domination/dom_yellow.md3");
696 precache_model("models/domination/dom_pink.md3");
697 precache_model("models/domination/dom_unclaimed.md3");
698 precache_sound("domination/claim.wav");
699 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
701 addstat(STAT_DOM_TOTAL_PPS , AS_FLOAT, dom_total_pps);
702 addstat(STAT_DOM_PPS_RED , AS_FLOAT, dom_pps_red);
703 addstat(STAT_DOM_PPS_BLUE , AS_FLOAT, dom_pps_blue);
704 addstat(STAT_DOM_PPS_PINK , AS_FLOAT, dom_pps_pink);
705 addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
707 g_domination_point_rate = cvar("g_domination_point_rate");
708 g_domination_point_amt = cvar("g_domination_point_amt");
710 // teamplay is always on in domination, defaults to hurt self but not teammates
712 // cvar_set("teamplay", "3");