]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Survival: Moved from server to common.
authorLyberta <lyberta@lyberta.net>
Thu, 31 Aug 2017 13:14:58 +0000 (16:14 +0300)
committerLyberta <lyberta@lyberta.net>
Thu, 31 Aug 2017 13:14:58 +0000 (16:14 +0300)
qcsrc/common/gamemodes/gamemode/_mod.inc
qcsrc/common/gamemodes/gamemode/_mod.qh
qcsrc/common/gamemodes/gamemode/survival/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh [new file with mode: 0644]
qcsrc/server/mutators/mutator/_mod.inc
qcsrc/server/mutators/mutator/_mod.qh
qcsrc/server/mutators/mutator/gamemode_survival.qc [deleted file]
qcsrc/server/mutators/mutator/gamemode_survival.qh [deleted file]

index 2fc2c404678883117dcd9205365e36317f5e997e..6056ba7cc278187d43b0b166cb9abda348803933 100644 (file)
@@ -2,3 +2,4 @@
 
 #include <common/gamemodes/gamemode/nexball/_mod.inc>
 #include <common/gamemodes/gamemode/onslaught/_mod.inc>
+#include <common/gamemodes/gamemode/survival/_mod.inc>
index d79957012609493478bdf9e0a03ea2116fec63c3..333d341709b9d1129dab243c7e23559c15f1bf6b 100644 (file)
@@ -2,3 +2,4 @@
 
 #include <common/gamemodes/gamemode/nexball/_mod.qh>
 #include <common/gamemodes/gamemode/onslaught/_mod.qh>
+#include <common/gamemodes/gamemode/survival/_mod.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/survival/_mod.inc b/qcsrc/common/gamemodes/gamemode/survival/_mod.inc
new file mode 100644 (file)
index 0000000..006e93f
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/survival/sv_survival.qc>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/survival/_mod.qh b/qcsrc/common/gamemodes/gamemode/survival/_mod.qh
new file mode 100644 (file)
index 0000000..5ace954
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/survival/sv_survival.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc
new file mode 100644 (file)
index 0000000..4f917e8
--- /dev/null
@@ -0,0 +1,2282 @@
+#include "sv_survival.qh"
+
+#include <common/mutators/mutator/overkill/hmg.qh>
+#include <common/mutators/mutator/overkill/rpc.qh>
+
+//============================ Constants ======================================
+
+const int SURVIVAL_TEAM_1_BIT = BIT(0); ///< Bitmask of the first team.
+const int SURVIVAL_TEAM_2_BIT = BIT(1); ///< Bitmask of the second team.
+
+/// \brief Used when bitfield team count is requested.
+const int SURVIVAL_TEAM_BITS = 3;
+
+enum
+{
+       SURVIVAL_TYPE_COOP, ///< All humans are on the defender team.
+       SURVIVAL_TYPE_VERSUS ///< Humans take turns between attackers and defenders.
+};
+
+const string SURVIVAL_TYPE_COOP_VALUE = "coop"; ///< Cvar value for coop.
+const string SURVIVAL_TYPE_VERSUS_VALUE = "versus"; ///< Cvar value for versus.
+
+enum
+{
+       /// \brief First round where there is timelimit set by the server.
+       SURVIVAL_ROUND_FIRST,
+       /// \brief Second round where defender team tries to survive for the first
+       /// round's time.
+       SURVIVAL_ROUND_SECOND
+};
+
+enum
+{
+       /// \brief Player is spectating and has no intention of playing.
+       SURVIVAL_STATE_NOT_PLAYING,
+       /// \brief Player is playing the game.
+       SURVIVAL_STATE_PLAYING = 1
+};
+
+enum
+{
+       SURVIVAL_ROLE_NONE, ///< Player is not playing.
+       SURVIVAL_ROLE_PLAYER, ///< Player is an attacker or defender.
+       SURVIVAL_ROLE_CANNON_FODDER ///< Player is a cannon fodder.
+};
+
+SOUND(SURV_3_FRAGS_LEFT, "announcer/default/3fragsleft");
+SOUND(SURV_2_FRAGS_LEFT, "announcer/default/2fragsleft");
+SOUND(SURV_1_FRAG_LEFT, "announcer/default/1fragleft");
+
+SOUND(SURV_RED_SCORES, "ctf/red_capture");
+SOUND(SURV_BLUE_SCORES, "ctf/blue_capture");
+
+//======================= Global variables ====================================
+
+float autocvar_g_surv_warmup; ///< Warmup time in seconds.
+float autocvar_g_surv_round_timelimit; ///< First round time limit in seconds.
+
+int autocvar_g_surv_point_limit; ///< Maximum number of points.
+int autocvar_g_surv_point_leadlimit; ///< Maximum lead of a single team.
+
+/// \brief How much players are allowed in teams (excluding cannon fodder).
+int autocvar_g_surv_team_size;
+/// \brief If set, defenders will not be shown on the radar.
+int autocvar_g_surv_stealth;
+/// \brief Whether to allow spectating enemy players while dead.
+bool autocvar_g_surv_spectate_enemies;
+
+/// \brief Whether to force overkill player models for attackers.
+int autocvar_g_surv_attacker_force_overkill_models;
+/// \brief Whether to force overkill player models for defenders.
+int autocvar_g_surv_defender_force_overkill_models;
+/// \brief Whether to force overkill player models for cannon fodder.
+int autocvar_g_surv_cannon_fodder_force_overkill_models;
+
+/// \brief How much score attackers gain per 1 point of damage.
+float autocvar_g_surv_attacker_damage_score;
+
+/// \brief How much score attackers get for fragging defenders.
+float autocvar_g_surv_attacker_frag_score;
+
+/// \brief How much health do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_health;
+/// \brief How much armor do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_armor;
+/// \brief How many shells do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_shells;
+/// \brief How many bullets do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_bullets;
+/// \brief How many rockets do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_rockets;
+/// \brief How many cells do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_cells;
+/// \brief How much plasma do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_plasma;
+/// \brief How much fuel do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_fuel;
+/// \brief How much health do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_health;
+/// \brief How much armor do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_armor;
+/// \brief How many shells do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_shells;
+/// \brief How many bullets do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_bullets;
+/// \brief How many rockets do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_rockets;
+/// \brief How many cells do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_cells;
+/// \brief How much plasma do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_plasma;
+/// \brief How much fuel do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_fuel;
+
+/// \brief A stat that is used to track the time left in the round.
+.float surv_round_time_stat = _STAT(SURV_ROUND_TIME);
+/// \brief A stat that is used to track defender team.
+.int surv_defender_team_stat = _STAT(SURV_DEFENDER_TEAM);
+/// \brief A stat that is used to track number of defenders alive.
+.int surv_defenders_alive_stat = _STAT(SURV_DEFENDERS_ALIVE);
+/// \brief A stat that is used to track the total health of defenders.
+.float surv_defender_health_stat = _STAT(SURV_DEFENDER_HEALTH);
+
+/// \brief Holds the state of the player. See SURVIVAL_STATE constants.
+.int surv_state;
+/// \brief Holds the role of the player. See SURVIVAL_ROLE constants.
+.int surv_role;
+.string surv_savedplayermodel; ///< Initial player model.
+/// \brief Player state used during replacement of bot player with real player.
+.entity surv_savedplayerstate;
+.string surv_playermodel; ///< Player model forced by the game.
+
+.entity surv_attack_sprite; ///< Holds the sprite telling attackers to attack.
+.entity surv_defend_sprite; ///< Holds the sprite telling defenders to defend.
+
+int surv_type; ///< Holds the type of survival. See SURVIVAL_TYPE constants.
+bool surv_warmup; ///< Holds whether warmup is active.
+/// \brief Holds the type of the current round. See SURVIVAL_ROUND constants.
+int surv_roundtype;
+bool surv_isroundactive; ///< Holds whether the round is active.
+float surv_roundstarttime; ///< Holds the time of the round start.
+/// \brief Holds the time needed to survive in the second round.
+float surv_timetobeat;
+
+int surv_attackerteam; ///< Holds the attacker team.
+int surv_defenderteam; ///< Holds the defender team.
+
+int surv_attackerteambit; ///< Hols the attacker team bitmask.
+int surv_defenderteambit; ///< Holds the defender team bitmask.
+
+int surv_numattackers; ///< Holds the number of players in attacker team.
+int surv_numdefenders; ///< Holds the number of players in defender team.
+
+/// \brief Holds the number of humans in attacker team.
+int surv_numattackerhumans;
+/// \brief Holds the number of humans in defender team.
+int surv_numdefenderhumans;
+
+/// \brief Holds the number of attacker players that are alive.
+int surv_numattackersalive;
+/// \brief Holds the number of defender players that are alive.
+int surv_numdefendersalive;
+
+bool surv_autobalance; ///< Holds whether autobalance is active.
+bool surv_announcefrags; ///< Holds whether remaining frags must be announced.
+bool surv_allowed_to_spawn; ///< Holds whether players are allowed to spawn.
+
+//====================== Forward declarations =================================
+
+/// \brief Determines whether the round can start.
+/// \return True if the round can start, false otherwise.
+bool Surv_CanRoundStart();
+
+/// \brief Determines whether the round can end.
+/// \return True if the round can end, false otherwise.
+bool Surv_CanRoundEnd();
+
+/// \brief Called when the round starts.
+/// \return No return.
+void Surv_RoundStart();
+
+/// \brief Returns whether player has been eliminated.
+/// \param[in] player Player to check.
+/// \return True if player is eliminated, false otherwise.
+bool Surv_IsEliminated(entity player);
+
+/// \brief Updates stats of team count on HUD.
+/// \return No return.
+void Surv_UpdateTeamStats();
+
+/// \brief Updates stats of alive players on HUD.
+/// \return No return.
+void Surv_UpdateAliveStats();
+
+/// \brief Updates defender health on the HUD.
+/// \return No return.
+void Surv_UpdateDefenderHealthStat();
+
+/// \brief Updates the health of defender sprite.
+/// \param[in,out] player Player that has the sprite.
+/// \return No return.
+void Surv_UpdateWaypointSpriteHealth(entity player);
+
+//========================= Free functions ====================================
+
+void Surv_Initialize()
+{
+       switch (cvar_string("g_surv_type"))
+       {
+               case SURVIVAL_TYPE_COOP_VALUE:
+               {
+                       surv_type = SURVIVAL_TYPE_COOP;
+                       break;
+               }
+               case SURVIVAL_TYPE_VERSUS_VALUE:
+               {
+                       surv_type = SURVIVAL_TYPE_VERSUS;
+                       break;
+               }
+               default:
+               {
+                       error("Invalid survival type.");
+               }
+       }
+       surv_roundtype = SURVIVAL_ROUND_FIRST;
+       surv_isroundactive = false;
+       surv_roundstarttime = time;
+       surv_timetobeat = autocvar_g_surv_round_timelimit;
+       if (random() < 0.5)
+       {
+               surv_attackerteam = NUM_TEAM_1;
+               surv_defenderteam = NUM_TEAM_2;
+               surv_attackerteambit = SURVIVAL_TEAM_1_BIT;
+               surv_defenderteambit = SURVIVAL_TEAM_2_BIT;
+       }
+       else
+       {
+               surv_attackerteam = NUM_TEAM_2;
+               surv_defenderteam = NUM_TEAM_1;
+               surv_attackerteambit = SURVIVAL_TEAM_2_BIT;
+               surv_defenderteambit = SURVIVAL_TEAM_1_BIT;
+       }
+       surv_numattackers = 0;
+       surv_numdefenders = 0;
+       surv_numattackerhumans = 0;
+       surv_numdefenderhumans = 0;
+       surv_numattackersalive = 0;
+       surv_numdefendersalive = 0;
+       surv_autobalance = true;
+       surv_announcefrags = true;
+       surv_allowed_to_spawn = true;
+       precache_all_playermodels("models/ok_player/*.dpm");
+       ScoreRules_basics(SURVIVAL_TEAM_BITS, SFL_SORT_PRIO_PRIMARY, 0, true);
+       ScoreInfo_SetLabel_TeamScore(1, "rounds", SFL_SORT_PRIO_PRIMARY);
+       ScoreRules_basics_end();
+       round_handler_Spawn(Surv_CanRoundStart, Surv_CanRoundEnd, Surv_RoundStart);
+       round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
+       EliminatedPlayers_Init(Surv_IsEliminated);
+       GameRules_teams(true);
+       GameRules_limit_score(autocvar_g_surv_point_limit);
+       GameRules_limit_lead(autocvar_g_surv_point_leadlimit);
+}
+
+/// \brief Returns the name of the template of the given player.
+/// \param[in] player Player to inspect.
+/// \return Name of the template of the given player.
+string Surv_GetPlayerTemplate(entity player)
+{
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       switch (player.surv_role)
+                       {
+                               case SURVIVAL_ROLE_NONE:
+                               case SURVIVAL_ROLE_PLAYER:
+                               {
+                                       return cvar_string("g_surv_attacker_template");
+                               }
+                               case SURVIVAL_ROLE_CANNON_FODDER:
+                               {
+                                       return cvar_string("g_surv_cannon_fodder_template");
+                               }
+                       }
+               }
+               case surv_defenderteam:
+               {
+                       return cvar_string("g_surv_defender_template");
+               }
+       }
+       return "default";
+}
+
+/// \brief Saves the player state. Used to seamlessly swap bots with humans.
+/// \param[in] player Player to save the state of.
+/// \return Entity containing the player state.
+entity Surv_SavePlayerState(entity player)
+{
+       entity state = spawn();
+       state.origin = player.origin;
+       state.velocity = player.velocity;
+       state.angles = player.angles;
+       state.health = player.health;
+       state.armorvalue = player.armorvalue;
+       state.ammo_shells = player.ammo_shells;
+       state.ammo_nails = player.ammo_nails;
+       state.ammo_rockets = player.ammo_rockets;
+       state.ammo_cells = player.ammo_cells;
+       state.weapons = player.weapons;
+       state.items = player.items;
+       state.superweapons_finished = player.superweapons_finished;
+       return state;
+}
+
+/// \brief Restores a saved player state.
+/// \param[in] player Player to restore the state of.
+/// \param[in] st State to restore.
+/// \return No return.
+void Surv_RestorePlayerState(entity player, entity st)
+{
+       player.origin = st.origin;
+       player.velocity = st.velocity;
+       player.angles = st.angles;
+       player.health = st.health;
+       player.armorvalue = st.armorvalue;
+       player.ammo_shells = st.ammo_shells;
+       player.ammo_nails = st.ammo_nails;
+       player.ammo_rockets = st.ammo_rockets;
+       player.ammo_cells = st.ammo_cells;
+       player.weapons = st.weapons;
+       player.items = st.items;
+       player.superweapons_finished = st.superweapons_finished;
+}
+
+/// \brief Returns the attacker with the lowest score.
+/// \param[in] bot Whether to search only for bots.
+/// \return Attacker with the lowest score or NULL if not found.
+entity Surv_FindLowestAttacker(bool bot)
+{
+       entity player = NULL;
+       float score = FLOAT_MAX;
+       FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
+       {
+               if ((it.team == surv_attackerteam) && (it.surv_role ==
+                       SURVIVAL_ROLE_PLAYER))
+               {
+                       float tempscore = PlayerScore_Get(it, SP_SCORE);
+                       if (tempscore < score)
+                       {
+                               player = it;
+                               score = tempscore;
+                       }
+               }
+       });
+       return player;
+}
+
+/// \brief Returns the defender with the lowest score.
+/// \param[in] bot Whether to search only for bots.
+/// \param[in] alive Whether to search only for alive players.
+/// \return Defender with the lowest score or NULL if not found.
+entity Surv_FindLowestDefender(bool bot, bool alive)
+{
+       entity player = NULL;
+       float score = FLOAT_MAX;
+       FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
+       {
+               if ((it.team == surv_defenderteam) && (alive ? !IS_DEAD(it) : true))
+               {
+                       float tempscore = PlayerScore_Get(it, SP_SCORE);
+                       if (tempscore < score)
+                       {
+                               player = it;
+                               score = tempscore;
+                       }
+               }
+       });
+       return player;
+}
+
+/// \brief Returns the cannon fodder.
+/// \return Cannon fodder or NULL if not found.
+entity Surv_FindCannonFodder()
+{
+       FOREACH_CLIENT(IS_BOT_CLIENT(it),
+       {
+               if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+               {
+                       return it;
+               }
+       });
+       return NULL;
+}
+
+/// \brief Changes the number of players in a team.
+/// \param[in] teamnum Team to adjust.
+/// \param[in] delta Amount to adjust by.
+/// \return No return.
+void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
+{
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       surv_numattackers += delta;
+                       LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
+                               " was = ", ftos(surv_numattackers - delta));
+                       Surv_UpdateTeamStats();
+                       return;
+               }
+               case surv_defenderteam:
+               {
+                       surv_numdefenders += delta;
+                       LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
+                               " was = ", ftos(surv_numdefenders - delta));
+                       Surv_UpdateTeamStats();
+                       return;
+               }
+       }
+}
+
+/// \brief Changes the number of alive players in a team.
+/// \param[in] teamnum Team to adjust.
+/// \param[in] delta Amount to adjust by.
+/// \return No return.
+void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
+{
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       surv_numattackersalive += delta;
+                       LOG_TRACE("Number of alive attackers = ", ftos(
+                               surv_numattackersalive), " was = ", ftos(surv_numattackersalive
+                               - delta));
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       surv_numdefendersalive += delta;
+                       LOG_TRACE("Number of alive defenders = ", ftos(
+                               surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
+                               - delta));
+                       break;
+               }
+       }
+       Surv_UpdateAliveStats();
+       eliminatedPlayers.SendFlags |= 1;
+}
+
+/// \brief Sets the player role.
+/// \param[in,out] player Player to adjust.
+/// \param[in] role Role to set.
+/// \return No return.
+void Surv_SetPlayerRole(entity player, int role)
+{
+       if (player.surv_role == role)
+       {
+               return;
+       }
+       player.surv_role = role;
+       switch (role)
+       {
+               case SURVIVAL_ROLE_NONE:
+               {
+                       LOG_TRACE(player.netname, " now has no role.");
+                       break;
+               }
+               case SURVIVAL_ROLE_PLAYER:
+               {
+                       LOG_TRACE(player.netname, " is now a player.");
+                       break;
+               }
+               case SURVIVAL_ROLE_CANNON_FODDER:
+               {
+                       LOG_TRACE(player.netname, " is now a cannon fodder.");
+                       break;
+               }
+       }
+}
+
+/// \brief Adds player to team. Handles bookkeeping information.
+/// \param[in] player Player to add.
+/// \param[in] teamnum Team to add to.
+/// \return True on success, false otherwise.
+bool Surv_AddPlayerToTeam(entity player, int teamnum)
+{
+       LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       LOG_TRACE("Attacker team");
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               LOG_TRACE("Cannon fodder is switching team");
+                               return true;
+                       }
+                       if (IS_BOT_CLIENT(player))
+                       {
+                               LOG_TRACE("Client is bot");
+                               LOG_TRACE("Attackers = ", ftos(surv_numattackers));
+                               if (surv_numattackers < autocvar_g_surv_team_size)
+                               {
+                                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                                       return true;
+                               }
+                               Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
+                               return true;
+                       }
+                       LOG_TRACE("Client is not a bot");
+                       LOG_TRACE("Attackers = ", ftos(surv_numattackers));
+                       if (surv_numattackers >= autocvar_g_surv_team_size)
+                       {
+                               LOG_TRACE("Removing bot");
+                               // Remove bot to make space for human.
+                               entity bot = Surv_FindLowestAttacker(true);
+                               if (bot == NULL)
+                               {
+                                       LOG_TRACE("No valid bot to remove");
+                                       // No space in team, denying team change.
+                                       TRANSMUTE(Spectator, player);
+                                       return false;
+                               }
+                               LOG_TRACE("Changing ", bot.netname,
+                                       " from attacker to cannon fodder.");
+                               Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
+                               if (!IS_DEAD(bot))
+                               {
+                                       Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
+                               }
+                               Surv_ChangeNumberOfPlayers(teamnum, -1);
+                               LOG_TRACE("Removed bot");
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                       ++surv_numattackerhumans;                       
+                       LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
+                       if ((surv_autobalance == false) || (surv_numattackers -
+                               surv_numdefenders) < 2)
+                       {
+                               return true;
+                       }
+                       entity lowestplayer = Surv_FindLowestAttacker(true);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                               surv_autobalance = savedautobalance;
+                               return true;
+                       }
+                       lowestplayer = Surv_FindLowestAttacker(false);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                               surv_autobalance = savedautobalance;
+                       }
+                       return true;
+               }
+               case surv_defenderteam:
+               {
+                       LOG_TRACE("Defender team");
+                       if (IS_BOT_CLIENT(player))
+                       {
+                               LOG_TRACE("Client is bot");
+                               LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
+                               if (surv_numdefenders < autocvar_g_surv_team_size)
+                               {
+                                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                                       return true;
+                               }
+                               LOG_TRACE("No space for defender, switching to attacker");
+                               SetPlayerTeamSimple(player, surv_attackerteam);
+                               return false;
+                       }
+                       LOG_TRACE("Client is not a bot");
+                       LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
+                       if (surv_numdefenders >= autocvar_g_surv_team_size)
+                       {
+                               LOG_TRACE("Removing bot");
+                               // Remove bot to make space for human.
+                               entity bot = Surv_FindLowestDefender(true, true);
+                               if (bot == NULL)
+                               {
+                                       bot = Surv_FindLowestDefender(true, false);
+                               }
+                               if (bot == NULL)
+                               {
+                                       LOG_TRACE("No valid bot to remove");
+                                       // No space in team, denying team change.
+                                       TRANSMUTE(Spectator, player);
+                                       return false;
+                               }
+                               LOG_TRACE("Changing ", bot.netname,
+                                       " from defender to cannon fodder.");
+                               if (!IS_DEAD(bot))
+                               {
+                                       player.surv_savedplayerstate = Surv_SavePlayerState(bot);
+                               }
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               surv_announcefrags = false;
+                               SetPlayerTeamSimple(bot, surv_attackerteam);
+                               surv_autobalance = savedautobalance;
+                               surv_announcefrags = true;
+                               LOG_TRACE("Removed bot");
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                       ++surv_numdefenderhumans;
+                       LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
+                       if ((surv_autobalance == false) || (surv_numdefenders -
+                               surv_numattackers) < 2)
+                       {
+                               return true;
+                       }
+                       entity lowestplayer = Surv_FindLowestDefender(true, false);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
+                               surv_autobalance = savedautobalance;
+                               return true;
+                       }
+                       lowestplayer = Surv_FindLowestDefender(false, false);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
+                               surv_autobalance = savedautobalance;
+                       }
+                       return true;
+               }
+               case -1:
+               {
+                       LOG_TRACE("Spectator team");
+                       player.surv_role = SURVIVAL_ROLE_NONE;                  
+                       return false;
+               }
+       }
+       LOG_TRACE("Invalid team");
+       player.surv_role = SURVIVAL_ROLE_NONE;
+       return false;
+}
+
+/// \brief Removes player from team. Handles bookkeeping information.
+/// \param[in] player Player to remove.
+/// \param[in] Team to remove from.
+/// \return No return.
+void Surv_RemovePlayerFromTeam(entity player, int teamnum)
+{
+       LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       LOG_TRACE("Attacker team");
+                       if (player.surv_role == SURVIVAL_ROLE_NONE)
+                       {
+                               string message = strcat("RemovePlayerFromTeam: ",
+                                       player.netname, " has invalid role.");
+                               DebugPrintToChatAll(message);
+                               return;
+                       }
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
+                               return;
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
+                       Surv_ChangeNumberOfPlayers(teamnum, -1);
+                       if (!IS_BOT_CLIENT(player))
+                       {
+                               --surv_numattackerhumans;
+                       }
+                       if ((surv_autobalance == false) || (surv_numattackers >=
+                               surv_numdefenders))
+                       {
+                               return;
+                       }
+                       // Add bot to keep teams balanced.
+                       entity lowestplayer = Surv_FindCannonFodder();
+                       if (lowestplayer != NULL)
+                       {
+                               LOG_TRACE("Changing ", lowestplayer.netname,
+                                       " from cannon fodder to attacker.");
+                               Surv_SetPlayerRole(lowestplayer, SURVIVAL_ROLE_PLAYER);
+                               Surv_ChangeNumberOfPlayers(teamnum, +1);
+                               if (!IS_DEAD(lowestplayer))
+                               {
+                                       Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
+                               }
+                               return;
+                       }
+                       lowestplayer = Surv_FindLowestDefender(true, false);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
+                               surv_autobalance = savedautobalance;
+                               return;
+                       }
+                       lowestplayer = Surv_FindLowestDefender(false, false);
+                       if (lowestplayer == NULL)
+                       {
+                               return;
+                       }
+                       bool savedautobalance = surv_autobalance;
+                       surv_autobalance = false;
+                       SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
+                       surv_autobalance = savedautobalance;
+                       return;
+               }
+               case surv_defenderteam:
+               {
+                       LOG_TRACE("Defender team");
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               // This happens during team switch. We don't need to change
+                               // anything.
+                               LOG_TRACE("Cannon fodder. Assuming team switch");
+                               return;
+                       }
+                       if (player.surv_role != SURVIVAL_ROLE_PLAYER)
+                       {
+                               string message = strcat("RemovePlayerFromTeam: ",
+                                       player.netname, " has invalid role.");
+                               DebugPrintToChatAll(message);
+                               return;
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
+                       Surv_ChangeNumberOfPlayers(teamnum, -1);
+                       if (!IS_BOT_CLIENT(player))
+                       {
+                               --surv_numdefenderhumans;
+                       }
+                       if ((surv_autobalance == false) || (surv_numdefenders >=
+                               surv_numattackers))
+                       {
+                               return;
+                       }
+                       // Add bot to keep teams balanced.
+                       entity lowestplayer = Surv_FindCannonFodder();
+                       if (lowestplayer != NULL)
+                       {
+                               LOG_TRACE("Changing ", lowestplayer.netname,
+                                       " from cannon fodder to defender.");
+                               if (!IS_DEAD(player))
+                               {
+                                       lowestplayer.surv_savedplayerstate =
+                                               Surv_SavePlayerState(player);
+                               }
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               surv_announcefrags = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                               surv_autobalance = savedautobalance;
+                               surv_announcefrags = true;                                      
+                               return;
+                       }
+                       lowestplayer = Surv_FindLowestAttacker(true);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               surv_announcefrags = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                               surv_autobalance = savedautobalance;
+                               surv_announcefrags = true;
+                               return;
+                       }
+                       lowestplayer = Surv_FindLowestAttacker(false);
+                       if (lowestplayer == NULL)
+                       {
+                               return;
+                       }
+                       bool savedautobalance = surv_autobalance;
+                       surv_autobalance = false;
+                       surv_announcefrags = false;
+                       SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                       surv_autobalance = savedautobalance;
+                       surv_announcefrags = true;
+                       return;
+               }
+               case -1:
+               {
+                       LOG_TRACE("Spectator team");
+                       return;
+               }
+               default:
+               {
+                       LOG_TRACE("Invalid team");
+                       return;
+               }
+       }
+}
+
+/// \brief Updates stats of team count on HUD.
+/// \return No return.
+void Surv_UpdateTeamStats()
+{
+       // Debug stuff
+       if (surv_attackerteam == NUM_TEAM_1)
+       {
+               yellowalive = surv_numattackers;
+               pinkalive = surv_numdefenders;
+       }
+       else
+       {
+               pinkalive = surv_numattackers;
+               yellowalive = surv_numdefenders;
+       }
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               it.yellowalive_stat = yellowalive;
+               it.pinkalive_stat = pinkalive;
+       });
+}
+
+/// \brief Adds player to alive list. Handles bookkeeping information.
+/// \param[in] player Player to add.
+/// \param[in] teamnum Team of the player.
+/// \return No return.
+void Surv_AddPlayerToAliveList(entity player, int teamnum)
+{
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_PLAYER)
+                       {
+                               Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
+                       }
+                       return;
+               }
+               case surv_defenderteam:
+               {
+                       Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
+                       return;
+               }
+       }
+}
+
+/// \brief Removes player from alive list. Handles bookkeeping information.
+/// \param[in] player Player to remove.
+/// \param[in] teamnum Team of the player.
+/// \return No return.
+void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
+{
+       if (player.surv_attack_sprite)
+       {
+               WaypointSprite_Kill(player.surv_attack_sprite);
+       }
+       if (player.surv_defend_sprite)
+       {
+               WaypointSprite_Kill(player.surv_defend_sprite);
+       }
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_PLAYER)
+                       {
+                               Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
+                       }
+                       return;
+               }
+               case surv_defenderteam:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               // This happens during team switch. We don't need to change
+                               // anything.
+                               return;
+                       }
+                       Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
+                       if (warmup_stage || surv_allowed_to_spawn || !surv_announcefrags)
+                       {
+                               return;
+                       }
+                       switch (surv_numdefendersalive)
+                       {
+                               case 1:
+                               {
+                                       sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
+                                               VOL_BASE, ATTEN_NONE);
+                                       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+                                       {
+                                               if (it.team == surv_defenderteam)
+                                               {
+                                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER,
+                                                               CENTER_ALONE);
+                                                       return;
+                                               }
+                                       });
+                                       return;
+                               }
+                               case 2:
+                               {
+                                       sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
+                                               VOL_BASE, ATTEN_NONE);
+                                       return;
+                               }
+                               case 3:
+                               {
+                                       sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
+                                               VOL_BASE, ATTEN_NONE);
+                                       return;
+                               }
+                       }
+                       return;
+               }
+       }
+}
+
+/// \brief Counts alive players.
+/// \return No return.
+/// \note In a perfect world this function shouldn't exist. However, since QC
+/// code is so bad and spurious mutators can really mess with your code, this
+/// function is called as a last resort.
+void Surv_CountAlivePlayers()
+{
+       int savednumdefenders = surv_numdefendersalive;
+       surv_numattackersalive = 0;
+       surv_numdefendersalive = 0;
+       FOREACH_CLIENT(IS_PLAYER(it),
+       {
+               switch (it.team)
+               {
+                       case surv_attackerteam:
+                       {
+                               if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
+                               {
+                                       ++surv_numattackersalive;
+                               }
+                               break;
+                       }
+                       case surv_defenderteam:
+                       {
+                               if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
+                               {
+                                       ++surv_numdefendersalive;
+                               }
+                               break;
+                       }
+               }
+       });
+       Surv_UpdateAliveStats();
+       eliminatedPlayers.SendFlags |= 1;
+       if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
+               surv_numdefendersalive))
+       {
+               return;
+       }
+       switch (surv_numdefendersalive)
+       {
+               case 1:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
+                       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+                       {
+                               if (it.team == surv_defenderteam)
+                               {
+                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
+                                       return;
+                               }
+                       });
+                       return;
+               }
+               case 2:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
+                               ATTEN_NONE);
+                       return;
+               }
+               case 3:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
+                               ATTEN_NONE);
+                       return;
+               }
+       }
+}
+
+/// \brief Updates stats of alive players on HUD.
+/// \return No return.
+void Surv_UpdateAliveStats()
+{
+       // Debug stuff
+       if (surv_attackerteam == NUM_TEAM_1)
+       {
+               redalive = surv_numattackersalive;
+               bluealive = surv_numdefendersalive;
+       }
+       else
+       {
+               bluealive = surv_numattackersalive;
+               redalive = surv_numdefendersalive;
+       }
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               it.surv_defenders_alive_stat = surv_numdefendersalive;
+               it.redalive_stat = redalive;
+               it.bluealive_stat = bluealive;
+       });
+       Surv_UpdateDefenderHealthStat();
+}
+
+/// \brief Updates defender health on the HUD.
+/// \return No return.
+void Surv_UpdateDefenderHealthStat()
+{
+       float maxhealth;
+       float totalhealth = 0;
+       if (autocvar_g_instagib == 1)
+       {
+               maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
+                       "surv_defender", "start_armor") + 1);
+               FOREACH_CLIENT(IS_PLAYER(it),
+               {
+                       if (it.team == surv_defenderteam)
+                       {
+                               totalhealth += GetResourceAmount(it, RESOURCE_ARMOR) + 1;
+                       }
+               });
+       }
+       else
+       {
+               maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
+                       "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
+                       "surv_defender", "start_armor"));
+               FOREACH_CLIENT(IS_PLAYER(it),
+               {
+                       if (it.team == surv_defenderteam)
+                       {
+                               totalhealth += GetResourceAmount(it, RESOURCE_HEALTH);
+                               totalhealth += GetResourceAmount(it, RESOURCE_ARMOR);
+                       }
+               });
+       }
+       float healthratio;
+       if (maxhealth == 0)
+       {
+               healthratio = 0;
+       }
+       else
+       {
+               healthratio = totalhealth / maxhealth;
+       }
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               it.surv_defender_health_stat = healthratio;
+       });
+}
+
+/// \brief Returns whether the player can spawn.
+/// \param[in] player Player to check.
+/// \return True if the player can spawn, false otherwise.
+bool Surv_CanPlayerSpawn(entity player)
+{
+       if ((player.team == surv_attackerteam) ||
+               (player.surv_savedplayerstate != NULL))
+       {
+               return true;
+       }
+       return surv_allowed_to_spawn;
+}
+
+/// \brief Switches the round type.
+/// \return No return.
+void Surv_SwitchRoundType()
+{
+       switch (surv_roundtype)
+       {
+               case SURVIVAL_ROUND_FIRST:
+               {
+                       surv_roundtype = SURVIVAL_ROUND_SECOND;
+                       return;
+               }
+               case SURVIVAL_ROUND_SECOND:
+               {
+                       surv_roundtype = SURVIVAL_ROUND_FIRST;
+                       return;
+               }
+       }
+}
+
+/// \brief Cleans up the mess after the round has finished.
+/// \return No return.
+void Surv_RoundCleanup()
+{
+       surv_allowed_to_spawn = false;
+       surv_isroundactive = false;
+       game_stopped = true;
+       FOREACH_CLIENT(true,
+       {
+               if (it.surv_attack_sprite)
+               {
+                       WaypointSprite_Kill(it.surv_attack_sprite);
+               }
+               if (it.surv_defend_sprite)
+               {
+                       WaypointSprite_Kill(it.surv_defend_sprite);
+               }
+               if (it.surv_savedplayerstate)
+               {
+                       delete(it.surv_savedplayerstate);
+                       it.surv_savedplayerstate = NULL;
+               }
+       });
+       if (surv_type == SURVIVAL_TYPE_VERSUS)
+       {
+               Surv_SwitchRoundType();
+               round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
+               return;
+       }
+       round_handler_Init(5, autocvar_g_surv_warmup,
+               autocvar_g_surv_round_timelimit);
+}
+
+/// \brief Swaps attacker and defender teams.
+/// \return No return.
+void Surv_SwapTeams()
+{
+       int temp = surv_attackerteam;
+       surv_attackerteam = surv_defenderteam;
+       surv_defenderteam = temp;
+       temp = surv_attackerteambit;
+       surv_attackerteambit = surv_defenderteambit;
+       surv_defenderteambit = temp;
+       temp = surv_numattackers;
+       surv_numattackers = surv_numdefenders;
+       surv_numdefenders = temp;
+       temp = surv_numattackerhumans;
+       surv_numattackerhumans = surv_numdefenderhumans;
+       surv_numdefenderhumans = temp;
+       FOREACH_CLIENT(true,
+       {
+               if ((it.team == surv_defenderteam) && (it.surv_role ==
+                       SURVIVAL_ROLE_CANNON_FODDER))
+               {
+                       SetPlayerTeamSimple(it, surv_attackerteam);
+               }
+       });
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               it.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
+       });
+}
+
+/// \brief Forces the overkill model for specific player.
+/// \param[in,out] player Player to force the model of.
+/// \return No return.
+void Surv_ForceOverkillPlayerModel(entity player)
+{
+       switch (player.team)
+       {
+               case NUM_TEAM_1:
+               {
+                       switch (floor(random() * 4))
+                       {
+                               case 0:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okrobot1.dpm";
+                                       return;
+                               }
+                               case 1:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okrobot2.dpm";
+                                       return;
+                               }
+                               case 2:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okrobot3.dpm";
+                                       return;
+                               }
+                               case 3:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okrobot4.dpm";
+                                       return;
+                               }
+                       }
+                       return;
+               }
+               case NUM_TEAM_2:
+               {
+                       switch (floor(random() * 4))
+                       {
+                               case 0:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okmale1.dpm";
+                                       return;
+                               }
+                               case 1:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okmale2.dpm";
+                                       return;
+                               }
+                               case 2:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okmale3.dpm";
+                                       return;
+                               }
+                               case 3:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okmale4.dpm";
+                                       return;
+                               }
+                       }
+                       return;
+               }
+       }
+}
+
+/// \brief Determines the player model to the one configured for the gamemode.
+/// \param[in,out] player Player to determine the model of.
+/// \return No return.
+void Surv_DeterminePlayerModel(entity player)
+{
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       switch (player.surv_role)
+                       {
+                               case SURVIVAL_ROLE_PLAYER:
+                               {
+                                       if (!autocvar_g_surv_attacker_force_overkill_models)
+                                       {
+                                               player.surv_playermodel = player.surv_savedplayermodel;
+                                               return;
+                                       }
+                                       Surv_ForceOverkillPlayerModel(player);
+                                       return;
+                               }
+                               case SURVIVAL_ROLE_CANNON_FODDER:
+                               {
+                                       if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
+                                       {
+                                               player.surv_playermodel = player.surv_savedplayermodel;
+                                               return;
+                                       }
+                                       Surv_ForceOverkillPlayerModel(player);
+                                       return;
+                               }
+                       }
+               }
+               case surv_defenderteam:
+               {
+                       if (!autocvar_g_surv_defender_force_overkill_models)
+                       {
+                               player.surv_playermodel = player.surv_savedplayermodel;
+                               return;
+                       }
+                       Surv_ForceOverkillPlayerModel(player);
+                       return;
+               }
+       }
+}
+
+/// \brief Setups a waypoint sprite used to track defenders.
+/// \param[in] player Player to attach sprite too.
+/// \return No return.
+void Surv_SetupWaypointSprite(entity player)
+{
+       WaypointSprite_Spawn(WP_SurvivalKill, 0, 0, player, '0 0 64', NULL,
+               surv_attackerteam, player, surv_attack_sprite, false,
+               RADARICON_OBJECTIVE);
+       WaypointSprite_Spawn(WP_SurvivalDefend, 0, 0, player, '0 0 64', NULL,
+               surv_defenderteam, player, surv_defend_sprite, false,
+               RADARICON_OBJECTIVE);
+       //player.surv_attack_sprite.colormod = colormapPaletteColor(player.team - 1,
+       //      false);
+       //player.surv_defend_sprite.colormod = colormapPaletteColor(player.team - 1,
+       //      false);
+       float max_hp;
+       if (autocvar_g_instagib == 1)
+       {
+               max_hp = PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(player),
+                       "start_armor") + 1;
+               WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite, max_hp);
+               WaypointSprite_UpdateMaxHealth(player.surv_defend_sprite, max_hp);
+               Surv_UpdateWaypointSpriteHealth(player);
+               return;
+       }
+       max_hp = PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(player),
+               "start_health") + PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(
+               player), "start_armor");
+       WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite, max_hp);
+       WaypointSprite_UpdateMaxHealth(player.surv_defend_sprite, max_hp);
+       Surv_UpdateWaypointSpriteHealth(player);
+}
+
+void Surv_UpdateWaypointSpriteHealth(entity player)
+{
+       float hp;
+       if (autocvar_g_instagib == 1)
+       {
+               hp = GetResourceAmount(player, RESOURCE_ARMOR) + 1;
+       }
+       else
+       {
+               hp = GetResourceAmount(player, RESOURCE_HEALTH) + GetResourceAmount(
+                       player, RESOURCE_ARMOR);
+       }
+       WaypointSprite_UpdateHealth(player.surv_attack_sprite, hp);
+       WaypointSprite_UpdateHealth(player.surv_defend_sprite, hp);
+}
+
+//=============================== Callbacks ===================================
+
+bool Surv_CanRoundStart()
+{
+       return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
+}
+
+bool Surv_CanRoundEnd()
+{
+       if (warmup_stage)
+       {
+               return false;
+       }
+       if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
+               time <= 0))
+       {
+               if (surv_roundtype == SURVIVAL_ROUND_FIRST)
+               {
+                       surv_timetobeat = time - surv_roundstarttime;
+                       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
+                               CENTER_SURVIVAL_DEFENDERS_SURVIVED);
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
+                               INFO_SURVIVAL_DEFENDERS_SURVIVED);
+                       Surv_RoundCleanup();
+                       return true;
+               }
+               surv_timetobeat = autocvar_g_surv_round_timelimit;
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
+                       CENTER_SURVIVAL_DEFENDERS_SURVIVED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
+                       INFO_SURVIVAL_DEFENDERS_SURVIVED);
+               switch (surv_defenderteam)
+               {
+                       case NUM_TEAM_1:
+                       {
+                               sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
+                                       ATTEN_NONE);
+                               break;
+                       }
+                       case NUM_TEAM_2:
+                       {
+                               sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
+                                       ATTEN_NONE);
+                               break;
+                       }
+               }
+               TeamScore_AddToTeam(surv_defenderteam, 1, 1);
+               Surv_RoundCleanup();
+               return true;
+       }
+       if (surv_numdefendersalive > 0)
+       {
+               return false;
+       }
+       if (surv_roundtype == SURVIVAL_ROUND_FIRST)
+       {
+               surv_timetobeat = time - surv_roundstarttime;
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
+                       CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
+                       INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
+               Surv_RoundCleanup();
+               return true;
+       }
+       surv_timetobeat = autocvar_g_surv_round_timelimit;
+       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
+               CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
+               INFO_SURVIVAL_DEFENDERS_ELIMINATED);
+       switch (surv_attackerteam)
+       {
+               case NUM_TEAM_1:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
+                               ATTEN_NONE);
+                       break;
+               }
+               case NUM_TEAM_2:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
+                               ATTEN_NONE);
+                       break;
+               }
+       }
+       TeamScore_AddToTeam(surv_attackerteam, 1, 1);
+       Surv_RoundCleanup();
+       return true;
+}
+
+void Surv_RoundStart()
+{
+       if (warmup_stage)
+       {
+               surv_allowed_to_spawn = true;
+               return;
+       }
+       surv_isroundactive = true;
+       surv_roundstarttime = time;
+       surv_allowed_to_spawn = false;
+       switch (surv_roundtype)
+       {
+               case SURVIVAL_ROUND_FIRST:
+               {
+                       FOREACH_CLIENT(IS_PLAYER(it),
+                       {
+                               if (it.team == surv_attackerteam)
+                               {
+                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                               CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
+                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                               INFO_SURVIVAL_1ST_ROUND_ATTACKER);
+                                       break;
+                               }
+                       });
+                       FOREACH_CLIENT(IS_PLAYER(it),
+                       {
+                               if (it.team == surv_defenderteam)
+                               {
+                                       if (surv_type == SURVIVAL_TYPE_COOP)
+                                       {
+                                               Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                                       CENTER_SURVIVAL_COOP_DEFENDER);
+                                               Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                                       INFO_SURVIVAL_COOP_DEFENDER);
+                                               break;
+                                       }
+                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                               CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
+                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                               INFO_SURVIVAL_1ST_ROUND_DEFENDER);
+                                       break;
+                               }
+                       });
+                       break;
+               }
+               case SURVIVAL_ROUND_SECOND:
+               {
+                       FOREACH_CLIENT(IS_PLAYER(it),
+                       {
+                               if (it.team == surv_attackerteam)
+                               {
+                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                               CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
+                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                               INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
+                                       break;
+                               }
+                       });
+                       FOREACH_CLIENT(IS_PLAYER(it),
+                       {
+                               if (it.team == surv_defenderteam)
+                               {
+                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                               CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
+                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                               INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
+                                       break;
+                               }
+                       });
+                       break;
+               }
+       }
+       if (autocvar_g_surv_stealth)
+       {
+               return;
+       }
+       FOREACH_CLIENT(IS_PLAYER(it),
+       {
+               switch (it.team)
+               {
+                       case surv_defenderteam:
+                       {
+                               if (it.surv_role == SURVIVAL_ROLE_PLAYER)
+                               {
+                                       Surv_SetupWaypointSprite(it);
+                               }
+                               break;
+                       }
+               }
+       });
+}
+
+bool Surv_IsEliminated(entity player)
+{
+       switch (player.surv_state)
+       {
+               case SURVIVAL_STATE_NOT_PLAYING:
+               {
+                       return true;
+               }
+               case SURVIVAL_STATE_PLAYING:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               // A hack until proper scoreboard is done.
+                               return true;
+                       }
+                       if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
+                               IS_OBSERVER(player)))
+                       {
+                               return true;
+                       }
+                       return false;
+               }
+       }
+       // Should never reach here
+       return true;
+}
+
+//============================= Hooks ========================================
+
+/// \brief Hook that is called to determine general rules of the game. 
+MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
+{
+       surv_warmup = warmup_stage;
+}
+
+/// \brief Hook that is called to determine if there is a weapon arena.
+MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
+{
+       // Removing any weapon arena.
+       M_ARGV(0, string) = "off";
+}
+
+/// \brief Hook that is called to determine start items of all players.
+MUTATOR_HOOKFUNCTION(surv, SetStartItems)
+{
+       if (autocvar_g_instagib == 1)
+       {
+               return;
+       }
+       start_weapons = WEPSET(Null);
+       warmup_start_weapons = WEPSET(Null);
+}
+
+/// \brief Hook that is called on every frame.
+MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
+{
+       if (game_stopped || !surv_isroundactive)
+       {
+               return;
+       }
+       float roundtime = 0;
+       switch (surv_roundtype)
+       {
+               case SURVIVAL_ROUND_FIRST:
+               {
+                       roundtime = time - surv_roundstarttime;
+                       break;
+               }
+               case SURVIVAL_ROUND_SECOND:
+               {
+                       roundtime = round_handler_GetEndTime() - time;
+                       break;
+               }
+       }
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               it.surv_round_time_stat = roundtime;
+       });
+}
+
+/// \brief Hook that determines which team player can join. This is called
+/// before ClientConnect.
+MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       entity player = M_ARGV(2, entity);
+       LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
+       if (player == NULL)
+       {
+               return SURVIVAL_TEAM_BITS;
+       }
+       if (IS_BOT_CLIENT(player))
+       {
+               int teambits = surv_attackerteambit;
+               if ((player.team == surv_defenderteam) || (surv_numdefenders <
+                       autocvar_g_surv_team_size))
+               {
+                       teambits |= surv_defenderteambit;
+               }
+               M_ARGV(0, float) = teambits;
+               return;
+       }
+       if (surv_type == SURVIVAL_TYPE_COOP)
+       {
+               if (surv_numdefenderhumans < autocvar_g_surv_team_size)
+               {
+                       M_ARGV(0, float) = surv_defenderteambit;
+                       return;
+               }
+               M_ARGV(0, float) = 0;
+               return;
+       }
+       int teambits = 0;
+       if (surv_numattackerhumans < autocvar_g_surv_team_size)
+       {
+               LOG_TRACE("Player can join attackers");
+               teambits |= surv_attackerteambit;
+       }
+       if (surv_numdefenderhumans < autocvar_g_surv_team_size)
+       {
+               LOG_TRACE("Player can join defenders");
+               teambits |= surv_defenderteambit;
+       }
+       M_ARGV(0, float) = teambits;
+       return;
+}
+
+/// \brief Hook that override team counts.
+MUTATOR_HOOKFUNCTION(surv, GetTeamCounts, CBC_ORDER_EXCLUSIVE)
+{
+       return true;
+}
+
+/// \brief Hook that sets the team count.
+MUTATOR_HOOKFUNCTION(surv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
+{
+       float teamnum = M_ARGV(0, float);
+       entity ignore = M_ARGV(1, entity);
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       M_ARGV(2, float) = surv_numattackers;
+                       M_ARGV(3, float) = surv_numattackers - surv_numattackerhumans;
+                       if (ignore.team == surv_attackerteam)
+                       {
+                               --M_ARGV(2, float);
+                               if (IS_BOT_CLIENT(ignore))
+                               {
+                                       --M_ARGV(3, float);
+                               }
+                       }
+                       entity lowestplayer = NULL;
+                       float lowestplayerscore = FLOAT_MAX;
+                       entity lowestbot = NULL;
+                       float lowestbotscore = FLOAT_MAX;
+                       FOREACH_CLIENT((it.team == surv_attackerteam) && (it.surv_role ==
+                               SURVIVAL_ROLE_PLAYER),
+                       {
+                               if (it == ignore)
+                               {
+                                       continue;
+                               }
+                               if (IS_BOT_CLIENT(it))
+                               {
+                                       float tempscore = PlayerScore_Get(it, SP_SCORE);
+                                       if (tempscore < lowestbotscore)
+                                       {
+                                               lowestbot = it;
+                                               lowestbotscore = tempscore;
+                                               continue;
+                                       }
+                               }
+                               float tempscore = PlayerScore_Get(it, SP_SCORE);
+                               if (tempscore < lowestplayerscore)
+                               {
+                                       lowestplayer = it;
+                                       lowestplayerscore = tempscore;
+                               }
+                       });
+                       M_ARGV(4, entity) = lowestplayer;
+                       M_ARGV(5, entity) = lowestbot;
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       M_ARGV(2, float) = surv_numdefenders;
+                       M_ARGV(3, float) = surv_numdefenders - surv_numdefenderhumans;
+                       if (ignore.team == surv_defenderteam)
+                       {
+                               --M_ARGV(2, float);
+                               if (IS_BOT_CLIENT(ignore))
+                               {
+                                       --M_ARGV(3, float);
+                               }
+                       }
+                       entity lowestplayer = NULL;
+                       float lowestplayerscore = FLOAT_MAX;
+                       entity lowestbot = NULL;
+                       float lowestbotscore = FLOAT_MAX;
+                       FOREACH_CLIENT((it.team == surv_defenderteam),
+                       {
+                               if (it == ignore)
+                               {
+                                       continue;
+                               }
+                               if (IS_BOT_CLIENT(it))
+                               {
+                                       float tempscore = PlayerScore_Get(it, SP_SCORE);
+                                       if (tempscore < lowestbotscore)
+                                       {
+                                               lowestbot = it;
+                                               lowestbotscore = tempscore;
+                                               continue;
+                                       }
+                               }
+                               float tempscore = PlayerScore_Get(it, SP_SCORE);
+                               if (tempscore < lowestplayerscore)
+                               {
+                                       lowestplayer = it;
+                                       lowestplayerscore = tempscore;
+                               }
+                       });
+                       M_ARGV(4, entity) = lowestplayer;
+                       M_ARGV(5, entity) = lowestbot;
+                       break;
+               }
+       }
+       return true;
+}
+
+/// \brief Hook that determines the best teams for the player to join.
+MUTATOR_HOOKFUNCTION(surv, FindBestTeams, CBC_ORDER_EXCLUSIVE)
+{
+       if (surv_type == SURVIVAL_TYPE_COOP)
+       {
+               return false;
+       }
+       entity player = M_ARGV(0, entity);
+       if (IS_BOT_CLIENT(player))
+       {
+               return false;
+       }
+       int numattackerhumans = surv_numattackerhumans;
+       int numdefenderhumans = surv_numdefenderhumans;
+       if (player.team == surv_attackerteam)
+       {
+               --numattackerhumans;
+       }
+       else if (player.team == surv_defenderteam)
+       {
+               --numdefenderhumans;
+       }
+       if (numattackerhumans < numdefenderhumans)
+       {
+               M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_attackerteam) - 1);
+               return true;
+       }
+       if (numattackerhumans > numdefenderhumans)
+       {
+               M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_defenderteam) - 1);
+               return true;
+       }
+       M_ARGV(1, float) = SURVIVAL_TEAM_BITS;
+       return true;
+}
+
+/// \brief Hook that is called when player has changed the team.
+MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
+{
+       entity player = M_ARGV(0, entity);
+       int oldteam = M_ARGV(1, float);
+       int newteam = M_ARGV(2, float);
+       string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
+               ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
+       LOG_TRACE(message);
+       DebugPrintToChatAll(message);
+       if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
+       {
+               Surv_RemovePlayerFromAliveList(player, oldteam);
+       }
+       Surv_RemovePlayerFromTeam(player, oldteam);
+       if (Surv_AddPlayerToTeam(player, newteam) == false)
+       {
+               return;
+       }
+       //Surv_CountAlivePlayers();
+       if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
+       {
+               Surv_AddPlayerToAliveList(player, newteam);
+       }
+}
+
+/// \brief Hook that is called when player is about to be killed when changing
+/// teams.
+MUTATOR_HOOKFUNCTION(surv, Player_ChangeTeamKill)
+{
+       entity player = M_ARGV(0, entity);
+       if (player.team != surv_defenderteam)
+       {
+               return false;
+       }
+       if (player.surv_savedplayerstate == NULL)
+       {
+               return false;
+       }
+       Surv_RestorePlayerState(player, player.surv_savedplayerstate);
+       delete(player.surv_savedplayerstate);
+       player.surv_savedplayerstate = NULL;
+       return true;
+}
+
+/// \brief Hook that is called when player is about to be killed as a result of
+/// the kill command or changing teams.
+MUTATOR_HOOKFUNCTION(surv, ClientKill_Now)
+{
+       entity player = M_ARGV(0, entity);
+       if (player.team == surv_defenderteam)
+       {
+               // Deny suicide.
+               return true;
+       }
+}
+
+/// \brief Hook that is called when player connects to the server.
+MUTATOR_HOOKFUNCTION(surv, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
+       player.surv_savedplayermodel = player.playermodel;
+       if (IS_REAL_CLIENT(player))
+       {
+               player.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
+               player.surv_defenders_alive_stat = surv_numdefendersalive;
+               player.redalive_stat = redalive;
+               player.bluealive_stat = bluealive;
+               player.yellowalive_stat = yellowalive;
+               player.pinkalive_stat = pinkalive;
+       }
+       if (player.surv_role == SURVIVAL_ROLE_NONE)
+       {
+               Surv_AddPlayerToTeam(player, player.team);
+       }
+       return true;
+}
+
+/// \brief Hook that is called when player disconnects from the server.
+MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
+{
+    entity player = M_ARGV(0, entity);
+       if (!IS_DEAD(player))
+       {
+               Surv_RemovePlayerFromAliveList(player, player.team);
+       }
+       Surv_RemovePlayerFromTeam(player, player.team);
+       //Surv_CountAlivePlayers();
+}
+
+/// \brief Hook that determines whether player can spawn. It is not called for
+/// players who have joined the team and are dead.
+MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
+       if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
+       {
+               return false;
+       }
+       return !Surv_CanPlayerSpawn(player);
+}
+
+MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
+       if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
+       {
+               LOG_TRACE("Transmuting to observer");
+               TRANSMUTE(Observer, player);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
+       if (player.killindicator_teamchange == -2) // player wants to spectate
+       {
+               LOG_TRACE("killindicator_teamchange == -2");
+               player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
+       }
+       if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
+       {
+               return false;  // allow team reset
+       }
+       return true;  // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(surv, reset_map_global)
+{
+       LOG_TRACE("Survival: reset_map_global");
+       surv_allowed_to_spawn = true;
+       if (surv_roundtype == SURVIVAL_ROUND_FIRST)
+       {
+               FOREACH_CLIENT(IS_REAL_CLIENT(it),
+               {
+                       it.surv_round_time_stat = 0;
+               });
+       }
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, reset_map_players)
+{
+       LOG_TRACE("Survival: reset_map_players");
+       surv_numattackersalive = 0;
+       surv_numdefendersalive = 0;
+       if (surv_warmup)
+       {
+               surv_warmup = false;
+       }
+       else if (surv_type == SURVIVAL_TYPE_VERSUS)
+       {
+               Surv_SwapTeams();
+       }
+       FOREACH_CLIENT(true,
+       {
+               it.killcount = 0;
+               if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
+               {
+                       it.team = -1;
+                       it.surv_state = SURVIVAL_STATE_PLAYING;
+               }
+               if (it.surv_state == SURVIVAL_STATE_PLAYING)
+               {
+                       TRANSMUTE(Player, it);
+                       it.surv_state = SURVIVAL_STATE_PLAYING;
+                       PutClientInServer(it);
+               }
+       });
+       bot_relinkplayerlist();
+       return true;
+}
+
+/// \brief Hook that is called when player spawns.
+MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
+       player.surv_state = SURVIVAL_STATE_PLAYING;
+       Surv_DeterminePlayerModel(player);
+       if (player.surv_savedplayerstate != NULL)
+       {
+               Surv_RestorePlayerState(player, player.surv_savedplayerstate);
+               delete(player.surv_savedplayerstate);
+               player.surv_savedplayerstate = NULL;
+       }
+       else
+       {
+               PlayerTemplateHook_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
+       }
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_PLAYER)
+                       {
+                               Send_Notification(NOTIF_ONE, player, MSG_CENTER,
+                                       CENTER_ASSAULT_ATTACKING);
+                       }
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       Send_Notification(NOTIF_ONE, player, MSG_CENTER,
+                               CENTER_ASSAULT_DEFENDING);
+                       break;
+               }
+       }
+       //Surv_CountAlivePlayers();
+       Surv_AddPlayerToAliveList(player, player.team);
+}
+
+/// \brief UGLY HACK. This is called every frame to keep player model correct.
+MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
+{
+       entity player = M_ARGV(2, entity);
+       M_ARGV(0, string) = player.surv_playermodel;
+}
+
+/// \brief Hook which is called when the player tries to throw their weapon.
+MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
+{
+       entity player = M_ARGV(0, entity);
+       return PlayerTemplateHook_ForbidThrowCurrentWeapon(
+               Surv_GetPlayerTemplate(player));
+}
+
+/// \brief Hook that is called every frame to determine how player health should
+/// regenerate.
+MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
+{
+       entity player = M_ARGV(0, entity);
+       if (player.team == surv_defenderteam)
+       {
+               return true;
+       }
+       return PlayerTemplateHook_PlayerRegen(player,
+               Surv_GetPlayerTemplate(player));
+}
+
+/// \brief Hook that is called to determine if balance messages will appear.
+MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
+{
+       return true;
+}
+
+/// \brief Hook that is called when player touches an item.
+MUTATOR_HOOKFUNCTION(surv, ItemTouch)
+{
+       entity item = M_ARGV(0, entity);
+       entity player = M_ARGV(1, entity);
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       return PlayerTemplateHook_ItemTouch(player, item,
+                               Surv_GetPlayerTemplate(player));
+               }
+               case surv_defenderteam:
+               {
+                       switch (item.classname)
+                       {
+                               case "item_strength":
+                               {
+                                       W_GiveWeapon(player, WEP_HMG.m_id);
+                                       player.superweapons_finished = max(
+                                               player.superweapons_finished, time) +
+                                               autocvar_g_balance_superweapons_time;
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "item_invincible":
+                               {
+                                       W_GiveWeapon(player, WEP_RPC.m_id);
+                                       player.superweapons_finished = max(
+                                               player.superweapons_finished, time) +
+                                               autocvar_g_balance_superweapons_time;
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return PlayerTemplateHook_ItemTouch(player, item,
+                                               Surv_GetPlayerTemplate(player));
+                               }
+                       }
+                       DebugPrintToChat(player, item.classname);
+                       return MUT_ITEMTOUCH_RETURN;
+               }
+       }
+       return MUT_ITEMTOUCH_CONTINUE;
+}
+
+/// \brief Hook which is called when the damage amount must be determined.
+MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float deathtype = M_ARGV(3, float);
+       float damage = M_ARGV(4, float);
+       M_ARGV(4, float) = PlayerTemplateHook_Damage_Calculate(frag_attacker,
+               Surv_GetPlayerTemplate(frag_attacker), frag_target,
+               Surv_GetPlayerTemplate(frag_target), deathtype, damage);
+}
+
+/// \brief Hook which is called when the player was damaged.
+MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
+{
+       entity target = M_ARGV(1, entity);
+       if (target.team != surv_defenderteam)
+       {
+               return;
+       }
+       Surv_UpdateDefenderHealthStat();
+       entity attacker = M_ARGV(0, entity);
+       if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
+               SURVIVAL_ROLE_PLAYER))
+       {
+               float health = M_ARGV(2, float);
+               float armor = M_ARGV(3, float);
+               float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
+               GameRules_scoring_add(attacker, SCORE, score);
+       }
+       if (autocvar_g_surv_stealth)
+       {
+               return;
+       }
+       if (GetResourceAmount(target, RESOURCE_HEALTH) < 1)
+       {
+               WaypointSprite_Kill(target.surv_attack_sprite);
+               WaypointSprite_Kill(target.surv_defend_sprite);
+       }
+       else
+       {
+               Surv_UpdateWaypointSpriteHealth(target);
+       }
+}
+
+/// \brief Hook which is called when the player dies.
+MUTATOR_HOOKFUNCTION(surv, PlayerDies, CBC_ORDER_FIRST)
+{
+       //DebugPrintToChatAll("PlayerDies");
+       entity attacker = M_ARGV(1, entity);
+       entity victim = M_ARGV(2, entity);
+       PlayerTemplateHook_PlayerDies(victim, Surv_GetPlayerTemplate(victim));
+       if ((attacker.team == surv_defenderteam) &&
+               (victim.team == surv_attackerteam))
+       {
+               switch (victim.surv_role)
+               {
+                       case SURVIVAL_ROLE_PLAYER:
+                       {
+                               GiveResource(attacker, RESOURCE_HEALTH,
+                                       autocvar_g_surv_defender_attacker_frag_health);
+                               GiveResource(attacker, RESOURCE_ARMOR,
+                                       autocvar_g_surv_defender_attacker_frag_armor);
+                               GiveResource(attacker, RESOURCE_SHELLS,
+                                       autocvar_g_surv_defender_attacker_frag_shells);
+                               GiveResource(attacker, RESOURCE_BULLETS,
+                                       autocvar_g_surv_defender_attacker_frag_bullets);
+                               GiveResource(attacker, RESOURCE_ROCKETS,
+                                       autocvar_g_surv_defender_attacker_frag_rockets);
+                               GiveResource(attacker, RESOURCE_CELLS,
+                                       autocvar_g_surv_defender_attacker_frag_cells);
+                               GiveResource(attacker, RESOURCE_PLASMA,
+                                       autocvar_g_surv_defender_attacker_frag_plasma);
+                               GiveResource(attacker, RESOURCE_FUEL,
+                                       autocvar_g_surv_defender_attacker_frag_fuel);
+                               break;
+                       }
+                       case SURVIVAL_ROLE_CANNON_FODDER:
+                       {
+                               GiveResource(attacker, RESOURCE_HEALTH,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_health);
+                               GiveResource(attacker, RESOURCE_ARMOR,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_armor);
+                               GiveResource(attacker, RESOURCE_SHELLS,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_shells);
+                               GiveResource(attacker, RESOURCE_BULLETS,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_bullets);
+                               GiveResource(attacker, RESOURCE_ROCKETS,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_rockets);
+                               GiveResource(attacker, RESOURCE_CELLS,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_cells);
+                               GiveResource(attacker, RESOURCE_PLASMA,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_plasma);
+                               GiveResource(attacker, RESOURCE_FUEL,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_fuel);
+                               break;
+                       }
+               }
+       }
+       if (!Surv_CanPlayerSpawn(victim))
+       {
+               victim.respawn_flags = RESPAWN_SILENT;
+               if (IS_BOT_CLIENT(victim))
+               {
+                       bot_clear(victim);
+               }
+       }
+       return true;
+}
+
+/// \brief Hook which is called after the player died.
+MUTATOR_HOOKFUNCTION(surv, PlayerDied)
+{
+       //DebugPrintToChatAll("PlayerDied");
+       entity player = M_ARGV(0, entity);
+       Surv_RemovePlayerFromAliveList(player, player.team);
+       //Surv_CountAlivePlayers();
+}
+
+/// \brief Hook which is called when player has scored a frag.
+MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       if (surv_type == SURVIVAL_TYPE_COOP)
+       {
+               return true;
+       }
+       entity attacker = M_ARGV(0, entity);
+       if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
+               SURVIVAL_ROLE_CANNON_FODDER))
+       {
+               M_ARGV(2, float) = 0;
+               return true;
+       }
+       entity target = M_ARGV(1, entity);
+       if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
+               surv_defenderteam))
+       {
+               M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
+       }
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, SpectateSet)
+{
+       entity client = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+
+       if (!autocvar_g_surv_spectate_enemies &&
+               (client.surv_state == SURVIVAL_STATE_PLAYING) &&
+               DIFF_TEAM(targ, client))
+       {
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(surv, SpectateNext)
+{
+       entity client = M_ARGV(0, entity);
+
+       if (!autocvar_g_surv_spectate_enemies &&
+               (client.surv_state == SURVIVAL_STATE_PLAYING))
+       {
+               entity targ = M_ARGV(1, entity);
+               M_ARGV(1, entity) = CA_SpectateNext(client, targ);
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(surv, SpectatePrev)
+{
+       entity client = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+       entity first = M_ARGV(2, entity);
+
+       if (!autocvar_g_surv_spectate_enemies &&
+               (client.surv_state == SURVIVAL_STATE_PLAYING))
+       {
+               do
+               {
+                       targ = targ.chain;
+               }
+               while (targ && DIFF_TEAM(targ, client));
+               if (!targ)
+               {
+                       for (targ = first; targ && DIFF_TEAM(targ, client);
+                               targ = targ.chain);
+
+                       if (targ == client.enemy)
+                       {
+                               return MUT_SPECPREV_RETURN;
+                       }
+               }
+       }
+       M_ARGV(1, entity) = targ;
+       return MUT_SPECPREV_FOUND;
+}
+
+/// \brief I'm not sure exactly what this function does but it is very
+/// important. Without it bots are completely broken. Is it a hack? Of course.
+MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
+               {
+                       ++M_ARGV(0, int);
+               }
+               ++M_ARGV(1, int);
+       });
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
+{
+       // Don't announce remaining frags
+       return false;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh
new file mode 100644 (file)
index 0000000..a381e07
--- /dev/null
@@ -0,0 +1,15 @@
+#pragma once
+
+/// \brief Initializes global data for the gametype.
+/// \return No return.
+void Surv_Initialize();
+
+REGISTER_MUTATOR(surv, false)
+{
+       MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               Surv_Initialize();
+       }
+       return 0;
+}
index a816108409dfe62bd2f923bd76160e554cf01080..6835f5d560b9a325a96e671c21ec17348915ff2a 100644 (file)
@@ -11,5 +11,4 @@
 #include <server/mutators/mutator/gamemode_keyhunt.qc>
 #include <server/mutators/mutator/gamemode_lms.qc>
 #include <server/mutators/mutator/gamemode_race.qc>
-#include <server/mutators/mutator/gamemode_survival.qc>
 #include <server/mutators/mutator/gamemode_tdm.qc>
index 0b4a9bbceeaecf3c5cfbec5ab657c5ad7cea80e3..aef0b332abbaa9a3c5c27560d3e0bcca21297005 100644 (file)
@@ -11,5 +11,4 @@
 #include <server/mutators/mutator/gamemode_keyhunt.qh>
 #include <server/mutators/mutator/gamemode_lms.qh>
 #include <server/mutators/mutator/gamemode_race.qh>
-#include <server/mutators/mutator/gamemode_survival.qh>
 #include <server/mutators/mutator/gamemode_tdm.qh>
diff --git a/qcsrc/server/mutators/mutator/gamemode_survival.qc b/qcsrc/server/mutators/mutator/gamemode_survival.qc
deleted file mode 100644 (file)
index e7d8105..0000000
+++ /dev/null
@@ -1,2251 +0,0 @@
-#include "gamemode_survival.qh"
-
-#include <common/mutators/mutator/overkill/hmg.qh>
-#include <common/mutators/mutator/overkill/rpc.qh>
-
-//============================ Constants ======================================
-
-const int SURVIVAL_TEAM_1_BIT = BIT(0); ///< Bitmask of the first team.
-const int SURVIVAL_TEAM_2_BIT = BIT(1); ///< Bitmask of the second team.
-
-/// \brief Used when bitfield team count is requested.
-const int SURVIVAL_TEAM_BITS = 3;
-
-enum
-{
-       SURVIVAL_TYPE_COOP, ///< All humans are on the defender team.
-       SURVIVAL_TYPE_VERSUS ///< Humans take turns between attackers and defenders.
-};
-
-const string SURVIVAL_TYPE_COOP_VALUE = "coop"; ///< Cvar value for coop.
-const string SURVIVAL_TYPE_VERSUS_VALUE = "versus"; ///< Cvar value for versus.
-
-enum
-{
-       /// \brief First round where there is timelimit set by the server.
-       SURVIVAL_ROUND_FIRST,
-       /// \brief Second round where defender team tries to survive for the first
-       /// round's time.
-       SURVIVAL_ROUND_SECOND
-};
-
-enum
-{
-       /// \brief Player is spectating and has no intention of playing.
-       SURVIVAL_STATE_NOT_PLAYING,
-       /// \brief Player is playing the game.
-       SURVIVAL_STATE_PLAYING = 1
-};
-
-enum
-{
-       SURVIVAL_ROLE_NONE, ///< Player is not playing.
-       SURVIVAL_ROLE_PLAYER, ///< Player is an attacker or defender.
-       SURVIVAL_ROLE_CANNON_FODDER ///< Player is a cannon fodder.
-};
-
-SOUND(SURV_3_FRAGS_LEFT, "announcer/default/3fragsleft");
-SOUND(SURV_2_FRAGS_LEFT, "announcer/default/2fragsleft");
-SOUND(SURV_1_FRAG_LEFT, "announcer/default/1fragleft");
-
-SOUND(SURV_RED_SCORES, "ctf/red_capture");
-SOUND(SURV_BLUE_SCORES, "ctf/blue_capture");
-
-//======================= Global variables ====================================
-
-float autocvar_g_surv_warmup; ///< Warmup time in seconds.
-float autocvar_g_surv_round_timelimit; ///< First round time limit in seconds.
-
-int autocvar_g_surv_point_limit; ///< Maximum number of points.
-int autocvar_g_surv_point_leadlimit; ///< Maximum lead of a single team.
-
-/// \brief How much players are allowed in teams (excluding cannon fodder).
-int autocvar_g_surv_team_size;
-/// \brief If set, defenders will not be shown on the radar.
-int autocvar_g_surv_stealth;
-/// \brief Whether to allow spectating enemy players while dead.
-bool autocvar_g_surv_spectate_enemies;
-
-/// \brief Whether to force overkill player models for attackers.
-int autocvar_g_surv_attacker_force_overkill_models;
-/// \brief Whether to force overkill player models for defenders.
-int autocvar_g_surv_defender_force_overkill_models;
-/// \brief Whether to force overkill player models for cannon fodder.
-int autocvar_g_surv_cannon_fodder_force_overkill_models;
-
-/// \brief How much score attackers gain per 1 point of damage.
-float autocvar_g_surv_attacker_damage_score;
-
-/// \brief How much score attackers get for fragging defenders.
-float autocvar_g_surv_attacker_frag_score;
-
-/// \brief How much health do defenders get when they frag an attacker.
-int autocvar_g_surv_defender_attacker_frag_health;
-/// \brief How much armor do defenders get when they frag an attacker.
-int autocvar_g_surv_defender_attacker_frag_armor;
-/// \brief How many shells do defenders get when they frag an attacker.
-int autocvar_g_surv_defender_attacker_frag_shells;
-/// \brief How many bullets do defenders get when they frag an attacker.
-int autocvar_g_surv_defender_attacker_frag_bullets;
-/// \brief How many rockets do defenders get when they frag an attacker.
-int autocvar_g_surv_defender_attacker_frag_rockets;
-/// \brief How many cells do defenders get when they frag an attacker.
-int autocvar_g_surv_defender_attacker_frag_cells;
-/// \brief How much plasma do defenders get when they frag an attacker.
-int autocvar_g_surv_defender_attacker_frag_plasma;
-/// \brief How much fuel do defenders get when they frag an attacker.
-int autocvar_g_surv_defender_attacker_frag_fuel;
-/// \brief How much health do defenders get when they frag cannon fodder.
-int autocvar_g_surv_defender_cannon_fodder_frag_health;
-/// \brief How much armor do defenders get when they frag cannon fodder.
-int autocvar_g_surv_defender_cannon_fodder_frag_armor;
-/// \brief How many shells do defenders get when they frag cannon fodder.
-int autocvar_g_surv_defender_cannon_fodder_frag_shells;
-/// \brief How many bullets do defenders get when they frag cannon fodder.
-int autocvar_g_surv_defender_cannon_fodder_frag_bullets;
-/// \brief How many rockets do defenders get when they frag cannon fodder.
-int autocvar_g_surv_defender_cannon_fodder_frag_rockets;
-/// \brief How many cells do defenders get when they frag cannon fodder.
-int autocvar_g_surv_defender_cannon_fodder_frag_cells;
-/// \brief How much plasma do defenders get when they frag cannon fodder.
-int autocvar_g_surv_defender_cannon_fodder_frag_plasma;
-/// \brief How much fuel do defenders get when they frag cannon fodder.
-int autocvar_g_surv_defender_cannon_fodder_frag_fuel;
-
-/// \brief A stat that is used to track the time left in the round.
-.float surv_round_time_stat = _STAT(SURV_ROUND_TIME);
-/// \brief A stat that is used to track defender team.
-.int surv_defender_team_stat = _STAT(SURV_DEFENDER_TEAM);
-/// \brief A stat that is used to track number of defenders alive.
-.int surv_defenders_alive_stat = _STAT(SURV_DEFENDERS_ALIVE);
-/// \brief A stat that is used to track the total health of defenders.
-.float surv_defender_health_stat = _STAT(SURV_DEFENDER_HEALTH);
-
-/// \brief Holds the state of the player. See SURVIVAL_STATE constants.
-.int surv_state;
-/// \brief Holds the role of the player. See SURVIVAL_ROLE constants.
-.int surv_role;
-.string surv_savedplayermodel; ///< Initial player model.
-/// \brief Player state used during replacement of bot player with real player.
-.entity surv_savedplayerstate;
-.string surv_playermodel; ///< Player model forced by the game.
-
-.entity surv_attack_sprite; ///< Holds the sprite telling attackers to attack.
-.entity surv_defend_sprite; ///< Holds the sprite telling defenders to defend.
-
-int surv_type; ///< Holds the type of survival. See SURVIVAL_TYPE constants.
-bool surv_warmup; ///< Holds whether warmup is active.
-/// \brief Holds the type of the current round. See SURVIVAL_ROUND constants.
-int surv_roundtype;
-bool surv_isroundactive; ///< Holds whether the round is active.
-float surv_roundstarttime; ///< Holds the time of the round start.
-/// \brief Holds the time needed to survive in the second round.
-float surv_timetobeat;
-
-int surv_attackerteam; ///< Holds the attacker team.
-int surv_defenderteam; ///< Holds the defender team.
-
-int surv_attackerteambit; ///< Hols the attacker team bitmask.
-int surv_defenderteambit; ///< Holds the defender team bitmask.
-
-int surv_numattackers; ///< Holds the number of players in attacker team.
-int surv_numdefenders; ///< Holds the number of players in defender team.
-
-/// \brief Holds the number of humans in attacker team.
-int surv_numattackerhumans;
-/// \brief Holds the number of humans in defender team.
-int surv_numdefenderhumans;
-
-/// \brief Holds the number of attacker players that are alive.
-int surv_numattackersalive;
-/// \brief Holds the number of defender players that are alive.
-int surv_numdefendersalive;
-
-bool surv_autobalance; ///< Holds whether autobalance is active.
-bool surv_announcefrags; ///< Holds whether remaining frags must be announced.
-bool surv_allowed_to_spawn; ///< Holds whether players are allowed to spawn.
-
-//====================== Forward declarations =================================
-
-/// \brief Determines whether the round can start.
-/// \return True if the round can start, false otherwise.
-bool Surv_CanRoundStart();
-
-/// \brief Determines whether the round can end.
-/// \return True if the round can end, false otherwise.
-bool Surv_CanRoundEnd();
-
-/// \brief Called when the round starts.
-/// \return No return.
-void Surv_RoundStart();
-
-/// \brief Returns whether player has been eliminated.
-/// \param[in] player Player to check.
-/// \return True if player is eliminated, false otherwise.
-bool Surv_IsEliminated(entity player);
-
-/// \brief Updates stats of team count on HUD.
-/// \return No return.
-void Surv_UpdateTeamStats();
-
-/// \brief Updates stats of alive players on HUD.
-/// \return No return.
-void Surv_UpdateAliveStats();
-
-/// \brief Updates defender health on the HUD.
-/// \return No return.
-void Surv_UpdateDefenderHealthStat();
-
-/// \brief Updates the health of defender sprite.
-/// \param[in,out] player Player that has the sprite.
-/// \return No return.
-void Surv_UpdateWaypointSpriteHealth(entity player);
-
-//========================= Free functions ====================================
-
-void Surv_Initialize()
-{
-       switch (cvar_string("g_surv_type"))
-       {
-               case SURVIVAL_TYPE_COOP_VALUE:
-               {
-                       surv_type = SURVIVAL_TYPE_COOP;
-                       break;
-               }
-               case SURVIVAL_TYPE_VERSUS_VALUE:
-               {
-                       surv_type = SURVIVAL_TYPE_VERSUS;
-                       break;
-               }
-               default:
-               {
-                       error("Invalid survival type.");
-               }
-       }
-       surv_roundtype = SURVIVAL_ROUND_FIRST;
-       surv_isroundactive = false;
-       surv_roundstarttime = time;
-       surv_timetobeat = autocvar_g_surv_round_timelimit;
-       if (random() < 0.5)
-       {
-               surv_attackerteam = NUM_TEAM_1;
-               surv_defenderteam = NUM_TEAM_2;
-               surv_attackerteambit = SURVIVAL_TEAM_1_BIT;
-               surv_defenderteambit = SURVIVAL_TEAM_2_BIT;
-       }
-       else
-       {
-               surv_attackerteam = NUM_TEAM_2;
-               surv_defenderteam = NUM_TEAM_1;
-               surv_attackerteambit = SURVIVAL_TEAM_2_BIT;
-               surv_defenderteambit = SURVIVAL_TEAM_1_BIT;
-       }
-       surv_numattackers = 0;
-       surv_numdefenders = 0;
-       surv_numattackerhumans = 0;
-       surv_numdefenderhumans = 0;
-       surv_numattackersalive = 0;
-       surv_numdefendersalive = 0;
-       surv_autobalance = true;
-       surv_announcefrags = true;
-       surv_allowed_to_spawn = true;
-       precache_all_playermodels("models/ok_player/*.dpm");
-       ScoreRules_basics(SURVIVAL_TEAM_BITS, SFL_SORT_PRIO_PRIMARY, 0, true);
-       ScoreInfo_SetLabel_TeamScore(1, "rounds", SFL_SORT_PRIO_PRIMARY);
-       ScoreRules_basics_end();
-       round_handler_Spawn(Surv_CanRoundStart, Surv_CanRoundEnd, Surv_RoundStart);
-       round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
-       EliminatedPlayers_Init(Surv_IsEliminated);
-       GameRules_teams(true);
-       GameRules_limit_score(autocvar_g_surv_point_limit);
-       GameRules_limit_lead(autocvar_g_surv_point_leadlimit);
-}
-
-/// \brief Returns the name of the template of the given player.
-/// \param[in] player Player to inspect.
-/// \return Name of the template of the given player.
-string Surv_GetPlayerTemplate(entity player)
-{
-       switch (player.team)
-       {
-               case surv_attackerteam:
-               {
-                       switch (player.surv_role)
-                       {
-                               case SURVIVAL_ROLE_NONE:
-                               case SURVIVAL_ROLE_PLAYER:
-                               {
-                                       return cvar_string("g_surv_attacker_template");
-                               }
-                               case SURVIVAL_ROLE_CANNON_FODDER:
-                               {
-                                       return cvar_string("g_surv_cannon_fodder_template");
-                               }
-                       }
-               }
-               case surv_defenderteam:
-               {
-                       return cvar_string("g_surv_defender_template");
-               }
-       }
-       return "default";
-}
-
-/// \brief Saves the player state. Used to seamlessly swap bots with humans.
-/// \param[in] player Player to save the state of.
-/// \return Entity containing the player state.
-entity Surv_SavePlayerState(entity player)
-{
-       entity state = spawn();
-       state.origin = player.origin;
-       state.velocity = player.velocity;
-       state.angles = player.angles;
-       state.health = player.health;
-       state.armorvalue = player.armorvalue;
-       state.ammo_shells = player.ammo_shells;
-       state.ammo_nails = player.ammo_nails;
-       state.ammo_rockets = player.ammo_rockets;
-       state.ammo_cells = player.ammo_cells;
-       state.weapons = player.weapons;
-       state.items = player.items;
-       state.superweapons_finished = player.superweapons_finished;
-       return state;
-}
-
-/// \brief Restores a saved player state.
-/// \param[in] player Player to restore the state of.
-/// \param[in] st State to restore.
-/// \return No return.
-void Surv_RestorePlayerState(entity player, entity st)
-{
-       player.origin = st.origin;
-       player.velocity = st.velocity;
-       player.angles = st.angles;
-       player.health = st.health;
-       player.armorvalue = st.armorvalue;
-       player.ammo_shells = st.ammo_shells;
-       player.ammo_nails = st.ammo_nails;
-       player.ammo_rockets = st.ammo_rockets;
-       player.ammo_cells = st.ammo_cells;
-       player.weapons = st.weapons;
-       player.items = st.items;
-       player.superweapons_finished = st.superweapons_finished;
-}
-
-/// \brief Returns the attacker with the lowest score.
-/// \param[in] bot Whether to search only for bots.
-/// \return Attacker with the lowest score or NULL if not found.
-entity Surv_FindLowestAttacker(bool bot)
-{
-       entity player = NULL;
-       float score = FLOAT_MAX;
-       FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
-       {
-               if ((it.team == surv_attackerteam) && (it.surv_role ==
-                       SURVIVAL_ROLE_PLAYER))
-               {
-                       float tempscore = PlayerScore_Get(it, SP_SCORE);
-                       if (tempscore < score)
-                       {
-                               player = it;
-                               score = tempscore;
-                       }
-               }
-       });
-       return player;
-}
-
-/// \brief Returns the defender with the lowest score.
-/// \param[in] bot Whether to search only for bots.
-/// \param[in] alive Whether to search only for alive players.
-/// \return Defender with the lowest score or NULL if not found.
-entity Surv_FindLowestDefender(bool bot, bool alive)
-{
-       entity player = NULL;
-       float score = FLOAT_MAX;
-       FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
-       {
-               if ((it.team == surv_defenderteam) && (alive ? !IS_DEAD(it) : true))
-               {
-                       float tempscore = PlayerScore_Get(it, SP_SCORE);
-                       if (tempscore < score)
-                       {
-                               player = it;
-                               score = tempscore;
-                       }
-               }
-       });
-       return player;
-}
-
-/// \brief Returns the cannon fodder.
-/// \return Cannon fodder or NULL if not found.
-entity Surv_FindCannonFodder()
-{
-       FOREACH_CLIENT(IS_BOT_CLIENT(it),
-       {
-               if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
-               {
-                       return it;
-               }
-       });
-       return NULL;
-}
-
-/// \brief Changes the number of players in a team.
-/// \param[in] teamnum Team to adjust.
-/// \param[in] delta Amount to adjust by.
-/// \return No return.
-void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
-{
-       switch (teamnum)
-       {
-               case surv_attackerteam:
-               {
-                       surv_numattackers += delta;
-                       LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
-                               " was = ", ftos(surv_numattackers - delta));
-                       Surv_UpdateTeamStats();
-                       return;
-               }
-               case surv_defenderteam:
-               {
-                       surv_numdefenders += delta;
-                       LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
-                               " was = ", ftos(surv_numdefenders - delta));
-                       Surv_UpdateTeamStats();
-                       return;
-               }
-       }
-}
-
-/// \brief Changes the number of alive players in a team.
-/// \param[in] teamnum Team to adjust.
-/// \param[in] delta Amount to adjust by.
-/// \return No return.
-void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
-{
-       switch (teamnum)
-       {
-               case surv_attackerteam:
-               {
-                       surv_numattackersalive += delta;
-                       LOG_TRACE("Number of alive attackers = ", ftos(
-                               surv_numattackersalive), " was = ", ftos(surv_numattackersalive
-                               - delta));
-                       break;
-               }
-               case surv_defenderteam:
-               {
-                       surv_numdefendersalive += delta;
-                       LOG_TRACE("Number of alive defenders = ", ftos(
-                               surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
-                               - delta));
-                       break;
-               }
-       }
-       Surv_UpdateAliveStats();
-       eliminatedPlayers.SendFlags |= 1;
-}
-
-/// \brief Sets the player role.
-/// \param[in,out] player Player to adjust.
-/// \param[in] role Role to set.
-/// \return No return.
-void Surv_SetPlayerRole(entity player, int role)
-{
-       if (player.surv_role == role)
-       {
-               return;
-       }
-       player.surv_role = role;
-       switch (role)
-       {
-               case SURVIVAL_ROLE_NONE:
-               {
-                       LOG_TRACE(player.netname, " now has no role.");
-                       break;
-               }
-               case SURVIVAL_ROLE_PLAYER:
-               {
-                       LOG_TRACE(player.netname, " is now a player.");
-                       break;
-               }
-               case SURVIVAL_ROLE_CANNON_FODDER:
-               {
-                       LOG_TRACE(player.netname, " is now a cannon fodder.");
-                       break;
-               }
-       }
-}
-
-/// \brief Adds player to team. Handles bookkeeping information.
-/// \param[in] player Player to add.
-/// \param[in] teamnum Team to add to.
-/// \return True on success, false otherwise.
-bool Surv_AddPlayerToTeam(entity player, int teamnum)
-{
-       LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
-       switch (teamnum)
-       {
-               case surv_attackerteam:
-               {
-                       LOG_TRACE("Attacker team");
-                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
-                       {
-                               LOG_TRACE("Cannon fodder is switching team");
-                               return true;
-                       }
-                       if (IS_BOT_CLIENT(player))
-                       {
-                               LOG_TRACE("Client is bot");
-                               LOG_TRACE("Attackers = ", ftos(surv_numattackers));
-                               if (surv_numattackers < autocvar_g_surv_team_size)
-                               {
-                                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
-                                       Surv_ChangeNumberOfPlayers(teamnum, +1);
-                                       return true;
-                               }
-                               Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
-                               return true;
-                       }
-                       LOG_TRACE("Client is not a bot");
-                       LOG_TRACE("Attackers = ", ftos(surv_numattackers));
-                       if (surv_numattackers >= autocvar_g_surv_team_size)
-                       {
-                               LOG_TRACE("Removing bot");
-                               // Remove bot to make space for human.
-                               entity bot = Surv_FindLowestAttacker(true);
-                               if (bot == NULL)
-                               {
-                                       LOG_TRACE("No valid bot to remove");
-                                       // No space in team, denying team change.
-                                       TRANSMUTE(Spectator, player);
-                                       return false;
-                               }
-                               LOG_TRACE("Changing ", bot.netname,
-                                       " from attacker to cannon fodder.");
-                               Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
-                               if (!IS_DEAD(bot))
-                               {
-                                       Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
-                               }
-                               Surv_ChangeNumberOfPlayers(teamnum, -1);
-                               LOG_TRACE("Removed bot");
-                       }
-                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
-                       Surv_ChangeNumberOfPlayers(teamnum, +1);
-                       ++surv_numattackerhumans;                       
-                       LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
-                       if ((surv_autobalance == false) || (surv_numattackers -
-                               surv_numdefenders) < 2)
-                       {
-                               return true;
-                       }
-                       entity lowestplayer = Surv_FindLowestAttacker(true);
-                       if (lowestplayer != NULL)
-                       {
-                               bool savedautobalance = surv_autobalance;
-                               surv_autobalance = false;
-                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
-                               surv_autobalance = savedautobalance;
-                               return true;
-                       }
-                       lowestplayer = Surv_FindLowestAttacker(false);
-                       if (lowestplayer != NULL)
-                       {
-                               bool savedautobalance = surv_autobalance;
-                               surv_autobalance = false;
-                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
-                               surv_autobalance = savedautobalance;
-                       }
-                       return true;
-               }
-               case surv_defenderteam:
-               {
-                       LOG_TRACE("Defender team");
-                       if (IS_BOT_CLIENT(player))
-                       {
-                               LOG_TRACE("Client is bot");
-                               LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
-                               if (surv_numdefenders < autocvar_g_surv_team_size)
-                               {
-                                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
-                                       Surv_ChangeNumberOfPlayers(teamnum, +1);
-                                       return true;
-                               }
-                               LOG_TRACE("No space for defender, switching to attacker");
-                               SetPlayerTeamSimple(player, surv_attackerteam);
-                               return false;
-                       }
-                       LOG_TRACE("Client is not a bot");
-                       LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
-                       if (surv_numdefenders >= autocvar_g_surv_team_size)
-                       {
-                               LOG_TRACE("Removing bot");
-                               // Remove bot to make space for human.
-                               entity bot = Surv_FindLowestDefender(true, true);
-                               if (bot == NULL)
-                               {
-                                       bot = Surv_FindLowestDefender(true, false);
-                               }
-                               if (bot == NULL)
-                               {
-                                       LOG_TRACE("No valid bot to remove");
-                                       // No space in team, denying team change.
-                                       TRANSMUTE(Spectator, player);
-                                       return false;
-                               }
-                               LOG_TRACE("Changing ", bot.netname,
-                                       " from defender to cannon fodder.");
-                               if (!IS_DEAD(bot))
-                               {
-                                       player.surv_savedplayerstate = Surv_SavePlayerState(bot);
-                               }
-                               bool savedautobalance = surv_autobalance;
-                               surv_autobalance = false;
-                               surv_announcefrags = false;
-                               SetPlayerTeamSimple(bot, surv_attackerteam);
-                               surv_autobalance = savedautobalance;
-                               surv_announcefrags = true;
-                               LOG_TRACE("Removed bot");
-                       }
-                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
-                       Surv_ChangeNumberOfPlayers(teamnum, +1);
-                       ++surv_numdefenderhumans;
-                       LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
-                       if ((surv_autobalance == false) || (surv_numdefenders -
-                               surv_numattackers) < 2)
-                       {
-                               return true;
-                       }
-                       entity lowestplayer = Surv_FindLowestDefender(true, false);
-                       if (lowestplayer != NULL)
-                       {
-                               bool savedautobalance = surv_autobalance;
-                               surv_autobalance = false;
-                               SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
-                               surv_autobalance = savedautobalance;
-                               return true;
-                       }
-                       lowestplayer = Surv_FindLowestDefender(false, false);
-                       if (lowestplayer != NULL)
-                       {
-                               bool savedautobalance = surv_autobalance;
-                               surv_autobalance = false;
-                               SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
-                               surv_autobalance = savedautobalance;
-                       }
-                       return true;
-               }
-               case -1:
-               {
-                       LOG_TRACE("Spectator team");
-                       player.surv_role = SURVIVAL_ROLE_NONE;                  
-                       return false;
-               }
-       }
-       LOG_TRACE("Invalid team");
-       player.surv_role = SURVIVAL_ROLE_NONE;
-       return false;
-}
-
-/// \brief Removes player from team. Handles bookkeeping information.
-/// \param[in] player Player to remove.
-/// \param[in] Team to remove from.
-/// \return No return.
-void Surv_RemovePlayerFromTeam(entity player, int teamnum)
-{
-       LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
-       switch (teamnum)
-       {
-               case surv_attackerteam:
-               {
-                       LOG_TRACE("Attacker team");
-                       if (player.surv_role == SURVIVAL_ROLE_NONE)
-                       {
-                               string message = strcat("RemovePlayerFromTeam: ",
-                                       player.netname, " has invalid role.");
-                               DebugPrintToChatAll(message);
-                               return;
-                       }
-                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
-                       {
-                               Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
-                               return;
-                       }
-                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
-                       Surv_ChangeNumberOfPlayers(teamnum, -1);
-                       if (!IS_BOT_CLIENT(player))
-                       {
-                               --surv_numattackerhumans;
-                       }
-                       if ((surv_autobalance == false) || (surv_numattackers >=
-                               surv_numdefenders))
-                       {
-                               return;
-                       }
-                       // Add bot to keep teams balanced.
-                       entity lowestplayer = Surv_FindCannonFodder();
-                       if (lowestplayer != NULL)
-                       {
-                               LOG_TRACE("Changing ", lowestplayer.netname,
-                                       " from cannon fodder to attacker.");
-                               Surv_SetPlayerRole(lowestplayer, SURVIVAL_ROLE_PLAYER);
-                               Surv_ChangeNumberOfPlayers(teamnum, +1);
-                               if (!IS_DEAD(lowestplayer))
-                               {
-                                       Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
-                               }
-                               return;
-                       }
-                       lowestplayer = Surv_FindLowestDefender(true, false);
-                       if (lowestplayer != NULL)
-                       {
-                               bool savedautobalance = surv_autobalance;
-                               surv_autobalance = false;
-                               SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
-                               surv_autobalance = savedautobalance;
-                               return;
-                       }
-                       lowestplayer = Surv_FindLowestDefender(false, false);
-                       if (lowestplayer == NULL)
-                       {
-                               return;
-                       }
-                       bool savedautobalance = surv_autobalance;
-                       surv_autobalance = false;
-                       SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
-                       surv_autobalance = savedautobalance;
-                       return;
-               }
-               case surv_defenderteam:
-               {
-                       LOG_TRACE("Defender team");
-                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
-                       {
-                               // This happens during team switch. We don't need to change
-                               // anything.
-                               LOG_TRACE("Cannon fodder. Assuming team switch");
-                               return;
-                       }
-                       if (player.surv_role != SURVIVAL_ROLE_PLAYER)
-                       {
-                               string message = strcat("RemovePlayerFromTeam: ",
-                                       player.netname, " has invalid role.");
-                               DebugPrintToChatAll(message);
-                               return;
-                       }
-                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
-                       Surv_ChangeNumberOfPlayers(teamnum, -1);
-                       if (!IS_BOT_CLIENT(player))
-                       {
-                               --surv_numdefenderhumans;
-                       }
-                       if ((surv_autobalance == false) || (surv_numdefenders >=
-                               surv_numattackers))
-                       {
-                               return;
-                       }
-                       // Add bot to keep teams balanced.
-                       entity lowestplayer = Surv_FindCannonFodder();
-                       if (lowestplayer != NULL)
-                       {
-                               LOG_TRACE("Changing ", lowestplayer.netname,
-                                       " from cannon fodder to defender.");
-                               if (!IS_DEAD(player))
-                               {
-                                       lowestplayer.surv_savedplayerstate =
-                                               Surv_SavePlayerState(player);
-                               }
-                               bool savedautobalance = surv_autobalance;
-                               surv_autobalance = false;
-                               surv_announcefrags = false;
-                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
-                               surv_autobalance = savedautobalance;
-                               surv_announcefrags = true;                                      
-                               return;
-                       }
-                       lowestplayer = Surv_FindLowestAttacker(true);
-                       if (lowestplayer != NULL)
-                       {
-                               bool savedautobalance = surv_autobalance;
-                               surv_autobalance = false;
-                               surv_announcefrags = false;
-                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
-                               surv_autobalance = savedautobalance;
-                               surv_announcefrags = true;
-                               return;
-                       }
-                       lowestplayer = Surv_FindLowestAttacker(false);
-                       if (lowestplayer == NULL)
-                       {
-                               return;
-                       }
-                       bool savedautobalance = surv_autobalance;
-                       surv_autobalance = false;
-                       surv_announcefrags = false;
-                       SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
-                       surv_autobalance = savedautobalance;
-                       surv_announcefrags = true;
-                       return;
-               }
-               case -1:
-               {
-                       LOG_TRACE("Spectator team");
-                       return;
-               }
-               default:
-               {
-                       LOG_TRACE("Invalid team");
-                       return;
-               }
-       }
-}
-
-/// \brief Updates stats of team count on HUD.
-/// \return No return.
-void Surv_UpdateTeamStats()
-{
-       // Debug stuff
-       if (surv_attackerteam == NUM_TEAM_1)
-       {
-               yellowalive = surv_numattackers;
-               pinkalive = surv_numdefenders;
-       }
-       else
-       {
-               pinkalive = surv_numattackers;
-               yellowalive = surv_numdefenders;
-       }
-       FOREACH_CLIENT(IS_REAL_CLIENT(it),
-       {
-               it.yellowalive_stat = yellowalive;
-               it.pinkalive_stat = pinkalive;
-       });
-}
-
-/// \brief Adds player to alive list. Handles bookkeeping information.
-/// \param[in] player Player to add.
-/// \param[in] teamnum Team of the player.
-/// \return No return.
-void Surv_AddPlayerToAliveList(entity player, int teamnum)
-{
-       switch (teamnum)
-       {
-               case surv_attackerteam:
-               {
-                       if (player.surv_role == SURVIVAL_ROLE_PLAYER)
-                       {
-                               Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
-                       }
-                       return;
-               }
-               case surv_defenderteam:
-               {
-                       Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
-                       return;
-               }
-       }
-}
-
-/// \brief Removes player from alive list. Handles bookkeeping information.
-/// \param[in] player Player to remove.
-/// \param[in] teamnum Team of the player.
-/// \return No return.
-void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
-{
-       if (player.surv_attack_sprite)
-       {
-               WaypointSprite_Kill(player.surv_attack_sprite);
-       }
-       if (player.surv_defend_sprite)
-       {
-               WaypointSprite_Kill(player.surv_defend_sprite);
-       }
-       switch (teamnum)
-       {
-               case surv_attackerteam:
-               {
-                       if (player.surv_role == SURVIVAL_ROLE_PLAYER)
-                       {
-                               Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
-                       }
-                       return;
-               }
-               case surv_defenderteam:
-               {
-                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
-                       {
-                               // This happens during team switch. We don't need to change
-                               // anything.
-                               return;
-                       }
-                       Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
-                       if (warmup_stage || surv_allowed_to_spawn || !surv_announcefrags)
-                       {
-                               return;
-                       }
-                       switch (surv_numdefendersalive)
-                       {
-                               case 1:
-                               {
-                                       sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
-                                               VOL_BASE, ATTEN_NONE);
-                                       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
-                                       {
-                                               if (it.team == surv_defenderteam)
-                                               {
-                                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER,
-                                                               CENTER_ALONE);
-                                                       return;
-                                               }
-                                       });
-                                       return;
-                               }
-                               case 2:
-                               {
-                                       sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
-                                               VOL_BASE, ATTEN_NONE);
-                                       return;
-                               }
-                               case 3:
-                               {
-                                       sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
-                                               VOL_BASE, ATTEN_NONE);
-                                       return;
-                               }
-                       }
-                       return;
-               }
-       }
-}
-
-/// \brief Counts alive players.
-/// \return No return.
-/// \note In a perfect world this function shouldn't exist. However, since QC
-/// code is so bad and spurious mutators can really mess with your code, this
-/// function is called as a last resort.
-void Surv_CountAlivePlayers()
-{
-       int savednumdefenders = surv_numdefendersalive;
-       surv_numattackersalive = 0;
-       surv_numdefendersalive = 0;
-       FOREACH_CLIENT(IS_PLAYER(it),
-       {
-               switch (it.team)
-               {
-                       case surv_attackerteam:
-                       {
-                               if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
-                               {
-                                       ++surv_numattackersalive;
-                               }
-                               break;
-                       }
-                       case surv_defenderteam:
-                       {
-                               if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
-                               {
-                                       ++surv_numdefendersalive;
-                               }
-                               break;
-                       }
-               }
-       });
-       Surv_UpdateAliveStats();
-       eliminatedPlayers.SendFlags |= 1;
-       if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
-               surv_numdefendersalive))
-       {
-               return;
-       }
-       switch (surv_numdefendersalive)
-       {
-               case 1:
-               {
-                       sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
-                       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
-                       {
-                               if (it.team == surv_defenderteam)
-                               {
-                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
-                                       return;
-                               }
-                       });
-                       return;
-               }
-               case 2:
-               {
-                       sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
-                               ATTEN_NONE);
-                       return;
-               }
-               case 3:
-               {
-                       sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
-                               ATTEN_NONE);
-                       return;
-               }
-       }
-}
-
-/// \brief Updates stats of alive players on HUD.
-/// \return No return.
-void Surv_UpdateAliveStats()
-{
-       // Debug stuff
-       if (surv_attackerteam == NUM_TEAM_1)
-       {
-               redalive = surv_numattackersalive;
-               bluealive = surv_numdefendersalive;
-       }
-       else
-       {
-               bluealive = surv_numattackersalive;
-               redalive = surv_numdefendersalive;
-       }
-       FOREACH_CLIENT(IS_REAL_CLIENT(it),
-       {
-               it.surv_defenders_alive_stat = surv_numdefendersalive;
-               it.redalive_stat = redalive;
-               it.bluealive_stat = bluealive;
-       });
-       Surv_UpdateDefenderHealthStat();
-}
-
-/// \brief Updates defender health on the HUD.
-/// \return No return.
-void Surv_UpdateDefenderHealthStat()
-{
-       float maxhealth;
-       float totalhealth = 0;
-       if (autocvar_g_instagib == 1)
-       {
-               maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
-                       "surv_defender", "start_armor") + 1);
-               FOREACH_CLIENT(IS_PLAYER(it),
-               {
-                       if (it.team == surv_defenderteam)
-                       {
-                               totalhealth += GetResourceAmount(it, RESOURCE_ARMOR) + 1;
-                       }
-               });
-       }
-       else
-       {
-               maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
-                       "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
-                       "surv_defender", "start_armor"));
-               FOREACH_CLIENT(IS_PLAYER(it),
-               {
-                       if (it.team == surv_defenderteam)
-                       {
-                               totalhealth += GetResourceAmount(it, RESOURCE_HEALTH);
-                               totalhealth += GetResourceAmount(it, RESOURCE_ARMOR);
-                       }
-               });
-       }
-       float healthratio;
-       if (maxhealth == 0)
-       {
-               healthratio = 0;
-       }
-       else
-       {
-               healthratio = totalhealth / maxhealth;
-       }
-       FOREACH_CLIENT(IS_REAL_CLIENT(it),
-       {
-               it.surv_defender_health_stat = healthratio;
-       });
-}
-
-/// \brief Returns whether the player can spawn.
-/// \param[in] player Player to check.
-/// \return True if the player can spawn, false otherwise.
-bool Surv_CanPlayerSpawn(entity player)
-{
-       if ((player.team == surv_attackerteam) ||
-               (player.surv_savedplayerstate != NULL))
-       {
-               return true;
-       }
-       return surv_allowed_to_spawn;
-}
-
-/// \brief Switches the round type.
-/// \return No return.
-void Surv_SwitchRoundType()
-{
-       switch (surv_roundtype)
-       {
-               case SURVIVAL_ROUND_FIRST:
-               {
-                       surv_roundtype = SURVIVAL_ROUND_SECOND;
-                       return;
-               }
-               case SURVIVAL_ROUND_SECOND:
-               {
-                       surv_roundtype = SURVIVAL_ROUND_FIRST;
-                       return;
-               }
-       }
-}
-
-/// \brief Cleans up the mess after the round has finished.
-/// \return No return.
-void Surv_RoundCleanup()
-{
-       surv_allowed_to_spawn = false;
-       surv_isroundactive = false;
-       game_stopped = true;
-       FOREACH_CLIENT(true,
-       {
-               if (it.surv_attack_sprite)
-               {
-                       WaypointSprite_Kill(it.surv_attack_sprite);
-               }
-               if (it.surv_defend_sprite)
-               {
-                       WaypointSprite_Kill(it.surv_defend_sprite);
-               }
-               if (it.surv_savedplayerstate)
-               {
-                       delete(it.surv_savedplayerstate);
-                       it.surv_savedplayerstate = NULL;
-               }
-       });
-       if (surv_type == SURVIVAL_TYPE_VERSUS)
-       {
-               Surv_SwitchRoundType();
-               round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
-               return;
-       }
-       round_handler_Init(5, autocvar_g_surv_warmup,
-               autocvar_g_surv_round_timelimit);
-}
-
-/// \brief Swaps attacker and defender teams.
-/// \return No return.
-void Surv_SwapTeams()
-{
-       int temp = surv_attackerteam;
-       surv_attackerteam = surv_defenderteam;
-       surv_defenderteam = temp;
-       temp = surv_attackerteambit;
-       surv_attackerteambit = surv_defenderteambit;
-       surv_defenderteambit = temp;
-       temp = surv_numattackers;
-       surv_numattackers = surv_numdefenders;
-       surv_numdefenders = temp;
-       temp = surv_numattackerhumans;
-       surv_numattackerhumans = surv_numdefenderhumans;
-       surv_numdefenderhumans = temp;
-       FOREACH_CLIENT(true,
-       {
-               if ((it.team == surv_defenderteam) && (it.surv_role ==
-                       SURVIVAL_ROLE_CANNON_FODDER))
-               {
-                       SetPlayerTeamSimple(it, surv_attackerteam);
-               }
-       });
-       FOREACH_CLIENT(IS_REAL_CLIENT(it),
-       {
-               it.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
-       });
-}
-
-/// \brief Forces the overkill model for specific player.
-/// \param[in,out] player Player to force the model of.
-/// \return No return.
-void Surv_ForceOverkillPlayerModel(entity player)
-{
-       switch (player.team)
-       {
-               case NUM_TEAM_1:
-               {
-                       switch (floor(random() * 4))
-                       {
-                               case 0:
-                               {
-                                       player.surv_playermodel = "models/ok_player/okrobot1.dpm";
-                                       return;
-                               }
-                               case 1:
-                               {
-                                       player.surv_playermodel = "models/ok_player/okrobot2.dpm";
-                                       return;
-                               }
-                               case 2:
-                               {
-                                       player.surv_playermodel = "models/ok_player/okrobot3.dpm";
-                                       return;
-                               }
-                               case 3:
-                               {
-                                       player.surv_playermodel = "models/ok_player/okrobot4.dpm";
-                                       return;
-                               }
-                       }
-                       return;
-               }
-               case NUM_TEAM_2:
-               {
-                       switch (floor(random() * 4))
-                       {
-                               case 0:
-                               {
-                                       player.surv_playermodel = "models/ok_player/okmale1.dpm";
-                                       return;
-                               }
-                               case 1:
-                               {
-                                       player.surv_playermodel = "models/ok_player/okmale2.dpm";
-                                       return;
-                               }
-                               case 2:
-                               {
-                                       player.surv_playermodel = "models/ok_player/okmale3.dpm";
-                                       return;
-                               }
-                               case 3:
-                               {
-                                       player.surv_playermodel = "models/ok_player/okmale4.dpm";
-                                       return;
-                               }
-                       }
-                       return;
-               }
-       }
-}
-
-/// \brief Determines the player model to the one configured for the gamemode.
-/// \param[in,out] player Player to determine the model of.
-/// \return No return.
-void Surv_DeterminePlayerModel(entity player)
-{
-       switch (player.team)
-       {
-               case surv_attackerteam:
-               {
-                       switch (player.surv_role)
-                       {
-                               case SURVIVAL_ROLE_PLAYER:
-                               {
-                                       if (!autocvar_g_surv_attacker_force_overkill_models)
-                                       {
-                                               player.surv_playermodel = player.surv_savedplayermodel;
-                                               return;
-                                       }
-                                       Surv_ForceOverkillPlayerModel(player);
-                                       return;
-                               }
-                               case SURVIVAL_ROLE_CANNON_FODDER:
-                               {
-                                       if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
-                                       {
-                                               player.surv_playermodel = player.surv_savedplayermodel;
-                                               return;
-                                       }
-                                       Surv_ForceOverkillPlayerModel(player);
-                                       return;
-                               }
-                       }
-               }
-               case surv_defenderteam:
-               {
-                       if (!autocvar_g_surv_defender_force_overkill_models)
-                       {
-                               player.surv_playermodel = player.surv_savedplayermodel;
-                               return;
-                       }
-                       Surv_ForceOverkillPlayerModel(player);
-                       return;
-               }
-       }
-}
-
-/// \brief Setups a waypoint sprite used to track defenders.
-/// \param[in] player Player to attach sprite too.
-/// \return No return.
-void Surv_SetupWaypointSprite(entity player)
-{
-       WaypointSprite_Spawn(WP_SurvivalKill, 0, 0, player, '0 0 64', NULL,
-               surv_attackerteam, player, surv_attack_sprite, false,
-               RADARICON_OBJECTIVE);
-       WaypointSprite_Spawn(WP_SurvivalDefend, 0, 0, player, '0 0 64', NULL,
-               surv_defenderteam, player, surv_defend_sprite, false,
-               RADARICON_OBJECTIVE);
-       //player.surv_attack_sprite.colormod = colormapPaletteColor(player.team - 1,
-       //      false);
-       //player.surv_defend_sprite.colormod = colormapPaletteColor(player.team - 1,
-       //      false);
-       float max_hp;
-       if (autocvar_g_instagib == 1)
-       {
-               max_hp = PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(player),
-                       "start_armor") + 1;
-               WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite, max_hp);
-               WaypointSprite_UpdateMaxHealth(player.surv_defend_sprite, max_hp);
-               Surv_UpdateWaypointSpriteHealth(player);
-               return;
-       }
-       max_hp = PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(player),
-               "start_health") + PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(
-               player), "start_armor");
-       WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite, max_hp);
-       WaypointSprite_UpdateMaxHealth(player.surv_defend_sprite, max_hp);
-       Surv_UpdateWaypointSpriteHealth(player);
-}
-
-void Surv_UpdateWaypointSpriteHealth(entity player)
-{
-       float hp;
-       if (autocvar_g_instagib == 1)
-       {
-               hp = GetResourceAmount(player, RESOURCE_ARMOR) + 1;
-       }
-       else
-       {
-               hp = GetResourceAmount(player, RESOURCE_HEALTH) + GetResourceAmount(
-                       player, RESOURCE_ARMOR);
-       }
-       WaypointSprite_UpdateHealth(player.surv_attack_sprite, hp);
-       WaypointSprite_UpdateHealth(player.surv_defend_sprite, hp);
-}
-
-//=============================== Callbacks ===================================
-
-bool Surv_CanRoundStart()
-{
-       return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
-}
-
-bool Surv_CanRoundEnd()
-{
-       if (warmup_stage)
-       {
-               return false;
-       }
-       if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
-               time <= 0))
-       {
-               if (surv_roundtype == SURVIVAL_ROUND_FIRST)
-               {
-                       surv_timetobeat = time - surv_roundstarttime;
-                       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
-                               CENTER_SURVIVAL_DEFENDERS_SURVIVED);
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
-                               INFO_SURVIVAL_DEFENDERS_SURVIVED);
-                       Surv_RoundCleanup();
-                       return true;
-               }
-               surv_timetobeat = autocvar_g_surv_round_timelimit;
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
-                       CENTER_SURVIVAL_DEFENDERS_SURVIVED);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
-                       INFO_SURVIVAL_DEFENDERS_SURVIVED);
-               switch (surv_defenderteam)
-               {
-                       case NUM_TEAM_1:
-                       {
-                               sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
-                                       ATTEN_NONE);
-                               break;
-                       }
-                       case NUM_TEAM_2:
-                       {
-                               sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
-                                       ATTEN_NONE);
-                               break;
-                       }
-               }
-               TeamScore_AddToTeam(surv_defenderteam, 1, 1);
-               Surv_RoundCleanup();
-               return true;
-       }
-       if (surv_numdefendersalive > 0)
-       {
-               return false;
-       }
-       if (surv_roundtype == SURVIVAL_ROUND_FIRST)
-       {
-               surv_timetobeat = time - surv_roundstarttime;
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
-                       CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
-                       INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
-               Surv_RoundCleanup();
-               return true;
-       }
-       surv_timetobeat = autocvar_g_surv_round_timelimit;
-       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
-               CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
-       Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
-               INFO_SURVIVAL_DEFENDERS_ELIMINATED);
-       switch (surv_attackerteam)
-       {
-               case NUM_TEAM_1:
-               {
-                       sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
-                               ATTEN_NONE);
-                       break;
-               }
-               case NUM_TEAM_2:
-               {
-                       sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
-                               ATTEN_NONE);
-                       break;
-               }
-       }
-       TeamScore_AddToTeam(surv_attackerteam, 1, 1);
-       Surv_RoundCleanup();
-       return true;
-}
-
-void Surv_RoundStart()
-{
-       if (warmup_stage)
-       {
-               surv_allowed_to_spawn = true;
-               return;
-       }
-       surv_isroundactive = true;
-       surv_roundstarttime = time;
-       surv_allowed_to_spawn = false;
-       switch (surv_roundtype)
-       {
-               case SURVIVAL_ROUND_FIRST:
-               {
-                       FOREACH_CLIENT(IS_PLAYER(it),
-                       {
-                               if (it.team == surv_attackerteam)
-                               {
-                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
-                                               CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
-                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
-                                               INFO_SURVIVAL_1ST_ROUND_ATTACKER);
-                                       break;
-                               }
-                       });
-                       FOREACH_CLIENT(IS_PLAYER(it),
-                       {
-                               if (it.team == surv_defenderteam)
-                               {
-                                       if (surv_type == SURVIVAL_TYPE_COOP)
-                                       {
-                                               Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
-                                                       CENTER_SURVIVAL_COOP_DEFENDER);
-                                               Send_Notification(NOTIF_TEAM, it, MSG_INFO,
-                                                       INFO_SURVIVAL_COOP_DEFENDER);
-                                               break;
-                                       }
-                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
-                                               CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
-                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
-                                               INFO_SURVIVAL_1ST_ROUND_DEFENDER);
-                                       break;
-                               }
-                       });
-                       break;
-               }
-               case SURVIVAL_ROUND_SECOND:
-               {
-                       FOREACH_CLIENT(IS_PLAYER(it),
-                       {
-                               if (it.team == surv_attackerteam)
-                               {
-                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
-                                               CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
-                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
-                                               INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
-                                       break;
-                               }
-                       });
-                       FOREACH_CLIENT(IS_PLAYER(it),
-                       {
-                               if (it.team == surv_defenderteam)
-                               {
-                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
-                                               CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
-                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
-                                               INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
-                                       break;
-                               }
-                       });
-                       break;
-               }
-       }
-       if (autocvar_g_surv_stealth)
-       {
-               return;
-       }
-       FOREACH_CLIENT(IS_PLAYER(it),
-       {
-               switch (it.team)
-               {
-                       case surv_defenderteam:
-                       {
-                               if (it.surv_role == SURVIVAL_ROLE_PLAYER)
-                               {
-                                       Surv_SetupWaypointSprite(it);
-                               }
-                               break;
-                       }
-               }
-       });
-}
-
-bool Surv_IsEliminated(entity player)
-{
-       switch (player.surv_state)
-       {
-               case SURVIVAL_STATE_NOT_PLAYING:
-               {
-                       return true;
-               }
-               case SURVIVAL_STATE_PLAYING:
-               {
-                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
-                       {
-                               // A hack until proper scoreboard is done.
-                               return true;
-                       }
-                       if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
-                               IS_OBSERVER(player)))
-                       {
-                               return true;
-                       }
-                       return false;
-               }
-       }
-       // Should never reach here
-       return true;
-}
-
-//============================= Hooks ========================================
-
-/// \brief Hook that is called to determine general rules of the game. 
-MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
-{
-       surv_warmup = warmup_stage;
-}
-
-/// \brief Hook that is called to determine if there is a weapon arena.
-MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
-{
-       // Removing any weapon arena.
-       M_ARGV(0, string) = "off";
-}
-
-/// \brief Hook that is called to determine start items of all players.
-MUTATOR_HOOKFUNCTION(surv, SetStartItems)
-{
-       if (autocvar_g_instagib == 1)
-       {
-               return;
-       }
-       start_weapons = WEPSET(Null);
-       warmup_start_weapons = WEPSET(Null);
-}
-
-/// \brief Hook that is called on every frame.
-MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
-{
-       if (game_stopped || !surv_isroundactive)
-       {
-               return;
-       }
-       float roundtime = 0;
-       switch (surv_roundtype)
-       {
-               case SURVIVAL_ROUND_FIRST:
-               {
-                       roundtime = time - surv_roundstarttime;
-                       break;
-               }
-               case SURVIVAL_ROUND_SECOND:
-               {
-                       roundtime = round_handler_GetEndTime() - time;
-                       break;
-               }
-       }
-       FOREACH_CLIENT(IS_REAL_CLIENT(it),
-       {
-               it.surv_round_time_stat = roundtime;
-       });
-}
-
-/// \brief Hook that determines which team player can join. This is called
-/// before ClientConnect.
-MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
-       entity player = M_ARGV(2, entity);
-       LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
-       if (player == NULL)
-       {
-               return SURVIVAL_TEAM_BITS;
-       }
-       if (IS_BOT_CLIENT(player))
-       {
-               int teambits = surv_attackerteambit;
-               if ((player.team == surv_defenderteam) || (surv_numdefenders <
-                       autocvar_g_surv_team_size))
-               {
-                       teambits |= surv_defenderteambit;
-               }
-               M_ARGV(0, float) = teambits;
-               return;
-       }
-       if (surv_type == SURVIVAL_TYPE_COOP)
-       {
-               if (surv_numdefenderhumans < autocvar_g_surv_team_size)
-               {
-                       M_ARGV(0, float) = surv_defenderteambit;
-                       return;
-               }
-               M_ARGV(0, float) = 0;
-               return;
-       }
-       int teambits = 0;
-       if (surv_numattackerhumans < autocvar_g_surv_team_size)
-       {
-               LOG_TRACE("Player can join attackers");
-               teambits |= surv_attackerteambit;
-       }
-       if (surv_numdefenderhumans < autocvar_g_surv_team_size)
-       {
-               LOG_TRACE("Player can join defenders");
-               teambits |= surv_defenderteambit;
-       }
-       M_ARGV(0, float) = teambits;
-       return;
-}
-
-/// \brief Hook that override team counts.
-MUTATOR_HOOKFUNCTION(surv, GetTeamCounts, CBC_ORDER_EXCLUSIVE)
-{
-       return true;
-}
-
-/// \brief Hook that sets the team count.
-MUTATOR_HOOKFUNCTION(surv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
-{
-       float teamnum = M_ARGV(0, float);
-       entity ignore = M_ARGV(1, entity);
-       switch (teamnum)
-       {
-               case surv_attackerteam:
-               {
-                       M_ARGV(2, float) = surv_numattackers;
-                       M_ARGV(3, float) = surv_numattackers - surv_numattackerhumans;
-                       if (ignore.team == surv_attackerteam)
-                       {
-                               --M_ARGV(2, float);
-                               if (IS_BOT_CLIENT(ignore))
-                               {
-                                       --M_ARGV(3, float);
-                               }
-                       }
-                       entity lowestplayer = NULL;
-                       float lowestplayerscore = FLOAT_MAX;
-                       entity lowestbot = NULL;
-                       float lowestbotscore = FLOAT_MAX;
-                       FOREACH_CLIENT((it.team == surv_attackerteam) && (it.surv_role ==
-                               SURVIVAL_ROLE_PLAYER),
-                       {
-                               if (it == ignore)
-                               {
-                                       continue;
-                               }
-                               if (IS_BOT_CLIENT(it))
-                               {
-                                       float tempscore = PlayerScore_Get(it, SP_SCORE);
-                                       if (tempscore < lowestbotscore)
-                                       {
-                                               lowestbot = it;
-                                               lowestbotscore = tempscore;
-                                               continue;
-                                       }
-                               }
-                               float tempscore = PlayerScore_Get(it, SP_SCORE);
-                               if (tempscore < lowestplayerscore)
-                               {
-                                       lowestplayer = it;
-                                       lowestplayerscore = tempscore;
-                               }
-                       });
-                       M_ARGV(4, entity) = lowestplayer;
-                       M_ARGV(5, entity) = lowestbot;
-                       break;
-               }
-               case surv_defenderteam:
-               {
-                       M_ARGV(2, float) = surv_numdefenders;
-                       M_ARGV(3, float) = surv_numdefenders - surv_numdefenderhumans;
-                       if (ignore.team == surv_defenderteam)
-                       {
-                               --M_ARGV(2, float);
-                               if (IS_BOT_CLIENT(ignore))
-                               {
-                                       --M_ARGV(3, float);
-                               }
-                       }
-                       entity lowestplayer = NULL;
-                       float lowestplayerscore = FLOAT_MAX;
-                       entity lowestbot = NULL;
-                       float lowestbotscore = FLOAT_MAX;
-                       FOREACH_CLIENT((it.team == surv_defenderteam),
-                       {
-                               if (it == ignore)
-                               {
-                                       continue;
-                               }
-                               if (IS_BOT_CLIENT(it))
-                               {
-                                       float tempscore = PlayerScore_Get(it, SP_SCORE);
-                                       if (tempscore < lowestbotscore)
-                                       {
-                                               lowestbot = it;
-                                               lowestbotscore = tempscore;
-                                               continue;
-                                       }
-                               }
-                               float tempscore = PlayerScore_Get(it, SP_SCORE);
-                               if (tempscore < lowestplayerscore)
-                               {
-                                       lowestplayer = it;
-                                       lowestplayerscore = tempscore;
-                               }
-                       });
-                       M_ARGV(4, entity) = lowestplayer;
-                       M_ARGV(5, entity) = lowestbot;
-                       break;
-               }
-       }
-       return true;
-}
-
-/// \brief Hook that determines the best teams for the player to join.
-MUTATOR_HOOKFUNCTION(surv, FindBestTeams, CBC_ORDER_EXCLUSIVE)
-{
-       if (surv_type == SURVIVAL_TYPE_COOP)
-       {
-               return false;
-       }
-       entity player = M_ARGV(0, entity);
-       if (IS_BOT_CLIENT(player))
-       {
-               return false;
-       }
-       int numattackerhumans = surv_numattackerhumans;
-       int numdefenderhumans = surv_numdefenderhumans;
-       if (player.team == surv_attackerteam)
-       {
-               --numattackerhumans;
-       }
-       else if (player.team == surv_defenderteam)
-       {
-               --numdefenderhumans;
-       }
-       if (numattackerhumans < numdefenderhumans)
-       {
-               M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_attackerteam) - 1);
-               return true;
-       }
-       if (numattackerhumans > numdefenderhumans)
-       {
-               M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_defenderteam) - 1);
-               return true;
-       }
-       M_ARGV(1, float) = SURVIVAL_TEAM_BITS;
-       return true;
-}
-
-/// \brief Hook that is called when player has changed the team.
-MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
-{
-       entity player = M_ARGV(0, entity);
-       int oldteam = M_ARGV(1, float);
-       int newteam = M_ARGV(2, float);
-       string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
-               ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
-       LOG_TRACE(message);
-       DebugPrintToChatAll(message);
-       if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
-       {
-               Surv_RemovePlayerFromAliveList(player, oldteam);
-       }
-       Surv_RemovePlayerFromTeam(player, oldteam);
-       if (Surv_AddPlayerToTeam(player, newteam) == false)
-       {
-               return;
-       }
-       //Surv_CountAlivePlayers();
-       if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
-       {
-               Surv_AddPlayerToAliveList(player, newteam);
-       }
-}
-
-/// \brief Hook that is called when player connects to the server.
-MUTATOR_HOOKFUNCTION(surv, ClientConnect)
-{
-       entity player = M_ARGV(0, entity);
-       LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
-       player.surv_savedplayermodel = player.playermodel;
-       if (IS_REAL_CLIENT(player))
-       {
-               player.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
-               player.surv_defenders_alive_stat = surv_numdefendersalive;
-               player.redalive_stat = redalive;
-               player.bluealive_stat = bluealive;
-               player.yellowalive_stat = yellowalive;
-               player.pinkalive_stat = pinkalive;
-       }
-       if (player.surv_role == SURVIVAL_ROLE_NONE)
-       {
-               Surv_AddPlayerToTeam(player, player.team);
-       }
-       return true;
-}
-
-/// \brief Hook that is called when player disconnects from the server.
-MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
-{
-    entity player = M_ARGV(0, entity);
-       if (!IS_DEAD(player))
-       {
-               Surv_RemovePlayerFromAliveList(player, player.team);
-       }
-       Surv_RemovePlayerFromTeam(player, player.team);
-       //Surv_CountAlivePlayers();
-}
-
-/// \brief Hook that determines whether player can spawn. It is not called for
-/// players who have joined the team and are dead.
-MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
-{
-       entity player = M_ARGV(0, entity);
-       LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
-       if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
-       {
-               return false;
-       }
-       return !Surv_CanPlayerSpawn(player);
-}
-
-MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
-{
-       entity player = M_ARGV(0, entity);
-       LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
-       if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
-       {
-               LOG_TRACE("Transmuting to observer");
-               TRANSMUTE(Observer, player);
-       }
-}
-
-MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
-{
-       entity player = M_ARGV(0, entity);
-       LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
-       if (player.killindicator_teamchange == -2) // player wants to spectate
-       {
-               LOG_TRACE("killindicator_teamchange == -2");
-               player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
-       }
-       if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
-       {
-               return false;  // allow team reset
-       }
-       return true;  // prevent team reset
-}
-
-MUTATOR_HOOKFUNCTION(surv, reset_map_global)
-{
-       LOG_TRACE("Survival: reset_map_global");
-       surv_allowed_to_spawn = true;
-       if (surv_roundtype == SURVIVAL_ROUND_FIRST)
-       {
-               FOREACH_CLIENT(IS_REAL_CLIENT(it),
-               {
-                       it.surv_round_time_stat = 0;
-               });
-       }
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(surv, reset_map_players)
-{
-       LOG_TRACE("Survival: reset_map_players");
-       surv_numattackersalive = 0;
-       surv_numdefendersalive = 0;
-       if (surv_warmup)
-       {
-               surv_warmup = false;
-       }
-       else if (surv_type == SURVIVAL_TYPE_VERSUS)
-       {
-               Surv_SwapTeams();
-       }
-       FOREACH_CLIENT(true,
-       {
-               it.killcount = 0;
-               if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
-               {
-                       it.team = -1;
-                       it.surv_state = SURVIVAL_STATE_PLAYING;
-               }
-               if (it.surv_state == SURVIVAL_STATE_PLAYING)
-               {
-                       TRANSMUTE(Player, it);
-                       it.surv_state = SURVIVAL_STATE_PLAYING;
-                       PutClientInServer(it);
-               }
-       });
-       bot_relinkplayerlist();
-       return true;
-}
-
-/// \brief Hook that is called when player spawns.
-MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
-{
-       entity player = M_ARGV(0, entity);
-       LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
-       player.surv_state = SURVIVAL_STATE_PLAYING;
-       Surv_DeterminePlayerModel(player);
-       if (player.surv_savedplayerstate != NULL)
-       {
-               Surv_RestorePlayerState(player, player.surv_savedplayerstate);
-               delete(player.surv_savedplayerstate);
-               player.surv_savedplayerstate = NULL;
-       }
-       else
-       {
-               PlayerTemplateHook_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
-       }
-       switch (player.team)
-       {
-               case surv_attackerteam:
-               {
-                       if (player.surv_role == SURVIVAL_ROLE_PLAYER)
-                       {
-                               Send_Notification(NOTIF_ONE, player, MSG_CENTER,
-                                       CENTER_ASSAULT_ATTACKING);
-                       }
-                       break;
-               }
-               case surv_defenderteam:
-               {
-                       Send_Notification(NOTIF_ONE, player, MSG_CENTER,
-                               CENTER_ASSAULT_DEFENDING);
-                       break;
-               }
-       }
-       //Surv_CountAlivePlayers();
-       Surv_AddPlayerToAliveList(player, player.team);
-}
-
-/// \brief UGLY HACK. This is called every frame to keep player model correct.
-MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
-{
-       entity player = M_ARGV(2, entity);
-       M_ARGV(0, string) = player.surv_playermodel;
-}
-
-/// \brief Hook which is called when the player tries to throw their weapon.
-MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
-{
-       entity player = M_ARGV(0, entity);
-       return PlayerTemplateHook_ForbidThrowCurrentWeapon(
-               Surv_GetPlayerTemplate(player));
-}
-
-/// \brief Hook that is called every frame to determine how player health should
-/// regenerate.
-MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
-{
-       entity player = M_ARGV(0, entity);
-       if (player.team == surv_defenderteam)
-       {
-               return true;
-       }
-       return PlayerTemplateHook_PlayerRegen(player,
-               Surv_GetPlayerTemplate(player));
-}
-
-/// \brief Hook that is called to determine if balance messages will appear.
-MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
-{
-       return true;
-}
-
-/// \brief Hook that is called when player touches an item.
-MUTATOR_HOOKFUNCTION(surv, ItemTouch)
-{
-       entity item = M_ARGV(0, entity);
-       entity player = M_ARGV(1, entity);
-       switch (player.team)
-       {
-               case surv_attackerteam:
-               {
-                       return PlayerTemplateHook_ItemTouch(player, item,
-                               Surv_GetPlayerTemplate(player));
-               }
-               case surv_defenderteam:
-               {
-                       switch (item.classname)
-                       {
-                               case "item_strength":
-                               {
-                                       W_GiveWeapon(player, WEP_HMG.m_id);
-                                       player.superweapons_finished = max(
-                                               player.superweapons_finished, time) +
-                                               autocvar_g_balance_superweapons_time;
-                                       Item_ScheduleRespawn(item);
-                                       sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
-                                               ATTEN_NORM);
-                                       return MUT_ITEMTOUCH_RETURN;
-                               }
-                               case "item_invincible":
-                               {
-                                       W_GiveWeapon(player, WEP_RPC.m_id);
-                                       player.superweapons_finished = max(
-                                               player.superweapons_finished, time) +
-                                               autocvar_g_balance_superweapons_time;
-                                       Item_ScheduleRespawn(item);
-                                       sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
-                                       return MUT_ITEMTOUCH_RETURN;
-                               }
-                               default:
-                               {
-                                       return PlayerTemplateHook_ItemTouch(player, item,
-                                               Surv_GetPlayerTemplate(player));
-                               }
-                       }
-                       DebugPrintToChat(player, item.classname);
-                       return MUT_ITEMTOUCH_RETURN;
-               }
-       }
-       return MUT_ITEMTOUCH_CONTINUE;
-}
-
-/// \brief Hook which is called when the damage amount must be determined.
-MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
-{
-       entity frag_attacker = M_ARGV(1, entity);
-       entity frag_target = M_ARGV(2, entity);
-       float deathtype = M_ARGV(3, float);
-       float damage = M_ARGV(4, float);
-       M_ARGV(4, float) = PlayerTemplateHook_Damage_Calculate(frag_attacker,
-               Surv_GetPlayerTemplate(frag_attacker), frag_target,
-               Surv_GetPlayerTemplate(frag_target), deathtype, damage);
-}
-
-/// \brief Hook which is called when the player was damaged.
-MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
-{
-       entity target = M_ARGV(1, entity);
-       if (target.team != surv_defenderteam)
-       {
-               return;
-       }
-       Surv_UpdateDefenderHealthStat();
-       entity attacker = M_ARGV(0, entity);
-       if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
-               SURVIVAL_ROLE_PLAYER))
-       {
-               float health = M_ARGV(2, float);
-               float armor = M_ARGV(3, float);
-               float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
-               GameRules_scoring_add(attacker, SCORE, score);
-       }
-       if (autocvar_g_surv_stealth)
-       {
-               return;
-       }
-       if (GetResourceAmount(target, RESOURCE_HEALTH) < 1)
-       {
-               WaypointSprite_Kill(target.surv_attack_sprite);
-               WaypointSprite_Kill(target.surv_defend_sprite);
-       }
-       else
-       {
-               Surv_UpdateWaypointSpriteHealth(target);
-       }
-}
-
-/// \brief Hook which is called when the player dies.
-MUTATOR_HOOKFUNCTION(surv, PlayerDies, CBC_ORDER_FIRST)
-{
-       //DebugPrintToChatAll("PlayerDies");
-       entity attacker = M_ARGV(1, entity);
-       entity victim = M_ARGV(2, entity);
-       PlayerTemplateHook_PlayerDies(victim, Surv_GetPlayerTemplate(victim));
-       if ((attacker.team == surv_defenderteam) &&
-               (victim.team == surv_attackerteam))
-       {
-               switch (victim.surv_role)
-               {
-                       case SURVIVAL_ROLE_PLAYER:
-                       {
-                               GiveResource(attacker, RESOURCE_HEALTH,
-                                       autocvar_g_surv_defender_attacker_frag_health);
-                               GiveResource(attacker, RESOURCE_ARMOR,
-                                       autocvar_g_surv_defender_attacker_frag_armor);
-                               GiveResource(attacker, RESOURCE_SHELLS,
-                                       autocvar_g_surv_defender_attacker_frag_shells);
-                               GiveResource(attacker, RESOURCE_BULLETS,
-                                       autocvar_g_surv_defender_attacker_frag_bullets);
-                               GiveResource(attacker, RESOURCE_ROCKETS,
-                                       autocvar_g_surv_defender_attacker_frag_rockets);
-                               GiveResource(attacker, RESOURCE_CELLS,
-                                       autocvar_g_surv_defender_attacker_frag_cells);
-                               GiveResource(attacker, RESOURCE_PLASMA,
-                                       autocvar_g_surv_defender_attacker_frag_plasma);
-                               GiveResource(attacker, RESOURCE_FUEL,
-                                       autocvar_g_surv_defender_attacker_frag_fuel);
-                               break;
-                       }
-                       case SURVIVAL_ROLE_CANNON_FODDER:
-                       {
-                               GiveResource(attacker, RESOURCE_HEALTH,
-                                       autocvar_g_surv_defender_cannon_fodder_frag_health);
-                               GiveResource(attacker, RESOURCE_ARMOR,
-                                       autocvar_g_surv_defender_cannon_fodder_frag_armor);
-                               GiveResource(attacker, RESOURCE_SHELLS,
-                                       autocvar_g_surv_defender_cannon_fodder_frag_shells);
-                               GiveResource(attacker, RESOURCE_BULLETS,
-                                       autocvar_g_surv_defender_cannon_fodder_frag_bullets);
-                               GiveResource(attacker, RESOURCE_ROCKETS,
-                                       autocvar_g_surv_defender_cannon_fodder_frag_rockets);
-                               GiveResource(attacker, RESOURCE_CELLS,
-                                       autocvar_g_surv_defender_cannon_fodder_frag_cells);
-                               GiveResource(attacker, RESOURCE_PLASMA,
-                                       autocvar_g_surv_defender_cannon_fodder_frag_plasma);
-                               GiveResource(attacker, RESOURCE_FUEL,
-                                       autocvar_g_surv_defender_cannon_fodder_frag_fuel);
-                               break;
-                       }
-               }
-       }
-       if (!Surv_CanPlayerSpawn(victim))
-       {
-               victim.respawn_flags = RESPAWN_SILENT;
-               if (IS_BOT_CLIENT(victim))
-               {
-                       bot_clear(victim);
-               }
-       }
-       return true;
-}
-
-/// \brief Hook which is called after the player died.
-MUTATOR_HOOKFUNCTION(surv, PlayerDied)
-{
-       //DebugPrintToChatAll("PlayerDied");
-       entity player = M_ARGV(0, entity);
-       Surv_RemovePlayerFromAliveList(player, player.team);
-       //Surv_CountAlivePlayers();
-}
-
-/// \brief Hook which is called when player has scored a frag.
-MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
-{
-       if (surv_type == SURVIVAL_TYPE_COOP)
-       {
-               return true;
-       }
-       entity attacker = M_ARGV(0, entity);
-       if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
-               SURVIVAL_ROLE_CANNON_FODDER))
-       {
-               M_ARGV(2, float) = 0;
-               return true;
-       }
-       entity target = M_ARGV(1, entity);
-       if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
-               surv_defenderteam))
-       {
-               M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
-       }
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(surv, SpectateSet)
-{
-       entity client = M_ARGV(0, entity);
-       entity targ = M_ARGV(1, entity);
-
-       if (!autocvar_g_surv_spectate_enemies &&
-               (client.surv_state == SURVIVAL_STATE_PLAYING) &&
-               DIFF_TEAM(targ, client))
-       {
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(surv, SpectateNext)
-{
-       entity client = M_ARGV(0, entity);
-
-       if (!autocvar_g_surv_spectate_enemies &&
-               (client.surv_state == SURVIVAL_STATE_PLAYING))
-       {
-               entity targ = M_ARGV(1, entity);
-               M_ARGV(1, entity) = CA_SpectateNext(client, targ);
-               return true;
-       }
-}
-
-MUTATOR_HOOKFUNCTION(surv, SpectatePrev)
-{
-       entity client = M_ARGV(0, entity);
-       entity targ = M_ARGV(1, entity);
-       entity first = M_ARGV(2, entity);
-
-       if (!autocvar_g_surv_spectate_enemies &&
-               (client.surv_state == SURVIVAL_STATE_PLAYING))
-       {
-               do
-               {
-                       targ = targ.chain;
-               }
-               while (targ && DIFF_TEAM(targ, client));
-               if (!targ)
-               {
-                       for (targ = first; targ && DIFF_TEAM(targ, client);
-                               targ = targ.chain);
-
-                       if (targ == client.enemy)
-                       {
-                               return MUT_SPECPREV_RETURN;
-                       }
-               }
-       }
-       M_ARGV(1, entity) = targ;
-       return MUT_SPECPREV_FOUND;
-}
-
-/// \brief I'm not sure exactly what this function does but it is very
-/// important. Without it bots are completely broken. Is it a hack? Of course.
-MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
-{
-       FOREACH_CLIENT(IS_REAL_CLIENT(it),
-       {
-               if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
-               {
-                       ++M_ARGV(0, int);
-               }
-               ++M_ARGV(1, int);
-       });
-       return true;
-}
-
-MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
-{
-       // Don't announce remaining frags
-       return false;
-}
diff --git a/qcsrc/server/mutators/mutator/gamemode_survival.qh b/qcsrc/server/mutators/mutator/gamemode_survival.qh
deleted file mode 100644 (file)
index a4128c2..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include "../gamemode.qh"
-
-/// \brief Initializes global data for the gametype.
-/// \return No return.
-void Surv_Initialize();
-
-REGISTER_MUTATOR(surv, false)
-{
-       MUTATOR_STATIC();
-       MUTATOR_ONADD
-       {
-               Surv_Initialize();
-       }
-       return 0;
-}