]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_lms.qc
Refactor game rules
[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                         if(IS_BOT_CLIENT(player))
180                                 bot_clear(player);
181                         player.frags = FRAGS_LMS_LOSER;
182                         PlayerScore_Add(player, SP_LMS_RANK, pl_cnt + 1);
183                 }
184                 else
185                 {
186                         lms_lowest_lives = 999;
187                         FOREACH_CLIENT(true, {
188                                 if (it.frags == FRAGS_LMS_LOSER)
189                                 {
190                                         float it_rank = PlayerScore_Add(it, SP_LMS_RANK, 0);
191                                         if (it_rank > player_rank && it_rank <= 256)
192                                                 PlayerScore_Add(it, SP_LMS_RANK, -1);
193                                         lms_lowest_lives = 0;
194                                 }
195                                 else if (it.frags != FRAGS_SPECTATOR)
196                                 {
197                                         float tl = PlayerScore_Add(it, SP_LMS_LIVES, 0);
198                                         if(tl < lms_lowest_lives)
199                                                 lms_lowest_lives = tl;
200                                 }
201                         });
202                         PlayerScore_Add(player, SP_LMS_RANK, 665 - quitters); // different from 666
203                         if(!warmup_stage)
204                         {
205                                 PlayerScore_Add(player, SP_LMS_LIVES, -PlayerScore_Add(player, SP_LMS_LIVES, 0));
206                                 ++quitters;
207                         }
208                         player.frags = FRAGS_LMS_LOSER;
209                         TRANSMUTE(Observer, player);
210                 }
211                 if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player
212                         lms_lowest_lives = 0; // end the game now!
213         }
214
215         if(CS(player).killcount != FRAGS_SPECTATOR)
216                 if(PlayerScore_Add(player, SP_LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
217                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
218                 else
219                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
220 }
221
222 MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
223 {
224         entity player = M_ARGV(0, entity);
225
226         lms_RemovePlayer(player);
227 }
228
229 MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
230 {
231     entity player = M_ARGV(0, entity);
232
233         lms_RemovePlayer(player);
234         return true;  // prevent team reset
235 }
236
237 MUTATOR_HOOKFUNCTION(lms, ClientConnect)
238 {
239         entity player = M_ARGV(0, entity);
240
241         TRANSMUTE(Player, player);
242         campaign_bots_may_start = true;
243
244         if(PlayerScore_Add(player, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0)
245         {
246                 PlayerScore_Add(player, SP_LMS_RANK, 666); // mark as forced spectator for the hud code
247                 player.frags = FRAGS_SPECTATOR;
248         }
249 }
250
251 MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
252 {
253         entity player = M_ARGV(0, entity);
254
255         if(player.deadflag == DEAD_DYING)
256                 player.deadflag = DEAD_RESPAWNING;
257 }
258
259 MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
260 {
261         if(autocvar_g_lms_regenerate)
262                 return false;
263         return true;
264 }
265
266 MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
267 {
268         // forbode!
269         return true;
270 }
271
272 MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
273 {
274         entity frag_target = M_ARGV(1, entity);
275
276         if (!warmup_stage)
277         {
278                 // remove a life
279                 int tl = PlayerScore_Add(frag_target, SP_LMS_LIVES, -1);
280                 if(tl < lms_lowest_lives)
281                         lms_lowest_lives = tl;
282                 if(tl <= 0)
283                 {
284                         int pl_cnt = 0;
285                         FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
286                         if(IS_BOT_CLIENT(frag_target))
287                                 bot_clear(frag_target);
288                         frag_target.frags = FRAGS_LMS_LOSER;
289                         PlayerScore_Add(frag_target, SP_LMS_RANK, pl_cnt);
290                 }
291         }
292         M_ARGV(2, float) = 0; // frag score
293
294         return true;
295 }
296
297 MUTATOR_HOOKFUNCTION(lms, SetStartItems)
298 {
299         start_items &= ~IT_UNLIMITED_AMMO;
300         start_health       = warmup_start_health       = cvar("g_lms_start_health");
301         start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
302         start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
303         start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
304         start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
305         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
306         start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
307         start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
308 }
309
310 MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
311 {
312         // don't clear player score
313         return true;
314 }
315
316 MUTATOR_HOOKFUNCTION(lms, FilterItem)
317 {
318         entity item = M_ARGV(0, entity);
319
320         if(autocvar_g_lms_extra_lives)
321         if(item.itemdef == ITEM_ExtraLife)
322                 return false;
323
324         return true;
325 }
326
327 void lms_extralife(entity this)
328 {
329         StartItem(this, ITEM_ExtraLife);
330 }
331
332 MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
333 {
334         if (!autocvar_g_powerups) return false;
335         if (!autocvar_g_lms_extra_lives) return false;
336
337         entity ent = M_ARGV(0, entity);
338
339         // Can't use .itemdef here
340         if (ent.classname != "item_health_mega") return false;
341
342         entity e = spawn();
343         setthink(e, lms_extralife);
344
345         e.nextthink = time + 0.1;
346         e.spawnflags = ent.spawnflags;
347         e.noalign = ent.noalign;
348         setorigin(e, ent.origin);
349
350         return true;
351 }
352
353 MUTATOR_HOOKFUNCTION(lms, ItemTouch)
354 {
355         entity item = M_ARGV(0, entity);
356         entity toucher = M_ARGV(1, entity);
357
358         if(item.itemdef == ITEM_ExtraLife)
359         {
360                 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
361                 PlayerScore_Add(toucher, SP_LMS_LIVES, autocvar_g_lms_extra_lives);
362                 return MUT_ITEMTOUCH_PICKUP;
363         }
364
365         return MUT_ITEMTOUCH_CONTINUE;
366 }
367
368 MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
369 {
370         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
371                 ++M_ARGV(0, int); // activerealplayers
372                 ++M_ARGV(1, int); // realplayers
373         });
374
375         return true;
376 }
377
378 MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
379 {
380     entity player = M_ARGV(0, entity);
381
382         if(warmup_stage || player.lms_spectate_warning)
383         {
384                 // for the forfeit message...
385                 player.lms_spectate_warning = 2;
386         }
387         else
388         {
389                 if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER)
390                 {
391                         player.lms_spectate_warning = 1;
392                         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");
393                 }
394                 return MUT_SPECCMD_RETURN;
395         }
396         return MUT_SPECCMD_CONTINUE;
397 }
398
399 MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
400 {
401         M_ARGV(0, float) = WinningCondition_LMS();
402         return true;
403 }
404
405 MUTATOR_HOOKFUNCTION(lms, WantWeapon)
406 {
407         M_ARGV(2, bool) = true; // all weapons
408 }
409
410 MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
411 {
412         return true;
413 }
414
415 MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
416 {
417         if(game_stopped)
418         if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
419                 return true; // allow writing to this field in intermission as it is needed for newly joining players
420 }
421
422 // scoreboard stuff
423 void lms_ScoreRules()
424 {
425     GameRules_score_enabled(false);
426         GameRules_scoring(0, 0, 0, {
427         field(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY);
428         field(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
429         });
430 }
431
432 void lms_Initialize()
433 {
434         lms_lowest_lives = 9999;
435
436         lms_ScoreRules();
437 }