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