diff --git a/client/src/cl_stubs.cpp b/client/src/cl_stubs.cpp index 0b1af89..458a153 100644 --- a/client/src/cl_stubs.cpp +++ b/client/src/cl_stubs.cpp @@ -61,6 +61,7 @@ void CTF_RememberFlagPos(mapthing2_t *mthing) {} void CTF_SpawnFlag(flag_t f) {} bool SV_AwarenessUpdate(player_t &pl, AActor* mo) { return true; } void SV_SendPackets(void) {} +void SV_SetPlayerSpec(player_t &player, bool setting, bool silent) {} VERSION_CONTROL (cl_stubs_cpp, "$Id: cl_stubs.cpp 4469 2014-01-03 23:38:29Z dr_sean $") diff --git a/client/src/hu_elements.cpp b/client/src/hu_elements.cpp index 7a56f37..5ca6aab 100644 --- a/client/src/hu_elements.cpp +++ b/client/src/hu_elements.cpp @@ -46,6 +46,7 @@ extern fixed_t FocalLengthX; EXTERN_CVAR (sv_fraglimit) EXTERN_CVAR (sv_gametype) +EXTERN_CVAR (sv_maxlives) EXTERN_CVAR (sv_maxclients) EXTERN_CVAR (sv_maxplayers) EXTERN_CVAR (sv_maxplayersperteam) @@ -200,6 +201,9 @@ std::string HelpText() { } } + if (sv_maxlives > 0 && warmup.get_status () == ::Warmup::INGAME) + return "Game has already started"; + return "Press USE to join"; } @@ -220,34 +224,48 @@ std::string SpyPlayerName(int& color) { return plyr->userinfo.netname; } + // Returns a string that contains the current warmup state. std::string Warmup(int& color) { color = CR_GREY; player_t *dp = &displayplayer(); player_t *cp = &consoleplayer(); + std::string type = ""; + + if (sv_maxlives > 0) + { + if (sv_gametype == GM_COOP) + type = "Survival: "; + if (sv_gametype == GM_DM) + type = "Last Man Standing: "; + if (sv_gametype == GM_TEAMDM) + type = "Team LMS: "; + } + else + type = "Warmup: "; ::Warmup::status_t wstatus = warmup.get_status(); if (wstatus == ::Warmup::WARMUP) { if (dp->spectator) - return "Warmup: You are spectating"; + return type + "You are spectating"; else if (dp->ready) { color = CR_GREEN; if (dp == cp) - return "Warmup: You are ready"; + return type + "You are ready"; else - return "Warmup: This player is ready"; + return type + "This player is ready"; } else { color = CR_RED; if (dp == cp) - return "Warmup: You are not ready"; + return type + "You are not ready"; else - return "Warmup: This player is not ready"; + return type + "This player is not ready"; } } else if (wstatus == ::Warmup::COUNTDOWN || wstatus == ::Warmup::FORCE_COUNTDOWN) diff --git a/client/src/hu_stuff.cpp b/client/src/hu_stuff.cpp index 6aeb486..1e59a98 100644 --- a/client/src/hu_stuff.cpp +++ b/client/src/hu_stuff.cpp @@ -240,6 +240,7 @@ BOOL HU_Responder (event_t *ev) EXTERN_CVAR(hud_targetcount) EXTERN_CVAR (sv_maxplayers) +EXTERN_CVAR (sv_maxlives) // // HU_Drawer @@ -446,12 +447,18 @@ void drawHeader(player_t *player, int y) { std::string str; // Center - if (sv_gametype == GM_COOP) { + if (sv_gametype == GM_COOP && sv_maxlives > 0) { + str = "SURVIVAL"; + } else if (sv_gametype == GM_COOP) { str = "COOPERATIVE"; } else if (sv_gametype == GM_DM && sv_maxplayers == 2) { str = "DUEL"; + } else if (sv_gametype == GM_DM && sv_maxlives > 0) { + str = "LAST MAN STANDING"; } else if (sv_gametype == GM_DM) { str = "DEATHMATCH"; + } else if (sv_gametype == GM_TEAMDM && sv_maxlives > 0) { + str = "TEAM LAST MAN STANDING"; } else if (sv_gametype == GM_TEAMDM) { str = "TEAM DEATHMATCH"; } else if (sv_gametype == GM_CTF) { @@ -919,12 +926,18 @@ void drawLowHeader(player_t *player, int y) { std::string str; // Center - if (sv_gametype == GM_COOP) { + if (sv_gametype == GM_COOP && sv_maxlives > 0) { + str = "SURVIVAL"; + } else if (sv_gametype == GM_COOP) { str = "COOPERATIVE"; } else if (sv_gametype == GM_DM && sv_maxplayers == 2) { str = "DUEL"; + } else if (sv_gametype == GM_DM && sv_maxlives > 0) { + str = "LAST MAN STANDING"; } else if (sv_gametype == GM_DM) { str = "DEATHMATCH"; + } else if (sv_gametype == GM_TEAMDM && sv_maxlives > 0) { + str = "TEAM LAST MAN STANDING"; } else if (sv_gametype == GM_TEAMDM) { str = "TEAM DEATHMATCH"; } else if (sv_gametype == GM_CTF) { diff --git a/common/p_interaction.cpp b/common/p_interaction.cpp index 4bd8ea3..017cdb4 100644 --- a/common/p_interaction.cpp +++ b/common/p_interaction.cpp @@ -97,6 +97,9 @@ void P_GiveKills(player_t* player, int num) player->killcount += num; } +EXTERN_CVAR (sv_maxlives) +void SV_SetPlayerSpec(player_t &player, bool setting, bool silent); + // Give coop kills to a player void P_GiveDeaths(player_t* player, int num) { @@ -1024,6 +1027,7 @@ void SexMessage (const char *from, char *to, int gender, const char *victim, con } extern player_t *last_winner; +size_t P_NumPlayersInGame(); // // P_KillMobj @@ -1229,9 +1233,61 @@ void P_KillMobj(AActor *source, AActor *target, AActor *inflictor, bool joinkill { ClientObituary(target, inflictor, source); } + + if (serverside && sv_maxlives > 0 && tplayer && tplayer->deathcount >= sv_maxlives) + { + SV_SetPlayerSpec (*tplayer, true, true); + SV_BroadcastPrintf(PRINT_HIGH, "%s ran out of lives.\n", tplayer->userinfo.netname.c_str ()); + } + // Check sv_fraglimit. - if (source && source->player && target->player && level.time) + if (source && splayer && tplayer && level.time) { + // [tm512 2014/04/16] Check for last man standing + if (sv_gametype != GM_COOP && sv_maxlives > 0) + { + if (sv_gametype == GM_DM && P_NumPlayersInGame () == 1) + { + player_t *lastplayer = NULL; + // find last player in the game, since it isn't always splayer + for (Players::iterator pit = players.begin();pit != players.end();++pit) + { + if (!pit->spectator && pit->ingame ()) + { + lastplayer = &*pit; + break; + } + } + + SV_BroadcastPrintf (PRINT_HIGH, "Game won by %s!\n", lastplayer->userinfo.netname.c_str ()); + shotclock = TICRATE * 2; + } + else if (sv_gametype == GM_TEAMDM) + { + int players_left [NUMTEAMS] = { 0 }; + + // count the number of players for each team + for (Players::iterator pit = players.begin();pit != players.end();++pit) + { + if (!pit->spectator && pit->ingame ()) + players_left [pit->userinfo.team] ++; + } + + // if more than two teams are added, this will need to be changed to something less simple + bool redwins = (players_left [0] == 0); + bool bluewins = (players_left [1] == 0); + + if (redwins || bluewins) + { + int winteam = bluewins ? 0 : 1; + + SV_BroadcastPrintf (PRINT_HIGH, "%s team wins!\n", team_names [winteam]); + shotclock = TICRATE * 2; + } + } + + } + // [Toke] Better sv_fraglimit if (sv_gametype == GM_DM && sv_fraglimit && splayer->fragcount >= sv_fraglimit && !shotclock) diff --git a/server/src/g_level.cpp b/server/src/g_level.cpp index 2ffb0eb..bfe8c71 100644 --- a/server/src/g_level.cpp +++ b/server/src/g_level.cpp @@ -84,6 +84,7 @@ EXTERN_CVAR (sv_loopepisode) EXTERN_CVAR (sv_intermissionlimit) EXTERN_CVAR (sv_warmup) EXTERN_CVAR (sv_timelimit) +EXTERN_CVAR (sv_maxlives) extern int mapchange; extern int shotclock; @@ -376,7 +377,7 @@ void G_InitNew (const char *mapname) } // [tm512 2014/04/10] For survival/duel, put waiting players in the game now - if (sv_maxplayers == 2 && last_winner) + if ((sv_maxlives > 0 && sv_gametype == GM_COOP) || (sv_maxplayers == 2 && last_winner)) { if (sv_maxplayers == 2) // respawn the winner of the previous duel { diff --git a/server/src/g_warmup.cpp b/server/src/g_warmup.cpp index 5d4943a..4329d9b 100644 --- a/server/src/g_warmup.cpp +++ b/server/src/g_warmup.cpp @@ -1,256 +1,287 @@ -// Emacs style mode select -*- C++ -*- -//----------------------------------------------------------------------------- -// -// $Id$ -// -// Copyright (C) 2012 by The Odamex Team. -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// DESCRIPTION: -// Implements warmup mode. -// -//----------------------------------------------------------------------------- - -#include "g_warmup.h" - -#include - -#include "c_cvars.h" -#include "c_dispatch.h" -#include "d_player.h" -#include "g_level.h" -#include "i_net.h" -#include "sv_main.h" - -EXTERN_CVAR(sv_gametype) -EXTERN_CVAR(sv_warmup) -EXTERN_CVAR(sv_warmup_autostart) -EXTERN_CVAR(sv_countdown) -EXTERN_CVAR(sv_timelimit) - -extern int mapchange; - -// Store Warmup state. -Warmup warmup; - -// Broadcast warmup state to client. -void SV_SendWarmupState(player_t &player, Warmup::status_t status, short count = 0) -{ - client_t* cl = &player.client; - MSG_WriteMarker(&cl->reliablebuf, svc_warmupstate); - MSG_WriteByte(&cl->reliablebuf, static_cast(status)); - if (status == Warmup::COUNTDOWN || status == Warmup::FORCE_COUNTDOWN) - MSG_WriteShort(&cl->reliablebuf, count); -} - -// Broadcast warmup state to all clients. -void SV_BroadcastWarmupState(Warmup::status_t status, short count = 0) -{ - for (Players::iterator it = players.begin(); it != players.end(); ++it) - { - if (!it->ingame()) - continue; - SV_SendWarmupState(*it, status, count); - } -} - -// Set warmup status and send clients warmup information. -void Warmup::set_status(Warmup::status_t new_status) -{ - this->status = new_status; - - // [AM] If we switch to countdown, set the correct time. - if (this->status == Warmup::COUNTDOWN || this->status == Warmup::FORCE_COUNTDOWN) - { - this->time_begin = level.time + (sv_countdown.asInt() * TICRATE); - SV_BroadcastWarmupState(new_status, (short)sv_countdown.asInt()); - } - else - SV_BroadcastWarmupState(new_status); -} - -// Status getter -Warmup::status_t Warmup::get_status() -{ - return this->status; -} - -// Warmup countdown getter -short Warmup::get_countdown() -{ - return ceil((this->time_begin - level.time) / (float)TICRATE); -} - -// Reset warmup to "factory defaults". -void Warmup::reset() -{ - if (sv_warmup && sv_gametype != GM_COOP) - this->set_status(Warmup::WARMUP); - else - this->set_status(Warmup::DISABLED); - this->time_begin = 0; -} - -// Don't allow a players score to change if the server is in the middle of warmup. -bool Warmup::checkscorechange() -{ - if (this->status == Warmup::WARMUP || this->status == Warmup::COUNTDOWN || - this->status == Warmup::FORCE_COUNTDOWN) - return false; - return true; -} - -// Don't allow the timeleft to advance if the server is in the middle of warmup. -bool Warmup::checktimeleftadvance() -{ - if (this->status == Warmup::WARMUP || this->status == Warmup::COUNTDOWN || - this->status == Warmup::FORCE_COUNTDOWN) - return false; - return true; -} - -// Don't allow players to fire their weapon if the server is in the middle -// of a countdown. -bool Warmup::checkfireweapon() -{ - if (this->status == Warmup::COUNTDOWN || this->status == Warmup::FORCE_COUNTDOWN) - return false; - return true; -} - -// Check to see if we should allow players to toggle their ready state. -bool Warmup::checkreadytoggle() -{ - if (this->status == Warmup::INGAME || this->status == Warmup::FORCE_COUNTDOWN) - return false; - return true; -} - -extern size_t P_NumPlayersInGame(); -extern size_t P_NumReadyPlayersInGame(); - -// Start or stop the countdown based on if players are ready or not. -void Warmup::readytoggle() -{ - // If warmup mode is disabled, don't bother. - if (this->status == Warmup::DISABLED) - return; - - // If we're not actually in a countdown we can control, don't bother. - if (this->status == Warmup::FORCE_COUNTDOWN) - return; - - // Rerun our checks to make sure we didn't skip them earlier. - if (!this->checkreadytoggle()) - return; - - // Check to see if we satisfy our autostart setting. - size_t ready = P_NumReadyPlayersInGame(); - size_t total = P_NumPlayersInGame(); - size_t needed; - - // We want at least one ingame ready player to start the game. Servers - // that start automatically with no ready players are handled by - // Warmup::tic(). - if (ready == 0 || total == 0) - return; - - float f_calc = total * sv_warmup_autostart; - size_t i_calc = (int)floor(f_calc + 0.5f); - if (f_calc > i_calc - MPEPSILON && f_calc < i_calc + MPEPSILON) { - needed = i_calc + 1; - } - needed = (int)ceil(f_calc); - - if (ready >= needed) - { - if (this->status == Warmup::WARMUP) - this->set_status(Warmup::COUNTDOWN); - } - else - { - if (this->status == Warmup::COUNTDOWN) - { - this->set_status(Warmup::WARMUP); - SV_BroadcastPrintf(PRINT_HIGH, "Countdown aborted: Player unreadied.\n"); - } - } - return; -} - -// We want to restart the map, so initialize a countdown that we -// can't bail out of. -void Warmup::restart() -{ - this->set_status(Warmup::FORCE_COUNTDOWN); -} - -// Force the start of the game. -void Warmup::forcestart() -{ - // Useless outside of warmup. - if (this->status != Warmup::WARMUP) - return; - - this->set_status(Warmup::COUNTDOWN); -} - -// Handle tic-by-tic maintenance of the warmup. -void Warmup::tic() -{ - // If warmup isn't enabled, return - if (this->status == Warmup::DISABLED) - return; - - // If autostart is zeroed out, start immediately. - if (this->status == Warmup::WARMUP && sv_warmup_autostart == 0.0f) - this->set_status(Warmup::COUNTDOWN); - - // If there aren't any more active players, go back to warm up mode [tm512 2014/04/08] - if (this->status != Warmup::WARMUP && P_NumPlayersInGame () == 0) - this->set_status (Warmup::WARMUP); - - // If we're not advancing the countdown, we don't care. - if (!(this->status == Warmup::COUNTDOWN || this->status == Warmup::FORCE_COUNTDOWN)) - return; - - // If we haven't reached the level tic that we begin the map on, - // we don't care. - if (this->time_begin > level.time) - { - // Broadcast a countdown (this should be handled clientside) - if ((this->time_begin - level.time) % TICRATE == 0) - { - SV_BroadcastWarmupState(this->status, this->get_countdown()); - } - return; - } - - if (sv_warmup) - this->set_status(Warmup::INGAME); - else - this->set_status(Warmup::DISABLED); - - // [SL] always reset the time (for now at least) - level.time = 0; - level.timeleft = sv_timelimit * TICRATE * 60; - level.inttimeleft = mapchange / TICRATE; - - G_DeferedFullReset(); - SV_BroadcastPrintf(PRINT_HIGH, "The match has started.\n"); -} - -BEGIN_COMMAND (forcestart) -{ - warmup.forcestart(); -} -END_COMMAND (forcestart) +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id$ +// +// Copyright (C) 2012 by The Odamex Team. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Implements warmup mode. +// +//----------------------------------------------------------------------------- + +#include "g_warmup.h" + +#include + +#include "c_cvars.h" +#include "c_dispatch.h" +#include "d_player.h" +#include "g_level.h" +#include "i_net.h" +#include "sv_main.h" + +EXTERN_CVAR(sv_gametype) +EXTERN_CVAR(sv_warmup) +EXTERN_CVAR(sv_warmup_autostart) +EXTERN_CVAR(sv_countdown) +EXTERN_CVAR(sv_timelimit) +EXTERN_CVAR(sv_maxlives) + +extern int mapchange; + +// Store Warmup state. +Warmup warmup; + +// Broadcast warmup state to client. +void SV_SendWarmupState(player_t &player, Warmup::status_t status, short count = 0) +{ + client_t* cl = &player.client; + MSG_WriteMarker(&cl->reliablebuf, svc_warmupstate); + MSG_WriteByte(&cl->reliablebuf, static_cast(status)); + if (status == Warmup::COUNTDOWN || status == Warmup::FORCE_COUNTDOWN) + MSG_WriteShort(&cl->reliablebuf, count); +} + +// Broadcast warmup state to all clients. +void SV_BroadcastWarmupState(Warmup::status_t status, short count = 0) +{ + for (Players::iterator it = players.begin(); it != players.end(); ++it) + { + if (!it->ingame()) + continue; + SV_SendWarmupState(*it, status, count); + } +} + +// Set warmup status and send clients warmup information. +void Warmup::set_status(Warmup::status_t new_status) +{ + this->status = new_status; + + // [AM] If we switch to countdown, set the correct time. + if (this->status == Warmup::COUNTDOWN || this->status == Warmup::FORCE_COUNTDOWN) + { + this->time_begin = level.time + (sv_countdown.asInt() * TICRATE); + SV_BroadcastWarmupState(new_status, (short)sv_countdown.asInt()); + } + else + SV_BroadcastWarmupState(new_status); +} + +// Status getter +Warmup::status_t Warmup::get_status() +{ + return this->status; +} + +// Warmup countdown getter +short Warmup::get_countdown() +{ + return ceil((this->time_begin - level.time) / (float)TICRATE); +} + +// Reset warmup to "factory defaults". +void Warmup::reset() +{ + if (sv_warmup && sv_gametype != GM_COOP) + this->set_status(Warmup::WARMUP); + else if (sv_maxlives > 0) + this->set_status(Warmup::WARMUP); + else + this->set_status(Warmup::DISABLED); + this->time_begin = 0; +} + +// Don't allow a players score to change if the server is in the middle of warmup. +bool Warmup::checkscorechange() +{ + if (this->status == Warmup::WARMUP || this->status == Warmup::COUNTDOWN || + this->status == Warmup::FORCE_COUNTDOWN) + return false; + return true; +} + +// Don't allow the timeleft to advance if the server is in the middle of warmup. +bool Warmup::checktimeleftadvance() +{ + if (this->status == Warmup::WARMUP || this->status == Warmup::COUNTDOWN || + this->status == Warmup::FORCE_COUNTDOWN) + return false; + return true; +} + +// Don't allow players to fire their weapon if the server is in the middle +// of a countdown. +bool Warmup::checkfireweapon() +{ + if (this->status == Warmup::COUNTDOWN || this->status == Warmup::FORCE_COUNTDOWN) + return false; + return true; +} + +// Check to see if we should allow players to toggle their ready state. +bool Warmup::checkreadytoggle() +{ + if (this->status == Warmup::INGAME || this->status == Warmup::FORCE_COUNTDOWN) + return false; + return true; +} + +extern size_t P_NumPlayersInGame(); +extern size_t P_NumReadyPlayersInGame(); + +// Start or stop the countdown based on if players are ready or not. +void Warmup::readytoggle() +{ + // If warmup mode is disabled, don't bother. + if (this->status == Warmup::DISABLED) + return; + + // If we're not actually in a countdown we can control, don't bother. + if (this->status == Warmup::FORCE_COUNTDOWN) + return; + + // Rerun our checks to make sure we didn't skip them earlier. + if (!this->checkreadytoggle()) + return; + + // Check to see if we satisfy our autostart setting. + size_t ready = P_NumReadyPlayersInGame(); + size_t total = P_NumPlayersInGame(); + size_t needed; + + // We want at least one ingame ready player to start the game. Servers + // that start automatically with no ready players are handled by + // Warmup::tic(). + if (ready == 0 || total == 0) + return; + + float f_calc = total * sv_warmup_autostart; + size_t i_calc = (int)floor(f_calc + 0.5f); + if (f_calc > i_calc - MPEPSILON && f_calc < i_calc + MPEPSILON) { + needed = i_calc + 1; + } + needed = (int)ceil(f_calc); + + if (ready >= needed) + { + if (this->status == Warmup::WARMUP) + this->set_status(Warmup::COUNTDOWN); + } + else + { + if (this->status == Warmup::COUNTDOWN) + { + this->set_status(Warmup::WARMUP); + SV_BroadcastPrintf(PRINT_HIGH, "Countdown aborted: Player unreadied.\n"); + } + } + return; +} + +// We want to restart the map, so initialize a countdown that we +// can't bail out of. +void Warmup::restart() +{ + this->set_status(Warmup::FORCE_COUNTDOWN); +} + +// Force the start of the game. +void Warmup::forcestart() +{ + // Useless outside of warmup. + if (this->status != Warmup::WARMUP) + return; + + this->set_status(Warmup::COUNTDOWN); +} + +player_t *SV_PopFromQueue (void); +void SV_SetPlayerSpec(player_t &player, bool setting, bool silent); + +// Handle tic-by-tic maintenance of the warmup. +void Warmup::tic() +{ + // If warmup isn't enabled, return + if (this->status == Warmup::DISABLED) + return; + + // If autostart is zeroed out, start immediately. + if (this->status == Warmup::WARMUP && sv_warmup_autostart == 0.0f) + this->set_status(Warmup::COUNTDOWN); + + // If there aren't any more active players, go back to warm up mode [tm512 2014/04/08] + if (this->status != Warmup::WARMUP && P_NumPlayersInGame () == 0) + { + this->set_status (Warmup::WARMUP); + if (sv_gametype == GM_COOP) // in survival we want to join some people now + { + player_t *pl; + while (pl = SV_PopFromQueue ()) + SV_SetPlayerSpec (*pl, false, true); + } + } + + // If we're in survival/LMS mode and someone joins the game, activate the countdown + if (sv_maxlives > 0 && P_NumPlayersInGame () > 0) + { + // we're on a brand new level, so don't initiate the countdown, just carry on with the game + if (level.time == 1) + { + this->set_status(Warmup::INGAME); + return; + } + + if (this->status == Warmup::WARMUP) + this->set_status(Warmup::FORCE_COUNTDOWN); + } + + // If we're not advancing the countdown, we don't care. + if (!(this->status == Warmup::COUNTDOWN || this->status == Warmup::FORCE_COUNTDOWN)) + return; + + // If we haven't reached the level tic that we begin the map on, + // we don't care. + if (this->time_begin > level.time) + { + // Broadcast a countdown (this should be handled clientside) + if ((this->time_begin - level.time) % TICRATE == 0) + { + SV_BroadcastWarmupState(this->status, this->get_countdown()); + } + return; + } + + if (sv_warmup || sv_maxlives > 0) + this->set_status(Warmup::INGAME); + else + this->set_status(Warmup::DISABLED); + + // [SL] always reset the time (for now at least) + level.time = 0; + level.timeleft = sv_timelimit * TICRATE * 60; + level.inttimeleft = mapchange / TICRATE; + + if (sv_maxlives > 0) + G_DeferedReset (); + else + G_DeferedFullReset(); + SV_BroadcastPrintf(PRINT_HIGH, "The match has started.\n"); +} + +BEGIN_COMMAND (forcestart) +{ + warmup.forcestart(); +} +END_COMMAND (forcestart) diff --git a/server/src/sv_main.cpp b/server/src/sv_main.cpp index 718b4c6..32235f4 100644 --- a/server/src/sv_main.cpp +++ b/server/src/sv_main.cpp @@ -3683,6 +3683,8 @@ void SV_Spectate (player_t &player) { // param to spec or unspec the player without a broadcasted message. void P_SetSpectatorFlags(player_t &player); +EXTERN_CVAR (sv_maxlives) + void SV_SetPlayerSpec(player_t &player, bool setting, bool silent) { // We don't care about spectators during intermission if (gamestate == GS_INTERMISSION) { @@ -3690,6 +3692,17 @@ void SV_SetPlayerSpec(player_t &player, bool setting, bool silent) { } if (!setting && player.spectator) { + + // Make sure a survival/LMS game isn't active + if (sv_maxlives > 0 && warmup.get_status () == Warmup::INGAME) + { + // Queue join if this is a survival game + if (sv_gametype == GM_COOP) + SV_QueueJoin (&player); + + return; + } + // We want to unspectate the player. if ((level.time > player.joinafterspectatortime + TICRATE * 3) || level.time > player.joinafterspectatortime + TICRATE * 5) { @@ -3796,7 +3809,7 @@ void SV_SetPlayerSpec(player_t &player, bool setting, bool silent) { SV_BroadcastPrintf(PRINT_HIGH, "%s became a spectator.\n", player.userinfo.netname.c_str()); } - // In a duel, try to fill empty spots + // In a duel, try to fill the empty spot if (sv_maxplayers == 2) { player_t *pl = SV_PopFromQueue ();