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