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