1 #include "sv_survival.qh"
3 float autocvar_g_survival_hunter_count = 0.25;
4 float autocvar_g_survival_round_timelimit = 120;
5 float autocvar_g_survival_warmup = 10;
6 bool autocvar_g_survival_punish_teamkill = true;
7 bool autocvar_g_survival_reward_survival = true;
9 void nades_Clear(entity player);
11 void Surv_UpdateScores(bool timed_out)
13 // give players their hard-earned kills now that the round is over
16 it.totalfrags += it.survival_validkills;
17 if(it.survival_validkills)
18 GameRules_scoring_add(it, SCORE, it.survival_validkills);
19 it.survival_validkills = 0;
20 // player survived the round
21 if(IS_PLAYER(it) && !IS_DEAD(it))
23 if(autocvar_g_survival_reward_survival && timed_out && it.survival_status == SURV_STATUS_PREY)
24 GameRules_scoring_add(it, SCORE, 1); // reward survivors who make it to the end of the round time limit
25 if(it.survival_status == SURV_STATUS_PREY)
26 GameRules_scoring_add(it, SURV_SURVIVALS, 1);
27 else if(it.survival_status == SURV_STATUS_HUNTER)
28 GameRules_scoring_add(it, SURV_HUNTS, 1);
33 float Surv_CheckWinner()
35 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
37 // if the match times out, survivors win too!
38 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
39 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
46 Surv_UpdateScores(true);
48 allowed_to_spawn = false;
50 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
54 int survivor_count = 0, hunter_count = 0;
55 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
57 if(it.survival_status == SURV_STATUS_PREY)
59 else if(it.survival_status == SURV_STATUS_HUNTER)
62 if(survivor_count > 0 && hunter_count > 0)
67 if(hunter_count > 0) // hunters win
69 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_HUNTER_WIN);
70 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_HUNTER_WIN);
72 else if(survivor_count > 0) // survivors win
74 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
75 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
79 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
80 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
83 Surv_UpdateScores(false);
85 allowed_to_spawn = false;
87 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
98 void Surv_RoundStart()
100 allowed_to_spawn = boolean(warmup_stage);
104 if(IS_PLAYER(it) && !IS_DEAD(it))
107 it.survival_status = SURV_STATUS_PREY;
110 it.survival_status = 0; // this is mostly a safety check; if a client manages to somehow maintain a survival status, clear it before the round starts!
111 it.survival_validkills = 0;
113 int hunter_count = bound(1, ((autocvar_g_survival_hunter_count >= 1) ? autocvar_g_survival_hunter_count : floor(playercount * autocvar_g_survival_hunter_count)), playercount - 1); // 20%, but ensure at least 1 and less than total
114 int total_hunters = 0;
115 FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it),
117 if(total_hunters >= hunter_count)
120 it.survival_status = SURV_STATUS_HUNTER;
123 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
125 if(it.survival_status == SURV_STATUS_PREY)
126 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR);
127 else if(it.survival_status == SURV_STATUS_HUNTER)
128 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_HUNTER);
132 bool Surv_CheckPlayers()
134 static int prev_missing_players;
135 allowed_to_spawn = true;
137 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
141 if (playercount >= 2)
143 if(prev_missing_players > 0)
144 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
145 prev_missing_players = -1;
150 if(prev_missing_players > 0)
151 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
152 prev_missing_players = -1;
155 // if we get here, only 1 player is missing
156 if(prev_missing_players != 1)
158 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, 1);
159 prev_missing_players = 1;
164 bool surv_isEliminated(entity e)
166 if(INGAME_JOINED(e) && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
168 if(INGAME_JOINING(e))
173 void surv_Initialize() // run at the start of a match, initiates game mode
175 GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
176 field(SP_SURV_SURVIVALS, "survivals", 0);
177 field(SP_SURV_HUNTS, "hunts", SFL_SORT_PRIO_SECONDARY);
180 allowed_to_spawn = true;
181 round_handler_Spawn(Surv_CheckPlayers, Surv_CheckWinner, Surv_RoundStart);
182 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
183 EliminatedPlayers_Init(surv_isEliminated);
191 MUTATOR_HOOKFUNCTION(surv, ClientObituary)
193 // in survival, announcing a frag would tell everyone who the hunter is
194 entity frag_attacker = M_ARGV(1, entity);
195 entity frag_target = M_ARGV(2, entity);
196 if(IS_PLAYER(frag_attacker) && frag_attacker != frag_target)
198 float frag_deathtype = M_ARGV(3, float);
199 entity wep_ent = M_ARGV(4, entity);
200 // "team" kill, a point is awarded to the player by default so we must take it away plus an extra one
201 // unless the player is going to be punished for suicide, in which case just remove one
202 if(frag_attacker.survival_status == frag_target.survival_status)
203 GiveFrags(frag_attacker, frag_target, ((autocvar_g_survival_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
206 if(frag_attacker.survival_status == SURV_STATUS_HUNTER)
207 M_ARGV(5, bool) = true; // anonymous attacker
210 MUTATOR_HOOKFUNCTION(surv, PlayerPreThink)
212 entity player = M_ARGV(0, entity);
214 if(IS_PLAYER(player) || INGAME_JOINED(player))
216 // update the scoreboard colour display to out the real killer at the end of the round
217 // running this every frame to avoid cheats
218 int plcolor = SURV_COLOR_PREY;
219 if(player.survival_status == SURV_STATUS_HUNTER && game_stopped)
220 plcolor = SURV_COLOR_HUNTER;
221 setcolor(player, plcolor);
225 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
227 entity player = M_ARGV(0, entity);
229 player.survival_status = 0;
230 player.survival_validkills = 0;
231 INGAME_STATUS_SET(player, INGAME_STATUS_JOINED);
233 eliminatedPlayers.SendFlags |= 1;
236 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
238 entity player = M_ARGV(0, entity);
240 // spectators / observers that weren't playing can join; they are
241 // immediately forced to observe in the PutClientInServer hook
242 // this way they are put in a team and can play in the next round
243 if (!allowed_to_spawn && INGAME(player))
248 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
250 entity player = M_ARGV(0, entity);
252 if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
254 TRANSMUTE(Observer, player);
255 if (CS(player).jointime != time && !INGAME(player)) // not when connecting
257 INGAME_STATUS_SET(player, INGAME_STATUS_JOINING);
258 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
263 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
265 FOREACH_CLIENT(true, {
266 CS(it).killcount = 0;
267 it.survival_status = 0;
268 if (INGAME(it) || IS_BOT_CLIENT(it))
270 TRANSMUTE(Player, it);
271 INGAME_STATUS_SET(it, INGAME_STATUS_JOINED);
272 PutClientInServer(it);
275 bot_relinkplayerlist();
279 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
281 allowed_to_spawn = true;
285 entity surv_LastPlayerForTeam(entity this)
287 entity last_pl = NULL;
288 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
289 if (!IS_DEAD(it) && this.survival_status == it.survival_status)
300 void surv_LastPlayerForTeam_Notify(entity this)
302 if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
304 entity pl = surv_LastPlayerForTeam(this);
306 Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE);
310 MUTATOR_HOOKFUNCTION(surv, PlayerDies)
312 entity frag_attacker = M_ARGV(1, entity);
313 entity frag_target = M_ARGV(2, entity);
314 float frag_deathtype = M_ARGV(3, float);
316 surv_LastPlayerForTeam_Notify(frag_target);
317 if (!allowed_to_spawn)
319 frag_target.respawn_flags = RESPAWN_SILENT;
320 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
321 frag_target.respawn_time = time + 2;
323 frag_target.respawn_flags |= RESPAWN_FORCE;
326 eliminatedPlayers.SendFlags |= 1;
327 if (IS_BOT_CLIENT(frag_target))
328 bot_clearqueue(frag_target);
331 // killed an ally! punishment is death
332 if(autocvar_g_survival_punish_teamkill && frag_attacker != frag_target && IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker.survival_status == frag_target.survival_status && !ITEM_DAMAGE_NEEDKILL(frag_deathtype))
333 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) // don't autokill if the round hasn't
334 Damage(frag_attacker, frag_attacker, frag_attacker, 100000, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
338 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
340 entity player = M_ARGV(0, entity);
342 if (IS_PLAYER(player) && !IS_DEAD(player))
343 surv_LastPlayerForTeam_Notify(player);
347 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
349 entity player = M_ARGV(0, entity);
350 bool is_forced = M_ARGV(1, bool);
351 if (is_forced && INGAME(player))
352 INGAME_STATUS_CLEAR(player);
354 if (IS_PLAYER(player) && !IS_DEAD(player))
355 surv_LastPlayerForTeam_Notify(player);
356 if (player.killindicator_teamchange == -2) // player wants to spectate
357 INGAME_STATUS_CLEAR(player);
359 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
361 eliminatedPlayers.SendFlags |= 1;
364 player.survival_validkills = 0;
365 player.survival_status = 0;
366 return false; // allow team reset
368 return true; // prevent team reset
371 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
373 // announce remaining frags?
377 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
379 entity frag_attacker = M_ARGV(0, entity);
380 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
381 frag_attacker.survival_validkills += M_ARGV(2, float);
382 M_ARGV(2, float) = 0; // score will be given to the winner when the round ends
386 MUTATOR_HOOKFUNCTION(surv, AddPlayerScore)
388 entity scorefield = M_ARGV(0, entity);
389 if(scorefield == SP_KILLS || scorefield == SP_DEATHS || scorefield == SP_SUICIDES || scorefield == SP_DMG || scorefield == SP_DMGTAKEN)
390 M_ARGV(1, float) = 0; // don't report that the player has killed or been killed, that would out them as a hunter!
393 MUTATOR_HOOKFUNCTION(surv, CalculateRespawnTime)
395 // no respawn calculations needed, player is forced to spectate anyway
399 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
401 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
402 if (IS_PLAYER(it) || INGAME_JOINED(it))
409 MUTATOR_HOOKFUNCTION(surv, ClientCommand_Spectate)
411 entity player = M_ARGV(0, entity);
415 // they're going to spec, we can do other checks
416 if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
417 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
418 return MUT_SPECCMD_FORCE;
421 return MUT_SPECCMD_CONTINUE;
424 MUTATOR_HOOKFUNCTION(surv, BotShouldAttack)
426 entity bot = M_ARGV(0, entity);
427 entity targ = M_ARGV(1, entity);
429 if(targ.survival_status == bot.survival_status)