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