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