]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/domination.qc
add much more stuff. REALLY need to initialize the scoring for the other game modes...
[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 spawnfunc_worldspawn in world.qc:
11 void() dom_init;
12 4. Add this line to the end of spawnfunc_worldspawn in world.qc:
13 dom_init();
14
15 Note: The only teams who can use dom control points are identified by spawnfunc_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 the team
124         // NOTE: this defaults to 0
125         if (self.goalentity.netname)
126         {
127                 teamfragamt = cvar("g_domination_point_teamamt");
128                 if(!teamfragamt)
129                         teamfragamt = self.frags;
130                 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_DOMPOINTS, teamfragamt);
131         }
132
133         // 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
134         if (!self.enemy.flags || self.enemy.team != self.goalentity.team || self.enemy.killcount == -666) // flags is zero on removed clients
135         {
136                 other = self.enemy;
137                 FOR_EACH_PLAYER(head)
138                         if (head.team == self.goalentity.team)
139                                 self.enemy = head;
140                 if(self.enemy == other) // search returned no matching player, reset dom point
141                 {
142                         dom_controlpoint_setup();
143                         self.enemy = world;
144                         self.cnt = 0;
145                         self.aiment = world;
146                 }
147         }
148
149         // give credit to the individual player
150         if (self.enemy)
151         {
152                 individualfragamt = cvar("g_domination_point_amt");
153                 if(!individualfragamt)
154                         individualfragamt = self.frags;
155                 UpdateFrags(self.enemy, individualfragamt);
156                 PlayerScore_Add(self.enemy, SP_DOM_DOMPOINTS, 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 spawnfunc_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 spawnfunc_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 spawnfunc_dom_team()
231 {
232         if(!g_domination)
233         {
234                 remove(self);
235                 return;
236         }
237         precache_model(self.model);
238         if (self.noise != "")
239                 precache_sound(self.noise);
240         if (self.noise1 != "")
241                 precache_sound(self.noise1);
242         self.classname = "dom_team";
243         setmodel(self, self.model); // precision not needed
244         self.mdl = self.model;
245         self.dmg = self.modelindex;
246         self.model = "";
247         self.modelindex = 0;
248         // this would have to be changed if used in quakeworld
249         self.team = self.cnt + 1;
250 };
251
252 void dom_controlpoint_setup()
253 {
254         local entity head;
255         // find the spawnfunc_dom_team representing unclaimed points
256         head = find(world, classname, "dom_team");
257         while(head && head.netname != "")
258                 head = find(head, classname, "dom_team");
259         if (!head)
260                 objerror("no spawnfunc_dom_team with netname \"\" found\n");
261
262         // copy important properties from spawnfunc_dom_team entity
263         self.goalentity = head;
264         setmodel(self, head.mdl); // precision already set
265         self.skin = head.skin;
266
267         self.cnt = -1;
268
269         if(!self.message)
270                 self.message = " has captured a control point";
271
272         if(!self.frags)
273                 self.frags = 1;
274         if(!self.wait)
275                 self.wait = 5;
276
277         if(!self.t_width)
278                 self.t_width = 0.1; // frame animation rate
279         if(!self.t_length)
280                 self.t_length = 119; // maximum frame
281
282         self.think = dompointthink;
283         self.nextthink = time;
284         self.touch = dompointtouch;
285         self.solid = SOLID_TRIGGER;
286         self.flags = FL_ITEM;
287         setsize(self, '-32 -32 -32', '32 32 32');
288         setorigin(self, self.origin + '0 0 20');
289         droptofloor();
290
291         waypoint_spawnforitem(self);
292 };
293
294
295
296 // player has joined game, get him on a team
297 // depreciated
298 /*void dom_player_join_team(entity pl)
299 {
300         entity head;
301         float c1, c2, c3, c4, totalteams, smallestteam, smallestteam_count, selectedteam;
302         float balance_teams, force_balance, balance_type;
303
304         balance_teams = cvar("g_balance_teams");
305         balance_teams = cvar("g_balance_teams_force");
306
307         c1 = c2 = c3 = c4 = -1;
308         totalteams = 0;
309
310         // first find out what teams are allowed
311         head = find(world, classname, "dom_team");
312         while(head)
313         {
314                 if(head.netname != "")
315                 {
316                         //if(head.team == pl.team)
317                         //      selected = head;
318                         if(head.team == COLOR_TEAM1)
319                         {
320                                         c1 = 0;
321                         }
322                         if(head.team == COLOR_TEAM2)
323                         {
324                                         c2 = 0;
325                         }
326                         if(head.team == COLOR_TEAM3)
327                         {
328                                         c3 = 0;
329                         }
330                         if(head.team == COLOR_TEAM4)
331                         {
332                                         c4 = 0;
333                         }
334                 }
335                 head = find(head, classname, "dom_team");
336         }
337
338         // make sure there are at least 2 teams to join
339         if(c1 >= 0)
340                 totalteams = totalteams + 1;
341         if(c2 >= 0)
342                 totalteams = totalteams + 1;
343         if(c3 >= 0)
344                 totalteams = totalteams + 1;
345         if(c4 >= 0)
346                 totalteams = totalteams + 1;
347
348         if(totalteams <= 1)
349                 error("dom_player_join_team: Too few teams available for domination\n");
350
351         // whichever teams that are available are set to 0 instead of -1
352
353         // if we don't care what team he ends up on, put him on whatever team he entered as.
354         // if he's not on a valid team, then put him on the smallest team
355         if(!balance_teams && !force_balance)
356         {
357                 if(     c1 >= 0 && pl.team == COLOR_TEAM1)
358                         selectedteam = pl.team;
359                 else if(c2 >= 0 && pl.team == COLOR_TEAM2)
360                         selectedteam = pl.team;
361                 else if(c3 >= 0 && pl.team == COLOR_TEAM3)
362                         selectedteam = pl.team;
363                 else if(c4 >= 0 && pl.team == COLOR_TEAM4)
364                         selectedteam = pl.team;
365                 else
366                         selectedteam = -1;
367                 if(selectedteam > 0)
368                 {
369                         SetPlayerColors(pl, selectedteam - 1);
370                         return;
371                 }
372                 // otherwise end up on the smallest team (handled below)
373         }
374
375         // now count how many players are on each team already
376
377         head = find(world, classname, "player");
378         while(head)
379         {
380                 //if(head.netname != "")
381                 {
382                         if(head.team == COLOR_TEAM1)
383                         {
384                                 if(c1 >= 0)
385                                         c1 = c1 + 1;
386                         }
387                         if(head.team == COLOR_TEAM2)
388                         {
389                                 if(c2 >= 0)
390                                         c2 = c2 + 1;
391                         }
392                         if(head.team == COLOR_TEAM3)
393                         {
394                                 if(c3 >= 0)
395                                         c3 = c3 + 1;
396                         }
397                         if(head.team == COLOR_TEAM4)
398                         {
399                                 if(c4 >= 0)
400                                         c4 = c4 + 1;
401                         }
402                 }
403                 head = find(head, classname, "player");
404         }
405
406         // c1...c4 now have counts of each team
407         // figure out which is smallest, giving priority to the team the player is already on as a tie-breaker
408
409         smallestteam = 0;
410         smallestteam_count = 999;
411
412         // 2 gives priority to what team you're already on, 1 goes in order
413         balance_type = 1;
414
415         if(balance_type == 1)
416         {
417                 if(c1 >= 0 && c1 < smallestteam_count)
418                 {
419                         smallestteam = 1;
420                         smallestteam_count = c1;
421                 }
422                 if(c2 >= 0 && c2 < smallestteam_count)
423                 {
424                         smallestteam = 2;
425                         smallestteam_count = c2;
426                 }
427                 if(c3 >= 0 && c3 < smallestteam_count)
428                 {
429                         smallestteam = 3;
430                         smallestteam_count = c3;
431                 }
432                 if(c4 >= 0 && c4 < smallestteam_count)
433                 {
434                         smallestteam = 4;
435                         smallestteam_count = c4;
436                 }
437         }
438         else
439         {
440                 if(c1 >= 0 && (c1 < smallestteam_count ||
441                                         (c1 == smallestteam_count && self.team == COLOR_TEAM1) ) )
442                 {
443                         smallestteam = 1;
444                         smallestteam_count = c1;
445                 }
446                 if(c2 >= 0 && c2 < (c2 < smallestteam_count ||
447                                         (c2 == smallestteam_count && self.team == COLOR_TEAM2) ) )
448                 {
449                         smallestteam = 2;
450                         smallestteam_count = c2;
451                 }
452                 if(c3 >= 0 && c3 < (c3 < smallestteam_count ||
453                                         (c3 == smallestteam_count && self.team == COLOR_TEAM3) ) )
454                 {
455                         smallestteam = 3;
456                         smallestteam_count = c3;
457                 }
458                 if(c4 >= 0 && c4 < (c4 < smallestteam_count ||
459                                         (c4 == smallestteam_count && self.team == COLOR_TEAM4) ) )
460                 {
461                         smallestteam = 4;
462                         smallestteam_count = c4;
463                 }
464         }
465
466         if(smallestteam == 1)
467         {
468                 selectedteam = COLOR_TEAM1 - 1;
469         }
470         if(smallestteam == 2)
471         {
472                 selectedteam = COLOR_TEAM2 - 1;
473         }
474         if(smallestteam == 3)
475         {
476                 selectedteam = COLOR_TEAM3 - 1;
477         }
478         if(smallestteam == 4)
479         {
480                 selectedteam = COLOR_TEAM4 - 1;
481         }
482
483         SetPlayerColors(pl, selectedteam);
484 }
485 */
486 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
487 Control point for Domination gameplay.
488 */
489 void spawnfunc_dom_controlpoint()
490 {
491         if(!g_domination)
492         {
493                 remove(self);
494                 return;
495         }
496         self.think = dom_controlpoint_setup;
497         self.nextthink = time + 0.1;
498
499         if(!self.scale)
500                 self.scale = 0.6;
501
502         //if(!self.glow_size)
503         //      self.glow_size = cvar("g_domination_point_glow");
504         self.effects = self.effects | EF_FULLBRIGHT | EF_LOWPRECISION;
505 };
506
507 // code from here on is just to support maps that don't have control point and team entities
508 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
509 {
510         local entity oldself;
511         oldself = self;
512         self = spawn();
513         self.classname = "dom_team";
514         self.netname = teamname;
515         self.cnt = teamcolor;
516         self.model = pointmodel;
517         self.skin = pointskin;
518         self.noise = capsound;
519         self.noise1 = capnarration;
520         self.message = capmessage;
521
522         // this code is identical to spawnfunc_dom_team
523         setmodel(self, self.model); // precision not needed
524         self.mdl = self.model;
525         self.dmg = self.modelindex;
526         self.model = "";
527         self.modelindex = 0;
528         // this would have to be changed if used in quakeworld
529         self.team = self.cnt + 1;
530
531         //eprint(self);
532         self = oldself;
533 };
534
535 void dom_spawnpoint(vector org)
536 {
537         local entity oldself;
538         oldself = self;
539         self = spawn();
540         self.classname = "dom_controlpoint";
541         self.think = spawnfunc_dom_controlpoint;
542         self.nextthink = time;
543         self.origin = org;
544         spawnfunc_dom_controlpoint();
545         self = oldself;
546 };
547
548 // spawn some default teams if the map is not set up for domination
549 void dom_spawnteams()
550 {
551         float numteams;
552
553         numteams = cvar("g_domination_default_teams");
554         // LordHavoc: edit this if you want to change defaults
555         dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
556         dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
557         if(numteams > 2)
558                 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
559         if(numteams > 3)
560                 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
561         dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
562 };
563
564 void dom_delayedinit()
565 {
566         local entity head;
567
568         self.think = SUB_Remove;
569         self.nextthink = time;
570         // if no teams are found, spawn defaults
571         if (find(world, classname, "dom_team") == world)
572                 dom_spawnteams();
573         // if no control points are found, spawn defaults
574         if (find(world, classname, "dom_controlpoint") == world)
575         {
576                 // here follow default domination points for each map
577                 /*
578                 if (world.model == "maps/e1m1.bsp")
579                 {
580                         dom_spawnpoint('0 0 0');
581                 }
582                 else
583                 */
584                 {
585                         // if no supported map was found, make every deathmatch spawn a point
586                         head = find(world, classname, "info_player_deathmatch");
587                         while (head)
588                         {
589                                 dom_spawnpoint(head.origin);
590                                 head = find(head, classname, "info_player_deathmatch");
591                         }
592                 }
593         }
594 };
595
596 void dom_init()
597 {
598         local entity e;
599         // we have to precache default models/sounds even if they might not be
600         // used because spawnfunc_worldspawn is executed before any other entities are read,
601         // so we don't even know yet if this map is set up for domination...
602         precache_model("models/domination/dom_red.md3");
603         precache_model("models/domination/dom_blue.md3");
604         precache_model("models/domination/dom_yellow.md3");
605         precache_model("models/domination/dom_pink.md3");
606         precache_model("models/domination/dom_unclaimed.md3");
607         precache_sound("domination/claim.wav");
608         e = spawn();
609         e.think = dom_delayedinit;
610         e.nextthink = time + 0.1;
611
612         // teamplay is always on in domination, defaults to hurt self but not teammates
613         //if(!cvar("teamplay"))
614         //      cvar_set("teamplay", "3");
615 };
616