]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/gamemodes/gamemode/lms/sv_lms.qc
Set lms_spectate right when player is moved to spectator, not when the spectate comma...
[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 MUTATOR_HOOKFUNCTION(lms, PlayerSpawn)
296 {
297         entity player = M_ARGV(0, entity);
298
299         if (warmup_stage || time < game_starttime)
300                 return true;
301
302         if (INGAME_JOINING(player))
303         {
304                 // spawn player with the same amount of health / armor
305                 // as the least healthy player with the least number of lives
306                 int pl_lives = GameRules_scoring_add(player, LMS_LIVES, 0);
307                 float min_health = start_health;
308                 float min_armorvalue = start_armorvalue;
309                 FOREACH_CLIENT(it != player && IS_PLAYER(it) && !IS_DEAD(it) && GameRules_scoring_add(it, LMS_LIVES, 0) == pl_lives, {
310                         if (GetResource(it, RES_HEALTH) < min_health)
311                                 min_health = GetResource(it, RES_HEALTH);
312                         if (GetResource(it, RES_ARMOR) < min_armorvalue)
313                                 min_armorvalue = GetResource(it, RES_ARMOR);
314                 });
315                 if (min_health != start_health)
316                         SetResource(player, RES_HEALTH, max(1, min_health));
317                 if (min_armorvalue != start_armorvalue)
318                         SetResource(player, RES_ARMOR, min_armorvalue);
319                 INGAME_STATUS_SET(player, INGAME_STATUS_JOINED);
320         }
321 }
322
323 MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
324 {
325         entity player = M_ARGV(0, entity);
326
327         if (warmup_stage || lms_AddPlayer(player))
328                 return false;
329
330         return true;
331 }
332
333 void lms_RemovePlayer(entity player)
334 {
335         if (warmup_stage || time < game_starttime)
336                 return;
337
338         float player_rank = GameRules_scoring_add(player, LMS_RANK, 0);
339         if (!player_rank)
340         {
341                 if (!player.lms_spectate)
342                 {
343                         player.frags = FRAGS_PLAYER_OUT_OF_GAME;
344                         int pl_cnt = 0;
345                         FOREACH_CLIENT(IS_PLAYER(it) && it.frags == FRAGS_PLAYER, {
346                                 pl_cnt++;
347                         });
348                         GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1);
349                 }
350                 else if (INGAME(player))
351                 {
352                         FOREACH_CLIENT(it != player, {
353                                 // update rank of other players
354                                 if (it.frags == FRAGS_PLAYER_OUT_OF_GAME)
355                                         GameRules_scoring_add(it, LMS_RANK, -1);
356                         });
357                         int rank = GameRules_scoring_add(player, LMS_RANK, 0);
358                         GameRules_scoring_add(player, LMS_RANK, -rank);
359                         if(!warmup_stage)
360                                 GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0));
361                         player.frags = FRAGS_SPECTATOR;
362                         TRANSMUTE(Observer, player);
363                         INGAME_STATUS_CLEAR(player);
364                         player.lms_spectate = false;
365                         CS(player).killcount = FRAGS_SPECTATOR;
366                 }
367                 if (autocvar_g_lms_leader_lives_diff > 0)
368                         lms_UpdateLeaders();
369         }
370
371         if (CS(player).killcount != FRAGS_SPECTATOR)
372         {
373                 if (GameRules_scoring_add(player, LMS_RANK, 0) > 0)
374                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
375         }
376 }
377
378 MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
379 {
380         entity player = M_ARGV(0, entity);
381
382         player.lms_spectate = true;
383
384         lms_RemovePlayer(player);
385         INGAME_STATUS_CLEAR(player);
386 }
387
388 MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
389 {
390         entity player = M_ARGV(0, entity);
391         bool is_forced = M_ARGV(1, bool);
392
393         if (!IS_PLAYER(player))
394                 return true;
395
396         if (warmup_stage || time <= game_starttime)
397         {
398                 GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0));
399                 player.frags = FRAGS_SPECTATOR;
400                 TRANSMUTE(Observer, player);
401                 INGAME_STATUS_CLEAR(player);
402         }
403         else
404         {
405                 if (is_forced || player.killindicator_teamchange == -2) // player is forced or wants to spectate
406                         player.lms_spectate = true;
407                 if (!GameRules_scoring_add(player, LMS_RANK, 0))
408                         lms_RemovePlayer(player);
409         }
410         return true;  // prevent team reset
411 }
412
413 MUTATOR_HOOKFUNCTION(lms, ClientConnect)
414 {
415         entity player = M_ARGV(0, entity);
416         player.frags = FRAGS_SPECTATOR;
417 }
418
419 MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
420 {
421         entity player = M_ARGV(0, entity);
422
423         // recycled REDALIVE and BLUEALIVE to avoid adding a dedicated stat
424         STAT(REDALIVE, player) = lms_leaders;
425         STAT(BLUEALIVE, player) = lms_leaders_lives_diff;
426
427         if(player.deadflag == DEAD_DYING)
428                 player.deadflag = DEAD_RESPAWNING;
429 }
430
431 MUTATOR_HOOKFUNCTION(lms, SV_StartFrame)
432 {
433         float leader_time = autocvar_g_lms_leader_wp_time;
434         float leader_interval = leader_time + autocvar_g_lms_leader_wp_interval;
435         lms_visible_leaders_prev = lms_visible_leaders;
436         lms_visible_leaders = (time > lms_visible_leaders_time && time < lms_visible_leaders_time + leader_time);
437         if (lms_visible_leaders_prev && !lms_visible_leaders)
438                 lms_visible_leaders_time = time + leader_interval + random() * autocvar_g_lms_leader_wp_interval_jitter;
439
440         lms_leaders = 0;
441         FOREACH_CLIENT(true, {
442                 STAT(OBJECTIVE_STATUS, it) = lms_visible_leaders;
443                 if (IS_PLAYER(it) && it.frags != FRAGS_PLAYER_OUT_OF_GAME)
444                 {
445                         if (it.lms_leader)
446                         {
447                                 if (!it.waypointsprite_attachedforcarrier)
448                                 {
449                                         WaypointSprite_AttachCarrier(WP_LmsLeader, it, RADARICON_FLAGCARRIER);
450                                         it.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = lms_waypointsprite_visible_for_player;
451                                         WaypointSprite_UpdateRule(it.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
452                                         vector pl_color = colormapPaletteColor(it.clientcolors & 0x0F, false);
453                                         WaypointSprite_UpdateTeamRadar(it.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, pl_color);
454                                         WaypointSprite_Ping(it.waypointsprite_attachedforcarrier);
455                                 }
456                                 if (!lms_visible_leaders_prev && lms_visible_leaders && IS_REAL_CLIENT(it))
457                                         Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_LMS_VISIBLE_LEADER);
458                                 lms_leaders++;
459                         }
460                         else // if (!it.lms_leader)
461                         {
462                                 if (IS_PLAYER(it) && it.frags != FRAGS_PLAYER_OUT_OF_GAME)
463                                 {
464                                         if (!lms_visible_leaders_prev && lms_visible_leaders && IS_REAL_CLIENT(it))
465                                                 Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_LMS_VISIBLE_OTHER);
466                                 }
467                                 if (it.waypointsprite_attachedforcarrier)
468                                         WaypointSprite_Kill(it.waypointsprite_attachedforcarrier);
469                         }
470                 }
471         });
472 }
473
474 MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
475 {
476         if(!autocvar_g_lms_regenerate)
477                 M_ARGV(2, float) = 0;
478         if(!autocvar_g_lms_rot)
479                 M_ARGV(3, float) = 0;
480         return (!autocvar_g_lms_regenerate && !autocvar_g_lms_rot);
481 }
482
483 MUTATOR_HOOKFUNCTION(lms, PlayerPowerups)
484 {
485         entity player = M_ARGV(0, entity);
486         if (player.waypointsprite_attachedforcarrier)
487                 player.effects |= (EF_ADDITIVE | EF_FULLBRIGHT);
488         else
489                 player.effects &= ~(EF_ADDITIVE | EF_FULLBRIGHT);
490 }
491
492 MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
493 {
494         // forbode!
495         return true;
496 }
497
498 MUTATOR_HOOKFUNCTION(lms, Damage_Calculate)
499 {
500         if (!autocvar_g_lms_dynamic_vampire)
501                 return;
502
503         entity frag_attacker = M_ARGV(1, entity);
504         entity frag_target = M_ARGV(2, entity);
505         float frag_damage = M_ARGV(4, float);
506
507         if (IS_PLAYER(frag_attacker) && !IS_DEAD(frag_attacker)
508                 && IS_PLAYER(frag_target) && !IS_DEAD(frag_target) && frag_attacker != frag_target)
509         {
510                 float vampire_factor = 0;
511
512                 int frag_attacker_lives = GameRules_scoring_add(frag_attacker, LMS_LIVES, 0);
513                 int frag_target_lives = GameRules_scoring_add(frag_target, LMS_LIVES, 0);
514                 int diff = frag_target_lives - frag_attacker_lives - autocvar_g_lms_dynamic_vampire_min_lives_diff;
515
516                 if (diff >= 0)
517                         vampire_factor = autocvar_g_lms_dynamic_vampire_factor_base + diff * autocvar_g_lms_dynamic_vampire_factor_increase;
518                 if (vampire_factor > 0)
519                 {
520                         vampire_factor = min(vampire_factor, autocvar_g_lms_dynamic_vampire_factor_max);
521                         SetResourceExplicit(frag_attacker, RES_HEALTH,
522                                 min(GetResource(frag_attacker, RES_HEALTH) + frag_damage * vampire_factor, start_health));
523                 }
524         }
525 }
526
527 MUTATOR_HOOKFUNCTION(lms, PlayerDied)
528 {
529         if (!warmup_stage && autocvar_g_lms_leader_lives_diff > 0)
530                 lms_UpdateLeaders();
531 }
532
533 MUTATOR_HOOKFUNCTION(lms, CalculateRespawnTime)
534 {
535         entity player = M_ARGV(0, entity);
536         player.respawn_flags |= RESPAWN_FORCE;
537
538         int pl_lives = GameRules_scoring_add(player, LMS_LIVES, 0);
539         if (pl_lives <= 0)
540         {
541                 player.respawn_flags = RESPAWN_SILENT;
542                 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
543                 player.respawn_time = time + 2;
544                 return true;
545         }
546
547         if (autocvar_g_lms_dynamic_respawn_delay <= 0)
548                 return false;
549
550         int max_lives = 0;
551         int pl_cnt = 0;
552         FOREACH_CLIENT(it != player && IS_PLAYER(it) && it.frags != FRAGS_PLAYER_OUT_OF_GAME, {
553                 int lives = GameRules_scoring_add(it, LMS_LIVES, 0);
554                 if (lives > max_lives)
555                         max_lives = lives;
556                 pl_cnt++;
557         });
558
559         // min delay with only 2 players
560         if (pl_cnt == 1) // player wasn't counted
561                 max_lives = 0;
562
563         float dlay = autocvar_g_lms_dynamic_respawn_delay_base +
564                 autocvar_g_lms_dynamic_respawn_delay_increase * max(0, max_lives - pl_lives);
565         player.respawn_time = time + min(autocvar_g_lms_dynamic_respawn_delay_max, dlay);
566         return true;
567 }
568
569 MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
570 {
571         entity frag_target = M_ARGV(1, entity);
572
573         if (!warmup_stage && time > game_starttime)
574         {
575                 // remove a life
576                 int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1);
577                 if(tl < lms_lowest_lives)
578                         lms_lowest_lives = tl;
579                 if(tl <= 0)
580                 {
581                         int pl_cnt = 0;
582                         FOREACH_CLIENT(IS_PLAYER(it) && it.frags == FRAGS_PLAYER, {
583                                 pl_cnt++;
584                         });
585                         frag_target.frags = FRAGS_PLAYER_OUT_OF_GAME;
586                         GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt);
587                 }
588         }
589         M_ARGV(2, float) = 0; // frag score
590
591         return true;
592 }
593
594 MUTATOR_HOOKFUNCTION(lms, SetStartItems)
595 {
596         start_items &= ~(IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS);
597         if(!cvar("g_use_ammunition"))
598                 start_items |= IT_UNLIMITED_AMMO;
599
600         start_health       = warmup_start_health       = cvar("g_lms_start_health");
601         start_armorvalue   = warmup_start_armorvalue   = cvar("g_lms_start_armor");
602         start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_lms_start_ammo_shells");
603         start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_lms_start_ammo_nails");
604         start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
605         start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_lms_start_ammo_cells");
606         start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_lms_start_ammo_plasma");
607         start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_lms_start_ammo_fuel");
608 }
609
610 MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
611 {
612         // don't clear player score
613         return true;
614 }
615
616 MUTATOR_HOOKFUNCTION(lms, FilterItemDefinition)
617 {
618         if (autocvar_g_lms_items)
619                 return false;
620
621         entity definition = M_ARGV(0, entity);
622
623         if (autocvar_g_lms_extra_lives && definition == ITEM_ExtraLife)
624         {
625                 return false;
626         }
627         return (autocvar_g_pickup_items <= 0); // only allow items if explicitly enabled
628 }
629
630 void lms_extralife(entity this)
631 {
632         StartItem(this, ITEM_ExtraLife);
633 }
634
635 MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
636 {
637         if (MUTATOR_RETURNVALUE) return false;
638         if (!autocvar_g_powerups) return false;
639         if (!autocvar_g_lms_extra_lives) return false;
640
641         entity ent = M_ARGV(0, entity);
642
643         // Can't use .itemdef here
644         if (ent.classname != "item_health_mega") return false;
645
646         entity e = spawn();
647         setthink(e, lms_extralife);
648
649         e.nextthink = time + 0.1;
650         e.spawnflags = ent.spawnflags;
651         e.noalign = ent.noalign;
652         setorigin(e, ent.origin);
653
654         return true;
655 }
656
657 MUTATOR_HOOKFUNCTION(lms, ItemTouch)
658 {
659         if(MUTATOR_RETURNVALUE) return false;
660
661         entity item = M_ARGV(0, entity);
662         entity toucher = M_ARGV(1, entity);
663
664         if(item.itemdef == ITEM_ExtraLife)
665         {
666                 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES, autocvar_g_lms_extra_lives);
667                 GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives);
668                 return MUT_ITEMTOUCH_PICKUP;
669         }
670
671         return MUT_ITEMTOUCH_CONTINUE;
672 }
673
674 MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
675 {
676         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
677                 if (INGAME(it))
678                         ++M_ARGV(0, int); // activerealplayers
679                 ++M_ARGV(1, int); // realplayers
680         });
681
682         return true;
683 }
684
685 MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
686 {
687         entity player = M_ARGV(0, entity);
688         if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_PLAYER_OUT_OF_GAME)
689                 return MUT_SPECCMD_CONTINUE;
690         // ranked players (out of game) can no longer become real spectators
691         return MUT_SPECCMD_RETURN;
692 }
693
694 MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
695 {
696         M_ARGV(0, float) = WinningCondition_LMS();
697         return true;
698 }
699
700 MUTATOR_HOOKFUNCTION(lms, SetWeaponArena)
701 {
702         if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
703                 M_ARGV(0, string) = autocvar_g_lms_weaponarena;
704 }
705
706 MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
707 {
708         if(game_stopped)
709         if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
710                 return true; // allow writing to this field in intermission as it is needed for newly joining players
711 }
712
713 void lms_Initialize()
714 {
715         lms_lowest_lives = 999;
716 }