]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Lyberta/Survival
authorLyberta <lyberta@lyberta.net>
Wed, 10 May 2017 12:41:05 +0000 (15:41 +0300)
committerLyberta <lyberta@lyberta.net>
Wed, 10 May 2017 12:41:05 +0000 (15:41 +0300)
21 files changed:
gamemodes.cfg
qcsrc/client/hud/panel/modicons.qc
qcsrc/common/mapinfo.qh
qcsrc/common/notifications/all.inc
qcsrc/common/stats.qh
qcsrc/common/t_items.qc
qcsrc/common/t_items.qh
qcsrc/menu/xonotic/dialog_multiplayer_create.qc
qcsrc/menu/xonotic/util.qc
qcsrc/server/client.qc
qcsrc/server/g_world.qc
qcsrc/server/mutators/events.qh
qcsrc/server/mutators/mutator/_mod.inc
qcsrc/server/mutators/mutator/_mod.qh
qcsrc/server/mutators/mutator/gamemode_survival.qc [new file with mode: 0644]
qcsrc/server/mutators/mutator/gamemode_survival.qh [new file with mode: 0644]
qcsrc/server/player.qc
qcsrc/server/player.qh
qcsrc/server/teamplay.qc
qcsrc/server/teamplay.qh
survival.cfg [new file with mode: 0644]

index 80d99022669f30b3819d93f0ba2da519c9bb30cc..0646f3dbbe5f49f93813c43a8d0b2e981f9acf91 100644 (file)
@@ -553,3 +553,5 @@ set g_invasion_spawn_delay 0.25
 set g_invasion_spawnpoint_spawn_delay 0.5
 set g_invasion_teams 0 "number of teams in invasion (note: use mapinfo to set this)"
 set g_invasion_team_spawns 1 "use team spawns in teamplay invasion mode"
+
+exec survival.cfg // Lyberta: surv
index b736dced60671a8025936d95b3c01dc844dabb6f..cd65063ba3d8a30dceb7e8e70ec470cd11b574d8 100644 (file)
@@ -710,6 +710,103 @@ void HUD_Mod_Dom(vector myPos, vector mySize)
        }
 }
 
+// Lyberta: surv
+void HUD_Mod_SURV(vector mypos, vector mysize)
+{
+       mod_active = 1; // required in each mod function that always shows something
+       float defenderhealth = STAT(SURV_DEFENDER_HEALTH);
+       // Draw a health bar
+       float margin = mysize.y / 10; // Leave a small margin to be stylish
+       vector healthbarpos = mypos;
+       healthbarpos.x += margin;
+       healthbarpos.y += margin;
+       vector healthbarsize = mysize;
+       healthbarsize.x -= margin * 2;
+       healthbarsize.y -= margin * 2;
+       vector healthbarcolor;
+       healthbarcolor.z = 0;
+       if (defenderhealth > 0.5)
+       {
+               healthbarcolor.x = defenderhealth * -2 + 2;
+               healthbarcolor.y = 1;
+       }
+       else
+       {
+               healthbarcolor.x = 1;
+               healthbarcolor.y = defenderhealth;
+       }
+       HUD_Panel_DrawProgressBar(healthbarpos, healthbarsize, "progressbar",
+               defenderhealth, false, 0, healthbarcolor, 0.50, DRAWFLAG_NORMAL);
+       // Draw defender picture
+       int defenderteam = STAT(SURV_DEFENDER_TEAM);
+       string defenderpic = "";
+       vector defendercolor;
+       switch (defenderteam)
+       {
+               case 1:
+               {
+                       defenderpic = "player_red";
+                       defendercolor = '1 0 0';
+                       break;
+               }
+               case 2:
+               {
+                       defenderpic = "player_blue";
+                       defendercolor = '0 0 1';
+                       break;
+               }
+               default:
+               {
+                       defendercolor = '1 1 1';
+                       break;
+               }
+       }
+       vector picpos = mypos;
+       vector picsize = mysize;
+       picsize.x = picsize.y;
+       drawpic_aspect_skin(picpos, defenderpic, picsize, '1 1 1', 1,
+               DRAWFLAG_NORMAL);
+       // Draw number of defenders
+       int numalive = STAT(SURV_DEFENDERS_ALIVE);
+       vector alivepos = mypos;
+       alivepos.x += picsize.x;
+       vector alivesize = picsize;
+       drawstring_aspect(alivepos, ftos(numalive), alivesize, defendercolor, 1,
+               DRAWFLAG_NORMAL);
+       // Draw the time left
+       float roundtime = STAT(SURV_ROUND_TIME);
+       if (roundtime < 0)
+       {
+               roundtime = 0;
+       }
+       vector roundtimepos = mypos;
+       roundtimepos.x += picsize.x * 2;
+       vector roundtimesize = mysize;
+       roundtimesize.x = mysize.x - picsize.x * 2;
+       drawstring_aspect(roundtimepos, seconds_tostring(roundtime), roundtimesize,
+               '1 1 1', 1, DRAWFLAG_NORMAL);
+       if (autocvar_developer == 0)
+       {
+               return;
+       }
+       // Debug info below
+       int redalive = STAT(REDALIVE);
+       int bluealive = STAT(BLUEALIVE);
+       int yellowalive = STAT(YELLOWALIVE);
+       int pinkalive = STAT(PINKALIVE);
+       string message = strcat(ftos(redalive), "/", ftos(yellowalive));
+       vector redpos = mypos;
+       redpos.y += mysize.y;
+       vector statsize = mysize;
+       statsize.x /= 2;
+       drawstring_aspect(redpos, message, statsize, '1 0 0', 1, DRAWFLAG_NORMAL);
+       message = strcat(ftos(bluealive), "/", ftos(pinkalive));
+       vector bluepos = mypos;
+       bluepos.x += mysize.x / 2;
+       bluepos.y += mysize.y;
+       drawstring_aspect(bluepos, message, statsize, '0 0 1', 1, DRAWFLAG_NORMAL);
+}
+
 void HUD_ModIcons_SetFunc()
 {
        HUD_ModIcons_GameType = gametype.m_modicons;
index 4c0fd782904bab4993f0839d428210bb42b2746d..138c944ee827c7fe4870fcc07fa71a03ca921f98 100644 (file)
@@ -85,7 +85,7 @@ CLASS(Gametype, Object)
     }
 ENDCLASS(Gametype)
 
-REGISTRY(Gametypes, 24)
+REGISTRY(Gametypes, 25) // Lyberta: added 1
 #define Gametypes_from(i) _Gametypes_from(i, NULL)
 REGISTER_REGISTRY(Gametypes)
 REGISTRY_CHECK(Gametypes)
@@ -477,6 +477,31 @@ CLASS(Invasion, Gametype)
 ENDCLASS(Invasion)
 REGISTER_GAMETYPE(INVASION, NEW(Invasion));
 
+//=============================================================================
+
+// Lyberta: adding survival gametype
+
+#ifdef CSQC
+void HUD_Mod_SURV(vector pos, vector mySize);
+#endif
+CLASS(Survival, Gametype)
+    INIT(Survival)
+    {
+        this.gametype_init(this, _("Survival"), "surv", "g_surv", true, "", "timelimit=20 pointlimit=5 teams=2 leadlimit=0", _("Survive as long as you can"));
+    }
+    METHOD(Survival, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
+    {
+        return true;
+    }
+#ifdef CSQC
+    ATTRIB(Survival, m_modicons, void(vector pos, vector mySize), HUD_Mod_SURV);
+#endif
+ENDCLASS(Survival)
+REGISTER_GAMETYPE(SURVIVAL, NEW(Survival));
+#define g_surv IS_GAMETYPE(SURVIVAL)
+
+//=============================================================================
+
 const int MAPINFO_FEATURE_WEAPONS       = 1; // not defined for instagib-only maps
 const int MAPINFO_FEATURE_VEHICLES      = 2;
 const int MAPINFO_FEATURE_TURRETS       = 4;
index 9f3eb37c24a5dbb4f0177dbbef9c588f7c07ff3e..1d1dbeca0a05faf28510d815eb8b753c8f2a7c51 100644 (file)
 
     MSG_INFO_NOTIF(SUPERWEAPON_PICKUP,                      N_CONSOLE,  1, 0, "s1", "s1",       "superweapons",         _("^BG%s^K1 picked up a Superweapon"), "")
 
+    MSG_INFO_NOTIF(SURVIVAL_1ST_ROUND_ATTACKER,             N_CONSOLE,    0, 0, "", "",         "",                     _("^BGFirst round. Eliminate the enemy team ^F1as fast as you can^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_1ST_ROUND_DEFENDER,             N_CONSOLE,    0, 0, "", "",         "",                     _("^BGFirst round. Defend yourself ^F1as long as you can^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_2ND_ROUND_ATTACKER,             N_CONSOLE,    0, 1, "f1time", "",   "",                     _("^BGSecond round. Eliminate the enemy team in less than ^F1%s^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_2ND_ROUND_DEFENDER,             N_CONSOLE,    0, 1, "f1time", "",   "",                     _("^BGSecond round. Defend yourself for ^F1%s^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_COOP_DEFENDER,                  N_CONSOLE,    0, 0, "", "",         "",                     _("^BGDefend yourself ^F1as long as you can^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_DEFENDERS_ELIMINATED,           N_CONSOLE,    0, 0, "", "",         "",                     _("^BGDefenders were ^F1eliminated^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_DEFENDERS_ELIMINATED_IN,        N_CONSOLE,    0, 1, "f1time", "",   "",                     _("^BGDefenders were eliminated in ^F1%s^BG."), "")
+    MSG_INFO_NOTIF(SURVIVAL_DEFENDERS_SURVIVED,             N_CONSOLE,    0, 0, "", "",         "",                     _("^BGDefenders have ^F1survived^BG."), "")
+
     MSG_INFO_NOTIF(TEAMCHANGE_LARGERTEAM,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^BGYou cannot change to a larger team"), "")
     MSG_INFO_NOTIF(TEAMCHANGE_NOTALLOWED,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^BGYou are not allowed to change teams"), "")
 
     MSG_CENTER_NOTIF(SUPERWEAPON_LOST,                  N_ENABLE,    0, 0, "",               CPID_POWERUP,           "0 0",  _("^F2Superweapons have been lost"), "")
     MSG_CENTER_NOTIF(SUPERWEAPON_PICKUP,                N_ENABLE,    0, 0, "",               CPID_POWERUP,           "0 0",  _("^F2You now have a superweapon"), "")
 
+    MSG_CENTER_NOTIF(SURVIVAL_1ST_ROUND_ATTACKER,       N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGFirst round. Eliminate the enemy team ^F1as fast as you can^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_1ST_ROUND_DEFENDER,       N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGFirst round. Defend yourself ^F1as long as you can^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_2ND_ROUND_ATTACKER,       N_ENABLE,    0, 1, "f1time",         CPID_Null,              "0 0",  _("^BGSecond round. Eliminate the enemy team in less than ^F1%s^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_2ND_ROUND_DEFENDER,       N_ENABLE,    0, 1, "f1time",         CPID_Null,              "0 0",  _("^BGSecond round. Defend yourself for ^F1%s^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_COOP_DEFENDER,            N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGDefend yourself ^F1as long as you can^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_DEFENDERS_ELIMINATED,     N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGDefenders were ^F1eliminated^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_DEFENDERS_ELIMINATED_IN,  N_ENABLE,    0, 1, "f1time",         CPID_Null,              "0 0",  _("^BGDefenders were eliminated in ^F1%s^BG."), "")
+    MSG_CENTER_NOTIF(SURVIVAL_DEFENDERS_SURVIVED,       N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGDefenders have ^F1survived^BG."), "")
+
     MULTITEAM_CENTER(TEAMCHANGE, 4,                     N_ENABLE,    0, 1, "",               CPID_TEAMCHANGE,        "1 f1", _("^K1Changing to ^TC^TT^K1 in ^COUNT"), "", NAME)
     MSG_CENTER_NOTIF(TEAMCHANGE_AUTO,                   N_ENABLE,    0, 1, "",               CPID_TEAMCHANGE,        "1 f1", _("^K1Changing team in ^COUNT"), "")
     MSG_CENTER_NOTIF(TEAMCHANGE_SPECTATE,               N_ENABLE,    0, 1, "",               CPID_TEAMCHANGE,        "1 f1", _("^K1Spectating in ^COUNT"), "")
index cde626a2a203ba5b30b8dd94eb18ab939b188303..f1686a63b5e98970f53aebe73e2648db7097d68d 100644 (file)
@@ -273,6 +273,12 @@ REGISTER_STAT(DOM_PPS_BLUE, float)
 REGISTER_STAT(DOM_PPS_YELLOW, float)
 REGISTER_STAT(DOM_PPS_PINK, float)
 
+// Lyberta: survival
+REGISTER_STAT(SURV_ROUND_TIME, float)
+REGISTER_STAT(SURV_DEFENDER_TEAM, int)
+REGISTER_STAT(SURV_DEFENDERS_ALIVE, int)
+REGISTER_STAT(SURV_DEFENDER_HEALTH, float)
+
 REGISTER_STAT(TELEPORT_MAXSPEED, float, autocvar_g_teleport_maxspeed)
 REGISTER_STAT(TELEPORT_TELEFRAG_AVOID, int, autocvar_g_telefrags_avoid)
 
index 3151d7cdb1c86fb696128d439ef6a38213cff553..6b2428bf7803b4719d304a231d3e8a3525f1a2c1 100644 (file)
@@ -641,6 +641,69 @@ void Item_ScheduleInitialRespawn(entity e)
        Item_ScheduleRespawnIn(e, max(0, game_starttime - time) + ((e.respawntimestart) ? e.respawntimestart : ITEM_RESPAWNTIME_INITIAL(e)));
 }
 
+void GivePlayerHealth(entity player, float amount)
+{
+       if (amount == 0)
+       {
+               return;
+       }
+       player.health = bound(player.health, player.health + amount,
+                g_pickup_healthmega_max);
+       player.pauserothealth_finished = max(player.pauserothealth_finished, time +
+               autocvar_g_balance_pause_health_rot);
+}
+
+void GivePlayerArmor(entity player, float amount)
+{
+       if (amount == 0)
+       {
+               return;
+       }
+       player.armorvalue = bound(player.armorvalue, player.armorvalue + amount,
+                g_pickup_armormega_max);
+       player.pauserotarmor_finished = max(player.pauserotarmor_finished, time +
+               autocvar_g_balance_pause_armor_rot);
+}
+
+void GivePlayerAmmo(entity player, .float ammotype, float amount)
+{
+       float maxvalue = 999;
+       switch (ammotype)
+       {
+               case ammo_shells:
+               {
+                       maxvalue = g_pickup_shells_max;
+                       break;
+               }
+               case ammo_cells:
+               {
+                       maxvalue = g_pickup_cells_max;
+                       break;
+               }
+               case ammo_rockets:
+               {
+                       maxvalue = g_pickup_rockets_max;
+                       break;
+               }
+               case ammo_plasma:
+               {
+                       maxvalue = g_pickup_plasma_max;
+                       break;
+               }
+               case ammo_nails:
+               {
+                       maxvalue = g_pickup_nails_max;
+                       break;
+               }
+               case ammo_fuel:
+               {
+                       maxvalue = g_pickup_fuel_max;
+                       break;
+               }
+       }
+       player.(ammotype) = min(player.(ammotype) + amount, maxvalue);
+}
+
 float Item_GiveAmmoTo(entity item, entity player, .float ammotype, float ammomax, float mode)
 {
        if (!item.(ammotype))
index 1b2293bcff0651d442032c328d7a0455783adb3b..17e3af939fca2927101456fde3d2f07049cb24c0 100644 (file)
@@ -81,6 +81,26 @@ void Item_ScheduleRespawnIn(entity e, float t);
 void Item_ScheduleRespawn(entity e);
 
 void Item_ScheduleInitialRespawn(entity e);
+
+/// \brief Gives health to the player.
+/// \param[in,out] player Player to give health to.
+/// \param[in] amount Amount of health to give.
+/// \return No return.
+void GivePlayerHealth(entity player, float amount);
+
+/// \brief Gives armor to the player.
+/// \param[in,out] player Player to give armor to.
+/// \param[in] amount Amount of armor to give.
+/// \return No return.
+void GivePlayerArmor(entity player, float amount);
+
+/// \brief Gives ammo of the specified type to the player.
+/// \param[in,out] player Player to give ammo to.
+/// \param[in] type Ammo type property.
+/// \param[in] amount Amount of ammo to give.
+/// \return No return.
+void GivePlayerAmmo(entity player, .float ammotype, float amount);
+
 float ITEM_MODE_NONE = 0;
 float ITEM_MODE_HEALTH = 1;
 float ITEM_MODE_ARMOR = 2;
index 481914200a99f625338e4be2466d158a1e5bcd34..63faabf35d4f94a7bccaaafb70e4ec83e6352a0f 100644 (file)
@@ -75,6 +75,7 @@ void GameType_ConfigureSliders_for_CurrentGametype(entity me)
                case MAPINFO_TYPE_CTS:             GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
                case MAPINFO_TYPE_INVASION:        GameType_ConfigureSliders(me, _("Point limit:"),    50,  500, 10, string_null,                 string_null,                    string_null); break;
                case MAPINFO_TYPE_TEAM_DEATHMATCH: GameType_ConfigureSliders(me, _("Point limit:"),     5,  100,  5, "g_tdm_point_limit",         "g_tdm_teams_override",         _("The amount of points needed before the match will end")); break;
+               case MAPINFO_TYPE_SURVIVAL:        GameType_ConfigureSliders(me, _("Point limit:"),     1,   20,  1, "g_surv_point_limit",        string_null,                    _("The amount of points needed before the match will end")); break;
                default:                           GameType_ConfigureSliders(me, _("Frag limit:"),      5,  100,  5, "fraglimit_override",        string_null,                    _("The amount of frags needed before the match will end")); break;
        }
 }
index 7f038a85fb53efe0f28d7e02a0efbd4855bce3b1..29fe0462f451392c568717bcb4159eb054c7435f 100644 (file)
@@ -693,6 +693,7 @@ float updateCompression()
        GAMETYPE(MAPINFO_TYPE_NEXBALL) \
        GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \
        GAMETYPE(MAPINFO_TYPE_ASSAULT) \
+       GAMETYPE(MAPINFO_TYPE_SURVIVAL) \
        /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \
        /**/
 
index 95f28752714a9c322ebfb08ba67cf0ffbcb930e6..4cf58a1c513382ee047f167264a03290bcef954b 100644 (file)
@@ -271,7 +271,9 @@ void PutObserverInServer(entity this)
        if (mutator_returnvalue) {
            // mutator prevents resetting teams+score
        } else {
+               int oldteam = this.team;
                this.team = -1;  // move this as it is needed to log the player spectating in eventlog
+               MUTATOR_CALLHOOK(Player_ChangedTeam, this, oldteam, this.team); // Lyberta: added hook
         this.frags = FRAGS_SPECTATOR;
         PlayerScore_Clear(this);  // clear scores when needed
     }
index 55c8f881d5a7d3084acc1ee8e3f06818b606d3b8..e2ba88114cf584b7ef98f165e125881fd1e28129 100644 (file)
@@ -281,6 +281,7 @@ void cvar_changes_init()
                BADCVAR("g_race_qualifying_timelimit");
                BADCVAR("g_race_qualifying_timelimit_override");
                BADCVAR("g_snafu");
+               BADCVAR("g_surv"); // Lyberta: adding gamemode cvar
                BADCVAR("g_tdm");
                BADCVAR("g_tdm_teams");
                BADCVAR("g_vip");
index f186dd73bdd094c52ee113cfdd17b1f694ef412e..474806462b2a808727e676f4d5caf17087037e9c 100644 (file)
@@ -130,6 +130,14 @@ MUTATOR_HOOKABLE(MatchEnd, EV_NO_ARGS);
     /**/
 MUTATOR_HOOKABLE(CheckAllowedTeams, EV_CheckAllowedTeams);
 
+/** allows overriding best team */
+#define EV_JoinBestTeam(i, o) \
+    /** player checked     */ i(entity, MUTATOR_ARGV_0_entity) \
+    /** team number        */ i(float, MUTATOR_ARGV_1_float) \
+    /**/                      o(float, MUTATOR_ARGV_1_float) \
+    /**/
+MUTATOR_HOOKABLE(JoinBestTeam, EV_JoinBestTeam);
+
 /** copies variables for spectating "spectatee" to "this" */
 #define EV_SpectateCopy(i, o) \
     /** spectatee   */ i(entity, MUTATOR_ARGV_0_entity) \
@@ -875,7 +883,9 @@ MUTATOR_HOOKABLE(PrepareExplosionByDamage, EV_PrepareExplosionByDamage);
     /**/
 MUTATOR_HOOKABLE(MonsterModel, EV_MonsterModel);
 
-/**/
+/**
+ * Called before player changes their team. Return true to block team change.
+ */
 #define EV_Player_ChangeTeam(i, o) \
     /** player */         i(entity, MUTATOR_ARGV_0_entity) \
        /** current team */   i(float, MUTATOR_ARGV_1_float) \
@@ -883,6 +893,16 @@ MUTATOR_HOOKABLE(MonsterModel, EV_MonsterModel);
     /**/
 MUTATOR_HOOKABLE(Player_ChangeTeam, EV_Player_ChangeTeam);
 
+/**
+ * Called after player has changed their team.
+ */
+#define EV_Player_ChangedTeam(i, o) \
+    /** player */         i(entity, MUTATOR_ARGV_0_entity) \
+       /** old team */   i(float, MUTATOR_ARGV_1_float) \
+       /** current team */       i(float, MUTATOR_ARGV_2_float) \
+    /**/
+MUTATOR_HOOKABLE(Player_ChangedTeam, EV_Player_ChangedTeam);
+
 /**/
 #define EV_URI_GetCallback(i, o) \
        /** id */       i(float, MUTATOR_ARGV_0_float) \
index 6835f5d560b9a325a96e671c21ec17348915ff2a..a816108409dfe62bd2f923bd76160e554cf01080 100644 (file)
@@ -11,4 +11,5 @@
 #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 aef0b332abbaa9a3c5c27560d3e0bcca21297005..0b4a9bbceeaecf3c5cfbec5ab657c5ad7cea80e3 100644 (file)
@@ -11,4 +11,5 @@
 #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
new file mode 100644 (file)
index 0000000..4759584
--- /dev/null
@@ -0,0 +1,2310 @@
+#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 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 Number of weapons that can be randomly given to attackers during
+/// spawn.
+int autocvar_g_surv_attacker_num_random_start_weapons;
+
+/// \brief How much health do defenders get during spawn.
+int autocvar_g_surv_defender_start_health;
+/// \brief How much armor do defenders get during spawn.
+int autocvar_g_surv_defender_start_armor;
+/// \brief How many shells do defenders get during spawn.
+int autocvar_g_surv_defender_start_ammo_shells;
+/// \brief How many bullets do defenders get during spawn.
+int autocvar_g_surv_defender_start_ammo_bullets;
+/// \brief How many rockets do defenders get during spawn.
+int autocvar_g_surv_defender_start_ammo_rockets;
+/// \brief How many cells do defenders get during spawn.
+int autocvar_g_surv_defender_start_ammo_cells;
+/// \brief Number of weapons that can be randomly given to defenders during
+/// spawn.
+int autocvar_g_surv_defender_num_random_start_weapons;
+
+/// \brief How much health does cannon fodder get during spawn.
+int autocvar_g_surv_cannon_fodder_start_health;
+/// \brief How much armor does cannon fodder get during spawn.
+int autocvar_g_surv_cannon_fodder_start_armor;
+/// \brief Number of weapons that can be randomly given to cannon fodder during
+/// spawn.
+int autocvar_g_surv_cannon_fodder_num_random_start_weapons;
+
+/// \brief How much health do attackers get when they pickup ammo.
+int autocvar_g_surv_attacker_pickup_ammo_health;
+/// \brief How much armor do attackers get when they pickup ammo.
+int autocvar_g_surv_attacker_pickup_ammo_armor;
+/// \brief How much health do attackers get when they pickup a weapon.
+int autocvar_g_surv_attacker_pickup_weapon_health;
+/// \brief How much armor do attackers get when they pickup a weapon.
+int autocvar_g_surv_attacker_pickup_weapon_armor;
+/// \brief How much health do attackers get when they pickup a dropped weapon.
+int autocvar_g_surv_attacker_pickup_droppedweapon_health;
+/// \brief How much armor do attackers get when they pickup a dropped weapon.
+int autocvar_g_surv_attacker_pickup_droppedweapon_armor;
+
+/// \brief How many health do defenders get when they pickup small health.
+int autocvar_g_surv_defender_pickup_health_small;
+/// \brief How many health do defenders get when they pickup medium health.
+int autocvar_g_surv_defender_pickup_health_medium;
+/// \brief How many health do defenders get when they pickup big health.
+int autocvar_g_surv_defender_pickup_health_big;
+/// \brief How many health do defenders get when they pickup mega health.
+int autocvar_g_surv_defender_pickup_health_mega;
+/// \brief How many armor do defenders get when they pickup small armor.
+int autocvar_g_surv_defender_pickup_armor_small;
+/// \brief How many armor do defenders get when they pickup medium armor.
+int autocvar_g_surv_defender_pickup_armor_medium;
+/// \brief How many armor do defenders get when they pickup big armor.
+int autocvar_g_surv_defender_pickup_armor_big;
+/// \brief How many armor do defenders get when they pickup mega armor.
+int autocvar_g_surv_defender_pickup_armor_mega;
+/// \brief How many shells do defenders get when they pickup small health/armor.
+int autocvar_g_surv_defender_pickup_shells_small;
+/// \brief How many shells do defenders get when they pickup medium
+/// health/armor.
+int autocvar_g_surv_defender_pickup_shells_medium;
+/// \brief How many shells do defenders get when they pickup big health/armor.
+int autocvar_g_surv_defender_pickup_shells_big;
+/// \brief How many shells do defenders get when they pickup mega health/armor.
+int autocvar_g_surv_defender_pickup_shells_mega;
+/// \brief How many bullets do defenders get when they pickup small
+/// health/armor.
+int autocvar_g_surv_defender_pickup_bullets_small;
+/// \brief How many bullets do defenders get when they pickup medium
+/// health/armor.
+int autocvar_g_surv_defender_pickup_bullets_medium;
+/// \brief How many bullets do defenders get when they pickup big health/armor.
+int autocvar_g_surv_defender_pickup_bullets_big;
+/// \brief How many bullets do defenders get when they pickup mega health/armor.
+int autocvar_g_surv_defender_pickup_bullets_mega;
+/// \brief How many rockets do defenders get when they pickup small
+/// health/armor.
+int autocvar_g_surv_defender_pickup_rockets_small;
+/// \brief How many rockets do defenders get when they pickup medium
+/// health/armor.
+int autocvar_g_surv_defender_pickup_rockets_medium;
+/// \brief How many rockets do defenders get when they pickup big health/armor.
+int autocvar_g_surv_defender_pickup_rockets_big;
+/// \brief How many rockets do defenders get when they pickup mega health/armor.
+int autocvar_g_surv_defender_pickup_rockets_mega;
+/// \brief How many cells do defenders get when they pickup small health/armor.
+int autocvar_g_surv_defender_pickup_cells_small;
+/// \brief How many cells do defenders get when they pickup medium health/armor.
+int autocvar_g_surv_defender_pickup_cells_medium;
+/// \brief How many cells do defenders get when they pickup big health/armor.
+int autocvar_g_surv_defender_pickup_cells_big;
+/// \brief How many cells do defenders get when they pickup mega health/armor.
+int autocvar_g_surv_defender_pickup_cells_mega;
+
+/// \brief How much score attackers gain per 1 point of damage.
+float autocvar_g_surv_attacker_damage_score;
+
+/// \brief How much defenders damage others. Higher values mean more damage.
+float autocvar_g_surv_defender_attack_scale;
+/// \brief How much defenders get damaged. High values mean less damage.
+float autocvar_g_surv_defender_defense_scale;
+
+/// \brief How much cannon fodder damages others. Higher values mean more
+/// damage.
+float autocvar_g_surv_cannon_fodder_attack_scale;
+/// \brief How much cannon fodder gets damaged. Higher values mean less damage.
+float autocvar_g_surv_cannon_fodder_defense_scale;
+
+/// \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 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 Whether defenders drop weapons after death.
+int autocvar_g_surv_defender_drop_weapons;
+
+/// \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.
+.string surv_playermodel; ///< Player model forced by the game.
+
+.entity surv_attack_sprite; ///< Holds the sprite telling attackers to attack.
+
+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_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();
+
+//========================= 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_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);
+       ActivateTeamplay();
+       SetLimits(autocvar_g_surv_point_limit, autocvar_g_surv_point_leadlimit,
+               autocvar_timelimit_override, -1);
+}
+
+/// \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.
+                               bool removedbot = false;
+                               surv_autobalance = false;
+                               FOREACH_CLIENT(true,
+                               {
+                                       if ((it.team == surv_attackerteam) && (it.surv_role ==
+                                               SURVIVAL_ROLE_PLAYER) && IS_BOT_CLIENT(it))
+                                       {
+                                               LOG_TRACE("Changing ", it.netname,
+                                                       " from attacker to cannon fodder.");
+                                               Surv_SetPlayerRole(it, SURVIVAL_ROLE_CANNON_FODDER);
+                                               if (!IS_DEAD(it))
+                                               {
+                                                       Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
+                                               }
+                                               Surv_ChangeNumberOfPlayers(teamnum, -1);
+                                               removedbot = true;
+                                               break;
+                                       }
+                               });
+                               surv_autobalance = true;
+                               if (!removedbot)
+                               {
+                                       LOG_TRACE("No valid bot to remove");
+                                       // No space in team, denying team change.
+                                       TRANSMUTE(Spectator, player);
+                                       return false;
+                               }
+                               LOG_TRACE("Removed bot");
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                       ++surv_numattackerhumans;
+                       LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
+                       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.
+                               bool removedbot = false;
+                               surv_autobalance = false;
+                               FOREACH_CLIENT(true,
+                               {
+                                       if ((it.team == surv_defenderteam) && IS_BOT_CLIENT(it))
+                                       {
+                                               LOG_TRACE("Changing ", it.netname,
+                                                       " from defender to cannon fodder.");
+                                               SetPlayerTeamSimple(it, surv_attackerteam);
+                                               removedbot = true;
+                                               break;
+                                       }
+                               });
+                               surv_autobalance = true;
+                               if (!removedbot)
+                               {
+                                       LOG_TRACE("No valid bot to remove");
+                                       // No space in team, denying team change.
+                                       TRANSMUTE(Spectator, player);
+                                       return false;
+                               }
+                               LOG_TRACE("Removed bot");
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                       ++surv_numdefenderhumans;
+                       LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
+                       return true;
+               }
+       }
+       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.
+                       FOREACH_CLIENT(true,
+                       {
+                               if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                               {
+                                       LOG_TRACE("Changing ", it.netname,
+                                               " from cannon fodder to attacker.");
+                                       Surv_SetPlayerRole(it, SURVIVAL_ROLE_PLAYER);
+                                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                                       if (!IS_DEAD(it))
+                                       {
+                                               Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
+                                       }
+                                       return;
+                               }
+                       });
+                       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.
+                       FOREACH_CLIENT(true,
+                       {
+                               if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                               {
+                                       LOG_TRACE("Changing ", it.netname,
+                                               " from cannon fodder to defender.");
+                                       SetPlayerTeamSimple(it, surv_defenderteam);                                             
+                                       return;
+                               }
+                       });
+                       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);
+       }
+       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)
+                       {
+                               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 = surv_numdefenders * (
+               autocvar_g_surv_defender_start_health +
+               autocvar_g_surv_defender_start_armor);
+       float totalhealth = 0;
+       FOREACH_CLIENT(IS_PLAYER(it),
+       {
+               if (it.team == surv_defenderteam)
+               {
+                       totalhealth += it.health;
+                       totalhealth += it.armorvalue;
+               }
+       });
+       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)
+       {
+               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 (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 Gives start weapons to the player.
+/// \param[in,out] player Player to give weapons to.
+/// \return No return.
+void Surv_GiveStartWeapons(entity player)
+{
+       int numrandomweapons = 0;
+       string randomweaponlist = "";
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       FOREACH(Weapons, it != WEP_Null,
+                       {
+                               if (it.weaponstart)
+                               {
+                                       player.weapons |= it.m_wepset;
+                               }
+                       });
+                       switch (player.surv_role)
+                       {
+                               case SURVIVAL_ROLE_PLAYER:
+                               {
+                                       numrandomweapons =
+                                               autocvar_g_surv_attacker_num_random_start_weapons;
+                                       randomweaponlist = "g_surv_attacker_random_start_weapons";
+                                       break;
+                               }
+                               case SURVIVAL_ROLE_CANNON_FODDER:
+                               {
+                                       numrandomweapons =
+                                               autocvar_g_surv_cannon_fodder_num_random_start_weapons;
+                                       randomweaponlist =
+                                               "g_surv_cannon_fodder_random_start_weapons";
+                                       break;
+                               }
+                       }
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       int numweapons = tokenize_console(cvar_string(
+                               "g_surv_defender_start_weapons"));
+                       for (int i = 0; i < numweapons; ++i)
+                       {
+                               string weapon = argv(i);
+                               FOREACH(Weapons, it != WEP_Null,
+                               {
+                                       if (it.netname == weapon)
+                                       {
+                                               player.weapons |= it.m_wepset;
+                                               break;
+                                       }
+                               });
+                       }
+                       numrandomweapons =
+                               autocvar_g_surv_defender_num_random_start_weapons;
+                       randomweaponlist = "g_surv_defender_random_start_weapons";
+                       break;
+               }
+       }
+       if (numrandomweapons == 0)
+       {
+               return;
+       }
+       int numweapons = tokenize_console(cvar_string(randomweaponlist));
+       if (warmup_stage)
+       {
+               // Give all weapons during warmup stage.
+               for (int i = 0; i < numweapons; ++i)
+               {
+                       string weapon = argv(i);
+                       FOREACH(Weapons, it != WEP_Null,
+                       {
+                               if (it.netname == weapon)
+                               {
+                                       player.weapons |= it.m_wepset;
+                                       break;
+                               }
+                       });
+               }
+               return;
+       }
+       for (int i = 0; i < numrandomweapons; ++i)
+       {
+               // Finding weapon which player doesn't have.
+               WepSet weaponbit = WEPSET(Null);
+               int numattempts = 0;
+               do
+               {
+                       string weapon = argv(floor(random() * numweapons));
+                       FOREACH(Weapons, it != WEP_Null,
+                       {
+                               if (it.netname == weapon)
+                               {
+                                       weaponbit = it.m_wepset;
+                                       break;
+                               }
+                       });
+                       ++numattempts;
+               }
+               while ((player.weapons & weaponbit) && (numattempts < 10));
+               player.weapons |= weaponbit;
+       }
+}
+
+//=============================== 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)
+                               {
+                                       WaypointSprite_Spawn(WP_AssaultDestroy, 0, 0, it, '0 0 64',
+                                               NULL, surv_attackerteam, it, surv_attack_sprite, false,
+                                               RADARICON_OBJECTIVE);
+                                       WaypointSprite_UpdateMaxHealth(it.surv_attack_sprite,
+                                               autocvar_g_surv_defender_start_health +
+                                               autocvar_g_surv_defender_start_armor);
+                                       WaypointSprite_UpdateHealth(it.surv_attack_sprite,
+                                               it.health + it.armorvalue);
+                               }
+                               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)
+{
+       start_weapons = WEPSET(Null);
+       warmup_start_weapons = WEPSET(Null);
+}
+
+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 (!IS_BOT_CLIENT(player))
+       {
+               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)
+               {
+                       teambits |= surv_attackerteambit;
+               }
+               if (surv_allowed_to_spawn && (surv_numdefenderhumans <
+                       autocvar_g_surv_team_size))
+               {
+                       teambits |= surv_defenderteambit;
+               }
+               M_ARGV(0, float) = teambits;
+               return;
+       }
+       int teambits = surv_attackerteambit;
+       if ((player.team == surv_defenderteam) || (surv_numdefenders <
+               autocvar_g_surv_team_size))
+       {
+               teambits |= surv_defenderteambit;
+       }
+       M_ARGV(0, float) = teambits;
+}
+
+/// \brief Hook that determines the best team for the player to join.
+MUTATOR_HOOKFUNCTION(surv, JoinBestTeam, CBC_ORDER_EXCLUSIVE)
+{
+       if (surv_type == SURVIVAL_TYPE_COOP)
+       {
+               return;
+       }
+       entity player = M_ARGV(0, entity);
+       if (IS_BOT_CLIENT(player))
+       {
+               return;
+       }
+       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) = Team_TeamToNumber(surv_attackerteam);
+               return;
+       }
+       if (numattackerhumans > numdefenderhumans)
+       {
+               M_ARGV(1, float) = Team_TeamToNumber(surv_defenderteam);
+               return;
+       }
+       M_ARGV(1, float) = floor(random() * 2) + 1;
+}
+
+/// \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();
+}
+
+MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
+       if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
+       {
+               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
+}
+
+/// \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, 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);
+       Surv_GiveStartWeapons(player);
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       switch (player.surv_role)
+                       {
+                               case SURVIVAL_ROLE_PLAYER:
+                               {
+                                       player.items |= IT_UNLIMITED_AMMO;
+                                       Send_Notification(NOTIF_ONE, player, MSG_CENTER,
+                                               CENTER_ASSAULT_ATTACKING);
+                                       break;
+                               }
+                               case SURVIVAL_ROLE_CANNON_FODDER:
+                               {
+                                       player.health = autocvar_g_surv_cannon_fodder_start_health;
+                                       player.armorvalue =
+                                               autocvar_g_surv_cannon_fodder_start_armor;
+                                       player.items |= IT_UNLIMITED_AMMO;
+                                       break;
+                               }
+                               default:
+                               {
+                                       LOG_TRACE("Survival: PlayerSpawn: Invalid attacker role.");
+                                       break;
+                               }
+                       }
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       if (player.surv_role != SURVIVAL_ROLE_PLAYER)
+                       {
+                               LOG_TRACE("Survival: PlayerSpawn: ", player.netname,
+                                       " has invalid defender role.");
+                       }
+                       player.health = autocvar_g_surv_defender_start_health;
+                       player.armorvalue = autocvar_g_surv_defender_start_armor;
+                       player.ammo_shells = autocvar_g_surv_defender_start_ammo_shells;
+                       player.ammo_nails = autocvar_g_surv_defender_start_ammo_bullets;
+                       player.ammo_rockets = autocvar_g_surv_defender_start_ammo_rockets;
+                       player.ammo_cells = autocvar_g_surv_defender_start_ammo_cells;
+                       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 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 false;
+}
+
+/// \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:
+               {
+                       switch (item.classname)
+                       {
+                               case "item_shells":
+                               case "item_bullets":
+                               case "item_rockets":
+                               case "item_cells":
+                               {
+                                       GivePlayerHealth(player,
+                                               autocvar_g_surv_attacker_pickup_ammo_health);
+                                       GivePlayerArmor(player,
+                                               autocvar_g_surv_attacker_pickup_ammo_armor);
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case "weapon_machinegun":
+                               case "weapon_uzi":
+                               case "weapon_grenadelauncher":
+                               case "weapon_electro":
+                               case "weapon_crylink":
+                               case "weapon_nex":
+                               case "weapon_hagar":
+                               case "weapon_rocketlauncher":
+                               case "replacedweapon":
+                               {
+                                       GivePlayerHealth(player,
+                                               autocvar_g_surv_attacker_pickup_weapon_health);
+                                       GivePlayerArmor(player,
+                                               autocvar_g_surv_attacker_pickup_weapon_armor);
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case "droppedweapon":
+                               {
+                                       GivePlayerHealth(player,
+                                               autocvar_g_surv_attacker_pickup_droppedweapon_health);
+                                       GivePlayerArmor(player,
+                                               autocvar_g_surv_attacker_pickup_droppedweapon_armor);
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+                       return MUT_ITEMTOUCH_CONTINUE;
+               }
+               case surv_defenderteam:
+               {
+                       switch (item.classname)
+                       {
+                               case "item_health_small":
+                               {
+                                       GivePlayerHealth(player,
+                                               autocvar_g_surv_defender_pickup_health_small);
+                                       GivePlayerAmmo(player, ammo_shells,
+                                               autocvar_g_surv_defender_pickup_shells_small);
+                                       GivePlayerAmmo(player, ammo_nails,
+                                               autocvar_g_surv_defender_pickup_bullets_small);
+                                       GivePlayerAmmo(player, ammo_rockets,
+                                               autocvar_g_surv_defender_pickup_rockets_small);
+                                       GivePlayerAmmo(player, ammo_cells,
+                                               autocvar_g_surv_defender_pickup_cells_small);
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_HealthSmall, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "item_health_medium":
+                               {
+                                       GivePlayerHealth(player,
+                                               autocvar_g_surv_defender_pickup_health_medium);
+                                       GivePlayerAmmo(player, ammo_shells,
+                                               autocvar_g_surv_defender_pickup_shells_medium);
+                                       GivePlayerAmmo(player, ammo_nails,
+                                               autocvar_g_surv_defender_pickup_bullets_medium);
+                                       GivePlayerAmmo(player, ammo_rockets,
+                                               autocvar_g_surv_defender_pickup_rockets_medium);
+                                       GivePlayerAmmo(player, ammo_cells,
+                                               autocvar_g_surv_defender_pickup_cells_medium);
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_HealthMedium, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "item_health_big":
+                               {
+                                       GivePlayerHealth(player,
+                                               autocvar_g_surv_defender_pickup_health_big);
+                                       GivePlayerAmmo(player, ammo_shells,
+                                               autocvar_g_surv_defender_pickup_shells_big);
+                                       GivePlayerAmmo(player, ammo_nails,
+                                               autocvar_g_surv_defender_pickup_bullets_big);
+                                       GivePlayerAmmo(player, ammo_rockets,
+                                               autocvar_g_surv_defender_pickup_rockets_big);
+                                       GivePlayerAmmo(player, ammo_cells,
+                                               autocvar_g_surv_defender_pickup_cells_big);
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_HealthBig, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "item_health_mega":
+                               {
+                                       GivePlayerHealth(player,
+                                               autocvar_g_surv_defender_pickup_health_mega);
+                                       GivePlayerAmmo(player, ammo_shells,
+                                               autocvar_g_surv_defender_pickup_shells_mega);
+                                       GivePlayerAmmo(player, ammo_nails,
+                                               autocvar_g_surv_defender_pickup_bullets_mega);
+                                       GivePlayerAmmo(player, ammo_rockets,
+                                               autocvar_g_surv_defender_pickup_rockets_mega);
+                                       GivePlayerAmmo(player, ammo_cells,
+                                               autocvar_g_surv_defender_pickup_cells_mega);
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_HealthMega, VOL_BASE, ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "item_armor_small":
+                               {
+                                       GivePlayerArmor(player,
+                                               autocvar_g_surv_defender_pickup_armor_small);
+                                       GivePlayerAmmo(player, ammo_shells,
+                                               autocvar_g_surv_defender_pickup_shells_small);
+                                       GivePlayerAmmo(player, ammo_nails,
+                                               autocvar_g_surv_defender_pickup_bullets_small);
+                                       GivePlayerAmmo(player, ammo_rockets,
+                                               autocvar_g_surv_defender_pickup_rockets_small);
+                                       GivePlayerAmmo(player, ammo_cells,
+                                               autocvar_g_surv_defender_pickup_cells_small);
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_HealthSmall, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "item_armor_medium":
+                               {
+                                       GivePlayerArmor(player,
+                                               autocvar_g_surv_defender_pickup_armor_medium);
+                                       GivePlayerAmmo(player, ammo_shells,
+                                               autocvar_g_surv_defender_pickup_shells_medium);
+                                       GivePlayerAmmo(player, ammo_nails,
+                                               autocvar_g_surv_defender_pickup_bullets_medium);
+                                       GivePlayerAmmo(player, ammo_rockets,
+                                               autocvar_g_surv_defender_pickup_rockets_medium);
+                                       GivePlayerAmmo(player, ammo_cells,
+                                               autocvar_g_surv_defender_pickup_cells_medium);
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_HealthMedium, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "item_armor_big":
+                               {
+                                       GivePlayerArmor(player,
+                                               autocvar_g_surv_defender_pickup_armor_big);
+                                       GivePlayerAmmo(player, ammo_shells,
+                                               autocvar_g_surv_defender_pickup_shells_big);
+                                       GivePlayerAmmo(player, ammo_nails,
+                                               autocvar_g_surv_defender_pickup_bullets_big);
+                                       GivePlayerAmmo(player, ammo_rockets,
+                                               autocvar_g_surv_defender_pickup_rockets_big);
+                                       GivePlayerAmmo(player, ammo_cells,
+                                               autocvar_g_surv_defender_pickup_cells_big);
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_HealthBig, VOL_BASE, ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "item_armor_mega":
+                               {
+                                       GivePlayerArmor(player,
+                                               autocvar_g_surv_defender_pickup_armor_mega);
+                                       GivePlayerAmmo(player, ammo_shells,
+                                               autocvar_g_surv_defender_pickup_shells_mega);
+                                       GivePlayerAmmo(player, ammo_nails,
+                                               autocvar_g_surv_defender_pickup_bullets_mega);
+                                       GivePlayerAmmo(player, ammo_rockets,
+                                               autocvar_g_surv_defender_pickup_rockets_mega);
+                                       GivePlayerAmmo(player, ammo_cells,
+                                               autocvar_g_surv_defender_pickup_cells_mega);
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_HealthMega, VOL_BASE, ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "item_shells":
+                               case "item_bullets":
+                               case "item_rockets":
+                               case "item_cells":
+                               {
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                               case "droppedweapon":
+                               {
+                                       switch (item.weapon)
+                                       {
+                                               case WEP_SHOTGUN.m_id:
+                                               {
+                                                       GivePlayerAmmo(player, ammo_shells, cvar(
+                                                               "g_pickup_shells_weapon"));
+                                                       break;
+                                               }
+                                               case WEP_MACHINEGUN.m_id:
+                                               {
+                                                       GivePlayerAmmo(player, ammo_nails, cvar(
+                                                               "g_pickup_nails_weapon"));
+                                                       break;
+                                               }
+                                               case WEP_MORTAR.m_id:
+                                               case WEP_HAGAR.m_id:
+                                               case WEP_DEVASTATOR.m_id:
+                                               {
+                                                       GivePlayerAmmo(player, ammo_rockets, cvar(
+                                                               "g_pickup_rockets_weapon"));
+                                                       break;
+                                               }
+                                               case WEP_ELECTRO.m_id:
+                                               case WEP_CRYLINK.m_id:
+                                               case WEP_VORTEX.m_id:
+                                               {
+                                                       GivePlayerAmmo(player, ammo_cells, cvar(
+                                                               "g_pickup_cells_weapon"));
+                                                       break;
+                                               }
+                                       }
+                                       delete(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "replacedweapon":
+                               {
+                                       switch (item.weapon)
+                                       {
+                                               case WEP_SHOTGUN.m_id:
+                                               {
+                                                       GivePlayerAmmo(player, ammo_shells, cvar(
+                                                               "g_pickup_shells_weapon"));
+                                                       break;
+                                               }
+                                               case WEP_RIFLE.m_id:
+                                               {
+                                                       GivePlayerAmmo(player, ammo_nails, cvar(
+                                                               "g_pickup_nails_weapon"));
+                                                       break;
+                                               }
+                                               case WEP_MINE_LAYER.m_id:
+                                               case WEP_SEEKER.m_id:
+                                               {
+                                                       GivePlayerAmmo(player, ammo_rockets, cvar(
+                                                               "g_pickup_rockets_weapon"));
+                                                       break;
+                                               }
+                                               case WEP_HLAC.m_id:
+                                               {
+                                                       GivePlayerAmmo(player, ammo_cells, cvar(
+                                                               "g_pickup_cells_weapon"));
+                                                       break;
+                                               }
+                                       }
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "weapon_machinegun":
+                               case "weapon_uzi":
+                               {
+                                       GivePlayerAmmo(player, ammo_nails,
+                                               cvar("g_pickup_nails_weapon"));
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "weapon_grenadelauncher":
+                               case "weapon_hagar":
+                               case "weapon_rocketlauncher":
+                               {
+                                       GivePlayerAmmo(player, ammo_rockets,
+                                               cvar("g_pickup_rockets_weapon"));
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "weapon_electro":
+                               case "weapon_crylink":
+                               case "weapon_nex":
+                               {
+                                       GivePlayerAmmo(player, ammo_cells,
+                                               cvar("g_pickup_cells_weapon"));
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_WEAPONPICKUP, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               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;
+                               }
+                       }
+                       DebugPrintToChat(player, item.classname);
+                       return MUT_ITEMTOUCH_RETURN;
+               }
+       }
+       return MUT_ITEMTOUCH_CONTINUE;
+}
+
+/// \brief Hook which is called when the player tries to throw their weapon.
+MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
+{
+       entity player = M_ARGV(0, entity);
+       if (player.team == surv_defenderteam)
+       {
+               return true;
+       }
+}
+
+/// \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 damage = M_ARGV(4, float);
+       switch (frag_attacker.team)
+       {
+               case surv_attackerteam:
+               {
+                       switch (frag_attacker.surv_role)
+                       {
+                               case SURVIVAL_ROLE_CANNON_FODDER:
+                               {
+                                       damage *= autocvar_g_surv_cannon_fodder_attack_scale;
+                                       break;
+                               }
+                       }
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       damage *= autocvar_g_surv_defender_attack_scale;
+                       break;
+               }
+       }
+       switch (frag_target.team)
+       {
+               case surv_attackerteam:
+               {
+                       switch (frag_target.surv_role)
+                       {
+                               case SURVIVAL_ROLE_CANNON_FODDER:
+                               {
+                                       damage /= autocvar_g_surv_cannon_fodder_defense_scale;
+                                       break;
+                               }
+                       }
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       damage /= autocvar_g_surv_defender_defense_scale;
+                       break;
+               }
+       }
+       M_ARGV(4, float) = 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;
+               PlayerScore_Add(attacker, SP_SCORE, score);
+       }
+       if (autocvar_g_surv_stealth)
+       {
+               return;
+       }
+       if (target.health < 1)
+       {
+               WaypointSprite_Kill(target.surv_attack_sprite);
+       }
+       else
+       {
+               WaypointSprite_UpdateHealth(target.surv_attack_sprite, target.health +
+                       target.armorvalue);
+       }
+}
+
+/// \brief Hook which is called when the player dies.
+MUTATOR_HOOKFUNCTION(surv, PlayerDies)
+{
+       //DebugPrintToChatAll("PlayerDies");
+       entity attacker = M_ARGV(1, entity);
+       entity victim = M_ARGV(2, entity);
+       if ((attacker.team == surv_defenderteam) &&
+               (victim.team == surv_attackerteam))
+       {
+               switch (victim.surv_role)
+               {
+                       case SURVIVAL_ROLE_PLAYER:
+                       {
+                               GivePlayerHealth(attacker,
+                                       autocvar_g_surv_defender_attacker_frag_health);
+                               GivePlayerArmor(attacker,
+                                       autocvar_g_surv_defender_attacker_frag_armor);
+                               GivePlayerAmmo(attacker, ammo_shells,
+                                       autocvar_g_surv_defender_attacker_frag_shells);
+                               GivePlayerAmmo(attacker, ammo_nails,
+                                       autocvar_g_surv_defender_attacker_frag_bullets);
+                               GivePlayerAmmo(attacker, ammo_rockets,
+                                       autocvar_g_surv_defender_attacker_frag_rockets);
+                               GivePlayerAmmo(attacker, ammo_cells,
+                                       autocvar_g_surv_defender_attacker_frag_cells);
+                               break;
+                       }
+                       case SURVIVAL_ROLE_CANNON_FODDER:
+                       {
+                               GivePlayerHealth(attacker,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_health);
+                               GivePlayerArmor(attacker,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_armor);
+                               GivePlayerAmmo(attacker, ammo_shells,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_shells);
+                               GivePlayerAmmo(attacker, ammo_nails,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_bullets);
+                               GivePlayerAmmo(attacker, ammo_rockets,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_rockets);
+                               GivePlayerAmmo(attacker, ammo_cells,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_cells);
+                               break;
+                       }
+               }
+       }
+       if ((victim.team == surv_defenderteam) &&
+               (autocvar_g_surv_defender_drop_weapons == false))
+       {
+               for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+               {
+                       .entity went = weaponentities[slot];
+                       victim.(went).m_weapon = WEP_Null;
+               }
+       }
+       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;
+}
+
+/// \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
new file mode 100644 (file)
index 0000000..93f997b
--- /dev/null
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "../gamemode.qh"
+
+/// \brief Initializes global data for the gametype.
+/// \return No return.
+void Surv_Initialize();
+
+REGISTER_MUTATOR(surv, false)
+{
+       MUTATOR_ONADD
+       {
+               if (time > 1) // game loads at time 1
+               {
+                       error("This is a game type and it cannot be added at runtime.");
+               }
+               Surv_Initialize();
+       }
+
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back dm_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               error("This is a game type and it cannot be removed at runtime.");
+               return -1;
+       }
+
+       return 0;
+}
index f22ff0daf8f5d1617fcda9cb121e96466c475857..6cee10bd9e30ee841e0268e7a62d57d0ed3a89a6 100644 (file)
@@ -662,15 +662,19 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage,
        }
 }
 
-void MoveToTeam(entity client, int team_colour, int type)
+bool MoveToTeam(entity client, int team_colour, int type)
 {
        int lockteams_backup = lockteams;  // backup any team lock
        lockteams = 0;  // disable locked teams
        TeamchangeFrags(client);  // move the players frags
-       SetPlayerColors(client, team_colour - 1);  // set the players colour
+       if (!SetPlayerTeamSimple(client, team_colour))
+       {
+               return false;
+       }
        Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, client.origin, '0 0 0');  // kill the player
        lockteams = lockteams_backup;  // restore the team lock
        LogTeamchange(client.playerid, client.team, type);
+       return true;
 }
 
 /** print(), but only print if the server is not local */
index b9a5fa928835208119144e2d7749149c7685af4e..3b520dabcea413b61ce234bac27ea6732b7770d7 100644 (file)
@@ -69,7 +69,12 @@ void calculate_player_respawn_time(entity this);
 
 void ClientKill_Now_TeamChange(entity this);
 
-void MoveToTeam(entity client, float team_colour, float type);
+/// \brief Moves player to the specified team.
+/// \param[in,out] client Client to move.
+/// \param[in] team_colour Color of the team.
+/// \param[in] type ???
+/// \return True on success, false otherwise.
+bool MoveToTeam(entity client, float team_colour, float type);
 
 void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force);
 
index d8bf72cd6b2a87bc27cc3700053716840c3bd8b3..beb5169020565eb5d316487e72a343f6491ea0f7 100644 (file)
@@ -190,28 +190,38 @@ void SetPlayerColors(entity pl, float _color)
        }
 }
 
-void SetPlayerTeam(entity pl, float t, float s, float noprint)
+bool SetPlayerTeamSimple(entity player, int teamnum)
 {
-       float _color;
-
-       if(t == 4)
-               _color = NUM_TEAM_4 - 1;
-       else if(t == 3)
-               _color = NUM_TEAM_3 - 1;
-       else if(t == 2)
-               _color = NUM_TEAM_2 - 1;
-       else
-               _color = NUM_TEAM_1 - 1;
-
-       SetPlayerColors(pl,_color);
-
-       if(t != s) {
-               LogTeamchange(pl.playerid, pl.team, 3);  // log manual team join
-
-               if(!noprint)
-                       bprint(playername(pl, false), "^7 has changed from ", Team_NumberToColoredFullName(s), "^7 to ", Team_NumberToColoredFullName(t), "\n");
+       if (player.team == teamnum)
+       {
+               return true;
        }
+       if (MUTATOR_CALLHOOK(Player_ChangeTeam, player, Team_TeamToNumber(
+               player.team), Team_TeamToNumber(teamnum)) == true)
+       {
+               // Mutator has blocked team change.
+               return false;
+       }
+       int oldteam = player.team;
+       SetPlayerColors(player, teamnum - 1);
+       MUTATOR_CALLHOOK(Player_ChangedTeam, player, oldteam, player.team);
+       return true;
+}
 
+void SetPlayerTeam(entity pl, float t, float s, float noprint)
+{
+       if (t == s)
+       {
+               return;
+       }
+       float teamnum = Team_NumberToTeam(t);
+       SetPlayerTeamSimple(pl, teamnum);
+       LogTeamchange(pl.playerid, pl.team, 3);  // log manual team join
+       if (noprint)
+       {
+               return;
+       }
+       bprint(playername(pl, false), "^7 has changed from ", Team_NumberToColoredFullName(s), "^7 to ", Team_NumberToColoredFullName(t), "\n");
 }
 
 // set c1...c4 to show what teams are allowed
@@ -522,7 +532,7 @@ float FindSmallestTeam(entity pl, float ignore_pl)
 
 int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam)
 {
-       float smallest, selectedteam;
+       float bestteam, selectedteam;
 
        // don't join a team if we're not playing a team game
        if(!teamplay)
@@ -550,7 +560,7 @@ int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam)
                {
                        if(!only_return_best)
                        {
-                               SetPlayerColors(this, selectedteam - 1);
+                               SetPlayerTeamSimple(this, selectedteam);
 
                                // when JoinBestTeam is called by client.qc/ClientKill_Now_TeamChange the players team is -1 and thus skipped
                                // when JoinBestTeam is called by client.qc/ClientConnect the player_id is 0 the log attempt is rejected
@@ -561,26 +571,17 @@ int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam)
                // otherwise end up on the smallest team (handled below)
        }
 
-       smallest = FindSmallestTeam(this, true);
+       bestteam = FindSmallestTeam(this, true);
+       MUTATOR_CALLHOOK(JoinBestTeam, this, bestteam);
+       bestteam = M_ARGV(1, float);
 
        if(!only_return_best && !this.bot_forced_team)
        {
-               TeamchangeFrags(this);
-               if(smallest == 1)
+               bestteam = Team_NumberToTeam(bestteam);
+               if (bestteam != -1)
                {
-                       SetPlayerColors(this, NUM_TEAM_1 - 1);
-               }
-               else if(smallest == 2)
-               {
-                       SetPlayerColors(this, NUM_TEAM_2 - 1);
-               }
-               else if(smallest == 3)
-               {
-                       SetPlayerColors(this, NUM_TEAM_3 - 1);
-               }
-               else if(smallest == 4)
-               {
-                       SetPlayerColors(this, NUM_TEAM_4 - 1);
+                       TeamchangeFrags(this);
+                       SetPlayerTeamSimple(this, bestteam);
                }
                else
                {
@@ -593,7 +594,7 @@ int JoinBestTeam(entity this, bool only_return_best, bool forcebestteam)
                        Damage(this, this, this, 100000, DEATH_TEAMCHANGE.m_id, this.origin, '0 0 0');
        }
 
-       return smallest;
+       return bestteam;
 }
 
 //void() ctf_playerchanged;
@@ -645,7 +646,6 @@ void SV_ChangeTeam(entity this, float _color)
        // not changing teams
        if(scolor == dcolor)
        {
-               //bprint("same team change\n");
                SetPlayerTeam(this, dteam, steam, true);
                return;
        }
@@ -674,8 +674,6 @@ void SV_ChangeTeam(entity this, float _color)
                TeamchangeFrags(this);
        }
 
-       MUTATOR_CALLHOOK(Player_ChangeTeam, this, steam, dteam);
-
        SetPlayerTeam(this, dteam, steam, !IS_CLIENT(this));
 
        if(IS_PLAYER(this) && steam != dteam)
index 127ac7a6d30caffc887e9571c40104148813eadb..3895bf6b6f2cbeaee00d823ead1bdef06903ca1d 100644 (file)
@@ -28,6 +28,12 @@ string getwelcomemessage(entity this);
 
 void SetPlayerColors(entity pl, float _color);
 
+/// \brief Sets the team of the player.
+/// \param[in,out] player Player to adjust.
+/// \param[in] teamnum Team number to set. See TEAM_NUM constants.
+/// \return True if team switch was successful, false otherwise.
+bool SetPlayerTeamSimple(entity player, int teamnum);
+
 void SetPlayerTeam(entity pl, float t, float s, float noprint);
 
 // set c1...c4 to show what teams are allowed
diff --git a/survival.cfg b/survival.cfg
new file mode 100644 (file)
index 0000000..0f9bc54
--- /dev/null
@@ -0,0 +1,101 @@
+// Cvars for survival gamemode.
+
+alias cl_hook_gamestart_surv
+alias sv_hook_gamestart_surv
+alias sv_vote_gametype_hook_surv
+
+set g_surv_respawn_delay_small 0
+set g_surv_respawn_delay_small_count 0
+set g_surv_respawn_delay_large 0
+set g_surv_respawn_delay_large_count 0
+set g_surv_respawn_delay_max 0
+set g_surv_respawn_waves 0
+set g_surv_weapon_stay 0
+
+set g_surv 0 "Survival: survive as long as you can"
+set g_surv_type "versus" "Type of the survival gametype"
+set g_surv_warmup 10 "How long the players will have time to run around the map before the round starts"
+set g_surv_round_timelimit 300 "Round time limit in seconds"
+seta g_surv_point_limit -1 "Survival point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_surv_point_leadlimit -1 "Survival point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+set g_surv_team_size 4 "How much players are allowed in teams (excluding cannon fodder)"
+set g_surv_stealth 0 "If set, defenders will not be shown on the radar"
+
+set g_surv_attacker_force_overkill_models 0 "Whether to force overkill player models for attackers"
+set g_surv_defender_force_overkill_models 1 "Whether to force overkill player models for defenders"
+set g_surv_cannon_fodder_force_overkill_models 0 "Whether to force overkill player models for cannon fodder"
+
+set g_surv_attacker_num_random_start_weapons 2 "Number of weapons that can be randomly given to attackers during spawn"
+set g_surv_attacker_random_start_weapons "machinegun mortar electro crylink vortex hagar devastator" "Weapons that can be randomly given to attackers during spawn"
+
+set g_surv_defender_start_health 200 "How much health do defenders get during spawn"
+set g_surv_defender_start_armor 200 "How much armor do defenders get during spawn"
+set g_surv_defender_start_ammo_shells 20 "How many shells do defenders get during spawn"
+set g_surv_defender_start_ammo_bullets 15 "How many bullets do defenders get during spawn"
+set g_surv_defender_start_ammo_rockets 0 "How many rockets do defenders get during spawn"
+set g_surv_defender_start_ammo_cells 30 "How many cells do defenders get during spawn"
+set g_surv_defender_start_weapons "okshotgun okmachinegun okvortex" "Which weapons to give to defenders during spawn"
+set g_surv_defender_num_random_start_weapons 0 "Number of weapons that can be randomly given to defenders during spawn"
+set g_surv_defender_random_start_weapons "" "Weapons that can be randomly given to defenders during spawn"
+
+set g_surv_cannon_fodder_start_health 100 "How much health does cannon fodder get during spawn"
+set g_surv_cannon_fodder_start_armor 0 "How much armor does cannon fodder get during spawn"
+set g_surv_cannon_fodder_num_random_start_weapons 2 "Number of weapons that can be randomly given to cannon fodder during spawn"
+set g_surv_cannon_fodder_random_start_weapons "machinegun mortar electro crylink vortex hagar devastator" "Weapons that can be randomly given to cannon fodder during spawn"
+
+set g_surv_attacker_pickup_ammo_health 25 "How much health do attackers get when they pickup ammo"
+set g_surv_attacker_pickup_ammo_armor 25 "How much armor do attackers get when they pickup ammo"
+set g_surv_attacker_pickup_weapon_health 0 "How much health do attackers get when they pickup a weapon"
+set g_surv_attacker_pickup_weapon_armor 0 "How much armor do attackers get when they pickup a weapon"
+set g_surv_attacker_pickup_droppedweapon_health 25 "How much health do attackers get when they pickup a dropped weapon"
+set g_surv_attacker_pickup_droppedweapon_armor 25 "How much armor do attackers get when they pickup a dropped weapon"
+
+set g_surv_defender_pickup_health_small 0 "How much health do defenders get when they pickup small health"
+set g_surv_defender_pickup_health_medium 0 "How much health do defenders get when they pickup medium health"
+set g_surv_defender_pickup_health_big 0 "How much health do defenders get when they pickup big health"
+set g_surv_defender_pickup_health_mega 0 "How much health do defenders get when they pickup mega health"
+set g_surv_defender_pickup_armor_small 0 "How much armor do defenders get when they pickup small armor"
+set g_surv_defender_pickup_armor_medium 0 "How much armor do defenders get when they pickup medium armor"
+set g_surv_defender_pickup_armor_big 0 "How much armor do defenders get when they pickup big armor"
+set g_surv_defender_pickup_armor_mega 0 "How much armor do defenders get when they pickup mega armor"
+set g_surv_defender_pickup_shells_small 6 "How many shells do defenders get when they pickup small health/armor"
+set g_surv_defender_pickup_shells_medium 15 "How many shells do defenders get when they pickup medium health/armor"
+set g_surv_defender_pickup_shells_big 30 "How many shells do defenders get when they pickup big health/armor"
+set g_surv_defender_pickup_shells_mega 60 "How many shells do defenders get when they pickup mega health/armor"
+set g_surv_defender_pickup_bullets_small 5 "How many bullets do defenders get when they pickup small health/armor"
+set g_surv_defender_pickup_bullets_medium 10 "How many bullets do defenders get when they pickup medium health/armor"
+set g_surv_defender_pickup_bullets_big 15 "How many bullets do defenders get when they pickup big health/armor"
+set g_surv_defender_pickup_bullets_mega 30 "How many bullets do defenders get when they pickup mega health/armor"
+set g_surv_defender_pickup_rockets_small 0 "How many rockets do defenders get when they pickup small health/armor"
+set g_surv_defender_pickup_rockets_medium 0 "How many rockets do defenders get when they pickup medium health/armor"
+set g_surv_defender_pickup_rockets_big 0 "How many rockets do defenders get when they pickup big health/armor"
+set g_surv_defender_pickup_rockets_mega 0 "How many rockets do defenders get when they pickup mega health/armor"
+set g_surv_defender_pickup_cells_small 10 "How many cells do defenders get when they pickup small health/armor"
+set g_surv_defender_pickup_cells_medium 20 "How many cells do defenders get when they pickup medium health/armor"
+set g_surv_defender_pickup_cells_big 40 "How many cells do defenders get when they pickup big health/armor"
+set g_surv_defender_pickup_cells_mega 50 "How many cells do defenders get when they pickup mega health/armor"
+
+set g_surv_attacker_damage_score 0.025 "How much score attackers gain per 1 point of damage"
+
+set g_surv_defender_attack_scale 1 "How much defenders damage others. Higher values mean more damage"
+set g_surv_defender_defense_scale 2 "How much defenders get damaged. Higher values mean less damage"
+
+set g_surv_cannon_fodder_attack_scale 1 "How much cannon fodder damages others. Higher values mean more damage"
+set g_surv_cannon_fodder_defense_scale 1 "How much cannon fodder gets damaged. Higher values mean less damage"
+
+set g_surv_attacker_frag_score 10 "How much score attackers get for fragging defenders"
+
+set g_surv_defender_attacker_frag_health 0 "How much health do defenders get when they frag an attacker"
+set g_surv_defender_attacker_frag_armor 0 "How much armor do defenders get when they frag an attacker"
+set g_surv_defender_attacker_frag_shells 0 "How many shells do defenders get when they frag an attacker"
+set g_surv_defender_attacker_frag_bullets 0 "How many bullets do defenders get when they frag an attacker"
+set g_surv_defender_attacker_frag_rockets 0 "How many rockets do defenders get when they frag an attacker"
+set g_surv_defender_attacker_frag_cells 0 "How many cells do defenders get when they frag an attacker"
+set g_surv_defender_cannon_fodder_frag_health 0 "How much health do defenders get when they frag cannon fodder"
+set g_surv_defender_cannon_fodder_frag_armor 0 "How much armor do defenders get when they frag cannon fodder"
+set g_surv_defender_cannon_fodder_frag_shells 0 "How many shells do defenders get when they frag cannon fodder"
+set g_surv_defender_cannon_fodder_frag_bullets 0 "How many bullets do defenders get when they frag cannon fodder"
+set g_surv_defender_cannon_fodder_frag_rockets 0 "How many rockets do defenders get when they frag cannon fodder"
+set g_surv_defender_cannon_fodder_frag_cells 0 "How many cells do defenders get when they frag cannon fodder"
+
+set g_surv_defender_drop_weapons 0 "Whether defenders drop weapons after death"