]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_lms.qc
Merge branch 'terencehill/LMS_improvements' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator / gamemode_lms.qc
1 #include "gamemode_lms.qh"
2
3 #include <common/mutators/mutator/instagib/items.qc>
4 #include <server/campaign.qh>
5 #include <server/command/_mod.qh>
6
7 int autocvar_g_lms_extra_lives;
8 bool autocvar_g_lms_join_anytime;
9 int autocvar_g_lms_last_join;
10 bool autocvar_g_lms_regenerate;
11
12 // main functions
13 float LMS_NewPlayerLives()
14 {
15         float fl;
16         fl = autocvar_fraglimit;
17         if(fl == 0)
18                 fl = 999;
19
20         // first player has left the game for dying too much? Nobody else can get in.
21         if(lms_lowest_lives < 1)
22                 return 0;
23
24         if(!autocvar_g_lms_join_anytime)
25                 if(lms_lowest_lives < fl - autocvar_g_lms_last_join)
26                         return 0;
27
28         return bound(1, lms_lowest_lives, fl);
29 }
30
31 void ClearWinners();
32
33 // LMS winning condition: game terminates if and only if there's at most one
34 // one player who's living lives. Top two scores being equal cancels the time
35 // limit.
36 int WinningCondition_LMS()
37 {
38         entity head, head2;
39         bool have_player = false;
40         bool have_players = false;
41
42         int l = LMS_NewPlayerLives();
43
44         head = find(NULL, classname, STR_PLAYER);
45         if(head)
46                 have_player = true;
47         head2 = find(head, classname, STR_PLAYER);
48         if(head2)
49                 have_players = true;
50
51         if(have_player)
52         {
53                 // we have at least one player
54                 if(have_players)
55                 {
56                         // two or more active players - continue with the game
57                 }
58                 else
59                 {
60                         // exactly one player?
61
62                         ClearWinners();
63                         SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
64
65                         if(l)
66                         {
67                                 // game still running (that is, nobody got removed from the game by a frag yet)? then continue
68                                 return WINNING_NO;
69                         }
70                         else
71                         {
72                                 // a winner!
73                                 // and assign him his first place
74                                 PlayerScore_Add(head, SP_LMS_RANK, 1);
75                                 if(warmup_stage)
76                                         return WINNING_NO;
77                                 else
78                                         return WINNING_YES;
79                         }
80                 }
81         }
82         else
83         {
84                 // nobody is playing at all...
85                 if(l)
86                 {
87                         // wait for players...
88                 }
89                 else
90                 {
91                         // SNAFU (maybe a draw game?)
92                         ClearWinners();
93                         LOG_TRACE("No players, ending game.");
94                         return WINNING_YES;
95                 }
96         }
97
98         // When we get here, we have at least two players who are actually LIVING,
99         // now check if the top two players have equal score.
100         WinningConditionHelper(NULL);
101
102         ClearWinners();
103         if(WinningConditionHelper_winner)
104                 WinningConditionHelper_winner.winning = true;
105         if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore)
106                 return WINNING_NEVER;
107
108         // Top two have different scores? Way to go for our beloved TIMELIMIT!
109         return WINNING_NO;
110 }
111
112 // mutator hooks
113 MUTATOR_HOOKFUNCTION(lms, reset_map_global)
114 {
115         lms_lowest_lives = 999;
116 }
117
118 MUTATOR_HOOKFUNCTION(lms, reset_map_players)
119 {
120         FOREACH_CLIENT(true, {
121                 TRANSMUTE(Player, it);
122                 it.frags = FRAGS_PLAYER;
123                 PlayerScore_Add(it, SP_LMS_LIVES, LMS_NewPlayerLives());
124                 PutClientInServer(it);
125         });
126 }
127
128 MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
129 {
130         entity player = M_ARGV(0, entity);
131
132         if(player.frags == FRAGS_SPECTATOR)
133                 TRANSMUTE(Observer, player);
134         else
135         {
136                 float tl = PlayerScore_Add(player, SP_LMS_LIVES, 0);
137                 if(tl < lms_lowest_lives)
138                         lms_lowest_lives = tl;
139                 if(tl <= 0)
140                         TRANSMUTE(Observer, player);
141                 if(warmup_stage)
142                         PlayerScore_Add(player, SP_LMS_RANK, -PlayerScore_Add(player, SP_LMS_RANK, 0));
143         }
144 }
145
146 MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
147 {
148         entity player = M_ARGV(0, entity);
149
150         if(warmup_stage)
151                 return false;
152         if(player.frags == FRAGS_SPECTATOR)
153                 return true;
154         if(PlayerScore_Add(player, SP_LMS_LIVES, 0) <= 0)
155         {
156                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
157                 return true;
158         }
159         return false;
160 }
161
162 MUTATOR_HOOKFUNCTION(lms, PlayerDies)
163 {
164         entity frag_target = M_ARGV(2, entity);
165
166         frag_target.respawn_flags |= RESPAWN_FORCE;
167 }
168
169 void lms_RemovePlayer(entity player)
170 {
171         static int quitters = 0;
172         float player_rank = PlayerScore_Add(player, SP_LMS_RANK, 0);
173         if (!player_rank)
174         {
175                 int pl_cnt = 0;
176                 FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
177                 if (player.lms_spectate_warning != 2)
178                 {
179                         player.frags = FRAGS_LMS_LOSER;
180                         PlayerScore_Add(player, SP_LMS_RANK, pl_cnt + 1);
181                 }
182                 else
183                 {
184                         lms_lowest_lives = 999;
185                         FOREACH_CLIENT(true, {
186                                 if (it.frags == FRAGS_LMS_LOSER)
187                                 {
188                                         float it_rank = PlayerScore_Add(it, SP_LMS_RANK, 0);
189                                         if (it_rank > player_rank && it_rank <= 256)
190                                                 PlayerScore_Add(it, SP_LMS_RANK, -1);
191                                         lms_lowest_lives = 0;
192                                 }
193                                 else if (it.frags != FRAGS_SPECTATOR)
194                                 {
195                                         float tl = PlayerScore_Add(it, SP_LMS_LIVES, 0);
196                                         if(tl < lms_lowest_lives)
197                                                 lms_lowest_lives = tl;
198                                 }
199                         });
200                         PlayerScore_Add(player, SP_LMS_RANK, 665 - quitters); // different from 666
201                         if(!warmup_stage)
202                         {
203                                 PlayerScore_Add(player, SP_LMS_LIVES, -PlayerScore_Add(player, SP_LMS_LIVES, 0));
204                                 ++quitters;
205                         }
206                         player.frags = FRAGS_LMS_LOSER;
207                         TRANSMUTE(Observer, player);
208                 }
209                 if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player
210                         lms_lowest_lives = 0; // end the game now!
211         }
212
213         if(player.killcount != FRAGS_SPECTATOR)
214                 if(PlayerScore_Add(player, SP_LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
215                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
216                 else
217                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
218 }
219
220 MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
221 {
222         entity player = M_ARGV(0, entity);
223
224         lms_RemovePlayer(player);
225 }
226
227 MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
228 {
229     entity player = M_ARGV(0, entity);
230
231         lms_RemovePlayer(player);
232         return true;  // prevent team reset
233 }
234
235 MUTATOR_HOOKFUNCTION(lms, ClientConnect)
236 {
237         entity player = M_ARGV(0, entity);
238
239         TRANSMUTE(Player, player);
240         campaign_bots_may_start = true;
241
242         if(PlayerScore_Add(player, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0)
243         {
244                 PlayerScore_Add(player, SP_LMS_RANK, 666); // mark as forced spectator for the hud code
245                 player.frags = FRAGS_SPECTATOR;
246         }
247 }
248
249 MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
250 {
251         entity player = M_ARGV(0, entity);
252
253         if(player.deadflag == DEAD_DYING)
254                 player.deadflag = DEAD_RESPAWNING;
255 }
256
257 MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
258 {
259         if(autocvar_g_lms_regenerate)
260                 return false;
261         return true;
262 }
263
264 MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
265 {
266         // forbode!
267         return true;
268 }
269
270 MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
271 {
272         entity frag_target = M_ARGV(1, entity);
273
274         if (!warmup_stage)
275         {
276                 // remove a life
277                 int tl = PlayerScore_Add(frag_target, SP_LMS_LIVES, -1);
278                 if(tl < lms_lowest_lives)
279                         lms_lowest_lives = tl;
280                 if(tl <= 0)
281                 {
282                         int pl_cnt = 0;
283                         FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
284                         frag_target.frags = FRAGS_LMS_LOSER;
285                         PlayerScore_Add(frag_target, SP_LMS_RANK, pl_cnt);
286                 }
287         }
288         M_ARGV(2, float) = 0; // frag score
289
290         return true;
291 }
292
293 MUTATOR_HOOKFUNCTION(lms, SetStartItems)
294 {
295         start_items &= ~IT_UNLIMITED_AMMO;
296         start_health       = warmup_start_health       = cvar("g_lms_start_health");
297         start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
298         start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
299         start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
300         start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
301         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
302         start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
303         start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
304 }
305
306 MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
307 {
308         // don't clear player score
309         return true;
310 }
311
312 MUTATOR_HOOKFUNCTION(lms, FilterItem)
313 {
314         entity item = M_ARGV(0, entity);
315
316         if(autocvar_g_lms_extra_lives)
317         if(item.itemdef == ITEM_ExtraLife)
318                 return false;
319
320         return true;
321 }
322
323 void lms_extralife(entity this)
324 {
325         StartItem(this, ITEM_ExtraLife);
326 }
327
328 MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
329 {
330         if (!autocvar_g_powerups) return false;
331         if (!autocvar_g_lms_extra_lives) return false;
332
333         entity ent = M_ARGV(0, entity);
334
335         // Can't use .itemdef here
336         if (ent.classname != "item_health_mega") return false;
337
338         entity e = spawn();
339         setthink(e, lms_extralife);
340
341         e.nextthink = time + 0.1;
342         e.spawnflags = ent.spawnflags;
343         e.noalign = ent.noalign;
344         setorigin(e, ent.origin);
345
346         return true;
347 }
348
349 MUTATOR_HOOKFUNCTION(lms, ItemTouch)
350 {
351         entity item = M_ARGV(0, entity);
352         entity toucher = M_ARGV(1, entity);
353
354         if(item.itemdef == ITEM_ExtraLife)
355         {
356                 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
357                 PlayerScore_Add(toucher, SP_LMS_LIVES, autocvar_g_lms_extra_lives);
358                 return MUT_ITEMTOUCH_PICKUP;
359         }
360
361         return MUT_ITEMTOUCH_CONTINUE;
362 }
363
364 MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
365 {
366         FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(
367                 ++M_ARGV(0, int); // activerealplayers
368                 ++M_ARGV(1, int); // realplayers
369         ));
370
371         return true;
372 }
373
374 MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
375 {
376     entity player = M_ARGV(0, entity);
377
378         if(warmup_stage || player.lms_spectate_warning)
379         {
380                 // for the forfeit message...
381                 player.lms_spectate_warning = 2;
382         }
383         else
384         {
385                 if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER)
386                 {
387                         player.lms_spectate_warning = 1;
388                         sprint(player, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
389                 }
390                 return MUT_SPECCMD_RETURN;
391         }
392         return MUT_SPECCMD_CONTINUE;
393 }
394
395 MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
396 {
397         M_ARGV(0, float) = WinningCondition_LMS();
398         return true;
399 }
400
401 MUTATOR_HOOKFUNCTION(lms, WantWeapon)
402 {
403         M_ARGV(2, bool) = true; // all weapons
404 }
405
406 MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
407 {
408         return true;
409 }
410
411 MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
412 {
413         if(gameover)
414         if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
415                 return true; // allow writing to this field in intermission as it is needed for newly joining players
416 }
417
418 // scoreboard stuff
419 void lms_ScoreRules()
420 {
421         ScoreRules_basics(0, 0, 0, false);
422         ScoreInfo_SetLabel_PlayerScore(SP_LMS_LIVES,    "lives",     SFL_SORT_PRIO_SECONDARY);
423         ScoreInfo_SetLabel_PlayerScore(SP_LMS_RANK,     "rank",      SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
424         ScoreRules_basics_end();
425 }
426
427 void lms_Initialize()
428 {
429         lms_lowest_lives = 9999;
430
431         lms_ScoreRules();
432 }