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 Returns the attacker with the lowest score.
324 /// \param[in] bot Whether to search only for bots.
325 /// \return Attacker with the lowest score or NULL if not found.
326 entity Surv_FindLowestAttacker(bool bot)
328 entity player = NULL;
329 float score = FLOAT_MAX;
330 FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
332 if ((it.team == surv_attackerteam) && (it.surv_role ==
333 SURVIVAL_ROLE_PLAYER))
335 float tempscore = PlayerScore_Get(it, SP_SCORE);
336 if (tempscore < score)
346 /// \brief Returns the defender with the lowest score.
347 /// \param[in] bot Whether to search only for bots.
348 /// \param[in] alive Whether to search only for alive players.
349 /// \return Defender with the lowest score or NULL if not found.
350 entity Surv_FindLowestDefender(bool bot, bool alive)
352 entity player = NULL;
353 float score = FLOAT_MAX;
354 FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
356 if ((it.team == surv_defenderteam) && (alive ? !IS_DEAD(it) : true))
358 float tempscore = PlayerScore_Get(it, SP_SCORE);
359 if (tempscore < score)
369 /// \brief Returns the cannon fodder.
370 /// \return Cannon fodder or NULL if not found.
371 entity Surv_FindCannonFodder()
373 FOREACH_CLIENT(IS_BOT_CLIENT(it),
375 if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
383 /// \brief Changes the number of players in a team.
384 /// \param[in] teamnum Team to adjust.
385 /// \param[in] delta Amount to adjust by.
386 /// \return No return.
387 void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
391 case surv_attackerteam:
393 surv_numattackers += delta;
394 LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
395 " was = ", ftos(surv_numattackers - delta));
396 Surv_UpdateTeamStats();
399 case surv_defenderteam:
401 surv_numdefenders += delta;
402 LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
403 " was = ", ftos(surv_numdefenders - delta));
404 Surv_UpdateTeamStats();
410 /// \brief Changes the number of alive players in a team.
411 /// \param[in] teamnum Team to adjust.
412 /// \param[in] delta Amount to adjust by.
413 /// \return No return.
414 void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
418 case surv_attackerteam:
420 surv_numattackersalive += delta;
421 LOG_TRACE("Number of alive attackers = ", ftos(
422 surv_numattackersalive), " was = ", ftos(surv_numattackersalive
426 case surv_defenderteam:
428 surv_numdefendersalive += delta;
429 LOG_TRACE("Number of alive defenders = ", ftos(
430 surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
435 Surv_UpdateAliveStats();
436 eliminatedPlayers.SendFlags |= 1;
439 /// \brief Sets the player role.
440 /// \param[in,out] player Player to adjust.
441 /// \param[in] role Role to set.
442 /// \return No return.
443 void Surv_SetPlayerRole(entity player, int role)
445 if (player.surv_role == role)
449 player.surv_role = role;
452 case SURVIVAL_ROLE_NONE:
454 LOG_TRACE(player.netname, " now has no role.");
457 case SURVIVAL_ROLE_PLAYER:
459 LOG_TRACE(player.netname, " is now a player.");
462 case SURVIVAL_ROLE_CANNON_FODDER:
464 LOG_TRACE(player.netname, " is now a cannon fodder.");
470 /// \brief Adds player to team. Handles bookkeeping information.
471 /// \param[in] player Player to add.
472 /// \param[in] teamnum Team to add to.
473 /// \return True on success, false otherwise.
474 bool Surv_AddPlayerToTeam(entity player, int teamnum)
476 LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
479 case surv_attackerteam:
481 LOG_TRACE("Attacker team");
482 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
484 LOG_TRACE("Cannon fodder is switching team");
487 if (IS_BOT_CLIENT(player))
489 LOG_TRACE("Client is bot");
490 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
491 if (surv_numattackers < autocvar_g_surv_team_size)
493 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
494 Surv_ChangeNumberOfPlayers(teamnum, +1);
497 Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
500 LOG_TRACE("Client is not a bot");
501 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
502 if (surv_numattackers >= autocvar_g_surv_team_size)
504 LOG_TRACE("Removing bot");
505 // Remove bot to make space for human.
506 entity bot = Surv_FindLowestAttacker(true);
509 LOG_TRACE("No valid bot to remove");
510 // No space in team, denying team change.
511 TRANSMUTE(Spectator, player);
514 LOG_TRACE("Changing ", bot.netname,
515 " from attacker to cannon fodder.");
516 Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
519 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
521 Surv_ChangeNumberOfPlayers(teamnum, -1);
522 LOG_TRACE("Removed bot");
524 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
525 Surv_ChangeNumberOfPlayers(teamnum, +1);
526 ++surv_numattackerhumans;
527 LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
528 if ((surv_autobalance == false) || (surv_numattackers -
529 surv_numdefenders) < 2)
533 entity lowestplayer = Surv_FindLowestAttacker(true);
534 if (lowestplayer != NULL)
536 bool savedautobalance = surv_autobalance;
537 surv_autobalance = false;
538 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
539 surv_autobalance = savedautobalance;
542 lowestplayer = Surv_FindLowestAttacker(false);
543 if (lowestplayer != NULL)
545 bool savedautobalance = surv_autobalance;
546 surv_autobalance = false;
547 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
548 surv_autobalance = savedautobalance;
552 case surv_defenderteam:
554 LOG_TRACE("Defender team");
555 if (IS_BOT_CLIENT(player))
557 LOG_TRACE("Client is bot");
558 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
559 if (surv_numdefenders < autocvar_g_surv_team_size)
561 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
562 Surv_ChangeNumberOfPlayers(teamnum, +1);
565 LOG_TRACE("No space for defender, switching to attacker");
566 SetPlayerTeamSimple(player, surv_attackerteam);
569 LOG_TRACE("Client is not a bot");
570 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
571 if (surv_numdefenders >= autocvar_g_surv_team_size)
573 LOG_TRACE("Removing bot");
574 // Remove bot to make space for human.
575 entity bot = Surv_FindLowestDefender(true, true);
578 bot = Surv_FindLowestDefender(true, false);
582 LOG_TRACE("No valid bot to remove");
583 // No space in team, denying team change.
584 TRANSMUTE(Spectator, player);
587 LOG_TRACE("Changing ", bot.netname,
588 " from defender to cannon fodder.");
591 player.surv_savedplayerstate = Surv_SavePlayerState(bot);
593 bool savedautobalance = surv_autobalance;
594 surv_autobalance = false;
595 surv_announcefrags = false;
596 SetPlayerTeamSimple(bot, surv_attackerteam);
597 surv_autobalance = savedautobalance;
598 surv_announcefrags = true;
599 LOG_TRACE("Removed bot");
601 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
602 Surv_ChangeNumberOfPlayers(teamnum, +1);
603 ++surv_numdefenderhumans;
604 LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
605 if ((surv_autobalance == false) || (surv_numdefenders -
606 surv_numattackers) < 2)
610 entity lowestplayer = Surv_FindLowestDefender(true, false);
611 if (lowestplayer != NULL)
613 bool savedautobalance = surv_autobalance;
614 surv_autobalance = false;
615 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
616 surv_autobalance = savedautobalance;
619 lowestplayer = Surv_FindLowestDefender(false, false);
620 if (lowestplayer != NULL)
622 bool savedautobalance = surv_autobalance;
623 surv_autobalance = false;
624 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
625 surv_autobalance = savedautobalance;
631 LOG_TRACE("Spectator team");
632 player.surv_role = SURVIVAL_ROLE_NONE;
636 LOG_TRACE("Invalid team");
637 player.surv_role = SURVIVAL_ROLE_NONE;
641 /// \brief Removes player from team. Handles bookkeeping information.
642 /// \param[in] player Player to remove.
643 /// \param[in] Team to remove from.
644 /// \return No return.
645 void Surv_RemovePlayerFromTeam(entity player, int teamnum)
647 LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
650 case surv_attackerteam:
652 LOG_TRACE("Attacker team");
653 if (player.surv_role == SURVIVAL_ROLE_NONE)
655 string message = strcat("RemovePlayerFromTeam: ",
656 player.netname, " has invalid role.");
657 DebugPrintToChatAll(message);
660 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
662 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
665 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
666 Surv_ChangeNumberOfPlayers(teamnum, -1);
667 if (!IS_BOT_CLIENT(player))
669 --surv_numattackerhumans;
671 if ((surv_autobalance == false) || (surv_numattackers >=
676 // Add bot to keep teams balanced.
677 entity lowestplayer = Surv_FindCannonFodder();
678 if (lowestplayer != NULL)
680 LOG_TRACE("Changing ", lowestplayer.netname,
681 " from cannon fodder to attacker.");
682 Surv_SetPlayerRole(lowestplayer, SURVIVAL_ROLE_PLAYER);
683 Surv_ChangeNumberOfPlayers(teamnum, +1);
684 if (!IS_DEAD(lowestplayer))
686 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
690 lowestplayer = Surv_FindLowestDefender(true, false);
691 if (lowestplayer != NULL)
693 bool savedautobalance = surv_autobalance;
694 surv_autobalance = false;
695 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
696 surv_autobalance = savedautobalance;
699 lowestplayer = Surv_FindLowestDefender(false, false);
700 if (lowestplayer == NULL)
704 bool savedautobalance = surv_autobalance;
705 surv_autobalance = false;
706 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
707 surv_autobalance = savedautobalance;
710 case surv_defenderteam:
712 LOG_TRACE("Defender team");
713 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
715 // This happens during team switch. We don't need to change
717 LOG_TRACE("Cannon fodder. Assuming team switch");
720 if (player.surv_role != SURVIVAL_ROLE_PLAYER)
722 string message = strcat("RemovePlayerFromTeam: ",
723 player.netname, " has invalid role.");
724 DebugPrintToChatAll(message);
727 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
728 Surv_ChangeNumberOfPlayers(teamnum, -1);
729 if (!IS_BOT_CLIENT(player))
731 --surv_numdefenderhumans;
733 if ((surv_autobalance == false) || (surv_numdefenders >=
738 // Add bot to keep teams balanced.
739 entity lowestplayer = Surv_FindCannonFodder();
740 if (lowestplayer != NULL)
742 LOG_TRACE("Changing ", lowestplayer.netname,
743 " from cannon fodder to defender.");
744 if (!IS_DEAD(player))
746 lowestplayer.surv_savedplayerstate =
747 Surv_SavePlayerState(player);
749 bool savedautobalance = surv_autobalance;
750 surv_autobalance = false;
751 surv_announcefrags = false;
752 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
753 surv_autobalance = savedautobalance;
754 surv_announcefrags = true;
757 lowestplayer = Surv_FindLowestAttacker(true);
758 if (lowestplayer != NULL)
760 bool savedautobalance = surv_autobalance;
761 surv_autobalance = false;
762 surv_announcefrags = false;
763 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
764 surv_autobalance = savedautobalance;
765 surv_announcefrags = true;
768 lowestplayer = Surv_FindLowestAttacker(false);
769 if (lowestplayer == NULL)
773 bool savedautobalance = surv_autobalance;
774 surv_autobalance = false;
775 surv_announcefrags = false;
776 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
777 surv_autobalance = savedautobalance;
778 surv_announcefrags = true;
783 LOG_TRACE("Spectator team");
788 LOG_TRACE("Invalid team");
794 /// \brief Updates stats of team count on HUD.
795 /// \return No return.
796 void Surv_UpdateTeamStats()
799 if (surv_attackerteam == NUM_TEAM_1)
801 yellowalive = surv_numattackers;
802 pinkalive = surv_numdefenders;
806 pinkalive = surv_numattackers;
807 yellowalive = surv_numdefenders;
809 FOREACH_CLIENT(IS_REAL_CLIENT(it),
811 it.yellowalive_stat = yellowalive;
812 it.pinkalive_stat = pinkalive;
816 /// \brief Adds player to alive list. Handles bookkeeping information.
817 /// \param[in] player Player to add.
818 /// \param[in] teamnum Team of the player.
819 /// \return No return.
820 void Surv_AddPlayerToAliveList(entity player, int teamnum)
824 case surv_attackerteam:
826 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
828 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
832 case surv_defenderteam:
834 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
840 /// \brief Removes player from alive list. Handles bookkeeping information.
841 /// \param[in] player Player to remove.
842 /// \param[in] teamnum Team of the player.
843 /// \return No return.
844 void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
846 if (player.surv_attack_sprite)
848 WaypointSprite_Kill(player.surv_attack_sprite);
852 case surv_attackerteam:
854 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
856 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
860 case surv_defenderteam:
862 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
864 // This happens during team switch. We don't need to change
868 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
869 if (warmup_stage || surv_allowed_to_spawn || !surv_announcefrags)
873 switch (surv_numdefendersalive)
877 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
878 VOL_BASE, ATTEN_NONE);
879 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
881 if (it.team == surv_defenderteam)
883 Send_Notification(NOTIF_ONE, it, MSG_CENTER,
892 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
893 VOL_BASE, ATTEN_NONE);
898 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
899 VOL_BASE, ATTEN_NONE);
908 /// \brief Counts alive players.
909 /// \return No return.
910 /// \note In a perfect world this function shouldn't exist. However, since QC
911 /// code is so bad and spurious mutators can really mess with your code, this
912 /// function is called as a last resort.
913 void Surv_CountAlivePlayers()
915 int savednumdefenders = surv_numdefendersalive;
916 surv_numattackersalive = 0;
917 surv_numdefendersalive = 0;
918 FOREACH_CLIENT(IS_PLAYER(it),
922 case surv_attackerteam:
924 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
926 ++surv_numattackersalive;
930 case surv_defenderteam:
932 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
934 ++surv_numdefendersalive;
940 Surv_UpdateAliveStats();
941 eliminatedPlayers.SendFlags |= 1;
942 if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
943 surv_numdefendersalive))
947 switch (surv_numdefendersalive)
951 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
952 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
954 if (it.team == surv_defenderteam)
956 Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
964 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
970 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
977 /// \brief Updates stats of alive players on HUD.
978 /// \return No return.
979 void Surv_UpdateAliveStats()
982 if (surv_attackerteam == NUM_TEAM_1)
984 redalive = surv_numattackersalive;
985 bluealive = surv_numdefendersalive;
989 bluealive = surv_numattackersalive;
990 redalive = surv_numdefendersalive;
992 FOREACH_CLIENT(IS_REAL_CLIENT(it),
994 it.surv_defenders_alive_stat = surv_numdefendersalive;
995 it.redalive_stat = redalive;
996 it.bluealive_stat = bluealive;
998 Surv_UpdateDefenderHealthStat();
1001 /// \brief Updates defender health on the HUD.
1002 /// \return No return.
1003 void Surv_UpdateDefenderHealthStat()
1006 float totalhealth = 0;
1007 if (autocvar_g_instagib == 1)
1009 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1010 "surv_defender", "start_armor") + 1);
1011 FOREACH_CLIENT(IS_PLAYER(it),
1013 if (it.team == surv_defenderteam)
1015 totalhealth += it.armorvalue + 1;
1021 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1022 "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
1023 "surv_defender", "start_armor"));
1024 FOREACH_CLIENT(IS_PLAYER(it),
1026 if (it.team == surv_defenderteam)
1028 totalhealth += it.health;
1029 totalhealth += it.armorvalue;
1040 healthratio = totalhealth / maxhealth;
1042 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1044 it.surv_defender_health_stat = healthratio;
1048 /// \brief Returns whether the player can spawn.
1049 /// \param[in] player Player to check.
1050 /// \return True if the player can spawn, false otherwise.
1051 bool Surv_CanPlayerSpawn(entity player)
1053 if ((player.team == surv_attackerteam) ||
1054 (player.surv_savedplayerstate != NULL))
1058 return surv_allowed_to_spawn;
1061 /// \brief Switches the round type.
1062 /// \return No return.
1063 void Surv_SwitchRoundType()
1065 switch (surv_roundtype)
1067 case SURVIVAL_ROUND_FIRST:
1069 surv_roundtype = SURVIVAL_ROUND_SECOND;
1072 case SURVIVAL_ROUND_SECOND:
1074 surv_roundtype = SURVIVAL_ROUND_FIRST;
1080 /// \brief Cleans up the mess after the round has finished.
1081 /// \return No return.
1082 void Surv_RoundCleanup()
1084 surv_allowed_to_spawn = false;
1085 surv_isroundactive = false;
1086 game_stopped = true;
1087 FOREACH_CLIENT(true,
1089 if (it.surv_attack_sprite)
1091 WaypointSprite_Kill(it.surv_attack_sprite);
1093 if (it.surv_savedplayerstate)
1095 delete(it.surv_savedplayerstate);
1096 it.surv_savedplayerstate = NULL;
1099 if (surv_type == SURVIVAL_TYPE_VERSUS)
1101 Surv_SwitchRoundType();
1102 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
1105 round_handler_Init(5, autocvar_g_surv_warmup,
1106 autocvar_g_surv_round_timelimit);
1109 /// \brief Swaps attacker and defender teams.
1110 /// \return No return.
1111 void Surv_SwapTeams()
1113 int temp = surv_attackerteam;
1114 surv_attackerteam = surv_defenderteam;
1115 surv_defenderteam = temp;
1116 temp = surv_attackerteambit;
1117 surv_attackerteambit = surv_defenderteambit;
1118 surv_defenderteambit = temp;
1119 temp = surv_numattackers;
1120 surv_numattackers = surv_numdefenders;
1121 surv_numdefenders = temp;
1122 temp = surv_numattackerhumans;
1123 surv_numattackerhumans = surv_numdefenderhumans;
1124 surv_numdefenderhumans = temp;
1125 FOREACH_CLIENT(true,
1127 if ((it.team == surv_defenderteam) && (it.surv_role ==
1128 SURVIVAL_ROLE_CANNON_FODDER))
1130 SetPlayerTeamSimple(it, surv_attackerteam);
1133 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1135 it.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1139 /// \brief Forces the overkill model for specific player.
1140 /// \param[in,out] player Player to force the model of.
1141 /// \return No return.
1142 void Surv_ForceOverkillPlayerModel(entity player)
1144 switch (player.team)
1148 switch (floor(random() * 4))
1152 player.surv_playermodel = "models/ok_player/okrobot1.dpm";
1157 player.surv_playermodel = "models/ok_player/okrobot2.dpm";
1162 player.surv_playermodel = "models/ok_player/okrobot3.dpm";
1167 player.surv_playermodel = "models/ok_player/okrobot4.dpm";
1175 switch (floor(random() * 4))
1179 player.surv_playermodel = "models/ok_player/okmale1.dpm";
1184 player.surv_playermodel = "models/ok_player/okmale2.dpm";
1189 player.surv_playermodel = "models/ok_player/okmale3.dpm";
1194 player.surv_playermodel = "models/ok_player/okmale4.dpm";
1203 /// \brief Determines the player model to the one configured for the gamemode.
1204 /// \param[in,out] player Player to determine the model of.
1205 /// \return No return.
1206 void Surv_DeterminePlayerModel(entity player)
1208 switch (player.team)
1210 case surv_attackerteam:
1212 switch (player.surv_role)
1214 case SURVIVAL_ROLE_PLAYER:
1216 if (!autocvar_g_surv_attacker_force_overkill_models)
1218 player.surv_playermodel = player.surv_savedplayermodel;
1221 Surv_ForceOverkillPlayerModel(player);
1224 case SURVIVAL_ROLE_CANNON_FODDER:
1226 if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
1228 player.surv_playermodel = player.surv_savedplayermodel;
1231 Surv_ForceOverkillPlayerModel(player);
1236 case surv_defenderteam:
1238 if (!autocvar_g_surv_defender_force_overkill_models)
1240 player.surv_playermodel = player.surv_savedplayermodel;
1243 Surv_ForceOverkillPlayerModel(player);
1249 /// \brief Setups a waypoint sprite used to track defenders.
1250 /// \param[in] player Player to attach sprite too.
1251 /// \return No return.
1252 void Surv_SetupWaypointSprite(entity player)
1254 WaypointSprite_Spawn(WP_AssaultDestroy, 0, 0, player, '0 0 64', NULL,
1255 surv_attackerteam, player, surv_attack_sprite, false,
1256 RADARICON_OBJECTIVE);
1257 if (autocvar_g_instagib == 1)
1259 WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite,
1260 PlayerTemplate_GetFloatValue("surv_defender", "start_armor") + 1);
1261 WaypointSprite_UpdateHealth(player.surv_attack_sprite,
1262 player.armorvalue + 1);
1265 WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite,
1266 PlayerTemplate_GetFloatValue("surv_defender", "start_health") +
1267 PlayerTemplate_GetFloatValue("surv_defender", "start_armor"));
1268 WaypointSprite_UpdateHealth(player.surv_attack_sprite, player.health +
1272 //=============================== Callbacks ===================================
1274 bool Surv_CanRoundStart()
1276 return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
1279 bool Surv_CanRoundEnd()
1285 if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
1288 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1290 surv_timetobeat = time - surv_roundstarttime;
1291 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1292 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1293 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1294 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1295 Surv_RoundCleanup();
1298 surv_timetobeat = autocvar_g_surv_round_timelimit;
1299 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1300 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1301 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1302 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1303 switch (surv_defenderteam)
1307 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1313 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1318 TeamScore_AddToTeam(surv_defenderteam, 1, 1);
1319 Surv_RoundCleanup();
1322 if (surv_numdefendersalive > 0)
1326 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1328 surv_timetobeat = time - surv_roundstarttime;
1329 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1330 CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1331 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1332 INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1333 Surv_RoundCleanup();
1336 surv_timetobeat = autocvar_g_surv_round_timelimit;
1337 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1338 CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
1339 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1340 INFO_SURVIVAL_DEFENDERS_ELIMINATED);
1341 switch (surv_attackerteam)
1345 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1351 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1356 TeamScore_AddToTeam(surv_attackerteam, 1, 1);
1357 Surv_RoundCleanup();
1361 void Surv_RoundStart()
1365 surv_allowed_to_spawn = true;
1368 surv_isroundactive = true;
1369 surv_roundstarttime = time;
1370 surv_allowed_to_spawn = false;
1371 switch (surv_roundtype)
1373 case SURVIVAL_ROUND_FIRST:
1375 FOREACH_CLIENT(IS_PLAYER(it),
1377 if (it.team == surv_attackerteam)
1379 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1380 CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
1381 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1382 INFO_SURVIVAL_1ST_ROUND_ATTACKER);
1386 FOREACH_CLIENT(IS_PLAYER(it),
1388 if (it.team == surv_defenderteam)
1390 if (surv_type == SURVIVAL_TYPE_COOP)
1392 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1393 CENTER_SURVIVAL_COOP_DEFENDER);
1394 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1395 INFO_SURVIVAL_COOP_DEFENDER);
1398 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1399 CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
1400 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1401 INFO_SURVIVAL_1ST_ROUND_DEFENDER);
1407 case SURVIVAL_ROUND_SECOND:
1409 FOREACH_CLIENT(IS_PLAYER(it),
1411 if (it.team == surv_attackerteam)
1413 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1414 CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1415 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1416 INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1420 FOREACH_CLIENT(IS_PLAYER(it),
1422 if (it.team == surv_defenderteam)
1424 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1425 CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1426 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1427 INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1434 if (autocvar_g_surv_stealth)
1438 FOREACH_CLIENT(IS_PLAYER(it),
1442 case surv_defenderteam:
1444 if (it.surv_role == SURVIVAL_ROLE_PLAYER)
1446 Surv_SetupWaypointSprite(it);
1454 bool Surv_IsEliminated(entity player)
1456 switch (player.surv_state)
1458 case SURVIVAL_STATE_NOT_PLAYING:
1462 case SURVIVAL_STATE_PLAYING:
1464 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
1466 // A hack until proper scoreboard is done.
1469 if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
1470 IS_OBSERVER(player)))
1477 // Should never reach here
1481 //============================= Hooks ========================================
1483 /// \brief Hook that is called to determine general rules of the game.
1484 MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
1486 surv_warmup = warmup_stage;
1489 /// \brief Hook that is called to determine if there is a weapon arena.
1490 MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
1492 // Removing any weapon arena.
1493 M_ARGV(0, string) = "off";
1496 /// \brief Hook that is called to determine start items of all players.
1497 MUTATOR_HOOKFUNCTION(surv, SetStartItems)
1499 if (autocvar_g_instagib == 1)
1503 start_weapons = WEPSET(Null);
1504 warmup_start_weapons = WEPSET(Null);
1507 /// \brief Hook that is called on every frame.
1508 MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
1510 if (game_stopped || !surv_isroundactive)
1514 float roundtime = 0;
1515 switch (surv_roundtype)
1517 case SURVIVAL_ROUND_FIRST:
1519 roundtime = time - surv_roundstarttime;
1522 case SURVIVAL_ROUND_SECOND:
1524 roundtime = round_handler_GetEndTime() - time;
1528 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1530 it.surv_round_time_stat = roundtime;
1534 /// \brief Hook that determines which team player can join. This is called
1535 /// before ClientConnect.
1536 MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
1538 entity player = M_ARGV(2, entity);
1539 LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
1542 return SURVIVAL_TEAM_BITS;
1544 if (IS_BOT_CLIENT(player))
1546 int teambits = surv_attackerteambit;
1547 if ((player.team == surv_defenderteam) || (surv_numdefenders <
1548 autocvar_g_surv_team_size))
1550 teambits |= surv_defenderteambit;
1552 M_ARGV(0, float) = teambits;
1555 if (surv_type == SURVIVAL_TYPE_COOP)
1557 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1559 M_ARGV(0, float) = surv_defenderteambit;
1562 M_ARGV(0, float) = 0;
1566 if (surv_numattackerhumans < autocvar_g_surv_team_size)
1568 LOG_TRACE("Player can join attackers");
1569 teambits |= surv_attackerteambit;
1571 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1573 LOG_TRACE("Player can join defenders");
1574 teambits |= surv_defenderteambit;
1576 M_ARGV(0, float) = teambits;
1580 /// \brief Hook that override team counts.
1581 MUTATOR_HOOKFUNCTION(surv, GetTeamCounts, CBC_ORDER_EXCLUSIVE)
1586 /// \brief Hook that sets the team count.
1587 MUTATOR_HOOKFUNCTION(surv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
1589 float teamnum = M_ARGV(0, float);
1590 entity ignore = M_ARGV(1, entity);
1593 case surv_attackerteam:
1595 M_ARGV(2, float) = surv_numattackers;
1596 M_ARGV(3, float) = surv_numattackers - surv_numattackerhumans;
1597 if (ignore.team == surv_attackerteam)
1600 if (IS_BOT_CLIENT(ignore))
1605 entity lowestplayer = NULL;
1606 float lowestplayerscore = FLOAT_MAX;
1607 entity lowestbot = NULL;
1608 float lowestbotscore = FLOAT_MAX;
1609 FOREACH_CLIENT((it.team == surv_attackerteam) && (it.surv_role ==
1610 SURVIVAL_ROLE_PLAYER),
1616 if (IS_BOT_CLIENT(it))
1618 float tempscore = PlayerScore_Get(it, SP_SCORE);
1619 if (tempscore < lowestbotscore)
1622 lowestbotscore = tempscore;
1626 float tempscore = PlayerScore_Get(it, SP_SCORE);
1627 if (tempscore < lowestplayerscore)
1630 lowestplayerscore = tempscore;
1633 M_ARGV(4, entity) = lowestplayer;
1634 M_ARGV(5, entity) = lowestbot;
1637 case surv_defenderteam:
1639 M_ARGV(2, float) = surv_numdefenders;
1640 M_ARGV(3, float) = surv_numdefenders - surv_numdefenderhumans;
1641 if (ignore.team == surv_defenderteam)
1644 if (IS_BOT_CLIENT(ignore))
1649 entity lowestplayer = NULL;
1650 float lowestplayerscore = FLOAT_MAX;
1651 entity lowestbot = NULL;
1652 float lowestbotscore = FLOAT_MAX;
1653 FOREACH_CLIENT((it.team == surv_defenderteam),
1659 if (IS_BOT_CLIENT(it))
1661 float tempscore = PlayerScore_Get(it, SP_SCORE);
1662 if (tempscore < lowestbotscore)
1665 lowestbotscore = tempscore;
1669 float tempscore = PlayerScore_Get(it, SP_SCORE);
1670 if (tempscore < lowestplayerscore)
1673 lowestplayerscore = tempscore;
1676 M_ARGV(4, entity) = lowestplayer;
1677 M_ARGV(5, entity) = lowestbot;
1684 /// \brief Hook that determines the best teams for the player to join.
1685 MUTATOR_HOOKFUNCTION(surv, FindBestTeams, CBC_ORDER_EXCLUSIVE)
1687 if (surv_type == SURVIVAL_TYPE_COOP)
1691 entity player = M_ARGV(0, entity);
1692 if (IS_BOT_CLIENT(player))
1696 int numattackerhumans = surv_numattackerhumans;
1697 int numdefenderhumans = surv_numdefenderhumans;
1698 if (player.team == surv_attackerteam)
1700 --numattackerhumans;
1702 else if (player.team == surv_defenderteam)
1704 --numdefenderhumans;
1706 if (numattackerhumans < numdefenderhumans)
1708 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_attackerteam) - 1);
1711 if (numattackerhumans > numdefenderhumans)
1713 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_defenderteam) - 1);
1716 M_ARGV(1, float) = SURVIVAL_TEAM_BITS;
1720 /// \brief Hook that is called when player has changed the team.
1721 MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
1723 entity player = M_ARGV(0, entity);
1724 int oldteam = M_ARGV(1, float);
1725 int newteam = M_ARGV(2, float);
1726 string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
1727 ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
1729 DebugPrintToChatAll(message);
1730 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1732 Surv_RemovePlayerFromAliveList(player, oldteam);
1734 Surv_RemovePlayerFromTeam(player, oldteam);
1735 if (Surv_AddPlayerToTeam(player, newteam) == false)
1739 //Surv_CountAlivePlayers();
1740 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1742 Surv_AddPlayerToAliveList(player, newteam);
1746 /// \brief Hook that is called when player is about to be killed when changing
1748 MUTATOR_HOOKFUNCTION(surv, Player_ChangeTeamKill)
1750 entity player = M_ARGV(0, entity);
1751 if (player.team != surv_defenderteam)
1755 if (player.surv_savedplayerstate == NULL)
1759 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1760 delete(player.surv_savedplayerstate);
1761 player.surv_savedplayerstate = NULL;
1765 /// \brief Hook that is called when player is about to be killed as a result of
1766 /// the kill command or changing teams.
1767 MUTATOR_HOOKFUNCTION(surv, ClientKill_Now)
1769 entity player = M_ARGV(0, entity);
1770 if (player.team == surv_defenderteam)
1777 /// \brief Hook that is called when player connects to the server.
1778 MUTATOR_HOOKFUNCTION(surv, ClientConnect)
1780 entity player = M_ARGV(0, entity);
1781 LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
1782 player.surv_savedplayermodel = player.playermodel;
1783 if (IS_REAL_CLIENT(player))
1785 player.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1786 player.surv_defenders_alive_stat = surv_numdefendersalive;
1787 player.redalive_stat = redalive;
1788 player.bluealive_stat = bluealive;
1789 player.yellowalive_stat = yellowalive;
1790 player.pinkalive_stat = pinkalive;
1792 if (player.surv_role == SURVIVAL_ROLE_NONE)
1794 Surv_AddPlayerToTeam(player, player.team);
1799 /// \brief Hook that is called when player disconnects from the server.
1800 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
1802 entity player = M_ARGV(0, entity);
1803 if (!IS_DEAD(player))
1805 Surv_RemovePlayerFromAliveList(player, player.team);
1807 Surv_RemovePlayerFromTeam(player, player.team);
1808 //Surv_CountAlivePlayers();
1811 /// \brief Hook that determines whether player can spawn. It is not called for
1812 /// players who have joined the team and are dead.
1813 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
1815 entity player = M_ARGV(0, entity);
1816 LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
1817 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1821 return !Surv_CanPlayerSpawn(player);
1824 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
1826 entity player = M_ARGV(0, entity);
1827 LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
1828 if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
1830 LOG_TRACE("Transmuting to observer");
1831 TRANSMUTE(Observer, player);
1835 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
1837 entity player = M_ARGV(0, entity);
1838 LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
1839 if (player.killindicator_teamchange == -2) // player wants to spectate
1841 LOG_TRACE("killindicator_teamchange == -2");
1842 player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
1844 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1846 return false; // allow team reset
1848 return true; // prevent team reset
1851 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
1853 LOG_TRACE("Survival: reset_map_global");
1854 surv_allowed_to_spawn = true;
1855 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1857 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1859 it.surv_round_time_stat = 0;
1865 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
1867 LOG_TRACE("Survival: reset_map_players");
1868 surv_numattackersalive = 0;
1869 surv_numdefendersalive = 0;
1872 surv_warmup = false;
1874 else if (surv_type == SURVIVAL_TYPE_VERSUS)
1878 FOREACH_CLIENT(true,
1881 if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
1884 it.surv_state = SURVIVAL_STATE_PLAYING;
1886 if (it.surv_state == SURVIVAL_STATE_PLAYING)
1888 TRANSMUTE(Player, it);
1889 it.surv_state = SURVIVAL_STATE_PLAYING;
1890 PutClientInServer(it);
1893 bot_relinkplayerlist();
1897 /// \brief Hook that is called when player spawns.
1898 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
1900 entity player = M_ARGV(0, entity);
1901 LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
1902 player.surv_state = SURVIVAL_STATE_PLAYING;
1903 Surv_DeterminePlayerModel(player);
1904 if (player.surv_savedplayerstate != NULL)
1906 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1907 delete(player.surv_savedplayerstate);
1908 player.surv_savedplayerstate = NULL;
1912 PlayerTemplate_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
1914 switch (player.team)
1916 case surv_attackerteam:
1918 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
1920 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1921 CENTER_ASSAULT_ATTACKING);
1925 case surv_defenderteam:
1927 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1928 CENTER_ASSAULT_DEFENDING);
1932 //Surv_CountAlivePlayers();
1933 Surv_AddPlayerToAliveList(player, player.team);
1936 /// \brief UGLY HACK. This is called every frame to keep player model correct.
1937 MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
1939 entity player = M_ARGV(2, entity);
1940 M_ARGV(0, string) = player.surv_playermodel;
1943 /// \brief Hook that is called every frame to determine how player health should
1945 MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
1947 entity player = M_ARGV(0, entity);
1948 if (player.team == surv_defenderteam)
1955 /// \brief Hook that is called to determine if balance messages will appear.
1956 MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
1961 /// \brief Hook that is called when player touches an item.
1962 MUTATOR_HOOKFUNCTION(surv, ItemTouch)
1964 entity item = M_ARGV(0, entity);
1965 entity player = M_ARGV(1, entity);
1966 switch (player.team)
1968 case surv_attackerteam:
1970 return PlayerTemplate_ItemTouch(player, item,
1971 Surv_GetPlayerTemplate(player));
1973 case surv_defenderteam:
1975 switch (item.classname)
1977 case "item_strength":
1979 W_GiveWeapon(player, WEP_HMG.m_id);
1980 player.superweapons_finished = max(
1981 player.superweapons_finished, time) +
1982 autocvar_g_balance_superweapons_time;
1983 Item_ScheduleRespawn(item);
1984 sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
1986 return MUT_ITEMTOUCH_RETURN;
1988 case "item_invincible":
1990 W_GiveWeapon(player, WEP_RPC.m_id);
1991 player.superweapons_finished = max(
1992 player.superweapons_finished, time) +
1993 autocvar_g_balance_superweapons_time;
1994 Item_ScheduleRespawn(item);
1995 sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
1996 return MUT_ITEMTOUCH_RETURN;
2000 return PlayerTemplate_ItemTouch(player, item,
2001 Surv_GetPlayerTemplate(player));
2004 DebugPrintToChat(player, item.classname);
2005 return MUT_ITEMTOUCH_RETURN;
2008 return MUT_ITEMTOUCH_CONTINUE;
2011 /// \brief Hook which is called when the player tries to throw their weapon.
2012 MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
2014 entity player = M_ARGV(0, entity);
2015 if (player.team == surv_defenderteam)
2021 /// \brief Hook which is called when the damage amount must be determined.
2022 MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
2024 entity frag_attacker = M_ARGV(1, entity);
2025 entity frag_target = M_ARGV(2, entity);
2026 float deathtype = M_ARGV(3, float);
2027 float damage = M_ARGV(4, float);
2028 M_ARGV(4, float) = PlayerTemplate_Damage_Calculate(frag_attacker,
2029 Surv_GetPlayerTemplate(frag_attacker), frag_target,
2030 Surv_GetPlayerTemplate(frag_target), deathtype, damage);
2033 /// \brief Hook which is called when the player was damaged.
2034 MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
2036 entity target = M_ARGV(1, entity);
2037 if (target.team != surv_defenderteam)
2041 Surv_UpdateDefenderHealthStat();
2042 entity attacker = M_ARGV(0, entity);
2043 if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
2044 SURVIVAL_ROLE_PLAYER))
2046 float health = M_ARGV(2, float);
2047 float armor = M_ARGV(3, float);
2048 float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
2049 PlayerScore_Add(attacker, SP_SCORE, score);
2051 if (autocvar_g_surv_stealth)
2055 if (target.health < 1)
2057 WaypointSprite_Kill(target.surv_attack_sprite);
2061 if (autocvar_g_instagib == 1)
2063 WaypointSprite_UpdateHealth(target.surv_attack_sprite,
2064 target.armorvalue + 1);
2068 WaypointSprite_UpdateHealth(target.surv_attack_sprite,
2069 target.health + target.armorvalue);
2074 /// \brief Hook which is called when the player dies.
2075 MUTATOR_HOOKFUNCTION(surv, PlayerDies)
2077 //DebugPrintToChatAll("PlayerDies");
2078 entity attacker = M_ARGV(1, entity);
2079 entity victim = M_ARGV(2, entity);
2080 if ((attacker.team == surv_defenderteam) &&
2081 (victim.team == surv_attackerteam))
2083 switch (victim.surv_role)
2085 case SURVIVAL_ROLE_PLAYER:
2087 GivePlayerHealth(attacker,
2088 autocvar_g_surv_defender_attacker_frag_health);
2089 GivePlayerArmor(attacker,
2090 autocvar_g_surv_defender_attacker_frag_armor);
2091 GivePlayerAmmo(attacker, ammo_shells,
2092 autocvar_g_surv_defender_attacker_frag_shells);
2093 GivePlayerAmmo(attacker, ammo_nails,
2094 autocvar_g_surv_defender_attacker_frag_bullets);
2095 GivePlayerAmmo(attacker, ammo_rockets,
2096 autocvar_g_surv_defender_attacker_frag_rockets);
2097 GivePlayerAmmo(attacker, ammo_cells,
2098 autocvar_g_surv_defender_attacker_frag_cells);
2101 case SURVIVAL_ROLE_CANNON_FODDER:
2103 GivePlayerHealth(attacker,
2104 autocvar_g_surv_defender_cannon_fodder_frag_health);
2105 GivePlayerArmor(attacker,
2106 autocvar_g_surv_defender_cannon_fodder_frag_armor);
2107 GivePlayerAmmo(attacker, ammo_shells,
2108 autocvar_g_surv_defender_cannon_fodder_frag_shells);
2109 GivePlayerAmmo(attacker, ammo_nails,
2110 autocvar_g_surv_defender_cannon_fodder_frag_bullets);
2111 GivePlayerAmmo(attacker, ammo_rockets,
2112 autocvar_g_surv_defender_cannon_fodder_frag_rockets);
2113 GivePlayerAmmo(attacker, ammo_cells,
2114 autocvar_g_surv_defender_cannon_fodder_frag_cells);
2119 if ((victim.team == surv_defenderteam) &&
2120 (autocvar_g_surv_defender_drop_weapons == false))
2122 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2124 .entity went = weaponentities[slot];
2125 victim.(went).m_weapon = WEP_Null;
2128 if (!Surv_CanPlayerSpawn(victim))
2130 victim.respawn_flags = RESPAWN_SILENT;
2131 if (IS_BOT_CLIENT(victim))
2139 /// \brief Hook which is called after the player died.
2140 MUTATOR_HOOKFUNCTION(surv, PlayerDied)
2142 //DebugPrintToChatAll("PlayerDied");
2143 entity player = M_ARGV(0, entity);
2144 Surv_RemovePlayerFromAliveList(player, player.team);
2145 //Surv_CountAlivePlayers();
2148 /// \brief Hook which is called when player has scored a frag.
2149 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
2151 if (surv_type == SURVIVAL_TYPE_COOP)
2155 entity attacker = M_ARGV(0, entity);
2156 if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
2157 SURVIVAL_ROLE_CANNON_FODDER))
2159 M_ARGV(2, float) = 0;
2162 entity target = M_ARGV(1, entity);
2163 if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
2166 M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
2171 /// \brief I'm not sure exactly what this function does but it is very
2172 /// important. Without it bots are completely broken. Is it a hack? Of course.
2173 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
2175 FOREACH_CLIENT(IS_REAL_CLIENT(it),
2177 if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
2186 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
2188 // Don't announce remaining frags