1 #include "gamemode_survival.qh"
3 #include <common/mutators/mutator/playertemplates/sv_playertemplates.qh>
4 #include <common/mutators/mutator/overkill/hmg.qh>
5 #include <common/mutators/mutator/overkill/rpc.qh>
7 //============================ Constants ======================================
9 const int SURVIVAL_TEAM_1_BIT = BIT(0); ///< Bitmask of the first team.
10 const int SURVIVAL_TEAM_2_BIT = BIT(1); ///< Bitmask of the second team.
12 /// \brief Used when bitfield team count is requested.
13 const int SURVIVAL_TEAM_BITS = 3;
17 SURVIVAL_TYPE_COOP, ///< All humans are on the defender team.
18 SURVIVAL_TYPE_VERSUS ///< Humans take turns between attackers and defenders.
21 const string SURVIVAL_TYPE_COOP_VALUE = "coop"; ///< Cvar value for coop.
22 const string SURVIVAL_TYPE_VERSUS_VALUE = "versus"; ///< Cvar value for versus.
26 /// \brief First round where there is timelimit set by the server.
28 /// \brief Second round where defender team tries to survive for the first
35 /// \brief Player is spectating and has no intention of playing.
36 SURVIVAL_STATE_NOT_PLAYING,
37 /// \brief Player is playing the game.
38 SURVIVAL_STATE_PLAYING = 1
43 SURVIVAL_ROLE_NONE, ///< Player is not playing.
44 SURVIVAL_ROLE_PLAYER, ///< Player is an attacker or defender.
45 SURVIVAL_ROLE_CANNON_FODDER ///< Player is a cannon fodder.
48 SOUND(SURV_3_FRAGS_LEFT, "announcer/default/3fragsleft");
49 SOUND(SURV_2_FRAGS_LEFT, "announcer/default/2fragsleft");
50 SOUND(SURV_1_FRAG_LEFT, "announcer/default/1fragleft");
52 SOUND(SURV_RED_SCORES, "ctf/red_capture");
53 SOUND(SURV_BLUE_SCORES, "ctf/blue_capture");
55 //======================= Global variables ====================================
57 float autocvar_g_surv_warmup; ///< Warmup time in seconds.
58 float autocvar_g_surv_round_timelimit; ///< First round time limit in seconds.
60 int autocvar_g_surv_point_limit; ///< Maximum number of points.
61 int autocvar_g_surv_point_leadlimit; ///< Maximum lead of a single team.
63 /// \brief How much players are allowed in teams (excluding cannon fodder).
64 int autocvar_g_surv_team_size;
65 /// \brief If set, defenders will not be shown on the radar.
66 int autocvar_g_surv_stealth;
68 /// \brief Whether to force overkill player models for attackers.
69 int autocvar_g_surv_attacker_force_overkill_models;
70 /// \brief Whether to force overkill player models for defenders.
71 int autocvar_g_surv_defender_force_overkill_models;
72 /// \brief Whether to force overkill player models for cannon fodder.
73 int autocvar_g_surv_cannon_fodder_force_overkill_models;
75 /// \brief How much score attackers gain per 1 point of damage.
76 float autocvar_g_surv_attacker_damage_score;
78 /// \brief How much score attackers get for fragging defenders.
79 float autocvar_g_surv_attacker_frag_score;
81 /// \brief How much health do defenders get when they frag an attacker.
82 int autocvar_g_surv_defender_attacker_frag_health;
83 /// \brief How much armor do defenders get when they frag an attacker.
84 int autocvar_g_surv_defender_attacker_frag_armor;
85 /// \brief How many shells do defenders get when they frag an attacker.
86 int autocvar_g_surv_defender_attacker_frag_shells;
87 /// \brief How many bullets do defenders get when they frag an attacker.
88 int autocvar_g_surv_defender_attacker_frag_bullets;
89 /// \brief How many rockets do defenders get when they frag an attacker.
90 int autocvar_g_surv_defender_attacker_frag_rockets;
91 /// \brief How many cells do defenders get when they frag an attacker.
92 int autocvar_g_surv_defender_attacker_frag_cells;
93 /// \brief How much health do defenders get when they frag cannon fodder.
94 int autocvar_g_surv_defender_cannon_fodder_frag_health;
95 /// \brief How much armor do defenders get when they frag cannon fodder.
96 int autocvar_g_surv_defender_cannon_fodder_frag_armor;
97 /// \brief How many shells do defenders get when they frag cannon fodder.
98 int autocvar_g_surv_defender_cannon_fodder_frag_shells;
99 /// \brief How many bullets do defenders get when they frag cannon fodder.
100 int autocvar_g_surv_defender_cannon_fodder_frag_bullets;
101 /// \brief How many rockets do defenders get when they frag cannon fodder.
102 int autocvar_g_surv_defender_cannon_fodder_frag_rockets;
103 /// \brief How many cells do defenders get when they frag cannon fodder.
104 int autocvar_g_surv_defender_cannon_fodder_frag_cells;
106 /// \brief Whether defenders drop weapons after death.
107 int autocvar_g_surv_defender_drop_weapons;
109 /// \brief A stat that is used to track the time left in the round.
110 .float surv_round_time_stat = _STAT(SURV_ROUND_TIME);
111 /// \brief A stat that is used to track defender team.
112 .int surv_defender_team_stat = _STAT(SURV_DEFENDER_TEAM);
113 /// \brief A stat that is used to track number of defenders alive.
114 .int surv_defenders_alive_stat = _STAT(SURV_DEFENDERS_ALIVE);
115 /// \brief A stat that is used to track the total health of defenders.
116 .float surv_defender_health_stat = _STAT(SURV_DEFENDER_HEALTH);
118 /// \brief Holds the state of the player. See SURVIVAL_STATE constants.
120 /// \brief Holds the role of the player. See SURVIVAL_ROLE constants.
122 .string surv_savedplayermodel; ///< Initial player model.
123 /// \brief Player state used during replacement of bot player with real player.
124 .entity surv_savedplayerstate;
125 .string surv_playermodel; ///< Player model forced by the game.
127 .entity surv_attack_sprite; ///< Holds the sprite telling attackers to attack.
129 int surv_type; ///< Holds the type of survival. See SURVIVAL_TYPE constants.
130 bool surv_warmup; ///< Holds whether warmup is active.
131 /// \brief Holds the type of the current round. See SURVIVAL_ROUND constants.
133 bool surv_isroundactive; ///< Holds whether the round is active.
134 float surv_roundstarttime; ///< Holds the time of the round start.
135 /// \brief Holds the time needed to survive in the second round.
136 float surv_timetobeat;
138 int surv_attackerteam; ///< Holds the attacker team.
139 int surv_defenderteam; ///< Holds the defender team.
141 int surv_attackerteambit; ///< Hols the attacker team bitmask.
142 int surv_defenderteambit; ///< Holds the defender team bitmask.
144 int surv_numattackers; ///< Holds the number of players in attacker team.
145 int surv_numdefenders; ///< Holds the number of players in defender team.
147 /// \brief Holds the number of humans in attacker team.
148 int surv_numattackerhumans;
149 /// \brief Holds the number of humans in defender team.
150 int surv_numdefenderhumans;
152 /// \brief Holds the number of attacker players that are alive.
153 int surv_numattackersalive;
154 /// \brief Holds the number of defender players that are alive.
155 int surv_numdefendersalive;
157 bool surv_autobalance; ///< Holds whether autobalance is active.
158 bool surv_allowed_to_spawn; ///< Holds whether players are allowed to spawn.
160 //====================== Forward declarations =================================
162 /// \brief Determines whether the round can start.
163 /// \return True if the round can start, false otherwise.
164 bool Surv_CanRoundStart();
166 /// \brief Determines whether the round can end.
167 /// \return True if the round can end, false otherwise.
168 bool Surv_CanRoundEnd();
170 /// \brief Called when the round starts.
171 /// \return No return.
172 void Surv_RoundStart();
174 /// \brief Returns whether player has been eliminated.
175 /// \param[in] player Player to check.
176 /// \return True if player is eliminated, false otherwise.
177 bool Surv_IsEliminated(entity player);
179 /// \brief Updates stats of team count on HUD.
180 /// \return No return.
181 void Surv_UpdateTeamStats();
183 /// \brief Updates stats of alive players on HUD.
184 /// \return No return.
185 void Surv_UpdateAliveStats();
187 /// \brief Updates defender health on the HUD.
188 /// \return No return.
189 void Surv_UpdateDefenderHealthStat();
191 //========================= Free functions ====================================
193 void Surv_Initialize()
195 switch (cvar_string("g_surv_type"))
197 case SURVIVAL_TYPE_COOP_VALUE:
199 surv_type = SURVIVAL_TYPE_COOP;
202 case SURVIVAL_TYPE_VERSUS_VALUE:
204 surv_type = SURVIVAL_TYPE_VERSUS;
209 error("Invalid survival type.");
212 surv_roundtype = SURVIVAL_ROUND_FIRST;
213 surv_isroundactive = false;
214 surv_roundstarttime = time;
215 surv_timetobeat = autocvar_g_surv_round_timelimit;
218 surv_attackerteam = NUM_TEAM_1;
219 surv_defenderteam = NUM_TEAM_2;
220 surv_attackerteambit = SURVIVAL_TEAM_1_BIT;
221 surv_defenderteambit = SURVIVAL_TEAM_2_BIT;
225 surv_attackerteam = NUM_TEAM_2;
226 surv_defenderteam = NUM_TEAM_1;
227 surv_attackerteambit = SURVIVAL_TEAM_2_BIT;
228 surv_defenderteambit = SURVIVAL_TEAM_1_BIT;
230 surv_numattackers = 0;
231 surv_numdefenders = 0;
232 surv_numattackerhumans = 0;
233 surv_numdefenderhumans = 0;
234 surv_numattackersalive = 0;
235 surv_numdefendersalive = 0;
236 surv_autobalance = true;
237 surv_allowed_to_spawn = true;
238 precache_all_playermodels("models/ok_player/*.dpm");
239 ScoreRules_basics(SURVIVAL_TEAM_BITS, SFL_SORT_PRIO_PRIMARY, 0, true);
240 ScoreInfo_SetLabel_TeamScore(1, "rounds", SFL_SORT_PRIO_PRIMARY);
241 ScoreRules_basics_end();
242 round_handler_Spawn(Surv_CanRoundStart, Surv_CanRoundEnd, Surv_RoundStart);
243 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
244 EliminatedPlayers_Init(Surv_IsEliminated);
246 SetLimits(autocvar_g_surv_point_limit, autocvar_g_surv_point_leadlimit,
247 autocvar_timelimit_override, -1);
250 string Surv_GetPlayerTemplate(entity player)
254 case surv_attackerteam:
256 switch (player.surv_role)
258 case SURVIVAL_ROLE_NONE:
259 case SURVIVAL_ROLE_PLAYER:
261 return "surv_attacker";
263 case SURVIVAL_ROLE_CANNON_FODDER:
265 return "surv_cannon_fodder";
269 case surv_defenderteam:
271 return "surv_defender";
277 /// \brief Saves the player state. Used to seamlessly swap bots with humans.
278 /// \param[in] player Player to save the state of.
279 /// \return Entity containing the player state.
280 entity Surv_SavePlayerState(entity player)
282 entity state = spawn();
283 state.origin = player.origin;
284 state.angles = player.angles;
285 state.health = player.health;
286 state.armorvalue = player.armorvalue;
287 state.ammo_shells = player.ammo_shells;
288 state.ammo_nails = player.ammo_nails;
289 state.ammo_rockets = player.ammo_rockets;
290 state.ammo_cells = player.ammo_cells;
291 state.weapons = player.weapons;
292 state.items = player.items;
293 state.superweapons_finished = player.superweapons_finished;
297 /// \brief Restores a saved player state.
298 /// \param[in] player Player to restore the state of.
299 /// \param[in] st State to restore.
300 /// \return No return.
301 void Surv_RestorePlayerState(entity player, entity st)
303 player.origin = st.origin;
304 player.angles = st.angles;
305 player.health = st.health;
306 player.armorvalue = st.armorvalue;
307 player.ammo_shells = st.ammo_shells;
308 player.ammo_nails = st.ammo_nails;
309 player.ammo_rockets = st.ammo_rockets;
310 player.ammo_cells = st.ammo_cells;
311 player.weapons = st.weapons;
312 player.items = st.items;
313 player.superweapons_finished = st.superweapons_finished;
316 /// \brief Changes the number of players in a team.
317 /// \param[in] teamnum Team to adjust.
318 /// \param[in] delta Amount to adjust by.
319 /// \return No return.
320 void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
324 case surv_attackerteam:
326 surv_numattackers += delta;
327 LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
328 " was = ", ftos(surv_numattackers - delta));
329 Surv_UpdateTeamStats();
332 case surv_defenderteam:
334 surv_numdefenders += delta;
335 LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
336 " was = ", ftos(surv_numdefenders - delta));
337 Surv_UpdateTeamStats();
343 /// \brief Changes the number of alive players in a team.
344 /// \param[in] teamnum Team to adjust.
345 /// \param[in] delta Amount to adjust by.
346 /// \return No return.
347 void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
351 case surv_attackerteam:
353 surv_numattackersalive += delta;
354 LOG_TRACE("Number of alive attackers = ", ftos(
355 surv_numattackersalive), " was = ", ftos(surv_numattackersalive
359 case surv_defenderteam:
361 surv_numdefendersalive += delta;
362 LOG_TRACE("Number of alive defenders = ", ftos(
363 surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
368 Surv_UpdateAliveStats();
369 eliminatedPlayers.SendFlags |= 1;
372 /// \brief Sets the player role.
373 /// \param[in,out] player Player to adjust.
374 /// \param[in] role Role to set.
375 /// \return No return.
376 void Surv_SetPlayerRole(entity player, int role)
378 if (player.surv_role == role)
382 player.surv_role = role;
385 case SURVIVAL_ROLE_NONE:
387 LOG_TRACE(player.netname, " now has no role.");
390 case SURVIVAL_ROLE_PLAYER:
392 LOG_TRACE(player.netname, " is now a player.");
395 case SURVIVAL_ROLE_CANNON_FODDER:
397 LOG_TRACE(player.netname, " is now a cannon fodder.");
403 /// \brief Adds player to team. Handles bookkeeping information.
404 /// \param[in] player Player to add.
405 /// \param[in] teamnum Team to add to.
406 /// \return True on success, false otherwise.
407 bool Surv_AddPlayerToTeam(entity player, int teamnum)
409 LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
412 case surv_attackerteam:
414 LOG_TRACE("Attacker team");
415 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
417 LOG_TRACE("Cannon fodder is switching team");
420 if (IS_BOT_CLIENT(player))
422 LOG_TRACE("Client is bot");
423 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
424 if (surv_numattackers < autocvar_g_surv_team_size)
426 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
427 Surv_ChangeNumberOfPlayers(teamnum, +1);
430 Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
433 LOG_TRACE("Client is not a bot");
434 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
435 if (surv_numattackers >= autocvar_g_surv_team_size)
437 LOG_TRACE("Removing bot");
438 // Remove bot to make space for human.
441 FOREACH_CLIENT(IS_BOT_CLIENT(it),
443 if ((it.team == surv_attackerteam) && (it.surv_role ==
444 SURVIVAL_ROLE_PLAYER))
446 float tempscore = PlayerScore_Get(it, SP_SCORE);
447 if (tempscore < score)
456 LOG_TRACE("No valid bot to remove");
457 // No space in team, denying team change.
458 TRANSMUTE(Spectator, player);
461 LOG_TRACE("Changing ", bot.netname,
462 " from attacker to cannon fodder.");
463 Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
466 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
468 Surv_ChangeNumberOfPlayers(teamnum, -1);
469 LOG_TRACE("Removed bot");
471 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
472 Surv_ChangeNumberOfPlayers(teamnum, +1);
473 ++surv_numattackerhumans;
474 LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
477 case surv_defenderteam:
479 LOG_TRACE("Defender team");
480 if (IS_BOT_CLIENT(player))
482 LOG_TRACE("Client is bot");
483 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
484 if (surv_numdefenders < autocvar_g_surv_team_size)
486 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
487 Surv_ChangeNumberOfPlayers(teamnum, +1);
490 LOG_TRACE("No space for defender, switching to attacker");
491 SetPlayerTeamSimple(player, surv_attackerteam);
494 LOG_TRACE("Client is not a bot");
495 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
496 if (surv_numdefenders >= autocvar_g_surv_team_size)
498 LOG_TRACE("Removing bot");
499 // Remove bot to make space for human.
502 FOREACH_CLIENT(IS_BOT_CLIENT(it),
504 if (!IS_DEAD(it) && (it.team == surv_defenderteam))
506 float tempscore = PlayerScore_Get(it, SP_SCORE);
507 if (tempscore < score)
516 FOREACH_CLIENT(IS_BOT_CLIENT(it),
518 if (it.team == surv_defenderteam)
520 float tempscore = PlayerScore_Get(it, SP_SCORE);
521 if (tempscore < score)
531 LOG_TRACE("No valid bot to remove");
532 // No space in team, denying team change.
533 TRANSMUTE(Spectator, player);
536 LOG_TRACE("Changing ", bot.netname,
537 " from defender to cannon fodder.");
538 if ((!IS_DEAD(bot)) && (!surv_allowed_to_spawn))
540 player.surv_savedplayerstate = Surv_SavePlayerState(bot);
542 surv_autobalance = false;
543 SetPlayerTeamSimple(bot, surv_attackerteam);
544 surv_autobalance = true;
545 LOG_TRACE("Removed bot");
547 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
548 Surv_ChangeNumberOfPlayers(teamnum, +1);
549 ++surv_numdefenderhumans;
550 LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
554 player.surv_role = SURVIVAL_ROLE_NONE;
558 /// \brief Removes player from team. Handles bookkeeping information.
559 /// \param[in] player Player to remove.
560 /// \param[in] Team to remove from.
561 /// \return No return.
562 void Surv_RemovePlayerFromTeam(entity player, int teamnum)
564 LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
567 case surv_attackerteam:
569 LOG_TRACE("Attacker team");
570 if (player.surv_role == SURVIVAL_ROLE_NONE)
572 string message = strcat("RemovePlayerFromTeam: ",
573 player.netname, " has invalid role.");
574 DebugPrintToChatAll(message);
577 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
579 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
582 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
583 Surv_ChangeNumberOfPlayers(teamnum, -1);
584 if (!IS_BOT_CLIENT(player))
586 --surv_numattackerhumans;
588 if ((surv_autobalance == false) || (surv_numattackers >=
593 // Add bot to keep teams balanced.
596 if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
598 LOG_TRACE("Changing ", it.netname,
599 " from cannon fodder to attacker.");
600 Surv_SetPlayerRole(it, SURVIVAL_ROLE_PLAYER);
601 Surv_ChangeNumberOfPlayers(teamnum, +1);
604 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
611 case surv_defenderteam:
613 LOG_TRACE("Defender team");
614 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
616 // This happens during team switch. We don't need to change
618 LOG_TRACE("Cannon fodder. Assuming team switch");
621 if (player.surv_role != SURVIVAL_ROLE_PLAYER)
623 string message = strcat("RemovePlayerFromTeam: ",
624 player.netname, " has invalid role.");
625 DebugPrintToChatAll(message);
628 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
629 Surv_ChangeNumberOfPlayers(teamnum, -1);
630 if (!IS_BOT_CLIENT(player))
632 --surv_numdefenderhumans;
634 if ((surv_autobalance == false) || (surv_numdefenders >=
639 // Add bot to keep teams balanced.
642 if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
644 LOG_TRACE("Changing ", it.netname,
645 " from cannon fodder to defender.");
646 SetPlayerTeamSimple(it, surv_defenderteam);
647 if (!IS_DEAD(player) && !surv_allowed_to_spawn)
649 entity state = Surv_SavePlayerState(player);
650 Surv_RestorePlayerState(it, state);
660 LOG_TRACE("Invalid team");
666 /// \brief Updates stats of team count on HUD.
667 /// \return No return.
668 void Surv_UpdateTeamStats()
671 if (surv_attackerteam == NUM_TEAM_1)
673 yellowalive = surv_numattackers;
674 pinkalive = surv_numdefenders;
678 pinkalive = surv_numattackers;
679 yellowalive = surv_numdefenders;
681 FOREACH_CLIENT(IS_REAL_CLIENT(it),
683 it.yellowalive_stat = yellowalive;
684 it.pinkalive_stat = pinkalive;
688 /// \brief Adds player to alive list. Handles bookkeeping information.
689 /// \param[in] player Player to add.
690 /// \param[in] teamnum Team of the player.
691 /// \return No return.
692 void Surv_AddPlayerToAliveList(entity player, int teamnum)
696 case surv_attackerteam:
698 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
700 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
704 case surv_defenderteam:
706 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
712 /// \brief Removes player from alive list. Handles bookkeeping information.
713 /// \param[in] player Player to remove.
714 /// \param[in] teamnum Team of the player.
715 /// \return No return.
716 void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
718 if (player.surv_attack_sprite)
720 WaypointSprite_Kill(player.surv_attack_sprite);
724 case surv_attackerteam:
726 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
728 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
732 case surv_defenderteam:
734 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
736 // This happens during team switch. We don't need to change
740 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
741 if (warmup_stage || surv_allowed_to_spawn)
745 switch (surv_numdefendersalive)
749 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
750 VOL_BASE, ATTEN_NONE);
751 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
753 if (it.team == surv_defenderteam)
755 Send_Notification(NOTIF_ONE, it, MSG_CENTER,
764 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
765 VOL_BASE, ATTEN_NONE);
770 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
771 VOL_BASE, ATTEN_NONE);
780 /// \brief Counts alive players.
781 /// \return No return.
782 /// \note In a perfect world this function shouldn't exist. However, since QC
783 /// code is so bad and spurious mutators can really mess with your code, this
784 /// function is called as a last resort.
785 void Surv_CountAlivePlayers()
787 int savednumdefenders = surv_numdefendersalive;
788 surv_numattackersalive = 0;
789 surv_numdefendersalive = 0;
790 FOREACH_CLIENT(IS_PLAYER(it),
794 case surv_attackerteam:
796 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
798 ++surv_numattackersalive;
802 case surv_defenderteam:
804 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
806 ++surv_numdefendersalive;
812 Surv_UpdateAliveStats();
813 eliminatedPlayers.SendFlags |= 1;
814 if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
815 surv_numdefendersalive))
819 switch (surv_numdefendersalive)
823 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
824 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
826 if (it.team == surv_defenderteam)
828 Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
836 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
842 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
849 /// \brief Updates stats of alive players on HUD.
850 /// \return No return.
851 void Surv_UpdateAliveStats()
854 if (surv_attackerteam == NUM_TEAM_1)
856 redalive = surv_numattackersalive;
857 bluealive = surv_numdefendersalive;
861 bluealive = surv_numattackersalive;
862 redalive = surv_numdefendersalive;
864 FOREACH_CLIENT(IS_REAL_CLIENT(it),
866 it.surv_defenders_alive_stat = surv_numdefendersalive;
867 it.redalive_stat = redalive;
868 it.bluealive_stat = bluealive;
870 Surv_UpdateDefenderHealthStat();
873 /// \brief Updates defender health on the HUD.
874 /// \return No return.
875 void Surv_UpdateDefenderHealthStat()
878 float totalhealth = 0;
879 if (autocvar_g_instagib == 1)
881 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
882 "surv_defender", "start_armor") + 1);
883 FOREACH_CLIENT(IS_PLAYER(it),
885 if (it.team == surv_defenderteam)
887 totalhealth += it.armorvalue + 1;
893 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
894 "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
895 "surv_defender", "start_armor"));
896 FOREACH_CLIENT(IS_PLAYER(it),
898 if (it.team == surv_defenderteam)
900 totalhealth += it.health;
901 totalhealth += it.armorvalue;
912 healthratio = totalhealth / maxhealth;
914 FOREACH_CLIENT(IS_REAL_CLIENT(it),
916 it.surv_defender_health_stat = healthratio;
920 /// \brief Returns whether the player can spawn.
921 /// \param[in] player Player to check.
922 /// \return True if the player can spawn, false otherwise.
923 bool Surv_CanPlayerSpawn(entity player)
925 if ((player.team == surv_attackerteam) ||
926 (player.surv_savedplayerstate != NULL))
930 return surv_allowed_to_spawn;
933 /// \brief Switches the round type.
934 /// \return No return.
935 void Surv_SwitchRoundType()
937 switch (surv_roundtype)
939 case SURVIVAL_ROUND_FIRST:
941 surv_roundtype = SURVIVAL_ROUND_SECOND;
944 case SURVIVAL_ROUND_SECOND:
946 surv_roundtype = SURVIVAL_ROUND_FIRST;
952 /// \brief Cleans up the mess after the round has finished.
953 /// \return No return.
954 void Surv_RoundCleanup()
956 surv_allowed_to_spawn = false;
957 surv_isroundactive = false;
961 if (it.surv_attack_sprite)
963 WaypointSprite_Kill(it.surv_attack_sprite);
966 if (surv_type == SURVIVAL_TYPE_VERSUS)
968 Surv_SwitchRoundType();
969 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
972 round_handler_Init(5, autocvar_g_surv_warmup,
973 autocvar_g_surv_round_timelimit);
976 /// \brief Swaps attacker and defender teams.
977 /// \return No return.
978 void Surv_SwapTeams()
980 int temp = surv_attackerteam;
981 surv_attackerteam = surv_defenderteam;
982 surv_defenderteam = temp;
983 temp = surv_attackerteambit;
984 surv_attackerteambit = surv_defenderteambit;
985 surv_defenderteambit = temp;
986 temp = surv_numattackers;
987 surv_numattackers = surv_numdefenders;
988 surv_numdefenders = temp;
989 temp = surv_numattackerhumans;
990 surv_numattackerhumans = surv_numdefenderhumans;
991 surv_numdefenderhumans = temp;
994 if ((it.team == surv_defenderteam) && (it.surv_role ==
995 SURVIVAL_ROLE_CANNON_FODDER))
997 SetPlayerTeamSimple(it, surv_attackerteam);
1000 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1002 it.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1006 /// \brief Forces the overkill model for specific player.
1007 /// \param[in,out] player Player to force the model of.
1008 /// \return No return.
1009 void Surv_ForceOverkillPlayerModel(entity player)
1011 switch (player.team)
1015 switch (floor(random() * 4))
1019 player.surv_playermodel = "models/ok_player/okrobot1.dpm";
1024 player.surv_playermodel = "models/ok_player/okrobot2.dpm";
1029 player.surv_playermodel = "models/ok_player/okrobot3.dpm";
1034 player.surv_playermodel = "models/ok_player/okrobot4.dpm";
1042 switch (floor(random() * 4))
1046 player.surv_playermodel = "models/ok_player/okmale1.dpm";
1051 player.surv_playermodel = "models/ok_player/okmale2.dpm";
1056 player.surv_playermodel = "models/ok_player/okmale3.dpm";
1061 player.surv_playermodel = "models/ok_player/okmale4.dpm";
1070 /// \brief Determines the player model to the one configured for the gamemode.
1071 /// \param[in,out] player Player to determine the model of.
1072 /// \return No return.
1073 void Surv_DeterminePlayerModel(entity player)
1075 switch (player.team)
1077 case surv_attackerteam:
1079 switch (player.surv_role)
1081 case SURVIVAL_ROLE_PLAYER:
1083 if (!autocvar_g_surv_attacker_force_overkill_models)
1085 player.surv_playermodel = player.surv_savedplayermodel;
1088 Surv_ForceOverkillPlayerModel(player);
1091 case SURVIVAL_ROLE_CANNON_FODDER:
1093 if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
1095 player.surv_playermodel = player.surv_savedplayermodel;
1098 Surv_ForceOverkillPlayerModel(player);
1103 case surv_defenderteam:
1105 if (!autocvar_g_surv_defender_force_overkill_models)
1107 player.surv_playermodel = player.surv_savedplayermodel;
1110 Surv_ForceOverkillPlayerModel(player);
1116 //=============================== Callbacks ===================================
1118 bool Surv_CanRoundStart()
1120 return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
1123 bool Surv_CanRoundEnd()
1129 if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
1132 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1134 surv_timetobeat = time - surv_roundstarttime;
1135 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1136 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1137 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1138 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1139 Surv_RoundCleanup();
1142 surv_timetobeat = autocvar_g_surv_round_timelimit;
1143 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1144 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1145 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1146 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1147 switch (surv_defenderteam)
1151 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1157 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1162 TeamScore_AddToTeam(surv_defenderteam, 1, 1);
1163 Surv_RoundCleanup();
1166 if (surv_numdefendersalive > 0)
1170 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1172 surv_timetobeat = time - surv_roundstarttime;
1173 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1174 CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1175 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1176 INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1177 Surv_RoundCleanup();
1180 surv_timetobeat = autocvar_g_surv_round_timelimit;
1181 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1182 CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
1183 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1184 INFO_SURVIVAL_DEFENDERS_ELIMINATED);
1185 switch (surv_attackerteam)
1189 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1195 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1200 TeamScore_AddToTeam(surv_attackerteam, 1, 1);
1201 Surv_RoundCleanup();
1205 void Surv_RoundStart()
1209 surv_allowed_to_spawn = true;
1212 surv_isroundactive = true;
1213 surv_roundstarttime = time;
1214 surv_allowed_to_spawn = false;
1215 switch (surv_roundtype)
1217 case SURVIVAL_ROUND_FIRST:
1219 FOREACH_CLIENT(IS_PLAYER(it),
1221 if (it.team == surv_attackerteam)
1223 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1224 CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
1225 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1226 INFO_SURVIVAL_1ST_ROUND_ATTACKER);
1230 FOREACH_CLIENT(IS_PLAYER(it),
1232 if (it.team == surv_defenderteam)
1234 if (surv_type == SURVIVAL_TYPE_COOP)
1236 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1237 CENTER_SURVIVAL_COOP_DEFENDER);
1238 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1239 INFO_SURVIVAL_COOP_DEFENDER);
1242 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1243 CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
1244 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1245 INFO_SURVIVAL_1ST_ROUND_DEFENDER);
1251 case SURVIVAL_ROUND_SECOND:
1253 FOREACH_CLIENT(IS_PLAYER(it),
1255 if (it.team == surv_attackerteam)
1257 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1258 CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1259 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1260 INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1264 FOREACH_CLIENT(IS_PLAYER(it),
1266 if (it.team == surv_defenderteam)
1268 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1269 CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1270 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1271 INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1278 if (autocvar_g_surv_stealth)
1282 FOREACH_CLIENT(IS_PLAYER(it),
1286 case surv_defenderteam:
1288 if (it.surv_role == SURVIVAL_ROLE_PLAYER)
1290 WaypointSprite_Spawn(WP_AssaultDestroy, 0, 0, it, '0 0 64',
1291 NULL, surv_attackerteam, it, surv_attack_sprite, false,
1292 RADARICON_OBJECTIVE);
1293 if (autocvar_g_instagib == 1)
1295 WaypointSprite_UpdateMaxHealth(it.surv_attack_sprite,
1296 PlayerTemplate_GetFloatValue("surv_defender",
1297 "start_armor") + 1);
1298 WaypointSprite_UpdateHealth(it.surv_attack_sprite,
1303 WaypointSprite_UpdateMaxHealth(it.surv_attack_sprite,
1304 PlayerTemplate_GetFloatValue("surv_defender",
1305 "start_health") + PlayerTemplate_GetFloatValue(
1306 "surv_defender", "start_armor"));
1307 WaypointSprite_UpdateHealth(it.surv_attack_sprite,
1308 it.health + it.armorvalue);
1317 bool Surv_IsEliminated(entity player)
1319 switch (player.surv_state)
1321 case SURVIVAL_STATE_NOT_PLAYING:
1325 case SURVIVAL_STATE_PLAYING:
1327 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
1329 // A hack until proper scoreboard is done.
1332 if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
1333 IS_OBSERVER(player)))
1340 // Should never reach here
1344 //============================= Hooks ========================================
1346 /// \brief Hook that is called to determine general rules of the game.
1347 MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
1349 surv_warmup = warmup_stage;
1352 /// \brief Hook that is called to determine if there is a weapon arena.
1353 MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
1355 // Removing any weapon arena.
1356 M_ARGV(0, string) = "off";
1359 /// \brief Hook that is called to determine start items of all players.
1360 MUTATOR_HOOKFUNCTION(surv, SetStartItems)
1362 if (autocvar_g_instagib == 1)
1366 start_weapons = WEPSET(Null);
1367 warmup_start_weapons = WEPSET(Null);
1370 MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
1372 if (game_stopped || !surv_isroundactive)
1376 float roundtime = 0;
1377 switch (surv_roundtype)
1379 case SURVIVAL_ROUND_FIRST:
1381 roundtime = time - surv_roundstarttime;
1384 case SURVIVAL_ROUND_SECOND:
1386 roundtime = round_handler_GetEndTime() - time;
1390 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1392 it.surv_round_time_stat = roundtime;
1396 /// \brief Hook that determines which team player can join. This is called
1397 /// before ClientConnect.
1398 MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
1400 entity player = M_ARGV(2, entity);
1401 LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
1402 if (IS_BOT_CLIENT(player))
1404 int teambits = surv_attackerteambit;
1405 if ((player.team == surv_defenderteam) || (surv_numdefenders <
1406 autocvar_g_surv_team_size))
1408 teambits |= surv_defenderteambit;
1410 M_ARGV(0, float) = teambits;
1413 if (surv_type == SURVIVAL_TYPE_COOP)
1415 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1417 M_ARGV(0, float) = surv_defenderteambit;
1420 M_ARGV(0, float) = 0;
1424 if (surv_numattackerhumans < autocvar_g_surv_team_size)
1426 teambits |= surv_attackerteambit;
1428 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1430 teambits |= surv_defenderteambit;
1432 M_ARGV(0, float) = teambits;
1436 /// \brief Hook that determines the best team for the player to join.
1437 MUTATOR_HOOKFUNCTION(surv, JoinBestTeam, CBC_ORDER_EXCLUSIVE)
1439 if (surv_type == SURVIVAL_TYPE_COOP)
1443 entity player = M_ARGV(0, entity);
1444 if (IS_BOT_CLIENT(player))
1448 int numattackerhumans = surv_numattackerhumans;
1449 int numdefenderhumans = surv_numdefenderhumans;
1450 if (player.team == surv_attackerteam)
1452 --numattackerhumans;
1454 else if (player.team == surv_defenderteam)
1456 --numdefenderhumans;
1458 if (numattackerhumans < numdefenderhumans)
1460 M_ARGV(1, float) = Team_TeamToNumber(surv_attackerteam);
1463 if (numattackerhumans > numdefenderhumans)
1465 M_ARGV(1, float) = Team_TeamToNumber(surv_defenderteam);
1468 M_ARGV(1, float) = floor(random() * 2) + 1;
1471 /// \brief Hook that is called when player has changed the team.
1472 MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
1474 entity player = M_ARGV(0, entity);
1475 int oldteam = M_ARGV(1, float);
1476 int newteam = M_ARGV(2, float);
1477 string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
1478 ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
1480 DebugPrintToChatAll(message);
1481 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1483 Surv_RemovePlayerFromAliveList(player, oldteam);
1485 Surv_RemovePlayerFromTeam(player, oldteam);
1486 if (Surv_AddPlayerToTeam(player, newteam) == false)
1490 //Surv_CountAlivePlayers();
1491 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1493 Surv_AddPlayerToAliveList(player, newteam);
1497 /// \brief Hook that is called when player connects to the server.
1498 MUTATOR_HOOKFUNCTION(surv, ClientConnect)
1500 entity player = M_ARGV(0, entity);
1501 LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
1502 player.surv_savedplayermodel = player.playermodel;
1503 if (IS_REAL_CLIENT(player))
1505 player.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1506 player.surv_defenders_alive_stat = surv_numdefendersalive;
1507 player.redalive_stat = redalive;
1508 player.bluealive_stat = bluealive;
1509 player.yellowalive_stat = yellowalive;
1510 player.pinkalive_stat = pinkalive;
1512 if (player.surv_role == SURVIVAL_ROLE_NONE)
1514 Surv_AddPlayerToTeam(player, player.team);
1519 /// \brief Hook that is called when player disconnects from the server.
1520 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
1522 entity player = M_ARGV(0, entity);
1523 if (!IS_DEAD(player))
1525 Surv_RemovePlayerFromAliveList(player, player.team);
1527 Surv_RemovePlayerFromTeam(player, player.team);
1528 //Surv_CountAlivePlayers();
1531 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
1533 entity player = M_ARGV(0, entity);
1534 LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
1535 if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
1537 TRANSMUTE(Observer, player);
1541 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
1543 entity player = M_ARGV(0, entity);
1544 LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
1545 if (player.killindicator_teamchange == -2) // player wants to spectate
1547 LOG_TRACE("killindicator_teamchange == -2");
1548 player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
1550 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1552 return false; // allow team reset
1554 return true; // prevent team reset
1557 /// \brief Hook that determines whether player can spawn. It is not called for
1558 /// players who have joined the team and are dead.
1559 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
1561 entity player = M_ARGV(0, entity);
1562 LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
1563 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1567 return !Surv_CanPlayerSpawn(player);
1570 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
1572 LOG_TRACE("Survival: reset_map_global");
1573 surv_allowed_to_spawn = true;
1574 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1576 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1578 it.surv_round_time_stat = 0;
1584 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
1586 LOG_TRACE("Survival: reset_map_players");
1587 surv_numattackersalive = 0;
1588 surv_numdefendersalive = 0;
1591 surv_warmup = false;
1593 else if (surv_type == SURVIVAL_TYPE_VERSUS)
1597 FOREACH_CLIENT(true,
1600 if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
1603 it.surv_state = SURVIVAL_STATE_PLAYING;
1605 if (it.surv_state == SURVIVAL_STATE_PLAYING)
1607 TRANSMUTE(Player, it);
1608 it.surv_state = SURVIVAL_STATE_PLAYING;
1609 PutClientInServer(it);
1612 bot_relinkplayerlist();
1616 /// \brief Hook that is called when player spawns.
1617 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
1619 entity player = M_ARGV(0, entity);
1620 LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
1621 player.surv_state = SURVIVAL_STATE_PLAYING;
1622 Surv_DeterminePlayerModel(player);
1623 if (player.surv_savedplayerstate != NULL)
1625 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1626 delete(player.surv_savedplayerstate);
1627 player.surv_savedplayerstate = NULL;
1631 PlayerTemplate_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
1633 switch (player.team)
1635 case surv_attackerteam:
1637 switch (player.surv_role)
1639 case SURVIVAL_ROLE_PLAYER:
1641 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1642 CENTER_ASSAULT_ATTACKING);
1647 LOG_TRACE("Survival: PlayerSpawn: Invalid attacker role.");
1653 case surv_defenderteam:
1655 if (player.surv_role != SURVIVAL_ROLE_PLAYER)
1657 LOG_TRACE("Survival: PlayerSpawn: ", player.netname,
1658 " has invalid defender role.");
1660 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1661 CENTER_ASSAULT_DEFENDING);
1665 //Surv_CountAlivePlayers();
1666 Surv_AddPlayerToAliveList(player, player.team);
1669 /// \brief UGLY HACK. This is called every frame to keep player model correct.
1670 MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
1672 entity player = M_ARGV(2, entity);
1673 M_ARGV(0, string) = player.surv_playermodel;
1676 /// \brief Hook that is called every frame to determine how player health should
1678 MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
1680 entity player = M_ARGV(0, entity);
1681 if (player.team == surv_defenderteam)
1688 /// \brief Hook that is called to determine if balance messages will appear.
1689 MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
1694 /// \brief Hook that is called when player touches an item.
1695 MUTATOR_HOOKFUNCTION(surv, ItemTouch)
1697 entity item = M_ARGV(0, entity);
1698 entity player = M_ARGV(1, entity);
1699 switch (player.team)
1701 case surv_attackerteam:
1703 return PlayerTemplate_ItemTouch(player, item,
1704 Surv_GetPlayerTemplate(player));
1706 case surv_defenderteam:
1708 switch (item.classname)
1710 case "item_strength":
1712 W_GiveWeapon(player, WEP_HMG.m_id);
1713 player.superweapons_finished = max(
1714 player.superweapons_finished, time) +
1715 autocvar_g_balance_superweapons_time;
1716 Item_ScheduleRespawn(item);
1717 sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
1719 return MUT_ITEMTOUCH_RETURN;
1721 case "item_invincible":
1723 W_GiveWeapon(player, WEP_RPC.m_id);
1724 player.superweapons_finished = max(
1725 player.superweapons_finished, time) +
1726 autocvar_g_balance_superweapons_time;
1727 Item_ScheduleRespawn(item);
1728 sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
1729 return MUT_ITEMTOUCH_RETURN;
1733 return PlayerTemplate_ItemTouch(player, item,
1734 Surv_GetPlayerTemplate(player));
1737 DebugPrintToChat(player, item.classname);
1738 return MUT_ITEMTOUCH_RETURN;
1741 return MUT_ITEMTOUCH_CONTINUE;
1744 /// \brief Hook which is called when the player tries to throw their weapon.
1745 MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
1747 entity player = M_ARGV(0, entity);
1748 if (player.team == surv_defenderteam)
1754 /// \brief Hook which is called when the damage amount must be determined.
1755 MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
1757 entity frag_attacker = M_ARGV(1, entity);
1758 entity frag_target = M_ARGV(2, entity);
1759 float deathtype = M_ARGV(3, float);
1760 float damage = M_ARGV(4, float);
1761 M_ARGV(4, float) = PlayerTemplate_Damage_Calculate(frag_attacker,
1762 Surv_GetPlayerTemplate(frag_attacker), frag_target,
1763 Surv_GetPlayerTemplate(frag_target), deathtype, damage);
1766 /// \brief Hook which is called when the player was damaged.
1767 MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
1769 entity target = M_ARGV(1, entity);
1770 if (target.team != surv_defenderteam)
1774 Surv_UpdateDefenderHealthStat();
1775 entity attacker = M_ARGV(0, entity);
1776 if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
1777 SURVIVAL_ROLE_PLAYER))
1779 float health = M_ARGV(2, float);
1780 float armor = M_ARGV(3, float);
1781 float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
1782 PlayerScore_Add(attacker, SP_SCORE, score);
1784 if (autocvar_g_surv_stealth)
1788 if (target.health < 1)
1790 WaypointSprite_Kill(target.surv_attack_sprite);
1794 WaypointSprite_UpdateHealth(target.surv_attack_sprite,
1795 target.armorvalue + 1);
1799 /// \brief Hook which is called when the player dies.
1800 MUTATOR_HOOKFUNCTION(surv, PlayerDies)
1802 //DebugPrintToChatAll("PlayerDies");
1803 entity attacker = M_ARGV(1, entity);
1804 entity victim = M_ARGV(2, entity);
1805 if ((attacker.team == surv_defenderteam) &&
1806 (victim.team == surv_attackerteam))
1808 switch (victim.surv_role)
1810 case SURVIVAL_ROLE_PLAYER:
1812 GivePlayerHealth(attacker,
1813 autocvar_g_surv_defender_attacker_frag_health);
1814 GivePlayerArmor(attacker,
1815 autocvar_g_surv_defender_attacker_frag_armor);
1816 GivePlayerAmmo(attacker, ammo_shells,
1817 autocvar_g_surv_defender_attacker_frag_shells);
1818 GivePlayerAmmo(attacker, ammo_nails,
1819 autocvar_g_surv_defender_attacker_frag_bullets);
1820 GivePlayerAmmo(attacker, ammo_rockets,
1821 autocvar_g_surv_defender_attacker_frag_rockets);
1822 GivePlayerAmmo(attacker, ammo_cells,
1823 autocvar_g_surv_defender_attacker_frag_cells);
1826 case SURVIVAL_ROLE_CANNON_FODDER:
1828 GivePlayerHealth(attacker,
1829 autocvar_g_surv_defender_cannon_fodder_frag_health);
1830 GivePlayerArmor(attacker,
1831 autocvar_g_surv_defender_cannon_fodder_frag_armor);
1832 GivePlayerAmmo(attacker, ammo_shells,
1833 autocvar_g_surv_defender_cannon_fodder_frag_shells);
1834 GivePlayerAmmo(attacker, ammo_nails,
1835 autocvar_g_surv_defender_cannon_fodder_frag_bullets);
1836 GivePlayerAmmo(attacker, ammo_rockets,
1837 autocvar_g_surv_defender_cannon_fodder_frag_rockets);
1838 GivePlayerAmmo(attacker, ammo_cells,
1839 autocvar_g_surv_defender_cannon_fodder_frag_cells);
1844 if ((victim.team == surv_defenderteam) &&
1845 (autocvar_g_surv_defender_drop_weapons == false))
1847 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1849 .entity went = weaponentities[slot];
1850 victim.(went).m_weapon = WEP_Null;
1853 if (!Surv_CanPlayerSpawn(victim))
1855 victim.respawn_flags = RESPAWN_SILENT;
1856 if (IS_BOT_CLIENT(victim))
1864 /// \brief Hook which is called after the player died.
1865 MUTATOR_HOOKFUNCTION(surv, PlayerDied)
1867 //DebugPrintToChatAll("PlayerDied");
1868 entity player = M_ARGV(0, entity);
1869 Surv_RemovePlayerFromAliveList(player, player.team);
1870 //Surv_CountAlivePlayers();
1873 /// \brief Hook which is called when player has scored a frag.
1874 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
1876 if (surv_type == SURVIVAL_TYPE_COOP)
1880 entity attacker = M_ARGV(0, entity);
1881 if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
1882 SURVIVAL_ROLE_CANNON_FODDER))
1884 M_ARGV(2, float) = 0;
1887 entity target = M_ARGV(1, entity);
1888 if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
1891 M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
1896 /// \brief I'm not sure exactly what this function does but it is very
1897 /// important. Without it bots are completely broken. Is it a hack? Of course.
1898 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
1900 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
1901 if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
1910 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
1912 // Don't announce remaining frags