]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc
Transifex autosync
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / gamemodes / gamemode / survival / sv_survival.qc
1 #include "sv_survival.qh"
2
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;
8
9 void nades_Clear(entity player);
10
11 entity survivalStatuses;
12 void SurvivalStatuses_Init();
13
14 void SurvivalStatuses_Send()
15 {
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;
19 }
20
21 bool SurvivalStatuses_SendEntity(entity this, entity dest, float sendflags)
22 {
23         Stream out = MSG_ENTITY;
24         WriteHeader(out, ENT_CLIENT_SURVIVALSTATUSES);
25
26         // TODO: optimize this instead of always setting it on
27         sendflags = BIT(0); // reset all flags and make all players survivors
28
29         if ((dest.survival_status == SURV_STATUS_HUNTER) || round_handler_AwaitingNextRound())
30                 sendflags |= BIT(1); // send hunter statuses
31
32         serialize(byte, out, sendflags);
33         if (sendflags & BIT(1)) {
34                 for (int i = 1; i <= maxclients; i += 8) {
35                         int f = 0;
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);
39                                 if (is_hunter)
40                                         f |= BIT(b);
41                         }
42                         serialize(byte, out, f);
43                 }
44         }
45         //print(sprintf("sent flags %f to %s\n", sendflags, dest.netname));
46         return true;
47 }
48
49 void SurvivalStatuses_Init()
50 {
51         if(survivalStatuses)
52         {
53                 backtrace("Can't spawn survivalStatuses again!");
54                 return;
55         }
56         Net_LinkEntity(survivalStatuses = new_pure(survivalStatuses), false, 0, SurvivalStatuses_SendEntity);
57 }
58
59 void Surv_UpdateScores(bool timed_out)
60 {
61         // give players their hard-earned kills now that the round is over
62         FOREACH_CLIENT(true,
63         {
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))
70                 {
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);
77                 }
78         });
79 }
80
81 float Surv_CheckWinner()
82 {
83         if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
84         {
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);
88                 FOREACH_CLIENT(true,
89                 {
90                         if(IS_PLAYER(it))
91                                 nades_Clear(it);
92                 });
93
94                 Surv_UpdateScores(true);
95
96                 allowed_to_spawn = false;
97                 game_stopped = true;
98                 round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
99                 SurvivalStatuses_Send();
100                 return 1;
101         }
102
103         int survivor_count = 0, hunter_count = 0;
104         FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
105         {
106                 if(it.survival_status == SURV_STATUS_PREY)
107                         survivor_count++;
108                 else if(it.survival_status == SURV_STATUS_HUNTER)
109                         hunter_count++;
110         });
111         if(survivor_count > 0 && hunter_count > 0)
112         {
113                 return 0;
114         }
115
116         if(hunter_count > 0) // hunters win
117         {
118                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_HUNTER_WIN);
119                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_HUNTER_WIN);
120         }
121         else if(survivor_count > 0) // survivors win
122         {
123                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
124                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
125         }
126         else
127         {
128                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
129                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
130         }
131
132         Surv_UpdateScores(false);
133
134         allowed_to_spawn = false;
135         game_stopped = true;
136         round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
137         SurvivalStatuses_Send();
138
139         FOREACH_CLIENT(true,
140         {
141                 if(IS_PLAYER(it))
142                         nades_Clear(it);
143         });
144
145         return 1;
146 }
147
148 void Surv_RoundStart()
149 {
150         allowed_to_spawn = boolean(warmup_stage);
151         int playercount = 0;
152         FOREACH_CLIENT(true,
153         {
154                 if(IS_PLAYER(it) && !IS_DEAD(it))
155                 {
156                         ++playercount;
157                         it.survival_status = SURV_STATUS_PREY;
158                 }
159                 else
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;
162         });
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),
166         {
167                 if(total_hunters >= hunter_count)
168                         break;
169                 total_hunters++;
170                 it.survival_status = SURV_STATUS_HUNTER;
171         });
172         SurvivalStatuses_Send();
173
174         FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
175         {
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);
180         });
181 }
182
183 bool Surv_CheckPlayers()
184 {
185         static int prev_missing_players;
186         allowed_to_spawn = true;
187         int playercount = 0;
188         FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
189         {
190                 ++playercount;
191         });
192         if (playercount >= 2)
193         {
194                 if(prev_missing_players > 0)
195                         Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
196                 prev_missing_players = -1;
197                 return true;
198         }
199         if(playercount == 0)
200         {
201                 if(prev_missing_players > 0)
202                         Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
203                 prev_missing_players = -1;
204                 return false;
205         }
206         // if we get here, only 1 player is missing
207         if(prev_missing_players != 1)
208         {
209                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, 1);
210                 prev_missing_players = 1;
211         }
212         return false;
213 }
214
215 bool surv_isEliminated(entity e)
216 {
217         if(INGAME_JOINED(e) && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
218                 return true;
219         if(INGAME_JOINING(e))
220                 return true;
221         return false;
222 }
223
224 void surv_Initialize() // run at the start of a match, initiates game mode
225 {
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);
229         });
230
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();
236 }
237
238
239 // ==============
240 // Hook Functions
241 // ==============
242
243 MUTATOR_HOOKFUNCTION(surv, ClientObituary)
244 {
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)
249         {
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);
256         }
257
258         if(frag_attacker.survival_status == SURV_STATUS_HUNTER)
259                 M_ARGV(5, bool) = true; // anonymous attacker
260 }
261
262 MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
263 {
264         entity player = M_ARGV(0, entity);
265
266         player.survival_status = 0;
267         player.survival_validkills = 0;
268         INGAME_STATUS_SET(player, INGAME_STATUS_JOINED);
269         if (!warmup_stage)
270                 eliminatedPlayers.SendFlags |= 1;
271 }
272
273 MUTATOR_HOOKFUNCTION(surv, ForbidSpawn)
274 {
275         entity player = M_ARGV(0, entity);
276
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))
281                 return true;
282         return false;
283 }
284
285 MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
286 {
287         entity player = M_ARGV(0, entity);
288
289         if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
290         {
291                 TRANSMUTE(Observer, player);
292                 if (CS(player).jointime != time && !INGAME(player)) // not when connecting
293                 {
294                         INGAME_STATUS_SET(player, INGAME_STATUS_JOINING);
295                         Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
296                 }
297         }
298
299         if (!warmup_stage)
300                 eliminatedPlayers.SendFlags |= 1;
301 }
302
303 MUTATOR_HOOKFUNCTION(surv, reset_map_players)
304 {
305         FOREACH_CLIENT(true, {
306                 CS(it).killcount = 0;
307                 it.survival_status = 0;
308                 if (INGAME(it) || IS_BOT_CLIENT(it))
309                 {
310                         TRANSMUTE(Player, it);
311                         INGAME_STATUS_SET(it, INGAME_STATUS_JOINED);
312                         PutClientInServer(it);
313                 }
314         });
315         bot_relinkplayerlist();
316         SurvivalStatuses_Send();
317         return true;
318 }
319
320 MUTATOR_HOOKFUNCTION(surv, reset_map_global)
321 {
322         allowed_to_spawn = true;
323         return true;
324 }
325
326 MUTATOR_HOOKFUNCTION(surv, MatchEnd_BeforeScores)
327 {
328         MatchEnd_RestoreSpectatorStatus();
329         return true;
330 }
331
332 entity surv_LastPlayerForTeam(entity this)
333 {
334         entity last_pl = NULL;
335         FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
336                 if (!IS_DEAD(it) && this.survival_status == it.survival_status)
337                 {
338                         if (!last_pl)
339                                 last_pl = it;
340                         else
341                                 return NULL;
342                 }
343         });
344         return last_pl;
345 }
346
347 void surv_LastPlayerForTeam_Notify(entity this)
348 {
349         if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
350         {
351                 entity pl = surv_LastPlayerForTeam(this);
352                 if (pl)
353                         Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE);
354         }
355 }
356
357 MUTATOR_HOOKFUNCTION(surv, PlayerDies)
358 {
359         entity frag_attacker = M_ARGV(1, entity);
360         entity frag_target = M_ARGV(2, entity);
361         float frag_deathtype = M_ARGV(3, float);
362
363         surv_LastPlayerForTeam_Notify(frag_target);
364         if (!allowed_to_spawn)
365         {
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;
369         }
370         frag_target.respawn_flags |= RESPAWN_FORCE;
371         if (!warmup_stage)
372         {
373                 eliminatedPlayers.SendFlags |= 1;
374                 if (IS_BOT_CLIENT(frag_target))
375                         bot_clearqueue(frag_target);
376         }
377
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');
382         return true;
383 }
384
385 MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
386 {
387         entity player = M_ARGV(0, entity);
388
389         if (IS_PLAYER(player) && !IS_DEAD(player))
390                 surv_LastPlayerForTeam_Notify(player);
391         return true;
392 }
393
394 MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
395 {
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);
400
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);
405         if (INGAME(player))
406                 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
407         if (!warmup_stage)
408                 eliminatedPlayers.SendFlags |= 1;
409         if (!INGAME(player))
410         {
411                 player.survival_validkills = 0;
412                 player.survival_status = 0;
413                 return false;  // allow team reset
414         }
415         return true;  // prevent team reset
416 }
417
418 MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
419 {
420         // announce remaining frags?
421         return true;
422 }
423
424 MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
425 {
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
430         return true;
431 }
432
433 MUTATOR_HOOKFUNCTION(surv, AddPlayerScore)
434 {
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!
438 }
439
440 MUTATOR_HOOKFUNCTION(surv, CalculateRespawnTime)
441 {
442         // no respawn calculations needed, player is forced to spectate anyway
443         return true;
444 }
445
446 MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
447 {
448         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
449                 if (IS_PLAYER(it) || INGAME_JOINED(it))
450                         ++M_ARGV(0, int);
451                 ++M_ARGV(1, int);
452         });
453         return true;
454 }
455
456 MUTATOR_HOOKFUNCTION(surv, ClientCommand_Spectate)
457 {
458         entity player = M_ARGV(0, entity);
459
460         if (INGAME(player))
461         {
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;
466         }
467
468         return MUT_SPECCMD_CONTINUE;
469 }
470
471 MUTATOR_HOOKFUNCTION(surv, BotShouldAttack)
472 {
473         entity bot = M_ARGV(0, entity);
474         entity targ = M_ARGV(1, entity);
475
476         if(targ.survival_status == bot.survival_status)
477                 return true;
478 }