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