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