1 #include "sv_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 plasma do defenders get when they frag an attacker.
95 int autocvar_g_surv_defender_attacker_frag_plasma;
96 /// \brief How much fuel do defenders get when they frag an attacker.
97 int autocvar_g_surv_defender_attacker_frag_fuel;
98 /// \brief How much health do defenders get when they frag cannon fodder.
99 int autocvar_g_surv_defender_cannon_fodder_frag_health;
100 /// \brief How much armor do defenders get when they frag cannon fodder.
101 int autocvar_g_surv_defender_cannon_fodder_frag_armor;
102 /// \brief How many shells do defenders get when they frag cannon fodder.
103 int autocvar_g_surv_defender_cannon_fodder_frag_shells;
104 /// \brief How many bullets do defenders get when they frag cannon fodder.
105 int autocvar_g_surv_defender_cannon_fodder_frag_bullets;
106 /// \brief How many rockets do defenders get when they frag cannon fodder.
107 int autocvar_g_surv_defender_cannon_fodder_frag_rockets;
108 /// \brief How many cells do defenders get when they frag cannon fodder.
109 int autocvar_g_surv_defender_cannon_fodder_frag_cells;
110 /// \brief How much plasma do defenders get when they frag cannon fodder.
111 int autocvar_g_surv_defender_cannon_fodder_frag_plasma;
112 /// \brief How much fuel do defenders get when they frag cannon fodder.
113 int autocvar_g_surv_defender_cannon_fodder_frag_fuel;
115 /// \brief Holds the state of the player. See SURVIVAL_STATE constants.
117 /// \brief Holds the role of the player. See SURVIVAL_ROLE constants.
119 .string surv_savedplayermodel; ///< Initial player model.
120 /// \brief Player state used during replacement of bot player with real player.
121 .entity surv_savedplayerstate;
122 .string surv_playermodel; ///< Player model forced by the game.
124 .entity surv_attack_sprite; ///< Holds the sprite telling attackers to attack.
125 .entity surv_defend_sprite; ///< Holds the sprite telling defenders to defend.
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 /// \brief Updates the health of defender sprite.
191 /// \param[in,out] player Player that has the sprite.
192 /// \return No return.
193 void Surv_UpdateWaypointSpriteHealth(entity player);
195 //========================= Free functions ====================================
197 void Surv_Initialize()
199 switch (cvar_string("g_surv_type"))
201 case SURVIVAL_TYPE_COOP_VALUE:
203 surv_type = SURVIVAL_TYPE_COOP;
206 case SURVIVAL_TYPE_VERSUS_VALUE:
208 surv_type = SURVIVAL_TYPE_VERSUS;
213 error("Invalid survival type.");
216 surv_roundtype = SURVIVAL_ROUND_FIRST;
217 surv_isroundactive = false;
218 surv_roundstarttime = time;
219 surv_timetobeat = autocvar_g_surv_round_timelimit;
222 surv_attackerteam = NUM_TEAM_1;
223 surv_defenderteam = NUM_TEAM_2;
224 surv_attackerteambit = SURVIVAL_TEAM_1_BIT;
225 surv_defenderteambit = SURVIVAL_TEAM_2_BIT;
229 surv_attackerteam = NUM_TEAM_2;
230 surv_defenderteam = NUM_TEAM_1;
231 surv_attackerteambit = SURVIVAL_TEAM_2_BIT;
232 surv_defenderteambit = SURVIVAL_TEAM_1_BIT;
234 surv_numattackers = 0;
235 surv_numdefenders = 0;
236 surv_numattackerhumans = 0;
237 surv_numdefenderhumans = 0;
238 surv_numattackersalive = 0;
239 surv_numdefendersalive = 0;
240 surv_autobalance = true;
241 surv_announcefrags = true;
242 surv_allowed_to_spawn = true;
243 precache_all_playermodels("models/ok_player/*.dpm");
244 ScoreRules_basics(SURVIVAL_TEAM_BITS, SFL_SORT_PRIO_PRIMARY, 0, true);
245 ScoreInfo_SetLabel_TeamScore(1, "rounds", SFL_SORT_PRIO_PRIMARY);
246 ScoreRules_basics_end();
247 round_handler_Spawn(Surv_CanRoundStart, Surv_CanRoundEnd, Surv_RoundStart);
248 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
249 EliminatedPlayers_Init(Surv_IsEliminated);
250 GameRules_teams(true);
251 GameRules_limit_score(autocvar_g_surv_point_limit);
252 GameRules_limit_lead(autocvar_g_surv_point_leadlimit);
255 /// \brief Returns the name of the template of the given player.
256 /// \param[in] player Player to inspect.
257 /// \return Name of the template of the given player.
258 string Surv_GetPlayerTemplate(entity player)
262 case surv_attackerteam:
264 switch (player.surv_role)
266 case SURVIVAL_ROLE_NONE:
267 case SURVIVAL_ROLE_PLAYER:
269 return cvar_string("g_surv_attacker_template");
271 case SURVIVAL_ROLE_CANNON_FODDER:
273 return cvar_string("g_surv_cannon_fodder_template");
277 case surv_defenderteam:
279 return cvar_string("g_surv_defender_template");
285 /// \brief Saves the player state. Used to seamlessly swap bots with humans.
286 /// \param[in] player Player to save the state of.
287 /// \return Entity containing the player state.
288 entity Surv_SavePlayerState(entity player)
290 entity state = spawn();
291 state.origin = player.origin;
292 state.velocity = player.velocity;
293 state.angles = player.angles;
294 state.health = player.health;
295 state.armorvalue = player.armorvalue;
296 state.ammo_shells = player.ammo_shells;
297 state.ammo_nails = player.ammo_nails;
298 state.ammo_rockets = player.ammo_rockets;
299 state.ammo_cells = player.ammo_cells;
300 state.weapons = player.weapons;
301 state.items = player.items;
302 state.superweapons_finished = player.superweapons_finished;
306 /// \brief Restores a saved player state.
307 /// \param[in] player Player to restore the state of.
308 /// \param[in] st State to restore.
309 /// \return No return.
310 void Surv_RestorePlayerState(entity player, entity st)
312 player.origin = st.origin;
313 player.velocity = st.velocity;
314 player.angles = st.angles;
315 player.health = st.health;
316 player.armorvalue = st.armorvalue;
317 player.ammo_shells = st.ammo_shells;
318 player.ammo_nails = st.ammo_nails;
319 player.ammo_rockets = st.ammo_rockets;
320 player.ammo_cells = st.ammo_cells;
321 player.weapons = st.weapons;
322 player.items = st.items;
323 player.superweapons_finished = st.superweapons_finished;
326 /// \brief Returns the attacker with the lowest score.
327 /// \param[in] bot Whether to search only for bots.
328 /// \return Attacker with the lowest score or NULL if not found.
329 entity Surv_FindLowestAttacker(bool bot)
331 entity player = NULL;
332 float score = FLOAT_MAX;
333 FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
335 if ((it.team == surv_attackerteam) && (it.surv_role ==
336 SURVIVAL_ROLE_PLAYER))
338 float tempscore = PlayerScore_Get(it, SP_SCORE);
339 if (tempscore < score)
349 /// \brief Returns the defender with the lowest score.
350 /// \param[in] bot Whether to search only for bots.
351 /// \param[in] alive Whether to search only for alive players.
352 /// \return Defender with the lowest score or NULL if not found.
353 entity Surv_FindLowestDefender(bool bot, bool alive)
355 entity player = NULL;
356 float score = FLOAT_MAX;
357 FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
359 if ((it.team == surv_defenderteam) && (alive ? !IS_DEAD(it) : true))
361 float tempscore = PlayerScore_Get(it, SP_SCORE);
362 if (tempscore < score)
372 /// \brief Returns the cannon fodder.
373 /// \return Cannon fodder or NULL if not found.
374 entity Surv_FindCannonFodder()
376 FOREACH_CLIENT(IS_BOT_CLIENT(it),
378 if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
386 /// \brief Changes the number of players in a team.
387 /// \param[in] teamnum Team to adjust.
388 /// \param[in] delta Amount to adjust by.
389 /// \return No return.
390 void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
394 case surv_attackerteam:
396 surv_numattackers += delta;
397 LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
398 " was = ", ftos(surv_numattackers - delta));
399 Surv_UpdateTeamStats();
402 case surv_defenderteam:
404 surv_numdefenders += delta;
405 LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
406 " was = ", ftos(surv_numdefenders - delta));
407 Surv_UpdateTeamStats();
413 /// \brief Changes the number of alive players in a team.
414 /// \param[in] teamnum Team to adjust.
415 /// \param[in] delta Amount to adjust by.
416 /// \return No return.
417 void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
421 case surv_attackerteam:
423 surv_numattackersalive += delta;
424 LOG_TRACE("Number of alive attackers = ", ftos(
425 surv_numattackersalive), " was = ", ftos(surv_numattackersalive
429 case surv_defenderteam:
431 surv_numdefendersalive += delta;
432 LOG_TRACE("Number of alive defenders = ", ftos(
433 surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
438 Surv_UpdateAliveStats();
439 eliminatedPlayers.SendFlags |= 1;
442 /// \brief Sets the player role.
443 /// \param[in,out] player Player to adjust.
444 /// \param[in] role Role to set.
445 /// \return No return.
446 void Surv_SetPlayerRole(entity player, int role)
448 if (player.surv_role == role)
452 player.surv_role = role;
455 case SURVIVAL_ROLE_NONE:
457 LOG_TRACE(player.netname, " now has no role.");
460 case SURVIVAL_ROLE_PLAYER:
462 LOG_TRACE(player.netname, " is now a player.");
465 case SURVIVAL_ROLE_CANNON_FODDER:
467 LOG_TRACE(player.netname, " is now a cannon fodder.");
473 /// \brief Adds player to team. Handles bookkeeping information.
474 /// \param[in] player Player to add.
475 /// \param[in] teamnum Team to add to.
476 /// \return True on success, false otherwise.
477 bool Surv_AddPlayerToTeam(entity player, int teamnum)
479 LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
482 case surv_attackerteam:
484 LOG_TRACE("Attacker team");
485 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
487 LOG_TRACE("Cannon fodder is switching team");
490 if (IS_BOT_CLIENT(player))
492 LOG_TRACE("Client is bot");
493 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
494 if (surv_numattackers < autocvar_g_surv_team_size)
496 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
497 Surv_ChangeNumberOfPlayers(teamnum, +1);
500 Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
503 LOG_TRACE("Client is not a bot");
504 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
505 if (surv_numattackers >= autocvar_g_surv_team_size)
507 LOG_TRACE("Removing bot");
508 // Remove bot to make space for human.
509 entity bot = Surv_FindLowestAttacker(true);
512 LOG_TRACE("No valid bot to remove");
513 // No space in team, denying team change.
514 TRANSMUTE(Spectator, player);
517 LOG_TRACE("Changing ", bot.netname,
518 " from attacker to cannon fodder.");
519 Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
522 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
524 Surv_ChangeNumberOfPlayers(teamnum, -1);
525 LOG_TRACE("Removed bot");
527 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
528 Surv_ChangeNumberOfPlayers(teamnum, +1);
529 ++surv_numattackerhumans;
530 LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
531 if ((surv_autobalance == false) || (surv_numattackers -
532 surv_numdefenders) < 2)
536 entity lowestplayer = Surv_FindLowestAttacker(true);
537 if (lowestplayer != NULL)
539 bool savedautobalance = surv_autobalance;
540 surv_autobalance = false;
541 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
542 surv_autobalance = savedautobalance;
545 lowestplayer = Surv_FindLowestAttacker(false);
546 if (lowestplayer != NULL)
548 bool savedautobalance = surv_autobalance;
549 surv_autobalance = false;
550 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
551 surv_autobalance = savedautobalance;
555 case surv_defenderteam:
557 LOG_TRACE("Defender team");
558 if (IS_BOT_CLIENT(player))
560 LOG_TRACE("Client is bot");
561 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
562 if (surv_numdefenders < autocvar_g_surv_team_size)
564 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
565 Surv_ChangeNumberOfPlayers(teamnum, +1);
568 LOG_TRACE("No space for defender, switching to attacker");
569 SetPlayerTeamSimple(player, surv_attackerteam);
572 LOG_TRACE("Client is not a bot");
573 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
574 if (surv_numdefenders >= autocvar_g_surv_team_size)
576 LOG_TRACE("Removing bot");
577 // Remove bot to make space for human.
578 entity bot = Surv_FindLowestDefender(true, true);
581 bot = Surv_FindLowestDefender(true, false);
585 LOG_TRACE("No valid bot to remove");
586 // No space in team, denying team change.
587 TRANSMUTE(Spectator, player);
590 LOG_TRACE("Changing ", bot.netname,
591 " from defender to cannon fodder.");
594 player.surv_savedplayerstate = Surv_SavePlayerState(bot);
596 bool savedautobalance = surv_autobalance;
597 surv_autobalance = false;
598 surv_announcefrags = false;
599 SetPlayerTeamSimple(bot, surv_attackerteam);
600 surv_autobalance = savedautobalance;
601 surv_announcefrags = true;
602 LOG_TRACE("Removed bot");
604 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
605 Surv_ChangeNumberOfPlayers(teamnum, +1);
606 ++surv_numdefenderhumans;
607 LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
608 if ((surv_autobalance == false) || (surv_numdefenders -
609 surv_numattackers) < 2)
613 entity lowestplayer = Surv_FindLowestDefender(true, false);
614 if (lowestplayer != NULL)
616 bool savedautobalance = surv_autobalance;
617 surv_autobalance = false;
618 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
619 surv_autobalance = savedautobalance;
622 lowestplayer = Surv_FindLowestDefender(false, false);
623 if (lowestplayer != NULL)
625 bool savedautobalance = surv_autobalance;
626 surv_autobalance = false;
627 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
628 surv_autobalance = savedautobalance;
634 LOG_TRACE("Spectator team");
635 player.surv_role = SURVIVAL_ROLE_NONE;
639 LOG_TRACE("Invalid team");
640 player.surv_role = SURVIVAL_ROLE_NONE;
644 /// \brief Removes player from team. Handles bookkeeping information.
645 /// \param[in] player Player to remove.
646 /// \param[in] Team to remove from.
647 /// \return No return.
648 void Surv_RemovePlayerFromTeam(entity player, int teamnum)
650 LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
653 case surv_attackerteam:
655 LOG_TRACE("Attacker team");
656 if (player.surv_role == SURVIVAL_ROLE_NONE)
658 string message = strcat("RemovePlayerFromTeam: ",
659 player.netname, " has invalid role.");
660 DebugPrintToChatAll(message);
663 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
665 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
668 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
669 Surv_ChangeNumberOfPlayers(teamnum, -1);
670 if (!IS_BOT_CLIENT(player))
672 --surv_numattackerhumans;
674 if ((surv_autobalance == false) || (surv_numattackers >=
679 // Add bot to keep teams balanced.
680 entity lowestplayer = Surv_FindCannonFodder();
681 if (lowestplayer != NULL)
683 LOG_TRACE("Changing ", lowestplayer.netname,
684 " from cannon fodder to attacker.");
685 Surv_SetPlayerRole(lowestplayer, SURVIVAL_ROLE_PLAYER);
686 Surv_ChangeNumberOfPlayers(teamnum, +1);
687 if (!IS_DEAD(lowestplayer))
689 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
693 lowestplayer = Surv_FindLowestDefender(true, false);
694 if (lowestplayer != NULL)
696 bool savedautobalance = surv_autobalance;
697 surv_autobalance = false;
698 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
699 surv_autobalance = savedautobalance;
702 lowestplayer = Surv_FindLowestDefender(false, false);
703 if (lowestplayer == NULL)
707 bool savedautobalance = surv_autobalance;
708 surv_autobalance = false;
709 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
710 surv_autobalance = savedautobalance;
713 case surv_defenderteam:
715 LOG_TRACE("Defender team");
716 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
718 // This happens during team switch. We don't need to change
720 LOG_TRACE("Cannon fodder. Assuming team switch");
723 if (player.surv_role != SURVIVAL_ROLE_PLAYER)
725 string message = strcat("RemovePlayerFromTeam: ",
726 player.netname, " has invalid role.");
727 DebugPrintToChatAll(message);
730 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
731 Surv_ChangeNumberOfPlayers(teamnum, -1);
732 if (!IS_BOT_CLIENT(player))
734 --surv_numdefenderhumans;
736 if ((surv_autobalance == false) || (surv_numdefenders >=
741 // Add bot to keep teams balanced.
742 entity lowestplayer = Surv_FindCannonFodder();
743 if (lowestplayer != NULL)
745 LOG_TRACE("Changing ", lowestplayer.netname,
746 " from cannon fodder to defender.");
747 if (!IS_DEAD(player))
749 lowestplayer.surv_savedplayerstate =
750 Surv_SavePlayerState(player);
752 bool savedautobalance = surv_autobalance;
753 surv_autobalance = false;
754 surv_announcefrags = false;
755 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
756 surv_autobalance = savedautobalance;
757 surv_announcefrags = true;
760 lowestplayer = Surv_FindLowestAttacker(true);
761 if (lowestplayer != NULL)
763 bool savedautobalance = surv_autobalance;
764 surv_autobalance = false;
765 surv_announcefrags = false;
766 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
767 surv_autobalance = savedautobalance;
768 surv_announcefrags = true;
771 lowestplayer = Surv_FindLowestAttacker(false);
772 if (lowestplayer == NULL)
776 bool savedautobalance = surv_autobalance;
777 surv_autobalance = false;
778 surv_announcefrags = false;
779 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
780 surv_autobalance = savedautobalance;
781 surv_announcefrags = true;
786 LOG_TRACE("Spectator team");
791 LOG_TRACE("Invalid team");
797 /// \brief Updates stats of team count on HUD.
798 /// \return No return.
799 void Surv_UpdateTeamStats()
802 if (surv_attackerteam == NUM_TEAM_1)
804 yellowalive = surv_numattackers;
805 pinkalive = surv_numdefenders;
809 pinkalive = surv_numattackers;
810 yellowalive = surv_numdefenders;
812 FOREACH_CLIENT(IS_REAL_CLIENT(it),
814 it.yellowalive_stat = yellowalive;
815 it.pinkalive_stat = pinkalive;
819 /// \brief Adds player to alive list. Handles bookkeeping information.
820 /// \param[in] player Player to add.
821 /// \param[in] teamnum Team of the player.
822 /// \return No return.
823 void Surv_AddPlayerToAliveList(entity player, int teamnum)
827 case surv_attackerteam:
829 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
831 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
835 case surv_defenderteam:
837 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
843 /// \brief Removes player from alive list. Handles bookkeeping information.
844 /// \param[in] player Player to remove.
845 /// \param[in] teamnum Team of the player.
846 /// \return No return.
847 void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
849 if (player.surv_attack_sprite)
851 WaypointSprite_Kill(player.surv_attack_sprite);
853 if (player.surv_defend_sprite)
855 WaypointSprite_Kill(player.surv_defend_sprite);
859 case surv_attackerteam:
861 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
863 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
867 case surv_defenderteam:
869 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
871 // This happens during team switch. We don't need to change
875 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
876 if (warmup_stage || surv_allowed_to_spawn || !surv_announcefrags)
880 switch (surv_numdefendersalive)
884 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
885 VOL_BASE, ATTEN_NONE);
886 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
888 if (it.team == surv_defenderteam)
890 Send_Notification(NOTIF_ONE, it, MSG_CENTER,
899 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
900 VOL_BASE, ATTEN_NONE);
905 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
906 VOL_BASE, ATTEN_NONE);
915 /// \brief Counts alive players.
916 /// \return No return.
917 /// \note In a perfect world this function shouldn't exist. However, since QC
918 /// code is so bad and spurious mutators can really mess with your code, this
919 /// function is called as a last resort.
920 void Surv_CountAlivePlayers()
922 int savednumdefenders = surv_numdefendersalive;
923 surv_numattackersalive = 0;
924 surv_numdefendersalive = 0;
925 FOREACH_CLIENT(IS_PLAYER(it),
929 case surv_attackerteam:
931 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
933 ++surv_numattackersalive;
937 case surv_defenderteam:
939 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
941 ++surv_numdefendersalive;
947 Surv_UpdateAliveStats();
948 eliminatedPlayers.SendFlags |= 1;
949 if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
950 surv_numdefendersalive))
954 switch (surv_numdefendersalive)
958 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
959 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
961 if (it.team == surv_defenderteam)
963 Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
971 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
977 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
984 /// \brief Updates stats of alive players on HUD.
985 /// \return No return.
986 void Surv_UpdateAliveStats()
989 if (surv_attackerteam == NUM_TEAM_1)
991 redalive = surv_numattackersalive;
992 bluealive = surv_numdefendersalive;
996 bluealive = surv_numattackersalive;
997 redalive = surv_numdefendersalive;
999 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1001 STAT(SURV_DEFENDERS_ALIVE, it) = surv_numdefendersalive;
1002 it.redalive_stat = redalive;
1003 it.bluealive_stat = bluealive;
1005 Surv_UpdateDefenderHealthStat();
1008 /// \brief Updates defender health on the HUD.
1009 /// \return No return.
1010 void Surv_UpdateDefenderHealthStat()
1013 float totalhealth = 0;
1014 if (autocvar_g_instagib == 1)
1016 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1017 "surv_defender", "start_armor") + 1);
1018 FOREACH_CLIENT(IS_PLAYER(it),
1020 if (it.team == surv_defenderteam)
1022 totalhealth += GetResourceAmount(it, RESOURCE_ARMOR) + 1;
1028 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1029 "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
1030 "surv_defender", "start_armor"));
1031 FOREACH_CLIENT(IS_PLAYER(it),
1033 if (it.team == surv_defenderteam)
1035 totalhealth += GetResourceAmount(it, RESOURCE_HEALTH);
1036 totalhealth += GetResourceAmount(it, RESOURCE_ARMOR);
1047 healthratio = totalhealth / maxhealth;
1049 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1051 STAT(SURV_DEFENDER_HEALTH, it) = healthratio;
1055 /// \brief Returns whether the player can spawn.
1056 /// \param[in] player Player to check.
1057 /// \return True if the player can spawn, false otherwise.
1058 bool Surv_CanPlayerSpawn(entity player)
1060 if ((player.team == surv_attackerteam) ||
1061 (player.surv_savedplayerstate != NULL))
1065 return surv_allowed_to_spawn;
1068 /// \brief Switches the round type.
1069 /// \return No return.
1070 void Surv_SwitchRoundType()
1072 switch (surv_roundtype)
1074 case SURVIVAL_ROUND_FIRST:
1076 surv_roundtype = SURVIVAL_ROUND_SECOND;
1079 case SURVIVAL_ROUND_SECOND:
1081 surv_roundtype = SURVIVAL_ROUND_FIRST;
1087 /// \brief Cleans up the mess after the round has finished.
1088 /// \return No return.
1089 void Surv_RoundCleanup()
1091 surv_allowed_to_spawn = false;
1092 surv_isroundactive = false;
1093 game_stopped = true;
1094 FOREACH_CLIENT(true,
1096 if (it.surv_attack_sprite)
1098 WaypointSprite_Kill(it.surv_attack_sprite);
1100 if (it.surv_defend_sprite)
1102 WaypointSprite_Kill(it.surv_defend_sprite);
1104 if (it.surv_savedplayerstate)
1106 delete(it.surv_savedplayerstate);
1107 it.surv_savedplayerstate = NULL;
1110 if (surv_type == SURVIVAL_TYPE_VERSUS)
1112 Surv_SwitchRoundType();
1113 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
1116 round_handler_Init(5, autocvar_g_surv_warmup,
1117 autocvar_g_surv_round_timelimit);
1120 /// \brief Swaps attacker and defender teams.
1121 /// \return No return.
1122 void Surv_SwapTeams()
1124 int temp = surv_attackerteam;
1125 surv_attackerteam = surv_defenderteam;
1126 surv_defenderteam = temp;
1127 temp = surv_attackerteambit;
1128 surv_attackerteambit = surv_defenderteambit;
1129 surv_defenderteambit = temp;
1130 temp = surv_numattackers;
1131 surv_numattackers = surv_numdefenders;
1132 surv_numdefenders = temp;
1133 temp = surv_numattackerhumans;
1134 surv_numattackerhumans = surv_numdefenderhumans;
1135 surv_numdefenderhumans = temp;
1136 FOREACH_CLIENT(true,
1138 if ((it.team == surv_defenderteam) && (it.surv_role ==
1139 SURVIVAL_ROLE_CANNON_FODDER))
1141 SetPlayerTeamSimple(it, surv_attackerteam);
1144 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1146 STAT(SURV_DEFENDER_TEAM, it) = Team_TeamToNumber(surv_defenderteam);
1150 /// \brief Forces the overkill model for specific player.
1151 /// \param[in,out] player Player to force the model of.
1152 /// \return No return.
1153 void Surv_ForceOverkillPlayerModel(entity player)
1155 switch (player.team)
1159 switch (floor(random() * 4))
1163 player.surv_playermodel = "models/ok_player/okrobot1.dpm";
1168 player.surv_playermodel = "models/ok_player/okrobot2.dpm";
1173 player.surv_playermodel = "models/ok_player/okrobot3.dpm";
1178 player.surv_playermodel = "models/ok_player/okrobot4.dpm";
1186 switch (floor(random() * 4))
1190 player.surv_playermodel = "models/ok_player/okmale1.dpm";
1195 player.surv_playermodel = "models/ok_player/okmale2.dpm";
1200 player.surv_playermodel = "models/ok_player/okmale3.dpm";
1205 player.surv_playermodel = "models/ok_player/okmale4.dpm";
1214 /// \brief Determines the player model to the one configured for the gamemode.
1215 /// \param[in,out] player Player to determine the model of.
1216 /// \return No return.
1217 void Surv_DeterminePlayerModel(entity player)
1219 switch (player.team)
1221 case surv_attackerteam:
1223 switch (player.surv_role)
1225 case SURVIVAL_ROLE_PLAYER:
1227 if (!autocvar_g_surv_attacker_force_overkill_models)
1229 player.surv_playermodel = player.surv_savedplayermodel;
1232 Surv_ForceOverkillPlayerModel(player);
1235 case SURVIVAL_ROLE_CANNON_FODDER:
1237 if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
1239 player.surv_playermodel = player.surv_savedplayermodel;
1242 Surv_ForceOverkillPlayerModel(player);
1247 case surv_defenderteam:
1249 if (!autocvar_g_surv_defender_force_overkill_models)
1251 player.surv_playermodel = player.surv_savedplayermodel;
1254 Surv_ForceOverkillPlayerModel(player);
1260 /// \brief Setups a waypoint sprite used to track defenders.
1261 /// \param[in] player Player to attach sprite too.
1262 /// \return No return.
1263 void Surv_SetupWaypointSprite(entity player)
1265 WaypointSprite_Spawn(WP_SurvivalKill, 0, 0, player, '0 0 64', NULL,
1266 surv_attackerteam, player, surv_attack_sprite, false,
1267 RADARICON_OBJECTIVE);
1268 WaypointSprite_Spawn(WP_SurvivalDefend, 0, 0, player, '0 0 64', NULL,
1269 surv_defenderteam, player, surv_defend_sprite, false,
1270 RADARICON_OBJECTIVE);
1271 //player.surv_attack_sprite.colormod = colormapPaletteColor(player.team - 1,
1273 //player.surv_defend_sprite.colormod = colormapPaletteColor(player.team - 1,
1276 if (autocvar_g_instagib == 1)
1278 max_hp = PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(player),
1280 WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite, max_hp);
1281 WaypointSprite_UpdateMaxHealth(player.surv_defend_sprite, max_hp);
1282 Surv_UpdateWaypointSpriteHealth(player);
1285 max_hp = PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(player),
1286 "start_health") + PlayerTemplate_GetFloatValue(Surv_GetPlayerTemplate(
1287 player), "start_armor");
1288 WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite, max_hp);
1289 WaypointSprite_UpdateMaxHealth(player.surv_defend_sprite, max_hp);
1290 Surv_UpdateWaypointSpriteHealth(player);
1293 void Surv_UpdateWaypointSpriteHealth(entity player)
1296 if (autocvar_g_instagib == 1)
1298 hp = GetResourceAmount(player, RESOURCE_ARMOR) + 1;
1302 hp = GetResourceAmount(player, RESOURCE_HEALTH) + GetResourceAmount(
1303 player, RESOURCE_ARMOR);
1305 WaypointSprite_UpdateHealth(player.surv_attack_sprite, hp);
1306 WaypointSprite_UpdateHealth(player.surv_defend_sprite, hp);
1309 //=============================== Callbacks ===================================
1311 bool Surv_CanRoundStart()
1313 return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
1316 bool Surv_CanRoundEnd()
1322 if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
1325 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1327 surv_timetobeat = time - surv_roundstarttime;
1328 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1329 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1330 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1331 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1332 Surv_RoundCleanup();
1335 surv_timetobeat = autocvar_g_surv_round_timelimit;
1336 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1337 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1338 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1339 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1340 switch (surv_defenderteam)
1344 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1350 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1355 TeamScore_AddToTeam(surv_defenderteam, 1, 1);
1356 Surv_RoundCleanup();
1359 if (surv_numdefendersalive > 0)
1363 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1365 surv_timetobeat = time - surv_roundstarttime;
1366 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1367 CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1368 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1369 INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1370 Surv_RoundCleanup();
1373 surv_timetobeat = autocvar_g_surv_round_timelimit;
1374 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1375 CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
1376 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1377 INFO_SURVIVAL_DEFENDERS_ELIMINATED);
1378 switch (surv_attackerteam)
1382 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1388 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1393 TeamScore_AddToTeam(surv_attackerteam, 1, 1);
1394 Surv_RoundCleanup();
1398 void Surv_RoundStart()
1402 surv_allowed_to_spawn = true;
1405 surv_isroundactive = true;
1406 surv_roundstarttime = time;
1407 surv_allowed_to_spawn = false;
1408 switch (surv_roundtype)
1410 case SURVIVAL_ROUND_FIRST:
1412 FOREACH_CLIENT(IS_PLAYER(it),
1414 if (it.team == surv_attackerteam)
1416 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1417 CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
1418 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1419 INFO_SURVIVAL_1ST_ROUND_ATTACKER);
1423 FOREACH_CLIENT(IS_PLAYER(it),
1425 if (it.team == surv_defenderteam)
1427 if (surv_type == SURVIVAL_TYPE_COOP)
1429 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1430 CENTER_SURVIVAL_COOP_DEFENDER);
1431 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1432 INFO_SURVIVAL_COOP_DEFENDER);
1435 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1436 CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
1437 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1438 INFO_SURVIVAL_1ST_ROUND_DEFENDER);
1444 case SURVIVAL_ROUND_SECOND:
1446 FOREACH_CLIENT(IS_PLAYER(it),
1448 if (it.team == surv_attackerteam)
1450 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1451 CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1452 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1453 INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1457 FOREACH_CLIENT(IS_PLAYER(it),
1459 if (it.team == surv_defenderteam)
1461 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1462 CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1463 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1464 INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1471 if (autocvar_g_surv_stealth)
1475 FOREACH_CLIENT(IS_PLAYER(it),
1479 case surv_defenderteam:
1481 if (it.surv_role == SURVIVAL_ROLE_PLAYER)
1483 Surv_SetupWaypointSprite(it);
1491 bool Surv_IsEliminated(entity player)
1493 switch (player.surv_state)
1495 case SURVIVAL_STATE_NOT_PLAYING:
1499 case SURVIVAL_STATE_PLAYING:
1501 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
1503 // A hack until proper scoreboard is done.
1506 if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
1507 IS_OBSERVER(player)))
1514 // Should never reach here
1518 //============================= Hooks ========================================
1520 /// \brief Hook that is called to determine general rules of the game.
1521 MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
1523 surv_warmup = warmup_stage;
1526 /// \brief Hook that is called to determine if there is a weapon arena.
1527 MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
1529 // Removing any weapon arena.
1530 M_ARGV(0, string) = "off";
1533 /// \brief Hook that is called to determine start items of all players.
1534 MUTATOR_HOOKFUNCTION(surv, SetStartItems)
1536 if (autocvar_g_instagib == 1)
1540 start_weapons = WEPSET(Null);
1541 warmup_start_weapons = WEPSET(Null);
1544 /// \brief Hook that is called on every frame.
1545 MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
1547 if (game_stopped || !surv_isroundactive)
1551 float roundtime = 0;
1552 switch (surv_roundtype)
1554 case SURVIVAL_ROUND_FIRST:
1556 roundtime = time - surv_roundstarttime;
1559 case SURVIVAL_ROUND_SECOND:
1561 roundtime = round_handler_GetEndTime() - time;
1565 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1567 STAT(SURV_ROUND_TIME, it) = roundtime;
1571 /// \brief Hook that determines which team player can join. This is called
1572 /// before ClientConnect.
1573 MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
1575 entity player = M_ARGV(2, entity);
1576 LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
1579 return SURVIVAL_TEAM_BITS;
1581 if (IS_BOT_CLIENT(player))
1583 int teambits = surv_attackerteambit;
1584 if ((player.team == surv_defenderteam) || (surv_numdefenders <
1585 autocvar_g_surv_team_size))
1587 teambits |= surv_defenderteambit;
1589 M_ARGV(0, float) = teambits;
1592 if (surv_type == SURVIVAL_TYPE_COOP)
1594 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1596 M_ARGV(0, float) = surv_defenderteambit;
1599 M_ARGV(0, float) = 0;
1603 if (surv_numattackerhumans < autocvar_g_surv_team_size)
1605 LOG_TRACE("Player can join attackers");
1606 teambits |= surv_attackerteambit;
1608 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1610 LOG_TRACE("Player can join defenders");
1611 teambits |= surv_defenderteambit;
1613 M_ARGV(0, float) = teambits;
1617 /// \brief Hook that override team counts.
1618 MUTATOR_HOOKFUNCTION(surv, GetTeamCounts, CBC_ORDER_EXCLUSIVE)
1623 /// \brief Hook that sets the team count.
1624 MUTATOR_HOOKFUNCTION(surv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
1626 float teamnum = M_ARGV(0, float);
1627 entity ignore = M_ARGV(1, entity);
1630 case surv_attackerteam:
1632 M_ARGV(2, float) = surv_numattackers;
1633 M_ARGV(3, float) = surv_numattackers - surv_numattackerhumans;
1634 if (ignore.team == surv_attackerteam)
1637 if (IS_BOT_CLIENT(ignore))
1642 entity lowestplayer = NULL;
1643 float lowestplayerscore = FLOAT_MAX;
1644 entity lowestbot = NULL;
1645 float lowestbotscore = FLOAT_MAX;
1646 FOREACH_CLIENT((it.team == surv_attackerteam) && (it.surv_role ==
1647 SURVIVAL_ROLE_PLAYER),
1653 if (IS_BOT_CLIENT(it))
1655 float tempscore = PlayerScore_Get(it, SP_SCORE);
1656 if (tempscore < lowestbotscore)
1659 lowestbotscore = tempscore;
1663 float tempscore = PlayerScore_Get(it, SP_SCORE);
1664 if (tempscore < lowestplayerscore)
1667 lowestplayerscore = tempscore;
1670 M_ARGV(4, entity) = lowestplayer;
1671 M_ARGV(5, entity) = lowestbot;
1674 case surv_defenderteam:
1676 M_ARGV(2, float) = surv_numdefenders;
1677 M_ARGV(3, float) = surv_numdefenders - surv_numdefenderhumans;
1678 if (ignore.team == surv_defenderteam)
1681 if (IS_BOT_CLIENT(ignore))
1686 entity lowestplayer = NULL;
1687 float lowestplayerscore = FLOAT_MAX;
1688 entity lowestbot = NULL;
1689 float lowestbotscore = FLOAT_MAX;
1690 FOREACH_CLIENT((it.team == surv_defenderteam),
1696 if (IS_BOT_CLIENT(it))
1698 float tempscore = PlayerScore_Get(it, SP_SCORE);
1699 if (tempscore < lowestbotscore)
1702 lowestbotscore = tempscore;
1706 float tempscore = PlayerScore_Get(it, SP_SCORE);
1707 if (tempscore < lowestplayerscore)
1710 lowestplayerscore = tempscore;
1713 M_ARGV(4, entity) = lowestplayer;
1714 M_ARGV(5, entity) = lowestbot;
1721 /// \brief Hook that determines the best teams for the player to join.
1722 MUTATOR_HOOKFUNCTION(surv, FindBestTeams, CBC_ORDER_EXCLUSIVE)
1724 if (surv_type == SURVIVAL_TYPE_COOP)
1728 entity player = M_ARGV(0, entity);
1729 if (IS_BOT_CLIENT(player))
1733 int numattackerhumans = surv_numattackerhumans;
1734 int numdefenderhumans = surv_numdefenderhumans;
1735 if (player.team == surv_attackerteam)
1737 --numattackerhumans;
1739 else if (player.team == surv_defenderteam)
1741 --numdefenderhumans;
1743 if (numattackerhumans < numdefenderhumans)
1745 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_attackerteam) - 1);
1748 if (numattackerhumans > numdefenderhumans)
1750 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_defenderteam) - 1);
1753 M_ARGV(1, float) = SURVIVAL_TEAM_BITS;
1757 /// \brief Hook that is called when player has changed the team.
1758 MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
1760 entity player = M_ARGV(0, entity);
1761 int oldteam = M_ARGV(1, float);
1762 int newteam = M_ARGV(2, float);
1763 string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
1764 ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
1766 DebugPrintToChatAll(message);
1767 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1769 Surv_RemovePlayerFromAliveList(player, oldteam);
1771 Surv_RemovePlayerFromTeam(player, oldteam);
1772 if (Surv_AddPlayerToTeam(player, newteam) == false)
1776 //Surv_CountAlivePlayers();
1777 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1779 Surv_AddPlayerToAliveList(player, newteam);
1783 /// \brief Hook that is called when player is about to be killed when changing
1785 MUTATOR_HOOKFUNCTION(surv, Player_ChangeTeamKill)
1787 entity player = M_ARGV(0, entity);
1788 if (player.team != surv_defenderteam)
1792 if (player.surv_savedplayerstate == NULL)
1796 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1797 delete(player.surv_savedplayerstate);
1798 player.surv_savedplayerstate = NULL;
1802 /// \brief Hook that is called when player is about to be killed as a result of
1803 /// the kill command or changing teams.
1804 MUTATOR_HOOKFUNCTION(surv, ClientKill_Now)
1806 entity player = M_ARGV(0, entity);
1807 if (player.team == surv_defenderteam)
1814 /// \brief Hook that is called when player connects to the server.
1815 MUTATOR_HOOKFUNCTION(surv, ClientConnect)
1817 entity player = M_ARGV(0, entity);
1818 LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
1819 player.surv_savedplayermodel = player.playermodel;
1820 if (IS_REAL_CLIENT(player))
1822 STAT(SURV_DEFENDER_TEAM, player) = Team_TeamToNumber(surv_defenderteam);
1823 STAT(SURV_DEFENDERS_ALIVE, player) = surv_numdefendersalive;
1824 player.redalive_stat = redalive;
1825 player.bluealive_stat = bluealive;
1826 player.yellowalive_stat = yellowalive;
1827 player.pinkalive_stat = pinkalive;
1829 if (player.surv_role == SURVIVAL_ROLE_NONE)
1831 Surv_AddPlayerToTeam(player, player.team);
1836 /// \brief Hook that is called when player disconnects from the server.
1837 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
1839 entity player = M_ARGV(0, entity);
1840 if (!IS_DEAD(player))
1842 Surv_RemovePlayerFromAliveList(player, player.team);
1844 Surv_RemovePlayerFromTeam(player, player.team);
1845 //Surv_CountAlivePlayers();
1848 /// \brief Hook that determines whether player can spawn. It is not called for
1849 /// players who have joined the team and are dead.
1850 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
1852 entity player = M_ARGV(0, entity);
1853 LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
1854 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1858 return !Surv_CanPlayerSpawn(player);
1861 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
1863 entity player = M_ARGV(0, entity);
1864 LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
1865 if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
1867 LOG_TRACE("Transmuting to observer");
1868 TRANSMUTE(Observer, player);
1872 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
1874 entity player = M_ARGV(0, entity);
1875 LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
1876 if (player.killindicator_teamchange == -2) // player wants to spectate
1878 LOG_TRACE("killindicator_teamchange == -2");
1879 player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
1881 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1883 return false; // allow team reset
1885 return true; // prevent team reset
1888 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
1890 LOG_TRACE("Survival: reset_map_global");
1891 surv_allowed_to_spawn = true;
1892 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1894 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1896 STAT(SURV_ROUND_TIME, it) = 0;
1902 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
1904 LOG_TRACE("Survival: reset_map_players");
1905 surv_numattackersalive = 0;
1906 surv_numdefendersalive = 0;
1909 surv_warmup = false;
1911 else if (surv_type == SURVIVAL_TYPE_VERSUS)
1915 FOREACH_CLIENT(true,
1918 if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
1921 it.surv_state = SURVIVAL_STATE_PLAYING;
1923 if (it.surv_state == SURVIVAL_STATE_PLAYING)
1925 TRANSMUTE(Player, it);
1926 it.surv_state = SURVIVAL_STATE_PLAYING;
1927 PutClientInServer(it);
1930 bot_relinkplayerlist();
1934 /// \brief Hook that is called when player spawns.
1935 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
1937 entity player = M_ARGV(0, entity);
1938 LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
1939 player.surv_state = SURVIVAL_STATE_PLAYING;
1940 Surv_DeterminePlayerModel(player);
1941 if (player.surv_savedplayerstate != NULL)
1943 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1944 delete(player.surv_savedplayerstate);
1945 player.surv_savedplayerstate = NULL;
1949 PlayerTemplateHook_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
1951 switch (player.team)
1953 case surv_attackerteam:
1955 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
1957 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1958 CENTER_ASSAULT_ATTACKING);
1962 case surv_defenderteam:
1964 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1965 CENTER_ASSAULT_DEFENDING);
1969 //Surv_CountAlivePlayers();
1970 Surv_AddPlayerToAliveList(player, player.team);
1973 /// \brief UGLY HACK. This is called every frame to keep player model correct.
1974 MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
1976 entity player = M_ARGV(2, entity);
1977 M_ARGV(0, string) = player.surv_playermodel;
1980 /// \brief Hook which is called when the player tries to throw their weapon.
1981 MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
1983 entity player = M_ARGV(0, entity);
1984 return PlayerTemplateHook_ForbidThrowCurrentWeapon(
1985 Surv_GetPlayerTemplate(player));
1988 /// \brief Hook that is called every frame to determine how player health should
1990 MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
1992 entity player = M_ARGV(0, entity);
1993 if (player.team == surv_defenderteam)
1997 return PlayerTemplateHook_PlayerRegen(player,
1998 Surv_GetPlayerTemplate(player));
2001 /// \brief Hook that is called to determine if balance messages will appear.
2002 MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
2007 /// \brief Hook that is called when player touches an item.
2008 MUTATOR_HOOKFUNCTION(surv, ItemTouch)
2010 entity item = M_ARGV(0, entity);
2011 entity player = M_ARGV(1, entity);
2012 switch (player.team)
2014 case surv_attackerteam:
2016 return PlayerTemplateHook_ItemTouch(player, item,
2017 Surv_GetPlayerTemplate(player));
2019 case surv_defenderteam:
2021 switch (item.classname)
2023 case "item_strength":
2025 W_GiveWeapon(player, WEP_HMG.m_id);
2026 player.superweapons_finished = max(
2027 player.superweapons_finished, time) +
2028 autocvar_g_balance_superweapons_time;
2029 Item_ScheduleRespawn(item);
2030 sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
2032 return MUT_ITEMTOUCH_RETURN;
2036 W_GiveWeapon(player, WEP_RPC.m_id);
2037 player.superweapons_finished = max(
2038 player.superweapons_finished, time) +
2039 autocvar_g_balance_superweapons_time;
2040 Item_ScheduleRespawn(item);
2041 sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
2042 return MUT_ITEMTOUCH_RETURN;
2046 return PlayerTemplateHook_ItemTouch(player, item,
2047 Surv_GetPlayerTemplate(player));
2050 DebugPrintToChat(player, item.classname);
2051 return MUT_ITEMTOUCH_RETURN;
2054 return MUT_ITEMTOUCH_CONTINUE;
2057 /// \brief Hook which is called when the damage amount must be determined.
2058 MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
2060 entity frag_attacker = M_ARGV(1, entity);
2061 entity frag_target = M_ARGV(2, entity);
2062 float deathtype = M_ARGV(3, float);
2063 float damage = M_ARGV(4, float);
2064 M_ARGV(4, float) = PlayerTemplateHook_Damage_Calculate(frag_attacker,
2065 Surv_GetPlayerTemplate(frag_attacker), frag_target,
2066 Surv_GetPlayerTemplate(frag_target), deathtype, damage);
2069 /// \brief Hook which is called when the player was damaged.
2070 MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
2072 entity target = M_ARGV(1, entity);
2073 if (target.team != surv_defenderteam)
2077 Surv_UpdateDefenderHealthStat();
2078 entity attacker = M_ARGV(0, entity);
2079 if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
2080 SURVIVAL_ROLE_PLAYER))
2082 float health = M_ARGV(2, float);
2083 float armor = M_ARGV(3, float);
2084 float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
2085 GameRules_scoring_add(attacker, SCORE, score);
2087 if (autocvar_g_surv_stealth)
2091 if (GetResourceAmount(target, RESOURCE_HEALTH) < 1)
2093 WaypointSprite_Kill(target.surv_attack_sprite);
2094 WaypointSprite_Kill(target.surv_defend_sprite);
2098 Surv_UpdateWaypointSpriteHealth(target);
2102 /// \brief Hook which is called when the player dies.
2103 MUTATOR_HOOKFUNCTION(surv, PlayerDies, CBC_ORDER_FIRST)
2105 //DebugPrintToChatAll("PlayerDies");
2106 entity attacker = M_ARGV(1, entity);
2107 entity victim = M_ARGV(2, entity);
2108 PlayerTemplateHook_PlayerDies(victim, Surv_GetPlayerTemplate(victim));
2109 if ((attacker.team == surv_defenderteam) &&
2110 (victim.team == surv_attackerteam))
2112 switch (victim.surv_role)
2114 case SURVIVAL_ROLE_PLAYER:
2116 GiveResource(attacker, RESOURCE_HEALTH,
2117 autocvar_g_surv_defender_attacker_frag_health);
2118 GiveResource(attacker, RESOURCE_ARMOR,
2119 autocvar_g_surv_defender_attacker_frag_armor);
2120 GiveResource(attacker, RESOURCE_SHELLS,
2121 autocvar_g_surv_defender_attacker_frag_shells);
2122 GiveResource(attacker, RESOURCE_BULLETS,
2123 autocvar_g_surv_defender_attacker_frag_bullets);
2124 GiveResource(attacker, RESOURCE_ROCKETS,
2125 autocvar_g_surv_defender_attacker_frag_rockets);
2126 GiveResource(attacker, RESOURCE_CELLS,
2127 autocvar_g_surv_defender_attacker_frag_cells);
2128 GiveResource(attacker, RESOURCE_PLASMA,
2129 autocvar_g_surv_defender_attacker_frag_plasma);
2130 GiveResource(attacker, RESOURCE_FUEL,
2131 autocvar_g_surv_defender_attacker_frag_fuel);
2134 case SURVIVAL_ROLE_CANNON_FODDER:
2136 GiveResource(attacker, RESOURCE_HEALTH,
2137 autocvar_g_surv_defender_cannon_fodder_frag_health);
2138 GiveResource(attacker, RESOURCE_ARMOR,
2139 autocvar_g_surv_defender_cannon_fodder_frag_armor);
2140 GiveResource(attacker, RESOURCE_SHELLS,
2141 autocvar_g_surv_defender_cannon_fodder_frag_shells);
2142 GiveResource(attacker, RESOURCE_BULLETS,
2143 autocvar_g_surv_defender_cannon_fodder_frag_bullets);
2144 GiveResource(attacker, RESOURCE_ROCKETS,
2145 autocvar_g_surv_defender_cannon_fodder_frag_rockets);
2146 GiveResource(attacker, RESOURCE_CELLS,
2147 autocvar_g_surv_defender_cannon_fodder_frag_cells);
2148 GiveResource(attacker, RESOURCE_PLASMA,
2149 autocvar_g_surv_defender_cannon_fodder_frag_plasma);
2150 GiveResource(attacker, RESOURCE_FUEL,
2151 autocvar_g_surv_defender_cannon_fodder_frag_fuel);
2156 if (!Surv_CanPlayerSpawn(victim))
2158 victim.respawn_flags = RESPAWN_SILENT;
2159 if (IS_BOT_CLIENT(victim))
2167 /// \brief Hook which is called after the player died.
2168 MUTATOR_HOOKFUNCTION(surv, PlayerDied)
2170 //DebugPrintToChatAll("PlayerDied");
2171 entity player = M_ARGV(0, entity);
2172 Surv_RemovePlayerFromAliveList(player, player.team);
2173 //Surv_CountAlivePlayers();
2176 /// \brief Hook which is called when player has scored a frag.
2177 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
2179 if (surv_type == SURVIVAL_TYPE_COOP)
2183 entity attacker = M_ARGV(0, entity);
2184 if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
2185 SURVIVAL_ROLE_CANNON_FODDER))
2187 M_ARGV(2, float) = 0;
2190 entity target = M_ARGV(1, entity);
2191 if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
2194 M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
2199 MUTATOR_HOOKFUNCTION(surv, SpectateSet)
2201 entity client = M_ARGV(0, entity);
2202 entity targ = M_ARGV(1, entity);
2204 if (!autocvar_g_surv_spectate_enemies &&
2205 (client.surv_state == SURVIVAL_STATE_PLAYING) &&
2206 DIFF_TEAM(targ, client))
2212 MUTATOR_HOOKFUNCTION(surv, SpectateNext)
2214 entity client = M_ARGV(0, entity);
2216 if (!autocvar_g_surv_spectate_enemies &&
2217 (client.surv_state == SURVIVAL_STATE_PLAYING))
2219 entity targ = M_ARGV(1, entity);
2220 M_ARGV(1, entity) = CA_SpectateNext(client, targ);
2225 MUTATOR_HOOKFUNCTION(surv, SpectatePrev)
2227 entity client = M_ARGV(0, entity);
2228 entity targ = M_ARGV(1, entity);
2229 entity first = M_ARGV(2, entity);
2231 if (!autocvar_g_surv_spectate_enemies &&
2232 (client.surv_state == SURVIVAL_STATE_PLAYING))
2238 while (targ && DIFF_TEAM(targ, client));
2241 for (targ = first; targ && DIFF_TEAM(targ, client);
2244 if (targ == client.enemy)
2246 return MUT_SPECPREV_RETURN;
2250 M_ARGV(1, entity) = targ;
2251 return MUT_SPECPREV_FOUND;
2254 /// \brief I'm not sure exactly what this function does but it is very
2255 /// important. Without it bots are completely broken. Is it a hack? Of course.
2256 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
2258 FOREACH_CLIENT(IS_REAL_CLIENT(it),
2260 if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
2269 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
2271 // Don't announce remaining frags