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_announcefrags; ///< Holds whether remaining frags must be announced.
159 bool surv_allowed_to_spawn; ///< Holds whether players are allowed to spawn.
161 //====================== Forward declarations =================================
163 /// \brief Determines whether the round can start.
164 /// \return True if the round can start, false otherwise.
165 bool Surv_CanRoundStart();
167 /// \brief Determines whether the round can end.
168 /// \return True if the round can end, false otherwise.
169 bool Surv_CanRoundEnd();
171 /// \brief Called when the round starts.
172 /// \return No return.
173 void Surv_RoundStart();
175 /// \brief Returns whether player has been eliminated.
176 /// \param[in] player Player to check.
177 /// \return True if player is eliminated, false otherwise.
178 bool Surv_IsEliminated(entity player);
180 /// \brief Updates stats of team count on HUD.
181 /// \return No return.
182 void Surv_UpdateTeamStats();
184 /// \brief Updates stats of alive players on HUD.
185 /// \return No return.
186 void Surv_UpdateAliveStats();
188 /// \brief Updates defender health on the HUD.
189 /// \return No return.
190 void Surv_UpdateDefenderHealthStat();
192 //========================= Free functions ====================================
194 void Surv_Initialize()
196 switch (cvar_string("g_surv_type"))
198 case SURVIVAL_TYPE_COOP_VALUE:
200 surv_type = SURVIVAL_TYPE_COOP;
203 case SURVIVAL_TYPE_VERSUS_VALUE:
205 surv_type = SURVIVAL_TYPE_VERSUS;
210 error("Invalid survival type.");
213 surv_roundtype = SURVIVAL_ROUND_FIRST;
214 surv_isroundactive = false;
215 surv_roundstarttime = time;
216 surv_timetobeat = autocvar_g_surv_round_timelimit;
219 surv_attackerteam = NUM_TEAM_1;
220 surv_defenderteam = NUM_TEAM_2;
221 surv_attackerteambit = SURVIVAL_TEAM_1_BIT;
222 surv_defenderteambit = SURVIVAL_TEAM_2_BIT;
226 surv_attackerteam = NUM_TEAM_2;
227 surv_defenderteam = NUM_TEAM_1;
228 surv_attackerteambit = SURVIVAL_TEAM_2_BIT;
229 surv_defenderteambit = SURVIVAL_TEAM_1_BIT;
231 surv_numattackers = 0;
232 surv_numdefenders = 0;
233 surv_numattackerhumans = 0;
234 surv_numdefenderhumans = 0;
235 surv_numattackersalive = 0;
236 surv_numdefendersalive = 0;
237 surv_autobalance = true;
238 surv_announcefrags = true;
239 surv_allowed_to_spawn = true;
240 precache_all_playermodels("models/ok_player/*.dpm");
241 ScoreRules_basics(SURVIVAL_TEAM_BITS, SFL_SORT_PRIO_PRIMARY, 0, true);
242 ScoreInfo_SetLabel_TeamScore(1, "rounds", SFL_SORT_PRIO_PRIMARY);
243 ScoreRules_basics_end();
244 round_handler_Spawn(Surv_CanRoundStart, Surv_CanRoundEnd, Surv_RoundStart);
245 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
246 EliminatedPlayers_Init(Surv_IsEliminated);
248 SetLimits(autocvar_g_surv_point_limit, autocvar_g_surv_point_leadlimit,
249 autocvar_timelimit_override, -1);
252 /// \brief Returns the name of the template of the given player.
253 /// \param[in] player Player to inspect.
254 /// \return Name of the template of the given player.
255 string Surv_GetPlayerTemplate(entity player)
259 case surv_attackerteam:
261 switch (player.surv_role)
263 case SURVIVAL_ROLE_NONE:
264 case SURVIVAL_ROLE_PLAYER:
266 return "surv_attacker";
268 case SURVIVAL_ROLE_CANNON_FODDER:
270 return "surv_cannon_fodder";
274 case surv_defenderteam:
276 return "surv_defender";
282 /// \brief Saves the player state. Used to seamlessly swap bots with humans.
283 /// \param[in] player Player to save the state of.
284 /// \return Entity containing the player state.
285 entity Surv_SavePlayerState(entity player)
287 entity state = spawn();
288 state.origin = player.origin;
289 state.velocity = player.velocity;
290 state.angles = player.angles;
291 state.health = player.health;
292 state.armorvalue = player.armorvalue;
293 state.ammo_shells = player.ammo_shells;
294 state.ammo_nails = player.ammo_nails;
295 state.ammo_rockets = player.ammo_rockets;
296 state.ammo_cells = player.ammo_cells;
297 state.weapons = player.weapons;
298 state.items = player.items;
299 state.superweapons_finished = player.superweapons_finished;
303 /// \brief Restores a saved player state.
304 /// \param[in] player Player to restore the state of.
305 /// \param[in] st State to restore.
306 /// \return No return.
307 void Surv_RestorePlayerState(entity player, entity st)
309 player.origin = st.origin;
310 player.velocity = st.velocity;
311 player.angles = st.angles;
312 player.health = st.health;
313 player.armorvalue = st.armorvalue;
314 player.ammo_shells = st.ammo_shells;
315 player.ammo_nails = st.ammo_nails;
316 player.ammo_rockets = st.ammo_rockets;
317 player.ammo_cells = st.ammo_cells;
318 player.weapons = st.weapons;
319 player.items = st.items;
320 player.superweapons_finished = st.superweapons_finished;
323 /// \brief Changes the number of players in a team.
324 /// \param[in] teamnum Team to adjust.
325 /// \param[in] delta Amount to adjust by.
326 /// \return No return.
327 void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
331 case surv_attackerteam:
333 surv_numattackers += delta;
334 LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
335 " was = ", ftos(surv_numattackers - delta));
336 Surv_UpdateTeamStats();
339 case surv_defenderteam:
341 surv_numdefenders += delta;
342 LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
343 " was = ", ftos(surv_numdefenders - delta));
344 Surv_UpdateTeamStats();
350 /// \brief Changes the number of alive players in a team.
351 /// \param[in] teamnum Team to adjust.
352 /// \param[in] delta Amount to adjust by.
353 /// \return No return.
354 void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
358 case surv_attackerteam:
360 surv_numattackersalive += delta;
361 LOG_TRACE("Number of alive attackers = ", ftos(
362 surv_numattackersalive), " was = ", ftos(surv_numattackersalive
366 case surv_defenderteam:
368 surv_numdefendersalive += delta;
369 LOG_TRACE("Number of alive defenders = ", ftos(
370 surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
375 Surv_UpdateAliveStats();
376 eliminatedPlayers.SendFlags |= 1;
379 /// \brief Sets the player role.
380 /// \param[in,out] player Player to adjust.
381 /// \param[in] role Role to set.
382 /// \return No return.
383 void Surv_SetPlayerRole(entity player, int role)
385 if (player.surv_role == role)
389 player.surv_role = role;
392 case SURVIVAL_ROLE_NONE:
394 LOG_TRACE(player.netname, " now has no role.");
397 case SURVIVAL_ROLE_PLAYER:
399 LOG_TRACE(player.netname, " is now a player.");
402 case SURVIVAL_ROLE_CANNON_FODDER:
404 LOG_TRACE(player.netname, " is now a cannon fodder.");
410 /// \brief Adds player to team. Handles bookkeeping information.
411 /// \param[in] player Player to add.
412 /// \param[in] teamnum Team to add to.
413 /// \return True on success, false otherwise.
414 bool Surv_AddPlayerToTeam(entity player, int teamnum)
416 LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
419 case surv_attackerteam:
421 LOG_TRACE("Attacker team");
422 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
424 LOG_TRACE("Cannon fodder is switching team");
427 if (IS_BOT_CLIENT(player))
429 LOG_TRACE("Client is bot");
430 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
431 if (surv_numattackers < autocvar_g_surv_team_size)
433 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
434 Surv_ChangeNumberOfPlayers(teamnum, +1);
437 Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
440 LOG_TRACE("Client is not a bot");
441 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
442 if (surv_numattackers >= autocvar_g_surv_team_size)
444 LOG_TRACE("Removing bot");
445 // Remove bot to make space for human.
447 float score = FLOAT_MAX;
448 FOREACH_CLIENT(IS_BOT_CLIENT(it),
450 if ((it.team == surv_attackerteam) && (it.surv_role ==
451 SURVIVAL_ROLE_PLAYER))
453 float tempscore = PlayerScore_Get(it, SP_SCORE);
454 if (tempscore < score)
463 LOG_TRACE("No valid bot to remove");
464 // No space in team, denying team change.
465 TRANSMUTE(Spectator, player);
468 LOG_TRACE("Changing ", bot.netname,
469 " from attacker to cannon fodder.");
470 Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
473 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
475 Surv_ChangeNumberOfPlayers(teamnum, -1);
476 LOG_TRACE("Removed bot");
478 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
479 Surv_ChangeNumberOfPlayers(teamnum, +1);
480 ++surv_numattackerhumans;
481 LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
482 if ((surv_autobalance == false) || (surv_numattackers -
483 surv_numdefenders) < 2)
487 entity lowestplayer = NULL;
488 float score = FLOAT_MAX;
489 FOREACH_CLIENT(IS_BOT_CLIENT(it),
491 if ((it.team == surv_attackerteam) && (it.surv_role ==
492 SURVIVAL_ROLE_PLAYER))
494 float tempscore = PlayerScore_Get(it, SP_SCORE);
495 if (tempscore < score)
502 if (lowestplayer != NULL)
504 bool savedautobalance = surv_autobalance;
505 surv_autobalance = false;
506 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
507 surv_autobalance = savedautobalance;
512 if ((it.team == surv_attackerteam) && (it.surv_role ==
513 SURVIVAL_ROLE_PLAYER))
515 float tempscore = PlayerScore_Get(it, SP_SCORE);
516 if (tempscore < score)
523 if (lowestplayer != NULL)
525 bool savedautobalance = surv_autobalance;
526 surv_autobalance = false;
527 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
528 surv_autobalance = savedautobalance;
532 case surv_defenderteam:
534 LOG_TRACE("Defender team");
535 if (IS_BOT_CLIENT(player))
537 LOG_TRACE("Client is bot");
538 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
539 if (surv_numdefenders < autocvar_g_surv_team_size)
541 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
542 Surv_ChangeNumberOfPlayers(teamnum, +1);
545 LOG_TRACE("No space for defender, switching to attacker");
546 SetPlayerTeamSimple(player, surv_attackerteam);
549 LOG_TRACE("Client is not a bot");
550 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
551 if (surv_numdefenders >= autocvar_g_surv_team_size)
553 LOG_TRACE("Removing bot");
554 // Remove bot to make space for human.
556 float score = FLOAT_MAX;
557 FOREACH_CLIENT(IS_BOT_CLIENT(it),
559 if (!IS_DEAD(it) && (it.team == surv_defenderteam))
561 float tempscore = PlayerScore_Get(it, SP_SCORE);
562 if (tempscore < score)
571 FOREACH_CLIENT(IS_BOT_CLIENT(it),
573 if (it.team == surv_defenderteam)
575 float tempscore = PlayerScore_Get(it, SP_SCORE);
576 if (tempscore < score)
586 LOG_TRACE("No valid bot to remove");
587 // No space in team, denying team change.
588 TRANSMUTE(Spectator, player);
591 LOG_TRACE("Changing ", bot.netname,
592 " from defender to cannon fodder.");
595 player.surv_savedplayerstate = Surv_SavePlayerState(bot);
597 bool savedautobalance = surv_autobalance;
598 surv_autobalance = false;
599 surv_announcefrags = false;
600 SetPlayerTeamSimple(bot, surv_attackerteam);
601 surv_autobalance = savedautobalance;
602 surv_announcefrags = true;
603 LOG_TRACE("Removed bot");
605 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
606 Surv_ChangeNumberOfPlayers(teamnum, +1);
607 ++surv_numdefenderhumans;
608 LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
609 if ((surv_autobalance == false) || (surv_numdefenders -
610 surv_numattackers) < 2)
614 entity lowestplayer = NULL;
615 float score = FLOAT_MAX;
616 FOREACH_CLIENT(IS_BOT_CLIENT(it),
618 if (it.team == surv_defenderteam)
620 float tempscore = PlayerScore_Get(it, SP_SCORE);
621 if (tempscore < score)
628 if (lowestplayer != NULL)
630 bool savedautobalance = surv_autobalance;
631 surv_autobalance = false;
632 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
633 surv_autobalance = savedautobalance;
638 if (it.team == surv_defenderteam)
640 float tempscore = PlayerScore_Get(it, SP_SCORE);
641 if (tempscore < score)
648 if (lowestplayer != NULL)
650 bool savedautobalance = surv_autobalance;
651 surv_autobalance = false;
652 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
653 surv_autobalance = savedautobalance;
659 LOG_TRACE("Spectator team");
660 player.surv_role = SURVIVAL_ROLE_NONE;
664 LOG_TRACE("Invalid team");
665 player.surv_role = SURVIVAL_ROLE_NONE;
669 /// \brief Removes player from team. Handles bookkeeping information.
670 /// \param[in] player Player to remove.
671 /// \param[in] Team to remove from.
672 /// \return No return.
673 void Surv_RemovePlayerFromTeam(entity player, int teamnum)
675 LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
678 case surv_attackerteam:
680 LOG_TRACE("Attacker team");
681 if (player.surv_role == SURVIVAL_ROLE_NONE)
683 string message = strcat("RemovePlayerFromTeam: ",
684 player.netname, " has invalid role.");
685 DebugPrintToChatAll(message);
688 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
690 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
693 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
694 Surv_ChangeNumberOfPlayers(teamnum, -1);
695 if (!IS_BOT_CLIENT(player))
697 --surv_numattackerhumans;
699 if ((surv_autobalance == false) || (surv_numattackers >=
704 // Add bot to keep teams balanced.
707 if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
709 LOG_TRACE("Changing ", it.netname,
710 " from cannon fodder to attacker.");
711 Surv_SetPlayerRole(it, SURVIVAL_ROLE_PLAYER);
712 Surv_ChangeNumberOfPlayers(teamnum, +1);
715 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
720 entity lowestplayer = NULL;
721 float score = FLOAT_MAX;
722 FOREACH_CLIENT(IS_BOT_CLIENT(it),
724 if (it.team == surv_defenderteam)
726 float tempscore = PlayerScore_Get(it, SP_SCORE);
727 if (tempscore < score)
734 if (lowestplayer != NULL)
736 bool savedautobalance = surv_autobalance;
737 surv_autobalance = false;
738 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
739 surv_autobalance = savedautobalance;
744 if (it.team == surv_defenderteam)
746 float tempscore = PlayerScore_Get(it, SP_SCORE);
747 if (tempscore < score)
754 if (lowestplayer == NULL)
758 bool savedautobalance = surv_autobalance;
759 surv_autobalance = false;
760 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
761 surv_autobalance = savedautobalance;
764 case surv_defenderteam:
766 LOG_TRACE("Defender team");
767 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
769 // This happens during team switch. We don't need to change
771 LOG_TRACE("Cannon fodder. Assuming team switch");
774 if (player.surv_role != SURVIVAL_ROLE_PLAYER)
776 string message = strcat("RemovePlayerFromTeam: ",
777 player.netname, " has invalid role.");
778 DebugPrintToChatAll(message);
781 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
782 Surv_ChangeNumberOfPlayers(teamnum, -1);
783 if (!IS_BOT_CLIENT(player))
785 --surv_numdefenderhumans;
787 if ((surv_autobalance == false) || (surv_numdefenders >=
792 // Add bot to keep teams balanced.
795 if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
797 LOG_TRACE("Changing ", it.netname,
798 " from cannon fodder to defender.");
799 if (!IS_DEAD(player))
801 it.surv_savedplayerstate = Surv_SavePlayerState(player);
803 bool savedautobalance = surv_autobalance;
804 surv_autobalance = false;
805 surv_announcefrags = false;
806 SetPlayerTeamSimple(it, surv_defenderteam);
807 surv_autobalance = savedautobalance;
808 surv_announcefrags = true;
812 entity lowestplayer = NULL;
813 float score = FLOAT_MAX;
814 FOREACH_CLIENT(IS_BOT_CLIENT(it),
816 if (it.team == surv_attackerteam)
818 float tempscore = PlayerScore_Get(it, SP_SCORE);
819 if (tempscore < score)
826 if (lowestplayer != NULL)
828 bool savedautobalance = surv_autobalance;
829 surv_autobalance = false;
830 surv_announcefrags = false;
831 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
832 surv_autobalance = savedautobalance;
833 surv_announcefrags = true;
838 if (it.team == surv_attackerteam)
840 float tempscore = PlayerScore_Get(it, SP_SCORE);
841 if (tempscore < score)
848 if (lowestplayer == NULL)
852 bool savedautobalance = surv_autobalance;
853 surv_autobalance = false;
854 surv_announcefrags = false;
855 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
856 surv_autobalance = savedautobalance;
857 surv_announcefrags = true;
862 LOG_TRACE("Spectator team");
867 LOG_TRACE("Invalid team");
873 /// \brief Updates stats of team count on HUD.
874 /// \return No return.
875 void Surv_UpdateTeamStats()
878 if (surv_attackerteam == NUM_TEAM_1)
880 yellowalive = surv_numattackers;
881 pinkalive = surv_numdefenders;
885 pinkalive = surv_numattackers;
886 yellowalive = surv_numdefenders;
888 FOREACH_CLIENT(IS_REAL_CLIENT(it),
890 it.yellowalive_stat = yellowalive;
891 it.pinkalive_stat = pinkalive;
895 /// \brief Adds player to alive list. Handles bookkeeping information.
896 /// \param[in] player Player to add.
897 /// \param[in] teamnum Team of the player.
898 /// \return No return.
899 void Surv_AddPlayerToAliveList(entity player, int teamnum)
903 case surv_attackerteam:
905 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
907 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
911 case surv_defenderteam:
913 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
919 /// \brief Removes player from alive list. Handles bookkeeping information.
920 /// \param[in] player Player to remove.
921 /// \param[in] teamnum Team of the player.
922 /// \return No return.
923 void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
925 if (player.surv_attack_sprite)
927 WaypointSprite_Kill(player.surv_attack_sprite);
931 case surv_attackerteam:
933 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
935 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
939 case surv_defenderteam:
941 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
943 // This happens during team switch. We don't need to change
947 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
948 if (warmup_stage || surv_allowed_to_spawn || !surv_announcefrags)
952 switch (surv_numdefendersalive)
956 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
957 VOL_BASE, ATTEN_NONE);
958 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
960 if (it.team == surv_defenderteam)
962 Send_Notification(NOTIF_ONE, it, MSG_CENTER,
971 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
972 VOL_BASE, ATTEN_NONE);
977 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
978 VOL_BASE, ATTEN_NONE);
987 /// \brief Counts alive players.
988 /// \return No return.
989 /// \note In a perfect world this function shouldn't exist. However, since QC
990 /// code is so bad and spurious mutators can really mess with your code, this
991 /// function is called as a last resort.
992 void Surv_CountAlivePlayers()
994 int savednumdefenders = surv_numdefendersalive;
995 surv_numattackersalive = 0;
996 surv_numdefendersalive = 0;
997 FOREACH_CLIENT(IS_PLAYER(it),
1001 case surv_attackerteam:
1003 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
1005 ++surv_numattackersalive;
1009 case surv_defenderteam:
1011 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
1013 ++surv_numdefendersalive;
1019 Surv_UpdateAliveStats();
1020 eliminatedPlayers.SendFlags |= 1;
1021 if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
1022 surv_numdefendersalive))
1026 switch (surv_numdefendersalive)
1030 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
1031 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
1033 if (it.team == surv_defenderteam)
1035 Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
1043 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
1049 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
1056 /// \brief Updates stats of alive players on HUD.
1057 /// \return No return.
1058 void Surv_UpdateAliveStats()
1061 if (surv_attackerteam == NUM_TEAM_1)
1063 redalive = surv_numattackersalive;
1064 bluealive = surv_numdefendersalive;
1068 bluealive = surv_numattackersalive;
1069 redalive = surv_numdefendersalive;
1071 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1073 it.surv_defenders_alive_stat = surv_numdefendersalive;
1074 it.redalive_stat = redalive;
1075 it.bluealive_stat = bluealive;
1077 Surv_UpdateDefenderHealthStat();
1080 /// \brief Updates defender health on the HUD.
1081 /// \return No return.
1082 void Surv_UpdateDefenderHealthStat()
1085 float totalhealth = 0;
1086 if (autocvar_g_instagib == 1)
1088 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1089 "surv_defender", "start_armor") + 1);
1090 FOREACH_CLIENT(IS_PLAYER(it),
1092 if (it.team == surv_defenderteam)
1094 totalhealth += it.armorvalue + 1;
1100 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1101 "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
1102 "surv_defender", "start_armor"));
1103 FOREACH_CLIENT(IS_PLAYER(it),
1105 if (it.team == surv_defenderteam)
1107 totalhealth += it.health;
1108 totalhealth += it.armorvalue;
1119 healthratio = totalhealth / maxhealth;
1121 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1123 it.surv_defender_health_stat = healthratio;
1127 /// \brief Returns whether the player can spawn.
1128 /// \param[in] player Player to check.
1129 /// \return True if the player can spawn, false otherwise.
1130 bool Surv_CanPlayerSpawn(entity player)
1132 if ((player.team == surv_attackerteam) ||
1133 (player.surv_savedplayerstate != NULL))
1137 return surv_allowed_to_spawn;
1140 /// \brief Switches the round type.
1141 /// \return No return.
1142 void Surv_SwitchRoundType()
1144 switch (surv_roundtype)
1146 case SURVIVAL_ROUND_FIRST:
1148 surv_roundtype = SURVIVAL_ROUND_SECOND;
1151 case SURVIVAL_ROUND_SECOND:
1153 surv_roundtype = SURVIVAL_ROUND_FIRST;
1159 /// \brief Cleans up the mess after the round has finished.
1160 /// \return No return.
1161 void Surv_RoundCleanup()
1163 surv_allowed_to_spawn = false;
1164 surv_isroundactive = false;
1165 game_stopped = true;
1166 FOREACH_CLIENT(true,
1168 if (it.surv_attack_sprite)
1170 WaypointSprite_Kill(it.surv_attack_sprite);
1172 if (it.surv_savedplayerstate)
1174 delete(it.surv_savedplayerstate);
1175 it.surv_savedplayerstate = NULL;
1178 if (surv_type == SURVIVAL_TYPE_VERSUS)
1180 Surv_SwitchRoundType();
1181 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
1184 round_handler_Init(5, autocvar_g_surv_warmup,
1185 autocvar_g_surv_round_timelimit);
1188 /// \brief Swaps attacker and defender teams.
1189 /// \return No return.
1190 void Surv_SwapTeams()
1192 int temp = surv_attackerteam;
1193 surv_attackerteam = surv_defenderteam;
1194 surv_defenderteam = temp;
1195 temp = surv_attackerteambit;
1196 surv_attackerteambit = surv_defenderteambit;
1197 surv_defenderteambit = temp;
1198 temp = surv_numattackers;
1199 surv_numattackers = surv_numdefenders;
1200 surv_numdefenders = temp;
1201 temp = surv_numattackerhumans;
1202 surv_numattackerhumans = surv_numdefenderhumans;
1203 surv_numdefenderhumans = temp;
1204 FOREACH_CLIENT(true,
1206 if ((it.team == surv_defenderteam) && (it.surv_role ==
1207 SURVIVAL_ROLE_CANNON_FODDER))
1209 SetPlayerTeamSimple(it, surv_attackerteam);
1212 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1214 it.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1218 /// \brief Forces the overkill model for specific player.
1219 /// \param[in,out] player Player to force the model of.
1220 /// \return No return.
1221 void Surv_ForceOverkillPlayerModel(entity player)
1223 switch (player.team)
1227 switch (floor(random() * 4))
1231 player.surv_playermodel = "models/ok_player/okrobot1.dpm";
1236 player.surv_playermodel = "models/ok_player/okrobot2.dpm";
1241 player.surv_playermodel = "models/ok_player/okrobot3.dpm";
1246 player.surv_playermodel = "models/ok_player/okrobot4.dpm";
1254 switch (floor(random() * 4))
1258 player.surv_playermodel = "models/ok_player/okmale1.dpm";
1263 player.surv_playermodel = "models/ok_player/okmale2.dpm";
1268 player.surv_playermodel = "models/ok_player/okmale3.dpm";
1273 player.surv_playermodel = "models/ok_player/okmale4.dpm";
1282 /// \brief Determines the player model to the one configured for the gamemode.
1283 /// \param[in,out] player Player to determine the model of.
1284 /// \return No return.
1285 void Surv_DeterminePlayerModel(entity player)
1287 switch (player.team)
1289 case surv_attackerteam:
1291 switch (player.surv_role)
1293 case SURVIVAL_ROLE_PLAYER:
1295 if (!autocvar_g_surv_attacker_force_overkill_models)
1297 player.surv_playermodel = player.surv_savedplayermodel;
1300 Surv_ForceOverkillPlayerModel(player);
1303 case SURVIVAL_ROLE_CANNON_FODDER:
1305 if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
1307 player.surv_playermodel = player.surv_savedplayermodel;
1310 Surv_ForceOverkillPlayerModel(player);
1315 case surv_defenderteam:
1317 if (!autocvar_g_surv_defender_force_overkill_models)
1319 player.surv_playermodel = player.surv_savedplayermodel;
1322 Surv_ForceOverkillPlayerModel(player);
1328 /// \brief Setups a waypoint sprite used to track defenders.
1329 /// \param[in] player Player to attach sprite too.
1330 /// \return No return.
1331 void Surv_SetupWaypointSprite(entity player)
1333 WaypointSprite_Spawn(WP_AssaultDestroy, 0, 0, player, '0 0 64', NULL,
1334 surv_attackerteam, player, surv_attack_sprite, false,
1335 RADARICON_OBJECTIVE);
1336 if (autocvar_g_instagib == 1)
1338 WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite,
1339 PlayerTemplate_GetFloatValue("surv_defender", "start_armor") + 1);
1340 WaypointSprite_UpdateHealth(player.surv_attack_sprite,
1341 player.armorvalue + 1);
1344 WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite,
1345 PlayerTemplate_GetFloatValue("surv_defender", "start_health") +
1346 PlayerTemplate_GetFloatValue("surv_defender", "start_armor"));
1347 WaypointSprite_UpdateHealth(player.surv_attack_sprite, player.health +
1351 //=============================== Callbacks ===================================
1353 bool Surv_CanRoundStart()
1355 return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
1358 bool Surv_CanRoundEnd()
1364 if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
1367 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1369 surv_timetobeat = time - surv_roundstarttime;
1370 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1371 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1372 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1373 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1374 Surv_RoundCleanup();
1377 surv_timetobeat = autocvar_g_surv_round_timelimit;
1378 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1379 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1380 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1381 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1382 switch (surv_defenderteam)
1386 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1392 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1397 TeamScore_AddToTeam(surv_defenderteam, 1, 1);
1398 Surv_RoundCleanup();
1401 if (surv_numdefendersalive > 0)
1405 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1407 surv_timetobeat = time - surv_roundstarttime;
1408 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1409 CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1410 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1411 INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1412 Surv_RoundCleanup();
1415 surv_timetobeat = autocvar_g_surv_round_timelimit;
1416 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1417 CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
1418 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1419 INFO_SURVIVAL_DEFENDERS_ELIMINATED);
1420 switch (surv_attackerteam)
1424 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1430 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1435 TeamScore_AddToTeam(surv_attackerteam, 1, 1);
1436 Surv_RoundCleanup();
1440 void Surv_RoundStart()
1444 surv_allowed_to_spawn = true;
1447 surv_isroundactive = true;
1448 surv_roundstarttime = time;
1449 surv_allowed_to_spawn = false;
1450 switch (surv_roundtype)
1452 case SURVIVAL_ROUND_FIRST:
1454 FOREACH_CLIENT(IS_PLAYER(it),
1456 if (it.team == surv_attackerteam)
1458 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1459 CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
1460 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1461 INFO_SURVIVAL_1ST_ROUND_ATTACKER);
1465 FOREACH_CLIENT(IS_PLAYER(it),
1467 if (it.team == surv_defenderteam)
1469 if (surv_type == SURVIVAL_TYPE_COOP)
1471 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1472 CENTER_SURVIVAL_COOP_DEFENDER);
1473 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1474 INFO_SURVIVAL_COOP_DEFENDER);
1477 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1478 CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
1479 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1480 INFO_SURVIVAL_1ST_ROUND_DEFENDER);
1486 case SURVIVAL_ROUND_SECOND:
1488 FOREACH_CLIENT(IS_PLAYER(it),
1490 if (it.team == surv_attackerteam)
1492 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1493 CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1494 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1495 INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1499 FOREACH_CLIENT(IS_PLAYER(it),
1501 if (it.team == surv_defenderteam)
1503 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1504 CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1505 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1506 INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1513 if (autocvar_g_surv_stealth)
1517 FOREACH_CLIENT(IS_PLAYER(it),
1521 case surv_defenderteam:
1523 if (it.surv_role == SURVIVAL_ROLE_PLAYER)
1525 Surv_SetupWaypointSprite(it);
1533 bool Surv_IsEliminated(entity player)
1535 switch (player.surv_state)
1537 case SURVIVAL_STATE_NOT_PLAYING:
1541 case SURVIVAL_STATE_PLAYING:
1543 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
1545 // A hack until proper scoreboard is done.
1548 if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
1549 IS_OBSERVER(player)))
1556 // Should never reach here
1560 //============================= Hooks ========================================
1562 /// \brief Hook that is called to determine general rules of the game.
1563 MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
1565 surv_warmup = warmup_stage;
1568 /// \brief Hook that is called to determine if there is a weapon arena.
1569 MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
1571 // Removing any weapon arena.
1572 M_ARGV(0, string) = "off";
1575 /// \brief Hook that is called to determine start items of all players.
1576 MUTATOR_HOOKFUNCTION(surv, SetStartItems)
1578 if (autocvar_g_instagib == 1)
1582 start_weapons = WEPSET(Null);
1583 warmup_start_weapons = WEPSET(Null);
1586 /// \brief Hook that is called on every frame.
1587 MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
1589 if (game_stopped || !surv_isroundactive)
1593 float roundtime = 0;
1594 switch (surv_roundtype)
1596 case SURVIVAL_ROUND_FIRST:
1598 roundtime = time - surv_roundstarttime;
1601 case SURVIVAL_ROUND_SECOND:
1603 roundtime = round_handler_GetEndTime() - time;
1607 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1609 it.surv_round_time_stat = roundtime;
1613 /// \brief Hook that determines which team player can join. This is called
1614 /// before ClientConnect.
1615 MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
1617 entity player = M_ARGV(2, entity);
1618 LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
1621 return SURVIVAL_TEAM_BITS;
1623 if (IS_BOT_CLIENT(player))
1625 int teambits = surv_attackerteambit;
1626 if ((player.team == surv_defenderteam) || (surv_numdefenders <
1627 autocvar_g_surv_team_size))
1629 teambits |= surv_defenderteambit;
1631 M_ARGV(0, float) = teambits;
1634 if (surv_type == SURVIVAL_TYPE_COOP)
1636 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1638 M_ARGV(0, float) = surv_defenderteambit;
1641 M_ARGV(0, float) = 0;
1645 if (surv_numattackerhumans < autocvar_g_surv_team_size)
1647 LOG_TRACE("Player can join attackers");
1648 teambits |= surv_attackerteambit;
1650 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1652 LOG_TRACE("Player can join defenders");
1653 teambits |= surv_defenderteambit;
1655 M_ARGV(0, float) = teambits;
1659 /// \brief Hook that override team counts.
1660 MUTATOR_HOOKFUNCTION(surv, GetTeamCounts, CBC_ORDER_EXCLUSIVE)
1665 /// \brief Hook that sets the team count.
1666 MUTATOR_HOOKFUNCTION(surv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
1668 float teamnum = M_ARGV(0, float);
1669 entity ignore = M_ARGV(1, entity);
1672 case surv_attackerteam:
1674 M_ARGV(2, float) = surv_numattackers;
1675 M_ARGV(3, float) = surv_numattackers - surv_numattackerhumans;
1676 entity lowestplayer = NULL;
1677 float lowestplayerscore = FLOAT_MAX;
1678 entity lowestbot = NULL;
1679 float lowestbotscore = FLOAT_MAX;
1680 FOREACH_CLIENT((it.team == surv_attackerteam) && (it.surv_role ==
1681 SURVIVAL_ROLE_PLAYER),
1687 if (IS_BOT_CLIENT(it))
1689 float tempscore = PlayerScore_Get(it, SP_SCORE);
1690 if (tempscore < lowestbotscore)
1693 lowestbotscore = tempscore;
1697 float tempscore = PlayerScore_Get(it, SP_SCORE);
1698 if (tempscore < lowestplayerscore)
1701 lowestplayerscore = tempscore;
1704 M_ARGV(4, entity) = lowestplayer;
1705 M_ARGV(5, entity) = lowestbot;
1706 if (ignore.team == surv_attackerteam)
1709 if (IS_BOT_CLIENT(ignore))
1716 case surv_defenderteam:
1718 M_ARGV(2, float) = surv_numdefenders;
1719 M_ARGV(3, float) = surv_numdefenders - surv_numdefenderhumans;
1720 entity lowestplayer = NULL;
1721 float lowestplayerscore = FLOAT_MAX;
1722 entity lowestbot = NULL;
1723 float lowestbotscore = FLOAT_MAX;
1724 FOREACH_CLIENT((it.team == surv_defenderteam),
1730 if (IS_BOT_CLIENT(it))
1732 float tempscore = PlayerScore_Get(it, SP_SCORE);
1733 if (tempscore < lowestbotscore)
1736 lowestbotscore = tempscore;
1740 float tempscore = PlayerScore_Get(it, SP_SCORE);
1741 if (tempscore < lowestplayerscore)
1744 lowestplayerscore = tempscore;
1747 M_ARGV(4, entity) = lowestplayer;
1748 M_ARGV(5, entity) = lowestbot;
1749 if (ignore.team == surv_defenderteam)
1752 if (IS_BOT_CLIENT(ignore))
1763 /// \brief Hook that determines the best teams for the player to join.
1764 MUTATOR_HOOKFUNCTION(surv, FindBestTeams, CBC_ORDER_EXCLUSIVE)
1766 if (surv_type == SURVIVAL_TYPE_COOP)
1770 entity player = M_ARGV(0, entity);
1771 if (IS_BOT_CLIENT(player))
1775 int numattackerhumans = surv_numattackerhumans;
1776 int numdefenderhumans = surv_numdefenderhumans;
1777 if (player.team == surv_attackerteam)
1779 --numattackerhumans;
1781 else if (player.team == surv_defenderteam)
1783 --numdefenderhumans;
1785 if (numattackerhumans < numdefenderhumans)
1787 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_attackerteam) - 1);
1790 if (numattackerhumans > numdefenderhumans)
1792 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_defenderteam) - 1);
1795 M_ARGV(1, float) = SURVIVAL_TEAM_BITS;
1799 /// \brief Hook that is called when player has changed the team.
1800 MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
1802 entity player = M_ARGV(0, entity);
1803 int oldteam = M_ARGV(1, float);
1804 int newteam = M_ARGV(2, float);
1805 string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
1806 ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
1808 DebugPrintToChatAll(message);
1809 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1811 Surv_RemovePlayerFromAliveList(player, oldteam);
1813 Surv_RemovePlayerFromTeam(player, oldteam);
1814 if (Surv_AddPlayerToTeam(player, newteam) == false)
1818 //Surv_CountAlivePlayers();
1819 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1821 Surv_AddPlayerToAliveList(player, newteam);
1825 /// \brief Hook that is called when player is about to be killed when changing
1827 MUTATOR_HOOKFUNCTION(surv, Player_ChangeTeamKill)
1829 entity player = M_ARGV(0, entity);
1830 if (player.team != surv_defenderteam)
1834 if (player.surv_savedplayerstate == NULL)
1838 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1839 delete(player.surv_savedplayerstate);
1840 player.surv_savedplayerstate = NULL;
1844 /// \brief Hook that is called when player is about to be killed as a result of
1845 /// the kill command or changing teams.
1846 MUTATOR_HOOKFUNCTION(surv, ClientKill_Now)
1848 entity player = M_ARGV(0, entity);
1849 if (player.team == surv_defenderteam)
1856 /// \brief Hook that is called when player connects to the server.
1857 MUTATOR_HOOKFUNCTION(surv, ClientConnect)
1859 entity player = M_ARGV(0, entity);
1860 LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
1861 player.surv_savedplayermodel = player.playermodel;
1862 if (IS_REAL_CLIENT(player))
1864 player.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1865 player.surv_defenders_alive_stat = surv_numdefendersalive;
1866 player.redalive_stat = redalive;
1867 player.bluealive_stat = bluealive;
1868 player.yellowalive_stat = yellowalive;
1869 player.pinkalive_stat = pinkalive;
1871 if (player.surv_role == SURVIVAL_ROLE_NONE)
1873 Surv_AddPlayerToTeam(player, player.team);
1878 /// \brief Hook that is called when player disconnects from the server.
1879 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
1881 entity player = M_ARGV(0, entity);
1882 if (!IS_DEAD(player))
1884 Surv_RemovePlayerFromAliveList(player, player.team);
1886 Surv_RemovePlayerFromTeam(player, player.team);
1887 //Surv_CountAlivePlayers();
1890 /// \brief Hook that determines whether player can spawn. It is not called for
1891 /// players who have joined the team and are dead.
1892 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
1894 entity player = M_ARGV(0, entity);
1895 LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
1896 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1900 return !Surv_CanPlayerSpawn(player);
1903 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
1905 entity player = M_ARGV(0, entity);
1906 LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
1907 if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
1909 LOG_TRACE("Transmuting to observer");
1910 TRANSMUTE(Observer, player);
1914 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
1916 entity player = M_ARGV(0, entity);
1917 LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
1918 if (player.killindicator_teamchange == -2) // player wants to spectate
1920 LOG_TRACE("killindicator_teamchange == -2");
1921 player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
1923 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1925 return false; // allow team reset
1927 return true; // prevent team reset
1930 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
1932 LOG_TRACE("Survival: reset_map_global");
1933 surv_allowed_to_spawn = true;
1934 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1936 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1938 it.surv_round_time_stat = 0;
1944 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
1946 LOG_TRACE("Survival: reset_map_players");
1947 surv_numattackersalive = 0;
1948 surv_numdefendersalive = 0;
1951 surv_warmup = false;
1953 else if (surv_type == SURVIVAL_TYPE_VERSUS)
1957 FOREACH_CLIENT(true,
1960 if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
1963 it.surv_state = SURVIVAL_STATE_PLAYING;
1965 if (it.surv_state == SURVIVAL_STATE_PLAYING)
1967 TRANSMUTE(Player, it);
1968 it.surv_state = SURVIVAL_STATE_PLAYING;
1969 PutClientInServer(it);
1972 bot_relinkplayerlist();
1976 /// \brief Hook that is called when player spawns.
1977 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
1979 entity player = M_ARGV(0, entity);
1980 LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
1981 player.surv_state = SURVIVAL_STATE_PLAYING;
1982 Surv_DeterminePlayerModel(player);
1983 if (player.surv_savedplayerstate != NULL)
1985 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1986 delete(player.surv_savedplayerstate);
1987 player.surv_savedplayerstate = NULL;
1991 PlayerTemplate_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
1993 switch (player.team)
1995 case surv_attackerteam:
1997 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
1999 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
2000 CENTER_ASSAULT_ATTACKING);
2004 case surv_defenderteam:
2006 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
2007 CENTER_ASSAULT_DEFENDING);
2011 //Surv_CountAlivePlayers();
2012 Surv_AddPlayerToAliveList(player, player.team);
2015 /// \brief UGLY HACK. This is called every frame to keep player model correct.
2016 MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
2018 entity player = M_ARGV(2, entity);
2019 M_ARGV(0, string) = player.surv_playermodel;
2022 /// \brief Hook that is called every frame to determine how player health should
2024 MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
2026 entity player = M_ARGV(0, entity);
2027 if (player.team == surv_defenderteam)
2034 /// \brief Hook that is called to determine if balance messages will appear.
2035 MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
2040 /// \brief Hook that is called when player touches an item.
2041 MUTATOR_HOOKFUNCTION(surv, ItemTouch)
2043 entity item = M_ARGV(0, entity);
2044 entity player = M_ARGV(1, entity);
2045 switch (player.team)
2047 case surv_attackerteam:
2049 return PlayerTemplate_ItemTouch(player, item,
2050 Surv_GetPlayerTemplate(player));
2052 case surv_defenderteam:
2054 switch (item.classname)
2056 case "item_strength":
2058 W_GiveWeapon(player, WEP_HMG.m_id);
2059 player.superweapons_finished = max(
2060 player.superweapons_finished, time) +
2061 autocvar_g_balance_superweapons_time;
2062 Item_ScheduleRespawn(item);
2063 sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
2065 return MUT_ITEMTOUCH_RETURN;
2067 case "item_invincible":
2069 W_GiveWeapon(player, WEP_RPC.m_id);
2070 player.superweapons_finished = max(
2071 player.superweapons_finished, time) +
2072 autocvar_g_balance_superweapons_time;
2073 Item_ScheduleRespawn(item);
2074 sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
2075 return MUT_ITEMTOUCH_RETURN;
2079 return PlayerTemplate_ItemTouch(player, item,
2080 Surv_GetPlayerTemplate(player));
2083 DebugPrintToChat(player, item.classname);
2084 return MUT_ITEMTOUCH_RETURN;
2087 return MUT_ITEMTOUCH_CONTINUE;
2090 /// \brief Hook which is called when the player tries to throw their weapon.
2091 MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
2093 entity player = M_ARGV(0, entity);
2094 if (player.team == surv_defenderteam)
2100 /// \brief Hook which is called when the damage amount must be determined.
2101 MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
2103 entity frag_attacker = M_ARGV(1, entity);
2104 entity frag_target = M_ARGV(2, entity);
2105 float deathtype = M_ARGV(3, float);
2106 float damage = M_ARGV(4, float);
2107 M_ARGV(4, float) = PlayerTemplate_Damage_Calculate(frag_attacker,
2108 Surv_GetPlayerTemplate(frag_attacker), frag_target,
2109 Surv_GetPlayerTemplate(frag_target), deathtype, damage);
2112 /// \brief Hook which is called when the player was damaged.
2113 MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
2115 entity target = M_ARGV(1, entity);
2116 if (target.team != surv_defenderteam)
2120 Surv_UpdateDefenderHealthStat();
2121 entity attacker = M_ARGV(0, entity);
2122 if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
2123 SURVIVAL_ROLE_PLAYER))
2125 float health = M_ARGV(2, float);
2126 float armor = M_ARGV(3, float);
2127 float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
2128 PlayerScore_Add(attacker, SP_SCORE, score);
2130 if (autocvar_g_surv_stealth)
2134 if (target.health < 1)
2136 WaypointSprite_Kill(target.surv_attack_sprite);
2140 if (autocvar_g_instagib == 1)
2142 WaypointSprite_UpdateHealth(target.surv_attack_sprite,
2143 target.armorvalue + 1);
2147 WaypointSprite_UpdateHealth(target.surv_attack_sprite,
2148 target.health + target.armorvalue);
2153 /// \brief Hook which is called when the player dies.
2154 MUTATOR_HOOKFUNCTION(surv, PlayerDies)
2156 //DebugPrintToChatAll("PlayerDies");
2157 entity attacker = M_ARGV(1, entity);
2158 entity victim = M_ARGV(2, entity);
2159 if ((attacker.team == surv_defenderteam) &&
2160 (victim.team == surv_attackerteam))
2162 switch (victim.surv_role)
2164 case SURVIVAL_ROLE_PLAYER:
2166 GivePlayerHealth(attacker,
2167 autocvar_g_surv_defender_attacker_frag_health);
2168 GivePlayerArmor(attacker,
2169 autocvar_g_surv_defender_attacker_frag_armor);
2170 GivePlayerAmmo(attacker, ammo_shells,
2171 autocvar_g_surv_defender_attacker_frag_shells);
2172 GivePlayerAmmo(attacker, ammo_nails,
2173 autocvar_g_surv_defender_attacker_frag_bullets);
2174 GivePlayerAmmo(attacker, ammo_rockets,
2175 autocvar_g_surv_defender_attacker_frag_rockets);
2176 GivePlayerAmmo(attacker, ammo_cells,
2177 autocvar_g_surv_defender_attacker_frag_cells);
2180 case SURVIVAL_ROLE_CANNON_FODDER:
2182 GivePlayerHealth(attacker,
2183 autocvar_g_surv_defender_cannon_fodder_frag_health);
2184 GivePlayerArmor(attacker,
2185 autocvar_g_surv_defender_cannon_fodder_frag_armor);
2186 GivePlayerAmmo(attacker, ammo_shells,
2187 autocvar_g_surv_defender_cannon_fodder_frag_shells);
2188 GivePlayerAmmo(attacker, ammo_nails,
2189 autocvar_g_surv_defender_cannon_fodder_frag_bullets);
2190 GivePlayerAmmo(attacker, ammo_rockets,
2191 autocvar_g_surv_defender_cannon_fodder_frag_rockets);
2192 GivePlayerAmmo(attacker, ammo_cells,
2193 autocvar_g_surv_defender_cannon_fodder_frag_cells);
2198 if ((victim.team == surv_defenderteam) &&
2199 (autocvar_g_surv_defender_drop_weapons == false))
2201 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2203 .entity went = weaponentities[slot];
2204 victim.(went).m_weapon = WEP_Null;
2207 if (!Surv_CanPlayerSpawn(victim))
2209 victim.respawn_flags = RESPAWN_SILENT;
2210 if (IS_BOT_CLIENT(victim))
2218 /// \brief Hook which is called after the player died.
2219 MUTATOR_HOOKFUNCTION(surv, PlayerDied)
2221 //DebugPrintToChatAll("PlayerDied");
2222 entity player = M_ARGV(0, entity);
2223 Surv_RemovePlayerFromAliveList(player, player.team);
2224 //Surv_CountAlivePlayers();
2227 /// \brief Hook which is called when player has scored a frag.
2228 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
2230 if (surv_type == SURVIVAL_TYPE_COOP)
2234 entity attacker = M_ARGV(0, entity);
2235 if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
2236 SURVIVAL_ROLE_CANNON_FODDER))
2238 M_ARGV(2, float) = 0;
2241 entity target = M_ARGV(1, entity);
2242 if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
2245 M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
2250 /// \brief I'm not sure exactly what this function does but it is very
2251 /// important. Without it bots are completely broken. Is it a hack? Of course.
2252 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
2254 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
2255 if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
2264 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
2266 // Don't announce remaining frags