diff --git a/src/main.cpp b/src/main.cpp index b5373a845..7082ae636 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -251,6 +251,7 @@ #include "utils/log.hpp" #include "utils/mini_glm.hpp" #include "utils/profiler.hpp" +#include "utils/separate_process.hpp" #include "utils/string_utils.hpp" #include "utils/translation.hpp" @@ -1326,6 +1327,14 @@ int handleCmdLine(bool has_server_config, bool has_parent_process) } } + int ai_num = 0; + if (CommandLine::has("--server-ai", &ai_num)) + { + Log::info("main", "Add %d server ai(s) server configurable will be " + "disabled.", ai_num); + ServerConfig::m_server_configurable = false; + } + std::string ipv4; std::string ipv6; bool has_ipv4 = CommandLine::has("--connect-now", &ipv4); @@ -1407,6 +1416,18 @@ int handleCmdLine(bool has_server_config, bool has_parent_process) Log::info("main", "Creating a LAN server '%s'.", server_name.c_str()); } + if (ai_num > 0) + { + STKHost::get()->setSeparateProcess( + new SeparateProcess( + SeparateProcess::getCurrentExecutableLocation(), + std::string("--stdout=server_ai.log --no-graphics" + " --auto-connect --connect-now=127.0.0.1:") + + StringUtils::toString(STKHost::get()->getPrivatePort()) + + " --no-console-log --network-ai=" + + StringUtils::toString(ai_num), false/*create_pipe*/, + "childprocess_ai"/*childprocess_name*/)); + } } if (CommandLine::has("--auto-connect")) diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index 09e2a53f6..3a3710301 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -361,10 +361,12 @@ void ClientLobby::update(int ticks) case LINKED: { NetworkConfig::get()->clearServerCapabilities(); + std::string ua = StringUtils::getUserAgentString(); + if (NetworkConfig::get()->isNetworkAITester()) + ua = "AI"; NetworkString* ns = getNetworkString(); ns->addUInt8(LE_CONNECTION_REQUESTED) - .addUInt32(ServerConfig::m_server_version) - .encodeString(StringUtils::getUserAgentString()) + .addUInt32(ServerConfig::m_server_version).encodeString(ua) .addUInt16((uint16_t)stk_config->m_network_capabilities.size()); for (const std::string& cap : stk_config->m_network_capabilities) ns->encodeString(cap); @@ -391,7 +393,10 @@ void ClientLobby::update(int ticks) bool encryption = false; uint32_t id = PlayerManager::getCurrentOnlineId(); - + bool lan_ai = !m_server->supportsEncryption() && + NetworkConfig::get()->isNetworkAITester(); + if (lan_ai) + id = 0; BareNetworkString* rest = new BareNetworkString(); if (m_server->supportsEncryption() && id != 0) { @@ -410,11 +415,22 @@ void ClientLobby::update(int ticks) rest->encodeString(ServerConfig::m_private_server_password) .addUInt8(player_count); - for (auto& p : NetworkConfig::get()->getNetworkPlayers()) + for (unsigned i = 0; + i < NetworkConfig::get()->getNetworkPlayers().size(); i++) { - core::stringw name; + auto& p = NetworkConfig::get()->getNetworkPlayers()[i]; PlayerProfile* player = std::get<1>(p); - rest->encodeString(player->getName()). + core::stringw name = player->getName(); + if (lan_ai) + { + // I18N: Shown in lobby to indicate it's a bot in LAN game + name = _("Bot"); + if (i > 0) + { + name += core::stringw(" ") + StringUtils::toWString(i); + } + } + rest->encodeString(name). addFloat(player->getDefaultKartColor()); // Per-player handicap rest->addUInt8(std::get<2>(p)); diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index c6c826b81..eca0de871 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -3201,7 +3201,7 @@ void ServerLobby::updateServerOwner() for (auto peer: peers) { // Only 127.0.0.1 can be server owner in case of graphics-client-server - if (peer->isValidated() && + if (peer->isValidated() && peer->getUserVersion() != "AI" && (NetworkConfig::get()->getServerIdFile().empty() || peer->getAddress().getIP() == 0x7f000001)) { diff --git a/src/network/stk_host.cpp b/src/network/stk_host.cpp index 323f01560..bd230a203 100644 --- a/src/network/stk_host.cpp +++ b/src/network/stk_host.cpp @@ -1639,3 +1639,17 @@ void STKHost::updatePlayers(unsigned* ingame, unsigned* waiting, if (total) *total = total_players; } // updatePlayers + +// ---------------------------------------------------------------------------- +/** True if this is a client and server in graphics mode made by server + * creation screen. */ +bool STKHost::isClientServer() const +{ + return NetworkConfig::get()->isClient() && m_separate_process != NULL; +} // isClientServer + +// ---------------------------------------------------------------------------- +bool STKHost::hasServerAI() const +{ + return NetworkConfig::get()->isServer() && m_separate_process != NULL; +} // hasServerAI diff --git a/src/network/stk_host.hpp b/src/network/stk_host.hpp index f21623f7f..a672b081f 100644 --- a/src/network/stk_host.hpp +++ b/src/network/stk_host.hpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -340,9 +341,15 @@ public: // ------------------------------------------------------------------------ void sendToServer(NetworkString *data, bool reliable = true); // ------------------------------------------------------------------------ - /** True if this is a client and server in graphics mode made by server - * creation screen. */ - bool isClientServer() const { return m_separate_process != NULL; } + bool isClientServer() const; + // ------------------------------------------------------------------------ + bool hasServerAI() const; + // ------------------------------------------------------------------------ + void setSeparateProcess(SeparateProcess* p) + { + assert(m_separate_process == NULL); + m_separate_process = p; + } // ------------------------------------------------------------------------ void initClientNetwork(ENetEvent& event, Network* new_network); // ------------------------------------------------------------------------ diff --git a/src/states_screens/online/create_server_screen.cpp b/src/states_screens/online/create_server_screen.cpp index f5331c5a4..12375508a 100644 --- a/src/states_screens/online/create_server_screen.cpp +++ b/src/states_screens/online/create_server_screen.cpp @@ -86,6 +86,7 @@ void CreateServerScreen::loadedFromFile() void CreateServerScreen::init() { Screen::init(); + m_supports_ai = NetworkConfig::get()->isLAN(); m_info_widget->setText("", false); LabelWidget *title = getWidget("title"); @@ -142,7 +143,14 @@ void CreateServerScreen::eventCallback(Widget* widget, const std::string& name, updateMoreOption(selection); m_prev_mode = selection; } - + else if (name == m_max_players_widget->m_properties[PROP_ID] && + m_supports_ai) + { + m_prev_value = m_more_options_spinner->getValue(); + const int selection = + m_game_mode_widget->getSelection(PLAYER_ID_GAME_MASTER); + updateMoreOption(selection); + } } // eventCallback // ---------------------------------------------------------------------------- @@ -154,17 +162,39 @@ void CreateServerScreen::updateMoreOption(int game_mode) case 1: { m_more_options_text->setVisible(true); - //I18N: In the create server screen - m_more_options_text->setText(_("No. of grand prix track(s)"), - false); m_more_options_spinner->setVisible(true); m_more_options_spinner->clearLabels(); - m_more_options_spinner->addLabel(_("Disabled")); - for (int i = 1; i <= 20; i++) + if (m_supports_ai) { - m_more_options_spinner->addLabel(StringUtils::toWString(i)); + m_more_options_text->setText(_("Number of AI karts"), + false); + for (int i = 0; i <= m_max_players_widget->getValue() - 2; i++) + { + m_more_options_spinner->addLabel( + StringUtils::toWString(i)); + } + if (m_prev_value > m_max_players_widget->getValue() - 2) + { + m_more_options_spinner->setValue( + m_max_players_widget->getValue() - 2); + } + else + m_more_options_spinner->setValue(m_prev_value); + + } + else + { + //I18N: In the create server screen + m_more_options_text->setText(_("No. of grand prix track(s)"), + false); + m_more_options_spinner->addLabel(_("Disabled")); + for (int i = 1; i <= 20; i++) + { + m_more_options_spinner->addLabel( + StringUtils::toWString(i)); + } + m_more_options_spinner->setValue(m_prev_value); } - m_more_options_spinner->setValue(m_prev_value); break; } case 2: @@ -360,9 +390,17 @@ void CreateServerScreen::createServer() } else { - // Grand prix track count - if (esi > 0) - server_cfg << " --network-gp=" << esi; + if (m_supports_ai) + { + if (esi > 0) + server_cfg << " --server-ai=" << esi; + } + else + { + // Grand prix track count + if (esi > 0) + server_cfg << " --network-gp=" << esi; + } } m_prev_mode = gamemode_widget->getSelection(PLAYER_ID_GAME_MASTER); m_prev_value = esi; diff --git a/src/states_screens/online/create_server_screen.hpp b/src/states_screens/online/create_server_screen.hpp index fbc86ca8c..98dae163d 100644 --- a/src/states_screens/online/create_server_screen.hpp +++ b/src/states_screens/online/create_server_screen.hpp @@ -34,6 +34,8 @@ class CreateServerScreen : public GUIEngine::Screen, private: int m_prev_mode, m_prev_value; + bool m_supports_ai; + friend class GUIEngine::ScreenSingleton; CreateServerScreen(); diff --git a/src/utils/separate_process.cpp b/src/utils/separate_process.cpp index 118b51f38..71724046e 100644 --- a/src/utils/separate_process.cpp +++ b/src/utils/separate_process.cpp @@ -79,14 +79,15 @@ std::string SeparateProcess::getCurrentExecutableLocation() // ---------------------------------------------------------------------------- SeparateProcess::SeparateProcess(const std::string& exe, - const std::string& argument, bool create_pipe) + const std::string& argument, bool create_pipe, + const std::string& childprocess_name) { #ifdef ANDROID m_child_handle = NULL; m_child_abort_proc = NULL; #endif - if (!createChildProcess(exe, argument, create_pipe)) + if (!createChildProcess(exe, argument, create_pipe, childprocess_name)) { Log::error("SeparateProcess", "Failed to run %s %s", exe.c_str(), argument.c_str()); @@ -184,7 +185,8 @@ SeparateProcess::~SeparateProcess() #if defined(WIN32) bool SeparateProcess::createChildProcess(const std::string& exe, const std::string& argument, - bool create_pipe) + bool create_pipe, + const std::string& childprocess_name) { // Based on: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx SECURITY_ATTRIBUTES sec_attr; @@ -281,7 +283,8 @@ bool SeparateProcess::createChildProcess(const std::string& exe, bool SeparateProcess::createChildProcess(const std::string& exe, const std::string& argument, - bool create_pipe) + bool create_pipe, + const std::string& childprocess_name) { if (create_pipe) { @@ -323,9 +326,10 @@ bool SeparateProcess::createChildProcess(const std::string& exe, } Log::info("SeparateProcess", "Data dir found in: %s", data_path.c_str()); - - std::string child_path = data_path + "/files/libchildprocess.so"; - + + std::string child_path = data_path + "/files/lib" + + childprocess_name + ".so"; + if (access(child_path.c_str(), R_OK) != 0) { Log::info("SeparateProcess", "Creating libchildprocess.so"); @@ -408,7 +412,8 @@ bool SeparateProcess::createChildProcess(const std::string& exe, bool SeparateProcess::createChildProcess(const std::string& exe, const std::string& argument, - bool create_pipe) + bool create_pipe, + const std::string& childprocess_name) { const int PIPE_READ=0; const int PIPE_WRITE=1; diff --git a/src/utils/separate_process.hpp b/src/utils/separate_process.hpp index 7cbc7e02f..94e348e28 100644 --- a/src/utils/separate_process.hpp +++ b/src/utils/separate_process.hpp @@ -51,7 +51,8 @@ private: // ------------------------------------------------------------------------ bool createChildProcess(const std::string& exe, - const std::string& argument, bool create_pipe); + const std::string& argument, bool create_pipe, + const std::string& childprocess_name); // ------------------------------------------------------------------------ std::string getLine(); @@ -60,7 +61,8 @@ public: static std::string getCurrentExecutableLocation(); // ------------------------------------------------------------------------ SeparateProcess(const std::string& exe, const std::string& argument, - bool create_pipe = false); + bool create_pipe = false, + const std::string& childprocess_name = "childprocess"); // ------------------------------------------------------------------------ ~SeparateProcess(); // ------------------------------------------------------------------------