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 void() dom_controlpoint_setup;
29 void LogDom(string mode, float team_before, entity actor)
32 if(!cvar("sv_eventlog"))
34 s = strcat(":dom:", mode);
35 s = strcat(s, ":", ftos(team_before));
36 s = strcat(s, ":", ftos(actor.playerid));
40 void() dom_spawnteams;
42 void dompoint_captured ()
45 local float old_delay, old_team, real_team;
47 // now that the delay has expired, switch to the latest team to lay claim to this point
53 LogDom("taken", self.team, self.dmg_inflictor);
54 self.dmg_inflictor = world;
56 self.goalentity = head;
57 self.model = head.mdl;
58 self.modelindex = head.dmg;
59 self.skin = head.skin;
61 //bprint(head.message);
64 //bprint(^3head.netname);
65 //bprint(head.netname);
66 //bprint(self.message);
69 float points, wait_time;
70 if (g_domination_point_amt)
71 points = g_domination_point_amt;
74 if (g_domination_point_rate)
75 wait_time = g_domination_point_rate;
77 wait_time = self.wait;
79 bprint("^3", head.netname, "^3", self.message);
80 bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
82 if(self.enemy.playerid == self.enemy_playerid)
83 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
89 sound(self.enemy, CHAN_AUTO, head.noise, VOL_BASE, ATTN_NORM);
91 sound(self, CHAN_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
92 if (head.noise1 != "")
93 play2all(head.noise1);
95 //self.nextthink = time + cvar("g_domination_point_rate");
96 //self.think = dompointthink;
98 self.delay = time + wait_time;
101 old_delay = self.delay;
102 old_team = self.team;
103 self.team = real_team;
107 self.delay = old_delay;
108 self.team = old_team;
110 switch(self.goalentity.team)
113 WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
116 WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
119 WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
122 WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
125 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
126 WaypointSprite_Ping(self.sprite);
131 void AnimateDomPoint()
133 if(self.pain_finished > time)
135 self.pain_finished = time + self.t_width;
136 if(self.nextthink > self.pain_finished)
137 self.nextthink = self.pain_finished;
139 self.frame = self.frame + 1;
140 if(self.frame > self.t_length)
148 self.nextthink = time + 0.1;
150 //self.frame = self.frame + 1;
151 //if(self.frame > 119)
157 if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points
160 g_domination_point_rate = cvar("g_domination_point_rate");
161 g_domination_point_amt = cvar("g_domination_point_amt");
163 if(g_domination_point_rate)
164 self.delay = time + g_domination_point_rate;
166 self.delay = time + self.wait;
168 // give credit to the team
169 // NOTE: this defaults to 0
170 if (self.goalentity.netname != "")
172 if(g_domination_point_amt)
173 fragamt = g_domination_point_amt;
175 fragamt = self.DOMPOINTFRAGS;
176 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
177 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
179 // give credit to the individual player, if he is still there
180 if (self.enemy.playerid == self.enemy_playerid)
182 PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
183 PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
193 if (other.classname != "player")
195 if (other.health < 1)
198 if(time < self.captime + 0.3)
201 // only valid teams can claim it
202 head = find(world, classname, "dom_team");
203 while (head && head.team != other.team)
204 head = find(head, classname, "dom_team");
205 if (!head || head.netname == "" || head == self.goalentity)
210 self.team = self.goalentity.team; // this stores the PREVIOUS team!
212 self.cnt = other.team;
213 self.owner = head; // team to switch to after the delay
214 self.dmg_inflictor = other;
217 // self.delay = time + cvar("g_domination_point_capturetime");
218 //self.nextthink = time + cvar("g_domination_point_capturetime");
219 //self.think = dompoint_captured;
221 // go to neutral team in the mean time
222 head = find(world, classname, "dom_team");
223 while (head && head.netname != "")
224 head = find(head, classname, "dom_team");
228 WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
229 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
230 WaypointSprite_Ping(self.sprite);
232 self.goalentity = head;
233 self.model = head.mdl;
234 self.modelindex = head.dmg;
235 self.skin = head.skin;
237 self.enemy = other; // individual player scoring
238 self.enemy_playerid = other.playerid;
242 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
243 Team declaration for Domination gameplay, this allows you to decide what team
244 names and control point models are used in your map.
246 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
247 can have netname set! The nameless team owns all control points at start.
251 Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
253 Scoreboard color of the team (for example 4 is red and 13 is blue)
255 Model to use for control points owned by this team (for example
256 "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
259 Skin of the model to use (for team skins on a single model)
261 Sound to play when this team captures a point.
262 (this is a localized sound, like a small alarm or other effect)
264 Narrator speech to play when this team captures a point.
265 (this is a global sound, like "Red team has captured a control point")
268 void spawnfunc_dom_team()
270 if(!g_domination || cvar("g_domination_teams_override") >= 2)
275 precache_model(self.model);
276 if (self.noise != "")
277 precache_sound(self.noise);
278 if (self.noise1 != "")
279 precache_sound(self.noise1);
280 self.classname = "dom_team";
281 setmodel(self, self.model); // precision not needed
282 self.mdl = self.model;
283 self.dmg = self.modelindex;
286 // this would have to be changed if used in quakeworld
288 self.team = self.cnt + 1; // WHY are these different anyway?
291 void dom_controlpoint_setup()
294 // find the spawnfunc_dom_team representing unclaimed points
295 head = find(world, classname, "dom_team");
296 while(head && head.netname != "")
297 head = find(head, classname, "dom_team");
299 objerror("no spawnfunc_dom_team with netname \"\" found\n");
301 // copy important properties from spawnfunc_dom_team entity
302 self.goalentity = head;
303 setmodel(self, head.mdl); // precision already set
304 self.skin = head.skin;
309 self.message = " has captured a control point";
311 if(!self.DOMPOINTFRAGS)
312 self.DOMPOINTFRAGS = 1;
317 self.t_width = 0.02; // frame animation rate
319 self.t_length = 239; // maximum frame
321 self.think = dompointthink;
322 self.nextthink = time;
323 self.touch = dompointtouch;
324 self.solid = SOLID_TRIGGER;
325 self.flags = FL_ITEM;
326 setsize(self, '-32 -32 -32', '32 32 32');
327 setorigin(self, self.origin + '0 0 20');
330 waypoint_spawnforitem(self);
331 WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite);
332 WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
337 // player has joined game, get him on a team
339 /*void dom_player_join_team(entity pl)
342 float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
343 float balance_teams, force_balance, balance_type;
345 balance_teams = cvar("g_balance_teams");
346 balance_teams = cvar("g_balance_teams_force");
348 c1 = c2 = c3 = c4 = -1;
351 // first find out what teams are allowed
352 head = find(world, classname, "dom_team");
355 if(head.netname != "")
357 //if(head.team == pl.team)
359 if(head.team == COLOR_TEAM1)
363 if(head.team == COLOR_TEAM2)
367 if(head.team == COLOR_TEAM3)
371 if(head.team == COLOR_TEAM4)
376 head = find(head, classname, "dom_team");
379 // make sure there are at least 2 teams to join
381 totalteams = totalteams + 1;
383 totalteams = totalteams + 1;
385 totalteams = totalteams + 1;
387 totalteams = totalteams + 1;
390 error("dom_player_join_team: Too few teams available for domination\n");
392 // whichever teams that are available are set to 0 instead of -1
394 // if we don't care what team he ends up on, put him on whatever team he entered as.
395 // if he's not on a valid team, then put him on the smallest team
396 if(!balance_teams && !force_balance)
398 if( c1 >= 0 && pl.team == COLOR_TEAM1)
399 selectedteam = pl.team;
400 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
401 selectedteam = pl.team;
402 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
403 selectedteam = pl.team;
404 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
405 selectedteam = pl.team;
410 SetPlayerColors(pl, selectedteam - 1);
413 // otherwise end up on the smallest team (handled below)
416 // now count how many players are on each team already
418 head = find(world, classname, "player");
421 //if(head.netname != "")
423 if(head.team == COLOR_TEAM1)
428 if(head.team == COLOR_TEAM2)
433 if(head.team == COLOR_TEAM3)
438 if(head.team == COLOR_TEAM4)
444 head = find(head, classname, "player");
447 // c1...c4 now have counts of each team
448 // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
451 smallestteam_count = 999;
453 // 2 gives priority to what team you're already on, 1 goes in order
456 if(balance_type == 1)
458 if(c1 >= 0 && c1 < smallestteam_count)
461 smallestteam_count = c1;
463 if(c2 >= 0 && c2 < smallestteam_count)
466 smallestteam_count = c2;
468 if(c3 >= 0 && c3 < smallestteam_count)
471 smallestteam_count = c3;
473 if(c4 >= 0 && c4 < smallestteam_count)
476 smallestteam_count = c4;
481 if(c1 >= 0 && (c1 < smallestteam_count ||
482 (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
485 smallestteam_count = c1;
487 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
488 (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
491 smallestteam_count = c2;
493 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
494 (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
497 smallestteam_count = c3;
499 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
500 (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
503 smallestteam_count = c4;
507 if(smallestteam == 1)
509 selectedteam = COLOR_TEAM1 - 1;
511 if(smallestteam == 2)
513 selectedteam = COLOR_TEAM2 - 1;
515 if(smallestteam == 3)
517 selectedteam = COLOR_TEAM3 - 1;
519 if(smallestteam == 4)
521 selectedteam = COLOR_TEAM4 - 1;
524 SetPlayerColors(pl, selectedteam);
527 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
528 Control point for Domination gameplay.
530 void spawnfunc_dom_controlpoint()
537 self.think = dom_controlpoint_setup;
538 self.nextthink = time + 0.1;
539 self.reset = dom_controlpoint_setup;
544 //if(!self.glow_size)
545 // self.glow_size = cvar("g_domination_point_glow");
546 self.effects = self.effects | EF_LOWPRECISION;
547 if (cvar("g_domination_point_fullbright"))
548 self.effects |= EF_FULLBRIGHT;
551 // code from here on is just to support maps that don't have control point and team entities
552 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
554 local entity oldself;
557 self.classname = "dom_team";
558 self.netname = teamname;
559 self.cnt = teamcolor;
560 self.model = pointmodel;
561 self.skin = pointskin;
562 self.noise = capsound;
563 self.noise1 = capnarration;
564 self.message = capmessage;
566 // this code is identical to spawnfunc_dom_team
567 setmodel(self, self.model); // precision not needed
568 self.mdl = self.model;
569 self.dmg = self.modelindex;
572 // this would have to be changed if used in quakeworld
573 self.team = self.cnt + 1;
579 void dom_spawnpoint(vector org)
581 local entity oldself;
584 self.classname = "dom_controlpoint";
585 self.think = spawnfunc_dom_controlpoint;
586 self.nextthink = time;
587 setorigin(self, org);
588 spawnfunc_dom_controlpoint();
592 // spawn some default teams if the map is not set up for domination
593 void dom_spawnteams()
596 if(cvar("g_domination_teams_override") < 2)
597 numteams = cvar("g_domination_default_teams");
599 numteams = cvar("g_domination_teams_override");
600 // LordHavoc: edit this if you want to change defaults
601 dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
602 dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
604 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
606 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
607 dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
610 void dom_delayedinit()
614 // if no teams are found, spawn defaults, if custom teams are set, use them
615 if (find(world, classname, "dom_team") == world || cvar("g_domination_teams_override") >= 2)
617 // if no control points are found, spawn defaults
618 if (find(world, classname, "dom_controlpoint") == world)
620 // here follow default domination points for each map
622 if (world.model == "maps/e1m1.bsp")
624 dom_spawnpoint('0 0 0');
629 // if no supported map was found, make every deathmatch spawn a point
630 head = find(world, classname, "info_player_deathmatch");
633 dom_spawnpoint(head.origin);
634 head = find(head, classname, "info_player_deathmatch");
644 // we have to precache default models/sounds even if they might not be
645 // used because spawnfunc_worldspawn is executed before any other entities are read,
646 // so we don't even know yet if this map is set up for domination...
647 precache_model("models/domination/dom_red.md3");
648 precache_model("models/domination/dom_blue.md3");
649 precache_model("models/domination/dom_yellow.md3");
650 precache_model("models/domination/dom_pink.md3");
651 precache_model("models/domination/dom_unclaimed.md3");
652 precache_sound("domination/claim.wav");
653 InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
655 // teamplay is always on in domination, defaults to hurt self but not teammates
657 // cvar_set("teamplay", "3");