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