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