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;
67 /// \brief Whether to allow spectating enemy players while dead.
68 bool autocvar_g_surv_spectate_enemies;
70 /// \brief Whether to force overkill player models for attackers.
71 int autocvar_g_surv_attacker_force_overkill_models;
72 /// \brief Whether to force overkill player models for defenders.
73 int autocvar_g_surv_defender_force_overkill_models;
74 /// \brief Whether to force overkill player models for cannon fodder.
75 int autocvar_g_surv_cannon_fodder_force_overkill_models;
77 /// \brief How much score attackers gain per 1 point of damage.
78 float autocvar_g_surv_attacker_damage_score;
80 /// \brief How much score attackers get for fragging defenders.
81 float autocvar_g_surv_attacker_frag_score;
83 /// \brief How much health do defenders get when they frag an attacker.
84 int autocvar_g_surv_defender_attacker_frag_health;
85 /// \brief How much armor do defenders get when they frag an attacker.
86 int autocvar_g_surv_defender_attacker_frag_armor;
87 /// \brief How many shells do defenders get when they frag an attacker.
88 int autocvar_g_surv_defender_attacker_frag_shells;
89 /// \brief How many bullets do defenders get when they frag an attacker.
90 int autocvar_g_surv_defender_attacker_frag_bullets;
91 /// \brief How many rockets do defenders get when they frag an attacker.
92 int autocvar_g_surv_defender_attacker_frag_rockets;
93 /// \brief How many cells do defenders get when they frag an attacker.
94 int autocvar_g_surv_defender_attacker_frag_cells;
95 /// \brief How much health do defenders get when they frag cannon fodder.
96 int autocvar_g_surv_defender_cannon_fodder_frag_health;
97 /// \brief How much armor do defenders get when they frag cannon fodder.
98 int autocvar_g_surv_defender_cannon_fodder_frag_armor;
99 /// \brief How many shells do defenders get when they frag cannon fodder.
100 int autocvar_g_surv_defender_cannon_fodder_frag_shells;
101 /// \brief How many bullets do defenders get when they frag cannon fodder.
102 int autocvar_g_surv_defender_cannon_fodder_frag_bullets;
103 /// \brief How many rockets do defenders get when they frag cannon fodder.
104 int autocvar_g_surv_defender_cannon_fodder_frag_rockets;
105 /// \brief How many cells do defenders get when they frag cannon fodder.
106 int autocvar_g_surv_defender_cannon_fodder_frag_cells;
108 /// \brief Whether defenders drop weapons after death.
109 int autocvar_g_surv_defender_drop_weapons;
111 /// \brief A stat that is used to track the time left in the round.
112 .float surv_round_time_stat = _STAT(SURV_ROUND_TIME);
113 /// \brief A stat that is used to track defender team.
114 .int surv_defender_team_stat = _STAT(SURV_DEFENDER_TEAM);
115 /// \brief A stat that is used to track number of defenders alive.
116 .int surv_defenders_alive_stat = _STAT(SURV_DEFENDERS_ALIVE);
117 /// \brief A stat that is used to track the total health of defenders.
118 .float surv_defender_health_stat = _STAT(SURV_DEFENDER_HEALTH);
120 /// \brief Holds the state of the player. See SURVIVAL_STATE constants.
122 /// \brief Holds the role of the player. See SURVIVAL_ROLE constants.
124 .string surv_savedplayermodel; ///< Initial player model.
125 /// \brief Player state used during replacement of bot player with real player.
126 .entity surv_savedplayerstate;
127 .string surv_playermodel; ///< Player model forced by the game.
129 .entity surv_attack_sprite; ///< Holds the sprite telling attackers to attack.
131 int surv_type; ///< Holds the type of survival. See SURVIVAL_TYPE constants.
132 bool surv_warmup; ///< Holds whether warmup is active.
133 /// \brief Holds the type of the current round. See SURVIVAL_ROUND constants.
135 bool surv_isroundactive; ///< Holds whether the round is active.
136 float surv_roundstarttime; ///< Holds the time of the round start.
137 /// \brief Holds the time needed to survive in the second round.
138 float surv_timetobeat;
140 int surv_attackerteam; ///< Holds the attacker team.
141 int surv_defenderteam; ///< Holds the defender team.
143 int surv_attackerteambit; ///< Hols the attacker team bitmask.
144 int surv_defenderteambit; ///< Holds the defender team bitmask.
146 int surv_numattackers; ///< Holds the number of players in attacker team.
147 int surv_numdefenders; ///< Holds the number of players in defender team.
149 /// \brief Holds the number of humans in attacker team.
150 int surv_numattackerhumans;
151 /// \brief Holds the number of humans in defender team.
152 int surv_numdefenderhumans;
154 /// \brief Holds the number of attacker players that are alive.
155 int surv_numattackersalive;
156 /// \brief Holds the number of defender players that are alive.
157 int surv_numdefendersalive;
159 bool surv_autobalance; ///< Holds whether autobalance is active.
160 bool surv_announcefrags; ///< Holds whether remaining frags must be announced.
161 bool surv_allowed_to_spawn; ///< Holds whether players are allowed to spawn.
163 //====================== Forward declarations =================================
165 /// \brief Determines whether the round can start.
166 /// \return True if the round can start, false otherwise.
167 bool Surv_CanRoundStart();
169 /// \brief Determines whether the round can end.
170 /// \return True if the round can end, false otherwise.
171 bool Surv_CanRoundEnd();
173 /// \brief Called when the round starts.
174 /// \return No return.
175 void Surv_RoundStart();
177 /// \brief Returns whether player has been eliminated.
178 /// \param[in] player Player to check.
179 /// \return True if player is eliminated, false otherwise.
180 bool Surv_IsEliminated(entity player);
182 /// \brief Updates stats of team count on HUD.
183 /// \return No return.
184 void Surv_UpdateTeamStats();
186 /// \brief Updates stats of alive players on HUD.
187 /// \return No return.
188 void Surv_UpdateAliveStats();
190 /// \brief Updates defender health on the HUD.
191 /// \return No return.
192 void Surv_UpdateDefenderHealthStat();
194 //========================= Free functions ====================================
196 void Surv_Initialize()
198 switch (cvar_string("g_surv_type"))
200 case SURVIVAL_TYPE_COOP_VALUE:
202 surv_type = SURVIVAL_TYPE_COOP;
205 case SURVIVAL_TYPE_VERSUS_VALUE:
207 surv_type = SURVIVAL_TYPE_VERSUS;
212 error("Invalid survival type.");
215 surv_roundtype = SURVIVAL_ROUND_FIRST;
216 surv_isroundactive = false;
217 surv_roundstarttime = time;
218 surv_timetobeat = autocvar_g_surv_round_timelimit;
221 surv_attackerteam = NUM_TEAM_1;
222 surv_defenderteam = NUM_TEAM_2;
223 surv_attackerteambit = SURVIVAL_TEAM_1_BIT;
224 surv_defenderteambit = SURVIVAL_TEAM_2_BIT;
228 surv_attackerteam = NUM_TEAM_2;
229 surv_defenderteam = NUM_TEAM_1;
230 surv_attackerteambit = SURVIVAL_TEAM_2_BIT;
231 surv_defenderteambit = SURVIVAL_TEAM_1_BIT;
233 surv_numattackers = 0;
234 surv_numdefenders = 0;
235 surv_numattackerhumans = 0;
236 surv_numdefenderhumans = 0;
237 surv_numattackersalive = 0;
238 surv_numdefendersalive = 0;
239 surv_autobalance = true;
240 surv_announcefrags = true;
241 surv_allowed_to_spawn = true;
242 precache_all_playermodels("models/ok_player/*.dpm");
243 ScoreRules_basics(SURVIVAL_TEAM_BITS, SFL_SORT_PRIO_PRIMARY, 0, true);
244 ScoreInfo_SetLabel_TeamScore(1, "rounds", SFL_SORT_PRIO_PRIMARY);
245 ScoreRules_basics_end();
246 round_handler_Spawn(Surv_CanRoundStart, Surv_CanRoundEnd, Surv_RoundStart);
247 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
248 EliminatedPlayers_Init(Surv_IsEliminated);
250 SetLimits(autocvar_g_surv_point_limit, autocvar_g_surv_point_leadlimit,
251 autocvar_timelimit_override, -1);
254 /// \brief Returns the name of the template of the given player.
255 /// \param[in] player Player to inspect.
256 /// \return Name of the template of the given player.
257 string Surv_GetPlayerTemplate(entity player)
261 case surv_attackerteam:
263 switch (player.surv_role)
265 case SURVIVAL_ROLE_NONE:
266 case SURVIVAL_ROLE_PLAYER:
268 return cvar_string("g_surv_attacker_template");
270 case SURVIVAL_ROLE_CANNON_FODDER:
272 return cvar_string("g_surv_cannon_fodder_template");
276 case surv_defenderteam:
278 return cvar_string("g_surv_defender_template");
284 /// \brief Saves the player state. Used to seamlessly swap bots with humans.
285 /// \param[in] player Player to save the state of.
286 /// \return Entity containing the player state.
287 entity Surv_SavePlayerState(entity player)
289 entity state = spawn();
290 state.origin = player.origin;
291 state.velocity = player.velocity;
292 state.angles = player.angles;
293 state.health = player.health;
294 state.armorvalue = player.armorvalue;
295 state.ammo_shells = player.ammo_shells;
296 state.ammo_nails = player.ammo_nails;
297 state.ammo_rockets = player.ammo_rockets;
298 state.ammo_cells = player.ammo_cells;
299 state.weapons = player.weapons;
300 state.items = player.items;
301 state.superweapons_finished = player.superweapons_finished;
305 /// \brief Restores a saved player state.
306 /// \param[in] player Player to restore the state of.
307 /// \param[in] st State to restore.
308 /// \return No return.
309 void Surv_RestorePlayerState(entity player, entity st)
311 player.origin = st.origin;
312 player.velocity = st.velocity;
313 player.angles = st.angles;
314 player.health = st.health;
315 player.armorvalue = st.armorvalue;
316 player.ammo_shells = st.ammo_shells;
317 player.ammo_nails = st.ammo_nails;
318 player.ammo_rockets = st.ammo_rockets;
319 player.ammo_cells = st.ammo_cells;
320 player.weapons = st.weapons;
321 player.items = st.items;
322 player.superweapons_finished = st.superweapons_finished;
325 /// \brief Returns the attacker with the lowest score.
326 /// \param[in] bot Whether to search only for bots.
327 /// \return Attacker with the lowest score or NULL if not found.
328 entity Surv_FindLowestAttacker(bool bot)
330 entity player = NULL;
331 float score = FLOAT_MAX;
332 FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
334 if ((it.team == surv_attackerteam) && (it.surv_role ==
335 SURVIVAL_ROLE_PLAYER))
337 float tempscore = PlayerScore_Get(it, SP_SCORE);
338 if (tempscore < score)
348 /// \brief Returns the defender with the lowest score.
349 /// \param[in] bot Whether to search only for bots.
350 /// \param[in] alive Whether to search only for alive players.
351 /// \return Defender with the lowest score or NULL if not found.
352 entity Surv_FindLowestDefender(bool bot, bool alive)
354 entity player = NULL;
355 float score = FLOAT_MAX;
356 FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
358 if ((it.team == surv_defenderteam) && (alive ? !IS_DEAD(it) : true))
360 float tempscore = PlayerScore_Get(it, SP_SCORE);
361 if (tempscore < score)
371 /// \brief Returns the cannon fodder.
372 /// \return Cannon fodder or NULL if not found.
373 entity Surv_FindCannonFodder()
375 FOREACH_CLIENT(IS_BOT_CLIENT(it),
377 if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
385 /// \brief Changes the number of players in a team.
386 /// \param[in] teamnum Team to adjust.
387 /// \param[in] delta Amount to adjust by.
388 /// \return No return.
389 void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
393 case surv_attackerteam:
395 surv_numattackers += delta;
396 LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
397 " was = ", ftos(surv_numattackers - delta));
398 Surv_UpdateTeamStats();
401 case surv_defenderteam:
403 surv_numdefenders += delta;
404 LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
405 " was = ", ftos(surv_numdefenders - delta));
406 Surv_UpdateTeamStats();
412 /// \brief Changes the number of alive players in a team.
413 /// \param[in] teamnum Team to adjust.
414 /// \param[in] delta Amount to adjust by.
415 /// \return No return.
416 void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
420 case surv_attackerteam:
422 surv_numattackersalive += delta;
423 LOG_TRACE("Number of alive attackers = ", ftos(
424 surv_numattackersalive), " was = ", ftos(surv_numattackersalive
428 case surv_defenderteam:
430 surv_numdefendersalive += delta;
431 LOG_TRACE("Number of alive defenders = ", ftos(
432 surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
437 Surv_UpdateAliveStats();
438 eliminatedPlayers.SendFlags |= 1;
441 /// \brief Sets the player role.
442 /// \param[in,out] player Player to adjust.
443 /// \param[in] role Role to set.
444 /// \return No return.
445 void Surv_SetPlayerRole(entity player, int role)
447 if (player.surv_role == role)
451 player.surv_role = role;
454 case SURVIVAL_ROLE_NONE:
456 LOG_TRACE(player.netname, " now has no role.");
459 case SURVIVAL_ROLE_PLAYER:
461 LOG_TRACE(player.netname, " is now a player.");
464 case SURVIVAL_ROLE_CANNON_FODDER:
466 LOG_TRACE(player.netname, " is now a cannon fodder.");
472 /// \brief Adds player to team. Handles bookkeeping information.
473 /// \param[in] player Player to add.
474 /// \param[in] teamnum Team to add to.
475 /// \return True on success, false otherwise.
476 bool Surv_AddPlayerToTeam(entity player, int teamnum)
478 LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
481 case surv_attackerteam:
483 LOG_TRACE("Attacker team");
484 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
486 LOG_TRACE("Cannon fodder is switching team");
489 if (IS_BOT_CLIENT(player))
491 LOG_TRACE("Client is bot");
492 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
493 if (surv_numattackers < autocvar_g_surv_team_size)
495 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
496 Surv_ChangeNumberOfPlayers(teamnum, +1);
499 Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
502 LOG_TRACE("Client is not a bot");
503 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
504 if (surv_numattackers >= autocvar_g_surv_team_size)
506 LOG_TRACE("Removing bot");
507 // Remove bot to make space for human.
508 entity bot = Surv_FindLowestAttacker(true);
511 LOG_TRACE("No valid bot to remove");
512 // No space in team, denying team change.
513 TRANSMUTE(Spectator, player);
516 LOG_TRACE("Changing ", bot.netname,
517 " from attacker to cannon fodder.");
518 Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
521 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
523 Surv_ChangeNumberOfPlayers(teamnum, -1);
524 LOG_TRACE("Removed bot");
526 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
527 Surv_ChangeNumberOfPlayers(teamnum, +1);
528 ++surv_numattackerhumans;
529 LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
530 if ((surv_autobalance == false) || (surv_numattackers -
531 surv_numdefenders) < 2)
535 entity lowestplayer = Surv_FindLowestAttacker(true);
536 if (lowestplayer != NULL)
538 bool savedautobalance = surv_autobalance;
539 surv_autobalance = false;
540 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
541 surv_autobalance = savedautobalance;
544 lowestplayer = Surv_FindLowestAttacker(false);
545 if (lowestplayer != NULL)
547 bool savedautobalance = surv_autobalance;
548 surv_autobalance = false;
549 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
550 surv_autobalance = savedautobalance;
554 case surv_defenderteam:
556 LOG_TRACE("Defender team");
557 if (IS_BOT_CLIENT(player))
559 LOG_TRACE("Client is bot");
560 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
561 if (surv_numdefenders < autocvar_g_surv_team_size)
563 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
564 Surv_ChangeNumberOfPlayers(teamnum, +1);
567 LOG_TRACE("No space for defender, switching to attacker");
568 SetPlayerTeamSimple(player, surv_attackerteam);
571 LOG_TRACE("Client is not a bot");
572 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
573 if (surv_numdefenders >= autocvar_g_surv_team_size)
575 LOG_TRACE("Removing bot");
576 // Remove bot to make space for human.
577 entity bot = Surv_FindLowestDefender(true, true);
580 bot = Surv_FindLowestDefender(true, false);
584 LOG_TRACE("No valid bot to remove");
585 // No space in team, denying team change.
586 TRANSMUTE(Spectator, player);
589 LOG_TRACE("Changing ", bot.netname,
590 " from defender to cannon fodder.");
593 player.surv_savedplayerstate = Surv_SavePlayerState(bot);
595 bool savedautobalance = surv_autobalance;
596 surv_autobalance = false;
597 surv_announcefrags = false;
598 SetPlayerTeamSimple(bot, surv_attackerteam);
599 surv_autobalance = savedautobalance;
600 surv_announcefrags = true;
601 LOG_TRACE("Removed bot");
603 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
604 Surv_ChangeNumberOfPlayers(teamnum, +1);
605 ++surv_numdefenderhumans;
606 LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
607 if ((surv_autobalance == false) || (surv_numdefenders -
608 surv_numattackers) < 2)
612 entity lowestplayer = Surv_FindLowestDefender(true, false);
613 if (lowestplayer != NULL)
615 bool savedautobalance = surv_autobalance;
616 surv_autobalance = false;
617 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
618 surv_autobalance = savedautobalance;
621 lowestplayer = Surv_FindLowestDefender(false, false);
622 if (lowestplayer != NULL)
624 bool savedautobalance = surv_autobalance;
625 surv_autobalance = false;
626 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
627 surv_autobalance = savedautobalance;
633 LOG_TRACE("Spectator team");
634 player.surv_role = SURVIVAL_ROLE_NONE;
638 LOG_TRACE("Invalid team");
639 player.surv_role = SURVIVAL_ROLE_NONE;
643 /// \brief Removes player from team. Handles bookkeeping information.
644 /// \param[in] player Player to remove.
645 /// \param[in] Team to remove from.
646 /// \return No return.
647 void Surv_RemovePlayerFromTeam(entity player, int teamnum)
649 LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
652 case surv_attackerteam:
654 LOG_TRACE("Attacker team");
655 if (player.surv_role == SURVIVAL_ROLE_NONE)
657 string message = strcat("RemovePlayerFromTeam: ",
658 player.netname, " has invalid role.");
659 DebugPrintToChatAll(message);
662 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
664 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
667 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
668 Surv_ChangeNumberOfPlayers(teamnum, -1);
669 if (!IS_BOT_CLIENT(player))
671 --surv_numattackerhumans;
673 if ((surv_autobalance == false) || (surv_numattackers >=
678 // Add bot to keep teams balanced.
679 entity lowestplayer = Surv_FindCannonFodder();
680 if (lowestplayer != NULL)
682 LOG_TRACE("Changing ", lowestplayer.netname,
683 " from cannon fodder to attacker.");
684 Surv_SetPlayerRole(lowestplayer, SURVIVAL_ROLE_PLAYER);
685 Surv_ChangeNumberOfPlayers(teamnum, +1);
686 if (!IS_DEAD(lowestplayer))
688 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
692 lowestplayer = Surv_FindLowestDefender(true, false);
693 if (lowestplayer != NULL)
695 bool savedautobalance = surv_autobalance;
696 surv_autobalance = false;
697 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
698 surv_autobalance = savedautobalance;
701 lowestplayer = Surv_FindLowestDefender(false, false);
702 if (lowestplayer == NULL)
706 bool savedautobalance = surv_autobalance;
707 surv_autobalance = false;
708 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
709 surv_autobalance = savedautobalance;
712 case surv_defenderteam:
714 LOG_TRACE("Defender team");
715 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
717 // This happens during team switch. We don't need to change
719 LOG_TRACE("Cannon fodder. Assuming team switch");
722 if (player.surv_role != SURVIVAL_ROLE_PLAYER)
724 string message = strcat("RemovePlayerFromTeam: ",
725 player.netname, " has invalid role.");
726 DebugPrintToChatAll(message);
729 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
730 Surv_ChangeNumberOfPlayers(teamnum, -1);
731 if (!IS_BOT_CLIENT(player))
733 --surv_numdefenderhumans;
735 if ((surv_autobalance == false) || (surv_numdefenders >=
740 // Add bot to keep teams balanced.
741 entity lowestplayer = Surv_FindCannonFodder();
742 if (lowestplayer != NULL)
744 LOG_TRACE("Changing ", lowestplayer.netname,
745 " from cannon fodder to defender.");
746 if (!IS_DEAD(player))
748 lowestplayer.surv_savedplayerstate =
749 Surv_SavePlayerState(player);
751 bool savedautobalance = surv_autobalance;
752 surv_autobalance = false;
753 surv_announcefrags = false;
754 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
755 surv_autobalance = savedautobalance;
756 surv_announcefrags = true;
759 lowestplayer = Surv_FindLowestAttacker(true);
760 if (lowestplayer != NULL)
762 bool savedautobalance = surv_autobalance;
763 surv_autobalance = false;
764 surv_announcefrags = false;
765 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
766 surv_autobalance = savedautobalance;
767 surv_announcefrags = true;
770 lowestplayer = Surv_FindLowestAttacker(false);
771 if (lowestplayer == NULL)
775 bool savedautobalance = surv_autobalance;
776 surv_autobalance = false;
777 surv_announcefrags = false;
778 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
779 surv_autobalance = savedautobalance;
780 surv_announcefrags = true;
785 LOG_TRACE("Spectator team");
790 LOG_TRACE("Invalid team");
796 /// \brief Updates stats of team count on HUD.
797 /// \return No return.
798 void Surv_UpdateTeamStats()
801 if (surv_attackerteam == NUM_TEAM_1)
803 yellowalive = surv_numattackers;
804 pinkalive = surv_numdefenders;
808 pinkalive = surv_numattackers;
809 yellowalive = surv_numdefenders;
811 FOREACH_CLIENT(IS_REAL_CLIENT(it),
813 it.yellowalive_stat = yellowalive;
814 it.pinkalive_stat = pinkalive;
818 /// \brief Adds player to alive list. Handles bookkeeping information.
819 /// \param[in] player Player to add.
820 /// \param[in] teamnum Team of the player.
821 /// \return No return.
822 void Surv_AddPlayerToAliveList(entity player, int teamnum)
826 case surv_attackerteam:
828 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
830 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
834 case surv_defenderteam:
836 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
842 /// \brief Removes player from alive list. Handles bookkeeping information.
843 /// \param[in] player Player to remove.
844 /// \param[in] teamnum Team of the player.
845 /// \return No return.
846 void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
848 if (player.surv_attack_sprite)
850 WaypointSprite_Kill(player.surv_attack_sprite);
854 case surv_attackerteam:
856 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
858 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
862 case surv_defenderteam:
864 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
866 // This happens during team switch. We don't need to change
870 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
871 if (warmup_stage || surv_allowed_to_spawn || !surv_announcefrags)
875 switch (surv_numdefendersalive)
879 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
880 VOL_BASE, ATTEN_NONE);
881 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
883 if (it.team == surv_defenderteam)
885 Send_Notification(NOTIF_ONE, it, MSG_CENTER,
894 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
895 VOL_BASE, ATTEN_NONE);
900 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
901 VOL_BASE, ATTEN_NONE);
910 /// \brief Counts alive players.
911 /// \return No return.
912 /// \note In a perfect world this function shouldn't exist. However, since QC
913 /// code is so bad and spurious mutators can really mess with your code, this
914 /// function is called as a last resort.
915 void Surv_CountAlivePlayers()
917 int savednumdefenders = surv_numdefendersalive;
918 surv_numattackersalive = 0;
919 surv_numdefendersalive = 0;
920 FOREACH_CLIENT(IS_PLAYER(it),
924 case surv_attackerteam:
926 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
928 ++surv_numattackersalive;
932 case surv_defenderteam:
934 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
936 ++surv_numdefendersalive;
942 Surv_UpdateAliveStats();
943 eliminatedPlayers.SendFlags |= 1;
944 if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
945 surv_numdefendersalive))
949 switch (surv_numdefendersalive)
953 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
954 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
956 if (it.team == surv_defenderteam)
958 Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
966 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
972 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
979 /// \brief Updates stats of alive players on HUD.
980 /// \return No return.
981 void Surv_UpdateAliveStats()
984 if (surv_attackerteam == NUM_TEAM_1)
986 redalive = surv_numattackersalive;
987 bluealive = surv_numdefendersalive;
991 bluealive = surv_numattackersalive;
992 redalive = surv_numdefendersalive;
994 FOREACH_CLIENT(IS_REAL_CLIENT(it),
996 it.surv_defenders_alive_stat = surv_numdefendersalive;
997 it.redalive_stat = redalive;
998 it.bluealive_stat = bluealive;
1000 Surv_UpdateDefenderHealthStat();
1003 /// \brief Updates defender health on the HUD.
1004 /// \return No return.
1005 void Surv_UpdateDefenderHealthStat()
1008 float totalhealth = 0;
1009 if (autocvar_g_instagib == 1)
1011 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1012 "surv_defender", "start_armor") + 1);
1013 FOREACH_CLIENT(IS_PLAYER(it),
1015 if (it.team == surv_defenderteam)
1017 totalhealth += it.armorvalue + 1;
1023 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1024 "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
1025 "surv_defender", "start_armor"));
1026 FOREACH_CLIENT(IS_PLAYER(it),
1028 if (it.team == surv_defenderteam)
1030 totalhealth += it.health;
1031 totalhealth += it.armorvalue;
1042 healthratio = totalhealth / maxhealth;
1044 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1046 it.surv_defender_health_stat = healthratio;
1050 /// \brief Returns whether the player can spawn.
1051 /// \param[in] player Player to check.
1052 /// \return True if the player can spawn, false otherwise.
1053 bool Surv_CanPlayerSpawn(entity player)
1055 if ((player.team == surv_attackerteam) ||
1056 (player.surv_savedplayerstate != NULL))
1060 return surv_allowed_to_spawn;
1063 /// \brief Switches the round type.
1064 /// \return No return.
1065 void Surv_SwitchRoundType()
1067 switch (surv_roundtype)
1069 case SURVIVAL_ROUND_FIRST:
1071 surv_roundtype = SURVIVAL_ROUND_SECOND;
1074 case SURVIVAL_ROUND_SECOND:
1076 surv_roundtype = SURVIVAL_ROUND_FIRST;
1082 /// \brief Cleans up the mess after the round has finished.
1083 /// \return No return.
1084 void Surv_RoundCleanup()
1086 surv_allowed_to_spawn = false;
1087 surv_isroundactive = false;
1088 game_stopped = true;
1089 FOREACH_CLIENT(true,
1091 if (it.surv_attack_sprite)
1093 WaypointSprite_Kill(it.surv_attack_sprite);
1095 if (it.surv_savedplayerstate)
1097 delete(it.surv_savedplayerstate);
1098 it.surv_savedplayerstate = NULL;
1101 if (surv_type == SURVIVAL_TYPE_VERSUS)
1103 Surv_SwitchRoundType();
1104 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
1107 round_handler_Init(5, autocvar_g_surv_warmup,
1108 autocvar_g_surv_round_timelimit);
1111 /// \brief Swaps attacker and defender teams.
1112 /// \return No return.
1113 void Surv_SwapTeams()
1115 int temp = surv_attackerteam;
1116 surv_attackerteam = surv_defenderteam;
1117 surv_defenderteam = temp;
1118 temp = surv_attackerteambit;
1119 surv_attackerteambit = surv_defenderteambit;
1120 surv_defenderteambit = temp;
1121 temp = surv_numattackers;
1122 surv_numattackers = surv_numdefenders;
1123 surv_numdefenders = temp;
1124 temp = surv_numattackerhumans;
1125 surv_numattackerhumans = surv_numdefenderhumans;
1126 surv_numdefenderhumans = temp;
1127 FOREACH_CLIENT(true,
1129 if ((it.team == surv_defenderteam) && (it.surv_role ==
1130 SURVIVAL_ROLE_CANNON_FODDER))
1132 SetPlayerTeamSimple(it, surv_attackerteam);
1135 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1137 it.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1141 /// \brief Forces the overkill model for specific player.
1142 /// \param[in,out] player Player to force the model of.
1143 /// \return No return.
1144 void Surv_ForceOverkillPlayerModel(entity player)
1146 switch (player.team)
1150 switch (floor(random() * 4))
1154 player.surv_playermodel = "models/ok_player/okrobot1.dpm";
1159 player.surv_playermodel = "models/ok_player/okrobot2.dpm";
1164 player.surv_playermodel = "models/ok_player/okrobot3.dpm";
1169 player.surv_playermodel = "models/ok_player/okrobot4.dpm";
1177 switch (floor(random() * 4))
1181 player.surv_playermodel = "models/ok_player/okmale1.dpm";
1186 player.surv_playermodel = "models/ok_player/okmale2.dpm";
1191 player.surv_playermodel = "models/ok_player/okmale3.dpm";
1196 player.surv_playermodel = "models/ok_player/okmale4.dpm";
1205 /// \brief Determines the player model to the one configured for the gamemode.
1206 /// \param[in,out] player Player to determine the model of.
1207 /// \return No return.
1208 void Surv_DeterminePlayerModel(entity player)
1210 switch (player.team)
1212 case surv_attackerteam:
1214 switch (player.surv_role)
1216 case SURVIVAL_ROLE_PLAYER:
1218 if (!autocvar_g_surv_attacker_force_overkill_models)
1220 player.surv_playermodel = player.surv_savedplayermodel;
1223 Surv_ForceOverkillPlayerModel(player);
1226 case SURVIVAL_ROLE_CANNON_FODDER:
1228 if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
1230 player.surv_playermodel = player.surv_savedplayermodel;
1233 Surv_ForceOverkillPlayerModel(player);
1238 case surv_defenderteam:
1240 if (!autocvar_g_surv_defender_force_overkill_models)
1242 player.surv_playermodel = player.surv_savedplayermodel;
1245 Surv_ForceOverkillPlayerModel(player);
1251 /// \brief Setups a waypoint sprite used to track defenders.
1252 /// \param[in] player Player to attach sprite too.
1253 /// \return No return.
1254 void Surv_SetupWaypointSprite(entity player)
1256 WaypointSprite_Spawn(WP_AssaultDestroy, 0, 0, player, '0 0 64', NULL,
1257 surv_attackerteam, player, surv_attack_sprite, false,
1258 RADARICON_OBJECTIVE);
1259 if (autocvar_g_instagib == 1)
1261 WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite,
1262 PlayerTemplate_GetFloatValue("surv_defender", "start_armor") + 1);
1263 WaypointSprite_UpdateHealth(player.surv_attack_sprite,
1264 player.armorvalue + 1);
1267 WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite,
1268 PlayerTemplate_GetFloatValue("surv_defender", "start_health") +
1269 PlayerTemplate_GetFloatValue("surv_defender", "start_armor"));
1270 WaypointSprite_UpdateHealth(player.surv_attack_sprite, player.health +
1274 //=============================== Callbacks ===================================
1276 bool Surv_CanRoundStart()
1278 return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
1281 bool Surv_CanRoundEnd()
1287 if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
1290 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1292 surv_timetobeat = time - surv_roundstarttime;
1293 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1294 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1295 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1296 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1297 Surv_RoundCleanup();
1300 surv_timetobeat = autocvar_g_surv_round_timelimit;
1301 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1302 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1303 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1304 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1305 switch (surv_defenderteam)
1309 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1315 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1320 TeamScore_AddToTeam(surv_defenderteam, 1, 1);
1321 Surv_RoundCleanup();
1324 if (surv_numdefendersalive > 0)
1328 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1330 surv_timetobeat = time - surv_roundstarttime;
1331 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1332 CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1333 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1334 INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1335 Surv_RoundCleanup();
1338 surv_timetobeat = autocvar_g_surv_round_timelimit;
1339 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1340 CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
1341 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1342 INFO_SURVIVAL_DEFENDERS_ELIMINATED);
1343 switch (surv_attackerteam)
1347 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1353 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1358 TeamScore_AddToTeam(surv_attackerteam, 1, 1);
1359 Surv_RoundCleanup();
1363 void Surv_RoundStart()
1367 surv_allowed_to_spawn = true;
1370 surv_isroundactive = true;
1371 surv_roundstarttime = time;
1372 surv_allowed_to_spawn = false;
1373 switch (surv_roundtype)
1375 case SURVIVAL_ROUND_FIRST:
1377 FOREACH_CLIENT(IS_PLAYER(it),
1379 if (it.team == surv_attackerteam)
1381 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1382 CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
1383 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1384 INFO_SURVIVAL_1ST_ROUND_ATTACKER);
1388 FOREACH_CLIENT(IS_PLAYER(it),
1390 if (it.team == surv_defenderteam)
1392 if (surv_type == SURVIVAL_TYPE_COOP)
1394 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1395 CENTER_SURVIVAL_COOP_DEFENDER);
1396 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1397 INFO_SURVIVAL_COOP_DEFENDER);
1400 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1401 CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
1402 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1403 INFO_SURVIVAL_1ST_ROUND_DEFENDER);
1409 case SURVIVAL_ROUND_SECOND:
1411 FOREACH_CLIENT(IS_PLAYER(it),
1413 if (it.team == surv_attackerteam)
1415 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1416 CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1417 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1418 INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1422 FOREACH_CLIENT(IS_PLAYER(it),
1424 if (it.team == surv_defenderteam)
1426 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1427 CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1428 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1429 INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1436 if (autocvar_g_surv_stealth)
1440 FOREACH_CLIENT(IS_PLAYER(it),
1444 case surv_defenderteam:
1446 if (it.surv_role == SURVIVAL_ROLE_PLAYER)
1448 Surv_SetupWaypointSprite(it);
1456 bool Surv_IsEliminated(entity player)
1458 switch (player.surv_state)
1460 case SURVIVAL_STATE_NOT_PLAYING:
1464 case SURVIVAL_STATE_PLAYING:
1466 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
1468 // A hack until proper scoreboard is done.
1471 if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
1472 IS_OBSERVER(player)))
1479 // Should never reach here
1483 //============================= Hooks ========================================
1485 /// \brief Hook that is called to determine general rules of the game.
1486 MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
1488 surv_warmup = warmup_stage;
1491 /// \brief Hook that is called to determine if there is a weapon arena.
1492 MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
1494 // Removing any weapon arena.
1495 M_ARGV(0, string) = "off";
1498 /// \brief Hook that is called to determine start items of all players.
1499 MUTATOR_HOOKFUNCTION(surv, SetStartItems)
1501 if (autocvar_g_instagib == 1)
1505 start_weapons = WEPSET(Null);
1506 warmup_start_weapons = WEPSET(Null);
1509 /// \brief Hook that is called on every frame.
1510 MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
1512 if (game_stopped || !surv_isroundactive)
1516 float roundtime = 0;
1517 switch (surv_roundtype)
1519 case SURVIVAL_ROUND_FIRST:
1521 roundtime = time - surv_roundstarttime;
1524 case SURVIVAL_ROUND_SECOND:
1526 roundtime = round_handler_GetEndTime() - time;
1530 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1532 it.surv_round_time_stat = roundtime;
1536 /// \brief Hook that determines which team player can join. This is called
1537 /// before ClientConnect.
1538 MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
1540 entity player = M_ARGV(2, entity);
1541 LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
1544 return SURVIVAL_TEAM_BITS;
1546 if (IS_BOT_CLIENT(player))
1548 int teambits = surv_attackerteambit;
1549 if ((player.team == surv_defenderteam) || (surv_numdefenders <
1550 autocvar_g_surv_team_size))
1552 teambits |= surv_defenderteambit;
1554 M_ARGV(0, float) = teambits;
1557 if (surv_type == SURVIVAL_TYPE_COOP)
1559 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1561 M_ARGV(0, float) = surv_defenderteambit;
1564 M_ARGV(0, float) = 0;
1568 if (surv_numattackerhumans < autocvar_g_surv_team_size)
1570 LOG_TRACE("Player can join attackers");
1571 teambits |= surv_attackerteambit;
1573 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1575 LOG_TRACE("Player can join defenders");
1576 teambits |= surv_defenderteambit;
1578 M_ARGV(0, float) = teambits;
1582 /// \brief Hook that override team counts.
1583 MUTATOR_HOOKFUNCTION(surv, GetTeamCounts, CBC_ORDER_EXCLUSIVE)
1588 /// \brief Hook that sets the team count.
1589 MUTATOR_HOOKFUNCTION(surv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
1591 float teamnum = M_ARGV(0, float);
1592 entity ignore = M_ARGV(1, entity);
1595 case surv_attackerteam:
1597 M_ARGV(2, float) = surv_numattackers;
1598 M_ARGV(3, float) = surv_numattackers - surv_numattackerhumans;
1599 if (ignore.team == surv_attackerteam)
1602 if (IS_BOT_CLIENT(ignore))
1607 entity lowestplayer = NULL;
1608 float lowestplayerscore = FLOAT_MAX;
1609 entity lowestbot = NULL;
1610 float lowestbotscore = FLOAT_MAX;
1611 FOREACH_CLIENT((it.team == surv_attackerteam) && (it.surv_role ==
1612 SURVIVAL_ROLE_PLAYER),
1618 if (IS_BOT_CLIENT(it))
1620 float tempscore = PlayerScore_Get(it, SP_SCORE);
1621 if (tempscore < lowestbotscore)
1624 lowestbotscore = tempscore;
1628 float tempscore = PlayerScore_Get(it, SP_SCORE);
1629 if (tempscore < lowestplayerscore)
1632 lowestplayerscore = tempscore;
1635 M_ARGV(4, entity) = lowestplayer;
1636 M_ARGV(5, entity) = lowestbot;
1639 case surv_defenderteam:
1641 M_ARGV(2, float) = surv_numdefenders;
1642 M_ARGV(3, float) = surv_numdefenders - surv_numdefenderhumans;
1643 if (ignore.team == surv_defenderteam)
1646 if (IS_BOT_CLIENT(ignore))
1651 entity lowestplayer = NULL;
1652 float lowestplayerscore = FLOAT_MAX;
1653 entity lowestbot = NULL;
1654 float lowestbotscore = FLOAT_MAX;
1655 FOREACH_CLIENT((it.team == surv_defenderteam),
1661 if (IS_BOT_CLIENT(it))
1663 float tempscore = PlayerScore_Get(it, SP_SCORE);
1664 if (tempscore < lowestbotscore)
1667 lowestbotscore = tempscore;
1671 float tempscore = PlayerScore_Get(it, SP_SCORE);
1672 if (tempscore < lowestplayerscore)
1675 lowestplayerscore = tempscore;
1678 M_ARGV(4, entity) = lowestplayer;
1679 M_ARGV(5, entity) = lowestbot;
1686 /// \brief Hook that determines the best teams for the player to join.
1687 MUTATOR_HOOKFUNCTION(surv, FindBestTeams, CBC_ORDER_EXCLUSIVE)
1689 if (surv_type == SURVIVAL_TYPE_COOP)
1693 entity player = M_ARGV(0, entity);
1694 if (IS_BOT_CLIENT(player))
1698 int numattackerhumans = surv_numattackerhumans;
1699 int numdefenderhumans = surv_numdefenderhumans;
1700 if (player.team == surv_attackerteam)
1702 --numattackerhumans;
1704 else if (player.team == surv_defenderteam)
1706 --numdefenderhumans;
1708 if (numattackerhumans < numdefenderhumans)
1710 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_attackerteam) - 1);
1713 if (numattackerhumans > numdefenderhumans)
1715 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_defenderteam) - 1);
1718 M_ARGV(1, float) = SURVIVAL_TEAM_BITS;
1722 /// \brief Hook that is called when player has changed the team.
1723 MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
1725 entity player = M_ARGV(0, entity);
1726 int oldteam = M_ARGV(1, float);
1727 int newteam = M_ARGV(2, float);
1728 string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
1729 ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
1731 DebugPrintToChatAll(message);
1732 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1734 Surv_RemovePlayerFromAliveList(player, oldteam);
1736 Surv_RemovePlayerFromTeam(player, oldteam);
1737 if (Surv_AddPlayerToTeam(player, newteam) == false)
1741 //Surv_CountAlivePlayers();
1742 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1744 Surv_AddPlayerToAliveList(player, newteam);
1748 /// \brief Hook that is called when player is about to be killed when changing
1750 MUTATOR_HOOKFUNCTION(surv, Player_ChangeTeamKill)
1752 entity player = M_ARGV(0, entity);
1753 if (player.team != surv_defenderteam)
1757 if (player.surv_savedplayerstate == NULL)
1761 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1762 delete(player.surv_savedplayerstate);
1763 player.surv_savedplayerstate = NULL;
1767 /// \brief Hook that is called when player is about to be killed as a result of
1768 /// the kill command or changing teams.
1769 MUTATOR_HOOKFUNCTION(surv, ClientKill_Now)
1771 entity player = M_ARGV(0, entity);
1772 if (player.team == surv_defenderteam)
1779 /// \brief Hook that is called when player connects to the server.
1780 MUTATOR_HOOKFUNCTION(surv, ClientConnect)
1782 entity player = M_ARGV(0, entity);
1783 LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
1784 player.surv_savedplayermodel = player.playermodel;
1785 if (IS_REAL_CLIENT(player))
1787 player.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1788 player.surv_defenders_alive_stat = surv_numdefendersalive;
1789 player.redalive_stat = redalive;
1790 player.bluealive_stat = bluealive;
1791 player.yellowalive_stat = yellowalive;
1792 player.pinkalive_stat = pinkalive;
1794 if (player.surv_role == SURVIVAL_ROLE_NONE)
1796 Surv_AddPlayerToTeam(player, player.team);
1801 /// \brief Hook that is called when player disconnects from the server.
1802 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
1804 entity player = M_ARGV(0, entity);
1805 if (!IS_DEAD(player))
1807 Surv_RemovePlayerFromAliveList(player, player.team);
1809 Surv_RemovePlayerFromTeam(player, player.team);
1810 //Surv_CountAlivePlayers();
1813 /// \brief Hook that determines whether player can spawn. It is not called for
1814 /// players who have joined the team and are dead.
1815 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
1817 entity player = M_ARGV(0, entity);
1818 LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
1819 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1823 return !Surv_CanPlayerSpawn(player);
1826 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
1828 entity player = M_ARGV(0, entity);
1829 LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
1830 if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
1832 LOG_TRACE("Transmuting to observer");
1833 TRANSMUTE(Observer, player);
1837 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
1839 entity player = M_ARGV(0, entity);
1840 LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
1841 if (player.killindicator_teamchange == -2) // player wants to spectate
1843 LOG_TRACE("killindicator_teamchange == -2");
1844 player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
1846 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1848 return false; // allow team reset
1850 return true; // prevent team reset
1853 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
1855 LOG_TRACE("Survival: reset_map_global");
1856 surv_allowed_to_spawn = true;
1857 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1859 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1861 it.surv_round_time_stat = 0;
1867 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
1869 LOG_TRACE("Survival: reset_map_players");
1870 surv_numattackersalive = 0;
1871 surv_numdefendersalive = 0;
1874 surv_warmup = false;
1876 else if (surv_type == SURVIVAL_TYPE_VERSUS)
1880 FOREACH_CLIENT(true,
1883 if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
1886 it.surv_state = SURVIVAL_STATE_PLAYING;
1888 if (it.surv_state == SURVIVAL_STATE_PLAYING)
1890 TRANSMUTE(Player, it);
1891 it.surv_state = SURVIVAL_STATE_PLAYING;
1892 PutClientInServer(it);
1895 bot_relinkplayerlist();
1899 /// \brief Hook that is called when player spawns.
1900 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
1902 entity player = M_ARGV(0, entity);
1903 LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
1904 player.surv_state = SURVIVAL_STATE_PLAYING;
1905 Surv_DeterminePlayerModel(player);
1906 if (player.surv_savedplayerstate != NULL)
1908 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1909 delete(player.surv_savedplayerstate);
1910 player.surv_savedplayerstate = NULL;
1914 PlayerTemplate_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
1916 switch (player.team)
1918 case surv_attackerteam:
1920 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
1922 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1923 CENTER_ASSAULT_ATTACKING);
1927 case surv_defenderteam:
1929 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1930 CENTER_ASSAULT_DEFENDING);
1934 //Surv_CountAlivePlayers();
1935 Surv_AddPlayerToAliveList(player, player.team);
1938 /// \brief UGLY HACK. This is called every frame to keep player model correct.
1939 MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
1941 entity player = M_ARGV(2, entity);
1942 M_ARGV(0, string) = player.surv_playermodel;
1945 /// \brief Hook that is called every frame to determine how player health should
1947 MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
1949 entity player = M_ARGV(0, entity);
1950 if (player.team == surv_defenderteam)
1954 return PlayerTemplate_PlayerRegen(player, Surv_GetPlayerTemplate(player));
1957 /// \brief Hook that is called to determine if balance messages will appear.
1958 MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
1963 /// \brief Hook that is called when player touches an item.
1964 MUTATOR_HOOKFUNCTION(surv, ItemTouch)
1966 entity item = M_ARGV(0, entity);
1967 entity player = M_ARGV(1, entity);
1968 switch (player.team)
1970 case surv_attackerteam:
1972 return PlayerTemplate_ItemTouch(player, item,
1973 Surv_GetPlayerTemplate(player));
1975 case surv_defenderteam:
1977 switch (item.classname)
1979 case "item_strength":
1981 W_GiveWeapon(player, WEP_HMG.m_id);
1982 player.superweapons_finished = max(
1983 player.superweapons_finished, time) +
1984 autocvar_g_balance_superweapons_time;
1985 Item_ScheduleRespawn(item);
1986 sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
1988 return MUT_ITEMTOUCH_RETURN;
1990 case "item_invincible":
1992 W_GiveWeapon(player, WEP_RPC.m_id);
1993 player.superweapons_finished = max(
1994 player.superweapons_finished, time) +
1995 autocvar_g_balance_superweapons_time;
1996 Item_ScheduleRespawn(item);
1997 sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
1998 return MUT_ITEMTOUCH_RETURN;
2002 return PlayerTemplate_ItemTouch(player, item,
2003 Surv_GetPlayerTemplate(player));
2006 DebugPrintToChat(player, item.classname);
2007 return MUT_ITEMTOUCH_RETURN;
2010 return MUT_ITEMTOUCH_CONTINUE;
2013 /// \brief Hook which is called when the player tries to throw their weapon.
2014 MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
2016 entity player = M_ARGV(0, entity);
2017 if (player.team == surv_defenderteam)
2023 /// \brief Hook which is called when the damage amount must be determined.
2024 MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
2026 entity frag_attacker = M_ARGV(1, entity);
2027 entity frag_target = M_ARGV(2, entity);
2028 float deathtype = M_ARGV(3, float);
2029 float damage = M_ARGV(4, float);
2030 M_ARGV(4, float) = PlayerTemplate_Damage_Calculate(frag_attacker,
2031 Surv_GetPlayerTemplate(frag_attacker), frag_target,
2032 Surv_GetPlayerTemplate(frag_target), deathtype, damage);
2035 /// \brief Hook which is called when the player was damaged.
2036 MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
2038 entity target = M_ARGV(1, entity);
2039 if (target.team != surv_defenderteam)
2043 Surv_UpdateDefenderHealthStat();
2044 entity attacker = M_ARGV(0, entity);
2045 if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
2046 SURVIVAL_ROLE_PLAYER))
2048 float health = M_ARGV(2, float);
2049 float armor = M_ARGV(3, float);
2050 float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
2051 PlayerScore_Add(attacker, SP_SCORE, score);
2053 if (autocvar_g_surv_stealth)
2057 if (target.health < 1)
2059 WaypointSprite_Kill(target.surv_attack_sprite);
2063 if (autocvar_g_instagib == 1)
2065 WaypointSprite_UpdateHealth(target.surv_attack_sprite,
2066 target.armorvalue + 1);
2070 WaypointSprite_UpdateHealth(target.surv_attack_sprite,
2071 target.health + target.armorvalue);
2076 /// \brief Hook which is called when the player dies.
2077 MUTATOR_HOOKFUNCTION(surv, PlayerDies)
2079 //DebugPrintToChatAll("PlayerDies");
2080 entity attacker = M_ARGV(1, entity);
2081 entity victim = M_ARGV(2, entity);
2082 if ((attacker.team == surv_defenderteam) &&
2083 (victim.team == surv_attackerteam))
2085 switch (victim.surv_role)
2087 case SURVIVAL_ROLE_PLAYER:
2089 GivePlayerHealth(attacker,
2090 autocvar_g_surv_defender_attacker_frag_health);
2091 GivePlayerArmor(attacker,
2092 autocvar_g_surv_defender_attacker_frag_armor);
2093 GivePlayerAmmo(attacker, ammo_shells,
2094 autocvar_g_surv_defender_attacker_frag_shells);
2095 GivePlayerAmmo(attacker, ammo_nails,
2096 autocvar_g_surv_defender_attacker_frag_bullets);
2097 GivePlayerAmmo(attacker, ammo_rockets,
2098 autocvar_g_surv_defender_attacker_frag_rockets);
2099 GivePlayerAmmo(attacker, ammo_cells,
2100 autocvar_g_surv_defender_attacker_frag_cells);
2103 case SURVIVAL_ROLE_CANNON_FODDER:
2105 GivePlayerHealth(attacker,
2106 autocvar_g_surv_defender_cannon_fodder_frag_health);
2107 GivePlayerArmor(attacker,
2108 autocvar_g_surv_defender_cannon_fodder_frag_armor);
2109 GivePlayerAmmo(attacker, ammo_shells,
2110 autocvar_g_surv_defender_cannon_fodder_frag_shells);
2111 GivePlayerAmmo(attacker, ammo_nails,
2112 autocvar_g_surv_defender_cannon_fodder_frag_bullets);
2113 GivePlayerAmmo(attacker, ammo_rockets,
2114 autocvar_g_surv_defender_cannon_fodder_frag_rockets);
2115 GivePlayerAmmo(attacker, ammo_cells,
2116 autocvar_g_surv_defender_cannon_fodder_frag_cells);
2121 if ((victim.team == surv_defenderteam) &&
2122 (autocvar_g_surv_defender_drop_weapons == false))
2124 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2126 .entity went = weaponentities[slot];
2127 victim.(went).m_weapon = WEP_Null;
2130 if (!Surv_CanPlayerSpawn(victim))
2132 victim.respawn_flags = RESPAWN_SILENT;
2133 if (IS_BOT_CLIENT(victim))
2141 /// \brief Hook which is called after the player died.
2142 MUTATOR_HOOKFUNCTION(surv, PlayerDied)
2144 //DebugPrintToChatAll("PlayerDied");
2145 entity player = M_ARGV(0, entity);
2146 Surv_RemovePlayerFromAliveList(player, player.team);
2147 //Surv_CountAlivePlayers();
2150 /// \brief Hook which is called when player has scored a frag.
2151 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
2153 if (surv_type == SURVIVAL_TYPE_COOP)
2157 entity attacker = M_ARGV(0, entity);
2158 if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
2159 SURVIVAL_ROLE_CANNON_FODDER))
2161 M_ARGV(2, float) = 0;
2164 entity target = M_ARGV(1, entity);
2165 if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
2168 M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
2173 MUTATOR_HOOKFUNCTION(surv, SpectateSet)
2175 entity client = M_ARGV(0, entity);
2176 entity targ = M_ARGV(1, entity);
2178 if (!autocvar_g_surv_spectate_enemies &&
2179 (client.surv_state == SURVIVAL_STATE_PLAYING) &&
2180 DIFF_TEAM(targ, client))
2186 MUTATOR_HOOKFUNCTION(surv, SpectateNext)
2188 entity client = M_ARGV(0, entity);
2190 if (!autocvar_g_surv_spectate_enemies &&
2191 (client.surv_state == SURVIVAL_STATE_PLAYING))
2193 entity targ = M_ARGV(1, entity);
2194 M_ARGV(1, entity) = CA_SpectateNext(client, targ);
2199 MUTATOR_HOOKFUNCTION(surv, SpectatePrev)
2201 entity client = M_ARGV(0, entity);
2202 entity targ = M_ARGV(1, entity);
2203 entity first = M_ARGV(2, entity);
2205 if (!autocvar_g_surv_spectate_enemies &&
2206 (client.surv_state == SURVIVAL_STATE_PLAYING))
2212 while (targ && DIFF_TEAM(targ, client));
2215 for (targ = first; targ && DIFF_TEAM(targ, client);
2218 if (targ == client.enemy)
2220 return MUT_SPECPREV_RETURN;
2224 M_ARGV(1, entity) = targ;
2225 return MUT_SPECPREV_FOUND;
2228 /// \brief I'm not sure exactly what this function does but it is very
2229 /// important. Without it bots are completely broken. Is it a hack? Of course.
2230 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
2232 FOREACH_CLIENT(IS_REAL_CLIENT(it),
2234 if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
2243 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
2245 // Don't announce remaining frags