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 Stream out = MSG_ENTITY;
19 WriteHeader(out, ENT_CLIENT_SURVIVALSTATUSES);
20
21 // always send hunters their own status and their allies
22 if (dest.survival_status == SURV_STATUS_HUNTER)
23 sendflags |= STATUS_SEND_HUNTERS;
24
25 serialize(byte, out, sendflags);
26 if (sendflags & STATUS_SEND_HUNTERS)
27 {
28 for (int i = 1; i <= maxclients; i += 8)
29 {
30 int f = 0;
31 entity e = edict_num(i);
32
33 for (int b = 0; b < 8; ++b, e = nextent(e))
34 {
35 bool is_hunter = (INGAME(e) && e.survival_status == SURV_STATUS_HUNTER);
36 if (is_hunter)
37 f |= BIT(b);
38 }
39 serialize(byte, out, f);
40 }
41 }
42 //print(sprintf("sent flags %f to %s\n", sendflags, dest.netname));
43 return true;
44}
45
47{
49 {
50 backtrace("Can't spawn survivalStatuses again!");
51 return;
52 }
54}
55
56void Surv_UpdateScores(bool timed_out)
57{
58 // give players their hard-earned kills now that the round is over
59 FOREACH_CLIENT(true,
60 {
61 it.totalfrags += it.survival_validkills;
62 if(it.survival_validkills)
63 GameRules_scoring_add(it, SCORE, it.survival_validkills);
64 it.survival_validkills = 0;
65 // player survived the round
66 if(IS_PLAYER(it) && !IS_DEAD(it))
67 {
68 if(autocvar_g_survival_reward_survival && timed_out && it.survival_status == SURV_STATUS_PREY)
69 GameRules_scoring_add(it, SCORE, 1); // reward survivors who make it to the end of the round time limit
70 if(it.survival_status == SURV_STATUS_PREY)
71 GameRules_scoring_add(it, SURV_SURVIVALS, 1);
72 else if(it.survival_status == SURV_STATUS_HUNTER)
73 GameRules_scoring_add(it, SURV_HUNTS, 1);
74 }
75 });
76}
77
79{
82 {
83 // if the match times out, survivors win too!
84 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
85 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
88 });
89
91
92 allowed_to_spawn = false;
93 game_stopped = true;
96 return 1;
97 }
98
99 int survivor_count = 0, hunter_count = 0;
100 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
101 {
102 if(it.survival_status == SURV_STATUS_PREY)
103 survivor_count++;
104 else if(it.survival_status == SURV_STATUS_HUNTER)
105 hunter_count++;
106 });
107 if(survivor_count > 0 && hunter_count > 0)
108 {
109 // Dr. Jaska:
110 // reset delay time here only for consistency
111 // Survival players currently have no known ways to resurrect
113 return 0;
114 }
115
116 // delay round ending a bit
119 && round_handler_GetEndTime() - time > 0) // don't delay past timelimit
120 {
122 {
124 return 0;
125 }
127 {
128 return 0;
129 }
130 }
131
132 if(hunter_count > 0) // hunters win
133 {
134 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_HUNTER_WIN);
135 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_HUNTER_WIN);
136 }
137 else if(survivor_count > 0) // survivors win
138 {
139 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
140 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
141 }
142 else
143 {
144 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
145 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
146 }
147
148 Surv_UpdateScores(false);
149
150 allowed_to_spawn = false;
151 game_stopped = true;
154
157 });
158
159 return 1;
160}
161
163{
165 int playercount = 0;
166 FOREACH_CLIENT(true,
167 {
168 if(IS_PLAYER(it) && !IS_DEAD(it))
169 {
170 ++playercount;
171 it.survival_status = SURV_STATUS_PREY;
172 }
173 else
174 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!
175 it.survival_validkills = 0;
176 });
177 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
178 int total_hunters = 0;
180 {
181 if(total_hunters >= hunter_count)
182 break;
183 total_hunters++;
184 it.survival_status = SURV_STATUS_HUNTER;
185 });
187
188 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
189 {
190 if(it.survival_status == SURV_STATUS_PREY)
191 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR);
192 else if(it.survival_status == SURV_STATUS_HUNTER)
193 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_HUNTER);
194 });
195}
196
198{
199 static int prev_missing_players;
200 allowed_to_spawn = true;
201 int playercount = 0;
202 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
203 {
204 ++playercount;
205 });
206 // NOTE missing_teams_mask stat is used for missing players in ffa games
207 if (playercount >= 2)
208 {
209 if(prev_missing_players > 0)
211 prev_missing_players = -1;
212 return true;
213 }
214 if(playercount == 0)
215 {
216 if(prev_missing_players > 0)
218 prev_missing_players = -1;
219 return false;
220 }
221 // if we get here, only 1 player is missing
222 if(prev_missing_players != 1)
223 {
225 prev_missing_players = 1;
226 }
227 return false;
228}
229
231{
232 if(INGAME_JOINED(e) && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
233 return true;
234 if(INGAME_JOINING(e))
235 return true;
236 return false;
237}
238
239void surv_Initialize() // run at the start of a match, initiates gametype
240{
242 field(SP_SURV_SURVIVALS, "survivals", 0);
243 field(SP_SURV_HUNTS, "hunts", SFL_SORT_PRIO_SECONDARY);
244 });
245
246 allowed_to_spawn = true;
251}
252
253
254// ==============
255// Hook Functions
256// ==============
257
258MUTATOR_HOOKFUNCTION(surv, ClientObituary)
259{
260 // in survival, announcing a frag would tell everyone who the hunter is
261 entity frag_attacker = M_ARGV(1, entity);
263 if(IS_PLAYER(frag_attacker) && frag_attacker != frag_target)
264 {
265 float frag_deathtype = M_ARGV(3, float);
266 entity wep_ent = M_ARGV(4, entity);
267 // "team" kill, a point is awarded to the player by default so we must take it away plus an extra one
268 // unless the player is going to be punished for suicide, in which case just remove one
269 if(frag_attacker.survival_status == frag_target.survival_status)
270 GiveFrags(frag_attacker, frag_target, ((autocvar_g_survival_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
271 }
272
273 if(frag_attacker.survival_status == SURV_STATUS_HUNTER)
274 M_ARGV(5, bool) = true; // anonymous attacker
275}
276
277MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
278{
279 entity player = M_ARGV(0, entity);
280
281 player.survival_status = 0;
282 player.survival_validkills = 0;
284 if (!warmup_stage)
285 eliminatedPlayers.SendFlags |= 1;
286}
287
288MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
289{
290 entity player = M_ARGV(0, entity);
291
292 // spectators / observers that weren't playing can join; they are
293 // immediately forced to observe in the PutClientInServer hook
294 // this way they are put in a team and can play in the next round
295 if (!allowed_to_spawn && INGAME(player))
296 return true;
297 return false;
298}
299
301{
302 entity player = M_ARGV(0, entity);
303
304 if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
305 {
306 TRANSMUTE(Observer, player);
307 if (CS(player).jointime != time && !INGAME(player)) // not when connecting
308 {
310 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
311 }
312 }
313
314 if (!warmup_stage)
315 eliminatedPlayers.SendFlags |= 1;
316}
317
318MUTATOR_HOOKFUNCTION(surv, reset_map_players)
319{
320 FOREACH_CLIENT(true, {
321 CS(it).killcount = 0;
322 it.survival_status = 0;
323 if (INGAME(it) || IS_BOT_CLIENT(it))
324 {
325 TRANSMUTE(Player, it);
328 }
329 });
332 return true;
333}
334
335MUTATOR_HOOKFUNCTION(surv, reset_map_global)
336{
337 allowed_to_spawn = true;
338 return true;
339}
340
341MUTATOR_HOOKFUNCTION(surv, MatchEnd_BeforeScores)
342{
344 return true;
345}
346
348{
349 entity last_pl = NULL;
350 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
351 if (!IS_DEAD(it) && this.survival_status == it.survival_status)
352 {
353 if (!last_pl)
354 last_pl = it;
355 else
356 return NULL;
357 }
358 });
359 return last_pl;
360}
361
363{
365 {
367 if (pl)
368 Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE);
369 }
370}
371
372MUTATOR_HOOKFUNCTION(surv, PlayerDies)
373{
374 entity frag_attacker = M_ARGV(1, entity);
376 float frag_deathtype = M_ARGV(3, float);
377
379 if (!allowed_to_spawn)
380 {
381 frag_target.respawn_flags = RESPAWN_SILENT;
382 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
383 frag_target.respawn_time = time + 2;
384 }
385 frag_target.respawn_flags |= RESPAWN_FORCE;
386 if (!warmup_stage)
387 {
388 eliminatedPlayers.SendFlags |= 1;
391 }
392
393 // killed an ally! punishment is death
394 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))
395 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) // don't autokill if the round hasn't started yet
396 Damage(frag_attacker, frag_attacker, frag_attacker, 100000, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
397 return true;
398}
399
401{
402 entity player = M_ARGV(0, entity);
403
404 if (IS_PLAYER(player) && !IS_DEAD(player))
406 return true;
407}
408
409MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
410{
411 entity player = M_ARGV(0, entity);
412 bool is_forced = M_ARGV(1, bool);
413 if (is_forced && INGAME(player))
414 INGAME_STATUS_CLEAR(player);
415
416 if (IS_PLAYER(player) && !IS_DEAD(player))
418 if (player.killindicator_teamchange == -2) // player wants to spectate
419 INGAME_STATUS_CLEAR(player);
420 if (INGAME(player))
421 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
422 if (!warmup_stage)
423 eliminatedPlayers.SendFlags |= 1;
424 if (!INGAME(player))
425 {
426 player.survival_validkills = 0;
427 player.survival_status = 0;
428 return false; // allow team reset
429 }
430 return true; // prevent team reset
431}
432
433MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
434{
435 // announce remaining frags?
436 return true;
437}
438
439MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
440{
441 entity frag_attacker = M_ARGV(0, entity);
443 frag_attacker.survival_validkills += M_ARGV(2, float);
444 M_ARGV(2, float) = 0; // score will be given to the winner when the round ends
445 return true;
446}
447
448MUTATOR_HOOKFUNCTION(surv, AddPlayerScore)
449{
450 entity scorefield = M_ARGV(0, entity);
451 if(scorefield == SP_KILLS || scorefield == SP_DEATHS || scorefield == SP_SUICIDES || scorefield == SP_DMG || scorefield == SP_DMGTAKEN)
452 M_ARGV(1, float) = 0; // don't report that the player has killed or been killed, that would out them as a hunter!
453}
454
455MUTATOR_HOOKFUNCTION(surv, CalculateRespawnTime)
456{
457 // no respawn calculations needed, player is forced to spectate anyway
458 return true;
459}
460
462{
464 if (IS_PLAYER(it) || INGAME_JOINED(it))
465 ++M_ARGV(0, int);
466 ++M_ARGV(1, int);
467 });
468 return true;
469}
470
471MUTATOR_HOOKFUNCTION(surv, ClientCommand_Spectate)
472{
473 entity player = M_ARGV(0, entity);
474
475 if (INGAME(player))
476 {
477 // they're going to spec, we can do other checks
478 if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
479 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
480 return MUT_SPECCMD_FORCE;
481 }
482
484}
485
486MUTATOR_HOOKFUNCTION(surv, BotShouldAttack)
487{
488 entity bot = M_ARGV(0, entity);
489 entity targ = M_ARGV(1, entity);
490
491 if(targ.survival_status == bot.survival_status)
492 return true;
493}
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:569
bool warmup_stage
Definition main.qh:120
#define M_ARGV(x, type)
Definition events.qh:17
#define IS_DEAD(s)
Definition player.qh:245
#define IS_PLAYER(s)
Definition player.qh:243
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:503
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
int Stream
Definition net.qh:234
#define serialize(T, stream,...)
Definition net.qh:243
const int MSG_ENTITY
Definition net.qh:115
#define WriteHeader(to, id)
Definition net.qh:221
void Net_LinkEntity(entity e, bool docull, float dt, bool(entity this, entity to, int sendflags) sendfunc)
Definition net.qh:123
#define backtrace(msg)
Definition log.qh:99
entity nextent(entity e)
float bound(float min, float value, float max)
float min(float f,...)
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:129
@ 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:2321
void nades_RemovePlayer(entity this)
Definition sv_nades.qc:885
#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:56
#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:50
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition utils.qh:15
void MatchEnd_RestoreSpectatorStatus()
Definition world.qc:1375