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