]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_lms.qc
Fix infomessage regression
[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                                 return WINNING_YES;
76                         }
77                 }
78         }
79         else
80         {
81                 // nobody is playing at all...
82                 if(l)
83                 {
84                         // wait for players...
85                 }
86                 else
87                 {
88                         // SNAFU (maybe a draw game?)
89                         ClearWinners();
90                         LOG_TRACE("No players, ending game.");
91                         return WINNING_YES;
92                 }
93         }
94
95         // When we get here, we have at least two players who are actually LIVING,
96         // now check if the top two players have equal score.
97         WinningConditionHelper(NULL);
98
99         ClearWinners();
100         if(WinningConditionHelper_winner)
101                 WinningConditionHelper_winner.winning = true;
102         if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore)
103                 return WINNING_NEVER;
104
105         // Top two have different scores? Way to go for our beloved TIMELIMIT!
106         return WINNING_NO;
107 }
108
109 // mutator hooks
110 MUTATOR_HOOKFUNCTION(lms, reset_map_global)
111 {
112         lms_lowest_lives = 999;
113 }
114
115 MUTATOR_HOOKFUNCTION(lms, reset_map_players)
116 {
117         if(restart_mapalreadyrestarted || (time < game_starttime))
118         FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(PlayerScore_Add(it, SP_LMS_LIVES, LMS_NewPlayerLives())));
119 }
120
121 MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
122 {
123         entity player = M_ARGV(0, entity);
124
125         if(player.frags == FRAGS_SPECTATOR)
126                 TRANSMUTE(Observer, player);
127         else
128         {
129                 float tl = PlayerScore_Add(player, SP_LMS_LIVES, 0);
130                 if(tl < lms_lowest_lives)
131                         lms_lowest_lives = tl;
132                 if(tl <= 0)
133                         TRANSMUTE(Observer, player);
134         }
135 }
136
137 MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
138 {
139         entity player = M_ARGV(0, entity);
140
141         if(player.frags == FRAGS_SPECTATOR)
142                 return true;
143         if(PlayerScore_Add(player, SP_LMS_LIVES, 0) <= 0)
144         {
145                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
146                 return true;
147         }
148         return false;
149 }
150
151 MUTATOR_HOOKFUNCTION(lms, PlayerDies)
152 {
153         entity frag_target = M_ARGV(2, entity);
154
155         frag_target.respawn_flags |= RESPAWN_FORCE;
156 }
157
158 void lms_RemovePlayer(entity player)
159 {
160         float player_rank = PlayerScore_Add(player, SP_LMS_RANK, 0);
161         if (!player_rank)
162         {
163                 int pl_cnt = 0;
164                 FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
165                 if (player.lms_spectate_warning != 2)
166                 {
167                         player.frags = FRAGS_LMS_LOSER;
168                         PlayerScore_Add(player, SP_LMS_RANK, pl_cnt + 1);
169                 }
170                 else
171                 {
172                         lms_lowest_lives = 999;
173                         FOREACH_CLIENT(true, {
174                                 if (it.frags == FRAGS_LMS_LOSER)
175                                 {
176                                         float it_rank = PlayerScore_Add(it, SP_LMS_RANK, 0);
177                                         if (it_rank > player_rank && it_rank < 665)
178                                                 PlayerScore_Add(it, SP_LMS_RANK, -1);
179                                         lms_lowest_lives = 0;
180                                 }
181                                 else if (it.frags != FRAGS_SPECTATOR)
182                                 {
183                                         float tl = PlayerScore_Add(it, SP_LMS_LIVES, 0);
184                                         if(tl < lms_lowest_lives)
185                                                 lms_lowest_lives = tl;
186                                 }
187                         });
188                         PlayerScore_Add(player, SP_LMS_LIVES, -PlayerScore_Add(player, SP_LMS_LIVES, 0));
189                         PlayerScore_Add(player, SP_LMS_RANK, 665); // different from 666
190                         player.frags = FRAGS_LMS_LOSER;
191                         TRANSMUTE(Observer, player);
192                 }
193                 if (pl_cnt == 2) // a player is forfeiting leaving only one player
194                         lms_lowest_lives = 0; // end the game now!
195         }
196
197         if(player.killcount != FRAGS_SPECTATOR)
198                 if(PlayerScore_Add(player, SP_LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
199                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
200                 else
201                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
202 }
203
204 MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
205 {
206         entity player = M_ARGV(0, entity);
207
208         lms_RemovePlayer(player);
209 }
210
211 MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
212 {
213     entity player = M_ARGV(0, entity);
214
215         lms_RemovePlayer(player);
216         return true;  // prevent team reset
217 }
218
219 MUTATOR_HOOKFUNCTION(lms, ClientConnect)
220 {
221         entity player = M_ARGV(0, entity);
222
223         TRANSMUTE(Player, player);
224         campaign_bots_may_start = true;
225
226         if(PlayerScore_Add(player, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0)
227         {
228                 PlayerScore_Add(player, SP_LMS_RANK, 666); // mark as forced spectator for the hud code
229                 player.frags = FRAGS_SPECTATOR;
230         }
231 }
232
233 MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
234 {
235         entity player = M_ARGV(0, entity);
236
237         if(player.deadflag == DEAD_DYING)
238                 player.deadflag = DEAD_RESPAWNING;
239 }
240
241 MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
242 {
243         if(autocvar_g_lms_regenerate)
244                 return false;
245         return true;
246 }
247
248 MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
249 {
250         // forbode!
251         return true;
252 }
253
254 MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
255 {
256         entity frag_target = M_ARGV(1, entity);
257
258         // remove a life
259         float tl;
260         tl = PlayerScore_Add(frag_target, SP_LMS_LIVES, -1);
261         if(tl < lms_lowest_lives)
262                 lms_lowest_lives = tl;
263         if(tl <= 0)
264         {
265                 int pl_cnt = 0;
266                 FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
267                 frag_target.frags = FRAGS_LMS_LOSER;
268                 PlayerScore_Add(frag_target, SP_LMS_RANK, pl_cnt);
269         }
270         M_ARGV(2, float) = 0; // frag score
271
272         return true;
273 }
274
275 MUTATOR_HOOKFUNCTION(lms, SetStartItems)
276 {
277         start_items &= ~IT_UNLIMITED_AMMO;
278         start_health       = warmup_start_health       = cvar("g_lms_start_health");
279         start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
280         start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
281         start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
282         start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
283         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
284         start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
285         start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
286 }
287
288 MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
289 {
290         // don't clear player score
291         return true;
292 }
293
294 MUTATOR_HOOKFUNCTION(lms, FilterItem)
295 {
296         entity item = M_ARGV(0, entity);
297
298         if(autocvar_g_lms_extra_lives)
299         if(item.itemdef == ITEM_ExtraLife)
300                 return false;
301
302         return true;
303 }
304
305 void lms_extralife(entity this)
306 {
307         StartItem(this, ITEM_ExtraLife);
308 }
309
310 MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
311 {
312         if (!autocvar_g_powerups) return false;
313         if (!autocvar_g_lms_extra_lives) return false;
314
315         entity ent = M_ARGV(0, entity);
316
317         // Can't use .itemdef here
318         if (ent.classname != "item_health_mega") return false;
319
320         entity e = spawn();
321         setthink(e, lms_extralife);
322
323         e.nextthink = time + 0.1;
324         e.spawnflags = ent.spawnflags;
325         e.noalign = ent.noalign;
326         setorigin(e, ent.origin);
327
328         return true;
329 }
330
331 MUTATOR_HOOKFUNCTION(lms, ItemTouch)
332 {
333         entity item = M_ARGV(0, entity);
334         entity toucher = M_ARGV(1, entity);
335
336         if(item.itemdef == ITEM_ExtraLife)
337         {
338                 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
339                 PlayerScore_Add(toucher, SP_LMS_LIVES, autocvar_g_lms_extra_lives);
340                 return MUT_ITEMTOUCH_PICKUP;
341         }
342
343         return MUT_ITEMTOUCH_CONTINUE;
344 }
345
346 MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
347 {
348         FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(
349                 ++M_ARGV(0, int); // activerealplayers
350                 ++M_ARGV(1, int); // realplayers
351         ));
352
353         return true;
354 }
355
356 MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
357 {
358     entity player = M_ARGV(0, entity);
359
360         if(player.lms_spectate_warning)
361         {
362                 // for the forfeit message...
363                 player.lms_spectate_warning = 2;
364         }
365         else
366         {
367                 if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER)
368                 {
369                         player.lms_spectate_warning = 1;
370                         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");
371                 }
372                 return MUT_SPECCMD_RETURN;
373         }
374         return MUT_SPECCMD_CONTINUE;
375 }
376
377 MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
378 {
379         M_ARGV(0, float) = WinningCondition_LMS();
380         return true;
381 }
382
383 MUTATOR_HOOKFUNCTION(lms, WantWeapon)
384 {
385         M_ARGV(2, bool) = true; // all weapons
386 }
387
388 MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
389 {
390         return true;
391 }
392
393 MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
394 {
395         if(gameover)
396         if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
397                 return true; // allow writing to this field in intermission as it is needed for newly joining players
398 }
399
400 // scoreboard stuff
401 void lms_ScoreRules()
402 {
403         ScoreRules_basics(0, 0, 0, false);
404         ScoreInfo_SetLabel_PlayerScore(SP_LMS_LIVES,    "lives",     SFL_SORT_PRIO_SECONDARY);
405         ScoreInfo_SetLabel_PlayerScore(SP_LMS_RANK,     "rank",      SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
406         ScoreRules_basics_end();
407 }
408
409 void lms_Initialize()
410 {
411         lms_lowest_lives = 9999;
412
413         lms_ScoreRules();
414 }