]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/domination.qc
- in-game voting now nags until you have voted
[divverent/nexuiz.git] / data / qcsrc / server / domination.qc
1
2 /*
3 Domination as a plugin for netquake mods
4 by LordHavoc (lordhavoc@ghdigital.com)
5
6 How to add domination points to a mod:
7 1. Add this line to progs.src above world.qc:
8 domination.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 worldspawn in world.qc:
11 void() dom_init;
12 4. Add this line to the end of worldspawn in world.qc:
13 dom_init();
14
15 Note: The only teams who can use dom control points are identified by dom_team entities (if none exist these default to red and blue and use only quake models/sounds).
16 */
17
18 void() dom_controlpoint_setup;
19
20 void LogDom(string mode, float team_before, entity actor)
21 {
22         string s;
23         if(!cvar("sv_eventlog"))
24                 return;
25         s = strcat(":dom:", mode);
26         s = strcat(s, ":", ftos(team_before));
27         s = strcat(s, ":", ftos(actor.playerid));
28         GameLogEcho(s, FALSE);
29 }
30
31 void() dom_spawnteams;
32
33 void dompoint_captured ()
34 {
35         local entity head;
36
37         // now that the delay has expired, switch to the latest team to lay claim to this point
38         head = self.aiment;
39
40         self.cnt = -1;
41
42         LogDom("taken", self.team, self.dmg_inflictor);
43         self.dmg_inflictor = world;
44
45         self.goalentity = head;
46         self.model = head.mdl;
47         self.modelindex = head.dmg;
48         self.skin = head.skin;
49
50         //bprint(head.message);
51         //bprint("\n");
52
53         //bprint(^3head.netname);
54         //bprint(head.netname);
55         //bprint(self.message);
56         //bprint("\n");
57
58         bprint(strcat("^3", head.netname, "^3", self.message, "\n"));
59
60         if (head.noise != "")
61                 sound(self, CHAN_BODY, head.noise, 1, ATTN_NORM);
62         if (head.noise1 != "")
63                 sound(self, CHAN_VOICE, head.noise1, 1, ATTN_NONE);
64
65         //self.nextthink = time + cvar("g_domination_point_rate");
66         //self.think = dompointthink;
67         if(cvar("g_domination_point_rate"))
68                 self.delay = time + cvar("g_domination_point_rate");
69         else
70                 self.delay = time + self.wait;
71 };
72
73 void AnimateDomPoint()
74 {
75         if(self.pain_finished > time)
76                 return;
77         self.pain_finished = time + self.t_width;
78         if(self.nextthink > self.pain_finished)
79                 self.nextthink = self.pain_finished;
80
81         self.frame = self.frame + 1;
82         if(self.frame > self.t_length)
83                 self.frame = 0;
84 }
85
86 void() dompointthink =
87 {
88         local entity head;
89         local float waittime;
90         local float teamfragamt;
91         local float individualfragamt;
92
93         self.nextthink = time + 0.1;
94
95         //self.frame = self.frame + 1;
96         //if(self.frame > 119)
97         //      self.frame = 0;
98         AnimateDomPoint();
99
100         // give points
101
102         if (gameover)   // game has ended, don't keep giving points
103                 return;
104
105         if(self.delay > time)
106                 return;
107
108 /*
109         if(self.state == 1)
110         {
111                 self.state = 0;
112                 dompoint_captured();
113                 return;
114         }
115 */
116
117         waittime = cvar("g_domination_point_rate");
118         if(!waittime)
119                 waittime = self.wait;
120         self.delay = time + waittime;
121
122         // give credit to all players of the team (rewards large teams)
123         // NOTE: this defaults to 0
124         if (self.goalentity.netname)
125         {
126                 teamfragamt = cvar("g_domination_point_teamamt");
127
128                 FOR_EACH_PLAYER(head)
129                         if (head.team == self.goalentity.team)
130                                 UpdateFrags(head, teamfragamt);
131         }
132
133         // if the player left the game, changed teams or became spectator, we have to find another player on the same team to give credit to
134         if (!self.enemy.flags || self.enemy.team != self.goalentity.team || self.enemy.killcount == -666) // flags is zero on removed clients
135         {
136                 other = self.enemy;
137                 FOR_EACH_PLAYER(head)
138                         if (head.team == self.goalentity.team)
139                                 self.enemy = head;
140                 if(self.enemy == other) // search returned no matching player, reset dom point
141                 {
142                         dom_controlpoint_setup();
143                         self.enemy = world;
144                         self.cnt = 0;
145                         self.aiment = world;
146                 }
147         }
148
149         // give credit to the individual player
150         if (self.enemy)
151         {
152                 individualfragamt = cvar("g_domination_point_amt");
153                 if(!individualfragamt)
154                         individualfragamt = self.frags;
155                 UpdateFrags(self.enemy, individualfragamt);
156         }
157 };
158
159 void() dompointtouch =
160 {
161         local entity head;
162         if (other.classname != "player")
163                 return;
164         if (other.health < 1)
165                 return;
166
167         // only valid teams can claim it
168         head = find(world, classname, "dom_team");
169         while (head && head.team != other.team)
170                 head = find(head, classname, "dom_team");
171         if (!head || head.netname == "" || head == self.goalentity)
172                 return;
173
174         // delay capture
175
176         self.team = self.goalentity.team; // this stores the PREVIOUS team!
177
178         self.cnt = other.team;
179         self.aiment = head; // team to switch to after the delay
180         self.dmg_inflictor = other;
181
182         // self.state = 1;
183         // self.delay = time + cvar("g_domination_point_capturetime");
184         //self.nextthink = time + cvar("g_domination_point_capturetime");
185         //self.think = dompoint_captured;
186
187         // go to neutral team in the mean time
188         head = find(world, classname, "dom_team");
189         while (head && head.netname != "")
190                 head = find(head, classname, "dom_team");
191         if(head == world)
192                 return;
193
194         self.goalentity = head;
195         self.model = head.mdl;
196         self.modelindex = head.dmg;
197         self.skin = head.skin;
198
199         self.enemy = other; // individual player scoring
200         dompoint_captured();
201 };
202
203 /*QUAKED dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
204 Team declaration for Domination gameplay, this allows you to decide what team
205 names and control point models are used in your map.
206
207 Note: If you use dom_team entities you must define at least 3 and only two
208 can have netname set!  The nameless team owns all control points at start.
209
210 Keys:
211 "netname"
212  Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
213 "cnt"
214  Scoreboard color of the team (for example 4 is red and 13 is blue)
215 "model"
216  Model to use for control points owned by this team (for example
217  "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
218  keycard)
219 "skin"
220  Skin of the model to use (for team skins on a single model)
221 "noise"
222  Sound to play when this team captures a point.
223  (this is a localized sound, like a small alarm or other effect)
224 "noise1"
225  Narrator speech to play when this team captures a point.
226  (this is a global sound, like "Red team has captured a control point")
227 */
228
229 void() dom_team =
230 {
231         precache_model(self.model);
232         if (self.noise != "")
233                 precache_sound(self.noise);
234         if (self.noise1 != "")
235                 precache_sound(self.noise1);
236         self.classname = "dom_team";
237         setmodel(self, self.model);
238         self.mdl = self.model;
239         self.dmg = self.modelindex;
240         self.model = "";
241         self.modelindex = 0;
242         // this would have to be changed if used in quakeworld
243         self.team = self.cnt + 1;
244 };
245
246 void() dom_controlpoint_setup =
247 {
248         local entity head;
249         // find the dom_team representing unclaimed points
250         head = find(world, classname, "dom_team");
251         while(head && head.netname != "")
252                 head = find(head, classname, "dom_team");
253         if (!head)
254                 objerror("no dom_team with netname \"\" found\n");
255
256         // copy important properties from dom_team entity
257         self.goalentity = head;
258         setmodel(self, head.mdl);
259         self.skin = head.skin;
260
261         self.cnt = -1;
262
263         if(!self.message)
264                 self.message = " has captured a control point";
265
266         if(!self.frags)
267                 self.frags = 1;
268         if(!self.wait)
269                 self.wait = 5;
270
271         if(!self.t_width)
272                 self.t_width = 0.1; // frame animation rate
273         if(!self.t_length)
274                 self.t_length = 119; // maximum frame
275
276         self.think = dompointthink;
277         self.nextthink = time;
278         self.touch = dompointtouch;
279         self.solid = SOLID_TRIGGER;
280         self.flags = FL_ITEM;
281         setsize(self, '-32 -32 -32', '32 32 32');
282         setorigin(self, self.origin + '0 0 20');
283         droptofloor();
284 };
285
286
287
288 // player has joined game, get him on a team
289 // depreciated
290 /*void dom_player_join_team(entity pl)
291 {
292         entity head;
293         float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
294         float balance_teams, force_balance, balance_type;
295
296         balance_teams = cvar("g_balance_teams");
297         balance_teams = cvar("g_balance_teams_force");
298
299         c1 = c2 = c3 = c4 = -1;
300         totalteams = 0;
301
302         // first find out what teams are allowed
303         head = find(world, classname, "dom_team");
304         while(head)
305         {
306                 if(head.netname != "")
307                 {
308                         //if(head.team == pl.team)
309                         //      selected = head;
310                         if(head.team == COLOR_TEAM1)
311                         {
312                                         c1 = 0;
313                         }
314                         if(head.team == COLOR_TEAM2)
315                         {
316                                         c2 = 0;
317                         }
318                         if(head.team == COLOR_TEAM3)
319                         {
320                                         c3 = 0;
321                         }
322                         if(head.team == COLOR_TEAM4)
323                         {
324                                         c4 = 0;
325                         }
326                 }
327                 head = find(head, classname, "dom_team");
328         }
329
330         // make sure there are at least 2 teams to join
331         if(c1 >= 0)
332                 totalteams = totalteams + 1;
333         if(c2 >= 0)
334                 totalteams = totalteams + 1;
335         if(c3 >= 0)
336                 totalteams = totalteams + 1;
337         if(c4 >= 0)
338                 totalteams = totalteams + 1;
339
340         if(totalteams <= 1)
341                 error("dom_player_join_team: Too few teams available for domination\n");
342
343         // whichever teams that are available are set to 0 instead of -1
344
345         // if we don't care what team he ends up on, put him on whatever team he entered as.
346         // if he's not on a valid team, then put him on the smallest team
347         if(!balance_teams && !force_balance)
348         {
349                 if(     c1 >= 0 && pl.team == COLOR_TEAM1)
350                         selectedteam = pl.team;
351                 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
352                         selectedteam = pl.team;
353                 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
354                         selectedteam = pl.team;
355                 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
356                         selectedteam = pl.team;
357                 else
358                         selectedteam = -1;
359                 if(selectedteam > 0)
360                 {
361                         SetPlayerColors(pl, selectedteam - 1);
362                         return;
363                 }
364                 // otherwise end up on the smallest team (handled below)
365         }
366
367         // now count how many players are on each team already
368
369         head = find(world, classname, "player");
370         while(head)
371         {
372                 //if(head.netname != "")
373                 {
374                         if(head.team == COLOR_TEAM1)
375                         {
376                                 if(c1 >= 0)
377                                         c1 = c1 + 1;
378                         }
379                         if(head.team == COLOR_TEAM2)
380                         {
381                                 if(c2 >= 0)
382                                         c2 = c2 + 1;
383                         }
384                         if(head.team == COLOR_TEAM3)
385                         {
386                                 if(c3 >= 0)
387                                         c3 = c3 + 1;
388                         }
389                         if(head.team == COLOR_TEAM4)
390                         {
391                                 if(c4 >= 0)
392                                         c4 = c4 + 1;
393                         }
394                 }
395                 head = find(head, classname, "player");
396         }
397
398         // c1...c4 now have counts of each team
399         // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
400
401         smallestteam = 0;
402         smallestteam_count = 999;
403
404         // 2 gives priority to what team you're already on, 1 goes in order
405         balance_type = 1;
406
407         if(balance_type == 1)
408         {
409                 if(c1 >= 0 && c1 < smallestteam_count)
410                 {
411                         smallestteam = 1;
412                         smallestteam_count = c1;
413                 }
414                 if(c2 >= 0 && c2 < smallestteam_count)
415                 {
416                         smallestteam = 2;
417                         smallestteam_count = c2;
418                 }
419                 if(c3 >= 0 && c3 < smallestteam_count)
420                 {
421                         smallestteam = 3;
422                         smallestteam_count = c3;
423                 }
424                 if(c4 >= 0 && c4 < smallestteam_count)
425                 {
426                         smallestteam = 4;
427                         smallestteam_count = c4;
428                 }
429         }
430         else
431         {
432                 if(c1 >= 0 && (c1 < smallestteam_count ||
433                                         (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
434                 {
435                         smallestteam = 1;
436                         smallestteam_count = c1;
437                 }
438                 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
439                                         (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
440                 {
441                         smallestteam = 2;
442                         smallestteam_count = c2;
443                 }
444                 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
445                                         (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
446                 {
447                         smallestteam = 3;
448                         smallestteam_count = c3;
449                 }
450                 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
451                                         (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
452                 {
453                         smallestteam = 4;
454                         smallestteam_count = c4;
455                 }
456         }
457
458         if(smallestteam == 1)
459         {
460                 selectedteam = COLOR_TEAM1 - 1;
461         }
462         if(smallestteam == 2)
463         {
464                 selectedteam = COLOR_TEAM2 - 1;
465         }
466         if(smallestteam == 3)
467         {
468                 selectedteam = COLOR_TEAM3 - 1;
469         }
470         if(smallestteam == 4)
471         {
472                 selectedteam = COLOR_TEAM4 - 1;
473         }
474
475         SetPlayerColors(pl, selectedteam);
476 }
477 */
478 /*QUAKED dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
479 Control point for Domination gameplay.
480 */
481 void() dom_controlpoint =
482 {
483         if(!cvar("g_domination"))
484         {
485                 remove(self);
486                 return;
487         }
488         self.think = dom_controlpoint_setup;
489         self.nextthink = time + 0.1;
490
491         if(!self.scale)
492                 self.scale = 0.6;
493
494         //if(!self.glow_size)
495         //      self.glow_size = cvar("g_domination_point_glow");
496         self.effects = self.effects | EF_FULLBRIGHT;
497 };
498
499 // code from here on is just to support maps that don't have control point and team entities
500 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
501 {
502         local entity oldself;
503         oldself = self;
504         self = spawn();
505         self.classname = "dom_team";
506         self.netname = teamname;
507         self.cnt = teamcolor;
508         self.model = pointmodel;
509         self.skin = pointskin;
510         self.noise = capsound;
511         self.noise1 = capnarration;
512         self.message = capmessage;
513
514         // this code is identical to dom_team
515         setmodel(self, self.model);
516         self.mdl = self.model;
517         self.dmg = self.modelindex;
518         self.model = "";
519         self.modelindex = 0;
520         // this would have to be changed if used in quakeworld
521         self.team = self.cnt + 1;
522
523         //eprint(self);
524         self = oldself;
525 };
526
527 void(vector org) dom_spawnpoint =
528 {
529         local entity oldself;
530         oldself = self;
531         self = spawn();
532         self.classname = "dom_controlpoint";
533         self.think = dom_controlpoint;
534         self.nextthink = time;
535         self.origin = org;
536         dom_controlpoint();
537         self = oldself;
538 };
539
540 // spawn some default teams if the map is not set up for domination
541 void() dom_spawnteams =
542 {
543         float numteams;
544
545         numteams = cvar("g_domination_default_teams");
546         // LordHavoc: edit this if you want to change defaults
547         dom_spawnteam("Red", 4, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
548         dom_spawnteam("Blue", 13, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
549         if(numteams > 2)
550                 dom_spawnteam("Pink", 9, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
551         if(numteams > 3)
552                 dom_spawnteam("Yellow", 12, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
553         dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
554 };
555
556 void() dom_delayedinit =
557 {
558         local entity head;
559
560         self.think = SUB_Remove;
561         self.nextthink = time;
562         // if no teams are found, spawn defaults
563         if (find(world, classname, "dom_team") == world)
564                 dom_spawnteams();
565         // if no control points are found, spawn defaults
566         if (find(world, classname, "dom_controlpoint") == world)
567         {
568                 // here follow default domination points for each map
569                 /*
570                 if (world.model == "maps/e1m1.bsp")
571                 {
572                         dom_spawnpoint('0 0 0');
573                 }
574                 else
575                 */
576                 {
577                         // if no supported map was found, make every deathmatch spawn a point
578                         head = find(world, classname, "info_player_deathmatch");
579                         while (head)
580                         {
581                                 dom_spawnpoint(head.origin);
582                                 head = find(head, classname, "info_player_deathmatch");
583                         }
584                 }
585         }
586 };
587
588 void() dom_init =
589 {
590         local entity e;
591         // we have to precache default models/sounds even if they might not be
592         // used because worldspawn is executed before any other entities are read,
593         // so we don't even know yet if this map is set up for domination...
594         precache_model("models/domination/dom_red.md3");
595         precache_model("models/domination/dom_blue.md3");
596         precache_model("models/domination/dom_pink.md3");
597         precache_model("models/domination/dom_yellow.md3");
598         precache_model("models/domination/dom_unclaimed.md3");
599         precache_sound("domination/claim.wav");
600         e = spawn();
601         e.think = dom_delayedinit;
602         e.nextthink = time + 0.1;
603
604         // teamplay is always on in domination, defaults to hurt self but not teammates
605         //if(!cvar("teamplay"))
606         //      cvar_set("teamplay", "3");
607 };
608