]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_survival.qc
Merge branch 'Lyberta/TeamplayFixes_' into Lyberta/Survival
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator / gamemode_survival.qc
1 #include "gamemode_survival.qh"
2
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>
6
7 //============================ Constants ======================================
8
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.
11
12 /// \brief Used when bitfield team count is requested.
13 const int SURVIVAL_TEAM_BITS = 3;
14
15 enum
16 {
17         SURVIVAL_TYPE_COOP, ///< All humans are on the defender team.
18         SURVIVAL_TYPE_VERSUS ///< Humans take turns between attackers and defenders.
19 };
20
21 const string SURVIVAL_TYPE_COOP_VALUE = "coop"; ///< Cvar value for coop.
22 const string SURVIVAL_TYPE_VERSUS_VALUE = "versus"; ///< Cvar value for versus.
23
24 enum
25 {
26         /// \brief First round where there is timelimit set by the server.
27         SURVIVAL_ROUND_FIRST,
28         /// \brief Second round where defender team tries to survive for the first
29         /// round's time.
30         SURVIVAL_ROUND_SECOND
31 };
32
33 enum
34 {
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
39 };
40
41 enum
42 {
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.
46 };
47
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");
51
52 SOUND(SURV_RED_SCORES, "ctf/red_capture");
53 SOUND(SURV_BLUE_SCORES, "ctf/blue_capture");
54
55 //======================= Global variables ====================================
56
57 float autocvar_g_surv_warmup; ///< Warmup time in seconds.
58 float autocvar_g_surv_round_timelimit; ///< First round time limit in seconds.
59
60 int autocvar_g_surv_point_limit; ///< Maximum number of points.
61 int autocvar_g_surv_point_leadlimit; ///< Maximum lead of a single team.
62
63 /// \brief How much players are allowed in teams (excluding cannon fodder).
64 int autocvar_g_surv_team_size;
65 /// \brief If set, defenders will not be shown on the radar.
66 int autocvar_g_surv_stealth;
67 /// \brief Whether to allow spectating enemy players while dead.
68 bool autocvar_g_surv_spectate_enemies;
69
70 /// \brief Whether to force overkill player models for attackers.
71 int autocvar_g_surv_attacker_force_overkill_models;
72 /// \brief Whether to force overkill player models for defenders.
73 int autocvar_g_surv_defender_force_overkill_models;
74 /// \brief Whether to force overkill player models for cannon fodder.
75 int autocvar_g_surv_cannon_fodder_force_overkill_models;
76
77 /// \brief How much score attackers gain per 1 point of damage.
78 float autocvar_g_surv_attacker_damage_score;
79
80 /// \brief How much score attackers get for fragging defenders.
81 float autocvar_g_surv_attacker_frag_score;
82
83 /// \brief How much health do defenders get when they frag an attacker.
84 int autocvar_g_surv_defender_attacker_frag_health;
85 /// \brief How much armor do defenders get when they frag an attacker.
86 int autocvar_g_surv_defender_attacker_frag_armor;
87 /// \brief How many shells do defenders get when they frag an attacker.
88 int autocvar_g_surv_defender_attacker_frag_shells;
89 /// \brief How many bullets do defenders get when they frag an attacker.
90 int autocvar_g_surv_defender_attacker_frag_bullets;
91 /// \brief How many rockets do defenders get when they frag an attacker.
92 int autocvar_g_surv_defender_attacker_frag_rockets;
93 /// \brief How many cells do defenders get when they frag an attacker.
94 int autocvar_g_surv_defender_attacker_frag_cells;
95 /// \brief How much health do defenders get when they frag cannon fodder.
96 int autocvar_g_surv_defender_cannon_fodder_frag_health;
97 /// \brief How much armor do defenders get when they frag cannon fodder.
98 int autocvar_g_surv_defender_cannon_fodder_frag_armor;
99 /// \brief How many shells do defenders get when they frag cannon fodder.
100 int autocvar_g_surv_defender_cannon_fodder_frag_shells;
101 /// \brief How many bullets do defenders get when they frag cannon fodder.
102 int autocvar_g_surv_defender_cannon_fodder_frag_bullets;
103 /// \brief How many rockets do defenders get when they frag cannon fodder.
104 int autocvar_g_surv_defender_cannon_fodder_frag_rockets;
105 /// \brief How many cells do defenders get when they frag cannon fodder.
106 int autocvar_g_surv_defender_cannon_fodder_frag_cells;
107
108 /// \brief Whether defenders drop weapons after death.
109 int autocvar_g_surv_defender_drop_weapons;
110
111 /// \brief A stat that is used to track the time left in the round.
112 .float surv_round_time_stat = _STAT(SURV_ROUND_TIME);
113 /// \brief A stat that is used to track defender team.
114 .int surv_defender_team_stat = _STAT(SURV_DEFENDER_TEAM);
115 /// \brief A stat that is used to track number of defenders alive.
116 .int surv_defenders_alive_stat = _STAT(SURV_DEFENDERS_ALIVE);
117 /// \brief A stat that is used to track the total health of defenders.
118 .float surv_defender_health_stat = _STAT(SURV_DEFENDER_HEALTH);
119
120 /// \brief Holds the state of the player. See SURVIVAL_STATE constants.
121 .int surv_state;
122 /// \brief Holds the role of the player. See SURVIVAL_ROLE constants.
123 .int surv_role;
124 .string surv_savedplayermodel; ///< Initial player model.
125 /// \brief Player state used during replacement of bot player with real player.
126 .entity surv_savedplayerstate;
127 .string surv_playermodel; ///< Player model forced by the game.
128
129 .entity surv_attack_sprite; ///< Holds the sprite telling attackers to attack.
130
131 int surv_type; ///< Holds the type of survival. See SURVIVAL_TYPE constants.
132 bool surv_warmup; ///< Holds whether warmup is active.
133 /// \brief Holds the type of the current round. See SURVIVAL_ROUND constants.
134 int surv_roundtype;
135 bool surv_isroundactive; ///< Holds whether the round is active.
136 float surv_roundstarttime; ///< Holds the time of the round start.
137 /// \brief Holds the time needed to survive in the second round.
138 float surv_timetobeat;
139
140 int surv_attackerteam; ///< Holds the attacker team.
141 int surv_defenderteam; ///< Holds the defender team.
142
143 int surv_attackerteambit; ///< Hols the attacker team bitmask.
144 int surv_defenderteambit; ///< Holds the defender team bitmask.
145
146 int surv_numattackers; ///< Holds the number of players in attacker team.
147 int surv_numdefenders; ///< Holds the number of players in defender team.
148
149 /// \brief Holds the number of humans in attacker team.
150 int surv_numattackerhumans;
151 /// \brief Holds the number of humans in defender team.
152 int surv_numdefenderhumans;
153
154 /// \brief Holds the number of attacker players that are alive.
155 int surv_numattackersalive;
156 /// \brief Holds the number of defender players that are alive.
157 int surv_numdefendersalive;
158
159 bool surv_autobalance; ///< Holds whether autobalance is active.
160 bool surv_announcefrags; ///< Holds whether remaining frags must be announced.
161 bool surv_allowed_to_spawn; ///< Holds whether players are allowed to spawn.
162
163 //====================== Forward declarations =================================
164
165 /// \brief Determines whether the round can start.
166 /// \return True if the round can start, false otherwise.
167 bool Surv_CanRoundStart();
168
169 /// \brief Determines whether the round can end.
170 /// \return True if the round can end, false otherwise.
171 bool Surv_CanRoundEnd();
172
173 /// \brief Called when the round starts.
174 /// \return No return.
175 void Surv_RoundStart();
176
177 /// \brief Returns whether player has been eliminated.
178 /// \param[in] player Player to check.
179 /// \return True if player is eliminated, false otherwise.
180 bool Surv_IsEliminated(entity player);
181
182 /// \brief Updates stats of team count on HUD.
183 /// \return No return.
184 void Surv_UpdateTeamStats();
185
186 /// \brief Updates stats of alive players on HUD.
187 /// \return No return.
188 void Surv_UpdateAliveStats();
189
190 /// \brief Updates defender health on the HUD.
191 /// \return No return.
192 void Surv_UpdateDefenderHealthStat();
193
194 //========================= Free functions ====================================
195
196 void Surv_Initialize()
197 {
198         switch (cvar_string("g_surv_type"))
199         {
200                 case SURVIVAL_TYPE_COOP_VALUE:
201                 {
202                         surv_type = SURVIVAL_TYPE_COOP;
203                         break;
204                 }
205                 case SURVIVAL_TYPE_VERSUS_VALUE:
206                 {
207                         surv_type = SURVIVAL_TYPE_VERSUS;
208                         break;
209                 }
210                 default:
211                 {
212                         error("Invalid survival type.");
213                 }
214         }
215         surv_roundtype = SURVIVAL_ROUND_FIRST;
216         surv_isroundactive = false;
217         surv_roundstarttime = time;
218         surv_timetobeat = autocvar_g_surv_round_timelimit;
219         if (random() < 0.5)
220         {
221                 surv_attackerteam = NUM_TEAM_1;
222                 surv_defenderteam = NUM_TEAM_2;
223                 surv_attackerteambit = SURVIVAL_TEAM_1_BIT;
224                 surv_defenderteambit = SURVIVAL_TEAM_2_BIT;
225         }
226         else
227         {
228                 surv_attackerteam = NUM_TEAM_2;
229                 surv_defenderteam = NUM_TEAM_1;
230                 surv_attackerteambit = SURVIVAL_TEAM_2_BIT;
231                 surv_defenderteambit = SURVIVAL_TEAM_1_BIT;
232         }
233         surv_numattackers = 0;
234         surv_numdefenders = 0;
235         surv_numattackerhumans = 0;
236         surv_numdefenderhumans = 0;
237         surv_numattackersalive = 0;
238         surv_numdefendersalive = 0;
239         surv_autobalance = true;
240         surv_announcefrags = true;
241         surv_allowed_to_spawn = true;
242         precache_all_playermodels("models/ok_player/*.dpm");
243         ScoreRules_basics(SURVIVAL_TEAM_BITS, SFL_SORT_PRIO_PRIMARY, 0, true);
244         ScoreInfo_SetLabel_TeamScore(1, "rounds", SFL_SORT_PRIO_PRIMARY);
245         ScoreRules_basics_end();
246         round_handler_Spawn(Surv_CanRoundStart, Surv_CanRoundEnd, Surv_RoundStart);
247         round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
248         EliminatedPlayers_Init(Surv_IsEliminated);
249         ActivateTeamplay();
250         SetLimits(autocvar_g_surv_point_limit, autocvar_g_surv_point_leadlimit,
251                 autocvar_timelimit_override, -1);
252 }
253
254 /// \brief Returns the name of the template of the given player.
255 /// \param[in] player Player to inspect.
256 /// \return Name of the template of the given player.
257 string Surv_GetPlayerTemplate(entity player)
258 {
259         switch (player.team)
260         {
261                 case surv_attackerteam:
262                 {
263                         switch (player.surv_role)
264                         {
265                                 case SURVIVAL_ROLE_NONE:
266                                 case SURVIVAL_ROLE_PLAYER:
267                                 {
268                                         return cvar_string("g_surv_attacker_template");
269                                 }
270                                 case SURVIVAL_ROLE_CANNON_FODDER:
271                                 {
272                                         return cvar_string("g_surv_cannon_fodder_template");
273                                 }
274                         }
275                 }
276                 case surv_defenderteam:
277                 {
278                         return cvar_string("g_surv_defender_template");
279                 }
280         }
281         return "default";
282 }
283
284 /// \brief Saves the player state. Used to seamlessly swap bots with humans.
285 /// \param[in] player Player to save the state of.
286 /// \return Entity containing the player state.
287 entity Surv_SavePlayerState(entity player)
288 {
289         entity state = spawn();
290         state.origin = player.origin;
291         state.velocity = player.velocity;
292         state.angles = player.angles;
293         state.health = player.health;
294         state.armorvalue = player.armorvalue;
295         state.ammo_shells = player.ammo_shells;
296         state.ammo_nails = player.ammo_nails;
297         state.ammo_rockets = player.ammo_rockets;
298         state.ammo_cells = player.ammo_cells;
299         state.weapons = player.weapons;
300         state.items = player.items;
301         state.superweapons_finished = player.superweapons_finished;
302         return state;
303 }
304
305 /// \brief Restores a saved player state.
306 /// \param[in] player Player to restore the state of.
307 /// \param[in] st State to restore.
308 /// \return No return.
309 void Surv_RestorePlayerState(entity player, entity st)
310 {
311         player.origin = st.origin;
312         player.velocity = st.velocity;
313         player.angles = st.angles;
314         player.health = st.health;
315         player.armorvalue = st.armorvalue;
316         player.ammo_shells = st.ammo_shells;
317         player.ammo_nails = st.ammo_nails;
318         player.ammo_rockets = st.ammo_rockets;
319         player.ammo_cells = st.ammo_cells;
320         player.weapons = st.weapons;
321         player.items = st.items;
322         player.superweapons_finished = st.superweapons_finished;
323 }
324
325 /// \brief Returns the attacker with the lowest score.
326 /// \param[in] bot Whether to search only for bots.
327 /// \return Attacker with the lowest score or NULL if not found.
328 entity Surv_FindLowestAttacker(bool bot)
329 {
330         entity player = NULL;
331         float score = FLOAT_MAX;
332         FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
333         {
334                 if ((it.team == surv_attackerteam) && (it.surv_role ==
335                         SURVIVAL_ROLE_PLAYER))
336                 {
337                         float tempscore = PlayerScore_Get(it, SP_SCORE);
338                         if (tempscore < score)
339                         {
340                                 player = it;
341                                 score = tempscore;
342                         }
343                 }
344         });
345         return player;
346 }
347
348 /// \brief Returns the defender with the lowest score.
349 /// \param[in] bot Whether to search only for bots.
350 /// \param[in] alive Whether to search only for alive players.
351 /// \return Defender with the lowest score or NULL if not found.
352 entity Surv_FindLowestDefender(bool bot, bool alive)
353 {
354         entity player = NULL;
355         float score = FLOAT_MAX;
356         FOREACH_CLIENT(bot ? IS_BOT_CLIENT(it) : true,
357         {
358                 if ((it.team == surv_defenderteam) && (alive ? !IS_DEAD(it) : true))
359                 {
360                         float tempscore = PlayerScore_Get(it, SP_SCORE);
361                         if (tempscore < score)
362                         {
363                                 player = it;
364                                 score = tempscore;
365                         }
366                 }
367         });
368         return player;
369 }
370
371 /// \brief Returns the cannon fodder.
372 /// \return Cannon fodder or NULL if not found.
373 entity Surv_FindCannonFodder()
374 {
375         FOREACH_CLIENT(IS_BOT_CLIENT(it),
376         {
377                 if (it.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
378                 {
379                         return it;
380                 }
381         });
382         return NULL;
383 }
384
385 /// \brief Changes the number of players in a team.
386 /// \param[in] teamnum Team to adjust.
387 /// \param[in] delta Amount to adjust by.
388 /// \return No return.
389 void Surv_ChangeNumberOfPlayers(int teamnum, int delta)
390 {
391         switch (teamnum)
392         {
393                 case surv_attackerteam:
394                 {
395                         surv_numattackers += delta;
396                         LOG_TRACE("Number of attackers = ", ftos(surv_numattackers),
397                                 " was = ", ftos(surv_numattackers - delta));
398                         Surv_UpdateTeamStats();
399                         return;
400                 }
401                 case surv_defenderteam:
402                 {
403                         surv_numdefenders += delta;
404                         LOG_TRACE("Number of defenders = ", ftos(surv_numdefenders),
405                                 " was = ", ftos(surv_numdefenders - delta));
406                         Surv_UpdateTeamStats();
407                         return;
408                 }
409         }
410 }
411
412 /// \brief Changes the number of alive players in a team.
413 /// \param[in] teamnum Team to adjust.
414 /// \param[in] delta Amount to adjust by.
415 /// \return No return.
416 void Surv_ChangeNumberOfAlivePlayers(int teamnum, int delta)
417 {
418         switch (teamnum)
419         {
420                 case surv_attackerteam:
421                 {
422                         surv_numattackersalive += delta;
423                         LOG_TRACE("Number of alive attackers = ", ftos(
424                                 surv_numattackersalive), " was = ", ftos(surv_numattackersalive
425                                 - delta));
426                         break;
427                 }
428                 case surv_defenderteam:
429                 {
430                         surv_numdefendersalive += delta;
431                         LOG_TRACE("Number of alive defenders = ", ftos(
432                                 surv_numdefendersalive), " was = ", ftos(surv_numdefendersalive
433                                 - delta));
434                         break;
435                 }
436         }
437         Surv_UpdateAliveStats();
438         eliminatedPlayers.SendFlags |= 1;
439 }
440
441 /// \brief Sets the player role.
442 /// \param[in,out] player Player to adjust.
443 /// \param[in] role Role to set.
444 /// \return No return.
445 void Surv_SetPlayerRole(entity player, int role)
446 {
447         if (player.surv_role == role)
448         {
449                 return;
450         }
451         player.surv_role = role;
452         switch (role)
453         {
454                 case SURVIVAL_ROLE_NONE:
455                 {
456                         LOG_TRACE(player.netname, " now has no role.");
457                         break;
458                 }
459                 case SURVIVAL_ROLE_PLAYER:
460                 {
461                         LOG_TRACE(player.netname, " is now a player.");
462                         break;
463                 }
464                 case SURVIVAL_ROLE_CANNON_FODDER:
465                 {
466                         LOG_TRACE(player.netname, " is now a cannon fodder.");
467                         break;
468                 }
469         }
470 }
471
472 /// \brief Adds player to team. Handles bookkeeping information.
473 /// \param[in] player Player to add.
474 /// \param[in] teamnum Team to add to.
475 /// \return True on success, false otherwise.
476 bool Surv_AddPlayerToTeam(entity player, int teamnum)
477 {
478         LOG_TRACE("Survival: AddPlayerToTeam, player: ", player.netname);
479         switch (teamnum)
480         {
481                 case surv_attackerteam:
482                 {
483                         LOG_TRACE("Attacker team");
484                         if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
485                         {
486                                 LOG_TRACE("Cannon fodder is switching team");
487                                 return true;
488                         }
489                         if (IS_BOT_CLIENT(player))
490                         {
491                                 LOG_TRACE("Client is bot");
492                                 LOG_TRACE("Attackers = ", ftos(surv_numattackers));
493                                 if (surv_numattackers < autocvar_g_surv_team_size)
494                                 {
495                                         Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
496                                         Surv_ChangeNumberOfPlayers(teamnum, +1);
497                                         return true;
498                                 }
499                                 Surv_SetPlayerRole(player, SURVIVAL_ROLE_CANNON_FODDER);
500                                 return true;
501                         }
502                         LOG_TRACE("Client is not a bot");
503                         LOG_TRACE("Attackers = ", ftos(surv_numattackers));
504                         if (surv_numattackers >= autocvar_g_surv_team_size)
505                         {
506                                 LOG_TRACE("Removing bot");
507                                 // Remove bot to make space for human.
508                                 entity bot = Surv_FindLowestAttacker(true);
509                                 if (bot == NULL)
510                                 {
511                                         LOG_TRACE("No valid bot to remove");
512                                         // No space in team, denying team change.
513                                         TRANSMUTE(Spectator, player);
514                                         return false;
515                                 }
516                                 LOG_TRACE("Changing ", bot.netname,
517                                         " from attacker to cannon fodder.");
518                                 Surv_SetPlayerRole(bot, SURVIVAL_ROLE_CANNON_FODDER);
519                                 if (!IS_DEAD(bot))
520                                 {
521                                         Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
522                                 }
523                                 Surv_ChangeNumberOfPlayers(teamnum, -1);
524                                 LOG_TRACE("Removed bot");
525                         }
526                         Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
527                         Surv_ChangeNumberOfPlayers(teamnum, +1);
528                         ++surv_numattackerhumans;                       
529                         LOG_TRACE("Human attackers = ", ftos(surv_numattackerhumans));
530                         if ((surv_autobalance == false) || (surv_numattackers -
531                                 surv_numdefenders) < 2)
532                         {
533                                 return true;
534                         }
535                         entity lowestplayer = Surv_FindLowestAttacker(true);
536                         if (lowestplayer != NULL)
537                         {
538                                 bool savedautobalance = surv_autobalance;
539                                 surv_autobalance = false;
540                                 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
541                                 surv_autobalance = savedautobalance;
542                                 return true;
543                         }
544                         lowestplayer = Surv_FindLowestAttacker(false);
545                         if (lowestplayer != NULL)
546                         {
547                                 bool savedautobalance = surv_autobalance;
548                                 surv_autobalance = false;
549                                 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
550                                 surv_autobalance = savedautobalance;
551                         }
552                         return true;
553                 }
554                 case surv_defenderteam:
555                 {
556                         LOG_TRACE("Defender team");
557                         if (IS_BOT_CLIENT(player))
558                         {
559                                 LOG_TRACE("Client is bot");
560                                 LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
561                                 if (surv_numdefenders < autocvar_g_surv_team_size)
562                                 {
563                                         Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
564                                         Surv_ChangeNumberOfPlayers(teamnum, +1);
565                                         return true;
566                                 }
567                                 LOG_TRACE("No space for defender, switching to attacker");
568                                 SetPlayerTeamSimple(player, surv_attackerteam);
569                                 return false;
570                         }
571                         LOG_TRACE("Client is not a bot");
572                         LOG_TRACE("Defenders = ", ftos(surv_numdefenders));
573                         if (surv_numdefenders >= autocvar_g_surv_team_size)
574                         {
575                                 LOG_TRACE("Removing bot");
576                                 // Remove bot to make space for human.
577                                 entity bot = Surv_FindLowestDefender(true, true);
578                                 if (bot == NULL)
579                                 {
580                                         bot = Surv_FindLowestDefender(true, false);
581                                 }
582                                 if (bot == NULL)
583                                 {
584                                         LOG_TRACE("No valid bot to remove");
585                                         // No space in team, denying team change.
586                                         TRANSMUTE(Spectator, player);
587                                         return false;
588                                 }
589                                 LOG_TRACE("Changing ", bot.netname,
590                                         " from defender to cannon fodder.");
591                                 if (!IS_DEAD(bot))
592                                 {
593                                         player.surv_savedplayerstate = Surv_SavePlayerState(bot);
594                                 }
595                                 bool savedautobalance = surv_autobalance;
596                                 surv_autobalance = false;
597                                 surv_announcefrags = false;
598                                 SetPlayerTeamSimple(bot, surv_attackerteam);
599                                 surv_autobalance = savedautobalance;
600                                 surv_announcefrags = true;
601                                 LOG_TRACE("Removed bot");
602                         }
603                         Surv_SetPlayerRole(player, SURVIVAL_ROLE_PLAYER);
604                         Surv_ChangeNumberOfPlayers(teamnum, +1);
605                         ++surv_numdefenderhumans;
606                         LOG_TRACE("Human defenders = ", ftos(surv_numdefenderhumans));
607                         if ((surv_autobalance == false) || (surv_numdefenders -
608                                 surv_numattackers) < 2)
609                         {
610                                 return true;
611                         }
612                         entity lowestplayer = Surv_FindLowestDefender(true, false);
613                         if (lowestplayer != NULL)
614                         {
615                                 bool savedautobalance = surv_autobalance;
616                                 surv_autobalance = false;
617                                 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
618                                 surv_autobalance = savedautobalance;
619                                 return true;
620                         }
621                         lowestplayer = Surv_FindLowestDefender(false, false);
622                         if (lowestplayer != NULL)
623                         {
624                                 bool savedautobalance = surv_autobalance;
625                                 surv_autobalance = false;
626                                 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
627                                 surv_autobalance = savedautobalance;
628                         }
629                         return true;
630                 }
631                 case -1:
632                 {
633                         LOG_TRACE("Spectator team");
634                         player.surv_role = SURVIVAL_ROLE_NONE;                  
635                         return false;
636                 }
637         }
638         LOG_TRACE("Invalid team");
639         player.surv_role = SURVIVAL_ROLE_NONE;
640         return false;
641 }
642
643 /// \brief Removes player from team. Handles bookkeeping information.
644 /// \param[in] player Player to remove.
645 /// \param[in] Team to remove from.
646 /// \return No return.
647 void Surv_RemovePlayerFromTeam(entity player, int teamnum)
648 {
649         LOG_TRACE("Survival: RemovePlayerFromTeam, player: ", player.netname);
650         switch (teamnum)
651         {
652                 case surv_attackerteam:
653                 {
654                         LOG_TRACE("Attacker team");
655                         if (player.surv_role == SURVIVAL_ROLE_NONE)
656                         {
657                                 string message = strcat("RemovePlayerFromTeam: ",
658                                         player.netname, " has invalid role.");
659                                 DebugPrintToChatAll(message);
660                                 return;
661                         }
662                         if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
663                         {
664                                 Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
665                                 return;
666                         }
667                         Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
668                         Surv_ChangeNumberOfPlayers(teamnum, -1);
669                         if (!IS_BOT_CLIENT(player))
670                         {
671                                 --surv_numattackerhumans;
672                         }
673                         if ((surv_autobalance == false) || (surv_numattackers >=
674                                 surv_numdefenders))
675                         {
676                                 return;
677                         }
678                         // Add bot to keep teams balanced.
679                         entity lowestplayer = Surv_FindCannonFodder();
680                         if (lowestplayer != NULL)
681                         {
682                                 LOG_TRACE("Changing ", lowestplayer.netname,
683                                         " from cannon fodder to attacker.");
684                                 Surv_SetPlayerRole(lowestplayer, SURVIVAL_ROLE_PLAYER);
685                                 Surv_ChangeNumberOfPlayers(teamnum, +1);
686                                 if (!IS_DEAD(lowestplayer))
687                                 {
688                                         Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
689                                 }
690                                 return;
691                         }
692                         lowestplayer = Surv_FindLowestDefender(true, false);
693                         if (lowestplayer != NULL)
694                         {
695                                 bool savedautobalance = surv_autobalance;
696                                 surv_autobalance = false;
697                                 SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
698                                 surv_autobalance = savedautobalance;
699                                 return;
700                         }
701                         lowestplayer = Surv_FindLowestDefender(false, false);
702                         if (lowestplayer == NULL)
703                         {
704                                 return;
705                         }
706                         bool savedautobalance = surv_autobalance;
707                         surv_autobalance = false;
708                         SetPlayerTeamSimple(lowestplayer, surv_attackerteam);
709                         surv_autobalance = savedautobalance;
710                         return;
711                 }
712                 case surv_defenderteam:
713                 {
714                         LOG_TRACE("Defender team");
715                         if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
716                         {
717                                 // This happens during team switch. We don't need to change
718                                 // anything.
719                                 LOG_TRACE("Cannon fodder. Assuming team switch");
720                                 return;
721                         }
722                         if (player.surv_role != SURVIVAL_ROLE_PLAYER)
723                         {
724                                 string message = strcat("RemovePlayerFromTeam: ",
725                                         player.netname, " has invalid role.");
726                                 DebugPrintToChatAll(message);
727                                 return;
728                         }
729                         Surv_SetPlayerRole(player, SURVIVAL_ROLE_NONE);
730                         Surv_ChangeNumberOfPlayers(teamnum, -1);
731                         if (!IS_BOT_CLIENT(player))
732                         {
733                                 --surv_numdefenderhumans;
734                         }
735                         if ((surv_autobalance == false) || (surv_numdefenders >=
736                                 surv_numattackers))
737                         {
738                                 return;
739                         }
740                         // Add bot to keep teams balanced.
741                         entity lowestplayer = Surv_FindCannonFodder();
742                         if (lowestplayer != NULL)
743                         {
744                                 LOG_TRACE("Changing ", lowestplayer.netname,
745                                         " from cannon fodder to defender.");
746                                 if (!IS_DEAD(player))
747                                 {
748                                         lowestplayer.surv_savedplayerstate =
749                                                 Surv_SavePlayerState(player);
750                                 }
751                                 bool savedautobalance = surv_autobalance;
752                                 surv_autobalance = false;
753                                 surv_announcefrags = false;
754                                 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
755                                 surv_autobalance = savedautobalance;
756                                 surv_announcefrags = true;                                      
757                                 return;
758                         }
759                         lowestplayer = Surv_FindLowestAttacker(true);
760                         if (lowestplayer != NULL)
761                         {
762                                 bool savedautobalance = surv_autobalance;
763                                 surv_autobalance = false;
764                                 surv_announcefrags = false;
765                                 SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
766                                 surv_autobalance = savedautobalance;
767                                 surv_announcefrags = true;
768                                 return;
769                         }
770                         lowestplayer = Surv_FindLowestAttacker(false);
771                         if (lowestplayer == NULL)
772                         {
773                                 return;
774                         }
775                         bool savedautobalance = surv_autobalance;
776                         surv_autobalance = false;
777                         surv_announcefrags = false;
778                         SetPlayerTeamSimple(lowestplayer, surv_defenderteam);
779                         surv_autobalance = savedautobalance;
780                         surv_announcefrags = true;
781                         return;
782                 }
783                 case -1:
784                 {
785                         LOG_TRACE("Spectator team");
786                         return;
787                 }
788                 default:
789                 {
790                         LOG_TRACE("Invalid team");
791                         return;
792                 }
793         }
794 }
795
796 /// \brief Updates stats of team count on HUD.
797 /// \return No return.
798 void Surv_UpdateTeamStats()
799 {
800         // Debug stuff
801         if (surv_attackerteam == NUM_TEAM_1)
802         {
803                 yellowalive = surv_numattackers;
804                 pinkalive = surv_numdefenders;
805         }
806         else
807         {
808                 pinkalive = surv_numattackers;
809                 yellowalive = surv_numdefenders;
810         }
811         FOREACH_CLIENT(IS_REAL_CLIENT(it),
812         {
813                 it.yellowalive_stat = yellowalive;
814                 it.pinkalive_stat = pinkalive;
815         });
816 }
817
818 /// \brief Adds player to alive list. Handles bookkeeping information.
819 /// \param[in] player Player to add.
820 /// \param[in] teamnum Team of the player.
821 /// \return No return.
822 void Surv_AddPlayerToAliveList(entity player, int teamnum)
823 {
824         switch (teamnum)
825         {
826                 case surv_attackerteam:
827                 {
828                         if (player.surv_role == SURVIVAL_ROLE_PLAYER)
829                         {
830                                 Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
831                         }
832                         return;
833                 }
834                 case surv_defenderteam:
835                 {
836                         Surv_ChangeNumberOfAlivePlayers(teamnum, +1);
837                         return;
838                 }
839         }
840 }
841
842 /// \brief Removes player from alive list. Handles bookkeeping information.
843 /// \param[in] player Player to remove.
844 /// \param[in] teamnum Team of the player.
845 /// \return No return.
846 void Surv_RemovePlayerFromAliveList(entity player, int teamnum)
847 {
848         if (player.surv_attack_sprite)
849         {
850                 WaypointSprite_Kill(player.surv_attack_sprite);
851         }
852         switch (teamnum)
853         {
854                 case surv_attackerteam:
855                 {
856                         if (player.surv_role == SURVIVAL_ROLE_PLAYER)
857                         {
858                                 Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
859                         }
860                         return;
861                 }
862                 case surv_defenderteam:
863                 {
864                         if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
865                         {
866                                 // This happens during team switch. We don't need to change
867                                 // anything.
868                                 return;
869                         }
870                         Surv_ChangeNumberOfAlivePlayers(teamnum, -1);
871                         if (warmup_stage || surv_allowed_to_spawn || !surv_announcefrags)
872                         {
873                                 return;
874                         }
875                         switch (surv_numdefendersalive)
876                         {
877                                 case 1:
878                                 {
879                                         sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT,
880                                                 VOL_BASE, ATTEN_NONE);
881                                         FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
882                                         {
883                                                 if (it.team == surv_defenderteam)
884                                                 {
885                                                         Send_Notification(NOTIF_ONE, it, MSG_CENTER,
886                                                                 CENTER_ALONE);
887                                                         return;
888                                                 }
889                                         });
890                                         return;
891                                 }
892                                 case 2:
893                                 {
894                                         sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT,
895                                                 VOL_BASE, ATTEN_NONE);
896                                         return;
897                                 }
898                                 case 3:
899                                 {
900                                         sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT,
901                                                 VOL_BASE, ATTEN_NONE);
902                                         return;
903                                 }
904                         }
905                         return;
906                 }
907         }
908 }
909
910 /// \brief Counts alive players.
911 /// \return No return.
912 /// \note In a perfect world this function shouldn't exist. However, since QC
913 /// code is so bad and spurious mutators can really mess with your code, this
914 /// function is called as a last resort.
915 void Surv_CountAlivePlayers()
916 {
917         int savednumdefenders = surv_numdefendersalive;
918         surv_numattackersalive = 0;
919         surv_numdefendersalive = 0;
920         FOREACH_CLIENT(IS_PLAYER(it),
921         {
922                 switch (it.team)
923                 {
924                         case surv_attackerteam:
925                         {
926                                 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
927                                 {
928                                         ++surv_numattackersalive;
929                                 }
930                                 break;
931                         }
932                         case surv_defenderteam:
933                         {
934                                 if ((it.surv_role == SURVIVAL_ROLE_PLAYER) && !IS_DEAD(it))
935                                 {
936                                         ++surv_numdefendersalive;
937                                 }
938                                 break;
939                         }
940                 }
941         });
942         Surv_UpdateAliveStats();
943         eliminatedPlayers.SendFlags |= 1;
944         if (warmup_stage || surv_allowed_to_spawn || (savednumdefenders <=
945                 surv_numdefendersalive))
946         {
947                 return;
948         }
949         switch (surv_numdefendersalive)
950         {
951                 case 1:
952                 {
953                         sound(NULL, CH_TRIGGER, SND_SURV_1_FRAG_LEFT, VOL_BASE, ATTEN_NONE);
954                         FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
955                         {
956                                 if (it.team == surv_defenderteam)
957                                 {
958                                         Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ALONE);
959                                         return;
960                                 }
961                         });
962                         return;
963                 }
964                 case 2:
965                 {
966                         sound(NULL, CH_TRIGGER, SND_SURV_2_FRAGS_LEFT, VOL_BASE,
967                                 ATTEN_NONE);
968                         return;
969                 }
970                 case 3:
971                 {
972                         sound(NULL, CH_TRIGGER, SND_SURV_3_FRAGS_LEFT, VOL_BASE,
973                                 ATTEN_NONE);
974                         return;
975                 }
976         }
977 }
978
979 /// \brief Updates stats of alive players on HUD.
980 /// \return No return.
981 void Surv_UpdateAliveStats()
982 {
983         // Debug stuff
984         if (surv_attackerteam == NUM_TEAM_1)
985         {
986                 redalive = surv_numattackersalive;
987                 bluealive = surv_numdefendersalive;
988         }
989         else
990         {
991                 bluealive = surv_numattackersalive;
992                 redalive = surv_numdefendersalive;
993         }
994         FOREACH_CLIENT(IS_REAL_CLIENT(it),
995         {
996                 it.surv_defenders_alive_stat = surv_numdefendersalive;
997                 it.redalive_stat = redalive;
998                 it.bluealive_stat = bluealive;
999         });
1000         Surv_UpdateDefenderHealthStat();
1001 }
1002
1003 /// \brief Updates defender health on the HUD.
1004 /// \return No return.
1005 void Surv_UpdateDefenderHealthStat()
1006 {
1007         float maxhealth;
1008         float totalhealth = 0;
1009         if (autocvar_g_instagib == 1)
1010         {
1011                 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1012                         "surv_defender", "start_armor") + 1);
1013                 FOREACH_CLIENT(IS_PLAYER(it),
1014                 {
1015                         if (it.team == surv_defenderteam)
1016                         {
1017                                 totalhealth += it.armorvalue + 1;
1018                         }
1019                 });
1020         }
1021         else
1022         {
1023                 maxhealth = surv_numdefenders * (PlayerTemplate_GetFloatValue(
1024                         "surv_defender", "start_health") + PlayerTemplate_GetFloatValue(
1025                         "surv_defender", "start_armor"));
1026                 FOREACH_CLIENT(IS_PLAYER(it),
1027                 {
1028                         if (it.team == surv_defenderteam)
1029                         {
1030                                 totalhealth += it.health;
1031                                 totalhealth += it.armorvalue;
1032                         }
1033                 });
1034         }
1035         float healthratio;
1036         if (maxhealth == 0)
1037         {
1038                 healthratio = 0;
1039         }
1040         else
1041         {
1042                 healthratio = totalhealth / maxhealth;
1043         }
1044         FOREACH_CLIENT(IS_REAL_CLIENT(it),
1045         {
1046                 it.surv_defender_health_stat = healthratio;
1047         });
1048 }
1049
1050 /// \brief Returns whether the player can spawn.
1051 /// \param[in] player Player to check.
1052 /// \return True if the player can spawn, false otherwise.
1053 bool Surv_CanPlayerSpawn(entity player)
1054 {
1055         if ((player.team == surv_attackerteam) ||
1056                 (player.surv_savedplayerstate != NULL))
1057         {
1058                 return true;
1059         }
1060         return surv_allowed_to_spawn;
1061 }
1062
1063 /// \brief Switches the round type.
1064 /// \return No return.
1065 void Surv_SwitchRoundType()
1066 {
1067         switch (surv_roundtype)
1068         {
1069                 case SURVIVAL_ROUND_FIRST:
1070                 {
1071                         surv_roundtype = SURVIVAL_ROUND_SECOND;
1072                         return;
1073                 }
1074                 case SURVIVAL_ROUND_SECOND:
1075                 {
1076                         surv_roundtype = SURVIVAL_ROUND_FIRST;
1077                         return;
1078                 }
1079         }
1080 }
1081
1082 /// \brief Cleans up the mess after the round has finished.
1083 /// \return No return.
1084 void Surv_RoundCleanup()
1085 {
1086         surv_allowed_to_spawn = false;
1087         surv_isroundactive = false;
1088         game_stopped = true;
1089         FOREACH_CLIENT(true,
1090         {
1091                 if (it.surv_attack_sprite)
1092                 {
1093                         WaypointSprite_Kill(it.surv_attack_sprite);
1094                 }
1095                 if (it.surv_savedplayerstate)
1096                 {
1097                         delete(it.surv_savedplayerstate);
1098                         it.surv_savedplayerstate = NULL;
1099                 }
1100         });
1101         if (surv_type == SURVIVAL_TYPE_VERSUS)
1102         {
1103                 Surv_SwitchRoundType();
1104                 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
1105                 return;
1106         }
1107         round_handler_Init(5, autocvar_g_surv_warmup,
1108                 autocvar_g_surv_round_timelimit);
1109 }
1110
1111 /// \brief Swaps attacker and defender teams.
1112 /// \return No return.
1113 void Surv_SwapTeams()
1114 {
1115         int temp = surv_attackerteam;
1116         surv_attackerteam = surv_defenderteam;
1117         surv_defenderteam = temp;
1118         temp = surv_attackerteambit;
1119         surv_attackerteambit = surv_defenderteambit;
1120         surv_defenderteambit = temp;
1121         temp = surv_numattackers;
1122         surv_numattackers = surv_numdefenders;
1123         surv_numdefenders = temp;
1124         temp = surv_numattackerhumans;
1125         surv_numattackerhumans = surv_numdefenderhumans;
1126         surv_numdefenderhumans = temp;
1127         FOREACH_CLIENT(true,
1128         {
1129                 if ((it.team == surv_defenderteam) && (it.surv_role ==
1130                         SURVIVAL_ROLE_CANNON_FODDER))
1131                 {
1132                         SetPlayerTeamSimple(it, surv_attackerteam);
1133                 }
1134         });
1135         FOREACH_CLIENT(IS_REAL_CLIENT(it),
1136         {
1137                 it.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1138         });
1139 }
1140
1141 /// \brief Forces the overkill model for specific player.
1142 /// \param[in,out] player Player to force the model of.
1143 /// \return No return.
1144 void Surv_ForceOverkillPlayerModel(entity player)
1145 {
1146         switch (player.team)
1147         {
1148                 case NUM_TEAM_1:
1149                 {
1150                         switch (floor(random() * 4))
1151                         {
1152                                 case 0:
1153                                 {
1154                                         player.surv_playermodel = "models/ok_player/okrobot1.dpm";
1155                                         return;
1156                                 }
1157                                 case 1:
1158                                 {
1159                                         player.surv_playermodel = "models/ok_player/okrobot2.dpm";
1160                                         return;
1161                                 }
1162                                 case 2:
1163                                 {
1164                                         player.surv_playermodel = "models/ok_player/okrobot3.dpm";
1165                                         return;
1166                                 }
1167                                 case 3:
1168                                 {
1169                                         player.surv_playermodel = "models/ok_player/okrobot4.dpm";
1170                                         return;
1171                                 }
1172                         }
1173                         return;
1174                 }
1175                 case NUM_TEAM_2:
1176                 {
1177                         switch (floor(random() * 4))
1178                         {
1179                                 case 0:
1180                                 {
1181                                         player.surv_playermodel = "models/ok_player/okmale1.dpm";
1182                                         return;
1183                                 }
1184                                 case 1:
1185                                 {
1186                                         player.surv_playermodel = "models/ok_player/okmale2.dpm";
1187                                         return;
1188                                 }
1189                                 case 2:
1190                                 {
1191                                         player.surv_playermodel = "models/ok_player/okmale3.dpm";
1192                                         return;
1193                                 }
1194                                 case 3:
1195                                 {
1196                                         player.surv_playermodel = "models/ok_player/okmale4.dpm";
1197                                         return;
1198                                 }
1199                         }
1200                         return;
1201                 }
1202         }
1203 }
1204
1205 /// \brief Determines the player model to the one configured for the gamemode.
1206 /// \param[in,out] player Player to determine the model of.
1207 /// \return No return.
1208 void Surv_DeterminePlayerModel(entity player)
1209 {
1210         switch (player.team)
1211         {
1212                 case surv_attackerteam:
1213                 {
1214                         switch (player.surv_role)
1215                         {
1216                                 case SURVIVAL_ROLE_PLAYER:
1217                                 {
1218                                         if (!autocvar_g_surv_attacker_force_overkill_models)
1219                                         {
1220                                                 player.surv_playermodel = player.surv_savedplayermodel;
1221                                                 return;
1222                                         }
1223                                         Surv_ForceOverkillPlayerModel(player);
1224                                         return;
1225                                 }
1226                                 case SURVIVAL_ROLE_CANNON_FODDER:
1227                                 {
1228                                         if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
1229                                         {
1230                                                 player.surv_playermodel = player.surv_savedplayermodel;
1231                                                 return;
1232                                         }
1233                                         Surv_ForceOverkillPlayerModel(player);
1234                                         return;
1235                                 }
1236                         }
1237                 }
1238                 case surv_defenderteam:
1239                 {
1240                         if (!autocvar_g_surv_defender_force_overkill_models)
1241                         {
1242                                 player.surv_playermodel = player.surv_savedplayermodel;
1243                                 return;
1244                         }
1245                         Surv_ForceOverkillPlayerModel(player);
1246                         return;
1247                 }
1248         }
1249 }
1250
1251 /// \brief Setups a waypoint sprite used to track defenders.
1252 /// \param[in] player Player to attach sprite too.
1253 /// \return No return.
1254 void Surv_SetupWaypointSprite(entity player)
1255 {
1256         WaypointSprite_Spawn(WP_AssaultDestroy, 0, 0, player, '0 0 64', NULL,
1257                 surv_attackerteam, player, surv_attack_sprite, false,
1258                 RADARICON_OBJECTIVE);
1259         if (autocvar_g_instagib == 1)
1260         {
1261                 WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite,
1262                         PlayerTemplate_GetFloatValue("surv_defender", "start_armor") + 1);
1263                 WaypointSprite_UpdateHealth(player.surv_attack_sprite,
1264                         player.armorvalue + 1);
1265                 return;
1266         }
1267         WaypointSprite_UpdateMaxHealth(player.surv_attack_sprite,
1268                 PlayerTemplate_GetFloatValue("surv_defender", "start_health") +
1269                 PlayerTemplate_GetFloatValue("surv_defender", "start_armor"));
1270         WaypointSprite_UpdateHealth(player.surv_attack_sprite, player.health +
1271                 player.armorvalue);
1272 }
1273
1274 //=============================== Callbacks ===================================
1275
1276 bool Surv_CanRoundStart()
1277 {
1278         return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
1279 }
1280
1281 bool Surv_CanRoundEnd()
1282 {
1283         if (warmup_stage)
1284         {
1285                 return false;
1286         }
1287         if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
1288                 time <= 0))
1289         {
1290                 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1291                 {
1292                         surv_timetobeat = time - surv_roundstarttime;
1293                         Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1294                                 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1295                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1296                                 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1297                         Surv_RoundCleanup();
1298                         return true;
1299                 }
1300                 surv_timetobeat = autocvar_g_surv_round_timelimit;
1301                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1302                         CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1303                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1304                         INFO_SURVIVAL_DEFENDERS_SURVIVED);
1305                 switch (surv_defenderteam)
1306                 {
1307                         case NUM_TEAM_1:
1308                         {
1309                                 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1310                                         ATTEN_NONE);
1311                                 break;
1312                         }
1313                         case NUM_TEAM_2:
1314                         {
1315                                 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1316                                         ATTEN_NONE);
1317                                 break;
1318                         }
1319                 }
1320                 TeamScore_AddToTeam(surv_defenderteam, 1, 1);
1321                 Surv_RoundCleanup();
1322                 return true;
1323         }
1324         if (surv_numdefendersalive > 0)
1325         {
1326                 return false;
1327         }
1328         if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1329         {
1330                 surv_timetobeat = time - surv_roundstarttime;
1331                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1332                         CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1333                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1334                         INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1335                 Surv_RoundCleanup();
1336                 return true;
1337         }
1338         surv_timetobeat = autocvar_g_surv_round_timelimit;
1339         Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1340                 CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
1341         Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1342                 INFO_SURVIVAL_DEFENDERS_ELIMINATED);
1343         switch (surv_attackerteam)
1344         {
1345                 case NUM_TEAM_1:
1346                 {
1347                         sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1348                                 ATTEN_NONE);
1349                         break;
1350                 }
1351                 case NUM_TEAM_2:
1352                 {
1353                         sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1354                                 ATTEN_NONE);
1355                         break;
1356                 }
1357         }
1358         TeamScore_AddToTeam(surv_attackerteam, 1, 1);
1359         Surv_RoundCleanup();
1360         return true;
1361 }
1362
1363 void Surv_RoundStart()
1364 {
1365         if (warmup_stage)
1366         {
1367                 surv_allowed_to_spawn = true;
1368                 return;
1369         }
1370         surv_isroundactive = true;
1371         surv_roundstarttime = time;
1372         surv_allowed_to_spawn = false;
1373         switch (surv_roundtype)
1374         {
1375                 case SURVIVAL_ROUND_FIRST:
1376                 {
1377                         FOREACH_CLIENT(IS_PLAYER(it),
1378                         {
1379                                 if (it.team == surv_attackerteam)
1380                                 {
1381                                         Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1382                                                 CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
1383                                         Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1384                                                 INFO_SURVIVAL_1ST_ROUND_ATTACKER);
1385                                         break;
1386                                 }
1387                         });
1388                         FOREACH_CLIENT(IS_PLAYER(it),
1389                         {
1390                                 if (it.team == surv_defenderteam)
1391                                 {
1392                                         if (surv_type == SURVIVAL_TYPE_COOP)
1393                                         {
1394                                                 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1395                                                         CENTER_SURVIVAL_COOP_DEFENDER);
1396                                                 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1397                                                         INFO_SURVIVAL_COOP_DEFENDER);
1398                                                 break;
1399                                         }
1400                                         Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1401                                                 CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
1402                                         Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1403                                                 INFO_SURVIVAL_1ST_ROUND_DEFENDER);
1404                                         break;
1405                                 }
1406                         });
1407                         break;
1408                 }
1409                 case SURVIVAL_ROUND_SECOND:
1410                 {
1411                         FOREACH_CLIENT(IS_PLAYER(it),
1412                         {
1413                                 if (it.team == surv_attackerteam)
1414                                 {
1415                                         Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1416                                                 CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1417                                         Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1418                                                 INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1419                                         break;
1420                                 }
1421                         });
1422                         FOREACH_CLIENT(IS_PLAYER(it),
1423                         {
1424                                 if (it.team == surv_defenderteam)
1425                                 {
1426                                         Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1427                                                 CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1428                                         Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1429                                                 INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1430                                         break;
1431                                 }
1432                         });
1433                         break;
1434                 }
1435         }
1436         if (autocvar_g_surv_stealth)
1437         {
1438                 return;
1439         }
1440         FOREACH_CLIENT(IS_PLAYER(it),
1441         {
1442                 switch (it.team)
1443                 {
1444                         case surv_defenderteam:
1445                         {
1446                                 if (it.surv_role == SURVIVAL_ROLE_PLAYER)
1447                                 {
1448                                         Surv_SetupWaypointSprite(it);
1449                                 }
1450                                 break;
1451                         }
1452                 }
1453         });
1454 }
1455
1456 bool Surv_IsEliminated(entity player)
1457 {
1458         switch (player.surv_state)
1459         {
1460                 case SURVIVAL_STATE_NOT_PLAYING:
1461                 {
1462                         return true;
1463                 }
1464                 case SURVIVAL_STATE_PLAYING:
1465                 {
1466                         if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
1467                         {
1468                                 // A hack until proper scoreboard is done.
1469                                 return true;
1470                         }
1471                         if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
1472                                 IS_OBSERVER(player)))
1473                         {
1474                                 return true;
1475                         }
1476                         return false;
1477                 }
1478         }
1479         // Should never reach here
1480         return true;
1481 }
1482
1483 //============================= Hooks ========================================
1484
1485 /// \brief Hook that is called to determine general rules of the game. 
1486 MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
1487 {
1488         surv_warmup = warmup_stage;
1489 }
1490
1491 /// \brief Hook that is called to determine if there is a weapon arena.
1492 MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
1493 {
1494         // Removing any weapon arena.
1495         M_ARGV(0, string) = "off";
1496 }
1497
1498 /// \brief Hook that is called to determine start items of all players.
1499 MUTATOR_HOOKFUNCTION(surv, SetStartItems)
1500 {
1501         if (autocvar_g_instagib == 1)
1502         {
1503                 return;
1504         }
1505         start_weapons = WEPSET(Null);
1506         warmup_start_weapons = WEPSET(Null);
1507 }
1508
1509 /// \brief Hook that is called on every frame.
1510 MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
1511 {
1512         if (game_stopped || !surv_isroundactive)
1513         {
1514                 return;
1515         }
1516         float roundtime = 0;
1517         switch (surv_roundtype)
1518         {
1519                 case SURVIVAL_ROUND_FIRST:
1520                 {
1521                         roundtime = time - surv_roundstarttime;
1522                         break;
1523                 }
1524                 case SURVIVAL_ROUND_SECOND:
1525                 {
1526                         roundtime = round_handler_GetEndTime() - time;
1527                         break;
1528                 }
1529         }
1530         FOREACH_CLIENT(IS_REAL_CLIENT(it),
1531         {
1532                 it.surv_round_time_stat = roundtime;
1533         });
1534 }
1535
1536 /// \brief Hook that determines which team player can join. This is called
1537 /// before ClientConnect.
1538 MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
1539 {
1540         entity player = M_ARGV(2, entity);
1541         LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
1542         if (player == NULL)
1543         {
1544                 return SURVIVAL_TEAM_BITS;
1545         }
1546         if (IS_BOT_CLIENT(player))
1547         {
1548                 int teambits = surv_attackerteambit;
1549                 if ((player.team == surv_defenderteam) || (surv_numdefenders <
1550                         autocvar_g_surv_team_size))
1551                 {
1552                         teambits |= surv_defenderteambit;
1553                 }
1554                 M_ARGV(0, float) = teambits;
1555                 return;
1556         }
1557         if (surv_type == SURVIVAL_TYPE_COOP)
1558         {
1559                 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1560                 {
1561                         M_ARGV(0, float) = surv_defenderteambit;
1562                         return;
1563                 }
1564                 M_ARGV(0, float) = 0;
1565                 return;
1566         }
1567         int teambits = 0;
1568         if (surv_numattackerhumans < autocvar_g_surv_team_size)
1569         {
1570                 LOG_TRACE("Player can join attackers");
1571                 teambits |= surv_attackerteambit;
1572         }
1573         if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1574         {
1575                 LOG_TRACE("Player can join defenders");
1576                 teambits |= surv_defenderteambit;
1577         }
1578         M_ARGV(0, float) = teambits;
1579         return;
1580 }
1581
1582 /// \brief Hook that override team counts.
1583 MUTATOR_HOOKFUNCTION(surv, GetTeamCounts, CBC_ORDER_EXCLUSIVE)
1584 {
1585         return true;
1586 }
1587
1588 /// \brief Hook that sets the team count.
1589 MUTATOR_HOOKFUNCTION(surv, GetTeamCount, CBC_ORDER_EXCLUSIVE)
1590 {
1591         float teamnum = M_ARGV(0, float);
1592         entity ignore = M_ARGV(1, entity);
1593         switch (teamnum)
1594         {
1595                 case surv_attackerteam:
1596                 {
1597                         M_ARGV(2, float) = surv_numattackers;
1598                         M_ARGV(3, float) = surv_numattackers - surv_numattackerhumans;
1599                         if (ignore.team == surv_attackerteam)
1600                         {
1601                                 --M_ARGV(2, float);
1602                                 if (IS_BOT_CLIENT(ignore))
1603                                 {
1604                                         --M_ARGV(3, float);
1605                                 }
1606                         }
1607                         entity lowestplayer = NULL;
1608                         float lowestplayerscore = FLOAT_MAX;
1609                         entity lowestbot = NULL;
1610                         float lowestbotscore = FLOAT_MAX;
1611                         FOREACH_CLIENT((it.team == surv_attackerteam) && (it.surv_role ==
1612                                 SURVIVAL_ROLE_PLAYER),
1613                         {
1614                                 if (it == ignore)
1615                                 {
1616                                         continue;
1617                                 }
1618                                 if (IS_BOT_CLIENT(it))
1619                                 {
1620                                         float tempscore = PlayerScore_Get(it, SP_SCORE);
1621                                         if (tempscore < lowestbotscore)
1622                                         {
1623                                                 lowestbot = it;
1624                                                 lowestbotscore = tempscore;
1625                                                 continue;
1626                                         }
1627                                 }
1628                                 float tempscore = PlayerScore_Get(it, SP_SCORE);
1629                                 if (tempscore < lowestplayerscore)
1630                                 {
1631                                         lowestplayer = it;
1632                                         lowestplayerscore = tempscore;
1633                                 }
1634                         });
1635                         M_ARGV(4, entity) = lowestplayer;
1636                         M_ARGV(5, entity) = lowestbot;
1637                         break;
1638                 }
1639                 case surv_defenderteam:
1640                 {
1641                         M_ARGV(2, float) = surv_numdefenders;
1642                         M_ARGV(3, float) = surv_numdefenders - surv_numdefenderhumans;
1643                         if (ignore.team == surv_defenderteam)
1644                         {
1645                                 --M_ARGV(2, float);
1646                                 if (IS_BOT_CLIENT(ignore))
1647                                 {
1648                                         --M_ARGV(3, float);
1649                                 }
1650                         }
1651                         entity lowestplayer = NULL;
1652                         float lowestplayerscore = FLOAT_MAX;
1653                         entity lowestbot = NULL;
1654                         float lowestbotscore = FLOAT_MAX;
1655                         FOREACH_CLIENT((it.team == surv_defenderteam),
1656                         {
1657                                 if (it == ignore)
1658                                 {
1659                                         continue;
1660                                 }
1661                                 if (IS_BOT_CLIENT(it))
1662                                 {
1663                                         float tempscore = PlayerScore_Get(it, SP_SCORE);
1664                                         if (tempscore < lowestbotscore)
1665                                         {
1666                                                 lowestbot = it;
1667                                                 lowestbotscore = tempscore;
1668                                                 continue;
1669                                         }
1670                                 }
1671                                 float tempscore = PlayerScore_Get(it, SP_SCORE);
1672                                 if (tempscore < lowestplayerscore)
1673                                 {
1674                                         lowestplayer = it;
1675                                         lowestplayerscore = tempscore;
1676                                 }
1677                         });
1678                         M_ARGV(4, entity) = lowestplayer;
1679                         M_ARGV(5, entity) = lowestbot;
1680                         break;
1681                 }
1682         }
1683         return true;
1684 }
1685
1686 /// \brief Hook that determines the best teams for the player to join.
1687 MUTATOR_HOOKFUNCTION(surv, FindBestTeams, CBC_ORDER_EXCLUSIVE)
1688 {
1689         if (surv_type == SURVIVAL_TYPE_COOP)
1690         {
1691                 return false;
1692         }
1693         entity player = M_ARGV(0, entity);
1694         if (IS_BOT_CLIENT(player))
1695         {
1696                 return false;
1697         }
1698         int numattackerhumans = surv_numattackerhumans;
1699         int numdefenderhumans = surv_numdefenderhumans;
1700         if (player.team == surv_attackerteam)
1701         {
1702                 --numattackerhumans;
1703         }
1704         else if (player.team == surv_defenderteam)
1705         {
1706                 --numdefenderhumans;
1707         }
1708         if (numattackerhumans < numdefenderhumans)
1709         {
1710                 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_attackerteam) - 1);
1711                 return true;
1712         }
1713         if (numattackerhumans > numdefenderhumans)
1714         {
1715                 M_ARGV(1, float) = BIT(Team_TeamToNumber(surv_defenderteam) - 1);
1716                 return true;
1717         }
1718         M_ARGV(1, float) = SURVIVAL_TEAM_BITS;
1719         return true;
1720 }
1721
1722 /// \brief Hook that is called when player has changed the team.
1723 MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
1724 {
1725         entity player = M_ARGV(0, entity);
1726         int oldteam = M_ARGV(1, float);
1727         int newteam = M_ARGV(2, float);
1728         string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
1729                 ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
1730         LOG_TRACE(message);
1731         DebugPrintToChatAll(message);
1732         if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1733         {
1734                 Surv_RemovePlayerFromAliveList(player, oldteam);
1735         }
1736         Surv_RemovePlayerFromTeam(player, oldteam);
1737         if (Surv_AddPlayerToTeam(player, newteam) == false)
1738         {
1739                 return;
1740         }
1741         //Surv_CountAlivePlayers();
1742         if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1743         {
1744                 Surv_AddPlayerToAliveList(player, newteam);
1745         }
1746 }
1747
1748 /// \brief Hook that is called when player connects to the server.
1749 MUTATOR_HOOKFUNCTION(surv, ClientConnect)
1750 {
1751         entity player = M_ARGV(0, entity);
1752         LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
1753         player.surv_savedplayermodel = player.playermodel;
1754         if (IS_REAL_CLIENT(player))
1755         {
1756                 player.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1757                 player.surv_defenders_alive_stat = surv_numdefendersalive;
1758                 player.redalive_stat = redalive;
1759                 player.bluealive_stat = bluealive;
1760                 player.yellowalive_stat = yellowalive;
1761                 player.pinkalive_stat = pinkalive;
1762         }
1763         if (player.surv_role == SURVIVAL_ROLE_NONE)
1764         {
1765                 Surv_AddPlayerToTeam(player, player.team);
1766         }
1767         return true;
1768 }
1769
1770 /// \brief Hook that is called when player disconnects from the server.
1771 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
1772 {
1773     entity player = M_ARGV(0, entity);
1774         if (!IS_DEAD(player))
1775         {
1776                 Surv_RemovePlayerFromAliveList(player, player.team);
1777         }
1778         Surv_RemovePlayerFromTeam(player, player.team);
1779         //Surv_CountAlivePlayers();
1780 }
1781
1782 /// \brief Hook that determines whether player can spawn. It is not called for
1783 /// players who have joined the team and are dead.
1784 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
1785 {
1786         entity player = M_ARGV(0, entity);
1787         LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
1788         if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1789         {
1790                 return false;
1791         }
1792         return !Surv_CanPlayerSpawn(player);
1793 }
1794
1795 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
1796 {
1797         entity player = M_ARGV(0, entity);
1798         LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
1799         if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
1800         {
1801                 LOG_TRACE("Transmuting to observer");
1802                 TRANSMUTE(Observer, player);
1803         }
1804 }
1805
1806 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
1807 {
1808         entity player = M_ARGV(0, entity);
1809         LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
1810         if (player.killindicator_teamchange == -2) // player wants to spectate
1811         {
1812                 LOG_TRACE("killindicator_teamchange == -2");
1813                 player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
1814         }
1815         if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1816         {
1817                 return false;  // allow team reset
1818         }
1819         return true;  // prevent team reset
1820 }
1821
1822 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
1823 {
1824         LOG_TRACE("Survival: reset_map_global");
1825         surv_allowed_to_spawn = true;
1826         if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1827         {
1828                 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1829                 {
1830                         it.surv_round_time_stat = 0;
1831                 });
1832         }
1833         return true;
1834 }
1835
1836 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
1837 {
1838         LOG_TRACE("Survival: reset_map_players");
1839         surv_numattackersalive = 0;
1840         surv_numdefendersalive = 0;
1841         if (surv_warmup)
1842         {
1843                 surv_warmup = false;
1844         }
1845         else if (surv_type == SURVIVAL_TYPE_VERSUS)
1846         {
1847                 Surv_SwapTeams();
1848         }
1849         FOREACH_CLIENT(true,
1850         {
1851                 it.killcount = 0;
1852                 if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
1853                 {
1854                         it.team = -1;
1855                         it.surv_state = SURVIVAL_STATE_PLAYING;
1856                 }
1857                 if (it.surv_state == SURVIVAL_STATE_PLAYING)
1858                 {
1859                         TRANSMUTE(Player, it);
1860                         it.surv_state = SURVIVAL_STATE_PLAYING;
1861                         PutClientInServer(it);
1862                 }
1863         });
1864         bot_relinkplayerlist();
1865         return true;
1866 }
1867
1868 /// \brief Hook that is called when player spawns.
1869 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
1870 {
1871         entity player = M_ARGV(0, entity);
1872         LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
1873         player.surv_state = SURVIVAL_STATE_PLAYING;
1874         Surv_DeterminePlayerModel(player);
1875         if (player.surv_savedplayerstate != NULL)
1876         {
1877                 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1878                 delete(player.surv_savedplayerstate);
1879                 player.surv_savedplayerstate = NULL;
1880         }
1881         else
1882         {
1883                 PlayerTemplate_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
1884         }
1885         switch (player.team)
1886         {
1887                 case surv_attackerteam:
1888                 {
1889                         if (player.surv_role == SURVIVAL_ROLE_PLAYER)
1890                         {
1891                                 Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1892                                         CENTER_ASSAULT_ATTACKING);
1893                         }
1894                         break;
1895                 }
1896                 case surv_defenderteam:
1897                 {
1898                         Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1899                                 CENTER_ASSAULT_DEFENDING);
1900                         break;
1901                 }
1902         }
1903         //Surv_CountAlivePlayers();
1904         Surv_AddPlayerToAliveList(player, player.team);
1905 }
1906
1907 /// \brief UGLY HACK. This is called every frame to keep player model correct.
1908 MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
1909 {
1910         entity player = M_ARGV(2, entity);
1911         M_ARGV(0, string) = player.surv_playermodel;
1912 }
1913
1914 /// \brief Hook that is called every frame to determine how player health should
1915 /// regenerate.
1916 MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
1917 {
1918         entity player = M_ARGV(0, entity);
1919         if (player.team == surv_defenderteam)
1920         {
1921                 return true;
1922         }
1923         return PlayerTemplate_PlayerRegen(player, Surv_GetPlayerTemplate(player));
1924 }
1925
1926 /// \brief Hook that is called to determine if balance messages will appear.
1927 MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
1928 {
1929         return true;
1930 }
1931
1932 /// \brief Hook that is called when player touches an item.
1933 MUTATOR_HOOKFUNCTION(surv, ItemTouch)
1934 {
1935         entity item = M_ARGV(0, entity);
1936         entity player = M_ARGV(1, entity);
1937         switch (player.team)
1938         {
1939                 case surv_attackerteam:
1940                 {
1941                         return PlayerTemplate_ItemTouch(player, item,
1942                                 Surv_GetPlayerTemplate(player));
1943                 }
1944                 case surv_defenderteam:
1945                 {
1946                         switch (item.classname)
1947                         {
1948                                 case "item_strength":
1949                                 {
1950                                         W_GiveWeapon(player, WEP_HMG.m_id);
1951                                         player.superweapons_finished = max(
1952                                                 player.superweapons_finished, time) +
1953                                                 autocvar_g_balance_superweapons_time;
1954                                         Item_ScheduleRespawn(item);
1955                                         sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
1956                                                 ATTEN_NORM);
1957                                         return MUT_ITEMTOUCH_RETURN;
1958                                 }
1959                                 case "item_invincible":
1960                                 {
1961                                         W_GiveWeapon(player, WEP_RPC.m_id);
1962                                         player.superweapons_finished = max(
1963                                                 player.superweapons_finished, time) +
1964                                                 autocvar_g_balance_superweapons_time;
1965                                         Item_ScheduleRespawn(item);
1966                                         sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
1967                                         return MUT_ITEMTOUCH_RETURN;
1968                                 }
1969                                 default:
1970                                 {
1971                                         return PlayerTemplate_ItemTouch(player, item,
1972                                                 Surv_GetPlayerTemplate(player));
1973                                 }
1974                         }
1975                         DebugPrintToChat(player, item.classname);
1976                         return MUT_ITEMTOUCH_RETURN;
1977                 }
1978         }
1979         return MUT_ITEMTOUCH_CONTINUE;
1980 }
1981
1982 /// \brief Hook which is called when the player tries to throw their weapon.
1983 MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
1984 {
1985         entity player = M_ARGV(0, entity);
1986         if (player.team == surv_defenderteam)
1987         {
1988                 return true;
1989         }
1990 }
1991
1992 /// \brief Hook which is called when the damage amount must be determined.
1993 MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
1994 {
1995         entity frag_attacker = M_ARGV(1, entity);
1996         entity frag_target = M_ARGV(2, entity);
1997         float deathtype = M_ARGV(3, float);
1998         float damage = M_ARGV(4, float);
1999         M_ARGV(4, float) = PlayerTemplate_Damage_Calculate(frag_attacker,
2000                 Surv_GetPlayerTemplate(frag_attacker), frag_target,
2001                 Surv_GetPlayerTemplate(frag_target), deathtype, damage);
2002 }
2003
2004 /// \brief Hook which is called when the player was damaged.
2005 MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
2006 {
2007         entity target = M_ARGV(1, entity);
2008         if (target.team != surv_defenderteam)
2009         {
2010                 return;
2011         }
2012         Surv_UpdateDefenderHealthStat();
2013         entity attacker = M_ARGV(0, entity);
2014         if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
2015                 SURVIVAL_ROLE_PLAYER))
2016         {
2017                 float health = M_ARGV(2, float);
2018                 float armor = M_ARGV(3, float);
2019                 float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
2020                 PlayerScore_Add(attacker, SP_SCORE, score);
2021         }
2022         if (autocvar_g_surv_stealth)
2023         {
2024                 return;
2025         }
2026         if (target.health < 1)
2027         {
2028                 WaypointSprite_Kill(target.surv_attack_sprite);
2029         }
2030         else
2031         {
2032                 if (autocvar_g_instagib == 1)
2033                 {
2034                         WaypointSprite_UpdateHealth(target.surv_attack_sprite,
2035                                 target.armorvalue + 1);
2036                 }
2037                 else
2038                 {
2039                         WaypointSprite_UpdateHealth(target.surv_attack_sprite,
2040                                 target.health + target.armorvalue);
2041                 }
2042         }
2043 }
2044
2045 /// \brief Hook which is called when the player dies.
2046 MUTATOR_HOOKFUNCTION(surv, PlayerDies)
2047 {
2048         //DebugPrintToChatAll("PlayerDies");
2049         entity attacker = M_ARGV(1, entity);
2050         entity victim = M_ARGV(2, entity);
2051         if ((attacker.team == surv_defenderteam) &&
2052                 (victim.team == surv_attackerteam))
2053         {
2054                 switch (victim.surv_role)
2055                 {
2056                         case SURVIVAL_ROLE_PLAYER:
2057                         {
2058                                 GivePlayerHealth(attacker,
2059                                         autocvar_g_surv_defender_attacker_frag_health);
2060                                 GivePlayerArmor(attacker,
2061                                         autocvar_g_surv_defender_attacker_frag_armor);
2062                                 GivePlayerAmmo(attacker, ammo_shells,
2063                                         autocvar_g_surv_defender_attacker_frag_shells);
2064                                 GivePlayerAmmo(attacker, ammo_nails,
2065                                         autocvar_g_surv_defender_attacker_frag_bullets);
2066                                 GivePlayerAmmo(attacker, ammo_rockets,
2067                                         autocvar_g_surv_defender_attacker_frag_rockets);
2068                                 GivePlayerAmmo(attacker, ammo_cells,
2069                                         autocvar_g_surv_defender_attacker_frag_cells);
2070                                 break;
2071                         }
2072                         case SURVIVAL_ROLE_CANNON_FODDER:
2073                         {
2074                                 GivePlayerHealth(attacker,
2075                                         autocvar_g_surv_defender_cannon_fodder_frag_health);
2076                                 GivePlayerArmor(attacker,
2077                                         autocvar_g_surv_defender_cannon_fodder_frag_armor);
2078                                 GivePlayerAmmo(attacker, ammo_shells,
2079                                         autocvar_g_surv_defender_cannon_fodder_frag_shells);
2080                                 GivePlayerAmmo(attacker, ammo_nails,
2081                                         autocvar_g_surv_defender_cannon_fodder_frag_bullets);
2082                                 GivePlayerAmmo(attacker, ammo_rockets,
2083                                         autocvar_g_surv_defender_cannon_fodder_frag_rockets);
2084                                 GivePlayerAmmo(attacker, ammo_cells,
2085                                         autocvar_g_surv_defender_cannon_fodder_frag_cells);
2086                                 break;
2087                         }
2088                 }
2089         }
2090         if ((victim.team == surv_defenderteam) &&
2091                 (autocvar_g_surv_defender_drop_weapons == false))
2092         {
2093                 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2094                 {
2095                         .entity went = weaponentities[slot];
2096                         victim.(went).m_weapon = WEP_Null;
2097                 }
2098         }
2099         if (!Surv_CanPlayerSpawn(victim))
2100         {
2101                 victim.respawn_flags = RESPAWN_SILENT;
2102                 if (IS_BOT_CLIENT(victim))
2103                 {
2104                         bot_clear(victim);
2105                 }
2106         }
2107         return true;
2108 }
2109
2110 /// \brief Hook which is called after the player died.
2111 MUTATOR_HOOKFUNCTION(surv, PlayerDied)
2112 {
2113         //DebugPrintToChatAll("PlayerDied");
2114         entity player = M_ARGV(0, entity);
2115         Surv_RemovePlayerFromAliveList(player, player.team);
2116         //Surv_CountAlivePlayers();
2117 }
2118
2119 /// \brief Hook which is called when player has scored a frag.
2120 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
2121 {
2122         if (surv_type == SURVIVAL_TYPE_COOP)
2123         {
2124                 return true;
2125         }
2126         entity attacker = M_ARGV(0, entity);
2127         if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
2128                 SURVIVAL_ROLE_CANNON_FODDER))
2129         {
2130                 M_ARGV(2, float) = 0;
2131                 return true;
2132         }
2133         entity target = M_ARGV(1, entity);
2134         if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
2135                 surv_defenderteam))
2136         {
2137                 M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
2138         }
2139         return true;
2140 }
2141
2142 MUTATOR_HOOKFUNCTION(surv, SpectateSet)
2143 {
2144         entity client = M_ARGV(0, entity);
2145         entity targ = M_ARGV(1, entity);
2146
2147         if (!autocvar_g_surv_spectate_enemies &&
2148                 (client.surv_state == SURVIVAL_STATE_PLAYING) &&
2149                 DIFF_TEAM(targ, client))
2150         {
2151                 return true;
2152         }
2153 }
2154
2155 MUTATOR_HOOKFUNCTION(surv, SpectateNext)
2156 {
2157         entity client = M_ARGV(0, entity);
2158
2159         if (!autocvar_g_surv_spectate_enemies &&
2160                 (client.surv_state == SURVIVAL_STATE_PLAYING))
2161         {
2162                 entity targ = M_ARGV(1, entity);
2163                 M_ARGV(1, entity) = CA_SpectateNext(client, targ);
2164                 return true;
2165         }
2166 }
2167
2168 MUTATOR_HOOKFUNCTION(surv, SpectatePrev)
2169 {
2170         entity client = M_ARGV(0, entity);
2171         entity targ = M_ARGV(1, entity);
2172         entity first = M_ARGV(2, entity);
2173
2174         if (!autocvar_g_surv_spectate_enemies &&
2175                 (client.surv_state == SURVIVAL_STATE_PLAYING))
2176         {
2177                 do
2178                 {
2179                         targ = targ.chain;
2180                 }
2181                 while (targ && DIFF_TEAM(targ, client));
2182                 if (!targ)
2183                 {
2184                         for (targ = first; targ && DIFF_TEAM(targ, client);
2185                                 targ = targ.chain);
2186
2187                         if (targ == client.enemy)
2188                         {
2189                                 return MUT_SPECPREV_RETURN;
2190                         }
2191                 }
2192         }
2193         M_ARGV(1, entity) = targ;
2194         return MUT_SPECPREV_FOUND;
2195 }
2196
2197 /// \brief I'm not sure exactly what this function does but it is very
2198 /// important. Without it bots are completely broken. Is it a hack? Of course.
2199 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
2200 {
2201         FOREACH_CLIENT(IS_REAL_CLIENT(it),
2202         {
2203                 if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
2204                 {
2205                         ++M_ARGV(0, int);
2206                 }
2207                 ++M_ARGV(1, int);
2208         });
2209         return true;
2210 }
2211
2212 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
2213 {
2214         // Don't announce remaining frags
2215         return false;
2216 }