]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/gamemodes/gamemode/clanarena/sv_clanarena.qc
Merge branch 'bones_was_here/gunoffset' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / gamemodes / gamemode / clanarena / sv_clanarena.qc
1 #include "sv_clanarena.qh"
2
3 float autocvar_g_ca_damage2score = 100;
4 bool autocvar_g_ca_prevent_stalemate;
5
6 float autocvar_g_ca_start_health = 200;
7 float autocvar_g_ca_start_armor = 200;
8 float autocvar_g_ca_start_ammo_shells = 60;
9 float autocvar_g_ca_start_ammo_nails = 320;
10 float autocvar_g_ca_start_ammo_rockets = 160;
11 float autocvar_g_ca_start_ammo_cells = 180;
12 float autocvar_g_ca_start_ammo_plasma = 180;
13 float autocvar_g_ca_start_ammo_fuel = 0;
14
15 .float ca_damage_counter;
16
17 void CA_count_alive_players()
18 {
19         total_players = 0;
20         for (int i = 1; i <= NUM_TEAMS; ++i)
21         {
22                 Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(i), 0);
23         }
24         FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it),
25         {
26                 ++total_players;
27                 if (IS_DEAD(it))
28                 {
29                         continue;
30                 }
31                 entity team_ = Entity_GetTeam(it);
32                 int num_alive = Team_GetNumberOfAlivePlayers(team_);
33                 ++num_alive;
34                 Team_SetNumberOfAlivePlayers(team_, num_alive);
35         });
36         FOREACH_CLIENT(IS_REAL_CLIENT(it),
37         {
38                 STAT(REDALIVE, it) = Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(1));
39                 STAT(BLUEALIVE, it) = Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(2));
40                 STAT(YELLOWALIVE, it) = Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(3));
41                 STAT(PINKALIVE, it) = Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(4));
42         });
43 }
44
45 void nades_Clear(entity player);
46
47 int CA_PreventStalemate()
48 {
49         //LOG_INFO("PreventStalemate running");
50         int winnerTeam = 0;
51         int secondTeam = 0;
52
53         for(int i = 1; i <= AVAILABLE_TEAMS; i++)
54         {
55                 if(!winnerTeam || Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) > Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)))
56                 {
57                         secondTeam = winnerTeam;
58                         winnerTeam = Team_IndexToTeam(i);
59                 }
60                 else
61                 {
62                         if(!secondTeam || Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) > Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)))
63                                 secondTeam = Team_IndexToTeam(i);
64                 }
65         }
66
67         if(Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)) != Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)))
68         {
69                 LOG_INFOF("Stalemate broken by alive players. Best team: %s%s (%d)^7 - Trailing team: %s%s (%d)",
70                         Team_ColorCode(winnerTeam), Team_ColorName(winnerTeam), Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)),
71                         Team_ColorCode(secondTeam), Team_ColorName(secondTeam), Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)));
72                 return winnerTeam;
73         }
74
75         // Equality. Let's check which team has more health now
76         //LOG_INFO("Equality. Checking health now.");
77         winnerTeam = 0;
78         secondTeam = 0;
79         int winnerTeamHealth = 0;
80         int secondTeamHealth = 0;
81         int teamIndex, teamHealth;
82
83         for(int i = 1; i <= AVAILABLE_TEAMS; i++)
84         {
85                 teamIndex = i;
86                 teamHealth = 0;
87
88                 // Add up health for the players in this team
89                 FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it) && it.team == Team_IndexToTeam(teamIndex),
90                 {
91                         if (IS_DEAD(it))
92                                 continue;
93                         teamHealth += GetResource(it, RES_HEALTH) + GetResource(it, RES_ARMOR);
94                 });
95
96                 // Set the winner teams
97                 if(!winnerTeam || teamHealth > winnerTeamHealth)
98                 {
99                         secondTeam = winnerTeam;
100                         secondTeamHealth = winnerTeamHealth;
101                         winnerTeam = Team_IndexToTeam(i);
102                         winnerTeamHealth = teamHealth;
103                 }
104                 else
105                 {
106                         if(!secondTeam || teamHealth > secondTeamHealth)
107                         {
108                                 secondTeam = Team_IndexToTeam(i);
109                                 secondTeamHealth = teamHealth;
110                         }
111                 }
112         }
113
114         if(winnerTeamHealth != secondTeamHealth)
115         {
116                 LOG_INFOF("Stalemate broken by team health. Best team: %s%s (%d)^7 - Trailing team: %s%s (%d)",
117                         Team_ColorCode(winnerTeam), Team_ColorName(winnerTeam), winnerTeamHealth,
118                         Team_ColorCode(secondTeam), Team_ColorName(secondTeam), secondTeamHealth);
119                 return winnerTeam;
120         }
121         else
122                 return -2; // Equality. Can't avoid the stalemate.
123 }
124
125 float CA_CheckWinner()
126 {
127         int winner_team = 0;
128
129         if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
130         {
131                 if(autocvar_g_ca_prevent_stalemate)
132                         winner_team = CA_PreventStalemate();
133                 else
134                         winner_team = -2;
135         }
136
137         CA_count_alive_players();
138         if (!winner_team)
139                 winner_team = Team_GetWinnerAliveTeam();
140         if (!winner_team)
141                 return 0;
142
143         if(winner_team > 0)
144         {
145                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
146                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
147                 TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
148         }
149         else if(winner_team == -1)
150         {
151                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
152                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
153         }
154         else if(winner_team == -2)
155         {
156                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
157                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
158         }
159
160         allowed_to_spawn = false;
161         game_stopped = true;
162         round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
163
164         FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
165
166         return 1;
167 }
168
169 void CA_RoundStart()
170 {
171         allowed_to_spawn = boolean(warmup_stage);
172 }
173
174 bool CA_CheckTeams()
175 {
176         static int prev_missing_teams_mask;
177         allowed_to_spawn = true;
178         CA_count_alive_players();
179         if (Team_GetNumberOfAliveTeams() == NumTeams(ca_teams))
180         {
181                 if(prev_missing_teams_mask > 0)
182                         Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
183                 prev_missing_teams_mask = -1;
184                 return true;
185         }
186         if(total_players == 0)
187         {
188                 if(prev_missing_teams_mask > 0)
189                         Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
190                 prev_missing_teams_mask = -1;
191                 return false;
192         }
193         int missing_teams_mask = 0;
194         for (int i = 1; i <= NUM_TEAMS; ++i)
195         {
196                 if ((ca_teams & Team_IndexToBit(i)) &&
197                         (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) == 0))
198                 {
199                         missing_teams_mask |= Team_IndexToBit(i);
200                 }
201         }
202         if(prev_missing_teams_mask != missing_teams_mask)
203         {
204                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
205                 prev_missing_teams_mask = missing_teams_mask;
206         }
207         return false;
208 }
209
210 bool ca_isEliminated(entity e)
211 {
212         if(INGAME_JOINED(e) && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
213                 return true;
214         if(INGAME_JOINING(e))
215                 return true;
216         return false;
217 }
218
219 /** Returns next available player to spectate if g_ca_spectate_enemies == 0 */
220 entity CA_SpectateNext(entity player, entity start)
221 {
222         if (SAME_TEAM(start, player)) return start;
223         // continue from current player
224         for (entity e = start; (e = find(e, classname, STR_PLAYER)); )
225         {
226                 if (SAME_TEAM(player, e)) return e;
227         }
228         // restart from the beginning
229         for (entity e = NULL; (e = find(e, classname, STR_PLAYER)); )
230         {
231                 if (SAME_TEAM(player, e)) return e;
232         }
233         return start;
234 }
235
236
237 MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
238 {
239         entity player = M_ARGV(0, entity);
240
241         INGAME_STATUS_SET(player, INGAME_STATUS_JOINED);
242         if (time <= game_starttime) // reset on game restart, not on round start
243                 player.ca_damage_counter = 0;
244         if (!warmup_stage)
245                 eliminatedPlayers.SendFlags |= 1;
246 }
247
248 MUTATOR_HOOKFUNCTION(ca, ForbidSpawn)
249 {
250         entity player = M_ARGV(0, entity);
251
252         // spectators / observers that weren't playing can join; they are
253         // immediately forced to observe in the PutClientInServer hook
254         // this way they are put in a team and can play in the next round
255         if (!allowed_to_spawn && INGAME(player))
256                 return true;
257         return false;
258 }
259
260 MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
261 {
262         entity player = M_ARGV(0, entity);
263
264         if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
265         {
266                 TRANSMUTE(Observer, player);
267                 if (CS(player).jointime != time && !INGAME(player)) // not when connecting
268                 {
269                         INGAME_STATUS_SET(player, INGAME_STATUS_JOINING);
270                         Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
271                 }
272         }
273 }
274
275 MUTATOR_HOOKFUNCTION(ca, reset_map_players)
276 {
277         g_ca_spectate_enemies = autocvar_g_ca_spectate_enemies;
278         observe_blocked_if_eliminated = (g_ca_spectate_enemies == -1);
279         // we can avoid sending observe_blocked_if_eliminated to all clients here (with ClientData_Touch)
280         // since it will get sent whenever the client spectates someone anyway
281
282         FOREACH_CLIENT(true, {
283                 CS(it).killcount = 0;
284                 if (INGAME(it) || IS_BOT_CLIENT(it))
285                 {
286                         TRANSMUTE(Player, it);
287                         INGAME_STATUS_SET(it, INGAME_STATUS_JOINED);
288                         PutClientInServer(it);
289                 }
290         });
291         return true;
292 }
293
294 MUTATOR_HOOKFUNCTION(ca, reset_map_global)
295 {
296         allowed_to_spawn = true;
297         return true;
298 }
299
300 MUTATOR_HOOKFUNCTION(ca, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
301 {
302         M_ARGV(0, float) = ca_teams;
303         return true;
304 }
305
306 entity ca_LastPlayerForTeam(entity this)
307 {
308         entity last_pl = NULL;
309         FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
310                 if (!IS_DEAD(it) && SAME_TEAM(this, it))
311                 {
312                         if (!last_pl)
313                                 last_pl = it;
314                         else
315                                 return NULL;
316                 }
317         });
318         return last_pl;
319 }
320
321 void ca_LastPlayerForTeam_Notify(entity this)
322 {
323         if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
324         {
325                 entity pl = ca_LastPlayerForTeam(this);
326                 if (pl)
327                         Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
328         }
329 }
330
331 MUTATOR_HOOKFUNCTION(ca, PlayerDies)
332 {
333         entity frag_target = M_ARGV(2, entity);
334
335         ca_LastPlayerForTeam_Notify(frag_target);
336         if (!allowed_to_spawn)
337         {
338                 frag_target.respawn_flags = RESPAWN_SILENT;
339                 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
340                 frag_target.respawn_time = time + 2;
341         }
342         frag_target.respawn_flags |= RESPAWN_FORCE;
343         if (!warmup_stage)
344                 eliminatedPlayers.SendFlags |= 1;
345         return true;
346 }
347
348
349 MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
350 {
351         entity player = M_ARGV(0, entity);
352
353         if (IS_PLAYER(player) && !IS_DEAD(player))
354                 ca_LastPlayerForTeam_Notify(player);
355         return true;
356 }
357
358 MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
359 {
360         entity player = M_ARGV(0, entity);
361
362         bool is_forced = M_ARGV(1, bool);
363         if (is_forced && INGAME(player))
364                 INGAME_STATUS_CLEAR(player);
365
366         if (IS_PLAYER(player) && !IS_DEAD(player))
367                 ca_LastPlayerForTeam_Notify(player);
368         if (player.killindicator_teamchange == -2) // player wants to spectate
369         {
370                 entcs_update_players(player);
371                 INGAME_STATUS_CLEAR(player);
372         }
373         if (INGAME(player))
374         {
375                 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
376                 player.would_spectate = observe_blocked_if_eliminated; // if blocked from observing force to spectate now
377         }
378         if (!warmup_stage)
379                 eliminatedPlayers.SendFlags |= 1;
380         if (!INGAME(player))
381                 return false;  // allow team reset
382         return true;  // prevent team reset
383 }
384
385 MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
386 {
387         return true;
388 }
389
390 MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
391 {
392         M_ARGV(2, float) = 0; // score will be given to the winner team when the round ends
393         return true;
394 }
395
396 MUTATOR_HOOKFUNCTION(ca, SetStartItems)
397 {
398         start_items       &= ~(IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS);
399         if(!cvar("g_use_ammunition"))
400                 start_items |= IT_UNLIMITED_AMMO;
401
402         start_health       = warmup_start_health       = autocvar_g_ca_start_health;
403         start_armorvalue   = warmup_start_armorvalue   = autocvar_g_ca_start_armor;
404         start_ammo_shells  = warmup_start_ammo_shells  = autocvar_g_ca_start_ammo_shells;
405         start_ammo_nails   = warmup_start_ammo_nails   = autocvar_g_ca_start_ammo_nails;
406         start_ammo_rockets = warmup_start_ammo_rockets = autocvar_g_ca_start_ammo_rockets;
407         start_ammo_cells   = warmup_start_ammo_cells   = autocvar_g_ca_start_ammo_cells;
408         start_ammo_plasma  = warmup_start_ammo_plasma  = autocvar_g_ca_start_ammo_plasma;
409         start_ammo_fuel    = warmup_start_ammo_fuel    = autocvar_g_ca_start_ammo_fuel;
410 }
411
412 MUTATOR_HOOKFUNCTION(ca, Damage_Calculate)
413 {
414         entity frag_attacker = M_ARGV(1, entity);
415         entity frag_target = M_ARGV(2, entity);
416         float frag_deathtype = M_ARGV(3, float);
417         float frag_damage = M_ARGV(4, float);
418         float frag_mirrordamage = M_ARGV(5, float);
419
420         if (IS_PLAYER(frag_target))
421         if (!IS_DEAD(frag_target))
422         if (frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
423                 frag_damage = 0;
424
425         frag_mirrordamage = 0;
426
427         M_ARGV(4, float) = frag_damage;
428         M_ARGV(5, float) = frag_mirrordamage;
429 }
430
431 MUTATOR_HOOKFUNCTION(ca, FilterItem)
432 {
433         entity item = M_ARGV(0, entity);
434
435         if (autocvar_g_powerups <= 0)
436         if (item.itemdef.instanceOfPowerup)
437                 return true;
438
439         if (autocvar_g_pickup_items <= 0)
440                 return true;
441 }
442
443 MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
444 {
445         if (time < game_starttime || (round_handler_IsActive() && !round_handler_IsRoundStarted()))
446                 return;
447
448         entity frag_attacker = M_ARGV(1, entity);
449         entity frag_target = M_ARGV(2, entity);
450         float frag_deathtype = M_ARGV(6, float);
451         float frag_damage = M_ARGV(7, float);
452         float damage_take = bound(0, M_ARGV(4, float), GetResource(frag_target, RES_HEALTH));
453         float damage_save = bound(0, M_ARGV(5, float), GetResource(frag_target, RES_ARMOR));
454
455         float excess = max(0, frag_damage - damage_take - damage_save);
456
457         if (autocvar_g_ca_damage2score <= 0 || frag_damage - excess == 0) return;
458
459         entity scorer = NULL;
460         float scorer_damage = 0;
461
462         if (IS_PLAYER(frag_attacker))
463         {
464                 if (DIFF_TEAM(frag_target, frag_attacker))
465                         scorer_damage = frag_damage - excess;
466                 else // friendly fire
467                         scorer_damage = -(frag_damage - excess);
468
469                 scorer = frag_attacker;
470         }
471         else
472         {
473                 //handle (environmental hazard) suiciding, check first if player has a registered attacker who most likely pushed them there to avoid punishing pushed players as pushers are already rewarded
474                 //deathtypes:
475                 //kill = suicide, drown = drown in water/liquid, hurttrigger = out of the map void or hurt triggers inside maps like electric sparks
476                 //camp = campcheck, lava = lava, slime = slime
477                 //team change / rebalance suicides are currently not included
478                 if (frag_deathtype == DEATH_KILL.m_id ||
479                         frag_deathtype == DEATH_DROWN.m_id ||
480                         frag_deathtype == DEATH_HURTTRIGGER.m_id ||
481                         frag_deathtype == DEATH_CAMP.m_id ||
482                         frag_deathtype == DEATH_LAVA.m_id ||
483                         frag_deathtype == DEATH_SLIME.m_id ||
484                         frag_deathtype == DEATH_SWAMP.m_id)
485                 {
486                         scorer_damage = -(frag_damage - excess);
487                         scorer = frag_target;
488                 }
489         }
490
491         if (scorer)
492                 GameRules_scoring_add_float2int(scorer, SCORE, scorer_damage, ca_damage_counter, autocvar_g_ca_damage2score);
493 }
494
495 MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime)
496 {
497         // no respawn calculations needed, player is forced to spectate anyway
498         return true;
499 }
500
501 MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
502 {
503         // no regeneration in CA
504         return true;
505 }
506
507 MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
508 {
509         // announce remaining frags
510         return true;
511 }
512
513 MUTATOR_HOOKFUNCTION(ca, SpectateSet)
514 {
515         entity client = M_ARGV(0, entity);
516         entity targ = M_ARGV(1, entity);
517
518         if (g_ca_spectate_enemies != 1 && INGAME(client))
519         if (DIFF_TEAM(targ, client))
520                 return true;
521 }
522
523 MUTATOR_HOOKFUNCTION(ca, SpectateNext)
524 {
525         entity client = M_ARGV(0, entity);
526
527         if (g_ca_spectate_enemies != 1 && INGAME(client)
528                 && Team_GetNumberOfAlivePlayers(Entity_GetTeam(client)))
529         {
530                 entity targ = M_ARGV(1, entity);
531                 M_ARGV(1, entity) = CA_SpectateNext(client, targ);
532                 return true;
533         }
534 }
535
536 MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
537 {
538         entity client = M_ARGV(0, entity);
539         entity targ = M_ARGV(1, entity);
540         entity first = M_ARGV(2, entity);
541
542         if (g_ca_spectate_enemies != 1 && INGAME(client)
543                 && Team_GetNumberOfAlivePlayers(Entity_GetTeam(client)))
544         {
545                 do { targ = targ.chain; }
546                 while(targ && DIFF_TEAM(targ, client));
547
548                 if (!targ)
549                 {
550                         for (targ = first; targ && DIFF_TEAM(targ, client); targ = targ.chain);
551
552                         if (targ == client.enemy)
553                                 return MUT_SPECPREV_RETURN;
554                 }
555         }
556         else
557                 return MUT_SPECPREV_CONTINUE;
558
559         M_ARGV(1, entity) = targ;
560
561         return MUT_SPECPREV_FOUND;
562 }
563
564 MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
565 {
566         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
567                 if (IS_PLAYER(it) || INGAME_JOINED(it))
568                         ++M_ARGV(0, int);
569                 ++M_ARGV(1, int);
570         });
571         return true;
572 }
573
574 MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
575 {
576         entity player = M_ARGV(0, entity);
577
578         if (INGAME(player))
579         {
580                 // they're going to spec, we can do other checks
581                 if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
582                         Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
583                 return MUT_SPECCMD_FORCE;
584         }
585
586         return MUT_SPECCMD_CONTINUE;
587 }
588
589 MUTATOR_HOOKFUNCTION(ca, HideTeamNagger)
590 {
591         return true; // doesn't work well with the whole spectator as player thing
592 }
593
594 MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
595 {
596         if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
597                 M_ARGV(0, string) = autocvar_g_ca_weaponarena;
598 }
599
600 MUTATOR_HOOKFUNCTION(ca, SV_ParseServerCommand)
601 {
602         string cmd_name = M_ARGV(0, string);
603         if (cmd_name == "shuffleteams")
604                 shuffleteams_on_reset_map = !allowed_to_spawn;
605         return false;
606 }