]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/domination.qc
make ifeq/ifneq work with spaces
[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 // pps: points per second
25 .float dom_total_pps;
26 .float dom_pps_red;
27 .float dom_pps_blue;
28 .float dom_pps_yellow;
29 .float dom_pps_pink;
30 float total_pps;
31 float pps_red;
32 float pps_blue;
33 float pps_yellow;
34 float pps_pink;
35 void set_dom_state(entity e)
36 {
37         e.dom_total_pps = total_pps;
38         e.dom_pps_red = pps_red;
39         e.dom_pps_blue = pps_blue;
40         if(c3 >= 0)
41                 e.dom_pps_yellow = pps_yellow;
42         if(c4 >= 0)
43                 e.dom_pps_pink = pps_pink;
44 }
45
46 void() dom_controlpoint_setup;
47
48 void LogDom(string mode, float team_before, entity actor)
49 {
50         string s;
51         if(!autocvar_sv_eventlog)
52                 return;
53         s = strcat(":dom:", mode);
54         s = strcat(s, ":", ftos(team_before));
55         s = strcat(s, ":", ftos(actor.playerid));
56         GameLogEcho(s);
57 }
58
59 void() dom_spawnteams;
60
61 void dompoint_captured ()
62 {
63         entity head;
64         float old_delay, old_team, real_team;
65
66         // now that the delay has expired, switch to the latest team to lay claim to this point
67         head = self.owner;
68
69         real_team = self.cnt;
70         self.cnt = -1;
71
72         LogDom("taken", self.team, self.dmg_inflictor);
73         self.dmg_inflictor = world;
74
75         self.goalentity = head;
76         self.model = head.mdl;
77         self.modelindex = head.dmg;
78         self.skin = head.skin;
79
80         //bprint(head.message);
81         //bprint("\n");
82
83         //bprint(^3head.netname);
84         //bprint(head.netname);
85         //bprint(self.message);
86         //bprint("\n");
87
88         float points, wait_time;
89         if (autocvar_g_domination_point_amt)
90                 points = autocvar_g_domination_point_amt;
91         else
92                 points = self.frags;
93         if (autocvar_g_domination_point_rate)
94                 wait_time = autocvar_g_domination_point_rate;
95         else
96                 wait_time = self.wait;
97
98         bprint("^3", head.netname, "^3", self.message);
99         if (points != 1)
100                 bprint(" ^7(", ftos(points), " points every ", ftos(wait_time), " seconds)\n");
101         else
102                 bprint(" ^7(", ftos(points), " point every ", ftos(wait_time), " seconds)\n");
103
104         if(self.enemy.playerid == self.enemy_playerid)
105                 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
106         else
107                 self.enemy = world;
108
109         if (head.noise != "")
110                 if(self.enemy)
111                         sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
112                 else
113                         sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTN_NORM);
114         if (head.noise1 != "")
115                 play2all(head.noise1);
116
117         //self.nextthink = time + autocvar_g_domination_point_rate;
118         //self.think = dompointthink;
119
120         self.delay = time + wait_time;
121
122         // do trigger work
123         old_delay = self.delay;
124         old_team = self.team;
125         self.team = real_team;
126         self.delay = 0;
127         activator = self;
128         SUB_UseTargets ();
129         self.delay = old_delay;
130         self.team = old_team;
131
132         switch(self.goalentity.team)
133         {
134                 case COLOR_TEAM1:
135                         WaypointSprite_UpdateSprites(self.sprite, "dom-red", "", "");
136                         break;
137                 case COLOR_TEAM2:
138                         WaypointSprite_UpdateSprites(self.sprite, "dom-blue", "", "");
139                         break;
140                 case COLOR_TEAM3:
141                         WaypointSprite_UpdateSprites(self.sprite, "dom-yellow", "", "");
142                         break;
143                 case COLOR_TEAM4:
144                         WaypointSprite_UpdateSprites(self.sprite, "dom-pink", "", "");
145         }
146
147         total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
148         for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
149         {
150                 if (autocvar_g_domination_point_amt)
151                         points = autocvar_g_domination_point_amt;
152                 else
153                         points = head.frags;
154                 if (autocvar_g_domination_point_rate)
155                         wait_time = autocvar_g_domination_point_rate;
156                 else
157                         wait_time = head.wait;
158                 switch(head.goalentity.team)
159                 {
160                         case COLOR_TEAM1:
161                                 pps_red += points/wait_time;
162                                 break;
163                         case COLOR_TEAM2:
164                                 pps_blue += points/wait_time;
165                                 break;
166                         case COLOR_TEAM3:
167                                 pps_yellow += points/wait_time;
168                                 break;
169                         case COLOR_TEAM4:
170                                 pps_pink += points/wait_time;
171                 }
172                 total_pps += points/wait_time;
173         }
174
175         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
176         WaypointSprite_Ping(self.sprite);
177
178         self.captime = time;
179
180         FOR_EACH_REALCLIENT(head)
181                 set_dom_state(head);
182 }
183
184 void AnimateDomPoint()
185 {
186         if(self.pain_finished > time)
187                 return;
188         self.pain_finished = time + self.t_width;
189         if(self.nextthink > self.pain_finished)
190                 self.nextthink = self.pain_finished;
191
192         self.frame = self.frame + 1;
193         if(self.frame > self.t_length)
194                 self.frame = 0;
195 }
196
197 void dompointthink()
198 {
199         float fragamt;
200
201         self.nextthink = time + 0.1;
202
203         //self.frame = self.frame + 1;
204         //if(self.frame > 119)
205         //      self.frame = 0;
206         AnimateDomPoint();
207
208         // give points
209
210         if (gameover || self.delay > time || time < game_starttime)     // game has ended, don't keep giving points
211                 return;
212
213         if(autocvar_g_domination_point_rate)
214                 self.delay = time + autocvar_g_domination_point_rate;
215         else
216                 self.delay = time + self.wait;
217
218         // give credit to the team
219         // NOTE: this defaults to 0
220         if (self.goalentity.netname != "")
221         {
222                 if(autocvar_g_domination_point_amt)
223                         fragamt = autocvar_g_domination_point_amt;
224                 else
225                         fragamt = self.DOMPOINTFRAGS;
226                 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
227                 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
228
229                 // give credit to the individual player, if he is still there
230                 if (self.enemy.playerid == self.enemy_playerid)
231                 {
232                         PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
233                         PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
234                 }
235                 else
236                         self.enemy = world;
237         }
238 }
239
240 void dompointtouch()
241 {
242         entity head;
243         if (other.classname != "player")
244                 return;
245         if (other.health < 1)
246                 return;
247
248         if(time < self.captime + 0.3)
249                 return;
250
251         // only valid teams can claim it
252         head = find(world, classname, "dom_team");
253         while (head && head.team != other.team)
254                 head = find(head, classname, "dom_team");
255         if (!head || head.netname == "" || head == self.goalentity)
256                 return;
257
258         // delay capture
259
260         self.team = self.goalentity.team; // this stores the PREVIOUS team!
261
262         self.cnt = other.team;
263         self.owner = head; // team to switch to after the delay
264         self.dmg_inflictor = other;
265
266         // self.state = 1;
267         // self.delay = time + cvar("g_domination_point_capturetime");
268         //self.nextthink = time + cvar("g_domination_point_capturetime");
269         //self.think = dompoint_captured;
270
271         // go to neutral team in the mean time
272         head = find(world, classname, "dom_team");
273         while (head && head.netname != "")
274                 head = find(head, classname, "dom_team");
275         if(head == world)
276                 return;
277
278         WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
279         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
280         WaypointSprite_Ping(self.sprite);
281
282         self.goalentity = head;
283         self.model = head.mdl;
284         self.modelindex = head.dmg;
285         self.skin = head.skin;
286
287         self.enemy = other; // individual player scoring
288         self.enemy_playerid = other.playerid;
289         dompoint_captured();
290 }
291
292 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
293 Team declaration for Domination gameplay, this allows you to decide what team
294 names and control point models are used in your map.
295
296 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
297 can have netname set!  The nameless team owns all control points at start.
298
299 Keys:
300 "netname"
301  Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
302 "cnt"
303  Scoreboard color of the team (for example 4 is red and 13 is blue)
304 "model"
305  Model to use for control points owned by this team (for example
306  "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
307  keycard)
308 "skin"
309  Skin of the model to use (for team skins on a single model)
310 "noise"
311  Sound to play when this team captures a point.
312  (this is a localized sound, like a small alarm or other effect)
313 "noise1"
314  Narrator speech to play when this team captures a point.
315  (this is a global sound, like "Red team has captured a control point")
316 */
317
318 void spawnfunc_dom_team()
319 {
320         if(!g_domination || autocvar_g_domination_teams_override >= 2)
321         {
322                 remove(self);
323                 return;
324         }
325         precache_model(self.model);
326         if (self.noise != "")
327                 precache_sound(self.noise);
328         if (self.noise1 != "")
329                 precache_sound(self.noise1);
330         self.classname = "dom_team";
331         setmodel(self, self.model); // precision not needed
332         self.mdl = self.model;
333         self.dmg = self.modelindex;
334         self.model = "";
335         self.modelindex = 0;
336         // this would have to be changed if used in quakeworld
337         if(self.cnt)
338                 self.team = self.cnt + 1; // WHY are these different anyway?
339 }
340
341 void dom_controlpoint_setup()
342 {
343         entity head;
344         // find the spawnfunc_dom_team representing unclaimed points
345         head = find(world, classname, "dom_team");
346         while(head && head.netname != "")
347                 head = find(head, classname, "dom_team");
348         if (!head)
349                 objerror("no spawnfunc_dom_team with netname \"\" found\n");
350
351         // copy important properties from spawnfunc_dom_team entity
352         self.goalentity = head;
353         setmodel(self, head.mdl); // precision already set
354         self.skin = head.skin;
355
356         self.cnt = -1;
357
358         if(!self.message)
359                 self.message = " has captured a control point";
360
361         if(self.DOMPOINTFRAGS <= 0)
362                 self.DOMPOINTFRAGS = 1;
363         if(self.wait <= 0)
364                 self.wait = 5;
365
366         float points, waittime;
367         if (autocvar_g_domination_point_amt)
368                 points = autocvar_g_domination_point_amt;
369         else
370                 points = self.frags;
371         if (autocvar_g_domination_point_rate)
372                 waittime = autocvar_g_domination_point_rate;
373         else
374                 waittime = self.wait;
375
376         total_pps += points/waittime;
377
378         if(!self.t_width)
379                 self.t_width = 0.02; // frame animation rate
380         if(!self.t_length)
381                 self.t_length = 239; // maximum frame
382
383         self.think = dompointthink;
384         self.nextthink = time;
385         self.touch = dompointtouch;
386         self.solid = SOLID_TRIGGER;
387         self.flags = FL_ITEM;
388         setsize(self, '-32 -32 -32', '32 32 32');
389         setorigin(self, self.origin + '0 0 20');
390         droptofloor();
391
392         waypoint_spawnforitem(self);
393         WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT, '0 1 1');
394 }
395
396
397
398 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
399 Control point for Domination gameplay.
400 */
401 void spawnfunc_dom_controlpoint()
402 {
403         if(!g_domination)
404         {
405                 remove(self);
406                 return;
407         }
408         self.think = dom_controlpoint_setup;
409         self.nextthink = time + 0.1;
410         self.reset = dom_controlpoint_setup;
411
412         if(!self.scale)
413                 self.scale = 0.6;
414
415         //if(!self.glow_size)
416         //      self.glow_size = cvar("g_domination_point_glow");
417         self.effects = self.effects | EF_LOWPRECISION;
418         if (autocvar_g_domination_point_fullbright)
419                 self.effects |= EF_FULLBRIGHT;
420 }
421
422 // code from here on is just to support maps that don't have control point and team entities
423 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
424 {
425         entity oldself;
426         oldself = self;
427         self = spawn();
428         self.classname = "dom_team";
429         self.netname = teamname;
430         self.cnt = teamcolor;
431         self.model = pointmodel;
432         self.skin = pointskin;
433         self.noise = capsound;
434         self.noise1 = capnarration;
435         self.message = capmessage;
436
437         // this code is identical to spawnfunc_dom_team
438         setmodel(self, self.model); // precision not needed
439         self.mdl = self.model;
440         self.dmg = self.modelindex;
441         self.model = "";
442         self.modelindex = 0;
443         // this would have to be changed if used in quakeworld
444         self.team = self.cnt + 1;
445
446         //eprint(self);
447         self = oldself;
448 }
449
450 void dom_spawnpoint(vector org)
451 {
452         entity oldself;
453         oldself = self;
454         self = spawn();
455         self.classname = "dom_controlpoint";
456         self.think = spawnfunc_dom_controlpoint;
457         self.nextthink = time;
458         setorigin(self, org);
459         spawnfunc_dom_controlpoint();
460         self = oldself;
461 }
462
463 // spawn some default teams if the map is not set up for domination
464 void dom_spawnteams()
465 {
466         float numteams;
467         if(autocvar_g_domination_teams_override < 2)
468                 numteams = autocvar_g_domination_default_teams;
469         else
470                 numteams = autocvar_g_domination_teams_override;
471         // LordHavoc: edit this if you want to change defaults
472         dom_spawnteam("Red", COLOR_TEAM1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
473         dom_spawnteam("Blue", COLOR_TEAM2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
474         if(numteams > 2)
475                 dom_spawnteam("Yellow", COLOR_TEAM3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
476         if(numteams > 3)
477                 dom_spawnteam("Pink", COLOR_TEAM4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
478         dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
479 }
480
481 void dom_delayedinit()
482 {
483         entity head;
484
485         // if no teams are found, spawn defaults, if custom teams are set, use them
486         if (find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
487                 dom_spawnteams();
488         // if no control points are found, spawn defaults
489         if (find(world, classname, "dom_controlpoint") == world)
490         {
491                 // TODO in a few months (maybe 2011/08): change this into error() and remove this very poor dom point selection
492                 backtrace("This map contains no dom_controlpoint entities. A very poor dom point placement will be chosen. Please fix the map.");
493
494                 // if no supported map was found, make every deathmatch spawn a point
495                 head = find(world, classname, "info_player_deathmatch");
496                 while (head)
497                 {
498                         dom_spawnpoint(head.origin);
499                         head = find(head, classname, "info_player_deathmatch");
500                 }
501         }
502
503         ScoreRules_dom();
504 }
505
506 void dom_init()
507 {
508         // we have to precache default models/sounds even if they might not be
509         // used because spawnfunc_worldspawn is executed before any other entities are read,
510         // so we don't even know yet if this map is set up for domination...
511         precache_model("models/domination/dom_red.md3");
512         precache_model("models/domination/dom_blue.md3");
513         precache_model("models/domination/dom_yellow.md3");
514         precache_model("models/domination/dom_pink.md3");
515         precache_model("models/domination/dom_unclaimed.md3");
516         precache_sound("domination/claim.wav");
517         InitializeEntity(world, dom_delayedinit, INITPRIO_GAMETYPE);
518
519         addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
520         addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
521         addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
522         if(c3 >= 0) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
523         if(c4 >= 0) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
524 }
525