]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/domination.qc
bots now play CTF properly
[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         waypoint_spawnforitem(self);
274 };
275
276
277
278 // player has joined game, get him on a team
279 // depreciated
280 /*void dom_player_join_team(entity pl)
281 {
282         entity head;
283         float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
284         float balance_teams, force_balance, balance_type;
285
286         balance_teams = cvar("g_balance_teams");
287         balance_teams = cvar("g_balance_teams_force");
288
289         c1 = c2 = c3 = c4 = -1;
290         totalteams = 0;
291
292         // first find out what teams are allowed
293         head = find(world, classname, "dom_team");
294         while(head)
295         {
296                 if(head.netname != "")
297                 {
298                         //if(head.team == pl.team)
299                         //      selected = head;
300                         if(head.team == COLOR_TEAM1)
301                         {
302                                         c1 = 0;
303                         }
304                         if(head.team == COLOR_TEAM2)
305                         {
306                                         c2 = 0;
307                         }
308                         if(head.team == COLOR_TEAM3)
309                         {
310                                         c3 = 0;
311                         }
312                         if(head.team == COLOR_TEAM4)
313                         {
314                                         c4 = 0;
315                         }
316                 }
317                 head = find(head, classname, "dom_team");
318         }
319
320         // make sure there are at least 2 teams to join
321         if(c1 >= 0)
322                 totalteams = totalteams + 1;
323         if(c2 >= 0)
324                 totalteams = totalteams + 1;
325         if(c3 >= 0)
326                 totalteams = totalteams + 1;
327         if(c4 >= 0)
328                 totalteams = totalteams + 1;
329
330         if(totalteams <= 1)
331                 error("dom_player_join_team: Too few teams available for domination\n");
332
333         // whichever teams that are available are set to 0 instead of -1
334
335         // if we don't care what team he ends up on, put him on whatever team he entered as.
336         // if he's not on a valid team, then put him on the smallest team
337         if(!balance_teams && !force_balance)
338         {
339                 if(     c1 >= 0 && pl.team == COLOR_TEAM1)
340                         selectedteam = pl.team;
341                 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
342                         selectedteam = pl.team;
343                 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
344                         selectedteam = pl.team;
345                 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
346                         selectedteam = pl.team;
347                 else
348                         selectedteam = -1;
349                 if(selectedteam > 0)
350                 {
351                         SetPlayerColors(pl, selectedteam - 1);
352                         return;
353                 }
354                 // otherwise end up on the smallest team (handled below)
355         }
356
357         // now count how many players are on each team already
358
359         head = find(world, classname, "player");
360         while(head)
361         {
362                 //if(head.netname != "")
363                 {
364                         if(head.team == COLOR_TEAM1)
365                         {
366                                 if(c1 >= 0)
367                                         c1 = c1 + 1;
368                         }
369                         if(head.team == COLOR_TEAM2)
370                         {
371                                 if(c2 >= 0)
372                                         c2 = c2 + 1;
373                         }
374                         if(head.team == COLOR_TEAM3)
375                         {
376                                 if(c3 >= 0)
377                                         c3 = c3 + 1;
378                         }
379                         if(head.team == COLOR_TEAM4)
380                         {
381                                 if(c4 >= 0)
382                                         c4 = c4 + 1;
383                         }
384                 }
385                 head = find(head, classname, "player");
386         }
387
388         // c1...c4 now have counts of each team
389         // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
390
391         smallestteam = 0;
392         smallestteam_count = 999;
393
394         // 2 gives priority to what team you're already on, 1 goes in order
395         balance_type = 1;
396
397         if(balance_type == 1)
398         {
399                 if(c1 >= 0 && c1 < smallestteam_count)
400                 {
401                         smallestteam = 1;
402                         smallestteam_count = c1;
403                 }
404                 if(c2 >= 0 && c2 < smallestteam_count)
405                 {
406                         smallestteam = 2;
407                         smallestteam_count = c2;
408                 }
409                 if(c3 >= 0 && c3 < smallestteam_count)
410                 {
411                         smallestteam = 3;
412                         smallestteam_count = c3;
413                 }
414                 if(c4 >= 0 && c4 < smallestteam_count)
415                 {
416                         smallestteam = 4;
417                         smallestteam_count = c4;
418                 }
419         }
420         else
421         {
422                 if(c1 >= 0 && (c1 < smallestteam_count ||
423                                         (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
424                 {
425                         smallestteam = 1;
426                         smallestteam_count = c1;
427                 }
428                 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
429                                         (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
430                 {
431                         smallestteam = 2;
432                         smallestteam_count = c2;
433                 }
434                 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
435                                         (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
436                 {
437                         smallestteam = 3;
438                         smallestteam_count = c3;
439                 }
440                 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
441                                         (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
442                 {
443                         smallestteam = 4;
444                         smallestteam_count = c4;
445                 }
446         }
447
448         if(smallestteam == 1)
449         {
450                 selectedteam = COLOR_TEAM1 - 1;
451         }
452         if(smallestteam == 2)
453         {
454                 selectedteam = COLOR_TEAM2 - 1;
455         }
456         if(smallestteam == 3)
457         {
458                 selectedteam = COLOR_TEAM3 - 1;
459         }
460         if(smallestteam == 4)
461         {
462                 selectedteam = COLOR_TEAM4 - 1;
463         }
464
465         SetPlayerColors(pl, selectedteam);
466 }
467 */
468 /*QUAKED dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
469 Control point for Domination gameplay.
470 */
471 void() dom_controlpoint =
472 {
473         if(!g_domination)
474         {
475                 remove(self);
476                 return;
477         }
478         self.think = dom_controlpoint_setup;
479         self.nextthink = time + 0.1;
480
481         if(!self.scale)
482                 self.scale = 0.6;
483
484         //if(!self.glow_size)
485         //      self.glow_size = cvar("g_domination_point_glow");
486         self.effects = self.effects | EF_FULLBRIGHT | EF_LOWPRECISION;
487 };
488
489 // code from here on is just to support maps that don't have control point and team entities
490 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
491 {
492         local entity oldself;
493         oldself = self;
494         self = spawn();
495         self.classname = "dom_team";
496         self.netname = teamname;
497         self.cnt = teamcolor;
498         self.model = pointmodel;
499         self.skin = pointskin;
500         self.noise = capsound;
501         self.noise1 = capnarration;
502         self.message = capmessage;
503
504         // this code is identical to dom_team
505         setmodel(self, self.model); // precision not needed
506         self.mdl = self.model;
507         self.dmg = self.modelindex;
508         self.model = "";
509         self.modelindex = 0;
510         // this would have to be changed if used in quakeworld
511         self.team = self.cnt + 1;
512
513         //eprint(self);
514         self = oldself;
515 };
516
517 void(vector org) dom_spawnpoint =
518 {
519         local entity oldself;
520         oldself = self;
521         self = spawn();
522         self.classname = "dom_controlpoint";
523         self.think = dom_controlpoint;
524         self.nextthink = time;
525         self.origin = org;
526         dom_controlpoint();
527         self = oldself;
528 };
529
530 // spawn some default teams if the map is not set up for domination
531 void() dom_spawnteams =
532 {
533         float numteams;
534
535         numteams = cvar("g_domination_default_teams");
536         // LordHavoc: edit this if you want to change defaults
537         dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
538         dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
539         if(numteams > 2)
540                 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
541         if(numteams > 3)
542                 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
543         dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
544 };
545
546 void() dom_delayedinit =
547 {
548         local entity head;
549
550         self.think = SUB_Remove;
551         self.nextthink = time;
552         // if no teams are found, spawn defaults
553         if (find(world, classname, "dom_team") == world)
554                 dom_spawnteams();
555         // if no control points are found, spawn defaults
556         if (find(world, classname, "dom_controlpoint") == world)
557         {
558                 // here follow default domination points for each map
559                 /*
560                 if (world.model == "maps/e1m1.bsp")
561                 {
562                         dom_spawnpoint('0 0 0');
563                 }
564                 else
565                 */
566                 {
567                         // if no supported map was found, make every deathmatch spawn a point
568                         head = find(world, classname, "info_player_deathmatch");
569                         while (head)
570                         {
571                                 dom_spawnpoint(head.origin);
572                                 head = find(head, classname, "info_player_deathmatch");
573                         }
574                 }
575         }
576 };
577
578 void() dom_init =
579 {
580         local entity e;
581         // we have to precache default models/sounds even if they might not be
582         // used because worldspawn is executed before any other entities are read,
583         // so we don't even know yet if this map is set up for domination...
584         precache_model("models/domination/dom_red.md3");
585         precache_model("models/domination/dom_blue.md3");
586         precache_model("models/domination/dom_yellow.md3");
587         precache_model("models/domination/dom_pink.md3");
588         precache_model("models/domination/dom_unclaimed.md3");
589         precache_sound("domination/claim.wav");
590         e = spawn();
591         e.think = dom_delayedinit;
592         e.nextthink = time + 0.1;
593
594         // teamplay is always on in domination, defaults to hurt self but not teammates
595         //if(!cvar("teamplay"))
596         //      cvar_set("teamplay", "3");
597 };
598