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