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))
14 WriteByte(MSG_ONE, 3); // svc_updatestat
15 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
17 WriteCoord(MSG_ONE, autocvar_timelimit);
19 WriteCoord(MSG_ONE, (t + 1) / 60);
22 void nades_Clear(entity player);
24 void Surv_UpdateScores(bool timed_out)
26 // give players their hard-earned kills now that the round is over
29 it.totalfrags += it.survival_validkills;
30 if(it.survival_validkills)
31 GameRules_scoring_add(it, SCORE, it.survival_validkills);
32 it.survival_validkills = 0;
33 // player survived the round
34 if(IS_PLAYER(it) && !IS_DEAD(it))
36 if(autocvar_g_survival_reward_survival && timed_out && it.survival_status == SV_STATUS_PREY)
37 GameRules_scoring_add(it, SCORE, 1); // reward survivors who make it to the end of the round time limit
38 if(it.survival_status == SV_STATUS_PREY)
39 GameRules_scoring_add(it, SV_SURVIVALS, 1);
40 else if(it.survival_status == SV_STATUS_HUNTER)
41 GameRules_scoring_add(it, SV_HUNTS, 1);
46 float Surv_CheckWinner()
48 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
50 // if the match times out, survivors win too!
51 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
52 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
57 surv_FakeTimeLimit(it, -1);
60 Surv_UpdateScores(true);
62 allowed_to_spawn = false;
64 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
68 int survivor_count = 0, hunter_count = 0;
69 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
71 if(it.survival_status == SV_STATUS_PREY)
73 else if(it.survival_status == SV_STATUS_HUNTER)
76 if(survivor_count > 0 && hunter_count > 0)
81 if(hunter_count > 0) // hunters win
83 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_HUNTER_WIN);
84 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_HUNTER_WIN);
86 else if(survivor_count > 0) // survivors win
88 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
89 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
93 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
94 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
97 Surv_UpdateScores(false);
99 allowed_to_spawn = false;
101 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
107 surv_FakeTimeLimit(it, -1);
113 void Surv_RoundStart()
115 allowed_to_spawn = boolean(warmup_stage);
119 if(IS_PLAYER(it) && !IS_DEAD(it))
122 it.survival_status = SV_STATUS_PREY;
125 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!
126 it.survival_validkills = 0;
128 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
129 int total_hunters = 0;
130 FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it),
132 if(total_hunters >= hunter_count)
135 it.survival_status = SV_STATUS_HUNTER;
138 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
140 if(it.survival_status == SV_STATUS_PREY)
141 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR);
142 else if(it.survival_status == SV_STATUS_HUNTER)
143 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_HUNTER);
145 surv_FakeTimeLimit(it, round_handler_GetEndTime());
149 bool Surv_CheckPlayers()
151 static int prev_missing_players;
152 allowed_to_spawn = true;
154 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
158 if (playercount >= 2)
160 if(prev_missing_players > 0)
161 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
162 prev_missing_players = -1;
167 if(prev_missing_players > 0)
168 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
169 prev_missing_players = -1;
172 // if we get here, only 1 player is missing
173 if(prev_missing_players != 1)
175 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, 1);
176 prev_missing_players = 1;
181 bool surv_isEliminated(entity e)
183 if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
185 if(e.caplayer == 0.5)
190 void surv_Initialize() // run at the start of a match, initiates game mode
192 GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
193 field(SP_SV_SURVIVALS, "survivals", 0);
194 field(SP_SV_HUNTS, "hunts", SFL_SORT_PRIO_SECONDARY);
197 allowed_to_spawn = true;
198 round_handler_Spawn(Surv_CheckPlayers, Surv_CheckWinner, Surv_RoundStart);
199 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
200 EliminatedPlayers_Init(surv_isEliminated);
208 MUTATOR_HOOKFUNCTION(sv, ClientObituary)
210 // in survival, announcing a frag would tell everyone who the hunter is
211 entity frag_attacker = M_ARGV(1, entity);
212 entity frag_target = M_ARGV(2, entity);
213 if(IS_PLAYER(frag_attacker) && frag_attacker != frag_target)
215 float frag_deathtype = M_ARGV(3, float);
216 entity wep_ent = M_ARGV(4, entity);
217 // "team" kill, a point is awarded to the player by default so we must take it away plus an extra one
218 // unless the player is going to be punished for suicide, in which case just remove one
219 if(frag_attacker.survival_status == frag_target.survival_status)
220 GiveFrags(frag_attacker, frag_target, ((autocvar_g_survival_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
223 M_ARGV(5, bool) = true; // anonymous attacker
226 MUTATOR_HOOKFUNCTION(sv, PlayerPreThink)
228 entity player = M_ARGV(0, entity);
230 if(IS_PLAYER(player) || player.caplayer)
232 // update the scoreboard colour display to out the real killer at the end of the round
233 // running this every frame to avoid cheats
234 int plcolor = SV_COLOR_PREY;
235 if(player.survival_status == SV_STATUS_HUNTER && game_stopped)
236 plcolor = SV_COLOR_HUNTER;
237 setcolor(player, plcolor);
241 MUTATOR_HOOKFUNCTION(sv, PlayerSpawn)
243 entity player = M_ARGV(0, entity);
245 player.survival_status = 0;
246 player.survival_validkills = 0;
249 eliminatedPlayers.SendFlags |= 1;
252 MUTATOR_HOOKFUNCTION(sv, ForbidSpawn)
254 entity player = M_ARGV(0, entity);
256 // spectators / observers that weren't playing can join; they are
257 // immediately forced to observe in the PutClientInServer hook
258 // this way they are put in a team and can play in the next round
259 if (!allowed_to_spawn && player.caplayer)
264 MUTATOR_HOOKFUNCTION(sv, PutClientInServer)
266 entity player = M_ARGV(0, entity);
268 if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
270 TRANSMUTE(Observer, player);
271 if (CS(player).jointime != time && !player.caplayer) // not when connecting
273 player.caplayer = 0.5;
274 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
279 MUTATOR_HOOKFUNCTION(sv, reset_map_players)
281 FOREACH_CLIENT(true, {
282 CS(it).killcount = 0;
283 it.survival_status = 0;
284 surv_FakeTimeLimit(it, -1); // restore original timelimit
285 if (!it.caplayer && IS_BOT_CLIENT(it))
289 TRANSMUTE(Player, it);
291 PutClientInServer(it);
294 bot_relinkplayerlist();
298 MUTATOR_HOOKFUNCTION(sv, reset_map_global)
300 allowed_to_spawn = true;
304 entity surv_LastPlayerForTeam(entity this)
306 entity last_pl = NULL;
307 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
308 if (!IS_DEAD(it) && this.survival_status == it.survival_status)
319 void surv_LastPlayerForTeam_Notify(entity this)
321 if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
323 entity pl = surv_LastPlayerForTeam(this);
325 Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
329 MUTATOR_HOOKFUNCTION(sv, PlayerDies)
331 entity frag_attacker = M_ARGV(1, entity);
332 entity frag_target = M_ARGV(2, entity);
333 float frag_deathtype = M_ARGV(3, float);
335 surv_LastPlayerForTeam_Notify(frag_target);
336 if (!allowed_to_spawn)
338 frag_target.respawn_flags = RESPAWN_SILENT;
339 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
340 frag_target.respawn_time = time + 2;
342 frag_target.respawn_flags |= RESPAWN_FORCE;
345 eliminatedPlayers.SendFlags |= 1;
346 if (IS_BOT_CLIENT(frag_target))
347 bot_clear(frag_target);
350 // killed an ally! punishment is death
351 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))
352 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) // don't autokill if the round hasn't
353 Damage(frag_attacker, frag_attacker, frag_attacker, 100000, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
357 MUTATOR_HOOKFUNCTION(sv, ClientDisconnect)
359 entity player = M_ARGV(0, entity);
361 if (IS_PLAYER(player) && !IS_DEAD(player))
362 surv_LastPlayerForTeam_Notify(player);
366 MUTATOR_HOOKFUNCTION(sv, MakePlayerObserver)
368 entity player = M_ARGV(0, entity);
370 if (IS_PLAYER(player) && !IS_DEAD(player))
371 surv_LastPlayerForTeam_Notify(player);
372 if (player.killindicator_teamchange == -2) // player wants to spectate
375 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
377 eliminatedPlayers.SendFlags |= 1;
378 if (!player.caplayer)
380 player.survival_validkills = 0;
381 player.survival_status = 0;
382 surv_FakeTimeLimit(player, -1); // restore original timelimit
383 return false; // allow team reset
385 return true; // prevent team reset
388 MUTATOR_HOOKFUNCTION(sv, Scores_CountFragsRemaining)
390 // announce remaining frags?
394 MUTATOR_HOOKFUNCTION(sv, GiveFragsForKill, CBC_ORDER_FIRST)
396 entity frag_attacker = M_ARGV(0, entity);
397 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
398 frag_attacker.survival_validkills += M_ARGV(2, float);
399 M_ARGV(2, float) = 0; // score will be given to the winner when the round ends
403 MUTATOR_HOOKFUNCTION(sv, AddPlayerScore)
405 entity scorefield = M_ARGV(0, entity);
406 if(scorefield == SP_KILLS || scorefield == SP_DEATHS || scorefield == SP_SUICIDES || scorefield == SP_DMG || scorefield == SP_DMGTAKEN)
407 M_ARGV(1, float) = 0; // don't report that the player has killed or been killed, that would out them as a hunter!
410 MUTATOR_HOOKFUNCTION(sv, CalculateRespawnTime)
412 // no respawn calculations needed, player is forced to spectate anyway
416 MUTATOR_HOOKFUNCTION(sv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
418 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
419 if (IS_PLAYER(it) || it.caplayer == 1)
426 MUTATOR_HOOKFUNCTION(sv, ClientCommand_Spectate)
428 entity player = M_ARGV(0, entity);
432 // they're going to spec, we can do other checks
433 if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
434 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
435 return MUT_SPECCMD_FORCE;
438 return MUT_SPECCMD_CONTINUE;
441 MUTATOR_HOOKFUNCTION(sv, GetPlayerStatus)
443 entity player = M_ARGV(0, entity);
445 return player.caplayer == 1;
448 MUTATOR_HOOKFUNCTION(sv, BotShouldAttack)
450 entity bot = M_ARGV(0, entity);
451 entity targ = M_ARGV(1, entity);
453 if(targ.survival_status == bot.survival_status)