]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'Lyberta/StandaloneOverkillWeapons' into Lyberta/master
authorLyberta <lyberta@lyberta.net>
Mon, 4 Dec 2017 17:58:10 +0000 (20:58 +0300)
committerLyberta <lyberta@lyberta.net>
Mon, 4 Dec 2017 17:58:10 +0000 (20:58 +0300)
31 files changed:
gamemodes-server.cfg
player-template-example.cfg [new file with mode: 0644]
qcsrc/client/hud/panel/modicons.qc
qcsrc/common/gamemodes/gamemode/_mod.inc
qcsrc/common/gamemodes/gamemode/_mod.qh
qcsrc/common/gamemodes/gamemode/gungame/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/gungame/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh [new file with mode: 0644]
qcsrc/common/mapinfo.qh
qcsrc/common/mutators/mutator/waypoints/all.inc
qcsrc/common/notifications/all.inc
qcsrc/common/stats.qh
qcsrc/common/teams.qh
qcsrc/menu/xonotic/dialog_multiplayer_create.qc
qcsrc/menu/xonotic/util.qc
qcsrc/server/_mod.inc
qcsrc/server/_mod.qh
qcsrc/server/bot/default/bot.qc
qcsrc/server/client.qc
qcsrc/server/g_world.qc
qcsrc/server/playertemplates.qc [new file with mode: 0644]
qcsrc/server/playertemplates.qh [new file with mode: 0644]
qcsrc/server/teamplay.qc
qcsrc/server/teamplay.qh
sound/misc/kill.ogg
survival.cfg [new file with mode: 0644]

index 6790a3b4fb9575ab91e070c57bc984ac97609ba2..c11ecacbf0673f7fe1e57216ee66b3b0c3391a1b 100644 (file)
@@ -28,6 +28,7 @@ alias sv_hook_gamestart_cts
 alias sv_hook_gamestart_ka
 alias sv_hook_gamestart_ft
 alias sv_hook_gamestart_inv
+alias sv_hook_gamestart_gg
 alias sv_hook_gamerestart
 alias sv_hook_gameend
 
@@ -47,6 +48,7 @@ alias sv_vote_gametype_hook_cts
 alias sv_vote_gametype_hook_dm
 alias sv_vote_gametype_hook_dom
 alias sv_vote_gametype_hook_ft
+alias sv_vote_gametype_hook_gg
 alias sv_vote_gametype_hook_inv
 alias sv_vote_gametype_hook_ka
 alias sv_vote_gametype_hook_kh
@@ -196,7 +198,13 @@ set g_inv_respawn_delay_large_count 0
 set g_inv_respawn_delay_max 0
 set g_inv_respawn_waves 0
 set g_inv_weapon_stay 0
-
+set g_gg_respawn_delay_small 0
+set g_gg_respawn_delay_small_count 0
+set g_gg_respawn_delay_large 0
+set g_gg_respawn_delay_large_count 0
+set g_gg_respawn_delay_max 0
+set g_gg_respawn_waves 0
+set g_gg_weapon_stay 0
 
 // =========
 //  assault
@@ -522,3 +530,12 @@ 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"
 set g_invasion_type 0 "type of invasion mode - 0: round-based, 1: hunting, 2: complete the stage (note: use mapinfo to set this)"
+
+// =========
+//  gungame
+// =========
+set g_gg 0 "GunGame: Kill players with all weapons"
+set g_gg_weapons "vortex mortar machinegun hagar arc electro devastator crylink shotgun blaster"
+set g_gg_kills_per_weapon 3 "Number of kills needed to advance to the next weapon"
+
+exec survival.cfg // Lyberta: surv
diff --git a/player-template-example.cfg b/player-template-example.cfg
new file mode 100644 (file)
index 0000000..77a2c4f
--- /dev/null
@@ -0,0 +1,91 @@
+// Player template example
+// This config file defines a player template named "example" that has vanilla
+// behavior. It is intended to be a starting point for making your own player
+// templates. Just copy the variables and replace "example" with your own name.
+
+// Spawn section
+// Variables in this section define what happens during spawn.
+set g_player_template_example_start_health "default" "How much health does the player get during spawn."
+set g_player_template_example_start_armor "default" "How much armor does the player get during spawn."
+set g_player_template_example_unlimited_ammo "default" "Whether to give player unlimited ammo."
+set g_player_template_example_start_ammo_shells "default" "How much shells does the player get during spawn."
+set g_player_template_example_start_ammo_bullets "default" "How much bullets does the player get during spawn."
+set g_player_template_example_start_ammo_rockets "default" "How much rockets does the player get during spawn."
+set g_player_template_example_start_ammo_cells "default" "How much cells does the player get during spawn."
+set g_player_template_example_start_ammo_plasma "default" "How much plasma does the player get during spawn."
+set g_player_template_example_start_ammo_fuel "default" "How much fuel does the player get during spawn."
+// These weapons are given to all players by default. They are usually blaster and shotgun.
+set g_player_template_example_default_start_weapons "default" "Whether to give player default start weapons."
+// These weapons will be given unconditionally.
+set g_player_template_example_start_weapons "default" "Which weapons does player get during spawn."
+// These weapons will be given randomly. The player will get the exact amount of
+// random weapons. The code will make sure that they are not duplicate.
+set g_player_template_example_random_start_weapons_count "default" "Number of weapons that can be randomly given to player during spawn."
+set g_player_template_example_random_start_weapons "default" "Weapons that can be randomly given to player during spawn."
+set g_player_template_example_random_start_shells "default" "How much shells does the player get with random start shell-based weapon."
+set g_player_template_example_random_start_bullets "default" "How much bullets does the player get with random start bullet-based weapon."
+set g_player_template_example_random_start_rockets "default" "How much rockets does the player get with random start rocket-based weapon."
+set g_player_template_example_random_start_cells "default" "How much cells does the player get with random start cell-based weapon."
+set g_player_template_example_random_start_plasma "default" "How much plasma does the player get with random start cell-based weapon."
+set g_player_template_example_start_extra_lives "default" "How many extra lives does the player get during spawn in instagib."
+set g_player_template_example_start_ammo_vaporizer_cells "default" "How many vaporizer cells does the player get during spawn in instagib."
+
+set g_player_template_example_drop_weapons "default" "Whether the player can drop weapons by throwing them or by dying."
+
+// Item pickup section
+// Variables in this section define what happens during item pickup.
+// Player can get health, armor and/or ammo on top of or instead of an item.
+// The format of the value is:
+// "<add|override> [health] [armor] [shells] [bullets] [rockets] [cells] [plasma] [fuel]"
+// For example "add 50" will give 50 health and the original item.
+// "override 0 0 5" will give 5 shells instead of the item.
+set g_player_template_example_pickup_health_small "default" "What items does player get when they pickup small health."
+set g_player_template_example_pickup_health_medium "default" "What items does player get when they pickup medium health."
+set g_player_template_example_pickup_health_big "default" "What items does player get when they pickup big health."
+set g_player_template_example_pickup_health_mega "default" "What items does player get when they pickup mega health."
+set g_player_template_example_pickup_armor_small "default" "What items does player get when they pickup small armor."
+set g_player_template_example_pickup_armor_medium "default" "What items does player get when they pickup medium armor."
+set g_player_template_example_pickup_armor_big "default" "What items does player get when they pickup big armor."
+set g_player_template_example_pickup_armor_mega "default" "What items does player get when they pickup mega armor."
+set g_player_template_example_pickup_item_shells "default" "What items does player get when they pickup shells."
+set g_player_template_example_pickup_item_bullets "default" "What items does player get when they pickup bullets."
+set g_player_template_example_pickup_item_rockets "default" "What items does player get when they pickup rockets."
+set g_player_template_example_pickup_item_cells "default" "What items does player get when they pickup cells."
+set g_player_template_example_pickup_item_plasma "default" "What items does player get when they pickup plasma."
+set g_player_template_example_pickup_item_fuel "default" "What items does player get when they pickup fuel."
+set g_player_template_example_pickup_weapon_blaster "default" "What items does player get when they pickup a blaster."
+set g_player_template_example_pickup_weapon_shotgun "default" "What items does player get when they pickup a shotgun."
+set g_player_template_example_pickup_weapon_machinegun "default" "What items does player get when they pickup a machinegun."
+set g_player_template_example_pickup_weapon_mortar "default" "What items does player get when they pickup a mortar."
+set g_player_template_example_pickup_weapon_electro "default" "What items does player get when they pickup an electro."
+set g_player_template_example_pickup_weapon_crylink "default" "What items does player get when they pickup a crylink."
+set g_player_template_example_pickup_weapon_vortex "default" "What items does player get when they pickup a vortex."
+set g_player_template_example_pickup_weapon_hagar "default" "What items does player get when they pickup a hagar."
+set g_player_template_example_pickup_weapon_devastator "default" "What items does player get when they pickup a devastator."
+set g_player_template_example_pickup_weapon_shockwave "default" "What items does player get when they pickup a shockwave."
+set g_player_template_example_pickup_weapon_arc "default" "What items does player get when they pickup an arc."
+set g_player_template_example_pickup_weapon_hook "default" "What items does player get when they pickup a hook."
+set g_player_template_example_pickup_weapon_tuba "default" "What items does player get when they pickup a tuba."
+set g_player_template_example_pickup_weapon_porto "default" "What items does player get when they pickup a port-o-launch."
+set g_player_template_example_pickup_weapon_fireball "default" "What items does player get when they pickup a fireball."
+set g_player_template_example_pickup_weapon_minelayer "default" "What items does player get when they pickup a mine layer."
+set g_player_template_example_pickup_weapon_hlac "default" "What items does player get when they pickup a HLAC."
+set g_player_template_example_pickup_weapon_rifle "default" "What items does player get when they pickup a rifle."
+set g_player_template_example_pickup_weapon_seeker "default" "What items does player get when they pickup a TAG seeker."
+set g_player_template_example_pickup_weapon_vaporizer "default" "What items does player get when they pickup a vaporizer."
+set g_player_template_example_pickup_weapon_hmg "default" "What items does player get when they pickup an HMG."
+set g_player_template_example_pickup_weapon_rpc "default" "What items does player get when they pickup an RPC."
+set g_player_template_example_pickup_item_strength "default" "What items does player get when they pickup strength."
+set g_player_template_example_pickup_item_shield "default" "What items does player get when they pickup shield."
+set g_player_template_example_pickup_item_fuel_regen "default" "What items does player get when they pickup fuel regeneration."
+set g_player_template_example_pickup_item_jetpack "default" "What items does player get when they pickup jetpack."
+set g_player_template_example_pickup_item_vaporizer_cells "default" "What items does player get when they pickup vaporizer cells."
+set g_player_template_example_pickup_item_invisibility "default" "What items does player get when they pickup invisibility."
+set g_player_template_example_pickup_item_extralife "default" "What items does player get when they pickup extra life."
+set g_player_template_example_pickup_item_speed "default" "What items does player get when they pickup speed."
+
+// Damage section
+// Variables in this section define what happens during damage.
+set g_player_template_example_attack_scale "default" "How much player damages others. Higher values mean more damage."
+set g_player_template_example_defense_scale "default" "How much player gets damaged. Higher values mean less damage."
+set g_player_template_example_blaster_self_damage "default" "Whether player gets damaged with their own blaster."
index 65682b3ec7d27006a653449ad69e7b725ba69516..2513a803f344cc13c5fc35062920a8a3a085bf93 100644 (file)
@@ -711,6 +711,135 @@ void HUD_Mod_Dom(vector myPos, vector mySize)
        }
 }
 
+void HUD_Mod_GG(vector pos, vector mySize)
+{
+       mod_active = 1; // required in each mod function that always shows something
+       int stat_weapon = STAT(GUNGAME_LEADING_WEAPON);
+       vector pic_pos, pic_size;
+       if (mySize.x > mySize.y)
+       {
+               pic_pos = pos + eX * 0.25 * mySize.x;
+               pic_size = vec2(0.5 * mySize.x, mySize.y);
+       }
+       else
+       {
+               pic_pos = pos + eY * 0.25 * mySize.y;
+               pic_size = vec2(mySize.x, 0.5 * mySize.y);
+       }
+       string weapon_pic = string_null;
+       FOREACH(Weapons, it != WEP_Null,
+       {
+               if (it.m_id == stat_weapon)
+               {
+                       weapon_pic = it.model2;
+                       break;
+               }
+       });
+       if (!weapon_pic)
+       {
+               return;
+       }
+       drawpic_aspect_skin(pic_pos, weapon_pic, pic_size, '1 1 1', 1,
+               DRAWFLAG_NORMAL);
+}
+
+// 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 2fc2c404678883117dcd9205365e36317f5e997e..866e16227928c9e3c25826a8284c71b85652acaa 100644 (file)
@@ -1,4 +1,6 @@
 // generated file; do not modify
 
+#include <common/gamemodes/gamemode/gungame/_mod.inc>
 #include <common/gamemodes/gamemode/nexball/_mod.inc>
 #include <common/gamemodes/gamemode/onslaught/_mod.inc>
+#include <common/gamemodes/gamemode/survival/_mod.inc>
index d79957012609493478bdf9e0a03ea2116fec63c3..f4c598f697af8f0cb85c1eeec5ff5d2be982c4db 100644 (file)
@@ -1,4 +1,6 @@
 // generated file; do not modify
 
+#include <common/gamemodes/gamemode/gungame/_mod.qh>
 #include <common/gamemodes/gamemode/nexball/_mod.qh>
 #include <common/gamemodes/gamemode/onslaught/_mod.qh>
+#include <common/gamemodes/gamemode/survival/_mod.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/gungame/_mod.inc b/qcsrc/common/gamemodes/gamemode/gungame/_mod.inc
new file mode 100644 (file)
index 0000000..a0bb673
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/gungame/sv_gungame.qc>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/gungame/_mod.qh b/qcsrc/common/gamemodes/gamemode/gungame/_mod.qh
new file mode 100644 (file)
index 0000000..d97ca6f
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/gungame/sv_gungame.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qc b/qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qc
new file mode 100644 (file)
index 0000000..b2db39b
--- /dev/null
@@ -0,0 +1,232 @@
+/// \file
+/// \brief Source file that contains implementation of the GunGame gamemode.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+#include "sv_gungame.qh"
+
+//============================ Constants ======================================
+
+const string GUNGAME_WEAPONS = "g_gg_weapons";
+
+//======================= Global variables ====================================
+
+/// \brief Number of kills needed to advance to the next weapon.
+int autocvar_g_gg_kills_per_weapon;
+
+int gungame_maxlevel; ///< Player who reaches this level wins.
+string gungame_weapons; ///< Holds weapons corresponding to levels.
+
+entity gungame_leading_player; ///< Holds the leading player.
+int gungame_leading_level; ///< Holds the leading level.
+entity gungame_leading_weapon; ///< Holds the leading weapon.
+
+//====================== Forward declarations =================================
+
+/// \brief Resets the state to initial one.
+/// \return No return.
+void GunGame_Reset();
+
+/// \brief Returns the weapon that corresponds to the given level.
+/// \param[in] level Level of the weapon.
+/// \return Weapon corresponding to the given level.
+entity GunGame_GetWeapon(int level);
+
+/// \brief Updates stats of all players.
+/// \return No return.
+void GunGame_UpdateStats();
+
+//========================= Free functions ====================================
+
+void GunGame_Initialize()
+{
+       GunGame_Reset();
+}
+
+void GunGame_Reset()
+{
+       if (gungame_weapons)
+       {
+               strunzone(gungame_weapons);
+       }
+       gungame_weapons = strzone(cvar_string(GUNGAME_WEAPONS));
+       gungame_maxlevel = tokenize_console(gungame_weapons) *
+               autocvar_g_gg_kills_per_weapon;
+       if (gungame_maxlevel == 0)
+       {
+               error("GunGame: Invalid weapon configuration.");
+       }
+       GameRules_limit_score(gungame_maxlevel);
+       gungame_leading_player = NULL;
+       gungame_leading_level = 0;
+       gungame_leading_weapon = GunGame_GetWeapon(0);
+       GunGame_UpdateStats();
+}
+
+entity GunGame_GetWeapon(int level)
+{
+       if (level >= gungame_maxlevel)
+       {
+               return NULL;
+       }
+       tokenize_console(gungame_weapons);
+       string weapon = argv(floor(level / autocvar_g_gg_kills_per_weapon));
+       FOREACH(Weapons, it != WEP_Null,
+       {
+               if (it.netname == weapon)
+               {
+                       return it;
+               }
+       });
+       error("GunGame_GetWeapon: Invalid level or weapon name");
+       return NULL;
+}
+
+/// \brief Returns the player level.
+/// \param[in] player Player to check.
+/// \return Level of the player.
+int GunGame_GetPlayerLevel(entity player)
+{
+       return PlayerScore_Get(player, SP_SCORE);
+}
+
+/// \brief Updates the information about the leading player.
+/// \return No return.
+void GunGame_UpdateLeadingPlayer()
+{
+       entity previous_leader = gungame_leading_player;
+       FOREACH_CLIENT(true,
+       {
+               if (gungame_leading_player == NULL)
+               {
+                       gungame_leading_player = it;
+                       continue;
+               }
+               if (GunGame_GetPlayerLevel(it) > GunGame_GetPlayerLevel(
+                       gungame_leading_player))
+               {
+                       gungame_leading_player = it;
+               }
+       });
+       if (gungame_leading_player == NULL)
+       {
+               return;
+       }
+       if ((gungame_leading_player == previous_leader) &&
+               (GunGame_GetPlayerLevel(gungame_leading_player) ==
+               gungame_leading_level))
+       {
+               return;
+       }
+       gungame_leading_level = GunGame_GetPlayerLevel(gungame_leading_player);
+       gungame_leading_weapon = GunGame_GetWeapon(gungame_leading_level);
+       GunGame_UpdateStats();
+       //PrintToChatAll(strcat(gungame_leading_player.netname,
+       //      " is leading with level ", ftos(gungame_leading_level)));
+}
+
+void GunGame_UpdateStats()
+{
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(GUNGAME_LEADING_WEAPON, it) = gungame_leading_weapon.m_id;
+       });
+}
+
+/// \brief Gives the player a weapon that corresponds to their level.
+/// \param[in,out] player Player to give weapon to.
+/// \return No return.
+void GunGame_GivePlayerWeapon(entity player)
+{
+       int level = GunGame_GetPlayerLevel(player);
+       if (level >= gungame_maxlevel)
+       {
+               return;
+       }
+       entity weapon = GunGame_GetWeapon(level);
+       player.weapons |= weapon.m_wepset;
+       centerprint(player, strcat("^3Level ", ftos(level + 1), ": ^2",
+               weapon.m_name));
+}
+
+//============================= Hooks ========================================
+
+/// \brief Hook that is called to determine if there is a weapon arena.
+MUTATOR_HOOKFUNCTION(gg, SetWeaponArena)
+{
+       //PrintToChatAll("SetWeaponArena");
+       M_ARGV(0, string) = "off";
+}
+
+/// \brief Hook that is called to determine start items of all players.
+MUTATOR_HOOKFUNCTION(gg, SetStartItems)
+{
+       //PrintToChatAll("SetStartItems");
+       start_weapons = WEPSET(Null);
+       warmup_start_weapons = WEPSET(Null);
+}
+
+/// \brief Hook that is called when an item is about to spawn.
+MUTATOR_HOOKFUNCTION(gg, FilterItem)
+{
+       //PrintToChatAll("FilterItem");
+       entity item = M_ARGV(0, entity);
+       if (item.itemdef.instanceOfWeaponPickup)
+       {
+               // Block weapons from spawning.
+               return true;
+       }
+}
+
+/// \brief Hook that is called when player connects to the server.
+MUTATOR_HOOKFUNCTION(gg, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+       if (!IS_REAL_CLIENT(player))
+       {
+               return true;
+       }
+       STAT(GUNGAME_LEADING_WEAPON, player) = gungame_leading_weapon.m_id;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(gg, reset_map_global)
+{
+       GunGame_Reset();
+}
+
+/// \brief Hook that is called when player spawns.
+MUTATOR_HOOKFUNCTION(gg, PlayerSpawn, CBC_ORDER_LAST)
+{
+       entity player = M_ARGV(0, entity);
+       player.weapons = WEPSET(Null);
+       GunGame_GivePlayerWeapon(player);
+       player.items |= IT_UNLIMITED_AMMO;
+}
+
+/// \brief Hook which is called when the player tries to throw their weapon.
+MUTATOR_HOOKFUNCTION(gg, ForbidThrowCurrentWeapon)
+{
+       return true;
+}
+
+/// \brief Hook that is called when player dies.
+MUTATOR_HOOKFUNCTION(gg, PlayerDies)
+{
+       GunGame_UpdateLeadingPlayer();
+       entity attacker = M_ARGV(1, entity);
+       if (!IS_PLAYER(attacker) || IS_DEAD(attacker) || (GunGame_GetPlayerLevel(
+               attacker) >= gungame_maxlevel))
+       {
+               return;
+       }
+       attacker.weapons = WEPSET(Null);
+       GunGame_GivePlayerWeapon(attacker);
+}
+
+/// \brief Hook that determines whether remaining frags are announced.
+MUTATOR_HOOKFUNCTION(gg, Scores_CountFragsRemaining)
+{
+       // announce remaining frags
+       return true;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qh b/qcsrc/common/gamemodes/gamemode/gungame/sv_gungame.qh
new file mode 100644 (file)
index 0000000..44df2e3
--- /dev/null
@@ -0,0 +1,20 @@
+/// \file
+/// \brief Header file that describes the GunGame gamemode.
+/// \author Lyberta
+/// \copyright GNU GPLv2 or any later version.
+
+#pragma once
+
+/// \brief Initializes global data for the gametype.
+/// \return No return.
+void GunGame_Initialize();
+
+REGISTER_MUTATOR(gg, false)
+{
+       MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               GunGame_Initialize();
+       }
+       return 0;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/survival/_mod.inc b/qcsrc/common/gamemodes/gamemode/survival/_mod.inc
new file mode 100644 (file)
index 0000000..006e93f
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/survival/sv_survival.qc>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/survival/_mod.qh b/qcsrc/common/gamemodes/gamemode/survival/_mod.qh
new file mode 100644 (file)
index 0000000..5ace954
--- /dev/null
@@ -0,0 +1,4 @@
+// generated file; do not modify
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/survival/sv_survival.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc
new file mode 100644 (file)
index 0000000..28fd01a
--- /dev/null
@@ -0,0 +1,2273 @@
+#include "sv_survival.qh"
+
+#include <common/mutators/mutator/overkill/hmg.qh>
+#include <common/mutators/mutator/overkill/rpc.qh>
+
+//============================ Constants ======================================
+
+const int SURVIVAL_TEAM_1_BIT = BIT(0); ///< Bitmask of the first team.
+const int SURVIVAL_TEAM_2_BIT = BIT(1); ///< Bitmask of the second team.
+
+/// \brief Used when bitfield team count is requested.
+const int SURVIVAL_TEAM_BITS = 3;
+
+enum
+{
+       SURVIVAL_TYPE_COOP, ///< All humans are on the defender team.
+       SURVIVAL_TYPE_VERSUS ///< Humans take turns between attackers and defenders.
+};
+
+const string SURVIVAL_TYPE_COOP_VALUE = "coop"; ///< Cvar value for coop.
+const string SURVIVAL_TYPE_VERSUS_VALUE = "versus"; ///< Cvar value for versus.
+
+enum
+{
+       /// \brief First round where there is timelimit set by the server.
+       SURVIVAL_ROUND_FIRST,
+       /// \brief Second round where defender team tries to survive for the first
+       /// round's time.
+       SURVIVAL_ROUND_SECOND
+};
+
+enum
+{
+       /// \brief Player is spectating and has no intention of playing.
+       SURVIVAL_STATE_NOT_PLAYING,
+       /// \brief Player is playing the game.
+       SURVIVAL_STATE_PLAYING = 1
+};
+
+enum
+{
+       SURVIVAL_ROLE_NONE, ///< Player is not playing.
+       SURVIVAL_ROLE_PLAYER, ///< Player is an attacker or defender.
+       SURVIVAL_ROLE_CANNON_FODDER ///< Player is a cannon fodder.
+};
+
+SOUND(SURV_3_FRAGS_LEFT, "announcer/default/3fragsleft");
+SOUND(SURV_2_FRAGS_LEFT, "announcer/default/2fragsleft");
+SOUND(SURV_1_FRAG_LEFT, "announcer/default/1fragleft");
+
+SOUND(SURV_RED_SCORES, "ctf/red_capture");
+SOUND(SURV_BLUE_SCORES, "ctf/blue_capture");
+
+//======================= Global variables ====================================
+
+float autocvar_g_surv_warmup; ///< Warmup time in seconds.
+float autocvar_g_surv_round_timelimit; ///< First round time limit in seconds.
+
+int autocvar_g_surv_point_limit; ///< Maximum number of points.
+int autocvar_g_surv_point_leadlimit; ///< Maximum lead of a single team.
+
+/// \brief How much players are allowed in teams (excluding cannon fodder).
+int autocvar_g_surv_team_size;
+/// \brief If set, defenders will not be shown on the radar.
+int autocvar_g_surv_stealth;
+/// \brief Whether to allow spectating enemy players while dead.
+bool autocvar_g_surv_spectate_enemies;
+
+/// \brief Whether to force overkill player models for attackers.
+int autocvar_g_surv_attacker_force_overkill_models;
+/// \brief Whether to force overkill player models for defenders.
+int autocvar_g_surv_defender_force_overkill_models;
+/// \brief Whether to force overkill player models for cannon fodder.
+int autocvar_g_surv_cannon_fodder_force_overkill_models;
+
+/// \brief How much score attackers gain per 1 point of damage.
+float autocvar_g_surv_attacker_damage_score;
+
+/// \brief How much score attackers get for fragging defenders.
+float autocvar_g_surv_attacker_frag_score;
+
+/// \brief How much health do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_health;
+/// \brief How much armor do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_armor;
+/// \brief How many shells do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_shells;
+/// \brief How many bullets do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_bullets;
+/// \brief How many rockets do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_rockets;
+/// \brief How many cells do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_cells;
+/// \brief How much plasma do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_plasma;
+/// \brief How much fuel do defenders get when they frag an attacker.
+int autocvar_g_surv_defender_attacker_frag_fuel;
+/// \brief How much health do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_health;
+/// \brief How much armor do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_armor;
+/// \brief How many shells do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_shells;
+/// \brief How many bullets do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_bullets;
+/// \brief How many rockets do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_rockets;
+/// \brief How many cells do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_cells;
+/// \brief How much plasma do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_plasma;
+/// \brief How much fuel do defenders get when they frag cannon fodder.
+int autocvar_g_surv_defender_cannon_fodder_frag_fuel;
+
+/// \brief Holds the state of the player. See SURVIVAL_STATE constants.
+.int surv_state;
+/// \brief Holds the role of the player. See SURVIVAL_ROLE constants.
+.int surv_role;
+.string surv_savedplayermodel; ///< Initial player model.
+/// \brief Player state used during replacement of bot player with real player.
+.entity surv_savedplayerstate;
+.string surv_playermodel; ///< Player model forced by the game.
+
+.entity surv_attack_sprite; ///< Holds the sprite telling attackers to attack.
+.entity surv_defend_sprite; ///< Holds the sprite telling defenders to defend.
+
+int surv_type; ///< Holds the type of survival. See SURVIVAL_TYPE constants.
+bool surv_warmup; ///< Holds whether warmup is active.
+/// \brief Holds the type of the current round. See SURVIVAL_ROUND constants.
+int surv_roundtype;
+bool surv_isroundactive; ///< Holds whether the round is active.
+float surv_roundstarttime; ///< Holds the time of the round start.
+/// \brief Holds the time needed to survive in the second round.
+float surv_timetobeat;
+
+int surv_attackerteam; ///< Holds the attacker team.
+int surv_defenderteam; ///< Holds the defender team.
+
+int surv_attackerteambit; ///< Hols the attacker team bitmask.
+int surv_defenderteambit; ///< Holds the defender team bitmask.
+
+int surv_numattackers; ///< Holds the number of players in attacker team.
+int surv_numdefenders; ///< Holds the number of players in defender team.
+
+/// \brief Holds the number of humans in attacker team.
+int surv_numattackerhumans;
+/// \brief Holds the number of humans in defender team.
+int surv_numdefenderhumans;
+
+/// \brief Holds the number of attacker players that are alive.
+int surv_numattackersalive;
+/// \brief Holds the number of defender players that are alive.
+int surv_numdefendersalive;
+
+bool surv_autobalance; ///< Holds whether autobalance is active.
+bool surv_announcefrags; ///< Holds whether remaining frags must be announced.
+bool surv_allowed_to_spawn; ///< Holds whether players are allowed to spawn.
+
+//====================== Forward declarations =================================
+
+/// \brief Determines whether the round can start.
+/// \return True if the round can start, false otherwise.
+bool Surv_CanRoundStart();
+
+/// \brief Determines whether the round can end.
+/// \return True if the round can end, false otherwise.
+bool Surv_CanRoundEnd();
+
+/// \brief Called when the round starts.
+/// \return No return.
+void Surv_RoundStart();
+
+/// \brief Returns whether player has been eliminated.
+/// \param[in] player Player to check.
+/// \return True if player is eliminated, false otherwise.
+bool Surv_IsEliminated(entity player);
+
+/// \brief Updates stats of team count on HUD.
+/// \return No return.
+void Surv_UpdateTeamStats();
+
+/// \brief Updates stats of alive players on HUD.
+/// \return No return.
+void Surv_UpdateAliveStats();
+
+/// \brief Updates defender health on the HUD.
+/// \return No return.
+void Surv_UpdateDefenderHealthStat();
+
+/// \brief Updates the health of defender sprite.
+/// \param[in,out] player Player that has the sprite.
+/// \return No return.
+void Surv_UpdateWaypointSpriteHealth(entity player);
+
+//========================= Free functions ====================================
+
+void Surv_Initialize()
+{
+       switch (cvar_string("g_surv_type"))
+       {
+               case SURVIVAL_TYPE_COOP_VALUE:
+               {
+                       surv_type = SURVIVAL_TYPE_COOP;
+                       break;
+               }
+               case SURVIVAL_TYPE_VERSUS_VALUE:
+               {
+                       surv_type = SURVIVAL_TYPE_VERSUS;
+                       break;
+               }
+               default:
+               {
+                       error("Invalid survival type.");
+               }
+       }
+       surv_roundtype = SURVIVAL_ROUND_FIRST;
+       surv_isroundactive = false;
+       surv_roundstarttime = time;
+       surv_timetobeat = autocvar_g_surv_round_timelimit;
+       if (random() < 0.5)
+       {
+               surv_attackerteam = NUM_TEAM_1;
+               surv_defenderteam = NUM_TEAM_2;
+               surv_attackerteambit = SURVIVAL_TEAM_1_BIT;
+               surv_defenderteambit = SURVIVAL_TEAM_2_BIT;
+       }
+       else
+       {
+               surv_attackerteam = NUM_TEAM_2;
+               surv_defenderteam = NUM_TEAM_1;
+               surv_attackerteambit = SURVIVAL_TEAM_2_BIT;
+               surv_defenderteambit = SURVIVAL_TEAM_1_BIT;
+       }
+       surv_numattackers = 0;
+       surv_numdefenders = 0;
+       surv_numattackerhumans = 0;
+       surv_numdefenderhumans = 0;
+       surv_numattackersalive = 0;
+       surv_numdefendersalive = 0;
+       surv_autobalance = true;
+       surv_announcefrags = true;
+       surv_allowed_to_spawn = true;
+       precache_all_playermodels("models/ok_player/*.dpm");
+       ScoreRules_basics(SURVIVAL_TEAM_BITS, SFL_SORT_PRIO_PRIMARY, 0, true);
+       ScoreInfo_SetLabel_TeamScore(1, "rounds", SFL_SORT_PRIO_PRIMARY);
+       ScoreRules_basics_end();
+       round_handler_Spawn(Surv_CanRoundStart, Surv_CanRoundEnd, Surv_RoundStart);
+       round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
+       EliminatedPlayers_Init(Surv_IsEliminated);
+       GameRules_teams(true);
+       GameRules_limit_score(autocvar_g_surv_point_limit);
+       GameRules_limit_lead(autocvar_g_surv_point_leadlimit);
+}
+
+/// \brief Returns the name of the template of the given player.
+/// \param[in] player Player to inspect.
+/// \return Name of the template of the given player.
+string Surv_GetPlayerTemplate(entity player)
+{
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       switch (player.surv_role)
+                       {
+                               case SURVIVAL_ROLE_NONE:
+                               case SURVIVAL_ROLE_PLAYER:
+                               {
+                                       return cvar_string("g_surv_attacker_template");
+                               }
+                               case SURVIVAL_ROLE_CANNON_FODDER:
+                               {
+                                       return cvar_string("g_surv_cannon_fodder_template");
+                               }
+                       }
+               }
+               case surv_defenderteam:
+               {
+                       return cvar_string("g_surv_defender_template");
+               }
+       }
+       return "default";
+}
+
+/// \brief Saves the player state. Used to seamlessly swap bots with humans.
+/// \param[in] player Player to save the state of.
+/// \return Entity containing the player state.
+entity Surv_SavePlayerState(entity player)
+{
+       entity state = spawn();
+       state.origin = player.origin;
+       state.velocity = player.velocity;
+       state.angles = player.angles;
+       state.health = player.health;
+       state.armorvalue = player.armorvalue;
+       state.ammo_shells = player.ammo_shells;
+       state.ammo_nails = player.ammo_nails;
+       state.ammo_rockets = player.ammo_rockets;
+       state.ammo_cells = player.ammo_cells;
+       state.weapons = player.weapons;
+       state.items = player.items;
+       state.superweapons_finished = player.superweapons_finished;
+       return state;
+}
+
+/// \brief Restores a saved player state.
+/// \param[in] player Player to restore the state of.
+/// \param[in] st State to restore.
+/// \return No return.
+void Surv_RestorePlayerState(entity player, entity st)
+{
+       player.origin = st.origin;
+       player.velocity = st.velocity;
+       player.angles = st.angles;
+       player.health = st.health;
+       player.armorvalue = st.armorvalue;
+       player.ammo_shells = st.ammo_shells;
+       player.ammo_nails = st.ammo_nails;
+       player.ammo_rockets = st.ammo_rockets;
+       player.ammo_cells = st.ammo_cells;
+       player.weapons = st.weapons;
+       player.items = st.items;
+       player.superweapons_finished = st.superweapons_finished;
+}
+
+/// \brief Returns the attacker with the lowest score.
+/// \param[in] bot Whether to search only for bots.
+/// \return Attacker with the lowest score or NULL if not found.
+entity Surv_FindLowestAttacker(bool bot)
+{
+       entity player = NULL;
+       float score = FLOAT_MAX;
+       FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
+       {
+               if ((it.team == surv_attackerteam) && (it.surv_role ==
+                       SURVIVAL_ROLE_PLAYER))
+               {
+                       float tempscore = PlayerScore_Get(it, SP_SCORE);
+                       if (tempscore < score)
+                       {
+                               player = it;
+                               score = tempscore;
+                       }
+               }
+       });
+       return player;
+}
+
+/// \brief Returns the defender with the lowest score.
+/// \param[in] bot Whether to search only for bots.
+/// \param[in] alive Whether to search only for alive players.
+/// \return Defender with the lowest score or NULL if not found.
+entity Surv_FindLowestDefender(bool bot, bool alive)
+{
+       entity player = NULL;
+       float score = FLOAT_MAX;
+       FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
+       {
+               if ((it.team == surv_defenderteam) && (alive ? !IS_DEAD(it) : true))
+               {
+                       float tempscore = PlayerScore_Get(it, SP_SCORE);
+                       if (tempscore < score)
+                       {
+                               player = it;
+                               score = tempscore;
+                       }
+               }
+       });
+       return player;
+}
+
+/// \brief Returns the cannon fodder.
+/// \return Cannon fodder or NULL if not found.
+entity Surv_FindCannonFodder()
+{
+       FOREACH_CLIENT(IS_BOT_CLIENT(it),
+       {
+               if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+               {
+                       return it;
+               }
+       });
+       return NULL;
+}
+
+/// \brief Changes the number of players in a team.
+/// \param[in] teamnum Team to adjust.
+/// \param[in] delta Amount to adjust by.
+/// \return No return.
+void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
+{
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       surv_numattackers += delta;
+                       LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
+                               " was = ", ftos(surv_numattackers - delta));
+                       Surv_UpdateTeamStats();
+                       return;
+               }
+               case surv_defenderteam:
+               {
+                       surv_numdefenders += delta;
+                       LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
+                               " was = ", ftos(surv_numdefenders - delta));
+                       Surv_UpdateTeamStats();
+                       return;
+               }
+       }
+}
+
+/// \brief Changes the number of alive players in a team.
+/// \param[in] teamnum Team to adjust.
+/// \param[in] delta Amount to adjust by.
+/// \return No return.
+void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
+{
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       surv_numattackersalive += delta;
+                       LOG_TRACE("Number of alive attackers = ", ftos(
+                               surv_numattackersalive), " was = ", ftos(surv_numattackersalive
+                               - delta));
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       surv_numdefendersalive += delta;
+                       LOG_TRACE("Number of alive defenders = ", ftos(
+                               surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
+                               - delta));
+                       break;
+               }
+       }
+       Surv_UpdateAliveStats();
+       eliminatedPlayers.SendFlags |= 1;
+}
+
+/// \brief Sets the player role.
+/// \param[in,out] player Player to adjust.
+/// \param[in] role Role to set.
+/// \return No return.
+void Surv_SetPlayerRole(entity player, int role)
+{
+       if (player.surv_role == role)
+       {
+               return;
+       }
+       player.surv_role = role;
+       switch (role)
+       {
+               case SURVIVAL_ROLE_NONE:
+               {
+                       LOG_TRACE(player.netname, " now has no role.");
+                       break;
+               }
+               case SURVIVAL_ROLE_PLAYER:
+               {
+                       LOG_TRACE(player.netname, " is now a player.");
+                       break;
+               }
+               case SURVIVAL_ROLE_CANNON_FODDER:
+               {
+                       LOG_TRACE(player.netname, " is now a cannon fodder.");
+                       break;
+               }
+       }
+}
+
+/// \brief Adds player to team. Handles bookkeeping information.
+/// \param[in] player Player to add.
+/// \param[in] teamnum Team to add to.
+/// \return True on success, false otherwise.
+bool Surv_AddPlayerToTeam(entity player, int teamnum)
+{
+       LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       LOG_TRACE("Attacker team");
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               LOG_TRACE("Cannon fodder is switching team");
+                               return true;
+                       }
+                       if (IS_BOT_CLIENT(player))
+                       {
+                               LOG_TRACE("Client is bot");
+                               LOG_TRACE("Attackers = ", ftos(surv_numattackers));
+                               if (surv_numattackers < autocvar_g_surv_team_size)
+                               {
+                                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                                       return true;
+                               }
+                               Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
+                               return true;
+                       }
+                       LOG_TRACE("Client is not a bot");
+                       LOG_TRACE("Attackers = ", ftos(surv_numattackers));
+                       if (surv_numattackers >= autocvar_g_surv_team_size)
+                       {
+                               LOG_TRACE("Removing bot");
+                               // Remove bot to make space for human.
+                               entity bot = Surv_FindLowestAttacker(true);
+                               if (bot == NULL)
+                               {
+                                       LOG_TRACE("No valid bot to remove");
+                                       // No space in team, denying team change.
+                                       TRANSMUTE(Spectator, player);
+                                       return false;
+                               }
+                               LOG_TRACE("Changing ", bot.netname,
+                                       " from attacker to cannon fodder.");
+                               Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
+                               if (!IS_DEAD(bot))
+                               {
+                                       Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
+                               }
+                               Surv_ChangeNumberOfPlayers(teamnum, -1);
+                               LOG_TRACE("Removed bot");
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                       ++surv_numattackerhumans;                       
+                       LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
+                       if ((surv_autobalance == false) || (surv_numattackers -
+                               surv_numdefenders) < 2)
+                       {
+                               return true;
+                       }
+                       entity lowestplayer = Surv_FindLowestAttacker(true);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                               surv_autobalance = savedautobalance;
+                               return true;
+                       }
+                       lowestplayer = Surv_FindLowestAttacker(false);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                               surv_autobalance = savedautobalance;
+                       }
+                       return true;
+               }
+               case surv_defenderteam:
+               {
+                       LOG_TRACE("Defender team");
+                       if (IS_BOT_CLIENT(player))
+                       {
+                               LOG_TRACE("Client is bot");
+                               LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
+                               if (surv_numdefenders < autocvar_g_surv_team_size)
+                               {
+                                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                                       return true;
+                               }
+                               LOG_TRACE("No space for defender, switching to attacker");
+                               SetPlayerTeamSimple(player, surv_attackerteam);
+                               return false;
+                       }
+                       LOG_TRACE("Client is not a bot");
+                       LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
+                       if (surv_numdefenders >= autocvar_g_surv_team_size)
+                       {
+                               LOG_TRACE("Removing bot");
+                               // Remove bot to make space for human.
+                               entity bot = Surv_FindLowestDefender(true, true);
+                               if (bot == NULL)
+                               {
+                                       bot = Surv_FindLowestDefender(true, false);
+                               }
+                               if (bot == NULL)
+                               {
+                                       LOG_TRACE("No valid bot to remove");
+                                       // No space in team, denying team change.
+                                       TRANSMUTE(Spectator, player);
+                                       return false;
+                               }
+                               LOG_TRACE("Changing ", bot.netname,
+                                       " from defender to cannon fodder.");
+                               if (!IS_DEAD(bot))
+                               {
+                                       player.surv_savedplayerstate = Surv_SavePlayerState(bot);
+                               }
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               surv_announcefrags = false;
+                               SetPlayerTeamSimple(bot, surv_attackerteam);
+                               surv_autobalance = savedautobalance;
+                               surv_announcefrags = true;
+                               LOG_TRACE("Removed bot");
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+                       Surv_ChangeNumberOfPlayers(teamnum, +1);
+                       ++surv_numdefenderhumans;
+                       LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
+                       if ((surv_autobalance == false) || (surv_numdefenders -
+                               surv_numattackers) < 2)
+                       {
+                               return true;
+                       }
+                       entity lowestplayer = Surv_FindLowestDefender(true, false);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
+                               surv_autobalance = savedautobalance;
+                               return true;
+                       }
+                       lowestplayer = Surv_FindLowestDefender(false, false);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
+                               surv_autobalance = savedautobalance;
+                       }
+                       return true;
+               }
+               case -1:
+               {
+                       LOG_TRACE("Spectator team");
+                       player.surv_role = SURVIVAL_ROLE_NONE;                  
+                       return false;
+               }
+       }
+       LOG_TRACE("Invalid team");
+       player.surv_role = SURVIVAL_ROLE_NONE;
+       return false;
+}
+
+/// \brief Removes player from team. Handles bookkeeping information.
+/// \param[in] player Player to remove.
+/// \param[in] Team to remove from.
+/// \return No return.
+void Surv_RemovePlayerFromTeam(entity player, int teamnum)
+{
+       LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       LOG_TRACE("Attacker team");
+                       if (player.surv_role == SURVIVAL_ROLE_NONE)
+                       {
+                               string message = strcat("RemovePlayerFromTeam: ",
+                                       player.netname, " has invalid role.");
+                               DebugPrintToChatAll(message);
+                               return;
+                       }
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
+                               return;
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
+                       Surv_ChangeNumberOfPlayers(teamnum, -1);
+                       if (!IS_BOT_CLIENT(player))
+                       {
+                               --surv_numattackerhumans;
+                       }
+                       if ((surv_autobalance == false) || (surv_numattackers >=
+                               surv_numdefenders))
+                       {
+                               return;
+                       }
+                       // Add bot to keep teams balanced.
+                       entity lowestplayer = Surv_FindCannonFodder();
+                       if (lowestplayer != NULL)
+                       {
+                               LOG_TRACE("Changing ", lowestplayer.netname,
+                                       " from cannon fodder to attacker.");
+                               Surv_SetPlayerRole(lowestplayer, SURVIVAL_ROLE_PLAYER);
+                               Surv_ChangeNumberOfPlayers(teamnum, +1);
+                               if (!IS_DEAD(lowestplayer))
+                               {
+                                       Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
+                               }
+                               return;
+                       }
+                       lowestplayer = Surv_FindLowestDefender(true, false);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
+                               surv_autobalance = savedautobalance;
+                               return;
+                       }
+                       lowestplayer = Surv_FindLowestDefender(false, false);
+                       if (lowestplayer == NULL)
+                       {
+                               return;
+                       }
+                       bool savedautobalance = surv_autobalance;
+                       surv_autobalance = false;
+                       SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
+                       surv_autobalance = savedautobalance;
+                       return;
+               }
+               case surv_defenderteam:
+               {
+                       LOG_TRACE("Defender team");
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               // This happens during team switch. We don't need to change
+                               // anything.
+                               LOG_TRACE("Cannon fodder. Assuming team switch");
+                               return;
+                       }
+                       if (player.surv_role != SURVIVAL_ROLE_PLAYER)
+                       {
+                               string message = strcat("RemovePlayerFromTeam: ",
+                                       player.netname, " has invalid role.");
+                               DebugPrintToChatAll(message);
+                               return;
+                       }
+                       Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
+                       Surv_ChangeNumberOfPlayers(teamnum, -1);
+                       if (!IS_BOT_CLIENT(player))
+                       {
+                               --surv_numdefenderhumans;
+                       }
+                       if ((surv_autobalance == false) || (surv_numdefenders >=
+                               surv_numattackers))
+                       {
+                               return;
+                       }
+                       // Add bot to keep teams balanced.
+                       entity lowestplayer = Surv_FindCannonFodder();
+                       if (lowestplayer != NULL)
+                       {
+                               LOG_TRACE("Changing ", lowestplayer.netname,
+                                       " from cannon fodder to defender.");
+                               if (!IS_DEAD(player))
+                               {
+                                       lowestplayer.surv_savedplayerstate =
+                                               Surv_SavePlayerState(player);
+                               }
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               surv_announcefrags = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                               surv_autobalance = savedautobalance;
+                               surv_announcefrags = true;                                      
+                               return;
+                       }
+                       lowestplayer = Surv_FindLowestAttacker(true);
+                       if (lowestplayer != NULL)
+                       {
+                               bool savedautobalance = surv_autobalance;
+                               surv_autobalance = false;
+                               surv_announcefrags = false;
+                               SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                               surv_autobalance = savedautobalance;
+                               surv_announcefrags = true;
+                               return;
+                       }
+                       lowestplayer = Surv_FindLowestAttacker(false);
+                       if (lowestplayer == NULL)
+                       {
+                               return;
+                       }
+                       bool savedautobalance = surv_autobalance;
+                       surv_autobalance = false;
+                       surv_announcefrags = false;
+                       SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
+                       surv_autobalance = savedautobalance;
+                       surv_announcefrags = true;
+                       return;
+               }
+               case -1:
+               {
+                       LOG_TRACE("Spectator team");
+                       return;
+               }
+               default:
+               {
+                       LOG_TRACE("Invalid team");
+                       return;
+               }
+       }
+}
+
+/// \brief Updates stats of team count on HUD.
+/// \return No return.
+void Surv_UpdateTeamStats()
+{
+       // Debug stuff
+       if (surv_attackerteam == NUM_TEAM_1)
+       {
+               yellowalive = surv_numattackers;
+               pinkalive = surv_numdefenders;
+       }
+       else
+       {
+               pinkalive = surv_numattackers;
+               yellowalive = surv_numdefenders;
+       }
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               it.yellowalive_stat = yellowalive;
+               it.pinkalive_stat = pinkalive;
+       });
+}
+
+/// \brief Adds player to alive list. Handles bookkeeping information.
+/// \param[in] player Player to add.
+/// \param[in] teamnum Team of the player.
+/// \return No return.
+void Surv_AddPlayerToAliveList(entity player, int teamnum)
+{
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_PLAYER)
+                       {
+                               Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
+                       }
+                       return;
+               }
+               case surv_defenderteam:
+               {
+                       Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
+                       return;
+               }
+       }
+}
+
+/// \brief Removes player from alive list. Handles bookkeeping information.
+/// \param[in] player Player to remove.
+/// \param[in] teamnum Team of the player.
+/// \return No return.
+void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
+{
+       if (player.surv_attack_sprite)
+       {
+               WaypointSprite_Kill(player.surv_attack_sprite);
+       }
+       if (player.surv_defend_sprite)
+       {
+               WaypointSprite_Kill(player.surv_defend_sprite);
+       }
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_PLAYER)
+                       {
+                               Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
+                       }
+                       return;
+               }
+               case surv_defenderteam:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               // This happens during team switch. We don't need to change
+                               // anything.
+                               return;
+                       }
+                       Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
+                       if (warmup_stage || surv_allowed_to_spawn || !surv_announcefrags)
+                       {
+                               return;
+                       }
+                       switch (surv_numdefendersalive)
+                       {
+                               case 1:
+                               {
+                                       sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
+                                               VOL_BASE, ATTEN_NONE);
+                                       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+                                       {
+                                               if (it.team == surv_defenderteam)
+                                               {
+                                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER,
+                                                               CENTER_ALONE);
+                                                       return;
+                                               }
+                                       });
+                                       return;
+                               }
+                               case 2:
+                               {
+                                       sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
+                                               VOL_BASE, ATTEN_NONE);
+                                       return;
+                               }
+                               case 3:
+                               {
+                                       sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
+                                               VOL_BASE, ATTEN_NONE);
+                                       return;
+                               }
+                       }
+                       return;
+               }
+       }
+}
+
+/// \brief Counts alive players.
+/// \return No return.
+/// \note In a perfect world this function shouldn't exist. However, since QC
+/// code is so bad and spurious mutators can really mess with your code, this
+/// function is called as a last resort.
+void Surv_CountAlivePlayers()
+{
+       int savednumdefenders = surv_numdefendersalive;
+       surv_numattackersalive = 0;
+       surv_numdefendersalive = 0;
+       FOREACH_CLIENT(IS_PLAYER(it),
+       {
+               switch (it.team)
+               {
+                       case surv_attackerteam:
+                       {
+                               if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
+                               {
+                                       ++surv_numattackersalive;
+                               }
+                               break;
+                       }
+                       case surv_defenderteam:
+                       {
+                               if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
+                               {
+                                       ++surv_numdefendersalive;
+                               }
+                               break;
+                       }
+               }
+       });
+       Surv_UpdateAliveStats();
+       eliminatedPlayers.SendFlags |= 1;
+       if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
+               surv_numdefendersalive))
+       {
+               return;
+       }
+       switch (surv_numdefendersalive)
+       {
+               case 1:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
+                       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+                       {
+                               if (it.team == surv_defenderteam)
+                               {
+                                       Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
+                                       return;
+                               }
+                       });
+                       return;
+               }
+               case 2:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
+                               ATTEN_NONE);
+                       return;
+               }
+               case 3:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
+                               ATTEN_NONE);
+                       return;
+               }
+       }
+}
+
+/// \brief Updates stats of alive players on HUD.
+/// \return No return.
+void Surv_UpdateAliveStats()
+{
+       // Debug stuff
+       if (surv_attackerteam == NUM_TEAM_1)
+       {
+               redalive = surv_numattackersalive;
+               bluealive = surv_numdefendersalive;
+       }
+       else
+       {
+               bluealive = surv_numattackersalive;
+               redalive = surv_numdefendersalive;
+       }
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(SURV_DEFENDERS_ALIVE, it) = surv_numdefendersalive;
+               it.redalive_stat = redalive;
+               it.bluealive_stat = bluealive;
+       });
+       Surv_UpdateDefenderHealthStat();
+}
+
+/// \brief Updates defender health on the HUD.
+/// \return No return.
+void Surv_UpdateDefenderHealthStat()
+{
+       float maxhealth;
+       float totalhealth = 0;
+       if (autocvar_g_instagib == 1)
+       {
+               maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
+                       "surv_defender", "start_armor") + 1);
+               FOREACH_CLIENT(IS_PLAYER(it),
+               {
+                       if (it.team == surv_defenderteam)
+                       {
+                               totalhealth += GetResourceAmount(it, RESOURCE_ARMOR) + 1;
+                       }
+               });
+       }
+       else
+       {
+               maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
+                       "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
+                       "surv_defender", "start_armor"));
+               FOREACH_CLIENT(IS_PLAYER(it),
+               {
+                       if (it.team == surv_defenderteam)
+                       {
+                               totalhealth += GetResourceAmount(it, RESOURCE_HEALTH);
+                               totalhealth += GetResourceAmount(it, RESOURCE_ARMOR);
+                       }
+               });
+       }
+       float healthratio;
+       if (maxhealth == 0)
+       {
+               healthratio = 0;
+       }
+       else
+       {
+               healthratio = totalhealth / maxhealth;
+       }
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(SURV_DEFENDER_HEALTH, it) = healthratio;
+       });
+}
+
+/// \brief Returns whether the player can spawn.
+/// \param[in] player Player to check.
+/// \return True if the player can spawn, false otherwise.
+bool Surv_CanPlayerSpawn(entity player)
+{
+       if ((player.team == surv_attackerteam) ||
+               (player.surv_savedplayerstate != NULL))
+       {
+               return true;
+       }
+       return surv_allowed_to_spawn;
+}
+
+/// \brief Switches the round type.
+/// \return No return.
+void Surv_SwitchRoundType()
+{
+       switch (surv_roundtype)
+       {
+               case SURVIVAL_ROUND_FIRST:
+               {
+                       surv_roundtype = SURVIVAL_ROUND_SECOND;
+                       return;
+               }
+               case SURVIVAL_ROUND_SECOND:
+               {
+                       surv_roundtype = SURVIVAL_ROUND_FIRST;
+                       return;
+               }
+       }
+}
+
+/// \brief Cleans up the mess after the round has finished.
+/// \return No return.
+void Surv_RoundCleanup()
+{
+       surv_allowed_to_spawn = false;
+       surv_isroundactive = false;
+       game_stopped = true;
+       FOREACH_CLIENT(true,
+       {
+               if (it.surv_attack_sprite)
+               {
+                       WaypointSprite_Kill(it.surv_attack_sprite);
+               }
+               if (it.surv_defend_sprite)
+               {
+                       WaypointSprite_Kill(it.surv_defend_sprite);
+               }
+               if (it.surv_savedplayerstate)
+               {
+                       delete(it.surv_savedplayerstate);
+                       it.surv_savedplayerstate = NULL;
+               }
+       });
+       if (surv_type == SURVIVAL_TYPE_VERSUS)
+       {
+               Surv_SwitchRoundType();
+               round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
+               return;
+       }
+       round_handler_Init(5, autocvar_g_surv_warmup,
+               autocvar_g_surv_round_timelimit);
+}
+
+/// \brief Swaps attacker and defender teams.
+/// \return No return.
+void Surv_SwapTeams()
+{
+       int temp = surv_attackerteam;
+       surv_attackerteam = surv_defenderteam;
+       surv_defenderteam = temp;
+       temp = surv_attackerteambit;
+       surv_attackerteambit = surv_defenderteambit;
+       surv_defenderteambit = temp;
+       temp = surv_numattackers;
+       surv_numattackers = surv_numdefenders;
+       surv_numdefenders = temp;
+       temp = surv_numattackerhumans;
+       surv_numattackerhumans = surv_numdefenderhumans;
+       surv_numdefenderhumans = temp;
+       FOREACH_CLIENT(true,
+       {
+               if ((it.team == surv_defenderteam) && (it.surv_role ==
+                       SURVIVAL_ROLE_CANNON_FODDER))
+               {
+                       SetPlayerTeamSimple(it, surv_attackerteam);
+               }
+       });
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(SURV_DEFENDER_TEAM, it) = Team_TeamToNumber(surv_defenderteam);
+       });
+}
+
+/// \brief Forces the overkill model for specific player.
+/// \param[in,out] player Player to force the model of.
+/// \return No return.
+void Surv_ForceOverkillPlayerModel(entity player)
+{
+       switch (player.team)
+       {
+               case NUM_TEAM_1:
+               {
+                       switch (floor(random() * 4))
+                       {
+                               case 0:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okrobot1.dpm";
+                                       return;
+                               }
+                               case 1:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okrobot2.dpm";
+                                       return;
+                               }
+                               case 2:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okrobot3.dpm";
+                                       return;
+                               }
+                               case 3:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okrobot4.dpm";
+                                       return;
+                               }
+                       }
+                       return;
+               }
+               case NUM_TEAM_2:
+               {
+                       switch (floor(random() * 4))
+                       {
+                               case 0:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okmale1.dpm";
+                                       return;
+                               }
+                               case 1:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okmale2.dpm";
+                                       return;
+                               }
+                               case 2:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okmale3.dpm";
+                                       return;
+                               }
+                               case 3:
+                               {
+                                       player.surv_playermodel = "models/ok_player/okmale4.dpm";
+                                       return;
+                               }
+                       }
+                       return;
+               }
+       }
+}
+
+/// \brief Determines the player model to the one configured for the gamemode.
+/// \param[in,out] player Player to determine the model of.
+/// \return No return.
+void Surv_DeterminePlayerModel(entity player)
+{
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       switch (player.surv_role)
+                       {
+                               case SURVIVAL_ROLE_PLAYER:
+                               {
+                                       if (!autocvar_g_surv_attacker_force_overkill_models)
+                                       {
+                                               player.surv_playermodel = player.surv_savedplayermodel;
+                                               return;
+                                       }
+                                       Surv_ForceOverkillPlayerModel(player);
+                                       return;
+                               }
+                               case SURVIVAL_ROLE_CANNON_FODDER:
+                               {
+                                       if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
+                                       {
+                                               player.surv_playermodel = player.surv_savedplayermodel;
+                                               return;
+                                       }
+                                       Surv_ForceOverkillPlayerModel(player);
+                                       return;
+                               }
+                       }
+               }
+               case surv_defenderteam:
+               {
+                       if (!autocvar_g_surv_defender_force_overkill_models)
+                       {
+                               player.surv_playermodel = player.surv_savedplayermodel;
+                               return;
+                       }
+                       Surv_ForceOverkillPlayerModel(player);
+                       return;
+               }
+       }
+}
+
+/// \brief Setups a waypoint sprite used to track defenders.
+/// \param[in] player Player to attach sprite too.
+/// \return No return.
+void Surv_SetupWaypointSprite(entity player)
+{
+       WaypointSprite_Spawn(WP_SurvivalKill, 0, 0, player, '0 0 64', NULL,
+               surv_attackerteam, player, surv_attack_sprite, false,
+               RADARICON_OBJECTIVE);
+       WaypointSprite_Spawn(WP_SurvivalDefend, 0, 0, player, '0 0 64', NULL,
+               surv_defenderteam, player, surv_defend_sprite, false,
+               RADARICON_OBJECTIVE);
+       //player.surv_attack_sprite.colormod = colormapPaletteColor(player.team - 1,
+       //      false);
+       //player.surv_defend_sprite.colormod = colormapPaletteColor(player.team - 1,
+       //      false);
+       float max_hp;
+       if (autocvar_g_instagib == 1)
+       {
+               max_hp = PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(player),
+                       "start_armor") + 1;
+               WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite, max_hp);
+               WaypointSprite_UpdateMaxHealth(player.surv_defend_sprite, max_hp);
+               Surv_UpdateWaypointSpriteHealth(player);
+               return;
+       }
+       max_hp = PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(player),
+               "start_health") + PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(
+               player), "start_armor");
+       WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite, max_hp);
+       WaypointSprite_UpdateMaxHealth(player.surv_defend_sprite, max_hp);
+       Surv_UpdateWaypointSpriteHealth(player);
+}
+
+void Surv_UpdateWaypointSpriteHealth(entity player)
+{
+       float hp;
+       if (autocvar_g_instagib == 1)
+       {
+               hp = GetResourceAmount(player, RESOURCE_ARMOR) + 1;
+       }
+       else
+       {
+               hp = GetResourceAmount(player, RESOURCE_HEALTH) + GetResourceAmount(
+                       player, RESOURCE_ARMOR);
+       }
+       WaypointSprite_UpdateHealth(player.surv_attack_sprite, hp);
+       WaypointSprite_UpdateHealth(player.surv_defend_sprite, hp);
+}
+
+//=============================== Callbacks ===================================
+
+bool Surv_CanRoundStart()
+{
+       return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
+}
+
+bool Surv_CanRoundEnd()
+{
+       if (warmup_stage)
+       {
+               return false;
+       }
+       if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
+               time <= 0))
+       {
+               if (surv_roundtype == SURVIVAL_ROUND_FIRST)
+               {
+                       surv_timetobeat = time - surv_roundstarttime;
+                       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
+                               CENTER_SURVIVAL_DEFENDERS_SURVIVED);
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
+                               INFO_SURVIVAL_DEFENDERS_SURVIVED);
+                       Surv_RoundCleanup();
+                       return true;
+               }
+               surv_timetobeat = autocvar_g_surv_round_timelimit;
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
+                       CENTER_SURVIVAL_DEFENDERS_SURVIVED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
+                       INFO_SURVIVAL_DEFENDERS_SURVIVED);
+               switch (surv_defenderteam)
+               {
+                       case NUM_TEAM_1:
+                       {
+                               sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
+                                       ATTEN_NONE);
+                               break;
+                       }
+                       case NUM_TEAM_2:
+                       {
+                               sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
+                                       ATTEN_NONE);
+                               break;
+                       }
+               }
+               TeamScore_AddToTeam(surv_defenderteam, 1, 1);
+               Surv_RoundCleanup();
+               return true;
+       }
+       if (surv_numdefendersalive > 0)
+       {
+               return false;
+       }
+       if (surv_roundtype == SURVIVAL_ROUND_FIRST)
+       {
+               surv_timetobeat = time - surv_roundstarttime;
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
+                       CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
+                       INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
+               Surv_RoundCleanup();
+               return true;
+       }
+       surv_timetobeat = autocvar_g_surv_round_timelimit;
+       Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
+               CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
+       Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
+               INFO_SURVIVAL_DEFENDERS_ELIMINATED);
+       switch (surv_attackerteam)
+       {
+               case NUM_TEAM_1:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
+                               ATTEN_NONE);
+                       break;
+               }
+               case NUM_TEAM_2:
+               {
+                       sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
+                               ATTEN_NONE);
+                       break;
+               }
+       }
+       TeamScore_AddToTeam(surv_attackerteam, 1, 1);
+       Surv_RoundCleanup();
+       return true;
+}
+
+void Surv_RoundStart()
+{
+       if (warmup_stage)
+       {
+               surv_allowed_to_spawn = true;
+               return;
+       }
+       surv_isroundactive = true;
+       surv_roundstarttime = time;
+       surv_allowed_to_spawn = false;
+       switch (surv_roundtype)
+       {
+               case SURVIVAL_ROUND_FIRST:
+               {
+                       FOREACH_CLIENT(IS_PLAYER(it),
+                       {
+                               if (it.team == surv_attackerteam)
+                               {
+                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                               CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
+                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                               INFO_SURVIVAL_1ST_ROUND_ATTACKER);
+                                       break;
+                               }
+                       });
+                       FOREACH_CLIENT(IS_PLAYER(it),
+                       {
+                               if (it.team == surv_defenderteam)
+                               {
+                                       if (surv_type == SURVIVAL_TYPE_COOP)
+                                       {
+                                               Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                                       CENTER_SURVIVAL_COOP_DEFENDER);
+                                               Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                                       INFO_SURVIVAL_COOP_DEFENDER);
+                                               break;
+                                       }
+                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                               CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
+                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                               INFO_SURVIVAL_1ST_ROUND_DEFENDER);
+                                       break;
+                               }
+                       });
+                       break;
+               }
+               case SURVIVAL_ROUND_SECOND:
+               {
+                       FOREACH_CLIENT(IS_PLAYER(it),
+                       {
+                               if (it.team == surv_attackerteam)
+                               {
+                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                               CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
+                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                               INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
+                                       break;
+                               }
+                       });
+                       FOREACH_CLIENT(IS_PLAYER(it),
+                       {
+                               if (it.team == surv_defenderteam)
+                               {
+                                       Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
+                                               CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
+                                       Send_Notification(NOTIF_TEAM, it, MSG_INFO,
+                                               INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
+                                       break;
+                               }
+                       });
+                       break;
+               }
+       }
+       if (autocvar_g_surv_stealth)
+       {
+               return;
+       }
+       FOREACH_CLIENT(IS_PLAYER(it),
+       {
+               switch (it.team)
+               {
+                       case surv_defenderteam:
+                       {
+                               if (it.surv_role == SURVIVAL_ROLE_PLAYER)
+                               {
+                                       Surv_SetupWaypointSprite(it);
+                               }
+                               break;
+                       }
+               }
+       });
+}
+
+bool Surv_IsEliminated(entity player)
+{
+       switch (player.surv_state)
+       {
+               case SURVIVAL_STATE_NOT_PLAYING:
+               {
+                       return true;
+               }
+               case SURVIVAL_STATE_PLAYING:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+                       {
+                               // A hack until proper scoreboard is done.
+                               return true;
+                       }
+                       if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
+                               IS_OBSERVER(player)))
+                       {
+                               return true;
+                       }
+                       return false;
+               }
+       }
+       // Should never reach here
+       return true;
+}
+
+//============================= Hooks ========================================
+
+/// \brief Hook that is called to determine general rules of the game. 
+MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
+{
+       surv_warmup = warmup_stage;
+}
+
+/// \brief Hook that is called to determine if there is a weapon arena.
+MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
+{
+       // Removing any weapon arena.
+       M_ARGV(0, string) = "off";
+}
+
+/// \brief Hook that is called to determine start items of all players.
+MUTATOR_HOOKFUNCTION(surv, SetStartItems)
+{
+       if (autocvar_g_instagib == 1)
+       {
+               return;
+       }
+       start_weapons = WEPSET(Null);
+       warmup_start_weapons = WEPSET(Null);
+}
+
+/// \brief Hook that is called on every frame.
+MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
+{
+       if (game_stopped || !surv_isroundactive)
+       {
+               return;
+       }
+       float roundtime = 0;
+       switch (surv_roundtype)
+       {
+               case SURVIVAL_ROUND_FIRST:
+               {
+                       roundtime = time - surv_roundstarttime;
+                       break;
+               }
+               case SURVIVAL_ROUND_SECOND:
+               {
+                       roundtime = round_handler_GetEndTime() - time;
+                       break;
+               }
+       }
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(SURV_ROUND_TIME, it) = roundtime;
+       });
+}
+
+/// \brief Hook that determines which team player can join. This is called
+/// before ClientConnect.
+MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+       entity player = M_ARGV(2, entity);
+       LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
+       if (player == NULL)
+       {
+               return SURVIVAL_TEAM_BITS;
+       }
+       if (IS_BOT_CLIENT(player))
+       {
+               int teambits = surv_attackerteambit;
+               if ((player.team == surv_defenderteam) || (surv_numdefenders <
+                       autocvar_g_surv_team_size))
+               {
+                       teambits |= surv_defenderteambit;
+               }
+               M_ARGV(0, float) = teambits;
+               return;
+       }
+       if (surv_type == SURVIVAL_TYPE_COOP)
+       {
+               if (surv_numdefenderhumans < autocvar_g_surv_team_size)
+               {
+                       M_ARGV(0, float) = surv_defenderteambit;
+                       return;
+               }
+               M_ARGV(0, float) = 0;
+               return;
+       }
+       int teambits = 0;
+       if (surv_numattackerhumans < autocvar_g_surv_team_size)
+       {
+               LOG_TRACE("Player can join attackers");
+               teambits |= surv_attackerteambit;
+       }
+       if (surv_numdefenderhumans < autocvar_g_surv_team_size)
+       {
+               LOG_TRACE("Player can join defenders");
+               teambits |= surv_defenderteambit;
+       }
+       M_ARGV(0, float) = teambits;
+       return;
+}
+
+/// \brief Hook that override team counts.
+MUTATOR_HOOKFUNCTION(surv, GetTeamCounts, CBC_ORDER_EXCLUSIVE)
+{
+       return true;
+}
+
+/// \brief Hook that sets the team count.
+MUTATOR_HOOKFUNCTION(surv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
+{
+       float teamnum = M_ARGV(0, float);
+       entity ignore = M_ARGV(1, entity);
+       switch (teamnum)
+       {
+               case surv_attackerteam:
+               {
+                       M_ARGV(2, float) = surv_numattackers;
+                       M_ARGV(3, float) = surv_numattackers - surv_numattackerhumans;
+                       if (ignore.team == surv_attackerteam)
+                       {
+                               --M_ARGV(2, float);
+                               if (IS_BOT_CLIENT(ignore))
+                               {
+                                       --M_ARGV(3, float);
+                               }
+                       }
+                       entity lowestplayer = NULL;
+                       float lowestplayerscore = FLOAT_MAX;
+                       entity lowestbot = NULL;
+                       float lowestbotscore = FLOAT_MAX;
+                       FOREACH_CLIENT((it.team == surv_attackerteam) && (it.surv_role ==
+                               SURVIVAL_ROLE_PLAYER),
+                       {
+                               if (it == ignore)
+                               {
+                                       continue;
+                               }
+                               if (IS_BOT_CLIENT(it))
+                               {
+                                       float tempscore = PlayerScore_Get(it, SP_SCORE);
+                                       if (tempscore < lowestbotscore)
+                                       {
+                                               lowestbot = it;
+                                               lowestbotscore = tempscore;
+                                               continue;
+                                       }
+                               }
+                               float tempscore = PlayerScore_Get(it, SP_SCORE);
+                               if (tempscore < lowestplayerscore)
+                               {
+                                       lowestplayer = it;
+                                       lowestplayerscore = tempscore;
+                               }
+                       });
+                       M_ARGV(4, entity) = lowestplayer;
+                       M_ARGV(5, entity) = lowestbot;
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       M_ARGV(2, float) = surv_numdefenders;
+                       M_ARGV(3, float) = surv_numdefenders - surv_numdefenderhumans;
+                       if (ignore.team == surv_defenderteam)
+                       {
+                               --M_ARGV(2, float);
+                               if (IS_BOT_CLIENT(ignore))
+                               {
+                                       --M_ARGV(3, float);
+                               }
+                       }
+                       entity lowestplayer = NULL;
+                       float lowestplayerscore = FLOAT_MAX;
+                       entity lowestbot = NULL;
+                       float lowestbotscore = FLOAT_MAX;
+                       FOREACH_CLIENT((it.team == surv_defenderteam),
+                       {
+                               if (it == ignore)
+                               {
+                                       continue;
+                               }
+                               if (IS_BOT_CLIENT(it))
+                               {
+                                       float tempscore = PlayerScore_Get(it, SP_SCORE);
+                                       if (tempscore < lowestbotscore)
+                                       {
+                                               lowestbot = it;
+                                               lowestbotscore = tempscore;
+                                               continue;
+                                       }
+                               }
+                               float tempscore = PlayerScore_Get(it, SP_SCORE);
+                               if (tempscore < lowestplayerscore)
+                               {
+                                       lowestplayer = it;
+                                       lowestplayerscore = tempscore;
+                               }
+                       });
+                       M_ARGV(4, entity) = lowestplayer;
+                       M_ARGV(5, entity) = lowestbot;
+                       break;
+               }
+       }
+       return true;
+}
+
+/// \brief Hook that determines the best teams for the player to join.
+MUTATOR_HOOKFUNCTION(surv, FindBestTeams, CBC_ORDER_EXCLUSIVE)
+{
+       if (surv_type == SURVIVAL_TYPE_COOP)
+       {
+               return false;
+       }
+       entity player = M_ARGV(0, entity);
+       if (IS_BOT_CLIENT(player))
+       {
+               return false;
+       }
+       int numattackerhumans = surv_numattackerhumans;
+       int numdefenderhumans = surv_numdefenderhumans;
+       if (player.team == surv_attackerteam)
+       {
+               --numattackerhumans;
+       }
+       else if (player.team == surv_defenderteam)
+       {
+               --numdefenderhumans;
+       }
+       if (numattackerhumans < numdefenderhumans)
+       {
+               M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_attackerteam) - 1);
+               return true;
+       }
+       if (numattackerhumans > numdefenderhumans)
+       {
+               M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_defenderteam) - 1);
+               return true;
+       }
+       M_ARGV(1, float) = SURVIVAL_TEAM_BITS;
+       return true;
+}
+
+/// \brief Hook that is called when player has changed the team.
+MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
+{
+       entity player = M_ARGV(0, entity);
+       int oldteam = M_ARGV(1, float);
+       int newteam = M_ARGV(2, float);
+       string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
+               ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
+       LOG_TRACE(message);
+       DebugPrintToChatAll(message);
+       if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
+       {
+               Surv_RemovePlayerFromAliveList(player, oldteam);
+       }
+       Surv_RemovePlayerFromTeam(player, oldteam);
+       if (Surv_AddPlayerToTeam(player, newteam) == false)
+       {
+               return;
+       }
+       //Surv_CountAlivePlayers();
+       if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
+       {
+               Surv_AddPlayerToAliveList(player, newteam);
+       }
+}
+
+/// \brief Hook that is called when player is about to be killed when changing
+/// teams.
+MUTATOR_HOOKFUNCTION(surv, Player_ChangeTeamKill)
+{
+       entity player = M_ARGV(0, entity);
+       if (player.team != surv_defenderteam)
+       {
+               return false;
+       }
+       if (player.surv_savedplayerstate == NULL)
+       {
+               return false;
+       }
+       Surv_RestorePlayerState(player, player.surv_savedplayerstate);
+       delete(player.surv_savedplayerstate);
+       player.surv_savedplayerstate = NULL;
+       return true;
+}
+
+/// \brief Hook that is called when player is about to be killed as a result of
+/// the kill command or changing teams.
+MUTATOR_HOOKFUNCTION(surv, ClientKill_Now)
+{
+       entity player = M_ARGV(0, entity);
+       if (player.team == surv_defenderteam)
+       {
+               // Deny suicide.
+               return true;
+       }
+}
+
+/// \brief Hook that is called when player connects to the server.
+MUTATOR_HOOKFUNCTION(surv, ClientConnect)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
+       player.surv_savedplayermodel = player.playermodel;
+       if (IS_REAL_CLIENT(player))
+       {
+               STAT(SURV_DEFENDER_TEAM, player) = Team_TeamToNumber(surv_defenderteam);
+               STAT(SURV_DEFENDERS_ALIVE, player) = surv_numdefendersalive;
+               player.redalive_stat = redalive;
+               player.bluealive_stat = bluealive;
+               player.yellowalive_stat = yellowalive;
+               player.pinkalive_stat = pinkalive;
+       }
+       if (player.surv_role == SURVIVAL_ROLE_NONE)
+       {
+               Surv_AddPlayerToTeam(player, player.team);
+       }
+       return true;
+}
+
+/// \brief Hook that is called when player disconnects from the server.
+MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
+{
+    entity player = M_ARGV(0, entity);
+       if (!IS_DEAD(player))
+       {
+               Surv_RemovePlayerFromAliveList(player, player.team);
+       }
+       Surv_RemovePlayerFromTeam(player, player.team);
+       //Surv_CountAlivePlayers();
+}
+
+/// \brief Hook that determines whether player can spawn. It is not called for
+/// players who have joined the team and are dead.
+MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
+       if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
+       {
+               return false;
+       }
+       return !Surv_CanPlayerSpawn(player);
+}
+
+MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
+       if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
+       {
+               LOG_TRACE("Transmuting to observer");
+               TRANSMUTE(Observer, player);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
+       if (player.killindicator_teamchange == -2) // player wants to spectate
+       {
+               LOG_TRACE("killindicator_teamchange == -2");
+               player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
+       }
+       if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
+       {
+               return false;  // allow team reset
+       }
+       return true;  // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(surv, reset_map_global)
+{
+       LOG_TRACE("Survival: reset_map_global");
+       surv_allowed_to_spawn = true;
+       if (surv_roundtype == SURVIVAL_ROUND_FIRST)
+       {
+               FOREACH_CLIENT(IS_REAL_CLIENT(it),
+               {
+                       STAT(SURV_ROUND_TIME, it) = 0;
+               });
+       }
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, reset_map_players)
+{
+       LOG_TRACE("Survival: reset_map_players");
+       surv_numattackersalive = 0;
+       surv_numdefendersalive = 0;
+       if (surv_warmup)
+       {
+               surv_warmup = false;
+       }
+       else if (surv_type == SURVIVAL_TYPE_VERSUS)
+       {
+               Surv_SwapTeams();
+       }
+       FOREACH_CLIENT(true,
+       {
+               it.killcount = 0;
+               if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
+               {
+                       it.team = -1;
+                       it.surv_state = SURVIVAL_STATE_PLAYING;
+               }
+               if (it.surv_state == SURVIVAL_STATE_PLAYING)
+               {
+                       TRANSMUTE(Player, it);
+                       it.surv_state = SURVIVAL_STATE_PLAYING;
+                       PutClientInServer(it);
+               }
+       });
+       bot_relinkplayerlist();
+       return true;
+}
+
+/// \brief Hook that is called when player spawns.
+MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+       LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
+       player.surv_state = SURVIVAL_STATE_PLAYING;
+       Surv_DeterminePlayerModel(player);
+       if (player.surv_savedplayerstate != NULL)
+       {
+               Surv_RestorePlayerState(player, player.surv_savedplayerstate);
+               delete(player.surv_savedplayerstate);
+               player.surv_savedplayerstate = NULL;
+       }
+       else
+       {
+               PlayerTemplateHook_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
+       }
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       if (player.surv_role == SURVIVAL_ROLE_PLAYER)
+                       {
+                               Send_Notification(NOTIF_ONE, player, MSG_CENTER,
+                                       CENTER_ASSAULT_ATTACKING);
+                       }
+                       break;
+               }
+               case surv_defenderteam:
+               {
+                       Send_Notification(NOTIF_ONE, player, MSG_CENTER,
+                               CENTER_ASSAULT_DEFENDING);
+                       break;
+               }
+       }
+       //Surv_CountAlivePlayers();
+       Surv_AddPlayerToAliveList(player, player.team);
+}
+
+/// \brief UGLY HACK. This is called every frame to keep player model correct.
+MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
+{
+       entity player = M_ARGV(2, entity);
+       M_ARGV(0, string) = player.surv_playermodel;
+}
+
+/// \brief Hook which is called when the player tries to throw their weapon.
+MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
+{
+       entity player = M_ARGV(0, entity);
+       return PlayerTemplateHook_ForbidThrowCurrentWeapon(
+               Surv_GetPlayerTemplate(player));
+}
+
+/// \brief Hook that is called every frame to determine how player health should
+/// regenerate.
+MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
+{
+       entity player = M_ARGV(0, entity);
+       if (player.team == surv_defenderteam)
+       {
+               return true;
+       }
+       return PlayerTemplateHook_PlayerRegen(player,
+               Surv_GetPlayerTemplate(player));
+}
+
+/// \brief Hook that is called to determine if balance messages will appear.
+MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
+{
+       return true;
+}
+
+/// \brief Hook that is called when player touches an item.
+MUTATOR_HOOKFUNCTION(surv, ItemTouch)
+{
+       entity item = M_ARGV(0, entity);
+       entity player = M_ARGV(1, entity);
+       switch (player.team)
+       {
+               case surv_attackerteam:
+               {
+                       return PlayerTemplateHook_ItemTouch(player, item,
+                               Surv_GetPlayerTemplate(player));
+               }
+               case surv_defenderteam:
+               {
+                       switch (item.classname)
+                       {
+                               case "item_strength":
+                               {
+                                       W_GiveWeapon(player, WEP_HMG.m_id);
+                                       player.superweapons_finished = max(
+                                               player.superweapons_finished, time) +
+                                               autocvar_g_balance_superweapons_time;
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
+                                               ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               case "item_shield":
+                               {
+                                       W_GiveWeapon(player, WEP_RPC.m_id);
+                                       player.superweapons_finished = max(
+                                               player.superweapons_finished, time) +
+                                               autocvar_g_balance_superweapons_time;
+                                       Item_ScheduleRespawn(item);
+                                       sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
+                                       return MUT_ITEMTOUCH_RETURN;
+                               }
+                               default:
+                               {
+                                       return PlayerTemplateHook_ItemTouch(player, item,
+                                               Surv_GetPlayerTemplate(player));
+                               }
+                       }
+                       DebugPrintToChat(player, item.classname);
+                       return MUT_ITEMTOUCH_RETURN;
+               }
+       }
+       return MUT_ITEMTOUCH_CONTINUE;
+}
+
+/// \brief Hook which is called when the damage amount must be determined.
+MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float deathtype = M_ARGV(3, float);
+       float damage = M_ARGV(4, float);
+       M_ARGV(4, float) = PlayerTemplateHook_Damage_Calculate(frag_attacker,
+               Surv_GetPlayerTemplate(frag_attacker), frag_target,
+               Surv_GetPlayerTemplate(frag_target), deathtype, damage);
+}
+
+/// \brief Hook which is called when the player was damaged.
+MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
+{
+       entity target = M_ARGV(1, entity);
+       if (target.team != surv_defenderteam)
+       {
+               return;
+       }
+       Surv_UpdateDefenderHealthStat();
+       entity attacker = M_ARGV(0, entity);
+       if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
+               SURVIVAL_ROLE_PLAYER))
+       {
+               float health = M_ARGV(2, float);
+               float armor = M_ARGV(3, float);
+               float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
+               GameRules_scoring_add(attacker, SCORE, score);
+       }
+       if (autocvar_g_surv_stealth)
+       {
+               return;
+       }
+       if (GetResourceAmount(target, RESOURCE_HEALTH) < 1)
+       {
+               WaypointSprite_Kill(target.surv_attack_sprite);
+               WaypointSprite_Kill(target.surv_defend_sprite);
+       }
+       else
+       {
+               Surv_UpdateWaypointSpriteHealth(target);
+       }
+}
+
+/// \brief Hook which is called when the player dies.
+MUTATOR_HOOKFUNCTION(surv, PlayerDies, CBC_ORDER_FIRST)
+{
+       //DebugPrintToChatAll("PlayerDies");
+       entity attacker = M_ARGV(1, entity);
+       entity victim = M_ARGV(2, entity);
+       PlayerTemplateHook_PlayerDies(victim, Surv_GetPlayerTemplate(victim));
+       if ((attacker.team == surv_defenderteam) &&
+               (victim.team == surv_attackerteam))
+       {
+               switch (victim.surv_role)
+               {
+                       case SURVIVAL_ROLE_PLAYER:
+                       {
+                               GiveResource(attacker, RESOURCE_HEALTH,
+                                       autocvar_g_surv_defender_attacker_frag_health);
+                               GiveResource(attacker, RESOURCE_ARMOR,
+                                       autocvar_g_surv_defender_attacker_frag_armor);
+                               GiveResource(attacker, RESOURCE_SHELLS,
+                                       autocvar_g_surv_defender_attacker_frag_shells);
+                               GiveResource(attacker, RESOURCE_BULLETS,
+                                       autocvar_g_surv_defender_attacker_frag_bullets);
+                               GiveResource(attacker, RESOURCE_ROCKETS,
+                                       autocvar_g_surv_defender_attacker_frag_rockets);
+                               GiveResource(attacker, RESOURCE_CELLS,
+                                       autocvar_g_surv_defender_attacker_frag_cells);
+                               GiveResource(attacker, RESOURCE_PLASMA,
+                                       autocvar_g_surv_defender_attacker_frag_plasma);
+                               GiveResource(attacker, RESOURCE_FUEL,
+                                       autocvar_g_surv_defender_attacker_frag_fuel);
+                               break;
+                       }
+                       case SURVIVAL_ROLE_CANNON_FODDER:
+                       {
+                               GiveResource(attacker, RESOURCE_HEALTH,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_health);
+                               GiveResource(attacker, RESOURCE_ARMOR,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_armor);
+                               GiveResource(attacker, RESOURCE_SHELLS,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_shells);
+                               GiveResource(attacker, RESOURCE_BULLETS,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_bullets);
+                               GiveResource(attacker, RESOURCE_ROCKETS,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_rockets);
+                               GiveResource(attacker, RESOURCE_CELLS,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_cells);
+                               GiveResource(attacker, RESOURCE_PLASMA,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_plasma);
+                               GiveResource(attacker, RESOURCE_FUEL,
+                                       autocvar_g_surv_defender_cannon_fodder_frag_fuel);
+                               break;
+                       }
+               }
+       }
+       if (!Surv_CanPlayerSpawn(victim))
+       {
+               victim.respawn_flags = RESPAWN_SILENT;
+               if (IS_BOT_CLIENT(victim))
+               {
+                       bot_clear(victim);
+               }
+       }
+       return true;
+}
+
+/// \brief Hook which is called after the player died.
+MUTATOR_HOOKFUNCTION(surv, PlayerDied)
+{
+       //DebugPrintToChatAll("PlayerDied");
+       entity player = M_ARGV(0, entity);
+       Surv_RemovePlayerFromAliveList(player, player.team);
+       //Surv_CountAlivePlayers();
+}
+
+/// \brief Hook which is called when player has scored a frag.
+MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       if (surv_type == SURVIVAL_TYPE_COOP)
+       {
+               return true;
+       }
+       entity attacker = M_ARGV(0, entity);
+       if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
+               SURVIVAL_ROLE_CANNON_FODDER))
+       {
+               M_ARGV(2, float) = 0;
+               return true;
+       }
+       entity target = M_ARGV(1, entity);
+       if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
+               surv_defenderteam))
+       {
+               M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
+       }
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, SpectateSet)
+{
+       entity client = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+
+       if (!autocvar_g_surv_spectate_enemies &&
+               (client.surv_state == SURVIVAL_STATE_PLAYING) &&
+               DIFF_TEAM(targ, client))
+       {
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(surv, SpectateNext)
+{
+       entity client = M_ARGV(0, entity);
+
+       if (!autocvar_g_surv_spectate_enemies &&
+               (client.surv_state == SURVIVAL_STATE_PLAYING))
+       {
+               entity targ = M_ARGV(1, entity);
+               M_ARGV(1, entity) = CA_SpectateNext(client, targ);
+               return true;
+       }
+}
+
+MUTATOR_HOOKFUNCTION(surv, SpectatePrev)
+{
+       entity client = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+       entity first = M_ARGV(2, entity);
+
+       if (!autocvar_g_surv_spectate_enemies &&
+               (client.surv_state == SURVIVAL_STATE_PLAYING))
+       {
+               do
+               {
+                       targ = targ.chain;
+               }
+               while (targ && DIFF_TEAM(targ, client));
+               if (!targ)
+               {
+                       for (targ = first; targ && DIFF_TEAM(targ, client);
+                               targ = targ.chain);
+
+                       if (targ == client.enemy)
+                       {
+                               return MUT_SPECPREV_RETURN;
+                       }
+               }
+       }
+       M_ARGV(1, entity) = targ;
+       return MUT_SPECPREV_FOUND;
+}
+
+/// \brief I'm not sure exactly what this function does but it is very
+/// important. Without it bots are completely broken. Is it a hack? Of course.
+MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
+               {
+                       ++M_ARGV(0, int);
+               }
+               ++M_ARGV(1, int);
+       });
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
+{
+       // Don't announce remaining frags
+       return false;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh
new file mode 100644 (file)
index 0000000..a381e07
--- /dev/null
@@ -0,0 +1,15 @@
+#pragma once
+
+/// \brief Initializes global data for the gametype.
+/// \return No return.
+void Surv_Initialize();
+
+REGISTER_MUTATOR(surv, false)
+{
+       MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               Surv_Initialize();
+       }
+       return 0;
+}
index 2dd84596e46991e8f3f75daeb63b524273825d01..834b38550a9b83e2cc7fbb5fbccc235553284cb7 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)
@@ -480,6 +480,52 @@ CLASS(Invasion, Gametype)
 ENDCLASS(Invasion)
 REGISTER_GAMETYPE(INVASION, NEW(Invasion));
 
+//=============================================================================
+
+#ifdef CSQC
+void HUD_Mod_GG(vector pos, vector mySize);
+#endif
+CLASS(GunGame, Gametype)
+    INIT(GunGame)
+    {
+        this.gametype_init(this, _("GunGame"), "gg", "g_gg", false, "", "timelimit=20", _("Kill players with all weapons"));
+    }
+    METHOD(GunGame, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
+    {
+        return true;
+    }
+#ifdef CSQC
+    ATTRIB(GunGame, m_modicons, void(vector pos, vector mySize), HUD_Mod_GG);
+#endif
+ENDCLASS(GunGame)
+REGISTER_GAMETYPE(GUNGAME, NEW(GunGame));
+#define g_gg IS_GAMETYPE(GUNGAME)
+
+//=============================================================================
+
+// 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 73f22b836ce6485c9a5e9164e96bf5238a14e95e..46ac1c3384056ceff911b37b6ab7995614c6ae25 100644 (file)
@@ -54,6 +54,9 @@ REGISTER_WAYPOINT(OnsCPAttack, _("Control point"), '1 0.5 0', 2);
 REGISTER_WAYPOINT(OnsGen, _("Generator"), '1 0.5 0', 1);
 REGISTER_WAYPOINT(OnsGenShielded, _("Generator"), '1 0.5 0', 1);
 
+REGISTER_WAYPOINT(SurvivalKill, _("Kill"), '0.8 0.8 0', 1);
+REGISTER_WAYPOINT(SurvivalDefend, _("Defend"), '0 1 0', 1);
+
 REGISTER_WAYPOINT(Weapon, _("Weapon"), '0 0 0', 1);
 
 REGISTER_WAYPOINT(Monster, _("Monster"), '1 0 0', 1);
index b4c6146bb6662535e983df75a4e73962e6289636..6ba3af93e79e3bb4f7271984c0f5ea35611cb1d7 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 4fbadb254c0b2e82e99c8cb8444e34d50541f591..3d778df1a8a354fe5c93bb778e8329a88177e614 100644 (file)
@@ -291,6 +291,15 @@ REGISTER_STAT(DOM_PPS_BLUE, float)
 REGISTER_STAT(DOM_PPS_YELLOW, float)
 REGISTER_STAT(DOM_PPS_PINK, float)
 
+// gungame
+REGISTER_STAT(GUNGAME_LEADING_WEAPON, int)
+
+// 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 1f8a603dd01744b1dde6a4f13f98b5b32bb61d03..57d644c0448549e13bff834f7cb6fd289d983cc3 100644 (file)
@@ -125,6 +125,42 @@ float Team_ColorToTeam(string team_color)
        return -1;
 }
 
+/// \brief Returns whether team is valid.
+/// \param[in] team_ Team to check.
+/// \return True if team is valid, false otherwise.
+bool Team_IsValidTeam(int team_)
+{
+       switch (team_)
+       {
+               case NUM_TEAM_1:
+               case NUM_TEAM_2:
+               case NUM_TEAM_3:
+               case NUM_TEAM_4:
+               {
+                       return true;
+               }
+       }
+       return false;
+}
+
+/// \brief Returns whether team number is valid.
+/// \param[in] number Team number to check.
+/// \return True if team number is valid, false otherwise.
+bool Team_IsValidNumber(int number)
+{
+       switch (number)
+       {
+               case 1:
+               case 2:
+               case 3:
+               case 4:
+               {
+                       return true;
+               }
+       }
+       return false;
+}
+
 float Team_NumberToTeam(float number)
 {
        switch(number)
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 7b381b544588ae4d9049d972ad0d74d44c7c9a23..05b54e3ad72d5f773fcb923a0781daa37c11ec4d 100644 (file)
@@ -693,6 +693,8 @@ float updateCompression()
        GAMETYPE(MAPINFO_TYPE_NEXBALL) \
        GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \
        GAMETYPE(MAPINFO_TYPE_ASSAULT) \
+       GAMETYPE(MAPINFO_TYPE_GUNGAME) \
+       GAMETYPE(MAPINFO_TYPE_SURVIVAL) \
        /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \
        /**/
 
index 569301c5d65c369b60deb900f44be3bf1447c5bf..a62111526fdb4313305adf78b926a71f4ef09b2f 100644 (file)
@@ -20,6 +20,7 @@
 #include <server/miscfunctions.qc>
 #include <server/player.qc>
 #include <server/playerdemo.qc>
+#include <server/playertemplates.qc>
 #include <server/portals.qc>
 #include <server/race.qc>
 #include <server/resources.qc>
index 2013fd6bb5db5c521737cf0985fc65c13a43846e..92072625767dbd7664a4960400c3848ac1c0b95a 100644 (file)
@@ -20,6 +20,7 @@
 #include <server/miscfunctions.qh>
 #include <server/player.qh>
 #include <server/playerdemo.qh>
+#include <server/playertemplates.qh>
 #include <server/portals.qh>
 #include <server/race.qh>
 #include <server/resources.qh>
index f8ddd0c637576d81efb058e330c11922d9438a56..6ea375c0879b19ec945269deb93518e496b4c216 100644 (file)
@@ -438,7 +438,7 @@ void bot_clientconnect(entity this)
        else if(this.bot_forced_team==4)
                this.team = NUM_TEAM_4;
        else
-               JoinBestTeam(this, false, true);
+               JoinBestTeam(this, true);
 
        havocbot_setupbot(this);
 }
index 856783df0dedd28ebf0599cadb057a2fcb7ec28d..399d11d63e63185d50053634bf99d55b0c5b2aae 100644 (file)
@@ -509,7 +509,7 @@ void PutPlayerInServer(entity this)
        accuracy_resend(this);
 
        if (this.team < 0)
-               JoinBestTeam(this, false, true);
+               JoinBestTeam(this, true);
 
        entity spot = SelectSpawnPoint(this, false);
        if (!spot) {
@@ -879,7 +879,7 @@ void ClientKill_Now_TeamChange(entity this)
 {
        if(this.killindicator_teamchange == -1)
        {
-               JoinBestTeam( this, false, true );
+               JoinBestTeam( this, true );
        }
        else if(this.killindicator_teamchange == -2)
        {
@@ -1191,7 +1191,7 @@ void ClientConnect(entity this)
     {
         int id = this.playerid;
         this.playerid = 0; // silent
-           JoinBestTeam(this, false, false); // if the team number is valid, keep it
+           JoinBestTeam(this, false); // if the team number is valid, keep it
            this.playerid = id;
     }
 
@@ -2006,7 +2006,7 @@ void Join(entity this)
 
        if(!this.team_selected)
        if(autocvar_g_campaign || autocvar_g_balance_teams)
-               JoinBestTeam(this, false, true);
+               JoinBestTeam(this, true);
 
        if(autocvar_g_campaign)
                campaign_bots_may_start = true;
index 2eda8584ef5d82eeb9f346cb131aa9a049a58488..e12091d4ecefdcf864d73f03036a87b042a9f828 100644 (file)
@@ -271,6 +271,7 @@ void cvar_changes_init()
                BADCVAR("g_domination_default_teams");
                BADCVAR("g_freezetag");
                BADCVAR("g_freezetag_teams");
+               BADCVAR("g_gg");
                BADCVAR("g_invasion_teams");
                BADCVAR("g_invasion_type");
                BADCVAR("g_jailbreak");
@@ -287,6 +288,7 @@ void cvar_changes_init()
                BADCVAR("g_race_qualifying_timelimit_override");
                BADCVAR("g_runematch");
                BADCVAR("g_snafu");
+               BADCVAR("g_surv"); // Lyberta: adding gamemode cvar
                BADCVAR("g_tdm");
                BADCVAR("g_tdm_teams");
                BADCVAR("g_vip");
diff --git a/qcsrc/server/playertemplates.qc b/qcsrc/server/playertemplates.qc
new file mode 100644 (file)
index 0000000..d003236
--- /dev/null
@@ -0,0 +1,725 @@
+/// \file
+/// \brief Source file that contains implementation of the player templates
+/// system.
+/// \author Lyberta
+/// \copyright GNU GPLv3 or any later version.
+
+#include <common/items/item.qh>
+#include "autocvars.qh"
+#include "resources.qh"
+#include "mutators/events.qh"
+#include <common/mutators/mutator/overkill/hmg.qh>
+#include <common/mutators/mutator/overkill/rpc.qh>
+
+const string playertemplate_cvar_prefix = "g_player_template_";
+
+string PlayerTemplate_GetFullCvarName(string template, string variable)
+{
+       return strcat(playertemplate_cvar_prefix, template, "_", variable);
+}
+
+string PlayerTemplate_GetDefaultCvarName(string variable)
+{
+       switch (variable)
+       {
+               case "start_health":
+               {
+                       return "g_balance_health_start";
+               }
+               case "start_armor":
+               {
+                       return "g_balance_armor_start";
+               }
+               case "start_ammo_shells":
+               {
+                       return "g_start_ammo_shells";
+               }
+               case "start_ammo_bullets":
+               {
+                       return "g_start_ammo_nails";
+               }
+               case "start_ammo_rockets":
+               {
+                       return "g_start_ammo_rockets";
+               }
+               case "start_ammo_cells":
+               {
+                       return "g_start_ammo_cells";
+               }
+               case "start_ammo_plasma":
+               {
+                       return "g_start_ammo_plasma";
+               }
+               case "start_ammo_fuel":
+               {
+                       return "g_start_ammo_fuel";
+               }
+               case "random_start_weapons_count":
+               {
+                       return "g_random_start_weapons_count";
+               }
+               case "random_start_weapons":
+               {
+                       return "g_random_start_weapons";
+               }
+               case "random_start_shells":
+               {
+                       return "g_random_start_shells";
+               }
+               case "random_start_bullets":
+               {
+                       return "g_random_start_bullets";
+               }
+               case "random_start_rockets":
+               {
+                       return "g_random_start_rockets";
+               }
+               case "random_start_cells":
+               {
+                       return "g_random_start_cells";
+               }
+               case "random_start_plasma":
+               {
+                       return "g_random_start_plasma";
+               }
+               case "start_ammo_vaporizer_cells":
+               {
+                       return "g_instagib_ammo_start";
+               }
+               case "drop_weapons":
+               {
+                       return "g_weapon_throwable";
+               }
+               case "health_regen_factor":
+               {
+                       return "g_balance_health_regen";
+               }
+               case "health_regen_linear":
+               {
+                       return "g_balance_health_regenlinear";
+               }
+               case "health_rot_factor":
+               {
+                       return "g_balance_health_rot";
+               }
+               case "health_rot_linear":
+               {
+                       return "g_balance_health_rotlinear";
+               }
+               case "health_regen_stable":
+               {
+                       return "g_balance_health_regenstable";
+               }
+               case "health_rot_stable":
+               {
+                       return "g_balance_health_rotstable";
+               }
+               default:
+               {
+                       // TODO: Report error.
+                       return "";
+               }
+       }
+}
+
+float PlayerTemplate_GetDefaultFloatValue(string variable)
+{
+       switch (variable)
+       {
+               case "start_health":
+               case "start_armor":
+               case "start_ammo_shells":
+               case "start_ammo_bullets":
+               case "start_ammo_rockets":
+               case "start_ammo_cells":
+               case "start_ammo_plasma":
+               case "start_ammo_fuel":
+               case "random_start_weapons_count":
+               case "random_start_shells":
+               case "random_start_bullets":
+               case "random_start_rockets":
+               case "random_start_cells":
+               case "random_start_plasma":
+               case "start_ammo_vaporizer_cells":
+               case "drop_weapons":
+               case "health_regen_factor":
+               case "health_regen_linear":
+               case "health_rot_factor":
+               case "health_rot_linear":
+               case "health_regen_stable":
+               case "health_rot_stable":
+               {
+                       return cvar(PlayerTemplate_GetDefaultCvarName(variable));
+               }
+               case "unlimited_ammo":
+               {
+                       return !autocvar_g_use_ammunition;
+               }
+               case "default_start_weapons":
+               {
+                       return 1;
+               }
+               case "start_extra_lives":
+               {
+                       return 0;
+               }
+               case "attack_scale":
+               case "defense_scale":
+               {
+                       return 1;
+               }
+               case "blaster_self_damage":
+               {
+                       return 1;
+               }
+               default:
+               {
+                       // TODO: Report error.
+                       return 0;
+               }
+       }
+}
+
+string PlayerTemplate_GetDefaultStringValue(string variable)
+{
+       switch (variable)
+       {
+               case "random_start_weapons":
+               {
+                       cvar_string(PlayerTemplate_GetDefaultCvarName(variable));
+               }
+               case "start_weapons":
+               {
+                       return "";
+               }
+               default:
+               {
+                       // TODO: Report error.
+                       return "";
+               }
+       }
+}
+
+float PlayerTemplate_GetFloatValue(string template, string variable)
+{
+       if (template == "default")
+       {
+               return PlayerTemplate_GetDefaultFloatValue(variable);
+       }
+       string fullname = PlayerTemplate_GetFullCvarName(template, variable);
+       if (!(cvar_type(fullname) & CVAR_TYPEFLAG_EXISTS))
+       {
+               return PlayerTemplate_GetDefaultFloatValue(variable);
+       }
+       if (cvar_string(fullname) == "default")
+       {
+               return PlayerTemplate_GetDefaultFloatValue(variable);
+       }
+       return cvar(fullname);
+}
+
+string PlayerTemplate_GetStringValue(string template, string variable)
+{
+       if (template == "default")
+       {
+               return PlayerTemplate_GetDefaultStringValue(variable);
+       }
+       string fullname = PlayerTemplate_GetFullCvarName(template, variable);
+       if (!(cvar_type(fullname) & CVAR_TYPEFLAG_EXISTS))
+       {
+               return PlayerTemplate_GetDefaultStringValue(variable);
+       }
+       if (cvar_string(fullname) == "default")
+       {
+               return PlayerTemplate_GetDefaultStringValue(variable);
+       }
+       return cvar_string(fullname);
+}
+
+float PlayerTemplate_GivePlayerItem(entity player, string template,
+       string variable)
+{
+       string value = PlayerTemplate_GetStringValue(template, variable);
+       if (value == "default")
+       {
+               return MUT_ITEMTOUCH_CONTINUE;
+       }
+       int numfields = tokenize_console(PlayerTemplate_GetStringValue(template,
+               variable));
+       if (numfields == 0)
+       {
+               return MUT_ITEMTOUCH_CONTINUE;
+       }
+       for (int i = 1; i < numfields; ++i)
+       {
+               switch (i)
+               {
+                       case 1:
+                       {
+                               GiveResource(player, RESOURCE_HEALTH, stof(argv(i)));
+                               break;
+                       }
+                       case 2:
+                       {
+                               GiveResource(player, RESOURCE_ARMOR, stof(argv(i)));
+                               break;
+                       }
+                       case 3:
+                       {
+                               GiveResource(player, RESOURCE_SHELLS, stof(argv(i)));
+                               break;
+                       }
+                       case 4:
+                       {
+                               GiveResource(player, RESOURCE_BULLETS, stof(argv(i)));
+                               break;
+                       }
+                       case 5:
+                       {
+                               GiveResource(player, RESOURCE_ROCKETS, stof(argv(i)));
+                               break;
+                       }
+                       case 6:
+                       {
+                               GiveResource(player, RESOURCE_CELLS, stof(argv(i)));
+                               break;
+                       }
+                       case 7:
+                       {
+                               GiveResource(player, RESOURCE_PLASMA, stof(argv(i)));
+                               break;
+                       }
+                       case 8:
+                       {
+                               GiveResource(player, RESOURCE_FUEL, stof(argv(i)));
+                               break;
+                       }
+               }
+       }
+       switch (argv(0))
+       {
+               case "add":
+               {
+                       return MUT_ITEMTOUCH_CONTINUE;
+               }
+               case "override":
+               {
+                       return MUT_ITEMTOUCH_PICKUP;
+               }
+               default:
+               {
+                       return MUT_ITEMTOUCH_CONTINUE;
+               }
+       }
+}
+
+// =========================== Hook handlers =================================
+
+void PlayerTemplateHook_PlayerSpawn(entity player, string template)
+{
+       if (template == "default")
+       {
+               return;
+       }
+       if (autocvar_g_instagib)
+       {
+               SetResourceAmount(player, RESOURCE_ARMOR,
+                       PlayerTemplate_GetFloatValue(template, "start_extra_lives"));
+               SetResourceAmount(player, RESOURCE_CELLS,
+                       PlayerTemplate_GetFloatValue(template,
+                       "start_ammo_vaporizer_cells"));
+               if (PlayerTemplate_GetFloatValue(template, "unlimited_ammo"))
+               {
+                       player.items |= IT_UNLIMITED_AMMO;
+               }
+               return;
+       }
+       // Give health, armor and ammo.
+       SetResourceAmount(player, RESOURCE_HEALTH,
+               PlayerTemplate_GetFloatValue(template, "start_health"));
+       SetResourceAmount(player, RESOURCE_ARMOR, 
+               PlayerTemplate_GetFloatValue(template, "start_armor"));
+       if (PlayerTemplate_GetFloatValue(template, "unlimited_ammo"))
+       {
+               player.items |= IT_UNLIMITED_AMMO;
+       }
+       else
+       {
+               SetResourceAmount(player, RESOURCE_SHELLS,
+                       PlayerTemplate_GetFloatValue(template, "start_ammo_shells"));
+               SetResourceAmount(player, RESOURCE_BULLETS,
+                       PlayerTemplate_GetFloatValue(template, "start_ammo_bullets"));
+               SetResourceAmount(player, RESOURCE_ROCKETS,
+                       PlayerTemplate_GetFloatValue(template, "start_ammo_rockets"));
+               SetResourceAmount(player, RESOURCE_CELLS,
+                       PlayerTemplate_GetFloatValue(template, "start_ammo_cells"));
+               SetResourceAmount(player, RESOURCE_PLASMA,
+                       PlayerTemplate_GetFloatValue(template, "start_ammo_plasma"));
+               SetResourceAmount(player, RESOURCE_FUEL,
+                       PlayerTemplate_GetFloatValue(template, "start_ammo_fuel"));
+       }
+       // Give weapons.
+       player.weapons = WEPSET(Null);
+       if (PlayerTemplate_GetFloatValue(template, "default_start_weapons"))
+       {
+               FOREACH(Weapons, it != WEP_Null,
+               {
+                       if (it.weaponstart)
+                       {
+                               player.weapons |= it.m_wepset;
+                       }
+               });
+       }
+       int numweapons = tokenize_console(PlayerTemplate_GetStringValue(template,
+               "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;
+                       }
+               });
+       }
+       if (!warmup_stage)
+       {
+               entity ammo_entity = spawn();
+               SetResourceAmount(ammo_entity, RESOURCE_SHELLS,
+                       PlayerTemplate_GetFloatValue(template, "random_start_shells"));
+               SetResourceAmount(ammo_entity, RESOURCE_BULLETS,
+                       PlayerTemplate_GetFloatValue(template, "random_start_bullets"));
+               SetResourceAmount(ammo_entity, RESOURCE_ROCKETS,
+                       PlayerTemplate_GetFloatValue(template, "random_start_rockets"));
+               SetResourceAmount(ammo_entity, RESOURCE_CELLS,
+                       PlayerTemplate_GetFloatValue(template, "random_start_cells"));
+               SetResourceAmount(ammo_entity, RESOURCE_PLASMA,
+                       PlayerTemplate_GetFloatValue(template, "random_start_plasma"));
+               GiveRandomWeapons(player, PlayerTemplate_GetFloatValue(template,
+                       "random_start_weapons_count"),
+                       PlayerTemplate_GetStringValue(template, "random_start_weapons"),
+                       ammo_entity);
+               remove(ammo_entity);
+               return;
+       }
+       // Give random weapons.
+       numweapons = tokenize_console(PlayerTemplate_GetStringValue(template,
+               "random_start_weapons"));
+       // 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;
+                       }
+               });
+       }
+}
+
+bool PlayerTemplateHook_ForbidThrowCurrentWeapon(string template)
+{
+       return !PlayerTemplate_GetFloatValue(template, "drop_weapons");
+}
+
+float PlayerTemplateHook_PlayerRegen(entity player, string template)
+{
+       if (template == "default")
+       {
+               return false;
+       }
+       M_ARGV(5, float) = PlayerTemplate_GetFloatValue(template,
+               "health_regen_factor");
+       M_ARGV(6, float) = PlayerTemplate_GetFloatValue(template,
+               "health_regen_linear");
+       M_ARGV(7, float) = PlayerTemplate_GetFloatValue(template,
+               "health_rot_factor");
+       M_ARGV(8, float) = PlayerTemplate_GetFloatValue(template,
+               "health_rot_linear");
+       M_ARGV(9, float) = PlayerTemplate_GetFloatValue(template,
+               "health_regen_stable");
+       M_ARGV(10, float) = PlayerTemplate_GetFloatValue(template,
+               "health_rot_stable");
+       return false;
+}
+
+float PlayerTemplateHook_ItemTouch(entity player, entity item, string template)
+{
+       if (template == "default")
+       {
+               return MUT_ITEMTOUCH_CONTINUE;
+       }
+       switch (item.classname)
+       {
+               case "item_health_small":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_health_small");
+               }
+               case "item_health_medium":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_health_medium");
+               }
+               case "item_health_big":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_health_big");
+               }
+               case "item_health_mega":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_health_mega");
+               }
+               case "item_armor_small":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_armor_small");
+               }
+               case "item_armor_medium":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_armor_medium");
+               }
+               case "item_armor_big":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_armor_big");
+               }
+               case "item_armor_mega":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_armor_mega");
+               }
+               case "item_shells":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_shells");
+               }
+               case "item_bullets":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_bullets");
+               }
+               case "item_rockets":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_rockets");
+               }
+               case "item_cells":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_cells");
+               }
+               case "item_plasma":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_plasma");
+               }
+               case "item_fuel":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_fuel");
+               }
+               case "weapon_blaster":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_blaster");
+               }
+               case "weapon_shotgun":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_shotgun");
+               }
+               case "weapon_machinegun":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_machinegun");
+               }
+               case "weapon_mortar":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_mortar");
+               }
+               case "weapon_electro":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_electro");
+               }
+               case "weapon_crylink":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_crylink");
+               }
+               case "weapon_vortex":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_vortex");
+               }
+               case "weapon_hagar":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_hagar");
+               }
+               case "weapon_devastator":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_devastator");
+               }
+               case "weapon_shockwave":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_shockwave");
+               }
+               case "weapon_arc":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_arc");
+               }
+               case "weapon_hook":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_hook");
+               }
+               case "weapon_tuba":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_tuba");
+               }
+               case "weapon_porto":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_porto");
+               }
+               case "weapon_fireball":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_fireball");
+               }
+               case "weapon_minelayer":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_minelayer");
+               }
+               case "weapon_hlac":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_hlac");
+               }
+               case "weapon_rifle":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_rifle");
+               }
+               case "weapon_seeker":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_seeker");
+               }
+               case "weapon_vaporizer":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_vaporizer");
+               }
+               case "weapon_hmg":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_hmg");
+               }
+               case "weapon_rpc":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_weapon_rpc");
+               }
+               case "item_strength":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_strength");
+               }
+               case "item_shield":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_shield");
+               }
+               case "item_fuel_regen":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_fuel_regen");
+               }
+               case "item_jetpack":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_jetpack");
+               }
+               case "item_vaporizer_cells":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_vaporizer_cells");
+               }
+               case "item_invisibility":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_invisibility");
+               }
+               case "item_extralife":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_extralife");
+               }
+               case "item_speed":
+               {
+                       return PlayerTemplate_GivePlayerItem(player, template,
+                               "pickup_item_speed");
+               }
+               default:
+               {
+                       switch (item.netname)
+                       {
+                               default:
+                               {
+                                       PrintToChatAll(strcat("Unrecognized item, classname: ",
+                                               item.classname, " netname: ", item.netname));
+                                       return MUT_ITEMTOUCH_CONTINUE;
+                               }
+                       }
+               }
+       }
+       return MUT_ITEMTOUCH_CONTINUE;
+}
+
+float PlayerTemplateHook_Damage_Calculate(entity attacker,
+       string attackertemplate, entity victim, string victimtemplate,
+       float deathtype, float damage)
+{
+       if (autocvar_g_instagib == 1)
+       {
+               return damage;
+       }
+       if ((attacker == victim) && (DEATH_ISWEAPON(deathtype, WEP_BLASTER)) &&
+               (PlayerTemplate_GetFloatValue(victimtemplate, "blaster_self_damage") ==
+               0))
+       {
+               return 0;
+       }
+       damage *= PlayerTemplate_GetFloatValue(attackertemplate, "attack_scale");
+       damage /= PlayerTemplate_GetFloatValue(victimtemplate, "defense_scale");
+       return damage;
+}
+
+void PlayerTemplateHook_PlayerDies(entity player, string template)
+{
+       if (template == "default")
+       {
+               return;
+       }
+       if (PlayerTemplate_GetFloatValue(template, "drop_weapons"))
+       {
+               return;
+       }
+       player.weapons = WEPSET(Null);
+}
diff --git a/qcsrc/server/playertemplates.qh b/qcsrc/server/playertemplates.qh
new file mode 100644 (file)
index 0000000..d9ecb7a
--- /dev/null
@@ -0,0 +1,95 @@
+/// \file
+/// \brief Header file that describes the player templates system.
+/// \author Lyberta
+/// \copyright GNU GPLv3 or any later version.
+
+#pragma once
+
+/// \brief Returns the full name of the player template cvar.
+/// \param[in] template Name of the template.
+/// \param[in] variable Name of the variable.
+/// \return Full name of the variable.
+string PlayerTemplate_GetFullCvarName(string template, string variable);
+
+/// \brief Returns the name of the cvar that is used for default value.
+/// \param[in] Name of the player template variable.
+/// \return Name of the cvar that is used for default value.
+string PlayerTemplate_GetDefaultCvarName(string variable);
+
+/// \brief Returns the default floating point value of the player template
+/// variable.
+/// \param[in] variable Name of the variable.
+/// \return Default floating point value of the player template variable.
+float PlayerTemplate_GetDefaultFloatValue(string variable);
+
+/// \brief Returns the default string value of the player template variable.
+/// \param[in] variable Name of the variable.
+/// \return Default string value of the player template variable.
+string PlayerTemplate_GetDefaultStringValue(string variable);
+
+/// \brief Gets the floating point value of the variable from the given
+/// template.
+/// \param[in] template Name of the template.
+/// \param[in] variable Name of the variable.
+/// \return Value of the variable.
+float PlayerTemplate_GetFloatValue(string template, string variable);
+
+/// \brief Gets the string value of the variable from the given template.
+/// \param[in] template Name of the template.
+/// \param[in] variable Name of the variable.
+/// \return Value of the variable.
+string PlayerTemplate_GetStringValue(string template, string variable);
+
+/// \brief Gives player items according to the given template's variable.
+/// \param[in] player Player to give items to.
+/// \param[in] template Name of the template.
+/// \param[in] variable Name of the variable.
+/// \return Enum value to pass to mutator hook.
+float PlayerTemplate_GivePlayerItem(entity player, string template,
+       string variable);
+
+// =========================== Hook handlers =================================
+
+/// \brief Setups the player during spawn according to the given template.
+/// \param[in,out] player Player to setup.
+/// \param[in] template Name of the template.
+/// \return No return.
+void PlayerTemplateHook_PlayerSpawn(entity player, string template);
+
+/// \brief Forbids weapon dropping according to the given template.
+/// \param[in] template Name of the template.
+/// \return Value to pass to mutator hook.
+bool PlayerTemplateHook_ForbidThrowCurrentWeapon(string template);
+
+/// \brief Regenerates player health according to the given template.
+/// \param[in] player Player to regenerate.
+/// \param[in] template Name of the template.
+/// \return Value to pass to mutator hook.
+float PlayerTemplateHook_PlayerRegen(entity player, string template);
+
+/// \brief Gives player items according to the given template.
+/// \param[in,out] player Player to give items to.
+/// \param[in] item Item which player has picked up.
+/// \param[in] template Name of the template.
+/// \return Enum value to pass to mutator hook.
+float PlayerTemplateHook_ItemTouch(entity player, entity item, string template);
+
+/// \brief Changes the damage done using templates' attack and defense scales.
+/// \param[in] attacke Attacker entity.
+/// \param[in] attackertemplate Template of the attacker.
+/// \param[in] victim Victim entity.
+/// \param[in] victimtemplate Template of the victim.
+/// \param[in] deathtype Type of the damage.
+/// \param[in] damage Damage to adjust.
+/// \return Adjusted damage.
+float PlayerTemplateHook_Damage_Calculate(entity attacker,
+       string attackertemplate, entity victim, string victimtemplate,
+       float deathtype, float damage);
+
+/// \brief Strips the player of their weapons if the player is not allowed to
+/// drop them.
+/// \param[in,out] player Player to work with.
+/// \param[in] template Name of the template.
+/// \return No return.
+/// \note You must hook with CBC_ORDER_FIRST in order for this to be effective.
+void PlayerTemplateHook_PlayerDies(entity player, string template);
index fd73969cb81a9d16cb88c55b02bee012cd0dfaa1..33ad8f8ed634f6123bf3a5c247eb83fad0dccf3d 100644 (file)
@@ -545,6 +545,14 @@ void GetTeamCounts(entity ignore)
 bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
        bool use_score)
 {
+       if (!Team_IsValidNumber(team_a))
+       {
+               LOG_FATALF("IsTeamSmallerThanTeam: team_a is invalid: %f", team_a);
+       }
+       if (!Team_IsValidNumber(team_b))
+       {
+               LOG_FATALF("IsTeamSmallerThanTeam: team_b is invalid: %f", team_b);
+       }
        if (team_a == team_b)
        {
                return false;
@@ -642,6 +650,14 @@ bool IsTeamSmallerThanTeam(int team_a, int team_b, entity player,
 
 bool IsTeamEqualToTeam(int team_a, int team_b, entity player, bool use_score)
 {
+       if (!Team_IsValidNumber(team_a))
+       {
+               LOG_FATALF("IsTeamEqualToTeam: team_a is invalid: %f", team_a);
+       }
+       if (!Team_IsValidNumber(team_b))
+       {
+               LOG_FATALF("IsTeamEqualToTeam: team_b is invalid: %f", team_b);
+       }
        if (team_a == team_b)
        {
                return true;
@@ -837,53 +853,56 @@ int FindSmallestTeam(entity player, float ignore_player)
        return RandomSelection_chosen_float;
 }
 
-int JoinBestTeam(entity this, bool only_return_best, bool force_best_team)
+void JoinBestTeam(entity this, bool force_best_team)
 {
        // don't join a team if we're not playing a team game
        if (!teamplay)
        {
-               return 0;
+               return;
        }
 
        // find out what teams are available
        CheckAllowedTeams(this);
 
-       // if we don't care what team he ends up on, put him on whatever team he entered as.
-       // if he's not on a valid team, then let other code put him on the smallest team
+       // if we don't care what team they end up on, put them on whatever team they entered as.
+       // if they're not on a valid team, then let other code put them on the smallest team
        if (!force_best_team)
        {
                int selected_team;
-               if(     c1 >= 0 && this.team == NUM_TEAM_1)
+               if ((c1 >= 0) && (this.team == NUM_TEAM_1))
+               {
                        selected_team = this.team;
-               else if(c2 >= 0 && this.team == NUM_TEAM_2)
+               }
+               else if ((c2 >= 0) && (this.team == NUM_TEAM_2))
+               {
                        selected_team = this.team;
-               else if(c3 >= 0 && this.team == NUM_TEAM_3)
+               }
+               else if ((c3 >= 0) && (this.team == NUM_TEAM_3))
+               {
                        selected_team = this.team;
-               else if(c4 >= 0 && this.team == NUM_TEAM_4)
+               }
+               else if ((c4 >= 0) && (this.team == NUM_TEAM_4))
+               {
                        selected_team = this.team;
+               }
                else
+               {
                        selected_team = -1;
+               }
 
                if (selected_team > 0)
                {
-                       if (!only_return_best)
-                       {
-                               SetPlayerTeamSimple(this, selected_team);
-
-                               // 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
-                               LogTeamchange(this.playerid, this.team, 99);
-                       }
-                       return selected_team;
+                       SetPlayerTeamSimple(this, selected_team);
+                       LogTeamchange(this.playerid, this.team, 99);
+                       return;
                }
-               // otherwise end up on the smallest team (handled below)
        }
-
-       int best_team = FindSmallestTeam(this, true);
-       if (only_return_best || this.bot_forced_team)
+       // otherwise end up on the smallest team (handled below)
+       if (this.bot_forced_team)
        {
-               return best_team;
+               return;
        }
+       int best_team = FindSmallestTeam(this, true);
        best_team = Team_NumberToTeam(best_team);
        if (best_team == -1)
        {
@@ -893,12 +912,11 @@ int JoinBestTeam(entity this, bool only_return_best, bool force_best_team)
        TeamchangeFrags(this);
        SetPlayerTeamSimple(this, best_team);
        LogTeamchange(this.playerid, this.team, 2); // log auto join
-       if (!IS_BOT_CLIENT(this))
+       if ((old_team != -1) && !IS_BOT_CLIENT(this))
        {
                AutoBalanceBots(old_team, Team_TeamToNumber(best_team));
        }
        KillPlayerForTeamChange(this);
-       return best_team;
 }
 
 void SV_ChangeTeam(entity this, float _color)
@@ -978,8 +996,15 @@ void SV_ChangeTeam(entity this, float _color)
 
 void AutoBalanceBots(int source_team, int destination_team)
 {
-       if ((source_team == -1) || (destination_team == -1))
+       if (!Team_IsValidNumber(source_team))
+       {
+               LOG_WARNF("AutoBalanceBots: Source team is invalid: %f", source_team);
+               return;
+       }
+       if (!Team_IsValidNumber(destination_team))
        {
+               LOG_WARNF("AutoBalanceBots: Destination team is invalid: %f",
+                       destination_team);
                return;
        }
        if (!autocvar_g_balance_teams ||
@@ -1013,6 +1038,10 @@ void AutoBalanceBots(int source_team, int destination_team)
                        break;
                }
        }
+       if (num_players_source_team < 0)
+       {
+               return;
+       }
        switch (destination_team)
        {
                case 1:
index 1813db04d8d111387a6a1667c6033e9b2e890f6c..7c4ebe77b6c2a82a51ae2949999734d5da4022e8 100644 (file)
@@ -107,7 +107,7 @@ int FindBestTeams(entity player, bool use_score);
 // NOTE: Assumes CheckAllowedTeams has already been called!
 int FindSmallestTeam(entity player, float ignore_player);
 
-int JoinBestTeam(entity this, bool only_return_best, bool force_best_team);
+void JoinBestTeam(entity this, bool force_best_team);
 
 /// \brief Auto balances bots in teams after the player has changed team.
 /// \param[in] source_team Previous team of the player (1, 2, 3, 4).
index c1d3b8c237a0a92221ec5f2c985dfe3997342434..158e827e687f1930a0caef796384ae8acf1892df 100644 (file)
Binary files a/sound/misc/kill.ogg and b/sound/misc/kill.ogg differ
diff --git a/survival.cfg b/survival.cfg
new file mode 100644 (file)
index 0000000..64e395a
--- /dev/null
@@ -0,0 +1,113 @@
+// 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_spectate_enemies 0 "Whether to allow spectating enemy players while dead."
+
+set g_surv_attacker_template "surv_attacker" "Player template of attackers."
+set g_surv_defender_template "surv_defender" "Player template of defenders."
+set g_surv_cannon_fodder_template "surv_cannon_fodder" "Player template of cannon fodder."
+
+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_player_template_surv_attacker_unlimited_ammo 1 "Whether to give attackers unlimited ammo."
+set g_player_template_surv_attacker_random_start_weapons_count 2 "Number of weapons that can be randomly given to attackers during spawn."
+set g_player_template_surv_attacker_random_start_weapons "machinegun mortar electro crylink vortex hagar devastator" "Weapons that can be randomly given to attackers during spawn."
+
+set g_player_template_surv_defender_start_health 200 "How much health do defenders get during spawn."
+set g_player_template_surv_defender_start_armor 200 "How much armor do defenders get during spawn."
+set g_player_template_surv_defender_start_ammo_shells 15 "How many shells do defenders get during spawn."
+set g_player_template_surv_defender_start_ammo_bullets 15 "How many bullets do defenders get during spawn."
+set g_player_template_surv_defender_start_ammo_rockets 0 "How many rockets do defenders get during spawn."
+set g_player_template_surv_defender_start_ammo_cells 30 "How many cells do defenders get during spawn."
+set g_player_template_surv_defender_default_start_weapons 0 "Whether to give defenders default start weapons."
+set g_player_template_surv_defender_start_weapons "okshotgun okmachinegun okvortex" "Which weapons do defenders get during spawn."
+set g_player_template_surv_defender_start_extra_lives 5 "How many extra lives do defenders get during spawn in instagib."
+
+set g_player_template_surv_cannon_fodder_unlimited_ammo 1 "Whether to give cannon fodder unlimited ammo."
+set g_player_template_surv_cannon_fodder_random_start_weapons_count 2 "Number of weapons that can be randomly given to cannon fodder during spawn."
+set g_player_template_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_player_template_surv_defender_drop_weapons 0 "Whether defenders can drop weapons by throwing them or by dying."
+
+set g_player_template_surv_attacker_pickup_item_shells "add 25 25" "What items do attackers get when they pickup shells."
+set g_player_template_surv_attacker_pickup_item_bullets "add 25 25" "What items do attackers get when they pickup bullets."
+set g_player_template_surv_attacker_pickup_item_rockets "add 25 25" "What items do attackers get when they pickup rockets."
+set g_player_template_surv_attacker_pickup_item_cells "add 25 25" "What items do attackers get when they pickup cells."
+
+set g_player_template_surv_defender_pickup_health_small "override 0 0 5 5 10 10" "What items do defenders get when they pickup small health."
+set g_player_template_surv_defender_pickup_health_medium "override 0 0 10 10 20 20" "What items do defenders get when they pickup medium health."
+set g_player_template_surv_defender_pickup_health_big "override 0 0 20 15 40 40" "What items do defenders get when they pickup big health."
+set g_player_template_surv_defender_pickup_health_mega "override 0 0 30 30 50 50" "What items do defenders get when they pickup mega health."
+set g_player_template_surv_defender_pickup_armor_small "override 0 0 5 5 10 10" "What items do defenders get when they pickup small armor."
+set g_player_template_surv_defender_pickup_armor_medium "override 0 0 10 10 20 20" "What items do defenders get when they pickup medium armor."
+set g_player_template_surv_defender_pickup_armor_big "override 0 0 20 15 40 40" "What items do defenders get when they pickup big armor."
+set g_player_template_surv_defender_pickup_armor_mega "override 0 0 30 30 50 50" "What items do defenders get when they pickup mega armor."
+set g_player_template_surv_defender_pickup_weapon_blaster "override 0 0 10 10 20 20" "What items do defenders get when they pickup a blaster."
+set g_player_template_surv_defender_pickup_weapon_shotgun "override 0 0 15" "What items do defenders get when they pickup a shotgun."
+set g_player_template_surv_defender_pickup_weapon_machinegun "override 0 0 0 80" "What items do defenders get when they pickup a machinegun."
+set g_player_template_surv_defender_pickup_weapon_mortar "override 0 0 0 0 40" "What items do defenders get when they pickup a mortar."
+set g_player_template_surv_defender_pickup_weapon_electro "override 0 0 0 0 0 30" "What items do defenders get when they pickup an electro."
+set g_player_template_surv_defender_pickup_weapon_crylink "override 0 0 0 0 0 30" "What items do defenders get when they pickup a crylink."
+set g_player_template_surv_defender_pickup_weapon_vortex "override 0 0 0 0 0 30" "What items do defenders get when they pickup a vortex."
+set g_player_template_surv_defender_pickup_weapon_hagar "override 0 0 0 0 40" "What items do defenders get when they pickup a hagar."
+set g_player_template_surv_defender_pickup_weapon_devastator "override 0 0 0 0 40" "What items do defenders get when they pickup a devastator."
+set g_player_template_surv_defender_pickup_weapon_shockwave "override 0 0 10 10 20 20" "What items do defenders get when they pickup a shockwave."
+set g_player_template_surv_defender_pickup_weapon_arc "override 0 0 0 0 0 30" "What items do defenders get when they pickup an arc."
+set g_player_template_surv_defender_pickup_weapon_hook "override 0 0 30 160 80 60" "What items do defenders get when they pickup a hook."
+set g_player_template_surv_defender_pickup_weapon_tuba "override 0 0 15 80 40 30" "What items do defenders get when they pickup a tuba."
+set g_player_template_surv_defender_pickup_weapon_porto "override 0 0 30 160 80 60" "What items do defenders get when they pickup a port-o-launch."
+set g_player_template_surv_defender_pickup_weapon_fireball "override 0 0 30 160 80 60" "What items do defenders get when they pickup a fireball."
+set g_player_template_surv_defender_pickup_weapon_minelayer "override 0 0 0 0 40" "What items do defenders get when they pickup a mine layer."
+set g_player_template_surv_defender_pickup_weapon_hlac "override 0 0 0 0 0 30" "What items do defenders get when they pickup a HLAC."
+set g_player_template_surv_defender_pickup_weapon_rifle "override 0 0 0 80" "What items do defenders get when they pickup a rifle."
+set g_player_template_surv_defender_pickup_weapon_seeker "override 0 0 0 0 40" "What items do defenders get when they pickup a TAG seeker."
+set g_player_template_surv_defender_pickup_weapon_vaporizer "override 0 0 0 0 0 180" "What items do defenders get when they pickup a vaporizer."
+
+set g_surv_attacker_damage_score 0.025 "How much score attackers gain per 1 point of damage."
+
+set g_player_template_surv_attacker_blaster_self_damage 0 "Whether attackers get damaged with their own blaster."
+
+set g_player_template_surv_defender_defense_scale 2 "How much defenders get damaged. Higher values mean less damage."
+set g_player_template_surv_defender_blaster_self_damage 0 "Whether defenders get damaged with their own blaster."
+
+set g_player_template_surv_cannon_fodder_blaster_self_damage 0 "Whether cannon fodder gets damaged with their own blaster."
+
+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_attacker_frag_plasma 0 "How much plasma do defenders get when they frag an attacker."
+set g_surv_defender_attacker_frag_fuel 0 "How much fuel 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_cannon_fodder_frag_plasma 0 "How much plasma do defenders get when they frag cannon fodder."
+set g_surv_defender_cannon_fodder_frag_fuel 0 "How much fuel do defenders get when they frag cannon fodder."