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