]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_ca.qc
Merge branch 'TimePath/gamemode_composition' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator / gamemode_ca.qc
1 #ifndef GAMEMODE_CA_H
2 #define GAMEMODE_CA_H
3
4 int autocvar_g_ca_point_limit;
5 int autocvar_g_ca_point_leadlimit;
6 bool autocvar_g_ca_team_spawns;
7
8 void ca_Initialize();
9
10 REGISTER_MUTATOR(ca, false)
11 {
12         ActivateTeamplay();
13         SetLimits(autocvar_g_ca_point_limit, autocvar_g_ca_point_leadlimit, -1, -1);
14
15         if (autocvar_g_ca_team_spawns)
16                 have_team_spawns = -1; // request team spawns
17
18         MUTATOR_ONADD
19         {
20                 if (time > 1) // game loads at time 1
21                         error("This is a game type and it cannot be added at runtime.");
22                 ca_Initialize();
23         }
24
25         MUTATOR_ONREMOVE
26         {
27                 LOG_INFO("This is a game type and it cannot be removed at runtime.");
28                 return -1;
29         }
30
31         return 0;
32 }
33
34 // should be removed in the future, as other code should not have to care
35 .float caplayer; // 0.5 if scheduled to join the next round
36 #endif
37
38 #ifdef IMPLEMENTATION
39 float autocvar_g_ca_damage2score_multiplier;
40 float autocvar_g_ca_round_timelimit;
41 bool autocvar_g_ca_spectate_enemies;
42 int autocvar_g_ca_teams;
43 int autocvar_g_ca_teams_override;
44 float autocvar_g_ca_warmup;
45
46 float ca_teams;
47 float allowed_to_spawn;
48
49 const float ST_CA_ROUNDS = 1;
50 void ca_ScoreRules(float teams)
51 {
52         ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
53         ScoreInfo_SetLabel_TeamScore(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
54         ScoreRules_basics_end();
55 }
56
57 void CA_count_alive_players()
58 {
59         entity e;
60         total_players = redalive = bluealive = yellowalive = pinkalive = 0;
61         FOR_EACH_PLAYER(e) {
62                 if(e.team == NUM_TEAM_1)
63                 {
64                         ++total_players;
65                         if (e.health >= 1) ++redalive;
66                 }
67                 else if(e.team == NUM_TEAM_2)
68                 {
69                         ++total_players;
70                         if (e.health >= 1) ++bluealive;
71                 }
72                 else if(e.team == NUM_TEAM_3)
73                 {
74                         ++total_players;
75                         if (e.health >= 1) ++yellowalive;
76                 }
77                 else if(e.team == NUM_TEAM_4)
78                 {
79                         ++total_players;
80                         if (e.health >= 1) ++pinkalive;
81                 }
82         }
83         FOR_EACH_REALCLIENT(e) {
84                 e.redalive_stat = redalive;
85                 e.bluealive_stat = bluealive;
86                 e.yellowalive_stat = yellowalive;
87                 e.pinkalive_stat = pinkalive;
88         }
89 }
90
91 float CA_GetWinnerTeam()
92 {
93         float winner_team = 0;
94         if(redalive >= 1)
95                 winner_team = NUM_TEAM_1;
96         if(bluealive >= 1)
97         {
98                 if(winner_team) return 0;
99                 winner_team = NUM_TEAM_2;
100         }
101         if(yellowalive >= 1)
102         {
103                 if(winner_team) return 0;
104                 winner_team = NUM_TEAM_3;
105         }
106         if(pinkalive >= 1)
107         {
108                 if(winner_team) return 0;
109                 winner_team = NUM_TEAM_4;
110         }
111         if(winner_team)
112                 return winner_team;
113         return -1; // no player left
114 }
115
116 #define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
117 #define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == ca_teams)
118 float CA_CheckWinner()
119 {
120         entity e;
121         if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
122         {
123                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
124                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
125                 allowed_to_spawn = false;
126                 round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
127                 FOR_EACH_PLAYER(e)
128                         nades_Clear(e);
129                 return 1;
130         }
131
132         CA_count_alive_players();
133         if(CA_ALIVE_TEAMS() > 1)
134                 return 0;
135
136         float winner_team = CA_GetWinnerTeam();
137         if(winner_team > 0)
138         {
139                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
140                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
141                 TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
142         }
143         else if(winner_team == -1)
144         {
145                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED);
146                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED);
147         }
148
149         allowed_to_spawn = false;
150         round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
151
152         FOR_EACH_PLAYER(e)
153                 nades_Clear(e);
154
155         return 1;
156 }
157
158 void CA_RoundStart()
159 {
160         if(warmup_stage)
161                 allowed_to_spawn = true;
162         else
163                 allowed_to_spawn = false;
164 }
165
166 float CA_CheckTeams()
167 {
168         static float prev_missing_teams_mask;
169         allowed_to_spawn = true;
170         CA_count_alive_players();
171         if(CA_ALIVE_TEAMS_OK())
172         {
173                 if(prev_missing_teams_mask > 0)
174                         Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
175                 prev_missing_teams_mask = -1;
176                 return 1;
177         }
178         if(total_players == 0)
179         {
180                 if(prev_missing_teams_mask > 0)
181                         Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS);
182                 prev_missing_teams_mask = -1;
183                 return 0;
184         }
185         float missing_teams_mask = (!redalive) + (!bluealive) * 2;
186         if(ca_teams >= 3) missing_teams_mask += (!yellowalive) * 4;
187         if(ca_teams >= 4) missing_teams_mask += (!pinkalive) * 8;
188         if(prev_missing_teams_mask != missing_teams_mask)
189         {
190                 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
191                 prev_missing_teams_mask = missing_teams_mask;
192         }
193         return 0;
194 }
195
196 float ca_isEliminated(entity e)
197 {
198         if(e.caplayer == 1 && (e.deadflag != DEAD_NO || e.frags == FRAGS_LMS_LOSER))
199                 return true;
200         if(e.caplayer == 0.5)
201                 return true;
202         return false;
203 }
204
205 // Returns next available player to spectate if g_ca_spectate_enemies == 0
206 entity CA_SpectateNext(entity player, entity start)
207 {
208         if(SAME_TEAM(start, player))
209                 return start;
210
211         entity spec_other = start;
212         // continue from current player
213         while(spec_other && DIFF_TEAM(spec_other, player))
214                 spec_other = find(spec_other, classname, "player");
215
216         if (!spec_other)
217         {
218                 // restart from begining
219                 spec_other = find(spec_other, classname, "player");
220                 while(spec_other && DIFF_TEAM(spec_other, player))
221                         spec_other = find(spec_other, classname, "player");
222         }
223
224         return spec_other;
225 }
226
227 MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
228 {SELFPARAM();
229         self.caplayer = 1;
230         if(!warmup_stage)
231                 eliminatedPlayers.SendFlags |= 1;
232         return 1;
233 }
234
235 MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
236 {SELFPARAM();
237         if(!allowed_to_spawn)
238         if(IS_PLAYER(self)) // this is true even when player is trying to join
239         {
240                 self.classname = "observer";
241                 if(self.jointime != time) //not when connecting
242                 if(!self.caplayer)
243                 {
244                         self.caplayer = 0.5;
245                         if(IS_REAL_CLIENT(self))
246                                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_JOIN_LATE);
247                 }
248         }
249         return 1;
250 }
251
252 MUTATOR_HOOKFUNCTION(ca, reset_map_players)
253 {SELFPARAM();
254         entity e;
255         FOR_EACH_CLIENT(e)
256         {
257                 setself(e);
258                 self.killcount = 0;
259                 if(!self.caplayer && IS_BOT_CLIENT(self))
260                 {
261                         self.team = -1;
262                         self.caplayer = 1;
263                 }
264                 if(self.caplayer)
265                 {
266                         self.classname = "player";
267                         self.caplayer = 1;
268                         PutClientInServer();
269                 }
270         }
271         return 1;
272 }
273
274 MUTATOR_HOOKFUNCTION(ca, ClientConnect)
275 {SELFPARAM();
276         self.classname = "observer";
277         return 1;
278 }
279
280 MUTATOR_HOOKFUNCTION(ca, reset_map_global)
281 {
282         allowed_to_spawn = true;
283         return 1;
284 }
285
286 MUTATOR_HOOKFUNCTION(ca, GetTeamCount, CBC_ORDER_EXCLUSIVE)
287 {
288         ret_float = ca_teams;
289         return false;
290 }
291
292 entity ca_LastPlayerForTeam()
293 {SELFPARAM();
294         entity pl, last_pl = world;
295         FOR_EACH_PLAYER(pl)
296         {
297                 if(pl.health >= 1)
298                 if(pl != self)
299                 if(pl.team == self.team)
300                 if(!last_pl)
301                         last_pl = pl;
302                 else
303                         return world;
304         }
305         return last_pl;
306 }
307
308 void ca_LastPlayerForTeam_Notify()
309 {
310         if(round_handler_IsActive())
311         if(round_handler_IsRoundStarted())
312         {
313                 entity pl = ca_LastPlayerForTeam();
314                 if(pl)
315                         Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
316         }
317 }
318
319 MUTATOR_HOOKFUNCTION(ca, PlayerDies)
320 {SELFPARAM();
321         ca_LastPlayerForTeam_Notify();
322         if(!allowed_to_spawn)
323                 self.respawn_flags =  RESPAWN_SILENT;
324         if(!warmup_stage)
325                 eliminatedPlayers.SendFlags |= 1;
326         return 1;
327 }
328
329 MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
330 {SELFPARAM();
331         if(self.caplayer == 1)
332                 ca_LastPlayerForTeam_Notify();
333         return 1;
334 }
335
336 MUTATOR_HOOKFUNCTION(ca, ForbidPlayerScore_Clear)
337 {
338         return 1;
339 }
340
341 MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
342 {SELFPARAM();
343         if(self.caplayer == 1)
344                 ca_LastPlayerForTeam_Notify();
345         if(self.killindicator_teamchange == -2)
346                 self.caplayer = 0;
347         if(self.caplayer)
348                 self.frags = FRAGS_LMS_LOSER;
349         if(!warmup_stage)
350                 eliminatedPlayers.SendFlags |= 1;
351         return true;
352 }
353
354 MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
355 {
356         return 1;
357 }
358
359 MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
360 {
361         frag_score = 0; // score will be given to the winner team when the round ends
362         return 1;
363 }
364
365 MUTATOR_HOOKFUNCTION(ca, SetStartItems)
366 {
367         start_items &= ~IT_UNLIMITED_AMMO;
368         start_health       = warmup_start_health       = cvar("g_lms_start_health");
369         start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
370         start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
371         start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
372         start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
373         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
374         start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
375         start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
376
377         return 0;
378 }
379
380 MUTATOR_HOOKFUNCTION(ca, PlayerDamage_Calculate)
381 {
382         if(IS_PLAYER(frag_target))
383         if(frag_target.deadflag == DEAD_NO)
384         if(frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
385                 frag_damage = 0;
386
387         frag_mirrordamage = 0;
388
389         return false;
390 }
391
392 MUTATOR_HOOKFUNCTION(ca, FilterItem)
393 {SELFPARAM();
394         if(autocvar_g_powerups <= 0)
395         if(self.flags & FL_POWERUP)
396                 return true;
397
398         if(autocvar_g_pickup_items <= 0)
399                 return true;
400
401         return false;
402 }
403
404 MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
405 {
406         float excess = max(0, frag_damage - damage_take - damage_save);
407
408         if(frag_target != frag_attacker && IS_PLAYER(frag_attacker))
409                 PlayerTeamScore_Add(frag_attacker, SP_SCORE, ST_SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
410
411         return false;
412 }
413
414 MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
415 {
416         // no regeneration in CA
417         return true;
418 }
419
420 MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
421 {
422         // announce remaining frags
423         return true;
424 }
425
426 MUTATOR_HOOKFUNCTION(ca, SpectateSet)
427 {
428         if(!autocvar_g_ca_spectate_enemies && self.caplayer)
429         if(DIFF_TEAM(spec_player, self))
430                 return true;
431         return false;
432 }
433
434 MUTATOR_HOOKFUNCTION(ca, SpectateNext)
435 {SELFPARAM();
436         if(!autocvar_g_ca_spectate_enemies && self.caplayer)
437         {
438                 spec_player = CA_SpectateNext(self, spec_player);
439                 return true;
440         }
441         return false;
442 }
443
444 MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
445 {SELFPARAM();
446         if(!autocvar_g_ca_spectate_enemies && self.caplayer)
447         {
448                 do { spec_player = spec_player.chain; }
449                 while(spec_player && DIFF_TEAM(spec_player, self));
450
451                 if (!spec_player)
452                 {
453                         spec_player = spec_first;
454                         while(spec_player && DIFF_TEAM(spec_player, self))
455                                 spec_player = spec_player.chain;
456                         if(spec_player == self.enemy)
457                                 return MUT_SPECPREV_RETURN;
458                 }
459         }
460
461         return MUT_SPECPREV_FOUND;
462 }
463
464 MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
465 {
466         entity head;
467         FOR_EACH_REALCLIENT(head)
468         {
469                 if(IS_PLAYER(head) || head.caplayer == 1)
470                         ++bot_activerealplayers;
471                 ++bot_realplayers;
472         }
473
474         return true;
475 }
476
477 MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
478 {
479         if(self.caplayer)
480         {
481                 // they're going to spec, we can do other checks
482                 if(autocvar_sv_spectate && (IS_SPEC(self) || IS_OBSERVER(self)))
483                         Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_LEAVE);
484                 return MUT_SPECCMD_FORCE;
485         }
486
487         return MUT_SPECCMD_CONTINUE;
488 }
489
490 MUTATOR_HOOKFUNCTION(ca, WantWeapon)
491 {
492         want_allguns = true;
493         return false;
494 }
495
496 MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
497 {
498         if(set_player.caplayer == 1)
499                 return true;
500         return false;
501 }
502
503 MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
504 {
505         // most weapons arena
506         if(ret_string == "0" || ret_string == "")
507                 ret_string = "most";
508         return false;
509 }
510
511 void ca_Initialize()
512 {
513         allowed_to_spawn = true;
514
515         ca_teams = autocvar_g_ca_teams_override;
516         if(ca_teams < 2)
517                 ca_teams = autocvar_g_ca_teams;
518         ca_teams = bound(2, ca_teams, 4);
519         ret_float = ca_teams;
520         ca_ScoreRules(ca_teams);
521
522         round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart);
523         round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
524
525         addstat(STAT_REDALIVE, AS_INT, redalive_stat);
526         addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
527         addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
528         addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
529
530         EliminatedPlayers_Init(ca_isEliminated);
531 }
532
533 #endif