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