1 #include "gamemode_survival.qh"
3 #include <common/mutators/mutator/playertemplates/sv_playertemplates.qh>
4 #include <common/mutators/mutator/overkill/hmg.qh>
5 #include <common/mutators/mutator/overkill/rpc.qh>
7 //============================ Constants ======================================
9 const int SURVIVAL_TEAM_1_BIT = BIT(0); ///< Bitmask of the first team.
10 const int SURVIVAL_TEAM_2_BIT = BIT(1); ///< Bitmask of the second team.
12 /// \brief Used when bitfield team count is requested.
13 const int SURVIVAL_TEAM_BITS = 3;
17 SURVIVAL_TYPE_COOP, ///< All humans are on the defender team.
18 SURVIVAL_TYPE_VERSUS ///< Humans take turns between attackers and defenders.
21 const string SURVIVAL_TYPE_COOP_VALUE = "coop"; ///< Cvar value for coop.
22 const string SURVIVAL_TYPE_VERSUS_VALUE = "versus"; ///< Cvar value for versus.
26 /// \brief First round where there is timelimit set by the server.
28 /// \brief Second round where defender team tries to survive for the first
35 /// \brief Player is spectating and has no intention of playing.
36 SURVIVAL_STATE_NOT_PLAYING,
37 /// \brief Player is playing the game.
38 SURVIVAL_STATE_PLAYING = 1
43 SURVIVAL_ROLE_NONE, ///< Player is not playing.
44 SURVIVAL_ROLE_PLAYER, ///< Player is an attacker or defender.
45 SURVIVAL_ROLE_CANNON_FODDER ///< Player is a cannon fodder.
48 SOUND(SURV_3_FRAGS_LEFT, "announcer/default/3fragsleft");
49 SOUND(SURV_2_FRAGS_LEFT, "announcer/default/2fragsleft");
50 SOUND(SURV_1_FRAG_LEFT, "announcer/default/1fragleft");
52 SOUND(SURV_RED_SCORES, "ctf/red_capture");
53 SOUND(SURV_BLUE_SCORES, "ctf/blue_capture");
55 //======================= Global variables ====================================
57 float autocvar_g_surv_warmup; ///< Warmup time in seconds.
58 float autocvar_g_surv_round_timelimit; ///< First round time limit in seconds.
60 int autocvar_g_surv_point_limit; ///< Maximum number of points.
61 int autocvar_g_surv_point_leadlimit; ///< Maximum lead of a single team.
63 /// \brief How much players are allowed in teams (excluding cannon fodder).
64 int autocvar_g_surv_team_size;
65 /// \brief If set, defenders will not be shown on the radar.
66 int autocvar_g_surv_stealth;
68 /// \brief Whether to force overkill player models for attackers.
69 int autocvar_g_surv_attacker_force_overkill_models;
70 /// \brief Whether to force overkill player models for defenders.
71 int autocvar_g_surv_defender_force_overkill_models;
72 /// \brief Whether to force overkill player models for cannon fodder.
73 int autocvar_g_surv_cannon_fodder_force_overkill_models;
75 /// \brief How much score attackers gain per 1 point of damage.
76 float autocvar_g_surv_attacker_damage_score;
78 /// \brief How much score attackers get for fragging defenders.
79 float autocvar_g_surv_attacker_frag_score;
81 /// \brief How much health do defenders get when they frag an attacker.
82 int autocvar_g_surv_defender_attacker_frag_health;
83 /// \brief How much armor do defenders get when they frag an attacker.
84 int autocvar_g_surv_defender_attacker_frag_armor;
85 /// \brief How many shells do defenders get when they frag an attacker.
86 int autocvar_g_surv_defender_attacker_frag_shells;
87 /// \brief How many bullets do defenders get when they frag an attacker.
88 int autocvar_g_surv_defender_attacker_frag_bullets;
89 /// \brief How many rockets do defenders get when they frag an attacker.
90 int autocvar_g_surv_defender_attacker_frag_rockets;
91 /// \brief How many cells do defenders get when they frag an attacker.
92 int autocvar_g_surv_defender_attacker_frag_cells;
93 /// \brief How much health do defenders get when they frag cannon fodder.
94 int autocvar_g_surv_defender_cannon_fodder_frag_health;
95 /// \brief How much armor do defenders get when they frag cannon fodder.
96 int autocvar_g_surv_defender_cannon_fodder_frag_armor;
97 /// \brief How many shells do defenders get when they frag cannon fodder.
98 int autocvar_g_surv_defender_cannon_fodder_frag_shells;
99 /// \brief How many bullets do defenders get when they frag cannon fodder.
100 int autocvar_g_surv_defender_cannon_fodder_frag_bullets;
101 /// \brief How many rockets do defenders get when they frag cannon fodder.
102 int autocvar_g_surv_defender_cannon_fodder_frag_rockets;
103 /// \brief How many cells do defenders get when they frag cannon fodder.
104 int autocvar_g_surv_defender_cannon_fodder_frag_cells;
106 /// \brief Whether defenders drop weapons after death.
107 int autocvar_g_surv_defender_drop_weapons;
109 /// \brief A stat that is used to track the time left in the round.
110 .float surv_round_time_stat = _STAT(SURV_ROUND_TIME);
111 /// \brief A stat that is used to track defender team.
112 .int surv_defender_team_stat = _STAT(SURV_DEFENDER_TEAM);
113 /// \brief A stat that is used to track number of defenders alive.
114 .int surv_defenders_alive_stat = _STAT(SURV_DEFENDERS_ALIVE);
115 /// \brief A stat that is used to track the total health of defenders.
116 .float surv_defender_health_stat = _STAT(SURV_DEFENDER_HEALTH);
118 /// \brief Holds the state of the player. See SURVIVAL_STATE constants.
120 /// \brief Holds the role of the player. See SURVIVAL_ROLE constants.
122 .string surv_savedplayermodel; ///< Initial player model.
123 /// \brief Player state used during replacement of bot player with real player.
124 .entity surv_savedplayerstate;
125 .string surv_playermodel; ///< Player model forced by the game.
127 .entity surv_attack_sprite; ///< Holds the sprite telling attackers to attack.
129 int surv_type; ///< Holds the type of survival. See SURVIVAL_TYPE constants.
130 bool surv_warmup; ///< Holds whether warmup is active.
131 /// \brief Holds the type of the current round. See SURVIVAL_ROUND constants.
133 bool surv_isroundactive; ///< Holds whether the round is active.
134 float surv_roundstarttime; ///< Holds the time of the round start.
135 /// \brief Holds the time needed to survive in the second round.
136 float surv_timetobeat;
138 int surv_attackerteam; ///< Holds the attacker team.
139 int surv_defenderteam; ///< Holds the defender team.
141 int surv_attackerteambit; ///< Hols the attacker team bitmask.
142 int surv_defenderteambit; ///< Holds the defender team bitmask.
144 int surv_numattackers; ///< Holds the number of players in attacker team.
145 int surv_numdefenders; ///< Holds the number of players in defender team.
147 /// \brief Holds the number of humans in attacker team.
148 int surv_numattackerhumans;
149 /// \brief Holds the number of humans in defender team.
150 int surv_numdefenderhumans;
152 /// \brief Holds the number of attacker players that are alive.
153 int surv_numattackersalive;
154 /// \brief Holds the number of defender players that are alive.
155 int surv_numdefendersalive;
157 bool surv_autobalance; ///< Holds whether autobalance is active.
158 bool surv_allowed_to_spawn; ///< Holds whether players are allowed to spawn.
160 //====================== Forward declarations =================================
162 /// \brief Determines whether the round can start.
163 /// \return True if the round can start, false otherwise.
164 bool Surv_CanRoundStart();
166 /// \brief Determines whether the round can end.
167 /// \return True if the round can end, false otherwise.
168 bool Surv_CanRoundEnd();
170 /// \brief Called when the round starts.
171 /// \return No return.
172 void Surv_RoundStart();
174 /// \brief Returns whether player has been eliminated.
175 /// \param[in] player Player to check.
176 /// \return True if player is eliminated, false otherwise.
177 bool Surv_IsEliminated(entity player);
179 /// \brief Updates stats of team count on HUD.
180 /// \return No return.
181 void Surv_UpdateTeamStats();
183 /// \brief Updates stats of alive players on HUD.
184 /// \return No return.
185 void Surv_UpdateAliveStats();
187 /// \brief Updates defender health on the HUD.
188 /// \return No return.
189 void Surv_UpdateDefenderHealthStat();
191 //========================= Free functions ====================================
193 void Surv_Initialize()
195 switch (cvar_string("g_surv_type"))
197 case SURVIVAL_TYPE_COOP_VALUE:
199 surv_type = SURVIVAL_TYPE_COOP;
202 case SURVIVAL_TYPE_VERSUS_VALUE:
204 surv_type = SURVIVAL_TYPE_VERSUS;
209 error("Invalid survival type.");
212 surv_roundtype = SURVIVAL_ROUND_FIRST;
213 surv_isroundactive = false;
214 surv_roundstarttime = time;
215 surv_timetobeat = autocvar_g_surv_round_timelimit;
218 surv_attackerteam = NUM_TEAM_1;
219 surv_defenderteam = NUM_TEAM_2;
220 surv_attackerteambit = SURVIVAL_TEAM_1_BIT;
221 surv_defenderteambit = SURVIVAL_TEAM_2_BIT;
225 surv_attackerteam = NUM_TEAM_2;
226 surv_defenderteam = NUM_TEAM_1;
227 surv_attackerteambit = SURVIVAL_TEAM_2_BIT;
228 surv_defenderteambit = SURVIVAL_TEAM_1_BIT;
230 surv_numattackers = 0;
231 surv_numdefenders = 0;
232 surv_numattackerhumans = 0;
233 surv_numdefenderhumans = 0;
234 surv_numattackersalive = 0;
235 surv_numdefendersalive = 0;
236 surv_autobalance = 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 string Surv_GetPlayerTemplate(entity player)
254 case surv_attackerteam:
256 switch (player.surv_role)
258 case SURVIVAL_ROLE_NONE:
259 case SURVIVAL_ROLE_PLAYER:
261 return "surv_attacker";
263 case SURVIVAL_ROLE_CANNON_FODDER:
265 return "surv_cannon_fodder";
269 case surv_defenderteam:
271 return "surv_defender";
277 /// \brief Saves the player state. Used to seamlessly swap bots with humans.
278 /// \param[in] player Player to save the state of.
279 /// \return Entity containing the player state.
280 entity Surv_SavePlayerState(entity player)
282 entity state = spawn();
283 state.origin = player.origin;
284 state.angles = player.angles;
285 state.health = player.health;
286 state.armorvalue = player.armorvalue;
287 state.ammo_shells = player.ammo_shells;
288 state.ammo_nails = player.ammo_nails;
289 state.ammo_rockets = player.ammo_rockets;
290 state.ammo_cells = player.ammo_cells;
291 state.weapons = player.weapons;
292 state.items = player.items;
293 state.superweapons_finished = player.superweapons_finished;
297 /// \brief Restores a saved player state.
298 /// \param[in] player Player to restore the state of.
299 /// \param[in] st State to restore.
300 /// \return No return.
301 void Surv_RestorePlayerState(entity player, entity st)
303 player.origin = st.origin;
304 player.angles = st.angles;
305 player.health = st.health;
306 player.armorvalue = st.armorvalue;
307 player.ammo_shells = st.ammo_shells;
308 player.ammo_nails = st.ammo_nails;
309 player.ammo_rockets = st.ammo_rockets;
310 player.ammo_cells = st.ammo_cells;
311 player.weapons = st.weapons;
312 player.items = st.items;
313 player.superweapons_finished = st.superweapons_finished;
316 /// \brief Changes the number of players in a team.
317 /// \param[in] teamnum Team to adjust.
318 /// \param[in] delta Amount to adjust by.
319 /// \return No return.
320 void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
324 case surv_attackerteam:
326 surv_numattackers += delta;
327 LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
328 " was = ", ftos(surv_numattackers - delta));
329 Surv_UpdateTeamStats();
332 case surv_defenderteam:
334 surv_numdefenders += delta;
335 LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
336 " was = ", ftos(surv_numdefenders - delta));
337 Surv_UpdateTeamStats();
343 /// \brief Changes the number of alive players in a team.
344 /// \param[in] teamnum Team to adjust.
345 /// \param[in] delta Amount to adjust by.
346 /// \return No return.
347 void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
351 case surv_attackerteam:
353 surv_numattackersalive += delta;
354 LOG_TRACE("Number of alive attackers = ", ftos(
355 surv_numattackersalive), " was = ", ftos(surv_numattackersalive
359 case surv_defenderteam:
361 surv_numdefendersalive += delta;
362 LOG_TRACE("Number of alive defenders = ", ftos(
363 surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
368 Surv_UpdateAliveStats();
369 eliminatedPlayers.SendFlags |= 1;
372 /// \brief Sets the player role.
373 /// \param[in,out] player Player to adjust.
374 /// \param[in] role Role to set.
375 /// \return No return.
376 void Surv_SetPlayerRole(entity player, int role)
378 if (player.surv_role == role)
382 player.surv_role = role;
385 case SURVIVAL_ROLE_NONE:
387 LOG_TRACE(player.netname, " now has no role.");
390 case SURVIVAL_ROLE_PLAYER:
392 LOG_TRACE(player.netname, " is now a player.");
395 case SURVIVAL_ROLE_CANNON_FODDER:
397 LOG_TRACE(player.netname, " is now a cannon fodder.");
403 /// \brief Adds player to team. Handles bookkeeping information.
404 /// \param[in] player Player to add.
405 /// \param[in] teamnum Team to add to.
406 /// \return True on success, false otherwise.
407 bool Surv_AddPlayerToTeam(entity player, int teamnum)
409 LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
412 case surv_attackerteam:
414 LOG_TRACE("Attacker team");
415 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
417 LOG_TRACE("Cannon fodder is switching team");
420 if (IS_BOT_CLIENT(player))
422 LOG_TRACE("Client is bot");
423 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
424 if (surv_numattackers < autocvar_g_surv_team_size)
426 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
427 Surv_ChangeNumberOfPlayers(teamnum, +1);
430 Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
433 LOG_TRACE("Client is not a bot");
434 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
435 if (surv_numattackers >= autocvar_g_surv_team_size)
437 LOG_TRACE("Removing bot");
438 // Remove bot to make space for human.
440 float score = FLOAT_MAX;
441 FOREACH_CLIENT(IS_BOT_CLIENT(it),
443 if ((it.team == surv_attackerteam) && (it.surv_role ==
444 SURVIVAL_ROLE_PLAYER))
446 float tempscore = PlayerScore_Get(it, SP_SCORE);
447 if (tempscore < score)
456 LOG_TRACE("No valid bot to remove");
457 // No space in team, denying team change.
458 TRANSMUTE(Spectator, player);
461 LOG_TRACE("Changing ", bot.netname,
462 " from attacker to cannon fodder.");
463 Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
466 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
468 Surv_ChangeNumberOfPlayers(teamnum, -1);
469 LOG_TRACE("Removed bot");
471 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
472 Surv_ChangeNumberOfPlayers(teamnum, +1);
473 ++surv_numattackerhumans;
474 LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
477 case surv_defenderteam:
479 LOG_TRACE("Defender team");
480 if (IS_BOT_CLIENT(player))
482 LOG_TRACE("Client is bot");
483 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
484 if (surv_numdefenders < autocvar_g_surv_team_size)
486 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
487 Surv_ChangeNumberOfPlayers(teamnum, +1);
490 LOG_TRACE("No space for defender, switching to attacker");
491 SetPlayerTeamSimple(player, surv_attackerteam);
494 LOG_TRACE("Client is not a bot");
495 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
496 if (surv_numdefenders >= autocvar_g_surv_team_size)
498 LOG_TRACE("Removing bot");
499 // Remove bot to make space for human.
501 float score = FLOAT_MAX;
502 FOREACH_CLIENT(IS_BOT_CLIENT(it),
504 if (!IS_DEAD(it) && (it.team == surv_defenderteam))
506 float tempscore = PlayerScore_Get(it, SP_SCORE);
507 if (tempscore < score)
516 FOREACH_CLIENT(IS_BOT_CLIENT(it),
518 if (it.team == surv_defenderteam)
520 float tempscore = PlayerScore_Get(it, SP_SCORE);
521 if (tempscore < score)
531 LOG_TRACE("No valid bot to remove");
532 // No space in team, denying team change.
533 TRANSMUTE(Spectator, player);
536 LOG_TRACE("Changing ", bot.netname,
537 " from defender to cannon fodder.");
538 if ((!IS_DEAD(bot)) && (!surv_allowed_to_spawn))
540 player.surv_savedplayerstate = Surv_SavePlayerState(bot);
542 surv_autobalance = false;
543 SetPlayerTeamSimple(bot, surv_attackerteam);
544 surv_autobalance = true;
545 LOG_TRACE("Removed bot");
547 Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
548 Surv_ChangeNumberOfPlayers(teamnum, +1);
549 ++surv_numdefenderhumans;
550 LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
554 player.surv_role = SURVIVAL_ROLE_NONE;
558 /// \brief Removes player from team. Handles bookkeeping information.
559 /// \param[in] player Player to remove.
560 /// \param[in] Team to remove from.
561 /// \return No return.
562 void Surv_RemovePlayerFromTeam(entity player, int teamnum)
564 LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
567 case surv_attackerteam:
569 LOG_TRACE("Attacker team");
570 if (player.surv_role == SURVIVAL_ROLE_NONE)
572 string message = strcat("RemovePlayerFromTeam: ",
573 player.netname, " has invalid role.");
574 DebugPrintToChatAll(message);
577 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
579 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
582 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
583 Surv_ChangeNumberOfPlayers(teamnum, -1);
584 if (!IS_BOT_CLIENT(player))
586 --surv_numattackerhumans;
588 if ((surv_autobalance == false) || (surv_numattackers >=
593 // Add bot to keep teams balanced.
596 if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
598 LOG_TRACE("Changing ", it.netname,
599 " from cannon fodder to attacker.");
600 Surv_SetPlayerRole(it, SURVIVAL_ROLE_PLAYER);
601 Surv_ChangeNumberOfPlayers(teamnum, +1);
604 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
611 case surv_defenderteam:
613 LOG_TRACE("Defender team");
614 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
616 // This happens during team switch. We don't need to change
618 LOG_TRACE("Cannon fodder. Assuming team switch");
621 if (player.surv_role != SURVIVAL_ROLE_PLAYER)
623 string message = strcat("RemovePlayerFromTeam: ",
624 player.netname, " has invalid role.");
625 DebugPrintToChatAll(message);
628 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
629 Surv_ChangeNumberOfPlayers(teamnum, -1);
630 if (!IS_BOT_CLIENT(player))
632 --surv_numdefenderhumans;
634 if ((surv_autobalance == false) || (surv_numdefenders >=
639 // Add bot to keep teams balanced.
642 if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
644 LOG_TRACE("Changing ", it.netname,
645 " from cannon fodder to defender.");
646 SetPlayerTeamSimple(it, surv_defenderteam);
647 if (!IS_DEAD(player) && !surv_allowed_to_spawn)
649 entity state = Surv_SavePlayerState(player);
650 Surv_RestorePlayerState(it, state);
660 LOG_TRACE("Invalid team");
666 /// \brief Updates stats of team count on HUD.
667 /// \return No return.
668 void Surv_UpdateTeamStats()
671 if (surv_attackerteam == NUM_TEAM_1)
673 yellowalive = surv_numattackers;
674 pinkalive = surv_numdefenders;
678 pinkalive = surv_numattackers;
679 yellowalive = surv_numdefenders;
681 FOREACH_CLIENT(IS_REAL_CLIENT(it),
683 it.yellowalive_stat = yellowalive;
684 it.pinkalive_stat = pinkalive;
688 /// \brief Adds player to alive list. Handles bookkeeping information.
689 /// \param[in] player Player to add.
690 /// \param[in] teamnum Team of the player.
691 /// \return No return.
692 void Surv_AddPlayerToAliveList(entity player, int teamnum)
696 case surv_attackerteam:
698 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
700 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
704 case surv_defenderteam:
706 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
712 /// \brief Removes player from alive list. Handles bookkeeping information.
713 /// \param[in] player Player to remove.
714 /// \param[in] teamnum Team of the player.
715 /// \return No return.
716 void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
718 if (player.surv_attack_sprite)
720 WaypointSprite_Kill(player.surv_attack_sprite);
724 case surv_attackerteam:
726 if (player.surv_role == SURVIVAL_ROLE_PLAYER)
728 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
732 case surv_defenderteam:
734 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
736 // This happens during team switch. We don't need to change
740 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
741 if (warmup_stage || surv_allowed_to_spawn)
745 switch (surv_numdefendersalive)
749 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
750 VOL_BASE, ATTEN_NONE);
751 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
753 if (it.team == surv_defenderteam)
755 Send_Notification(NOTIF_ONE, it, MSG_CENTER,
764 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
765 VOL_BASE, ATTEN_NONE);
770 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
771 VOL_BASE, ATTEN_NONE);
780 /// \brief Counts alive players.
781 /// \return No return.
782 /// \note In a perfect world this function shouldn't exist. However, since QC
783 /// code is so bad and spurious mutators can really mess with your code, this
784 /// function is called as a last resort.
785 void Surv_CountAlivePlayers()
787 int savednumdefenders = surv_numdefendersalive;
788 surv_numattackersalive = 0;
789 surv_numdefendersalive = 0;
790 FOREACH_CLIENT(IS_PLAYER(it),
794 case surv_attackerteam:
796 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
798 ++surv_numattackersalive;
802 case surv_defenderteam:
804 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
806 ++surv_numdefendersalive;
812 Surv_UpdateAliveStats();
813 eliminatedPlayers.SendFlags |= 1;
814 if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
815 surv_numdefendersalive))
819 switch (surv_numdefendersalive)
823 sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
824 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
826 if (it.team == surv_defenderteam)
828 Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
836 sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
842 sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
849 /// \brief Updates stats of alive players on HUD.
850 /// \return No return.
851 void Surv_UpdateAliveStats()
854 if (surv_attackerteam == NUM_TEAM_1)
856 redalive = surv_numattackersalive;
857 bluealive = surv_numdefendersalive;
861 bluealive = surv_numattackersalive;
862 redalive = surv_numdefendersalive;
864 FOREACH_CLIENT(IS_REAL_CLIENT(it),
866 it.surv_defenders_alive_stat = surv_numdefendersalive;
867 it.redalive_stat = redalive;
868 it.bluealive_stat = bluealive;
870 Surv_UpdateDefenderHealthStat();
873 /// \brief Updates defender health on the HUD.
874 /// \return No return.
875 void Surv_UpdateDefenderHealthStat()
878 float totalhealth = 0;
879 if (autocvar_g_instagib == 1)
881 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
882 "surv_defender", "start_armor") + 1);
883 FOREACH_CLIENT(IS_PLAYER(it),
885 if (it.team == surv_defenderteam)
887 totalhealth += it.armorvalue + 1;
893 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
894 "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
895 "surv_defender", "start_armor"));
896 FOREACH_CLIENT(IS_PLAYER(it),
898 if (it.team == surv_defenderteam)
900 totalhealth += it.health;
901 totalhealth += it.armorvalue;
912 healthratio = totalhealth / maxhealth;
914 FOREACH_CLIENT(IS_REAL_CLIENT(it),
916 it.surv_defender_health_stat = healthratio;
920 /// \brief Returns whether the player can spawn.
921 /// \param[in] player Player to check.
922 /// \return True if the player can spawn, false otherwise.
923 bool Surv_CanPlayerSpawn(entity player)
925 if ((player.team == surv_attackerteam) ||
926 (player.surv_savedplayerstate != NULL))
930 return surv_allowed_to_spawn;
933 /// \brief Switches the round type.
934 /// \return No return.
935 void Surv_SwitchRoundType()
937 switch (surv_roundtype)
939 case SURVIVAL_ROUND_FIRST:
941 surv_roundtype = SURVIVAL_ROUND_SECOND;
944 case SURVIVAL_ROUND_SECOND:
946 surv_roundtype = SURVIVAL_ROUND_FIRST;
952 /// \brief Cleans up the mess after the round has finished.
953 /// \return No return.
954 void Surv_RoundCleanup()
956 surv_allowed_to_spawn = false;
957 surv_isroundactive = false;
961 if (it.surv_attack_sprite)
963 WaypointSprite_Kill(it.surv_attack_sprite);
965 if (it.surv_savedplayerstate)
967 delete(it.surv_savedplayerstate);
968 it.surv_savedplayerstate = NULL;
971 if (surv_type == SURVIVAL_TYPE_VERSUS)
973 Surv_SwitchRoundType();
974 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
977 round_handler_Init(5, autocvar_g_surv_warmup,
978 autocvar_g_surv_round_timelimit);
981 /// \brief Swaps attacker and defender teams.
982 /// \return No return.
983 void Surv_SwapTeams()
985 int temp = surv_attackerteam;
986 surv_attackerteam = surv_defenderteam;
987 surv_defenderteam = temp;
988 temp = surv_attackerteambit;
989 surv_attackerteambit = surv_defenderteambit;
990 surv_defenderteambit = temp;
991 temp = surv_numattackers;
992 surv_numattackers = surv_numdefenders;
993 surv_numdefenders = temp;
994 temp = surv_numattackerhumans;
995 surv_numattackerhumans = surv_numdefenderhumans;
996 surv_numdefenderhumans = temp;
999 if ((it.team == surv_defenderteam) && (it.surv_role ==
1000 SURVIVAL_ROLE_CANNON_FODDER))
1002 SetPlayerTeamSimple(it, surv_attackerteam);
1005 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1007 it.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1011 /// \brief Forces the overkill model for specific player.
1012 /// \param[in,out] player Player to force the model of.
1013 /// \return No return.
1014 void Surv_ForceOverkillPlayerModel(entity player)
1016 switch (player.team)
1020 switch (floor(random() * 4))
1024 player.surv_playermodel = "models/ok_player/okrobot1.dpm";
1029 player.surv_playermodel = "models/ok_player/okrobot2.dpm";
1034 player.surv_playermodel = "models/ok_player/okrobot3.dpm";
1039 player.surv_playermodel = "models/ok_player/okrobot4.dpm";
1047 switch (floor(random() * 4))
1051 player.surv_playermodel = "models/ok_player/okmale1.dpm";
1056 player.surv_playermodel = "models/ok_player/okmale2.dpm";
1061 player.surv_playermodel = "models/ok_player/okmale3.dpm";
1066 player.surv_playermodel = "models/ok_player/okmale4.dpm";
1075 /// \brief Determines the player model to the one configured for the gamemode.
1076 /// \param[in,out] player Player to determine the model of.
1077 /// \return No return.
1078 void Surv_DeterminePlayerModel(entity player)
1080 switch (player.team)
1082 case surv_attackerteam:
1084 switch (player.surv_role)
1086 case SURVIVAL_ROLE_PLAYER:
1088 if (!autocvar_g_surv_attacker_force_overkill_models)
1090 player.surv_playermodel = player.surv_savedplayermodel;
1093 Surv_ForceOverkillPlayerModel(player);
1096 case SURVIVAL_ROLE_CANNON_FODDER:
1098 if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
1100 player.surv_playermodel = player.surv_savedplayermodel;
1103 Surv_ForceOverkillPlayerModel(player);
1108 case surv_defenderteam:
1110 if (!autocvar_g_surv_defender_force_overkill_models)
1112 player.surv_playermodel = player.surv_savedplayermodel;
1115 Surv_ForceOverkillPlayerModel(player);
1121 //=============================== Callbacks ===================================
1123 bool Surv_CanRoundStart()
1125 return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
1128 bool Surv_CanRoundEnd()
1134 if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
1137 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1139 surv_timetobeat = time - surv_roundstarttime;
1140 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1141 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1142 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1143 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1144 Surv_RoundCleanup();
1147 surv_timetobeat = autocvar_g_surv_round_timelimit;
1148 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1149 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1150 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1151 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1152 switch (surv_defenderteam)
1156 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1162 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1167 TeamScore_AddToTeam(surv_defenderteam, 1, 1);
1168 Surv_RoundCleanup();
1171 if (surv_numdefendersalive > 0)
1175 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1177 surv_timetobeat = time - surv_roundstarttime;
1178 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1179 CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1180 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1181 INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1182 Surv_RoundCleanup();
1185 surv_timetobeat = autocvar_g_surv_round_timelimit;
1186 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1187 CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
1188 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1189 INFO_SURVIVAL_DEFENDERS_ELIMINATED);
1190 switch (surv_attackerteam)
1194 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1200 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1205 TeamScore_AddToTeam(surv_attackerteam, 1, 1);
1206 Surv_RoundCleanup();
1210 void Surv_RoundStart()
1214 surv_allowed_to_spawn = true;
1217 surv_isroundactive = true;
1218 surv_roundstarttime = time;
1219 surv_allowed_to_spawn = false;
1220 switch (surv_roundtype)
1222 case SURVIVAL_ROUND_FIRST:
1224 FOREACH_CLIENT(IS_PLAYER(it),
1226 if (it.team == surv_attackerteam)
1228 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1229 CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
1230 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1231 INFO_SURVIVAL_1ST_ROUND_ATTACKER);
1235 FOREACH_CLIENT(IS_PLAYER(it),
1237 if (it.team == surv_defenderteam)
1239 if (surv_type == SURVIVAL_TYPE_COOP)
1241 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1242 CENTER_SURVIVAL_COOP_DEFENDER);
1243 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1244 INFO_SURVIVAL_COOP_DEFENDER);
1247 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1248 CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
1249 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1250 INFO_SURVIVAL_1ST_ROUND_DEFENDER);
1256 case SURVIVAL_ROUND_SECOND:
1258 FOREACH_CLIENT(IS_PLAYER(it),
1260 if (it.team == surv_attackerteam)
1262 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1263 CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1264 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1265 INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1269 FOREACH_CLIENT(IS_PLAYER(it),
1271 if (it.team == surv_defenderteam)
1273 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1274 CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1275 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1276 INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1283 if (autocvar_g_surv_stealth)
1287 FOREACH_CLIENT(IS_PLAYER(it),
1291 case surv_defenderteam:
1293 if (it.surv_role == SURVIVAL_ROLE_PLAYER)
1295 WaypointSprite_Spawn(WP_AssaultDestroy, 0, 0, it, '0 0 64',
1296 NULL, surv_attackerteam, it, surv_attack_sprite, false,
1297 RADARICON_OBJECTIVE);
1298 if (autocvar_g_instagib == 1)
1300 WaypointSprite_UpdateMaxHealth(it.surv_attack_sprite,
1301 PlayerTemplate_GetFloatValue("surv_defender",
1302 "start_armor") + 1);
1303 WaypointSprite_UpdateHealth(it.surv_attack_sprite,
1308 WaypointSprite_UpdateMaxHealth(it.surv_attack_sprite,
1309 PlayerTemplate_GetFloatValue("surv_defender",
1310 "start_health") + PlayerTemplate_GetFloatValue(
1311 "surv_defender", "start_armor"));
1312 WaypointSprite_UpdateHealth(it.surv_attack_sprite,
1313 it.health + it.armorvalue);
1322 bool Surv_IsEliminated(entity player)
1324 switch (player.surv_state)
1326 case SURVIVAL_STATE_NOT_PLAYING:
1330 case SURVIVAL_STATE_PLAYING:
1332 if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
1334 // A hack until proper scoreboard is done.
1337 if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
1338 IS_OBSERVER(player)))
1345 // Should never reach here
1349 //============================= Hooks ========================================
1351 /// \brief Hook that is called to determine general rules of the game.
1352 MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
1354 surv_warmup = warmup_stage;
1357 /// \brief Hook that is called to determine if there is a weapon arena.
1358 MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
1360 // Removing any weapon arena.
1361 M_ARGV(0, string) = "off";
1364 /// \brief Hook that is called to determine start items of all players.
1365 MUTATOR_HOOKFUNCTION(surv, SetStartItems)
1367 if (autocvar_g_instagib == 1)
1371 start_weapons = WEPSET(Null);
1372 warmup_start_weapons = WEPSET(Null);
1375 MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
1377 if (game_stopped || !surv_isroundactive)
1381 float roundtime = 0;
1382 switch (surv_roundtype)
1384 case SURVIVAL_ROUND_FIRST:
1386 roundtime = time - surv_roundstarttime;
1389 case SURVIVAL_ROUND_SECOND:
1391 roundtime = round_handler_GetEndTime() - time;
1395 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1397 it.surv_round_time_stat = roundtime;
1401 /// \brief Hook that determines which team player can join. This is called
1402 /// before ClientConnect.
1403 MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
1405 entity player = M_ARGV(2, entity);
1406 LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
1407 if (IS_BOT_CLIENT(player))
1409 int teambits = surv_attackerteambit;
1410 if ((player.team == surv_defenderteam) || (surv_numdefenders <
1411 autocvar_g_surv_team_size))
1413 teambits |= surv_defenderteambit;
1415 M_ARGV(0, float) = teambits;
1418 if (surv_type == SURVIVAL_TYPE_COOP)
1420 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1422 M_ARGV(0, float) = surv_defenderteambit;
1425 M_ARGV(0, float) = 0;
1429 if (surv_numattackerhumans < autocvar_g_surv_team_size)
1431 teambits |= surv_attackerteambit;
1433 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1435 teambits |= surv_defenderteambit;
1437 M_ARGV(0, float) = teambits;
1441 /// \brief Hook that determines the best team for the player to join.
1442 MUTATOR_HOOKFUNCTION(surv, JoinBestTeam, CBC_ORDER_EXCLUSIVE)
1444 if (surv_type == SURVIVAL_TYPE_COOP)
1448 entity player = M_ARGV(0, entity);
1449 if (IS_BOT_CLIENT(player))
1453 int numattackerhumans = surv_numattackerhumans;
1454 int numdefenderhumans = surv_numdefenderhumans;
1455 if (player.team == surv_attackerteam)
1457 --numattackerhumans;
1459 else if (player.team == surv_defenderteam)
1461 --numdefenderhumans;
1463 if (numattackerhumans < numdefenderhumans)
1465 M_ARGV(1, float) = Team_TeamToNumber(surv_attackerteam);
1468 if (numattackerhumans > numdefenderhumans)
1470 M_ARGV(1, float) = Team_TeamToNumber(surv_defenderteam);
1473 M_ARGV(1, float) = floor(random() * 2) + 1;
1476 /// \brief Hook that is called when player has changed the team.
1477 MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
1479 entity player = M_ARGV(0, entity);
1480 int oldteam = M_ARGV(1, float);
1481 int newteam = M_ARGV(2, float);
1482 string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
1483 ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
1485 DebugPrintToChatAll(message);
1486 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1488 Surv_RemovePlayerFromAliveList(player, oldteam);
1490 Surv_RemovePlayerFromTeam(player, oldteam);
1491 if (Surv_AddPlayerToTeam(player, newteam) == false)
1495 //Surv_CountAlivePlayers();
1496 if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1498 Surv_AddPlayerToAliveList(player, newteam);
1502 /// \brief Hook that is called when player connects to the server.
1503 MUTATOR_HOOKFUNCTION(surv, ClientConnect)
1505 entity player = M_ARGV(0, entity);
1506 LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
1507 player.surv_savedplayermodel = player.playermodel;
1508 if (IS_REAL_CLIENT(player))
1510 player.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1511 player.surv_defenders_alive_stat = surv_numdefendersalive;
1512 player.redalive_stat = redalive;
1513 player.bluealive_stat = bluealive;
1514 player.yellowalive_stat = yellowalive;
1515 player.pinkalive_stat = pinkalive;
1517 if (player.surv_role == SURVIVAL_ROLE_NONE)
1519 Surv_AddPlayerToTeam(player, player.team);
1524 /// \brief Hook that is called when player disconnects from the server.
1525 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
1527 entity player = M_ARGV(0, entity);
1528 if (!IS_DEAD(player))
1530 Surv_RemovePlayerFromAliveList(player, player.team);
1532 Surv_RemovePlayerFromTeam(player, player.team);
1533 //Surv_CountAlivePlayers();
1536 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
1538 entity player = M_ARGV(0, entity);
1539 LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
1540 if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
1542 TRANSMUTE(Observer, player);
1546 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
1548 entity player = M_ARGV(0, entity);
1549 LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
1550 if (player.killindicator_teamchange == -2) // player wants to spectate
1552 LOG_TRACE("killindicator_teamchange == -2");
1553 player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
1555 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1557 return false; // allow team reset
1559 return true; // prevent team reset
1562 /// \brief Hook that determines whether player can spawn. It is not called for
1563 /// players who have joined the team and are dead.
1564 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
1566 entity player = M_ARGV(0, entity);
1567 LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
1568 if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1572 return !Surv_CanPlayerSpawn(player);
1575 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
1577 LOG_TRACE("Survival: reset_map_global");
1578 surv_allowed_to_spawn = true;
1579 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1581 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1583 it.surv_round_time_stat = 0;
1589 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
1591 LOG_TRACE("Survival: reset_map_players");
1592 surv_numattackersalive = 0;
1593 surv_numdefendersalive = 0;
1596 surv_warmup = false;
1598 else if (surv_type == SURVIVAL_TYPE_VERSUS)
1602 FOREACH_CLIENT(true,
1605 if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
1608 it.surv_state = SURVIVAL_STATE_PLAYING;
1610 if (it.surv_state == SURVIVAL_STATE_PLAYING)
1612 TRANSMUTE(Player, it);
1613 it.surv_state = SURVIVAL_STATE_PLAYING;
1614 PutClientInServer(it);
1617 bot_relinkplayerlist();
1621 /// \brief Hook that is called when player spawns.
1622 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
1624 entity player = M_ARGV(0, entity);
1625 LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
1626 player.surv_state = SURVIVAL_STATE_PLAYING;
1627 Surv_DeterminePlayerModel(player);
1628 if (player.surv_savedplayerstate != NULL)
1630 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1631 delete(player.surv_savedplayerstate);
1632 player.surv_savedplayerstate = NULL;
1636 PlayerTemplate_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
1638 switch (player.team)
1640 case surv_attackerteam:
1642 switch (player.surv_role)
1644 case SURVIVAL_ROLE_PLAYER:
1646 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1647 CENTER_ASSAULT_ATTACKING);
1652 LOG_TRACE("Survival: PlayerSpawn: Invalid attacker role.");
1658 case surv_defenderteam:
1660 if (player.surv_role != SURVIVAL_ROLE_PLAYER)
1662 LOG_TRACE("Survival: PlayerSpawn: ", player.netname,
1663 " has invalid defender role.");
1665 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1666 CENTER_ASSAULT_DEFENDING);
1670 //Surv_CountAlivePlayers();
1671 Surv_AddPlayerToAliveList(player, player.team);
1674 /// \brief UGLY HACK. This is called every frame to keep player model correct.
1675 MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
1677 entity player = M_ARGV(2, entity);
1678 M_ARGV(0, string) = player.surv_playermodel;
1681 /// \brief Hook that is called every frame to determine how player health should
1683 MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
1685 entity player = M_ARGV(0, entity);
1686 if (player.team == surv_defenderteam)
1693 /// \brief Hook that is called to determine if balance messages will appear.
1694 MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
1699 /// \brief Hook that is called when player touches an item.
1700 MUTATOR_HOOKFUNCTION(surv, ItemTouch)
1702 entity item = M_ARGV(0, entity);
1703 entity player = M_ARGV(1, entity);
1704 switch (player.team)
1706 case surv_attackerteam:
1708 return PlayerTemplate_ItemTouch(player, item,
1709 Surv_GetPlayerTemplate(player));
1711 case surv_defenderteam:
1713 switch (item.classname)
1715 case "item_strength":
1717 W_GiveWeapon(player, WEP_HMG.m_id);
1718 player.superweapons_finished = max(
1719 player.superweapons_finished, time) +
1720 autocvar_g_balance_superweapons_time;
1721 Item_ScheduleRespawn(item);
1722 sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
1724 return MUT_ITEMTOUCH_RETURN;
1726 case "item_invincible":
1728 W_GiveWeapon(player, WEP_RPC.m_id);
1729 player.superweapons_finished = max(
1730 player.superweapons_finished, time) +
1731 autocvar_g_balance_superweapons_time;
1732 Item_ScheduleRespawn(item);
1733 sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
1734 return MUT_ITEMTOUCH_RETURN;
1738 return PlayerTemplate_ItemTouch(player, item,
1739 Surv_GetPlayerTemplate(player));
1742 DebugPrintToChat(player, item.classname);
1743 return MUT_ITEMTOUCH_RETURN;
1746 return MUT_ITEMTOUCH_CONTINUE;
1749 /// \brief Hook which is called when the player tries to throw their weapon.
1750 MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
1752 entity player = M_ARGV(0, entity);
1753 if (player.team == surv_defenderteam)
1759 /// \brief Hook which is called when the damage amount must be determined.
1760 MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
1762 entity frag_attacker = M_ARGV(1, entity);
1763 entity frag_target = M_ARGV(2, entity);
1764 float deathtype = M_ARGV(3, float);
1765 float damage = M_ARGV(4, float);
1766 M_ARGV(4, float) = PlayerTemplate_Damage_Calculate(frag_attacker,
1767 Surv_GetPlayerTemplate(frag_attacker), frag_target,
1768 Surv_GetPlayerTemplate(frag_target), deathtype, damage);
1771 /// \brief Hook which is called when the player was damaged.
1772 MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
1774 entity target = M_ARGV(1, entity);
1775 if (target.team != surv_defenderteam)
1779 Surv_UpdateDefenderHealthStat();
1780 entity attacker = M_ARGV(0, entity);
1781 if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
1782 SURVIVAL_ROLE_PLAYER))
1784 float health = M_ARGV(2, float);
1785 float armor = M_ARGV(3, float);
1786 float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
1787 PlayerScore_Add(attacker, SP_SCORE, score);
1789 if (autocvar_g_surv_stealth)
1793 if (target.health < 1)
1795 WaypointSprite_Kill(target.surv_attack_sprite);
1799 if (autocvar_g_instagib == 1)
1801 WaypointSprite_UpdateHealth(target.surv_attack_sprite,
1802 target.armorvalue + 1);
1806 WaypointSprite_UpdateHealth(target.surv_attack_sprite,
1807 target.health + target.armorvalue);
1812 /// \brief Hook which is called when the player dies.
1813 MUTATOR_HOOKFUNCTION(surv, PlayerDies)
1815 //DebugPrintToChatAll("PlayerDies");
1816 entity attacker = M_ARGV(1, entity);
1817 entity victim = M_ARGV(2, entity);
1818 if ((attacker.team == surv_defenderteam) &&
1819 (victim.team == surv_attackerteam))
1821 switch (victim.surv_role)
1823 case SURVIVAL_ROLE_PLAYER:
1825 GivePlayerHealth(attacker,
1826 autocvar_g_surv_defender_attacker_frag_health);
1827 GivePlayerArmor(attacker,
1828 autocvar_g_surv_defender_attacker_frag_armor);
1829 GivePlayerAmmo(attacker, ammo_shells,
1830 autocvar_g_surv_defender_attacker_frag_shells);
1831 GivePlayerAmmo(attacker, ammo_nails,
1832 autocvar_g_surv_defender_attacker_frag_bullets);
1833 GivePlayerAmmo(attacker, ammo_rockets,
1834 autocvar_g_surv_defender_attacker_frag_rockets);
1835 GivePlayerAmmo(attacker, ammo_cells,
1836 autocvar_g_surv_defender_attacker_frag_cells);
1839 case SURVIVAL_ROLE_CANNON_FODDER:
1841 GivePlayerHealth(attacker,
1842 autocvar_g_surv_defender_cannon_fodder_frag_health);
1843 GivePlayerArmor(attacker,
1844 autocvar_g_surv_defender_cannon_fodder_frag_armor);
1845 GivePlayerAmmo(attacker, ammo_shells,
1846 autocvar_g_surv_defender_cannon_fodder_frag_shells);
1847 GivePlayerAmmo(attacker, ammo_nails,
1848 autocvar_g_surv_defender_cannon_fodder_frag_bullets);
1849 GivePlayerAmmo(attacker, ammo_rockets,
1850 autocvar_g_surv_defender_cannon_fodder_frag_rockets);
1851 GivePlayerAmmo(attacker, ammo_cells,
1852 autocvar_g_surv_defender_cannon_fodder_frag_cells);
1857 if ((victim.team == surv_defenderteam) &&
1858 (autocvar_g_surv_defender_drop_weapons == false))
1860 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1862 .entity went = weaponentities[slot];
1863 victim.(went).m_weapon = WEP_Null;
1866 if (!Surv_CanPlayerSpawn(victim))
1868 victim.respawn_flags = RESPAWN_SILENT;
1869 if (IS_BOT_CLIENT(victim))
1877 /// \brief Hook which is called after the player died.
1878 MUTATOR_HOOKFUNCTION(surv, PlayerDied)
1880 //DebugPrintToChatAll("PlayerDied");
1881 entity player = M_ARGV(0, entity);
1882 Surv_RemovePlayerFromAliveList(player, player.team);
1883 //Surv_CountAlivePlayers();
1886 /// \brief Hook which is called when player has scored a frag.
1887 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
1889 if (surv_type == SURVIVAL_TYPE_COOP)
1893 entity attacker = M_ARGV(0, entity);
1894 if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
1895 SURVIVAL_ROLE_CANNON_FODDER))
1897 M_ARGV(2, float) = 0;
1900 entity target = M_ARGV(1, entity);
1901 if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
1904 M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
1909 /// \brief I'm not sure exactly what this function does but it is very
1910 /// important. Without it bots are completely broken. Is it a hack? Of course.
1911 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
1913 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
1914 if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
1923 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
1925 // Don't announce remaining frags