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