]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_survival.qc
Merge branch 'Lyberta/StandaloneOverkillWeapons' into Lyberta/master
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator / gamemode_survival.qc
1 #include "gamemode_survival.qh"
2
3 #include <common/mutators/mutator/playertemplates/sv_playertemplates.qh>
4 #include <common/mutators/mutator/overkill/hmg.qh>
5 #include <common/mutators/mutator/overkill/rpc.qh>
6
7 //============================ Constants ======================================
8
9 const int SURVIVAL_TEAM_1_BIT = BIT(0); ///< Bitmask of the first team.
10 const int SURVIVAL_TEAM_2_BIT = BIT(1); ///< Bitmask of the second team.
11
12 /// \brief Used when bitfield team count is requested.
13 const int SURVIVAL_TEAM_BITS = 3;
14
15 enum
16 {
17         SURVIVAL_TYPE_COOP, ///< All humans are on the defender team.
18         SURVIVAL_TYPE_VERSUS ///< Humans take turns between attackers and defenders.
19 };
20
21 const string SURVIVAL_TYPE_COOP_VALUE = "coop"; ///< Cvar value for coop.
22 const string SURVIVAL_TYPE_VERSUS_VALUE = "versus"; ///< Cvar value for versus.
23
24 enum
25 {
26         /// \brief First round where there is timelimit set by the server.
27         SURVIVAL_ROUND_FIRST,
28         /// \brief Second round where defender team tries to survive for the first
29         /// round's time.
30         SURVIVAL_ROUND_SECOND
31 };
32
33 enum
34 {
35         /// \brief Player is spectating and has no intention of playing.
36         SURVIVAL_STATE_NOT_PLAYING,
37         /// \brief Player is playing the game.
38         SURVIVAL_STATE_PLAYING = 1
39 };
40
41 enum
42 {
43         SURVIVAL_ROLE_NONE, ///< Player is not playing.
44         SURVIVAL_ROLE_PLAYER, ///< Player is an attacker or defender.
45         SURVIVAL_ROLE_CANNON_FODDER ///< Player is a cannon fodder.
46 };
47
48 SOUND(SURV_3_FRAGS_LEFT, "announcer/default/3fragsleft");
49 SOUND(SURV_2_FRAGS_LEFT, "announcer/default/2fragsleft");
50 SOUND(SURV_1_FRAG_LEFT, "announcer/default/1fragleft");
51
52 SOUND(SURV_RED_SCORES, "ctf/red_capture");
53 SOUND(SURV_BLUE_SCORES, "ctf/blue_capture");
54
55 //======================= Global variables ====================================
56
57 float autocvar_g_surv_warmup; ///< Warmup time in seconds.
58 float autocvar_g_surv_round_timelimit; ///< First round time limit in seconds.
59
60 int autocvar_g_surv_point_limit; ///< Maximum number of points.
61 int autocvar_g_surv_point_leadlimit; ///< Maximum lead of a single team.
62
63 /// \brief How much players are allowed in teams (excluding cannon fodder).
64 int autocvar_g_surv_team_size;
65 /// \brief If set, defenders will not be shown on the radar.
66 int autocvar_g_surv_stealth;
67
68 /// \brief Whether to force overkill player models for attackers.
69 int autocvar_g_surv_attacker_force_overkill_models;
70 /// \brief Whether to force overkill player models for defenders.
71 int autocvar_g_surv_defender_force_overkill_models;
72 /// \brief Whether to force overkill player models for cannon fodder.
73 int autocvar_g_surv_cannon_fodder_force_overkill_models;
74
75 /// \brief How much score attackers gain per 1 point of damage.
76 float autocvar_g_surv_attacker_damage_score;
77
78 /// \brief How much score attackers get for fragging defenders.
79 float autocvar_g_surv_attacker_frag_score;
80
81 /// \brief How much health do defenders get when they frag an attacker.
82 int autocvar_g_surv_defender_attacker_frag_health;
83 /// \brief How much armor do defenders get when they frag an attacker.
84 int autocvar_g_surv_defender_attacker_frag_armor;
85 /// \brief How many shells do defenders get when they frag an attacker.
86 int autocvar_g_surv_defender_attacker_frag_shells;
87 /// \brief How many bullets do defenders get when they frag an attacker.
88 int autocvar_g_surv_defender_attacker_frag_bullets;
89 /// \brief How many rockets do defenders get when they frag an attacker.
90 int autocvar_g_surv_defender_attacker_frag_rockets;
91 /// \brief How many cells do defenders get when they frag an attacker.
92 int autocvar_g_surv_defender_attacker_frag_cells;
93 /// \brief How much health do defenders get when they frag cannon fodder.
94 int autocvar_g_surv_defender_cannon_fodder_frag_health;
95 /// \brief How much armor do defenders get when they frag cannon fodder.
96 int autocvar_g_surv_defender_cannon_fodder_frag_armor;
97 /// \brief How many shells do defenders get when they frag cannon fodder.
98 int autocvar_g_surv_defender_cannon_fodder_frag_shells;
99 /// \brief How many bullets do defenders get when they frag cannon fodder.
100 int autocvar_g_surv_defender_cannon_fodder_frag_bullets;
101 /// \brief How many rockets do defenders get when they frag cannon fodder.
102 int autocvar_g_surv_defender_cannon_fodder_frag_rockets;
103 /// \brief How many cells do defenders get when they frag cannon fodder.
104 int autocvar_g_surv_defender_cannon_fodder_frag_cells;
105
106 /// \brief Whether defenders drop weapons after death.
107 int autocvar_g_surv_defender_drop_weapons;
108
109 /// \brief A stat that is used to track the time left in the round.
110 .float surv_round_time_stat = _STAT(SURV_ROUND_TIME);
111 /// \brief A stat that is used to track defender team.
112 .int surv_defender_team_stat = _STAT(SURV_DEFENDER_TEAM);
113 /// \brief A stat that is used to track number of defenders alive.
114 .int surv_defenders_alive_stat = _STAT(SURV_DEFENDERS_ALIVE);
115 /// \brief A stat that is used to track the total health of defenders.
116 .float surv_defender_health_stat = _STAT(SURV_DEFENDER_HEALTH);
117
118 /// \brief Holds the state of the player. See SURVIVAL_STATE constants.
119 .int surv_state;
120 /// \brief Holds the role of the player. See SURVIVAL_ROLE constants.
121 .int surv_role;
122 .string surv_savedplayermodel; ///< Initial player model.
123 /// \brief Player state used during replacement of bot player with real player.
124 .entity surv_savedplayerstate;
125 .string surv_playermodel; ///< Player model forced by the game.
126
127 .entity surv_attack_sprite; ///< Holds the sprite telling attackers to attack.
128
129 int surv_type; ///< Holds the type of survival. See SURVIVAL_TYPE constants.
130 bool surv_warmup; ///< Holds whether warmup is active.
131 /// \brief Holds the type of the current round. See SURVIVAL_ROUND constants.
132 int surv_roundtype;
133 bool surv_isroundactive; ///< Holds whether the round is active.
134 float surv_roundstarttime; ///< Holds the time of the round start.
135 /// \brief Holds the time needed to survive in the second round.
136 float surv_timetobeat;
137
138 int surv_attackerteam; ///< Holds the attacker team.
139 int surv_defenderteam; ///< Holds the defender team.
140
141 int surv_attackerteambit; ///< Hols the attacker team bitmask.
142 int surv_defenderteambit; ///< Holds the defender team bitmask.
143
144 int surv_numattackers; ///< Holds the number of players in attacker team.
145 int surv_numdefenders; ///< Holds the number of players in defender team.
146
147 /// \brief Holds the number of humans in attacker team.
148 int surv_numattackerhumans;
149 /// \brief Holds the number of humans in defender team.
150 int surv_numdefenderhumans;
151
152 /// \brief Holds the number of attacker players that are alive.
153 int surv_numattackersalive;
154 /// \brief Holds the number of defender players that are alive.
155 int surv_numdefendersalive;
156
157 bool surv_autobalance; ///< Holds whether autobalance is active.
158 bool surv_announcefrags; ///< Holds whether remaining frags must be announced.
159 bool surv_allowed_to_spawn; ///< Holds whether players are allowed to spawn.
160
161 //====================== Forward declarations =================================
162
163 /// \brief Determines whether the round can start.
164 /// \return True if the round can start, false otherwise.
165 bool Surv_CanRoundStart();
166
167 /// \brief Determines whether the round can end.
168 /// \return True if the round can end, false otherwise.
169 bool Surv_CanRoundEnd();
170
171 /// \brief Called when the round starts.
172 /// \return No return.
173 void Surv_RoundStart();
174
175 /// \brief Returns whether player has been eliminated.
176 /// \param[in] player Player to check.
177 /// \return True if player is eliminated, false otherwise.
178 bool Surv_IsEliminated(entity player);
179
180 /// \brief Updates stats of team count on HUD.
181 /// \return No return.
182 void Surv_UpdateTeamStats();
183
184 /// \brief Updates stats of alive players on HUD.
185 /// \return No return.
186 void Surv_UpdateAliveStats();
187
188 /// \brief Updates defender health on the HUD.
189 /// \return No return.
190 void Surv_UpdateDefenderHealthStat();
191
192 //========================= Free functions ====================================
193
194 void Surv_Initialize()
195 {
196         switch (cvar_string("g_surv_type"))
197         {
198                 case SURVIVAL_TYPE_COOP_VALUE:
199                 {
200                         surv_type = SURVIVAL_TYPE_COOP;
201                         break;
202                 }
203                 case SURVIVAL_TYPE_VERSUS_VALUE:
204                 {
205                         surv_type = SURVIVAL_TYPE_VERSUS;
206                         break;
207                 }
208                 default:
209                 {
210                         error("Invalid survival type.");
211                 }
212         }
213         surv_roundtype = SURVIVAL_ROUND_FIRST;
214         surv_isroundactive = false;
215         surv_roundstarttime = time;
216         surv_timetobeat = autocvar_g_surv_round_timelimit;
217         if (random() < 0.5)
218         {
219                 surv_attackerteam = NUM_TEAM_1;
220                 surv_defenderteam = NUM_TEAM_2;
221                 surv_attackerteambit = SURVIVAL_TEAM_1_BIT;
222                 surv_defenderteambit = SURVIVAL_TEAM_2_BIT;
223         }
224         else
225         {
226                 surv_attackerteam = NUM_TEAM_2;
227                 surv_defenderteam = NUM_TEAM_1;
228                 surv_attackerteambit = SURVIVAL_TEAM_2_BIT;
229                 surv_defenderteambit = SURVIVAL_TEAM_1_BIT;
230         }
231         surv_numattackers = 0;
232         surv_numdefenders = 0;
233         surv_numattackerhumans = 0;
234         surv_numdefenderhumans = 0;
235         surv_numattackersalive = 0;
236         surv_numdefendersalive = 0;
237         surv_autobalance = true;
238         surv_announcefrags = true;
239         surv_allowed_to_spawn = true;
240         precache_all_playermodels("models/ok_player/*.dpm");
241         ScoreRules_basics(SURVIVAL_TEAM_BITS, SFL_SORT_PRIO_PRIMARY, 0, true);
242         ScoreInfo_SetLabel_TeamScore(1, "rounds", SFL_SORT_PRIO_PRIMARY);
243         ScoreRules_basics_end();
244         round_handler_Spawn(Surv_CanRoundStart, Surv_CanRoundEnd, Surv_RoundStart);
245         round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
246         EliminatedPlayers_Init(Surv_IsEliminated);
247         ActivateTeamplay();
248         SetLimits(autocvar_g_surv_point_limit, autocvar_g_surv_point_leadlimit,
249                 autocvar_timelimit_override, -1);
250 }
251
252 /// \brief Returns the name of the template of the given player.
253 /// \param[in] player Player to inspect.
254 /// \return Name of the template of the given player.
255 string Surv_GetPlayerTemplate(entity player)
256 {
257         switch (player.team)
258         {
259                 case surv_attackerteam:
260                 {
261                         switch (player.surv_role)
262                         {
263                                 case SURVIVAL_ROLE_NONE:
264                                 case SURVIVAL_ROLE_PLAYER:
265                                 {
266                                         return "surv_attacker";
267                                 }
268                                 case SURVIVAL_ROLE_CANNON_FODDER:
269                                 {
270                                         return "surv_cannon_fodder";
271                                 }
272                         }
273                 }
274                 case surv_defenderteam:
275                 {
276                         return "surv_defender";
277                 }
278         }
279         return "default";
280 }
281
282 /// \brief Saves the player state. Used to seamlessly swap bots with humans.
283 /// \param[in] player Player to save the state of.
284 /// \return Entity containing the player state.
285 entity Surv_SavePlayerState(entity player)
286 {
287         entity state = spawn();
288         state.origin = player.origin;
289         state.velocity = player.velocity;
290         state.angles = player.angles;
291         state.health = player.health;
292         state.armorvalue = player.armorvalue;
293         state.ammo_shells = player.ammo_shells;
294         state.ammo_nails = player.ammo_nails;
295         state.ammo_rockets = player.ammo_rockets;
296         state.ammo_cells = player.ammo_cells;
297         state.weapons = player.weapons;
298         state.items = player.items;
299         state.superweapons_finished = player.superweapons_finished;
300         return state;
301 }
302
303 /// \brief Restores a saved player state.
304 /// \param[in] player Player to restore the state of.
305 /// \param[in] st State to restore.
306 /// \return No return.
307 void Surv_RestorePlayerState(entity player, entity st)
308 {
309         player.origin = st.origin;
310         player.velocity = st.velocity;
311         player.angles = st.angles;
312         player.health = st.health;
313         player.armorvalue = st.armorvalue;
314         player.ammo_shells = st.ammo_shells;
315         player.ammo_nails = st.ammo_nails;
316         player.ammo_rockets = st.ammo_rockets;
317         player.ammo_cells = st.ammo_cells;
318         player.weapons = st.weapons;
319         player.items = st.items;
320         player.superweapons_finished = st.superweapons_finished;
321 }
322
323 /// \brief Returns the attacker with the lowest score.
324 /// \param[in] bot Whether to search only for bots.
325 /// \return Attacker with the lowest score or NULL if not found.
326 entity Surv_FindLowestAttacker(bool bot)
327 {
328         entity player = NULL;
329         float score = FLOAT_MAX;
330         FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
331         {
332                 if ((it.team == surv_attackerteam) && (it.surv_role ==
333                         SURVIVAL_ROLE_PLAYER))
334                 {
335                         float tempscore = PlayerScore_Get(it, SP_SCORE);
336                         if (tempscore < score)
337                         {
338                                 player = it;
339                                 score = tempscore;
340                         }
341                 }
342         });
343         return player;
344 }
345
346 /// \brief Returns the defender with the lowest score.
347 /// \param[in] bot Whether to search only for bots.
348 /// \param[in] alive Whether to search only for alive players.
349 /// \return Defender with the lowest score or NULL if not found.
350 entity Surv_FindLowestDefender(bool bot, bool alive)
351 {
352         entity player = NULL;
353         float score = FLOAT_MAX;
354         FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
355         {
356                 if ((it.team == surv_defenderteam) && (alive ? !IS_DEAD(it) : true))
357                 {
358                         float tempscore = PlayerScore_Get(it, SP_SCORE);
359                         if (tempscore < score)
360                         {
361                                 player = it;
362                                 score = tempscore;
363                         }
364                 }
365         });
366         return player;
367 }
368
369 /// \brief Returns the cannon fodder.
370 /// \return Cannon fodder or NULL if not found.
371 entity Surv_FindCannonFodder()
372 {
373         FOREACH_CLIENT(IS_BOT_CLIENT(it),
374         {
375                 if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
376                 {
377                         return it;
378                 }
379         });
380         return NULL;
381 }
382
383 /// \brief Changes the number of players in a team.
384 /// \param[in] teamnum Team to adjust.
385 /// \param[in] delta Amount to adjust by.
386 /// \return No return.
387 void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
388 {
389         switch (teamnum)
390         {
391                 case surv_attackerteam:
392                 {
393                         surv_numattackers += delta;
394                         LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
395                                 " was = ", ftos(surv_numattackers - delta));
396                         Surv_UpdateTeamStats();
397                         return;
398                 }
399                 case surv_defenderteam:
400                 {
401                         surv_numdefenders += delta;
402                         LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
403                                 " was = ", ftos(surv_numdefenders - delta));
404                         Surv_UpdateTeamStats();
405                         return;
406                 }
407         }
408 }
409
410 /// \brief Changes the number of alive players in a team.
411 /// \param[in] teamnum Team to adjust.
412 /// \param[in] delta Amount to adjust by.
413 /// \return No return.
414 void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
415 {
416         switch (teamnum)
417         {
418                 case surv_attackerteam:
419                 {
420                         surv_numattackersalive += delta;
421                         LOG_TRACE("Number of alive attackers = ", ftos(
422                                 surv_numattackersalive), " was = ", ftos(surv_numattackersalive
423                                 - delta));
424                         break;
425                 }
426                 case surv_defenderteam:
427                 {
428                         surv_numdefendersalive += delta;
429                         LOG_TRACE("Number of alive defenders = ", ftos(
430                                 surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
431                                 - delta));
432                         break;
433                 }
434         }
435         Surv_UpdateAliveStats();
436         eliminatedPlayers.SendFlags |= 1;
437 }
438
439 /// \brief Sets the player role.
440 /// \param[in,out] player Player to adjust.
441 /// \param[in] role Role to set.
442 /// \return No return.
443 void Surv_SetPlayerRole(entity player, int role)
444 {
445         if (player.surv_role == role)
446         {
447                 return;
448         }
449         player.surv_role = role;
450         switch (role)
451         {
452                 case SURVIVAL_ROLE_NONE:
453                 {
454                         LOG_TRACE(player.netname, " now has no role.");
455                         break;
456                 }
457                 case SURVIVAL_ROLE_PLAYER:
458                 {
459                         LOG_TRACE(player.netname, " is now a player.");
460                         break;
461                 }
462                 case SURVIVAL_ROLE_CANNON_FODDER:
463                 {
464                         LOG_TRACE(player.netname, " is now a cannon fodder.");
465                         break;
466                 }
467         }
468 }
469
470 /// \brief Adds player to team. Handles bookkeeping information.
471 /// \param[in] player Player to add.
472 /// \param[in] teamnum Team to add to.
473 /// \return True on success, false otherwise.
474 bool Surv_AddPlayerToTeam(entity player, int teamnum)
475 {
476         LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
477         switch (teamnum)
478         {
479                 case surv_attackerteam:
480                 {
481                         LOG_TRACE("Attacker team");
482                         if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
483                         {
484                                 LOG_TRACE("Cannon fodder is switching team");
485                                 return true;
486                         }
487                         if (IS_BOT_CLIENT(player))
488                         {
489                                 LOG_TRACE("Client is bot");
490                                 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
491                                 if (surv_numattackers < autocvar_g_surv_team_size)
492                                 {
493                                         Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
494                                         Surv_ChangeNumberOfPlayers(teamnum, +1);
495                                         return true;
496                                 }
497                                 Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
498                                 return true;
499                         }
500                         LOG_TRACE("Client is not a bot");
501                         LOG_TRACE("Attackers = ", ftos(surv_numattackers));
502                         if (surv_numattackers >= autocvar_g_surv_team_size)
503                         {
504                                 LOG_TRACE("Removing bot");
505                                 // Remove bot to make space for human.
506                                 entity bot = Surv_FindLowestAttacker(true);
507                                 if (bot == NULL)
508                                 {
509                                         LOG_TRACE("No valid bot to remove");
510                                         // No space in team, denying team change.
511                                         TRANSMUTE(Spectator, player);
512                                         return false;
513                                 }
514                                 LOG_TRACE("Changing ", bot.netname,
515                                         " from attacker to cannon fodder.");
516                                 Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
517                                 if (!IS_DEAD(bot))
518                                 {
519                                         Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
520                                 }
521                                 Surv_ChangeNumberOfPlayers(teamnum, -1);
522                                 LOG_TRACE("Removed bot");
523                         }
524                         Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
525                         Surv_ChangeNumberOfPlayers(teamnum, +1);
526                         ++surv_numattackerhumans;                       
527                         LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
528                         if ((surv_autobalance == false) || (surv_numattackers -
529                                 surv_numdefenders) < 2)
530                         {
531                                 return true;
532                         }
533                         entity lowestplayer = Surv_FindLowestAttacker(true);
534                         if (lowestplayer != NULL)
535                         {
536                                 bool savedautobalance = surv_autobalance;
537                                 surv_autobalance = false;
538                                 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
539                                 surv_autobalance = savedautobalance;
540                                 return true;
541                         }
542                         lowestplayer = Surv_FindLowestAttacker(false);
543                         if (lowestplayer != NULL)
544                         {
545                                 bool savedautobalance = surv_autobalance;
546                                 surv_autobalance = false;
547                                 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
548                                 surv_autobalance = savedautobalance;
549                         }
550                         return true;
551                 }
552                 case surv_defenderteam:
553                 {
554                         LOG_TRACE("Defender team");
555                         if (IS_BOT_CLIENT(player))
556                         {
557                                 LOG_TRACE("Client is bot");
558                                 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
559                                 if (surv_numdefenders < autocvar_g_surv_team_size)
560                                 {
561                                         Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
562                                         Surv_ChangeNumberOfPlayers(teamnum, +1);
563                                         return true;
564                                 }
565                                 LOG_TRACE("No space for defender, switching to attacker");
566                                 SetPlayerTeamSimple(player, surv_attackerteam);
567                                 return false;
568                         }
569                         LOG_TRACE("Client is not a bot");
570                         LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
571                         if (surv_numdefenders >= autocvar_g_surv_team_size)
572                         {
573                                 LOG_TRACE("Removing bot");
574                                 // Remove bot to make space for human.
575                                 entity bot = Surv_FindLowestDefender(true, true);
576                                 if (bot == NULL)
577                                 {
578                                         bot = Surv_FindLowestDefender(true, false);
579                                 }
580                                 if (bot == NULL)
581                                 {
582                                         LOG_TRACE("No valid bot to remove");
583                                         // No space in team, denying team change.
584                                         TRANSMUTE(Spectator, player);
585                                         return false;
586                                 }
587                                 LOG_TRACE("Changing ", bot.netname,
588                                         " from defender to cannon fodder.");
589                                 if (!IS_DEAD(bot))
590                                 {
591                                         player.surv_savedplayerstate = Surv_SavePlayerState(bot);
592                                 }
593                                 bool savedautobalance = surv_autobalance;
594                                 surv_autobalance = false;
595                                 surv_announcefrags = false;
596                                 SetPlayerTeamSimple(bot, surv_attackerteam);
597                                 surv_autobalance = savedautobalance;
598                                 surv_announcefrags = true;
599                                 LOG_TRACE("Removed bot");
600                         }
601                         Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
602                         Surv_ChangeNumberOfPlayers(teamnum, +1);
603                         ++surv_numdefenderhumans;
604                         LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
605                         if ((surv_autobalance == false) || (surv_numdefenders -
606                                 surv_numattackers) < 2)
607                         {
608                                 return true;
609                         }
610                         entity lowestplayer = Surv_FindLowestDefender(true, false);
611                         if (lowestplayer != NULL)
612                         {
613                                 bool savedautobalance = surv_autobalance;
614                                 surv_autobalance = false;
615                                 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
616                                 surv_autobalance = savedautobalance;
617                                 return true;
618                         }
619                         lowestplayer = Surv_FindLowestDefender(false, false);
620                         if (lowestplayer != NULL)
621                         {
622                                 bool savedautobalance = surv_autobalance;
623                                 surv_autobalance = false;
624                                 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
625                                 surv_autobalance = savedautobalance;
626                         }
627                         return true;
628                 }
629                 case -1:
630                 {
631                         LOG_TRACE("Spectator team");
632                         player.surv_role = SURVIVAL_ROLE_NONE;                  
633                         return false;
634                 }
635         }
636         LOG_TRACE("Invalid team");
637         player.surv_role = SURVIVAL_ROLE_NONE;
638         return false;
639 }
640
641 /// \brief Removes player from team. Handles bookkeeping information.
642 /// \param[in] player Player to remove.
643 /// \param[in] Team to remove from.
644 /// \return No return.
645 void Surv_RemovePlayerFromTeam(entity player, int teamnum)
646 {
647         LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
648         switch (teamnum)
649         {
650                 case surv_attackerteam:
651                 {
652                         LOG_TRACE("Attacker team");
653                         if (player.surv_role == SURVIVAL_ROLE_NONE)
654                         {
655                                 string message = strcat("RemovePlayerFromTeam: ",
656                                         player.netname, " has invalid role.");
657                                 DebugPrintToChatAll(message);
658                                 return;
659                         }
660                         if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
661                         {
662                                 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
663                                 return;
664                         }
665                         Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
666                         Surv_ChangeNumberOfPlayers(teamnum, -1);
667                         if (!IS_BOT_CLIENT(player))
668                         {
669                                 --surv_numattackerhumans;
670                         }
671                         if ((surv_autobalance == false) || (surv_numattackers >=
672                                 surv_numdefenders))
673                         {
674                                 return;
675                         }
676                         // Add bot to keep teams balanced.
677                         entity lowestplayer = Surv_FindCannonFodder();
678                         if (lowestplayer != NULL)
679                         {
680                                 LOG_TRACE("Changing ", lowestplayer.netname,
681                                         " from cannon fodder to attacker.");
682                                 Surv_SetPlayerRole(lowestplayer, SURVIVAL_ROLE_PLAYER);
683                                 Surv_ChangeNumberOfPlayers(teamnum, +1);
684                                 if (!IS_DEAD(lowestplayer))
685                                 {
686                                         Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
687                                 }
688                                 return;
689                         }
690                         lowestplayer = Surv_FindLowestDefender(true, false);
691                         if (lowestplayer != NULL)
692                         {
693                                 bool savedautobalance = surv_autobalance;
694                                 surv_autobalance = false;
695                                 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
696                                 surv_autobalance = savedautobalance;
697                                 return;
698                         }
699                         lowestplayer = Surv_FindLowestDefender(false, false);
700                         if (lowestplayer == NULL)
701                         {
702                                 return;
703                         }
704                         bool savedautobalance = surv_autobalance;
705                         surv_autobalance = false;
706                         SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
707                         surv_autobalance = savedautobalance;
708                         return;
709                 }
710                 case surv_defenderteam:
711                 {
712                         LOG_TRACE("Defender team");
713                         if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
714                         {
715                                 // This happens during team switch. We don't need to change
716                                 // anything.
717                                 LOG_TRACE("Cannon fodder. Assuming team switch");
718                                 return;
719                         }
720                         if (player.surv_role != SURVIVAL_ROLE_PLAYER)
721                         {
722                                 string message = strcat("RemovePlayerFromTeam: ",
723                                         player.netname, " has invalid role.");
724                                 DebugPrintToChatAll(message);
725                                 return;
726                         }
727                         Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
728                         Surv_ChangeNumberOfPlayers(teamnum, -1);
729                         if (!IS_BOT_CLIENT(player))
730                         {
731                                 --surv_numdefenderhumans;
732                         }
733                         if ((surv_autobalance == false) || (surv_numdefenders >=
734                                 surv_numattackers))
735                         {
736                                 return;
737                         }
738                         // Add bot to keep teams balanced.
739                         entity lowestplayer = Surv_FindCannonFodder();
740                         if (lowestplayer != NULL)
741                         {
742                                 LOG_TRACE("Changing ", lowestplayer.netname,
743                                         " from cannon fodder to defender.");
744                                 if (!IS_DEAD(player))
745                                 {
746                                         lowestplayer.surv_savedplayerstate =
747                                                 Surv_SavePlayerState(player);
748                                 }
749                                 bool savedautobalance = surv_autobalance;
750                                 surv_autobalance = false;
751                                 surv_announcefrags = false;
752                                 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
753                                 surv_autobalance = savedautobalance;
754                                 surv_announcefrags = true;                                      
755                                 return;
756                         }
757                         lowestplayer = Surv_FindLowestAttacker(true);
758                         if (lowestplayer != NULL)
759                         {
760                                 bool savedautobalance = surv_autobalance;
761                                 surv_autobalance = false;
762                                 surv_announcefrags = false;
763                                 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
764                                 surv_autobalance = savedautobalance;
765                                 surv_announcefrags = true;
766                                 return;
767                         }
768                         lowestplayer = Surv_FindLowestAttacker(false);
769                         if (lowestplayer == NULL)
770                         {
771                                 return;
772                         }
773                         bool savedautobalance = surv_autobalance;
774                         surv_autobalance = false;
775                         surv_announcefrags = false;
776                         SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
777                         surv_autobalance = savedautobalance;
778                         surv_announcefrags = true;
779                         return;
780                 }
781                 case -1:
782                 {
783                         LOG_TRACE("Spectator team");
784                         return;
785                 }
786                 default:
787                 {
788                         LOG_TRACE("Invalid team");
789                         return;
790                 }
791         }
792 }
793
794 /// \brief Updates stats of team count on HUD.
795 /// \return No return.
796 void Surv_UpdateTeamStats()
797 {
798         // Debug stuff
799         if (surv_attackerteam == NUM_TEAM_1)
800         {
801                 yellowalive = surv_numattackers;
802                 pinkalive = surv_numdefenders;
803         }
804         else
805         {
806                 pinkalive = surv_numattackers;
807                 yellowalive = surv_numdefenders;
808         }
809         FOREACH_CLIENT(IS_REAL_CLIENT(it),
810         {
811                 it.yellowalive_stat = yellowalive;
812                 it.pinkalive_stat = pinkalive;
813         });
814 }
815
816 /// \brief Adds player to alive list. Handles bookkeeping information.
817 /// \param[in] player Player to add.
818 /// \param[in] teamnum Team of the player.
819 /// \return No return.
820 void Surv_AddPlayerToAliveList(entity player, int teamnum)
821 {
822         switch (teamnum)
823         {
824                 case surv_attackerteam:
825                 {
826                         if (player.surv_role == SURVIVAL_ROLE_PLAYER)
827                         {
828                                 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
829                         }
830                         return;
831                 }
832                 case surv_defenderteam:
833                 {
834                         Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
835                         return;
836                 }
837         }
838 }
839
840 /// \brief Removes player from alive list. Handles bookkeeping information.
841 /// \param[in] player Player to remove.
842 /// \param[in] teamnum Team of the player.
843 /// \return No return.
844 void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
845 {
846         if (player.surv_attack_sprite)
847         {
848                 WaypointSprite_Kill(player.surv_attack_sprite);
849         }
850         switch (teamnum)
851         {
852                 case surv_attackerteam:
853                 {
854                         if (player.surv_role == SURVIVAL_ROLE_PLAYER)
855                         {
856                                 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
857                         }
858                         return;
859                 }
860                 case surv_defenderteam:
861                 {
862                         if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
863                         {
864                                 // This happens during team switch. We don't need to change
865                                 // anything.
866                                 return;
867                         }
868                         Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
869                         if (warmup_stage || surv_allowed_to_spawn || !surv_announcefrags)
870                         {
871                                 return;
872                         }
873                         switch (surv_numdefendersalive)
874                         {
875                                 case 1:
876                                 {
877                                         sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
878                                                 VOL_BASE, ATTEN_NONE);
879                                         FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
880                                         {
881                                                 if (it.team == surv_defenderteam)
882                                                 {
883                                                         Send_Notification(NOTIF_ONE, it, MSG_CENTER,
884                                                                 CENTER_ALONE);
885                                                         return;
886                                                 }
887                                         });
888                                         return;
889                                 }
890                                 case 2:
891                                 {
892                                         sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
893                                                 VOL_BASE, ATTEN_NONE);
894                                         return;
895                                 }
896                                 case 3:
897                                 {
898                                         sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
899                                                 VOL_BASE, ATTEN_NONE);
900                                         return;
901                                 }
902                         }
903                         return;
904                 }
905         }
906 }
907
908 /// \brief Counts alive players.
909 /// \return No return.
910 /// \note In a perfect world this function shouldn't exist. However, since QC
911 /// code is so bad and spurious mutators can really mess with your code, this
912 /// function is called as a last resort.
913 void Surv_CountAlivePlayers()
914 {
915         int savednumdefenders = surv_numdefendersalive;
916         surv_numattackersalive = 0;
917         surv_numdefendersalive = 0;
918         FOREACH_CLIENT(IS_PLAYER(it),
919         {
920                 switch (it.team)
921                 {
922                         case surv_attackerteam:
923                         {
924                                 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
925                                 {
926                                         ++surv_numattackersalive;
927                                 }
928                                 break;
929                         }
930                         case surv_defenderteam:
931                         {
932                                 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
933                                 {
934                                         ++surv_numdefendersalive;
935                                 }
936                                 break;
937                         }
938                 }
939         });
940         Surv_UpdateAliveStats();
941         eliminatedPlayers.SendFlags |= 1;
942         if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
943                 surv_numdefendersalive))
944         {
945                 return;
946         }
947         switch (surv_numdefendersalive)
948         {
949                 case 1:
950                 {
951                         sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
952                         FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
953                         {
954                                 if (it.team == surv_defenderteam)
955                                 {
956                                         Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
957                                         return;
958                                 }
959                         });
960                         return;
961                 }
962                 case 2:
963                 {
964                         sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
965                                 ATTEN_NONE);
966                         return;
967                 }
968                 case 3:
969                 {
970                         sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
971                                 ATTEN_NONE);
972                         return;
973                 }
974         }
975 }
976
977 /// \brief Updates stats of alive players on HUD.
978 /// \return No return.
979 void Surv_UpdateAliveStats()
980 {
981         // Debug stuff
982         if (surv_attackerteam == NUM_TEAM_1)
983         {
984                 redalive = surv_numattackersalive;
985                 bluealive = surv_numdefendersalive;
986         }
987         else
988         {
989                 bluealive = surv_numattackersalive;
990                 redalive = surv_numdefendersalive;
991         }
992         FOREACH_CLIENT(IS_REAL_CLIENT(it),
993         {
994                 it.surv_defenders_alive_stat = surv_numdefendersalive;
995                 it.redalive_stat = redalive;
996                 it.bluealive_stat = bluealive;
997         });
998         Surv_UpdateDefenderHealthStat();
999 }
1000
1001 /// \brief Updates defender health on the HUD.
1002 /// \return No return.
1003 void Surv_UpdateDefenderHealthStat()
1004 {
1005         float maxhealth;
1006         float totalhealth = 0;
1007         if (autocvar_g_instagib == 1)
1008         {
1009                 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1010                         "surv_defender", "start_armor") + 1);
1011                 FOREACH_CLIENT(IS_PLAYER(it),
1012                 {
1013                         if (it.team == surv_defenderteam)
1014                         {
1015                                 totalhealth += it.armorvalue + 1;
1016                         }
1017                 });
1018         }
1019         else
1020         {
1021                 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1022                         "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
1023                         "surv_defender", "start_armor"));
1024                 FOREACH_CLIENT(IS_PLAYER(it),
1025                 {
1026                         if (it.team == surv_defenderteam)
1027                         {
1028                                 totalhealth += it.health;
1029                                 totalhealth += it.armorvalue;
1030                         }
1031                 });
1032         }
1033         float healthratio;
1034         if (maxhealth == 0)
1035         {
1036                 healthratio = 0;
1037         }
1038         else
1039         {
1040                 healthratio = totalhealth / maxhealth;
1041         }
1042         FOREACH_CLIENT(IS_REAL_CLIENT(it),
1043         {
1044                 it.surv_defender_health_stat = healthratio;
1045         });
1046 }
1047
1048 /// \brief Returns whether the player can spawn.
1049 /// \param[in] player Player to check.
1050 /// \return True if the player can spawn, false otherwise.
1051 bool Surv_CanPlayerSpawn(entity player)
1052 {
1053         if ((player.team == surv_attackerteam) ||
1054                 (player.surv_savedplayerstate != NULL))
1055         {
1056                 return true;
1057         }
1058         return surv_allowed_to_spawn;
1059 }
1060
1061 /// \brief Switches the round type.
1062 /// \return No return.
1063 void Surv_SwitchRoundType()
1064 {
1065         switch (surv_roundtype)
1066         {
1067                 case SURVIVAL_ROUND_FIRST:
1068                 {
1069                         surv_roundtype = SURVIVAL_ROUND_SECOND;
1070                         return;
1071                 }
1072                 case SURVIVAL_ROUND_SECOND:
1073                 {
1074                         surv_roundtype = SURVIVAL_ROUND_FIRST;
1075                         return;
1076                 }
1077         }
1078 }
1079
1080 /// \brief Cleans up the mess after the round has finished.
1081 /// \return No return.
1082 void Surv_RoundCleanup()
1083 {
1084         surv_allowed_to_spawn = false;
1085         surv_isroundactive = false;
1086         game_stopped = true;
1087         FOREACH_CLIENT(true,
1088         {
1089                 if (it.surv_attack_sprite)
1090                 {
1091                         WaypointSprite_Kill(it.surv_attack_sprite);
1092                 }
1093                 if (it.surv_savedplayerstate)
1094                 {
1095                         delete(it.surv_savedplayerstate);
1096                         it.surv_savedplayerstate = NULL;
1097                 }
1098         });
1099         if (surv_type == SURVIVAL_TYPE_VERSUS)
1100         {
1101                 Surv_SwitchRoundType();
1102                 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
1103                 return;
1104         }
1105         round_handler_Init(5, autocvar_g_surv_warmup,
1106                 autocvar_g_surv_round_timelimit);
1107 }
1108
1109 /// \brief Swaps attacker and defender teams.
1110 /// \return No return.
1111 void Surv_SwapTeams()
1112 {
1113         int temp = surv_attackerteam;
1114         surv_attackerteam = surv_defenderteam;
1115         surv_defenderteam = temp;
1116         temp = surv_attackerteambit;
1117         surv_attackerteambit = surv_defenderteambit;
1118         surv_defenderteambit = temp;
1119         temp = surv_numattackers;
1120         surv_numattackers = surv_numdefenders;
1121         surv_numdefenders = temp;
1122         temp = surv_numattackerhumans;
1123         surv_numattackerhumans = surv_numdefenderhumans;
1124         surv_numdefenderhumans = temp;
1125         FOREACH_CLIENT(true,
1126         {
1127                 if ((it.team == surv_defenderteam) && (it.surv_role ==
1128                         SURVIVAL_ROLE_CANNON_FODDER))
1129                 {
1130                         SetPlayerTeamSimple(it, surv_attackerteam);
1131                 }
1132         });
1133         FOREACH_CLIENT(IS_REAL_CLIENT(it),
1134         {
1135                 it.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1136         });
1137 }
1138
1139 /// \brief Forces the overkill model for specific player.
1140 /// \param[in,out] player Player to force the model of.
1141 /// \return No return.
1142 void Surv_ForceOverkillPlayerModel(entity player)
1143 {
1144         switch (player.team)
1145         {
1146                 case NUM_TEAM_1:
1147                 {
1148                         switch (floor(random() * 4))
1149                         {
1150                                 case 0:
1151                                 {
1152                                         player.surv_playermodel = "models/ok_player/okrobot1.dpm";
1153                                         return;
1154                                 }
1155                                 case 1:
1156                                 {
1157                                         player.surv_playermodel = "models/ok_player/okrobot2.dpm";
1158                                         return;
1159                                 }
1160                                 case 2:
1161                                 {
1162                                         player.surv_playermodel = "models/ok_player/okrobot3.dpm";
1163                                         return;
1164                                 }
1165                                 case 3:
1166                                 {
1167                                         player.surv_playermodel = "models/ok_player/okrobot4.dpm";
1168                                         return;
1169                                 }
1170                         }
1171                         return;
1172                 }
1173                 case NUM_TEAM_2:
1174                 {
1175                         switch (floor(random() * 4))
1176                         {
1177                                 case 0:
1178                                 {
1179                                         player.surv_playermodel = "models/ok_player/okmale1.dpm";
1180                                         return;
1181                                 }
1182                                 case 1:
1183                                 {
1184                                         player.surv_playermodel = "models/ok_player/okmale2.dpm";
1185                                         return;
1186                                 }
1187                                 case 2:
1188                                 {
1189                                         player.surv_playermodel = "models/ok_player/okmale3.dpm";
1190                                         return;
1191                                 }
1192                                 case 3:
1193                                 {
1194                                         player.surv_playermodel = "models/ok_player/okmale4.dpm";
1195                                         return;
1196                                 }
1197                         }
1198                         return;
1199                 }
1200         }
1201 }
1202
1203 /// \brief Determines the player model to the one configured for the gamemode.
1204 /// \param[in,out] player Player to determine the model of.
1205 /// \return No return.
1206 void Surv_DeterminePlayerModel(entity player)
1207 {
1208         switch (player.team)
1209         {
1210                 case surv_attackerteam:
1211                 {
1212                         switch (player.surv_role)
1213                         {
1214                                 case SURVIVAL_ROLE_PLAYER:
1215                                 {
1216                                         if (!autocvar_g_surv_attacker_force_overkill_models)
1217                                         {
1218                                                 player.surv_playermodel = player.surv_savedplayermodel;
1219                                                 return;
1220                                         }
1221                                         Surv_ForceOverkillPlayerModel(player);
1222                                         return;
1223                                 }
1224                                 case SURVIVAL_ROLE_CANNON_FODDER:
1225                                 {
1226                                         if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
1227                                         {
1228                                                 player.surv_playermodel = player.surv_savedplayermodel;
1229                                                 return;
1230                                         }
1231                                         Surv_ForceOverkillPlayerModel(player);
1232                                         return;
1233                                 }
1234                         }
1235                 }
1236                 case surv_defenderteam:
1237                 {
1238                         if (!autocvar_g_surv_defender_force_overkill_models)
1239                         {
1240                                 player.surv_playermodel = player.surv_savedplayermodel;
1241                                 return;
1242                         }
1243                         Surv_ForceOverkillPlayerModel(player);
1244                         return;
1245                 }
1246         }
1247 }
1248
1249 /// \brief Setups a waypoint sprite used to track defenders.
1250 /// \param[in] player Player to attach sprite too.
1251 /// \return No return.
1252 void Surv_SetupWaypointSprite(entity player)
1253 {
1254         WaypointSprite_Spawn(WP_AssaultDestroy, 0, 0, player, '0 0 64', NULL,
1255                 surv_attackerteam, player, surv_attack_sprite, false,
1256                 RADARICON_OBJECTIVE);
1257         if (autocvar_g_instagib == 1)
1258         {
1259                 WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite,
1260                         PlayerTemplate_GetFloatValue("surv_defender", "start_armor") + 1);
1261                 WaypointSprite_UpdateHealth(player.surv_attack_sprite,
1262                         player.armorvalue + 1);
1263                 return;
1264         }
1265         WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite,
1266                 PlayerTemplate_GetFloatValue("surv_defender", "start_health") +
1267                 PlayerTemplate_GetFloatValue("surv_defender", "start_armor"));
1268         WaypointSprite_UpdateHealth(player.surv_attack_sprite, player.health +
1269                 player.armorvalue);
1270 }
1271
1272 //=============================== Callbacks ===================================
1273
1274 bool Surv_CanRoundStart()
1275 {
1276         return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
1277 }
1278
1279 bool Surv_CanRoundEnd()
1280 {
1281         if (warmup_stage)
1282         {
1283                 return false;
1284         }
1285         if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
1286                 time <= 0))
1287         {
1288                 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1289                 {
1290                         surv_timetobeat = time - surv_roundstarttime;
1291                         Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1292                                 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1293                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1294                                 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1295                         Surv_RoundCleanup();
1296                         return true;
1297                 }
1298                 surv_timetobeat = autocvar_g_surv_round_timelimit;
1299                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1300                         CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1301                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1302                         INFO_SURVIVAL_DEFENDERS_SURVIVED);
1303                 switch (surv_defenderteam)
1304                 {
1305                         case NUM_TEAM_1:
1306                         {
1307                                 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1308                                         ATTEN_NONE);
1309                                 break;
1310                         }
1311                         case NUM_TEAM_2:
1312                         {
1313                                 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1314                                         ATTEN_NONE);
1315                                 break;
1316                         }
1317                 }
1318                 TeamScore_AddToTeam(surv_defenderteam, 1, 1);
1319                 Surv_RoundCleanup();
1320                 return true;
1321         }
1322         if (surv_numdefendersalive > 0)
1323         {
1324                 return false;
1325         }
1326         if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1327         {
1328                 surv_timetobeat = time - surv_roundstarttime;
1329                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1330                         CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1331                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1332                         INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1333                 Surv_RoundCleanup();
1334                 return true;
1335         }
1336         surv_timetobeat = autocvar_g_surv_round_timelimit;
1337         Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1338                 CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
1339         Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1340                 INFO_SURVIVAL_DEFENDERS_ELIMINATED);
1341         switch (surv_attackerteam)
1342         {
1343                 case NUM_TEAM_1:
1344                 {
1345                         sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1346                                 ATTEN_NONE);
1347                         break;
1348                 }
1349                 case NUM_TEAM_2:
1350                 {
1351                         sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1352                                 ATTEN_NONE);
1353                         break;
1354                 }
1355         }
1356         TeamScore_AddToTeam(surv_attackerteam, 1, 1);
1357         Surv_RoundCleanup();
1358         return true;
1359 }
1360
1361 void Surv_RoundStart()
1362 {
1363         if (warmup_stage)
1364         {
1365                 surv_allowed_to_spawn = true;
1366                 return;
1367         }
1368         surv_isroundactive = true;
1369         surv_roundstarttime = time;
1370         surv_allowed_to_spawn = false;
1371         switch (surv_roundtype)
1372         {
1373                 case SURVIVAL_ROUND_FIRST:
1374                 {
1375                         FOREACH_CLIENT(IS_PLAYER(it),
1376                         {
1377                                 if (it.team == surv_attackerteam)
1378                                 {
1379                                         Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1380                                                 CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
1381                                         Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1382                                                 INFO_SURVIVAL_1ST_ROUND_ATTACKER);
1383                                         break;
1384                                 }
1385                         });
1386                         FOREACH_CLIENT(IS_PLAYER(it),
1387                         {
1388                                 if (it.team == surv_defenderteam)
1389                                 {
1390                                         if (surv_type == SURVIVAL_TYPE_COOP)
1391                                         {
1392                                                 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1393                                                         CENTER_SURVIVAL_COOP_DEFENDER);
1394                                                 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1395                                                         INFO_SURVIVAL_COOP_DEFENDER);
1396                                                 break;
1397                                         }
1398                                         Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1399                                                 CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
1400                                         Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1401                                                 INFO_SURVIVAL_1ST_ROUND_DEFENDER);
1402                                         break;
1403                                 }
1404                         });
1405                         break;
1406                 }
1407                 case SURVIVAL_ROUND_SECOND:
1408                 {
1409                         FOREACH_CLIENT(IS_PLAYER(it),
1410                         {
1411                                 if (it.team == surv_attackerteam)
1412                                 {
1413                                         Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1414                                                 CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1415                                         Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1416                                                 INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1417                                         break;
1418                                 }
1419                         });
1420                         FOREACH_CLIENT(IS_PLAYER(it),
1421                         {
1422                                 if (it.team == surv_defenderteam)
1423                                 {
1424                                         Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1425                                                 CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1426                                         Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1427                                                 INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1428                                         break;
1429                                 }
1430                         });
1431                         break;
1432                 }
1433         }
1434         if (autocvar_g_surv_stealth)
1435         {
1436                 return;
1437         }
1438         FOREACH_CLIENT(IS_PLAYER(it),
1439         {
1440                 switch (it.team)
1441                 {
1442                         case surv_defenderteam:
1443                         {
1444                                 if (it.surv_role == SURVIVAL_ROLE_PLAYER)
1445                                 {
1446                                         Surv_SetupWaypointSprite(it);
1447                                 }
1448                                 break;
1449                         }
1450                 }
1451         });
1452 }
1453
1454 bool Surv_IsEliminated(entity player)
1455 {
1456         switch (player.surv_state)
1457         {
1458                 case SURVIVAL_STATE_NOT_PLAYING:
1459                 {
1460                         return true;
1461                 }
1462                 case SURVIVAL_STATE_PLAYING:
1463                 {
1464                         if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
1465                         {
1466                                 // A hack until proper scoreboard is done.
1467                                 return true;
1468                         }
1469                         if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
1470                                 IS_OBSERVER(player)))
1471                         {
1472                                 return true;
1473                         }
1474                         return false;
1475                 }
1476         }
1477         // Should never reach here
1478         return true;
1479 }
1480
1481 //============================= Hooks ========================================
1482
1483 /// \brief Hook that is called to determine general rules of the game. 
1484 MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
1485 {
1486         surv_warmup = warmup_stage;
1487 }
1488
1489 /// \brief Hook that is called to determine if there is a weapon arena.
1490 MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
1491 {
1492         // Removing any weapon arena.
1493         M_ARGV(0, string) = "off";
1494 }
1495
1496 /// \brief Hook that is called to determine start items of all players.
1497 MUTATOR_HOOKFUNCTION(surv, SetStartItems)
1498 {
1499         if (autocvar_g_instagib == 1)
1500         {
1501                 return;
1502         }
1503         start_weapons = WEPSET(Null);
1504         warmup_start_weapons = WEPSET(Null);
1505 }
1506
1507 /// \brief Hook that is called on every frame.
1508 MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
1509 {
1510         if (game_stopped || !surv_isroundactive)
1511         {
1512                 return;
1513         }
1514         float roundtime = 0;
1515         switch (surv_roundtype)
1516         {
1517                 case SURVIVAL_ROUND_FIRST:
1518                 {
1519                         roundtime = time - surv_roundstarttime;
1520                         break;
1521                 }
1522                 case SURVIVAL_ROUND_SECOND:
1523                 {
1524                         roundtime = round_handler_GetEndTime() - time;
1525                         break;
1526                 }
1527         }
1528         FOREACH_CLIENT(IS_REAL_CLIENT(it),
1529         {
1530                 it.surv_round_time_stat = roundtime;
1531         });
1532 }
1533
1534 /// \brief Hook that determines which team player can join. This is called
1535 /// before ClientConnect.
1536 MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
1537 {
1538         entity player = M_ARGV(2, entity);
1539         LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
1540         if (player == NULL)
1541         {
1542                 return SURVIVAL_TEAM_BITS;
1543         }
1544         if (IS_BOT_CLIENT(player))
1545         {
1546                 int teambits = surv_attackerteambit;
1547                 if ((player.team == surv_defenderteam) || (surv_numdefenders <
1548                         autocvar_g_surv_team_size))
1549                 {
1550                         teambits |= surv_defenderteambit;
1551                 }
1552                 M_ARGV(0, float) = teambits;
1553                 return;
1554         }
1555         if (surv_type == SURVIVAL_TYPE_COOP)
1556         {
1557                 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1558                 {
1559                         M_ARGV(0, float) = surv_defenderteambit;
1560                         return;
1561                 }
1562                 M_ARGV(0, float) = 0;
1563                 return;
1564         }
1565         int teambits = 0;
1566         if (surv_numattackerhumans < autocvar_g_surv_team_size)
1567         {
1568                 LOG_TRACE("Player can join attackers");
1569                 teambits |= surv_attackerteambit;
1570         }
1571         if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1572         {
1573                 LOG_TRACE("Player can join defenders");
1574                 teambits |= surv_defenderteambit;
1575         }
1576         M_ARGV(0, float) = teambits;
1577         return;
1578 }
1579
1580 /// \brief Hook that override team counts.
1581 MUTATOR_HOOKFUNCTION(surv, GetTeamCounts, CBC_ORDER_EXCLUSIVE)
1582 {
1583         return true;
1584 }
1585
1586 /// \brief Hook that sets the team count.
1587 MUTATOR_HOOKFUNCTION(surv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
1588 {
1589         float teamnum = M_ARGV(0, float);
1590         entity ignore = M_ARGV(1, entity);
1591         switch (teamnum)
1592         {
1593                 case surv_attackerteam:
1594                 {
1595                         M_ARGV(2, float) = surv_numattackers;
1596                         M_ARGV(3, float) = surv_numattackers - surv_numattackerhumans;
1597                         if (ignore.team == surv_attackerteam)
1598                         {
1599                                 --M_ARGV(2, float);
1600                                 if (IS_BOT_CLIENT(ignore))
1601                                 {
1602                                         --M_ARGV(3, float);
1603                                 }
1604                         }
1605                         entity lowestplayer = NULL;
1606                         float lowestplayerscore = FLOAT_MAX;
1607                         entity lowestbot = NULL;
1608                         float lowestbotscore = FLOAT_MAX;
1609                         FOREACH_CLIENT((it.team == surv_attackerteam) && (it.surv_role ==
1610                                 SURVIVAL_ROLE_PLAYER),
1611                         {
1612                                 if (it == ignore)
1613                                 {
1614                                         continue;
1615                                 }
1616                                 if (IS_BOT_CLIENT(it))
1617                                 {
1618                                         float tempscore = PlayerScore_Get(it, SP_SCORE);
1619                                         if (tempscore < lowestbotscore)
1620                                         {
1621                                                 lowestbot = it;
1622                                                 lowestbotscore = tempscore;
1623                                                 continue;
1624                                         }
1625                                 }
1626                                 float tempscore = PlayerScore_Get(it, SP_SCORE);
1627                                 if (tempscore < lowestplayerscore)
1628                                 {
1629                                         lowestplayer = it;
1630                                         lowestplayerscore = tempscore;
1631                                 }
1632                         });
1633                         M_ARGV(4, entity) = lowestplayer;
1634                         M_ARGV(5, entity) = lowestbot;
1635                         break;
1636                 }
1637                 case surv_defenderteam:
1638                 {
1639                         M_ARGV(2, float) = surv_numdefenders;
1640                         M_ARGV(3, float) = surv_numdefenders - surv_numdefenderhumans;
1641                         if (ignore.team == surv_defenderteam)
1642                         {
1643                                 --M_ARGV(2, float);
1644                                 if (IS_BOT_CLIENT(ignore))
1645                                 {
1646                                         --M_ARGV(3, float);
1647                                 }
1648                         }
1649                         entity lowestplayer = NULL;
1650                         float lowestplayerscore = FLOAT_MAX;
1651                         entity lowestbot = NULL;
1652                         float lowestbotscore = FLOAT_MAX;
1653                         FOREACH_CLIENT((it.team == surv_defenderteam),
1654                         {
1655                                 if (it == ignore)
1656                                 {
1657                                         continue;
1658                                 }
1659                                 if (IS_BOT_CLIENT(it))
1660                                 {
1661                                         float tempscore = PlayerScore_Get(it, SP_SCORE);
1662                                         if (tempscore < lowestbotscore)
1663                                         {
1664                                                 lowestbot = it;
1665                                                 lowestbotscore = tempscore;
1666                                                 continue;
1667                                         }
1668                                 }
1669                                 float tempscore = PlayerScore_Get(it, SP_SCORE);
1670                                 if (tempscore < lowestplayerscore)
1671                                 {
1672                                         lowestplayer = it;
1673                                         lowestplayerscore = tempscore;
1674                                 }
1675                         });
1676                         M_ARGV(4, entity) = lowestplayer;
1677                         M_ARGV(5, entity) = lowestbot;
1678                         break;
1679                 }
1680         }
1681         return true;
1682 }
1683
1684 /// \brief Hook that determines the best teams for the player to join.
1685 MUTATOR_HOOKFUNCTION(surv, FindBestTeams, CBC_ORDER_EXCLUSIVE)
1686 {
1687         if (surv_type == SURVIVAL_TYPE_COOP)
1688         {
1689                 return false;
1690         }
1691         entity player = M_ARGV(0, entity);
1692         if (IS_BOT_CLIENT(player))
1693         {
1694                 return false;
1695         }
1696         int numattackerhumans = surv_numattackerhumans;
1697         int numdefenderhumans = surv_numdefenderhumans;
1698         if (player.team == surv_attackerteam)
1699         {
1700                 --numattackerhumans;
1701         }
1702         else if (player.team == surv_defenderteam)
1703         {
1704                 --numdefenderhumans;
1705         }
1706         if (numattackerhumans < numdefenderhumans)
1707         {
1708                 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_attackerteam) - 1);
1709                 return true;
1710         }
1711         if (numattackerhumans > numdefenderhumans)
1712         {
1713                 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_defenderteam) - 1);
1714                 return true;
1715         }
1716         M_ARGV(1, float) = SURVIVAL_TEAM_BITS;
1717         return true;
1718 }
1719
1720 /// \brief Hook that is called when player has changed the team.
1721 MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
1722 {
1723         entity player = M_ARGV(0, entity);
1724         int oldteam = M_ARGV(1, float);
1725         int newteam = M_ARGV(2, float);
1726         string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
1727                 ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
1728         LOG_TRACE(message);
1729         DebugPrintToChatAll(message);
1730         if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1731         {
1732                 Surv_RemovePlayerFromAliveList(player, oldteam);
1733         }
1734         Surv_RemovePlayerFromTeam(player, oldteam);
1735         if (Surv_AddPlayerToTeam(player, newteam) == false)
1736         {
1737                 return;
1738         }
1739         //Surv_CountAlivePlayers();
1740         if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1741         {
1742                 Surv_AddPlayerToAliveList(player, newteam);
1743         }
1744 }
1745
1746 /// \brief Hook that is called when player is about to be killed when changing
1747 /// teams.
1748 MUTATOR_HOOKFUNCTION(surv, Player_ChangeTeamKill)
1749 {
1750         entity player = M_ARGV(0, entity);
1751         if (player.team != surv_defenderteam)
1752         {
1753                 return false;
1754         }
1755         if (player.surv_savedplayerstate == NULL)
1756         {
1757                 return false;
1758         }
1759         Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1760         delete(player.surv_savedplayerstate);
1761         player.surv_savedplayerstate = NULL;
1762         return true;
1763 }
1764
1765 /// \brief Hook that is called when player is about to be killed as a result of
1766 /// the kill command or changing teams.
1767 MUTATOR_HOOKFUNCTION(surv, ClientKill_Now)
1768 {
1769         entity player = M_ARGV(0, entity);
1770         if (player.team == surv_defenderteam)
1771         {
1772                 // Deny suicide.
1773                 return true;
1774         }
1775 }
1776
1777 /// \brief Hook that is called when player connects to the server.
1778 MUTATOR_HOOKFUNCTION(surv, ClientConnect)
1779 {
1780         entity player = M_ARGV(0, entity);
1781         LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
1782         player.surv_savedplayermodel = player.playermodel;
1783         if (IS_REAL_CLIENT(player))
1784         {
1785                 player.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1786                 player.surv_defenders_alive_stat = surv_numdefendersalive;
1787                 player.redalive_stat = redalive;
1788                 player.bluealive_stat = bluealive;
1789                 player.yellowalive_stat = yellowalive;
1790                 player.pinkalive_stat = pinkalive;
1791         }
1792         if (player.surv_role == SURVIVAL_ROLE_NONE)
1793         {
1794                 Surv_AddPlayerToTeam(player, player.team);
1795         }
1796         return true;
1797 }
1798
1799 /// \brief Hook that is called when player disconnects from the server.
1800 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
1801 {
1802     entity player = M_ARGV(0, entity);
1803         if (!IS_DEAD(player))
1804         {
1805                 Surv_RemovePlayerFromAliveList(player, player.team);
1806         }
1807         Surv_RemovePlayerFromTeam(player, player.team);
1808         //Surv_CountAlivePlayers();
1809 }
1810
1811 /// \brief Hook that determines whether player can spawn. It is not called for
1812 /// players who have joined the team and are dead.
1813 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
1814 {
1815         entity player = M_ARGV(0, entity);
1816         LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
1817         if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1818         {
1819                 return false;
1820         }
1821         return !Surv_CanPlayerSpawn(player);
1822 }
1823
1824 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
1825 {
1826         entity player = M_ARGV(0, entity);
1827         LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
1828         if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
1829         {
1830                 LOG_TRACE("Transmuting to observer");
1831                 TRANSMUTE(Observer, player);
1832         }
1833 }
1834
1835 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
1836 {
1837         entity player = M_ARGV(0, entity);
1838         LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
1839         if (player.killindicator_teamchange == -2) // player wants to spectate
1840         {
1841                 LOG_TRACE("killindicator_teamchange == -2");
1842                 player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
1843         }
1844         if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1845         {
1846                 return false;  // allow team reset
1847         }
1848         return true;  // prevent team reset
1849 }
1850
1851 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
1852 {
1853         LOG_TRACE("Survival: reset_map_global");
1854         surv_allowed_to_spawn = true;
1855         if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1856         {
1857                 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1858                 {
1859                         it.surv_round_time_stat = 0;
1860                 });
1861         }
1862         return true;
1863 }
1864
1865 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
1866 {
1867         LOG_TRACE("Survival: reset_map_players");
1868         surv_numattackersalive = 0;
1869         surv_numdefendersalive = 0;
1870         if (surv_warmup)
1871         {
1872                 surv_warmup = false;
1873         }
1874         else if (surv_type == SURVIVAL_TYPE_VERSUS)
1875         {
1876                 Surv_SwapTeams();
1877         }
1878         FOREACH_CLIENT(true,
1879         {
1880                 it.killcount = 0;
1881                 if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
1882                 {
1883                         it.team = -1;
1884                         it.surv_state = SURVIVAL_STATE_PLAYING;
1885                 }
1886                 if (it.surv_state == SURVIVAL_STATE_PLAYING)
1887                 {
1888                         TRANSMUTE(Player, it);
1889                         it.surv_state = SURVIVAL_STATE_PLAYING;
1890                         PutClientInServer(it);
1891                 }
1892         });
1893         bot_relinkplayerlist();
1894         return true;
1895 }
1896
1897 /// \brief Hook that is called when player spawns.
1898 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
1899 {
1900         entity player = M_ARGV(0, entity);
1901         LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
1902         player.surv_state = SURVIVAL_STATE_PLAYING;
1903         Surv_DeterminePlayerModel(player);
1904         if (player.surv_savedplayerstate != NULL)
1905         {
1906                 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1907                 delete(player.surv_savedplayerstate);
1908                 player.surv_savedplayerstate = NULL;
1909         }
1910         else
1911         {
1912                 PlayerTemplate_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
1913         }
1914         switch (player.team)
1915         {
1916                 case surv_attackerteam:
1917                 {
1918                         if (player.surv_role == SURVIVAL_ROLE_PLAYER)
1919                         {
1920                                 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1921                                         CENTER_ASSAULT_ATTACKING);
1922                         }
1923                         break;
1924                 }
1925                 case surv_defenderteam:
1926                 {
1927                         Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1928                                 CENTER_ASSAULT_DEFENDING);
1929                         break;
1930                 }
1931         }
1932         //Surv_CountAlivePlayers();
1933         Surv_AddPlayerToAliveList(player, player.team);
1934 }
1935
1936 /// \brief UGLY HACK. This is called every frame to keep player model correct.
1937 MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
1938 {
1939         entity player = M_ARGV(2, entity);
1940         M_ARGV(0, string) = player.surv_playermodel;
1941 }
1942
1943 /// \brief Hook that is called every frame to determine how player health should
1944 /// regenerate.
1945 MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
1946 {
1947         entity player = M_ARGV(0, entity);
1948         if (player.team == surv_defenderteam)
1949         {
1950                 return true;
1951         }
1952         return false;
1953 }
1954
1955 /// \brief Hook that is called to determine if balance messages will appear.
1956 MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
1957 {
1958         return true;
1959 }
1960
1961 /// \brief Hook that is called when player touches an item.
1962 MUTATOR_HOOKFUNCTION(surv, ItemTouch)
1963 {
1964         entity item = M_ARGV(0, entity);
1965         entity player = M_ARGV(1, entity);
1966         switch (player.team)
1967         {
1968                 case surv_attackerteam:
1969                 {
1970                         return PlayerTemplate_ItemTouch(player, item,
1971                                 Surv_GetPlayerTemplate(player));
1972                 }
1973                 case surv_defenderteam:
1974                 {
1975                         switch (item.classname)
1976                         {
1977                                 case "item_strength":
1978                                 {
1979                                         W_GiveWeapon(player, WEP_HMG.m_id);
1980                                         player.superweapons_finished = max(
1981                                                 player.superweapons_finished, time) +
1982                                                 autocvar_g_balance_superweapons_time;
1983                                         Item_ScheduleRespawn(item);
1984                                         sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
1985                                                 ATTEN_NORM);
1986                                         return MUT_ITEMTOUCH_RETURN;
1987                                 }
1988                                 case "item_invincible":
1989                                 {
1990                                         W_GiveWeapon(player, WEP_RPC.m_id);
1991                                         player.superweapons_finished = max(
1992                                                 player.superweapons_finished, time) +
1993                                                 autocvar_g_balance_superweapons_time;
1994                                         Item_ScheduleRespawn(item);
1995                                         sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
1996                                         return MUT_ITEMTOUCH_RETURN;
1997                                 }
1998                                 default:
1999                                 {
2000                                         return PlayerTemplate_ItemTouch(player, item,
2001                                                 Surv_GetPlayerTemplate(player));
2002                                 }
2003                         }
2004                         DebugPrintToChat(player, item.classname);
2005                         return MUT_ITEMTOUCH_RETURN;
2006                 }
2007         }
2008         return MUT_ITEMTOUCH_CONTINUE;
2009 }
2010
2011 /// \brief Hook which is called when the player tries to throw their weapon.
2012 MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
2013 {
2014         entity player = M_ARGV(0, entity);
2015         if (player.team == surv_defenderteam)
2016         {
2017                 return true;
2018         }
2019 }
2020
2021 /// \brief Hook which is called when the damage amount must be determined.
2022 MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
2023 {
2024         entity frag_attacker = M_ARGV(1, entity);
2025         entity frag_target = M_ARGV(2, entity);
2026         float deathtype = M_ARGV(3, float);
2027         float damage = M_ARGV(4, float);
2028         M_ARGV(4, float) = PlayerTemplate_Damage_Calculate(frag_attacker,
2029                 Surv_GetPlayerTemplate(frag_attacker), frag_target,
2030                 Surv_GetPlayerTemplate(frag_target), deathtype, damage);
2031 }
2032
2033 /// \brief Hook which is called when the player was damaged.
2034 MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
2035 {
2036         entity target = M_ARGV(1, entity);
2037         if (target.team != surv_defenderteam)
2038         {
2039                 return;
2040         }
2041         Surv_UpdateDefenderHealthStat();
2042         entity attacker = M_ARGV(0, entity);
2043         if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
2044                 SURVIVAL_ROLE_PLAYER))
2045         {
2046                 float health = M_ARGV(2, float);
2047                 float armor = M_ARGV(3, float);
2048                 float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
2049                 PlayerScore_Add(attacker, SP_SCORE, score);
2050         }
2051         if (autocvar_g_surv_stealth)
2052         {
2053                 return;
2054         }
2055         if (target.health < 1)
2056         {
2057                 WaypointSprite_Kill(target.surv_attack_sprite);
2058         }
2059         else
2060         {
2061                 if (autocvar_g_instagib == 1)
2062                 {
2063                         WaypointSprite_UpdateHealth(target.surv_attack_sprite,
2064                                 target.armorvalue + 1);
2065                 }
2066                 else
2067                 {
2068                         WaypointSprite_UpdateHealth(target.surv_attack_sprite,
2069                                 target.health + target.armorvalue);
2070                 }
2071         }
2072 }
2073
2074 /// \brief Hook which is called when the player dies.
2075 MUTATOR_HOOKFUNCTION(surv, PlayerDies)
2076 {
2077         //DebugPrintToChatAll("PlayerDies");
2078         entity attacker = M_ARGV(1, entity);
2079         entity victim = M_ARGV(2, entity);
2080         if ((attacker.team == surv_defenderteam) &&
2081                 (victim.team == surv_attackerteam))
2082         {
2083                 switch (victim.surv_role)
2084                 {
2085                         case SURVIVAL_ROLE_PLAYER:
2086                         {
2087                                 GivePlayerHealth(attacker,
2088                                         autocvar_g_surv_defender_attacker_frag_health);
2089                                 GivePlayerArmor(attacker,
2090                                         autocvar_g_surv_defender_attacker_frag_armor);
2091                                 GivePlayerAmmo(attacker, ammo_shells,
2092                                         autocvar_g_surv_defender_attacker_frag_shells);
2093                                 GivePlayerAmmo(attacker, ammo_nails,
2094                                         autocvar_g_surv_defender_attacker_frag_bullets);
2095                                 GivePlayerAmmo(attacker, ammo_rockets,
2096                                         autocvar_g_surv_defender_attacker_frag_rockets);
2097                                 GivePlayerAmmo(attacker, ammo_cells,
2098                                         autocvar_g_surv_defender_attacker_frag_cells);
2099                                 break;
2100                         }
2101                         case SURVIVAL_ROLE_CANNON_FODDER:
2102                         {
2103                                 GivePlayerHealth(attacker,
2104                                         autocvar_g_surv_defender_cannon_fodder_frag_health);
2105                                 GivePlayerArmor(attacker,
2106                                         autocvar_g_surv_defender_cannon_fodder_frag_armor);
2107                                 GivePlayerAmmo(attacker, ammo_shells,
2108                                         autocvar_g_surv_defender_cannon_fodder_frag_shells);
2109                                 GivePlayerAmmo(attacker, ammo_nails,
2110                                         autocvar_g_surv_defender_cannon_fodder_frag_bullets);
2111                                 GivePlayerAmmo(attacker, ammo_rockets,
2112                                         autocvar_g_surv_defender_cannon_fodder_frag_rockets);
2113                                 GivePlayerAmmo(attacker, ammo_cells,
2114                                         autocvar_g_surv_defender_cannon_fodder_frag_cells);
2115                                 break;
2116                         }
2117                 }
2118         }
2119         if ((victim.team == surv_defenderteam) &&
2120                 (autocvar_g_surv_defender_drop_weapons == false))
2121         {
2122                 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2123                 {
2124                         .entity went = weaponentities[slot];
2125                         victim.(went).m_weapon = WEP_Null;
2126                 }
2127         }
2128         if (!Surv_CanPlayerSpawn(victim))
2129         {
2130                 victim.respawn_flags = RESPAWN_SILENT;
2131                 if (IS_BOT_CLIENT(victim))
2132                 {
2133                         bot_clear(victim);
2134                 }
2135         }
2136         return true;
2137 }
2138
2139 /// \brief Hook which is called after the player died.
2140 MUTATOR_HOOKFUNCTION(surv, PlayerDied)
2141 {
2142         //DebugPrintToChatAll("PlayerDied");
2143         entity player = M_ARGV(0, entity);
2144         Surv_RemovePlayerFromAliveList(player, player.team);
2145         //Surv_CountAlivePlayers();
2146 }
2147
2148 /// \brief Hook which is called when player has scored a frag.
2149 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
2150 {
2151         if (surv_type == SURVIVAL_TYPE_COOP)
2152         {
2153                 return true;
2154         }
2155         entity attacker = M_ARGV(0, entity);
2156         if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
2157                 SURVIVAL_ROLE_CANNON_FODDER))
2158         {
2159                 M_ARGV(2, float) = 0;
2160                 return true;
2161         }
2162         entity target = M_ARGV(1, entity);
2163         if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
2164                 surv_defenderteam))
2165         {
2166                 M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
2167         }
2168         return true;
2169 }
2170
2171 /// \brief I'm not sure exactly what this function does but it is very
2172 /// important. Without it bots are completely broken. Is it a hack? Of course.
2173 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
2174 {
2175         FOREACH_CLIENT(IS_REAL_CLIENT(it),
2176         {
2177                 if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
2178                 {
2179                         ++M_ARGV(0, int);
2180                 }
2181                 ++M_ARGV(1, int);
2182         });
2183         return true;
2184 }
2185
2186 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
2187 {
2188         // Don't announce remaining frags
2189         return false;
2190 }