diff --git a/Release/Plugins/RenX.Core.lib b/Release/Plugins/RenX.Core.lib index 8d90ffc..ba038ca 100644 Binary files a/Release/Plugins/RenX.Core.lib and b/Release/Plugins/RenX.Core.lib differ diff --git a/RenX.Commands/RenX_Commands.cpp b/RenX.Commands/RenX_Commands.cpp index b8d6894..45a811e 100644 --- a/RenX.Commands/RenX_Commands.cpp +++ b/RenX.Commands/RenX_Commands.cpp @@ -1300,7 +1300,7 @@ void GameOverIRCCommand::create() this->setAccessLevel(3); } -void GameOverIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString &channel, const Jupiter::ReadableString &nick, const Jupiter::ReadableString &) +void GameOverIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString &channel, const Jupiter::ReadableString &nick, const Jupiter::ReadableString ¶meters) { Jupiter::IRC::Client::Channel *chan = source->getChannel(channel); if (chan != nullptr) @@ -1313,8 +1313,19 @@ void GameOverIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString if (server->isLogChanType(type)) { match = true; - if (server->gameover() == false) - source->sendMessage(channel, STRING_LITERAL_AS_REFERENCE("Error: Server does not support gameover.")); + if (parameters.equalsi("empty"_jrs)) + server->gameoverWhenEmpty(); + if (parameters.equalsi("if empty"_jrs)) + { + if (server->players.size() == 0) + server->gameover(); + } + else if (parameters.equalsi("now"_jrs)) + server->gameover(); + else if (parameters.isEmpty()) + server->gameover(std::chrono::seconds(10)); + else + server->gameover(std::chrono::seconds(parameters.asInt())); } } if (match == false) @@ -1324,7 +1335,7 @@ void GameOverIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString const Jupiter::ReadableString &GameOverIRCCommand::getHelp(const Jupiter::ReadableString &) { - static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Ends the game immediately. Syntax: Gameover"); + static STRING_LITERAL_AS_NAMED_REFERENCE(defaultHelp, "Ends the game immediately. Syntax: Gameover [NOW | Empty | Seconds = 10]"); return defaultHelp; } diff --git a/RenX.Core/RenX_Server.cpp b/RenX.Core/RenX_Server.cpp index d92057a..bf039a1 100644 --- a/RenX.Core/RenX_Server.cpp +++ b/RenX.Core/RenX_Server.cpp @@ -38,6 +38,7 @@ int RenX::Server::think() { if (RenX::Server::connected == false) { + // Not connected; attempt retry if needed if (RenX::Server::maxAttempts < 0 || RenX::Server::attempts < RenX::Server::maxAttempts) { if (std::chrono::steady_clock::now() >= RenX::Server::lastAttempt + RenX::Server::delay) @@ -52,11 +53,13 @@ int RenX::Server::think() } else if (RenX::Server::awaitingPong && std::chrono::steady_clock::now() - RenX::Server::lastActivity >= RenX::Server::pingTimeoutThreshold) // ping timeout { + // Ping timeout; disconnect immediately RenX::Server::sendLogChan(STRING_LITERAL_AS_REFERENCE(IRCCOLOR "04[Error]" IRCCOLOR " Disconnected from Renegade-X server (ping timeout).")); RenX::Server::disconnect(RenX::DisconnectReason::PingTimeout); } else { + // Connected and fine if (RenX::Server::sock.recv() > 0) { Jupiter::ReadableString::TokenizeResult result = Jupiter::ReferenceString::tokenize(RenX::Server::sock.getBuffer(), '\n'); @@ -110,6 +113,12 @@ int RenX::Server::think() if (RenX::Server::buildingUpdateRate != std::chrono::milliseconds::zero() && std::chrono::steady_clock::now() > RenX::Server::lastBuildingListUpdate + RenX::Server::buildingUpdateRate) RenX::Server::updateBuildingList(); } + + if (RenX::Server::gameover_pending && RenX::Server::gameover_time < std::chrono::steady_clock::now()) + { + this->gameover(); + RenX::Server::gameover_pending = false; + } } return 0; } @@ -279,18 +288,27 @@ std::chrono::milliseconds RenX::Server::getGameTime(const RenX::PlayerInfo *play return std::chrono::duration_cast(std::chrono::steady_clock::now() - player->joinTime); } +size_t RenX::Server::getBotCount() const +{ + return RenX::Server::bot_count; +} + RenX::PlayerInfo *RenX::Server::getPlayer(int id) const { - if (RenX::Server::players.size() == 0) return nullptr; + if (RenX::Server::players.size() == 0) + return nullptr; + for (Jupiter::DLList::Node *node = RenX::Server::players.getNode(0); node != nullptr; node = node->next) if (node->data->id == id) return node->data; + return nullptr; } RenX::PlayerInfo *RenX::Server::getPlayerByName(const Jupiter::ReadableString &name) const { - if (RenX::Server::players.size() == 0) return nullptr; + if (RenX::Server::players.size() == 0) + return nullptr; for (Jupiter::DLList::Node *node = RenX::Server::players.getNode(0); node != nullptr; node = node->next) if (node->data->name == name) @@ -314,18 +332,25 @@ RenX::PlayerInfo *RenX::Server::getPlayerByName(const Jupiter::ReadableString &n RenX::PlayerInfo *RenX::Server::getPlayerByPartName(const Jupiter::ReadableString &partName) const { - if (RenX::Server::players.size() == 0) return nullptr; + if (RenX::Server::players.size() == 0) + return nullptr; + RenX::PlayerInfo *r = RenX::Server::getPlayerByName(partName); - if (r != nullptr) return r; + if (r != nullptr) + return r; + return RenX::Server::getPlayerByPartNameFast(partName); } RenX::PlayerInfo *RenX::Server::getPlayerByPartNameFast(const Jupiter::ReadableString &partName) const { - if (RenX::Server::players.size() == 0) return nullptr; + if (RenX::Server::players.size() == 0) + return nullptr; + for (Jupiter::DLList::Node *node = RenX::Server::players.getNode(0); node != nullptr; node = node->next) if (node->data->name.findi(partName) != Jupiter::INVALID_INDEX) return node->data; + return nullptr; } @@ -587,7 +612,9 @@ void RenX::Server::banPlayer(const RenX::PlayerInfo *player, const Jupiter::Read bool RenX::Server::removePlayer(int id) { - if (RenX::Server::players.size() == 0) return false; + if (RenX::Server::players.size() == 0) + return false; + for (Jupiter::DLList::Node *node = RenX::Server::players.getNode(0); node != nullptr; node = node->next) { if (node->data->id == id) @@ -596,6 +623,8 @@ bool RenX::Server::removePlayer(int id) Jupiter::ArrayList &xPlugins = *RenX::getCore()->getPlugins(); for (size_t i = 0; i < xPlugins.size(); i++) xPlugins.get(i)->RenX_OnPlayerDelete(this, p); + if (p->isBot) + --this->bot_count; delete p; return true; } @@ -619,16 +648,11 @@ bool RenX::Server::updateClientList() { RenX::Server::lastClientListUpdate = std::chrono::steady_clock::now(); - size_t botCount = 0; - for (size_t i = 0; i != RenX::Server::players.size(); i++) - if (RenX::Server::players.get(i)->isBot) - botCount++; - int r = 0; - if (RenX::Server::players.size() != botCount) + if (RenX::Server::players.size() != RenX::Server::bot_count) r = RenX::Server::sock.send(STRING_LITERAL_AS_REFERENCE("cclientvarlist ID\xA0""SCORE\xA0""CREDITS\xA0""PING\n")) > 0; - if (botCount != 0) + if (RenX::Server::bot_count != 0) r |= RenX::Server::sock.send(STRING_LITERAL_AS_REFERENCE("cbotvarlist ID\xA0""SCORE\xA0""CREDITS\n")) > 0; return r != 0; @@ -642,9 +666,29 @@ bool RenX::Server::updateBuildingList() bool RenX::Server::gameover() { + RenX::Server::gameover_when_empty = false; return RenX::Server::send("endmap"_jrs) > 0; } +void RenX::Server::gameover(std::chrono::seconds delay) +{ + if (delay == std::chrono::seconds::zero()) + this->gameover(); + else + { + this->gameover_time = std::chrono::steady_clock::now() + delay; + this->gameover_pending = true; + } +} + +void RenX::Server::gameoverWhenEmpty() +{ + if (this->players.size() != this->bot_count) + this->gameover(); + else + this->gameover_when_empty = true; +} + bool RenX::Server::setMap(const Jupiter::ReadableString &map) { return RenX::Server::send(Jupiter::StringS::Format("changemap %.*s", map.size(), map.ptr())) > 0; @@ -1239,6 +1283,8 @@ void RenX::Server::processLine(const Jupiter::ReadableString &line) RenX::exemptionDatabase->exemption_check(r); this->banCheck(r); } + else + ++bot_count; for (size_t i = 0; i < xPlugins.size(); i++) xPlugins.get(i)->RenX_OnPlayerCreate(this, r); @@ -2185,6 +2231,7 @@ void RenX::Server::processLine(const Jupiter::ReadableString &line) for (size_t i = 0; i < xPlugins.size(); i++) xPlugins.get(i)->RenX_OnGameOver(this, RenX::WinType::Tie, RenX::TeamType::None, gScore, nScore); } + this->gameover_pending = false; } else { @@ -2278,6 +2325,9 @@ void RenX::Server::processLine(const Jupiter::ReadableString &line) for (size_t i = 0; i < xPlugins.size(); i++) xPlugins.get(i)->RenX_OnPart(this, player); this->removePlayer(player); + + if (this->gameover_when_empty && this->players.size() == this->bot_count) + this->gameover(); } else if (subHeader.equals("Kick;")) { @@ -2801,6 +2851,7 @@ void RenX::Server::wipeData() xPlugins.get(index)->RenX_OnPlayerDelete(this, player); delete player; } + RenX::Server::bot_count = 0; RenX::Server::buildings.emptyAndDelete(); RenX::Server::mutators.emptyAndDelete(); RenX::Server::maps.emptyAndDelete(); diff --git a/RenX.Core/RenX_Server.h b/RenX.Core/RenX_Server.h index 2256b40..bb779d5 100644 --- a/RenX.Core/RenX_Server.h +++ b/RenX.Core/RenX_Server.h @@ -265,6 +265,13 @@ namespace RenX */ std::chrono::milliseconds getGameTime(const RenX::PlayerInfo *player) const; + /** + * @brief Fetches the number of bots in the server. + * + * @return Number of bots in the server. + */ + size_t getBotCount() const; + /** * @brief Fetches a player's data based on their ID number. * @@ -410,12 +417,24 @@ namespace RenX bool updateBuildingList(); /** - * @brief Forces the current game to end. + * @brief Forces the current game to end immediately. * * @return True on success, false otherwise. */ bool gameover(); + /** + * @brief Forces the game to end, after a given delay. + * + * @param delay The number of seconds from now that the gameover should trigger. + */ + void gameover(std::chrono::seconds delay); + + /** + * @brief Forces the game to end when the server is empty + */ + void gameoverWhenEmpty(); + /** * @brief Forces the current game to end and changes the map. * @@ -922,6 +941,8 @@ namespace RenX void init(); /** Tracking variables */ + bool gameover_when_empty = false; + bool gameover_pending = false; bool pure = false; bool connected = false; bool seamless = false; @@ -946,6 +967,7 @@ namespace RenX int vehicleLimit = 0; int mineLimit = 0; int timeLimit = 0; + size_t bot_count = 0; unsigned int rconVersion = 0; double crateRespawnAfterPickup = 0.0; uuid_func calc_uuid; @@ -954,6 +976,7 @@ namespace RenX std::chrono::steady_clock::time_point lastClientListUpdate = std::chrono::steady_clock::now(); std::chrono::steady_clock::time_point lastBuildingListUpdate = std::chrono::steady_clock::now(); std::chrono::steady_clock::time_point lastActivity = std::chrono::steady_clock::now(); + std::chrono::steady_clock::time_point gameover_time; Jupiter::String lastLine; Jupiter::StringS rconUser; Jupiter::StringS gameVersion;