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