1 #include "gamemode_lms.qh"
3 #include <common/mutators/mutator/instagib/items.qc>
4 #include <server/campaign.qh>
5 #include <server/command/_mod.qh>
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;
13 float LMS_NewPlayerLives()
16 fl = autocvar_fraglimit;
21 // first player has left the game for dying too much? Nobody else can get in.
22 if (lms_lowest_lives < 1) {
26 if (!autocvar_g_lms_join_anytime) {
27 if (lms_lowest_lives < fl - autocvar_g_lms_last_join) {
32 return bound(1, lms_lowest_lives, fl);
37 // LMS winning condition: game terminates if and only if there's at most one
38 // one player who's living lives. Top two scores being equal cancels the time
40 int WinningCondition_LMS()
43 bool have_player = false;
44 bool have_players = false;
46 int l = LMS_NewPlayerLives();
48 head = find(NULL, classname, STR_PLAYER);
52 head2 = find(head, classname, STR_PLAYER);
58 // we have at least one player
60 // two or more active players - continue with the game
62 // exactly one player?
65 SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
68 // game still running (that is, nobody got removed from the game by a frag yet)? then continue
72 // and assign him his first place
73 GameRules_scoring_add(head, LMS_RANK, 1);
82 // nobody is playing at all...
84 // wait for players...
86 // SNAFU (maybe a draw game?)
88 LOG_TRACE("No players, ending game.");
93 // When we get here, we have at least two players who are actually LIVING,
94 // now check if the top two players have equal score.
95 WinningConditionHelper(NULL);
98 if (WinningConditionHelper_winner) {
99 WinningConditionHelper_winner.winning = true;
101 if (WinningConditionHelper_topscore == WinningConditionHelper_secondscore) {
102 return WINNING_NEVER;
105 // Top two have different scores? Way to go for our beloved TIMELIMIT!
110 MUTATOR_HOOKFUNCTION(lms, reset_map_global)
112 lms_lowest_lives = 999;
115 MUTATOR_HOOKFUNCTION(lms, reset_map_players)
117 FOREACH_CLIENT(true, {
118 TRANSMUTE(Player, it);
119 it.frags = FRAGS_PLAYER;
120 GameRules_scoring_add(it, LMS_LIVES, LMS_NewPlayerLives());
121 PutClientInServer(it);
125 MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
127 entity player = M_ARGV(0, entity);
129 if (player.frags == FRAGS_SPECTATOR) {
130 TRANSMUTE(Observer, player);
132 float tl = GameRules_scoring_add(player, LMS_LIVES, 0);
133 if (tl < lms_lowest_lives) {
134 lms_lowest_lives = tl;
137 TRANSMUTE(Observer, player);
140 GameRules_scoring_add(player, LMS_RANK, -GameRules_scoring_add(player, LMS_RANK, 0));
145 MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
147 entity player = M_ARGV(0, entity);
152 if (player.frags == FRAGS_SPECTATOR) {
155 if (GameRules_scoring_add(player, LMS_LIVES, 0) <= 0) {
156 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
162 MUTATOR_HOOKFUNCTION(lms, PlayerDies)
164 entity frag_target = M_ARGV(2, entity);
166 frag_target.respawn_flags |= RESPAWN_FORCE;
169 void lms_RemovePlayer(entity player)
171 static int quitters = 0;
172 float player_rank = GameRules_scoring_add(player, LMS_RANK, 0);
175 FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++;
177 if (player.lms_spectate_warning != 2) {
178 if (IS_BOT_CLIENT(player)) {
181 player.frags = FRAGS_LMS_LOSER;
182 GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1);
184 lms_lowest_lives = 999;
185 FOREACH_CLIENT(true, {
186 if (it.frags == FRAGS_LMS_LOSER) {
187 float it_rank = GameRules_scoring_add(it, LMS_RANK, 0);
188 if (it_rank > player_rank && it_rank <= 256) {
189 GameRules_scoring_add(it, LMS_RANK, -1);
191 lms_lowest_lives = 0;
192 } else if (it.frags != FRAGS_SPECTATOR) {
193 float tl = GameRules_scoring_add(it, LMS_LIVES, 0);
194 if (tl < lms_lowest_lives) {
195 lms_lowest_lives = tl;
199 GameRules_scoring_add(player, LMS_RANK, 665 - quitters); // different from 666
201 GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0));
204 player.frags = FRAGS_LMS_LOSER;
205 TRANSMUTE(Observer, player);
207 if (pl_cnt == 2 && !warmup_stage) { // a player is forfeiting leaving only one player
208 lms_lowest_lives = 0; // end the game now!
212 if (CS(player).killcount != FRAGS_SPECTATOR) {
213 if (GameRules_scoring_add(player, LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2) {
214 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
216 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
221 MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
223 entity player = M_ARGV(0, entity);
225 lms_RemovePlayer(player);
228 MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
230 entity player = M_ARGV(0, entity);
232 lms_RemovePlayer(player);
233 return true; // prevent team reset
236 MUTATOR_HOOKFUNCTION(lms, ClientConnect)
238 entity player = M_ARGV(0, entity);
240 TRANSMUTE(Player, player);
241 campaign_bots_may_start = true;
243 if (GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives()) <= 0) {
244 GameRules_scoring_add(player, LMS_RANK, 666); // mark as forced spectator for the hud code
245 player.frags = FRAGS_SPECTATOR;
249 MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
251 entity player = M_ARGV(0, entity);
253 if (player.deadflag == DEAD_DYING) {
254 player.deadflag = DEAD_RESPAWNING;
258 MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
260 if (autocvar_g_lms_regenerate) {
266 MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
272 MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
274 entity frag_target = M_ARGV(1, entity);
278 int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1);
279 if (tl < lms_lowest_lives) {
280 lms_lowest_lives = tl;
284 FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++;
286 if (IS_BOT_CLIENT(frag_target)) {
287 bot_clear(frag_target);
289 frag_target.frags = FRAGS_LMS_LOSER;
290 GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt);
293 M_ARGV(2, float) = 0; // frag score
298 MUTATOR_HOOKFUNCTION(lms, SetStartItems)
300 start_items &= ~IT_UNLIMITED_AMMO;
301 start_health = warmup_start_health = cvar("g_lms_start_health");
302 start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor");
303 start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells");
304 start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails");
305 start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
306 start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells");
307 start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma");
308 start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel");
311 MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
313 // don't clear player score
317 MUTATOR_HOOKFUNCTION(lms, FilterItem)
319 entity item = M_ARGV(0, entity);
321 if (autocvar_g_lms_extra_lives) {
322 if (item.itemdef == ITEM_ExtraLife) {
330 void lms_extralife(entity this)
332 StartItem(this, ITEM_ExtraLife);
335 MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
337 if (!autocvar_g_powerups) { return false; }
338 if (!autocvar_g_lms_extra_lives) { return false; }
340 entity ent = M_ARGV(0, entity);
342 // Can't use .itemdef here
343 if (ent.classname != "item_health_mega") { return false; }
346 setthink(e, lms_extralife);
348 e.nextthink = time + 0.1;
349 e.spawnflags = ent.spawnflags;
350 e.noalign = ent.noalign;
351 setorigin(e, ent.origin);
356 MUTATOR_HOOKFUNCTION(lms, ItemTouch)
358 entity item = M_ARGV(0, entity);
359 entity toucher = M_ARGV(1, entity);
361 if (item.itemdef == ITEM_ExtraLife) {
362 Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
363 GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives);
364 return MUT_ITEMTOUCH_PICKUP;
367 return MUT_ITEMTOUCH_CONTINUE;
370 MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
372 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
373 ++M_ARGV(0, int); // activerealplayers
374 ++M_ARGV(1, int); // realplayers
380 MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
382 entity player = M_ARGV(0, entity);
384 if (warmup_stage || player.lms_spectate_warning) {
385 // for the forfeit message...
386 player.lms_spectate_warning = 2;
388 if (player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER) {
389 player.lms_spectate_warning = 1;
390 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");
392 return MUT_SPECCMD_RETURN;
394 return MUT_SPECCMD_CONTINUE;
397 MUTATOR_HOOKFUNCTION(lms, CheckRules_World)
399 M_ARGV(0, float) = WinningCondition_LMS();
403 MUTATOR_HOOKFUNCTION(lms, WantWeapon)
405 M_ARGV(2, bool) = true; // all weapons
408 MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
413 MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
416 if (M_ARGV(0, entity) == SP_LMS_RANK) { // score field
417 return true; // allow writing to this field in intermission as it is needed for newly joining players
422 void lms_Initialize()
424 lms_lowest_lives = 9999;