]> 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_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         });
966         if (surv_type == SURVIVAL_TYPE_VERSUS)
967         {
968                 Surv_SwitchRoundType();
969                 round_handler_Init(5, autocvar_g_surv_warmup, surv_timetobeat);
970                 return;
971         }
972         round_handler_Init(5, autocvar_g_surv_warmup,
973                 autocvar_g_surv_round_timelimit);
974 }
975
976 /// \brief Swaps attacker and defender teams.
977 /// \return No return.
978 void Surv_SwapTeams()
979 {
980         int temp = surv_attackerteam;
981         surv_attackerteam = surv_defenderteam;
982         surv_defenderteam = temp;
983         temp = surv_attackerteambit;
984         surv_attackerteambit = surv_defenderteambit;
985         surv_defenderteambit = temp;
986         temp = surv_numattackers;
987         surv_numattackers = surv_numdefenders;
988         surv_numdefenders = temp;
989         temp = surv_numattackerhumans;
990         surv_numattackerhumans = surv_numdefenderhumans;
991         surv_numdefenderhumans = temp;
992         FOREACH_CLIENT(true,
993         {
994                 if ((it.team == surv_defenderteam) && (it.surv_role ==
995                         SURVIVAL_ROLE_CANNON_FODDER))
996                 {
997                         SetPlayerTeamSimple(it, surv_attackerteam);
998                 }
999         });
1000         FOREACH_CLIENT(IS_REAL_CLIENT(it),
1001         {
1002                 it.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1003         });
1004 }
1005
1006 /// \brief Forces the overkill model for specific player.
1007 /// \param[in,out] player Player to force the model of.
1008 /// \return No return.
1009 void Surv_ForceOverkillPlayerModel(entity player)
1010 {
1011         switch (player.team)
1012         {
1013                 case NUM_TEAM_1:
1014                 {
1015                         switch (floor(random() * 4))
1016                         {
1017                                 case 0:
1018                                 {
1019                                         player.surv_playermodel = "models/ok_player/okrobot1.dpm";
1020                                         return;
1021                                 }
1022                                 case 1:
1023                                 {
1024                                         player.surv_playermodel = "models/ok_player/okrobot2.dpm";
1025                                         return;
1026                                 }
1027                                 case 2:
1028                                 {
1029                                         player.surv_playermodel = "models/ok_player/okrobot3.dpm";
1030                                         return;
1031                                 }
1032                                 case 3:
1033                                 {
1034                                         player.surv_playermodel = "models/ok_player/okrobot4.dpm";
1035                                         return;
1036                                 }
1037                         }
1038                         return;
1039                 }
1040                 case NUM_TEAM_2:
1041                 {
1042                         switch (floor(random() * 4))
1043                         {
1044                                 case 0:
1045                                 {
1046                                         player.surv_playermodel = "models/ok_player/okmale1.dpm";
1047                                         return;
1048                                 }
1049                                 case 1:
1050                                 {
1051                                         player.surv_playermodel = "models/ok_player/okmale2.dpm";
1052                                         return;
1053                                 }
1054                                 case 2:
1055                                 {
1056                                         player.surv_playermodel = "models/ok_player/okmale3.dpm";
1057                                         return;
1058                                 }
1059                                 case 3:
1060                                 {
1061                                         player.surv_playermodel = "models/ok_player/okmale4.dpm";
1062                                         return;
1063                                 }
1064                         }
1065                         return;
1066                 }
1067         }
1068 }
1069
1070 /// \brief Determines the player model to the one configured for the gamemode.
1071 /// \param[in,out] player Player to determine the model of.
1072 /// \return No return.
1073 void Surv_DeterminePlayerModel(entity player)
1074 {
1075         switch (player.team)
1076         {
1077                 case surv_attackerteam:
1078                 {
1079                         switch (player.surv_role)
1080                         {
1081                                 case SURVIVAL_ROLE_PLAYER:
1082                                 {
1083                                         if (!autocvar_g_surv_attacker_force_overkill_models)
1084                                         {
1085                                                 player.surv_playermodel = player.surv_savedplayermodel;
1086                                                 return;
1087                                         }
1088                                         Surv_ForceOverkillPlayerModel(player);
1089                                         return;
1090                                 }
1091                                 case SURVIVAL_ROLE_CANNON_FODDER:
1092                                 {
1093                                         if (!autocvar_g_surv_cannon_fodder_force_overkill_models)
1094                                         {
1095                                                 player.surv_playermodel = player.surv_savedplayermodel;
1096                                                 return;
1097                                         }
1098                                         Surv_ForceOverkillPlayerModel(player);
1099                                         return;
1100                                 }
1101                         }
1102                 }
1103                 case surv_defenderteam:
1104                 {
1105                         if (!autocvar_g_surv_defender_force_overkill_models)
1106                         {
1107                                 player.surv_playermodel = player.surv_savedplayermodel;
1108                                 return;
1109                         }
1110                         Surv_ForceOverkillPlayerModel(player);
1111                         return;
1112                 }
1113         }
1114 }
1115
1116 //=============================== Callbacks ===================================
1117
1118 bool Surv_CanRoundStart()
1119 {
1120         return (surv_numattackersalive > 0) && (surv_numdefendersalive > 0);
1121 }
1122
1123 bool Surv_CanRoundEnd()
1124 {
1125         if (warmup_stage)
1126         {
1127                 return false;
1128         }
1129         if((round_handler_GetEndTime() > 0) && (round_handler_GetEndTime() -
1130                 time <= 0))
1131         {
1132                 if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1133                 {
1134                         surv_timetobeat = time - surv_roundstarttime;
1135                         Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1136                                 CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1137                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1138                                 INFO_SURVIVAL_DEFENDERS_SURVIVED);
1139                         Surv_RoundCleanup();
1140                         return true;
1141                 }
1142                 surv_timetobeat = autocvar_g_surv_round_timelimit;
1143                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1144                         CENTER_SURVIVAL_DEFENDERS_SURVIVED);
1145                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1146                         INFO_SURVIVAL_DEFENDERS_SURVIVED);
1147                 switch (surv_defenderteam)
1148                 {
1149                         case NUM_TEAM_1:
1150                         {
1151                                 sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1152                                         ATTEN_NONE);
1153                                 break;
1154                         }
1155                         case NUM_TEAM_2:
1156                         {
1157                                 sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1158                                         ATTEN_NONE);
1159                                 break;
1160                         }
1161                 }
1162                 TeamScore_AddToTeam(surv_defenderteam, 1, 1);
1163                 Surv_RoundCleanup();
1164                 return true;
1165         }
1166         if (surv_numdefendersalive > 0)
1167         {
1168                 return false;
1169         }
1170         if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1171         {
1172                 surv_timetobeat = time - surv_roundstarttime;
1173                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1174                         CENTER_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1175                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1176                         INFO_SURVIVAL_DEFENDERS_ELIMINATED_IN, surv_timetobeat);
1177                 Surv_RoundCleanup();
1178                 return true;
1179         }
1180         surv_timetobeat = autocvar_g_surv_round_timelimit;
1181         Send_Notification(NOTIF_ALL, NULL, MSG_CENTER,
1182                 CENTER_SURVIVAL_DEFENDERS_ELIMINATED);
1183         Send_Notification(NOTIF_ALL, NULL, MSG_INFO,
1184                 INFO_SURVIVAL_DEFENDERS_ELIMINATED);
1185         switch (surv_attackerteam)
1186         {
1187                 case NUM_TEAM_1:
1188                 {
1189                         sound(NULL, CH_TRIGGER, SND_SURV_RED_SCORES, VOL_BASE,
1190                                 ATTEN_NONE);
1191                         break;
1192                 }
1193                 case NUM_TEAM_2:
1194                 {
1195                         sound(NULL, CH_TRIGGER, SND_SURV_BLUE_SCORES, VOL_BASE,
1196                                 ATTEN_NONE);
1197                         break;
1198                 }
1199         }
1200         TeamScore_AddToTeam(surv_attackerteam, 1, 1);
1201         Surv_RoundCleanup();
1202         return true;
1203 }
1204
1205 void Surv_RoundStart()
1206 {
1207         if (warmup_stage)
1208         {
1209                 surv_allowed_to_spawn = true;
1210                 return;
1211         }
1212         surv_isroundactive = true;
1213         surv_roundstarttime = time;
1214         surv_allowed_to_spawn = false;
1215         switch (surv_roundtype)
1216         {
1217                 case SURVIVAL_ROUND_FIRST:
1218                 {
1219                         FOREACH_CLIENT(IS_PLAYER(it),
1220                         {
1221                                 if (it.team == surv_attackerteam)
1222                                 {
1223                                         Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1224                                                 CENTER_SURVIVAL_1ST_ROUND_ATTACKER);
1225                                         Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1226                                                 INFO_SURVIVAL_1ST_ROUND_ATTACKER);
1227                                         break;
1228                                 }
1229                         });
1230                         FOREACH_CLIENT(IS_PLAYER(it),
1231                         {
1232                                 if (it.team == surv_defenderteam)
1233                                 {
1234                                         if (surv_type == SURVIVAL_TYPE_COOP)
1235                                         {
1236                                                 Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1237                                                         CENTER_SURVIVAL_COOP_DEFENDER);
1238                                                 Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1239                                                         INFO_SURVIVAL_COOP_DEFENDER);
1240                                                 break;
1241                                         }
1242                                         Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1243                                                 CENTER_SURVIVAL_1ST_ROUND_DEFENDER);
1244                                         Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1245                                                 INFO_SURVIVAL_1ST_ROUND_DEFENDER);
1246                                         break;
1247                                 }
1248                         });
1249                         break;
1250                 }
1251                 case SURVIVAL_ROUND_SECOND:
1252                 {
1253                         FOREACH_CLIENT(IS_PLAYER(it),
1254                         {
1255                                 if (it.team == surv_attackerteam)
1256                                 {
1257                                         Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1258                                                 CENTER_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1259                                         Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1260                                                 INFO_SURVIVAL_2ND_ROUND_ATTACKER, surv_timetobeat);
1261                                         break;
1262                                 }
1263                         });
1264                         FOREACH_CLIENT(IS_PLAYER(it),
1265                         {
1266                                 if (it.team == surv_defenderteam)
1267                                 {
1268                                         Send_Notification(NOTIF_TEAM, it, MSG_CENTER,
1269                                                 CENTER_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1270                                         Send_Notification(NOTIF_TEAM, it, MSG_INFO,
1271                                                 INFO_SURVIVAL_2ND_ROUND_DEFENDER, surv_timetobeat);
1272                                         break;
1273                                 }
1274                         });
1275                         break;
1276                 }
1277         }
1278         if (autocvar_g_surv_stealth)
1279         {
1280                 return;
1281         }
1282         FOREACH_CLIENT(IS_PLAYER(it),
1283         {
1284                 switch (it.team)
1285                 {
1286                         case surv_defenderteam:
1287                         {
1288                                 if (it.surv_role == SURVIVAL_ROLE_PLAYER)
1289                                 {
1290                                         WaypointSprite_Spawn(WP_AssaultDestroy, 0, 0, it, '0 0 64',
1291                                                 NULL, surv_attackerteam, it, surv_attack_sprite, false,
1292                                                 RADARICON_OBJECTIVE);
1293                                         if (autocvar_g_instagib == 1)
1294                                         {
1295                                                 WaypointSprite_UpdateMaxHealth(it.surv_attack_sprite,
1296                                                         PlayerTemplate_GetFloatValue("surv_defender",
1297                                                         "start_armor") + 1);
1298                                                 WaypointSprite_UpdateHealth(it.surv_attack_sprite,
1299                                                         it.armorvalue + 1);
1300                                         }
1301                                         else
1302                                         {
1303                                                 WaypointSprite_UpdateMaxHealth(it.surv_attack_sprite,
1304                                                         PlayerTemplate_GetFloatValue("surv_defender",
1305                                                         "start_health") + PlayerTemplate_GetFloatValue(
1306                                                         "surv_defender", "start_armor"));
1307                                                 WaypointSprite_UpdateHealth(it.surv_attack_sprite,
1308                                                         it.health + it.armorvalue);
1309                                         }
1310                                 }
1311                                 break;
1312                         }
1313                 }
1314         });
1315 }
1316
1317 bool Surv_IsEliminated(entity player)
1318 {
1319         switch (player.surv_state)
1320         {
1321                 case SURVIVAL_STATE_NOT_PLAYING:
1322                 {
1323                         return true;
1324                 }
1325                 case SURVIVAL_STATE_PLAYING:
1326                 {
1327                         if (player.surv_role == SURVIVAL_ROLE_CANNON_FODDER)
1328                         {
1329                                 // A hack until proper scoreboard is done.
1330                                 return true;
1331                         }
1332                         if ((player.team == surv_defenderteam) && (IS_DEAD(player) ||
1333                                 IS_OBSERVER(player)))
1334                         {
1335                                 return true;
1336                         }
1337                         return false;
1338                 }
1339         }
1340         // Should never reach here
1341         return true;
1342 }
1343
1344 //============================= Hooks ========================================
1345
1346 /// \brief Hook that is called to determine general rules of the game. 
1347 MUTATOR_HOOKFUNCTION(surv, ReadLevelCvars)
1348 {
1349         surv_warmup = warmup_stage;
1350 }
1351
1352 /// \brief Hook that is called to determine if there is a weapon arena.
1353 MUTATOR_HOOKFUNCTION(surv, SetWeaponArena)
1354 {
1355         // Removing any weapon arena.
1356         M_ARGV(0, string) = "off";
1357 }
1358
1359 /// \brief Hook that is called to determine start items of all players.
1360 MUTATOR_HOOKFUNCTION(surv, SetStartItems)
1361 {
1362         if (autocvar_g_instagib == 1)
1363         {
1364                 return;
1365         }
1366         start_weapons = WEPSET(Null);
1367         warmup_start_weapons = WEPSET(Null);
1368 }
1369
1370 MUTATOR_HOOKFUNCTION(surv, SV_StartFrame)
1371 {
1372         if (game_stopped || !surv_isroundactive)
1373         {
1374                 return;
1375         }
1376         float roundtime = 0;
1377         switch (surv_roundtype)
1378         {
1379                 case SURVIVAL_ROUND_FIRST:
1380                 {
1381                         roundtime = time - surv_roundstarttime;
1382                         break;
1383                 }
1384                 case SURVIVAL_ROUND_SECOND:
1385                 {
1386                         roundtime = round_handler_GetEndTime() - time;
1387                         break;
1388                 }
1389         }
1390         FOREACH_CLIENT(IS_REAL_CLIENT(it),
1391         {
1392                 it.surv_round_time_stat = roundtime;
1393         });
1394 }
1395
1396 /// \brief Hook that determines which team player can join. This is called
1397 /// before ClientConnect.
1398 MUTATOR_HOOKFUNCTION(surv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
1399 {
1400         entity player = M_ARGV(2, entity);
1401         LOG_TRACE("Survival: CheckAllowedTeams, player = ", player.netname);
1402         if (IS_BOT_CLIENT(player))
1403         {
1404                 int teambits = surv_attackerteambit;
1405                 if ((player.team == surv_defenderteam) || (surv_numdefenders <
1406                         autocvar_g_surv_team_size))
1407                 {
1408                         teambits |= surv_defenderteambit;
1409                 }
1410                 M_ARGV(0, float) = teambits;
1411                 return;
1412         }
1413         if (surv_type == SURVIVAL_TYPE_COOP)
1414         {
1415                 if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1416                 {
1417                         M_ARGV(0, float) = surv_defenderteambit;
1418                         return;
1419                 }
1420                 M_ARGV(0, float) = 0;
1421                 return;
1422         }
1423         int teambits = 0;
1424         if (surv_numattackerhumans < autocvar_g_surv_team_size)
1425         {
1426                 teambits |= surv_attackerteambit;
1427         }
1428         if (surv_numdefenderhumans < autocvar_g_surv_team_size)
1429         {
1430                 teambits |= surv_defenderteambit;
1431         }
1432         M_ARGV(0, float) = teambits;
1433         return;
1434 }
1435
1436 /// \brief Hook that determines the best team for the player to join.
1437 MUTATOR_HOOKFUNCTION(surv, JoinBestTeam, CBC_ORDER_EXCLUSIVE)
1438 {
1439         if (surv_type == SURVIVAL_TYPE_COOP)
1440         {
1441                 return;
1442         }
1443         entity player = M_ARGV(0, entity);
1444         if (IS_BOT_CLIENT(player))
1445         {
1446                 return;
1447         }
1448         int numattackerhumans = surv_numattackerhumans;
1449         int numdefenderhumans = surv_numdefenderhumans;
1450         if (player.team == surv_attackerteam)
1451         {
1452                 --numattackerhumans;
1453         }
1454         else if (player.team == surv_defenderteam)
1455         {
1456                 --numdefenderhumans;
1457         }
1458         if (numattackerhumans < numdefenderhumans)
1459         {
1460                 M_ARGV(1, float) = Team_TeamToNumber(surv_attackerteam);
1461                 return;
1462         }
1463         if (numattackerhumans > numdefenderhumans)
1464         {
1465                 M_ARGV(1, float) = Team_TeamToNumber(surv_defenderteam);
1466                 return;
1467         }
1468         M_ARGV(1, float) = floor(random() * 2) + 1;
1469 }
1470
1471 /// \brief Hook that is called when player has changed the team.
1472 MUTATOR_HOOKFUNCTION(surv, Player_ChangedTeam)
1473 {
1474         entity player = M_ARGV(0, entity);
1475         int oldteam = M_ARGV(1, float);
1476         int newteam = M_ARGV(2, float);
1477         string message = strcat("Survival: Player_ChangedTeam, ", player.netname,
1478                 ", old team = ", ftos(oldteam), " new team = ", ftos(newteam));
1479         LOG_TRACE(message);
1480         DebugPrintToChatAll(message);
1481         if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1482         {
1483                 Surv_RemovePlayerFromAliveList(player, oldteam);
1484         }
1485         Surv_RemovePlayerFromTeam(player, oldteam);
1486         if (Surv_AddPlayerToTeam(player, newteam) == false)
1487         {
1488                 return;
1489         }
1490         //Surv_CountAlivePlayers();
1491         if ((oldteam != -1) && IS_PLAYER(player) && !IS_DEAD(player))
1492         {
1493                 Surv_AddPlayerToAliveList(player, newteam);
1494         }
1495 }
1496
1497 /// \brief Hook that is called when player connects to the server.
1498 MUTATOR_HOOKFUNCTION(surv, ClientConnect)
1499 {
1500         entity player = M_ARGV(0, entity);
1501         LOG_TRACE("Survival: ClientConnect, player = ", player.netname);
1502         player.surv_savedplayermodel = player.playermodel;
1503         if (IS_REAL_CLIENT(player))
1504         {
1505                 player.surv_defender_team_stat = Team_TeamToNumber(surv_defenderteam);
1506                 player.surv_defenders_alive_stat = surv_numdefendersalive;
1507                 player.redalive_stat = redalive;
1508                 player.bluealive_stat = bluealive;
1509                 player.yellowalive_stat = yellowalive;
1510                 player.pinkalive_stat = pinkalive;
1511         }
1512         if (player.surv_role == SURVIVAL_ROLE_NONE)
1513         {
1514                 Surv_AddPlayerToTeam(player, player.team);
1515         }
1516         return true;
1517 }
1518
1519 /// \brief Hook that is called when player disconnects from the server.
1520 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
1521 {
1522     entity player = M_ARGV(0, entity);
1523         if (!IS_DEAD(player))
1524         {
1525                 Surv_RemovePlayerFromAliveList(player, player.team);
1526         }
1527         Surv_RemovePlayerFromTeam(player, player.team);
1528         //Surv_CountAlivePlayers();
1529 }
1530
1531 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
1532 {
1533         entity player = M_ARGV(0, entity);
1534         LOG_TRACE("Survival: PutClientInServer, player = ", player.netname);
1535         if (!Surv_CanPlayerSpawn(player) && IS_PLAYER(player))
1536         {
1537                 TRANSMUTE(Observer, player);
1538         }
1539 }
1540
1541 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
1542 {
1543         entity player = M_ARGV(0, entity);
1544         LOG_TRACE("Survival: MakePlayerObserver, player = ", player.netname);
1545         if (player.killindicator_teamchange == -2) // player wants to spectate
1546         {
1547                 LOG_TRACE("killindicator_teamchange == -2");
1548                 player.surv_state = SURVIVAL_STATE_NOT_PLAYING;
1549         }
1550         if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1551         {
1552                 return false;  // allow team reset
1553         }
1554         return true;  // prevent team reset
1555 }
1556
1557 /// \brief Hook that determines whether player can spawn. It is not called for
1558 /// players who have joined the team and are dead.
1559 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
1560 {
1561         entity player = M_ARGV(0, entity);
1562         LOG_TRACE("Survival: ForbidSpawn, player = ", player.netname);
1563         if (player.surv_state == SURVIVAL_STATE_NOT_PLAYING)
1564         {
1565                 return false;
1566         }
1567         return !Surv_CanPlayerSpawn(player);
1568 }
1569
1570 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
1571 {
1572         LOG_TRACE("Survival: reset_map_global");
1573         surv_allowed_to_spawn = true;
1574         if (surv_roundtype == SURVIVAL_ROUND_FIRST)
1575         {
1576                 FOREACH_CLIENT(IS_REAL_CLIENT(it),
1577                 {
1578                         it.surv_round_time_stat = 0;
1579                 });
1580         }
1581         return true;
1582 }
1583
1584 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
1585 {
1586         LOG_TRACE("Survival: reset_map_players");
1587         surv_numattackersalive = 0;
1588         surv_numdefendersalive = 0;
1589         if (surv_warmup)
1590         {
1591                 surv_warmup = false;
1592         }
1593         else if (surv_type == SURVIVAL_TYPE_VERSUS)
1594         {
1595                 Surv_SwapTeams();
1596         }
1597         FOREACH_CLIENT(true,
1598         {
1599                 it.killcount = 0;
1600                 if ((it.surv_state == SURVIVAL_STATE_NOT_PLAYING) && IS_BOT_CLIENT(it))
1601                 {
1602                         it.team = -1;
1603                         it.surv_state = SURVIVAL_STATE_PLAYING;
1604                 }
1605                 if (it.surv_state == SURVIVAL_STATE_PLAYING)
1606                 {
1607                         TRANSMUTE(Player, it);
1608                         it.surv_state = SURVIVAL_STATE_PLAYING;
1609                         PutClientInServer(it);
1610                 }
1611         });
1612         bot_relinkplayerlist();
1613         return true;
1614 }
1615
1616 /// \brief Hook that is called when player spawns.
1617 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
1618 {
1619         entity player = M_ARGV(0, entity);
1620         LOG_TRACE("Survival: PlayerSpawn, player = ", player.netname);
1621         player.surv_state = SURVIVAL_STATE_PLAYING;
1622         Surv_DeterminePlayerModel(player);
1623         if (player.surv_savedplayerstate != NULL)
1624         {
1625                 Surv_RestorePlayerState(player, player.surv_savedplayerstate);
1626                 delete(player.surv_savedplayerstate);
1627                 player.surv_savedplayerstate = NULL;
1628         }
1629         else
1630         {
1631                 PlayerTemplate_PlayerSpawn(player, Surv_GetPlayerTemplate(player));
1632         }
1633         switch (player.team)
1634         {
1635                 case surv_attackerteam:
1636                 {
1637                         switch (player.surv_role)
1638                         {
1639                                 case SURVIVAL_ROLE_PLAYER:
1640                                 {
1641                                         Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1642                                                 CENTER_ASSAULT_ATTACKING);
1643                                         break;
1644                                 }
1645                                 default:
1646                                 {
1647                                         LOG_TRACE("Survival: PlayerSpawn: Invalid attacker role.");
1648                                         break;
1649                                 }
1650                         }
1651                         break;
1652                 }
1653                 case surv_defenderteam:
1654                 {
1655                         if (player.surv_role != SURVIVAL_ROLE_PLAYER)
1656                         {
1657                                 LOG_TRACE("Survival: PlayerSpawn: ", player.netname,
1658                                         " has invalid defender role.");
1659                         }
1660                         Send_Notification(NOTIF_ONE, player, MSG_CENTER,
1661                                 CENTER_ASSAULT_DEFENDING);
1662                         break;
1663                 }
1664         }
1665         //Surv_CountAlivePlayers();
1666         Surv_AddPlayerToAliveList(player, player.team);
1667 }
1668
1669 /// \brief UGLY HACK. This is called every frame to keep player model correct.
1670 MUTATOR_HOOKFUNCTION(surv, FixPlayermodel)
1671 {
1672         entity player = M_ARGV(2, entity);
1673         M_ARGV(0, string) = player.surv_playermodel;
1674 }
1675
1676 /// \brief Hook that is called every frame to determine how player health should
1677 /// regenerate.
1678 MUTATOR_HOOKFUNCTION(surv, PlayerRegen)
1679 {
1680         entity player = M_ARGV(0, entity);
1681         if (player.team == surv_defenderteam)
1682         {
1683                 return true;
1684         }
1685         return false;
1686 }
1687
1688 /// \brief Hook that is called to determine if balance messages will appear.
1689 MUTATOR_HOOKFUNCTION(surv, HideTeamNagger)
1690 {
1691         return true;
1692 }
1693
1694 /// \brief Hook that is called when player touches an item.
1695 MUTATOR_HOOKFUNCTION(surv, ItemTouch)
1696 {
1697         entity item = M_ARGV(0, entity);
1698         entity player = M_ARGV(1, entity);
1699         switch (player.team)
1700         {
1701                 case surv_attackerteam:
1702                 {
1703                         return PlayerTemplate_ItemTouch(player, item,
1704                                 Surv_GetPlayerTemplate(player));
1705                 }
1706                 case surv_defenderteam:
1707                 {
1708                         switch (item.classname)
1709                         {
1710                                 case "item_strength":
1711                                 {
1712                                         W_GiveWeapon(player, WEP_HMG.m_id);
1713                                         player.superweapons_finished = max(
1714                                                 player.superweapons_finished, time) +
1715                                                 autocvar_g_balance_superweapons_time;
1716                                         Item_ScheduleRespawn(item);
1717                                         sound(player, CH_TRIGGER, SND_Strength, VOL_BASE,
1718                                                 ATTEN_NORM);
1719                                         return MUT_ITEMTOUCH_RETURN;
1720                                 }
1721                                 case "item_invincible":
1722                                 {
1723                                         W_GiveWeapon(player, WEP_RPC.m_id);
1724                                         player.superweapons_finished = max(
1725                                                 player.superweapons_finished, time) +
1726                                                 autocvar_g_balance_superweapons_time;
1727                                         Item_ScheduleRespawn(item);
1728                                         sound(player, CH_TRIGGER, SND_Shield, VOL_BASE, ATTEN_NORM);
1729                                         return MUT_ITEMTOUCH_RETURN;
1730                                 }
1731                                 default:
1732                                 {
1733                                         return PlayerTemplate_ItemTouch(player, item,
1734                                                 Surv_GetPlayerTemplate(player));
1735                                 }
1736                         }
1737                         DebugPrintToChat(player, item.classname);
1738                         return MUT_ITEMTOUCH_RETURN;
1739                 }
1740         }
1741         return MUT_ITEMTOUCH_CONTINUE;
1742 }
1743
1744 /// \brief Hook which is called when the player tries to throw their weapon.
1745 MUTATOR_HOOKFUNCTION(surv, ForbidThrowCurrentWeapon)
1746 {
1747         entity player = M_ARGV(0, entity);
1748         if (player.team == surv_defenderteam)
1749         {
1750                 return true;
1751         }
1752 }
1753
1754 /// \brief Hook which is called when the damage amount must be determined.
1755 MUTATOR_HOOKFUNCTION(surv, Damage_Calculate)
1756 {
1757         entity frag_attacker = M_ARGV(1, entity);
1758         entity frag_target = M_ARGV(2, entity);
1759         float deathtype = M_ARGV(3, float);
1760         float damage = M_ARGV(4, float);
1761         M_ARGV(4, float) = PlayerTemplate_Damage_Calculate(frag_attacker,
1762                 Surv_GetPlayerTemplate(frag_attacker), frag_target,
1763                 Surv_GetPlayerTemplate(frag_target), deathtype, damage);
1764 }
1765
1766 /// \brief Hook which is called when the player was damaged.
1767 MUTATOR_HOOKFUNCTION(surv, PlayerDamaged)
1768 {
1769         entity target = M_ARGV(1, entity);
1770         if (target.team != surv_defenderteam)
1771         {
1772                 return;
1773         }
1774         Surv_UpdateDefenderHealthStat();
1775         entity attacker = M_ARGV(0, entity);
1776         if ((attacker.team == surv_attackerteam) && (attacker.surv_role ==
1777                 SURVIVAL_ROLE_PLAYER))
1778         {
1779                 float health = M_ARGV(2, float);
1780                 float armor = M_ARGV(3, float);
1781                 float score = (health + armor) * autocvar_g_surv_attacker_damage_score;
1782                 PlayerScore_Add(attacker, SP_SCORE, score);
1783         }
1784         if (autocvar_g_surv_stealth)
1785         {
1786                 return;
1787         }
1788         if (target.health < 1)
1789         {
1790                 WaypointSprite_Kill(target.surv_attack_sprite);
1791         }
1792         else
1793         {
1794                 WaypointSprite_UpdateHealth(target.surv_attack_sprite,
1795                         target.armorvalue + 1);
1796         }
1797 }
1798
1799 /// \brief Hook which is called when the player dies.
1800 MUTATOR_HOOKFUNCTION(surv, PlayerDies)
1801 {
1802         //DebugPrintToChatAll("PlayerDies");
1803         entity attacker = M_ARGV(1, entity);
1804         entity victim = M_ARGV(2, entity);
1805         if ((attacker.team == surv_defenderteam) &&
1806                 (victim.team == surv_attackerteam))
1807         {
1808                 switch (victim.surv_role)
1809                 {
1810                         case SURVIVAL_ROLE_PLAYER:
1811                         {
1812                                 GivePlayerHealth(attacker,
1813                                         autocvar_g_surv_defender_attacker_frag_health);
1814                                 GivePlayerArmor(attacker,
1815                                         autocvar_g_surv_defender_attacker_frag_armor);
1816                                 GivePlayerAmmo(attacker, ammo_shells,
1817                                         autocvar_g_surv_defender_attacker_frag_shells);
1818                                 GivePlayerAmmo(attacker, ammo_nails,
1819                                         autocvar_g_surv_defender_attacker_frag_bullets);
1820                                 GivePlayerAmmo(attacker, ammo_rockets,
1821                                         autocvar_g_surv_defender_attacker_frag_rockets);
1822                                 GivePlayerAmmo(attacker, ammo_cells,
1823                                         autocvar_g_surv_defender_attacker_frag_cells);
1824                                 break;
1825                         }
1826                         case SURVIVAL_ROLE_CANNON_FODDER:
1827                         {
1828                                 GivePlayerHealth(attacker,
1829                                         autocvar_g_surv_defender_cannon_fodder_frag_health);
1830                                 GivePlayerArmor(attacker,
1831                                         autocvar_g_surv_defender_cannon_fodder_frag_armor);
1832                                 GivePlayerAmmo(attacker, ammo_shells,
1833                                         autocvar_g_surv_defender_cannon_fodder_frag_shells);
1834                                 GivePlayerAmmo(attacker, ammo_nails,
1835                                         autocvar_g_surv_defender_cannon_fodder_frag_bullets);
1836                                 GivePlayerAmmo(attacker, ammo_rockets,
1837                                         autocvar_g_surv_defender_cannon_fodder_frag_rockets);
1838                                 GivePlayerAmmo(attacker, ammo_cells,
1839                                         autocvar_g_surv_defender_cannon_fodder_frag_cells);
1840                                 break;
1841                         }
1842                 }
1843         }
1844         if ((victim.team == surv_defenderteam) &&
1845                 (autocvar_g_surv_defender_drop_weapons == false))
1846         {
1847                 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1848                 {
1849                         .entity went = weaponentities[slot];
1850                         victim.(went).m_weapon = WEP_Null;
1851                 }
1852         }
1853         if (!Surv_CanPlayerSpawn(victim))
1854         {
1855                 victim.respawn_flags = RESPAWN_SILENT;
1856                 if (IS_BOT_CLIENT(victim))
1857                 {
1858                         bot_clear(victim);
1859                 }
1860         }
1861         return true;
1862 }
1863
1864 /// \brief Hook which is called after the player died.
1865 MUTATOR_HOOKFUNCTION(surv, PlayerDied)
1866 {
1867         //DebugPrintToChatAll("PlayerDied");
1868         entity player = M_ARGV(0, entity);
1869         Surv_RemovePlayerFromAliveList(player, player.team);
1870         //Surv_CountAlivePlayers();
1871 }
1872
1873 /// \brief Hook which is called when player has scored a frag.
1874 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
1875 {
1876         if (surv_type == SURVIVAL_TYPE_COOP)
1877         {
1878                 return true;
1879         }
1880         entity attacker = M_ARGV(0, entity);
1881         if ((attacker.team == surv_defenderteam) || (attacker.surv_role ==
1882                 SURVIVAL_ROLE_CANNON_FODDER))
1883         {
1884                 M_ARGV(2, float) = 0;
1885                 return true;
1886         }
1887         entity target = M_ARGV(1, entity);
1888         if ((attacker.surv_role == SURVIVAL_ROLE_PLAYER) && (target.team ==
1889                 surv_defenderteam))
1890         {
1891                 M_ARGV(2, float) = autocvar_g_surv_attacker_frag_score;
1892         }
1893         return true;
1894 }
1895
1896 /// \brief I'm not sure exactly what this function does but it is very
1897 /// important. Without it bots are completely broken. Is it a hack? Of course.
1898 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
1899 {
1900         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
1901                 if (IS_PLAYER(it) || (it.surv_state == SURVIVAL_STATE_PLAYING))
1902                 {
1903                         ++M_ARGV(0, int);
1904                 }
1905                 ++M_ARGV(1, int);
1906         });
1907         return true;
1908 }
1909
1910 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
1911 {
1912         // Don't announce remaining frags
1913         return false;
1914 }