diff --git a/README.md b/README.md index 71d549bcc2..fbc4296fe7 100644 --- a/README.md +++ b/README.md @@ -383,7 +383,7 @@ Both 'stable' and 'nightly' versions are available for download: - most people should choose the 'stable' version, as this has been more extensively tested - the 'nightly' version includes the latest changes and features, but may sometimes be less reliable -On some platforms OpenTTD will also be available via your OS package manager or a similar service. +OpenTTD is also available for free on [Steam](https://store.steampowered.com/app/1536610/OpenTTD/), [GOG.com](https://www.gog.com/game/openttd), and the [Microsoft Store](https://www.microsoft.com/p/openttd-official/9ncjg5rvrr1c). On some platforms OpenTTD will be available via your OS package manager or a similar service. ## 1.2) OpenTTD gameplay manual diff --git a/docs/game_coordinator.md b/docs/game_coordinator.md new file mode 100644 index 0000000000..e8260f2e1c --- /dev/null +++ b/docs/game_coordinator.md @@ -0,0 +1,64 @@ +# Game Coordinator + +To allow two players to play together, OpenTTD uses a Game Coordinator to +facilitate this. + +When a server starts, it can register itself to the Game Coordinator. This +happens when `server_game_type` is set to either `invite-only` or `public`. +Upon registration, the Game Coordinator probes the network of the server, and +assigns the server an unique code, called an `invite code`. + +When a client wants to join a server, it asks the Game Coordinator to help +with this by giving it the `invite code` of the server. The Game Coordinator +will, in this order, attempt several ways to connect the client and server +together: + +## 1) Via direct IPv4 / IPv6 + +Upon registration, the Game Coordinator probes the server to see if a +direction connection to the server is possible based on the game port. It +tries this over the public IPv4 and IPv6, as announced by the server. +If either (or both) are successful, a client will always be asked to try +these direct IPs first when it wants to connect to this server. + +- If the server is IPv4 only, the client is only asked to connect via IPv4. +- If the server is IPv6 only, the client is only asked to connect via IPv6. +- If the server is both IPv4 and IPv6, the client is asked to connect to to + one of those first. If that fails, it is asked to connect to the other. + Whether it tries IPv4 or IPv6 first, strongly depends on several network + infrastructure related events. The biggest influence is the network + latency over both protocols to the OpenTTD infrastructure. + +In the end, if either the server is not reachable directly from the Internet +or the client fails to connect to either one of them, the connection attempt +continues with the next available method. + +## 2) Via STUN + +When a client wants to join a server via STUN, both the client and server +are asked to create a connection to the STUN server (normally +`stun.openttd.org`) via both IPv4 and IPv6. If the client or server has no +IPv4 or IPv6, it will not make a connection attempt via that protocol. + +The STUN server collects the public IPv4 and/or IPv6 of the client and server, +together with the port the connection came in from. For most NAT gateways it +holds true that as long as you use the same local IP + port, your public +IP + port will remain the same, and allow for bi-directional communication. +And this fact is used to later on pair the client and server. + +The STUN server reports this information directly to the Game Coordinator +(this in contrast to most STUN implementation, where this information is +first reported back to the peer itself, which has to relay it back to the +coordinator. OpenTTD skips this step, as the STUN server can directly talk to +the Game Coordinator). When the Game Coordinator sees a matching pair (in +terms of IPv4 / IPv6), it will ask both the client and server to connect to +their peer based on this public IP + port. + +As the NAT gateway forwards the traffic on the public IP + port to the local +port, this creates a bi-directional socket between client and server. The +connection to the STUN server can now safely be closed, and the client and +server can continue to talk to each other. + +Some NAT gateways do not allow this method; for those this attempt will fail, +and this also means that it is not possible to create a connection between +the client and server. diff --git a/docs/multiplayer.md b/docs/multiplayer.md index daccbf06d6..c7e1839cf3 100644 --- a/docs/multiplayer.md +++ b/docs/multiplayer.md @@ -48,6 +48,10 @@ Last updated: 2011-02-16 - click add server - type in the ip address or hostname - if you want to add a port use : + - If you want to play and you have the invite code of the game server you + want connect to. + - click add server + - type in the invite code - Now you can select a company and press: "Join company", to help that company - Or you can press "Spectate game", to spectate the game - Or you can press "New company", and start your own company (if there are @@ -110,9 +114,10 @@ Last updated: 2011-02-16 - You can let your server automatically restart a map when, let's say, year 2030 is reached. See 'set restart_game_date' for detail. - - If you want to be on the server-list, enable Advertising. To do this, select - 'Internet (advertise)' in the Start Server menu, or type in console: - 'set server_advertise 1'. + - If you want to be on the server-list, make your server public. You can do + this either from the Start Server GUI, via the in-game Online Players GUI, + or by typing in the console: + 'set server_game_type public'. - You can protect your server with a password via the console: 'set server_pw', or via the Start Server menu. diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 563d59b681..b77465efa4 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -793,9 +793,10 @@ DEF_CONSOLE_CMD(ConServerInfo) return true; } - IConsolePrintF(CC_DEFAULT, "Current/maximum clients: %2d/%2d", _network_game_info.clients_on, _settings_client.network.max_clients); - IConsolePrintF(CC_DEFAULT, "Current/maximum companies: %2d/%2d", (int)Company::GetNumItems(), _settings_client.network.max_companies); - IConsolePrintF(CC_DEFAULT, "Current/maximum spectators: %2d/%2d", NetworkSpectatorCount(), _settings_client.network.max_spectators); + IConsolePrintF(CC_DEFAULT, "Invite code: %s", _network_server_invite_code.c_str()); + IConsolePrintF(CC_DEFAULT, "Current/maximum clients: %3d/%3d", _network_game_info.clients_on, _settings_client.network.max_clients); + IConsolePrintF(CC_DEFAULT, "Current/maximum companies: %3d/%3d", (int)Company::GetNumItems(), _settings_client.network.max_companies); + IConsolePrintF(CC_DEFAULT, "Current/maximum spectators: %3d/%3d", NetworkSpectatorCount(), _settings_client.network.max_spectators); return true; } @@ -3470,7 +3471,6 @@ void IConsoleStdLibRegister() IConsole::AliasRegister("name", "setting client_name %+"); IConsole::AliasRegister("server_name", "setting server_name %+"); IConsole::AliasRegister("server_port", "setting server_port %+"); - IConsole::AliasRegister("server_advertise", "setting server_advertise %+"); IConsole::AliasRegister("max_clients", "setting max_clients %+"); IConsole::AliasRegister("max_companies", "setting max_companies %+"); IConsole::AliasRegister("max_spectators", "setting max_spectators %+"); diff --git a/src/debug.cpp b/src/debug.cpp index 2838322ddc..8d6e960909 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -204,9 +204,7 @@ static void debug_print(const char *dbg, const char *buf) * crashing, and NetworkTextMessage includes these */ #if defined(_WIN32) if (strcmp(dbg, "desync") != 0) { - wchar_t system_buf[512]; - convert_to_fs(buffer, system_buf, lengthof(system_buf)); - fputws(system_buf, stderr); + fputs(buffer, stderr); } #else fputs(buffer, stderr); diff --git a/src/fileio.cpp b/src/fileio.cpp index d4a150a53f..202d2a1bd8 100644 --- a/src/fileio.cpp +++ b/src/fileio.cpp @@ -828,7 +828,7 @@ static std::string GetHomeDir() find_directory(B_USER_SETTINGS_DIRECTORY, &path); return std::string(path.Path()); #else - const char *home_env = getenv("HOME"); // Stack var, shouldn't be freed + const char *home_env = std::getenv("HOME"); // Stack var, shouldn't be freed if (home_env != nullptr) return std::string(home_env); const struct passwd *pw = getpwuid(getuid()); @@ -846,7 +846,7 @@ void DetermineBasePaths(const char *exe) std::string tmp; const std::string homedir = GetHomeDir(); #ifdef USE_XDG - const char *xdg_data_home = getenv("XDG_DATA_HOME"); + const char *xdg_data_home = std::getenv("XDG_DATA_HOME"); if (xdg_data_home != nullptr) { tmp = xdg_data_home; tmp += PATHSEP; @@ -977,7 +977,7 @@ void DeterminePaths(const char *exe, bool only_local_path) #ifdef USE_XDG std::string config_home; const std::string homedir = GetHomeDir(); - const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); + const char *xdg_config_home = std::getenv("XDG_CONFIG_HOME"); if (xdg_config_home != nullptr) { config_home = xdg_config_home; config_home += PATHSEP; diff --git a/src/fontcache.cpp b/src/fontcache.cpp index 50ad07973b..3f2eee8512 100644 --- a/src/fontcache.cpp +++ b/src/fontcache.cpp @@ -617,9 +617,9 @@ const Sprite *FreeTypeFontCache::InternalGetGlyph(GlyphID key, bool aa) if (this->fs == FS_NORMAL && !aa) { for (uint y = 0; y < (uint)slot->bitmap.rows; y++) { for (uint x = 0; x < (uint)slot->bitmap.width; x++) { - if (aa ? (slot->bitmap.buffer[x + y * slot->bitmap.pitch] > 0) : HasBit(slot->bitmap.buffer[(x / 8) + y * slot->bitmap.pitch], 7 - (x % 8))) { + if (HasBit(slot->bitmap.buffer[(x / 8) + y * slot->bitmap.pitch], 7 - (x % 8))) { sprite.data[1 + x + (1 + y) * sprite.width].m = SHADOW_COLOUR; - sprite.data[1 + x + (1 + y) * sprite.width].a = aa ? slot->bitmap.buffer[x + y * slot->bitmap.pitch] : 0xFF; + sprite.data[1 + x + (1 + y) * sprite.width].a = 0xFF; } } } diff --git a/src/graph_gui.cpp b/src/graph_gui.cpp index f5acdaead6..a6cd054bb6 100644 --- a/src/graph_gui.cpp +++ b/src/graph_gui.cpp @@ -988,7 +988,7 @@ struct PaymentRatesGraphWindow : BaseGraphWindow { bool lowered = !HasBit(_legend_excluded_cargo, cs->Index()); /* Redraw box if lowered */ - if (lowered) DrawFrameRect(r.left, y, r.right, y + this->line_height - 1, COLOUR_BROWN, lowered ? FR_LOWERED : FR_NONE); + if (lowered) DrawFrameRect(r.left, y, r.right, y + this->line_height - 1, COLOUR_BROWN, FR_LOWERED); byte clk_dif = lowered ? 1 : 0; int rect_x = clk_dif + (rtl ? r.right - this->legend_width - WD_FRAMERECT_RIGHT : r.left + WD_FRAMERECT_LEFT); diff --git a/src/industry_gui.cpp b/src/industry_gui.cpp index eb09419bd2..b3527940e7 100644 --- a/src/industry_gui.cpp +++ b/src/industry_gui.cpp @@ -608,9 +608,9 @@ public: ShowErrorMessage(STR_ERROR_CAN_T_GENERATE_INDUSTRIES, STR_ERROR_MUST_FOUND_TOWN_FIRST, WL_INFO); } else { extern void GenerateIndustries(); - _generating_world = true; + Backup old_generating_world(_generating_world, true, FILE_LINE); GenerateIndustries(); - _generating_world = false; + old_generating_world.Restore(); } } @@ -712,15 +712,15 @@ public: } Backup cur_company(_current_company, OWNER_NONE, FILE_LINE); - _generating_world = true; + Backup old_generating_world(_generating_world, true, FILE_LINE); _ignore_restrictions = true; DoCommandP(tile, (layout_index << 8) | this->selected_type, seed, CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY), &CcBuildIndustry); cur_company.Restore(); + old_generating_world.Restore(); _ignore_restrictions = false; - _generating_world = false; } else { success = DoCommandP(tile, (layout_index << 8) | this->selected_type, seed, CMD_BUILD_INDUSTRY | CMD_MSG(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY)); } diff --git a/src/lang/afrikaans.txt b/src/lang/afrikaans.txt index 3b01ad507e..3cf6835d3a 100644 --- a/src/lang/afrikaans.txt +++ b/src/lang/afrikaans.txt @@ -1915,6 +1915,8 @@ STR_FACE_TIE :Das: STR_FACE_EARRING :Oorbel: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Verander das of oorbel +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multispeler @@ -1964,7 +1966,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Begin be STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Begin u eie verskaffer STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Tik in jou naam -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Invoeg die adres van die gasheer # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Begin nuwe multispeler speletjie @@ -2042,6 +2043,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Toeskou # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Verskaffer @@ -2152,6 +2156,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Die vers STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Die verskaffer is besig om te weerbegin...{}Wag asb... STR_NETWORK_MESSAGE_KICKED :*** {STRING} is geskop. Rede: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Laai Inhoud af STR_CONTENT_TYPE_CAPTION :{BLACK}Tipe diff --git a/src/lang/arabic_egypt.txt b/src/lang/arabic_egypt.txt index 0db2b0da27..cc78426e7a 100644 --- a/src/lang/arabic_egypt.txt +++ b/src/lang/arabic_egypt.txt @@ -1612,6 +1612,8 @@ STR_FACE_TIE :الربطة: STR_FACE_EARRING :أقراط الأذان: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}غير الربطة أو أقراط الأذن +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}تعدد اللاعبين @@ -1661,7 +1663,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}أبدأ STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}أبدأ خادمك الخاص STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}أدخل أسمك -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}أدخل عنوان المضيف # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}أبدا لعبة متعددة اللاعبين جديدة @@ -1739,6 +1740,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :شاهد # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :خادم @@ -1837,6 +1841,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} ق STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}اقفل الخادم الجلسة STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}يتم بدأ الخادم من جديد ...{} الرجاء الأنتظار + # Content downloading window STR_CONTENT_TITLE :{WHITE}تنزيل المحتوى STR_CONTENT_TYPE_CAPTION :{BLACK} نوع diff --git a/src/lang/basque.txt b/src/lang/basque.txt index 3ace3bc747..478f9b0e92 100644 --- a/src/lang/basque.txt +++ b/src/lang/basque.txt @@ -1790,6 +1790,8 @@ STR_FACE_TIE :Korbata: STR_FACE_EARRING :Belarritakoak: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Aldatu korbata eta belarritakoak +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multijokalaria @@ -1839,7 +1841,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Zerbitza STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Zure zerbitzaria sortu STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Sartu zure izena -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Sartu antolatzailearen helbidea # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Multijokalari joko berri bat hasi @@ -1917,6 +1918,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Ikusle # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Zerbitzaria @@ -2025,6 +2029,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING}-k STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Zebitzariak saioa itxi du STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Zerbitzaria berriro hasten ari da...{}Mesedez itxaron... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Edukiak deskargatzen STR_CONTENT_TYPE_CAPTION :{BLACK}Mota diff --git a/src/lang/belarusian.txt b/src/lang/belarusian.txt index aa608b2597..284111311e 100644 --- a/src/lang/belarusian.txt +++ b/src/lang/belarusian.txt @@ -2225,6 +2225,8 @@ STR_FACE_TIE :Гальшту STR_FACE_EARRING :Завушніца: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Зьмяніць гальштук або завушніцу +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Сеткавая гульня @@ -2274,7 +2276,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Запу STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Запуск сэрвэра на вашым кампутары. Да гэтае гульні змогуць далучыцца іншыя гульцы. STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Увядзіце вашае імя -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Увядзіце адрас сэрвэра # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Запуск новай сеткавай гульні @@ -2352,6 +2353,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Назірац # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Сэрвэр @@ -2461,6 +2465,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} з STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Гэты сэрвэр закрыў сэсію STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Гэты сэрвэр перазапускаецца...{}Пачакайце, калі ласка + # Content downloading window STR_CONTENT_TITLE :{WHITE}Кантэнт запампоўваецца STR_CONTENT_TYPE_CAPTION :{BLACK}Тып diff --git a/src/lang/brazilian_portuguese.txt b/src/lang/brazilian_portuguese.txt index f3a1d4bb1f..988574f7c8 100644 --- a/src/lang/brazilian_portuguese.txt +++ b/src/lang/brazilian_portuguese.txt @@ -2002,8 +2002,9 @@ STR_FACE_TIE :Gravata: STR_FACE_EARRING :Brinco: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Alterar gravata ou brinco -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privado +############ Next lines match ServerGameType STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Público +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multi-jogador @@ -2057,7 +2058,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Iniciar STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Iniciar um servidor próprio STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Coloque seu nome -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Coloque o endereço IP do servidor # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Iniciar novo jogo @@ -2162,6 +2162,9 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Esse é STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Esse é o hospedeiro do jogo STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} cliente{P "" s} / {NUM} empresa{P "" s} +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Expulsar STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Banir STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Excluir @@ -2289,6 +2292,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}O servid STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}O servidor está reiniciando...{}Aguarde... STR_NETWORK_MESSAGE_KICKED :*** {STRING} foi kickado. Motivo: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Baixando conteúdo STR_CONTENT_TYPE_CAPTION :{BLACK}Tipo diff --git a/src/lang/bulgarian.txt b/src/lang/bulgarian.txt index 34c1cde8f4..0416a620cf 100644 --- a/src/lang/bulgarian.txt +++ b/src/lang/bulgarian.txt @@ -1832,6 +1832,8 @@ STR_FACE_TIE :Вратовр STR_FACE_EARRING :Oбица: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Cмени вратовръзкатa или oбицатa +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Онлайн играчи @@ -1881,7 +1883,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Пуск STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Пуснете ваш собствен сървър STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Въведете името си -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Въведете адреса на хоста # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Започни нова мрежова игра @@ -1959,6 +1960,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Наблюда # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Сървър @@ -2067,6 +2071,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} с STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Сървърът прекъсна сесията STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Сървърът се рестартира...{}Моля изчакайте... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Съдържание за сваляне STR_CONTENT_TYPE_CAPTION :{BLACK}Вид diff --git a/src/lang/catalan.txt b/src/lang/catalan.txt index 511be8d613..5a4742215f 100644 --- a/src/lang/catalan.txt +++ b/src/lang/catalan.txt @@ -2002,8 +2002,9 @@ STR_FACE_TIE :Corbata: STR_FACE_EARRING :Arracades: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Canvia la corbata o les arracades -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privada +############ Next lines match ServerGameType STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Pública +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multijugador @@ -2057,7 +2058,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Inicia e STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Inicia en un servidor propi STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Posa el teu nom -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Entra l'adreça IP del servidor # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Inicia una partida multijugador nova @@ -2162,6 +2162,9 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Aquest e STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Aquest és l'hoste de la partida. STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} client{P "" s} / {NUM} companyi{P a es} +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Treu STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Expulsa STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Esborra @@ -2289,6 +2292,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}El servi STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}El servidor està reiniciant...{}Espera un moment... STR_NETWORK_MESSAGE_KICKED :*** S'ha expulsat {STRING}. Motiu: {STRING} + # Content downloading window STR_CONTENT_TITLE :{WHITE}Descàrregues de contingut STR_CONTENT_TYPE_CAPTION :{BLACK}Tipus diff --git a/src/lang/chuvash.txt b/src/lang/chuvash.txt index 5471098cff..bde2d9a298 100644 --- a/src/lang/chuvash.txt +++ b/src/lang/chuvash.txt @@ -731,6 +731,8 @@ STR_FACE_COLLAR :Ҫуха: STR_FACE_TIE :Галстук: STR_FACE_EARRING :Алка: +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_PLAYER_NAME :{BLACK}Вӑйӑҫӑ ят: @@ -793,6 +795,9 @@ STR_NETWORK_START_SERVER_SET_PASSWORD :{BLACK}Вырн # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + @@ -814,6 +819,7 @@ STR_NETWORK_SERVER_MESSAGE :*** {1:STRING} ############ Leave those lines in this order!! ############ End of leave-in-this-order + # Content downloading window STR_CONTENT_DETAIL_VERSION :{SILVER}Верси: {WHITE}{STRING} diff --git a/src/lang/croatian.txt b/src/lang/croatian.txt index e931c2a367..b7d86cc3db 100644 --- a/src/lang/croatian.txt +++ b/src/lang/croatian.txt @@ -2020,6 +2020,8 @@ STR_FACE_TIE :Kravata: STR_FACE_EARRING :Naušnica: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Promijeni kravatu ili naušnicu +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Više igrača @@ -2069,7 +2071,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Pokreni STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Pokreni vlastiti poslužitelj STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Upiši svoje ime -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Unesi adresu domaćina # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Započni novu igru za više igrača @@ -2147,6 +2148,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Promatraj # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Poslužitelj @@ -2257,6 +2261,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Posluži STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Poslužitelj se ponovno pokreće...{}Molimo pričekajte... STR_NETWORK_MESSAGE_KICKED :*** {STRING} je izbačen. Razlog: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Preuzimanje sadržaja STR_CONTENT_TYPE_CAPTION :{BLACK}Vrsta diff --git a/src/lang/czech.txt b/src/lang/czech.txt index 1a8e76e9ee..50a7009c13 100644 --- a/src/lang/czech.txt +++ b/src/lang/czech.txt @@ -1243,6 +1243,7 @@ STR_CONFIG_SETTING_TYPE_GAME_MENU :Nastavení hry STR_CONFIG_SETTING_TYPE_GAME_INGAME :Nastavení hry (uchováno v uložené hře; ovlivní pouze stávající hru) STR_CONFIG_SETTING_TYPE_COMPANY_MENU :Nastavení společnosti (uchováno v uložených hrách; ovlivní pouze nové hry) STR_CONFIG_SETTING_TYPE_COMPANY_INGAME :Nastavení společnosti (uchováno v uložené hře; ovlivní pouze stávající společnost) +STR_CONFIG_SETTING_RESET_ALL_CONFIRMATION_DIALOG_TEXT :{WHITE}Tato akce resetuje všechna nastavení hry na výchozí hodnoty.{}Jste si jistý, že chcete pokračovat? STR_CONFIG_SETTING_RESTRICT_CATEGORY :{BLACK}Kategorie: STR_CONFIG_SETTING_RESTRICT_TYPE :{BLACK}Druh: @@ -2331,6 +2332,8 @@ STR_FACE_TIE :Kravata: STR_FACE_EARRING :Náušnice: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Změnit kravatu nebo náušnice +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multiplayer @@ -2384,7 +2387,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Spustit STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Spustit vlastní server STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Zadej své jméno -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Vlož IP adresu serveru # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Začít novou hru @@ -2461,8 +2463,19 @@ STR_NETWORK_COMPANY_LIST_CLIENT_LIST :Seznam hráčů STR_NETWORK_COMPANY_LIST_SPECTATE :Pozorovat # Network client list +STR_NETWORK_CLIENT_LIST_SERVER_NAME_TOOLTIP :{BLACK}Název serveru, na kterém hrajete +STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP :{BLACK}Upravte název svého serveru +STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :Název serveru +STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP :{BLACK}Jméno vašeho hráče +STR_NETWORK_CLIENT_LIST_JOIN_TOOLTIP :{BLACK}Připojit se a hrát za tuto společnost +STR_NETWORK_CLIENT_LIST_CHAT_CLIENT_TOOLTIP :{BLACK}Poslat zprávu tomuto hráči +STR_NETWORK_CLIENT_LIST_CHAT_COMPANY_TOOLTIP :{BLACK}Poslat zprávu všem hráčům této firmy + +############ Begin of ConnectionType +############ End of ConnectionType +STR_NETWORK_CLIENT_LIST_ASK_COMPANY_RESET :{YELLOW}Jste si jisti, že chcete smazat společnost '{COMPANY}'? STR_NETWORK_SERVER :Server STR_NETWORK_CLIENT :Klient @@ -2519,6 +2532,7 @@ STR_NETWORK_ERROR_TIMEOUT_PASSWORD :{WHITE}Vložen STR_NETWORK_ERROR_TIMEOUT_COMPUTER :{WHITE}Tvůj počítač je přiliš pomalý pro udržení kroku se serverem STR_NETWORK_ERROR_TIMEOUT_MAP :{WHITE}Tvému počítači trvalo stahování mapy příliš dlouho STR_NETWORK_ERROR_TIMEOUT_JOIN :{WHITE}Tvému počítači trvalo připojování na server příliš dlouho +STR_NETWORK_ERROR_INVALID_CLIENT_NAME :{WHITE}Jméno vašeho hráče není platné ############ Leave those lines in this order!! STR_NETWORK_ERROR_CLIENT_GENERAL :všeobecná chyba @@ -2578,6 +2592,8 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Server u STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Server se restartuje...{}Počkejte prosím... STR_NETWORK_MESSAGE_KICKED :*** {STRING} byl vyhozen. Důvod: ({STRING}) +STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}Váš server neumožňuje vzdálené připojení + # Content downloading window STR_CONTENT_TITLE :{WHITE}Součásti ke stažení STR_CONTENT_TYPE_CAPTION :{BLACK}Druh diff --git a/src/lang/danish.txt b/src/lang/danish.txt index 3837031b0d..90c17c09c8 100644 --- a/src/lang/danish.txt +++ b/src/lang/danish.txt @@ -1930,6 +1930,8 @@ STR_FACE_TIE :Slips: STR_FACE_EARRING :Ørering: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Ændre slips eller ørering +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Netværksspil @@ -1979,7 +1981,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Start se STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Start en ny server STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Indtast dit navn -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Skriv adressen på en server # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Start et nyt netværksspil @@ -2057,6 +2058,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Tilslut som til # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Server @@ -2167,6 +2171,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Serveren STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Serveren genstarter...{}Vent venligst... STR_NETWORK_MESSAGE_KICKED :*** {STRING} blev sparket ud. Grund: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Download af indhold STR_CONTENT_TYPE_CAPTION :{BLACK}Type diff --git a/src/lang/dutch.txt b/src/lang/dutch.txt index e96bee9c13..8f12115d1a 100644 --- a/src/lang/dutch.txt +++ b/src/lang/dutch.txt @@ -2001,8 +2001,11 @@ STR_FACE_TIE :Stropdas: STR_FACE_EARRING :Oorbel: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Verander das of oorbel -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privé +############ Next lines match ServerGameType +STR_NETWORK_SERVER_VISIBILITY_LOCAL :Lokaal STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Openbaar +STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY :Alleen op uitnodiging +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Netwerkspel @@ -2036,6 +2039,7 @@ STR_NETWORK_SERVER_LIST_SERVER_VERSION :{SILVER}Serverv STR_NETWORK_SERVER_LIST_SERVER_ADDRESS :{SILVER}Serveradres: {WHITE}{STRING} STR_NETWORK_SERVER_LIST_START_DATE :{SILVER}Startdatum: {WHITE}{DATE_SHORT} STR_NETWORK_SERVER_LIST_CURRENT_DATE :{SILVER}Huidige datum: {WHITE}{DATE_SHORT} +STR_NETWORK_SERVER_LIST_GAMESCRIPT :{SILVER}Spelscript: {WHITE}{STRING} (v{NUM}) STR_NETWORK_SERVER_LIST_PASSWORD :{SILVER}Beveiligd met wachtwoord! STR_NETWORK_SERVER_LIST_SERVER_OFFLINE :{SILVER}SERVER OFFLINE STR_NETWORK_SERVER_LIST_SERVER_FULL :{SILVER}SERVER VOL @@ -2051,12 +2055,12 @@ STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET_TOOLTIP :{BLACK}Op het i STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN :{BLACK}Op LAN zoeken STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN_TOOLTIP :{BLACK}Op lokaal netwerk zoeken naar servers STR_NETWORK_SERVER_LIST_ADD_SERVER :{BLACK}Voeg server toe -STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Voegt een server toe aan de lijst die altijd gecontroleerd zal worden op draaiende spellen +STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Voegt een server toe aan de lijst. Dit kan een serveradres of een uitnodigingscode zijn STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Start server STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Start eigen server STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Vul je naam in -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Voer het IP-adres van de server in +STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS :{BLACK}Voer serveradres of uitnodigingscode in # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Nieuw spel met meerdere spelers starten @@ -2143,6 +2147,10 @@ STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP :{BLACK}De naam STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :Servernaam STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY :{BLACK}Zichtbaarheid STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP :{BLACK}Bepaalt of andere mensen je server kunnen zien in de openbare lijst +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE :{BLACK}Uitnodigingscode +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP :{BLACK}Uitnodigingscode waarmee andere spelers toegang krijgen tot deze server +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE :{BLACK}Type verbinding +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP :{BLACK}Of en hoe je server toegankelijk is voor anderen STR_NETWORK_CLIENT_LIST_PLAYER :{BLACK}Speler STR_NETWORK_CLIENT_LIST_PLAYER_NAME :{BLACK}Naam STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP :{BLACK}Je spelernaam @@ -2161,6 +2169,12 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Dit ben STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Dit is de host van het spel STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} klant{P "" en} / {NUM} bedrij{P f ven} +############ Begin of ConnectionType +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN :{BLACK}Lokaal +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_ISOLATED :{RED}Externe spelers kunnen geen verbinding maken +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_DIRECT :{BLACK}Openbaar +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Eruit schoppen STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Bannen STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Verwijderen @@ -2288,6 +2302,10 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}De serve STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}De server wordt opnieuw gestart...{}Wacht alstublieft... STR_NETWORK_MESSAGE_KICKED :*** {STRING} is eruit geschopt. Reden: ({STRING}) +STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED :{WHITE}Serverregistratie mislukt +STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}Je server staat geen externe verbindingen toe +STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL :{WHITE}Andere spelers krijgen geen toegang tot je server + # Content downloading window STR_CONTENT_TITLE :{WHITE}Download extra inhoud STR_CONTENT_TYPE_CAPTION :{BLACK}Type diff --git a/src/lang/english.txt b/src/lang/english.txt index 5e6f6e7a56..be0e7180a3 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2434,8 +2434,11 @@ STR_FACE_TIE :Tie: STR_FACE_EARRING :Earring: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Change tie or earring -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Private +############ Next lines match ServerGameType +STR_NETWORK_SERVER_VISIBILITY_LOCAL :Local STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Public +STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY :Invite only +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multiplayer @@ -2469,6 +2472,7 @@ STR_NETWORK_SERVER_LIST_SERVER_VERSION :{SILVER}Server STR_NETWORK_SERVER_LIST_SERVER_ADDRESS :{SILVER}Server address: {WHITE}{RAW_STRING} STR_NETWORK_SERVER_LIST_START_DATE :{SILVER}Start date: {WHITE}{DATE_SHORT} STR_NETWORK_SERVER_LIST_CURRENT_DATE :{SILVER}Current date: {WHITE}{DATE_SHORT} +STR_NETWORK_SERVER_LIST_GAMESCRIPT :{SILVER}Game Script: {WHITE}{RAW_STRING} (v{NUM}) STR_NETWORK_SERVER_LIST_PASSWORD :{SILVER}Password protected! STR_NETWORK_SERVER_LIST_SERVER_OFFLINE :{SILVER}SERVER OFFLINE STR_NETWORK_SERVER_LIST_SERVER_FULL :{SILVER}SERVER FULL @@ -2484,12 +2488,12 @@ STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET_TOOLTIP :{BLACK}Search i STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN :{BLACK}Search LAN STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN_TOOLTIP :{BLACK}Search local area network for servers STR_NETWORK_SERVER_LIST_ADD_SERVER :{BLACK}Add server -STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Adds a server to the list which will always be checked for running games +STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Adds a server to the list. This can either be a server address or an invite code STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Start server STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Start your own server STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Enter your name -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Enter the address of the host +STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS :{BLACK}Enter server address or invite code # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Start new multiplayer game @@ -2576,6 +2580,10 @@ STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP :{BLACK}Edit the STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :Name of the server STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY :{BLACK}Visibility STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP :{BLACK}Whether other people can see your server in the public listing +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE :{BLACK}Invite code +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP :{BLACK}Invite code other players can use to join this server +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE :{BLACK}Connection type +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP :{BLACK}Whether and how your server can be reached by others STR_NETWORK_CLIENT_LIST_PLAYER :{BLACK}Player STR_NETWORK_CLIENT_LIST_PLAYER_NAME :{BLACK}Name STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP :{BLACK}Your player name @@ -2594,6 +2602,13 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}This is STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}This is the host of the game STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} client{P "" s} / {NUM} compan{P y ies} +############ Begin of ConnectionType +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN :{BLACK}Local +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_ISOLATED :{RED}Remote players can't connect +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_DIRECT :{BLACK}Public +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_STUN :{BLACK}Behind NAT +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Kick STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Ban STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Delete @@ -2724,6 +2739,10 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}The serv STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}The server is restarting...{}Please wait... STR_NETWORK_MESSAGE_KICKED :*** {RAW_STRING} was kicked. Reason: ({RAW_STRING}) +STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED :{WHITE}Server registration failed +STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}Your server doesn't allow remote connections +STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL :{WHITE}Other players won't be able to connect to your server + # Content downloading window STR_CONTENT_TITLE :{WHITE}Content downloading STR_CONTENT_TYPE_CAPTION :{BLACK}Type diff --git a/src/lang/english_AU.txt b/src/lang/english_AU.txt index 09f68280fa..714bdc72ec 100644 --- a/src/lang/english_AU.txt +++ b/src/lang/english_AU.txt @@ -1870,6 +1870,8 @@ STR_FACE_TIE :Tie: STR_FACE_EARRING :Earring: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Change tie or earring +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multiplayer @@ -1919,7 +1921,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Start se STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Start your own server STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Enter your name -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Enter the address of the host # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Start new multiplayer game @@ -1997,6 +1998,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Spectate # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Server @@ -2105,6 +2109,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} ha STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}The server closed the session STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}The server is restarting...{}Please wait... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Content downloading STR_CONTENT_TYPE_CAPTION :{BLACK}Type diff --git a/src/lang/english_US.txt b/src/lang/english_US.txt index 71590ead7d..19261e680c 100644 --- a/src/lang/english_US.txt +++ b/src/lang/english_US.txt @@ -2025,8 +2025,10 @@ STR_FACE_TIE :Tie: STR_FACE_EARRING :Earring: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Change tie or earring -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Private +############ Next lines match ServerGameType +STR_NETWORK_SERVER_VISIBILITY_LOCAL :Local STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Public +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multiplayer @@ -2075,12 +2077,12 @@ STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET_TOOLTIP :{BLACK}Search i STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN :{BLACK}Search LAN STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN_TOOLTIP :{BLACK}Search local area network for servers STR_NETWORK_SERVER_LIST_ADD_SERVER :{BLACK}Add server -STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Adds a server to the list which will always be checked for running games +STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Adds a server to the list. This can either be a server address or an invite code STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Start server STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Start your own server STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Enter your name -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Enter the IP address of the server +STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS :{BLACK}Enter server address or invite code # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Start new multiplayer game @@ -2166,6 +2168,10 @@ STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP :{BLACK}Edit the STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :Name of the server STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY :{BLACK}Visibility STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP :{BLACK}Whether other people can see your server in the public listing +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE :{BLACK}Invite code +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP :{BLACK}Invite code other players can use to join this server +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE :{BLACK}Connection type +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP :{BLACK}Whether and how your server can be reached by others STR_NETWORK_CLIENT_LIST_PLAYER :{BLACK}Player STR_NETWORK_CLIENT_LIST_PLAYER_NAME :{BLACK}Name STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP :{BLACK}Your player name @@ -2184,6 +2190,12 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}This is STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}This is the host of the game STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} client{P "" s} / {NUM} compan{P y ies} +############ Begin of ConnectionType +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN :{BLACK}Local +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_ISOLATED :{RED}Remote players can't connect +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_DIRECT :{BLACK}Public +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Kick STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Ban STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Delete @@ -2312,6 +2324,10 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}The serv STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}The server is restarting...{}Please wait... STR_NETWORK_MESSAGE_KICKED :*** {STRING} was kicked. Reason: ({STRING}) +STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED :{WHITE}Server registration failed +STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}Your server doesn't allow remote connections +STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL :{WHITE}Other players won't be able to connect to your server + # Content downloading window STR_CONTENT_TITLE :{WHITE}Content downloading STR_CONTENT_TYPE_CAPTION :{BLACK}Type diff --git a/src/lang/esperanto.txt b/src/lang/esperanto.txt index b0407a1a7a..dc81871be3 100644 --- a/src/lang/esperanto.txt +++ b/src/lang/esperanto.txt @@ -1530,6 +1530,8 @@ STR_FACE_TIE :Kravato: STR_FACE_EARRING :Orelringo: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Ŝanĝi kravaton aŭ orelringon. +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Pluraj ludantoj @@ -1579,7 +1581,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Startu s STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Startu propran servilon STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Tajpu vian nomon -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Enigu la adreson de la gastiganto # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Komencu novan ludon por pluraj ludantoj @@ -1657,6 +1658,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Spekti # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Servilo @@ -1753,6 +1757,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} ŝ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}La servilo fermis la seancon STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}La servilo restartiĝas...{}Bonvolu atendi... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Enhavo elŝutiĝas STR_CONTENT_TYPE_CAPTION :{BLACK}Tipo diff --git a/src/lang/estonian.txt b/src/lang/estonian.txt index 9f553477b8..98a24c3d34 100644 --- a/src/lang/estonian.txt +++ b/src/lang/estonian.txt @@ -2049,8 +2049,9 @@ STR_FACE_TIE :Lips: STR_FACE_EARRING :Kõrvarõngas: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Vaheta kraed või kõrvarõngast -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privaatne +############ Next lines match ServerGameType STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Avalik +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Mitmikmäng @@ -2104,7 +2105,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Tee serv STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Tee oma server STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Sisesta enda nimi -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Sisesta serveri aadress # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Mitmikmängu alustamine @@ -2209,6 +2209,9 @@ STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP :{BLACK}Loo uus STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Tema oled sina STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Tema on mängu korraldaja +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Viska välja STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Keela STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Kustuta @@ -2335,6 +2338,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Server s STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Server restardib...{}Palun oota... STR_NETWORK_MESSAGE_KICKED :*** {STRING} visati välja. Põhjendus: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Aineste allalaadimine STR_CONTENT_TYPE_CAPTION :{BLACK}Liik diff --git a/src/lang/faroese.txt b/src/lang/faroese.txt index 53cf563df2..44ac0f4698 100644 --- a/src/lang/faroese.txt +++ b/src/lang/faroese.txt @@ -1696,6 +1696,8 @@ STR_FACE_TIE :Slips: STR_FACE_EARRING :Oyraringur: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Broyt slips ella oyraring +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Hópspæl @@ -1745,7 +1747,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Set serv STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Set tín egna servara í gongd STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Skriva títt navn -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Skriva bústað hjá verti # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Byrja eitt nýtt hópspæl @@ -1823,6 +1824,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Eygleið # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Servari @@ -1931,6 +1935,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} he STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Servarin endaði setuna STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Servarin endurbyrjar...{}Vinarliga bíða... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Tilfar verur heinta niður STR_CONTENT_TYPE_CAPTION :{BLACK}Slag diff --git a/src/lang/finnish.txt b/src/lang/finnish.txt index 1ea556e7c3..7eccc125ea 100644 --- a/src/lang/finnish.txt +++ b/src/lang/finnish.txt @@ -2001,8 +2001,11 @@ STR_FACE_TIE :Solmio: STR_FACE_EARRING :Korvakoru: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Vaihda solmio tai korvakoru -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Yksityinen +############ Next lines match ServerGameType +STR_NETWORK_SERVER_VISIBILITY_LOCAL :Paikallinen STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Julkinen +STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY :Vain kutsutut +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Moninpeli @@ -2051,12 +2054,12 @@ STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET_TOOLTIP :{BLACK}Etsi jul STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN :{BLACK}Etsi lähiverkosta STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN_TOOLTIP :{BLACK}Etsi palvelimia lähiverkosta STR_NETWORK_SERVER_LIST_ADD_SERVER :{BLACK}Lisää palvelin -STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Lisää palvelin listaan, joka käydään läpi aina uusia pelejä haettaessa +STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Lisää palvelimen luetteloon. Tämä voi olla palvelimen osoite tai kutsukoodi. STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Käynnistä palvelin STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Käynnistä oma palvelin STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Kirjoita nimesi -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Syötä palvelimen IP-osoite +STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS :{BLACK}Anna palvelimen osoite tai kutsukoodi # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Aloita uusi peli @@ -2143,6 +2146,10 @@ STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP :{BLACK}Muokkaa STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :Palvelimen nimi STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY :{BLACK}Näkyvyys STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP :{BLACK}Näkyykö palvelimesi muille julkisessa listauksessa +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE :{BLACK}Kutsukoodi +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP :{BLACK}Kutsukoodi, jolla muut pelaajat voivat liittyä tälle palvelimelle +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE :{BLACK}Yhteystyyppi +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP :{BLACK}Voivatko muut ottaa yhteyden palvelimeesi, ja miten STR_NETWORK_CLIENT_LIST_PLAYER :{BLACK}Pelaaja STR_NETWORK_CLIENT_LIST_PLAYER_NAME :{BLACK}Nimi STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP :{BLACK}Pelaajanimesi @@ -2161,6 +2168,12 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Tämä o STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Tämä on pelin ylläpitäjä STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} pelaaja{P "" a} / {NUM} yhtiö{P "" tä} +############ Begin of ConnectionType +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN :{BLACK}Paikallinen +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_ISOLATED :{RED}Etäpelaajat eivät voi ottaa yhteyttä +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_DIRECT :{BLACK}Julkinen +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Potki STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Estä STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Poista @@ -2288,6 +2301,10 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Palvelin STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Palvelin käynnistyy uudelleen...{}Odota, ole hyvä... STR_NETWORK_MESSAGE_KICKED :{STRING} potkaistiin ulos. Syy: ({STRING}) +STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED :{WHITE}Palvelimen rekisteröinti epäonnistui +STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}Palvelimesi ei salli etäyhteyksiä +STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL :{WHITE}Muut pelaajat eivät voi saada yhteyttä palvelimeesi + # Content downloading window STR_CONTENT_TITLE :{WHITE}Sisällön lataus STR_CONTENT_TYPE_CAPTION :{BLACK}Tyyppi diff --git a/src/lang/french.txt b/src/lang/french.txt index 3b6ab87b56..924dcbd7fd 100644 --- a/src/lang/french.txt +++ b/src/lang/french.txt @@ -2002,8 +2002,9 @@ STR_FACE_TIE :Cravate{NBSP}: STR_FACE_EARRING :Boucle d'oreille{NBSP}: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Modifier la cravate ou la boucle d'oreille -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privé +############ Next lines match ServerGameType STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Public +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multijoueurs @@ -2057,7 +2058,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Héberge STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Démarrer son propre serveur, sur cet ordinateur-ci STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Entrer votre nom -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Saisir l'adresse IP du serveur # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Commencer une nouvelle partie @@ -2161,6 +2161,9 @@ STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP :{BLACK}Créer u STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}C'est vous STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}C'est l'hôte du jeu +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Exclure STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Bannir STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Supprimer @@ -2288,6 +2291,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Le serve STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Le serveur redémarre...{}Veuillez patienter... STR_NETWORK_MESSAGE_KICKED :*** {STRING} a été exclu. Raison{NBSP}: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Téléchargement de modules STR_CONTENT_TYPE_CAPTION :{BLACK}Type diff --git a/src/lang/frisian.txt b/src/lang/frisian.txt index dd715d8d07..aaa0db4dd9 100644 --- a/src/lang/frisian.txt +++ b/src/lang/frisian.txt @@ -1795,6 +1795,8 @@ STR_FACE_TIE :Strik: STR_FACE_EARRING :Earbel: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Feroarje strik of earbel +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multiplayer @@ -1834,7 +1836,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Start ts STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Start dien eigen tsjinner STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Fier dyn namme in -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Fier de namme fan de host yn # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Start in spul foar multispilers @@ -1899,6 +1900,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :{WHITE}Taskôgj # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Tsjinner @@ -1977,6 +1981,7 @@ STR_NETWORK_MESSAGE_CLIENT_COMPANY_SPECTATE :*** {STRING} is STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} hat syn/har namme oanpast nei {STRING} STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Tsjinner hat de ferbining sluten + # Content downloading window STR_CONTENT_TITLE :{WHITE}Ynhâld delheljen STR_CONTENT_TYPE_CAPTION :{BLACK}Type diff --git a/src/lang/gaelic.txt b/src/lang/gaelic.txt index 4f4c56e6d8..acd7aa818f 100644 --- a/src/lang/gaelic.txt +++ b/src/lang/gaelic.txt @@ -2081,6 +2081,8 @@ STR_FACE_TIE :Tàidh: STR_FACE_EARRING :Fàinne-chluaise: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Atharraich an tàidh no an fhàinne-chluaise +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Ioma-chluicheadair @@ -2130,7 +2132,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Tòisich STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Tòisich frithealaiche agad fhèin STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Cuir d' ainm a-steach -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Cuir seòladh an òstair a-steach # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Tòisich geama ioma-chluicheadair ùr @@ -2208,6 +2209,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Coimhead air # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Frithealaiche @@ -2316,6 +2320,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** Dh'atharrai STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Dhùin am frithealaiche an seisean STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Tha am frithealaiche ag ath-thòiseachadh...{}Fuirich greis... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Susbaint ri luchdadh a-nuas STR_CONTENT_TYPE_CAPTION :{BLACK}Seòrsa diff --git a/src/lang/galician.txt b/src/lang/galician.txt index a482aa5553..9588319129 100644 --- a/src/lang/galician.txt +++ b/src/lang/galician.txt @@ -1917,6 +1917,8 @@ STR_FACE_TIE :Garavata: STR_FACE_EARRING :Pendentes: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Cambia-la garavata ou os pendentes +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multixogador @@ -1966,7 +1968,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Iniciar STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Comezar o teu propio servidor STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Escribe o teu nome -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Introduce a dirección IP do servidor # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Comezar nova partida multixogador @@ -2044,6 +2045,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Observar # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Servidor @@ -2153,6 +2157,7 @@ STR_NETWORK_MESSAGE_GIVE_MONEY :*** {STRING} de STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}O servidor pechou a sesión STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}O servidor estase a reiniciar...{}Agarda por favor... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Descargando contidos STR_CONTENT_TYPE_CAPTION :{BLACK}Tipo diff --git a/src/lang/german.txt b/src/lang/german.txt index 5c466e9689..29e5a2109e 100644 --- a/src/lang/german.txt +++ b/src/lang/german.txt @@ -2312,8 +2312,10 @@ STR_FACE_TIE :Krawatte: STR_FACE_EARRING :Ohrring: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Krawatte oder Ohrring ändern -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privat +############ Next lines match ServerGameType +STR_NETWORK_SERVER_VISIBILITY_LOCAL :Lokal STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Öffentlich +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Mehrspieler @@ -2362,12 +2364,12 @@ STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET_TOOLTIP :{BLACK}Internet STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN :{BLACK}LAN durchsuchen STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN_TOOLTIP :{BLACK}Lokales Netzwerk nach Spielservern durchsuchen STR_NETWORK_SERVER_LIST_ADD_SERVER :{BLACK}Server hinzufügen -STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Fügt einen Server zu der Liste von Servern hinzu, die immer nach laufenden Spielen kontrolliert werden +STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Fügt einen Server zur Liste hinzu. Dies kann entweder eine Serveradresse oder ein Einladungscode ein STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Server starten STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Einen eigenen Server starten STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Bitte eigenen Namen eingeben -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}IP-Adresse des Servers eingeben +STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS :{BLACK}Serveradresse oder Einladungscode eingeben # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Neues Mehrspieler-Spiel beginnen @@ -2454,6 +2456,10 @@ STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP :{BLACK}Ihren Se STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :Name des Servers STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY :{BLACK}Sichtbarkeit STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP :{BLACK}Ob andere Personen Ihren Server in der öffentlichen Liste sehen können +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE :{BLACK}Einladungscode +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP :{BLACK}Einladungscode, den andere Spieler benutzen können, um diesem Server beizutreten +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE :{BLACK}Verbindungstyp +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP :{BLACK}Ob und wie dein Server von anderen erreicht werden kann STR_NETWORK_CLIENT_LIST_PLAYER :{BLACK}Spieler STR_NETWORK_CLIENT_LIST_PLAYER_NAME :{BLACK}Name STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP :{BLACK}Ihr Spielername @@ -2472,6 +2478,12 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Das sind STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Dies ist der Host des Spiels STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} Client{P "" s} / {NUM} Firm{P a en} +############ Begin of ConnectionType +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN :{BLACK}Lokal +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_ISOLATED :{RED}Nicht-lokale Spieler können sich nicht verbinden +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_DIRECT :{BLACK}Öffentlich +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Hinauswerfen STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Bannen STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Löschen @@ -2602,6 +2614,10 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Der Serv STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Der Server startet neu...{}Bitte warten... STR_NETWORK_MESSAGE_KICKED :*** {STRING} wurde vom Server hinausgeworfen. Grund: ({STRING}) +STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED :{WHITE}Serverregistration fehlgeschlagen +STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}Dein Server lässt keine nicht-lokalen Verbindungen zu +STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL :{WHITE}Andere Spieler werden nicht in der Lage sein, sich zu deinem Server zu verbinden + # Content downloading window STR_CONTENT_TITLE :{WHITE}Herunterladen von Erweiterungen STR_CONTENT_TYPE_CAPTION :{BLACK}Art diff --git a/src/lang/greek.txt b/src/lang/greek.txt index 7c19cfe963..da5405e5e2 100644 --- a/src/lang/greek.txt +++ b/src/lang/greek.txt @@ -2037,6 +2037,8 @@ STR_FACE_TIE :Γραβάτα: STR_FACE_EARRING :Σκουλαρίκι: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Αλλαγή γραβάτας ή σκουλαρικιού +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Παιχνίδι πολλών παικτών @@ -2087,7 +2089,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Εκκί STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Ξεκινήστε το δικό σας διακομιστή STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Εισάγετε το όνομά σας -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Εισάγετε τη διεύθυνση του οικοδεσπότη # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Έναρξη νέου παιχνιδιού για πολλούς παίκτες @@ -2165,6 +2166,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Παρακολ # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Διακομιστής @@ -2278,6 +2282,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Ο δι STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Ο διακομιστής επανεκκινεί...{}Παρακαλώ περιμένετε... STR_NETWORK_MESSAGE_KICKED :*** {STRING} εκδιώχθηκε. Λόγος: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Περιεχόμενο κατεβαίνει STR_CONTENT_TYPE_CAPTION :{BLACK}Τύπος diff --git a/src/lang/hebrew.txt b/src/lang/hebrew.txt index 0f23a31f31..c7823466d8 100644 --- a/src/lang/hebrew.txt +++ b/src/lang/hebrew.txt @@ -1895,6 +1895,8 @@ STR_FACE_TIE ::עניבה STR_FACE_EARRING ::עגילים STR_FACE_TIE_EARRING_TOOLTIP :{BLACK} שנה עניבה/עגילים +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}משחק רשת @@ -1944,7 +1946,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}הפעל STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}הפעל שרת חדש STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK} :שמך -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}הזן את הכתובת של השרת # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}התחל משחק רב-משתתפים חדש @@ -2022,6 +2023,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :צפה # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :שרת @@ -2130,6 +2134,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :{STRING} שינ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}השרת סגר את המשחק STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}השרת מופעל מחדש...{}אנא המתן... + # Content downloading window STR_CONTENT_TITLE :{WHITE}הורדת תוכן STR_CONTENT_TYPE_CAPTION :{BLACK}סוג diff --git a/src/lang/hindi.txt b/src/lang/hindi.txt index 0a4246e00f..cd76a92a69 100644 --- a/src/lang/hindi.txt +++ b/src/lang/hindi.txt @@ -44,6 +44,7 @@ STR_CARGO_PLURAL_FRUIT :फल STR_CARGO_SINGULAR_NOTHING : STR_CARGO_SINGULAR_MAIZE :मक्का STR_CARGO_SINGULAR_SWEETS :मिठाई +STR_CARGO_SINGULAR_BUBBLE :बुलबुला STR_CARGO_SINGULAR_TOFFEE :टॉफी # Quantity of cargo @@ -73,6 +74,7 @@ STR_UNITS_POWER_METRIC :{COMMA}{NBSP}hp # Common window strings +STR_TOOLTIP_RESIZE :{BLACK}खिड़की का आकार बदलने के लिये क्लिक करके खींचें # Show engines button @@ -107,6 +109,8 @@ STR_SCENEDIT_FILE_MENU_QUIT :निकास ############ range for SE file menu starts ############ range for settings menu starts +STR_SETTINGS_MENU_CONFIG_SETTINGS_TREE :समायोजन +STR_SETTINGS_MENU_WAYPOINTS_DISPLAYED :पथ-संकेतों के नाम दिखायें ############ range ends here ############ range for file menu starts @@ -132,6 +136,7 @@ STR_INDUSTRY_MENU_INDUSTRY_CHAIN :औद्यो ############ range ends here ############ range for railway construction menu starts +STR_RAIL_MENU_ELRAIL_CONSTRUCTION :विद्युतिकृत रेलवे निर्माण ############ range ends here ############ range for road construction menu starts @@ -161,13 +166,16 @@ STR_ORDINAL_NUMBER_2ND :द्वित ############ range for ordinal numbers ends ############ range for days starts -STR_DAY_NUMBER_15TH :१५वीं +STR_DAY_NUMBER_14TH :१४ +STR_DAY_NUMBER_15TH :१५ +STR_DAY_NUMBER_23RD :२३ ############ range for days ends ############ range for months starts STR_MONTH_ABBREV_JAN :जन STR_MONTH_ABBREV_NOV :नव +STR_MONTH_AUG :अगस्त ############ range for months ends # Graph window @@ -201,6 +209,7 @@ STR_HIGHSCORE_PRESIDENT_OF_COMPANY_ACHIEVES_STATUS :{BIG_FONT}{WHIT # Smallmap window +STR_SMALLMAP_LEGENDA_TRUCK_LOADING_BAY :{TINY_FONT}{BLACK}ट्रक लदान वीथी STR_SMALLMAP_LEGENDA_BARE_LAND :{TINY_FONT}{BLACK}रिक्त भूमि STR_SMALLMAP_LEGENDA_TOWNS :{TINY_FONT}{BLACK}नगर @@ -290,6 +299,7 @@ STR_CURRENCY_DECREASE_EXCHANGE_RATE_TOOLTIP :{BLACK}एक +STR_VARIETY_MEDIUM :मध्यम STR_AI_SPEED_SLOW :धीमा @@ -321,17 +331,19 @@ STR_CONFIG_SETTING_BRIBE_HELPTEXT :कंपनि -STR_CONFIG_SETTING_ORDER_REVIEW_HELPTEXT :सक्षम होने पर वाहनों के निर्देशों की आवधिक जाँच की जाती है, और कुछ सुस्पष्ट मामलों को पता चलते ही एक संदेश द्वारा बताया जाता है। +STR_CONFIG_SETTING_ORDER_REVIEW_HELPTEXT :सक्षम होने पर वाहनों के निर्देशों की आवधिक जाँच की जाती है, और कुछ सुस्पष्ट मामलों का पता चलते ही एक संदेश द्वारा बताया जाता है। STR_CONFIG_SETTING_ORDER_REVIEW_OFF :नहीं STR_CONFIG_SETTING_STATION_SPREAD_HELPTEXT :एक स्टेशन के हिस्सों के विस्तार हेतु अधिकतम क्षेत्र निर्धारित करें। ऊंची संख्या खेल को धीमा कर सकती है। STR_CONFIG_SETTING_SMALLMAP_LAND_COLOUR_VIOLET :बैंगनी +STR_CONFIG_SETTING_OSK_ACTIVATION_DISABLED :अक्षम +STR_CONFIG_SETTING_FAST_FORWARD_SPEED_LIMIT_ZERO :सीमाहीन (आपके कंप्यूटर द्वारा सीमित) STR_CONFIG_SETTING_SOUND_NEWS :समाचार पत्र: {STRING} @@ -352,6 +364,7 @@ STR_CONFIG_SETTING_SOFT_LIMIT_VALUE :{COMMA} STR_CONFIG_SETTING_SPRITE_ZOOM_LVL_IN_2X :२x +STR_CONFIG_SETTING_LOCALISATION_UNITS_VELOCITY_SI :अंतर्राष्ट्रीय मानक (m/s) @@ -397,7 +410,10 @@ STR_ABANDON_SCENARIO_QUERY :{YELLOW}क् # Face selection window STR_FACE_LOAD_DONE :{WHITE}ओपनटीटीडी प्रारूप पत्र से आपका प्रिय चेहरा भर लिया गया है +STR_FACE_COLLAR_TOOLTIP :{BLACK}कॉलर बदलें +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list @@ -433,6 +449,9 @@ STR_NETWORK_GAME_LOBBY_INAUGURATION_YEAR :{SILVER}उद # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + @@ -456,13 +475,16 @@ STR_NETWORK_SERVER_MESSAGE_GAME_STILL_PAUSED_3 :खेल अ ############ End of leave-in-this-order STR_NETWORK_MESSAGE_CLIENT_LEAVING :छोड़ रहा है + # Content downloading window +STR_CONTENT_SEARCH_EXTERNAL_TOOLTIP :{BLACK}खोजी गयी सामग्री OpenTTD की सामग्री सेवा में उपलब्ध नहीं है, आप OpenTTD से असंबद्ध वेबसाइटों पर खोज सकते हैं STR_CONTENT_DETAIL_SUBTITLE_ALREADY_HERE :{SILVER}यह आपके पास पहले से मौजूद है STR_CONTENT_DETAIL_SUBTITLE_DOES_NOT_EXIST :{SILVER}यह सामग्री अज्ञात है और इसे ओपनटीटीडी में प्राप्त नहीं किया जा सकता है # Order of these is important! # Content downloading progress window +STR_CONTENT_DOWNLOAD_COMPLETE :{WHITE}डाउनलोड पूरा हुआ # Content downloading error messages STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD :{WHITE}डाउनलोड असफल हुआ... @@ -567,6 +589,7 @@ STR_LAI_STATION_DESCRIPTION_RAILROAD_STATION :रेलवे STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL :इस्पात का रेल झूला पुल +STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL :इस्पात बाहुधरण रेल पुल @@ -574,6 +597,7 @@ STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL :इस्पा # About OpenTTD window # Framerate display window +STR_FRAMERATE_MS_GOOD :{LTBLUE}{DECIMAL} ms STR_FRAMERATE_MS_WARN :{YELLOW}{DECIMAL} ms STR_FRAMERATE_BYTES_GOOD :{LTBLUE}{BYTES} STR_FRAMERATE_BYTES_WARN :{YELLOW}{BYTES} @@ -603,6 +627,7 @@ STR_MAPGEN_BORDER_RANDOM :{BLACK}या STR_GENERATION_PROGRESS_NUM :{BLACK}{NUM} / {NUM} # NewGRF settings +STR_NEWGRF_SETTINGS_PRESET_LIST_TOOLTIP :{BLACK}चुने गये पूर्वनिश्चित समायोजन को खोलें @@ -637,6 +662,7 @@ STR_NEWGRF_ERROR_MSG_INFO :{SILVER}{STRING # Placeholders for other invalid stuff, e.g. vehicles that have gone (Game Script). # NewGRF scanning window +STR_NEWGRF_SCAN_MESSAGE :{BLACK}NewGRF खोजा जा रहा है। संख्या के अनुसार इसमें कुछ समय लग सकता है... # Sign list window @@ -658,6 +684,7 @@ STR_TOWN_VIEW_EXPAND_BUTTON :{BLACK}फै STR_LOCAL_AUTHORITY_ACTION_SMALL_ADVERTISING_CAMPAIGN :लघु विज्ञापन अभियान +STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_ROAD_RECONSTRUCTION :{YELLOW}नगरों के सड़क तंत्र के पुनर्निर्माण में निवेश करें।{}इससे अधिकतम ६ महीने तक यातायात में बहुत विघ्न पड़ता है।{}मूल्य : {CURRENCY_LONG} # Goal window STR_GOALS_TEXT :{ORANGE}{STRING} @@ -704,6 +731,7 @@ STR_WAYPOINT_VIEW_CAPTION :{WHITE}{WAYPOIN # Finances window STR_FINANCES_YEAR :{WHITE}{NUM} +STR_FINANCES_SECTION_PROPERTY_MAINTENANCE :{GOLD}संपत्ति का रखरखाव STR_FINANCES_POSITIVE_INCOME :{BLACK}+{CURRENCY_LONG} STR_FINANCES_TOTAL_CURRENCY :{BLACK}{CURRENCY_LONG} STR_FINANCES_BORROW_BUTTON :{BLACK}{CURRENCY_LONG} उधार @@ -756,6 +784,7 @@ STR_INDUSTRY_VIEW_ACCEPT_CARGO :{YELLOW}{STRING ############ range for vehicle availability starts +STR_BUY_VEHICLE_TRAIN_ALL_CAPTION :नई ट्रेनें ############ range for vehicle availability ends STR_PURCHASE_INFO_COST_WEIGHT :{BLACK}मूल्य : {GOLD}{CURRENCY_LONG}{BLACK} भार : {GOLD}{WEIGHT_SHORT} @@ -856,6 +885,7 @@ STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE :{LTBLUE}{ENGINE # Order view +STR_ORDER_INDEX :{COMMA}:{NBSP} STR_ORDER_TEXT :{STRING} {STRING} {STRING} @@ -968,6 +998,7 @@ STR_ERROR_TOO_HIGH :{WHITE}... ब # Company related errors +STR_ERROR_CAN_T_SELL_25_SHARE_IN :{WHITE}इस कंपनी का २५% अंश नहीं बेच सकते... # Town related errors STR_ERROR_CAN_T_RENAME_TOWN :{WHITE}नगर का नाम नहीं बदला जा सकता है... @@ -979,6 +1010,7 @@ STR_ERROR_FOREST_CAN_ONLY_BE_PLANTED :{WHITE}... व # Station construction related errors +STR_ERROR_TOO_MANY_STATIONS_LOADING :{WHITE}अत्यधिक स्टेशन/लादन क्षेत्र # Station destruction related errors @@ -1002,8 +1034,10 @@ STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK :{WHITE}पट # Road construction errors +STR_ERROR_INCOMPATIBLE_TRAMWAY :{WHITE}... असंगत ट्रामवे # Waterway construction errors +STR_ERROR_MUST_BE_BUILT_ON_WATER :{WHITE}... पानी पर बनाया जाना चाहिये। # Tree related errors @@ -1053,6 +1087,7 @@ STR_BASESOUNDS_WIN_DESCRIPTION :ट्रां ##id 0x4800 # industry names STR_INDUSTRY_NAME_POWER_STATION :बिजलीघर +STR_INDUSTRY_NAME_STEEL_MILL :इस्पात चक्की STR_INDUSTRY_NAME_BANK_TROPIC_ARCTIC :बैंक ############ WARNING, using range 0x6000 for strings that are stored in the savegame @@ -1062,6 +1097,7 @@ STR_SV_EMPTY : STR_SV_STNAME :{STRING} STR_SV_STNAME_NORTH :{STRING} उत्तर +STR_SV_STNAME_CENTRAL :{STRING} केंद्रीय STR_SV_STNAME_AIRPORT :{STRING} हवाई अड्डा STR_SV_STNAME_BUOY :{STRING} STR_SV_STNAME_WAYPOINT :{STRING} @@ -1074,8 +1110,10 @@ STR_VEHICLE_NAME_TRAIN_WAGON_RAIL_COAL_CAR :कोयला STR_VEHICLE_NAME_TRAIN_WAGON_RAIL_FRUIT_TRUCK :फल वाहन STR_VEHICLE_NAME_TRAIN_ENGINE_MONORAIL_X2001_ELECTRIC :'X2001' (विद्युतीय) STR_VEHICLE_NAME_TRAIN_WAGON_MONORAIL_TOY_VAN :खिलौनों का डब्बा +STR_VEHICLE_NAME_TRAIN_WAGON_MAGLEV_WATER_TANKER :पानी का टैंकर STR_VEHICLE_NAME_TRAIN_WAGON_MAGLEV_BUBBLE_VAN :बबल वैन STR_VEHICLE_NAME_ROAD_VEHICLE_PLODDYPHUT_MKIII_BUS :प्लॉडीपीहट एमके३ बस +STR_VEHICLE_NAME_ROAD_VEHICLE_MORELAND_WOOD_TRUCK :मोरलैंड काष्ठ ट्रक STR_VEHICLE_NAME_ROAD_VEHICLE_FOSTER_ARMORED_TRUCK :फोस्टर कवचयुक्त ट्रक STR_VEHICLE_NAME_ROAD_VEHICLE_POWERNAUGHT_CANDY_TRUCK :पावरनोट मिष्ठान्न ट्रक STR_VEHICLE_NAME_AIRCRAFT_BAKEWELL_COTSWALD_LB_3 :बेकवेल कॉट्सवॉल्ड एलबी-३ diff --git a/src/lang/hungarian.txt b/src/lang/hungarian.txt index 3e7b3d8152..0a42512de8 100644 --- a/src/lang/hungarian.txt +++ b/src/lang/hungarian.txt @@ -2056,8 +2056,9 @@ STR_FACE_TIE :Nyakkendő: STR_FACE_EARRING :Fülbevaló: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Nyakkendő vagy fülbevaló cseréje -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privát +############ Next lines match ServerGameType STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Nyilvános +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Hálózati játék @@ -2111,7 +2112,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Szerver STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Saját szervert indít STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Add meg a neved -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Írd be a szerver IP címét # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Új játékot kezd @@ -2215,6 +2215,9 @@ STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP :{BLACK}Hozz lé STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Ez vagy te STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Ez a játék elindítója +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Kirúgás STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Tiltás STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Törlés @@ -2341,6 +2344,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}A szerve STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}A szerver újraindul...{}Türelem... STR_NETWORK_MESSAGE_KICKED :*** {STRING} ki lett rúgva. Oka: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Tartalom letöltés STR_CONTENT_TYPE_CAPTION :{BLACK}Típus diff --git a/src/lang/icelandic.txt b/src/lang/icelandic.txt index 7b66659a46..0b03306ffa 100644 --- a/src/lang/icelandic.txt +++ b/src/lang/icelandic.txt @@ -1734,6 +1734,8 @@ STR_FACE_TIE :Bindi: STR_FACE_EARRING :Eyrnalokkur: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Breyta bindi eða eyrnalokk +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Fjölspilun @@ -1783,7 +1785,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Hefja þ STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Hefja eigin þjón STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Sláðu inn nafn þitt -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Sláðu inn IP tölu þjónsins # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Hefja nýjan fjölspilunarleik @@ -1861,6 +1862,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Fylgjast með # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Þjónn @@ -1969,6 +1973,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} he STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Þjónninn sleit tengingunni STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Verið er að endurræsa þjóninn...{}Vinsamlega bíðið... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Niðurhel efni STR_CONTENT_TYPE_CAPTION :{BLACK}Týpa diff --git a/src/lang/ido.txt b/src/lang/ido.txt index a6d5fa5887..60536c6939 100644 --- a/src/lang/ido.txt +++ b/src/lang/ido.txt @@ -607,6 +607,8 @@ STR_QUIT_NO :{BLACK}Ne # Face selection window +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list @@ -641,6 +643,9 @@ STR_NETWORK_SERVER_LIST_MAP_SIZE_SHORT :{BLACK}{COMMA}x # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + @@ -664,6 +669,7 @@ STR_NETWORK_SERVER_MESSAGE :*** {1:STRING} ############ Leave those lines in this order!! ############ End of leave-in-this-order + # Content downloading window # Order of these is important! diff --git a/src/lang/indonesian.txt b/src/lang/indonesian.txt index 29ff93fc24..799a693d82 100644 --- a/src/lang/indonesian.txt +++ b/src/lang/indonesian.txt @@ -1992,8 +1992,9 @@ STR_FACE_TIE :Dasi: STR_FACE_EARRING :Anting-anting: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Ubah dasi atau anting-anting -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privat +############ Next lines match ServerGameType STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Umum +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Bermain bersama @@ -2047,7 +2048,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Mulai se STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Mulai melayani STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Masukkan nama anda -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Masukkan alamat IP server # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Mulai permainan bersama baru @@ -2151,6 +2151,9 @@ STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP :{BLACK}Ciptakan STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Ini adalah Anda STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Ini adalah hos permainan +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Diusir STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Melarang STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Hapus @@ -2277,6 +2280,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Server m STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Server memulai ulang...{}Tunggulah... STR_NETWORK_MESSAGE_KICKED :*** {STRING} telah dikeluarkan. Alasan: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Mengunduh konten STR_CONTENT_TYPE_CAPTION :{BLACK}Tipe diff --git a/src/lang/irish.txt b/src/lang/irish.txt index bad6110603..a23c96e4cf 100644 --- a/src/lang/irish.txt +++ b/src/lang/irish.txt @@ -1869,6 +1869,8 @@ STR_FACE_TIE :Carbhat: STR_FACE_EARRING :Fáinne cluaise: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Athraigh carbhat nó fáinne cluaise +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Ilimreoirí @@ -1918,7 +1920,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Tosaigh STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Tosaigh d'fhreastalaí féin STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Iontráil d'ainm -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Iontráil seoladh an óstaigh # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Tosaigh cluiche ilimreora nua @@ -1996,6 +1997,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Féach air # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Freastalaí @@ -2104,6 +2108,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** D'athraigh STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Dhún an freastalaí an seisiún STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Tá an freastalaí á atosú...{}Fan go fóill... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Inneachar á íoslódáil STR_CONTENT_TYPE_CAPTION :{BLACK}Cineál diff --git a/src/lang/italian.txt b/src/lang/italian.txt index b75cedbec6..3c949654c5 100644 --- a/src/lang/italian.txt +++ b/src/lang/italian.txt @@ -2006,6 +2006,8 @@ STR_FACE_TIE :Cravatta: STR_FACE_EARRING :Orecchino: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Cambia la cravatta o l'orecchino +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multigiocatore @@ -2059,7 +2061,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Avvia se STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Avvia il proprio server STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Inserire il nome del giocatore -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Digitare l'indirizzo del server # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Avvia nuova partita multigiocatore @@ -2138,6 +2139,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Diventa spettat # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ASK_COMPANY_RESET :{YELLOW}Sei sicuro di voler eliminare la compagnia '{COMPANY}'? @@ -2252,6 +2256,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Il serve STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Il server si sta riavviando...{}Attendere prego... STR_NETWORK_MESSAGE_KICKED :*** {STRING} è stato espulso. Motivo: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Download contenuti STR_CONTENT_TYPE_CAPTION :{BLACK}Tipo diff --git a/src/lang/japanese.txt b/src/lang/japanese.txt index 710fd42fba..5f640287df 100644 --- a/src/lang/japanese.txt +++ b/src/lang/japanese.txt @@ -1997,8 +1997,9 @@ STR_FACE_TIE :ネクタイ: STR_FACE_EARRING :イヤリング: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}ネクタイ/イヤリングを変更します -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :プライベート +############ Next lines match ServerGameType STR_NETWORK_SERVER_VISIBILITY_PUBLIC :公開 +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}マルチプレイヤーゲーム @@ -2052,7 +2053,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}サー STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}自分でサーバーを立ち上げます STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}プレイヤー名を入力 -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}ホストアドレスを入力します # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}新規マルチプレイヤーゲームを開始 @@ -2156,6 +2156,9 @@ STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP :{BLACK}新し STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}これはあなたです STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}ゲームのホストです +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :キック STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :BAN STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :削除 @@ -2283,6 +2286,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}サー STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}このサーバーは再起動中です…{}しばらくお待ちください… STR_NETWORK_MESSAGE_KICKED :*** {STRING}がキックされました。理由: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}コンテンツをダウンロード中 STR_CONTENT_TYPE_CAPTION :{BLACK}種類 diff --git a/src/lang/korean.txt b/src/lang/korean.txt index 6d78ff6f59..d144bde217 100644 --- a/src/lang/korean.txt +++ b/src/lang/korean.txt @@ -2063,7 +2063,7 @@ STR_CONFIG_SETTING_DISTRIBUTION_PAX :승객에 대 STR_CONFIG_SETTING_DISTRIBUTION_PAX_HELPTEXT :"대칭"은 A역에서 B역으로 가려는 승객의 수가 B에서 A로 가려는 승객의 수와 비슷하다는 뜻입니다. "비대칭"은 승객이 아무 방향이나 임의의 양만큼 가게 됨을 뜻합니다. "수동"은 자동적인 승객 분배가 일어나지 않고 기존 방식을 사용하겠음을 뜻합니다. STR_CONFIG_SETTING_DISTRIBUTION_MAIL :우편에 대한 분배 형식: {STRING} STR_CONFIG_SETTING_DISTRIBUTION_MAIL_HELPTEXT :"대칭"은 A역에서 B역으로 가려는 우편물의 수가 B에서 A로 가려는 우편물의 수와 비슷하다는 뜻입니다. "비대칭"은 우편물이 아무 방향이나 임의의 양만큼 가게 됨을 뜻합니다. "수동"은 자동적인 우편물 분배가 일어나지 않고 기존 방식을 사용하겠음을 뜻합니다. -STR_CONFIG_SETTING_DISTRIBUTION_ARMOURED :장갑 화물에 대한 분배 형식: {STRING} +STR_CONFIG_SETTING_DISTRIBUTION_ARMOURED :귀금속 화물에 대한 분배 형식: {STRING} STR_CONFIG_SETTING_DISTRIBUTION_ARMOURED_HELPTEXT :장갑 화물은 온대 기후의 귀금속, 아열대 기후의 다이아몬드 또는 아한대 기후의 금을 말합니다. NewGRF을 사용하면 달라질 수 있습니다. "대칭"은 A역에서 B역으로 가려는 화물의 수가 B에서 A로 가려는 화물의 수와 비슷하다는 뜻입니다. "비대칭"은 화물이 아무 방향이나 임의의 양만큼 가게 됨을 뜻합니다. "수동"은 자동적인 화물 분배가 일어나지 않고 기존 방식을 사용하겠음을 뜻합니다. 아한대 기후에서는 대칭으로 설정하면 은행이 금광으로 금을 보내지 않으려 하기 때문에 비대칭이나 수동으로 설정하는 것을 추천합니다. 온대 기후나 아열대 기후에서는 은행이 일부 적재한 귀금속을 원래 은행으로 보내려고 하기 때문에 대칭을 선택해도 됩니다. STR_CONFIG_SETTING_DISTRIBUTION_DEFAULT :다른 화물에 대한 분배 형식: {STRING} STR_CONFIG_SETTING_DISTRIBUTION_DEFAULT_HELPTEXT :"비대칭"은 화물이 아무 방향이나 임의의 양만큼 가게 됨을 뜻합니다. "수동"은 자동적인 화물 분배가 일어나지 않고 기존 방식을 사용하겠음을 뜻합니다. 특별한 이유가 없는 한, "비대칭"이나 "수동"으로 설정하십시오. @@ -2434,8 +2434,11 @@ STR_FACE_TIE :넥타이: STR_FACE_EARRING :귀걸이: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}넥타이/귀걸이 변경 -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :비공개 +############ Next lines match ServerGameType +STR_NETWORK_SERVER_VISIBILITY_LOCAL :로컬 STR_NETWORK_SERVER_VISIBILITY_PUBLIC :공개 +STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY :초대만 +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}멀티 플레이 @@ -2484,12 +2487,12 @@ STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET_TOOLTIP :{BLACK}인터 STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN :{BLACK}LAN 검색 STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN_TOOLTIP :{BLACK}로컬 영역 네트워크에서 서버를 검색합니다 STR_NETWORK_SERVER_LIST_ADD_SERVER :{BLACK}서버 추가 -STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}서버를 목록에 수동으로 추가합니다. +STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}서버를 목록에 추가합니다. 서버 주소나 초대 코드를 입력하세요. STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}서버 열기 STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}당신이 서버가 되어 게임을 진행합니다. STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}이름을 입력하세요 -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}호스트(IP) 주소 입력 +STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS :{BLACK}서버 주소나 초대 코드를 입력하세요 # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}새 멀티플레이 게임 시작하기 @@ -2576,6 +2579,10 @@ STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP :{BLACK}서버 STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :서버 이름 STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY :{BLACK}공개 여부 STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP :{BLACK}서버 목록에 이 서버를 공개할 지 여부를 설정합니다 +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE :{BLACK}초대 코드 +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP :{BLACK}다른 플레이어가 이 서버에 들어오기 위한 초대 코드입니다 +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE :{BLACK}접속 방식 +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP :{BLACK}다른 사람이 이 서버에 들어올 수 있는지 여부를 나타냅니다 STR_NETWORK_CLIENT_LIST_PLAYER :{BLACK}플레이어 STR_NETWORK_CLIENT_LIST_PLAYER_NAME :{BLACK}이름 STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP :{BLACK}당신의 접속자 이름입니다 @@ -2594,6 +2601,12 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}당신 STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}이 게임을 연 사람입니다 STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}접속자 {NUM}명 / 회사 {NUM}개 +############ Begin of ConnectionType +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN :{BLACK}로컬 +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_ISOLATED :{RED}다른 플레이어가 접속할 수 없게 됩니다 +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_DIRECT :{BLACK}공개 +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :추방 STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :차단 STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :삭제 @@ -2724,6 +2737,10 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}서버 STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}서버가 재시작되고 있습니다...{}기다려주세요... STR_NETWORK_MESSAGE_KICKED :*** {STRING} - 서버에서 강제로 추방되었습니다. 사유: ({STRING}) +STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED :{WHITE}서버 등록 실패 +STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}서버가 원격 접속을 허용하지 않습니다 +STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL :{WHITE}다른 플레이어가 이 서버에 들어올 수 없게 될 것입니다 + # Content downloading window STR_CONTENT_TITLE :{WHITE}콘텐츠 다운로드 STR_CONTENT_TYPE_CAPTION :{BLACK}종류 diff --git a/src/lang/latin.txt b/src/lang/latin.txt index a43578be7d..62df86890a 100644 --- a/src/lang/latin.txt +++ b/src/lang/latin.txt @@ -2088,6 +2088,8 @@ STR_FACE_TIE :Focale: STR_FACE_EARRING :Inauris: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Mutare focale vel inaurem +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Modus Plurium Lusorum @@ -2137,7 +2139,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Servatru STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Servatrum tuum proprium incohare STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Nomen tuum inscribe -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Inscribe inscriptionem IP servatri # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Incipere novum ludum cum pluribus lusoribus @@ -2215,6 +2216,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Spectare # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Servatrum @@ -2323,6 +2327,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} no STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Servatrum iam clausum est STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Servatrum resumit...{}Maneas... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Arcessitus Rerum STR_CONTENT_TYPE_CAPTION :{BLACK}Typus diff --git a/src/lang/latvian.txt b/src/lang/latvian.txt index 1d2f50d3a2..a4576f5c14 100644 --- a/src/lang/latvian.txt +++ b/src/lang/latvian.txt @@ -1961,6 +1961,8 @@ STR_FACE_TIE :Kaklasaite: STR_FACE_EARRING :Auskars: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Mainīt kaklasaiti vai auskarus +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Vairākspēlētāju spēle @@ -2014,7 +2016,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Palaist STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Palaist jūsu personīgo serveri STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Ievadīt savu vārdu -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Ievadīt servera adresi # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Sākt jaunu vairākspēlētāju spēli @@ -2092,6 +2093,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Skatīt # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Serveris @@ -2205,6 +2209,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Serveris STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Serveris pārstartējas...{}Lūdzu uzgaidiet... STR_NETWORK_MESSAGE_KICKED :*** {STRING} tika izmests. Iemesls: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Satura lejupielāde STR_CONTENT_TYPE_CAPTION :{BLACK}Tips diff --git a/src/lang/lithuanian.txt b/src/lang/lithuanian.txt index b4b7b53455..1d2fa9c346 100644 --- a/src/lang/lithuanian.txt +++ b/src/lang/lithuanian.txt @@ -2205,6 +2205,8 @@ STR_FACE_TIE :Kaklaraištis: STR_FACE_EARRING :Auskaras: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Pakeisti kaklaraištį arba auskarą +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Žaidimas tinkle @@ -2258,7 +2260,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Sukurti STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Sukurti savo serverį STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Įrašykite savo vardą -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Įvesk adresą (IP) # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Pradėti naują daugiažaidėjinį žaidimą @@ -2336,6 +2337,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Stebėti # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Serveris @@ -2449,6 +2453,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Serveris STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Serveris persikrauna...{}Prašau palaukti... STR_NETWORK_MESSAGE_KICKED :*** {STRING} buvo išmestas. Priežastis: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Turinio atsisiuntimas STR_CONTENT_TYPE_CAPTION :{BLACK}Tipas diff --git a/src/lang/luxembourgish.txt b/src/lang/luxembourgish.txt index aa5c0f08ef..f760815ceb 100644 --- a/src/lang/luxembourgish.txt +++ b/src/lang/luxembourgish.txt @@ -1986,6 +1986,8 @@ STR_FACE_TIE :Krawatt: STR_FACE_EARRING :Ouerréng: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Krawatt oder Ouerréng änneren +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multiplayer @@ -2039,7 +2041,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Server s STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Eegenen Server starten STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Gëff däin Numm an -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}D'Address vum Host uginn # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Nei Multiplayerpartie starten @@ -2117,6 +2118,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Nokucken # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Server @@ -2230,6 +2234,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}De Serve STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}De Server gëtt nei gestart...{}W.e.g. waarden... STR_NETWORK_MESSAGE_KICKED :*** {STRING} gouf gekickt. Grond: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Lueden Inhalt erof STR_CONTENT_TYPE_CAPTION :{BLACK}Typ diff --git a/src/lang/macedonian.txt b/src/lang/macedonian.txt index cd36e80ce7..72bc4a654b 100644 --- a/src/lang/macedonian.txt +++ b/src/lang/macedonian.txt @@ -965,6 +965,8 @@ STR_ABANDON_GAME_QUERY :{YELLOW}Дал # Face selection window +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list @@ -999,6 +1001,9 @@ STR_NETWORK_SERVER_LIST_MAP_SIZE_SHORT :{BLACK}{COMMA}x # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + @@ -1024,6 +1029,7 @@ STR_NETWORK_SERVER_MESSAGE_GAME_STILL_PAUSED_4 :Игра ушт STR_NETWORK_SERVER_MESSAGE_GAME_REASON_GAME_SCRIPT :игра скрипта ############ End of leave-in-this-order + # Content downloading window STR_CONTENT_SEARCH_EXTERNAL :{BLACK}Пребарај надворешни веб-страни STR_CONTENT_SEARCH_EXTERNAL_DISCLAIMER_CAPTION :{WHITE}Вие заминувате од OpenTTD! diff --git a/src/lang/malay.txt b/src/lang/malay.txt index ce2582f95d..763731ba4b 100644 --- a/src/lang/malay.txt +++ b/src/lang/malay.txt @@ -1631,6 +1631,8 @@ STR_FACE_TIE :Tali leher: STR_FACE_EARRING :Anting-anting: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Tukar tali leher atau anting-anting +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Pemain berbilang @@ -1680,7 +1682,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Mulakan STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Mulakan pelayan sendiri STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Masukkan nama anda -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Masukkan alamat hos # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Mulakan permainan baru berbilang pemain @@ -1758,6 +1759,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Saksi # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Pelayan @@ -1866,6 +1870,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} te STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Pelayan telah menutup sesi ini STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Pelayan sedang dimulakan semula...{}Harap bersabar... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Kandungan sedang dimuat turun STR_CONTENT_TYPE_CAPTION :{BLACK}Jenis diff --git a/src/lang/maltese.txt b/src/lang/maltese.txt index f574ea4e9d..110ec64347 100644 --- a/src/lang/maltese.txt +++ b/src/lang/maltese.txt @@ -533,6 +533,8 @@ STR_CHEAT_CHANGE_DATE_QUERY_CAPT :{WHITE}Ibdel is # Face selection window +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list @@ -569,6 +571,9 @@ STR_NETWORK_CONNECTING_DOWNLOADING_2 :{BLACK}{BYTES} # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + @@ -593,6 +598,7 @@ STR_NETWORK_SERVER_MESSAGE :*** {1:STRING} ############ Leave those lines in this order!! ############ End of leave-in-this-order + # Content downloading window # Order of these is important! diff --git a/src/lang/marathi.txt b/src/lang/marathi.txt index 71f7d02864..3841e36232 100644 --- a/src/lang/marathi.txt +++ b/src/lang/marathi.txt @@ -897,6 +897,8 @@ STR_FACE_LIPS_MOUSTACHE_TOOLTIP :{BLACK}ओठ STR_FACE_CHIN :हनुवटी: STR_FACE_CHIN_TOOLTIP :{BLACK}हनुवटी बदला +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list @@ -939,6 +941,9 @@ STR_NETWORK_CONNECTING_DOWNLOADING_2 :{BLACK}{BYTES} # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + @@ -962,6 +967,7 @@ STR_NETWORK_SERVER_MESSAGE :*** {1:STRING} ############ Leave those lines in this order!! ############ End of leave-in-this-order + # Content downloading window # Order of these is important! diff --git a/src/lang/norwegian_bokmal.txt b/src/lang/norwegian_bokmal.txt index e88181f21f..b0bbb8dbe7 100644 --- a/src/lang/norwegian_bokmal.txt +++ b/src/lang/norwegian_bokmal.txt @@ -2004,8 +2004,9 @@ STR_FACE_TIE :Slips: STR_FACE_EARRING :Ørering: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Endre slips eller ørering -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privat +############ Next lines match ServerGameType STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Offentlig +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Flerspiller @@ -2059,7 +2060,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Start tj STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Start en egen tjener STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Skriv inn ditt navn -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Skriv inn IP-adressen til verten # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Start et nytt flerspillerspill @@ -2165,6 +2165,9 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Dette er STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Dette er verten for spillet STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} klient{P "" s} / {NUM} firma{P et er} +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Spark STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Utesteng STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Slett @@ -2292,6 +2295,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Tjeneren STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Tjeneren starter på nytt...{}Vennligst vent... STR_NETWORK_MESSAGE_KICKED :*** {STRING} ble kastet ut. Grunn: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Laster ned innhold STR_CONTENT_TYPE_CAPTION :{BLACK}Type diff --git a/src/lang/norwegian_nynorsk.txt b/src/lang/norwegian_nynorsk.txt index ccbecd148a..2ab7f001b3 100644 --- a/src/lang/norwegian_nynorsk.txt +++ b/src/lang/norwegian_nynorsk.txt @@ -1793,6 +1793,8 @@ STR_FACE_TIE :Slips: STR_FACE_EARRING :Øyrering: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Byt slips eller øyrering +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Fleirspelar @@ -1842,7 +1844,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Start te STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Start ein eigen tenar STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Set inn namnet ditt -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Skriv inn IP-adressa til tenaren # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Start eit nytt fleirspelarspel @@ -1920,6 +1921,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Vær tilskodar # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Tenar @@ -2028,6 +2032,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} ha STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Tenaren avslutta spelet STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Tenaren startar om att...{}Vær venleg og vent... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Lastar ned innhald STR_CONTENT_TYPE_CAPTION :{BLACK}Type diff --git a/src/lang/persian.txt b/src/lang/persian.txt index 38f757a3ca..91cb13fc38 100644 --- a/src/lang/persian.txt +++ b/src/lang/persian.txt @@ -1591,6 +1591,8 @@ STR_FACE_TIE :کراوات: STR_FACE_EARRING :گوشواره: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}تغییر کراوات یا گوشواره +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}چندنفره @@ -1640,7 +1642,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}اجرا STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}راه اندازی سرویس دهنده خود STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}نام خود را وارد نماببد -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}نشانی میزبان را وارد کنید # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}آغاز بازی چندنفره @@ -1718,6 +1719,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :{WHITE}تماش # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :سرویس دهنده @@ -1826,6 +1830,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} ن STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}سرویس دهنده جلسه را تعطیل کرد STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}راه اندازی مجدد سرویس دهنده...{}لطفا صبر کنید... + # Content downloading window STR_CONTENT_TITLE :{WHITE}در حال دانلود STR_CONTENT_TYPE_CAPTION :{BLACK}نوع diff --git a/src/lang/polish.txt b/src/lang/polish.txt index c68adc2b3f..1c702030b1 100644 --- a/src/lang/polish.txt +++ b/src/lang/polish.txt @@ -2372,6 +2372,8 @@ STR_FACE_TIE :Krawat: STR_FACE_EARRING :Kolczyk: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Zmień krawat lub kolczyk +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Gra wieloosobowa @@ -2425,7 +2427,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Uruchom STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Uruchom własny serwer STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Wprowadź swoje imię -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Wpisz adres IP serwera # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Rozpocznij nową grę wieloosobową @@ -2503,6 +2504,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Obserwuj # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Serwer @@ -2616,6 +2620,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Serwer z STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Restart serwera...{}Proszę czekać... STR_NETWORK_MESSAGE_KICKED :*** {STRING} został wyrzucony. Powód: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Przeglądarka dodatkowej zawartości STR_CONTENT_TYPE_CAPTION :{BLACK}Typ diff --git a/src/lang/portuguese.txt b/src/lang/portuguese.txt index 9f0f10e4d0..1408228718 100644 --- a/src/lang/portuguese.txt +++ b/src/lang/portuguese.txt @@ -2002,8 +2002,11 @@ STR_FACE_TIE :Gravata: STR_FACE_EARRING :Brinco: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Mudar gravata ou brinco -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privado +############ Next lines match ServerGameType +STR_NETWORK_SERVER_VISIBILITY_LOCAL :Local STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Público +STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY :Apenas convites +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multi-jogador @@ -2037,6 +2040,7 @@ STR_NETWORK_SERVER_LIST_SERVER_VERSION :{SILVER}Versão STR_NETWORK_SERVER_LIST_SERVER_ADDRESS :{SILVER}Endereço do servidor: {WHITE}{STRING} STR_NETWORK_SERVER_LIST_START_DATE :{SILVER}Data de início: {WHITE}{DATE_SHORT} STR_NETWORK_SERVER_LIST_CURRENT_DATE :{SILVER}Data actual: {WHITE}{DATE_SHORT} +STR_NETWORK_SERVER_LIST_GAMESCRIPT :{SILVER}Script de Jogo: {WHITE}{STRING} (v{NUM}) STR_NETWORK_SERVER_LIST_PASSWORD :{SILVER}Protegido por palavra-chave! STR_NETWORK_SERVER_LIST_SERVER_OFFLINE :{SILVER}SERVIDOR DESLIGADO STR_NETWORK_SERVER_LIST_SERVER_FULL :{SILVER}SERVIDOR CHEIO @@ -2052,12 +2056,12 @@ STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET_TOOLTIP :{BLACK}Pesquisa STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN :{BLACK}Search LAN STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN_TOOLTIP :{BLACK} Pesquisa de rede local para servidores STR_NETWORK_SERVER_LIST_ADD_SERVER :{BLACK}Adicionar servidor -STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Adiciona um servidor à lista que será sempre verificado se existem jogos a decorrer. +STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Adiciona um servidor à lista. Pode ser um endereço de servidor ou um código de convite STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Iniciar servidor STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Iniciar um servidor próprio STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Introduza o seu nome -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Introduza o endereço IP do servidor +STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS :{BLACK}Introduza o endereço de servidor ou código de convite # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Iniciar novo jogo @@ -2144,6 +2148,10 @@ STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP :{BLACK}Editar o STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :Nome do servidor STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY :{BLACK}Visibilidade STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP :{BLACK}Se as outras pessoas podem ver o seu servidor na lista pública +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE :{BLACK}Código de convite +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP :{BLACK}Código de convite que os outros jogadores podem usar para entrar neste servidor +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE :{BLACK}Tipo de conexão +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP :{BLACK}Se e como o seu servidor pode ser alcançado por outros STR_NETWORK_CLIENT_LIST_PLAYER :{BLACK}Jogador STR_NETWORK_CLIENT_LIST_PLAYER_NAME :{BLACK}Nome STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP :{BLACK}O seu nome de jogador @@ -2162,6 +2170,12 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Este é STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Este é o anfitrião do jogo STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} cliente{P "" s} / {NUM} companhi{P a as} +############ Begin of ConnectionType +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN :{BLACK}Local +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_ISOLATED :{RED}Jogadores remotos não conseguem conetar +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_DIRECT :{BLACK}Público +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Expulsar STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Banir STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Apagar @@ -2289,6 +2303,10 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}O servid STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}O servidor está a reiniciar...{}Por favor espere... STR_NETWORK_MESSAGE_KICKED :*** {STRING} foi expulso. Motivo: ({STRING}) +STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED :{WHITE}Falha ao registar o servidor +STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}O seu servidor não permite conexões remotas +STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL :{WHITE}Outros jogadores não conseguirão conetar ao seu servidor + # Content downloading window STR_CONTENT_TITLE :{WHITE}Descarregamento de conteúdo STR_CONTENT_TYPE_CAPTION :{BLACK}Tipo diff --git a/src/lang/romanian.txt b/src/lang/romanian.txt index 17215ee699..d69ba0d275 100644 --- a/src/lang/romanian.txt +++ b/src/lang/romanian.txt @@ -1919,7 +1919,8 @@ STR_FACE_TIE :Cravată: STR_FACE_EARRING :Cercei: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Schimbă cravata sau cerceii -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privat +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multiplayer @@ -1973,7 +1974,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Porneşt STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Porneşte un server propriu STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Introduceţi numele dvs. -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Introduceţi adresa serverului # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Începe un joc nou @@ -2056,6 +2056,9 @@ STR_NETWORK_CLIENT_LIST_PLAYER_NAME_EDIT_TOOLTIP :{BLACK}Modific STR_NETWORK_CLIENT_LIST_CHAT_COMPANY_TOOLTIP :{BLACK}Trimite un mesaj tuturor jucătorilor acestei companii STR_NETWORK_CLIENT_LIST_CHAT_SPECTATOR_TOOLTIP :{BLACK}Trimite un mesaj tuturor spectatorilor +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_UNLOCK :Deblocare cu parolă STR_NETWORK_CLIENT_LIST_ASK_CLIENT_KICK :{YELLOW}Sigur vrei să dai afară jucătorul '{STRING}'? @@ -2171,6 +2174,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Serverul STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Serverul este repornit...{}Vă rugăm aşteptaţi... STR_NETWORK_MESSAGE_KICKED :*** {STRING} a fost dat afară. Motiv: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Descărcare resurse online STR_CONTENT_TYPE_CAPTION :{BLACK}Tip diff --git a/src/lang/russian.txt b/src/lang/russian.txt index da49bc1ac6..4dcfac1e8c 100644 --- a/src/lang/russian.txt +++ b/src/lang/russian.txt @@ -2155,8 +2155,9 @@ STR_FACE_TIE :Галстук: STR_FACE_EARRING :Серьга: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Изменить галстук или серьгу -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Публичный +############ Next lines match ServerGameType STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Частный +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Сетевая игра @@ -2210,7 +2211,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Запу STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Запуск сервера на вашем компьютере. К этой игре смогут подсоединяться другие игроки. STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Введите ваше имя -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Введите адрес сервера # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Запуск новой сетевой игры @@ -2315,6 +2315,9 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Это STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Это организатор игры STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} клиент{P "" а ов} / {NUM} компани{P я и й} +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Отключить STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Заблокировать STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Удалить @@ -2442,6 +2445,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Серв STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Сервер перезапускается...{}Пожалуйста, подождите... STR_NETWORK_MESSAGE_KICKED :*** {STRING} был исключён из игры. Причина: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Загрузка контента STR_CONTENT_TYPE_CAPTION :{BLACK}Тип diff --git a/src/lang/serbian.txt b/src/lang/serbian.txt index 563fffb5d4..e1187849e2 100644 --- a/src/lang/serbian.txt +++ b/src/lang/serbian.txt @@ -2181,6 +2181,8 @@ STR_FACE_TIE :Kravata: STR_FACE_EARRING :Minđuše: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Promena kravate ili minđuša +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Mrežna partija @@ -2234,7 +2236,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Pokreni STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Pokrenite sopstveni server STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Unesite Vaše ime -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Unesite adresu računara-servera # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Pokreni novu mrežnu partiju @@ -2312,6 +2313,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Praćenje # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Server @@ -2425,6 +2429,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Server j STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Server se ponovo pokreće...{}Molimo sačekajte... STR_NETWORK_MESSAGE_KICKED :*** {STRING} je izvačen. Razlog: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Dodaci za preuzimanje STR_CONTENT_TYPE_CAPTION :{BLACK}Vrsta diff --git a/src/lang/simplified_chinese.txt b/src/lang/simplified_chinese.txt index d76ee78255..f6e7bb2977 100644 --- a/src/lang/simplified_chinese.txt +++ b/src/lang/simplified_chinese.txt @@ -1991,6 +1991,8 @@ STR_FACE_TIE :领带 STR_FACE_EARRING :耳环 STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}选择领带或是耳环 +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}联机游戏 @@ -2044,7 +2046,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}启动 STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}启动本机作为服务器 STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}输入姓名 -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}输入服务器地址 # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}开始新的联机游戏 @@ -2122,6 +2123,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :旁观 # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :服务器 @@ -2235,6 +2239,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}服务 STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}服务器正在重新启动。{}请等待…… STR_NETWORK_MESSAGE_KICKED :*** {STRING} 被踢出服务器。原因:({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}下载内容 STR_CONTENT_TYPE_CAPTION :{BLACK}类型 diff --git a/src/lang/slovak.txt b/src/lang/slovak.txt index 9f6b59ef67..e20c00715d 100644 --- a/src/lang/slovak.txt +++ b/src/lang/slovak.txt @@ -2060,8 +2060,9 @@ STR_FACE_TIE :Kravata: STR_FACE_EARRING :Náušnica: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Zmeniť kravatu alebo náušnicu -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privátny +############ Next lines match ServerGameType STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Verejný +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Hra pre viac hráčov @@ -2115,7 +2116,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Spustiť STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Spustiť vlastný server STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Zadajte vaše meno -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Zadajte IP adresu serveru # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Spustiť novú hru pre viacero hráčov @@ -2219,6 +2219,9 @@ STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP :{BLACK}Založi STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Toto ste vy STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Toto je hosť hry +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Vyhodiť STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Ban STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Vymazať @@ -2345,6 +2348,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Server u STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Server sa reštartuje...{}Čakajte prosím... STR_NETWORK_MESSAGE_KICKED :*** Hráč {STRING} bol vyhodený. Dôvod: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Sťahovanie obsahu STR_CONTENT_TYPE_CAPTION :{BLACK}Typ diff --git a/src/lang/slovenian.txt b/src/lang/slovenian.txt index 4cc9964622..0228276663 100644 --- a/src/lang/slovenian.txt +++ b/src/lang/slovenian.txt @@ -2025,6 +2025,8 @@ STR_FACE_TIE :Kravata: STR_FACE_EARRING :Uhani: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Spremeni kravato ali uhane +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Več igralcev @@ -2074,7 +2076,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Poženi STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Poženi lasten streznik STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Vpiši tvoje ime -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Vnesi naslov gostitelja # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Poženi novo igro za več igralcev @@ -2152,6 +2153,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Opazuj # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Strežnik @@ -2260,6 +2264,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} je STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Strežnik je zaprl sejo STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Strežnik se zaganja...{}Prosim počakaj... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Prenos vsebin STR_CONTENT_TYPE_CAPTION :{BLACK}Vrsta diff --git a/src/lang/spanish.txt b/src/lang/spanish.txt index 48f3e97cd3..94a67a5a54 100644 --- a/src/lang/spanish.txt +++ b/src/lang/spanish.txt @@ -2002,8 +2002,9 @@ STR_FACE_TIE :Corbata: STR_FACE_EARRING :Pendientes: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Cambiar corbata o pendientes -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privado +############ Next lines match ServerGameType STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Público +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multijugador @@ -2057,7 +2058,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Iniciar STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Inicia un servidor nuevo STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Introduce tu nombre -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Introduce la dirección IP del servidor # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Empezar nueva partida en multijugador @@ -2161,6 +2161,9 @@ STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP :{BLACK}Crea una STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Éste eres tú STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Éste es el servidor de la partida +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Expulsar STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Prohibir el acceso STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Eliminar @@ -2288,6 +2291,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}El servi STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}El servidor está reiniciando...{}Espera por favor... STR_NETWORK_MESSAGE_KICKED :*** {STRING} ha sido expulsado. Razón: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Descarga de contenido STR_CONTENT_TYPE_CAPTION :{BLACK}Tipo diff --git a/src/lang/spanish_MX.txt b/src/lang/spanish_MX.txt index 28b2edc568..722699f7e1 100644 --- a/src/lang/spanish_MX.txt +++ b/src/lang/spanish_MX.txt @@ -307,7 +307,7 @@ STR_SORT_BY_TIMETABLE_DELAY :Retraso en itin STR_SORT_BY_FACILITY :Tipo de estación STR_SORT_BY_WAITING_TOTAL :Carga total en espera STR_SORT_BY_WAITING_AVAILABLE :Carga disponible en espera -STR_SORT_BY_RATING_MAX :Valoración más alta de cargamento +STR_SORT_BY_RATING_MAX :Mayor índice de carga STR_SORT_BY_RATING_MIN :Menor índice de carga STR_SORT_BY_ENGINE_ID :Id. locomotora (orden clásico) STR_SORT_BY_COST :Costo @@ -336,7 +336,7 @@ STR_TOOLBAR_TOOLTIP_FORWARD :{BLACK}Avance r STR_TOOLBAR_TOOLTIP_OPTIONS :{BLACK}Opciones STR_TOOLBAR_TOOLTIP_SAVE_GAME_ABANDON_GAME :{BLACK}Guardar partida y salir del juego STR_TOOLBAR_TOOLTIP_DISPLAY_MAP :{BLACK}Mostrar mapa, ventana de vista adicional o lista de carteles -STR_TOOLBAR_TOOLTIP_DISPLAY_TOWN_DIRECTORY :{BLACK}Mostrar guía de pueblos +STR_TOOLBAR_TOOLTIP_DISPLAY_TOWN_DIRECTORY :{BLACK}Mostrar guía de localidades STR_TOOLBAR_TOOLTIP_DISPLAY_SUBSIDIES :{BLACK}Mostrar subsidios STR_TOOLBAR_TOOLTIP_DISPLAY_LIST_OF_COMPANY_STATIONS :{BLACK}Mostrar lista de estaciones STR_TOOLBAR_TOOLTIP_DISPLAY_COMPANY_FINANCES :{BLACK}Mostrar información financiera de la empresa @@ -370,9 +370,9 @@ STR_SCENEDIT_TOOLBAR_SCENARIO_EDITOR :{YELLOW}Editor STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_BACKWARD :{BLACK}Retroceder un año la fecha de inicio STR_SCENEDIT_TOOLBAR_TOOLTIP_MOVE_THE_STARTING_DATE_FORWARD :{BLACK}Avanzar un año la fecha de inicio STR_SCENEDIT_TOOLBAR_TOOLTIP_SET_DATE :{BLACK}Clic para establecer el año inicial -STR_SCENEDIT_TOOLBAR_TOOLTIP_DISPLAY_MAP_TOWN_DIRECTORY :{BLACK}Mostrar mapa, guía de pueblos +STR_SCENEDIT_TOOLBAR_TOOLTIP_DISPLAY_MAP_TOWN_DIRECTORY :{BLACK}Mostrar mapa, guía de localidades STR_SCENEDIT_TOOLBAR_LANDSCAPE_GENERATION :{BLACK}Generación de terreno -STR_SCENEDIT_TOOLBAR_TOWN_GENERATION :{BLACK}Generación de pueblos +STR_SCENEDIT_TOOLBAR_TOWN_GENERATION :{BLACK}Generación de localidades STR_SCENEDIT_TOOLBAR_INDUSTRY_GENERATION :{BLACK}Generación de industrias STR_SCENEDIT_TOOLBAR_ROAD_CONSTRUCTION :{BLACK}Construcción de carreteras STR_SCENEDIT_TOOLBAR_TRAM_CONSTRUCTION :{BLACK}Construcción de tranvía @@ -396,7 +396,7 @@ STR_SETTINGS_MENU_CONFIG_SETTINGS_TREE :Configuración STR_SETTINGS_MENU_SCRIPT_SETTINGS :Configuración de scripts STR_SETTINGS_MENU_NEWGRF_SETTINGS :Configuración de NewGRF STR_SETTINGS_MENU_TRANSPARENCY_OPTIONS :Opciones de transparencia -STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED :Mostrar nombres de pueblos +STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED :Mostrar nombres de localidades STR_SETTINGS_MENU_STATION_NAMES_DISPLAYED :Mostrar nombres de estaciones STR_SETTINGS_MENU_WAYPOINTS_DISPLAYED :Mostrar puntos de ruta STR_SETTINGS_MENU_SIGNS_DISPLAYED :Mostrar carteles propios @@ -422,8 +422,8 @@ STR_MAP_MENU_LINGRAPH_LEGEND :Leyenda de fluj STR_MAP_MENU_SIGN_LIST :Lista de carteles ############ range for town menu starts -STR_TOWN_MENU_TOWN_DIRECTORY :Guía de pueblos -STR_TOWN_MENU_FOUND_TOWN :Fundar pueblo +STR_TOWN_MENU_TOWN_DIRECTORY :Guía de localidades +STR_TOWN_MENU_FOUND_TOWN :Fundar localidad ############ range ends here ############ range for subsidies menu starts @@ -647,8 +647,8 @@ STR_PERFORMANCE_DETAIL_STATIONS_TOOLTIP :{BLACK}Número STR_PERFORMANCE_DETAIL_MIN_PROFIT_TOOLTIP :{BLACK}Utilidad del vehículo con menores ingresos (de entre aquellos con más de 2 años) STR_PERFORMANCE_DETAIL_MIN_INCOME_TOOLTIP :{BLACK}Cantidad de efectivo ganado en el trimestre con la utilidad más baja de los últimos 12 trimestres STR_PERFORMANCE_DETAIL_MAX_INCOME_TOOLTIP :{BLACK}Cantidad de efectivo ganado en el trimestre con la utilidad más alta de los últimos 12 trimestres -STR_PERFORMANCE_DETAIL_DELIVERED_TOOLTIP :{BLACK}Unidades de cargamento entregadas en los últimos cuatro trimestres -STR_PERFORMANCE_DETAIL_CARGO_TOOLTIP :{BLACK}Número de tipos de cargamento entregados en el último trimestre +STR_PERFORMANCE_DETAIL_DELIVERED_TOOLTIP :{BLACK}Unidades de carga entregadas en los últimos cuatro trimestres +STR_PERFORMANCE_DETAIL_CARGO_TOOLTIP :{BLACK}Número de tipos de carga entregadas en el último trimestre STR_PERFORMANCE_DETAIL_MONEY_TOOLTIP :{BLACK}Cantidad de dinero que esta empresa tiene en el banco STR_PERFORMANCE_DETAIL_LOAN_TOOLTIP :{BLACK}Cantidad de dinero que esta empresa ha recibido como préstamo STR_PERFORMANCE_DETAIL_TOTAL_TOOLTIP :{BLACK}Total de puntos ganados del máximo posible @@ -760,12 +760,12 @@ STR_SMALLMAP_LEGENDA_TREES :{TINY_FONT}{BLA STR_SMALLMAP_LEGENDA_ROCKS :{TINY_FONT}{BLACK}Rocas STR_SMALLMAP_LEGENDA_WATER :{TINY_FONT}{BLACK}Agua STR_SMALLMAP_LEGENDA_NO_OWNER :{TINY_FONT}{BLACK}Sin propietario -STR_SMALLMAP_LEGENDA_TOWNS :{TINY_FONT}{BLACK}Pueblos +STR_SMALLMAP_LEGENDA_TOWNS :{TINY_FONT}{BLACK}Localidades STR_SMALLMAP_LEGENDA_INDUSTRIES :{TINY_FONT}{BLACK}Industrias STR_SMALLMAP_LEGENDA_DESERT :{TINY_FONT}{BLACK}Desierto STR_SMALLMAP_LEGENDA_SNOW :{TINY_FONT}{BLACK}Nieve -STR_SMALLMAP_TOOLTIP_TOGGLE_TOWN_NAMES_ON_OFF :{BLACK}Mostrar u ocultar nombres de pueblos en el mapa +STR_SMALLMAP_TOOLTIP_TOGGLE_TOWN_NAMES_ON_OFF :{BLACK}Mostrar u ocultar nombres de localidades en el mapa STR_SMALLMAP_CENTER :{BLACK}Centrar la vista en la ubicación actual STR_SMALLMAP_INDUSTRY :{TINY_FONT}{STRING} ({NUM}) STR_SMALLMAP_LINKSTATS :{TINY_FONT}{STRING} @@ -831,14 +831,14 @@ STR_NEWS_COMPANY_LAUNCH_DESCRIPTION :{BIG_FONT}{BLAC STR_NEWS_MERGER_TAKEOVER_TITLE :{BIG_FONT}{BLACK}¡{STRING} ha sido adquirida por {STRING}! STR_PRESIDENT_NAME_MANAGER :{BLACK}{PRESIDENT_NAME}{}(Presidente) -STR_NEWS_NEW_TOWN :{BLACK}{BIG_FONT}¡{STRING} patrocina la creación del nuevo pueblo de {TOWN}! -STR_NEWS_NEW_TOWN_UNSPONSORED :{BLACK}{BIG_FONT}¡El nuevo pueblo de {TOWN} ha sido formado! +STR_NEWS_NEW_TOWN :{BLACK}{BIG_FONT}¡{STRING} patrocinó la creación de la nueva localidad de {TOWN}! +STR_NEWS_NEW_TOWN_UNSPONSORED :{BLACK}{BIG_FONT}¡La nueva localidad de {TOWN} ha sido formada! STR_NEWS_INDUSTRY_CONSTRUCTION :{BIG_FONT}{BLACK}¡Nuev{G o a} {STRING} en construcción cerca de {TOWN}! STR_NEWS_INDUSTRY_PLANTED :{BIG_FONT}{BLACK}¡Nuev{G o a} {STRING} fundad{G o a} cerca de {TOWN}! STR_NEWS_INDUSTRY_CLOSURE_GENERAL :{BIG_FONT}{BLACK}¡La industria {STRING} anuncia su inminente cierre! -STR_NEWS_INDUSTRY_CLOSURE_SUPPLY_PROBLEMS :{BIG_FONT}{BLACK}¡Problemas con suministros provocan que {STRING} anuncie su inminente cierre! +STR_NEWS_INDUSTRY_CLOSURE_SUPPLY_PROBLEMS :{BIG_FONT}{BLACK}¡La falta de abastecimiento provoca que {STRING} anuncie su inminente cierre! STR_NEWS_INDUSTRY_CLOSURE_LACK_OF_TREES :{BIG_FONT}{BLACK}¡La falta de árboles hace que {STRING} anuncie su inminente cierre! STR_NEWS_EURO_INTRODUCTION :{BIG_FONT}{BLACK}¡Unión Monetaria Europea!{}{}¡El Euro es introducido como la nueva moneda oficial de todas las transacciones! @@ -961,7 +961,7 @@ STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_LEFT :Manejar por la STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_RIGHT :Manejar por la derecha STR_GAME_OPTIONS_TOWN_NAMES_FRAME :{BLACK}Nombres de pueblos: -STR_GAME_OPTIONS_TOWN_NAMES_DROPDOWN_TOOLTIP :{BLACK}Elegir el estilo de nombres para pueblos +STR_GAME_OPTIONS_TOWN_NAMES_DROPDOWN_TOOLTIP :{BLACK}Elegir el estilo de nombres para las localidades ############ start of townname region STR_GAME_OPTIONS_TOWN_NAME_ORIGINAL_ENGLISH :Inglés @@ -1313,8 +1313,8 @@ STR_CONFIG_SETTING_PLANE_CRASHES_HELPTEXT :Probabilidad de STR_CONFIG_SETTING_PLANE_CRASHES_NONE :Ninguno* STR_CONFIG_SETTING_PLANE_CRASHES_REDUCED :Reducida STR_CONFIG_SETTING_PLANE_CRASHES_NORMAL :Normal -STR_CONFIG_SETTING_STOP_ON_TOWN_ROAD :Permitir la construcción de paradas intermedias sobre carreteras en pueblos: {STRING} -STR_CONFIG_SETTING_STOP_ON_TOWN_ROAD_HELPTEXT :Construir paradas de autobuses intermedias en carreteras que sean propiedad de los pueblos +STR_CONFIG_SETTING_STOP_ON_TOWN_ROAD :Permitir la construcción de paradas intermedias en localidades: {STRING} +STR_CONFIG_SETTING_STOP_ON_TOWN_ROAD_HELPTEXT :Permitir la construcción de paradas intermedias en carreteras propiedad de las localidades STR_CONFIG_SETTING_STOP_ON_COMPETITOR_ROAD :Permitir la construcción de paradas intermedias sobre carreteras de la competencia: {STRING} STR_CONFIG_SETTING_STOP_ON_COMPETITOR_ROAD_HELPTEXT :Construir paradas de autobuses intermedias en carreteras que sean propiedad de otras empresas STR_CONFIG_SETTING_DYNAMIC_ENGINES_EXISTING_VEHICLES :{WHITE}No se puede cambiar esta opción si ya existen vehículos @@ -1373,7 +1373,7 @@ STR_CONFIG_SETTING_INDUSTRY_DENSITY_HELPTEXT :Número de indu STR_CONFIG_SETTING_OIL_REF_EDGE_DISTANCE :Distancia máxima de industrias petrolíferas al borde del mapa: {STRING} STR_CONFIG_SETTING_OIL_REF_EDGE_DISTANCE_HELPTEXT :Distancia límite desde el borde del mapa a partir del cual se pueden construir refinerías y plataformas de petróleo. En mapas con forma de isla esto garantiza que se ubiquen cerca de la costa. En mapas de más de 256 casillas el valor se amplía. STR_CONFIG_SETTING_SNOWLINE_HEIGHT :Nivel de inicio de nieve: {STRING} -STR_CONFIG_SETTING_SNOWLINE_HEIGHT_HELPTEXT :Controlar la altura donde la nieve empieza en mapas de clima Subártico, lo cual afectará la generación de industrias y los requisitos de crecimiento de pueblos. Este valor se puede cambiar en el Editor de mapas o se calculará según la "Extensión de nieve" +STR_CONFIG_SETTING_SNOWLINE_HEIGHT_HELPTEXT :Controlar la altura al que empieza la nieve en geografía de Subártico, lo cual afectará la generación de industrias y los requisitos de crecimiento de localidades. Solo puede cambiarse en el Editor de mapas, si no, se calculará según la "Extensión de nieve" STR_CONFIG_SETTING_SNOW_COVERAGE :Extensión de nieve: {STRING} STR_CONFIG_SETTING_SNOW_COVERAGE_HELPTEXT :Controlar la cantidad aproximada de nieve al generar un mapa de geografía de Subártico, la cual afectará la generación de industrias y los requisitos de crecimiento de las localidades. La superficie casi al ras del nivel del mar nunca tiene nieve STR_CONFIG_SETTING_SNOW_COVERAGE_VALUE :{NUM}% @@ -1529,7 +1529,7 @@ STR_CONFIG_SETTING_MAX_SHIPS :Número máximo STR_CONFIG_SETTING_MAX_SHIPS_HELPTEXT :Número máximo de barcos que una empresa puede tener STR_CONFIG_SETTING_AI_BUILDS_TRAINS :Desactivar trenes para la computadora: {STRING} -STR_CONFIG_SETTING_AI_BUILDS_TRAINS_HELPTEXT :Activar esta opción para deshabilitar la construcción de trenes por jugadores no humanos +STR_CONFIG_SETTING_AI_BUILDS_TRAINS_HELPTEXT :Activar esta opción deshabilita la construcción de trenes por jugadores no humanos STR_CONFIG_SETTING_AI_BUILDS_ROAD_VEHICLES :Desactivar vehículos de carretera para la computadora: {STRING} STR_CONFIG_SETTING_AI_BUILDS_ROAD_VEHICLES_HELPTEXT :Activar esta opción deshabilita la construcción de vehículos de carretera por jugadores no humanos STR_CONFIG_SETTING_AI_BUILDS_AIRCRAFT :Desactivar aeroplanos para la computadora: {STRING} @@ -1643,8 +1643,8 @@ STR_CONFIG_SETTING_CYCLE_SIGNAL_NORMAL :Solo señales d STR_CONFIG_SETTING_CYCLE_SIGNAL_PBS :Solo señales de ruta STR_CONFIG_SETTING_CYCLE_SIGNAL_ALL :Todas -STR_CONFIG_SETTING_TOWN_LAYOUT :Diseño urbano para los nuevos pueblos: {STRING} -STR_CONFIG_SETTING_TOWN_LAYOUT_HELPTEXT :Diseño de carreteras y calles para las redes de transporte en los pueblos +STR_CONFIG_SETTING_TOWN_LAYOUT :Diseño urbano para nuevas localidades: {STRING} +STR_CONFIG_SETTING_TOWN_LAYOUT_HELPTEXT :Diseño de carreteras dentro de las localidades STR_CONFIG_SETTING_TOWN_LAYOUT_DEFAULT :Original STR_CONFIG_SETTING_TOWN_LAYOUT_BETTER_ROADS :Mejorado STR_CONFIG_SETTING_TOWN_LAYOUT_2X2_GRID :Rejilla de 2×2 @@ -1656,8 +1656,8 @@ STR_CONFIG_SETTING_ALLOW_TOWN_LEVEL_CROSSINGS :Permitir a las STR_CONFIG_SETTING_ALLOW_TOWN_LEVEL_CROSSINGS_HELPTEXT :Activar esta opción permite a las localidades construir pasos a nivel STR_CONFIG_SETTING_NOISE_LEVEL :Permitir el ruido de aeropuertos controlado por localidades: {STRING} STR_CONFIG_SETTING_NOISE_LEVEL_HELPTEXT :Al desactivarse, puede haber hasta dos aeropuertos por localidad. Al activarse, el número de aeropuertos por localidad depende de su nivel de ruido permitido, el cual depende de la población, el tamaño de cada aeropuerto y la distancia -STR_CONFIG_SETTING_TOWN_FOUNDING :Fundar pueblos: {STRING} -STR_CONFIG_SETTING_TOWN_FOUNDING_HELPTEXT :Los jugadores podrán crear nuevos pueblos durante la partida +STR_CONFIG_SETTING_TOWN_FOUNDING :Fundar localidades: {STRING} +STR_CONFIG_SETTING_TOWN_FOUNDING_HELPTEXT :Activar esta opción permite a los jugadores funda nuevas localidades durante la partida STR_CONFIG_SETTING_TOWN_FOUNDING_FORBIDDEN :Prohibido STR_CONFIG_SETTING_TOWN_FOUNDING_ALLOWED :Permitido STR_CONFIG_SETTING_TOWN_FOUNDING_ALLOWED_CUSTOM_LAYOUT :Permitido, diseño urbano personalizado @@ -1700,8 +1700,8 @@ STR_CONFIG_SETTING_ZOOM_LVL_OUT_8X :8x STR_CONFIG_SETTING_SPRITE_ZOOM_LVL_MIN :4x STR_CONFIG_SETTING_SPRITE_ZOOM_LVL_IN_2X :2x STR_CONFIG_SETTING_SPRITE_ZOOM_LVL_NORMAL :1x -STR_CONFIG_SETTING_TOWN_GROWTH :Velocidad de crecimiento de pueblos: {STRING} -STR_CONFIG_SETTING_TOWN_GROWTH_HELPTEXT :A qué velocidad se expanden los pueblos +STR_CONFIG_SETTING_TOWN_GROWTH :Velocidad de crecimiento de localidades: {STRING} +STR_CONFIG_SETTING_TOWN_GROWTH_HELPTEXT :A qué velocidad se expanden las localidades STR_CONFIG_SETTING_TOWN_GROWTH_NONE :Ninguna STR_CONFIG_SETTING_TOWN_GROWTH_SLOW :Lenta STR_CONFIG_SETTING_TOWN_GROWTH_NORMAL :Normal @@ -2002,8 +2002,11 @@ STR_FACE_TIE :Corbata: STR_FACE_EARRING :Aretes: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Cambiar corbata o aretes -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privado +############ Next lines match ServerGameType +STR_NETWORK_SERVER_VISIBILITY_LOCAL :Local STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Público +STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY :Solo con invitación +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Multijugador @@ -2037,6 +2040,7 @@ STR_NETWORK_SERVER_LIST_SERVER_VERSION :{SILVER}Versió STR_NETWORK_SERVER_LIST_SERVER_ADDRESS :{SILVER}Dirección del servidor: {WHITE}{STRING} STR_NETWORK_SERVER_LIST_START_DATE :{SILVER}Fecha de inicio: {WHITE}{DATE_SHORT} STR_NETWORK_SERVER_LIST_CURRENT_DATE :{SILVER}Fecha actual: {WHITE}{DATE_SHORT} +STR_NETWORK_SERVER_LIST_GAMESCRIPT :{SILVER}Script de juego: {WHITE}{STRING} (versión {NUM}) STR_NETWORK_SERVER_LIST_PASSWORD :{SILVER}¡Protegido por contraseña! STR_NETWORK_SERVER_LIST_SERVER_OFFLINE :{SILVER}SERVIDOR APAGADO STR_NETWORK_SERVER_LIST_SERVER_FULL :{SILVER}SERVIDOR LLENO @@ -2052,12 +2056,12 @@ STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET_TOOLTIP :{BLACK}Buscar s STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN :{BLACK}Buscar en red local STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN_TOOLTIP :{BLACK}Buscar servidores en la red local STR_NETWORK_SERVER_LIST_ADD_SERVER :{BLACK}Añadir servidor -STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Añadir el servidor a una lista que siempre será analizada en busca de partidas activas +STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Añadir el servidor a la lista. Puede ser una dirección o un código de invitación STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Iniciar servidor STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Iniciar un nuevo servidor STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Introducir nombre del jugador -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Introducir la dirección IP del servidor +STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS :{BLACK}Dirección del servidor o código de invitación # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Empezar nueva partida multijugador @@ -2144,6 +2148,10 @@ STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP :{BLACK}Modifica STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :Nombre del servidor STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY :{BLACK}Visibilidad STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP :{BLACK}Quién puede ver tu servidor en la lista pública +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE :{BLACK}Código de invitación +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP :{BLACK}Código para que otros jugadores se unan a este servidor +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE :{BLACK}Tipo de conexión +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP :{BLACK}Quién puede conectarse a tu servidor STR_NETWORK_CLIENT_LIST_PLAYER :{BLACK}Jugador STR_NETWORK_CLIENT_LIST_PLAYER_NAME :{BLACK}Nombre STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP :{BLACK}Tu nombre de jugador @@ -2162,6 +2170,12 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Este ere STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Este es el host del juego STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} cliente{P "" s}/{NUM} empresa{P "" s} +############ Begin of ConnectionType +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN :{BLACK}Local +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_ISOLATED :{RED}Los jugadores remotos no pueden conectarse +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_DIRECT :{BLACK}Pública +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Expulsar STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Bloquear acceso STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Eliminar @@ -2289,6 +2303,10 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}El servi STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Se está reiniciando el servidor...{}Espera por favor... STR_NETWORK_MESSAGE_KICKED :*** {STRING} ha sido expulsado. Razón: ({STRING}) +STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED :{WHITE}El registro del servidor falló +STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}Tu servidor no permite conexiones remotas +STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL :{WHITE}Otros jugadores no podrán conectarse a tu servidor + # Content downloading window STR_CONTENT_TITLE :{WHITE}Descarga de contenido STR_CONTENT_TYPE_CAPTION :{BLACK}Tipo @@ -2376,7 +2394,7 @@ STR_TRANSPARENT_BUILDINGS_TOOLTIP :{BLACK}Transpar STR_TRANSPARENT_BRIDGES_TOOLTIP :{BLACK}Transparencia de puentes. Ctrl+Clic para bloquear STR_TRANSPARENT_STRUCTURES_TOOLTIP :{BLACK}Transparencia de estructuras como faros o antenas. Ctrl+Clic para bloquear STR_TRANSPARENT_CATENARY_TOOLTIP :{BLACK}Transparencia de catenaria. Ctrl+Clic para bloquear -STR_TRANSPARENT_LOADING_TOOLTIP :{BLACK}Transparencia de indicadores de cargamento. Ctrl+Clic para bloquear +STR_TRANSPARENT_LOADING_TOOLTIP :{BLACK}Transparencia de indicadores de embarque. Ctrl+Clic para bloquear STR_TRANSPARENT_INVISIBLE_TOOLTIP :{BLACK}Ocultar objetos totalmente # Linkgraph legend window @@ -2471,7 +2489,7 @@ STR_BUILD_SIGNAL_ELECTRIC_EXIT_TOOLTIP :{BLACK}Señal d STR_BUILD_SIGNAL_ELECTRIC_COMBO_TOOLTIP :{BLACK}Señal combo (eléctrica){}Hace lo mismo que las señales de entrada y de salida, lo que permite construir amplios "árboles" de señales condicionales STR_BUILD_SIGNAL_ELECTRIC_PBS_TOOLTIP :{BLACK}Señal de ruta (eléctrica){}Permite a más de un tren entrar al mismo tiempo en un tramo de vía con señales, en tanto que el tren pueda reservar una ruta hasta un lugar seguro. Puede ser pasada por detrás STR_BUILD_SIGNAL_ELECTRIC_PBS_OWAY_TOOLTIP :{BLACK}Señal de ruta de un sentido (eléctrica){}Igual que una señal de ruta pero no puede ser pasada por detrás -STR_BUILD_SIGNAL_CONVERT_TOOLTIP :{BLACK}Conversión de señal{}Activar para pulsar sobre una señal existente y convertirla en el tipo y variante elegidos. Ctrl+Clic permite cambiar entre variantes de señales. Mayús+Clic muestra una estimación del precio de conversión +STR_BUILD_SIGNAL_CONVERT_TOOLTIP :{BLACK}Conversión de señal{}Activar para pulsar sobre una señal existente y convertirla en el tipo y variante elegidos. Ctrl+Clic permite cambiar entre variantes de señales. Mayús+Clic muestra un precio estimado STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_TOOLTIP :{BLACK}Distancia entre señales STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_DECREASE_TOOLTIP :{BLACK}Reducir distancia entre señales STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_INCREASE_TOOLTIP :{BLACK}Aumentar distancia entre señales @@ -2583,13 +2601,13 @@ STR_STATION_BUILD_NOISE :{BLACK}Ruido ge # Landscaping toolbar STR_LANDSCAPING_TOOLBAR :{WHITE}Modificación de terreno STR_LANDSCAPING_TOOLTIP_LOWER_A_CORNER_OF_LAND :{BLACK}Disminuir una esquina del terreno. Al arrastrar el ratón se reduce la primera esquina elegida y se nivela el resto del terreno seleccionado a dicha altura. Ctrl para seleccionar un área en diagonal. -STR_LANDSCAPING_TOOLTIP_RAISE_A_CORNER_OF_LAND :{BLACK}Elevar una esquina del terreno. Al arrastrar el ratón se eleva la primera esquina elegida y se nivela el resto del terreno seleccionado a dicha altura. Ctrl para seleccionar un área en diagonal. Mayús muestra una estimación del precio -STR_LANDSCAPING_LEVEL_LAND_TOOLTIP :{BLACK}Nivela un área de terreno a la altura de la primera esquina seleccionada. Ctrl para seleccionar un área en diagonal. Mayús muestra una estimación del precio -STR_LANDSCAPING_TOOLTIP_PURCHASE_LAND :{BLACK}Comprar terreno para usos futuros. Mayús muestra una estimación del precio +STR_LANDSCAPING_TOOLTIP_RAISE_A_CORNER_OF_LAND :{BLACK}Elevar una esquina del terreno. Al arrastrar el ratón se eleva la primera esquina elegida y se nivela el resto del terreno seleccionado a dicha altura. Ctrl para seleccionar un área en diagonal. Mayús muestra un precio estimado +STR_LANDSCAPING_LEVEL_LAND_TOOLTIP :{BLACK}Nivelar un área de terreno a la altura de la primera esquina seleccionada. Ctrl para seleccionar un área en diagonal. Mayús muestra un precio estimado +STR_LANDSCAPING_TOOLTIP_PURCHASE_LAND :{BLACK}Comprar terreno para usos futuros. Mayús muestra un precio estimado # Object construction window STR_OBJECT_BUILD_CAPTION :{WHITE}Selección de objeto -STR_OBJECT_BUILD_TOOLTIP :{BLACK}Elegir una estructura u objeto a construir. Mayús muestra una estimación del precio +STR_OBJECT_BUILD_TOOLTIP :{BLACK}Elegir una estructura u objeto a construir. Mayús muestra un precio estimado STR_OBJECT_BUILD_CLASS_TOOLTIP :{BLACK}Elegir el tipo de objeto o estructura a construir STR_OBJECT_BUILD_PREVIEW_TOOLTIP :{BLACK}Vista previa del objeto STR_OBJECT_BUILD_SIZE :{BLACK}Tamaño: {GOLD}{NUM}×{NUM} casillas @@ -2626,31 +2644,31 @@ STR_QUERY_RESET_LANDSCAPE_CAPTION :{WHITE}Restaura STR_RESET_LANDSCAPE_CONFIRMATION_TEXT :{WHITE}¿Eliminar todas las propiedades de las empresas? # Town generation window (SE) -STR_FOUND_TOWN_CAPTION :{WHITE}Creación de pueblos -STR_FOUND_TOWN_NEW_TOWN_BUTTON :{BLACK}Nuevo pueblo -STR_FOUND_TOWN_NEW_TOWN_TOOLTIP :{BLACK}Fundar un nuevo pueblo. Mayús muestra una estimación del precio -STR_FOUND_TOWN_RANDOM_TOWN_BUTTON :{BLACK}Pueblo aleatorio -STR_FOUND_TOWN_RANDOM_TOWN_TOOLTIP :{BLACK}Fundar pueblo en un lugar aleatorio -STR_FOUND_TOWN_MANY_RANDOM_TOWNS :{BLACK}Muchos pueblos aleatorios -STR_FOUND_TOWN_RANDOM_TOWNS_TOOLTIP :{BLACK}Cubrir el mapa con pueblos colocados al azar - -STR_FOUND_TOWN_NAME_TITLE :{YELLOW}Nombre del pueblo: -STR_FOUND_TOWN_NAME_EDITOR_TITLE :{BLACK}Indicar el nombre del pueblo -STR_FOUND_TOWN_NAME_EDITOR_HELP :{BLACK}Clic para indicar el nombre del pueblo +STR_FOUND_TOWN_CAPTION :{WHITE}Generación de localidades +STR_FOUND_TOWN_NEW_TOWN_BUTTON :{BLACK}Nueva localidad +STR_FOUND_TOWN_NEW_TOWN_TOOLTIP :{BLACK}Fundar una nueva localidad. Mayús muestra una estimación del precio +STR_FOUND_TOWN_RANDOM_TOWN_BUTTON :{BLACK}Localidad aleatoria +STR_FOUND_TOWN_RANDOM_TOWN_TOOLTIP :{BLACK}Fundar la localidad en un lugar aleatorio +STR_FOUND_TOWN_MANY_RANDOM_TOWNS :{BLACK}Muchas localidades aleatorias +STR_FOUND_TOWN_RANDOM_TOWNS_TOOLTIP :{BLACK}Cubrir el mapa con localidades colocadas al azar + +STR_FOUND_TOWN_NAME_TITLE :{YELLOW}Nombre de la localidad: +STR_FOUND_TOWN_NAME_EDITOR_TITLE :{BLACK}Ingresar el nombre de la localidad +STR_FOUND_TOWN_NAME_EDITOR_HELP :{BLACK}Clic para ingresar el nombre de la localidad STR_FOUND_TOWN_NAME_RANDOM_BUTTON :{BLACK}Nombre aleatorio STR_FOUND_TOWN_NAME_RANDOM_TOOLTIP :{BLACK}Generar nuevo nombre al azar -STR_FOUND_TOWN_INITIAL_SIZE_TITLE :{YELLOW}Tamaño del pueblo: +STR_FOUND_TOWN_INITIAL_SIZE_TITLE :{YELLOW}Tamaño de la localidad: STR_FOUND_TOWN_INITIAL_SIZE_SMALL_BUTTON :{BLACK}Pequeño STR_FOUND_TOWN_INITIAL_SIZE_MEDIUM_BUTTON :{BLACK}Mediano STR_FOUND_TOWN_INITIAL_SIZE_LARGE_BUTTON :{BLACK}Grande STR_FOUND_TOWN_SIZE_RANDOM :{BLACK}Aleatorio -STR_FOUND_TOWN_INITIAL_SIZE_TOOLTIP :{BLACK}Elegir el tamaño del pueblo +STR_FOUND_TOWN_INITIAL_SIZE_TOOLTIP :{BLACK}Elegir el tamaño de la localidad STR_FOUND_TOWN_CITY :{BLACK}Ciudad -STR_FOUND_TOWN_CITY_TOOLTIP :{BLACK}Las ciudades crecen más rápido que los pueblos{}Según la configuración del juego, al ser creadas tendrán mayor población +STR_FOUND_TOWN_CITY_TOOLTIP :{BLACK}Las ciudades crecen más rápido que las localidades{}Según la configuración del juego, al ser creadas son más grandes STR_FOUND_TOWN_ROAD_LAYOUT :{YELLOW}Diseño urbano: -STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT :{BLACK}Elegir el diseño de carreteras y cuadras para este pueblo +STR_FOUND_TOWN_SELECT_TOWN_ROAD_LAYOUT :{BLACK}Elegir el diseño urbano de esta localidad STR_FOUND_TOWN_SELECT_LAYOUT_ORIGINAL :{BLACK}Original STR_FOUND_TOWN_SELECT_LAYOUT_BETTER_ROADS :{BLACK}Mejorado STR_FOUND_TOWN_SELECT_LAYOUT_2X2_GRID :{BLACK}Rejilla de 2×2 @@ -2912,7 +2930,7 @@ STR_MAPGEN_WORLD_GENERATION_CAPTION :{WHITE}Generaci STR_MAPGEN_MAPSIZE :{BLACK}Tamaño del mapa: STR_MAPGEN_MAPSIZE_TOOLTIP :{BLACK}Elegir el tamaño del mapa en número de casillas. El número real de casillas disponibles en el mapa será ligeramente inferior STR_MAPGEN_BY :{BLACK}× -STR_MAPGEN_NUMBER_OF_TOWNS :{BLACK}Núm. de pueblos: +STR_MAPGEN_NUMBER_OF_TOWNS :{BLACK}Núm. de localidades: STR_MAPGEN_DATE :{BLACK}Fecha: STR_MAPGEN_NUMBER_OF_INDUSTRIES :{BLACK}Núm. de industrias: STR_MAPGEN_HEIGHTMAP_HEIGHT :{BLACK}Cima más alta: @@ -3183,7 +3201,7 @@ STR_EDIT_SIGN_PREVIOUS_SIGN_TOOLTIP :{BLACK}Ir al ca STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Indicar un nombre para el cartel # Town directory window -STR_TOWN_DIRECTORY_CAPTION :{WHITE}Pueblos +STR_TOWN_DIRECTORY_CAPTION :{WHITE}Localidades STR_TOWN_DIRECTORY_NONE :{ORANGE}- Ninguno - STR_TOWN_DIRECTORY_TOWN :{ORANGE}{TOWN}{BLACK} ({COMMA}) STR_TOWN_DIRECTORY_CITY :{ORANGE}{TOWN}{YELLOW} (ciudad){BLACK} ({COMMA}) @@ -3201,19 +3219,19 @@ STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED_WINTER :{ORANGE}{STRING STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_DELIVERED_GENERAL :{ORANGE}{STRING}{GREEN} entregado STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_REQUIRED :{ORANGE}{CARGO_TINY}/{CARGO_LONG}{RED} (todavía requerido) STR_TOWN_VIEW_CARGO_FOR_TOWNGROWTH_DELIVERED :{ORANGE}{CARGO_TINY}/{CARGO_LONG}{GREEN} (entregado) -STR_TOWN_VIEW_TOWN_GROWS_EVERY :{BLACK}El pueblo crece cada {ORANGE}{COMMA}{BLACK}{NBSP}día{P "" s} -STR_TOWN_VIEW_TOWN_GROWS_EVERY_FUNDED :{BLACK}El pueblo crece cada {ORANGE}{COMMA}{BLACK}{NBSP}días{P "" s} (obra negra) -STR_TOWN_VIEW_TOWN_GROW_STOPPED :{BLACK}El pueblo {RED}no{BLACK} está creciendo -STR_TOWN_VIEW_NOISE_IN_TOWN :{BLACK}Nivel de ruido en el pueblo: {ORANGE}{COMMA}{BLACK} Máx.: {ORANGE}{COMMA} +STR_TOWN_VIEW_TOWN_GROWS_EVERY :{BLACK}La localidad crece cada {ORANGE}{COMMA}{BLACK}{NBSP}día{P "" s} +STR_TOWN_VIEW_TOWN_GROWS_EVERY_FUNDED :{BLACK}La localidad crece cada {ORANGE}{COMMA}{BLACK}{NBSP}días{P "" s} (obra negra) +STR_TOWN_VIEW_TOWN_GROW_STOPPED :{BLACK}La localidad {RED}no{BLACK} está creciendo +STR_TOWN_VIEW_NOISE_IN_TOWN :{BLACK}Nivel de ruido en la localidad: {ORANGE}{COMMA}{BLACK} máx.: {ORANGE}{COMMA} STR_TOWN_VIEW_CENTER_TOOLTIP :{BLACK}Centrar la vista en el pueblo. Ctrl+Clic abre una vista aparte STR_TOWN_VIEW_LOCAL_AUTHORITY_BUTTON :{BLACK}Ayuntamiento STR_TOWN_VIEW_LOCAL_AUTHORITY_TOOLTIP :{BLACK}Mostrar información sobre el ayuntamiento local -STR_TOWN_VIEW_RENAME_TOOLTIP :{BLACK}Cambiar nombre del pueblo +STR_TOWN_VIEW_RENAME_TOOLTIP :{BLACK}Cambiar nombre de la localidad STR_TOWN_VIEW_EXPAND_BUTTON :{BLACK}Aumentar -STR_TOWN_VIEW_EXPAND_TOOLTIP :{BLACK}Aumentar la población del pueblo +STR_TOWN_VIEW_EXPAND_TOOLTIP :{BLACK}Aumentar la población de la localidad STR_TOWN_VIEW_DELETE_BUTTON :{BLACK}Eliminar -STR_TOWN_VIEW_DELETE_TOOLTIP :{BLACK}Eliminar este pueblo completamente +STR_TOWN_VIEW_DELETE_TOOLTIP :{BLACK}Eliminar esta localidad completamente STR_TOWN_VIEW_RENAME_TOWN_BUTTON :Cambiar nombre @@ -3224,7 +3242,7 @@ STR_LOCAL_AUTHORITY_ZONE_TOOLTIP :{BLACK}Mostrar STR_LOCAL_AUTHORITY_COMPANY_RATINGS :{BLACK}Evaluación de empresas de transporte: STR_LOCAL_AUTHORITY_COMPANY_RATING :{YELLOW}{COMPANY} {COMPANY_NUM}: {ORANGE}{STRING} STR_LOCAL_AUTHORITY_ACTIONS_TITLE :{BLACK}Acciones disponibles: -STR_LOCAL_AUTHORITY_ACTIONS_TOOLTIP :{BLACK}Lista de acciones que pueden llevarse a cabo en este pueblo. Clic en un elemento de la lista para obtener más detalles +STR_LOCAL_AUTHORITY_ACTIONS_TOOLTIP :{BLACK}Acciones que pueden realizarse en esta localidad. Clic muestra más detalles STR_LOCAL_AUTHORITY_DO_IT_BUTTON :{BLACK}Realizar STR_LOCAL_AUTHORITY_DO_IT_TOOLTIP :{BLACK}Llevar a cabo la acción elegida de la lista @@ -3237,9 +3255,9 @@ STR_LOCAL_AUTHORITY_ACTION_NEW_BUILDINGS :Pagar la constr STR_LOCAL_AUTHORITY_ACTION_EXCLUSIVE_TRANSPORT :Comprar los derechos exclusivos de transporte STR_LOCAL_AUTHORITY_ACTION_BRIBE :Sobornar al ayuntamiento -STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_SMALL_ADVERTISING :{YELLOW}Iniciar una pequeña campaña publicitaria local para atraer más pasajeros y cargamento a tus servicios de transporte.{}Otorga un aumento temporal a la evaluación de estaciones en un radio pequeño alrededor del centro del pueblo.{}Costo: {CURRENCY_LONG} -STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_MEDIUM_ADVERTISING :{YELLOW}Iniciar una campaña publicitaria local mediana para atraer más pasajeros y cargamento a tus servicios de transporte.{}Otorga un aumento temporal a la evaluación de estaciones en un radio mediano alrededor del centro del pueblo.{}Costo: {CURRENCY_LONG} -STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_LARGE_ADVERTISING :{YELLOW}Iniciar una gran campaña publicitaria local para atraer más pasajeros y cargamento a tus servicios de transporte.{}Otorga un aumento temporal a la evaluación de estaciones en un radio grande alrededor del centro del pueblo.{}Costo: {CURRENCY_LONG} +STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_SMALL_ADVERTISING :{YELLOW}Iniciar una pequeña campaña publicitaria local para atraer más pasajeros y carga a tus servicios de transporte.{}Otorga un aumento temporal a la evaluación de estaciones en un radio pequeño alrededor del centro de la localidad.{}Costo: {CURRENCY_LONG} +STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_MEDIUM_ADVERTISING :{YELLOW}Iniciar una campaña publicitaria local mediana para atraer más pasajeros y carga a tus servicios de transporte.{}Otorga un aumento temporal a la evaluación de estaciones en un radio mediano alrededor del centro de la localidad.{}Costo: {CURRENCY_LONG} +STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_LARGE_ADVERTISING :{YELLOW}Iniciar una gran campaña publicitaria local para atraer más pasajeros y carga a tus servicios de transporte.{}Otorga un aumento temporal a la evaluación de estaciones en un radio grande alrededor del centro de la localidad.{}Costo: {CURRENCY_LONG} STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_ROAD_RECONSTRUCTION :{YELLOW}Pagar la reconstrucción de las carreteras locales.{}Provoca considerables complicaciones al tráfico hasta por 6 meses.{}Costo: {CURRENCY_LONG} STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_STATUE_OF_COMPANY :{YELLOW}Construir una estatua en honor a tu empresa.{}Otorga un aumento permanente a la evaluación estaciones en este pueblo.{}Costo: {CURRENCY_LONG} STR_LOCAL_AUTHORITY_ACTION_TOOLTIP_NEW_BUILDINGS :{YELLOW}Pagar la construcción de nuevos edificios comerciales en el pueblo.{}Otorga un aumento temporal a su crecimiento.{}Costo: {CURRENCY_LONG} @@ -3330,12 +3348,12 @@ STR_STATION_VIEW_ACCEPTS_BUTTON :{BLACK}Acepta STR_STATION_VIEW_ACCEPTS_TOOLTIP :{BLACK}Lista de carga aceptada STR_STATION_VIEW_ACCEPTS_CARGO :{BLACK}Acepta: {WHITE}{CARGO_LIST} -STR_STATION_VIEW_EXCLUSIVE_RIGHTS_SELF :{BLACK}Esta estación tiene los derechos exclusivos de transporte en este pueblo. -STR_STATION_VIEW_EXCLUSIVE_RIGHTS_COMPANY :{YELLOW}{COMPANY}{BLACK} compró los derechos exclusivos de transporte en este pueblo. +STR_STATION_VIEW_EXCLUSIVE_RIGHTS_SELF :{BLACK}Esta estación tiene los derechos exclusivos de transporte en esta localidad. +STR_STATION_VIEW_EXCLUSIVE_RIGHTS_COMPANY :{YELLOW}{COMPANY}{BLACK} compró los derechos exclusivos de transporte en esta localidad. STR_STATION_VIEW_RATINGS_BUTTON :{BLACK}Evaluación STR_STATION_VIEW_RATINGS_TOOLTIP :{BLACK}Ver evaluación de la estación -STR_STATION_VIEW_SUPPLY_RATINGS_TITLE :{BLACK}Suministro mensual y evaluación local: +STR_STATION_VIEW_SUPPLY_RATINGS_TITLE :{BLACK}Abastecimiento mensual y evaluación local: STR_STATION_VIEW_CARGO_SUPPLY_RATING :{WHITE}{STRING}: {YELLOW}{COMMA}/{STRING} ({COMMA}%) STR_STATION_VIEW_GROUP :{BLACK}Agrupar por @@ -3451,7 +3469,7 @@ STR_COMPANY_VIEW_BUILD_HQ_TOOLTIP :{BLACK}Construi STR_COMPANY_VIEW_VIEW_HQ_BUTTON :{BLACK}Ver sede STR_COMPANY_VIEW_VIEW_HQ_TOOLTIP :{BLACK}Ver edificio de las oficinas centrales de la empresa STR_COMPANY_VIEW_RELOCATE_HQ :{BLACK}Reubicar sede -STR_COMPANY_VIEW_RELOCATE_COMPANY_HEADQUARTERS :{BLACK}Reubicar la sede de la empresa a cualquier otro lugar con el costo del 1% del valor total de la empresa. Mayús+Clic muestra una estimación del precio sin mover la sede +STR_COMPANY_VIEW_RELOCATE_COMPANY_HEADQUARTERS :{BLACK}Reubicar la sede de la empresa pagando el 1% del valor total de la empresa. Mayús+Clic muestra un precio estimado sin reubicar la sede STR_COMPANY_VIEW_INFRASTRUCTURE_BUTTON :{BLACK}Detalles STR_COMPANY_VIEW_INFRASTRUCTURE_TOOLTIP :{BLACK}Ver informe detallado de infraestructura STR_COMPANY_VIEW_GIVE_MONEY_BUTTON :{BLACK}Transferir capital @@ -3468,8 +3486,8 @@ STR_COMPANY_VIEW_PRESIDENT_NAME_TOOLTIP :{BLACK}Cambiar STR_COMPANY_VIEW_BUY_SHARE_BUTTON :{BLACK}Comprar un 25% de las acciones STR_COMPANY_VIEW_SELL_SHARE_BUTTON :{BLACK}Vender 25% de las acciones -STR_COMPANY_VIEW_BUY_SHARE_TOOLTIP :{BLACK}Comprar 25% de las acciones de esta empresa. Mayús+Clic muestra una estimación del precio sin comprar ninguna acción -STR_COMPANY_VIEW_SELL_SHARE_TOOLTIP :{BLACK}Vender 25% de las acciones de esta empresa. Mayús+Clic muestra una estimación del beneficio sin vender ninguna acción +STR_COMPANY_VIEW_BUY_SHARE_TOOLTIP :{BLACK}Comprar 25% de las acciones de esta empresa. Mayús+Clic muestra un precio estimado sin comprar nada +STR_COMPANY_VIEW_SELL_SHARE_TOOLTIP :{BLACK}Vender 25% de las acciones de esta empresa. Mayús+Clic muestra el ingreso estimado sin vender ninguna acción STR_COMPANY_VIEW_COMPANY_NAME_QUERY_CAPTION :Nombre de la empresa STR_COMPANY_VIEW_PRESIDENT_S_NAME_QUERY_CAPTION :Nombre del presidente @@ -3649,15 +3667,15 @@ STR_BUY_VEHICLE_ROAD_VEHICLE_BUY_REFIT_VEHICLE_BUTTON :{BLACK}Comprar STR_BUY_VEHICLE_SHIP_BUY_REFIT_VEHICLE_BUTTON :{BLACK}Comprar y reformar barco STR_BUY_VEHICLE_AIRCRAFT_BUY_REFIT_VEHICLE_BUTTON :{BLACK}Comprar y reformar aeronave -STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP :{BLACK}Comprar el tren elegido. Mayús+Clic muestra una estimación del precio sin realizar la compra -STR_BUY_VEHICLE_ROAD_VEHICLE_BUY_VEHICLE_TOOLTIP :{BLACK}Comprar el vehículo de carretera elegido. Mayús+Clic muestra una estimación del precio sin realizar la compra -STR_BUY_VEHICLE_SHIP_BUY_VEHICLE_TOOLTIP :{BLACK}Comprar el barco elegido. Mayús+Clic muestra una estimación del precio sin realizar la compra -STR_BUY_VEHICLE_AIRCRAFT_BUY_VEHICLE_TOOLTIP :{BLACK}Comprar la aeronave elegida. Mayús+Clic muestra una estimación del precio sin realizar la compra +STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP :{BLACK}Comprar el tren elegido. Mayús+Clic muestra un precio estimado sin comprar nada +STR_BUY_VEHICLE_ROAD_VEHICLE_BUY_VEHICLE_TOOLTIP :{BLACK}Comprar el vehículo de carretera elegido. Mayús+Clic muestra un precio estimado sin comprar nada +STR_BUY_VEHICLE_SHIP_BUY_VEHICLE_TOOLTIP :{BLACK}Comprar el barco elegido. Mayús+Clic muestra un precio estimado sin comprar nada +STR_BUY_VEHICLE_AIRCRAFT_BUY_VEHICLE_TOOLTIP :{BLACK}Comprar la aeronave elegida. Mayús+Clic muestra un precio estimado sin comprar nada -STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Comprar y reformar tren seleccionado. Mayús+Clic muestra una estimación del precio sin realizar la compra -STR_BUY_VEHICLE_ROAD_VEHICLE_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Comprar y reformar vehículo seleccionado. Mayús+Clic muestra una estimación del precio sin realizar la compra -STR_BUY_VEHICLE_SHIP_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Comprar y reformar barco seleccionado. Mayús+Clic muestra una estimación del precio sin realizar la compra -STR_BUY_VEHICLE_AIRCRAFT_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Comprar y reformar areonave seleccionada. Mayús+Clic muestra una estimación del precio sin realizar la compra +STR_BUY_VEHICLE_TRAIN_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Comprar y reformar el tren elegido. Mayús+Clic muestra un precio estimado sin comprar nada +STR_BUY_VEHICLE_ROAD_VEHICLE_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Comprar y reformar el vehículo de carretera elegido. Mayús+Clic muestra un precio estimado sin comprar nada +STR_BUY_VEHICLE_SHIP_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Comprar y reformar el barco elegido. Mayús+Clic muestra un precio estimado sin comprar nada +STR_BUY_VEHICLE_AIRCRAFT_BUY_REFIT_VEHICLE_TOOLTIP :{BLACK}Comprar y reformar la areonave seleccionada. Mayús+Clic muestra un precio estimado sin realizar la compra STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON :{BLACK}Cambiar nombre STR_BUY_VEHICLE_ROAD_VEHICLE_RENAME_BUTTON :{BLACK}Cambiar nombre @@ -3737,10 +3755,10 @@ STR_DEPOT_CLONE_ROAD_VEHICLE :{BLACK}Clonar STR_DEPOT_CLONE_SHIP :{BLACK}Clonar STR_DEPOT_CLONE_AIRCRAFT :{BLACK}Clonar -STR_DEPOT_CLONE_TRAIN_DEPOT_INFO :{BLACK}Esto comprará una copia idéntica del tren, incluyendo todos sus vagones y carros. Clic en este botón y después en el tren a copiar. Ctrl+Clic para compartir las órdenes. Mayús+Clic muestra una estimación del precio sin realizar la compra -STR_DEPOT_CLONE_ROAD_VEHICLE_DEPOT_INFO :{BLACK}Esto comprará una copia idéntica del vehículo de carretera. Clic en este botón y después en el vehículo a copiar. Ctrl+Clic para compartir las órdenes. Mayús+Clic muestra una estimación del precio sin realizar la compra -STR_DEPOT_CLONE_SHIP_DEPOT_INFO :{BLACK}Esto comprará una copia idéntica del barco. Clic en este botón y después en el barco a copiar. Ctrl+Clic para compartir las órdenes. Mayús+Clic muestra una estimación del precio sin realizar la compra -STR_DEPOT_CLONE_AIRCRAFT_INFO_HANGAR_WINDOW :{BLACK}Esto comprará una copia idéntica de la aeronave. Clic en este botón y después en la aeronave a copiar. Ctrl+Clic para compartir las órdenes. Mayús+Clic muestra una estimación del precio sin realizar la compra +STR_DEPOT_CLONE_TRAIN_DEPOT_INFO :{BLACK}Esto comprará una copia del tren y sus vagones. Clic en este botón y después en el tren a copiar. Ctrl+Clic para compartir las órdenes. Mayús+Clic muestra un precio estimado sin comprar nada +STR_DEPOT_CLONE_ROAD_VEHICLE_DEPOT_INFO :{BLACK}Esto comprará una copia del vehículo de carretera. Clic en este botón y después en el vehículo a copiar. Ctrl+Clic para compartir las órdenes. Mayús+Clic muestra un precio estimado sin comprar nada +STR_DEPOT_CLONE_SHIP_DEPOT_INFO :{BLACK}Esto comprará una copia del barco. Clic en este botón y después en el barco a copiar. Ctrl+Clic para compartir las órdenes. Mayús+Clic muestra un precio estimado sin comprar nada +STR_DEPOT_CLONE_AIRCRAFT_INFO_HANGAR_WINDOW :{BLACK}Esto comprará una copia de la aeronave. Clic en este botón y después en la aeronave a copiar. Ctrl+Clic para compartir las órdenes. Mayús+Clic muestra un precio estimado sin comprar nada STR_DEPOT_TRAIN_LOCATION_TOOLTIP :{BLACK}Centrar la vista en el depósito de trenes. Ctrl+Clic abre una vista aparte STR_DEPOT_ROAD_VEHICLE_LOCATION_TOOLTIP :{BLACK}Centrar la vista en el depósito de vehículos. Ctrl+Clic abre una vista aparte @@ -4354,8 +4372,8 @@ STR_GAME_SAVELOAD_NOT_AVAILABLE : STR_WARNING_LOADGAME_REMOVED_TRAMS :{WHITE}La partida fue guardada en una versión que no admite tranvías. Todos los tranvías serán eliminados # Map generation messages -STR_ERROR_COULD_NOT_CREATE_TOWN :{WHITE}Generación de mapa abortada...{}... no hay ubicaciones apropiadas para colocar pueblos -STR_ERROR_NO_TOWN_IN_SCENARIO :{WHITE}... no hay pueblos en el mapa +STR_ERROR_COULD_NOT_CREATE_TOWN :{WHITE}Generación de mapa abortada...{}... no hay ubicaciones apropiadas para localidades +STR_ERROR_NO_TOWN_IN_SCENARIO :{WHITE}... no hay localidades en el mapa STR_ERROR_PNGMAP :{WHITE}No se puede cargar mapa desde PNG... STR_ERROR_PNGMAP_FILE_NOT_FOUND :{WHITE}... archivo no encontrado @@ -4407,7 +4425,7 @@ STR_ERROR_NOT_ALLOWED_WHILE_PAUSED :{WHITE}No permi # Local authority errors STR_ERROR_LOCAL_AUTHORITY_REFUSES_TO_ALLOW_THIS :{WHITE}El ayuntamiento de {TOWN} se opone a esta acción -STR_ERROR_LOCAL_AUTHORITY_REFUSES_AIRPORT :{WHITE}El ayuntamiento de {TOWN} se opone a la construcción de otro aeropuerto en este pueblo +STR_ERROR_LOCAL_AUTHORITY_REFUSES_AIRPORT :{WHITE}El ayuntamiento de {TOWN} se opone a la construcción de otro aeropuerto en esta localidad STR_ERROR_LOCAL_AUTHORITY_REFUSES_NOISE :{WHITE}El ayuntamiento de {TOWN} se opone a otorgar permiso para la construcción del aeropuerto debido a cuestiones de ruido STR_ERROR_BRIBE_FAILED :{WHITE}El intento de soborno ha sido descubierto por un investigador de la zona @@ -4439,18 +4457,18 @@ STR_ERROR_CAN_T_SELL_25_SHARE_IN :{WHITE}No se pu STR_ERROR_PROTECTED :{WHITE}Esta empresa es muy reciente para comerciar con acciones... # Town related errors -STR_ERROR_CAN_T_GENERATE_TOWN :{WHITE}No se puede crear ningún pueblo -STR_ERROR_CAN_T_RENAME_TOWN :{WHITE}No se puede cambiar nombre del pueblo... -STR_ERROR_CAN_T_FOUND_TOWN_HERE :{WHITE}No se puede crear pueblo aquí... -STR_ERROR_CAN_T_EXPAND_TOWN :{WHITE}No se puede expandir el pueblo... +STR_ERROR_CAN_T_GENERATE_TOWN :{WHITE}No se puede crear ninguna localidad +STR_ERROR_CAN_T_RENAME_TOWN :{WHITE}No se puede cambiar nombre de la localidad... +STR_ERROR_CAN_T_FOUND_TOWN_HERE :{WHITE}No se puede crear localidad aquí... +STR_ERROR_CAN_T_EXPAND_TOWN :{WHITE}No se puede expandir la localidad... STR_ERROR_TOO_CLOSE_TO_EDGE_OF_MAP_SUB :{WHITE}... demasiado cerca del borde del mapa -STR_ERROR_TOO_CLOSE_TO_ANOTHER_TOWN :{WHITE}... demasiado cerca de otro pueblo -STR_ERROR_TOO_MANY_TOWNS :{WHITE}... demasiados pueblos +STR_ERROR_TOO_CLOSE_TO_ANOTHER_TOWN :{WHITE}... demasiado cerca de otra localidad +STR_ERROR_TOO_MANY_TOWNS :{WHITE}... demasiadas localidades STR_ERROR_NO_SPACE_FOR_TOWN :{WHITE}... ya no hay espacio en el mapa -STR_ERROR_TOWN_EXPAND_WARN_NO_ROADS :{WHITE}El pueblo no construirá carreteras. La función de construcción de carreteras puede activarse en Configuración->Ambiente->Pueblos +STR_ERROR_TOWN_EXPAND_WARN_NO_ROADS :{WHITE}La localidad no construirá carreteras. Se puede activar esta función en Configuración->Ambiente->Localidades STR_ERROR_ROAD_WORKS_IN_PROGRESS :{WHITE}Obras de carretera en progreso -STR_ERROR_TOWN_CAN_T_DELETE :{WHITE}No se puede eliminar este pueblo...{}Aún tiene estaciones o depósitos vinculados, o una casilla en su jurisdicción no se puede quitar -STR_ERROR_STATUE_NO_SUITABLE_PLACE :{WHITE}... no hay ningún lugar apto para una estatua en el centro de este pueblo +STR_ERROR_TOWN_CAN_T_DELETE :{WHITE}No se puede eliminar esta localidad...{}Aún tiene una estación o depósito, o una de sus casillas no se puede quitar +STR_ERROR_STATUE_NO_SUITABLE_PLACE :{WHITE}... no hay lugar apto para una estatua en el centro de esta localidad # Industry related errors STR_ERROR_TOO_MANY_INDUSTRIES :{WHITE}... demasiadas industrias @@ -4458,13 +4476,13 @@ STR_ERROR_CAN_T_GENERATE_INDUSTRIES :{WHITE}No se pu STR_ERROR_CAN_T_BUILD_HERE :{WHITE}No se puede construir {STRING} aquí... STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY :{WHITE}No se puede construir este tipo de industria aquí... STR_ERROR_INDUSTRY_TOO_CLOSE :{WHITE}... demasiado cerca de otra industria -STR_ERROR_MUST_FOUND_TOWN_FIRST :{WHITE}... primero se debe crear al menos un pueblo -STR_ERROR_ONLY_ONE_ALLOWED_PER_TOWN :{WHITE}... solo se permite una por pueblo -STR_ERROR_CAN_ONLY_BE_BUILT_IN_TOWNS_WITH_POPULATION_OF_1200 :{WHITE}... solo se puede construir en pueblos de al menos 1,200 habitantes +STR_ERROR_MUST_FOUND_TOWN_FIRST :{WHITE}... primero se debe crear al menos una localidad +STR_ERROR_ONLY_ONE_ALLOWED_PER_TOWN :{WHITE}... solo se permite una por localidad +STR_ERROR_CAN_ONLY_BE_BUILT_IN_TOWNS_WITH_POPULATION_OF_1200 :{WHITE}... solo se puede construir en localidades de al menos 1,200 habitantes STR_ERROR_CAN_ONLY_BE_BUILT_IN_RAINFOREST :{WHITE}... solo se puede construir en áreas de selva STR_ERROR_CAN_ONLY_BE_BUILT_IN_DESERT :{WHITE}... solo se puede construir en áreas desérticas -STR_ERROR_CAN_ONLY_BE_BUILT_IN_TOWNS :{WHITE}... solo se puede construir en pueblos (reemplazando casas) -STR_ERROR_CAN_ONLY_BE_BUILT_NEAR_TOWN_CENTER :{WHITE}... solo se puede construir cerca del centro de un pueblo +STR_ERROR_CAN_ONLY_BE_BUILT_IN_TOWNS :{WHITE}... solo se puede construir en localidades (reemplazando casas) +STR_ERROR_CAN_ONLY_BE_BUILT_NEAR_TOWN_CENTER :{WHITE}... solo se puede construir cerca del centro de una localidad STR_ERROR_CAN_ONLY_BE_BUILT_IN_LOW_AREAS :{WHITE}... solo se puede construir en zonas bajas STR_ERROR_CAN_ONLY_BE_POSITIONED :{WHITE}... solo se puede situar cerca de los bordes del mapa STR_ERROR_FOREST_CAN_ONLY_BE_PLANTED :{WHITE}... los bosques solo se pueden plantar sobre la nieve @@ -4492,7 +4510,7 @@ STR_ERROR_TOO_MANY_TRUCK_STOPS :{WHITE}Demasiad STR_ERROR_TOO_CLOSE_TO_ANOTHER_DOCK :{WHITE}Demasiado cerca de otro muelle STR_ERROR_TOO_CLOSE_TO_ANOTHER_AIRPORT :{WHITE}Demasiado cerca de otro aeropuerto STR_ERROR_CAN_T_RENAME_STATION :{WHITE}No se puede cambiar nombre de la estación... -STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... esta carretera es propiedad de un pueblo +STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD :{WHITE}... esta carretera es propiedad de una localidad STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... carretera en el sentido incorrecto STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... las estaciones y paradas intermedias no pueden ponerse sobre esquinas STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... las estaciones y paradas intermedias no pueden ponerse sobre intersecciones diff --git a/src/lang/swedish.txt b/src/lang/swedish.txt index 53de27445b..5373b991b8 100644 --- a/src/lang/swedish.txt +++ b/src/lang/swedish.txt @@ -2001,8 +2001,11 @@ STR_FACE_TIE :Slips: STR_FACE_EARRING :Örhänge: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Ändra slips eller örhänge -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Privat +############ Next lines match ServerGameType +STR_NETWORK_SERVER_VISIBILITY_LOCAL :Lokal STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Offentlig +STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY :Endast inbjudna +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Flera spelare @@ -2051,12 +2054,12 @@ STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET_TOOLTIP :{BLACK}Sök onl STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN :{BLACK}Sök LAN STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN_TOOLTIP :{BLACK}Sök i lokalt nätverk för servrar STR_NETWORK_SERVER_LIST_ADD_SERVER :{BLACK}Lägg till server -STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Lägg till en server till listan som alltid kommer kontrolleras för aktiva spel +STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP :{BLACK}Lägger till en server i listan. Denna kan antingen vara en serveradress eller en inbjudningskod STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Starta server STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Starta en server för andra att ansluta till STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Mata in ditt namn -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}IP-adressen till servern +STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS :{BLACK}Ange serveradress eller inbjudningskod # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Starta ett nytt spel i flerspelarläge @@ -2143,6 +2146,10 @@ STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP :{BLACK}Ändra d STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :Serverns namn STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY :{BLACK}Synlighet STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP :{BLACK}Huruvida andra människor kan se din server i den offentliga listan +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE :{BLACK}Inbjudningskod +STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP :{BLACK}Inbjudningskod som andra spelare kan använda för att ansluta till denna server +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE :{BLACK}Anslutningstyp +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP :{BLACK}Huruvida, och i så fall hur, din server kan nås av andra STR_NETWORK_CLIENT_LIST_PLAYER :{BLACK}Spelare STR_NETWORK_CLIENT_LIST_PLAYER_NAME :{BLACK}Namn STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP :{BLACK}Ditt spelarnamn @@ -2161,6 +2168,12 @@ STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Det här STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Det här är spelets värd STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} klient{P "" er} / {NUM} företag +############ Begin of ConnectionType +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN :{BLACK}Lokal +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_ISOLATED :{RED}Fjärran belägna spelare kan inte ansluta +STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_DIRECT :{BLACK}Offentlig +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Kasta ut STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Bannlys STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Ta bort @@ -2288,6 +2301,10 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Servern STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Servern startar om...{}Var vänlig vänta... STR_NETWORK_MESSAGE_KICKED :*** {STRING} kastades ut. Orsak: ({STRING}) +STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED :{WHITE}Serverregistrering misslyckades +STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}Din server tillåter inte fjärranslutningar +STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL :{WHITE}Andra spelare kommer ej ha möjlighet att ansluta till din server + # Content downloading window STR_CONTENT_TITLE :{WHITE}Nedladdning av innehåll STR_CONTENT_TYPE_CAPTION :{BLACK}Typ diff --git a/src/lang/tamil.txt b/src/lang/tamil.txt index 7e866edc01..6a9727461f 100644 --- a/src/lang/tamil.txt +++ b/src/lang/tamil.txt @@ -1736,6 +1736,8 @@ STR_FACE_TIE :Tie: STR_FACE_EARRING :கம்மல்: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Tie அல்லது காதணியை மாற்றவும் +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}பல்வீரர் @@ -1857,6 +1859,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :கவனி # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :சர்வர் @@ -1965,6 +1970,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}சர STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}சர்வர் மீண்டும் தொடங்குகிறது...{}சற்று பொறுக்கவும்... STR_NETWORK_MESSAGE_KICKED :*** {STRING} வெளியேற்றப்பட்டார். காரணம்: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}கோப்புகள் பதிவிறக்கம் செய்யப்படுகின்றன STR_CONTENT_TYPE_CAPTION :{BLACK}வகை diff --git a/src/lang/thai.txt b/src/lang/thai.txt index 9bb7b2424c..c52223f1e8 100644 --- a/src/lang/thai.txt +++ b/src/lang/thai.txt @@ -1820,6 +1820,8 @@ STR_FACE_TIE :เนคไท STR_FACE_EARRING :ต่างหู: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}เปลี่ยนเนคไทหรือต่างหู +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}เล่นหลายคน @@ -1869,7 +1871,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}เร STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}เริ่มเซิร์ฟเวอร์ใหม่ของคุณ STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}ป้อนชื่อของคุณ -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}ป้อนที่อยู่ของโฮสต์ # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}เริ่มเกมผู้เล่นหลายคนใหม่ @@ -1947,6 +1948,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :ผู้ชม # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :เซิฟเวอร์ @@ -2055,6 +2059,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}เซิฟเวอร์ปืดเซสซั่นนี้ STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}เซิฟเวอร์กำลังทำการเริ่มต้นใหม่...{}กรุณารอซักครู่... + # Content downloading window STR_CONTENT_TITLE :{WHITE}กำลังโหลดเนื้อหา STR_CONTENT_TYPE_CAPTION :{BLACK}ประเภท diff --git a/src/lang/traditional_chinese.txt b/src/lang/traditional_chinese.txt index 9ac36d84bd..57c989f2be 100644 --- a/src/lang/traditional_chinese.txt +++ b/src/lang/traditional_chinese.txt @@ -1881,6 +1881,8 @@ STR_FACE_TIE :領帶: STR_FACE_EARRING :耳飾: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}改變領帶或耳飾 +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}多人遊戲 @@ -1932,7 +1934,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}起動 STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}起動您自己的伺服器 STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}輸入您的名稱 -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}輸入主機位址 # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}開始新的多人遊戲 @@ -2012,6 +2013,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :旁觀者 STR_NETWORK_CLIENT_LIST_CAPTION :{WHITE}多人遊戲 STR_NETWORK_CLIENT_LIST_SERVER :{BLACK}伺服器 +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :踢出 @@ -2122,6 +2126,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}伺服 STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}伺服器重新啟動中...{}請稍候... STR_NETWORK_MESSAGE_KICKED :*** {STRING} 已被踢出。原因:({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}內容下載中 STR_CONTENT_TYPE_CAPTION :{BLACK}種類 diff --git a/src/lang/turkish.txt b/src/lang/turkish.txt index fe90f626a2..aac0ca9004 100644 --- a/src/lang/turkish.txt +++ b/src/lang/turkish.txt @@ -1992,8 +1992,9 @@ STR_FACE_TIE :Kravat: STR_FACE_EARRING :Küpe: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Kravatı veya küpeyi değiştir -STR_NETWORK_SERVER_VISIBILITY_PRIVATE :Özel +############ Next lines match ServerGameType STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Halka açık +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Çok Oyunculu @@ -2047,7 +2048,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Sunucu b STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Kendi sunucunu başlat STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}İsminizi girin -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Sunucunun adresini girin # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Yeni çok oyunculu oyun başlat @@ -2151,6 +2151,9 @@ STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP :{BLACK}Yeni bir STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Bu sensin STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Bu, oyunun ev sahibi +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :At STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Yasakla STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Sil @@ -2277,6 +2280,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Sunucu k STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Sunucu baştan başlatılıyor...{}Lütfen bekleyin... STR_NETWORK_MESSAGE_KICKED :*** {STRING} atıldı. Sebep: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}İçerik indirme STR_CONTENT_TYPE_CAPTION :{BLACK}Tür diff --git a/src/lang/ukrainian.txt b/src/lang/ukrainian.txt index a0416effa5..2dfb1c780e 100644 --- a/src/lang/ukrainian.txt +++ b/src/lang/ukrainian.txt @@ -2114,6 +2114,8 @@ STR_FACE_TIE :Краватк STR_FACE_EARRING :Сережки: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Змінити комір або сережки +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Мережева гра @@ -2167,7 +2169,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Ство STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Створити ваш власний сервер STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Введіть ваше ім'я -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Введіть адресу сервера # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Створити нову мережеву гру @@ -2245,6 +2246,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Спостер # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Сервер @@ -2358,6 +2362,7 @@ STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Серв STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Перезавантаження сервера...{}Зачекайте... STR_NETWORK_MESSAGE_KICKED :*** {STRING} відключено. Причина: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Завантаження вмісту STR_CONTENT_TYPE_CAPTION :{BLACK}Тип diff --git a/src/lang/urdu.txt b/src/lang/urdu.txt index f8fd7a0b80..abb75c93c9 100644 --- a/src/lang/urdu.txt +++ b/src/lang/urdu.txt @@ -1482,6 +1482,8 @@ STR_FACE_TIE :ٹائی: STR_FACE_EARRING :بالیاں: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}ٹائی یا بالیاں بدلیں +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}زیادہ کھلاڑی @@ -1531,7 +1533,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}سرور STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}اپنا سرور خود شروع کریں STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}اپنا نام لکھیں -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}ہوسٹ کا ایڈریس لکھیں # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}نیا زیادہ کھلاڑیوں والا کھیل شروع کریں @@ -1609,6 +1610,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :{WHITE}نظار # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :سرور @@ -1717,6 +1721,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} ن STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}سرور نے سیشن بند کر دیا STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}سرور دوبارہ سے سٹارٹ ہو رہا ہے - - -{}برائے مہربانی انتظار فرمائیے ۔ ۔ ۔ + # Content downloading window STR_CONTENT_TITLE :{WHITE}مواد ڈاون لوڈ کیا جا رہا ہے STR_CONTENT_TYPE_CAPTION :{BLACK}طرذ diff --git a/src/lang/vietnamese.txt b/src/lang/vietnamese.txt index e1db7d4773..1c957b3122 100644 --- a/src/lang/vietnamese.txt +++ b/src/lang/vietnamese.txt @@ -889,6 +889,11 @@ STR_NEWS_STATION_NOW_ACCEPTS_CARGO_AND_CARGO :{WHITE}{STATION STR_NEWS_OFFER_OF_SUBSIDY_EXPIRED :{BIG_FONT}{BLACK}Lời đề nghị trợ cấp đã hết hạn:{}{}{STRING} từ {STRING} đến {STRING} bây giờ sẽ không có trợ cấp. STR_NEWS_SUBSIDY_WITHDRAWN_SERVICE :{BIG_FONT}{BLACK}Trợ cấp đã hết:{}{}Dịch vụ vận chuyển {STRING} từ {STRING} đến {STRING} sẽ không được trợ cấp nữa. +STR_NEWS_SERVICE_SUBSIDY_OFFERED :{BIG_FONT}{BLACK}Đề nghị trợ cấp:{}{}Dịch vụ vận tải {STRING} đầu tiên từ {STRING} đến {STRING} sẽ nhận được tiền trợ cấp trong {NUM} năm của chính quyền địa phương! +STR_NEWS_SERVICE_SUBSIDY_AWARDED_HALF :{BIG_FONT}{BLACK}Trợ cấp dịch vụ được trao cho {STRING}!{}{}Dịch vụ {STRING} từ {STRING} đến {STRING} sẽ thu lợi cao hơn 50% trong vòng {NUM} năm tới! +STR_NEWS_SERVICE_SUBSIDY_AWARDED_DOUBLE :{BIG_FONT}{BLACK}Trợ cấp dịch vụ được trao cho {STRING}!{}{}Dịch vụ {STRING} từ {STRING} đến {STRING} sẽ thu lợi gấp đôi trong vòng {NUM} năm tới! +STR_NEWS_SERVICE_SUBSIDY_AWARDED_TRIPLE :{BIG_FONT}{BLACK}Trợ cấp dịch vụ được trao cho {STRING}!{}{}Dịch vụ {STRING} từ {STRING} đến {STRING} sẽ thu lợi gấp ba trong vòng {NUM} năm tới! +STR_NEWS_SERVICE_SUBSIDY_AWARDED_QUADRUPLE :{BIG_FONT}{BLACK}Trợ cấp dịch vụ được trao cho {STRING}!{}{}Dịch vụ {STRING} từ {STRING} đến {STRING} sẽ thu lợi gấp tư trong vòng {NUM} năm tới! STR_NEWS_ROAD_REBUILDING :{BIG_FONT}{BLACK}Giao thông hỗn loạn tại {TOWN}!{}{}Chương trình sửa đường đầu tư bởi {STRING} mang 6 tháng khốn đốn cho người chạy mô tô! STR_NEWS_EXCLUSIVE_RIGHTS_TITLE :{BIG_FONT}{BLACK}Độc quyền vận tải! @@ -954,7 +959,7 @@ STR_GAME_OPTIONS_CURRENCY_MYR :Ringgit Malaysi STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_LEFT :Lái bên trái STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_RIGHT :Lái bên phải -STR_GAME_OPTIONS_TOWN_NAMES_FRAME :{BLACK}Tên thị trấn +STR_GAME_OPTIONS_TOWN_NAMES_FRAME :{BLACK}Tên thị trấn: STR_GAME_OPTIONS_TOWN_NAMES_DROPDOWN_TOOLTIP :{BLACK}Lựa chọn kiểu tên thị trấn ############ start of townname region @@ -994,6 +999,7 @@ STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_12_MONTHS :Hàng năm STR_GAME_OPTIONS_LANGUAGE :{BLACK}Ngôn ngữ STR_GAME_OPTIONS_LANGUAGE_TOOLTIP :{BLACK}Lựa chọn sử dụng ngôn ngữ giao diện +STR_GAME_OPTIONS_LANGUAGE_PERCENTAGE :{STRING} (hoàn thành {NUM}%) STR_GAME_OPTIONS_FULLSCREEN :{BLACK}Toàn màn hình STR_GAME_OPTIONS_FULLSCREEN_TOOLTIP :{BLACK}Đánh dấu vào ô này để chơi OpenTTD ở chế độ fullscreen @@ -1007,6 +1013,8 @@ STR_GAME_OPTIONS_VIDEO_ACCELERATION :{BLACK}Tăng t STR_GAME_OPTIONS_VIDEO_ACCELERATION_TOOLTIP :{BLACK}Đánh dấu vào ô này để cho phép OpenTTD thử sử dụng tăng tốc phần cứng. Sẽ có tác dụng sau khi khởi động lại trò chơi STR_GAME_OPTIONS_VIDEO_ACCELERATION_RESTART :{WHITE}Thiết lập chỉ có tác dụng sau khi khởi động lại trò chơi +STR_GAME_OPTIONS_VIDEO_VSYNC :{BLACK}VSync +STR_GAME_OPTIONS_VIDEO_VSYNC_TOOLTIP :{BLACK}Đánh dấu vào ô này để bật tính năng v-sync cho màn hình. Sẽ có tác dụng sau khi khởi động lại trò chơi. Chỉ có thể hoạt động khi tăng tốc phần cứng được bật STR_GAME_OPTIONS_GUI_ZOOM_FRAME :{BLACK}Kích thước giao diện STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_TOOLTIP :{BLACK}Chọn kích thước của các đối tượng trên giao diện @@ -1201,6 +1209,10 @@ STR_CONFIG_SETTING_VEHICLE_BREAKDOWNS :Mức hỏng h STR_CONFIG_SETTING_VEHICLE_BREAKDOWNS_HELPTEXT :Thiết lập mức độ hỏng hóc đối với phương tiện không bảo trì thường xuyên STR_CONFIG_SETTING_SUBSIDY_MULTIPLIER :Tỉ lệ chi trả: {STRING} STR_CONFIG_SETTING_SUBSIDY_MULTIPLIER_HELPTEXT :Thiết lập mức chi trả cho tuyến vận chuyển phụ trợ +STR_CONFIG_SETTING_SUBSIDY_DURATION :Thời hạn trợ cấp: {STRING} +STR_CONFIG_SETTING_SUBSIDY_DURATION_HELPTEXT :Đặt số năm được hưởng trợ cấp dịch vụ +STR_CONFIG_SETTING_SUBSIDY_DURATION_VALUE :{NUM} năm +STR_CONFIG_SETTING_SUBSIDY_DURATION_DISABLED :Không có trợ cấp STR_CONFIG_SETTING_CONSTRUCTION_COSTS :Chi phí xây dựng: {STRING} STR_CONFIG_SETTING_CONSTRUCTION_COSTS_HELPTEXT :Thiết lập mức độ xây dựng và chi phí mua sắm STR_CONFIG_SETTING_RECESSIONS :Suy thoái: {STRING} @@ -1654,7 +1666,7 @@ STR_CONFIG_SETTING_TOWN_CARGOGENMODE_ORIGINAL :Tỉ lệ bình STR_CONFIG_SETTING_TOWN_CARGOGENMODE_BITCOUNT :Tuyến tính STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT :Trồng cây trong trò chơi: {STRING} -STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT_HELPTEXT :Điều khiển sự xuất hiện tự dộng của cây cối khi đang chơi. Điều này có thể ảnh hưởng đến những nhà máy dựa vào cây cối, ví dự như nhà máy chế biến gỗgỗ +STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT_HELPTEXT :Điều khiển sự xuất hiện tự dộng của cây cối khi đang chơi. Điều này có thể ảnh hưởng đến những nhà máy dựa vào cây cối, ví dự như nhà máy chế biến gỗ STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT_NO_SPREAD :Mọc nhưng không trải {RED}(nhà máy chế biến gỗ sẽ không hoạt động) STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT_SPREAD_RAINFOREST :Mọc nhưng chỉ trải ở rừng nhiệt đới STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT_SPREAD_ALL :Mọc và trải mọi nơi @@ -1832,7 +1844,7 @@ STR_INTRO_LOAD_GAME :{BLACK}Nạp V STR_INTRO_PLAY_SCENARIO :{BLACK}Chơi Màn Chơi Kịch Bản STR_INTRO_PLAY_HEIGHTMAP :{BLACK}Chơi Bản Đồ Địa Hình STR_INTRO_SCENARIO_EDITOR :{BLACK}Biên Tập Màn Chơi Kịch Bản -STR_INTRO_MULTIPLAYER :{BLACK}Chơi Trên Mạng +STR_INTRO_MULTIPLAYER :{BLACK}Nhiều Người Chơi STR_INTRO_GAME_OPTIONS :{BLACK}Cấu Hình Trò Chơi STR_INTRO_HIGHSCORE :{BLACK}Bảng điểm chơi cao nhất @@ -1847,7 +1859,7 @@ STR_INTRO_TOOLTIP_LOAD_GAME :{BLACK}Tải tr STR_INTRO_TOOLTIP_PLAY_HEIGHTMAP :{BLACK}Chơi ván mới, dùng bản đồ địa hình làm nền đất STR_INTRO_TOOLTIP_PLAY_SCENARIO :{BLACK}Chơi ván mới, dùng màn chơi kịch bản theo ý riêng STR_INTRO_TOOLTIP_SCENARIO_EDITOR :{BLACK}Tạo màn chơi kịch bản/bản đồ theo ý riêng -STR_INTRO_TOOLTIP_MULTIPLAYER :{BLACK}Bắt đầu chơi trên mạng nhiều người +STR_INTRO_TOOLTIP_MULTIPLAYER :{BLACK}Bắt đầu ván chơi trên mạng nhiều người STR_INTRO_TOOLTIP_TEMPERATE :{BLACK}Chọn kiểu quang cảnh 'ôn hòa' STR_INTRO_TOOLTIP_SUB_ARCTIC_LANDSCAPE :{BLACK}Chọn kiểu quang cảnh 'giá rét' @@ -1989,6 +2001,9 @@ STR_FACE_TIE :Cà vạt: STR_FACE_EARRING :Bông tai: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Thay đổi cà vạt hoặc bông tai +############ Next lines match ServerGameType +STR_NETWORK_SERVER_VISIBILITY_PUBLIC :Công khai +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Ván Chơi Mạng @@ -2042,7 +2057,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Tạo se STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Tạo server của riêng bạn STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Gõ tên của bạn -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Gõ địa chỉ của server # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Bắt đầu ván chơi mạng nhiều người @@ -2052,6 +2066,8 @@ STR_NETWORK_START_SERVER_NEW_GAME_NAME_TOOLTIP :{BLACK}Tên c STR_NETWORK_START_SERVER_SET_PASSWORD :{BLACK}Đặt mật khẩu STR_NETWORK_START_SERVER_PASSWORD_TOOLTIP :{BLACK}Bảo vệ game của bạn bằng mật khẩu nếu bạn không muốn người khác vào tùy tiện +STR_NETWORK_START_SERVER_VISIBILITY_LABEL :{BLACK}Hiển thị +STR_NETWORK_START_SERVER_VISIBILITY_TOOLTIP :{BLACK}Cho phép người khác thấy server của bạn trong danh sách công khai STR_NETWORK_START_SERVER_CLIENTS_SELECT :{BLACK}{NUM} máy trạm STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS :{BLACK}Số máy trạm tối đa: STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS_TOOLTIP :{BLACK}Chọn số lượng máy trạm tối đa. Không nhất thiết phải chọn đầy các dòng @@ -2115,14 +2131,49 @@ STR_NETWORK_NEED_GAME_PASSWORD_CAPTION :{WHITE}Server y STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION :{WHITE}Công ty yêu cầu xác thực. Nhập mật khẩu # Network company list added strings -STR_NETWORK_COMPANY_LIST_CLIENT_LIST :Danh sách máy trạm +STR_NETWORK_COMPANY_LIST_CLIENT_LIST :Người chơi trực tuyến STR_NETWORK_COMPANY_LIST_SPECTATE :Xem # Network client list +STR_NETWORK_CLIENT_LIST_CAPTION :{WHITE}Chế độ nhiều người chơi +STR_NETWORK_CLIENT_LIST_SERVER :{BLACK}Server +STR_NETWORK_CLIENT_LIST_SERVER_NAME :{BLACK}Tên +STR_NETWORK_CLIENT_LIST_SERVER_NAME_TOOLTIP :{BLACK}Tên server mà bạn đang chơi +STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP :{BLACK}Sửa tên server +STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION :Tên của server +STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY :{BLACK}Hiển thị +STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP :{BLACK}Cho phép người khác thấy server của bạn trong danh sách công khai +STR_NETWORK_CLIENT_LIST_PLAYER :{BLACK}Người chơi +STR_NETWORK_CLIENT_LIST_PLAYER_NAME :{BLACK}Tên +STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP :{BLACK}Tên người chơi +STR_NETWORK_CLIENT_LIST_PLAYER_NAME_EDIT_TOOLTIP :{BLACK}Sửa tên người chơi +STR_NETWORK_CLIENT_LIST_PLAYER_NAME_QUERY_CAPTION :Tên người chơi +STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_TOOLTIP :{BLACK}Các tác vụ quản lý thực hiện cho client này +STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_TOOLTIP :{BLACK}Các tác vụ quản lý thực hiện cho công ty này STR_NETWORK_CLIENT_LIST_JOIN_TOOLTIP :{BLACK}Gia nhập công ty này - +STR_NETWORK_CLIENT_LIST_CHAT_CLIENT_TOOLTIP :{BLACK}Gửi tin nhắn tới người chơi này +STR_NETWORK_CLIENT_LIST_CHAT_COMPANY_TOOLTIP :{BLACK}Gửi tin nhắn tới tất cả người chơi trong công ty này +STR_NETWORK_CLIENT_LIST_CHAT_SPECTATOR_TOOLTIP :{BLACK}Gửi tin nhắn tới tất cả người xem +STR_NETWORK_CLIENT_LIST_SPECTATORS :Người xem +STR_NETWORK_CLIENT_LIST_NEW_COMPANY :(Công ty mới) +STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP :{BLACK}Khai sinh một công ty và gia nhập +STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP :{BLACK}Đây là bạn +STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP :{BLACK}Đây là người tổ chức ván chơi +STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT :{BLACK}{NUM} người chơi / {NUM} công ty + +############ Begin of ConnectionType +############ End of ConnectionType + +STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Đá ra STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Cấm +STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Xoá +STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_UNLOCK :Mật khẩu công ty +STR_NETWORK_CLIENT_LIST_ASK_CAPTION :{WHITE}Tác vụ quản lý +STR_NETWORK_CLIENT_LIST_ASK_CLIENT_KICK :{YELLOW}Bạn có muốn đá người chơi '{STRING}' ra? +STR_NETWORK_CLIENT_LIST_ASK_CLIENT_BAN :{YELLOW}Bạn có muốn cấm người chơi '{STRING}'? +STR_NETWORK_CLIENT_LIST_ASK_COMPANY_RESET :{YELLOW}Bạn có muốn xoá công ty '{COMPANY}'? +STR_NETWORK_CLIENT_LIST_ASK_COMPANY_UNLOCK :{YELLOW}Bạn có muốn đặt lại mật khẩu cho công ty '{COMPANY}'? STR_NETWORK_SERVER :Server STR_NETWORK_CLIENT :Máy trạm @@ -2167,6 +2218,8 @@ STR_NETWORK_ERROR_SERVER_START :{WHITE}Không t STR_NETWORK_ERROR_CLIENT_START :{WHITE}Không thể tạo kết nối STR_NETWORK_ERROR_TIMEOUT :{WHITE}Kết nối #{NUM} quá lâu STR_NETWORK_ERROR_SERVER_ERROR :{WHITE}Có lỗi trong giao thức và kết nối bị đóng +STR_NETWORK_ERROR_BAD_PLAYER_NAME :{WHITE}Bạn chưa đặt tên người chơi. Bạn có thể đặt tên này tại phía trên của cửa sổ Chơi trên mạng +STR_NETWORK_ERROR_BAD_SERVER_NAME :{WHITE}Bạn chưa đặt tên server. Bạn có thể đặt tên này tại phía trên của cửa sổ Chơi trên mạng STR_NETWORK_ERROR_WRONG_REVISION :{WHITE}Phiên bản của máy trạm không hợp với phiên bản máy server STR_NETWORK_ERROR_WRONG_PASSWORD :{WHITE}Sai mật khẩu STR_NETWORK_ERROR_SERVER_FULL :{WHITE}Server bị đầy @@ -2179,6 +2232,8 @@ STR_NETWORK_ERROR_TIMEOUT_PASSWORD :{WHITE}Bạn nh STR_NETWORK_ERROR_TIMEOUT_COMPUTER :{WHITE}Máy của bạn quá chậm để có thể theo kịp máy chủ STR_NETWORK_ERROR_TIMEOUT_MAP :{WHITE}Thời gian tải bản đồ quá lâu STR_NETWORK_ERROR_TIMEOUT_JOIN :{WHITE}Thời gian tham gia máy chủ quá lâu +STR_NETWORK_ERROR_INVALID_CLIENT_NAME :{WHITE}Tên người chơi không hợp lệ +STR_NETWORK_ERROR_SERVER_TOO_OLD :{WHITE}Server được yêu cầu dùng phiên bản cũ hơn so với client ############ Leave those lines in this order!! STR_NETWORK_ERROR_CLIENT_GENERAL :lỗi chung @@ -2201,6 +2256,7 @@ STR_NETWORK_ERROR_CLIENT_TIMEOUT_PASSWORD :không nhận STR_NETWORK_ERROR_CLIENT_TIMEOUT_COMPUTER :lỗi quá thời gian STR_NETWORK_ERROR_CLIENT_TIMEOUT_MAP :tải bản đồ quá thời gian STR_NETWORK_ERROR_CLIENT_TIMEOUT_JOIN :xử lý bản đồ quá thời gian +STR_NETWORK_ERROR_CLIENT_INVALID_CLIENT_NAME :tên client không đúng ############ End of leave-in-this-order STR_NETWORK_ERROR_CLIENT_GUI_LOST_CONNECTION_CAPTION :{WHITE}Kết nối có thể đã bị mất @@ -2229,12 +2285,13 @@ STR_NETWORK_MESSAGE_CLIENT_COMPANY_JOIN :*** {STRING} gi STR_NETWORK_MESSAGE_CLIENT_COMPANY_SPECTATE :*** {STRING} vào xem ván chơi STR_NETWORK_MESSAGE_CLIENT_COMPANY_NEW :*** {STRING} khai trương công ty mới (#{2:NUM}) STR_NETWORK_MESSAGE_CLIENT_LEFT :*** {STRING} rời bỏ ván chơi ({2:STRING}) -STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} đổi tên thành {STRING} +STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} đã đổi tên thành {STRING} STR_NETWORK_MESSAGE_GIVE_MONEY :*** {STRING} tặng {2:CURRENCY_LONG} cho {1:STRING} STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Server kết thúc phiên STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Server khởi động lại...{}Xin chờ... STR_NETWORK_MESSAGE_KICKED :*** {STRING} đã bị đá khỏi ván chơi. Lý do: ({STRING}) + # Content downloading window STR_CONTENT_TITLE :{WHITE}Đang tải nội dung STR_CONTENT_TYPE_CAPTION :{BLACK}Kiểu @@ -3044,6 +3101,7 @@ STR_NEWGRF_ERROR_MSG_WARNING :{RED}Cảnh bá STR_NEWGRF_ERROR_MSG_ERROR :{RED}Lỗi: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_FATAL :{RED}Lỗi nghiêm trọng: {SILVER}{STRING} STR_NEWGRF_ERROR_FATAL_POPUP :{WHITE}Xảy ra lỗi NewGRF nghiêm trọng:{}{STRING} +STR_NEWGRF_ERROR_POPUP :{WHITE}Có lỗi NewGRF xảy ra:{}{STRING} STR_NEWGRF_ERROR_VERSION_NUMBER :{1:STRING} sẽ không hoạt động với phiên bản TTDPatch version theo như báo cáo của OpenTTD. STR_NEWGRF_ERROR_DOS_OR_WINDOWS :{1:STRING} để dành cho phiên bản {STRING} của TTD. STR_NEWGRF_ERROR_UNSET_SWITCH :{1:STRING} được thiết kế để xài với {STRING} @@ -3521,7 +3579,7 @@ STR_GROUP_CREATE_TOOLTIP :{BLACK}Ấn và STR_GROUP_DELETE_TOOLTIP :{BLACK}Xoá nhóm đã chọn STR_GROUP_RENAME_TOOLTIP :{BLACK}Đổi tên nhóm STR_GROUP_LIVERY_TOOLTIP :{BLACK}Thay đổi phục trang cho nhóm được chọn -STR_GROUP_REPLACE_PROTECTION_TOOLTIP :{BLACK}Không để nhóm này tự thay thế (thiết lập chung) khi hết hạn +STR_GROUP_REPLACE_PROTECTION_TOOLTIP :{BLACK}Không để nhóm này tự thay thế (thiết lập chung) khi hết hạn. Ctrl+Click để áp dụng lên nhóm con. STR_QUERY_GROUP_DELETE_CAPTION :{WHITE}Xóa Nhóm STR_GROUP_DELETE_QUERY_TEXT :{WHITE}Bạn có chắc chắn muốn xóa nhóm này và tất cả con của nó? @@ -3774,7 +3832,9 @@ STR_REPLACE_MAGLEV_VEHICLES :Đầu máy đ STR_REPLACE_ROAD_VEHICLES :Các xe ô-tô STR_REPLACE_TRAM_VEHICLES :Các xe điện +STR_REPLACE_REMOVE_WAGON :{BLACK}Xoá bỏ toa xe ({STRING}): {ORANGE}{STRING} STR_REPLACE_REMOVE_WAGON_HELP :{BLACK}Tự động thay thế sẽ giữ nguyên độ dài đoàn tàu bằng cách bỏ bớt toa xe (bỏ từ phía đầu), nếu như việc thay thế đầu máy làm đoàn tàu dài hơn. +STR_REPLACE_REMOVE_WAGON_GROUP_HELP :{STRING}. Ctrl+Click để áp dụng cho nhóm con # Vehicle view STR_VEHICLE_VIEW_CAPTION :{WHITE}{VEHICLE} diff --git a/src/lang/welsh.txt b/src/lang/welsh.txt index 3918c2aa62..e1f5409f47 100644 --- a/src/lang/welsh.txt +++ b/src/lang/welsh.txt @@ -1877,6 +1877,8 @@ STR_FACE_TIE :Tei: STR_FACE_EARRING :Clustlws: STR_FACE_TIE_EARRING_TOOLTIP :{BLACK}Newid tei neu glustlws +############ Next lines match ServerGameType +############ End of leave-in-this-order # Network server list STR_NETWORK_SERVER_LIST_CAPTION :{WHITE}Amlchwaraewr @@ -1926,7 +1928,6 @@ STR_NETWORK_SERVER_LIST_START_SERVER :{BLACK}Dechrau STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP :{BLACK}Dechrau eich gweinydd eich hun STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE :{BLACK}Rhowch eich enw -STR_NETWORK_SERVER_LIST_ENTER_IP :{BLACK}Rhowch gyfeiriad y gwesteiwr # Start new multiplayer server STR_NETWORK_START_SERVER_CAPTION :{WHITE}Dechrau gêm newydd amlchwaraewr @@ -2004,6 +2005,9 @@ STR_NETWORK_COMPANY_LIST_SPECTATE :Gwylio # Network client list +############ Begin of ConnectionType +############ End of ConnectionType + STR_NETWORK_SERVER :Gweinydd @@ -2112,6 +2116,7 @@ STR_NETWORK_MESSAGE_NAME_CHANGE :*** Mae {STRING STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}Fe gaewyd y sesiwn gan y gweinydd STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}Mae'r gweinydd yn ailgychwyn...{}Arhoswch... + # Content downloading window STR_CONTENT_TITLE :{WHITE}Llawrlwytho cynnwys STR_CONTENT_TYPE_CAPTION :{BLACK}Math diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 1c27d62066..8b01579189 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -14,6 +14,8 @@ add_files( network_content.h network_content_gui.cpp network_content_gui.h + network_coordinator.cpp + network_coordinator.h network_func.h network_gamelist.cpp network_gamelist.h @@ -22,6 +24,8 @@ add_files( network_internal.h network_server.cpp network_server.h + network_stun.cpp + network_stun.h network_type.h network_udp.cpp network_udp.h diff --git a/src/network/core/CMakeLists.txt b/src/network/core/CMakeLists.txt index bf713be99c..bcecad38c8 100644 --- a/src/network/core/CMakeLists.txt +++ b/src/network/core/CMakeLists.txt @@ -1,6 +1,7 @@ add_files( address.cpp address.h + config.cpp config.h core.cpp core.h @@ -20,11 +21,15 @@ add_files( tcp_content.cpp tcp_content.h tcp_content_type.h + tcp_coordinator.cpp + tcp_coordinator.h tcp_game.cpp tcp_game.h tcp_http.cpp tcp_http.h tcp_listen.h + tcp_stun.cpp + tcp_stun.h udp.cpp udp.h ) diff --git a/src/network/core/address.cpp b/src/network/core/address.cpp index 0807f427fd..0a2066b7dd 100644 --- a/src/network/core/address.cpp +++ b/src/network/core/address.cpp @@ -10,6 +10,7 @@ #include "../../stdafx.h" #include "address.h" +#include "../network_internal.h" #include "../../debug.h" #include "../../safeguards.h" @@ -335,13 +336,12 @@ static SOCKET ListenLoopProc(addrinfo *runp) DEBUG(net, 1, "Setting no-delay mode failed: %s", NetworkError::GetLast().AsString()); } - int on = 1; - /* The (const char*) cast is needed for windows!! */ - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on)) == -1) { + if (!SetReusePort(sock)) { DEBUG(net, 0, "Setting reuse-address mode failed: %s", NetworkError::GetLast().AsString()); } #ifndef __OS2__ + int on = 1; if (runp->ai_family == AF_INET6 && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&on, sizeof(on)) == -1) { DEBUG(net, 3, "Could not disable IPv4 over IPv6: %s", NetworkError::GetLast().AsString()); @@ -422,6 +422,38 @@ void NetworkAddress::Listen(int socktype, SocketList *sockets) } } +/** + * Get the peer address of a socket as NetworkAddress. + * @param sock The socket to get the peer address of. + * @return The NetworkAddress of the peer address. + */ +/* static */ NetworkAddress NetworkAddress::GetPeerAddress(SOCKET sock) +{ + sockaddr_storage addr = {}; + socklen_t addr_len = sizeof(addr); + if (getpeername(sock, (sockaddr *)&addr, &addr_len) != 0) { + DEBUG(net, 0, "Failed to get address of the peer: %s", NetworkError::GetLast().AsString()); + return NetworkAddress(); + } + return NetworkAddress(addr, addr_len); +} + +/** + * Get the local address of a socket as NetworkAddress. + * @param sock The socket to get the local address of. + * @return The NetworkAddress of the local address. + */ +/* static */ NetworkAddress NetworkAddress::GetSockAddress(SOCKET sock) +{ + sockaddr_storage addr = {}; + socklen_t addr_len = sizeof(addr); + if (getsockname(sock, (sockaddr *)&addr, &addr_len) != 0) { + DEBUG(net, 0, "Failed to get address of the socket: %s", NetworkError::GetLast().AsString()); + return NetworkAddress(); + } + return NetworkAddress(addr, addr_len); +} + /** * Get the peer name of a socket in string format. * @param sock The socket to get the peer name of. @@ -429,8 +461,27 @@ void NetworkAddress::Listen(int socktype, SocketList *sockets) */ /* static */ const std::string NetworkAddress::GetPeerName(SOCKET sock) { - sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); - getpeername(sock, (sockaddr *)&addr, &addr_len); - return NetworkAddress(addr, addr_len).GetAddressAsString(); + return NetworkAddress::GetPeerAddress(sock).GetAddressAsString(); +} + +/** + * Convert a string containing either "hostname", "hostname:port" or invite code + * to a ServerAddress, where the string can be postfixed with "#company" to + * indicate the requested company. + * + * @param connection_string The string to parse. + * @param default_port The default port to set port to if not in connection_string. + * @param company Pointer to the company variable to set iff indicated. + * @return A valid ServerAddress of the parsed information. + */ +/* static */ ServerAddress ServerAddress::Parse(const std::string &connection_string, uint16 default_port, CompanyID *company_id) +{ + if (StrStartsWith(connection_string, "+")) { + std::string_view invite_code = ParseCompanyFromConnectionString(connection_string, company_id); + return ServerAddress(SERVER_ADDRESS_INVITE_CODE, std::string(invite_code)); + } + + uint16 port = default_port; + std::string_view ip = ParseFullConnectionString(connection_string, port, company_id); + return ServerAddress(SERVER_ADDRESS_DIRECT, std::string(ip) + ":" + std::to_string(port)); } diff --git a/src/network/core/address.h b/src/network/core/address.h index 88da7ab949..bd1deb737d 100644 --- a/src/network/core/address.h +++ b/src/network/core/address.h @@ -12,6 +12,7 @@ #include "os_abstraction.h" #include "config.h" +#include "../../company_type.h" #include "../../string_func.h" #include "../../core/smallmap_type.hpp" @@ -175,6 +176,8 @@ public: static const char *SocketTypeAsString(int socktype); static const char *AddressFamilyAsString(int family); + static NetworkAddress GetPeerAddress(SOCKET sock); + static NetworkAddress GetSockAddress(SOCKET sock); static const std::string GetPeerName(SOCKET sock); }; @@ -191,4 +194,38 @@ private: char buf[NETWORK_HOSTNAME_PORT_LENGTH + 7]; }; +/** + * Types of server addresses we know. + * + * Sorting will prefer entries at the top of this list above ones at the bottom. + */ +enum ServerAddressType { + SERVER_ADDRESS_DIRECT, ///< Server-address is based on an hostname:port. + SERVER_ADDRESS_INVITE_CODE, ///< Server-address is based on an invite code. +}; + +/** + * Address to a game server. + * + * This generalises addresses which are based on different identifiers. + */ +class ServerAddress { +private: + /** + * Create a new ServerAddress object. + * + * Please use ServerAddress::Parse() instead of calling this directly. + * + * @param type The type of the ServerAdress. + * @param connection_string The connection_string that belongs to this ServerAddress type. + */ + ServerAddress(ServerAddressType type, const std::string &connection_string) : type(type), connection_string(connection_string) {} + +public: + ServerAddressType type; ///< The type of this ServerAddress. + std::string connection_string; ///< The connection string for this ServerAddress. + + static ServerAddress Parse(const std::string &connection_string, uint16 default_port, CompanyID *company_id = nullptr); +}; + #endif /* NETWORK_CORE_ADDRESS_H */ diff --git a/src/network/core/config.cpp b/src/network/core/config.cpp new file mode 100644 index 0000000000..6e7f8a11c3 --- /dev/null +++ b/src/network/core/config.cpp @@ -0,0 +1,69 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file config.cpp Configuration of the connection strings for network stuff using environment variables. + */ + +#include "../../stdafx.h" + +#include +#include "../../string_func.h" + +#include "../../safeguards.h" + +/** + * Get the environment variable using std::getenv and when it is an empty string (or nullptr), return a fallback value instead. + * @param variable The environment variable to read from. + * @param fallback The fallback in case the environment variable is not set. + * @return The environment value, or when that does not exist the given fallback value. + */ +static const char *GetEnv(const char *variable, const char *fallback) +{ + const char *value = std::getenv(variable); + return StrEmpty(value) ? fallback : value; +} + +/** + * Get the connection string for the game coordinator from the environment variable OTTD_COORDINATOR_CS, + * or when it has not been set a hard coded default DNS hostname of the production server. + * @return The game coordinator's connection string. + */ +const char *NetworkCoordinatorConnectionString() +{ + return GetEnv("OTTD_COORDINATOR_CS", "coordinator.openttd.org"); +} + +/** + * Get the connection string for the STUN server from the environment variable OTTD_STUN_CS, + * or when it has not been set a hard coded default DNS hostname of the production server. + * @return The STUN server's connection string. + */ +const char *NetworkStunConnectionString() +{ + return GetEnv("OTTD_STUN_CS", "stun.openttd.org"); +} + +/** + * Get the connection string for the content server from the environment variable OTTD_CONTENT_SERVER_CS, + * or when it has not been set a hard coded default DNS hostname of the production server. + * @return The content server's connection string. + */ +const char *NetworkContentServerConnectionString() +{ + return GetEnv("OTTD_CONTENT_SERVER_CS", "content.openttd.org"); +} + +/** + * Get the connection string for the content mirror from the environment variable OTTD_CONTENT_MIRROR_CS, + * or when it has not been set a hard coded default DNS hostname of the production server. + * @return The content mirror's connection string. + */ +const char *NetworkContentMirrorConnectionString() +{ + return GetEnv("OTTD_CONTENT_MIRROR_CS", "binaries.openttd.org"); +} diff --git a/src/network/core/config.h b/src/network/core/config.h index 4bb3111152..e12f8b1696 100644 --- a/src/network/core/config.h +++ b/src/network/core/config.h @@ -12,26 +12,24 @@ #ifndef NETWORK_CORE_CONFIG_H #define NETWORK_CORE_CONFIG_H -/** DNS hostname of the masterserver */ -static const char * const NETWORK_MASTER_SERVER_HOST = "master.openttd.org"; -/** DNS hostname of the content server */ -static const char * const NETWORK_CONTENT_SERVER_HOST = "content.openttd.org"; -/** DNS hostname of the HTTP-content mirror server */ -static const char * const NETWORK_CONTENT_MIRROR_HOST = "binaries.openttd.org"; +const char *NetworkCoordinatorConnectionString(); +const char *NetworkStunConnectionString(); +const char *NetworkContentServerConnectionString(); +const char *NetworkContentMirrorConnectionString(); + /** URL of the HTTP mirror system */ static const char * const NETWORK_CONTENT_MIRROR_URL = "/bananas"; -/** Message sent to the masterserver to 'identify' this client as OpenTTD */ -static const char * const NETWORK_MASTER_SERVER_WELCOME_MESSAGE = "OpenTTDRegister"; -static const uint16 NETWORK_MASTER_SERVER_PORT = 3978; ///< The default port of the master server (UDP) -static const uint16 NETWORK_CONTENT_SERVER_PORT = 3978; ///< The default port of the content server (TCP) -static const uint16 NETWORK_CONTENT_MIRROR_PORT = 80; ///< The default port of the content mirror (TCP) -static const uint16 NETWORK_DEFAULT_PORT = 3979; ///< The default port of the game server (TCP & UDP) -static const uint16 NETWORK_ADMIN_PORT = 3977; ///< The default port for admin network -static const uint16 NETWORK_DEFAULT_DEBUGLOG_PORT = 3982; ///< The default port debug-log is sent to (TCP) +static const uint16 NETWORK_COORDINATOR_SERVER_PORT = 3976; ///< The default port of the Game Coordinator server (TCP) +static const uint16 NETWORK_STUN_SERVER_PORT = 3975; ///< The default port of the STUN server (TCP) +static const uint16 NETWORK_CONTENT_SERVER_PORT = 3978; ///< The default port of the content server (TCP) +static const uint16 NETWORK_CONTENT_MIRROR_PORT = 80; ///< The default port of the content mirror (TCP) +static const uint16 NETWORK_DEFAULT_PORT = 3979; ///< The default port of the game server (TCP & UDP) +static const uint16 NETWORK_ADMIN_PORT = 3977; ///< The default port for admin network +static const uint16 NETWORK_DEFAULT_DEBUGLOG_PORT = 3982; ///< The default port debug-log is sent to (TCP) -static const uint16 UDP_MTU = 1460; ///< Number of bytes we can pack in a single UDP packet -static const uint16 UDP_MTU_SHORT = 1400; ///< Number of bytes we can pack in a single UDP packet (conservative) +static const uint16 UDP_MTU = 1460; ///< Number of bytes we can pack in a single UDP packet +static const uint16 UDP_MTU_SHORT = 1400; ///< Number of bytes we can pack in a single UDP packet (conservative) /* * Technically a TCP packet could become 64kiB, however the high bit is kept so it becomes possible in the future * to go to (significantly) larger packets if needed. This would entail a strategy such as employed for UTF-8. @@ -46,42 +44,56 @@ static const uint16 UDP_MTU_SHORT = 1400; ///< Number of * Send_uint16(GB(size, 16, 14) | 0b10 << 14) * Send_uint16(GB(size, 0, 16)) */ -static const uint16 TCP_MTU = 32767; ///< Number of bytes we can pack in a single TCP packet -static const uint16 COMPAT_MTU = 1460; ///< Number of bytes we can pack in a single packet for backward compatibility +static const uint16 TCP_MTU = 32767; ///< Number of bytes we can pack in a single TCP packet +static const uint16 COMPAT_MTU = 1460; ///< Number of bytes we can pack in a single packet for backward compatibility -static const byte NETWORK_GAME_ADMIN_VERSION = 1; ///< What version of the admin network do we use? -static const byte NETWORK_GAME_INFO_VERSION = 4; ///< What version of game-info do we use? -static const byte NETWORK_COMPANY_INFO_VERSION = 6; ///< What version of company info is this? -static const byte NETWORK_MASTER_SERVER_VERSION = 2; ///< What version of master-server-protocol do we use? +static const byte NETWORK_GAME_ADMIN_VERSION = 1; ///< What version of the admin network do we use? +static const byte NETWORK_GAME_INFO_VERSION = 5; ///< What version of game-info do we use? +static const byte NETWORK_COMPANY_INFO_VERSION = 6; ///< What version of company info is this? +static const byte NETWORK_COORDINATOR_VERSION = 3; ///< What version of game-coordinator-protocol do we use? -static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0' -static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0' -static const uint NETWORK_HOSTNAME_LENGTH = 80; ///< The maximum length of the host name, in bytes including '\0' -static const uint NETWORK_HOSTNAME_PORT_LENGTH = 80 + 6; ///< The maximum length of the host name + port, in bytes including '\0'. The extra six is ":" + port number (with a max of 65536) -static const uint NETWORK_SERVER_ID_LENGTH = 33; ///< The maximum length of the network id of the servers, in bytes including '\0' -static const uint NETWORK_REVISION_LENGTH = 33; ///< The maximum length of the revision, in bytes including '\0' -static const uint NETWORK_LONG_REVISION_LENGTH = 64; ///< The maximum length of the revision, in bytes including '\0' -static const uint NETWORK_PASSWORD_LENGTH = 33; ///< The maximum length of the password, in bytes including '\0' (must be >= NETWORK_SERVER_ID_LENGTH) -static const uint NETWORK_CLIENTS_LENGTH = 200; ///< The maximum length for the list of clients that controls a company, in bytes including '\0' -static const uint NETWORK_CLIENT_NAME_LENGTH = 25; ///< The maximum length of a client's name, in bytes including '\0' -static const uint NETWORK_RCONCOMMAND_LENGTH = 500; ///< The maximum length of a rconsole command, in bytes including '\0' -static const uint NETWORK_GAMESCRIPT_JSON_LENGTH = COMPAT_MTU-3; ///< The maximum length of a gamescript json string, in bytes including '\0'. Must not be longer than COMPAT_MTU including header (3 bytes) -static const uint NETWORK_CHAT_LENGTH = 900; ///< The maximum length of a chat message, in bytes including '\0' -static const uint NETWORK_CONTENT_FILENAME_LENGTH = 48; ///< The maximum length of a content's filename, in bytes including '\0'. -static const uint NETWORK_CONTENT_NAME_LENGTH = 64; ///< The maximum length of a content's name, in bytes including '\0'. -static const uint NETWORK_CONTENT_VERSION_LENGTH = 16; ///< The maximum length of a content's version, in bytes including '\0'. -static const uint NETWORK_CONTENT_URL_LENGTH = 96; ///< The maximum length of a content's url, in bytes including '\0'. -static const uint NETWORK_CONTENT_DESC_LENGTH = 512; ///< The maximum length of a content's description, in bytes including '\0'. -static const uint NETWORK_CONTENT_TAG_LENGTH = 32; ///< The maximum length of a content's tag, in bytes including '\0'. +static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0' +static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0' +static const uint NETWORK_HOSTNAME_LENGTH = 80; ///< The maximum length of the host name, in bytes including '\0' +static const uint NETWORK_HOSTNAME_PORT_LENGTH = 80 + 6; ///< The maximum length of the host name + port, in bytes including '\0'. The extra six is ":" + port number (with a max of 65536) +static const uint NETWORK_SERVER_ID_LENGTH = 33; ///< The maximum length of the network id of the servers, in bytes including '\0' +static const uint NETWORK_REVISION_LENGTH = 33; ///< The maximum length of the revision, in bytes including '\0' +static const uint NETWORK_LONG_REVISION_LENGTH = 64; ///< The maximum length of the revision, in bytes including '\0' +static const uint NETWORK_PASSWORD_LENGTH = 33; ///< The maximum length of the password, in bytes including '\0' (must be >= NETWORK_SERVER_ID_LENGTH) +static const uint NETWORK_CLIENTS_LENGTH = 200; ///< The maximum length for the list of clients that controls a company, in bytes including '\0' +static const uint NETWORK_CLIENT_NAME_LENGTH = 25; ///< The maximum length of a client's name, in bytes including '\0' +static const uint NETWORK_RCONCOMMAND_LENGTH = 500; ///< The maximum length of a rconsole command, in bytes including '\0' +static const uint NETWORK_GAMESCRIPT_JSON_LENGTH = COMPAT_MTU - 3; ///< The maximum length of a gamescript json string, in bytes including '\0'. Must not be longer than COMPAT_MTU including header (3 bytes) +static const uint NETWORK_CHAT_LENGTH = 900; ///< The maximum length of a chat message, in bytes including '\0' +static const uint NETWORK_CONTENT_FILENAME_LENGTH = 48; ///< The maximum length of a content's filename, in bytes including '\0'. +static const uint NETWORK_CONTENT_NAME_LENGTH = 64; ///< The maximum length of a content's name, in bytes including '\0'. +static const uint NETWORK_CONTENT_VERSION_LENGTH = 16; ///< The maximum length of a content's version, in bytes including '\0'. +static const uint NETWORK_CONTENT_URL_LENGTH = 96; ///< The maximum length of a content's url, in bytes including '\0'. +static const uint NETWORK_CONTENT_DESC_LENGTH = 512; ///< The maximum length of a content's description, in bytes including '\0'. +static const uint NETWORK_CONTENT_TAG_LENGTH = 32; ///< The maximum length of a content's tag, in bytes including '\0'. +static const uint NETWORK_ERROR_DETAIL_LENGTH = 100; ///< The maximum length of the error detail, in bytes including '\0'. +static const uint NETWORK_INVITE_CODE_LENGTH = 64; ///< The maximum length of the invite code, in bytes including '\0'. +static const uint NETWORK_INVITE_CODE_SECRET_LENGTH = 80; ///< The maximum length of the invite code secret, in bytes including '\0'. +static const uint NETWORK_TOKEN_LENGTH = 64; ///< The maximum length of a token, in bytes including '\0'. -static const uint NETWORK_GRF_NAME_LENGTH = 80; ///< Maximum length of the name of a GRF +static const uint NETWORK_GRF_NAME_LENGTH = 80; ///< Maximum length of the name of a GRF /** * Maximum number of GRFs that can be sent. - * This limit is reached when PACKET_UDP_SERVER_RESPONSE reaches the maximum size of UDP_MTU bytes. + * + * This limit exists to avoid that the SERVER_INFO packet exceeding the + * maximum MTU. At the time of writing this limit is 32767 (TCP_MTU). + * + * In the SERVER_INFO packet is the NetworkGameInfo struct, which is + * 142 bytes + 100 per NewGRF (under the assumption strings are used to + * their max). This brings us to roughly 326 possible NewGRFs. Round it + * down so people don't freak out because they see a weird value, and you + * get the limit: 255. + * + * PS: in case you ever want to raise this number, please be mindful that + * "amount of NewGRFs" in NetworkGameInfo is currently an uint8. */ -static const uint NETWORK_MAX_GRF_COUNT = 62; -static const uint NETWORK_MAX_GRF_COUNT_SHORT = 59; +static const uint NETWORK_MAX_GRF_COUNT = 255; /** * The number of landscapes in OpenTTD. @@ -91,6 +103,6 @@ static const uint NETWORK_MAX_GRF_COUNT_SHORT = 59; * there is a compile assertion to check that this NUM_LANDSCAPE is equal * to NETWORK_NUM_LANDSCAPES. */ -static const uint NETWORK_NUM_LANDSCAPES = 4; +static const uint NETWORK_NUM_LANDSCAPES = 4; #endif /* NETWORK_CORE_CONFIG_H */ diff --git a/src/network/core/game_info.cpp b/src/network/core/game_info.cpp index 54150ea357..294b552681 100644 --- a/src/network/core/game_info.cpp +++ b/src/network/core/game_info.cpp @@ -16,6 +16,8 @@ #include "../../date_func.h" #include "../../debug.h" #include "../../map_func.h" +#include "../../game/game.hpp" +#include "../../game/game_info.hpp" #include "../../settings_type.h" #include "../../string_func.h" #include "../../rev.h" @@ -149,7 +151,11 @@ void FillStaticNetworkServerGameInfo() */ const NetworkServerGameInfo *GetCurrentNetworkServerGameInfo() { - /* Client_on is used as global variable to keep track on the number of clients. */ + /* These variables are updated inside _network_game_info as if they are global variables: + * - clients_on + * - invite_code + * These don't need to be updated manually here. + */ _network_game_info.companies_on = (byte)Company::GetNumItems(); _network_game_info.spectators_on = NetworkSpectatorCount(); _network_game_info.game_date = _date; @@ -199,6 +205,11 @@ void SerializeNetworkGameInfo(Packet *p, const NetworkServerGameInfo *info) /* Update the documentation in game_info.h on changes * to the NetworkGameInfo wire-protocol! */ + /* NETWORK_GAME_INFO_VERSION = 5 */ + GameInfo *game_info = Game::GetInfo(); + p->Send_uint32(game_info == nullptr ? -1 : (uint32)game_info->GetVersion()); + p->Send_string(game_info == nullptr ? "" : game_info->GetName()); + /* NETWORK_GAME_INFO_VERSION = 4 */ { /* Only send the GRF Identification (GRF_ID and MD5 checksum) of @@ -218,12 +229,7 @@ void SerializeNetworkGameInfo(Packet *p, const NetworkServerGameInfo *info) uint index = 0; for (c = info->grfconfig; c != nullptr; c = c->next) { if (!HasBit(c->flags, GCF_STATIC)) { - if (index == NETWORK_MAX_GRF_COUNT - 1 && count > NETWORK_MAX_GRF_COUNT) { - /* Send fake GRF ID */ - - p->Send_uint32(0x56D2B000); - p->Send_binary((const char*) _out_of_band_grf_md5, 16); - } else if (index >= NETWORK_MAX_GRF_COUNT) { + if (index >= NETWORK_MAX_GRF_COUNT) { break; } else { SerializeGRFIdentifier(p, &c->ident); @@ -327,6 +333,12 @@ void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info) * to the NetworkGameInfo wire-protocol! */ switch (game_info_version) { + case 5: { + info->gamescript_version = (int)p->Recv_uint32(); + info->gamescript_name = p->Recv_string(NETWORK_NAME_LENGTH); + FALLTHROUGH; + } + case 4: { GRFConfig **dst = &info->grfconfig; uint i; diff --git a/src/network/core/game_info.h b/src/network/core/game_info.h index 4e5de52169..ead7e0bbce 100644 --- a/src/network/core/game_info.h +++ b/src/network/core/game_info.h @@ -76,6 +76,8 @@ struct NetworkServerGameInfo { byte spectators_on; ///< How many spectators do we have? byte spectators_max; ///< Max spectators allowed on server byte landscape; ///< The used landscape + int gamescript_version; ///< Version of the gamescript. + std::string gamescript_name; ///< Name of the gamescript. }; /** diff --git a/src/network/core/os_abstraction.cpp b/src/network/core/os_abstraction.cpp index b75c8e207e..205e107172 100644 --- a/src/network/core/os_abstraction.cpp +++ b/src/network/core/os_abstraction.cpp @@ -180,6 +180,23 @@ bool SetNoDelay(SOCKET d) #endif } +/** + * Try to set the socket to reuse ports. + * @param d The socket to reuse ports on. + * @return True if disabling the delaying succeeded, otherwise false. + */ +bool SetReusePort(SOCKET d) +{ +#ifdef _WIN32 + /* Windows has no SO_REUSEPORT, but for our usecases SO_REUSEADDR does the same job. */ + int reuse_port = 1; + return setsockopt(d, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse_port, sizeof(reuse_port)) == 0; +#else + int reuse_port = 1; + return setsockopt(d, SOL_SOCKET, SO_REUSEPORT, &reuse_port, sizeof(reuse_port)) == 0; +#endif +} + /** * Try to shutdown the socket in one or both directions. * @param d The socket to disable the delaying for. diff --git a/src/network/core/os_abstraction.h b/src/network/core/os_abstraction.h index 9f812ee459..63d19a8597 100644 --- a/src/network/core/os_abstraction.h +++ b/src/network/core/os_abstraction.h @@ -215,6 +215,7 @@ static inline socklen_t FixAddrLenForEmscripten(struct sockaddr_storage &address bool SetNonBlocking(SOCKET d); bool SetBlocking(SOCKET d); bool SetNoDelay(SOCKET d); +bool SetReusePort(SOCKET d); bool ShutdownSocket(SOCKET d, bool read, bool write, uint linger_timeout); NetworkError GetSocketError(SOCKET d); diff --git a/src/network/core/tcp.h b/src/network/core/tcp.h index 035da4361f..3875f69d16 100644 --- a/src/network/core/tcp.h +++ b/src/network/core/tcp.h @@ -95,10 +95,12 @@ private: RESOLVING, ///< The hostname is being resolved (threaded). FAILURE, ///< Resolving failed. CONNECTING, ///< We are currently connecting. + CONNECTED, ///< The connection is established. }; std::thread resolve_thread; ///< Thread used during resolving. std::atomic status = Status::INIT; ///< The current status of the connecter. + std::atomic killed = false; ///< Whether this connecter is marked as killed. addrinfo *ai = nullptr; ///< getaddrinfo() allocated linked-list of resolved addresses. std::vector addresses; ///< Addresses we can connect to. @@ -109,17 +111,24 @@ private: std::chrono::steady_clock::time_point last_attempt; ///< Time we last tried to connect. std::string connection_string; ///< Current address we are connecting to (before resolving). + NetworkAddress bind_address; ///< Address we're binding to, if any. + int family = AF_UNSPEC; ///< Family we are using to connect with. void Resolve(); void OnResolved(addrinfo *ai); bool TryNextAddress(); void Connect(addrinfo *address); - bool CheckActivity(); + virtual bool CheckActivity(); + + /* We do not want any other derived classes from this class being able to + * access these private members, but it is okay for TCPServerConnecter. */ + friend class TCPServerConnecter; static void ResolveThunk(TCPConnecter *connecter); public: - TCPConnecter(const std::string &connection_string, uint16 default_port); + TCPConnecter() {}; + TCPConnecter(const std::string &connection_string, uint16 default_port, NetworkAddress bind_address = {}, int family = AF_UNSPEC); virtual ~TCPConnecter(); /** @@ -133,8 +142,25 @@ public: */ virtual void OnFailure() {} + void Kill(); + static void CheckCallbacks(); static void KillAll(); }; +class TCPServerConnecter : public TCPConnecter { +private: + SOCKET socket = INVALID_SOCKET; ///< The socket when a connection is established. + + bool CheckActivity() override; + +public: + ServerAddress server_address; ///< Address we are connecting to. + + TCPServerConnecter(const std::string &connection_string, uint16 default_port); + + void SetConnected(SOCKET sock); + void SetFailure(); +}; + #endif /* NETWORK_CORE_TCP_H */ diff --git a/src/network/core/tcp_connect.cpp b/src/network/core/tcp_connect.cpp index a2a1438e97..2cbecdab49 100644 --- a/src/network/core/tcp_connect.cpp +++ b/src/network/core/tcp_connect.cpp @@ -13,6 +13,7 @@ #include "../../thread.h" #include "tcp.h" +#include "../network_coordinator.h" #include "../network_internal.h" #include @@ -23,16 +24,45 @@ static std::vector _tcp_connecters; /** - * Create a new connecter for the given address - * @param connection_string the address to connect to + * Create a new connecter for the given address. + * @param connection_string The address to connect to. + * @param default_port If not indicated in connection_string, what port to use. + * @param bind_address The local bind address to use. Defaults to letting the OS find one. */ -TCPConnecter::TCPConnecter(const std::string &connection_string, uint16 default_port) +TCPConnecter::TCPConnecter(const std::string &connection_string, uint16 default_port, NetworkAddress bind_address, int family) : + bind_address(bind_address), + family(family) { this->connection_string = NormalizeConnectionString(connection_string, default_port); _tcp_connecters.push_back(this); } +/** + * Create a new connecter for the server. + * @param connection_string The address to connect to. + * @param default_port If not indicated in connection_string, what port to use. + */ +TCPServerConnecter::TCPServerConnecter(const std::string &connection_string, uint16 default_port) : + server_address(ServerAddress::Parse(connection_string, default_port)) +{ + switch (this->server_address.type) { + case SERVER_ADDRESS_DIRECT: + this->connection_string = this->server_address.connection_string; + break; + + case SERVER_ADDRESS_INVITE_CODE: + this->status = Status::CONNECTING; + _network_coordinator_client.ConnectToServer(this->server_address.connection_string, this); + break; + + default: + NOT_REACHED(); + } + + _tcp_connecters.push_back(this); +} + TCPConnecter::~TCPConnecter() { if (this->resolve_thread.joinable()) { @@ -48,6 +78,16 @@ TCPConnecter::~TCPConnecter() if (this->ai != nullptr) freeaddrinfo(this->ai); } +/** + * Kill this connecter. + * It will abort as soon as it can and not call any of the callbacks. + */ +void TCPConnecter::Kill() +{ + /* Delay the removing of the socket till the next CheckActivity(). */ + this->killed = true; +} + /** * Start a connection to the indicated address. * @param address The address to connection to. @@ -60,6 +100,18 @@ void TCPConnecter::Connect(addrinfo *address) return; } + if (!SetReusePort(sock)) { + DEBUG(net, 0, "Setting reuse-port mode failed: %s", NetworkError::GetLast().AsString()); + } + + if (this->bind_address.GetPort() > 0) { + if (bind(sock, (const sockaddr *)this->bind_address.GetAddress(), this->bind_address.GetAddressLength()) != 0) { + DEBUG(net, 1, "Could not bind socket on %s: %s", NetworkAddressDumper().GetAddressAsString(&(this->bind_address)), NetworkError::GetLast().AsString()); + closesocket(sock); + return; + } + } + if (!SetNoDelay(sock)) { DEBUG(net, 1, "Setting TCP_NODELAY failed: %s", NetworkError::GetLast().AsString()); } @@ -123,6 +175,9 @@ void TCPConnecter::OnResolved(addrinfo *ai) /* Convert the addrinfo into NetworkAddresses. */ for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) { + /* Skip entries if the family is set and it is not matching. */ + if (this->family != AF_UNSPEC && this->family != runp->ai_family) continue; + if (resort) { if (runp->ai_family == AF_INET6) { addresses_ipv6.emplace_back(runp); @@ -219,7 +274,9 @@ void TCPConnecter::Resolve() */ bool TCPConnecter::CheckActivity() { - switch (this->status.load()) { + if (this->killed) return true; + + switch (this->status) { case Status::INIT: /* Start the thread delayed, so the vtable is loaded. This allows classes * to overload functions used by Resolve() (in case threading is disabled). */ @@ -246,6 +303,7 @@ bool TCPConnecter::CheckActivity() return true; case Status::CONNECTING: + case Status::CONNECTED: break; } @@ -344,9 +402,63 @@ bool TCPConnecter::CheckActivity() } this->OnConnect(connected_socket); + this->status = Status::CONNECTED; return true; } +/** + * Check if there was activity for this connecter. + * @return True iff the TCPConnecter is done and can be cleaned up. + */ +bool TCPServerConnecter::CheckActivity() +{ + if (this->killed) return true; + + switch (this->server_address.type) { + case SERVER_ADDRESS_DIRECT: + return TCPConnecter::CheckActivity(); + + case SERVER_ADDRESS_INVITE_CODE: + /* Check if a result has come in. */ + switch (this->status) { + case Status::FAILURE: + this->OnFailure(); + return true; + + case Status::CONNECTED: + this->OnConnect(this->socket); + return true; + + default: + break; + } + + return false; + + default: + NOT_REACHED(); + } +} + +/** + * The connection was successfully established. + * This socket is fully setup and ready to send/recv game protocol packets. + * @param sock The socket of the established connection. + */ +void TCPServerConnecter::SetConnected(SOCKET sock) +{ + this->socket = sock; + this->status = Status::CONNECTED; +} + +/** + * The connection couldn't be established. + */ +void TCPServerConnecter::SetFailure() +{ + this->status = Status::FAILURE; +} + /** * Check whether we need to call the callback, i.e. whether we * have connected or aborted and call the appropriate callback diff --git a/src/network/core/tcp_coordinator.cpp b/src/network/core/tcp_coordinator.cpp new file mode 100644 index 0000000000..ba6d10792e --- /dev/null +++ b/src/network/core/tcp_coordinator.cpp @@ -0,0 +1,101 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file tcp_coordinator.cpp Basic functions to receive and send Game Coordinator packets. + */ + +#include "../../stdafx.h" +#include "../../date_func.h" +#include "../../debug.h" +#include "tcp_coordinator.h" + +#include "../../safeguards.h" + +/** + * Handle the given packet, i.e. pass it to the right. + * parser receive command. + * @param p The packet to handle. + * @return True iff we should immediately handle further packets. + */ +bool NetworkCoordinatorSocketHandler::HandlePacket(Packet *p) +{ + PacketCoordinatorType type = (PacketCoordinatorType)p->Recv_uint8(); + + switch (type) { + case PACKET_COORDINATOR_GC_ERROR: return this->Receive_GC_ERROR(p); + case PACKET_COORDINATOR_SERVER_REGISTER: return this->Receive_SERVER_REGISTER(p); + case PACKET_COORDINATOR_GC_REGISTER_ACK: return this->Receive_GC_REGISTER_ACK(p); + case PACKET_COORDINATOR_SERVER_UPDATE: return this->Receive_SERVER_UPDATE(p); + case PACKET_COORDINATOR_CLIENT_LISTING: return this->Receive_CLIENT_LISTING(p); + case PACKET_COORDINATOR_GC_LISTING: return this->Receive_GC_LISTING(p); + case PACKET_COORDINATOR_CLIENT_CONNECT: return this->Receive_CLIENT_CONNECT(p); + case PACKET_COORDINATOR_GC_CONNECTING: return this->Receive_GC_CONNECTING(p); + case PACKET_COORDINATOR_SERCLI_CONNECT_FAILED: return this->Receive_SERCLI_CONNECT_FAILED(p); + case PACKET_COORDINATOR_GC_CONNECT_FAILED: return this->Receive_GC_CONNECT_FAILED(p); + case PACKET_COORDINATOR_CLIENT_CONNECTED: return this->Receive_CLIENT_CONNECTED(p); + case PACKET_COORDINATOR_GC_DIRECT_CONNECT: return this->Receive_GC_DIRECT_CONNECT(p); + case PACKET_COORDINATOR_GC_STUN_REQUEST: return this->Receive_GC_STUN_REQUEST(p); + case PACKET_COORDINATOR_SERCLI_STUN_RESULT: return this->Receive_SERCLI_STUN_RESULT(p); + case PACKET_COORDINATOR_GC_STUN_CONNECT: return this->Receive_GC_STUN_CONNECT(p); + + default: + DEBUG(net, 0, "[tcp/coordinator] Received invalid packet type %u", type); + return false; + } +} + +/** + * Receive a packet at TCP level. + * @return Whether at least one packet was received. + */ +bool NetworkCoordinatorSocketHandler::ReceivePackets() +{ + /* + * We read only a few of the packets. This allows the GUI to update when + * a large set of servers is being received. Otherwise the interface + * "hangs" while the game is updating the server-list. + * + * What arbitrary number to choose is the ultimate question though. + */ + std::unique_ptr p; + static const int MAX_PACKETS_TO_RECEIVE = 42; + int i = MAX_PACKETS_TO_RECEIVE; + while (--i != 0 && (p = this->ReceivePacket()) != nullptr) { + bool cont = this->HandlePacket(p.get()); + if (!cont) return true; + } + + return i != MAX_PACKETS_TO_RECEIVE - 1; +} + +/** + * Helper for logging receiving invalid packets. + * @param type The received packet type. + * @return Always false, as it's an error. + */ +bool NetworkCoordinatorSocketHandler::ReceiveInvalidPacket(PacketCoordinatorType type) +{ + DEBUG(net, 0, "[tcp/coordinator] Received illegal packet type %u", type); + return false; +} + +bool NetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_ERROR); } +bool NetworkCoordinatorSocketHandler::Receive_SERVER_REGISTER(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_REGISTER); } +bool NetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_REGISTER_ACK); } +bool NetworkCoordinatorSocketHandler::Receive_SERVER_UPDATE(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_UPDATE); } +bool NetworkCoordinatorSocketHandler::Receive_CLIENT_LISTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_LISTING); } +bool NetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_LISTING); } +bool NetworkCoordinatorSocketHandler::Receive_CLIENT_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_CONNECT); } +bool NetworkCoordinatorSocketHandler::Receive_GC_CONNECTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_CONNECTING); } +bool NetworkCoordinatorSocketHandler::Receive_SERCLI_CONNECT_FAILED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERCLI_CONNECT_FAILED); } +bool NetworkCoordinatorSocketHandler::Receive_GC_CONNECT_FAILED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_CONNECT_FAILED); } +bool NetworkCoordinatorSocketHandler::Receive_CLIENT_CONNECTED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_CONNECTED); } +bool NetworkCoordinatorSocketHandler::Receive_GC_DIRECT_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_DIRECT_CONNECT); } +bool NetworkCoordinatorSocketHandler::Receive_GC_STUN_REQUEST(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_STUN_REQUEST); } +bool NetworkCoordinatorSocketHandler::Receive_SERCLI_STUN_RESULT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERCLI_STUN_RESULT); } +bool NetworkCoordinatorSocketHandler::Receive_GC_STUN_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_STUN_CONNECT); } diff --git a/src/network/core/tcp_coordinator.h b/src/network/core/tcp_coordinator.h new file mode 100644 index 0000000000..40502e7e3f --- /dev/null +++ b/src/network/core/tcp_coordinator.h @@ -0,0 +1,277 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file tcp_coordinator.h Basic functions to receive and send TCP packets to/from the Game Coordinator server. + */ + +#ifndef NETWORK_CORE_TCP_COORDINATOR_H +#define NETWORK_CORE_TCP_COORDINATOR_H + +#include "os_abstraction.h" +#include "tcp.h" +#include "packet.h" +#include "game_info.h" + +/** + * Enum with all types of TCP Game Coordinator packets. The order MUST not be changed. + * + * GC -> packets from Game Coordinator to either Client or Server. + * SERVER -> packets from Server to Game Coordinator. + * CLIENT -> packets from Client to Game Coordinator. + * SERCLI -> packets from either the Server or Client to Game Coordinator. + **/ +enum PacketCoordinatorType { + PACKET_COORDINATOR_GC_ERROR, ///< Game Coordinator indicates there was an error. + PACKET_COORDINATOR_SERVER_REGISTER, ///< Server registration. + PACKET_COORDINATOR_GC_REGISTER_ACK, ///< Game Coordinator accepts the registration. + PACKET_COORDINATOR_SERVER_UPDATE, ///< Server sends an set intervals an update of the server. + PACKET_COORDINATOR_CLIENT_LISTING, ///< Client is requesting a listing of all public servers. + PACKET_COORDINATOR_GC_LISTING, ///< Game Coordinator returns a listing of all public servers. + PACKET_COORDINATOR_CLIENT_CONNECT, ///< Client wants to connect to a server based on an invite code. + PACKET_COORDINATOR_GC_CONNECTING, ///< Game Coordinator informs the client of the token assigned to the connection attempt. + PACKET_COORDINATOR_SERCLI_CONNECT_FAILED, ///< Client/server tells the Game Coordinator the current connection attempt failed. + PACKET_COORDINATOR_GC_CONNECT_FAILED, ///< Game Coordinator informs client/server it has given up on the connection attempt. + PACKET_COORDINATOR_CLIENT_CONNECTED, ///< Client informs the Game Coordinator the connection with the server is established. + PACKET_COORDINATOR_GC_DIRECT_CONNECT, ///< Game Coordinator tells client to directly connect to the hostname:port of the server. + PACKET_COORDINATOR_GC_STUN_REQUEST, ///< Game Coordinator tells client/server to initiate a STUN request. + PACKET_COORDINATOR_SERCLI_STUN_RESULT, ///< Client/server informs the Game Coordinator of the result of the STUN request. + PACKET_COORDINATOR_GC_STUN_CONNECT, ///< Game Coordinator tells client/server to connect() reusing the STUN local address. + PACKET_COORDINATOR_END, ///< Must ALWAYS be on the end of this list!! (period) +}; + +/** + * The type of connection the Game Coordinator can detect we have. + */ +enum ConnectionType { + CONNECTION_TYPE_UNKNOWN, ///< The Game Coordinator hasn't informed us yet what type of connection we have. + CONNECTION_TYPE_ISOLATED, ///< The Game Coordinator failed to find a way to connect to your server. Nobody will be able to join. + CONNECTION_TYPE_DIRECT, ///< The Game Coordinator can directly connect to your server. + CONNECTION_TYPE_STUN, ///< The Game Coordinator can connect to your server via a STUN request. +}; + +/** + * The type of error from the Game Coordinator. + */ +enum NetworkCoordinatorErrorType { + NETWORK_COORDINATOR_ERROR_UNKNOWN, ///< There was an unknown error. + NETWORK_COORDINATOR_ERROR_REGISTRATION_FAILED, ///< Your request for registration failed. + NETWORK_COORDINATOR_ERROR_INVALID_INVITE_CODE, ///< The invite code given is invalid. +}; + +/** Base socket handler for all Game Coordinator TCP sockets. */ +class NetworkCoordinatorSocketHandler : public NetworkTCPSocketHandler { +protected: + bool ReceiveInvalidPacket(PacketCoordinatorType type); + + /** + * Game Coordinator indicates there was an error. This can either be a + * permanent error causing the connection to be dropped, or in response + * to a request that is invalid. + * + * uint8 Type of error (see NetworkCoordinatorErrorType). + * string Details of the error. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_GC_ERROR(Packet *p); + + /** + * Server is starting a multiplayer game and wants to let the + * Game Coordinator know. + * + * uint8 Game Coordinator protocol version. + * uint8 Type of game (see ServerGameType). + * uint16 Local port of the server. + * string Invite code the server wants to use (can be empty; coordinator will assign a new invite code). + * string Secret that belongs to the invite code (empty if invite code is empty). + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_SERVER_REGISTER(Packet *p); + + /** + * Game Coordinator acknowledges the registration. + * + * string Invite code that can be used to join this server. + * string Secret that belongs to the invite code (only needed if reusing the invite code on next SERVER_REGISTER). + * uint8 Type of connection was detected (see ConnectionType). + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_GC_REGISTER_ACK(Packet *p); + + /** + * Send an update of the current state of the server to the Game Coordinator. + * + * uint8 Game Coordinator protocol version. + * Serialized NetworkGameInfo. See game_info.hpp for details. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_SERVER_UPDATE(Packet *p); + + /** + * Client requests a list of all public servers. + * + * uint8 Game Coordinator protocol version. + * uint8 Game-info version used by this client. + * string Revision of the client. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_CLIENT_LISTING(Packet *p); + + /** + * Game Coordinator replies with a list of all public servers. Multiple + * of these packets are received after a request till all servers are + * sent over. Last packet will have server count of 0. + * + * uint16 Amount of public servers in this packet. + * For each server: + * string Connection string for this server. + * Serialized NetworkGameInfo. See game_info.hpp for details. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_GC_LISTING(Packet *p); + + /** + * Client wants to connect to a Server. + * + * uint8 Game Coordinator protocol version. + * string Invite code of the Server to join. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_CLIENT_CONNECT(Packet *p); + + /** + * Game Coordinator informs the Client under what token it will start the + * attempt to connect the Server and Client together. + * + * string Token to track the current connect request. + * string Invite code of the Server to join. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_GC_CONNECTING(Packet *p); + + /** + * Client or Server failed to connect to the remote side. + * + * uint8 Game Coordinator protocol version. + * string Token to track the current connect request. + * uint8 Tracking number to track current connect request. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_SERCLI_CONNECT_FAILED(Packet *p); + + /** + * Game Coordinator informs the Client that it failed to find a way to + * connect the Client to the Server. Any open connections for this token + * should be closed now. + * + * string Token to track the current connect request. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_GC_CONNECT_FAILED(Packet *p); + + /** + * Client informs the Game Coordinator the connection with the Server is + * established. The Client will disconnect from the Game Coordinator next. + * + * uint8 Game Coordinator protocol version. + * string Token to track the current connect request. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_CLIENT_CONNECTED(Packet *p); + + /** + * Game Coordinator requests that the Client makes a direct connection to + * the indicated peer, which is a Server. + * + * string Token to track the current connect request. + * uint8 Tracking number to track current connect request. + * string Hostname of the peer. + * uint16 Port of the peer. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_GC_DIRECT_CONNECT(Packet *p); + + /** + * Game Coordinator requests the client/server to do a STUN request to the + * STUN server. Important is to remember the local port these STUN requests + * are sent from, as this will be needed for later conenctions too. + * The client/server should do multiple STUN requests for every available + * interface that connects to the Internet (e.g., once for IPv4 and once + * for IPv6). + * + * string Token to track the current connect request. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_GC_STUN_REQUEST(Packet *p); + + /** + * Client/server informs the Game Coordinator the result of a STUN request. + * + * uint8 Game Coordinator protocol version. + * string Token to track the current connect request. + * uint8 Interface number, as given during STUN request. + * bool Whether the STUN connection was successful. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_SERCLI_STUN_RESULT(Packet *p); + + /** + * Game Coordinator informs the client/server of its STUN peer (the host:ip + * of the other side). It should start a connect() to this peer ASAP with + * the local address as used with the STUN request. + * + * string Token to track the current connect request. + * uint8 Tracking number to track current connect request. + * uint8 Interface number, as given during STUN request. + * string Host of the peer. + * uint16 Port of the peer. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_GC_STUN_CONNECT(Packet *p); + + bool HandlePacket(Packet *p); +public: + /** + * Create a new cs socket handler for a given cs. + * @param s The socket we are connected with. + */ + NetworkCoordinatorSocketHandler(SOCKET s = INVALID_SOCKET) : NetworkTCPSocketHandler(s) {} + + bool ReceivePackets(); +}; + +#endif /* NETWORK_CORE_TCP_COORDINATOR_H */ diff --git a/src/network/core/tcp_listen.h b/src/network/core/tcp_listen.h index 40fc84efee..47179462de 100644 --- a/src/network/core/tcp_listen.h +++ b/src/network/core/tcp_listen.h @@ -30,6 +30,42 @@ class TCPListenHandler { static SocketList sockets; public: + static bool ValidateClient(SOCKET s, NetworkAddress &address) + { + /* Check if the client is banned. */ + for (const auto &entry : _network_ban_list) { + if (address.IsInNetmask(entry.c_str())) { + Packet p(Tban_packet); + p.PrepareToSend(); + + DEBUG(net, 2, "[%s] Banned ip tried to join (%s), refused", Tsocket::GetName(), entry.c_str()); + + if (p.TransferOut(send, s, 0) < 0) { + DEBUG(net, 0, "[%s] send failed: %s", Tsocket::GetName(), NetworkError::GetLast().AsString()); + } + closesocket(s); + return false; + } + } + + /* Can we handle a new client? */ + if (!Tsocket::AllowConnection()) { + /* No more clients allowed? + * Send to the client that we are full! */ + Packet p(Tfull_packet); + p.PrepareToSend(); + + if (p.TransferOut(send, s, 0) < 0) { + DEBUG(net, 0, "[%s] send failed: %s", Tsocket::GetName(), NetworkError::GetLast().AsString()); + } + closesocket(s); + + return false; + } + + return true; + } + /** * Accepts clients from the sockets. * @param ls Socket to accept clients from. @@ -53,41 +89,7 @@ public: SetNoDelay(s); // XXX error handling? - /* Check if the client is banned */ - bool banned = false; - for (const auto &entry : _network_ban_list) { - banned = address.IsInNetmask(entry.c_str()); - if (banned) { - Packet p(Tban_packet); - p.PrepareToSend(); - - DEBUG(net, 2, "[%s] Banned ip tried to join (%s), refused", Tsocket::GetName(), entry.c_str()); - - if (p.TransferOut(send, s, 0) < 0) { - DEBUG(net, 0, "[%s] send failed: %s", Tsocket::GetName(), NetworkError::GetLast().AsString()); - } - closesocket(s); - break; - } - } - /* If this client is banned, continue with next client */ - if (banned) continue; - - /* Can we handle a new client? */ - if (!Tsocket::AllowConnection()) { - /* no more clients allowed? - * Send to the client that we are full! */ - Packet p(Tfull_packet); - p.PrepareToSend(); - - if (p.TransferOut(send, s, 0) < 0) { - DEBUG(net, 0, "[%s] send failed: %s", Tsocket::GetName(), NetworkError::GetLast().AsString()); - } - closesocket(s); - - continue; - } - + if (!Tsocket::ValidateClient(s, address)) continue; Tsocket::AcceptConnection(s, address); } } diff --git a/src/network/core/tcp_stun.cpp b/src/network/core/tcp_stun.cpp new file mode 100644 index 0000000000..dc565f0c0d --- /dev/null +++ b/src/network/core/tcp_stun.cpp @@ -0,0 +1,29 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file tcp_stun.cpp Basic functions to receive and send STUN packets. + */ + +#include "../../stdafx.h" +#include "../../debug.h" +#include "tcp_stun.h" + +#include "../../safeguards.h" + +/** + * Helper for logging receiving invalid packets. + * @param type The received packet type. + * @return Always false, as it's an error. + */ +bool NetworkStunSocketHandler::ReceiveInvalidPacket(PacketStunType type) +{ + DEBUG(net, 0, "[tcp/stun] Received illegal packet type %u", type); + return false; +} + +bool NetworkStunSocketHandler::Receive_SERCLI_STUN(Packet *p) { return this->ReceiveInvalidPacket(PACKET_STUN_SERCLI_STUN); } diff --git a/src/network/core/tcp_stun.h b/src/network/core/tcp_stun.h new file mode 100644 index 0000000000..e96a97ef36 --- /dev/null +++ b/src/network/core/tcp_stun.h @@ -0,0 +1,53 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file tcp_stun.h Basic functions to receive and send TCP packets to/from the STUN server. + */ + +#ifndef NETWORK_CORE_TCP_STUN_H +#define NETWORK_CORE_TCP_STUN_H + +#include "os_abstraction.h" +#include "tcp.h" +#include "packet.h" + +/** Enum with all types of TCP STUN packets. The order MUST not be changed. **/ +enum PacketStunType { + PACKET_STUN_SERCLI_STUN, ///< Send a STUN request to the STUN server. + PACKET_STUN_END, ///< Must ALWAYS be on the end of this list!! (period) +}; + +/** Base socket handler for all STUN TCP sockets. */ +class NetworkStunSocketHandler : public NetworkTCPSocketHandler { +protected: + bool ReceiveInvalidPacket(PacketStunType type); + + /** + * Send a STUN request to the STUN server letting the Game Coordinator know + * what our actually public IP:port is. + * + * uint8 Game Coordinator protocol version. + * string Token to track the current STUN request. + * uint8 Which interface number this is (for example, IPv4 or IPv6). + * The Game Coordinator relays this number back in later packets. + * + * @param p The packet that was just received. + * @return True upon success, otherwise false. + */ + virtual bool Receive_SERCLI_STUN(Packet *p); + +public: + /** + * Create a new cs socket handler for a given cs. + * @param s the socket we are connected with. + * @param address IP etc. of the client. + */ + NetworkStunSocketHandler(SOCKET s = INVALID_SOCKET) : NetworkTCPSocketHandler(s) {} +}; + +#endif /* NETWORK_CORE_TCP_STUN_H */ diff --git a/src/network/core/udp.cpp b/src/network/core/udp.cpp index 52caa7af09..a67c787502 100644 --- a/src/network/core/udp.cpp +++ b/src/network/core/udp.cpp @@ -194,16 +194,6 @@ void NetworkUDPSocketHandler::HandleUDPPacket(Packet *p, NetworkAddress *client_ switch (this->HasClientQuit() ? PACKET_UDP_END : type) { case PACKET_UDP_CLIENT_FIND_SERVER: this->Receive_CLIENT_FIND_SERVER(p, client_addr); break; case PACKET_UDP_SERVER_RESPONSE: this->Receive_SERVER_RESPONSE(p, client_addr); break; - case PACKET_UDP_CLIENT_DETAIL_INFO: this->Receive_CLIENT_DETAIL_INFO(p, client_addr); break; - case PACKET_UDP_SERVER_DETAIL_INFO: this->Receive_SERVER_DETAIL_INFO(p, client_addr); break; - case PACKET_UDP_SERVER_REGISTER: this->Receive_SERVER_REGISTER(p, client_addr); break; - case PACKET_UDP_MASTER_ACK_REGISTER: this->Receive_MASTER_ACK_REGISTER(p, client_addr); break; - case PACKET_UDP_CLIENT_GET_LIST: this->Receive_CLIENT_GET_LIST(p, client_addr); break; - case PACKET_UDP_MASTER_RESPONSE_LIST: this->Receive_MASTER_RESPONSE_LIST(p, client_addr); break; - case PACKET_UDP_SERVER_UNREGISTER: this->Receive_SERVER_UNREGISTER(p, client_addr); break; - case PACKET_UDP_CLIENT_GET_NEWGRFS: this->Receive_CLIENT_GET_NEWGRFS(p, client_addr); break; - case PACKET_UDP_SERVER_NEWGRFS: this->Receive_SERVER_NEWGRFS(p, client_addr); break; - case PACKET_UDP_MASTER_SESSION_KEY: this->Receive_MASTER_SESSION_KEY(p, client_addr); break; case PACKET_UDP_EX_MULTI: this->Receive_EX_MULTI(p, client_addr); break; case PACKET_UDP_EX_SERVER_RESPONSE: this->Receive_EX_SERVER_RESPONSE(p, client_addr); break; @@ -301,13 +291,3 @@ void NetworkUDPSocketHandler::ReceiveInvalidPacket(PacketUDPType type, NetworkAd void NetworkUDPSocketHandler::Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_FIND_SERVER, client_addr); } void NetworkUDPSocketHandler::Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_RESPONSE, client_addr); } void NetworkUDPSocketHandler::Receive_EX_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_EX_SERVER_RESPONSE, client_addr); } -void NetworkUDPSocketHandler::Receive_CLIENT_DETAIL_INFO(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_DETAIL_INFO, client_addr); } -void NetworkUDPSocketHandler::Receive_SERVER_DETAIL_INFO(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_DETAIL_INFO, client_addr); } -void NetworkUDPSocketHandler::Receive_SERVER_REGISTER(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_REGISTER, client_addr); } -void NetworkUDPSocketHandler::Receive_MASTER_ACK_REGISTER(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_MASTER_ACK_REGISTER, client_addr); } -void NetworkUDPSocketHandler::Receive_CLIENT_GET_LIST(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_GET_LIST, client_addr); } -void NetworkUDPSocketHandler::Receive_MASTER_RESPONSE_LIST(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_MASTER_RESPONSE_LIST, client_addr); } -void NetworkUDPSocketHandler::Receive_SERVER_UNREGISTER(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_UNREGISTER, client_addr); } -void NetworkUDPSocketHandler::Receive_CLIENT_GET_NEWGRFS(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_GET_NEWGRFS, client_addr); } -void NetworkUDPSocketHandler::Receive_SERVER_NEWGRFS(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_NEWGRFS, client_addr); } -void NetworkUDPSocketHandler::Receive_MASTER_SESSION_KEY(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_MASTER_SESSION_KEY, client_addr); } diff --git a/src/network/core/udp.h b/src/network/core/udp.h index 2270434392..dddc6514e9 100644 --- a/src/network/core/udp.h +++ b/src/network/core/udp.h @@ -23,31 +23,12 @@ enum PacketUDPType { PACKET_UDP_CLIENT_FIND_SERVER, ///< Queries a game server for game information PACKET_UDP_SERVER_RESPONSE, ///< Reply of the game server with game information - PACKET_UDP_CLIENT_DETAIL_INFO, ///< Queries a game server about details of the game, such as companies - PACKET_UDP_SERVER_DETAIL_INFO, ///< Reply of the game server about details of the game, such as companies - PACKET_UDP_SERVER_REGISTER, ///< Packet to register itself to the master server - PACKET_UDP_MASTER_ACK_REGISTER, ///< Packet indicating registration has succeeded - PACKET_UDP_CLIENT_GET_LIST, ///< Request for serverlist from master server - PACKET_UDP_MASTER_RESPONSE_LIST, ///< Response from master server with server ip's + port's - PACKET_UDP_SERVER_UNREGISTER, ///< Request to be removed from the server-list - PACKET_UDP_CLIENT_GET_NEWGRFS, ///< Requests the name for a list of GRFs (GRF_ID and MD5) - PACKET_UDP_SERVER_NEWGRFS, ///< Sends the list of NewGRF's requested. - PACKET_UDP_MASTER_SESSION_KEY, ///< Sends a fresh session key to the client PACKET_UDP_END, ///< Must ALWAYS be the last non-extended item in the list!! (period) PACKET_UDP_EX_MULTI = 128, ///< Extended/multi packet type PACKET_UDP_EX_SERVER_RESPONSE, ///< Reply of the game server with extended game information }; -/** The types of server lists we can get */ -enum ServerListType { - SLT_IPv4 = 0, ///< Get the IPv4 addresses - SLT_IPv6 = 1, ///< Get the IPv6 addresses - SLT_AUTODETECT, ///< Autodetect the type based on the connection - - SLT_END = SLT_AUTODETECT, ///< End of 'arrays' marker -}; - /** Base socket handler for all UDP sockets */ class NetworkUDPSocketHandler : public NetworkSocketHandler { protected: @@ -76,8 +57,7 @@ protected: virtual void Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr); /** - * Return of server information to the client. - * Serialized NetworkGameInfo. See game_info.h for details. + * Response to a query letting the client know we are here. * @param p The received packet. * @param client_addr The origin of the packet. */ @@ -85,120 +65,6 @@ protected: virtual void Receive_EX_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr); - /** - * Query for detailed information about companies. - * @param p The received packet. - * @param client_addr The origin of the packet. - */ - virtual void Receive_CLIENT_DETAIL_INFO(Packet *p, NetworkAddress *client_addr); - - /** - * Reply with detailed company information. - * uint8 Version of the packet. - * uint8 Number of companies. - * For each company: - * uint8 ID of the company. - * string Name of the company. - * uint32 Year the company was inaugurated. - * uint64 Value. - * uint64 Money. - * uint64 Income. - * uint16 Performance (last quarter). - * bool Company is password protected. - * uint16 Number of trains. - * uint16 Number of lorries. - * uint16 Number of busses. - * uint16 Number of planes. - * uint16 Number of ships. - * uint16 Number of train stations. - * uint16 Number of lorry stations. - * uint16 Number of bus stops. - * uint16 Number of airports and heliports. - * uint16 Number of harbours. - * bool Company is an AI. - * @param p The received packet. - * @param client_addr The origin of the packet. - */ - virtual void Receive_SERVER_DETAIL_INFO(Packet *p, NetworkAddress *client_addr); - - /** - * Registers the server to the master server. - * string The "welcome" message to root out other binary packets. - * uint8 Version of the protocol. - * uint16 The port to unregister. - * uint64 The session key. - * @param p The received packet. - * @param client_addr The origin of the packet. - */ - virtual void Receive_SERVER_REGISTER(Packet *p, NetworkAddress *client_addr); - - /** - * The master server acknowledges the registration. - * @param p The received packet. - * @param client_addr The origin of the packet. - */ - virtual void Receive_MASTER_ACK_REGISTER(Packet *p, NetworkAddress *client_addr); - - /** - * The client requests a list of servers. - * uint8 The protocol version. - * uint8 The type of server to look for: IPv4, IPv6 or based on the received packet. - * @param p The received packet. - * @param client_addr The origin of the packet. - */ - virtual void Receive_CLIENT_GET_LIST(Packet *p, NetworkAddress *client_addr); - - /** - * The server sends a list of servers. - * uint8 The protocol version. - * For each server: - * 4 or 16 bytes of IPv4 or IPv6 address. - * uint8 The port. - * @param p The received packet. - * @param client_addr The origin of the packet. - */ - virtual void Receive_MASTER_RESPONSE_LIST(Packet *p, NetworkAddress *client_addr); - - /** - * A server unregisters itself at the master server. - * uint8 Version of the protocol. - * uint16 The port to unregister. - * @param p The received packet. - * @param client_addr The origin of the packet. - */ - virtual void Receive_SERVER_UNREGISTER(Packet *p, NetworkAddress *client_addr); - - /** - * The client requests information about some NewGRFs. - * uint8 The number of NewGRFs information is requested about. - * For each NewGRF: - * uint32 The GRFID. - * 16 * uint8 MD5 checksum of the GRF. - * @param p The received packet. - * @param client_addr The origin of the packet. - */ - virtual void Receive_CLIENT_GET_NEWGRFS(Packet *p, NetworkAddress *client_addr); - - /** - * The server returns information about some NewGRFs. - * uint8 The number of NewGRFs information is requested about. - * For each NewGRF: - * uint32 The GRFID. - * 16 * uint8 MD5 checksum of the GRF. - * string The name of the NewGRF. - * @param p The received packet. - * @param client_addr The origin of the packet. - */ - virtual void Receive_SERVER_NEWGRFS(Packet *p, NetworkAddress *client_addr); - - /** - * The master server sends us a session key. - * uint64 The session key. - * @param p The received packet. - * @param client_addr The origin of the packet. - */ - virtual void Receive_MASTER_SESSION_KEY(Packet *p, NetworkAddress *client_addr); - void HandleUDPPacket(Packet *p, NetworkAddress *client_addr); virtual void Receive_EX_MULTI(Packet *p, NetworkAddress *client_addr); diff --git a/src/network/network.cpp b/src/network/network.cpp index 8145b7220f..bd3dcb8ccd 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -19,6 +19,7 @@ #include "network_udp.h" #include "network_gamelist.h" #include "network_base.h" +#include "network_coordinator.h" #include "core/udp.h" #include "core/host.h" #include "network_gui.h" @@ -62,7 +63,6 @@ bool _network_settings_access; ///< Can this client change server settings? NetworkCompanyState *_network_company_states = nullptr; ///< Statistics about some companies. ClientID _network_own_client_id; ///< Our client identifier. ClientID _redirect_console_to_client; ///< If not invalid, redirect the console output to a client. -bool _network_need_advertise; ///< Whether we need to advertise. uint8 _network_reconnect; ///< Reconnect timeout StringList _network_bind_list; ///< The addresses to bind on. StringList _network_host_list; ///< The servers we know. @@ -478,6 +478,41 @@ static void CheckPauseOnJoin() CheckPauseHelper(NetworkHasJoiningClient(), PM_PAUSED_JOIN); } +/** + * Parse the company part ("#company" postfix) of a connecting string. + * @param connection_string The string with the connection data. + * @param company_id The company ID to set, if available. + * @return A std::string_view into the connection string without the company part. + */ +std::string_view ParseCompanyFromConnectionString(const std::string &connection_string, CompanyID *company_id) +{ + std::string_view ip = connection_string; + if (company_id == nullptr) return ip; + + size_t offset = ip.find_last_of('#'); + if (offset != std::string::npos) { + std::string_view company_string = ip.substr(offset + 1); + ip = ip.substr(0, offset); + + uint8 company_value; + auto [_, err] = std::from_chars(company_string.data(), company_string.data() + company_string.size(), company_value); + if (err == std::errc()) { + if (company_value != COMPANY_NEW_COMPANY && company_value != COMPANY_SPECTATOR) { + if (company_value > MAX_COMPANIES || company_value == 0) { + *company_id = COMPANY_SPECTATOR; + } else { + /* "#1" means the first company, which has index 0. */ + *company_id = (CompanyID)(company_value - 1); + } + } else { + *company_id = (CompanyID)company_value; + } + } + } + + return ip; +} + /** * Converts a string to ip/port/company * Format: IP:port#company @@ -495,29 +530,7 @@ static void CheckPauseOnJoin() */ std::string_view ParseFullConnectionString(const std::string &connection_string, uint16 &port, CompanyID *company_id) { - std::string_view ip = connection_string; - if (company_id != nullptr) { - size_t offset = ip.find_last_of('#'); - if (offset != std::string::npos) { - std::string_view company_string = ip.substr(offset + 1); - ip = ip.substr(0, offset); - - uint8 company_value; - auto [_, err] = std::from_chars(company_string.data(), company_string.data() + company_string.size(), company_value); - if (err == std::errc()) { - if (company_value != COMPANY_NEW_COMPANY && company_value != COMPANY_SPECTATOR) { - if (company_value > MAX_COMPANIES || company_value == 0) { - *company_id = COMPANY_SPECTATOR; - } else { - /* "#1" means the first company, which has index 0. */ - *company_id = (CompanyID)(company_value - 1); - } - } else { - *company_id = (CompanyID)company_value; - } - } - } - } + std::string_view ip = ParseCompanyFromConnectionString(connection_string, company_id); size_t port_offset = ip.find_last_of(':'); size_t ipv6_close = ip.find_last_of(']'); @@ -557,23 +570,6 @@ NetworkAddress ParseConnectionString(const std::string &connection_string, uint1 return NetworkAddress(ip, port); } -/** - * Convert a string containing either "hostname" or "hostname:ip" to a - * NetworkAddress, where the string can be postfixed with "#company" to - * indicate the requested company. - * - * @param connection_string The string to parse. - * @param default_port The default port to set port to if not in connection_string. - * @param company Pointer to the company variable to set iff indicted. - * @return A valid NetworkAddress of the parsed information. - */ -static NetworkAddress ParseGameConnectionString(const std::string &connection_string, uint16 default_port, CompanyID *company) -{ - uint16 port = default_port; - std::string_view ip = ParseFullConnectionString(connection_string, port, company); - return NetworkAddress(ip, port); -} - /** * Handle the accepting of a connection to the server. * @param s The socket of the new connection. @@ -617,9 +613,15 @@ void NetworkClose(bool close_admins) } ServerNetworkGameSocketHandler::CloseListeners(); ServerNetworkAdminSocketHandler::CloseListeners(); - } else if (MyClient::my_client != nullptr) { - MyClient::SendQuit(); - MyClient::my_client->CloseConnection(NETWORK_RECV_STATUS_CLIENT_QUIT); + + _network_coordinator_client.CloseConnection(); + } else { + if (MyClient::my_client != nullptr) { + MyClient::SendQuit(); + MyClient::my_client->CloseConnection(NETWORK_RECV_STATUS_CLIENT_QUIT); + } + + _network_coordinator_client.CloseAllTokens(); } TCPConnecter::KillAll(); @@ -639,7 +641,6 @@ void NetworkClose(bool close_admins) static void NetworkInitialize(bool close_admins = true) { InitializeNetworkPools(close_admins); - NetworkUDPInitialize(); _sync_frame = 0; _network_first_time = true; @@ -652,12 +653,12 @@ static void NetworkInitialize(bool close_admins = true) } /** Non blocking connection to query servers for their game info. */ -class TCPQueryConnecter : TCPConnecter { +class TCPQueryConnecter : TCPServerConnecter { private: std::string connection_string; public: - TCPQueryConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {} + TCPQueryConnecter(const std::string &connection_string) : TCPServerConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {} void OnFailure() override { @@ -689,12 +690,12 @@ void NetworkQueryServer(const std::string &connection_string) } /** Non blocking connection to query servers for their game and company info. */ -class TCPLobbyQueryConnecter : TCPConnecter { +class TCPLobbyQueryConnecter : TCPServerConnecter { private: std::string connection_string; public: - TCPLobbyQueryConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {} + TCPLobbyQueryConnecter(const std::string &connection_string) : TCPServerConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {} void OnFailure() override { @@ -729,9 +730,11 @@ void NetworkQueryLobbyServer(const std::string &connection_string) * the list. If you use this function, the games will be marked * as manually added. * @param connection_string The IP:port of the server to add. + * @param manually Whether the enter should be marked as manual added. + * @param never_expire Whether the entry can expire (removed when no longer found in the public listing). * @return The entry on the game list. */ -NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually) +NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually, bool never_expire) { if (connection_string.empty()) return nullptr; @@ -747,6 +750,7 @@ NetworkGameList *NetworkAddServer(const std::string &connection_string, bool man } if (manually) item->manually = true; + if (never_expire) item->version = INT32_MAX; return item; } @@ -781,12 +785,12 @@ void NetworkRebuildHostList() } /** Non blocking connection create to actually connect to servers */ -class TCPClientConnecter : TCPConnecter { +class TCPClientConnecter : TCPServerConnecter { private: std::string connection_string; public: - TCPClientConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {} + TCPClientConnecter(const std::string &connection_string) : TCPServerConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {} void OnFailure() override { @@ -822,7 +826,7 @@ public: bool NetworkClientConnectGame(const std::string &connection_string, CompanyID default_company, const std::string &join_server_password, const std::string &join_company_password) { CompanyID join_as = default_company; - std::string resolved_connection_string = ParseGameConnectionString(connection_string, NETWORK_DEFAULT_PORT, &join_as).GetAddressAsString(false); + std::string resolved_connection_string = ServerAddress::Parse(connection_string, NETWORK_DEFAULT_PORT, &join_as).connection_string; if (!_network_available) return false; if (!NetworkValidateOurClientName()) return false; @@ -932,6 +936,7 @@ bool NetworkServerStart() NetworkDisconnect(false, false); NetworkInitialize(false); + NetworkUDPInitialize(); DEBUG(net, 5, "Starting listeners for clients"); if (!ServerNetworkGameSocketHandler::Listen(_settings_client.network.server_port)) return false; @@ -959,15 +964,15 @@ bool NetworkServerStart() NetworkInitGameInfo(); + if (_settings_client.network.server_game_type != SERVER_GAME_TYPE_LOCAL) { + _network_coordinator_client.Register(); + } + /* execute server initialization script */ IConsoleCmdExec("exec scripts/on_server.scr 0"); /* if the server is dedicated ... add some other script */ if (_network_dedicated) IConsoleCmdExec("exec scripts/on_dedicated.scr 0"); - /* Try to register us to the master server */ - _network_need_advertise = true; - NetworkUDPAdvertise(); - /* welcome possibly still connected admins - this can only happen on a dedicated server. */ if (_network_dedicated) ServerNetworkAdminSocketHandler::WelcomeAll(); @@ -1016,8 +1021,6 @@ void NetworkDisconnect(bool blocking, bool close_admins) } } - if (_settings_client.network.server_advertise) NetworkUDPRemoveAdvertise(blocking); - DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN); NetworkClose(close_admins); @@ -1026,6 +1029,29 @@ void NetworkDisconnect(bool blocking, bool close_admins) NetworkUDPInitialize(); } +/** + * The setting server_game_type was updated; possibly we need to take some + * action. + */ +void NetworkUpdateServerGameType() +{ + if (!_networking) return; + + switch (_settings_client.network.server_game_type) { + case SERVER_GAME_TYPE_LOCAL: + _network_coordinator_client.CloseConnection(); + break; + + case SERVER_GAME_TYPE_INVITE_ONLY: + case SERVER_GAME_TYPE_PUBLIC: + _network_coordinator_client.Register(); + break; + + default: + NOT_REACHED(); + } +} + /** * Receives something from the network. * @return true if everything went fine, false when the connection got closed. @@ -1059,6 +1085,7 @@ static void NetworkSend() void NetworkBackgroundLoop() { _network_content_client.SendReceive(); + _network_coordinator_client.SendReceive(); TCPConnecter::CheckCallbacks(); NetworkHTTPSocketHandler::HTTPReceive(); @@ -1297,7 +1324,6 @@ void NetworkStartUp() /* Network is available */ _network_available = NetworkCoreInitialize(); _network_dedicated = false; - _network_need_advertise = true; /* Generate an server id when there is none yet */ if (_settings_client.network.network_id.empty()) NetworkGenerateServerId(); @@ -1305,6 +1331,7 @@ void NetworkStartUp() _network_game_info = {}; NetworkInitialize(); + NetworkUDPInitialize(); DEBUG(net, 3, "Network online, multiplayer available"); NetworkFindBroadcastIPs(&_broadcast_list); } @@ -1327,7 +1354,7 @@ extern "C" { void CDECL em_openttd_add_server(const char *connection_string) { - NetworkAddServer(connection_string, false); + NetworkAddServer(connection_string, false, true); } } diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 2fb7536ffd..7e2c167d4c 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -140,9 +140,8 @@ void ClientNetworkEmergencySave() if (!_networking) return; if (!ClientNetworkGameSocketHandler::EmergencySavePossible()) return; - const char *filename = "netsave.sav"; - DEBUG(net, 0, "Client: Performing emergency save (%s)", filename); - SaveOrLoad(filename, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR, false, SMF_ZSTD_OK); + static int _netsave_ctr = 0; + DoAutoOrNetsave(_netsave_ctr, true); } @@ -715,7 +714,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_GAME_INFO(Packe item->online = true; /* It could be either window, but only one is open, so redraw both. */ - SetWindowDirty(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME); + UpdateNetworkGameWindow(); SetWindowDirty(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY); /* We will receive company info next, so keep connection open. */ diff --git a/src/network/network_content.cpp b/src/network/network_content.cpp index 185d071732..8091939ff3 100644 --- a/src/network/network_content.cpp +++ b/src/network/network_content.cpp @@ -367,7 +367,7 @@ void ClientNetworkContentSocketHandler::DownloadSelectedContentHTTP(const Conten this->http_response_index = -1; - new NetworkHTTPContentConnecter(NETWORK_CONTENT_MIRROR_HOST, this, NETWORK_CONTENT_MIRROR_URL, content_request); + new NetworkHTTPContentConnecter(NetworkContentMirrorConnectionString(), this, NETWORK_CONTENT_MIRROR_URL, content_request); /* NetworkHTTPContentConnecter takes over freeing of content_request! */ } @@ -799,7 +799,7 @@ void ClientNetworkContentSocketHandler::Connect() { if (this->sock != INVALID_SOCKET || this->isConnecting) return; this->isConnecting = true; - new NetworkContentConnecter(NETWORK_CONTENT_SERVER_HOST); + new NetworkContentConnecter(NetworkContentServerConnectionString()); } /** diff --git a/src/network/network_coordinator.cpp b/src/network/network_coordinator.cpp new file mode 100644 index 0000000000..c45c51f473 --- /dev/null +++ b/src/network/network_coordinator.cpp @@ -0,0 +1,687 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file network_coordinator.cpp Game Coordinator sending/receiving part of the network protocol. */ + +#include "../stdafx.h" +#include "../debug.h" +#include "../error.h" +#include "../rev.h" +#include "../settings_type.h" +#include "../strings_func.h" +#include "../window_func.h" +#include "../window_type.h" +#include "network.h" +#include "network_coordinator.h" +#include "network_gamelist.h" +#include "network_internal.h" +#include "network_server.h" +#include "network_stun.h" +#include "table/strings.h" + +#include "../safeguards.h" + +static const auto NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES = std::chrono::seconds(30); ///< How many time between updates the server sends to the Game Coordinator. +ClientNetworkCoordinatorSocketHandler _network_coordinator_client; ///< The connection to the Game Coordinator. +ConnectionType _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; ///< What type of connection the Game Coordinator detected we are on. +std::string _network_server_invite_code = ""; ///< Our invite code as indicated by the Game Coordinator. + +/** Connect to a game server by IP:port. */ +class NetworkDirectConnecter : public TCPConnecter { +private: + std::string token; ///< Token of this connection. + uint8 tracking_number; ///< Tracking number of this connection. + +public: + /** + * Try to establish a direct (hostname:port based) connection. + * @param hostname The hostname of the server. + * @param port The port of the server. + * @param token The token as given by the Game Coordinator to track this connection attempt. + * @param tracking_number The tracking number as given by the Game Coordinator to track this connection attempt. + */ + NetworkDirectConnecter(const std::string &hostname, uint16 port, const std::string &token, uint8 tracking_number) : TCPConnecter(hostname, port), token(token), tracking_number(tracking_number) {} + + void OnFailure() override + { + _network_coordinator_client.ConnectFailure(this->token, this->tracking_number); + } + + void OnConnect(SOCKET s) override + { + NetworkAddress address = NetworkAddress::GetPeerAddress(s); + _network_coordinator_client.ConnectSuccess(this->token, s, address); + } +}; + +/** Connecter used after STUN exchange to connect from both sides to each other. */ +class NetworkReuseStunConnecter : public TCPConnecter { +private: + std::string token; ///< Token of this connection. + uint8 tracking_number; ///< Tracking number of this connection. + uint8 family; ///< Family of this connection. + +public: + /** + * Try to establish a STUN-based connection. + * @param hostname The hostname of the peer. + * @param port The port of the peer. + * @param bind_address The local bind address used for this connection. + * @param token The connection token. + * @param tracking_number The tracking number of the connection. + * @param family The family this connection is using. + */ + NetworkReuseStunConnecter(const std::string &hostname, uint16 port, const NetworkAddress &bind_address, std::string token, uint8 tracking_number, uint8 family) : + TCPConnecter(hostname, port, bind_address), + token(token), + tracking_number(tracking_number), + family(family) + { + } + + void OnFailure() override + { + /* Close the STUN connection too, as it is no longer of use. */ + _network_coordinator_client.CloseStunHandler(this->token, this->family); + + _network_coordinator_client.ConnectFailure(this->token, this->tracking_number); + } + + void OnConnect(SOCKET s) override + { + NetworkAddress address = NetworkAddress::GetPeerAddress(s); + _network_coordinator_client.ConnectSuccess(this->token, s, address); + } +}; + +/** Connect to the Game Coordinator server. */ +class NetworkCoordinatorConnecter : TCPConnecter { +public: + /** + * Initiate the connecting. + * @param connection_string The address of the Game Coordinator server. + */ + NetworkCoordinatorConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_COORDINATOR_SERVER_PORT) {} + + void OnFailure() override + { + _network_coordinator_client.connecting = false; + _network_coordinator_client.CloseConnection(true); + } + + void OnConnect(SOCKET s) override + { + assert(_network_coordinator_client.sock == INVALID_SOCKET); + + _network_coordinator_client.sock = s; + _network_coordinator_client.last_activity = std::chrono::steady_clock::now(); + _network_coordinator_client.connecting = false; + } +}; + +bool ClientNetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet *p) +{ + NetworkCoordinatorErrorType error = (NetworkCoordinatorErrorType)p->Recv_uint8(); + std::string detail = p->Recv_string(NETWORK_ERROR_DETAIL_LENGTH); + + switch (error) { + case NETWORK_COORDINATOR_ERROR_UNKNOWN: + this->CloseConnection(); + return false; + + case NETWORK_COORDINATOR_ERROR_REGISTRATION_FAILED: + SetDParamStr(0, detail); + ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED, STR_JUST_RAW_STRING, WL_ERROR); + + /* To prevent that we constantly try to reconnect, switch to local game. */ + _settings_client.network.server_game_type = SERVER_GAME_TYPE_LOCAL; + + this->CloseConnection(); + return false; + + case NETWORK_COORDINATOR_ERROR_INVALID_INVITE_CODE: { + this->CloseToken(detail); + + /* Mark the server as offline. */ + NetworkGameList *item = NetworkGameListAddItem(detail); + item->online = false; + + UpdateNetworkGameWindow(); + return true; + } + + default: + DEBUG(net, 0, "Invalid error type %u received from Game Coordinator", error); + this->CloseConnection(); + return false; + } +} + +bool ClientNetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p) +{ + /* Schedule sending an update. */ + this->next_update = std::chrono::steady_clock::now(); + + _settings_client.network.server_invite_code = p->Recv_string(NETWORK_INVITE_CODE_LENGTH); + _settings_client.network.server_invite_code_secret = p->Recv_string(NETWORK_INVITE_CODE_SECRET_LENGTH); + _network_server_connection_type = (ConnectionType)p->Recv_uint8(); + + if (_network_server_connection_type == CONNECTION_TYPE_ISOLATED) { + ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_ISOLATED, STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL, WL_ERROR); + } + + /* Users can change the invite code in the settings, but this has no effect + * on the invite code as assigned by the server. So + * _network_server_invite_code contains the current invite code, + * and _settings_client.network.server_invite_code contains the one we will + * attempt to re-use when registering again. */ + _network_server_invite_code = _settings_client.network.server_invite_code; + + SetWindowDirty(WC_CLIENT_LIST, 0); + + if (_network_dedicated) { + std::string connection_type; + switch (_network_server_connection_type) { + case CONNECTION_TYPE_ISOLATED: connection_type = "Remote players can't connect"; break; + case CONNECTION_TYPE_DIRECT: connection_type = "Public"; break; + case CONNECTION_TYPE_STUN: connection_type = "Behind NAT"; break; + + case CONNECTION_TYPE_UNKNOWN: // Never returned from Game Coordinator. + default: connection_type = "Unknown"; break; // Should never happen, but don't fail if it does. + } + + std::string game_type; + switch (_settings_client.network.server_game_type) { + case SERVER_GAME_TYPE_INVITE_ONLY: game_type = "Invite only"; break; + case SERVER_GAME_TYPE_PUBLIC: game_type = "Public"; break; + + case SERVER_GAME_TYPE_LOCAL: // Impossible to register local servers. + default: game_type = "Unknown"; break; // Should never happen, but don't fail if it does. + } + + DEBUG(net, 3, "----------------------------------------"); + DEBUG(net, 3, "Your server is now registered with the Game Coordinator:"); + DEBUG(net, 3, " Game type: %s", game_type.c_str()); + DEBUG(net, 3, " Connection type: %s", connection_type.c_str()); + DEBUG(net, 3, " Invite code: %s", _network_server_invite_code.c_str()); + DEBUG(net, 3, "----------------------------------------"); + } else { + DEBUG(net, 3, "Game Coordinator registered our server with invite code '%s'", _network_server_invite_code.c_str()); + } + + return true; +} + +bool ClientNetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p) +{ + uint8 servers = p->Recv_uint16(); + + /* End of list; we can now remove all expired items from the list. */ + if (servers == 0) { + NetworkGameListRemoveExpired(); + return true; + } + + for (; servers > 0; servers--) { + std::string connection_string = p->Recv_string(NETWORK_HOSTNAME_PORT_LENGTH); + + /* Read the NetworkGameInfo from the packet. */ + NetworkGameInfo ngi = {}; + DeserializeNetworkGameInfo(p, &ngi); + + /* Now we know the connection string, we can add it to our list. */ + NetworkGameList *item = NetworkGameListAddItem(connection_string); + + /* Clear any existing GRFConfig chain. */ + ClearGRFConfigList(&item->info.grfconfig); + /* Copy the new NetworkGameInfo info. */ + item->info = ngi; + /* Check for compatability with the client. */ + CheckGameCompatibility(item->info); + /* Mark server as online. */ + item->online = true; + /* Mark the item as up-to-date. */ + item->version = _network_game_list_version; + } + + UpdateNetworkGameWindow(); + return true; +} + +bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECTING(Packet *p) +{ + std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH); + std::string invite_code = p->Recv_string(NETWORK_INVITE_CODE_LENGTH); + + /* Find the connecter based on the invite code. */ + auto connecter_it = this->connecter_pre.find(invite_code); + if (connecter_it == this->connecter_pre.end()) { + this->CloseConnection(); + return false; + } + + /* Now store it based on the token. */ + this->connecter[token] = connecter_it->second; + this->connecter_pre.erase(connecter_it); + + return true; +} + +bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECT_FAILED(Packet *p) +{ + std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH); + + auto connecter_it = this->connecter.find(token); + if (connecter_it != this->connecter.end()) { + connecter_it->second->SetFailure(); + this->connecter.erase(connecter_it); + } + + /* Close all remaining connections. */ + this->CloseToken(token); + + return true; +} + +bool ClientNetworkCoordinatorSocketHandler::Receive_GC_DIRECT_CONNECT(Packet *p) +{ + std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH); + uint8 tracking_number = p->Recv_uint8(); + std::string hostname = p->Recv_string(NETWORK_HOSTNAME_LENGTH); + uint16 port = p->Recv_uint16(); + + /* Ensure all other pending connection attempts are killed. */ + if (this->game_connecter != nullptr) { + this->game_connecter->Kill(); + this->game_connecter = nullptr; + } + + this->game_connecter = new NetworkDirectConnecter(hostname, port, token, tracking_number); + return true; +} + +bool ClientNetworkCoordinatorSocketHandler::Receive_GC_STUN_REQUEST(Packet *p) +{ + std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH); + + this->stun_handlers[token][AF_INET6] = ClientNetworkStunSocketHandler::Stun(token, AF_INET6); + this->stun_handlers[token][AF_INET] = ClientNetworkStunSocketHandler::Stun(token, AF_INET); + return true; +} + +bool ClientNetworkCoordinatorSocketHandler::Receive_GC_STUN_CONNECT(Packet *p) +{ + std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH); + uint8 tracking_number = p->Recv_uint8(); + uint8 family = p->Recv_uint8(); + std::string host = p->Recv_string(NETWORK_HOSTNAME_PORT_LENGTH); + uint16 port = p->Recv_uint16(); + + /* Check if we know this token. */ + auto stun_it = this->stun_handlers.find(token); + if (stun_it == this->stun_handlers.end()) return true; + auto family_it = stun_it->second.find(family); + if (family_it == stun_it->second.end()) return true; + + /* Ensure all other pending connection attempts are killed. */ + if (this->game_connecter != nullptr) { + this->game_connecter->Kill(); + this->game_connecter = nullptr; + } + + /* We now mark the connection as closed, but we do not really close the + * socket yet. We do this when the NetworkReuseStunConnecter is connected. + * This prevents any NAT to already remove the route while we create the + * second connection on top of the first. */ + family_it->second->CloseConnection(false); + + /* Connect to our peer from the same local address as we use for the + * STUN server. This means that if there is any NAT in the local network, + * the public ip:port is still pointing to the local address, and as such + * a connection can be established. */ + this->game_connecter = new NetworkReuseStunConnecter(host, port, family_it->second->local_addr, token, tracking_number, family); + return true; +} + +void ClientNetworkCoordinatorSocketHandler::Connect() +{ + /* We are either already connected or are trying to connect. */ + if (this->sock != INVALID_SOCKET || this->connecting) return; + + this->Reopen(); + + this->connecting = true; + this->last_activity = std::chrono::steady_clock::now(); + + new NetworkCoordinatorConnecter(NetworkCoordinatorConnectionString()); +} + +NetworkRecvStatus ClientNetworkCoordinatorSocketHandler::CloseConnection(bool error) +{ + NetworkCoordinatorSocketHandler::CloseConnection(error); + + this->CloseSocket(); + this->connecting = false; + + _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; + this->next_update = {}; + + this->CloseAllTokens(); + + SetWindowDirty(WC_CLIENT_LIST, 0); + + return NETWORK_RECV_STATUS_OKAY; +} + +/** + * Register our server to receive our invite code. + */ +void ClientNetworkCoordinatorSocketHandler::Register() +{ + _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; + this->next_update = {}; + + SetWindowDirty(WC_CLIENT_LIST, 0); + + this->Connect(); + + Packet *p = new Packet(PACKET_COORDINATOR_SERVER_REGISTER); + p->Send_uint8(NETWORK_COORDINATOR_VERSION); + p->Send_uint8(_settings_client.network.server_game_type); + p->Send_uint16(_settings_client.network.server_port); + if (_settings_client.network.server_invite_code.empty() || _settings_client.network.server_invite_code_secret.empty()) { + p->Send_string(""); + p->Send_string(""); + } else { + p->Send_string(_settings_client.network.server_invite_code); + p->Send_string(_settings_client.network.server_invite_code_secret); + } + + this->SendPacket(p); +} + +/** + * Send an update of our server status to the Game Coordinator. + */ +void ClientNetworkCoordinatorSocketHandler::SendServerUpdate() +{ + DEBUG(net, 6, "Sending server update to Game Coordinator"); + this->next_update = std::chrono::steady_clock::now() + NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES; + + Packet *p = new Packet(PACKET_COORDINATOR_SERVER_UPDATE, TCP_MTU); + p->Send_uint8(NETWORK_COORDINATOR_VERSION); + SerializeNetworkGameInfo(p, GetCurrentNetworkServerGameInfo()); + + this->SendPacket(p); +} + +/** + * Request a listing of all public servers. + */ +void ClientNetworkCoordinatorSocketHandler::GetListing() +{ + this->Connect(); + + _network_game_list_version++; + + Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_LISTING); + p->Send_uint8(NETWORK_COORDINATOR_VERSION); + p->Send_uint8(NETWORK_GAME_INFO_VERSION); + p->Send_string(_openttd_revision); + + this->SendPacket(p); +} + +/** + * Join a server based on an invite code. + * @param invite_code The invite code of the server to connect to. + * @param connecter The connecter of the request. + */ +void ClientNetworkCoordinatorSocketHandler::ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter) +{ + assert(StrStartsWith(invite_code, "+")); + + if (this->connecter_pre.find(invite_code) != this->connecter_pre.end()) { + /* If someone is hammering the refresh key, one can sent out two + * requests for the same invite code. There isn't really a great way + * of handling this, so just ignore this request. */ + connecter->SetFailure(); + return; + } + + /* Initially we store based on invite code; on first reply we know the + * token, and will start using that key instead. */ + this->connecter_pre[invite_code] = connecter; + + this->Connect(); + + Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_CONNECT); + p->Send_uint8(NETWORK_COORDINATOR_VERSION); + p->Send_string(invite_code); + + this->SendPacket(p); +} + +/** + * Callback from a Connecter to let the Game Coordinator know the connection failed. + * @param token Token of the connecter that failed. + * @param tracking_number Tracking number of the connecter that failed. + */ +void ClientNetworkCoordinatorSocketHandler::ConnectFailure(const std::string &token, uint8 tracking_number) +{ + /* Connecter will destroy itself. */ + this->game_connecter = nullptr; + + Packet *p = new Packet(PACKET_COORDINATOR_SERCLI_CONNECT_FAILED); + p->Send_uint8(NETWORK_COORDINATOR_VERSION); + p->Send_string(token); + p->Send_uint8(tracking_number); + + this->SendPacket(p); + + /* We do not close the associated connecter here yet, as the + * Game Coordinator might have other methods of connecting available. */ +} + +/** + * Callback from a Connecter to let the Game Coordinator know the connection + * to the game server is established. + * @param token Token of the connecter that succeeded. + * @param sock The socket that the connecter can now use. + */ +void ClientNetworkCoordinatorSocketHandler::ConnectSuccess(const std::string &token, SOCKET sock, NetworkAddress &address) +{ + /* Connecter will destroy itself. */ + this->game_connecter = nullptr; + + if (_network_server) { + if (!ServerNetworkGameSocketHandler::ValidateClient(sock, address)) return; + DEBUG(net, 3, "[%s] Client connected from %s on frame %u", ServerNetworkGameSocketHandler::GetName(), address.GetHostname(), _frame_counter); + ServerNetworkGameSocketHandler::AcceptConnection(sock, address); + } else { + /* The client informs the Game Coordinator about the success. The server + * doesn't have to, as it is implied by the client telling. */ + Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_CONNECTED); + p->Send_uint8(NETWORK_COORDINATOR_VERSION); + p->Send_string(token); + this->SendPacket(p); + + auto connecter_it = this->connecter.find(token); + assert(connecter_it != this->connecter.end()); + + connecter_it->second->SetConnected(sock); + this->connecter.erase(connecter_it); + } + + /* Close all remaining connections. */ + this->CloseToken(token); +} + +/** + * Callback from the STUN connecter to inform the Game Coordinator about the + * result of the STUN. + * + * This helps the Game Coordinator not to wait for a timeout on its end, but + * rather react as soon as the client/server knows the result. + */ +void ClientNetworkCoordinatorSocketHandler::StunResult(const std::string &token, uint8 family, bool result) +{ + Packet *p = new Packet(PACKET_COORDINATOR_SERCLI_STUN_RESULT); + p->Send_uint8(NETWORK_COORDINATOR_VERSION); + p->Send_string(token); + p->Send_uint8(family); + p->Send_bool(result); + this->SendPacket(p); +} + +void ClientNetworkCoordinatorSocketHandler::CloseStunHandler(const std::string &token, uint8 family) +{ + auto stun_it = this->stun_handlers.find(token); + if (stun_it == this->stun_handlers.end()) return; + + if (family == AF_UNSPEC) { + for (auto &[family, stun_handler] : stun_it->second) { + stun_handler->CloseConnection(); + stun_handler->CloseSocket(); + } + + this->stun_handlers.erase(stun_it); + } else { + auto family_it = stun_it->second.find(family); + if (family_it == stun_it->second.end()) return; + + family_it->second->CloseConnection(); + family_it->second->CloseSocket(); + + stun_it->second.erase(family_it); + } +} + +/** + * Close everything related to this connection token. + * @param token The connection token to close. + */ +void ClientNetworkCoordinatorSocketHandler::CloseToken(const std::string &token) +{ + /* Ensure all other pending connection attempts are also killed. */ + if (this->game_connecter != nullptr) { + this->game_connecter->Kill(); + this->game_connecter = nullptr; + } + + /* Close all remaining STUN connections. */ + this->CloseStunHandler(token); + + /* Close the caller of the connection attempt. */ + auto connecter_it = this->connecter.find(token); + if (connecter_it != this->connecter.end()) { + connecter_it->second->SetFailure(); + this->connecter.erase(connecter_it); + } + auto connecter_pre_it = this->connecter_pre.find(token); + if (connecter_pre_it != this->connecter_pre.end()) { + connecter_pre_it->second->SetFailure(); + this->connecter_pre.erase(connecter_pre_it); + } +} + +/** + * Close all pending connection tokens. + */ +void ClientNetworkCoordinatorSocketHandler::CloseAllTokens() +{ + /* Ensure all other pending connection attempts are also killed. */ + if (this->game_connecter != nullptr) { + this->game_connecter->Kill(); + this->game_connecter = nullptr; + } + + /* Mark any pending connecters as failed. */ + for (auto &[token, it] : this->connecter) { + this->CloseStunHandler(token); + it->SetFailure(); + } + for (auto &[invite_code, it] : this->connecter_pre) { + it->SetFailure(); + } + this->connecter.clear(); + this->connecter_pre.clear(); +} + +/** + * Check whether we received/can send some data from/to the Game Coordinator server and + * when that's the case handle it appropriately. + */ +void ClientNetworkCoordinatorSocketHandler::SendReceive() +{ + /* Private games are not listed via the Game Coordinator. */ + if (_network_server && _settings_client.network.server_game_type == SERVER_GAME_TYPE_LOCAL) { + if (this->sock != INVALID_SOCKET) { + this->CloseConnection(); + } + return; + } + + static int last_attempt_backoff = 1; + static bool first_reconnect = true; + + if (this->sock == INVALID_SOCKET) { + static std::chrono::steady_clock::time_point last_attempt = {}; + + /* Don't auto-reconnect when we are not a server. */ + if (!_network_server) return; + /* Don't reconnect if we are connecting. */ + if (this->connecting) return; + /* Throttle how often we try to reconnect. */ + if (std::chrono::steady_clock::now() < last_attempt + std::chrono::seconds(1) * last_attempt_backoff) return; + + last_attempt = std::chrono::steady_clock::now(); + /* Delay reconnecting with up to 32 seconds. */ + if (last_attempt_backoff < 32) { + last_attempt_backoff *= 2; + } + + /* Do not reconnect on the first attempt, but only initialize the + * last_attempt variables. Otherwise after an outage all servers + * reconnect at the same time, potentially overwhelming the + * Game Coordinator. */ + if (first_reconnect) { + first_reconnect = false; + return; + } + + DEBUG(net, 1, "Connection with Game Coordinator lost; reconnecting..."); + this->Register(); + return; + } + + last_attempt_backoff = 1; + first_reconnect = true; + + if (_network_server && _network_server_connection_type != CONNECTION_TYPE_UNKNOWN && std::chrono::steady_clock::now() > this->next_update) { + this->SendServerUpdate(); + } + + if (!_network_server && std::chrono::steady_clock::now() > this->last_activity + IDLE_TIMEOUT) { + this->CloseConnection(); + return; + } + + if (this->CanSendReceive()) { + if (this->ReceivePackets()) { + this->last_activity = std::chrono::steady_clock::now(); + } + } + + this->SendPackets(); + + for (const auto &[token, families] : this->stun_handlers) { + for (const auto &[family, stun_handler] : families) { + stun_handler->SendReceive(); + } + } +} diff --git a/src/network/network_coordinator.h b/src/network/network_coordinator.h new file mode 100644 index 0000000000..f6859f859f --- /dev/null +++ b/src/network/network_coordinator.h @@ -0,0 +1,97 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file network_coordinator.h Part of the network protocol handling Game Coordinator requests. */ + +#ifndef NETWORK_COORDINATOR_H +#define NETWORK_COORDINATOR_H + +#include "core/tcp_coordinator.h" +#include "network_stun.h" +#include + +/** + * Game Coordinator communication. + * For more detail about what the Game Coordinator does, please see + * docs/game_coordinator.md. + * + * For servers: + * - Server sends SERVER_REGISTER. + * - Game Coordinator probes server to check if it can directly connect. + * - Game Coordinator sends GC_REGISTER_ACK with type of connection. + * - Server sends every 30 seconds SERVER_UPDATE. + * + * For clients (listing): + * - Client sends CLIENT_LISTING. + * - Game Coordinator returns the full list of public servers via GC_LISTING (multiple packets). + * + * For clients (connecting): + * - Client sends CLIENT_CONNECT. + * - Game Coordinator checks what type of connections the servers supports: + * 1) Direct connect? + * - Send the client a GC_CONNECT with the peer address. + * - a) Client connects, client sends CLIENT_CONNECTED to Game Coordinator. + * - b) Client connect fails, client sends CLIENT_CONNECT_FAILED to Game Coordinator. + * 2) STUN? + * - Game Coordinator sends GC_STUN_REQUEST to server/client (asking for both IPv4 and IPv6 STUN requests). + * - Game Coordinator collects what combination works and sends GC_STUN_CONNECT to server/client. + * - a) Server/client connect, client sends CLIENT_CONNECTED to Game Coordinator. + * - b) Server/client connect fails, both send SERCLI_CONNECT_FAILED to Game Coordinator. + * - Game Coordinator tries other combination if available. + * - If all fails, Game Coordinator sends GC_CONNECT_FAILED to indicate no connection is possible. + */ + +/** Class for handling the client side of the Game Coordinator connection. */ +class ClientNetworkCoordinatorSocketHandler : public NetworkCoordinatorSocketHandler { +private: + std::chrono::steady_clock::time_point next_update; ///< When to send the next update (if server and public). + std::map connecter; ///< Based on tokens, the current connecters that are pending. + std::map connecter_pre; ///< Based on invite codes, the current connecters that are pending. + std::map>> stun_handlers; ///< All pending STUN handlers, stored by token:family. + TCPConnecter *game_connecter = nullptr; ///< Pending connecter to the game server. + +protected: + bool Receive_GC_ERROR(Packet *p) override; + bool Receive_GC_REGISTER_ACK(Packet *p) override; + bool Receive_GC_LISTING(Packet *p) override; + bool Receive_GC_CONNECTING(Packet *p) override; + bool Receive_GC_CONNECT_FAILED(Packet *p) override; + bool Receive_GC_DIRECT_CONNECT(Packet *p) override; + bool Receive_GC_STUN_REQUEST(Packet *p) override; + bool Receive_GC_STUN_CONNECT(Packet *p) override; + +public: + /** The idle timeout; when to close the connection because it's idle. */ + static constexpr std::chrono::seconds IDLE_TIMEOUT = std::chrono::seconds(60); + + std::chrono::steady_clock::time_point last_activity; ///< The last time there was network activity. + bool connecting; ///< Are we connecting to the Game Coordinator? + + ClientNetworkCoordinatorSocketHandler() : connecting(false) {} + + NetworkRecvStatus CloseConnection(bool error = true) override; + void SendReceive(); + + void ConnectFailure(const std::string &token, uint8 tracking_number); + void ConnectSuccess(const std::string &token, SOCKET sock, NetworkAddress &address); + void StunResult(const std::string &token, uint8 family, bool result); + + void Connect(); + void CloseToken(const std::string &token); + void CloseAllTokens(); + void CloseStunHandler(const std::string &token, uint8 family = AF_UNSPEC); + + void Register(); + void SendServerUpdate(); + void GetListing(); + + void ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter); +}; + +extern ClientNetworkCoordinatorSocketHandler _network_coordinator_client; + +#endif /* NETWORK_COORDINATOR_H */ diff --git a/src/network/network_func.h b/src/network/network_func.h index 55691d3a5a..596f9a5c25 100644 --- a/src/network/network_func.h +++ b/src/network/network_func.h @@ -28,7 +28,6 @@ extern NetworkCompanyState *_network_company_states; extern ClientID _network_own_client_id; extern ClientID _redirect_console_to_client; -extern bool _network_need_advertise; extern uint8 _network_reconnect; extern StringList _network_bind_list; extern StringList _network_host_list; @@ -40,6 +39,7 @@ bool NetworkValidateOurClientName(); bool NetworkValidateClientName(std::string &client_name); bool NetworkValidateServerName(std::string &server_name); void NetworkUpdateClientName(const std::string &client_name); +void NetworkUpdateServerGameType(); bool NetworkCompanyHasClients(CompanyID company); std::string NetworkChangeCompanyPassword(CompanyID company_id, std::string password); void NetworkReboot(); diff --git a/src/network/network_gamelist.cpp b/src/network/network_gamelist.cpp index fcbf1b4ac3..d4843ff673 100644 --- a/src/network/network_gamelist.cpp +++ b/src/network/network_gamelist.cpp @@ -20,51 +20,13 @@ #include "../safeguards.h" -NetworkGameList *_network_game_list = nullptr; - -/** The games to insert when the GUI thread has time for us. */ -static std::atomic _network_game_delayed_insertion_list(nullptr); - -/** - * Add a new item to the linked gamelist, but do it delayed in the next tick - * or so to prevent race conditions. - * @param item the item to add. Will be freed once added. - */ -void NetworkGameListAddItemDelayed(NetworkGameList *item) -{ - item->next = _network_game_delayed_insertion_list.load(std::memory_order_relaxed); - while (!_network_game_delayed_insertion_list.compare_exchange_weak(item->next, item, std::memory_order_acq_rel)) {} -} - -/** Perform the delayed (thread safe) insertion into the game list */ -static void NetworkGameListHandleDelayedInsert() -{ - while (true) { - NetworkGameList *ins_item = _network_game_delayed_insertion_list.load(std::memory_order_acquire); - while (ins_item != nullptr && !_network_game_delayed_insertion_list.compare_exchange_weak(ins_item, ins_item->next, std::memory_order_acq_rel)) {} - if (ins_item == nullptr) break; // No item left. - - NetworkGameList *item = NetworkGameListAddItem(ins_item->connection_string); - - if (item != nullptr) { - if (item->info.server_name.empty()) { - ClearGRFConfigList(&item->info.grfconfig); - item->info = {}; - item->info.server_name = ins_item->info.server_name; - item->online = false; - } - item->manually |= ins_item->manually; - if (item->manually) NetworkRebuildHostList(); - UpdateNetworkGameWindow(); - } - delete ins_item; - } -} +NetworkGameList *_network_game_list = nullptr; ///< Game list of this client. +int _network_game_list_version = 0; ///< Current version of all items in the list. /** * Add a new item to the linked gamelist. If the IP and Port match * return the existing item instead of adding it again - * @param address the address of the to-be added item + * @param connection_string the address of the to-be added item * @return a point to the newly added or already existing item */ NetworkGameList *NetworkGameListAddItem(const std::string &connection_string) @@ -72,7 +34,7 @@ NetworkGameList *NetworkGameListAddItem(const std::string &connection_string) NetworkGameList *item, *prev_item; /* Parse the connection string to ensure the default port is there. */ - const std::string resolved_connection_string = ParseConnectionString(connection_string, NETWORK_DEFAULT_PORT).GetAddressAsString(false); + const std::string resolved_connection_string = ServerAddress::Parse(connection_string, NETWORK_DEFAULT_PORT).connection_string; prev_item = nullptr; for (item = _network_game_list; item != nullptr; item = item->next) { @@ -81,6 +43,7 @@ NetworkGameList *NetworkGameListAddItem(const std::string &connection_string) } item = new NetworkGameList(resolved_connection_string); + item->version = _network_game_list_version; if (prev_item == nullptr) { _network_game_list = item; @@ -120,29 +83,31 @@ void NetworkGameListRemoveItem(NetworkGameList *remove) } } -static const uint MAX_GAME_LIST_REQUERY_COUNT = 10; ///< How often do we requery in number of times per server? -static const uint REQUERY_EVERY_X_GAMELOOPS = 60; ///< How often do we requery in time? -static const uint REFRESH_GAMEINFO_X_REQUERIES = 50; ///< Refresh the game info itself after REFRESH_GAMEINFO_X_REQUERIES * REQUERY_EVERY_X_GAMELOOPS game loops - -/** Requeries the (game) servers we have not gotten a reply from */ -void NetworkGameListRequery() +/** + * Remove all servers that have not recently been updated. + * Call this after you received all the servers from the Game Coordinator, so + * the ones that are no longer listed are removed. + */ +void NetworkGameListRemoveExpired() { - NetworkGameListHandleDelayedInsert(); + NetworkGameList **prev_item = &_network_game_list; - static uint8 requery_cnt = 0; - - if (++requery_cnt < REQUERY_EVERY_X_GAMELOOPS) return; - requery_cnt = 0; - - for (NetworkGameList *item = _network_game_list; item != nullptr; item = item->next) { - item->retries++; - if (item->retries < REFRESH_GAMEINFO_X_REQUERIES && (item->online || item->retries >= MAX_GAME_LIST_REQUERY_COUNT)) continue; + for (NetworkGameList *item = _network_game_list; item != nullptr;) { + if (!item->manually && item->version < _network_game_list_version) { + NetworkGameList *remove = item; + item = item->next; + *prev_item = item; - /* item gets mostly zeroed by NetworkUDPQueryServer */ - uint8 retries = item->retries; - NetworkUDPQueryServer(item->connection_string); - item->retries = (retries >= REFRESH_GAMEINFO_X_REQUERIES) ? 0 : retries; + /* Remove GRFConfig information */ + ClearGRFConfigList(&remove->info.grfconfig); + delete remove; + } else { + prev_item = &item->next; + item = item->next; + } } + + UpdateNetworkGameWindow(); } /** diff --git a/src/network/network_gamelist.h b/src/network/network_gamelist.h index 6ef9d8e41f..9a98207d96 100644 --- a/src/network/network_gamelist.h +++ b/src/network/network_gamelist.h @@ -16,25 +16,22 @@ /** Structure with information shown in the game list (GUI) */ struct NetworkGameList { - NetworkGameList(const std::string &connection_string, bool manually = false) : - connection_string(connection_string), manually(manually) - { - } + NetworkGameList(const std::string &connection_string) : connection_string(connection_string) {} NetworkGameInfo info = {}; ///< The game information of this server std::string connection_string; ///< Address of the server bool online = false; ///< False if the server did not respond (default status) bool manually = false; ///< True if the server was added manually uint8 retries = 0; ///< Number of retries (to stop requerying) + int version = 0; ///< Used to see which servers are no longer available on the Game Coordinator and can be removed. NetworkGameList *next = nullptr; ///< Next pointer to make a linked game list }; -/** Game list of this client */ extern NetworkGameList *_network_game_list; +extern int _network_game_list_version; -void NetworkGameListAddItemDelayed(NetworkGameList *item); NetworkGameList *NetworkGameListAddItem(const std::string &connection_string); void NetworkGameListRemoveItem(NetworkGameList *remove); -void NetworkGameListRequery(); +void NetworkGameListRemoveExpired(); #endif /* NETWORK_GAMELIST_H */ diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 7393a0324c..7db95af7c9 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -18,6 +18,7 @@ #include "network_base.h" #include "network_content.h" #include "network_server.h" +#include "network_coordinator.h" #include "../gui.h" #include "network_udp.h" #include "../window_func.h" @@ -56,19 +57,11 @@ static void ShowNetworkStartServerWindow(); static void ShowNetworkLobbyWindow(NetworkGameList *ngl); +static const int NETWORK_LIST_REFRESH_DELAY = 30; ///< Time, in seconds, between updates of the network list. + static ClientID _admin_client_id = INVALID_CLIENT_ID; ///< For what client a confirmation window is open. static CompanyID _admin_company_id = INVALID_COMPANY; ///< For what company a confirmation window is open. -/** - * Visibility of the server. Public servers advertise, where private servers - * do not. - */ -static const StringID _server_visibility_dropdown[] = { - STR_NETWORK_SERVER_VISIBILITY_PRIVATE, - STR_NETWORK_SERVER_VISIBILITY_PUBLIC, - INVALID_STRING_ID -}; - /** * Update the network new window because a new server is * found on the network. @@ -78,6 +71,17 @@ void UpdateNetworkGameWindow() InvalidateWindowData(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME, 0); } +static DropDownList BuildVisibilityDropDownList() +{ + DropDownList list; + + list.emplace_back(new DropDownListStringItem(STR_NETWORK_SERVER_VISIBILITY_LOCAL, SERVER_GAME_TYPE_LOCAL, false)); + list.emplace_back(new DropDownListStringItem(STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY, SERVER_GAME_TYPE_INVITE_ONLY, false)); + list.emplace_back(new DropDownListStringItem(STR_NETWORK_SERVER_VISIBILITY_PUBLIC, SERVER_GAME_TYPE_PUBLIC, false)); + + return list; +} + typedef GUIList GUIGameServerList; typedef int ServerListPosition; static const ServerListPosition SLP_INVALID = -1; @@ -234,14 +238,15 @@ protected: static GUIGameServerList::SortFunction * const sorter_funcs[]; static GUIGameServerList::FilterFunction * const filter_funcs[]; - NetworkGameList *server; ///< selected server - NetworkGameList *last_joined; ///< the last joined server - GUIGameServerList servers; ///< list with game servers. - ServerListPosition list_pos; ///< position of the selected server - Scrollbar *vscroll; ///< vertical scrollbar of the list of servers - QueryString name_editbox; ///< Client name editbox. - QueryString filter_editbox; ///< Editbox for filter on servers - GUITimer requery_timer; ///< Timer for network requery + NetworkGameList *server; ///< Selected server. + NetworkGameList *last_joined; ///< The last joined server. + GUIGameServerList servers; ///< List with game servers. + ServerListPosition list_pos; ///< Position of the selected server. + Scrollbar *vscroll; ///< Vertical scrollbar of the list of servers. + QueryString name_editbox; ///< Client name editbox. + QueryString filter_editbox; ///< Editbox for filter on servers. + GUITimer requery_timer; ///< Timer for network requery. + bool searched_internet = false; ///< Did we ever press "Search Internet" button? int lock_offset; ///< Left offset for lock icon. int blot_offset; ///< Left offset for green/yellow/red compatibility icon. @@ -259,8 +264,18 @@ protected: /* Create temporary array of games to use for listing */ this->servers.clear(); + bool found_current_server = false; for (NetworkGameList *ngl = _network_game_list; ngl != nullptr; ngl = ngl->next) { this->servers.push_back(ngl); + if (ngl == this->server) { + found_current_server = true; + } + } + /* A refresh can cause the current server to be delete; so unselect. */ + if (!found_current_server) { + if (this->server == this->last_joined) this->last_joined = nullptr; + this->server = nullptr; + this->list_pos = SLP_INVALID; } /* Apply the filter condition immediately, if a search string has been provided. */ @@ -494,7 +509,7 @@ public: this->last_joined = NetworkAddServer(_settings_client.network.last_joined, false); this->server = this->last_joined; - this->requery_timer.SetInterval(MILLISECONDS_PER_TICK); + this->requery_timer.SetInterval(NETWORK_LIST_REFRESH_DELAY * 1000); this->servers.SetListing(this->last_sorting); this->servers.SetSortFuncs(this->sorter_funcs); @@ -675,6 +690,13 @@ public: DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CURRENT_DATE); // current date y += FONT_HEIGHT_NORMAL; + if (sel->info.gamescript_version != -1) { + SetDParamStr(0, sel->info.gamescript_name); + SetDParam(1, sel->info.gamescript_version); + DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_GAMESCRIPT); // gamescript name and version + y += FONT_HEIGHT_NORMAL; + } + y += WD_PAR_VSEP_NORMAL; if (!sel->info.compatible) { @@ -740,7 +762,8 @@ public: } case WID_NG_SEARCH_INTERNET: - NetworkUDPQueryMasterServer(); + _network_coordinator_client.GetListing(); + this->searched_internet = true; break; case WID_NG_SEARCH_LAN: @@ -751,7 +774,7 @@ public: SetDParamStr(0, _settings_client.network.connect_to_ip); ShowQueryString( STR_JUST_RAW_STRING, - STR_NETWORK_SERVER_LIST_ENTER_IP, + STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS, NETWORK_HOSTNAME_PORT_LENGTH, // maximum number of characters including '\0' this, CS_ALPHANUMERAL, QSF_ACCEPT_UNCHANGED); break; @@ -856,10 +879,11 @@ public: void OnRealtimeTick(uint delta_ms) override { + if (!this->searched_internet) return; if (!this->requery_timer.Elapsed(delta_ms)) return; - this->requery_timer.SetInterval(MILLISECONDS_PER_TICK); + this->requery_timer.SetInterval(NETWORK_LIST_REFRESH_DELAY * 1000); - NetworkGameListRequery(); + _network_coordinator_client.GetListing(); } }; @@ -1014,7 +1038,7 @@ struct NetworkStartServerWindow : public Window { { switch (widget) { case WID_NSS_CONNTYPE_BTN: - SetDParam(0, _server_visibility_dropdown[_settings_client.network.server_advertise]); + SetDParam(0, STR_NETWORK_SERVER_VISIBILITY_LOCAL + _settings_client.network.server_game_type); break; case WID_NSS_CLIENTS_TXT: @@ -1035,7 +1059,7 @@ struct NetworkStartServerWindow : public Window { { switch (widget) { case WID_NSS_CONNTYPE_BTN: - *size = maxdim(GetStringBoundingBox(_server_visibility_dropdown[0]), GetStringBoundingBox(_server_visibility_dropdown[1])); + *size = maxdim(maxdim(GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_LOCAL), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_PUBLIC)), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY)); size->width += padding.width; size->height += padding.height; break; @@ -1065,7 +1089,7 @@ struct NetworkStartServerWindow : public Window { break; case WID_NSS_CONNTYPE_BTN: // Connection type - ShowDropDownMenu(this, _server_visibility_dropdown, _settings_client.network.server_advertise, WID_NSS_CONNTYPE_BTN, 0, 0); // do it for widget WID_NSS_CONNTYPE_BTN + ShowDropDownList(this, BuildVisibilityDropDownList(), _settings_client.network.server_game_type, WID_NSS_CONNTYPE_BTN); break; case WID_NSS_CLIENTS_BTND: case WID_NSS_CLIENTS_BTNU: // Click on up/down button for number of clients @@ -1143,7 +1167,7 @@ struct NetworkStartServerWindow : public Window { { switch (widget) { case WID_NSS_CONNTYPE_BTN: - _settings_client.network.server_advertise = (index != 0); + _settings_client.network.server_game_type = (ServerGameType)index; break; default: NOT_REACHED(); @@ -1622,21 +1646,31 @@ static const NWidgetPart _nested_client_list_widgets[] = { NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER, STR_NULL), SetPadding(4, 4, 0, 4), SetPIP(0, 2, 0), NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_NAME, STR_NULL), - NWidget(NWID_SPACER), SetMinimalSize(20, 0), + NWidget(NWID_SPACER), SetMinimalSize(10, 0), NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_NAME), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_NAME_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_CL_SERVER_NAME_EDIT), SetMinimalSize(12, 14), SetDataTip(SPR_RENAME, STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP), EndContainer(), NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY, STR_NULL), - NWidget(NWID_SPACER), SetMinimalSize(20, 0), SetFill(1, 0), SetResize(1, 0), + NWidget(NWID_SPACER), SetMinimalSize(10, 0), SetFill(1, 0), SetResize(1, 0), NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_CL_SERVER_VISIBILITY), SetDataTip(STR_BLACK_STRING, STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP), EndContainer(), + NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), + NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE, STR_NULL), + NWidget(NWID_SPACER), SetMinimalSize(10, 0), + NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_INVITE_CODE), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT), + EndContainer(), + NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), + NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE, STR_NULL), + NWidget(NWID_SPACER), SetMinimalSize(10, 0), + NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_CONNECTION_TYPE), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_STRING, STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT), + EndContainer(), EndContainer(), EndContainer(), NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_PLAYER, STR_NULL), SetPadding(4, 4, 4, 4), SetPIP(0, 2, 0), NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_PLAYER_NAME, STR_NULL), - NWidget(NWID_SPACER), SetMinimalSize(20, 0), + NWidget(NWID_SPACER), SetMinimalSize(10, 0), NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_CLIENT_NAME), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_CL_CLIENT_NAME_EDIT), SetMinimalSize(12, 14), SetDataTip(SPR_RENAME, STR_NETWORK_CLIENT_LIST_PLAYER_NAME_EDIT_TOOLTIP), EndContainer(), @@ -2030,7 +2064,7 @@ public: { switch (widget) { case WID_CL_SERVER_VISIBILITY: - *size = maxdim(GetStringBoundingBox(_server_visibility_dropdown[0]), GetStringBoundingBox(_server_visibility_dropdown[1])); + *size = maxdim(maxdim(GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_LOCAL), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_PUBLIC)), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY)); size->width += padding.width; size->height += padding.height; break; @@ -2062,7 +2096,17 @@ public: break; case WID_CL_SERVER_VISIBILITY: - SetDParam(0, _server_visibility_dropdown[_settings_client.network.server_advertise]); + SetDParam(0, STR_NETWORK_SERVER_VISIBILITY_LOCAL + _settings_client.network.server_game_type); + break; + + case WID_CL_SERVER_INVITE_CODE: { + static std::string empty = {}; + SetDParamStr(0, _network_server_connection_type == CONNECTION_TYPE_UNKNOWN ? empty : _network_server_invite_code); + break; + } + + case WID_CL_SERVER_CONNECTION_TYPE: + SetDParam(0, STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN + _network_server_connection_type); break; case WID_CL_CLIENT_NAME: @@ -2096,7 +2140,7 @@ public: case WID_CL_SERVER_VISIBILITY: if (!_network_server) break; - ShowDropDownMenu(this, _server_visibility_dropdown, _settings_client.network.server_advertise, WID_CL_SERVER_VISIBILITY, 0, 0); + ShowDropDownList(this, BuildVisibilityDropDownList(), _settings_client.network.server_game_type, WID_CL_SERVER_VISIBILITY); break; case WID_CL_MATRIX: { @@ -2162,7 +2206,8 @@ public: case WID_CL_SERVER_VISIBILITY: if (!_network_server) break; - _settings_client.network.server_advertise = (index != 0); + _settings_client.network.server_game_type = (ServerGameType)index; + NetworkUpdateServerGameType(); break; case WID_CL_MATRIX: { diff --git a/src/network/network_internal.h b/src/network/network_internal.h index 260984d84e..ffa1269402 100644 --- a/src/network/network_internal.h +++ b/src/network/network_internal.h @@ -11,6 +11,7 @@ #define NETWORK_INTERNAL_H #include "network_func.h" +#include "core/tcp_coordinator.h" #include "core/tcp_game.h" #include "../command_type.h" @@ -89,6 +90,8 @@ extern NetworkJoinStatus _network_join_status; extern uint8 _network_join_waiting; extern uint32 _network_join_bytes; extern uint32 _network_join_bytes_total; +extern ConnectionType _network_server_connection_type; +extern std::string _network_server_invite_code; extern uint8 _network_reconnect; @@ -98,7 +101,7 @@ void NetworkQueryServer(const std::string &connection_string); void NetworkQueryLobbyServer(const std::string &connection_string); void GetBindAddresses(NetworkAddressList *addresses, uint16 port); -struct NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually = true); +struct NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually = true, bool never_expire = false); void NetworkRebuildHostList(); void UpdateNetworkGameWindow(); @@ -127,6 +130,7 @@ StringID GetNetworkErrorMsg(NetworkErrorCode err); bool NetworkMakeClientNameUnique(std::string &new_name); std::string GenerateCompanyPasswordHash(const std::string &password, const std::string &password_server_id, uint32 password_game_seed); +std::string_view ParseCompanyFromConnectionString(const std::string &connection_string, CompanyID *company_id); NetworkAddress ParseConnectionString(const std::string &connection_string, uint16 default_port); std::string NormalizeConnectionString(const std::string &connection_string, uint16 default_port); diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index 39587b0559..4809562de6 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -360,7 +360,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendClientInfo(NetworkClientIn /** Send the client information about the server. */ NetworkRecvStatus ServerNetworkGameSocketHandler::SendGameInfo() { - Packet *p = new Packet(PACKET_SERVER_GAME_INFO, SHRT_MAX); + Packet *p = new Packet(PACKET_SERVER_GAME_INFO, TCP_MTU); SerializeNetworkGameInfo(p, GetCurrentNetworkServerGameInfo()); this->SendPacket(p); @@ -504,7 +504,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendDesyncLog(const std::strin /** Send the check for the NewGRFs. */ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGRFCheck() { - Packet *p = new Packet(PACKET_SERVER_CHECK_NEWGRFS, SHRT_MAX); + Packet *p = new Packet(PACKET_SERVER_CHECK_NEWGRFS, TCP_MTU); const GRFConfig *c; uint grf_count = 0; @@ -2058,9 +2058,6 @@ void NetworkServer_Tick(bool send_frame) #endif } } - - /* See if we need to advertise */ - NetworkUDPAdvertise(); } /** Yearly "callback". Called whenever the year changes. */ diff --git a/src/network/network_stun.cpp b/src/network/network_stun.cpp new file mode 100644 index 0000000000..4af9b1e9d4 --- /dev/null +++ b/src/network/network_stun.cpp @@ -0,0 +1,140 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file network_stun.cpp STUN sending/receiving part of the network protocol. */ + +#include "../stdafx.h" +#include "../debug.h" +#include "network.h" +#include "network_coordinator.h" +#include "network_stun.h" + +#include "../safeguards.h" + +/** Connect to the STUN server. */ +class NetworkStunConnecter : public TCPConnecter { +private: + ClientNetworkStunSocketHandler *stun_handler; + std::string token; + uint8 family; + +public: + /** + * Initiate the connecting. + * @param stun_handler The handler for this request. + * @param connection_string The address of the server. + */ + NetworkStunConnecter(ClientNetworkStunSocketHandler *stun_handler, const std::string &connection_string, const std::string &token, uint8 family) : + TCPConnecter(connection_string, NETWORK_STUN_SERVER_PORT, NetworkAddress(), family), + stun_handler(stun_handler), + token(token), + family(family) + { + } + + void OnFailure() override + { + this->stun_handler->connecter = nullptr; + + /* Connection to STUN server failed. For example, the client doesn't + * support IPv6, which means it will fail that attempt. */ + + _network_coordinator_client.StunResult(this->token, this->family, false); + } + + void OnConnect(SOCKET s) override + { + this->stun_handler->connecter = nullptr; + + assert(this->stun_handler->sock == INVALID_SOCKET); + this->stun_handler->sock = s; + + /* Store the local address; later connects will reuse it again. + * This is what makes STUN work for most NATs. */ + this->stun_handler->local_addr = NetworkAddress::GetSockAddress(s); + + /* We leave the connection open till the real connection is setup later. */ + } +}; + +/** + * Connect to the STUN server over either IPv4 or IPv6. + * @param token The token as received from the Game Coordinator. + * @param family What IP family to use. + */ +void ClientNetworkStunSocketHandler::Connect(const std::string &token, uint8 family) +{ + this->token = token; + this->family = family; + + this->connecter = new NetworkStunConnecter(this, NetworkStunConnectionString(), token, family); +} + +/** + * Send a STUN packet to the STUN server. + * @param token The token as received from the Game Coordinator. + * @param family What IP family this STUN request is for. + * @return The handler for this STUN request. + */ +std::unique_ptr ClientNetworkStunSocketHandler::Stun(const std::string &token, uint8 family) +{ + auto stun_handler = std::make_unique(); + + stun_handler->Connect(token, family); + + Packet *p = new Packet(PACKET_STUN_SERCLI_STUN); + p->Send_uint8(NETWORK_COORDINATOR_VERSION); + p->Send_string(token); + p->Send_uint8(family); + + stun_handler->SendPacket(p); + + return stun_handler; +} + +NetworkRecvStatus ClientNetworkStunSocketHandler::CloseConnection(bool error) +{ + NetworkStunSocketHandler::CloseConnection(error); + + /* If our connecter is still pending, shut it down too. Otherwise the + * callback of the connecter can call into us, and our object is most + * likely about to be destroyed. */ + if (this->connecter != nullptr) { + this->connecter->Kill(); + this->connecter = nullptr; + } + + return NETWORK_RECV_STATUS_OKAY; +} + +/** + * Check whether we received/can send some data from/to the STUN server and + * when that's the case handle it appropriately. + */ +void ClientNetworkStunSocketHandler::SendReceive() +{ + if (this->sock == INVALID_SOCKET) return; + + /* We never attempt to receive anything on a STUN socket. After + * connecting a STUN connection, the local address will be reused to + * to establish the connection with the real server. If we would be to + * read this socket, some OSes get confused and deliver us packets meant + * for the real connection. It appears most OSes play best when we simply + * never attempt to read it to start with (and the packets will remain + * available on the other socket). + * Protocol-wise, the STUN server will never send any packet back anyway. */ + + this->CanSendReceive(); + if (this->SendPackets() == SPS_ALL_SENT && !this->sent_result) { + /* We delay giving the GC the result this long, as to make sure we + * have sent the STUN packet first. This means the GC is more likely + * to have the result ready by the time our StunResult() packet + * arrives. */ + this->sent_result = true; + _network_coordinator_client.StunResult(this->token, this->family, true); + } +} diff --git a/src/network/network_stun.h b/src/network/network_stun.h new file mode 100644 index 0000000000..8ffbff5002 --- /dev/null +++ b/src/network/network_stun.h @@ -0,0 +1,34 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file network_stun.h Part of the network protocol handling STUN requests. */ + +#ifndef NETWORK_STUN_H +#define NETWORK_STUN_H + +#include "core/tcp_stun.h" + +/** Class for handling the client side of the STUN connection. */ +class ClientNetworkStunSocketHandler : public NetworkStunSocketHandler { +private: + std::string token; ///< Token of this STUN handler. + uint8 family = AF_UNSPEC; ///< Family of this STUN handler. + bool sent_result = false; ///< Did we sent the result of the STUN connection? + +public: + TCPConnecter *connecter = nullptr; ///< Connecter instance. + NetworkAddress local_addr; ///< Local addresses of the socket. + + NetworkRecvStatus CloseConnection(bool error = true) override; + void SendReceive(); + + void Connect(const std::string &token, uint8 family); + + static std::unique_ptr Stun(const std::string &token, uint8 family); +}; + +#endif /* NETWORK_STUN_H */ diff --git a/src/network/network_type.h b/src/network/network_type.h index cb859e5c58..f4b6e702c8 100644 --- a/src/network/network_type.h +++ b/src/network/network_type.h @@ -10,8 +10,6 @@ #ifndef NETWORK_TYPE_H #define NETWORK_TYPE_H -#include "core/config.h" - /** How many clients can we have */ static const uint MAX_CLIENTS = 255; @@ -35,6 +33,16 @@ enum NetworkVehicleType { NETWORK_VEH_END }; +/** + * Game type the server can be using. + * Used on the network protocol to communicate with Game Coordinator. + */ +enum ServerGameType : uint8 { + SERVER_GAME_TYPE_LOCAL = 0, + SERVER_GAME_TYPE_PUBLIC, + SERVER_GAME_TYPE_INVITE_ONLY, +}; + /** 'Unique' identifier to be given to clients */ enum ClientID : uint32 { INVALID_CLIENT_ID = 0, ///< Client is not part of anything diff --git a/src/network/network_udp.cpp b/src/network/network_udp.cpp index db54a701c7..fea9b31b1c 100644 --- a/src/network/network_udp.cpp +++ b/src/network/network_udp.cpp @@ -8,7 +8,7 @@ /** * @file network_udp.cpp This file handles the UDP related communication. * - * This is the GameServer <-> MasterServer and GameServer <-> GameClient + * This is the GameServer <-> GameClient * communication before the game is being joined. */ @@ -23,15 +23,10 @@ #include "network.h" #include "../core/endian_func.hpp" #include "../company_base.h" -#include "../thread.h" #include "../rev.h" #include "../newgrf_text.h" #include "../strings_func.h" #include "table/strings.h" -#include -#if defined(__MINGW32__) -#include "../3rdparty/mingw-std-threads/mingw.mutex.h" -#endif #include "core/udp.h" @@ -39,54 +34,31 @@ #include "../safeguards.h" -extern const uint8 _out_of_band_grf_md5[16] { 0x00, 0xB0, 0xC0, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0, 0xC0, 0xDE, 0x00, 0x00, 0x00, 0x00 }; - -/** Session key to register ourselves to the master server */ -static uint64 _session_key = 0; - -static const std::chrono::minutes ADVERTISE_NORMAL_INTERVAL(15); ///< interval between advertising. -static const std::chrono::seconds ADVERTISE_RETRY_INTERVAL(10); ///< re-advertise when no response after this amount of time. -static const uint32 ADVERTISE_RETRY_TIMES = 3; ///< give up re-advertising after this much failed retries - static bool _network_udp_server; ///< Is the UDP server started? static uint16 _network_udp_broadcast; ///< Timeout for the UDP broadcasts. -static uint8 _network_advertise_retries; ///< The number of advertisement retries we did. /** Some information about a socket, which exists before the actual socket has been created to provide locking and the likes. */ struct UDPSocket { const std::string name; ///< The name of the socket. - std::mutex mutex; ///< Mutex for everything that (indirectly) touches the sockets within the handler. NetworkUDPSocketHandler *socket; ///< The actual socket, which may be nullptr when not initialized yet. - std::atomic receive_iterations_locked; ///< The number of receive iterations the mutex was locked. - UDPSocket(const std::string &name_) : name(name_), socket(nullptr) {} + UDPSocket(const std::string &name) : name(name), socket(nullptr) {} void CloseSocket() { - std::lock_guard lock(mutex); - socket->CloseSocket(); - delete socket; - socket = nullptr; + this->socket->CloseSocket(); + delete this->socket; + this->socket = nullptr; } void ReceivePackets() { - std::unique_lock lock(mutex, std::defer_lock); - if (!lock.try_lock()) { - if (++receive_iterations_locked % 32 == 0) { - DEBUG(net, 0, "%s background UDP loop processing appears to be blocked. Your OS may be low on UDP send buffers.", name.c_str()); - } - return; - } - - receive_iterations_locked.store(0); - socket->ReceivePackets(); + this->socket->ReceivePackets(); } }; static UDPSocket _udp_client("Client"); ///< udp client socket static UDPSocket _udp_server("Server"); ///< udp server socket -static UDPSocket _udp_master("Master"); ///< udp master socket static Packet PrepareUdpClientFindServerPacket() { @@ -97,79 +69,13 @@ static Packet PrepareUdpClientFindServerPacket() return p; } -/** - * Helper function doing the actual work for querying the server. - * @param connection_string The address of the server. - * @param needs_mutex Whether we need to acquire locks when sending the packet or not. - * @param manually Whether the address was entered manually. - */ -static void DoNetworkUDPQueryServer(const std::string &connection_string, bool needs_mutex, bool manually) -{ - /* Clear item in gamelist */ - NetworkGameList *item = new NetworkGameList(connection_string, manually); - item->info.server_name = connection_string; - NetworkGameListAddItemDelayed(item); - - std::unique_lock lock(_udp_client.mutex, std::defer_lock); - if (needs_mutex) lock.lock(); - /* Init the packet */ - NetworkAddress address = NetworkAddress(ParseConnectionString(connection_string, NETWORK_DEFAULT_PORT)); - Packet p = PrepareUdpClientFindServerPacket(); - if (_udp_client.socket != nullptr) _udp_client.socket->SendPacket(&p, &address); -} - -/** - * Query a specific server. - * @param connection_string The address of the server. - * @param manually Whether the address was entered manually. - */ -void NetworkUDPQueryServer(const std::string &connection_string, bool manually) -{ - if (!StartNewThread(nullptr, "ottd:udp-query", &DoNetworkUDPQueryServer, std::move(connection_string), true, std::move(manually))) { - DoNetworkUDPQueryServer(connection_string, true, manually); - } -} - -///*** Communication with the masterserver ***/ - -/** Helper class for connecting to the master server. */ -class MasterNetworkUDPSocketHandler : public NetworkUDPSocketHandler { -protected: - void Receive_MASTER_ACK_REGISTER(Packet *p, NetworkAddress *client_addr) override; - void Receive_MASTER_SESSION_KEY(Packet *p, NetworkAddress *client_addr) override; -public: - /** - * Create the socket. - * @param addresses The addresses to bind on. - */ - MasterNetworkUDPSocketHandler(NetworkAddressList *addresses) : NetworkUDPSocketHandler(addresses) {} - virtual ~MasterNetworkUDPSocketHandler() {} -}; - -void MasterNetworkUDPSocketHandler::Receive_MASTER_ACK_REGISTER(Packet *p, NetworkAddress *client_addr) -{ - _network_advertise_retries = 0; - DEBUG(net, 3, "Advertising on master server successful (%s)", NetworkAddress::AddressFamilyAsString(client_addr->GetAddress()->ss_family)); - - /* We are advertised, but we don't want to! */ - if (!_settings_client.network.server_advertise) NetworkUDPRemoveAdvertise(false); -} - -void MasterNetworkUDPSocketHandler::Receive_MASTER_SESSION_KEY(Packet *p, NetworkAddress *client_addr) -{ - _session_key = p->Recv_uint64(); - DEBUG(net, 6, "Received new session key from master server (%s)", NetworkAddress::AddressFamilyAsString(client_addr->GetAddress()->ss_family)); -} - ///*** Communication with clients (we are server) ***/ /** Helper class for handling all server side communication. */ class ServerNetworkUDPSocketHandler : public NetworkUDPSocketHandler { protected: void Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr) override; - void Receive_CLIENT_DETAIL_INFO(Packet *p, NetworkAddress *client_addr) override; - void Receive_CLIENT_GET_NEWGRFS(Packet *p, NetworkAddress *client_addr) override; - void Reply_CLIENT_FIND_SERVER_extended(Packet *p, NetworkAddress *client_addr, const NetworkServerGameInfo *ngi); + void Reply_CLIENT_FIND_SERVER_extended(Packet *p, NetworkAddress *client_addr); public: /** * Create the socket. @@ -181,375 +87,64 @@ public: void ServerNetworkUDPSocketHandler::Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr) { - /* Just a fail-safe.. should never happen */ - if (!_network_udp_server) { - return; - } - if (p->CanReadFromPacket(8) && p->Recv_uint32() == FIND_SERVER_EXTENDED_TOKEN) { - this->Reply_CLIENT_FIND_SERVER_extended(p, client_addr, GetCurrentNetworkServerGameInfo()); + this->Reply_CLIENT_FIND_SERVER_extended(p, client_addr); return; } Packet packet(PACKET_UDP_SERVER_RESPONSE); - SerializeNetworkGameInfo(&packet, GetCurrentNetworkServerGameInfo()); - - /* Let the client know that we are here */ this->SendPacket(&packet, client_addr); DEBUG(net, 7, "Queried from %s", client_addr->GetHostname()); } -void ServerNetworkUDPSocketHandler::Reply_CLIENT_FIND_SERVER_extended(Packet *p, NetworkAddress *client_addr, const NetworkServerGameInfo *ngi) +void ServerNetworkUDPSocketHandler::Reply_CLIENT_FIND_SERVER_extended(Packet *p, NetworkAddress *client_addr) { - uint16 flags = p->Recv_uint16(); + [[maybe_unused]] uint16 flags = p->Recv_uint16(); uint16 version = p->Recv_uint16(); - Packet packet(PACKET_UDP_EX_SERVER_RESPONSE, SHRT_MAX); - SerializeNetworkGameInfoExtended(&packet, ngi, flags, version); - - /* Let the client know that we are here */ - this->SendPacket(&packet, client_addr, false, false, true); - - DEBUG(net, 2, "[udp] queried (extended: %u) from %s", version, client_addr->GetHostname()); -} - -void ServerNetworkUDPSocketHandler::Receive_CLIENT_DETAIL_INFO(Packet *p, NetworkAddress *client_addr) -{ - /* Just a fail-safe.. should never happen */ - if (!_network_udp_server) return; - - Packet packet(PACKET_UDP_SERVER_DETAIL_INFO); - - /* Send the amount of active companies */ - packet.Send_uint8 (NETWORK_COMPANY_INFO_VERSION); - packet.Send_uint8 ((uint8)Company::GetNumItems()); - - /* Fetch the latest version of the stats */ - NetworkCompanyStats company_stats[MAX_COMPANIES]; - NetworkPopulateCompanyStats(company_stats); - - /* The minimum company information "blob" size. */ - static const uint MIN_CI_SIZE = 54; - uint max_cname_length = NETWORK_COMPANY_NAME_LENGTH; - - if (!packet.CanWriteToPacket(Company::GetNumItems() * (MIN_CI_SIZE + NETWORK_COMPANY_NAME_LENGTH))) { - /* Assume we can at least put the company information in the packets. */ - assert(packet.CanWriteToPacket(Company::GetNumItems() * MIN_CI_SIZE)); - - /* At this moment the company names might not fit in the - * packet. Check whether that is really the case. */ - - for (;;) { - size_t required = 0; - for (const Company *company : Company::Iterate()) { - char company_name[NETWORK_COMPANY_NAME_LENGTH]; - SetDParam(0, company->index); - GetString(company_name, STR_COMPANY_NAME, company_name + max_cname_length - 1); - required += MIN_CI_SIZE; - required += strlen(company_name); - } - if (packet.CanWriteToPacket(required)) break; - - /* Try again, with slightly shorter strings. */ - assert(max_cname_length > 0); - max_cname_length--; - } - } - - /* Go through all the companies */ - for (const Company *company : Company::Iterate()) { - /* Send the information */ - this->SendCompanyInformation(&packet, company, &company_stats[company->index], max_cname_length); - } - + Packet packet(PACKET_UDP_EX_SERVER_RESPONSE); this->SendPacket(&packet, client_addr); -} - -/** - * A client has requested the names of some NewGRFs. - * - * Replying this can be tricky as we have a limit of UDP_MTU bytes - * in the reply packet and we can send up to 100 bytes per NewGRF - * (GRF ID, MD5sum and NETWORK_GRF_NAME_LENGTH bytes for the name). - * As UDP_MTU is _much_ less than 100 * NETWORK_MAX_GRF_COUNT, it - * could be that a packet overflows. To stop this we only reply - * with the first N NewGRFs so that if the first N + 1 NewGRFs - * would be sent, the packet overflows. - * in_reply and in_reply_count are used to keep a list of GRFs to - * send in the reply. - */ -void ServerNetworkUDPSocketHandler::Receive_CLIENT_GET_NEWGRFS(Packet *p, NetworkAddress *client_addr) -{ - uint8 num_grfs; - uint i; - - struct GRFInfo { - GRFIdentifier ident; - const char *name = nullptr; - }; - std::vector in_reply; - - DEBUG(net, 7, "NewGRF data request from %s", client_addr->GetAddressAsString().c_str()); - - size_t packet_len = 0; - - num_grfs = p->Recv_uint8 (); - DEBUG(net, 6, "[udp] newgrf data request (%u) from %s", num_grfs, NetworkAddressDumper().GetAddressAsString(client_addr)); - - if (num_grfs > NETWORK_MAX_GRF_COUNT) return; - - auto flush_response = [&]() { - if (in_reply.empty()) return; - - Packet packet(PACKET_UDP_SERVER_NEWGRFS); - packet.Send_uint8(static_cast(in_reply.size())); - for (const GRFInfo &info : in_reply) { - char name[NETWORK_GRF_NAME_LENGTH]; - - /* The name could be an empty string, if so take the filename */ - strecpy(name, info.name, lastof(name)); - SerializeGRFIdentifier(&packet, &info.ident); - packet.Send_string(name); - } - - this->SendPacket(&packet, client_addr); - DEBUG(net, 6, "[udp] sent newgrf data response (%u of %u) to %s", (uint) in_reply.size(), num_grfs, NetworkAddressDumper().GetAddressAsString(client_addr)); - - packet_len = 0; - in_reply.clear(); - }; - - for (i = 0; i < num_grfs; i++) { - GRFInfo info; - DeserializeGRFIdentifier(p, &info.ident); - - if (memcmp(info.ident.md5sum, _out_of_band_grf_md5, 16) == 0) { - if (info.ident.grfid == 0x56D2B000) { - info.name = "=*=*= More than 62 GRFs in total =*=*="; - } else { - continue; - } - } else { - const GRFConfig *f; - /* Find the matching GRF file */ - f = FindGRFConfig(info.ident.grfid, FGCM_EXACT, info.ident.md5sum); - if (f == nullptr) continue; // The GRF is unknown to this server - - info.ident = f->ident; - info.name = f->GetName(); - } - - /* If the reply might exceed the size of the packet, only reply - * the current list and do not send the other data. - * The name could be an empty string, if so take the filename. */ - size_t required_length = sizeof(info.ident.grfid) + sizeof(info.ident.md5sum) + - std::min(strlen(info.name) + 1, (size_t)NETWORK_GRF_NAME_LENGTH); - if (packet_len + required_length > UDP_MTU_SHORT - 4) { // 4 is 3 byte header + grf count in reply - flush_response(); - } - packet_len += required_length; - - in_reply.push_back(info); - } - flush_response(); + DEBUG(net, 7, "Queried (extended: %u) from %s", version, client_addr->GetHostname()); } ///*** Communication with servers (we are client) ***/ /** Helper class for handling all client side communication. */ class ClientNetworkUDPSocketHandler : public NetworkUDPSocketHandler { -private: - void Receive_SERVER_RESPONSE_Common(Packet *p, NetworkAddress *client_addr, bool extended); protected: void Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) override; void Receive_EX_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) override; - void Receive_MASTER_RESPONSE_LIST(Packet *p, NetworkAddress *client_addr) override; - void Receive_SERVER_NEWGRFS(Packet *p, NetworkAddress *client_addr) override; public: virtual ~ClientNetworkUDPSocketHandler() {} }; void ClientNetworkUDPSocketHandler::Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) { - this->Receive_SERVER_RESPONSE_Common(p, client_addr, false); -} - -void ClientNetworkUDPSocketHandler::Receive_EX_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) -{ - this->Receive_SERVER_RESPONSE_Common(p, client_addr, true); -} - -void ClientNetworkUDPSocketHandler::Receive_SERVER_RESPONSE_Common(Packet *p, NetworkAddress *client_addr, bool extended) -{ - NetworkGameList *item; - - /* Just a fail-safe.. should never happen */ - if (_network_udp_server) return; - - DEBUG(net, 3, "%s server response from %s", extended ? " extended" : "", NetworkAddressDumper().GetAddressAsString(client_addr)); - - /* Find next item */ - item = NetworkGameListAddItem(client_addr->GetAddressAsString(false)); - - /* Clear any existing GRFConfig chain. */ - ClearGRFConfigList(&item->info.grfconfig); - if (extended) { - /* Retrieve the NetworkGameInfo from the packet. */ - DeserializeNetworkGameInfoExtended(p, &item->info); - } else { - /* Retrieve the NetworkGameInfo from the packet. */ - DeserializeNetworkGameInfo(p, &item->info); - } - /* Check for compatability with the client. */ - CheckGameCompatibility(item->info, extended); - /* Ensure we consider the server online. */ - item->online = true; - - { - /* Checks whether there needs to be a request for names of GRFs and makes - * the request if necessary. GRFs that need to be requested are the GRFs - * that do not exist on the clients system and we do not have the name - * resolved of, i.e. the name is still UNKNOWN_GRF_NAME_PLACEHOLDER. - * The in_request array and in_request_count are used so there is no need - * to do a second loop over the GRF list, which can be relatively expensive - * due to the string comparisons. */ - const GRFConfig *in_request[NETWORK_MAX_GRF_COUNT]; - const GRFConfig *c; - uint in_request_count = 0; - - auto flush_request = [&]() { - /* There are 'unknown' GRFs, now send a request for them */ - uint i; - Packet packet(PACKET_UDP_CLIENT_GET_NEWGRFS); - - packet.Send_uint8(in_request_count); - for (i = 0; i < in_request_count; i++) { - SerializeGRFIdentifier(&packet, &in_request[i]->ident); - } - - NetworkAddress address = NetworkAddress(ParseConnectionString(item->connection_string, NETWORK_DEFAULT_PORT)); - this->SendPacket(&packet, &address); - - DEBUG(net, 4, "[udp] sent newgrf data request (%u) to %s", in_request_count, NetworkAddressDumper().GetAddressAsString(client_addr)); - - in_request_count = 0; - }; - - for (c = item->info.grfconfig; c != nullptr; c = c->next) { - if (c->status == GCS_NOT_FOUND) item->info.compatible = false; - if (c->status != GCS_NOT_FOUND || strcmp(c->GetName(), UNKNOWN_GRF_NAME_PLACEHOLDER) != 0) continue; - in_request[in_request_count] = c; - in_request_count++; - if (in_request_count == NETWORK_MAX_GRF_COUNT_SHORT) flush_request(); - } - - if (in_request_count > 0) flush_request(); - } - - if (client_addr->GetAddress()->ss_family == AF_INET6) { - item->info.server_name.append(" (IPv6)"); - } - - UpdateNetworkGameWindow(); -} - -void ClientNetworkUDPSocketHandler::Receive_MASTER_RESPONSE_LIST(Packet *p, NetworkAddress *client_addr) -{ - /* packet begins with the protocol version (uint8) - * then an uint16 which indicates how many - * ip:port pairs are in this packet, after that - * an uint32 (ip) and an uint16 (port) for each pair. - */ + DEBUG(net, 3, "Server response from %s", NetworkAddressDumper().GetAddressAsString(client_addr)); - ServerListType type = (ServerListType)(p->Recv_uint8() - 1); - - if (type < SLT_END) { - for (int i = p->Recv_uint16(); i != 0 ; i--) { - sockaddr_storage addr_storage; - memset(&addr_storage, 0, sizeof(addr_storage)); - - if (type == SLT_IPv4) { - addr_storage.ss_family = AF_INET; - ((sockaddr_in*)&addr_storage)->sin_addr.s_addr = TO_LE32(p->Recv_uint32()); - } else { - assert(type == SLT_IPv6); - addr_storage.ss_family = AF_INET6; - byte *addr = (byte*)&((sockaddr_in6*)&addr_storage)->sin6_addr; - for (uint i = 0; i < sizeof(in6_addr); i++) *addr++ = p->Recv_uint8(); - } - NetworkAddress addr(addr_storage, type == SLT_IPv4 ? sizeof(sockaddr_in) : sizeof(sockaddr_in6)); - addr.SetPort(p->Recv_uint16()); - - /* Somehow we reached the end of the packet */ - if (this->HasClientQuit()) return; - - DoNetworkUDPQueryServer(addr.GetAddressAsString(false), false, false); - } - } + NetworkAddServer(client_addr->GetAddressAsString(false), false, true); } -/** The return of the client's request of the names of some NewGRFs */ -void ClientNetworkUDPSocketHandler::Receive_SERVER_NEWGRFS(Packet *p, NetworkAddress *client_addr) +void ClientNetworkUDPSocketHandler::Receive_EX_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) { - uint8 num_grfs; - uint i; - - num_grfs = p->Recv_uint8 (); - DEBUG(net, 7, "NewGRF data reply (%u) from %s", num_grfs, NetworkAddressDumper().GetAddressAsString(client_addr)); - - if (num_grfs > NETWORK_MAX_GRF_COUNT) return; - - for (i = 0; i < num_grfs; i++) { - GRFIdentifier c; + DEBUG(net, 3, "Extended server response from %s", NetworkAddressDumper().GetAddressAsString(client_addr)); - DeserializeGRFIdentifier(p, &c); - std::string name = p->Recv_string(NETWORK_GRF_NAME_LENGTH); - - /* An empty name is not possible under normal circumstances - * and causes problems when showing the NewGRF list. */ - if (name.empty()) continue; - - /* Try to find the GRFTextWrapper for the name of this GRF ID and MD5sum tuple. - * If it exists and not resolved yet, then name of the fake GRF is - * overwritten with the name from the reply. */ - GRFTextWrapper unknown_name = FindUnknownGRFName(c.grfid, c.md5sum, false); - if (unknown_name && strcmp(GetGRFStringFromGRFText(unknown_name), UNKNOWN_GRF_NAME_PLACEHOLDER) == 0) { - AddGRFTextToList(unknown_name, name); - } - } + NetworkAddServer(client_addr->GetAddressAsString(false), false, true); // TODO, mark as extended } /** Broadcast to all ips */ static void NetworkUDPBroadCast(NetworkUDPSocketHandler *socket) { for (NetworkAddress &addr : _broadcast_list) { - Packet p = PrepareUdpClientFindServerPacket(); - DEBUG(net, 5, "Broadcasting to %s", addr.GetHostname()); + Packet p = PrepareUdpClientFindServerPacket(); socket->SendPacket(&p, &addr, true, true); } } - -/** Request the the server-list from the master server */ -void NetworkUDPQueryMasterServer() -{ - Packet p(PACKET_UDP_CLIENT_GET_LIST); - NetworkAddress out_addr(NETWORK_MASTER_SERVER_HOST, NETWORK_MASTER_SERVER_PORT); - - /* packet only contains protocol version */ - p.Send_uint8(NETWORK_MASTER_SERVER_VERSION); - p.Send_uint8(SLT_AUTODETECT); - - std::lock_guard lock(_udp_client.mutex); - _udp_client.socket->SendPacket(&p, &out_addr, true); - - DEBUG(net, 6, "Master server queried at %s", NetworkAddressDumper().GetAddressAsString(&out_addr)); -} - /** Find all servers */ void NetworkUDPSearchGame() { @@ -562,113 +157,6 @@ void NetworkUDPSearchGame() _network_udp_broadcast = 300; // Stay searching for 300 ticks } -/** - * Thread entry point for de-advertising. - */ -static void NetworkUDPRemoveAdvertiseThread() -{ - DEBUG(net, 3, "Removing advertise from master server"); - - /* Find somewhere to send */ - NetworkAddress out_addr(NETWORK_MASTER_SERVER_HOST, NETWORK_MASTER_SERVER_PORT); - - /* Send the packet */ - Packet p(PACKET_UDP_SERVER_UNREGISTER); - /* Packet is: Version, server_port */ - p.Send_uint8 (NETWORK_MASTER_SERVER_VERSION); - p.Send_uint16(_settings_client.network.server_port); - - std::lock_guard lock(_udp_master.mutex); - if (_udp_master.socket != nullptr) _udp_master.socket->SendPacket(&p, &out_addr, true); -} - -/** - * Remove our advertise from the master-server. - * @param blocking whether to wait until the removal has finished. - */ -void NetworkUDPRemoveAdvertise(bool blocking) -{ - /* Check if we are advertising */ - if (!_networking || !_network_server || !_network_udp_server) return; - - if (blocking || !StartNewThread(nullptr, "ottd:udp-advert", &NetworkUDPRemoveAdvertiseThread)) { - NetworkUDPRemoveAdvertiseThread(); - } -} - -/** - * Thread entry point for advertising. - */ -static void NetworkUDPAdvertiseThread() -{ - /* Find somewhere to send */ - NetworkAddress out_addr(NETWORK_MASTER_SERVER_HOST, NETWORK_MASTER_SERVER_PORT); - - DEBUG(net, 3, "Advertising to master server"); - - /* Add a bit more messaging when we cannot get a session key */ - static byte session_key_retries = 0; - if (_session_key == 0 && session_key_retries++ == 2) { - DEBUG(net, 0, "Advertising to the master server is failing"); - DEBUG(net, 0, " we are not receiving the session key from the server"); - DEBUG(net, 0, " please allow udp packets from %s to you to be delivered", NetworkAddressDumper().GetAddressAsString(&out_addr, false)); - DEBUG(net, 0, " please allow udp packets from you to %s to be delivered", NetworkAddressDumper().GetAddressAsString(&out_addr, false)); - } - if (_session_key != 0 && _network_advertise_retries == 0) { - DEBUG(net, 0, "Advertising to the master server is failing"); - DEBUG(net, 0, " we are not receiving the acknowledgement from the server"); - DEBUG(net, 0, " this usually means that the master server cannot reach us"); - DEBUG(net, 0, " please allow udp and tcp packets to port %u to be delivered", _settings_client.network.server_port); - DEBUG(net, 0, " please allow udp and tcp packets from port %u to be delivered", _settings_client.network.server_port); - } - - /* Send the packet */ - Packet p(PACKET_UDP_SERVER_REGISTER); - /* Packet is: WELCOME_MESSAGE, Version, server_port */ - p.Send_string(NETWORK_MASTER_SERVER_WELCOME_MESSAGE); - p.Send_uint8 (NETWORK_MASTER_SERVER_VERSION); - p.Send_uint16(_settings_client.network.server_port); - p.Send_uint64(_session_key); - - std::lock_guard lock(_udp_master.mutex); - if (_udp_master.socket != nullptr) _udp_master.socket->SendPacket(&p, &out_addr, true); -} - -/** - * Register us to the master server - * This function checks if it needs to send an advertise - */ -void NetworkUDPAdvertise() -{ - static std::chrono::steady_clock::time_point _last_advertisement = {}; ///< The last time we performed an advertisement. - - /* Check if we should send an advertise */ - if (!_networking || !_network_server || !_network_udp_server || !_settings_client.network.server_advertise) return; - - if (_network_need_advertise) { - /* Forced advertisement. */ - _network_need_advertise = false; - _network_advertise_retries = ADVERTISE_RETRY_TIMES; - } else { - /* Only send once every ADVERTISE_NORMAL_INTERVAL ticks */ - if (_network_advertise_retries == 0) { - if (std::chrono::steady_clock::now() <= _last_advertisement + ADVERTISE_NORMAL_INTERVAL) return; - - _network_advertise_retries = ADVERTISE_RETRY_TIMES; - } else { - /* An actual retry. */ - if (std::chrono::steady_clock::now() <= _last_advertisement + ADVERTISE_RETRY_INTERVAL) return; - } - } - - _network_advertise_retries--; - _last_advertisement = std::chrono::steady_clock::now(); - - if (!StartNewThread(nullptr, "ottd:udp-advert", &NetworkUDPAdvertiseThread)) { - NetworkUDPAdvertiseThread(); - } -} - /** Initialize the whole UDP bit. */ void NetworkUDPInitialize() { @@ -676,15 +164,7 @@ void NetworkUDPInitialize() if (_udp_server.socket != nullptr) NetworkUDPClose(); DEBUG(net, 3, "Initializing UDP listeners"); - assert(_udp_client.socket == nullptr && _udp_server.socket == nullptr && _udp_master.socket == nullptr); - - // std::scoped_lock lock(_udp_client.mutex, _udp_server.mutex, _udp_master.mutex); - - /* Avoid std::scoped_lock for MacOS 10.12 compatibility */ - std::unique_lock lock1(_udp_client.mutex, std::defer_lock); - std::unique_lock lock2(_udp_server.mutex, std::defer_lock); - std::unique_lock lock3(_udp_master.mutex, std::defer_lock); - std::lock(lock1, lock2, lock3); + assert(_udp_client.socket == nullptr && _udp_server.socket == nullptr); _udp_client.socket = new ClientNetworkUDPSocketHandler(); @@ -692,19 +172,13 @@ void NetworkUDPInitialize() GetBindAddresses(&server, _settings_client.network.server_port); _udp_server.socket = new ServerNetworkUDPSocketHandler(&server); - server.clear(); - GetBindAddresses(&server, 0); - _udp_master.socket = new MasterNetworkUDPSocketHandler(&server); - _network_udp_server = false; _network_udp_broadcast = 0; - _network_advertise_retries = 0; } /** Start the listening of the UDP server component. */ void NetworkUDPServerListen() { - std::lock_guard lock(_udp_server.mutex); _network_udp_server = _udp_server.socket->Listen(); } @@ -713,7 +187,6 @@ void NetworkUDPClose() { _udp_client.CloseSocket(); _udp_server.CloseSocket(); - _udp_master.CloseSocket(); _network_udp_server = false; _network_udp_broadcast = 0; @@ -725,7 +198,6 @@ void NetworkBackgroundUDPLoop() { if (_network_udp_server) { _udp_server.ReceivePackets(); - _udp_master.ReceivePackets(); } else { _udp_client.ReceivePackets(); if (_network_udp_broadcast > 0) _network_udp_broadcast--; diff --git a/src/network/network_udp.h b/src/network/network_udp.h index ced949cd32..b640da018d 100644 --- a/src/network/network_udp.h +++ b/src/network/network_udp.h @@ -14,10 +14,6 @@ void NetworkUDPInitialize(); void NetworkUDPSearchGame(); -void NetworkUDPQueryMasterServer(); -void NetworkUDPQueryServer(const std::string &connection_string, bool manually = false); -void NetworkUDPAdvertise(); -void NetworkUDPRemoveAdvertise(bool blocking); void NetworkUDPClose(); void NetworkUDPServerListen(); void NetworkBackgroundUDPLoop(); diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 0b2fdda6ba..c011a8f3e9 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -39,7 +39,7 @@ #include "strings_func.h" #include "date_func.h" #include "string_func.h" -#include "network/network.h" +#include "network/core/config.h" #include "smallmap_gui.h" #include "genworld.h" #include "error.h" @@ -52,6 +52,7 @@ #include "table/build_industry.h" #include "3rdparty/cpp-btree/btree_map.h" +#include #include "safeguards.h" @@ -82,7 +83,6 @@ static uint32 _ttdpatch_flags[8]; GRFLoadedFeatures _loaded_newgrf_features; static const uint MAX_SPRITEGROUP = UINT8_MAX; ///< Maximum GRF-local ID for a spritegroup. -static const uint MAX_GRF_COUNT = 300; ///< Maximum number of NewGRF files that could be loaded. /** Base GRF ID for OpenTTD's base graphics GRFs. */ static const uint32 OPENTTD_GRAPHICS_BASE_GRF_ID = BSWAP32(0xFF4F5400); @@ -10637,12 +10637,6 @@ void LoadNewGRF(uint load_index, uint num_baseset) num_non_static++; } - if (num_grfs >= MAX_GRF_COUNT) { - DEBUG(grf, 0, "'%s' is not loaded as the maximum number of file slots has been reached", c->filename); - c->status = GCS_DISABLED; - c->error = new GRFError(STR_NEWGRF_ERROR_MSG_FATAL, STR_NEWGRF_ERROR_TOO_MANY_NEWGRFS_LOADED); - continue; - } num_grfs++; LoadNewGRFFile(c, stage, subdir, false); diff --git a/src/openttd.cpp b/src/openttd.cpp index 632cacad20..e2f461d5e8 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -786,7 +786,7 @@ int openttd_main(int argc, char *argv[]) if (res != SL_OK || _load_check_data.HasErrors()) { fprintf(stderr, "Failed to open savegame\n"); if (_load_check_data.HasErrors()) { - InitializeLanguagePacks(); + InitializeLanguagePacks(); // A language pack is needed for GetString() char buf[256]; SetDParamStr(0, _load_check_data.error_data); GetString(buf, _load_check_data.error, lastof(buf)); @@ -1887,24 +1887,8 @@ void StateGameLoop() */ static void DoAutosave() { - char buf[MAX_PATH]; - - if (_settings_client.gui.keep_all_autosave) { - GenerateDefaultSaveName(buf, lastof(buf)); - strecat(buf, ".sav", lastof(buf)); - } else { - static int _autosave_ctr = 0; - - /* generate a savegame name and number according to _settings_client.gui.max_num_autosaves */ - seprintf(buf, lastof(buf), "autosave%d.sav", _autosave_ctr); - - if (++_autosave_ctr >= _settings_client.gui.max_num_autosaves) _autosave_ctr = 0; - } - - DEBUG(sl, 2, "Autosaving to '%s'", buf); - if (SaveOrLoad(buf, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR, true, SMF_ZSTD_OK) != SL_OK) { - ShowErrorMessage(STR_ERROR_AUTOSAVE_FAILED, INVALID_STRING_ID, WL_ERROR); - } + static int _autosave_ctr = 0; + DoAutoOrNetsave(_autosave_ctr); } /** diff --git a/src/os/windows/win32.cpp b/src/os/windows/win32.cpp index a743719a84..cb34391e67 100644 --- a/src/os/windows/win32.cpp +++ b/src/os/windows/win32.cpp @@ -428,6 +428,9 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi * be available between subsequent calls to FS2OTTD(). */ char *cmdline = stredup(FS2OTTD(GetCommandLine()).c_str()); + /* Set the console codepage to UTF-8. */ + SetConsoleOutputCP(CP_UTF8); + #if defined(_DEBUG) CreateConsole(); #endif diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index b2ff9b78fb..3235614482 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -3635,6 +3635,40 @@ SaveOrLoadResult SaveOrLoad(const std::string &filename, SaveLoadOperation fop, } } +/** + * Create an autosave or netsave. + * @param counter A reference to the counter variable to be used for rotating the file name. + * @param netsave Indicates if this is a regular autosave or a netsave. + */ +void DoAutoOrNetsave(int &counter, bool netsave) +{ + char buf[MAX_PATH]; + + if (_settings_client.gui.keep_all_autosave) { + GenerateDefaultSaveName(buf, lastof(buf)); + if (!netsave) { + strecat(buf, ".sav", lastof(buf)); + } else { + strecat(buf, "-netsave.sav", lastof(buf)); + } + } else { + /* Generate a savegame name and number according to _settings_client.gui.max_num_autosaves. */ + if (!netsave) { + seprintf(buf, lastof(buf), "autosave%d.sav", counter); + } else { + seprintf(buf, lastof(buf), "netsave%d.sav", counter); + } + + if (++counter >= _settings_client.gui.max_num_autosaves) counter = 0; + } + + DEBUG(sl, 2, "Autosaving to '%s'", buf); + if (SaveOrLoad(buf, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR, !netsave, SMF_ZSTD_OK) != SL_OK) { + ShowErrorMessage(STR_ERROR_AUTOSAVE_FAILED, INVALID_STRING_ID, WL_ERROR); + } +} + + /** Do a save when exiting the game (_settings_client.gui.autosave_on_exit) */ void DoExitSave() { diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index a0dd9fdb3f..fe30a9e837 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -67,6 +67,8 @@ void WaitTillSaved(); void ProcessAsyncSaveFinish(); void DoExitSave(); +void DoAutoOrNetsave(int &counter, bool netsave = false); + SaveOrLoadResult SaveWithFilter(struct SaveFilter *writer, bool threaded, SaveModeFlags flags); SaveOrLoadResult LoadWithFilter(struct LoadFilter *reader); bool IsNetworkServerSave(); diff --git a/src/settings.cpp b/src/settings.cpp index 48ce5bcff7..3e5d726d42 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -29,6 +29,7 @@ #include "screenshot.h" #include "network/network.h" #include "network/network_func.h" +#include "network/core/config.h" #include "settings_internal.h" #include "command_func.h" #include "console_func.h" @@ -1886,6 +1887,7 @@ static GRFConfig *GRFLoadConfig(IniFile &ini, const char *grpname, bool is_stati if (group == nullptr) return nullptr; + uint num_grfs = 0; for (item = group->item; item != nullptr; item = item->next) { GRFConfig *c = nullptr; @@ -1960,8 +1962,14 @@ static GRFConfig *GRFLoadConfig(IniFile &ini, const char *grpname, bool is_stati continue; } - /* Mark file as static to avoid saving in savegame. */ - if (is_static) SetBit(c->flags, GCF_STATIC); + if (is_static) { + /* Mark file as static to avoid saving in savegame. */ + SetBit(c->flags, GCF_STATIC); + } else if (++num_grfs > NETWORK_MAX_GRF_COUNT) { + /* Check we will not load more non-static NewGRFs than allowed. This could trigger issues for game servers. */ + ShowErrorMessage(STR_CONFIG_ERROR, STR_NEWGRF_ERROR_TOO_MANY_NEWGRFS_LOADED, WL_CRITICAL); + break; + } /* Add item to list */ *curr = c; @@ -2587,15 +2595,14 @@ void IConsoleSetSetting(const char *name, const char *value, bool force_newgame) if (sd->IsStringSetting()) { success = SetSettingValue(sd->AsStringSetting(), value, force_newgame); } else if (sd->IsIntSetting()) { - uint32 val; - extern bool GetArgumentInteger(uint32 *value, const char *arg); - success = GetArgumentInteger(&val, value); - if (!success) { - IConsolePrintF(CC_ERROR, "'%s' is not an integer.", value); + const IntSettingDesc *isd = sd->AsIntSetting(); + size_t val = isd->ParseValue(value); + if (!_settings_error_list.empty()) { + IConsolePrintF(CC_ERROR, "'%s' is not a valid value for this setting.", value); + _settings_error_list.clear(); return; } - - success = SetSettingValue(sd->AsIntSetting(), val, force_newgame); + success = SetSettingValue(isd, (int32)val, force_newgame); } if (!success) { diff --git a/src/settings_type.h b/src/settings_type.h index a76b8f690f..26425330ae 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -14,7 +14,7 @@ #include "economy_type.h" #include "town_type.h" #include "transport_type.h" -#include "network/core/config.h" +#include "network/network_type.h" #include "company_type.h" #include "cargotype.h" #include "linkgraph/linkgraph_type.h" @@ -318,27 +318,30 @@ struct NewsSettings { /** All settings related to the network. */ struct NetworkSettings { - uint16 sync_freq; ///< how often do we check whether we are still in-sync - uint8 frame_freq; ///< how often do we send commands to the clients - uint16 commands_per_frame; ///< how many commands may be sent each frame_freq frames? - uint16 max_commands_in_queue; ///< how many commands may there be in the incoming queue before dropping the connection? - uint16 bytes_per_frame; ///< how many bytes may, over a long period, be received per frame? - uint16 bytes_per_frame_burst; ///< how many bytes may, over a short period, be received? - uint16 max_init_time; ///< maximum amount of time, in game ticks, a client may take to initiate joining - uint16 max_join_time; ///< maximum amount of time, in game ticks, a client may take to sync up during joining - uint16 max_download_time; ///< maximum amount of time, in game ticks, a client may take to download the map - uint16 max_password_time; ///< maximum amount of time, in game ticks, a client may take to enter the password - uint16 max_lag_time; ///< maximum amount of time, in game ticks, a client may be lagging behind the server - bool pause_on_join; ///< pause the game when people join - uint16 server_port; ///< port the server listens on - uint16 server_admin_port; ///< port the server listens on for the admin network - bool server_admin_chat; ///< allow private chat for the server to be distributed to the admin network + uint16 sync_freq; ///< how often do we check whether we are still in-sync + uint8 frame_freq; ///< how often do we send commands to the clients + uint16 commands_per_frame; ///< how many commands may be sent each frame_freq frames? + uint16 max_commands_in_queue; ///< how many commands may there be in the incoming queue before dropping the connection? + uint16 bytes_per_frame; ///< how many bytes may, over a long period, be received per frame? + uint16 bytes_per_frame_burst; ///< how many bytes may, over a short period, be received? + uint16 max_init_time; ///< maximum amount of time, in game ticks, a client may take to initiate joining + uint16 max_join_time; ///< maximum amount of time, in game ticks, a client may take to sync up during joining + uint16 max_download_time; ///< maximum amount of time, in game ticks, a client may take to download the map + uint16 max_password_time; ///< maximum amount of time, in game ticks, a client may take to enter the password + uint16 max_lag_time; ///< maximum amount of time, in game ticks, a client may be lagging behind the server + bool pause_on_join; ///< pause the game when people join + uint16 server_port; ///< port the server listens on + uint16 server_admin_port; ///< port the server listens on for the admin network + bool server_admin_chat; ///< allow private chat for the server to be distributed to the admin network + ServerGameType server_game_type; ///< Server type: local / public / invite-only. + std::string server_invite_code; ///< Invite code to use when registering as server. + std::string server_invite_code_secret; ///< Secret to proof we got this invite code from the Game Coordinator. std::string server_name; ///< name of the server std::string server_password; ///< password for joining this server std::string rcon_password; ///< password for rconsole (server side) std::string admin_password; ///< password for the admin network std::string settings_password; ///< password for game settings (server side) - bool server_advertise; ///< advertise the server to the masterserver + bool server_advertise; ///< Advertise the server to the game coordinator. std::string client_name; ///< name of the player (as client) std::string default_company_pass; ///< default password for new companies in encrypted form std::string connect_to_ip; ///< default for the "Add server" query diff --git a/src/strings.cpp b/src/strings.cpp index cf84df0003..628ac52837 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -2219,18 +2219,18 @@ const char *GetCurrentLocale(const char *param) { const char *env; - env = getenv("LANGUAGE"); + env = std::getenv("LANGUAGE"); if (env != nullptr) return env; - env = getenv("LC_ALL"); + env = std::getenv("LC_ALL"); if (env != nullptr) return env; if (param != nullptr) { - env = getenv(param); + env = std::getenv(param); if (env != nullptr) return env; } - return getenv("LANG"); + return std::getenv("LANG"); } #else const char *GetCurrentLocale(const char *param); diff --git a/src/table/settings/network_secrets_settings.ini b/src/table/settings/network_secrets_settings.ini index 2a01a79f41..98d2b03e0d 100644 --- a/src/table/settings/network_secrets_settings.ini +++ b/src/table/settings/network_secrets_settings.ini @@ -84,3 +84,17 @@ type = SLE_STR length = NETWORK_SERVER_ID_LENGTH flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY def = nullptr + +[SDTC_SSTR] +var = network.server_invite_code +type = SLE_STR +length = NETWORK_INVITE_CODE_LENGTH +flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY +def = nullptr + +[SDTC_SSTR] +var = network.server_invite_code_secret +type = SLE_STR +length = NETWORK_INVITE_CODE_SECRET_LENGTH +flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY +def = nullptr diff --git a/src/table/settings/network_settings.ini b/src/table/settings/network_settings.ini index b96df71b30..550b838565 100644 --- a/src/table/settings/network_settings.ini +++ b/src/table/settings/network_settings.ini @@ -9,14 +9,18 @@ [pre-amble] static void UpdateClientConfigValues(); +static std::initializer_list _server_game_type{"local", "public", "invite-only"}; + static const SettingTable _network_settings = { [post-amble] }; [templates] SDTC_BOOL = SDTC_BOOL( $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $extver, $cat, $guiproc, $startup, nullptr), +SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $extver, $cat, $guiproc, $startup, nullptr), SDTC_VAR = SDTC_VAR( $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $extver, $cat, $guiproc, $startup, nullptr), [validation] +SDTC_OMANY = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size"); SDTC_VAR = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size"); [defaults] @@ -161,10 +165,16 @@ flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY def = true cat = SC_EXPERT -[SDTC_BOOL] -var = network.server_advertise +[SDTC_OMANY] +var = network.server_game_type +type = SLE_UINT8 flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY -def = false +def = SERVER_GAME_TYPE_LOCAL +min = SERVER_GAME_TYPE_LOCAL +max = SERVER_GAME_TYPE_INVITE_ONLY +full = _server_game_type +post_cb = [](auto) { NetworkUpdateServerGameType(); } +cat = SC_BASIC [SDTC_BOOL] var = network.autoclean_companies diff --git a/src/table/settings/settings.ini b/src/table/settings/settings.ini index 0d144df084..f3609cf777 100644 --- a/src/table/settings/settings.ini +++ b/src/table/settings/settings.ini @@ -1557,14 +1557,14 @@ post_cb = MaxVehiclesChanged cat = SC_BASIC [SDTG_BOOL] -name = nullptr +name = ""vehicle.servint_ispercent"" flags = SF_NO_NETWORK var = _old_vds.servint_ispercent def = false to = SLV_120 [SDTG_VAR] -name = nullptr +name = ""vehicle.servint_trains"" type = SLE_UINT16 flags = SF_GUI_0_IS_SPECIAL var = _old_vds.servint_trains @@ -1574,7 +1574,7 @@ max = 800 to = SLV_120 [SDTG_VAR] -name = nullptr +name = ""vehicle.servint_roadveh"" type = SLE_UINT16 flags = SF_GUI_0_IS_SPECIAL var = _old_vds.servint_roadveh @@ -1584,7 +1584,7 @@ max = 800 to = SLV_120 [SDTG_VAR] -name = nullptr +name = ""vehicle.servint_ships"" type = SLE_UINT16 flags = SF_GUI_0_IS_SPECIAL var = _old_vds.servint_ships @@ -1594,7 +1594,7 @@ max = 800 to = SLV_120 [SDTG_VAR] -name = nullptr +name = ""vehicle.servint_aircraft"" type = SLE_UINT16 flags = SF_GUI_0_IS_SPECIAL var = _old_vds.servint_aircraft diff --git a/src/terraform_cmd.cpp b/src/terraform_cmd.cpp index 551109a078..e876c51b46 100644 --- a/src/terraform_cmd.cpp +++ b/src/terraform_cmd.cpp @@ -16,6 +16,7 @@ #include "object_base.h" #include "company_base.h" #include "company_func.h" +#include "core/backup_type.hpp" #include "table/strings.h" @@ -279,8 +280,8 @@ CommandCost CmdTerraformLand(TileIndex tile, DoCommandFlag flags, uint32 p1, uin bool indirectly_cleared = coa != nullptr && coa->first_tile != t; /* Check tiletype-specific things, and add extra-cost */ - const bool curr_gen = _generating_world; - if (_game_mode == GM_EDITOR) _generating_world = true; // used to create green terraformed land + Backup old_generating_world(_generating_world, FILE_LINE); + if (_game_mode == GM_EDITOR) old_generating_world.Change(true); // used to create green terraformed land DoCommandFlag tile_flags = flags | DC_AUTO | DC_FORCE_CLEAR_TILE; if (pass == 0) { tile_flags &= ~DC_EXEC; @@ -292,7 +293,7 @@ CommandCost CmdTerraformLand(TileIndex tile, DoCommandFlag flags, uint32 p1, uin } else { cost = _tile_type_procs[GetTileType(t)]->terraform_tile_proc(t, tile_flags, z_min, tileh); } - _generating_world = curr_gen; + old_generating_world.Restore(); if (cost.Failed()) { _terraform_err_tile = t; return cost; diff --git a/src/terraform_gui.cpp b/src/terraform_gui.cpp index c3feaaf33e..133c71b8f7 100644 --- a/src/terraform_gui.cpp +++ b/src/terraform_gui.cpp @@ -8,6 +8,7 @@ /** @file terraform_gui.cpp GUI related to terraforming the map. */ #include "stdafx.h" +#include "core/backup_type.hpp" #include "clear_map.h" #include "company_func.h" #include "company_base.h" @@ -62,7 +63,7 @@ static void GenerateDesertArea(TileIndex end, TileIndex start) { if (_game_mode != GM_EDITOR) return; - _generating_world = true; + Backup old_generating_world(_generating_world, true, FILE_LINE); TileArea ta(start, end); for (TileIndex tile : ta) { @@ -70,7 +71,7 @@ static void GenerateDesertArea(TileIndex end, TileIndex start) DoCommandP(tile, 0, 0, CMD_LANDSCAPE_CLEAR); MarkTileDirtyByTile(tile); } - _generating_world = false; + old_generating_world.Restore(); InvalidateWindowClassesData(WC_TOWN_VIEW, 0); } @@ -591,7 +592,7 @@ static void ResetLandscapeConfirmationCallback(Window *w, bool confirmed) if (confirmed) { /* Set generating_world to true to get instant-green grass after removing * company property. */ - _generating_world = true; + Backup old_generating_world(_generating_world, true, FILE_LINE); /* Delete all companies */ for (Company *c : Company::Iterate()) { @@ -599,7 +600,7 @@ static void ResetLandscapeConfirmationCallback(Window *w, bool confirmed) delete c; } - _generating_world = false; + old_generating_world.Restore(); /* Delete all station signs */ for (BaseStation *st : BaseStation::Iterate()) { diff --git a/src/town_gui.cpp b/src/town_gui.cpp index 0995f7aad0..07099e9aa9 100644 --- a/src/town_gui.cpp +++ b/src/town_gui.cpp @@ -27,6 +27,7 @@ #include "querystring_gui.h" #include "window_func.h" #include "townname_func.h" +#include "core/backup_type.hpp" #include "core/geometry_func.hpp" #include "genworld.h" #include "stringfilter_type.h" @@ -1202,15 +1203,16 @@ public: this->SetFocusedWidget(WID_TF_TOWN_NAME_EDITBOX); break; - case WID_TF_MANY_RANDOM_TOWNS: - _generating_world = true; + case WID_TF_MANY_RANDOM_TOWNS: { + Backup old_generating_world(_generating_world, true, FILE_LINE); UpdateNearestTownForRoadTiles(true); if (!GenerateTowns(this->town_layout)) { ShowErrorMessage(STR_ERROR_CAN_T_GENERATE_TOWN, STR_ERROR_NO_SPACE_FOR_TOWN, WL_INFO); } UpdateNearestTownForRoadTiles(false); - _generating_world = false; + old_generating_world.Restore(); break; + } case WID_TF_SIZE_SMALL: case WID_TF_SIZE_MEDIUM: case WID_TF_SIZE_LARGE: case WID_TF_SIZE_RANDOM: this->town_size = (TownSize)(widget - WID_TF_SIZE_SMALL); diff --git a/src/widgets/network_widget.h b/src/widgets/network_widget.h index a96a56fa81..c8ec22e861 100644 --- a/src/widgets/network_widget.h +++ b/src/widgets/network_widget.h @@ -101,6 +101,8 @@ enum ClientListWidgets { WID_CL_SERVER_NAME, ///< Server name. WID_CL_SERVER_NAME_EDIT, ///< Edit button for server name. WID_CL_SERVER_VISIBILITY, ///< Server visibility. + WID_CL_SERVER_INVITE_CODE, ///< Invite code for this server. + WID_CL_SERVER_CONNECTION_TYPE, ///< The type of connection the Game Coordinator detected for this server. WID_CL_CLIENT_NAME, ///< Client name. WID_CL_CLIENT_NAME_EDIT, ///< Edit button for client name. WID_CL_MATRIX, ///< Company/client list.