]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/gamemodes/gamemode/lms/sv_lms.qc
Merge branch 'master' into terencehill/lms_spec
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / gamemodes / gamemode / lms / sv_lms.qc
1 #include "sv_lms.qh"
2
3 #include <common/mutators/mutator/instagib/items.qh>
4 #include <server/campaign.qh>
5 #include <server/command/_mod.qh>
6 #include <server/world.qh>
7 #include <server/items/items.qh>
8
9 int autocvar_g_lms_extra_lives;
10 float autocvar_g_lms_forfeit_min_match_time;
11 bool autocvar_g_lms_join_anytime;
12 int autocvar_g_lms_last_join;
13 bool autocvar_g_lms_items;
14 bool autocvar_g_lms_regenerate;
15 int autocvar_g_lms_leader_lives_diff = 2;
16 float autocvar_g_lms_leader_minpercent = 0.5;
17 float autocvar_g_lms_leader_wp_interval = 25;
18 float autocvar_g_lms_leader_wp_interval_jitter = 10;
19 float autocvar_g_lms_leader_wp_time = 5;
20 float autocvar_g_lms_dynamic_respawn_delay = 1;
21 float autocvar_g_lms_dynamic_respawn_delay_base = 2;
22 float autocvar_g_lms_dynamic_respawn_delay_increase = 3;
23 float autocvar_g_lms_dynamic_respawn_delay_max = 20;
24 bool autocvar_g_lms_dynamic_vampire = 1;
25 float autocvar_g_lms_dynamic_vampire_factor_base = 0.1;
26 float autocvar_g_lms_dynamic_vampire_factor_increase = 0.1;
27 float autocvar_g_lms_dynamic_vampire_factor_max = 0.5;
28 int autocvar_g_lms_dynamic_vampire_min_lives_diff = 2;
29
30 .float lms_leader;
31 int lms_leaders;
32 float lms_visible_leaders_time;
33 bool lms_visible_leaders = true; // triggers lms_visible_leaders_time update in the first frame
34 bool lms_visible_leaders_prev;
35 bool autocvar_g_lms_rot;
36
37 // main functions
38 int LMS_NewPlayerLives()
39 {
40         int fl = floor(autocvar_fraglimit);
41         if(fl == 0 || fl > 999)
42                 fl = 999;
43
44         // first player has left the game for dying too much? Nobody else can get in.
45         if(lms_lowest_lives < 1)
46                 return 0;
47
48         if(!autocvar_g_lms_join_anytime)
49                 if(lms_lowest_lives < fl - max(0, floor(autocvar_g_lms_last_join)))
50                         return 0;
51
52         return bound(1, lms_lowest_lives, fl);
53 }
54
55 void ClearWinners();
56
57 // LMS winning condition: game terminates if and only if there's at most one
58 // one player who's living lives. Top two scores being equal cancels the time
59 // limit.
60 int WinningCondition_LMS()
61 {
62         if (warmup_stage || time <= game_starttime)
63                 return WINNING_NO;
64
65         entity first_player = NULL;
66         int totalplayers = 0;
67         int totalplayed = 0;
68         FOREACH_CLIENT(true, {
69                 if (IS_PLAYER(it) && it.frags == FRAGS_PLAYER)
70                 {
71                         if (!totalplayers)
72                                 first_player = it;
73                         ++totalplayers;
74                 }
75                 else if (GameRules_scoring_add(it, LMS_RANK, 0))
76                         ++totalplayed;
77         });
78
79         if (totalplayers)
80         {
81                 if (totalplayers > 1)
82                 {
83                         // two or more active players - continue with the game
84
85                         if (autocvar_g_campaign && campaign_bots_may_start)
86                         {
87                                 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
88                                         float pl_lives = GameRules_scoring_add(it, LMS_LIVES, 0);
89                                         if (!pl_lives)
90                                                 return WINNING_YES; // human player lost, game over
91                                         break;
92                                 });
93                         }
94                 }
95                 else
96                 {
97                         // exactly one player?
98
99                         ClearWinners();
100
101                         if (LMS_NewPlayerLives())
102                         {
103                                 if (totalplayed && game_starttime > 0 && time > game_starttime + autocvar_g_lms_forfeit_min_match_time) // give players time to join
104                                 {
105                                         GameRules_scoring_add(first_player, LMS_RANK, 1);
106                                         first_player.winning = 1;
107                                         return WINNING_YES;
108                                 }
109                                 // game still running (that is, nobody got removed from the game by a frag yet)? then continue
110                                 return WINNING_NO;
111                         }
112                         else
113                         {
114                                 // a winner!
115                                 // and assign him his first place
116                                 GameRules_scoring_add(first_player, LMS_RANK, 1);
117                                 first_player.winning = 1;
118                                 return WINNING_YES;
119                         }
120                 }
121         }
122         else
123         {
124                 // nobody is playing at all...
125                 if (LMS_NewPlayerLives())
126                 {
127                         if (totalplayed && game_starttime > 0 && time > game_starttime + autocvar_g_lms_forfeit_min_match_time) // give players time to join
128                         {
129                                 ClearWinners();
130                                 return WINNING_YES;
131                         }
132                         // wait for players...
133                 }
134                 else
135                 {
136                         // SNAFU (maybe a draw game?)
137                         ClearWinners();
138                         LOG_TRACE("No players, ending game.");
139                         return WINNING_YES;
140                 }
141         }
142
143         // When we get here, we have at least two players who are actually LIVING,
144         // now check if the top two players have equal score.
145         WinningConditionHelper(NULL);
146
147         ClearWinners();
148         if(WinningConditionHelper_winner)
149                 WinningConditionHelper_winner.winning = true;
150         if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore)
151                 return WINNING_NEVER;
152
153         // Top two have different scores? Way to go for our beloved TIMELIMIT!
154         return WINNING_NO;
155 }
156
157 // runs on waypoints which are attached to leaders, updates once per frame
158 bool lms_waypointsprite_visible_for_player(entity this, entity player, entity view)
159 {
160         if(view.lms_leader)
161                 if(IS_SPEC(player))
162                         return false; // we don't want spectators of leaders to see the attached waypoint on the top of their screen
163
164         if (!lms_visible_leaders)
165                 return false;
166
167         return true;
168 }
169
170 int lms_leaders_lives_diff;
171 void lms_UpdateLeaders()
172 {
173         int max_lives = 0;
174         int pl_cnt = 0;
175         FOREACH_CLIENT(IS_PLAYER(it) && it.frags != FRAGS_PLAYER_OUT_OF_GAME, {
176                 int lives = GameRules_scoring_add(it, LMS_LIVES, 0);
177                 if (lives > max_lives)
178                         max_lives = lives;
179                 pl_cnt++;
180         });
181
182         int second_max_lives = 0;
183         int pl_cnt_with_max_lives = 0;
184         FOREACH_CLIENT(IS_PLAYER(it) && it.frags != FRAGS_PLAYER_OUT_OF_GAME, {
185                 int lives = GameRules_scoring_add(it, LMS_LIVES, 0);
186                 if (lives == max_lives)
187                         pl_cnt_with_max_lives++;
188                 else if (lives > second_max_lives)
189                         second_max_lives = lives;
190         });
191
192         lms_leaders_lives_diff = max_lives - second_max_lives;
193
194         int lives_diff = autocvar_g_lms_leader_lives_diff;
195         if (lms_leaders_lives_diff >= lives_diff && pl_cnt_with_max_lives <= pl_cnt * autocvar_g_lms_leader_minpercent)
196                 FOREACH_CLIENT(IS_PLAYER(it) && it.frags != FRAGS_PLAYER_OUT_OF_GAME, {
197                         int lives = GameRules_scoring_add(it, LMS_LIVES, 0);
198                         if (lives == max_lives)
199                         {
200                                 if (!it.lms_leader)
201                                         it.lms_leader = true;
202                         }
203                         else
204                         {
205                                 it.lms_leader = false;
206                         }
207                 });
208         else
209                 FOREACH_CLIENT(IS_PLAYER(it) && it.frags != FRAGS_PLAYER_OUT_OF_GAME, {
210                         if (it.waypointsprite_attachedforcarrier)
211                                 WaypointSprite_Kill(it.waypointsprite_attachedforcarrier);
212                         it.lms_leader = false;
213                 });
214 }
215
216 // mutator hooks
217 MUTATOR_HOOKFUNCTION(lms, reset_map_global)
218 {
219         lms_lowest_lives = 999;
220 }
221
222 MUTATOR_HOOKFUNCTION(lms, reset_map_players)
223 {
224         FOREACH_CLIENT(true, {
225                 if (it.frags == FRAGS_PLAYER_OUT_OF_GAME)
226                         it.frags = FRAGS_PLAYER;
227
228                 CS(it).killcount = 0;
229                 INGAME_STATUS_CLEAR(it);
230                 it.lms_spectate = false;
231                 GameRules_scoring_add(it, LMS_RANK, -GameRules_scoring_add(it, LMS_RANK, 0));
232                 GameRules_scoring_add(it, LMS_LIVES, -GameRules_scoring_add(it, LMS_LIVES, 0));
233
234                 if (it.frags != FRAGS_PLAYER)
235                         continue;
236
237                 TRANSMUTE(Player, it);
238                 PutClientInServer(it);
239                 it.lms_leader = false;
240                 if (it.waypointsprite_attachedforcarrier)
241                         WaypointSprite_Kill(it.waypointsprite_attachedforcarrier);
242         });
243 }
244
245 // FIXME add support for sv_ready_restart_after_countdown
246 // that is find a way to respawn/reset players IN GAME without setting lives to 0
247 MUTATOR_HOOKFUNCTION(lms, ReadLevelCvars)
248 {
249         // incompatible
250         sv_ready_restart_after_countdown = 0;
251 }
252
253 // returns true if player is added to the game
254 bool lms_AddPlayer(entity player)
255 {
256         if (!INGAME(player))
257         {
258                 int lives = GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives());
259                 if(lives <= 0)
260                         return false;
261                 if (time < game_starttime)
262                         INGAME_STATUS_SET(player, INGAME_STATUS_JOINED);
263                 else
264                         INGAME_STATUS_SET(player, INGAME_STATUS_JOINING); // this is just to delay setting health and armor that can't be done here
265         }
266         if (warmup_stage || time <= game_starttime)
267         {
268                 player.lms_spectate = false;
269                 GameRules_scoring_add(player, LMS_RANK, -GameRules_scoring_add(player, LMS_RANK, 0));
270                 int lives = GameRules_scoring_add(player, LMS_LIVES, 0);
271                 if(lives <= 0)
272                         GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives());
273         }
274         else
275         {
276                 if(GameRules_scoring_add(player, LMS_LIVES, 0) <= 0)
277                 {
278                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
279                         return false;
280                 }
281         }
282         return true;
283 }
284
285 MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
286 {
287         entity player = M_ARGV(0, entity);
288         if (!warmup_stage && (IS_BOT_CLIENT(player) || CS(player).jointime != time))
289         {
290                 if (GameRules_scoring_add(player, LMS_RANK, 0) || !lms_AddPlayer(player))
291                         TRANSMUTE(Observer, player);
292         }
293 }
294
295 int last_forfeiter_lives;
296 float last_forfeiter_health;
297 float last_forfeiter_armorvalue;
298 MUTATOR_HOOKFUNCTION(lms, PlayerSpawn)
299 {
300         entity player = M_ARGV(0, entity);
301
302         if (warmup_stage || time < game_starttime)
303                 return true;
304
305         if (INGAME_JOINING(player))
306         {
307                 // spawn player with the same amount of health / armor
308                 // as the least healthy player with the least number of lives
309                 int pl_lives = GameRules_scoring_add(player, LMS_LIVES, 0);
310                 float min_health = start_health;
311                 float min_armorvalue = start_armorvalue;
312                 if (last_forfeiter_lives == pl_lives)
313                 {
314                         min_health = last_forfeiter_health;
315                         min_armorvalue = last_forfeiter_armorvalue;
316                 }
317                 FOREACH_CLIENT(it != player && IS_PLAYER(it) && !IS_DEAD(it) && GameRules_scoring_add(it, LMS_LIVES, 0) == pl_lives, {
318                         if (GetResource(it, RES_HEALTH) < min_health)
319                                 min_health = GetResource(it, RES_HEALTH);
320                         if (GetResource(it, RES_ARMOR) < min_armorvalue)
321                                 min_armorvalue = GetResource(it, RES_ARMOR);
322                 });
323                 if (min_health != start_health)
324                         SetResource(player, RES_HEALTH, max(1, min_health));
325                 if (min_armorvalue != start_armorvalue)
326                         SetResource(player, RES_ARMOR, min_armorvalue);
327                 INGAME_STATUS_SET(player, INGAME_STATUS_JOINED);
328         }
329 }
330
331 MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
332 {
333         entity player = M_ARGV(0, entity);
334
335         if (warmup_stage || lms_AddPlayer(player))
336                 return false;
337
338         return true;
339 }
340
341 void lms_RemovePlayer(entity player)
342 {
343         if (warmup_stage || time < game_starttime)
344                 return;
345
346         float player_rank = GameRules_scoring_add(player, LMS_RANK, 0);
347         if (!player_rank)
348         {
349                 if (!player.lms_spectate)
350                 {
351                         player.frags = FRAGS_PLAYER_OUT_OF_GAME;
352                         int pl_cnt = 0;
353                         FOREACH_CLIENT(IS_PLAYER(it) && it.frags == FRAGS_PLAYER, {
354                                 pl_cnt++;
355                         });
356                         GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1);
357                 }
358                 else if (INGAME(player))
359                 {
360                         FOREACH_CLIENT(it != player, {
361                                 // update rank of other players
362                                 if (it.frags == FRAGS_PLAYER_OUT_OF_GAME)
363                                         GameRules_scoring_add(it, LMS_RANK, -1);
364                         });
365                         int rank = GameRules_scoring_add(player, LMS_RANK, 0);
366                         GameRules_scoring_add(player, LMS_RANK, -rank);
367                         if(!warmup_stage)
368                         {
369                                 int pl_lives = GameRules_scoring_add(player, LMS_LIVES, 0);
370                                 if (!last_forfeiter_lives || pl_lives < last_forfeiter_lives)
371                                 {
372                                         last_forfeiter_lives = pl_lives;
373                                         last_forfeiter_health = GetResource(player, RES_HEALTH);
374                                         last_forfeiter_armorvalue = GetResource(player, RES_ARMOR);
375                                 }
376                                 else if (pl_lives == last_forfeiter_lives)
377                                 {
378                                         // these values actually can belong to a different forfeiter
379                                         last_forfeiter_health = min(last_forfeiter_health, GetResource(player, RES_HEALTH));
380                                         last_forfeiter_armorvalue = min(last_forfeiter_armorvalue, GetResource(player, RES_ARMOR));
381                                 }
382                                 GameRules_scoring_add(player, LMS_LIVES, -pl_lives);
383                         }
384                         player.frags = FRAGS_SPECTATOR;
385                         TRANSMUTE(Observer, player);
386                         INGAME_STATUS_CLEAR(player);
387                         player.lms_spectate = false;
388                         CS(player).killcount = FRAGS_SPECTATOR;
389                 }
390                 if (autocvar_g_lms_leader_lives_diff > 0)
391                         lms_UpdateLeaders();
392         }
393
394         if (CS(player).killcount != FRAGS_SPECTATOR)
395         {
396                 if (GameRules_scoring_add(player, LMS_RANK, 0) > 0)
397                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
398         }
399 }
400
401 MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
402 {
403         entity player = M_ARGV(0, entity);
404
405         player.lms_spectate = true;
406
407         lms_RemovePlayer(player);
408         INGAME_STATUS_CLEAR(player);
409 }
410
411 MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
412 {
413         entity player = M_ARGV(0, entity);
414         bool is_forced = M_ARGV(1, bool);
415
416         if (!IS_PLAYER(player))
417                 return true;
418
419         if (warmup_stage || time <= game_starttime)
420         {
421                 GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0));
422                 player.frags = FRAGS_SPECTATOR;
423                 TRANSMUTE(Observer, player);
424                 INGAME_STATUS_CLEAR(player);
425         }
426         else
427         {
428                 if (is_forced || player.killindicator_teamchange == -2) // player is forced or wants to spectate
429                         player.lms_spectate = true;
430                 if (!GameRules_scoring_add(player, LMS_RANK, 0))
431                         lms_RemovePlayer(player);
432         }
433         return true;  // prevent team reset
434 }
435
436 MUTATOR_HOOKFUNCTION(lms, ClientConnect)
437 {
438         entity player = M_ARGV(0, entity);
439         player.frags = FRAGS_SPECTATOR;
440 }
441
442 MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
443 {
444         entity player = M_ARGV(0, entity);
445
446         // recycled REDALIVE and BLUEALIVE to avoid adding a dedicated stat
447         STAT(REDALIVE, player) = lms_leaders;
448         STAT(BLUEALIVE, player) = lms_leaders_lives_diff;
449
450         if(player.deadflag == DEAD_DYING)
451                 player.deadflag = DEAD_RESPAWNING;
452 }
453
454 MUTATOR_HOOKFUNCTION(lms, SV_StartFrame)
455 {
456         if (intermission_running)
457                 return;
458
459         lms_leaders = 0;
460         FOREACH_CLIENT(true, {
461                 if (IS_PLAYER(it) && it.frags != FRAGS_PLAYER_OUT_OF_GAME && it.lms_leader)
462                         lms_leaders++;
463         });
464
465         float leader_time = autocvar_g_lms_leader_wp_time;
466         float leader_interval = leader_time + autocvar_g_lms_leader_wp_interval;
467         lms_visible_leaders_prev = lms_visible_leaders;
468         lms_visible_leaders = (lms_leaders && time > lms_visible_leaders_time && time < lms_visible_leaders_time + leader_time);
469         if (!lms_leaders || (lms_visible_leaders_prev && !lms_visible_leaders))
470                 lms_visible_leaders_time = time + leader_interval + random() * autocvar_g_lms_leader_wp_interval_jitter;
471
472         FOREACH_CLIENT(true, {
473                 STAT(OBJECTIVE_STATUS, it) = lms_visible_leaders;
474                 if (IS_PLAYER(it) && it.frags != FRAGS_PLAYER_OUT_OF_GAME)
475                 {
476                         if (it.lms_leader)
477                         {
478                                 if (!it.waypointsprite_attachedforcarrier)
479                                 {
480                                         WaypointSprite_AttachCarrier(WP_LmsLeader, it, RADARICON_FLAGCARRIER);
481                                         it.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = lms_waypointsprite_visible_for_player;
482                                         WaypointSprite_UpdateRule(it.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
483                                         vector pl_color = colormapPaletteColor(it.clientcolors & 0x0F, false);
484                                         WaypointSprite_UpdateTeamRadar(it.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, pl_color);
485                                         WaypointSprite_Ping(it.waypointsprite_attachedforcarrier);
486                                 }
487                                 if (!lms_visible_leaders_prev && lms_visible_leaders && IS_REAL_CLIENT(it))
488                                         Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_LMS_VISIBLE_LEADER);
489                         }
490                         else // if (!it.lms_leader)
491                         {
492                                 if (IS_PLAYER(it) && it.frags != FRAGS_PLAYER_OUT_OF_GAME)
493                                 {
494                                         if (!lms_visible_leaders_prev && lms_visible_leaders && IS_REAL_CLIENT(it))
495                                                 Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_LMS_VISIBLE_OTHER);
496                                 }
497                                 if (it.waypointsprite_attachedforcarrier)
498                                         WaypointSprite_Kill(it.waypointsprite_attachedforcarrier);
499                         }
500                 }
501         });
502 }
503
504 MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
505 {
506         if(!autocvar_g_lms_regenerate)
507                 M_ARGV(2, float) = 0;
508         if(!autocvar_g_lms_rot)
509                 M_ARGV(3, float) = 0;
510         return (!autocvar_g_lms_regenerate && !autocvar_g_lms_rot);
511 }
512
513 MUTATOR_HOOKFUNCTION(lms, PlayerPowerups)
514 {
515         entity player = M_ARGV(0, entity);
516         if (player.waypointsprite_attachedforcarrier)
517                 player.effects |= (EF_ADDITIVE | EF_FULLBRIGHT);
518         else
519                 player.effects &= ~(EF_ADDITIVE | EF_FULLBRIGHT);
520 }
521
522 MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
523 {
524         // forbode!
525         return true;
526 }
527
528 MUTATOR_HOOKFUNCTION(lms, Damage_Calculate)
529 {
530         if (!autocvar_g_lms_dynamic_vampire)
531                 return;
532
533         entity frag_attacker = M_ARGV(1, entity);
534         entity frag_target = M_ARGV(2, entity);
535         float frag_damage = M_ARGV(4, float);
536
537         if (IS_PLAYER(frag_attacker) && !IS_DEAD(frag_attacker)
538                 && IS_PLAYER(frag_target) && !IS_DEAD(frag_target) && frag_attacker != frag_target)
539         {
540                 float vampire_factor = 0;
541
542                 int frag_attacker_lives = GameRules_scoring_add(frag_attacker, LMS_LIVES, 0);
543                 int frag_target_lives = GameRules_scoring_add(frag_target, LMS_LIVES, 0);
544                 int diff = frag_target_lives - frag_attacker_lives - autocvar_g_lms_dynamic_vampire_min_lives_diff;
545
546                 if (diff >= 0)
547                         vampire_factor = autocvar_g_lms_dynamic_vampire_factor_base + diff * autocvar_g_lms_dynamic_vampire_factor_increase;
548                 if (vampire_factor > 0)
549                 {
550                         vampire_factor = min(vampire_factor, autocvar_g_lms_dynamic_vampire_factor_max);
551                         SetResourceExplicit(frag_attacker, RES_HEALTH,
552                                 min(GetResource(frag_attacker, RES_HEALTH) + frag_damage * vampire_factor, start_health));
553                 }
554         }
555 }
556
557 MUTATOR_HOOKFUNCTION(lms, PlayerDied)
558 {
559         if (!warmup_stage && autocvar_g_lms_leader_lives_diff > 0)
560                 lms_UpdateLeaders();
561 }
562
563 MUTATOR_HOOKFUNCTION(lms, CalculateRespawnTime)
564 {
565         entity player = M_ARGV(0, entity);
566         player.respawn_flags |= RESPAWN_FORCE;
567
568         int pl_lives = GameRules_scoring_add(player, LMS_LIVES, 0);
569         if (pl_lives <= 0)
570         {
571                 player.respawn_flags = RESPAWN_SILENT;
572                 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
573                 player.respawn_time = time + 2;
574                 return true;
575         }
576
577         if (autocvar_g_lms_dynamic_respawn_delay <= 0)
578                 return false;
579
580         int max_lives = 0;
581         int pl_cnt = 0;
582         FOREACH_CLIENT(it != player && IS_PLAYER(it) && it.frags != FRAGS_PLAYER_OUT_OF_GAME, {
583                 int lives = GameRules_scoring_add(it, LMS_LIVES, 0);
584                 if (lives > max_lives)
585                         max_lives = lives;
586                 pl_cnt++;
587         });
588
589         // min delay with only 2 players
590         if (pl_cnt == 1) // player wasn't counted
591                 max_lives = 0;
592
593         float dlay = autocvar_g_lms_dynamic_respawn_delay_base +
594                 autocvar_g_lms_dynamic_respawn_delay_increase * max(0, max_lives - pl_lives);
595         player.respawn_time = time + min(autocvar_g_lms_dynamic_respawn_delay_max, dlay);
596         return true;
597 }
598
599 MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
600 {
601         entity frag_target = M_ARGV(1, entity);
602
603         if (!warmup_stage && time > game_starttime)
604         {
605                 // remove a life
606                 int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1);
607                 if(tl < lms_lowest_lives)
608                         lms_lowest_lives = tl;
609                 if(tl <= 0)
610                 {
611                         int pl_cnt = 0;
612                         FOREACH_CLIENT(IS_PLAYER(it) && it.frags == FRAGS_PLAYER, {
613                                 pl_cnt++;
614                         });
615                         frag_target.frags = FRAGS_PLAYER_OUT_OF_GAME;
616                         GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt);
617                 }
618         }
619         M_ARGV(2, float) = 0; // frag score
620
621         return true;
622 }
623
624 MUTATOR_HOOKFUNCTION(lms, SetStartItems)
625 {
626         start_items &= ~(IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS);
627         if(!cvar("g_use_ammunition"))
628                 start_items |= IT_UNLIMITED_AMMO;
629
630         start_health       = warmup_start_health       = cvar("g_lms_start_health");
631         start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
632         start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
633         start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
634         start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
635         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
636         start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
637         start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
638 }
639
640 MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
641 {
642         // don't clear player score
643         return true;
644 }
645
646 MUTATOR_HOOKFUNCTION(lms, FilterItemDefinition)
647 {
648         if (autocvar_g_lms_items)
649                 return false;
650
651         entity definition = M_ARGV(0, entity);
652
653         if (autocvar_g_lms_extra_lives && definition == ITEM_ExtraLife)
654         {
655                 return false;
656         }
657         return (autocvar_g_pickup_items <= 0); // only allow items if explicitly enabled
658 }
659
660 void lms_extralife(entity this)
661 {
662         StartItem(this, ITEM_ExtraLife);
663 }
664
665 MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
666 {
667         if (MUTATOR_RETURNVALUE) return false;
668         if (!autocvar_g_powerups) return false;
669         if (!autocvar_g_lms_extra_lives) return false;
670
671         entity ent = M_ARGV(0, entity);
672
673         // Can't use .itemdef here
674         if (ent.classname != "item_health_mega") return false;
675
676         entity e = spawn();
677         setthink(e, lms_extralife);
678
679         e.nextthink = time + 0.1;
680         e.spawnflags = ent.spawnflags;
681         e.noalign = ent.noalign;
682         setorigin(e, ent.origin);
683
684         return true;
685 }
686
687 MUTATOR_HOOKFUNCTION(lms, ItemTouch)
688 {
689         if(MUTATOR_RETURNVALUE) return false;
690
691         entity item = M_ARGV(0, entity);
692         entity toucher = M_ARGV(1, entity);
693
694         if(item.itemdef == ITEM_ExtraLife)
695         {
696                 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES, autocvar_g_lms_extra_lives);
697                 GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives);
698                 return MUT_ITEMTOUCH_PICKUP;
699         }
700
701         return MUT_ITEMTOUCH_CONTINUE;
702 }
703
704 MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
705 {
706         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
707                 if (INGAME(it))
708                         ++M_ARGV(0, int); // activerealplayers
709                 ++M_ARGV(1, int); // realplayers
710         });
711
712         return true;
713 }
714
715 MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
716 {
717         entity player = M_ARGV(0, entity);
718         if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_PLAYER_OUT_OF_GAME)
719                 return MUT_SPECCMD_CONTINUE;
720         // ranked players (out of game) can no longer become real spectators
721         return MUT_SPECCMD_RETURN;
722 }
723
724 MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
725 {
726         M_ARGV(0, float) = WinningCondition_LMS();
727         return true;
728 }
729
730 MUTATOR_HOOKFUNCTION(lms, SetWeaponArena)
731 {
732         if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
733                 M_ARGV(0, string) = autocvar_g_lms_weaponarena;
734 }
735
736 MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
737 {
738         if(game_stopped)
739         if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
740                 return true; // allow writing to this field in intermission as it is needed for newly joining players
741 }
742
743 void lms_Initialize()
744 {
745         lms_lowest_lives = 999;
746 }