1 #include "gamemode_survival.qh"
3 #include <common/mutators/mutator/overkill/hmg.qh>
4 #include <common/mutators/mutator/overkill/rpc.qh>
6 //============================ Constants ======================================
8 const int SURVIVAL_TEAM_1_BIT = BIT(0); ///< Bitmask of the first team.
9 const int SURVIVAL_TEAM_2_BIT = BIT(1); ///< Bitmask of the second team.
11 /// \brief Used when bitfield team count is requested.
12 const int SURVIVAL_TEAM_BITS = 3;
16 SURVIVAL_TYPE_COOP, ///< All humans are on the defender team.
17 SURVIVAL_TYPE_VERSUS ///< Humans take turns between attackers and defenders.
20 const string SURVIVAL_TYPE_COOP_VALUE = "coop"; ///< Cvar value for coop.
21 const string SURVIVAL_TYPE_VERSUS_VALUE = "versus"; ///< Cvar value for versus.
25 /// \brief First round where there is timelimit set by the server.
27 /// \brief Second round where defender team tries to survive for the first
34 /// \brief Player is spectating and has no intention of playing.
35 SURVIVAL_STATE_NOT_PLAYING,
36 /// \brief Player is playing the game.
37 SURVIVAL_STATE_PLAYING = 1
42 SURVIVAL_ROLE_NONE, ///< Player is not playing.
43 SURVIVAL_ROLE_PLAYER, ///< Player is an attacker or defender.
44 SURVIVAL_ROLE_CANNON_FODDER ///< Player is a cannon fodder.
47 SOUND(SURV_3_FRAGS_LEFT, "announcer/default/3fragsleft");
48 SOUND(SURV_2_FRAGS_LEFT, "announcer/default/2fragsleft");
49 SOUND(SURV_1_FRAG_LEFT, "announcer/default/1fragleft");
51 SOUND(SURV_RED_SCORES, "ctf/red_capture");
52 SOUND(SURV_BLUE_SCORES, "ctf/blue_capture");
54 //======================= Global variables ====================================
56 float autocvar_g_surv_warmup; ///< Warmup time in seconds.
57 float autocvar_g_surv_round_timelimit; ///< First round time limit in seconds.
59 int autocvar_g_surv_point_limit; ///< Maximum number of points.
60 int autocvar_g_surv_point_leadlimit; ///< Maximum lead of a single team.
62 /// \brief How much players are allowed in teams (excluding cannon fodder).
63 int autocvar_g_surv_team_size;
64 /// \brief If set, defenders will not be shown on the radar.
65 int autocvar_g_surv_stealth;
66 /// \brief Whether to allow spectating enemy players while dead.
67 bool autocvar_g_surv_spectate_enemies;
69 /// \brief Whether to force overkill player models for attackers.
70 int autocvar_g_surv_attacker_force_overkill_models;
71 /// \brief Whether to force overkill player models for defenders.
72 int autocvar_g_surv_defender_force_overkill_models;
73 /// \brief Whether to force overkill player models for cannon fodder.
74 int autocvar_g_surv_cannon_fodder_force_overkill_models;
76 /// \brief How much score attackers gain per 1 point of damage.
77 float autocvar_g_surv_attacker_damage_score;
79 /// \brief How much score attackers get for fragging defenders.
80 float autocvar_g_surv_attacker_frag_score;
82 /// \brief How much health do defenders get when they frag an attacker.
83 int autocvar_g_surv_defender_attacker_frag_health;
84 /// \brief How much armor do defenders get when they frag an attacker.
85 int autocvar_g_surv_defender_attacker_frag_armor;
86 /// \brief How many shells do defenders get when they frag an attacker.
87 int autocvar_g_surv_defender_attacker_frag_shells;
88 /// \brief How many bullets do defenders get when they frag an attacker.
89 int autocvar_g_surv_defender_attacker_frag_bullets;
90 /// \brief How many rockets do defenders get when they frag an attacker.
91 int autocvar_g_surv_defender_attacker_frag_rockets;
92 /// \brief How many cells do defenders get when they frag an attacker.
93 int autocvar_g_surv_defender_attacker_frag_cells;
94 /// \brief How much health do defenders get when they frag cannon fodder.
95 int autocvar_g_surv_defender_cannon_fodder_frag_health;
96 /// \brief How much armor do defenders get when they frag cannon fodder.
97 int autocvar_g_surv_defender_cannon_fodder_frag_armor;
98 /// \brief How many shells do defenders get when they frag cannon fodder.
99 int autocvar_g_surv_defender_cannon_fodder_frag_shells;
100 /// \brief How many bullets do defenders get when they frag cannon fodder.
101 int autocvar_g_surv_defender_cannon_fodder_frag_bullets;
102 /// \brief How many rockets do defenders get when they frag cannon fodder.
103 int autocvar_g_surv_defender_cannon_fodder_frag_rockets;
104 /// \brief How many cells do defenders get when they frag cannon fodder.
105 int autocvar_g_surv_defender_cannon_fodder_frag_cells;
107 /// \brief A stat that is used to track the time left in the round.
108 .float surv_round_time_stat = _STAT(SURV_ROUND_TIME);
109 /// \brief A stat that is used to track defender team.
110 .int surv_defender_team_stat = _STAT(SURV_DEFENDER_TEAM);
111 /// \brief A stat that is used to track number of defenders alive.
112 .int surv_defenders_alive_stat = _STAT(SURV_DEFENDERS_ALIVE);
113 /// \brief A stat that is used to track the total health of defenders.
114 .float surv_defender_health_stat = _STAT(SURV_DEFENDER_HEALTH);
116 /// \brief Holds the state of the player. See SURVIVAL_STATE constants.
118 /// \brief Holds the role of the player. See SURVIVAL_ROLE constants.
120 .string surv_savedplayermodel; ///< Initial player model.
121 /// \brief Player state used during replacement of bot player with real player.
122 .entity surv_savedplayerstate;
123 .string surv_playermodel; ///< Player model forced by the game.
125 .entity surv_attack_sprite; ///< Holds the sprite telling attackers to attack.
127 int surv_type; ///< Holds the type of survival. See SURVIVAL_TYPE constants.
128 bool surv_warmup; ///< Holds whether warmup is active.
129 /// \brief Holds the type of the current round. See SURVIVAL_ROUND constants.
131 bool surv_isroundactive; ///< Holds whether the round is active.
132 float surv_roundstarttime; ///< Holds the time of the round start.
133 /// \brief Holds the time needed to survive in the second round.
134 float surv_timetobeat;
136 int surv_attackerteam; ///< Holds the attacker team.
137 int surv_defenderteam; ///< Holds the defender team.
139 int surv_attackerteambit; ///< Hols the attacker team bitmask.
140 int surv_defenderteambit; ///< Holds the defender team bitmask.
142 int surv_numattackers; ///< Holds the number of players in attacker team.
143 int surv_numdefenders; ///< Holds the number of players in defender team.
145 /// \brief Holds the number of humans in attacker team.
146 int surv_numattackerhumans;
147 /// \brief Holds the number of humans in defender team.
148 int surv_numdefenderhumans;
150 /// \brief Holds the number of attacker players that are alive.
151 int surv_numattackersalive;
152 /// \brief Holds the number of defender players that are alive.
153 int surv_numdefendersalive;
155 bool surv_autobalance; ///< Holds whether autobalance is active.
156 bool surv_announcefrags; ///< Holds whether remaining frags must be announced.
157 bool surv_allowed_to_spawn; ///< Holds whether players are allowed to spawn.
159 //====================== Forward declarations =================================
161 /// \brief Determines whether the round can start.
162 /// \return True if the round can start, false otherwise.
163 bool Surv_CanRoundStart();
165 /// \brief Determines whether the round can end.
166 /// \return True if the round can end, false otherwise.
167 bool Surv_CanRoundEnd();
169 /// \brief Called when the round starts.
170 /// \return No return.
171 void Surv_RoundStart();
173 /// \brief Returns whether player has been eliminated.
174 /// \param[in] player Player to check.
175 /// \return True if player is eliminated, false otherwise.
176 bool Surv_IsEliminated(entity player);
178 /// \brief Updates stats of team count on HUD.
179 /// \return No return.
180 void Surv_UpdateTeamStats();
182 /// \brief Updates stats of alive players on HUD.
183 /// \return No return.
184 void Surv_UpdateAliveStats();
186 /// \brief Updates defender health on the HUD.
187 /// \return No return.
188 void Surv_UpdateDefenderHealthStat();
190 //========================= Free functions ====================================
192 void Surv_Initialize()
194 switch (cvar_string("g_surv_type"))
196 case SURVIVAL_TYPE_COOP_VALUE:
198 surv_type = SURVIVAL_TYPE_COOP;
201 case SURVIVAL_TYPE_VERSUS_VALUE:
203 surv_type = SURVIVAL_TYPE_VERSUS;
208 error("Invalid survival type.");
211 surv_roundtype = SURVIVAL_ROUND_FIRST;
212 surv_isroundactive = false;
213 surv_roundstarttime = time;
214 surv_timetobeat = autocvar_g_surv_round_timelimit;
217 surv_attackerteam = NUM_TEAM_1;
218 surv_defenderteam = NUM_TEAM_2;
219 surv_attackerteambit = SURVIVAL_TEAM_1_BIT;
220 surv_defenderteambit = SURVIVAL_TEAM_2_BIT;
224 surv_attackerteam = NUM_TEAM_2;
225 surv_defenderteam = NUM_TEAM_1;
226 surv_attackerteambit = SURVIVAL_TEAM_2_BIT;
227 surv_defenderteambit = SURVIVAL_TEAM_1_BIT;
229 surv_numattackers = 0;
230 surv_numdefenders = 0;
231 surv_numattackerhumans = 0;
232 surv_numdefenderhumans = 0;
233 surv_numattackersalive = 0;
234 surv_numdefendersalive = 0;
235 surv_autobalance = true;
236 surv_announcefrags = true;
237 surv_allowed_to_spawn = true;
238 precache_all_playermodels("models/ok_player/*.dpm");
239 ScoreRules_basics(SURVIVAL_TEAM_BITS, SFL_SORT_PRIO_PRIMARY, 0, true);
240 ScoreInfo_SetLabel_TeamScore(1, "rounds", SFL_SORT_PRIO_PRIMARY);
241 ScoreRules_basics_end();
242 round_handler_Spawn(Surv_CanRoundStart, Surv_CanRoundEnd, Surv_RoundStart);
243 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
244 EliminatedPlayers_Init(Surv_IsEliminated);
246 SetLimits(autocvar_g_surv_point_limit, autocvar_g_surv_point_leadlimit,
247 autocvar_timelimit_override, -1);
250 /// \brief Returns the name of the template of the given player.
251 /// \param[in] player Player to inspect.
252 /// \return Name of the template of the given player.
253 string Surv_GetPlayerTemplate(entity player)
257 case surv_attackerteam:
259 switch (player.surv_role)
261 case SURVIVAL_ROLE_NONE:
262 case SURVIVAL_ROLE_PLAYER:
264 return cvar_string("g_surv_attacker_template");
266 case SURVIVAL_ROLE_CANNON_FODDER:
268 return cvar_string("g_surv_cannon_fodder_template");
272 case surv_defenderteam:
274 return cvar_string("g_surv_defender_template");
280 /// \brief Saves the player state. Used to seamlessly swap bots with humans.
281 /// \param[in] player Player to save the state of.
282 /// \return Entity containing the player state.
283 entity Surv_SavePlayerState(entity player)
285 entity state = spawn();
286 state.origin = player.origin;
287 state.velocity = player.velocity;
288 state.angles = player.angles;
289 state.health = player.health;
290 state.armorvalue = player.armorvalue;
291 state.ammo_shells = player.ammo_shells;
292 state.ammo_nails = player.ammo_nails;
293 state.ammo_rockets = player.ammo_rockets;
294 state.ammo_cells = player.ammo_cells;
295 state.weapons = player.weapons;
296 state.items = player.items;
297 state.superweapons_finished = player.superweapons_finished;
301 /// \brief Restores a saved player state.
302 /// \param[in] player Player to restore the state of.
303 /// \param[in] st State to restore.
304 /// \return No return.
305 void Surv_RestorePlayerState(entity player, entity st)
307 player.origin = st.origin;
308 player.velocity = st.velocity;
309 player.angles = st.angles;
310 player.health = st.health;
311 player.armorvalue = st.armorvalue;
312 player.ammo_shells = st.ammo_shells;
313 player.ammo_nails = st.ammo_nails;
314 player.ammo_rockets = st.ammo_rockets;
315 player.ammo_cells = st.ammo_cells;
316 player.weapons = st.weapons;
317 player.items = st.items;
318 player.superweapons_finished = st.superweapons_finished;
321 /// \brief Returns the attacker with the lowest score.
322 /// \param[in] bot Whether to search only for bots.
323 /// \return Attacker with the lowest score or NULL if not found.
324 entity Surv_FindLowestAttacker(bool bot)
326 entity player = NULL;
327 float score = FLOAT_MAX;
328 FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
330 if ((it.team == surv_attackerteam) && (it.surv_role ==
331 SURVIVAL_ROLE_PLAYER))
333 float tempscore = PlayerScore_Get(it, SP_SCORE);
334 if (tempscore < score)
344 /// \brief Returns the defender with the lowest score.
345 /// \param[in] bot Whether to search only for bots.
346 /// \param[in] alive Whether to search only for alive players.
347 /// \return Defender with the lowest score or NULL if not found.
348 entity Surv_FindLowestDefender(bool bot, bool alive)
350 entity player = NULL;
351 float score = FLOAT_MAX;
352 FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
354 if ((it.team == surv_defenderteam) && (alive ? !IS_DEAD(it) : true))
356 float tempscore = PlayerScore_Get(it, SP_SCORE);
357 if (tempscore < score)
367 /// \brief Returns the cannon fodder.
368 /// \return Cannon fodder or NULL if not found.
369 entity Surv_FindCannonFodder()
371 FOREACH_CLIENT(IS_BOT_CLIENT(it),
373 if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
381 /// \brief Changes the number of players in a team.
382 /// \param[in] teamnum Team to adjust.
383 /// \param[in] delta Amount to adjust by.
384 /// \return No return.
385 void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
389 case surv_attackerteam:
391 surv_numattackers += delta;
392 LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
393 " was = ", ftos(surv_numattackers - delta));
394 Surv_UpdateTeamStats();
397 case surv_defenderteam:
399 surv_numdefenders += delta;
400 LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
401 " was = ", ftos(surv_numdefenders - delta));
402 Surv_UpdateTeamStats();
408 /// \brief Changes the number of alive players in a team.
409 /// \param[in] teamnum Team to adjust.
410 /// \param[in] delta Amount to adjust by.
411 /// \return No return.
412 void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
416 case surv_attackerteam:
418 surv_numattackersalive += delta;
419 LOG_TRACE("Number of alive attackers = ", ftos(
420 surv_numattackersalive), " was = ", ftos(surv_numattackersalive
424 case surv_defenderteam:
426 surv_numdefendersalive += delta;
427 LOG_TRACE("Number of alive defenders = ", ftos(
428 surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
433 Surv_UpdateAliveStats();
434 eliminatedPlayers.SendFlags |= 1;
437 /// \brief Sets the player role.
438 /// \param[in,out] player Player to adjust.
439 /// \param[in] role Role to set.
440 /// \return No return.
441 void Surv_SetPlayerRole(entity player, int role)
443 if (player.surv_role == role)
447 player.surv_role = role;
450 case SURVIVAL_ROLE_NONE:
452 LOG_TRACE(player.netname, " now has no role.");
455 case SURVIVAL_ROLE_PLAYER:
457 LOG_TRACE(player.netname, " is now a player.");
460 case SURVIVAL_ROLE_CANNON_FODDER:
462 LOG_TRACE(player.netname, " is now a cannon fodder.");
468 /// \brief Adds player to team. Handles bookkeeping information.
469 /// \param[in] player Player to add.
470 /// \param[in] teamnum Team to add to.
471 /// \return True on success, false otherwise.
472 bool Surv_AddPlayerToTeam(entity player, int teamnum)
474 LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
477 case surv_attackerteam:
479 LOG_TRACE("Attacker team");
480 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
482 LOG_TRACE("Cannon fodder is switching team");
485 if (IS_BOT_CLIENT(player))
487 LOG_TRACE("Client is bot");
488 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
489 if (surv_numattackers < autocvar_g_surv_team_size)
491 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
492 Surv_ChangeNumberOfPlayers(teamnum, +1);
495 Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
498 LOG_TRACE("Client is not a bot");
499 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
500 if (surv_numattackers >= autocvar_g_surv_team_size)
502 LOG_TRACE("Removing bot");
503 // Remove bot to make space for human.
504 entity bot = Surv_FindLowestAttacker(true);
507 LOG_TRACE("No valid bot to remove");
508 // No space in team, denying team change.
509 TRANSMUTE(Spectator, player);
512 LOG_TRACE("Changing ", bot.netname,
513 " from attacker to cannon fodder.");
514 Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
517 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
519 Surv_ChangeNumberOfPlayers(teamnum, -1);
520 LOG_TRACE("Removed bot");
522 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
523 Surv_ChangeNumberOfPlayers(teamnum, +1);
524 ++surv_numattackerhumans;
525 LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
526 if ((surv_autobalance == false) || (surv_numattackers -
527 surv_numdefenders) < 2)
531 entity lowestplayer = Surv_FindLowestAttacker(true);
532 if (lowestplayer != NULL)
534 bool savedautobalance = surv_autobalance;
535 surv_autobalance = false;
536 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
537 surv_autobalance = savedautobalance;
540 lowestplayer = Surv_FindLowestAttacker(false);
541 if (lowestplayer != NULL)
543 bool savedautobalance = surv_autobalance;
544 surv_autobalance = false;
545 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
546 surv_autobalance = savedautobalance;
550 case surv_defenderteam:
552 LOG_TRACE("Defender team");
553 if (IS_BOT_CLIENT(player))
555 LOG_TRACE("Client is bot");
556 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
557 if (surv_numdefenders < autocvar_g_surv_team_size)
559 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
560 Surv_ChangeNumberOfPlayers(teamnum, +1);
563 LOG_TRACE("No space for defender, switching to attacker");
564 SetPlayerTeamSimple(player, surv_attackerteam);
567 LOG_TRACE("Client is not a bot");
568 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
569 if (surv_numdefenders >= autocvar_g_surv_team_size)
571 LOG_TRACE("Removing bot");
572 // Remove bot to make space for human.
573 entity bot = Surv_FindLowestDefender(true, true);
576 bot = Surv_FindLowestDefender(true, false);
580 LOG_TRACE("No valid bot to remove");
581 // No space in team, denying team change.
582 TRANSMUTE(Spectator, player);
585 LOG_TRACE("Changing ", bot.netname,
586 " from defender to cannon fodder.");
589 player.surv_savedplayerstate = Surv_SavePlayerState(bot);
591 bool savedautobalance = surv_autobalance;
592 surv_autobalance = false;
593 surv_announcefrags = false;
594 SetPlayerTeamSimple(bot, surv_attackerteam);
595 surv_autobalance = savedautobalance;
596 surv_announcefrags = true;
597 LOG_TRACE("Removed bot");
599 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
600 Surv_ChangeNumberOfPlayers(teamnum, +1);
601 ++surv_numdefenderhumans;
602 LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
603 if ((surv_autobalance == false) || (surv_numdefenders -
604 surv_numattackers) < 2)
608 entity lowestplayer = Surv_FindLowestDefender(true, false);
609 if (lowestplayer != NULL)
611 bool savedautobalance = surv_autobalance;
612 surv_autobalance = false;
613 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
614 surv_autobalance = savedautobalance;
617 lowestplayer = Surv_FindLowestDefender(false, false);
618 if (lowestplayer != NULL)
620 bool savedautobalance = surv_autobalance;
621 surv_autobalance = false;
622 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
623 surv_autobalance = savedautobalance;
629 LOG_TRACE("Spectator team");
630 player.surv_role = SURVIVAL_ROLE_NONE;
634 LOG_TRACE("Invalid team");
635 player.surv_role = SURVIVAL_ROLE_NONE;
639 /// \brief Removes player from team. Handles bookkeeping information.
640 /// \param[in] player Player to remove.
641 /// \param[in] Team to remove from.
642 /// \return No return.
643 void Surv_RemovePlayerFromTeam(entity player, int teamnum)
645 LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
648 case surv_attackerteam:
650 LOG_TRACE("Attacker team");
651 if (player.surv_role == SURVIVAL_ROLE_NONE)
653 string message = strcat("RemovePlayerFromTeam: ",
654 player.netname, " has invalid role.");
655 DebugPrintToChatAll(message);
658 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
660 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
663 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
664 Surv_ChangeNumberOfPlayers(teamnum, -1);
665 if (!IS_BOT_CLIENT(player))
667 --surv_numattackerhumans;
669 if ((surv_autobalance == false) || (surv_numattackers >=
674 // Add bot to keep teams balanced.
675 entity lowestplayer = Surv_FindCannonFodder();
676 if (lowestplayer != NULL)
678 LOG_TRACE("Changing ", lowestplayer.netname,
679 " from cannon fodder to attacker.");
680 Surv_SetPlayerRole(lowestplayer, SURVIVAL_ROLE_PLAYER);
681 Surv_ChangeNumberOfPlayers(teamnum, +1);
682 if (!IS_DEAD(lowestplayer))
684 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
688 lowestplayer = Surv_FindLowestDefender(true, false);
689 if (lowestplayer != NULL)
691 bool savedautobalance = surv_autobalance;
692 surv_autobalance = false;
693 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
694 surv_autobalance = savedautobalance;
697 lowestplayer = Surv_FindLowestDefender(false, false);
698 if (lowestplayer == NULL)
702 bool savedautobalance = surv_autobalance;
703 surv_autobalance = false;
704 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
705 surv_autobalance = savedautobalance;
708 case surv_defenderteam:
710 LOG_TRACE("Defender team");
711 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
713 // This happens during team switch. We don't need to change
715 LOG_TRACE("Cannon fodder. Assuming team switch");
718 if (player.surv_role != SURVIVAL_ROLE_PLAYER)
720 string message = strcat("RemovePlayerFromTeam: ",
721 player.netname, " has invalid role.");
722 DebugPrintToChatAll(message);
725 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
726 Surv_ChangeNumberOfPlayers(teamnum, -1);
727 if (!IS_BOT_CLIENT(player))
729 --surv_numdefenderhumans;
731 if ((surv_autobalance == false) || (surv_numdefenders >=
736 // Add bot to keep teams balanced.
737 entity lowestplayer = Surv_FindCannonFodder();
738 if (lowestplayer != NULL)
740 LOG_TRACE("Changing ", lowestplayer.netname,
741 " from cannon fodder to defender.");
742 if (!IS_DEAD(player))
744 lowestplayer.surv_savedplayerstate =
745 Surv_SavePlayerState(player);
747 bool savedautobalance = surv_autobalance;
748 surv_autobalance = false;
749 surv_announcefrags = false;
750 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
751 surv_autobalance = savedautobalance;
752 surv_announcefrags = true;
755 lowestplayer = Surv_FindLowestAttacker(true);
756 if (lowestplayer != NULL)
758 bool savedautobalance = surv_autobalance;
759 surv_autobalance = false;
760 surv_announcefrags = false;
761 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
762 surv_autobalance = savedautobalance;
763 surv_announcefrags = true;
766 lowestplayer = Surv_FindLowestAttacker(false);
767 if (lowestplayer == NULL)
771 bool savedautobalance = surv_autobalance;
772 surv_autobalance = false;
773 surv_announcefrags = false;
774 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
775 surv_autobalance = savedautobalance;
776 surv_announcefrags = true;
781 LOG_TRACE("Spectator team");
786 LOG_TRACE("Invalid team");
792 /// \brief Updates stats of team count on HUD.
793 /// \return No return.
794 void Surv_UpdateTeamStats()
797 if (surv_attackerteam == NUM_TEAM_1)
799 yellowalive = surv_numattackers;
800 pinkalive = surv_numdefenders;
804 pinkalive = surv_numattackers;
805 yellowalive = surv_numdefenders;
807 FOREACH_CLIENT(IS_REAL_CLIENT(it),
809 it.yellowalive_stat = yellowalive;
810 it.pinkalive_stat = pinkalive;
814 /// \brief Adds player to alive list. Handles bookkeeping information.
815 /// \param[in] player Player to add.
816 /// \param[in] teamnum Team of the player.
817 /// \return No return.
818 void Surv_AddPlayerToAliveList(entity player, int teamnum)
822 case surv_attackerteam:
824 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
826 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
830 case surv_defenderteam:
832 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
838 /// \brief Removes player from alive list. Handles bookkeeping information.
839 /// \param[in] player Player to remove.
840 /// \param[in] teamnum Team of the player.
841 /// \return No return.
842 void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
844 if (player.surv_attack_sprite)
846 WaypointSprite_Kill(player.surv_attack_sprite);
850 case surv_attackerteam:
852 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
854 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
858 case surv_defenderteam:
860 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
862 // This happens during team switch. We don't need to change
866 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
867 if (warmup_stage || surv_allowed_to_spawn || !surv_announcefrags)
871 switch (surv_numdefendersalive)
875 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
876 VOL_BASE, ATTEN_NONE);
877 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
879 if (it.team == surv_defenderteam)
881 Send_Notification(NOTIF_ONE, it, MSG_CENTER,
890 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
891 VOL_BASE, ATTEN_NONE);
896 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
897 VOL_BASE, ATTEN_NONE);
906 /// \brief Counts alive players.
907 /// \return No return.
908 /// \note In a perfect world this function shouldn't exist. However, since QC
909 /// code is so bad and spurious mutators can really mess with your code, this
910 /// function is called as a last resort.
911 void Surv_CountAlivePlayers()
913 int savednumdefenders = surv_numdefendersalive;
914 surv_numattackersalive = 0;
915 surv_numdefendersalive = 0;
916 FOREACH_CLIENT(IS_PLAYER(it),
920 case surv_attackerteam:
922 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
924 ++surv_numattackersalive;
928 case surv_defenderteam:
930 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
932 ++surv_numdefendersalive;
938 Surv_UpdateAliveStats();
939 eliminatedPlayers.SendFlags |= 1;
940 if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
941 surv_numdefendersalive))
945 switch (surv_numdefendersalive)
949 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
950 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
952 if (it.team == surv_defenderteam)
954 Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
962 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
968 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
975 /// \brief Updates stats of alive players on HUD.
976 /// \return No return.
977 void Surv_UpdateAliveStats()
980 if (surv_attackerteam == NUM_TEAM_1)
982 redalive = surv_numattackersalive;
983 bluealive = surv_numdefendersalive;
987 bluealive = surv_numattackersalive;
988 redalive = surv_numdefendersalive;
990 FOREACH_CLIENT(IS_REAL_CLIENT(it),
992 it.surv_defenders_alive_stat = surv_numdefendersalive;
993 it.redalive_stat = redalive;
994 it.bluealive_stat = bluealive;
996 Surv_UpdateDefenderHealthStat();
999 /// \brief Updates defender health on the HUD.
1000 /// \return No return.
1001 void Surv_UpdateDefenderHealthStat()
1004 float totalhealth = 0;
1005 if (autocvar_g_instagib == 1)
1007 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1008 "surv_defender", "start_armor") + 1);
1009 FOREACH_CLIENT(IS_PLAYER(it),
1011 if (it.team == surv_defenderteam)
1013 totalhealth += it.armorvalue + 1;
1019 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1020 "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
1021 "surv_defender", "start_armor"));
1022 FOREACH_CLIENT(IS_PLAYER(it),
1024 if (it.team == surv_defenderteam)
1026 totalhealth += it.health;
1027 totalhealth += it.armorvalue;
1038 healthratio = totalhealth / maxhealth;
1040 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1042 it.surv_defender_health_stat = healthratio;
1046 /// \brief Returns whether the player can spawn.
1047 /// \param[in] player Player to check.
1048 /// \return True if the player can spawn, false otherwise.
1049 bool Surv_CanPlayerSpawn(entity player)
1051 if ((player.team == surv_attackerteam) ||
1052 (player.surv_savedplayerstate != NULL))
1056 return surv_allowed_to_spawn;
1059 /// \brief Switches the round type.
1060 /// \return No return.
1061 void Surv_SwitchRoundType()
1063 switch (surv_roundtype)
1065 case SURVIVAL_ROUND_FIRST:
1067 surv_roundtype = SURVIVAL_ROUND_SECOND;
1070 case SURVIVAL_ROUND_SECOND:
1072 surv_roundtype = SURVIVAL_ROUND_FIRST;
1078 /// \brief Cleans up the mess after the round has finished.
1079 /// \return No return.
1080 void Surv_RoundCleanup()
1082 surv_allowed_to_spawn = false;
1083 surv_isroundactive = false;
1084 game_stopped = true;
1085 FOREACH_CLIENT(true,
1087 if (it.surv_attack_sprite)
1089 WaypointSprite_Kill(it.surv_attack_sprite);
1091 if (it.surv_savedplayerstate)
1093 delete(it.surv_savedplayerstate);
1094 it.surv_savedplayerstate = NULL;
1097 if (surv_type == SURVIVAL_TYPE_VERSUS)
1099 Surv_SwitchRoundType();
1100 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
1103 round_handler_Init(5, autocvar_g_surv_warmup,
1104 autocvar_g_surv_round_timelimit);
1107 /// \brief Swaps attacker and defender teams.
1108 /// \return No return.
1109 void Surv_SwapTeams()
1111 int temp = surv_attackerteam;
1112 surv_attackerteam = surv_defenderteam;
1113 surv_defenderteam = temp;
1114 temp = surv_attackerteambit;
1115 surv_attackerteambit = surv_defenderteambit;
1116 surv_defenderteambit = temp;
1117 temp = surv_numattackers;
1118 surv_numattackers = surv_numdefenders;
1119 surv_numdefenders = temp;
1120 temp = surv_numattackerhumans;
1121 surv_numattackerhumans = surv_numdefenderhumans;
1122 surv_numdefenderhumans = temp;
1123 FOREACH_CLIENT(true,
1125 if ((it.team == surv_defenderteam) && (it.surv_role ==
1126 SURVIVAL_ROLE_CANNON_FODDER))
1128 SetPlayerTeamSimple(it, surv_attackerteam);
1131 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1133 it.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1137 /// \brief Forces the overkill model for specific player.
1138 /// \param[in,out] player Player to force the model of.
1139 /// \return No return.
1140 void Surv_ForceOverkillPlayerModel(entity player)
1142 switch (player.team)
1146 switch (floor(random() * 4))
1150 player.surv_playermodel = "models/ok_player/okrobot1.dpm";
1155 player.surv_playermodel = "models/ok_player/okrobot2.dpm";
1160 player.surv_playermodel = "models/ok_player/okrobot3.dpm";
1165 player.surv_playermodel = "models/ok_player/okrobot4.dpm";
1173 switch (floor(random() * 4))
1177 player.surv_playermodel = "models/ok_player/okmale1.dpm";
1182 player.surv_playermodel = "models/ok_player/okmale2.dpm";
1187 player.surv_playermodel = "models/ok_player/okmale3.dpm";
1192 player.surv_playermodel = "models/ok_player/okmale4.dpm";
1201 /// \brief Determines the player model to the one configured for the gamemode.
1202 /// \param[in,out] player Player to determine the model of.
1203 /// \return No return.
1204 void Surv_DeterminePlayerModel(entity player)
1206 switch (player.team)
1208 case surv_attackerteam:
1210 switch (player.surv_role)
1212 case SURVIVAL_ROLE_PLAYER:
1214 if (!autocvar_g_surv_attacker_force_overkill_models)
1216 player.surv_playermodel = player.surv_savedplayermodel;
1219 Surv_ForceOverkillPlayerModel(player);
1222 case SURVIVAL_ROLE_CANNON_FODDER:
1224 if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
1226 player.surv_playermodel = player.surv_savedplayermodel;
1229 Surv_ForceOverkillPlayerModel(player);
1234 case surv_defenderteam:
1236 if (!autocvar_g_surv_defender_force_overkill_models)
1238 player.surv_playermodel = player.surv_savedplayermodel;
1241 Surv_ForceOverkillPlayerModel(player);
1247 /// \brief Setups a waypoint sprite used to track defenders.
1248 /// \param[in] player Player to attach sprite too.
1249 /// \return No return.
1250 void Surv_SetupWaypointSprite(entity player)
1252 WaypointSprite_Spawn(WP_AssaultDestroy, 0, 0, player, '0 0 64', NULL,
1253 surv_attackerteam, player, surv_attack_sprite, false,
1254 RADARICON_OBJECTIVE);
1255 if (autocvar_g_instagib == 1)
1257 WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite,
1258 PlayerTemplate_GetFloatValue("surv_defender", "start_armor") + 1);
1259 WaypointSprite_UpdateHealth(player.surv_attack_sprite,
1260 player.armorvalue + 1);
1263 WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite,
1264 PlayerTemplate_GetFloatValue("surv_defender", "start_health") +
1265 PlayerTemplate_GetFloatValue("surv_defender", "start_armor"));
1266 WaypointSprite_UpdateHealth(player.surv_attack_sprite, player.health +
1270 //=============================== Callbacks ===================================
1272 bool Surv_CanRoundStart()
1274 return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
1277 bool Surv_CanRoundEnd()
1283 if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
1286 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1288 surv_timetobeat = time - surv_roundstarttime;
1289 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1290 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1291 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1292 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1293 Surv_RoundCleanup();
1296 surv_timetobeat = autocvar_g_surv_round_timelimit;
1297 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1298 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1299 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1300 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1301 switch (surv_defenderteam)
1305 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1311 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1316 TeamScore_AddToTeam(surv_defenderteam, 1, 1);
1317 Surv_RoundCleanup();
1320 if (surv_numdefendersalive > 0)
1324 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1326 surv_timetobeat = time - surv_roundstarttime;
1327 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1328 CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1329 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1330 INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1331 Surv_RoundCleanup();
1334 surv_timetobeat = autocvar_g_surv_round_timelimit;
1335 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1336 CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
1337 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1338 INFO_SURVIVAL_DEFENDERS_ELIMINATED);
1339 switch (surv_attackerteam)
1343 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1349 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1354 TeamScore_AddToTeam(surv_attackerteam, 1, 1);
1355 Surv_RoundCleanup();
1359 void Surv_RoundStart()
1363 surv_allowed_to_spawn = true;
1366 surv_isroundactive = true;
1367 surv_roundstarttime = time;
1368 surv_allowed_to_spawn = false;
1369 switch (surv_roundtype)
1371 case SURVIVAL_ROUND_FIRST:
1373 FOREACH_CLIENT(IS_PLAYER(it),
1375 if (it.team == surv_attackerteam)
1377 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1378 CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
1379 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1380 INFO_SURVIVAL_1ST_ROUND_ATTACKER);
1384 FOREACH_CLIENT(IS_PLAYER(it),
1386 if (it.team == surv_defenderteam)
1388 if (surv_type == SURVIVAL_TYPE_COOP)
1390 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1391 CENTER_SURVIVAL_COOP_DEFENDER);
1392 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1393 INFO_SURVIVAL_COOP_DEFENDER);
1396 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1397 CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
1398 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1399 INFO_SURVIVAL_1ST_ROUND_DEFENDER);
1405 case SURVIVAL_ROUND_SECOND:
1407 FOREACH_CLIENT(IS_PLAYER(it),
1409 if (it.team == surv_attackerteam)
1411 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1412 CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1413 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1414 INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1418 FOREACH_CLIENT(IS_PLAYER(it),
1420 if (it.team == surv_defenderteam)
1422 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1423 CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1424 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1425 INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1432 if (autocvar_g_surv_stealth)
1436 FOREACH_CLIENT(IS_PLAYER(it),
1440 case surv_defenderteam:
1442 if (it.surv_role == SURVIVAL_ROLE_PLAYER)
1444 Surv_SetupWaypointSprite(it);
1452 bool Surv_IsEliminated(entity player)
1454 switch (player.surv_state)
1456 case SURVIVAL_STATE_NOT_PLAYING:
1460 case SURVIVAL_STATE_PLAYING:
1462 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
1464 // A hack until proper scoreboard is done.
1467 if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
1468 IS_OBSERVER(player)))
1475 // Should never reach here
1479 //============================= Hooks ========================================
1481 /// \brief Hook that is called to determine general rules of the game.
1482 MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
1484 surv_warmup = warmup_stage;
1487 /// \brief Hook that is called to determine if there is a weapon arena.
1488 MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
1490 // Removing any weapon arena.
1491 M_ARGV(0, string) = "off";
1494 /// \brief Hook that is called to determine start items of all players.
1495 MUTATOR_HOOKFUNCTION(surv, SetStartItems)
1497 if (autocvar_g_instagib == 1)
1501 start_weapons = WEPSET(Null);
1502 warmup_start_weapons = WEPSET(Null);
1505 /// \brief Hook that is called on every frame.
1506 MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
1508 if (game_stopped || !surv_isroundactive)
1512 float roundtime = 0;
1513 switch (surv_roundtype)
1515 case SURVIVAL_ROUND_FIRST:
1517 roundtime = time - surv_roundstarttime;
1520 case SURVIVAL_ROUND_SECOND:
1522 roundtime = round_handler_GetEndTime() - time;
1526 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1528 it.surv_round_time_stat = roundtime;
1532 /// \brief Hook that determines which team player can join. This is called
1533 /// before ClientConnect.
1534 MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
1536 entity player = M_ARGV(2, entity);
1537 LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
1540 return SURVIVAL_TEAM_BITS;
1542 if (IS_BOT_CLIENT(player))
1544 int teambits = surv_attackerteambit;
1545 if ((player.team == surv_defenderteam) || (surv_numdefenders <
1546 autocvar_g_surv_team_size))
1548 teambits |= surv_defenderteambit;
1550 M_ARGV(0, float) = teambits;
1553 if (surv_type == SURVIVAL_TYPE_COOP)
1555 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1557 M_ARGV(0, float) = surv_defenderteambit;
1560 M_ARGV(0, float) = 0;
1564 if (surv_numattackerhumans < autocvar_g_surv_team_size)
1566 LOG_TRACE("Player can join attackers");
1567 teambits |= surv_attackerteambit;
1569 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1571 LOG_TRACE("Player can join defenders");
1572 teambits |= surv_defenderteambit;
1574 M_ARGV(0, float) = teambits;
1578 /// \brief Hook that override team counts.
1579 MUTATOR_HOOKFUNCTION(surv, GetTeamCounts, CBC_ORDER_EXCLUSIVE)
1584 /// \brief Hook that sets the team count.
1585 MUTATOR_HOOKFUNCTION(surv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
1587 float teamnum = M_ARGV(0, float);
1588 entity ignore = M_ARGV(1, entity);
1591 case surv_attackerteam:
1593 M_ARGV(2, float) = surv_numattackers;
1594 M_ARGV(3, float) = surv_numattackers - surv_numattackerhumans;
1595 if (ignore.team == surv_attackerteam)
1598 if (IS_BOT_CLIENT(ignore))
1603 entity lowestplayer = NULL;
1604 float lowestplayerscore = FLOAT_MAX;
1605 entity lowestbot = NULL;
1606 float lowestbotscore = FLOAT_MAX;
1607 FOREACH_CLIENT((it.team == surv_attackerteam) && (it.surv_role ==
1608 SURVIVAL_ROLE_PLAYER),
1614 if (IS_BOT_CLIENT(it))
1616 float tempscore = PlayerScore_Get(it, SP_SCORE);
1617 if (tempscore < lowestbotscore)
1620 lowestbotscore = tempscore;
1624 float tempscore = PlayerScore_Get(it, SP_SCORE);
1625 if (tempscore < lowestplayerscore)
1628 lowestplayerscore = tempscore;
1631 M_ARGV(4, entity) = lowestplayer;
1632 M_ARGV(5, entity) = lowestbot;
1635 case surv_defenderteam:
1637 M_ARGV(2, float) = surv_numdefenders;
1638 M_ARGV(3, float) = surv_numdefenders - surv_numdefenderhumans;
1639 if (ignore.team == surv_defenderteam)
1642 if (IS_BOT_CLIENT(ignore))
1647 entity lowestplayer = NULL;
1648 float lowestplayerscore = FLOAT_MAX;
1649 entity lowestbot = NULL;
1650 float lowestbotscore = FLOAT_MAX;
1651 FOREACH_CLIENT((it.team == surv_defenderteam),
1657 if (IS_BOT_CLIENT(it))
1659 float tempscore = PlayerScore_Get(it, SP_SCORE);
1660 if (tempscore < lowestbotscore)
1663 lowestbotscore = tempscore;
1667 float tempscore = PlayerScore_Get(it, SP_SCORE);
1668 if (tempscore < lowestplayerscore)
1671 lowestplayerscore = tempscore;
1674 M_ARGV(4, entity) = lowestplayer;
1675 M_ARGV(5, entity) = lowestbot;
1682 /// \brief Hook that determines the best teams for the player to join.
1683 MUTATOR_HOOKFUNCTION(surv, FindBestTeams, CBC_ORDER_EXCLUSIVE)
1685 if (surv_type == SURVIVAL_TYPE_COOP)
1689 entity player = M_ARGV(0, entity);
1690 if (IS_BOT_CLIENT(player))
1694 int numattackerhumans = surv_numattackerhumans;
1695 int numdefenderhumans = surv_numdefenderhumans;
1696 if (player.team == surv_attackerteam)
1698 --numattackerhumans;
1700 else if (player.team == surv_defenderteam)
1702 --numdefenderhumans;
1704 if (numattackerhumans < numdefenderhumans)
1706 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_attackerteam) - 1);
1709 if (numattackerhumans > numdefenderhumans)
1711 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_defenderteam) - 1);
1714 M_ARGV(1, float) = SURVIVAL_TEAM_BITS;
1718 /// \brief Hook that is called when player has changed the team.
1719 MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
1721 entity player = M_ARGV(0, entity);
1722 int oldteam = M_ARGV(1, float);
1723 int newteam = M_ARGV(2, float);
1724 string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
1725 ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
1727 DebugPrintToChatAll(message);
1728 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1730 Surv_RemovePlayerFromAliveList(player, oldteam);
1732 Surv_RemovePlayerFromTeam(player, oldteam);
1733 if (Surv_AddPlayerToTeam(player, newteam) == false)
1737 //Surv_CountAlivePlayers();
1738 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1740 Surv_AddPlayerToAliveList(player, newteam);
1744 /// \brief Hook that is called when player connects to the server.
1745 MUTATOR_HOOKFUNCTION(surv, ClientConnect)
1747 entity player = M_ARGV(0, entity);
1748 LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
1749 player.surv_savedplayermodel = player.playermodel;
1750 if (IS_REAL_CLIENT(player))
1752 player.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1753 player.surv_defenders_alive_stat = surv_numdefendersalive;
1754 player.redalive_stat = redalive;
1755 player.bluealive_stat = bluealive;
1756 player.yellowalive_stat = yellowalive;
1757 player.pinkalive_stat = pinkalive;
1759 if (player.surv_role == SURVIVAL_ROLE_NONE)
1761 Surv_AddPlayerToTeam(player, player.team);
1766 /// \brief Hook that is called when player disconnects from the server.
1767 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
1769 entity player = M_ARGV(0, entity);
1770 if (!IS_DEAD(player))
1772 Surv_RemovePlayerFromAliveList(player, player.team);
1774 Surv_RemovePlayerFromTeam(player, player.team);
1775 //Surv_CountAlivePlayers();
1778 /// \brief Hook that determines whether player can spawn. It is not called for
1779 /// players who have joined the team and are dead.
1780 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
1782 entity player = M_ARGV(0, entity);
1783 LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
1784 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1788 return !Surv_CanPlayerSpawn(player);
1791 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
1793 entity player = M_ARGV(0, entity);
1794 LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
1795 if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
1797 LOG_TRACE("Transmuting to observer");
1798 TRANSMUTE(Observer, player);
1802 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
1804 entity player = M_ARGV(0, entity);
1805 LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
1806 if (player.killindicator_teamchange == -2) // player wants to spectate
1808 LOG_TRACE("killindicator_teamchange == -2");
1809 player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
1811 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1813 return false; // allow team reset
1815 return true; // prevent team reset
1818 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
1820 LOG_TRACE("Survival: reset_map_global");
1821 surv_allowed_to_spawn = true;
1822 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1824 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1826 it.surv_round_time_stat = 0;
1832 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
1834 LOG_TRACE("Survival: reset_map_players");
1835 surv_numattackersalive = 0;
1836 surv_numdefendersalive = 0;
1839 surv_warmup = false;
1841 else if (surv_type == SURVIVAL_TYPE_VERSUS)
1845 FOREACH_CLIENT(true,
1848 if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
1851 it.surv_state = SURVIVAL_STATE_PLAYING;
1853 if (it.surv_state == SURVIVAL_STATE_PLAYING)
1855 TRANSMUTE(Player, it);
1856 it.surv_state = SURVIVAL_STATE_PLAYING;
1857 PutClientInServer(it);
1860 bot_relinkplayerlist();
1864 /// \brief Hook that is called when player spawns.
1865 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
1867 entity player = M_ARGV(0, entity);
1868 LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
1869 player.surv_state = SURVIVAL_STATE_PLAYING;
1870 Surv_DeterminePlayerModel(player);
1871 if (player.surv_savedplayerstate != NULL)
1873 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1874 delete(player.surv_savedplayerstate);
1875 player.surv_savedplayerstate = NULL;
1879 PlayerTemplate_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
1881 switch (player.team)
1883 case surv_attackerteam:
1885 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
1887 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1888 CENTER_ASSAULT_ATTACKING);
1892 case surv_defenderteam:
1894 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1895 CENTER_ASSAULT_DEFENDING);
1899 //Surv_CountAlivePlayers();
1900 Surv_AddPlayerToAliveList(player, player.team);
1903 /// \brief UGLY HACK. This is called every frame to keep player model correct.
1904 MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
1906 entity player = M_ARGV(2, entity);
1907 M_ARGV(0, string) = player.surv_playermodel;
1910 /// \brief Hook which is called when the player tries to throw their weapon.
1911 MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
1913 entity player = M_ARGV(0, entity);
1914 return PlayerTemplate_ForbidThrowCurrentWeapon(player,
1915 Surv_GetPlayerTemplate(player));
1918 /// \brief Hook that is called every frame to determine how player health should
1920 MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
1922 entity player = M_ARGV(0, entity);
1923 if (player.team == surv_defenderteam)
1927 return PlayerTemplate_PlayerRegen(player, Surv_GetPlayerTemplate(player));
1930 /// \brief Hook that is called to determine if balance messages will appear.
1931 MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
1936 /// \brief Hook that is called when player touches an item.
1937 MUTATOR_HOOKFUNCTION(surv, ItemTouch)
1939 entity item = M_ARGV(0, entity);
1940 entity player = M_ARGV(1, entity);
1941 switch (player.team)
1943 case surv_attackerteam:
1945 return PlayerTemplate_ItemTouch(player, item,
1946 Surv_GetPlayerTemplate(player));
1948 case surv_defenderteam:
1950 switch (item.classname)
1952 case "item_strength":
1954 W_GiveWeapon(player, WEP_HMG.m_id);
1955 player.superweapons_finished = max(
1956 player.superweapons_finished, time) +
1957 autocvar_g_balance_superweapons_time;
1958 Item_ScheduleRespawn(item);
1959 sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
1961 return MUT_ITEMTOUCH_RETURN;
1963 case "item_invincible":
1965 W_GiveWeapon(player, WEP_RPC.m_id);
1966 player.superweapons_finished = max(
1967 player.superweapons_finished, time) +
1968 autocvar_g_balance_superweapons_time;
1969 Item_ScheduleRespawn(item);
1970 sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
1971 return MUT_ITEMTOUCH_RETURN;
1975 return PlayerTemplate_ItemTouch(player, item,
1976 Surv_GetPlayerTemplate(player));
1979 DebugPrintToChat(player, item.classname);
1980 return MUT_ITEMTOUCH_RETURN;
1983 return MUT_ITEMTOUCH_CONTINUE;
1986 /// \brief Hook which is called when the damage amount must be determined.
1987 MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
1989 entity frag_attacker = M_ARGV(1, entity);
1990 entity frag_target = M_ARGV(2, entity);
1991 float deathtype = M_ARGV(3, float);
1992 float damage = M_ARGV(4, float);
1993 M_ARGV(4, float) = PlayerTemplate_Damage_Calculate(frag_attacker,
1994 Surv_GetPlayerTemplate(frag_attacker), frag_target,
1995 Surv_GetPlayerTemplate(frag_target), deathtype, damage);
1998 /// \brief Hook which is called when the player was damaged.
1999 MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
2001 entity target = M_ARGV(1, entity);
2002 if (target.team != surv_defenderteam)
2006 Surv_UpdateDefenderHealthStat();
2007 entity attacker = M_ARGV(0, entity);
2008 if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
2009 SURVIVAL_ROLE_PLAYER))
2011 float health = M_ARGV(2, float);
2012 float armor = M_ARGV(3, float);
2013 float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
2014 PlayerScore_Add(attacker, SP_SCORE, score);
2016 if (autocvar_g_surv_stealth)
2020 if (target.health < 1)
2022 WaypointSprite_Kill(target.surv_attack_sprite);
2026 if (autocvar_g_instagib == 1)
2028 WaypointSprite_UpdateHealth(target.surv_attack_sprite,
2029 target.armorvalue + 1);
2033 WaypointSprite_UpdateHealth(target.surv_attack_sprite,
2034 target.health + target.armorvalue);
2039 /// \brief Hook which is called when the player dies.
2040 MUTATOR_HOOKFUNCTION(surv, PlayerDies, CBC_ORDER_FIRST)
2042 //DebugPrintToChatAll("PlayerDies");
2043 entity attacker = M_ARGV(1, entity);
2044 entity victim = M_ARGV(2, entity);
2045 PlayerTemplate_PlayerDies(victim, Surv_GetPlayerTemplate(victim));
2046 if ((attacker.team == surv_defenderteam) &&
2047 (victim.team == surv_attackerteam))
2049 switch (victim.surv_role)
2051 case SURVIVAL_ROLE_PLAYER:
2053 GivePlayerHealth(attacker,
2054 autocvar_g_surv_defender_attacker_frag_health);
2055 GivePlayerArmor(attacker,
2056 autocvar_g_surv_defender_attacker_frag_armor);
2057 GivePlayerAmmo(attacker, ammo_shells,
2058 autocvar_g_surv_defender_attacker_frag_shells);
2059 GivePlayerAmmo(attacker, ammo_nails,
2060 autocvar_g_surv_defender_attacker_frag_bullets);
2061 GivePlayerAmmo(attacker, ammo_rockets,
2062 autocvar_g_surv_defender_attacker_frag_rockets);
2063 GivePlayerAmmo(attacker, ammo_cells,
2064 autocvar_g_surv_defender_attacker_frag_cells);
2067 case SURVIVAL_ROLE_CANNON_FODDER:
2069 GivePlayerHealth(attacker,
2070 autocvar_g_surv_defender_cannon_fodder_frag_health);
2071 GivePlayerArmor(attacker,
2072 autocvar_g_surv_defender_cannon_fodder_frag_armor);
2073 GivePlayerAmmo(attacker, ammo_shells,
2074 autocvar_g_surv_defender_cannon_fodder_frag_shells);
2075 GivePlayerAmmo(attacker, ammo_nails,
2076 autocvar_g_surv_defender_cannon_fodder_frag_bullets);
2077 GivePlayerAmmo(attacker, ammo_rockets,
2078 autocvar_g_surv_defender_cannon_fodder_frag_rockets);
2079 GivePlayerAmmo(attacker, ammo_cells,
2080 autocvar_g_surv_defender_cannon_fodder_frag_cells);
2085 if (!Surv_CanPlayerSpawn(victim))
2087 victim.respawn_flags = RESPAWN_SILENT;
2088 if (IS_BOT_CLIENT(victim))
2096 /// \brief Hook which is called after the player died.
2097 MUTATOR_HOOKFUNCTION(surv, PlayerDied)
2099 //DebugPrintToChatAll("PlayerDied");
2100 entity player = M_ARGV(0, entity);
2101 Surv_RemovePlayerFromAliveList(player, player.team);
2102 //Surv_CountAlivePlayers();
2105 /// \brief Hook which is called when player has scored a frag.
2106 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
2108 if (surv_type == SURVIVAL_TYPE_COOP)
2112 entity attacker = M_ARGV(0, entity);
2113 if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
2114 SURVIVAL_ROLE_CANNON_FODDER))
2116 M_ARGV(2, float) = 0;
2119 entity target = M_ARGV(1, entity);
2120 if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
2123 M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
2128 MUTATOR_HOOKFUNCTION(surv, SpectateSet)
2130 entity client = M_ARGV(0, entity);
2131 entity targ = M_ARGV(1, entity);
2133 if (!autocvar_g_surv_spectate_enemies &&
2134 (client.surv_state == SURVIVAL_STATE_PLAYING) &&
2135 DIFF_TEAM(targ, client))
2141 MUTATOR_HOOKFUNCTION(surv, SpectateNext)
2143 entity client = M_ARGV(0, entity);
2145 if (!autocvar_g_surv_spectate_enemies &&
2146 (client.surv_state == SURVIVAL_STATE_PLAYING))
2148 entity targ = M_ARGV(1, entity);
2149 M_ARGV(1, entity) = CA_SpectateNext(client, targ);
2154 MUTATOR_HOOKFUNCTION(surv, SpectatePrev)
2156 entity client = M_ARGV(0, entity);
2157 entity targ = M_ARGV(1, entity);
2158 entity first = M_ARGV(2, entity);
2160 if (!autocvar_g_surv_spectate_enemies &&
2161 (client.surv_state == SURVIVAL_STATE_PLAYING))
2167 while (targ && DIFF_TEAM(targ, client));
2170 for (targ = first; targ && DIFF_TEAM(targ, client);
2173 if (targ == client.enemy)
2175 return MUT_SPECPREV_RETURN;
2179 M_ARGV(1, entity) = targ;
2180 return MUT_SPECPREV_FOUND;
2183 /// \brief I'm not sure exactly what this function does but it is very
2184 /// important. Without it bots are completely broken. Is it a hack? Of course.
2185 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
2187 FOREACH_CLIENT(IS_REAL_CLIENT(it),
2189 if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
2198 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
2200 // Don't announce remaining frags