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