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