1 #include "sv_survival.qh"
3 float autocvar_g_survival_hunter_count = 0.25;
4 float autocvar_g_survival_round_timelimit = 180;
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 surv_FakeTimeLimit(entity e, float t)
11 if(!IS_REAL_CLIENT(e))
15 WriteByte(MSG_ONE, 3); // svc_updatestat
16 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
18 WriteCoord(MSG_ONE, autocvar_timelimit);
20 WriteCoord(MSG_ONE, (t + 1) / 60);
22 STAT(SURVIVAL_ROUNDTIMER, e) = t;
26 void nades_Clear(entity player);
28 void Surv_UpdateScores(bool timed_out)
30 // give players their hard-earned kills now that the round is over
33 it.totalfrags += it.survival_validkills;
34 if(it.survival_validkills)
35 GameRules_scoring_add(it, SCORE, it.survival_validkills);
36 it.survival_validkills = 0;
37 // player survived the round
38 if(IS_PLAYER(it) && !IS_DEAD(it))
40 if(autocvar_g_survival_reward_survival && timed_out && it.survival_status == SV_STATUS_PREY)
41 GameRules_scoring_add(it, SCORE, 1); // reward survivors who make it to the end of the round time limit
42 if(it.survival_status == SV_STATUS_PREY)
43 GameRules_scoring_add(it, SV_SURVIVALS, 1);
44 else if(it.survival_status == SV_STATUS_HUNTER)
45 GameRules_scoring_add(it, SV_HUNTS, 1);
50 float Surv_CheckWinner()
52 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
54 // if the match times out, survivors win too!
55 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
56 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
61 surv_FakeTimeLimit(it, -1);
64 Surv_UpdateScores(true);
66 allowed_to_spawn = false;
68 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
72 int survivor_count = 0, hunter_count = 0;
73 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
75 if(it.survival_status == SV_STATUS_PREY)
77 else if(it.survival_status == SV_STATUS_HUNTER)
80 if(survivor_count > 0 && hunter_count > 0)
85 if(hunter_count > 0) // hunters win
87 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_HUNTER_WIN);
88 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_HUNTER_WIN);
90 else if(survivor_count > 0) // survivors win
92 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
93 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
97 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
98 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
101 Surv_UpdateScores(false);
103 allowed_to_spawn = false;
105 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
111 surv_FakeTimeLimit(it, -1);
117 void Surv_RoundStart()
119 allowed_to_spawn = boolean(warmup_stage);
123 if(IS_PLAYER(it) && !IS_DEAD(it))
126 it.survival_status = SV_STATUS_PREY;
129 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!
130 it.survival_validkills = 0;
132 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
133 int total_hunters = 0;
134 FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it),
136 if(total_hunters >= hunter_count)
139 it.survival_status = SV_STATUS_HUNTER;
142 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
144 if(it.survival_status == SV_STATUS_PREY)
145 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR);
146 else if(it.survival_status == SV_STATUS_HUNTER)
147 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_HUNTER);
149 surv_FakeTimeLimit(it, round_handler_GetEndTime());
153 bool Surv_CheckPlayers()
155 static int prev_missing_players;
156 allowed_to_spawn = true;
158 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
162 if (playercount >= 2)
164 if(prev_missing_players > 0)
165 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
166 prev_missing_players = -1;
171 if(prev_missing_players > 0)
172 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
173 prev_missing_players = -1;
176 // if we get here, only 1 player is missing
177 if(prev_missing_players != 1)
179 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, 1);
180 prev_missing_players = 1;
185 bool surv_isEliminated(entity e)
187 if(INGAME_JOINED(e) && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
189 if(INGAME_JOINING(e))
194 void surv_Initialize() // run at the start of a match, initiates game mode
196 GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
197 field(SP_SV_SURVIVALS, "survivals", 0);
198 field(SP_SV_HUNTS, "hunts", SFL_SORT_PRIO_SECONDARY);
201 allowed_to_spawn = true;
202 round_handler_Spawn(Surv_CheckPlayers, Surv_CheckWinner, Surv_RoundStart);
203 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
204 EliminatedPlayers_Init(surv_isEliminated);
212 MUTATOR_HOOKFUNCTION(sv, ClientObituary)
214 // in survival, announcing a frag would tell everyone who the hunter is
215 entity frag_attacker = M_ARGV(1, entity);
216 entity frag_target = M_ARGV(2, entity);
217 if(IS_PLAYER(frag_attacker) && frag_attacker != frag_target)
219 float frag_deathtype = M_ARGV(3, float);
220 entity wep_ent = M_ARGV(4, entity);
221 // "team" kill, a point is awarded to the player by default so we must take it away plus an extra one
222 // unless the player is going to be punished for suicide, in which case just remove one
223 if(frag_attacker.survival_status == frag_target.survival_status)
224 GiveFrags(frag_attacker, frag_target, ((autocvar_g_survival_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
227 M_ARGV(5, bool) = true; // anonymous attacker
230 MUTATOR_HOOKFUNCTION(sv, PlayerPreThink)
232 entity player = M_ARGV(0, entity);
234 if(IS_PLAYER(player) || INGAME_JOINED(player))
236 // update the scoreboard colour display to out the real killer at the end of the round
237 // running this every frame to avoid cheats
238 int plcolor = SV_COLOR_PREY;
239 if(player.survival_status == SV_STATUS_HUNTER && game_stopped)
240 plcolor = SV_COLOR_HUNTER;
241 setcolor(player, plcolor);
245 MUTATOR_HOOKFUNCTION(sv, PlayerSpawn)
247 entity player = M_ARGV(0, entity);
249 player.survival_status = 0;
250 player.survival_validkills = 0;
251 INGAME_STATUS_SET(player, INGAME_STATUS_JOINED);
253 eliminatedPlayers.SendFlags |= 1;
256 MUTATOR_HOOKFUNCTION(sv, ForbidSpawn)
258 entity player = M_ARGV(0, entity);
260 // spectators / observers that weren't playing can join; they are
261 // immediately forced to observe in the PutClientInServer hook
262 // this way they are put in a team and can play in the next round
263 if (!allowed_to_spawn && INGAME(player))
268 MUTATOR_HOOKFUNCTION(sv, PutClientInServer)
270 entity player = M_ARGV(0, entity);
272 if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
274 TRANSMUTE(Observer, player);
275 if (CS(player).jointime != time && !INGAME(player)) // not when connecting
277 INGAME_STATUS_SET(player, INGAME_STATUS_JOINING);
278 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
283 MUTATOR_HOOKFUNCTION(sv, reset_map_players)
285 FOREACH_CLIENT(true, {
286 CS(it).killcount = 0;
287 it.survival_status = 0;
288 surv_FakeTimeLimit(it, -1); // restore original timelimit
289 if (INGAME(it) || IS_BOT_CLIENT(it))
291 TRANSMUTE(Player, it);
292 INGAME_STATUS_SET(it, INGAME_STATUS_JOINED);
293 PutClientInServer(it);
296 bot_relinkplayerlist();
300 MUTATOR_HOOKFUNCTION(sv, reset_map_global)
302 allowed_to_spawn = true;
306 entity surv_LastPlayerForTeam(entity this)
308 entity last_pl = NULL;
309 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
310 if (!IS_DEAD(it) && this.survival_status == it.survival_status)
321 void surv_LastPlayerForTeam_Notify(entity this)
323 if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
325 entity pl = surv_LastPlayerForTeam(this);
327 Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE);
331 MUTATOR_HOOKFUNCTION(sv, PlayerDies)
333 entity frag_attacker = M_ARGV(1, entity);
334 entity frag_target = M_ARGV(2, entity);
335 float frag_deathtype = M_ARGV(3, float);
337 surv_LastPlayerForTeam_Notify(frag_target);
338 if (!allowed_to_spawn)
340 frag_target.respawn_flags = RESPAWN_SILENT;
341 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
342 frag_target.respawn_time = time + 2;
344 frag_target.respawn_flags |= RESPAWN_FORCE;
347 eliminatedPlayers.SendFlags |= 1;
348 if (IS_BOT_CLIENT(frag_target))
349 bot_clearqueue(frag_target);
352 // killed an ally! punishment is death
353 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))
354 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) // don't autokill if the round hasn't
355 Damage(frag_attacker, frag_attacker, frag_attacker, 100000, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
359 MUTATOR_HOOKFUNCTION(sv, ClientDisconnect)
361 entity player = M_ARGV(0, entity);
363 if (IS_PLAYER(player) && !IS_DEAD(player))
364 surv_LastPlayerForTeam_Notify(player);
368 MUTATOR_HOOKFUNCTION(sv, MakePlayerObserver)
370 entity player = M_ARGV(0, entity);
371 bool is_forced = M_ARGV(1, bool);
372 if (is_forced && INGAME(player))
373 INGAME_STATUS_CLEAR(player);
375 if (IS_PLAYER(player) && !IS_DEAD(player))
376 surv_LastPlayerForTeam_Notify(player);
377 if (player.killindicator_teamchange == -2) // player wants to spectate
378 INGAME_STATUS_CLEAR(player);
380 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
382 eliminatedPlayers.SendFlags |= 1;
385 player.survival_validkills = 0;
386 player.survival_status = 0;
387 surv_FakeTimeLimit(player, -1); // restore original timelimit
388 return false; // allow team reset
390 return true; // prevent team reset
393 MUTATOR_HOOKFUNCTION(sv, Scores_CountFragsRemaining)
395 // announce remaining frags?
399 MUTATOR_HOOKFUNCTION(sv, GiveFragsForKill, CBC_ORDER_FIRST)
401 entity frag_attacker = M_ARGV(0, entity);
402 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
403 frag_attacker.survival_validkills += M_ARGV(2, float);
404 M_ARGV(2, float) = 0; // score will be given to the winner when the round ends
408 MUTATOR_HOOKFUNCTION(sv, AddPlayerScore)
410 entity scorefield = M_ARGV(0, entity);
411 if(scorefield == SP_KILLS || scorefield == SP_DEATHS || scorefield == SP_SUICIDES || scorefield == SP_DMG || scorefield == SP_DMGTAKEN)
412 M_ARGV(1, float) = 0; // don't report that the player has killed or been killed, that would out them as a hunter!
415 MUTATOR_HOOKFUNCTION(sv, CalculateRespawnTime)
417 // no respawn calculations needed, player is forced to spectate anyway
421 MUTATOR_HOOKFUNCTION(sv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
423 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
424 if (IS_PLAYER(it) || INGAME_JOINED(it))
431 MUTATOR_HOOKFUNCTION(sv, ClientCommand_Spectate)
433 entity player = M_ARGV(0, entity);
437 // they're going to spec, we can do other checks
438 if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
439 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
440 return MUT_SPECCMD_FORCE;
443 return MUT_SPECCMD_CONTINUE;
446 MUTATOR_HOOKFUNCTION(sv, BotShouldAttack)
448 entity bot = M_ARGV(0, entity);
449 entity targ = M_ARGV(1, entity);
451 if(targ.survival_status == bot.survival_status)