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