--- /dev/null
+#include "sv_survival.qh"
+
+#include <common/mutators/mutator/overkill/hmg.qh>
+#include <common/mutators/mutator/overkill/rpc.qh>
+
+//============================ Constants ======================================
+
+/// \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_attacker_team; ///< Holds the attacker team index.
+int surv_defender_team; ///< Holds the defender team index.
+
+int surv_attacker_team_bit; ///< Holds the attacker team bitmask.
+int surv_defender_team_bit; ///< Holds the defender team bitmask.
+
+int surv_num_attackers; ///< Holds the number of players in attacker team.
+int surv_num_defenders; ///< Holds the number of players in defender team.
+
+/// \brief Holds the number of humans in attacker team.
+int surv_num_attacker_humans;
+/// \brief Holds the number of humans in defender team.
+int surv_num_defender_humans;
+
+/// \brief Holds the number of attacker players that are alive.
+int surv_num_attackers_alive;
+/// \brief Holds the number of defender players that are alive.
+int surv_num_defenders_alive;
+
+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.
+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.
+void Surv_UpdateTeamStats();
+
+/// \brief Updates stats of alive players on HUD.
+void Surv_UpdateAliveStats();
+
+/// \brief Updates defender health on the HUD.
+void Surv_UpdateDefenderHealthStat();
+
+/// \brief Updates the health of defender sprite.
+/// \param[in,out] player Player that has the sprite.
+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:
+ {
+ LOG_FATAL("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_attacker_team = 1;
+ surv_defender_team = 2;
+ }
+ else
+ {
+ surv_attacker_team = 2;
+ surv_defender_team = 1;
+ }
+ surv_attacker_team_bit = Team_IndexToBit(surv_attacker_team);
+ surv_defender_team_bit = Team_IndexToBit(surv_defender_team);
+ surv_num_attackers = 0;
+ surv_num_defenders = 0;
+ surv_num_attacker_humans = 0;
+ surv_num_defender_humans = 0;
+ surv_num_attackers_alive = 0;
+ surv_num_defenders_alive = 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 whether player is an attacker.
+/// \param[in] player Player to check.
+/// \return True if player is an attacker, false otherwise.
+bool Surv_IsPlayerAttacker(entity player)
+{
+ return Entity_GetTeamIndex(player) == surv_attacker_team;
+}
+
+/// \brief Returns whether player is a defender.
+/// \param[in] player Player to check.
+/// \return True if player is a defender, false otherwise.
+bool Surv_IsPlayerDefender(entity player)
+{
+ return Entity_GetTeamIndex(player) == surv_defender_team;
+}
+
+/// \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 (Entity_GetTeamIndex(player))
+ {
+ case surv_attacker_team:
+ {
+ 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_defender_team:
+ {
+ 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.
+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 (Surv_IsPlayerAttacker(it) && (it.surv_role == SURVIVAL_ROLE_PLAYER))
+ {
+ float temp_score = PlayerScore_Get(it, SP_SCORE);
+ if (temp_score < score)
+ {
+ player = it;
+ score = temp_score;
+ }
+ }
+ });
+ 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 (Surv_IsPlayerDefender(it) && (alive ? !IS_DEAD(it) : true))
+ {
+ float temp_score = PlayerScore_Get(it, SP_SCORE);
+ if (temp_score < score)
+ {
+ player = it;
+ score = temp_score;
+ }
+ }
+ });
+ 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] team_index Team index to adjust.
+/// \param[in] delta Amount to adjust by.
+void Surv_ChangeNumberOfPlayers(int team_index, int delta)
+{
+ switch (team_index)
+ {
+ case surv_attacker_team:
+ {
+ surv_num_attackers += delta;
+ LOG_TRACEF("Number of attackers = %f was = %f", surv_num_attackers,
+ surv_num_attackers - delta);
+ Surv_UpdateTeamStats();
+ return;
+ }
+ case surv_defender_team:
+ {
+ surv_num_defenders += delta;
+ LOG_TRACEF("Number of defenders = %f was = %f", surv_num_defenders,
+ surv_num_defenders - delta);
+ Surv_UpdateTeamStats();
+ return;
+ }
+ }
+}
+
+/// \brief Changes the number of alive players in a team.
+/// \param[in] team_index Team index to adjust.
+/// \param[in] delta Amount to adjust by.
+void Surv_ChangeNumberOfAlivePlayers(int team_index, int delta)
+{
+ switch (team_index)
+ {
+ case surv_attacker_team:
+ {
+ surv_num_attackers_alive += delta;
+ LOG_TRACEF("Number of alive attackers = %f was = %f",
+ surv_num_attackers_alive, surv_num_attackers_alive - delta);
+ break;
+ }
+ case surv_defender_team:
+ {
+ surv_num_defenders_alive += delta;
+ LOG_TRACEF("Number of alive defenders = %f was = %f",
+ surv_num_defenders_alive, surv_num_defenders_alive - 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.
+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] team_index Team index to add to.
+/// \return True on success, false otherwise.
+bool Surv_AddPlayerToTeam(entity player, int team_index)
+{
+ LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
+ switch (team_index)
+ {
+ case surv_attacker_team:
+ {
+ 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_TRACEF("Attackers = %f", surv_num_attackers);
+ if (surv_num_attackers < autocvar_g_surv_team_size)
+ {
+ Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+ Surv_ChangeNumberOfPlayers(team_index, +1);
+ return true;
+ }
+ Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
+ return true;
+ }
+ LOG_TRACE("Client is not a bot");
+ LOG_TRACEF("Attackers = %f", surv_num_attackers);
+ if (surv_num_attackers >= 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(team_index, -1);
+ }
+ Surv_ChangeNumberOfPlayers(team_index, -1);
+ LOG_TRACE("Removed bot");
+ }
+ Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+ Surv_ChangeNumberOfPlayers(team_index, +1);
+ ++surv_num_attacker_humans;
+ LOG_TRACEF("Human attackers = %f", surv_num_attacker_humans);
+ if ((surv_autobalance == false) || (surv_num_attackers -
+ surv_num_defenders) < 2)
+ {
+ return true;
+ }
+ entity lowest_player = Surv_FindLowestAttacker(true);
+ if (lowest_player != NULL)
+ {
+ bool saved_autobalance = surv_autobalance;
+ surv_autobalance = false;
+ Player_SetTeamIndex(lowest_player, surv_defender_team);
+ surv_autobalance = saved_autobalance;
+ return true;
+ }
+ lowest_player = Surv_FindLowestAttacker(false);
+ if (lowest_player != NULL)
+ {
+ bool saved_autobalance = surv_autobalance;
+ surv_autobalance = false;
+ Player_SetTeamIndex(lowest_player, surv_defender_team);
+ surv_autobalance = saved_autobalance;
+ }
+ return true;
+ }
+ case surv_defender_team:
+ {
+ LOG_TRACE("Defender team");
+ if (IS_BOT_CLIENT(player))
+ {
+ LOG_TRACE("Client is bot");
+ LOG_TRACEF("Defenders = %f", surv_num_defenders);
+ if (surv_num_defenders < autocvar_g_surv_team_size)
+ {
+ Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+ Surv_ChangeNumberOfPlayers(team_index, +1);
+ return true;
+ }
+ LOG_TRACE("No space for defender, switching to attacker");
+ Player_SetTeamIndex(player, surv_attacker_team);
+ return false;
+ }
+ LOG_TRACE("Client is not a bot");
+ LOG_TRACEF("Defenders = %f", surv_num_defenders);
+ if (surv_num_defenders >= 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 saved_autobalance = surv_autobalance;
+ surv_autobalance = false;
+ surv_announcefrags = false;
+ Player_SetTeamIndex(bot, surv_attacker_team);
+ surv_autobalance = saved_autobalance;
+ surv_announcefrags = true;
+ LOG_TRACE("Removed bot");
+ }
+ Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
+ Surv_ChangeNumberOfPlayers(team_index, +1);
+ ++surv_num_defender_humans;
+ LOG_TRACEF("Human defenders = %f", surv_num_defender_humans);
+ if ((surv_autobalance == false) || (surv_num_defenders -
+ surv_num_attackers) < 2)
+ {
+ return true;
+ }
+ entity lowest_player = Surv_FindLowestDefender(true, false);
+ if (lowest_player != NULL)
+ {
+ bool saved_autobalance = surv_autobalance;
+ surv_autobalance = false;
+ Player_SetTeamIndex(lowest_player, surv_attacker_team);
+ surv_autobalance = saved_autobalance;
+ return true;
+ }
+ lowest_player = Surv_FindLowestDefender(false, false);
+ if (lowest_player != NULL)
+ {
+ bool saved_autobalance = surv_autobalance;
+ surv_autobalance = false;
+ Player_SetTeamIndex(lowest_player, surv_attacker_team);
+ surv_autobalance = saved_autobalance;
+ }
+ 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_index Team index to remove from.
+void Surv_RemovePlayerFromTeam(entity player, int team_index)
+{
+ LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
+ switch (team_index)
+ {
+ case surv_attacker_team:
+ {
+ 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(team_index, -1);
+ if (!IS_BOT_CLIENT(player))
+ {
+ --surv_num_attacker_humans;
+ }
+ if ((surv_autobalance == false) || (surv_num_attackers >=
+ surv_num_defenders))
+ {
+ return;
+ }
+ // Add bot to keep teams balanced.
+ entity lowest_player = Surv_FindCannonFodder();
+ if (lowest_player != NULL)
+ {
+ LOG_TRACE("Changing ", lowest_player.netname,
+ " from cannon fodder to attacker.");
+ Surv_SetPlayerRole(lowest_player, SURVIVAL_ROLE_PLAYER);
+ Surv_ChangeNumberOfPlayers(team_index, +1);
+ if (!IS_DEAD(lowest_player))
+ {
+ Surv_ChangeNumberOfAlivePlayers(team_index, +1);
+ }
+ return;
+ }
+ lowest_player = Surv_FindLowestDefender(true, false);
+ if (lowest_player != NULL)
+ {
+ bool saved_autobalance = surv_autobalance;
+ surv_autobalance = false;
+ Player_SetTeamIndex(lowest_player, surv_attacker_team);
+ surv_autobalance = saved_autobalance;
+ return;
+ }
+ lowest_player = Surv_FindLowestDefender(false, false);
+ if (lowest_player == NULL)
+ {
+ return;
+ }
+ bool saved_autobalance = surv_autobalance;
+ surv_autobalance = false;
+ Player_SetTeamIndex(lowest_player, surv_attacker_team);
+ surv_autobalance = saved_autobalance;
+ return;
+ }
+ case surv_defender_team:
+ {
+ 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(team_index, -1);
+ if (!IS_BOT_CLIENT(player))
+ {
+ --surv_num_defender_humans;
+ }
+ if ((surv_autobalance == false) || (surv_num_defenders >=
+ surv_num_attackers))
+ {
+ return;
+ }
+ // Add bot to keep teams balanced.
+ entity lowest_player = Surv_FindCannonFodder();
+ if (lowest_player != NULL)
+ {
+ LOG_TRACE("Changing ", lowest_player.netname,
+ " from cannon fodder to defender.");
+ if (!IS_DEAD(player))
+ {
+ lowest_player.surv_savedplayerstate =
+ Surv_SavePlayerState(player);
+ }
+ bool saved_autobalance = surv_autobalance;
+ surv_autobalance = false;
+ surv_announcefrags = false;
+ Player_SetTeamIndex(lowest_player, surv_defender_team);
+ surv_autobalance = saved_autobalance;
+ surv_announcefrags = true;
+ return;
+ }
+ lowest_player = Surv_FindLowestAttacker(true);
+ if (lowest_player != NULL)
+ {
+ bool saved_autobalance = surv_autobalance;
+ surv_autobalance = false;
+ surv_announcefrags = false;
+ Player_SetTeamIndex(lowest_player, surv_defender_team);
+ surv_autobalance = saved_autobalance;
+ surv_announcefrags = true;
+ return;
+ }
+ lowest_player = Surv_FindLowestAttacker(false);
+ if (lowest_player == NULL)
+ {
+ return;
+ }
+ bool saved_autobalance = surv_autobalance;
+ surv_autobalance = false;
+ surv_announcefrags = false;
+ Player_SetTeamIndex(lowest_player, surv_defender_team);
+ surv_autobalance = saved_autobalance;
+ 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.
+void Surv_UpdateTeamStats()
+{
+ // Debug stuff
+ int yellow_alive;
+ int pink_alive;
+ if (surv_attacker_team == 1)
+ {
+ yellow_alive = surv_num_attackers;
+ pink_alive = surv_num_defenders;
+ }
+ else
+ {
+ pink_alive = surv_num_attackers;
+ yellow_alive = surv_num_defenders;
+ }
+ Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(3), yellow_alive);
+ Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(4), pink_alive);
+ FOREACH_CLIENT(IS_REAL_CLIENT(it),
+ {
+ STAT(YELLOWALIVE, it) = yellow_alive;
+ STAT(PINKALIVE, it) = pink_alive;
+ });
+}
+
+/// \brief Adds player to alive list. Handles bookkeeping information.
+/// \param[in] player Player to add.
+/// \param[in] team_index Team index of the player.
+void Surv_AddPlayerToAliveList(entity player, int team_index)
+{
+ switch (team_index)
+ {
+ case surv_attacker_team:
+ {
+ if (player.surv_role == SURVIVAL_ROLE_PLAYER)
+ {
+ Surv_ChangeNumberOfAlivePlayers(team_index, +1);
+ }
+ return;
+ }
+ case surv_defender_team:
+ {
+ Surv_ChangeNumberOfAlivePlayers(team_index, +1);
+ return;
+ }
+ }
+}
+
+/// \brief Removes player from alive list. Handles bookkeeping information.
+/// \param[in] player Player to remove.
+/// \param[in] team_index Team index of the player.
+void Surv_RemovePlayerFromAliveList(entity player, int team_index)
+{
+ if (player.surv_attack_sprite)
+ {
+ WaypointSprite_Kill(player.surv_attack_sprite);
+ }
+ if (player.surv_defend_sprite)
+ {
+ WaypointSprite_Kill(player.surv_defend_sprite);
+ }
+ switch (team_index)
+ {
+ case surv_attacker_team:
+ {
+ if (player.surv_role == SURVIVAL_ROLE_PLAYER)
+ {
+ Surv_ChangeNumberOfAlivePlayers(team_index, -1);
+ }
+ return;
+ }
+ case surv_defender_team:
+ {
+ if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
+ {
+ // This happens during team switch. We don't need to change
+ // anything.
+ return;
+ }
+ Surv_ChangeNumberOfAlivePlayers(team_index, -1);
+ if (warmup_stage || surv_allowed_to_spawn || !surv_announcefrags)
+ {
+ return;
+ }
+ switch (surv_num_defenders_alive)
+ {
+ case 1:
+ {
+ sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
+ VOL_BASE, ATTEN_NONE);
+ FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+ {
+ if (Surv_IsPlayerDefender(it))
+ {
+ 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.
+/// \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 saved_num_defenders = surv_num_defenders_alive;
+ surv_num_attackers_alive = 0;
+ surv_num_defenders_alive = 0;
+ FOREACH_CLIENT(IS_PLAYER(it),
+ {
+ switch (Entity_GetTeamIndex(it))
+ {
+ case surv_attacker_team:
+ {
+ if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
+ {
+ ++surv_num_attackers_alive;
+ }
+ break;
+ }
+ case surv_defender_team:
+ {
+ if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
+ {
+ ++surv_num_defenders_alive;
+ }
+ break;
+ }
+ }
+ });
+ Surv_UpdateAliveStats();
+ eliminatedPlayers.SendFlags |= 1;
+ if (warmup_stage || surv_allowed_to_spawn || (saved_num_defenders <=
+ surv_num_defenders_alive))
+ {
+ return;
+ }
+ switch (surv_num_defenders_alive)
+ {
+ case 1:
+ {
+ sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
+ FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+ {
+ if (Surv_IsPlayerDefender(it))
+ {
+ 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.
+void Surv_UpdateAliveStats()
+{
+ // Debug stuff
+ int red_alive;
+ int blue_alive;
+ if (surv_attacker_team == 1)
+ {
+ red_alive = surv_num_attackers_alive;
+ blue_alive = surv_num_defenders_alive;
+ }
+ else
+ {
+ blue_alive = surv_num_attackers_alive;
+ red_alive = surv_num_defenders_alive;
+ }
+ Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(1), red_alive);
+ Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(2), blue_alive);
+ FOREACH_CLIENT(IS_REAL_CLIENT(it),
+ {
+ STAT(SURV_DEFENDERS_ALIVE, it) = surv_num_defenders_alive;
+ STAT(REDALIVE, it) = red_alive;
+ STAT(BLUEALIVE, it) = blue_alive;
+ });
+ Surv_UpdateDefenderHealthStat();
+}
+
+/// \brief Spawns a monster.
+void Surv_SpawnMonster()
+{
+ entity spawn_point = SelectSpawnPoint(NULL, true);
+ RandomSelection_Init();
+ FOREACH(Monsters, it != MON_Null,
+ {
+ if ((it.spawnflags & MON_FLAG_HIDDEN) ||
+ (it.spawnflags & MONSTER_TYPE_PASSIVE) ||
+ (it.spawnflags & MONSTER_TYPE_FLY) ||
+ (it.spawnflags & MONSTER_TYPE_SWIM) ||
+ (it.spawnflags & MONSTER_SIZE_QUAKE))
+ {
+ continue;
+ }
+ RandomSelection_AddEnt(it, 1, 1);
+ });
+ entity monster = spawnmonster(spawn(), RandomSelection_chosen_ent.netname,
+ RandomSelection_chosen_ent.monsterid, NULL, NULL, spawn_point.origin,
+ false, false, 2);
+ monster.team = surv_attacker_team;
+}
+
+/// \brief Updates defender health on the HUD.
+void Surv_UpdateDefenderHealthStat()
+{
+ float max_health;
+ float total_health = 0;
+ if (autocvar_g_instagib == 1)
+ {
+ max_health = surv_num_defenders * (PlayerTemplate_GetFloatValue(
+ "surv_defender", "start_armor") + 1);
+ FOREACH_CLIENT(IS_PLAYER(it),
+ {
+ if (Surv_IsPlayerDefender(it))
+ {
+ total_health += GetResourceAmount(it, RESOURCE_ARMOR) + 1;
+ }
+ });
+ }
+ else
+ {
+ max_health = surv_num_defenders * (PlayerTemplate_GetFloatValue(
+ "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
+ "surv_defender", "start_armor"));
+ FOREACH_CLIENT(IS_PLAYER(it),
+ {
+ if (Surv_IsPlayerDefender(it))
+ {
+ total_health += GetResourceAmount(it, RESOURCE_HEALTH);
+ total_health += GetResourceAmount(it, RESOURCE_ARMOR);
+ }
+ });
+ }
+ float health_ratio;
+ if (max_health == 0)
+ {
+ health_ratio = 0;
+ }
+ else
+ {
+ health_ratio = total_health / max_health;
+ }
+ FOREACH_CLIENT(IS_REAL_CLIENT(it),
+ {
+ STAT(SURV_DEFENDER_HEALTH, it) = health_ratio;
+ });
+}
+
+/// \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 (Surv_IsPlayerAttacker(player) || (player.surv_savedplayerstate != NULL))
+ {
+ return true;
+ }
+ return surv_allowed_to_spawn;
+}
+
+/// \brief Switches the round type.
+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.
+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.
+void Surv_SwapTeams()
+{
+ int temp = surv_attacker_team;
+ surv_attacker_team = surv_defender_team;
+ surv_defender_team = temp;
+ temp = surv_attacker_team_bit;
+ surv_attacker_team_bit = surv_defender_team_bit;
+ surv_defender_team_bit = temp;
+ temp = surv_num_attackers;
+ surv_num_attackers = surv_num_defenders;
+ surv_num_defenders = temp;
+ temp = surv_num_attacker_humans;
+ surv_num_attacker_humans = surv_num_defender_humans;
+ surv_num_defender_humans = temp;
+ FOREACH_CLIENT(true,
+ {
+ if (Surv_IsPlayerDefender(it) && (it.surv_role ==
+ SURVIVAL_ROLE_CANNON_FODDER))
+ {
+ Player_SetTeamIndex(it, surv_attacker_team);
+ }
+ });
+ FOREACH_CLIENT(IS_REAL_CLIENT(it),
+ {
+ STAT(SURV_DEFENDER_TEAM, it) = surv_defender_team;
+ });
+}
+
+/// \brief Forces the overkill model for specific player.
+/// \param[in,out] player Player to force the model of.
+void Surv_ForceOverkillPlayerModel(entity player)
+{
+ switch (Entity_GetTeamIndex(player))
+ {
+ case 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 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.
+void Surv_DeterminePlayerModel(entity player)
+{
+ switch (Entity_GetTeamIndex(player))
+ {
+ case surv_attacker_team:
+ {
+ 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_defender_team:
+ {
+ 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.
+void Surv_SetupWaypointSprite(entity player)
+{
+ WaypointSprite_Spawn(WP_SurvivalKill, 0, 0, player, '0 0 64', NULL,
+ Team_IndexToTeam(surv_attacker_team), player, surv_attack_sprite, false,
+ RADARICON_OBJECTIVE);
+ WaypointSprite_Spawn(WP_SurvivalDefend, 0, 0, player, '0 0 64', NULL,
+ Team_IndexToTeam(surv_defender_team), 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_num_attackers_alive > 0) && (surv_num_defenders_alive > 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_defender_team)
+ {
+ case 1:
+ {
+ sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
+ ATTEN_NONE);
+ break;
+ }
+ case 2:
+ {
+ sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
+ ATTEN_NONE);
+ break;
+ }
+ }
+ TeamScore_AddToTeam(Team_IndexToTeam(surv_defender_team), 1, 1);
+ Surv_RoundCleanup();
+ return true;
+ }
+ if (surv_num_defenders_alive > 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_attacker_team)
+ {
+ case 1:
+ {
+ sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
+ ATTEN_NONE);
+ break;
+ }
+ case 2:
+ {
+ sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
+ ATTEN_NONE);
+ break;
+ }
+ }
+ TeamScore_AddToTeam(Team_IndexToTeam(surv_attacker_team), 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 (Surv_IsPlayerAttacker(it))
+ {
+ 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 (Surv_IsPlayerDefender(it))
+ {
+ 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 (Surv_IsPlayerAttacker(it))
+ {
+ 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 (Surv_IsPlayerDefender(it))
+ {
+ 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;
+ }
+ }
+ for (int i = 0; i < 8; ++i)
+ {
+ Surv_SpawnMonster();
+ }
+ if (autocvar_g_surv_stealth)
+ {
+ return;
+ }
+ FOREACH_CLIENT(IS_PLAYER(it),
+ {
+ switch (Entity_GetTeamIndex(it))
+ {
+ case surv_defender_team:
+ {
+ 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 (Surv_IsPlayerDefender(player) && (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, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ entity player = M_ARGV(2, entity);
+ LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
+ if (player == NULL)
+ {
+ M_ARGV(0, float) = SURVIVAL_TEAM_BITS;
+ return true;
+ }
+ if (IS_BOT_CLIENT(player))
+ {
+ int team_bits = surv_attacker_team_bit;
+ if (Surv_IsPlayerDefender(player) || (surv_num_defenders <
+ autocvar_g_surv_team_size))
+ {
+ team_bits |= surv_defender_team_bit;
+ }
+ M_ARGV(0, float) = team_bits;
+ return true;
+ }
+ if (surv_type == SURVIVAL_TYPE_COOP)
+ {
+ if (surv_num_defender_humans < autocvar_g_surv_team_size)
+ {
+ M_ARGV(0, float) = surv_defender_team_bit;
+ return;
+ }
+ M_ARGV(0, float) = 0;
+ return true;
+ }
+ int team_bits = 0;
+ if (surv_num_attacker_humans < autocvar_g_surv_team_size)
+ {
+ LOG_TRACE("Player can join attackers");
+ team_bits |= surv_attacker_team_bit;
+ }
+ if (surv_num_defender_humans < autocvar_g_surv_team_size)
+ {
+ LOG_TRACE("Player can join defenders");
+ team_bits |= surv_defender_team_bit;
+ }
+ M_ARGV(0, float) = team_bits;
+ return true;
+}
+
+/// \brief Hook that override team counts.
+MUTATOR_HOOKFUNCTION(surv, TeamBalance_GetTeamCounts, CBC_ORDER_EXCLUSIVE)
+{
+ return true;
+}
+
+/// \brief Hook that sets the team count.
+MUTATOR_HOOKFUNCTION(surv, TeamBalance_GetTeamCount, CBC_ORDER_EXCLUSIVE)
+{
+ int team_index = M_ARGV(0, float);
+ entity ignore = M_ARGV(1, entity);
+ switch (team_index)
+ {
+ case surv_attacker_team:
+ {
+ M_ARGV(2, float) = surv_num_attackers;
+ M_ARGV(3, float) = surv_num_attackers - surv_num_attacker_humans;
+ if (Surv_IsPlayerAttacker(ignore))
+ {
+ --M_ARGV(2, float);
+ if (IS_BOT_CLIENT(ignore))
+ {
+ --M_ARGV(3, float);
+ }
+ }
+ entity lowest_player = NULL;
+ float lowest_player_score = FLOAT_MAX;
+ entity lowest_bot = NULL;
+ float lowest_bot_score = FLOAT_MAX;
+ FOREACH_CLIENT(Surv_IsPlayerAttacker(it) && (it.surv_role ==
+ SURVIVAL_ROLE_PLAYER),
+ {
+ if (it == ignore)
+ {
+ continue;
+ }
+ if (IS_BOT_CLIENT(it))
+ {
+ float temp_score = PlayerScore_Get(it, SP_SCORE);
+ if (temp_score < lowest_bot_score)
+ {
+ lowest_bot = it;
+ lowest_bot_score = temp_score;
+ continue;
+ }
+ }
+ float temp_score = PlayerScore_Get(it, SP_SCORE);
+ if (temp_score < lowest_player_score)
+ {
+ lowest_player = it;
+ lowest_player_score = temp_score;
+ }
+ });
+ M_ARGV(4, entity) = lowest_player;
+ M_ARGV(5, entity) = lowest_bot;
+ break;
+ }
+ case surv_defender_team:
+ {
+ M_ARGV(2, float) = surv_num_defenders;
+ M_ARGV(3, float) = surv_num_defenders - surv_num_defender_humans;
+ if (Surv_IsPlayerDefender(ignore))
+ {
+ --M_ARGV(2, float);
+ if (IS_BOT_CLIENT(ignore))
+ {
+ --M_ARGV(3, float);
+ }
+ }
+ entity lowest_player = NULL;
+ float lowest_player_score = FLOAT_MAX;
+ entity lowest_bot = NULL;
+ float lowest_bot_score = FLOAT_MAX;
+ FOREACH_CLIENT(Surv_IsPlayerDefender(it),
+ {
+ if (it == ignore)
+ {
+ continue;
+ }
+ if (IS_BOT_CLIENT(it))
+ {
+ float temp_score = PlayerScore_Get(it, SP_SCORE);
+ if (temp_score < lowest_bot_score)
+ {
+ lowest_bot = it;
+ lowest_bot_score = temp_score;
+ continue;
+ }
+ }
+ float temp_score = PlayerScore_Get(it, SP_SCORE);
+ if (temp_score < lowest_player_score)
+ {
+ lowest_player = it;
+ lowest_player_score = temp_score;
+ }
+ });
+ M_ARGV(4, entity) = lowest_player;
+ M_ARGV(5, entity) = lowest_bot;
+ break;
+ }
+ }
+ return true;
+}
+
+/// \brief Hook that determines the best teams for the player to join.
+MUTATOR_HOOKFUNCTION(surv, TeamBalance_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 num_attacker_humans = surv_num_attacker_humans;
+ int num_defender_humans = surv_num_defender_humans;
+ if (Surv_IsPlayerAttacker(player))
+ {
+ --num_attacker_humans;
+ }
+ else if (Surv_IsPlayerDefender(player))
+ {
+ --num_defender_humans;
+ }
+ if (num_attacker_humans < num_defender_humans)
+ {
+ M_ARGV(1, float) = surv_attacker_team_bit;
+ return true;
+ }
+ if (num_attacker_humans > num_defender_humans)
+ {
+ M_ARGV(1, float) = surv_defender_team_bit;
+ 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 old_team_index = M_ARGV(1, float);
+ int new_team_index = M_ARGV(2, float);
+ string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
+ ", old team = ", ftos(old_team_index), " new team = ", ftos(
+ new_team_index));
+ LOG_TRACE(message);
+ DebugPrintToChatAll(message);
+ if ((old_team_index != -1) && IS_PLAYER(player) && !IS_DEAD(player))
+ {
+ Surv_RemovePlayerFromAliveList(player, old_team_index);
+ }
+ Surv_RemovePlayerFromTeam(player, old_team_index);
+ if (Surv_AddPlayerToTeam(player, new_team_index) == false)
+ {
+ return;
+ }
+ //Surv_CountAlivePlayers();
+ if ((old_team_index != -1) && IS_PLAYER(player) && !IS_DEAD(player))
+ {
+ Surv_AddPlayerToAliveList(player, new_team_index);
+ }
+}
+
+/// \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 (!Surv_IsPlayerDefender(player))
+ {
+ 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 (Surv_IsPlayerDefender(player))
+ {
+ // 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) = surv_defender_team;
+ STAT(SURV_DEFENDERS_ALIVE, player) = surv_num_defenders_alive;
+ STAT(REDALIVE, player) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(1));
+ STAT(BLUEALIVE, player) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(2));
+ STAT(YELLOWALIVE, player) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(3));
+ STAT(PINKALIVE, player) = Team_GetNumberOfAlivePlayers(
+ Team_GetTeamFromIndex(4));
+ }
+ if (player.surv_role == SURVIVAL_ROLE_NONE)
+ {
+ Surv_AddPlayerToTeam(player, Entity_GetTeamIndex(player));
+ }
+ return true;
+}
+
+/// \brief Hook that is called when player disconnects from the server.
+MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+ int team_index = Entity_GetTeamIndex(player);
+ if (!IS_DEAD(player))
+ {
+ Surv_RemovePlayerFromAliveList(player, team_index);
+ }
+ Surv_RemovePlayerFromTeam(player, team_index);
+ //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_num_attackers_alive = 0;
+ surv_num_defenders_alive = 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 (Entity_GetTeamIndex(player))
+ {
+ case surv_attacker_team:
+ {
+ if (player.surv_role == SURVIVAL_ROLE_PLAYER)
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER,
+ CENTER_ASSAULT_ATTACKING);
+ }
+ break;
+ }
+ case surv_defender_team:
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER,
+ CENTER_ASSAULT_DEFENDING);
+ break;
+ }
+ }
+ //Surv_CountAlivePlayers();
+ Surv_AddPlayerToAliveList(player, Entity_GetTeamIndex(player));
+}
+
+/// \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 (Surv_IsPlayerDefender(player))
+ {
+ 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 (Entity_GetTeamIndex(player))
+ {
+ case surv_attacker_team:
+ {
+ return PlayerTemplateHook_ItemTouch(player, item,
+ Surv_GetPlayerTemplate(player));
+ }
+ case surv_defender_team:
+ {
+ 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 (!Surv_IsPlayerDefender(target))
+ {
+ return;
+ }
+ Surv_UpdateDefenderHealthStat();
+ entity attacker = M_ARGV(0, entity);
+ if (Surv_IsPlayerAttacker(attacker) && (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 (Surv_IsPlayerDefender(attacker) && (Entity_GetTeamIndex(victim) ==
+ surv_attacker_team))
+ {
+ 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, Entity_GetTeamIndex(player));
+ //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 (Surv_IsPlayerDefender(attacker) || 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) && Surv_IsPlayerDefender(
+ target))
+ {
+ 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;
+}
--- /dev/null
+/// \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);
+}