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
30 void send_CSQC_dom_state()
32 WriteByte(MSG_ALL, SVC_TEMPENTITY);
33 WriteByte(MSG_ALL, TE_CSQC_DOM);
34 WriteShort(MSG_ALL, dom_pps_red * 100.0);
35 WriteShort(MSG_ALL, dom_pps_blue * 100.0);
36 if (c3 >= 0) WriteShort(MSG_ALL, dom_pps_yellow * 100.0);
37 if (c4 >= 0) WriteShort(MSG_ALL, dom_pps_pink * 100.0);
39 //Must be called ONLY when a client connects to send total pps and state
40 //If yellow/pink team doesn't exist sends a negative dom_pps_yellow/dom_pps_pink
41 //to let know the client to not read these values anymore
42 void send_CSQC_dom_all()
44 WriteByte(MSG_ALL, SVC_TEMPENTITY);
45 WriteByte(MSG_ALL, TE_CSQC_DOM);
46 WriteShort(MSG_ALL, dom_total_pps * 100.0);
47 WriteShort(MSG_ALL, dom_pps_red * 100.0);
48 WriteShort(MSG_ALL, dom_pps_blue * 100.0);
49 WriteShort(MSG_ALL, dom_pps_yellow * 100.0);
50 WriteShort(MSG_ALL, dom_pps_pink * 100.0);
53 void() dom_controlpoint_setup;
55 void LogDom(string mode, float team_before, entity actor)
58 if(!autocvar_sv_eventlog)
60 s = strcat(":dom:", mode);
61 s = strcat(s, ":", ftos(team_before));
62 s = strcat(s, ":", ftos(actor.playerid));
66 void() dom_spawnteams;
68 void dompoint_captured ()
71 local float old_delay, old_team, real_team;
73 // now that the delay has expired, switch to the latest team to lay claim to this point
79 LogDom("taken", self.team, self.dmg_inflictor);
80 self.dmg_inflictor = world;
82 self.goalentity = head;
83 self.model = head.mdl;
84 self.modelindex = head.dmg;
85 self.skin = head.skin;
87 //bprint(head.message);
90 //bprint(^3head.netname);
91 //bprint(head.netname);
92 //bprint(self.message);
95 float points, wait_time;
96 if (autocvar_g_domination_point_amt)
97 points = autocvar_g_domination_point_amt;
100 if (autocvar_g_domination_point_rate)
101 wait_time = autocvar_g_domination_point_rate;
103 wait_time = self.wait;
105 bprint("^3", head.netname, "^3", self.message);
107 bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
109 bprint(" ^7(", ftos(points), " point every ", ftos(wait_time), " seconds)\n");
111 if(self.enemy.playerid == self.enemy_playerid)
112 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
116 if (head.noise != "")
118 sound(self.enemy, CHAN_AUTO, head.noise, VOL_BASE, ATTN_NORM);
120 sound(self, CHAN_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
121 if (head.noise1 != "")
122 play2all(head.noise1);
124 //self.nextthink = time + autocvar_g_domination_point_rate;
125 //self.think = dompointthink;
127 self.delay = time + wait_time;
130 old_delay = self.delay;
131 old_team = self.team;
132 self.team = real_team;
136 self.delay = old_delay;
137 self.team = old_team;
141 // "fix" pps when slightly under 0 because of approximation errors
143 dom_pps_red -= (points/wait_time);
144 if (dom_pps_red < 0) dom_pps_red = 0;
147 dom_pps_blue -= (points/wait_time);
148 if (dom_pps_blue < 0) dom_pps_blue = 0;
151 dom_pps_yellow -= (points/wait_time);
152 if (dom_pps_yellow < 0) dom_pps_yellow = 0;
155 dom_pps_pink -= (points/wait_time);
156 if (dom_pps_pink < 0) dom_pps_pink = 0;
159 switch(self.goalentity.team)
161 // "fix" pps when slightly over dom_total_pps because of approximation errors
163 dom_pps_red += (points/wait_time);
164 if (dom_pps_red > dom_total_pps) dom_pps_red = dom_total_pps;
165 WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
168 dom_pps_blue += (points/wait_time);
169 if (dom_pps_blue > dom_total_pps) dom_pps_blue = dom_total_pps;
170 WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
173 dom_pps_yellow += (points/wait_time);
174 if (dom_pps_yellow > dom_total_pps) dom_pps_yellow = dom_total_pps;
175 WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
178 dom_pps_pink += (points/wait_time);
179 if (dom_pps_pink > dom_total_pps) dom_pps_pink = dom_total_pps;
180 WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
183 send_CSQC_dom_state();
185 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
186 WaypointSprite_Ping(self.sprite);
191 void AnimateDomPoint()
193 if(self.pain_finished > time)
195 self.pain_finished = time + self.t_width;
196 if(self.nextthink > self.pain_finished)
197 self.nextthink = self.pain_finished;
199 self.frame = self.frame + 1;
200 if(self.frame > self.t_length)
208 self.nextthink = time + 0.1;
210 //self.frame = self.frame + 1;
211 //if(self.frame > 119)
217 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
220 if(autocvar_g_domination_point_rate)
221 self.delay = time + autocvar_g_domination_point_rate;
223 self.delay = time + self.wait;
225 // give credit to the team
226 // NOTE: this defaults to 0
227 if (self.goalentity.netname != "")
229 if(autocvar_g_domination_point_amt)
230 fragamt = autocvar_g_domination_point_amt;
232 fragamt = self.DOMPOINTFRAGS;
233 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
234 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
236 // give credit to the individual player, if he is still there
237 if (self.enemy.playerid == self.enemy_playerid)
239 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
240 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
250 if (other.classname != "player")
252 if (other.health < 1)
255 if(time < self.captime + 0.3)
258 // only valid teams can claim it
259 head = find(world, classname, "dom_team");
260 while (head && head.team != other.team)
261 head = find(head, classname, "dom_team");
262 if (!head || head.netname == "" || head == self.goalentity)
267 self.team = self.goalentity.team; // this stores the PREVIOUS team!
269 self.cnt = other.team;
270 self.owner = head; // team to switch to after the delay
271 self.dmg_inflictor = other;
274 // self.delay = time + cvar("g_domination_point_capturetime");
275 //self.nextthink = time + cvar("g_domination_point_capturetime");
276 //self.think = dompoint_captured;
278 // go to neutral team in the mean time
279 head = find(world, classname, "dom_team");
280 while (head && head.netname != "")
281 head = find(head, classname, "dom_team");
285 WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
286 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
287 WaypointSprite_Ping(self.sprite);
289 self.goalentity = head;
290 self.model = head.mdl;
291 self.modelindex = head.dmg;
292 self.skin = head.skin;
294 self.enemy = other; // individual player scoring
295 self.enemy_playerid = other.playerid;
299 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
300 Team declaration for Domination gameplay, this allows you to decide what team
301 names and control point models are used in your map.
303 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
304 can have netname set! The nameless team owns all control points at start.
308 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
310 Scoreboard color of the team (for example 4 is red and 13 is blue)
312 Model to use for control points owned by this team (for example
313 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
316 Skin of the model to use (for team skins on a single model)
318 Sound to play when this team captures a point.
319 (this is a localized sound, like a small alarm or other effect)
321 Narrator speech to play when this team captures a point.
322 (this is a global sound, like "Red team has captured a control point")
325 void spawnfunc_dom_team()
327 if(!g_domination || autocvar_g_domination_teams_override >= 2)
332 precache_model(self.model);
333 if (self.noise != "")
334 precache_sound(self.noise);
335 if (self.noise1 != "")
336 precache_sound(self.noise1);
337 self.classname = "dom_team";
338 setmodel(self, self.model); // precision not needed
339 self.mdl = self.model;
340 self.dmg = self.modelindex;
343 // this would have to be changed if used in quakeworld
345 self.team = self.cnt + 1; // WHY are these different anyway?
348 void dom_controlpoint_setup()
351 // find the spawnfunc_dom_team representing unclaimed points
352 head = find(world, classname, "dom_team");
353 while(head && head.netname != "")
354 head = find(head, classname, "dom_team");
356 objerror("no spawnfunc_dom_team with netname \"\" found\n");
358 // copy important properties from spawnfunc_dom_team entity
359 self.goalentity = head;
360 setmodel(self, head.mdl); // precision already set
361 self.skin = head.skin;
366 self.message = " has captured a control point";
368 if(self.DOMPOINTFRAGS <= 0)
369 self.DOMPOINTFRAGS = 1;
373 float points, waittime;
374 if (autocvar_g_domination_point_rate)
375 points = autocvar_g_domination_point_rate;
378 if (autocvar_g_domination_point_amt)
379 waittime = autocvar_g_domination_point_amt;
381 waittime = self.wait;
383 dom_total_pps += points/waittime;
386 self.t_width = 0.02; // frame animation rate
388 self.t_length = 239; // maximum frame
390 self.think = dompointthink;
391 self.nextthink = time;
392 self.touch = dompointtouch;
393 self.solid = SOLID_TRIGGER;
394 self.flags = FL_ITEM;
395 setsize(self, '-32 -32 -32', '32 32 32');
396 setorigin(self, self.origin + '0 0 20');
399 waypoint_spawnforitem(self);
400 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite);
401 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
406 // player has joined game, get him on a team
408 /*void dom_player_join_team(entity pl)
411 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
412 float balance_teams, force_balance, balance_type;
414 balance_teams = autocvar_g_balance_teams;
415 balance_teams = autocvar_g_balance_teams_force;
417 c1 = c2 = c3 = c4 = -1;
420 // first find out what teams are allowed
421 head = find(world, classname, "dom_team");
424 if(head.netname != "")
426 //if(head.team == pl.team)
428 if(head.team == COLOR_TEAM1)
432 if(head.team == COLOR_TEAM2)
436 if(head.team == COLOR_TEAM3)
440 if(head.team == COLOR_TEAM4)
445 head = find(head, classname, "dom_team");
448 // make sure there are at least 2 teams to join
450 totalteams = totalteams + 1;
452 totalteams = totalteams + 1;
454 totalteams = totalteams + 1;
456 totalteams = totalteams + 1;
459 error("dom_player_join_team: Too few teams available for domination\n");
461 // whichever teams that are available are set to 0 instead of -1
463 // if we don't care what team he ends up on, put him on whatever team he entered as.
464 // if he's not on a valid team, then put him on the smallest team
465 if(!balance_teams && !force_balance)
467 if( c1 >= 0 && pl.team == COLOR_TEAM1)
468 selectedteam = pl.team;
469 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
470 selectedteam = pl.team;
471 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
472 selectedteam = pl.team;
473 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
474 selectedteam = pl.team;
479 SetPlayerColors(pl, selectedteam - 1);
482 // otherwise end up on the smallest team (handled below)
485 // now count how many players are on each team already
487 head = find(world, classname, "player");
490 //if(head.netname != "")
492 if(head.team == COLOR_TEAM1)
497 if(head.team == COLOR_TEAM2)
502 if(head.team == COLOR_TEAM3)
507 if(head.team == COLOR_TEAM4)
513 head = find(head, classname, "player");
516 // c1...c4 now have counts of each team
517 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
520 smallestteam_count = 999;
522 // 2 gives priority to what team you're already on, 1 goes in order
525 if(balance_type == 1)
527 if(c1 >= 0 && c1 < smallestteam_count)
530 smallestteam_count = c1;
532 if(c2 >= 0 && c2 < smallestteam_count)
535 smallestteam_count = c2;
537 if(c3 >= 0 && c3 < smallestteam_count)
540 smallestteam_count = c3;
542 if(c4 >= 0 && c4 < smallestteam_count)
545 smallestteam_count = c4;
550 if(c1 >= 0 && (c1 < smallestteam_count ||
551 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
554 smallestteam_count = c1;
556 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
557 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
560 smallestteam_count = c2;
562 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
563 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
566 smallestteam_count = c3;
568 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
569 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
572 smallestteam_count = c4;
576 if(smallestteam == 1)
578 selectedteam = COLOR_TEAM1 - 1;
580 if(smallestteam == 2)
582 selectedteam = COLOR_TEAM2 - 1;
584 if(smallestteam == 3)
586 selectedteam = COLOR_TEAM3 - 1;
588 if(smallestteam == 4)
590 selectedteam = COLOR_TEAM4 - 1;
593 SetPlayerColors(pl, selectedteam);
596 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
597 Control point for Domination gameplay.
599 void spawnfunc_dom_controlpoint()
606 self.think = dom_controlpoint_setup;
607 self.nextthink = time + 0.1;
608 self.reset = dom_controlpoint_setup;
613 //if(!self.glow_size)
614 // self.glow_size = cvar("g_domination_point_glow");
615 self.effects = self.effects | EF_LOWPRECISION;
616 if (autocvar_g_domination_point_fullbright)
617 self.effects |= EF_FULLBRIGHT;
620 // code from here on is just to support maps that don't have control point and team entities
621 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
623 local entity oldself;
626 self.classname = "dom_team";
627 self.netname = teamname;
628 self.cnt = teamcolor;
629 self.model = pointmodel;
630 self.skin = pointskin;
631 self.noise = capsound;
632 self.noise1 = capnarration;
633 self.message = capmessage;
635 // this code is identical to spawnfunc_dom_team
636 setmodel(self, self.model); // precision not needed
637 self.mdl = self.model;
638 self.dmg = self.modelindex;
641 // this would have to be changed if used in quakeworld
642 self.team = self.cnt + 1;
648 void dom_spawnpoint(vector org)
650 local entity oldself;
653 self.classname = "dom_controlpoint";
654 self.think = spawnfunc_dom_controlpoint;
655 self.nextthink = time;
656 setorigin(self, org);
657 spawnfunc_dom_controlpoint();
661 // spawn some default teams if the map is not set up for domination
662 void dom_spawnteams()
665 if(autocvar_g_domination_teams_override < 2)
666 numteams = autocvar_g_domination_default_teams;
668 numteams = autocvar_g_domination_teams_override;
669 // LordHavoc: edit this if you want to change defaults
670 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
671 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
673 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
675 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
676 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
679 void dom_delayedinit()
683 // if no teams are found, spawn defaults, if custom teams are set, use them
684 if (find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
686 // if no control points are found, spawn defaults
687 if (find(world, classname, "dom_controlpoint") == world)
689 // here follow default domination points for each map
691 if (world.model == "maps/e1m1.bsp")
693 dom_spawnpoint('0 0 0');
698 // if no supported map was found, make every deathmatch spawn a point
699 head = find(world, classname, "info_player_deathmatch");
702 dom_spawnpoint(head.origin);
703 head = find(head, classname, "info_player_deathmatch");
707 if (c3 == -1) dom_pps_yellow = -1;
708 if (c4 == -1) dom_pps_pink = -1;
715 // we have to precache default models/sounds even if they might not be
716 // used because spawnfunc_worldspawn is executed before any other entities are read,
717 // so we don't even know yet if this map is set up for domination...
718 precache_model("models/domination/dom_red.md3");
719 precache_model("models/domination/dom_blue.md3");
720 precache_model("models/domination/dom_yellow.md3");
721 precache_model("models/domination/dom_pink.md3");
722 precache_model("models/domination/dom_unclaimed.md3");
723 precache_sound("domination/claim.wav");
724 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);