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 entity survivalStatuses;
12 void SurvivalStatuses_Init();
14 void SurvivalStatuses_Send()
16 // SendFlags can be set to anything != 0, SurvivalStatuses_SendEntity won't use its value
17 // Dr. Jaska: this was a lie, the flags were not reset until now
18 survivalStatuses.SendFlags = 1;
21 bool SurvivalStatuses_SendEntity(entity this, entity dest, float sendflags)
23 Stream out = MSG_ENTITY;
24 WriteHeader(out, ENT_CLIENT_SURVIVALSTATUSES);
26 // TODO: optimize this instead of always setting it on
27 sendflags = BIT(0); // reset all flags and make all players survivors
29 if ((dest.survival_status == SURV_STATUS_HUNTER) || round_handler_AwaitingNextRound())
30 sendflags |= BIT(1); // send hunter statuses
32 serialize(byte, out, sendflags);
33 if (sendflags & BIT(1)) {
34 for (int i = 1; i <= maxclients; i += 8) {
36 entity e = edict_num(i);
37 for (int b = 0; b < 8; ++b, e = nextent(e)) {
38 bool is_hunter = (INGAME(e) && e.survival_status == SURV_STATUS_HUNTER);
42 serialize(byte, out, f);
45 //print(sprintf("sent flags %f to %s\n", sendflags, dest.netname));
49 void SurvivalStatuses_Init()
53 backtrace("Can't spawn survivalStatuses again!");
56 Net_LinkEntity(survivalStatuses = new_pure(survivalStatuses), false, 0, SurvivalStatuses_SendEntity);
59 void Surv_UpdateScores(bool timed_out)
61 // give players their hard-earned kills now that the round is over
64 it.totalfrags += it.survival_validkills;
65 if(it.survival_validkills)
66 GameRules_scoring_add(it, SCORE, it.survival_validkills);
67 it.survival_validkills = 0;
68 // player survived the round
69 if(IS_PLAYER(it) && !IS_DEAD(it))
71 if(autocvar_g_survival_reward_survival && timed_out && it.survival_status == SURV_STATUS_PREY)
72 GameRules_scoring_add(it, SCORE, 1); // reward survivors who make it to the end of the round time limit
73 if(it.survival_status == SURV_STATUS_PREY)
74 GameRules_scoring_add(it, SURV_SURVIVALS, 1);
75 else if(it.survival_status == SURV_STATUS_HUNTER)
76 GameRules_scoring_add(it, SURV_HUNTS, 1);
81 float Surv_CheckWinner()
83 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
85 // if the match times out, survivors win too!
86 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
87 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
94 Surv_UpdateScores(true);
96 allowed_to_spawn = false;
98 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
99 SurvivalStatuses_Send();
103 int survivor_count = 0, hunter_count = 0;
104 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
106 if(it.survival_status == SURV_STATUS_PREY)
108 else if(it.survival_status == SURV_STATUS_HUNTER)
111 if(survivor_count > 0 && hunter_count > 0)
116 if(hunter_count > 0) // hunters win
118 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_HUNTER_WIN);
119 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_HUNTER_WIN);
121 else if(survivor_count > 0) // survivors win
123 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
124 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
128 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
129 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
132 Surv_UpdateScores(false);
134 allowed_to_spawn = false;
136 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
137 SurvivalStatuses_Send();
148 void Surv_RoundStart()
150 allowed_to_spawn = boolean(warmup_stage);
154 if(IS_PLAYER(it) && !IS_DEAD(it))
157 it.survival_status = SURV_STATUS_PREY;
160 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!
161 it.survival_validkills = 0;
163 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
164 int total_hunters = 0;
165 FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it),
167 if(total_hunters >= hunter_count)
170 it.survival_status = SURV_STATUS_HUNTER;
172 SurvivalStatuses_Send();
174 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
176 if(it.survival_status == SURV_STATUS_PREY)
177 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR);
178 else if(it.survival_status == SURV_STATUS_HUNTER)
179 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_HUNTER);
183 bool Surv_CheckPlayers()
185 static int prev_missing_players;
186 allowed_to_spawn = true;
188 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
192 if (playercount >= 2)
194 if(prev_missing_players > 0)
195 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
196 prev_missing_players = -1;
201 if(prev_missing_players > 0)
202 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
203 prev_missing_players = -1;
206 // if we get here, only 1 player is missing
207 if(prev_missing_players != 1)
209 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, 1);
210 prev_missing_players = 1;
215 bool surv_isEliminated(entity e)
217 if(INGAME_JOINED(e) && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
219 if(INGAME_JOINING(e))
224 void surv_Initialize() // run at the start of a match, initiates game mode
226 GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
227 field(SP_SURV_SURVIVALS, "survivals", 0);
228 field(SP_SURV_HUNTS, "hunts", SFL_SORT_PRIO_SECONDARY);
231 allowed_to_spawn = true;
232 round_handler_Spawn(Surv_CheckPlayers, Surv_CheckWinner, Surv_RoundStart);
233 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
234 EliminatedPlayers_Init(surv_isEliminated);
235 SurvivalStatuses_Init();
243 MUTATOR_HOOKFUNCTION(surv, ClientObituary)
245 // in survival, announcing a frag would tell everyone who the hunter is
246 entity frag_attacker = M_ARGV(1, entity);
247 entity frag_target = M_ARGV(2, entity);
248 if(IS_PLAYER(frag_attacker) && frag_attacker != frag_target)
250 float frag_deathtype = M_ARGV(3, float);
251 entity wep_ent = M_ARGV(4, entity);
252 // "team" kill, a point is awarded to the player by default so we must take it away plus an extra one
253 // unless the player is going to be punished for suicide, in which case just remove one
254 if(frag_attacker.survival_status == frag_target.survival_status)
255 GiveFrags(frag_attacker, frag_target, ((autocvar_g_survival_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
258 if(frag_attacker.survival_status == SURV_STATUS_HUNTER)
259 M_ARGV(5, bool) = true; // anonymous attacker
262 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
264 entity player = M_ARGV(0, entity);
266 player.survival_status = 0;
267 player.survival_validkills = 0;
268 INGAME_STATUS_SET(player, INGAME_STATUS_JOINED);
270 eliminatedPlayers.SendFlags |= 1;
273 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
275 entity player = M_ARGV(0, entity);
277 // spectators / observers that weren't playing can join; they are
278 // immediately forced to observe in the PutClientInServer hook
279 // this way they are put in a team and can play in the next round
280 if (!allowed_to_spawn && INGAME(player))
285 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
287 entity player = M_ARGV(0, entity);
289 if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
291 TRANSMUTE(Observer, player);
292 if (CS(player).jointime != time && !INGAME(player)) // not when connecting
294 INGAME_STATUS_SET(player, INGAME_STATUS_JOINING);
295 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
300 eliminatedPlayers.SendFlags |= 1;
303 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
305 FOREACH_CLIENT(true, {
306 CS(it).killcount = 0;
307 it.survival_status = 0;
308 if (INGAME(it) || IS_BOT_CLIENT(it))
310 TRANSMUTE(Player, it);
311 INGAME_STATUS_SET(it, INGAME_STATUS_JOINED);
312 PutClientInServer(it);
315 bot_relinkplayerlist();
316 SurvivalStatuses_Send();
320 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
322 allowed_to_spawn = true;
326 MUTATOR_HOOKFUNCTION(surv, MatchEnd_BeforeScores)
328 MatchEnd_RestoreSpectatorStatus();
332 entity surv_LastPlayerForTeam(entity this)
334 entity last_pl = NULL;
335 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
336 if (!IS_DEAD(it) && this.survival_status == it.survival_status)
347 void surv_LastPlayerForTeam_Notify(entity this)
349 if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
351 entity pl = surv_LastPlayerForTeam(this);
353 Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE);
357 MUTATOR_HOOKFUNCTION(surv, PlayerDies)
359 entity frag_attacker = M_ARGV(1, entity);
360 entity frag_target = M_ARGV(2, entity);
361 float frag_deathtype = M_ARGV(3, float);
363 surv_LastPlayerForTeam_Notify(frag_target);
364 if (!allowed_to_spawn)
366 frag_target.respawn_flags = RESPAWN_SILENT;
367 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
368 frag_target.respawn_time = time + 2;
370 frag_target.respawn_flags |= RESPAWN_FORCE;
373 eliminatedPlayers.SendFlags |= 1;
374 if (IS_BOT_CLIENT(frag_target))
375 bot_clearqueue(frag_target);
378 // killed an ally! punishment is death
379 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))
380 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) // don't autokill if the round hasn't started yet
381 Damage(frag_attacker, frag_attacker, frag_attacker, 100000, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
385 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
387 entity player = M_ARGV(0, entity);
389 if (IS_PLAYER(player) && !IS_DEAD(player))
390 surv_LastPlayerForTeam_Notify(player);
394 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
396 entity player = M_ARGV(0, entity);
397 bool is_forced = M_ARGV(1, bool);
398 if (is_forced && INGAME(player))
399 INGAME_STATUS_CLEAR(player);
401 if (IS_PLAYER(player) && !IS_DEAD(player))
402 surv_LastPlayerForTeam_Notify(player);
403 if (player.killindicator_teamchange == -2) // player wants to spectate
404 INGAME_STATUS_CLEAR(player);
406 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
408 eliminatedPlayers.SendFlags |= 1;
411 player.survival_validkills = 0;
412 player.survival_status = 0;
413 return false; // allow team reset
415 return true; // prevent team reset
418 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
420 // announce remaining frags?
424 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
426 entity frag_attacker = M_ARGV(0, entity);
427 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
428 frag_attacker.survival_validkills += M_ARGV(2, float);
429 M_ARGV(2, float) = 0; // score will be given to the winner when the round ends
433 MUTATOR_HOOKFUNCTION(surv, AddPlayerScore)
435 entity scorefield = M_ARGV(0, entity);
436 if(scorefield == SP_KILLS || scorefield == SP_DEATHS || scorefield == SP_SUICIDES || scorefield == SP_DMG || scorefield == SP_DMGTAKEN)
437 M_ARGV(1, float) = 0; // don't report that the player has killed or been killed, that would out them as a hunter!
440 MUTATOR_HOOKFUNCTION(surv, CalculateRespawnTime)
442 // no respawn calculations needed, player is forced to spectate anyway
446 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
448 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
449 if (IS_PLAYER(it) || INGAME_JOINED(it))
456 MUTATOR_HOOKFUNCTION(surv, ClientCommand_Spectate)
458 entity player = M_ARGV(0, entity);
462 // they're going to spec, we can do other checks
463 if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
464 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
465 return MUT_SPECCMD_FORCE;
468 return MUT_SPECCMD_CONTINUE;
471 MUTATOR_HOOKFUNCTION(surv, BotShouldAttack)
473 entity bot = M_ARGV(0, entity);
474 entity targ = M_ARGV(1, entity);
476 if(targ.survival_status == bot.survival_status)