]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_domination.qc
Merge branch 'master' into Mario/qc_physics_prehax
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_domination.qc
1 #include "../../common/triggers/subs.qh"
2
3 void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
4 {
5         if(autocvar_sv_eventlog)
6                 GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
7 }
8
9 void set_dom_state(entity e)
10 {
11         e.dom_total_pps = total_pps;
12         e.dom_pps_red = pps_red;
13         e.dom_pps_blue = pps_blue;
14         if(domination_teams >= 3)
15                 e.dom_pps_yellow = pps_yellow;
16         if(domination_teams >= 4)
17                 e.dom_pps_pink = pps_pink;
18 }
19
20 void dompoint_captured ()
21 {
22         entity head;
23         float old_delay, old_team, real_team;
24         string msg = "dom-neut";
25
26         // now that the delay has expired, switch to the latest team to lay claim to this point
27         head = self.owner;
28
29         real_team = self.cnt;
30         self.cnt = -1;
31
32         dom_EventLog("taken", self.team, self.dmg_inflictor);
33         self.dmg_inflictor = world;
34
35         self.goalentity = head;
36         self.model = head.mdl;
37         self.modelindex = head.dmg;
38         self.skin = head.skin;
39
40         float points, wait_time;
41         if (autocvar_g_domination_point_amt)
42                 points = autocvar_g_domination_point_amt;
43         else
44                 points = self.frags;
45         if (autocvar_g_domination_point_rate)
46                 wait_time = autocvar_g_domination_point_rate;
47         else
48                 wait_time = self.wait;
49
50         if(domination_roundbased)
51                 bprint(sprintf("^3%s^3%s\n", head.netname, self.message));
52         else
53                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time);
54
55         if(self.enemy.playerid == self.enemy_playerid)
56                 PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1);
57         else
58                 self.enemy = world;
59
60         if (head.noise != "")
61                 if(self.enemy)
62                         sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
63                 else
64                         sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
65         if (head.noise1 != "")
66                 play2all(head.noise1);
67
68         self.delay = time + wait_time;
69
70         // do trigger work
71         old_delay = self.delay;
72         old_team = self.team;
73         self.team = real_team;
74         self.delay = 0;
75         activator = self;
76         SUB_UseTargets ();
77         self.delay = old_delay;
78         self.team = old_team;
79
80         switch(self.team)
81         {
82                 case NUM_TEAM_1: msg = "dom-red"; break;
83                 case NUM_TEAM_2: msg = "dom-blue"; break;
84                 case NUM_TEAM_3: msg = "dom-yellow"; break;
85                 case NUM_TEAM_4: msg = "dom-pink"; break;
86         }
87
88         WaypointSprite_UpdateSprites(self.sprite, msg, "", "");
89
90         total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
91         for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; )
92         {
93                 if (autocvar_g_domination_point_amt)
94                         points = autocvar_g_domination_point_amt;
95                 else
96                         points = head.frags;
97                 if (autocvar_g_domination_point_rate)
98                         wait_time = autocvar_g_domination_point_rate;
99                 else
100                         wait_time = head.wait;
101                 switch(head.goalentity.team)
102                 {
103                         case NUM_TEAM_1:
104                                 pps_red += points/wait_time;
105                                 break;
106                         case NUM_TEAM_2:
107                                 pps_blue += points/wait_time;
108                                 break;
109                         case NUM_TEAM_3:
110                                 pps_yellow += points/wait_time;
111                                 break;
112                         case NUM_TEAM_4:
113                                 pps_pink += points/wait_time;
114                                 break;
115                 }
116                 total_pps += points/wait_time;
117         }
118
119         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0));
120         WaypointSprite_Ping(self.sprite);
121
122         self.captime = time;
123
124         FOR_EACH_REALCLIENT(head)
125                 set_dom_state(head);
126 }
127
128 void AnimateDomPoint()
129 {
130         if(self.pain_finished > time)
131                 return;
132         self.pain_finished = time + self.t_width;
133         if(self.nextthink > self.pain_finished)
134                 self.nextthink = self.pain_finished;
135
136         self.frame = self.frame + 1;
137         if(self.frame > self.t_length)
138                 self.frame = 0;
139 }
140
141 void dompointthink()
142 {
143         float fragamt;
144
145         self.nextthink = time + 0.1;
146
147         //self.frame = self.frame + 1;
148         //if(self.frame > 119)
149         //      self.frame = 0;
150         AnimateDomPoint();
151
152         // give points
153
154         if (gameover || self.delay > time || time < game_starttime)     // game has ended, don't keep giving points
155                 return;
156
157         if(autocvar_g_domination_point_rate)
158                 self.delay = time + autocvar_g_domination_point_rate;
159         else
160                 self.delay = time + self.wait;
161
162         // give credit to the team
163         // NOTE: this defaults to 0
164         if (!domination_roundbased)
165         if (self.goalentity.netname != "")
166         {
167                 if(autocvar_g_domination_point_amt)
168                         fragamt = autocvar_g_domination_point_amt;
169                 else
170                         fragamt = self.frags;
171                 TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt);
172                 TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt);
173
174                 // give credit to the individual player, if he is still there
175                 if (self.enemy.playerid == self.enemy_playerid)
176                 {
177                         PlayerScore_Add(self.enemy, SP_SCORE, fragamt);
178                         PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt);
179                 }
180                 else
181                         self.enemy = world;
182         }
183 }
184
185 void dompointtouch()
186 {
187         entity head;
188         if (!IS_PLAYER(other))
189                 return;
190         if (other.health < 1)
191                 return;
192
193         if(round_handler_IsActive() && !round_handler_IsRoundStarted())
194                 return;
195
196         if(time < self.captime + 0.3)
197                 return;
198
199         // only valid teams can claim it
200         head = find(world, classname, "dom_team");
201         while (head && head.team != other.team)
202                 head = find(head, classname, "dom_team");
203         if (!head || head.netname == "" || head == self.goalentity)
204                 return;
205
206         // delay capture
207
208         self.team = self.goalentity.team; // this stores the PREVIOUS team!
209
210         self.cnt = other.team;
211         self.owner = head; // team to switch to after the delay
212         self.dmg_inflictor = other;
213
214         // self.state = 1;
215         // self.delay = time + cvar("g_domination_point_capturetime");
216         //self.nextthink = time + cvar("g_domination_point_capturetime");
217         //self.think = dompoint_captured;
218
219         // go to neutral team in the mean time
220         head = find(world, classname, "dom_team");
221         while (head && head.netname != "")
222                 head = find(head, classname, "dom_team");
223         if(head == world)
224                 return;
225
226         WaypointSprite_UpdateSprites(self.sprite, "dom-neut", "", "");
227         WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1');
228         WaypointSprite_Ping(self.sprite);
229
230         self.goalentity = head;
231         self.model = head.mdl;
232         self.modelindex = head.dmg;
233         self.skin = head.skin;
234
235         self.enemy = other; // individual player scoring
236         self.enemy_playerid = other.playerid;
237         dompoint_captured();
238 }
239
240 void dom_controlpoint_setup()
241 {
242         entity head;
243         // find the spawnfunc_dom_team representing unclaimed points
244         head = find(world, classname, "dom_team");
245         while(head && head.netname != "")
246                 head = find(head, classname, "dom_team");
247         if (!head)
248                 objerror("no spawnfunc_dom_team with netname \"\" found\n");
249
250         // copy important properties from spawnfunc_dom_team entity
251         self.goalentity = head;
252         setmodel(self, head.mdl); // precision already set
253         self.skin = head.skin;
254
255         self.cnt = -1;
256
257         if(self.message == "")
258                 self.message = " has captured a control point";
259
260         if(self.frags <= 0)
261                 self.frags = 1;
262         if(self.wait <= 0)
263                 self.wait = 5;
264
265         float points, waittime;
266         if (autocvar_g_domination_point_amt)
267                 points = autocvar_g_domination_point_amt;
268         else
269                 points = self.frags;
270         if (autocvar_g_domination_point_rate)
271                 waittime = autocvar_g_domination_point_rate;
272         else
273                 waittime = self.wait;
274
275         total_pps += points/waittime;
276
277         if(!self.t_width)
278                 self.t_width = 0.02; // frame animation rate
279         if(!self.t_length)
280                 self.t_length = 239; // maximum frame
281
282         self.think = dompointthink;
283         self.nextthink = time;
284         self.touch = dompointtouch;
285         self.solid = SOLID_TRIGGER;
286         self.flags = FL_ITEM;
287         setsize(self, '-32 -32 -32', '32 32 32');
288         setorigin(self, self.origin + '0 0 20');
289         droptofloor();
290
291         waypoint_spawnforitem(self);
292         WaypointSprite_SpawnFixed("dom-neut", self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT, '0 1 1');
293 }
294
295 float total_controlpoints, redowned, blueowned, yellowowned, pinkowned;
296 void Domination_count_controlpoints()
297 {
298         entity e;
299         total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
300         for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; )
301         {
302                 ++total_controlpoints;
303                 redowned += (e.goalentity.team == NUM_TEAM_1);
304                 blueowned += (e.goalentity.team == NUM_TEAM_2);
305                 yellowowned += (e.goalentity.team == NUM_TEAM_3);
306                 pinkowned += (e.goalentity.team == NUM_TEAM_4);
307         }
308 }
309
310 float Domination_GetWinnerTeam()
311 {
312         float winner_team = 0;
313         if(redowned == total_controlpoints)
314                 winner_team = NUM_TEAM_1;
315         if(blueowned == total_controlpoints)
316         {
317                 if(winner_team) return 0;
318                 winner_team = NUM_TEAM_2;
319         }
320         if(yellowowned == total_controlpoints)
321         {
322                 if(winner_team) return 0;
323                 winner_team = NUM_TEAM_3;
324         }
325         if(pinkowned == total_controlpoints)
326         {
327                 if(winner_team) return 0;
328                 winner_team = NUM_TEAM_4;
329         }
330         if(winner_team)
331                 return winner_team;
332         return -1; // no control points left?
333 }
334
335 #define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
336 #define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints)
337 float Domination_CheckWinner()
338 {
339         if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
340         {
341                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
342                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
343                 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
344                 return 1;
345         }
346
347         Domination_count_controlpoints();
348
349         float winner_team = Domination_GetWinnerTeam();
350
351         if(winner_team == -1)
352                 return 0;
353
354         if(winner_team > 0)
355         {
356                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
357                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
358                 TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
359         }
360         else if(winner_team == -1)
361         {
362                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
363                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
364         }
365
366         round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
367
368         return 1;
369 }
370
371 float Domination_CheckPlayers()
372 {
373         return 1;
374 }
375
376 void Domination_RoundStart()
377 {
378         entity e;
379         FOR_EACH_PLAYER(e)
380                 e.player_blocked = 0;
381 }
382
383 //go to best items, or control points you don't own
384 void havocbot_role_dom()
385 {
386         if(self.deadflag != DEAD_NO)
387                 return;
388
389         if (self.bot_strategytime < time)
390         {
391                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
392                 navigation_goalrating_start();
393                 havocbot_goalrating_controlpoints(10000, self.origin, 15000);
394                 havocbot_goalrating_items(8000, self.origin, 8000);
395                 //havocbot_goalrating_enemyplayers(3000, self.origin, 2000);
396                 //havocbot_goalrating_waypoints(1, self.origin, 1000);
397                 navigation_goalrating_end();
398         }
399 }
400
401 MUTATOR_HOOKFUNCTION(dom_GetTeamCount)
402 {
403         ret_float = domination_teams;
404         return 0;
405 }
406
407 MUTATOR_HOOKFUNCTION(dom_ResetMap)
408 {
409         total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
410         FOR_EACH_PLAYER(self)
411         {
412                 PutClientInServer();
413                 self.player_blocked = 1;
414                 if(IS_REAL_CLIENT(self))
415                         set_dom_state(self);
416         }
417         return 1;
418 }
419
420 MUTATOR_HOOKFUNCTION(dom_PlayerSpawn)
421 {
422         if(domination_roundbased)
423         if(!round_handler_IsRoundStarted())
424                 self.player_blocked = 1;
425         else
426                 self.player_blocked = 0;
427         return false;
428 }
429
430 MUTATOR_HOOKFUNCTION(dom_ClientConnect)
431 {
432         set_dom_state(self);
433         return false;
434 }
435
436 MUTATOR_HOOKFUNCTION(dom_BotRoles)
437 {
438         self.havocbot_role = havocbot_role_dom;
439         return true;
440 }
441
442 /*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32)
443 Control point for Domination gameplay.
444 */
445 void spawnfunc_dom_controlpoint()
446 {
447         if(!g_domination)
448         {
449                 remove(self);
450                 return;
451         }
452         self.think = dom_controlpoint_setup;
453         self.nextthink = time + 0.1;
454         self.reset = dom_controlpoint_setup;
455
456         if(!self.scale)
457                 self.scale = 0.6;
458
459         self.effects = self.effects | EF_LOWPRECISION;
460         if (autocvar_g_domination_point_fullbright)
461                 self.effects |= EF_FULLBRIGHT;
462 }
463
464 /*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32)
465 Team declaration for Domination gameplay, this allows you to decide what team
466 names and control point models are used in your map.
467
468 Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two
469 can have netname set!  The nameless team owns all control points at start.
470
471 Keys:
472 "netname"
473  Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc)
474 "cnt"
475  Scoreboard color of the team (for example 4 is red and 13 is blue)
476 "model"
477  Model to use for control points owned by this team (for example
478  "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver
479  keycard)
480 "skin"
481  Skin of the model to use (for team skins on a single model)
482 "noise"
483  Sound to play when this team captures a point.
484  (this is a localized sound, like a small alarm or other effect)
485 "noise1"
486  Narrator speech to play when this team captures a point.
487  (this is a global sound, like "Red team has captured a control point")
488 */
489
490 void spawnfunc_dom_team()
491 {
492         if(!g_domination || autocvar_g_domination_teams_override >= 2)
493         {
494                 remove(self);
495                 return;
496         }
497         precache_model(self.model);
498         if (self.noise != "")
499                 precache_sound(self.noise);
500         if (self.noise1 != "")
501                 precache_sound(self.noise1);
502         self.classname = "dom_team";
503         setmodel(self, self.model); // precision not needed
504         self.mdl = self.model;
505         self.dmg = self.modelindex;
506         self.model = "";
507         self.modelindex = 0;
508         // this would have to be changed if used in quakeworld
509         if(self.cnt)
510                 self.team = self.cnt + 1; // WHY are these different anyway?
511 }
512
513 // scoreboard setup
514 void ScoreRules_dom(float teams)
515 {
516         if(domination_roundbased)
517         {
518                 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
519                 ScoreInfo_SetLabel_TeamScore  (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
520                 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0);
521                 ScoreRules_basics_end();
522         }
523         else
524         {
525                 float sp_domticks, sp_score;
526                 sp_score = sp_domticks = 0;
527                 if(autocvar_g_domination_disable_frags)
528                         sp_domticks = SFL_SORT_PRIO_PRIMARY;
529                 else
530                         sp_score = SFL_SORT_PRIO_PRIMARY;
531                 ScoreRules_basics(teams, sp_score, sp_score, true);
532                 ScoreInfo_SetLabel_TeamScore  (ST_DOM_TICKS,    "ticks",     sp_domticks);
533                 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS,    "ticks",     sp_domticks);
534                 ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES,    "takes",     0);
535                 ScoreRules_basics_end();
536         }
537 }
538
539 // code from here on is just to support maps that don't have control point and team entities
540 void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage)
541 {
542         entity oldself;
543         oldself = self;
544         self = spawn();
545         self.classname = "dom_team";
546         self.netname = teamname;
547         self.cnt = teamcolor;
548         self.model = pointmodel;
549         self.skin = pointskin;
550         self.noise = capsound;
551         self.noise1 = capnarration;
552         self.message = capmessage;
553
554         // this code is identical to spawnfunc_dom_team
555         setmodel(self, self.model); // precision not needed
556         self.mdl = self.model;
557         self.dmg = self.modelindex;
558         self.model = "";
559         self.modelindex = 0;
560         // this would have to be changed if used in quakeworld
561         self.team = self.cnt + 1;
562
563         //eprint(self);
564         self = oldself;
565 }
566
567 void dom_spawnpoint(vector org)
568 {
569         entity oldself;
570         oldself = self;
571         self = spawn();
572         self.classname = "dom_controlpoint";
573         self.think = spawnfunc_dom_controlpoint;
574         self.nextthink = time;
575         setorigin(self, org);
576         spawnfunc_dom_controlpoint();
577         self = oldself;
578 }
579
580 // spawn some default teams if the map is not set up for domination
581 void dom_spawnteams(float teams)
582 {
583         dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, "domination/claim.wav", "", "Red team has captured a control point");
584         dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, "domination/claim.wav", "", "Blue team has captured a control point");
585         if(teams >= 3)
586                 dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, "domination/claim.wav", "", "Yellow team has captured a control point");
587         if(teams >= 4)
588                 dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, "domination/claim.wav", "", "Pink team has captured a control point");
589         dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", "");
590 }
591
592 void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
593 {
594         // if no teams are found, spawn defaults
595         if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2)
596         {
597                 print("No ""dom_team"" entities found on this map, creating them anyway.\n");
598                 domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4);
599                 dom_spawnteams(domination_teams);
600         }
601
602         CheckAllowedTeams(world);
603         domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
604
605         addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps);
606         addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red);
607         addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue);
608         if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow);
609         if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink);
610
611         domination_roundbased = autocvar_g_domination_roundbased;
612
613         ScoreRules_dom(domination_teams);
614
615         if(domination_roundbased)
616         {
617                 round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart);
618                 round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
619         }
620 }
621
622 void dom_Initialize()
623 {
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
631         InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE);
632 }
633
634
635 MUTATOR_DEFINITION(gamemode_domination)
636 {
637         MUTATOR_HOOK(GetTeamCount, dom_GetTeamCount, CBC_ORDER_ANY);
638         MUTATOR_HOOK(reset_map_players, dom_ResetMap, CBC_ORDER_ANY);
639         MUTATOR_HOOK(PlayerSpawn, dom_PlayerSpawn, CBC_ORDER_ANY);
640         MUTATOR_HOOK(ClientConnect, dom_ClientConnect, CBC_ORDER_ANY);
641         MUTATOR_HOOK(HavocBot_ChooseRole, dom_BotRoles, CBC_ORDER_ANY);
642
643         MUTATOR_ONADD
644         {
645                 if(time > 1) // game loads at time 1
646                         error("This is a game type and it cannot be added at runtime.");
647                 dom_Initialize();
648         }
649
650         MUTATOR_ONREMOVE
651         {
652                 print("This is a game type and it cannot be removed at runtime.");
653                 return -1;
654         }
655
656         return 0;
657 }