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