]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_ca.qc
Merge branch 'master' into terencehill/menu_hudskin_selector
[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         MUTATOR_ONADD
13         {
14                 if (time > 1) // game loads at time 1
15                         error("This is a game type and it cannot be added at runtime.");
16                 ca_Initialize();
17
18                 ActivateTeamplay();
19                 SetLimits(autocvar_g_ca_point_limit, autocvar_g_ca_point_leadlimit, -1, -1);
20
21                 if (autocvar_g_ca_team_spawns)
22                         have_team_spawns = -1; // request team spawns
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)) return start;
209         // continue from current player
210         for (entity e = start; (e = find(e, classname, "player")); )
211         {
212                 if (SAME_TEAM(player, e)) return e;
213         }
214         // restart from begining
215         for (entity e = NULL; (e = find(e, classname, "player")); )
216         {
217                 if (SAME_TEAM(player, e)) return e;
218         }
219         return start;
220 }
221
222
223 MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
224 {SELFPARAM();
225         self.caplayer = 1;
226         if(!warmup_stage)
227                 eliminatedPlayers.SendFlags |= 1;
228         return 1;
229 }
230
231 MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
232 {SELFPARAM();
233         if(!allowed_to_spawn)
234         if(IS_PLAYER(self)) // this is true even when player is trying to join
235         {
236                 self.classname = STR_OBSERVER;
237                 if(self.jointime != time) //not when connecting
238                 if(!self.caplayer)
239                 {
240                         self.caplayer = 0.5;
241                         if(IS_REAL_CLIENT(self))
242                                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_JOIN_LATE);
243                 }
244         }
245         return 1;
246 }
247
248 MUTATOR_HOOKFUNCTION(ca, reset_map_players)
249 {SELFPARAM();
250         entity e;
251         FOR_EACH_CLIENT(e)
252         {
253                 setself(e);
254                 self.killcount = 0;
255                 if(!self.caplayer && IS_BOT_CLIENT(self))
256                 {
257                         self.team = -1;
258                         self.caplayer = 1;
259                 }
260                 if(self.caplayer)
261                 {
262                         self.classname = STR_PLAYER;
263                         self.caplayer = 1;
264                         PutClientInServer();
265                 }
266         }
267         return 1;
268 }
269
270 MUTATOR_HOOKFUNCTION(ca, ClientConnect)
271 {SELFPARAM();
272         self.classname = STR_OBSERVER;
273         return 1;
274 }
275
276 MUTATOR_HOOKFUNCTION(ca, reset_map_global)
277 {
278         allowed_to_spawn = true;
279         return 1;
280 }
281
282 MUTATOR_HOOKFUNCTION(ca, GetTeamCount, CBC_ORDER_EXCLUSIVE)
283 {
284         ret_float = ca_teams;
285         return false;
286 }
287
288 entity ca_LastPlayerForTeam()
289 {SELFPARAM();
290         entity pl, last_pl = world;
291         FOR_EACH_PLAYER(pl)
292         {
293                 if(pl.health >= 1)
294                 if(pl != self)
295                 if(pl.team == self.team)
296                 if(!last_pl)
297                         last_pl = pl;
298                 else
299                         return world;
300         }
301         return last_pl;
302 }
303
304 void ca_LastPlayerForTeam_Notify()
305 {
306         if(round_handler_IsActive())
307         if(round_handler_IsRoundStarted())
308         {
309                 entity pl = ca_LastPlayerForTeam();
310                 if(pl)
311                         Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
312         }
313 }
314
315 MUTATOR_HOOKFUNCTION(ca, PlayerDies)
316 {SELFPARAM();
317         ca_LastPlayerForTeam_Notify();
318         if(!allowed_to_spawn)
319                 self.respawn_flags =  RESPAWN_SILENT;
320         if(!warmup_stage)
321                 eliminatedPlayers.SendFlags |= 1;
322         return 1;
323 }
324
325 MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
326 {SELFPARAM();
327         if(self.caplayer == 1)
328                 ca_LastPlayerForTeam_Notify();
329         return 1;
330 }
331
332 MUTATOR_HOOKFUNCTION(ca, ForbidPlayerScore_Clear)
333 {
334         return 1;
335 }
336
337 MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
338 {SELFPARAM();
339         if(self.caplayer == 1)
340                 ca_LastPlayerForTeam_Notify();
341         if(self.killindicator_teamchange == -2)
342                 self.caplayer = 0;
343         if(self.caplayer)
344                 self.frags = FRAGS_LMS_LOSER;
345         if(!warmup_stage)
346                 eliminatedPlayers.SendFlags |= 1;
347         return true;
348 }
349
350 MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
351 {
352         return 1;
353 }
354
355 MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
356 {
357         frag_score = 0; // score will be given to the winner team when the round ends
358         return 1;
359 }
360
361 MUTATOR_HOOKFUNCTION(ca, SetStartItems)
362 {
363         start_items &= ~IT_UNLIMITED_AMMO;
364         start_health       = warmup_start_health       = cvar("g_lms_start_health");
365         start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
366         start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
367         start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
368         start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
369         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
370         start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
371         start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
372
373         return 0;
374 }
375
376 MUTATOR_HOOKFUNCTION(ca, PlayerDamage_Calculate)
377 {
378         if(IS_PLAYER(frag_target))
379         if(frag_target.deadflag == DEAD_NO)
380         if(frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
381                 frag_damage = 0;
382
383         frag_mirrordamage = 0;
384
385         return false;
386 }
387
388 MUTATOR_HOOKFUNCTION(ca, FilterItem)
389 {SELFPARAM();
390         if(autocvar_g_powerups <= 0)
391         if(self.flags & FL_POWERUP)
392                 return true;
393
394         if(autocvar_g_pickup_items <= 0)
395                 return true;
396
397         return false;
398 }
399
400 MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
401 {
402         float excess = max(0, frag_damage - damage_take - damage_save);
403
404         if(frag_target != frag_attacker && IS_PLAYER(frag_attacker))
405                 PlayerTeamScore_Add(frag_attacker, SP_SCORE, ST_SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
406
407         return false;
408 }
409
410 MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
411 {
412         // no regeneration in CA
413         return true;
414 }
415
416 MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
417 {
418         // announce remaining frags
419         return true;
420 }
421
422 MUTATOR_HOOKFUNCTION(ca, SpectateSet)
423 {
424         if(!autocvar_g_ca_spectate_enemies && self.caplayer)
425         if(DIFF_TEAM(spec_player, self))
426                 return true;
427         return false;
428 }
429
430 MUTATOR_HOOKFUNCTION(ca, SpectateNext)
431 {SELFPARAM();
432         if(!autocvar_g_ca_spectate_enemies && self.caplayer)
433         {
434                 spec_player = CA_SpectateNext(self, spec_player);
435                 return true;
436         }
437         return false;
438 }
439
440 MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
441 {SELFPARAM();
442         if(!autocvar_g_ca_spectate_enemies && self.caplayer)
443         {
444                 do { spec_player = spec_player.chain; }
445                 while(spec_player && DIFF_TEAM(spec_player, self));
446
447                 if (!spec_player)
448                 {
449                         spec_player = spec_first;
450                         while(spec_player && DIFF_TEAM(spec_player, self))
451                                 spec_player = spec_player.chain;
452                         if(spec_player == self.enemy)
453                                 return MUT_SPECPREV_RETURN;
454                 }
455         }
456
457         return MUT_SPECPREV_FOUND;
458 }
459
460 MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
461 {
462         entity head;
463         FOR_EACH_REALCLIENT(head)
464         {
465                 if(IS_PLAYER(head) || head.caplayer == 1)
466                         ++bot_activerealplayers;
467                 ++bot_realplayers;
468         }
469
470         return true;
471 }
472
473 MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
474 {
475         if(self.caplayer)
476         {
477                 // they're going to spec, we can do other checks
478                 if(autocvar_sv_spectate && (IS_SPEC(self) || IS_OBSERVER(self)))
479                         Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_LEAVE);
480                 return MUT_SPECCMD_FORCE;
481         }
482
483         return MUT_SPECCMD_CONTINUE;
484 }
485
486 MUTATOR_HOOKFUNCTION(ca, WantWeapon)
487 {
488         want_allguns = true;
489         return false;
490 }
491
492 MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
493 {
494         if(set_player.caplayer == 1)
495                 return true;
496         return false;
497 }
498
499 MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
500 {
501         // most weapons arena
502         if(ret_string == "0" || ret_string == "")
503                 ret_string = "most";
504         return false;
505 }
506
507 void ca_Initialize()
508 {
509         allowed_to_spawn = true;
510
511         ca_teams = autocvar_g_ca_teams_override;
512         if(ca_teams < 2)
513                 ca_teams = autocvar_g_ca_teams;
514         ca_teams = bound(2, ca_teams, 4);
515         ret_float = ca_teams;
516         ca_ScoreRules(ca_teams);
517
518         round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart);
519         round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
520
521         addstat(STAT_REDALIVE, AS_INT, redalive_stat);
522         addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
523         addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
524         addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
525
526         EliminatedPlayers_Init(ca_isEliminated);
527 }
528
529 #endif