Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
sv_survival.qc
Go to the documentation of this file.
1#include "sv_survival.qh"
2
9
12
13#define STATUS_SEND_RESET 1
14#define STATUS_SEND_HUNTERS 2
15
16bool SurvivalStatuses_SendEntity(entity this, entity dest, float sendflags)
17{
18 // always send hunters their own status and their allies
19 if (dest.survival_status == SURV_STATUS_HUNTER)
20 sendflags |= STATUS_SEND_HUNTERS;
21
22 WriteHeader(MSG_ENTITY, ENT_CLIENT_SURVIVALSTATUSES);
23 WriteByte(MSG_ENTITY, sendflags);
24 if (sendflags & STATUS_SEND_HUNTERS)
25 {
26 for (int i = 1; i <= maxclients; i += 8)
27 {
28 int f = 0;
29 entity e = edict_num(i);
30
31 for (int b = 0; b < 8; ++b, e = nextent(e))
32 {
33 bool is_hunter = (INGAME(e) && e.survival_status == SURV_STATUS_HUNTER);
34 if (is_hunter)
35 f |= BIT(b);
36 }
38 }
39 }
40 //print(sprintf("sent flags %f to %s\n", sendflags, dest.netname));
41 return true;
42}
43
45{
47 {
48 backtrace("Can't spawn survivalStatuses again!");
49 return;
50 }
52}
53
54void Surv_UpdateScores(bool timed_out)
55{
56 // give players their hard-earned kills now that the round is over
57 FOREACH_CLIENT(true,
58 {
59 it.totalfrags += it.survival_validkills;
60 if(it.survival_validkills)
61 GameRules_scoring_add(it, SCORE, it.survival_validkills);
62 it.survival_validkills = 0;
63 // player survived the round
64 if(IS_PLAYER(it) && !IS_DEAD(it))
65 {
66 if(autocvar_g_survival_reward_survival && timed_out && it.survival_status == SURV_STATUS_PREY)
67 GameRules_scoring_add(it, SCORE, 1); // reward survivors who make it to the end of the round time limit
68 if(it.survival_status == SURV_STATUS_PREY)
69 GameRules_scoring_add(it, SURV_SURVIVALS, 1);
70 else if(it.survival_status == SURV_STATUS_HUNTER)
71 GameRules_scoring_add(it, SURV_HUNTS, 1);
72 }
73 });
74}
75
77{
80 {
81 // if the match times out, survivors win too!
82 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
83 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
86 });
87
89
90 allowed_to_spawn = false;
91 game_stopped = true;
94 return 1;
95 }
96
97 int survivor_count = 0, hunter_count = 0;
99 {
100 if(it.survival_status == SURV_STATUS_PREY)
101 ++survivor_count;
102 else if(it.survival_status == SURV_STATUS_HUNTER)
103 ++hunter_count;
104 });
105 if(survivor_count > 0 && hunter_count > 0)
106 {
107 // Dr. Jaska:
108 // reset delay time here only for consistency
109 // Survival players currently have no known ways to resurrect
111 return 0;
112 }
113
114 // delay round ending a bit
117 && round_handler_GetEndTime() - time > 0) // don't delay past timelimit
118 {
120 {
122 return 0;
123 }
125 {
126 return 0;
127 }
128 }
129
130 if(hunter_count > 0) // hunters win
131 {
132 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_HUNTER_WIN);
133 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_HUNTER_WIN);
134 }
135 else if(survivor_count > 0) // survivors win
136 {
137 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
138 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
139 }
140 else
141 {
142 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
143 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
144 }
145
146 Surv_UpdateScores(false);
147
148 allowed_to_spawn = false;
149 game_stopped = true;
152
155 });
156
157 return 1;
158}
159
161{
163 int playercount = 0;
164 FOREACH_CLIENT(true,
165 {
166 if(IS_PLAYER(it) && !IS_DEAD(it))
167 {
168 ++playercount;
169 it.survival_status = SURV_STATUS_PREY;
170 }
171 else
172 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!
173 it.survival_validkills = 0;
174 });
175 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
176 int total_hunters = 0;
178 {
179 if(total_hunters >= hunter_count)
180 break;
181 ++total_hunters;
182 it.survival_status = SURV_STATUS_HUNTER;
183 });
185
186 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
187 {
188 if(it.survival_status == SURV_STATUS_PREY)
189 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR);
190 else if(it.survival_status == SURV_STATUS_HUNTER)
191 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_HUNTER);
192 });
193}
194
196{
197 static int prev_missing_players;
198 allowed_to_spawn = true;
199 int playercount = 0;
200 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
201 {
202 ++playercount;
203 });
204 // NOTE missing_teams_mask stat is used for missing players in ffa games
205 if (playercount >= 2)
206 {
207 if(prev_missing_players > 0)
209 prev_missing_players = -1;
210 return true;
211 }
212 if(playercount == 0)
213 {
214 if(prev_missing_players > 0)
216 prev_missing_players = -1;
217 return false;
218 }
219 // if we get here, only 1 player is missing
220 if(prev_missing_players != 1)
221 {
223 prev_missing_players = 1;
224 }
225 return false;
226}
227
229{
230 if(INGAME_JOINED(e) && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
231 return true;
232 if(INGAME_JOINING(e))
233 return true;
234 return false;
235}
236
237void surv_Initialize() // run at the start of a match, initiates gametype
238{
240 field(SP_SURV_SURVIVALS, "survivals", 0);
241 field(SP_SURV_HUNTS, "hunts", SFL_SORT_PRIO_SECONDARY);
242 });
243
244 allowed_to_spawn = true;
249}
250
251
252// ==============
253// Hook Functions
254// ==============
255
256MUTATOR_HOOKFUNCTION(surv, ClientObituary)
257{
258 // in survival, announcing a frag would tell everyone who the hunter is
259 entity frag_attacker = M_ARGV(1, entity);
261 if(IS_PLAYER(frag_attacker) && frag_attacker != frag_target)
262 {
263 float frag_deathtype = M_ARGV(3, float);
264 entity wep_ent = M_ARGV(4, entity);
265 // "team" kill, a point is awarded to the player by default so we must take it away plus an extra one
266 // unless the player is going to be punished for suicide, in which case just remove one
267 if(frag_attacker.survival_status == frag_target.survival_status)
268 GiveFrags(frag_attacker, frag_target, ((autocvar_g_survival_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
269 }
270
271 if(frag_attacker.survival_status == SURV_STATUS_HUNTER)
272 M_ARGV(5, bool) = true; // anonymous attacker
273}
274
275MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
276{
277 entity player = M_ARGV(0, entity);
278
279 player.survival_status = 0;
280 player.survival_validkills = 0;
282 if (!warmup_stage)
283 eliminatedPlayers.SendFlags = 0xFFFFFF;
284}
285
286MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
287{
288 entity player = M_ARGV(0, entity);
289
290 // spectators / observers that weren't playing can join; they are
291 // immediately forced to observe in the PutClientInServer hook
292 // this way they are put in a team and can play in the next round
293 if (!allowed_to_spawn && INGAME(player))
294 return true;
295 return false;
296}
297
299{
300 entity player = M_ARGV(0, entity);
301
302 if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
303 {
304 TRANSMUTE(Observer, player);
305 if (CS(player).jointime != time && !INGAME(player)) // not when connecting
306 {
308 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
309 }
310 }
311
312 if (!warmup_stage)
313 eliminatedPlayers.SendFlags = 0xFFFFFF;
314}
315
316MUTATOR_HOOKFUNCTION(surv, reset_map_players)
317{
318 FOREACH_CLIENT(true, {
319 CS(it).killcount = 0;
320 it.survival_status = 0;
321 if (INGAME(it) || IS_BOT_CLIENT(it))
322 {
323 TRANSMUTE(Player, it);
326 }
327 });
330 return true;
331}
332
333MUTATOR_HOOKFUNCTION(surv, reset_map_global)
334{
335 allowed_to_spawn = true;
336 return true;
337}
338
339MUTATOR_HOOKFUNCTION(surv, MatchEnd_BeforeScores)
340{
342 return true;
343}
344
346{
347 entity last_pl = NULL;
348 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
349 if (!IS_DEAD(it) && this.survival_status == it.survival_status)
350 {
351 if (!last_pl)
352 last_pl = it;
353 else
354 return NULL;
355 }
356 });
357 return last_pl;
358}
359
361{
363 {
365 if (pl)
366 Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE);
367 }
368}
369
370MUTATOR_HOOKFUNCTION(surv, PlayerDies)
371{
372 entity frag_attacker = M_ARGV(1, entity);
374 float frag_deathtype = M_ARGV(3, float);
375
377 if (!allowed_to_spawn)
378 {
379 frag_target.respawn_flags = RESPAWN_SILENT;
380 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
381 frag_target.respawn_time = time + 2;
382 }
383 frag_target.respawn_flags |= RESPAWN_FORCE;
384 if (!warmup_stage)
385 {
386 eliminatedPlayers.SendFlags = 0xFFFFFF;
389 }
390
391 // killed an ally! punishment is death
392 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))
393 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) // don't autokill if the round hasn't started yet
394 Damage(frag_attacker, frag_attacker, frag_attacker, 100000, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
395 return true;
396}
397
399{
400 entity player = M_ARGV(0, entity);
401
402 if (IS_PLAYER(player) && !IS_DEAD(player))
404 return true;
405}
406
407MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
408{
409 entity player = M_ARGV(0, entity);
410 bool is_forced = M_ARGV(1, bool);
411 if (is_forced && INGAME(player))
412 INGAME_STATUS_CLEAR(player);
413
414 if (IS_PLAYER(player) && !IS_DEAD(player))
416 if (player.killindicator_teamchange == -2) // player wants to spectate
417 INGAME_STATUS_CLEAR(player);
418 if (INGAME(player))
419 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
420 if (!warmup_stage)
421 eliminatedPlayers.SendFlags = 0xFFFFFF;
422 if (!INGAME(player))
423 {
424 player.survival_validkills = 0;
425 player.survival_status = 0;
426 return false; // allow team reset
427 }
428 return true; // prevent team reset
429}
430
431MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
432{
433 // announce remaining frags?
434 return true;
435}
436
437MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
438{
439 entity frag_attacker = M_ARGV(0, entity);
441 frag_attacker.survival_validkills += M_ARGV(2, float);
442 M_ARGV(2, float) = 0; // score will be given to the winner when the round ends
443 return true;
444}
445
446MUTATOR_HOOKFUNCTION(surv, AddPlayerScore)
447{
448 entity scorefield = M_ARGV(0, entity);
449 if(scorefield == SP_KILLS || scorefield == SP_DEATHS || scorefield == SP_SUICIDES || scorefield == SP_DMG || scorefield == SP_DMGTAKEN)
450 M_ARGV(1, float) = 0; // don't report that the player has killed or been killed, that would out them as a hunter!
451}
452
453MUTATOR_HOOKFUNCTION(surv, CalculateRespawnTime)
454{
455 // no respawn calculations needed, player is forced to spectate anyway
456 return true;
457}
458
460{
462 if (IS_PLAYER(it) || INGAME_JOINED(it))
463 ++M_ARGV(0, int);
464 ++M_ARGV(1, int);
465 });
466 return true;
467}
468
469MUTATOR_HOOKFUNCTION(surv, ClientCommand_Spectate)
470{
471 entity player = M_ARGV(0, entity);
472
473 if (INGAME(player))
474 {
475 // they're going to spec, we can do other checks
476 if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
477 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
478 return MUT_SPECCMD_FORCE;
479 }
480
482}
483
484MUTATOR_HOOKFUNCTION(surv, Bot_ForbidAttack)
485{
486 entity bot = M_ARGV(0, entity);
487 entity targ = M_ARGV(1, entity);
488
489 if(targ.survival_status == bot.survival_status)
490 return true;
491}
void bot_relinkplayerlist()
Definition bot.qc:424
const int CBC_ORDER_FIRST
Definition base.qh:10
#define MUTATOR_HOOKFUNCTION(...)
Definition base.qh:335
const int CBC_ORDER_EXCLUSIVE
Definition base.qh:12
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition bits.qh:8
#define boolean(value)
Definition bool.qh:9
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
int survival_status
Definition main.qc:567
bool warmup_stage
Definition main.qh:120
#define M_ARGV(x, type)
Definition events.qh:17
#define IS_DEAD(s)
Definition player.qh:244
#define IS_PLAYER(s)
Definition player.qh:242
const int SFL_SORT_PRIO_SECONDARY
Scoring priority (NOTE: PRIMARY is used for fraglimit) NOTE: SFL_SORT_PRIO_SECONDARY value must be lo...
Definition scores.qh:133
const int SFL_SORT_PRIO_PRIMARY
Definition scores.qh:134
int missing_teams_mask
Definition stats.qh:85
float game_stopped
Definition stats.qh:81
const int FRAGS_PLAYER_OUT_OF_GAME
Definition constants.qh:5
float maxclients
float time
void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition damage.qc:493
void GiveFrags(entity attacker, entity targ, float f, int deathtype,.entity weaponentity)
Definition damage.qc:43
#define DMG_NOWEP
Definition damage.qh:104
void EliminatedPlayers_Init(float(entity) isEliminated_func)
entity eliminatedPlayers
Definition elimination.qh:3
void bot_clearqueue(entity bot)
Definition scripting.qc:21
vector dest
Definition jumppads.qh:54
#define PutClientInServer
Definition _all.inc:246
#define ClientDisconnect
Definition _all.inc:242
const int MSG_ENTITY
Definition net.qh:156
#define WriteHeader(to, id)
Definition net.qh:265
void Net_LinkEntity(entity e, bool docull, float dt, bool(entity this, entity to, int sendflags) sendfunc)
Definition net.qh:167
#define backtrace(msg)
Definition log.qh:99
entity nextent(entity e)
float bound(float min, float value, float max)
float min(float f,...)
void WriteByte(float data, float dest, float desto)
float floor(float f)
void Send_Notification(NOTIF broadcast, entity client, MSG net_type, Notification net_name,...count)
Definition all.qc:1573
#define TRANSMUTE(cname, this,...)
Definition oo.qh:136
#define new_pure(class)
purely logical entities (not linked to the area grid)
Definition oo.qh:67
#define NULL
Definition post.qh:14
void round_handler_Init(float the_delay, float the_count, float the_round_timelimit)
void round_handler_Spawn(bool() canRoundStart_func, bool() canRoundEnd_func, void() roundStart_func)
#define round_handler_IsActive()
#define round_handler_GetEndTime()
#define round_handler_SetEndDelayTime(t)
#define round_handler_GetEndDelayTime()
#define round_handler_IsRoundStarted()
#define round_handler_ResetEndDelayTime()
const int RESPAWN_SILENT
Definition client.qh:327
bool autocvar_sv_spectate
Definition client.qh:57
float jointime
Definition client.qh:66
const int RESPAWN_FORCE
Definition client.qh:326
#define ITEM_DAMAGE_NEEDKILL(dt)
Definition items.qh:123
@ MUT_SPECCMD_FORCE
Definition events.qh:1006
@ MUT_SPECCMD_CONTINUE
Definition events.qh:1004
ClientState CS(Client this)
Definition state.qh:47
const int SURV_STATUS_HUNTER
Definition survival.qh:38
const int SURV_STATUS_PREY
Definition survival.qh:37
bool allowed_to_spawn
entity frag_target
Definition sv_ctf.qc:2314
void nades_RemovePlayer(entity this)
Remove nades and bonus nades from a player.
Definition sv_nades.qc:914
#define INGAME_STATUS_JOINED
Definition sv_rules.qh:17
#define INGAME_STATUS_JOINING
Definition sv_rules.qh:16
#define INGAME(it)
Definition sv_rules.qh:24
#define GameRules_scoring_add(client, fld, value)
Definition sv_rules.qh:85
#define INGAME_STATUS_CLEAR(it)
Definition sv_rules.qh:22
#define INGAME_JOINED(it)
Definition sv_rules.qh:25
#define INGAME_STATUS_SET(it, s)
Definition sv_rules.qh:21
#define GameRules_scoring(teams, spprio, stprio, fields)
Definition sv_rules.qh:58
#define INGAME_JOINING(it)
Definition sv_rules.qh:26
bool surv_isEliminated(entity e)
bool Surv_CheckPlayers()
void Surv_RoundStart()
float Surv_CheckWinner()
float autocvar_g_survival_round_enddelay
Definition sv_survival.qc:8
void surv_LastPlayerForTeam_Notify(entity this)
float autocvar_g_survival_warmup
Definition sv_survival.qc:5
void surv_Initialize()
float autocvar_g_survival_round_timelimit
Definition sv_survival.qc:4
bool SurvivalStatuses_SendEntity(entity this, entity dest, float sendflags)
#define STATUS_SEND_RESET
entity survivalStatuses
bool autocvar_g_survival_punish_teamkill
Definition sv_survival.qc:6
bool autocvar_g_survival_reward_survival
Definition sv_survival.qc:7
entity surv_LastPlayerForTeam(entity this)
void Surv_UpdateScores(bool timed_out)
void SurvivalStatuses_Init()
#define STATUS_SEND_HUNTERS
float autocvar_g_survival_hunter_count
Definition sv_survival.qc:3
#define IS_OBSERVER(v)
Definition utils.qh:11
#define FOREACH_CLIENT_RANDOM(cond, body)
Definition utils.qh:58
#define IS_SPEC(v)
Definition utils.qh:10
#define IS_REAL_CLIENT(v)
Definition utils.qh:17
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:52
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition utils.qh:15
void MatchEnd_RestoreSpectatorStatus()
Definition world.qc:1396