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
33 void send_CSQC_dom_state()
35 WriteByte(MSG_ALL, SVC_TEMPENTITY);
36 WriteByte(MSG_ALL, TE_CSQC_DOM);
37 WriteShort(MSG_ALL, dom_pps_red * 100.0);
38 WriteShort(MSG_ALL, dom_pps_blue * 100.0);
39 if (c3 >= 0) WriteShort(MSG_ALL, dom_pps_yellow * 100.0);
40 if (c4 >= 0) WriteShort(MSG_ALL, dom_pps_pink * 100.0);
42 //Must be called ONLY when a client connects to send total pps and state
43 //If yellow/pink team doesn't exist sends a negative dom_pps_yellow/dom_pps_pink
44 //to let know the client to not read these values anymore
45 void send_CSQC_dom_all()
47 WriteByte(MSG_ALL, SVC_TEMPENTITY);
48 WriteByte(MSG_ALL, TE_CSQC_DOM);
49 WriteShort(MSG_ALL, dom_total_pps * 100.0);
50 WriteShort(MSG_ALL, dom_pps_red * 100.0);
51 WriteShort(MSG_ALL, dom_pps_blue * 100.0);
52 WriteShort(MSG_ALL, dom_pps_yellow * 100.0);
53 WriteShort(MSG_ALL, dom_pps_pink * 100.0);
56 void() dom_controlpoint_setup;
58 void LogDom(string mode, float team_before, entity actor)
61 if(!cvar("sv_eventlog"))
63 s = strcat(":dom:", mode);
64 s = strcat(s, ":", ftos(team_before));
65 s = strcat(s, ":", ftos(actor.playerid));
69 void() dom_spawnteams;
71 void dompoint_captured ()
74 local float old_delay, old_team, real_team;
76 // now that the delay has expired, switch to the latest team to lay claim to this point
82 LogDom("taken", self.team, self.dmg_inflictor);
83 self.dmg_inflictor = world;
85 self.goalentity = head;
86 self.model = head.mdl;
87 self.modelindex = head.dmg;
88 self.skin = head.skin;
90 //bprint(head.message);
93 //bprint(^3head.netname);
94 //bprint(head.netname);
95 //bprint(self.message);
98 float points, wait_time;
99 if (g_domination_point_amt)
100 points = g_domination_point_amt;
103 if (g_domination_point_rate)
104 wait_time = g_domination_point_rate;
106 wait_time = self.wait;
108 bprint("^3", head.netname, "^3", self.message);
110 bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
112 bprint(" ^7(", ftos(points), " point every ", ftos(wait_time), " seconds)\n");
114 if(self.enemy.playerid == self.enemy_playerid)
115 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
119 if (head.noise != "")
121 sound(self.enemy, CHAN_AUTO, head.noise, VOL_BASE, ATTN_NORM);
123 sound(self, CHAN_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
124 if (head.noise1 != "")
125 play2all(head.noise1);
127 //self.nextthink = time + cvar("g_domination_point_rate");
128 //self.think = dompointthink;
130 self.delay = time + wait_time;
133 old_delay = self.delay;
134 old_team = self.team;
135 self.team = real_team;
139 self.delay = old_delay;
140 self.team = old_team;
144 // "fix" pps when slightly under 0 because of approximation errors
146 dom_pps_red -= (points/wait_time);
147 if (dom_pps_red < 0) dom_pps_red = 0;
150 dom_pps_blue -= (points/wait_time);
151 if (dom_pps_blue < 0) dom_pps_blue = 0;
154 dom_pps_yellow -= (points/wait_time);
155 if (dom_pps_yellow < 0) dom_pps_yellow = 0;
158 dom_pps_pink -= (points/wait_time);
159 if (dom_pps_pink < 0) dom_pps_pink = 0;
162 switch(self.goalentity.team)
164 // "fix" pps when slightly over dom_total_pps because of approximation errors
166 dom_pps_red += (points/wait_time);
167 if (dom_pps_red > dom_total_pps) dom_pps_red = dom_total_pps;
168 WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
171 dom_pps_blue += (points/wait_time);
172 if (dom_pps_blue > dom_total_pps) dom_pps_blue = dom_total_pps;
173 WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
176 dom_pps_yellow += (points/wait_time);
177 if (dom_pps_yellow > dom_total_pps) dom_pps_yellow = dom_total_pps;
178 WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
181 dom_pps_pink += (points/wait_time);
182 if (dom_pps_pink > dom_total_pps) dom_pps_pink = dom_total_pps;
183 WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
186 send_CSQC_dom_state();
188 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
189 WaypointSprite_Ping(self.sprite);
194 void AnimateDomPoint()
196 if(self.pain_finished > time)
198 self.pain_finished = time + self.t_width;
199 if(self.nextthink > self.pain_finished)
200 self.nextthink = self.pain_finished;
202 self.frame = self.frame + 1;
203 if(self.frame > self.t_length)
211 self.nextthink = time + 0.1;
213 //self.frame = self.frame + 1;
214 //if(self.frame > 119)
220 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
223 if(g_domination_point_rate)
224 self.delay = time + g_domination_point_rate;
226 self.delay = time + self.wait;
228 // give credit to the team
229 // NOTE: this defaults to 0
230 if (self.goalentity.netname != "")
232 if(g_domination_point_amt)
233 fragamt = g_domination_point_amt;
235 fragamt = self.DOMPOINTFRAGS;
236 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
237 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
239 // give credit to the individual player, if he is still there
240 if (self.enemy.playerid == self.enemy_playerid)
242 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
243 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
253 if (other.classname != "player")
255 if (other.health < 1)
258 if(time < self.captime + 0.3)
261 // only valid teams can claim it
262 head = find(world, classname, "dom_team");
263 while (head && head.team != other.team)
264 head = find(head, classname, "dom_team");
265 if (!head || head.netname == "" || head == self.goalentity)
270 self.team = self.goalentity.team; // this stores the PREVIOUS team!
272 self.cnt = other.team;
273 self.owner = head; // team to switch to after the delay
274 self.dmg_inflictor = other;
277 // self.delay = time + cvar("g_domination_point_capturetime");
278 //self.nextthink = time + cvar("g_domination_point_capturetime");
279 //self.think = dompoint_captured;
281 // go to neutral team in the mean time
282 head = find(world, classname, "dom_team");
283 while (head && head.netname != "")
284 head = find(head, classname, "dom_team");
288 WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
289 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
290 WaypointSprite_Ping(self.sprite);
292 self.goalentity = head;
293 self.model = head.mdl;
294 self.modelindex = head.dmg;
295 self.skin = head.skin;
297 self.enemy = other; // individual player scoring
298 self.enemy_playerid = other.playerid;
302 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
303 Team declaration for Domination gameplay, this allows you to decide what team
304 names and control point models are used in your map.
306 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
307 can have netname set! The nameless team owns all control points at start.
311 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
313 Scoreboard color of the team (for example 4 is red and 13 is blue)
315 Model to use for control points owned by this team (for example
316 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
319 Skin of the model to use (for team skins on a single model)
321 Sound to play when this team captures a point.
322 (this is a localized sound, like a small alarm or other effect)
324 Narrator speech to play when this team captures a point.
325 (this is a global sound, like "Red team has captured a control point")
328 void spawnfunc_dom_team()
330 if(!g_domination || cvar("g_domination_teams_override") >= 2)
335 precache_model(self.model);
336 if (self.noise != "")
337 precache_sound(self.noise);
338 if (self.noise1 != "")
339 precache_sound(self.noise1);
340 self.classname = "dom_team";
341 setmodel(self, self.model); // precision not needed
342 self.mdl = self.model;
343 self.dmg = self.modelindex;
346 // this would have to be changed if used in quakeworld
348 self.team = self.cnt + 1; // WHY are these different anyway?
351 void dom_controlpoint_setup()
354 // find the spawnfunc_dom_team representing unclaimed points
355 head = find(world, classname, "dom_team");
356 while(head && head.netname != "")
357 head = find(head, classname, "dom_team");
359 objerror("no spawnfunc_dom_team with netname \"\" found\n");
361 // copy important properties from spawnfunc_dom_team entity
362 self.goalentity = head;
363 setmodel(self, head.mdl); // precision already set
364 self.skin = head.skin;
369 self.message = " has captured a control point";
371 if(self.DOMPOINTFRAGS <= 0)
372 self.DOMPOINTFRAGS = 1;
376 float points, waittime;
377 if (g_domination_point_rate)
378 points = g_domination_point_rate;
381 if (g_domination_point_amt)
382 waittime = g_domination_point_amt;
384 waittime = self.wait;
386 dom_total_pps += points/waittime;
389 self.t_width = 0.02; // frame animation rate
391 self.t_length = 239; // maximum frame
393 self.think = dompointthink;
394 self.nextthink = time;
395 self.touch = dompointtouch;
396 self.solid = SOLID_TRIGGER;
397 self.flags = FL_ITEM;
398 setsize(self, '-32 -32 -32', '32 32 32');
399 setorigin(self, self.origin + '0 0 20');
402 waypoint_spawnforitem(self);
403 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite);
404 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
409 // player has joined game, get him on a team
411 /*void dom_player_join_team(entity pl)
414 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
415 float balance_teams, force_balance, balance_type;
417 balance_teams = cvar("g_balance_teams");
418 balance_teams = cvar("g_balance_teams_force");
420 c1 = c2 = c3 = c4 = -1;
423 // first find out what teams are allowed
424 head = find(world, classname, "dom_team");
427 if(head.netname != "")
429 //if(head.team == pl.team)
431 if(head.team == COLOR_TEAM1)
435 if(head.team == COLOR_TEAM2)
439 if(head.team == COLOR_TEAM3)
443 if(head.team == COLOR_TEAM4)
448 head = find(head, classname, "dom_team");
451 // make sure there are at least 2 teams to join
453 totalteams = totalteams + 1;
455 totalteams = totalteams + 1;
457 totalteams = totalteams + 1;
459 totalteams = totalteams + 1;
462 error("dom_player_join_team: Too few teams available for domination\n");
464 // whichever teams that are available are set to 0 instead of -1
466 // if we don't care what team he ends up on, put him on whatever team he entered as.
467 // if he's not on a valid team, then put him on the smallest team
468 if(!balance_teams && !force_balance)
470 if( c1 >= 0 && pl.team == COLOR_TEAM1)
471 selectedteam = pl.team;
472 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
473 selectedteam = pl.team;
474 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
475 selectedteam = pl.team;
476 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
477 selectedteam = pl.team;
482 SetPlayerColors(pl, selectedteam - 1);
485 // otherwise end up on the smallest team (handled below)
488 // now count how many players are on each team already
490 head = find(world, classname, "player");
493 //if(head.netname != "")
495 if(head.team == COLOR_TEAM1)
500 if(head.team == COLOR_TEAM2)
505 if(head.team == COLOR_TEAM3)
510 if(head.team == COLOR_TEAM4)
516 head = find(head, classname, "player");
519 // c1...c4 now have counts of each team
520 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
523 smallestteam_count = 999;
525 // 2 gives priority to what team you're already on, 1 goes in order
528 if(balance_type == 1)
530 if(c1 >= 0 && c1 < smallestteam_count)
533 smallestteam_count = c1;
535 if(c2 >= 0 && c2 < smallestteam_count)
538 smallestteam_count = c2;
540 if(c3 >= 0 && c3 < smallestteam_count)
543 smallestteam_count = c3;
545 if(c4 >= 0 && c4 < smallestteam_count)
548 smallestteam_count = c4;
553 if(c1 >= 0 && (c1 < smallestteam_count ||
554 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
557 smallestteam_count = c1;
559 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
560 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
563 smallestteam_count = c2;
565 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
566 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
569 smallestteam_count = c3;
571 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
572 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
575 smallestteam_count = c4;
579 if(smallestteam == 1)
581 selectedteam = COLOR_TEAM1 - 1;
583 if(smallestteam == 2)
585 selectedteam = COLOR_TEAM2 - 1;
587 if(smallestteam == 3)
589 selectedteam = COLOR_TEAM3 - 1;
591 if(smallestteam == 4)
593 selectedteam = COLOR_TEAM4 - 1;
596 SetPlayerColors(pl, selectedteam);
599 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
600 Control point for Domination gameplay.
602 void spawnfunc_dom_controlpoint()
609 self.think = dom_controlpoint_setup;
610 self.nextthink = time + 0.1;
611 self.reset = dom_controlpoint_setup;
616 //if(!self.glow_size)
617 // self.glow_size = cvar("g_domination_point_glow");
618 self.effects = self.effects | EF_LOWPRECISION;
619 if (cvar("g_domination_point_fullbright"))
620 self.effects |= EF_FULLBRIGHT;
623 // code from here on is just to support maps that don't have control point and team entities
624 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
626 local entity oldself;
629 self.classname = "dom_team";
630 self.netname = teamname;
631 self.cnt = teamcolor;
632 self.model = pointmodel;
633 self.skin = pointskin;
634 self.noise = capsound;
635 self.noise1 = capnarration;
636 self.message = capmessage;
638 // this code is identical to spawnfunc_dom_team
639 setmodel(self, self.model); // precision not needed
640 self.mdl = self.model;
641 self.dmg = self.modelindex;
644 // this would have to be changed if used in quakeworld
645 self.team = self.cnt + 1;
651 void dom_spawnpoint(vector org)
653 local entity oldself;
656 self.classname = "dom_controlpoint";
657 self.think = spawnfunc_dom_controlpoint;
658 self.nextthink = time;
659 setorigin(self, org);
660 spawnfunc_dom_controlpoint();
664 // spawn some default teams if the map is not set up for domination
665 void dom_spawnteams()
668 if(cvar("g_domination_teams_override") < 2)
669 numteams = cvar("g_domination_default_teams");
671 numteams = cvar("g_domination_teams_override");
672 // LordHavoc: edit this if you want to change defaults
673 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
674 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
676 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
678 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
679 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
682 void dom_delayedinit()
686 // if no teams are found, spawn defaults, if custom teams are set, use them
687 if (find(world, classname, "dom_team") == world || cvar("g_domination_teams_override") >= 2)
689 // if no control points are found, spawn defaults
690 if (find(world, classname, "dom_controlpoint") == world)
692 // here follow default domination points for each map
694 if (world.model == "maps/e1m1.bsp")
696 dom_spawnpoint('0 0 0');
701 // if no supported map was found, make every deathmatch spawn a point
702 head = find(world, classname, "info_player_deathmatch");
705 dom_spawnpoint(head.origin);
706 head = find(head, classname, "info_player_deathmatch");
710 if (c3 == -1) dom_pps_yellow = -1;
711 if (c4 == -1) dom_pps_pink = -1;
718 // we have to precache default models/sounds even if they might not be
719 // used because spawnfunc_worldspawn is executed before any other entities are read,
720 // so we don't even know yet if this map is set up for domination...
721 precache_model("models/domination/dom_red.md3");
722 precache_model("models/domination/dom_blue.md3");
723 precache_model("models/domination/dom_yellow.md3");
724 precache_model("models/domination/dom_pink.md3");
725 precache_model("models/domination/dom_unclaimed.md3");
726 precache_sound("domination/claim.wav");
727 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
729 g_domination_point_rate = cvar("g_domination_point_rate");
730 g_domination_point_amt = cvar("g_domination_point_amt");
732 // teamplay is always on in domination, defaults to hurt self but not teammates
734 // cvar_set("teamplay", "3");