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