From 64523709bffa32b2a61ff8aeb522182636b2efea Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Sun, 12 Feb 2023 12:07:31 +0100 Subject: [PATCH 01/30] Add: use https:// for content-service connections (#10448) This requires the use of WinHTTP (for Windows) or libcurl (for all others except Emscripten). Emscripten does not support http(s) calls currently. On Linux it requires ca-certificates to be installed, so the HTTPS certificate can be validated. It is really likely this is installed on any modern machine, as most connections these days are HTTPS. (On MacOS and Windows the certificate store is filled by default) Reminder: in case the http(s):// connection cannot be established, OpenTTD falls back to a custom TCP-based connection to fetch the content from the content-service. Emscripten will always do this. --- .github/workflows/ci-build.yml | 2 + .github/workflows/codeql.yml | 1 + .github/workflows/release-linux.yml | 1 + .github/workflows/release-macos.yml | 2 + CMakeLists.txt | 13 ++ COMPILING.md | 5 +- src/crashlog.cpp | 13 ++ src/network/core/CMakeLists.txt | 16 +- src/network/core/config.cpp | 10 +- src/network/core/config.h | 5 +- src/network/core/http.h | 65 ++++++ src/network/core/http_curl.cpp | 227 ++++++++++++++++++++ src/network/core/http_none.cpp | 37 ++++ src/network/core/http_winhttp.cpp | 316 +++++++++++++++++++++++++++ src/network/core/tcp_http.cpp | 322 ---------------------------- src/network/core/tcp_http.h | 121 ----------- src/network/network.cpp | 2 + src/network/network_content.cpp | 9 +- src/network/network_content.h | 2 +- 19 files changed, 705 insertions(+), 464 deletions(-) create mode 100644 src/network/core/http.h create mode 100644 src/network/core/http_curl.cpp create mode 100644 src/network/core/http_none.cpp create mode 100644 src/network/core/http_winhttp.cpp delete mode 100644 src/network/core/tcp_http.cpp delete mode 100644 src/network/core/tcp_http.h diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 4d83725045..54b314d9e8 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -102,6 +102,7 @@ jobs: echo "::group::Install dependencies" sudo apt-get install -y --no-install-recommends \ liballegro4-dev \ + libcurl4-openssl-dev \ libfontconfig-dev \ libicu-dev \ liblzma-dev \ @@ -184,6 +185,7 @@ jobs: - name: Prepare vcpkg run: | vcpkg install --triplet=${{ matrix.arch }}-osx \ + curl \ liblzma \ libpng \ lzo \ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d30e9b3755..153a4706c7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -31,6 +31,7 @@ jobs: echo "::group::Install dependencies" sudo apt-get install -y --no-install-recommends \ liballegro4-dev \ + libcurl4-openssl-dev \ libfontconfig-dev \ libicu-dev \ liblzma-dev \ diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index c0cd7babe4..dd05777d0e 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -29,6 +29,7 @@ jobs: yum install -y \ fontconfig-devel \ freetype-devel \ + libcurl4-openssl-dev \ libicu-devel \ libpng-devel \ lzo-devel \ diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 50df24c7ca..8323260501 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -45,6 +45,8 @@ jobs: - name: Prepare vcpkg run: | vcpkg install \ + curl:x64-osx \ + curl:arm64-osx \ liblzma:x64-osx \ liblzma:arm64-osx \ libpng:x64-osx \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 29549d8634..e91ecf613b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,6 +124,14 @@ find_package(LibLZMA) find_package(LZO) find_package(PNG) +if(WIN32 OR EMSCRIPTEN) + # Windows uses WinHttp for HTTP requests. + # Emscripten uses Javascript for HTTP requests. +else() + # All other targets use libcurl. + find_package(CURL) +endif() + if(NOT OPTION_DEDICATED) if(NOT WIN32) find_package(Allegro) @@ -259,6 +267,10 @@ link_package(ZLIB TARGET ZLIB::ZLIB ENCOURAGED) link_package(LIBLZMA TARGET LibLZMA::LibLZMA ENCOURAGED) link_package(LZO) +if(NOT WIN32 AND NOT EMSCRIPTEN) + link_package(CURL ENCOURAGED) +endif() + if(NOT OPTION_DEDICATED) link_package(Fluidsynth) link_package(SDL) @@ -368,6 +380,7 @@ if(WIN32) imm32 usp10 psapi + winhttp ) endif() diff --git a/COMPILING.md b/COMPILING.md index 63ae18e76c..5b1d457224 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -10,14 +10,17 @@ OpenTTD makes use of the following external libraries: - (encouraged) libpng: making screenshots and loading heightmaps - (optional) liblzo2: (de)compressing of old (pre 0.3.0) savegames -For Linux, the following additional libraries are used (for non-dedicated only): +For Linux, the following additional libraries are used: +- (encouraged) libcurl: content downloads - libSDL2: hardware access (video, sound, mouse) - libfreetype: loading generic fonts and rendering them - libfontconfig: searching for fonts, resolving font names to actual fonts - libicu: handling of right-to-left scripts (e.g. Arabic and Persian) and natural sorting of strings +If you are building a dedicated-server only, you don't need the last four. + OpenTTD does not require any of the libraries to be present, but without liblzma you cannot open most recent savegames and without zlib you cannot open most older savegames or use the content downloading system. diff --git a/src/crashlog.cpp b/src/crashlog.cpp index 594884c712..5d26a47278 100644 --- a/src/crashlog.cpp +++ b/src/crashlog.cpp @@ -64,6 +64,9 @@ #ifdef WITH_ZLIB # include #endif +#ifdef WITH_CURL +# include +#endif #include "safeguards.h" @@ -273,6 +276,16 @@ char *CrashLog::LogLibraries(char *buffer, const char *last) const buffer += seprintf(buffer, last, " Zlib: %s\n", zlibVersion()); #endif +#ifdef WITH_CURL + auto *curl_v = curl_version_info(CURLVERSION_NOW); + buffer += seprintf(buffer, last, " Curl: %s\n", curl_v->version); + if (curl_v->ssl_version != nullptr) { + buffer += seprintf(buffer, last, " Curl SSL: %s\n", curl_v->ssl_version); + } else { + buffer += seprintf(buffer, last, " Curl SSL: none\n"); + } +#endif + buffer += seprintf(buffer, last, "\n"); return buffer; } diff --git a/src/network/core/CMakeLists.txt b/src/network/core/CMakeLists.txt index 756fa9e8f3..9e7a2ed506 100644 --- a/src/network/core/CMakeLists.txt +++ b/src/network/core/CMakeLists.txt @@ -9,6 +9,7 @@ add_files( game_info.h host.cpp host.h + http.h os_abstraction.cpp os_abstraction.h packet.cpp @@ -25,8 +26,6 @@ add_files( tcp_coordinator.h tcp_game.cpp tcp_game.h - tcp_http.cpp - tcp_http.h tcp_listen.h tcp_stun.cpp tcp_stun.h @@ -35,3 +34,16 @@ add_files( udp.cpp udp.h ) + +add_files( + http_curl.cpp + CONDITION CURL_FOUND +) +add_files( + http_winhttp.cpp + CONDITION WIN32 +) +add_files( + http_none.cpp + CONDITION NOT CURL_FOUND AND NOT WIN32 +) diff --git a/src/network/core/config.cpp b/src/network/core/config.cpp index 6e7f8a11c3..c2f4467d8e 100644 --- a/src/network/core/config.cpp +++ b/src/network/core/config.cpp @@ -59,11 +59,11 @@ const char *NetworkContentServerConnectionString() } /** - * 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. + * Get the URI string for the content mirror from the environment variable OTTD_CONTENT_MIRROR_URI, + * or when it has not been set a hard coded URI of the production server. + * @return The content mirror's URI string. */ -const char *NetworkContentMirrorConnectionString() +const char *NetworkContentMirrorUriString() { - return GetEnv("OTTD_CONTENT_MIRROR_CS", "binaries.openttd.org"); + return GetEnv("OTTD_CONTENT_MIRROR_URI", "https://binaries.openttd.org/bananas"); } diff --git a/src/network/core/config.h b/src/network/core/config.h index 667dceef58..175d865a8b 100644 --- a/src/network/core/config.h +++ b/src/network/core/config.h @@ -15,10 +15,7 @@ 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"; +const char *NetworkContentMirrorUriString(); 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) diff --git a/src/network/core/http.h b/src/network/core/http.h new file mode 100644 index 0000000000..cad503862f --- /dev/null +++ b/src/network/core/http.h @@ -0,0 +1,65 @@ +/* + * 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 http.h Basic functions to send and receive HTTP packets. + */ + +#ifndef NETWORK_CORE_HTTP_H +#define NETWORK_CORE_HTTP_H + +#include "tcp.h" + +/** Callback for when the HTTP handler has something to tell us. */ +struct HTTPCallback { + /** + * An error has occurred and the connection has been closed. + * @note HTTP socket handler is closed/freed. + */ + virtual void OnFailure() = 0; + + /** + * We're receiving data. + * @param data the received data, nullptr when all data has been received. + * @param length the amount of received data, 0 when all data has been received. + * @note When nullptr is sent the HTTP socket handler is closed/freed. + */ + virtual void OnReceiveData(const char *data, size_t length) = 0; + + /** Silentium */ + virtual ~HTTPCallback() {} +}; + +/** Base socket handler for HTTP traffic. */ +class NetworkHTTPSocketHandler { +public: + /** + * Connect to the given URI. + * + * @param uri the URI to connect to (https://.../..). + * @param callback the callback to send data back on. + * @param data optionally, the data we want to send. When set, this will be a POST request, otherwise a GET request. + */ + static void Connect(const std::string &uri, HTTPCallback *callback, const char *data = nullptr); + + /** + * Do the receiving for all HTTP connections. + */ + static void HTTPReceive(); +}; + +/** + * Initialize the HTTP socket handler. + */ +void NetworkHTTPInitialize(); + +/** + * Uninitialize the HTTP socket handler. + */ +void NetworkHTTPUninitialize(); + +#endif /* NETWORK_CORE_HTTP_H */ diff --git a/src/network/core/http_curl.cpp b/src/network/core/http_curl.cpp new file mode 100644 index 0000000000..5933c52dd8 --- /dev/null +++ b/src/network/core/http_curl.cpp @@ -0,0 +1,227 @@ +/* + * 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 http_curl.cpp CURL-based implementation for HTTP requests. + */ + +#include "../../stdafx.h" +#include "../../debug.h" +#include "../../rev.h" +#include "../network_internal.h" + +#include "http.h" + +#include + +#include "../../safeguards.h" + +/** Single HTTP request. */ +class NetworkHTTPRequest { +private: + std::string uri; ///< URI to connect to. + HTTPCallback *callback; ///< Callback to send data back on. + const char *data; ///< Data to send, if any. + + CURL *curl = nullptr; ///< CURL handle. + CURLM *multi_handle = nullptr; ///< CURL multi-handle. + +public: + NetworkHTTPRequest(const std::string &uri, HTTPCallback *callback, const char *data = nullptr); + + ~NetworkHTTPRequest(); + + void Connect(); + bool Receive(); +}; + +static std::vector _http_requests; +static std::vector _new_http_requests; +static CURLSH *_curl_share = nullptr; + +/** + * Create a new HTTP request. + * + * @param uri the URI to connect to (https://.../..). + * @param callback the callback to send data back on. + * @param data optionally, the data we want to send. When set, this will be a POST request, otherwise a GET request. + */ +NetworkHTTPRequest::NetworkHTTPRequest(const std::string &uri, HTTPCallback *callback, const char *data) : + uri(uri), + callback(callback), + data(data) +{ +} + +/** + * Start the HTTP request handling. + * + * This is done in an async manner, so we can do other things while waiting for + * the HTTP request to finish. The actual receiving of the data is done in + * Receive(). + */ +void NetworkHTTPRequest::Connect() +{ + Debug(net, 1, "HTTP request to {}", uri); + + this->curl = curl_easy_init(); + assert(this->curl != nullptr); + + if (_debug_net_level >= 5) { + curl_easy_setopt(this->curl, CURLOPT_VERBOSE, 1L); + } + + curl_easy_setopt(curl, CURLOPT_SHARE, _curl_share); + + if (this->data != nullptr) { + curl_easy_setopt(this->curl, CURLOPT_POST, 1L); + curl_easy_setopt(this->curl, CURLOPT_POSTFIELDS, this->data); + } + + curl_easy_setopt(this->curl, CURLOPT_URL, this->uri.c_str()); + + /* Setup our (C-style) callback function which we pipe back into the callback. */ + curl_easy_setopt(this->curl, CURLOPT_WRITEFUNCTION, +[](char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t { + Debug(net, 4, "HTTP callback: {} bytes", size * nmemb); + HTTPCallback *callback = static_cast(userdata); + callback->OnReceiveData(ptr, size * nmemb); + return size * nmemb; + }); + curl_easy_setopt(this->curl, CURLOPT_WRITEDATA, this->callback); + + /* Setup some default options. */ + std::string user_agent = fmt::format("OpenTTD/{}", GetNetworkRevisionString()); + curl_easy_setopt(this->curl, CURLOPT_USERAGENT, user_agent.c_str()); + curl_easy_setopt(this->curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(this->curl, CURLOPT_MAXREDIRS, 5L); + + /* Give the connection about 10 seconds to complete. */ + curl_easy_setopt(this->curl, CURLOPT_CONNECTTIMEOUT, 10L); + + /* Set a buffer of 100KiB, as the default of 16KiB seems a bit small. */ + curl_easy_setopt(this->curl, CURLOPT_BUFFERSIZE, 100L * 1024L); + + /* Fail our call if we don't receive a 2XX return value. */ + curl_easy_setopt(this->curl, CURLOPT_FAILONERROR, 1L); + + /* Create a multi-handle so we can do the call async. */ + this->multi_handle = curl_multi_init(); + curl_multi_add_handle(this->multi_handle, this->curl); + + /* Trigger it for the first time so it becomes active. */ + int still_running; + curl_multi_perform(this->multi_handle, &still_running); +} + +/** + * Poll and process the HTTP request/response. + * + * @return True iff the request is done; no call to Receive() should be done after it returns true. + */ +bool NetworkHTTPRequest::Receive() +{ + int still_running = 0; + + /* Check for as long as there is activity on the socket, but once in a while return. + * This allows the GUI to always update, even on really fast downloads. */ + for (int count = 0; count < 100; count++) { + /* Check if there was activity in the multi-handle. */ + int numfds; + curl_multi_poll(this->multi_handle, NULL, 0, 0, &numfds); + if (numfds == 0) return false; + + /* Let CURL process the activity. */ + curl_multi_perform(this->multi_handle, &still_running); + if (still_running == 0) break; + } + + /* The download is still pending (so the count is reached). Update GUI. */ + if (still_running != 0) return false; + + /* The request is done; check the result and close up. */ + int msgq; + CURLMsg *msg = curl_multi_info_read(this->multi_handle, &msgq); + /* We can safely assume this returns something, as otherwise the multi-handle wouldn't be empty. */ + assert(msg != nullptr); + assert(msg->msg == CURLMSG_DONE); + + CURLcode res = msg->data.result; + if (res == CURLE_OK) { + Debug(net, 1, "HTTP request succeeded"); + this->callback->OnReceiveData(nullptr, 0); + } else { + Debug(net, 0, "HTTP request failed: {}", curl_easy_strerror(res)); + this->callback->OnFailure(); + } + + return true; +} + +/** + * Destructor of the HTTP request. + * + * Makes sure all handlers are closed, and all memory is free'd. + */ +NetworkHTTPRequest::~NetworkHTTPRequest() +{ + if (this->curl) { + curl_multi_remove_handle(this->multi_handle, this->curl); + curl_multi_cleanup(this->multi_handle); + + curl_easy_cleanup(this->curl); + } + + free(this->data); +} + +/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const char *data) +{ + auto request = new NetworkHTTPRequest(uri, callback, data); + request->Connect(); + _new_http_requests.push_back(request); +} + +/* static */ void NetworkHTTPSocketHandler::HTTPReceive() +{ + if (!_new_http_requests.empty()) { + /* We delay adding new requests, as Receive() below can cause a callback which adds a new requests. */ + _http_requests.insert(_http_requests.end(), _new_http_requests.begin(), _new_http_requests.end()); + _new_http_requests.clear(); + } + + if (_http_requests.empty()) return; + + for (auto it = _http_requests.begin(); it != _http_requests.end(); /* nothing */) { + NetworkHTTPRequest *cur = *it; + + if (cur->Receive()) { + it = _http_requests.erase(it); + delete cur; + continue; + } + + ++it; + } +} + +void NetworkHTTPInitialize() +{ + curl_global_init(CURL_GLOBAL_DEFAULT); + + /* Create a share that tracks DNS, SSL session, and connections. As this + * always runs in the same thread, sharing a connection should be fine. */ + _curl_share = curl_share_init(); + curl_share_setopt(_curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); + curl_share_setopt(_curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); + curl_share_setopt(_curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); +} + +void NetworkHTTPUninitialize() +{ + curl_share_cleanup(_curl_share); + curl_global_cleanup(); +} diff --git a/src/network/core/http_none.cpp b/src/network/core/http_none.cpp new file mode 100644 index 0000000000..aae03cbff8 --- /dev/null +++ b/src/network/core/http_none.cpp @@ -0,0 +1,37 @@ +/* + * 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 http_emscripten.cpp Emscripten-based implementation for HTTP requests. + */ + +#include "../../stdafx.h" +#include "../../debug.h" +#include "../../rev.h" +#include "../network_internal.h" + +#include "http.h" + +#include "../../safeguards.h" + +/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const char *data) +{ + /* No valid HTTP backend was compiled in, so we fail all HTTP requests. */ + callback->OnFailure(); +} + +/* static */ void NetworkHTTPSocketHandler::HTTPReceive() +{ +} + +void NetworkHTTPInitialize() +{ +} + +void NetworkHTTPUninitialize() +{ +} diff --git a/src/network/core/http_winhttp.cpp b/src/network/core/http_winhttp.cpp new file mode 100644 index 0000000000..209316759f --- /dev/null +++ b/src/network/core/http_winhttp.cpp @@ -0,0 +1,316 @@ +/* + * 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 http_winhttp.cpp WinHTTP-based implementation for HTTP requests. + */ + +#include "../../stdafx.h" +#include "../../debug.h" +#include "../../rev.h" +#include "../network_internal.h" + +#include "http.h" + +#include + +#include "../../safeguards.h" + +static HINTERNET _winhttp_session = nullptr; + +/** Single HTTP request. */ +class NetworkHTTPRequest { +private: + std::wstring uri; ///< URI to connect to. + HTTPCallback *callback; ///< Callback to send data back on. + const char *data; ///< Data to send, if any. + + HINTERNET connection = nullptr; ///< Current connection object. + HINTERNET request = nullptr; ///< Current request object. + bool finished = false; ///< Whether we are finished with the request. + int depth = 0; ///< Current redirect depth we are in. + +public: + NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *callback, const char *data = nullptr); + + ~NetworkHTTPRequest(); + + void Connect(); + bool Receive(); + void WinHttpCallback(DWORD code, void *info, DWORD length); +}; + +static std::vector _http_requests; +static std::vector _new_http_requests; + +/** + * Create a new HTTP request. + * + * @param uri the URI to connect to (https://.../..). + * @param callback the callback to send data back on. + * @param data optionally, the data we want to send. When set, this will be a POST request, otherwise a GET request. + */ +NetworkHTTPRequest::NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *callback, const char *data) : + uri(uri), + callback(callback), + data(data) +{ +} + +static std::string GetLastErrorAsString() +{ + char buffer[512]; + DWORD error_code = GetLastError(); + + if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandleA("winhttp.dll"), error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), NULL) == 0) { + return fmt::format("unknown error {}", error_code); + } + + return buffer; +} + +/** + * Callback from the WinHTTP library, called when-ever something changes about the HTTP request status. + * + * The callback needs to call some WinHttp functions for certain states, so WinHttp continues + * to read the request. This also allows us to abort when things go wrong, by simply not calling + * those functions. + * Comments with "Next step:" mark where WinHttp needs a call to continue. + * + * @param code The code of the event. + * @param info The information about the event. + * @param length The length of the information. + */ +void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length) +{ + switch (code) { + case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME: + case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED: + case WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER: + case WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER: + case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: + case WINHTTP_CALLBACK_STATUS_REQUEST_SENT: + case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE: + case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED: + case WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION: + case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED: + case WINHTTP_CALLBACK_STATUS_HANDLE_CREATED: + case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: + /* We don't care about these events, and explicitly ignore them. */ + break; + + case WINHTTP_CALLBACK_STATUS_REDIRECT: + /* Make sure we are not in a redirect loop. */ + if (this->depth++ > 5) { + Debug(net, 0, "HTTP request failed: too many redirects"); + this->callback->OnFailure(); + this->finished = true; + return; + } + break; + + case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: + /* Next step: read response. */ + WinHttpReceiveResponse(this->request, nullptr); + break; + + case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: + { + /* Retrieve the status code. */ + DWORD status_code = 0; + DWORD status_code_size = sizeof(status_code); + WinHttpQueryHeaders(this->request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &status_code, &status_code_size, WINHTTP_NO_HEADER_INDEX); + Debug(net, 3, "HTTP request status code: {}", status_code); + + /* If there is any error, we simply abort the request. */ + if (status_code >= 400) { + Debug(net, 0, "HTTP request failed: status-code {}", status_code); + this->callback->OnFailure(); + this->finished = true; + return; + } + + /* Next step: query for any data. */ + WinHttpQueryDataAvailable(this->request, nullptr); + } break; + + case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: + { + /* Retrieve the amount of data available to process. */ + DWORD size = *(DWORD *)info; + + /* Next step: read the data in a temporary allocated buffer. + * The buffer will be free'd in the next step. */ + char *buffer = size == 0 ? nullptr : MallocT(size); + WinHttpReadData(this->request, buffer, size, 0); + } break; + + case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: + Debug(net, 4, "HTTP callback: {} bytes", length); + + this->callback->OnReceiveData(static_cast(info), length); + /* Free the temporary buffer that was allocated in the previous step. */ + free(info); + + if (length == 0) { + /* Next step: no more data available: request is finished. */ + this->finished = true; + Debug(net, 1, "HTTP request succeeded"); + } else { + /* Next step: query for more data. */ + WinHttpQueryDataAvailable(this->request, nullptr); + } + + break; + + case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE: + case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: + Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString()); + this->callback->OnFailure(); + this->finished = true; + break; + + default: + Debug(net, 0, "HTTP request failed: unexepected callback code 0x{:x}", code); + this->callback->OnFailure(); + this->finished = true; + return; + } +} + +static void CALLBACK StaticWinHttpCallback(HINTERNET handle, DWORD_PTR context, DWORD code, void *info, DWORD length) +{ + NetworkHTTPRequest *request = (NetworkHTTPRequest *)context; + request->WinHttpCallback(code, info, length); +} + +/** + * Start the HTTP request handling. + * + * This is done in an async manner, so we can do other things while waiting for + * the HTTP request to finish. The actual receiving of the data is done in + * Receive(). + */ +void NetworkHTTPRequest::Connect() +{ + Debug(net, 1, "HTTP request to {}", std::string(uri.begin(), uri.end())); + + URL_COMPONENTS url_components = {}; + wchar_t scheme[32]; + wchar_t hostname[128]; + wchar_t url_path[4096]; + + /* Convert the URL to its components. */ + url_components.dwStructSize = sizeof(url_components); + url_components.lpszScheme = scheme; + url_components.dwSchemeLength = lengthof(scheme); + url_components.lpszHostName = hostname; + url_components.dwHostNameLength = lengthof(hostname); + url_components.lpszUrlPath = url_path; + url_components.dwUrlPathLength = lengthof(url_path); + WinHttpCrackUrl(this->uri.c_str(), 0, 0, &url_components); + + /* Create the HTTP connection. */ + this->connection = WinHttpConnect(_winhttp_session, url_components.lpszHostName, url_components.nPort, 0); + if (this->connection == nullptr) { + Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString()); + this->callback->OnFailure(); + this->finished = true; + return; + } + + this->request = WinHttpOpenRequest(connection, data == nullptr ? L"GET" : L"POST", url_components.lpszUrlPath, nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, url_components.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0); + if (this->request == nullptr) { + WinHttpCloseHandle(this->connection); + + Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString()); + this->callback->OnFailure(); + this->finished = true; + return; + } + + /* Send the request (possibly with a payload). */ + if (data == nullptr) { + WinHttpSendRequest(this->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, reinterpret_cast(this)); + } else { + WinHttpSendRequest(this->request, L"Content-Type: application/x-www-form-urlencoded\r\n", -1, const_cast(data), static_cast(strlen(data)), static_cast(strlen(data)), reinterpret_cast(this)); + } +} + +/** + * Poll and process the HTTP request/response. + * + * @return True iff the request is done; no call to Receive() should be done after it returns true. + */ +bool NetworkHTTPRequest::Receive() +{ + return this->finished; +} + +/** + * Destructor of the HTTP request. + * + * Makes sure all handlers are closed, and all memory is free'd. + */ +NetworkHTTPRequest::~NetworkHTTPRequest() +{ + if (this->request) { + WinHttpCloseHandle(this->request); + WinHttpCloseHandle(this->connection); + } + + free(this->data); +} + +/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const char *data) +{ + auto request = new NetworkHTTPRequest(std::wstring(uri.begin(), uri.end()), callback, data); + request->Connect(); + _new_http_requests.push_back(request); +} + +/* static */ void NetworkHTTPSocketHandler::HTTPReceive() +{ + if (!_new_http_requests.empty()) { + /* We delay adding new requests, as Receive() below can cause a callback which adds a new requests. */ + _http_requests.insert(_http_requests.end(), _new_http_requests.begin(), _new_http_requests.end()); + _new_http_requests.clear(); + } + + if (_http_requests.empty()) return; + + for (auto it = _http_requests.begin(); it != _http_requests.end(); /* nothing */) { + NetworkHTTPRequest *cur = *it; + + if (cur->Receive()) { + it = _http_requests.erase(it); + delete cur; + continue; + } + + ++it; + } +} + +void NetworkHTTPInitialize() +{ + /* We create a single session, from which we build up every other request. */ + std::string user_agent = fmt::format("OpenTTD/{}", GetNetworkRevisionString()); + _winhttp_session = WinHttpOpen(std::wstring(user_agent.begin(), user_agent.end()).c_str(), WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC); + + /* Set the callback function for all requests. The "context" maps it back into the actual request instance. */ + WinHttpSetStatusCallback(_winhttp_session, StaticWinHttpCallback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0); + + /* 10 seconds timeout for requests. */ + WinHttpSetTimeouts(_winhttp_session, 10000, 10000, 10000, 10000); +} + +void NetworkHTTPUninitialize() +{ + WinHttpCloseHandle(_winhttp_session); +} diff --git a/src/network/core/tcp_http.cpp b/src/network/core/tcp_http.cpp deleted file mode 100644 index 3472a7642a..0000000000 --- a/src/network/core/tcp_http.cpp +++ /dev/null @@ -1,322 +0,0 @@ -/* - * 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_http.cpp Basic functions to receive and send HTTP TCP packets. - */ - -#include "../../stdafx.h" -#include "../../debug.h" -#include "../../rev.h" -#include "../network_internal.h" -#include "game_info.h" - -#include "tcp_http.h" - -#include "../../safeguards.h" - -/** List of open HTTP connections. */ -static std::vector _http_connections; - -/** - * Start the querying - * @param s the socket of this connection - * @param callback the callback for HTTP retrieval - * @param host the hostname of the server to connect to - * @param url the url at the server - * @param data the data to send - * @param depth the depth (redirect recursion) of the queries - */ -NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s, - HTTPCallback *callback, const std::string &host, const char *url, - const char *data, int depth) : - NetworkSocketHandler(), - recv_pos(0), - recv_length(0), - callback(callback), - data(data), - redirect_depth(depth), - sock(s) -{ - Debug(net, 5, "[tcp/http] Requesting {}{}", host, url); - std::string request; - if (data != nullptr) { - request = fmt::format("POST {} HTTP/1.0\r\nHost: {}\r\nUser-Agent: OpenTTD/{}\r\nContent-Type: text/plain\r\nContent-Length: {}\r\n\r\n{}\r\n", url, host, GetNetworkRevisionString(), strlen(data), data); - } else { - request = fmt::format("GET {} HTTP/1.0\r\nHost: {}\r\nUser-Agent: OpenTTD/{}\r\n\r\n", url, host, GetNetworkRevisionString()); - } - - ssize_t res = send(this->sock, request.data(), (int)request.size(), 0); - if (res != (ssize_t)request.size()) { - /* Sending all data failed. Socket can't handle this little bit - * of information? Just fall back to the old system! */ - this->callback->OnFailure(); - delete this; - return; - } - - _http_connections.push_back(this); -} - -/** Free whatever needs to be freed. */ -NetworkHTTPSocketHandler::~NetworkHTTPSocketHandler() -{ - this->CloseSocket(); - - free(this->data); -} - -/** - * Close the actual socket of the connection. - */ -void NetworkHTTPSocketHandler::CloseSocket() -{ - if (this->sock != INVALID_SOCKET) closesocket(this->sock); - this->sock = INVALID_SOCKET; -} - -/** - * Helper to simplify the error handling. - * @param msg the error message to show. - */ -#define return_error(msg) { Debug(net, 1, msg); return -1; } - -static const char * const NEWLINE = "\r\n"; ///< End of line marker -static const char * const END_OF_HEADER = "\r\n\r\n"; ///< End of header marker -static const char * const HTTP_1_0 = "HTTP/1.0 "; ///< Preamble for HTTP 1.0 servers -static const char * const HTTP_1_1 = "HTTP/1.1 "; ///< Preamble for HTTP 1.1 servers -static const char * const CONTENT_LENGTH = "Content-Length: "; ///< Header for the length of the content -static const char * const LOCATION = "Location: "; ///< Header for location - -/** - * Handle the header of a HTTP reply. - * @return amount of data to continue downloading. - * > 0: we need to download N bytes. - * = 0: we're being redirected. - * < 0: an error occurred. Downloading failed. - * @note if an error occurred the header might not be in its - * original state. No effort is undertaken to bring - * the header in its original state. - */ -int NetworkHTTPSocketHandler::HandleHeader() -{ - assert(strlen(HTTP_1_0) == strlen(HTTP_1_1)); - assert(strstr(this->recv_buffer, END_OF_HEADER) != nullptr); - - /* We expect a HTTP/1.[01] reply */ - if (strncmp(this->recv_buffer, HTTP_1_0, strlen(HTTP_1_0)) != 0 && - strncmp(this->recv_buffer, HTTP_1_1, strlen(HTTP_1_1)) != 0) { - return_error("[tcp/http] Received invalid HTTP reply"); - } - - char *status = this->recv_buffer + strlen(HTTP_1_0); - if (strncmp(status, "200", 3) == 0) { - /* We are going to receive a document. */ - - /* Get the length of the document to receive */ - char *length = strcasestr(this->recv_buffer, CONTENT_LENGTH); - if (length == nullptr) return_error("[tcp/http] Missing 'content-length' header"); - - /* Skip the header */ - length += strlen(CONTENT_LENGTH); - - /* Search the end of the line. This is safe because the header will - * always end with two newlines. */ - char *end_of_line = strstr(length, NEWLINE); - - /* Read the length */ - *end_of_line = '\0'; - int len = atoi(length); - /* Restore the header. */ - *end_of_line = '\r'; - - /* Make sure we're going to download at least something; - * zero sized files are, for OpenTTD's purposes, always - * wrong. You can't have gzips of 0 bytes! */ - if (len == 0) return_error("[tcp/http] Refusing to download 0 bytes"); - - Debug(net, 7, "[tcp/http] Downloading {} bytes", len); - return len; - } - - if (strncmp(status, "301", 3) != 0 && - strncmp(status, "302", 3) != 0 && - strncmp(status, "303", 3) != 0 && - strncmp(status, "307", 3) != 0) { - /* We are not going to be redirected :(. */ - - /* Search the end of the line. This is safe because the header will - * always end with two newlines. */ - *strstr(status, NEWLINE) = '\0'; - Debug(net, 1, "[tcp/http] Unhandled status reply {}", status); - return -1; - } - - if (this->redirect_depth == 5) return_error("[tcp/http] Too many redirects, looping redirects?"); - - /* Redirect to other URL */ - char *uri = strcasestr(this->recv_buffer, LOCATION); - if (uri == nullptr) return_error("[tcp/http] Missing 'location' header for redirect"); - - uri += strlen(LOCATION); - - /* Search the end of the line. This is safe because the header will - * always end with two newlines. */ - char *end_of_line = strstr(uri, NEWLINE); - *end_of_line = '\0'; - - Debug(net, 7, "[tcp/http] Redirecting to {}", uri); - - int ret = NetworkHTTPSocketHandler::Connect(uri, this->callback, this->data, this->redirect_depth + 1); - if (ret != 0) return ret; - - /* We've relinquished control of data now. */ - this->data = nullptr; - - /* Restore the header. */ - *end_of_line = '\r'; - return 0; -} - -/** - * Connect to the given URI. - * @param uri the URI to connect to. - * @param callback the callback to send data back on. - * @param data the data we want to send (as POST). - * @param depth the recursion/redirect depth. - */ -/* static */ int NetworkHTTPSocketHandler::Connect(char *uri, HTTPCallback *callback, const char *data, int depth) -{ - char *hname = strstr(uri, "://"); - if (hname == nullptr) return_error("[tcp/http] Invalid location"); - - hname += 3; - - char *url = strchr(hname, '/'); - if (url == nullptr) return_error("[tcp/http] Invalid location"); - - *url = '\0'; - - std::string hostname = std::string(hname); - - /* Restore the URL. */ - *url = '/'; - new NetworkHTTPContentConnecter(hostname, callback, url, data, depth); - return 0; -} - -#undef return_error - -/** - * Handle receiving of HTTP data. - * @return state of the receival of HTTP data. - * > 0: we need more cycles for downloading - * = 0: we are done downloading - * < 0: we have hit an error - */ -int NetworkHTTPSocketHandler::Receive() -{ - for (;;) { - ssize_t res = recv(this->sock, (char *)this->recv_buffer + this->recv_pos, lengthof(this->recv_buffer) - this->recv_pos, 0); - if (res == -1) { - NetworkError err = NetworkError::GetLast(); - if (!err.WouldBlock()) { - /* Something went wrong... */ - if (!err.IsConnectionReset()) Debug(net, 0, "Recv failed: {}", err.AsString()); - return -1; - } - /* Connection would block, so stop for now */ - return 1; - } - - /* No more data... did we get everything we wanted? */ - if (res == 0) { - if (this->recv_length != 0) return -1; - - this->callback->OnReceiveData(nullptr, 0); - return 0; - } - - /* Wait till we read the end-of-header identifier */ - if (this->recv_length == 0) { - ssize_t read = this->recv_pos + res; - ssize_t end = std::min(read, lengthof(this->recv_buffer) - 1); - - /* Do a 'safe' search for the end of the header. */ - char prev = this->recv_buffer[end]; - this->recv_buffer[end] = '\0'; - char *end_of_header = strstr(this->recv_buffer, END_OF_HEADER); - this->recv_buffer[end] = prev; - - if (end_of_header == nullptr) { - if (read == lengthof(this->recv_buffer)) { - Debug(net, 1, "[tcp/http] Header too big"); - return -1; - } - this->recv_pos = read; - } else { - int ret = this->HandleHeader(); - if (ret <= 0) return ret; - - this->recv_length = ret; - - end_of_header += strlen(END_OF_HEADER); - int len = std::min(read - (end_of_header - this->recv_buffer), res); - if (len != 0) { - this->callback->OnReceiveData(end_of_header, len); - this->recv_length -= len; - } - - this->recv_pos = 0; - } - } else { - res = std::min(this->recv_length, res); - /* Receive whatever we're expecting. */ - this->callback->OnReceiveData(this->recv_buffer, res); - this->recv_length -= res; - } - } -} - -/** - * Do the receiving for all HTTP connections. - */ -/* static */ void NetworkHTTPSocketHandler::HTTPReceive() -{ - /* No connections, just bail out. */ - if (_http_connections.size() == 0) return; - - fd_set read_fd; - struct timeval tv; - - FD_ZERO(&read_fd); - for (NetworkHTTPSocketHandler *handler : _http_connections) { - FD_SET(handler->sock, &read_fd); - } - - tv.tv_sec = tv.tv_usec = 0; // don't block at all. - int n = select(FD_SETSIZE, &read_fd, nullptr, nullptr, &tv); - if (n == -1) return; - - for (auto iter = _http_connections.begin(); iter < _http_connections.end(); /* nothing */) { - NetworkHTTPSocketHandler *cur = *iter; - - if (FD_ISSET(cur->sock, &read_fd)) { - int ret = cur->Receive(); - /* First send the failure. */ - if (ret < 0) cur->callback->OnFailure(); - if (ret <= 0) { - /* Then... the connection can be closed */ - cur->CloseSocket(); - iter = _http_connections.erase(iter); - delete cur; - continue; - } - } - iter++; - } -} diff --git a/src/network/core/tcp_http.h b/src/network/core/tcp_http.h deleted file mode 100644 index 608d50200c..0000000000 --- a/src/network/core/tcp_http.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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_http.h Basic functions to receive and send HTTP TCP packets. - */ - -#ifndef NETWORK_CORE_TCP_HTTP_H -#define NETWORK_CORE_TCP_HTTP_H - -#include "tcp.h" - -/** Callback for when the HTTP handler has something to tell us. */ -struct HTTPCallback { - /** - * An error has occurred and the connection has been closed. - * @note HTTP socket handler is closed/freed. - */ - virtual void OnFailure() = 0; - - /** - * We're receiving data. - * @param data the received data, nullptr when all data has been received. - * @param length the amount of received data, 0 when all data has been received. - * @note When nullptr is sent the HTTP socket handler is closed/freed. - */ - virtual void OnReceiveData(const char *data, size_t length) = 0; - - /** Silentium */ - virtual ~HTTPCallback() {} -}; - -/** Base socket handler for HTTP traffic. */ -class NetworkHTTPSocketHandler : public NetworkSocketHandler { -private: - char recv_buffer[4096]; ///< Partially received message. - int recv_pos; ///< Current position in buffer. - int recv_length; ///< Length of the data still retrieving. - HTTPCallback *callback; ///< The callback to call for the incoming data. - const char *data; ///< The (POST) data we might want to forward (to a redirect). - int redirect_depth; ///< The depth of the redirection. - - int HandleHeader(); - int Receive(); -public: - SOCKET sock; ///< The socket currently connected to - - /** - * Whether this socket is currently bound to a socket. - * @return true when the socket is bound, false otherwise - */ - bool IsConnected() const - { - return this->sock != INVALID_SOCKET; - } - - void CloseSocket(); - - NetworkHTTPSocketHandler(SOCKET sock, HTTPCallback *callback, - const std::string &host, const char *url, const char *data, int depth); - - ~NetworkHTTPSocketHandler(); - - static int Connect(char *uri, HTTPCallback *callback, - const char *data = nullptr, int depth = 0); - - static void HTTPReceive(); -}; - -/** Connect with a HTTP server and do ONE query. */ -class NetworkHTTPContentConnecter : TCPConnecter { - std::string hostname; ///< Hostname we are connecting to. - HTTPCallback *callback; ///< Callback to tell that we received some data (or won't). - const char *url; ///< The URL we want to get at the server. - const char *data; ///< The data to send - int depth; ///< How far we have recursed - -public: - /** - * Start the connecting. - * @param hostname The hostname to connect to. - * @param callback The callback for HTTP retrieval. - * @param url The url at the server. - * @param data The data to send. - * @param depth The depth (redirect recursion) of the queries. - */ - NetworkHTTPContentConnecter(const std::string &hostname, HTTPCallback *callback, const char *url, const char *data = nullptr, int depth = 0) : - TCPConnecter(hostname, 80), - hostname(hostname), - callback(callback), - url(stredup(url)), - data(data), - depth(depth) - { - } - - /** Free all our allocated data. */ - ~NetworkHTTPContentConnecter() - { - free(this->url); - } - - void OnFailure() override - { - this->callback->OnFailure(); - free(this->data); - } - - void OnConnect(SOCKET s) override - { - new NetworkHTTPSocketHandler(s, this->callback, this->hostname, this->url, this->data, this->depth); - /* We've relinquished control of data now. */ - this->data = nullptr; - } -}; - -#endif /* NETWORK_CORE_TCP_HTTP_H */ diff --git a/src/network/network.cpp b/src/network/network.cpp index 05e606993f..8acc740994 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -1268,12 +1268,14 @@ void NetworkStartUp() NetworkUDPInitialize(); Debug(net, 3, "Network online, multiplayer available"); NetworkFindBroadcastIPs(&_broadcast_list); + NetworkHTTPInitialize(); } /** This shuts the network down */ void NetworkShutDown() { NetworkDisconnect(true); + NetworkHTTPUninitialize(); NetworkUDPClose(); Debug(net, 3, "Shutting down network"); diff --git a/src/network/network_content.cpp b/src/network/network_content.cpp index 885593f484..79bb4ed052 100644 --- a/src/network/network_content.cpp +++ b/src/network/network_content.cpp @@ -311,13 +311,6 @@ void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uin { bytes = 0; -#ifdef __EMSCRIPTEN__ - /* Emscripten is loaded via an HTTPS connection. As such, it is very - * difficult to make HTTP connections. So always use the TCP method of - * downloading content. */ - fallback = true; -#endif - ContentIDList content; for (const ContentInfo *ci : this->infos) { if (!ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) continue; @@ -361,7 +354,7 @@ void ClientNetworkContentSocketHandler::DownloadSelectedContentHTTP(const Conten this->http_response_index = -1; - new NetworkHTTPContentConnecter(NetworkContentMirrorConnectionString(), this, NETWORK_CONTENT_MIRROR_URL, content_request); + NetworkHTTPSocketHandler::Connect(NetworkContentMirrorUriString(), this, content_request); /* NetworkHTTPContentConnecter takes over freeing of content_request! */ } diff --git a/src/network/network_content.h b/src/network/network_content.h index cf60681a20..21d324cab7 100644 --- a/src/network/network_content.h +++ b/src/network/network_content.h @@ -11,7 +11,7 @@ #define NETWORK_CONTENT_H #include "core/tcp_content.h" -#include "core/tcp_http.h" +#include "core/http.h" #include /** Vector with content info */ From 695ce0ab095cf5653e33d1071b5e4482c6dbe616 Mon Sep 17 00:00:00 2001 From: translators Date: Sun, 12 Feb 2023 18:45:01 +0000 Subject: [PATCH 02/30] Update: Translations from eints swedish: 3 changes by joeax910 italian: 1 change by Rivarossi russian: 1 change by Ln-Wolf ukrainian: 1 change by serg-bloim latvian: 7 changes by lexuslatvia --- src/lang/italian.txt | 2 +- src/lang/latvian.txt | 8 +++++++- src/lang/russian.txt | 2 +- src/lang/swedish.txt | 4 +++- src/lang/ukrainian.txt | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/lang/italian.txt b/src/lang/italian.txt index 9007516899..e6adfb495b 100644 --- a/src/lang/italian.txt +++ b/src/lang/italian.txt @@ -4570,7 +4570,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Elimina STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Elimina l'impostazione del limite di velocità per l'ordine selezionato. CTRL+clic rimuove la velocità da tutti gli ordini STR_TIMETABLE_RESET_LATENESS :{BLACK}Azzera ritardo -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Azzera il contatore del ritardo, in modo che il veicolo sia considerato in orario +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Azzera il contatore dei ritardi, in modo che il veicolo sia puntuale. Ctrl+clic azzera l'intero gruppo, in modo che l'ultimo veicolo sia puntuale e tutti gli altri siano in anticipo. STR_TIMETABLE_AUTOFILL :{BLACK}Auto STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Riempie automaticamente la tabella oraria con i tempi del prossimo viaggio. CTRL+clic per cercare di mantenere i tempi di attesa diff --git a/src/lang/latvian.txt b/src/lang/latvian.txt index c92f1233e3..2237f672a0 100644 --- a/src/lang/latvian.txt +++ b/src/lang/latvian.txt @@ -1208,7 +1208,9 @@ STR_CONFIG_SETTING_HORIZONTAL_POS_RIGHT :pa labi STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN :Maksimālais sākotnējais aizdevums: {STRING} STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_HELPTEXT :Maksimālais aizdevuma daudzums, ko uzņēmums var izsniegt (neskaitot inflāciju) +STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_VALUE :{CURRENCY_LONG} ###setting-zero-is-special +STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_DISABLED :Nav aizdevuma {RED}Lai nodrošinātu sākotnējos līdzekļus, ir nepieciešams spēles skripts STR_CONFIG_SETTING_INTEREST_RATE :Procentu likme: {STRING} STR_CONFIG_SETTING_INTEREST_RATE_HELPTEXT :Aizdevumu procentu likme; ja ieslēgts, ietekmē arī inflāciju @@ -1934,7 +1936,7 @@ STR_CONFIG_SETTING_DISTRIBUTION_MAIL_HELPTEXT :"simetriska" no STR_CONFIG_SETTING_DISTRIBUTION_ARMOURED :Izplatīšanas režīms APSARGĀJAMAI preču klasei: {STRING} STR_CONFIG_SETTING_DISTRIBUTION_ARMOURED_HELPTEXT :APSARGĀJAMĀ kravas klase satur vērtslietas mērenajā joslā, dimantus subtropu joslā un zeltu subarktiskajā joslā. NewGRF paplašinājumi var to mainīt. "simetriska" nozīmē, ka apmēram vienāds daudzums kravas tiks nosūtīts virzienā no stacijas A uz B, cik no B uz A. "asimetriska" nozīmē, ka patvaļīgs kravas daudzums var tikt nosūtīts katrā no virzieniem. "manuāli" nozīmē, ka pastam netiks veikta automātiska izplatīšana. Šo vērtību vēlams iestatīt kā asimetrisku vai manuālu, kad spēlē subarktiskajā joslā, jo bankas nesūta zeltu atpakaļ uz zelta raktuvēm. Mērenajā un subtropu joslā var izvēlēties simetrisku, jo bankas sūtīs vērtslietas atpakaļ uz izcelsmes banku, kad saņems vērtslietu pievedumu. STR_CONFIG_SETTING_DISTRIBUTION_DEFAULT :Izplatīšanas modelis citām kravu klasēm: {STRING} -STR_CONFIG_SETTING_DISTRIBUTION_DEFAULT_HELPTEXT :"asimetriska" nozīmē, ka patvaļīgu kravas daudzumu var nosūtīt abos virzienos."manuāli" nozīmē, ka šīm kravām netiks veikta automātiska izplatīšana. +STR_CONFIG_SETTING_DISTRIBUTION_DEFAULT_HELPTEXT :"Asimetrisks" nozīmē, ka jebkurā virzienā var nosūtīt patvaļīgu daudzumu kravas. "Manuāli" nozīmē, ka šīm kravām automātiska sadale nenotiks ###length 3 STR_CONFIG_SETTING_DISTRIBUTION_MANUAL :manuāli STR_CONFIG_SETTING_DISTRIBUTION_ASYMMETRIC :asimetriska @@ -2703,6 +2705,9 @@ STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_INCREASE_TOOLTIP :{BLACK}Palielin STR_SELECT_RAIL_BRIDGE_CAPTION :{WHITE}Izvēlēties dzelzceļa tiltu STR_SELECT_ROAD_BRIDGE_CAPTION :{WHITE}Izvēlēties tiltu STR_SELECT_BRIDGE_SELECTION_TOOLTIP :{BLACK}Tiltu izvēle - klikšķināt uz vēlamo tiltu, lai to uzbūvētu +STR_SELECT_BRIDGE_INFO_NAME :{GOLD}{STRING} +STR_SELECT_BRIDGE_INFO_NAME_MAX_SPEED :{GOLD}{STRING},{} {VELOCITY} +STR_SELECT_BRIDGE_INFO_NAME_MAX_SPEED_COST :{GOLD}{STRING},{} {VELOCITY} {WHITE}{CURRENCY_LONG} STR_BRIDGE_NAME_SUSPENSION_STEEL :Vanšu, tērauda STR_BRIDGE_NAME_GIRDER_STEEL :Siju, tērauda STR_BRIDGE_NAME_CANTILEVER_STEEL :Izgriežamais, tērauda @@ -4627,6 +4632,7 @@ STR_SCREENSHOT_HEIGHTMAP_SCREENSHOT :{BLACK}Augstumk STR_SCREENSHOT_MINIMAP_SCREENSHOT :{BLACK}Minikartes ekrānuzņēmums # Script Parameters +STR_AI_SETTINGS_CAPTION :{WHITE}{STRING} Iestatījumi STR_AI_SETTINGS_CAPTION_AI :{WHITE}MI Parametri STR_AI_SETTINGS_CLOSE :{BLACK}Aizvērt STR_AI_SETTINGS_RESET :{BLACK}Atiestatīt diff --git a/src/lang/russian.txt b/src/lang/russian.txt index 32349c0374..e8185b1da6 100644 --- a/src/lang/russian.txt +++ b/src/lang/russian.txt @@ -4716,7 +4716,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Сбро STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Сбросить ограничение скорости движения для выделенного задания. Ctrl+щелчок - сбросить время для всех заданий. STR_TIMETABLE_RESET_LATENESS :{BLACK}Сбросить счетчик опозд. -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Сбросить счётчик отклонения от графика, чтобы транспорт снова считался приходящим вовремя +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Сбросить счётчик опоздания, чтобы ТС считалось идущим по графику. Ctrl+щелчок сбросит счётчики у всей группы, так что последнее ТС будет идти по графику, а остальные - раньше графика. STR_TIMETABLE_AUTOFILL :{BLACK}Авторасчёт STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Заполнить график автоматически временем, затраченным на движение в следующей поездке. Ctrl+щелчок - не изменять время ожидания. diff --git a/src/lang/swedish.txt b/src/lang/swedish.txt index b259149c69..243ebc4bca 100644 --- a/src/lang/swedish.txt +++ b/src/lang/swedish.txt @@ -3352,6 +3352,8 @@ STR_NEWGRF_ERROR_MSG_INFO :{SILVER}{STRING STR_NEWGRF_ERROR_MSG_WARNING :{RED}Varning: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_ERROR :{RED}Fel: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_FATAL :{RED}Fatalt: {SILVER}{STRING} +STR_NEWGRF_ERROR_FATAL_POPUP :{WHITE}NewGRF:en "{STRING}" har returnerat ett allvarligt fel:{}{STRING} +STR_NEWGRF_ERROR_POPUP :{WHITE}NewGRF:en "{STRING}" har returnerat ett fel:{}{STRING} STR_NEWGRF_ERROR_VERSION_NUMBER :{1:STRING} kommer inte att fungera med den TTDPatchversion som rapporterades av OpenTTD STR_NEWGRF_ERROR_DOS_OR_WINDOWS :{1:STRING} är för {2:STRING}-versionen av TTD STR_NEWGRF_ERROR_UNSET_SWITCH :{1:STRING} är designat för att användas med {2:STRING} @@ -4527,7 +4529,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Rensa ha STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Ta bort hastighetsgräns för markerad order. Ctrl+klick tar bort hastighetsgränsen för alla ordrar STR_TIMETABLE_RESET_LATENESS :{BLACK}Rensa räknaren för sen ankomst -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Nollställ räknaren för sen ankomst så att fordonet kommer i tid +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Nollställ räknaren för sen ankomst så att fordonet kommer i tid. Ctrl+klick nollställer för hela gruppen så att det senaste fordonet kommer att vara i tid och alla andra tidiga STR_TIMETABLE_AUTOFILL :{BLACK}Fyll i automatiskt STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Fyll i tidtabellen automatiskt med värden från nästa resa. Ctrl+klicka för att försöka behålla väntetiderna diff --git a/src/lang/ukrainian.txt b/src/lang/ukrainian.txt index eb3aac208b..ead9afb663 100644 --- a/src/lang/ukrainian.txt +++ b/src/lang/ukrainian.txt @@ -4652,7 +4652,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Скас STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Скасувати обмеження швидкості для виділеного пункту. Ctrl+клац видалить швидкість в усіх завданнях STR_TIMETABLE_RESET_LATENESS :{BLACK}Скасувати відхилення -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Скасувати лічильник відхилення від графіка, щоб транспорт встигнув +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Скасувати лічильник відхилення від графіка, щоб транспорт встигнув. Ctrl+клац щоб скасувати лічильник для всіх транспортів. При цьому останній транспорт стане йти за графіком, а інщі опереджати графік. STR_TIMETABLE_AUTOFILL :{BLACK}Авторозрахунок STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Автоматично розрахувати розклад впродовж наступної поїздки. Ctrl+клац, щоб спробувати зберегти час очікування From 04847b1208e08bf905b9dc9194306c35670e0ec9 Mon Sep 17 00:00:00 2001 From: translators Date: Mon, 13 Feb 2023 18:47:38 +0000 Subject: [PATCH 03/30] Update: Translations from eints english (us): 1 change by 2TallTyler portuguese: 2 changes by ppxppy --- src/lang/english_US.txt | 2 +- src/lang/portuguese.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/english_US.txt b/src/lang/english_US.txt index df8c5eada6..fcfbaa63d8 100644 --- a/src/lang/english_US.txt +++ b/src/lang/english_US.txt @@ -4529,7 +4529,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Clear Sp STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Clear the maximum travel speed of the highlighted order. Ctrl+Click clears the speed for all orders STR_TIMETABLE_RESET_LATENESS :{BLACK}Reset Late Counter -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Reset the lateness counter, so the vehicle will be on time +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Reset the lateness counter, so the vehicle will be on time. Ctrl+Click will reset the entire group so the latest vehicle will be on time and all others will be early STR_TIMETABLE_AUTOFILL :{BLACK}Autofill STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Fill the timetable automatically with the values from the next journey. Ctrl+Click to try to keep waiting times diff --git a/src/lang/portuguese.txt b/src/lang/portuguese.txt index 1cb674a9e6..75346f169f 100644 --- a/src/lang/portuguese.txt +++ b/src/lang/portuguese.txt @@ -4126,7 +4126,7 @@ STR_REPLACE_ALL_ROADTYPE :Todos os veícu ###length 2 STR_REPLACE_HELP_RAILTYPE :{BLACK}Selecione o tipo de carril para o qual deseja efectuar a substituição dos motores -STR_REPLACE_HELP_ROADTYPE :BLACK}Selecione o tipo de estrada para o qual deseja efectuar a substituição dos motores +STR_REPLACE_HELP_ROADTYPE :{BLACK}Selecione o tipo de estrada para o qual deseja efectuar a substituição dos motores ###next-name-looks-similar STR_REPLACE_HELP_REPLACE_INFO_TAB :{BLACK}Exibe o tipo de motor que substituirá o que está seleccionado à esquerda, se algum @@ -4530,7 +4530,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Remover STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Limpar a velocidade máxima de viagem da ordem selecionada. Ctrl+Clique limpa as velocidades para todas as ordens. STR_TIMETABLE_RESET_LATENESS :{BLACK}Apagar Contador Atraso -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Limpar o contador de atraso, para que o veículo passe a estar a horas +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Limpar o contador de atraso, para que o veículo esteja pontual. Ctrl+Click reiniciará o grupo todo, para que o último veículo esteja pontual e todos os outros estejam antecipados. STR_TIMETABLE_AUTOFILL :{BLACK}Auto preencher STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Preencher o horário automaticamente com os valores da próxima viagem. Ctrl+Clique para tentar manter os tempos de espera. From 274bcf8d80fed050ce292695c1c24502b642712e Mon Sep 17 00:00:00 2001 From: frosch Date: Sun, 12 Feb 2023 23:56:13 +0100 Subject: [PATCH 04/30] Fix 64523709: rpm uses different package names than deb. --- .github/workflows/release-linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index dd05777d0e..0892c68ec6 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -29,7 +29,7 @@ jobs: yum install -y \ fontconfig-devel \ freetype-devel \ - libcurl4-openssl-dev \ + libcurl-devel \ libicu-devel \ libpng-devel \ lzo-devel \ From d7fcb420c40bbc45ee40af7e8129332acc9d960c Mon Sep 17 00:00:00 2001 From: frosch Date: Mon, 13 Feb 2023 20:09:49 +0100 Subject: [PATCH 05/30] Fix: compilation with libcurl from 2013. --- src/network/core/http_curl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/core/http_curl.cpp b/src/network/core/http_curl.cpp index 5933c52dd8..6741a5b922 100644 --- a/src/network/core/http_curl.cpp +++ b/src/network/core/http_curl.cpp @@ -131,7 +131,7 @@ bool NetworkHTTPRequest::Receive() for (int count = 0; count < 100; count++) { /* Check if there was activity in the multi-handle. */ int numfds; - curl_multi_poll(this->multi_handle, NULL, 0, 0, &numfds); + curl_multi_wait(this->multi_handle, NULL, 0, 0, &numfds); if (numfds == 0) return false; /* Let CURL process the activity. */ From fe2c8a1240957d8569ae8bae7fe9499ba3bca666 Mon Sep 17 00:00:00 2001 From: dP Date: Tue, 14 Feb 2023 14:29:11 +0400 Subject: [PATCH 06/30] Codechange: Decouple INDUSTRY_CTRL into separate commands (#10475) --- src/command_type.h | 4 +- src/industry.h | 7 --- src/industry_cmd.cpp | 79 ++++++++++++++++++------------ src/industry_cmd.h | 13 +++-- src/script/api/script_industry.cpp | 8 +-- 5 files changed, 63 insertions(+), 48 deletions(-) diff --git a/src/command_type.h b/src/command_type.h index 8af665d3b9..80caa8440f 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -232,7 +232,9 @@ enum Commands : uint16 { CMD_CHANGE_SERVICE_INT, ///< change the server interval of a vehicle CMD_BUILD_INDUSTRY, ///< build a new industry - CMD_INDUSTRY_CTRL, ///< change industry properties + CMD_INDUSTRY_SET_FLAGS, ///< change industry control flags + CMD_INDUSTRY_SET_EXCLUSIVITY, ///< change industry exclusive consumer/supplier + CMD_INDUSTRY_SET_TEXT, ///< change additional text for the industry CMD_SET_COMPANY_MANAGER_FACE, ///< set the manager's face of the company CMD_SET_COMPANY_COLOUR, ///< set the colour of the company diff --git a/src/industry.h b/src/industry.h index ce30114cb8..fd0d5ef9e9 100644 --- a/src/industry.h +++ b/src/industry.h @@ -33,13 +33,6 @@ enum ProductionLevels { PRODLEVEL_MAXIMUM = 0x80, ///< the industry is running at full speed }; -enum class IndustryAction : byte { - SetControlFlags = 0, ///< Set IndustryControlFlags - SetExclusiveSupplier = 1, ///< Set exclusive supplier - SetExclusiveConsumer = 2, ///< Set exclusive consumer - SetText = 3, ///< Set additional text -}; - /** * Flags to control/override the behaviour of an industry. * These flags are controlled by game scripts. diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index 8aa0cbcb79..2bfee78afa 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -2092,56 +2092,73 @@ CommandCost CmdBuildIndustry(DoCommandFlag flags, TileIndex tile, IndustryType i } /** - * Change industry properties + * Set industry control flags. + * @param flags Type of operation. + * @param ind_id IndustryID + * @param ctlflags IndustryControlFlags + * @return Empty cost or an error. + */ +CommandCost CmdIndustrySetFlags(DoCommandFlag flags, IndustryID ind_id, IndustryControlFlags ctlflags) +{ + if (_current_company != OWNER_DEITY) return CMD_ERROR; + + Industry *ind = Industry::GetIfValid(ind_id); + if (ind == nullptr) return CMD_ERROR; + + if (flags & DC_EXEC) ind->ctlflags = ctlflags & INDCTL_MASK; + + return CommandCost(); +} + +/** + * Change exclusive consumer or supplier for the industry. * @param flags Type of operation. * @param ind_id IndustryID - * @param action IndustryAction to perform - * @param ctlflags IndustryControlFlags (only used with set control flags) * @param company_id CompanyID to set or INVALID_OWNER (available to everyone) or * OWNER_NONE (neutral stations only) or OWNER_DEITY (no one) - * (only used with set exclusive supplier / consumer) - * @param text - Additional industry text (only used with set text action) + * @param consumer Set exclusive consumer if true, supplier if false. * @return Empty cost or an error. */ -CommandCost CmdIndustryCtrl(DoCommandFlag flags, IndustryID ind_id, IndustryAction action, IndustryControlFlags ctlflags, Owner company_id, const std::string &text) +CommandCost CmdIndustrySetExclusivity(DoCommandFlag flags, IndustryID ind_id, Owner company_id, bool consumer) { if (_current_company != OWNER_DEITY) return CMD_ERROR; Industry *ind = Industry::GetIfValid(ind_id); if (ind == nullptr) return CMD_ERROR; - switch (action) { - case IndustryAction::SetControlFlags: { - if (flags & DC_EXEC) ind->ctlflags = ctlflags & INDCTL_MASK; + if (company_id != OWNER_NONE && company_id != INVALID_OWNER && company_id != OWNER_DEITY + && !Company::IsValidID(company_id)) return CMD_ERROR; - break; + if (flags & DC_EXEC) { + if (consumer) { + ind->exclusive_consumer = company_id; + } else { + ind->exclusive_supplier = company_id; } + } - case IndustryAction::SetExclusiveSupplier: - case IndustryAction::SetExclusiveConsumer: { - if (company_id != OWNER_NONE && company_id != INVALID_OWNER && company_id != OWNER_DEITY - && !Company::IsValidID(company_id)) return CMD_ERROR; - if (flags & DC_EXEC) { - if (action == IndustryAction::SetExclusiveSupplier) { - ind->exclusive_supplier = company_id; - } else { - ind->exclusive_consumer = company_id; - } - } + return CommandCost(); +} - break; - } +/** + * Change additional industry text. + * @param flags Type of operation. + * @param ind_id IndustryID + * @param text - Additional industry text. + * @return Empty cost or an error. + */ +CommandCost CmdIndustrySetText(DoCommandFlag flags, IndustryID ind_id, const std::string &text) +{ + if (_current_company != OWNER_DEITY) return CMD_ERROR; - case IndustryAction::SetText: { - ind->text.clear(); - if (!text.empty()) ind->text = text; - InvalidateWindowData(WC_INDUSTRY_VIEW, ind->index); - break; - } + Industry *ind = Industry::GetIfValid(ind_id); + if (ind == nullptr) return CMD_ERROR; - default: - return CMD_ERROR; + if (flags & DC_EXEC) { + ind->text.clear(); + if (!text.empty()) ind->text = text; + InvalidateWindowData(WC_INDUSTRY_VIEW, ind->index); } return CommandCost(); diff --git a/src/industry_cmd.h b/src/industry_cmd.h index 21d9200a30..16de6b8331 100644 --- a/src/industry_cmd.h +++ b/src/industry_cmd.h @@ -14,14 +14,17 @@ #include "company_type.h" #include "industry_type.h" -enum class IndustryAction : byte; enum IndustryControlFlags : byte; CommandCost CmdBuildIndustry(DoCommandFlag flags, TileIndex tile, IndustryType it, uint32 first_layout, bool fund, uint32 seed); -CommandCost CmdIndustryCtrl(DoCommandFlag flags, IndustryID ind_id, IndustryAction action, IndustryControlFlags ctlflags, Owner company_id, const std::string &text); - -DEF_CMD_TRAIT(CMD_BUILD_INDUSTRY, CmdBuildIndustry, CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION) -DEF_CMD_TRAIT(CMD_INDUSTRY_CTRL, CmdIndustryCtrl, CMD_DEITY | CMD_STR_CTRL, CMDT_OTHER_MANAGEMENT) +CommandCost CmdIndustrySetFlags(DoCommandFlag flags, IndustryID ind_id, IndustryControlFlags ctlflags); +CommandCost CmdIndustrySetExclusivity(DoCommandFlag flags, IndustryID ind_id, Owner company_id, bool consumer); +CommandCost CmdIndustrySetText(DoCommandFlag flags, IndustryID ind_id, const std::string &text); + +DEF_CMD_TRAIT(CMD_BUILD_INDUSTRY, CmdBuildIndustry, CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION) +DEF_CMD_TRAIT(CMD_INDUSTRY_SET_FLAGS, CmdIndustrySetFlags, CMD_DEITY, CMDT_OTHER_MANAGEMENT) +DEF_CMD_TRAIT(CMD_INDUSTRY_SET_EXCLUSIVITY, CmdIndustrySetExclusivity, CMD_DEITY, CMDT_OTHER_MANAGEMENT) +DEF_CMD_TRAIT(CMD_INDUSTRY_SET_TEXT, CmdIndustrySetText, CMD_DEITY | CMD_STR_CTRL, CMDT_OTHER_MANAGEMENT) void CcBuildIndustry(Commands cmd, const CommandCost &result, TileIndex tile, IndustryType indtype, uint32, bool, uint32); diff --git a/src/script/api/script_industry.cpp b/src/script/api/script_industry.cpp index ca0eaafc43..a27ea1a6e3 100644 --- a/src/script/api/script_industry.cpp +++ b/src/script/api/script_industry.cpp @@ -60,7 +60,7 @@ } EnforcePrecondition(false, IsValidIndustry(industry_id)); - return ScriptObject::Command::Do(industry_id, IndustryAction::SetText, INDCTL_NONE, INVALID_OWNER, std::string{ encoded_text ? encoded_text : "" }); + return ScriptObject::Command::Do(industry_id, std::string{ encoded_text ? encoded_text : "" }); } /* static */ ScriptIndustry::CargoAcceptState ScriptIndustry::IsCargoAccepted(IndustryID industry_id, CargoID cargo_id) @@ -258,7 +258,7 @@ bool ScriptIndustry::SetControlFlags(IndustryID industry_id, uint32 control_flag if (ScriptObject::GetCompany() != OWNER_DEITY) return false; if (!IsValidIndustry(industry_id)) return false; - return ScriptObject::Command::Do(industry_id, IndustryAction::SetControlFlags, (::IndustryControlFlags)control_flags & ::INDCTL_MASK, INVALID_OWNER, {}); + return ScriptObject::Command::Do(industry_id, (::IndustryControlFlags)control_flags & ::INDCTL_MASK); } /* static */ ScriptCompany::CompanyID ScriptIndustry::GetExclusiveSupplier(IndustryID industry_id) @@ -277,7 +277,7 @@ bool ScriptIndustry::SetControlFlags(IndustryID industry_id, uint32 control_flag auto company = ScriptCompany::ResolveCompanyID(company_id); ::Owner owner = (company == ScriptCompany::COMPANY_INVALID ? ::INVALID_OWNER : (::Owner)company); - return ScriptObject::Command::Do(industry_id, IndustryAction::SetExclusiveSupplier, INDCTL_NONE, owner, {}); + return ScriptObject::Command::Do(industry_id, owner, false); } /* static */ ScriptCompany::CompanyID ScriptIndustry::GetExclusiveConsumer(IndustryID industry_id) @@ -296,5 +296,5 @@ bool ScriptIndustry::SetControlFlags(IndustryID industry_id, uint32 control_flag auto company = ScriptCompany::ResolveCompanyID(company_id); ::Owner owner = (company == ScriptCompany::COMPANY_INVALID ? ::INVALID_OWNER : (::Owner)company); - return ScriptObject::Command::Do(industry_id, IndustryAction::SetExclusiveConsumer, INDCTL_NONE, owner, {}); + return ScriptObject::Command::Do(industry_id, owner, true); } From 228b34c2bfd7cc7753bffc4df573e5ffd952a62c Mon Sep 17 00:00:00 2001 From: translators Date: Tue, 14 Feb 2023 18:46:38 +0000 Subject: [PATCH 07/30] Update: Translations from eints english (au): 1 change by krysclarke czech: 2 changes by jachymozo finnish: 1 change by hpiirai --- src/lang/czech.txt | 3 ++- src/lang/english_AU.txt | 2 +- src/lang/finnish.txt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lang/czech.txt b/src/lang/czech.txt index 7f21c6de7d..eb4828360f 100644 --- a/src/lang/czech.txt +++ b/src/lang/czech.txt @@ -1295,6 +1295,7 @@ STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN :Maximální pů STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_HELPTEXT :Jak moc si může společnost půjčit (bez ohledu na inflaci) STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_VALUE :{CURRENCY_LONG} ###setting-zero-is-special +STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_DISABLED :Žádná půjčka {RED}Vyžaduje herní skript pro získání základního kapitálu. STR_CONFIG_SETTING_INTEREST_RATE :Výše úroků: {STRING} STR_CONFIG_SETTING_INTEREST_RATE_HELPTEXT :Výše úroků z půjček; rovněž ovlivňuje inflaci, pokud je zapnuta @@ -4624,7 +4625,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Odstrani STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Odstranit maximální cestovní rychlost u vybraného příkazu. Ctrl+klik odstraní maximální rychlosti u všech příkazů STR_TIMETABLE_RESET_LATENESS :{BLACK}Zapomenout zpoždění -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Smazat zpoždění, takže vozidlo pojede na čas +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Smazat zpoždění, takže vozidlo pojede na čas. Ctrl+Click smaže zpoždění celé skupině, takže poslední vozidlo pojede na čas a ostatní brzy. STR_TIMETABLE_AUTOFILL :{BLACK}Automaticky STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Automaticky vyplň rozvrh hodnotami z následující cesty. Ctrl+klik pro zachování vyčkávací doby. diff --git a/src/lang/english_AU.txt b/src/lang/english_AU.txt index a254b2b1af..b0cc185897 100644 --- a/src/lang/english_AU.txt +++ b/src/lang/english_AU.txt @@ -4529,7 +4529,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Clear Sp STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Clear the maximum travel speed of the highlighted order. Ctrl+Click clears the speed for all orders STR_TIMETABLE_RESET_LATENESS :{BLACK}Reset Late Counter -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Reset the lateness counter, so the vehicle will be on time +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Reset the lateness counter, so the vehicle will be on time. Ctrl+Click will reset the entire group so the latest vehicle will be on time and all others will be early STR_TIMETABLE_AUTOFILL :{BLACK}Autofill STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Fill the timetable automatically with the values from the next journey. Ctrl+Click to try to keep waiting times diff --git a/src/lang/finnish.txt b/src/lang/finnish.txt index 3e32292780..534b7e546e 100644 --- a/src/lang/finnish.txt +++ b/src/lang/finnish.txt @@ -4529,7 +4529,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Tyhjenn STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Poista suurin sallittu nopeus valitulta käskyltä. Ctrl+napsautus poistaa kaikkien käskyjen nopeusrajoitukset STR_TIMETABLE_RESET_LATENESS :{BLACK}Nollaa myöhästymislaskuri -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Nollaa myöhästymislaskuri, jotta vaunu olisi taas aikataulussa +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Nollaa myöhästymislaskuri, jotta kulkuneuvo olisi taas aikataulussa. Ctrl+napsautus nollaa koko ryhmän, minkä jälkeen eniten myöhässä ollut kulkuneuvo on aikataulussa ja muut ovat etuajassa STR_TIMETABLE_AUTOFILL :{BLACK}Automaattinen STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Täytä aikataulu automaattisesti seuraavan matkan arvoilla. Ctrl+napsautus: yritä säilyttää odotusajat From ea90fa24f8eb0c8e8f9d01ab2d340fed91771a68 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 15 Feb 2023 21:56:19 +0100 Subject: [PATCH 08/30] Codechange: move curl into a thread so simplify code (#10480) With a thread, we can just run curl_easy_perform() and let CURL and threads handle the blocking part. With async solution there are too many things to keep track of, and it makes "when to update the GUI" tricky. By using a thread that all gets a lot simpler, as the game-thread and download-thread run side-by-side. This is similar to how the WinHttp backend already works. --- src/network/core/http_curl.cpp | 285 ++++++++++++++------------------- 1 file changed, 119 insertions(+), 166 deletions(-) diff --git a/src/network/core/http_curl.cpp b/src/network/core/http_curl.cpp index 6741a5b922..1727cf24b3 100644 --- a/src/network/core/http_curl.cpp +++ b/src/network/core/http_curl.cpp @@ -12,216 +12,169 @@ #include "../../stdafx.h" #include "../../debug.h" #include "../../rev.h" +#include "../../thread.h" #include "../network_internal.h" #include "http.h" +#include +#include #include +#include +#include +#include #include "../../safeguards.h" /** Single HTTP request. */ class NetworkHTTPRequest { -private: - std::string uri; ///< URI to connect to. - HTTPCallback *callback; ///< Callback to send data back on. - const char *data; ///< Data to send, if any. - - CURL *curl = nullptr; ///< CURL handle. - CURLM *multi_handle = nullptr; ///< CURL multi-handle. - public: - NetworkHTTPRequest(const std::string &uri, HTTPCallback *callback, const char *data = nullptr); + /** + * Create a new HTTP request. + * + * @param uri the URI to connect to (https://.../..). + * @param callback the callback to send data back on. + * @param data optionally, the data we want to send. When set, this will be a POST request, otherwise a GET request. + */ + NetworkHTTPRequest(const std::string &uri, HTTPCallback *callback, const char *data = nullptr) : + uri(uri), + callback(callback), + data(data) + { + } - ~NetworkHTTPRequest(); + /** + * Destructor of the HTTP request. + */ + ~NetworkHTTPRequest() + { + free(this->data); + } - void Connect(); - bool Receive(); + std::string uri; ///< URI to connect to. + HTTPCallback *callback; ///< Callback to send data back on. + const char *data; ///< Data to send, if any. }; -static std::vector _http_requests; -static std::vector _new_http_requests; -static CURLSH *_curl_share = nullptr; +static std::thread _http_thread; +static std::atomic _http_thread_exit = false; +static std::queue> _http_requests; +static std::mutex _http_mutex; +static std::condition_variable _http_cv; -/** - * Create a new HTTP request. - * - * @param uri the URI to connect to (https://.../..). - * @param callback the callback to send data back on. - * @param data optionally, the data we want to send. When set, this will be a POST request, otherwise a GET request. - */ -NetworkHTTPRequest::NetworkHTTPRequest(const std::string &uri, HTTPCallback *callback, const char *data) : - uri(uri), - callback(callback), - data(data) +/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const char *data) { + std::lock_guard lock(_http_mutex); + _http_requests.push(std::make_unique(uri, callback, data)); + _http_cv.notify_one(); } -/** - * Start the HTTP request handling. - * - * This is done in an async manner, so we can do other things while waiting for - * the HTTP request to finish. The actual receiving of the data is done in - * Receive(). - */ -void NetworkHTTPRequest::Connect() +/* static */ void NetworkHTTPSocketHandler::HTTPReceive() { - Debug(net, 1, "HTTP request to {}", uri); - - this->curl = curl_easy_init(); - assert(this->curl != nullptr); - - if (_debug_net_level >= 5) { - curl_easy_setopt(this->curl, CURLOPT_VERBOSE, 1L); - } - - curl_easy_setopt(curl, CURLOPT_SHARE, _curl_share); - - if (this->data != nullptr) { - curl_easy_setopt(this->curl, CURLOPT_POST, 1L); - curl_easy_setopt(this->curl, CURLOPT_POSTFIELDS, this->data); - } - - curl_easy_setopt(this->curl, CURLOPT_URL, this->uri.c_str()); - - /* Setup our (C-style) callback function which we pipe back into the callback. */ - curl_easy_setopt(this->curl, CURLOPT_WRITEFUNCTION, +[](char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t { - Debug(net, 4, "HTTP callback: {} bytes", size * nmemb); - HTTPCallback *callback = static_cast(userdata); - callback->OnReceiveData(ptr, size * nmemb); - return size * nmemb; - }); - curl_easy_setopt(this->curl, CURLOPT_WRITEDATA, this->callback); - - /* Setup some default options. */ - std::string user_agent = fmt::format("OpenTTD/{}", GetNetworkRevisionString()); - curl_easy_setopt(this->curl, CURLOPT_USERAGENT, user_agent.c_str()); - curl_easy_setopt(this->curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(this->curl, CURLOPT_MAXREDIRS, 5L); - - /* Give the connection about 10 seconds to complete. */ - curl_easy_setopt(this->curl, CURLOPT_CONNECTTIMEOUT, 10L); - - /* Set a buffer of 100KiB, as the default of 16KiB seems a bit small. */ - curl_easy_setopt(this->curl, CURLOPT_BUFFERSIZE, 100L * 1024L); - - /* Fail our call if we don't receive a 2XX return value. */ - curl_easy_setopt(this->curl, CURLOPT_FAILONERROR, 1L); - - /* Create a multi-handle so we can do the call async. */ - this->multi_handle = curl_multi_init(); - curl_multi_add_handle(this->multi_handle, this->curl); - - /* Trigger it for the first time so it becomes active. */ - int still_running; - curl_multi_perform(this->multi_handle, &still_running); } -/** - * Poll and process the HTTP request/response. - * - * @return True iff the request is done; no call to Receive() should be done after it returns true. - */ -bool NetworkHTTPRequest::Receive() +void HttpThread() { - int still_running = 0; - - /* Check for as long as there is activity on the socket, but once in a while return. - * This allows the GUI to always update, even on really fast downloads. */ - for (int count = 0; count < 100; count++) { - /* Check if there was activity in the multi-handle. */ - int numfds; - curl_multi_wait(this->multi_handle, NULL, 0, 0, &numfds); - if (numfds == 0) return false; - - /* Let CURL process the activity. */ - curl_multi_perform(this->multi_handle, &still_running); - if (still_running == 0) break; - } + CURL *curl = curl_easy_init(); + assert(curl != nullptr); - /* The download is still pending (so the count is reached). Update GUI. */ - if (still_running != 0) return false; - - /* The request is done; check the result and close up. */ - int msgq; - CURLMsg *msg = curl_multi_info_read(this->multi_handle, &msgq); - /* We can safely assume this returns something, as otherwise the multi-handle wouldn't be empty. */ - assert(msg != nullptr); - assert(msg->msg == CURLMSG_DONE); - - CURLcode res = msg->data.result; - if (res == CURLE_OK) { - Debug(net, 1, "HTTP request succeeded"); - this->callback->OnReceiveData(nullptr, 0); - } else { - Debug(net, 0, "HTTP request failed: {}", curl_easy_strerror(res)); - this->callback->OnFailure(); - } + for (;;) { + std::unique_lock lock(_http_mutex); - return true; -} + /* Wait for a new request. */ + while (_http_requests.empty() && !_http_thread_exit) { + _http_cv.wait(lock); + } + if (_http_thread_exit) break; -/** - * Destructor of the HTTP request. - * - * Makes sure all handlers are closed, and all memory is free'd. - */ -NetworkHTTPRequest::~NetworkHTTPRequest() -{ - if (this->curl) { - curl_multi_remove_handle(this->multi_handle, this->curl); - curl_multi_cleanup(this->multi_handle); + std::unique_ptr request = std::move(_http_requests.front()); + _http_requests.pop(); - curl_easy_cleanup(this->curl); - } + /* Release the lock, as we will take a while to process the request. */ + lock.unlock(); - free(this->data); -} + /* Reset to default settings. */ + curl_easy_reset(curl); -/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const char *data) -{ - auto request = new NetworkHTTPRequest(uri, callback, data); - request->Connect(); - _new_http_requests.push_back(request); -} + if (_debug_net_level >= 5) { + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + } -/* static */ void NetworkHTTPSocketHandler::HTTPReceive() -{ - if (!_new_http_requests.empty()) { - /* We delay adding new requests, as Receive() below can cause a callback which adds a new requests. */ - _http_requests.insert(_http_requests.end(), _new_http_requests.begin(), _new_http_requests.end()); - _new_http_requests.clear(); - } + /* Setup some default options. */ + std::string user_agent = fmt::format("OpenTTD/{}", GetNetworkRevisionString()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent.c_str()); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L); - if (_http_requests.empty()) return; + /* Give the connection about 10 seconds to complete. */ + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); - for (auto it = _http_requests.begin(); it != _http_requests.end(); /* nothing */) { - NetworkHTTPRequest *cur = *it; + /* Set a buffer of 100KiB, as the default of 16KiB seems a bit small. */ + curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 100L * 1024L); - if (cur->Receive()) { - it = _http_requests.erase(it); - delete cur; - continue; - } + /* Fail our call if we don't receive a 2XX return value. */ + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); - ++it; + /* Prepare POST body and URI. */ + if (request->data != nullptr) { + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request->data); + } + curl_easy_setopt(curl, CURLOPT_URL, request->uri.c_str()); + + /* Setup our (C-style) callback function which we pipe back into the callback. */ + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +[](char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t { + Debug(net, 4, "HTTP callback: {} bytes", size * nmemb); + HTTPCallback *callback = static_cast(userdata); + callback->OnReceiveData(ptr, size * nmemb); + return size * nmemb; + }); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, request->callback); + + /* Create a callback from which we can cancel. Sadly, there is no other + * thread-safe way to do this. If the connection went idle, it can take + * up to a second before this callback is called. There is little we can + * do about this. */ + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, +[](void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) -> int { + return _http_thread_exit ? 1 : 0; + }); + + /* Perform the request. */ + CURLcode res = curl_easy_perform(curl); + + if (res == CURLE_OK) { + Debug(net, 1, "HTTP request succeeded"); + request->callback->OnReceiveData(nullptr, 0); + } else { + Debug(net, 0, "HTTP request failed: {}", curl_easy_strerror(res)); + request->callback->OnFailure(); + } } + + curl_easy_cleanup(curl); } void NetworkHTTPInitialize() { curl_global_init(CURL_GLOBAL_DEFAULT); - /* Create a share that tracks DNS, SSL session, and connections. As this - * always runs in the same thread, sharing a connection should be fine. */ - _curl_share = curl_share_init(); - curl_share_setopt(_curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); - curl_share_setopt(_curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); - curl_share_setopt(_curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); + _http_thread_exit = false; + StartNewThread(&_http_thread, "ottd:http", &HttpThread); } void NetworkHTTPUninitialize() { - curl_share_cleanup(_curl_share); curl_global_cleanup(); + + _http_thread_exit = true; + + { + std::lock_guard lock(_http_mutex); + _http_cv.notify_one(); + } + + if (_http_thread.joinable()) { + _http_thread.join(); + } } From 16352559f28c2915e86d8e2bcfc23c278b658164 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 15 Feb 2023 22:18:23 +0100 Subject: [PATCH 09/30] Change: release with (much) newer versions of dependencies for Generic Linux (#10484) --- .github/workflows/release-linux.yml | 66 +++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 0892c68ec6..048e1b0710 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -23,30 +23,69 @@ jobs: run: | tar -xf source.tar.gz --strip-components=1 + - name: Enable vcpkg cache + uses: actions/cache@v3 + with: + path: /vcpkg/installed + key: ubuntu-20.04-vcpkg-release-0 # Increase the number whenever dependencies are modified + restore-keys: | + ubuntu-20.04-vcpkg-release + - name: Install dependencies run: | - echo "::group::Install dependencies" + echo "::group::Install system dependencies" + # ICU is used as vcpkg fails to install ICU. Other dependencies + # are needed either for vcpkg or for the packages installed with + # vcpkg. yum install -y \ - fontconfig-devel \ - freetype-devel \ - libcurl-devel \ libicu-devel \ - libpng-devel \ - lzo-devel \ - SDL2-devel \ + perl-IPC-Cmd \ wget \ - xz-devel \ - zlib-devel \ + zip \ # EOF echo "::endgroup::" + # We use vcpkg for our dependencies, to get more up-to-date version. + echo "::group::Install vcpkg and dependencies" + + # We do a little dance to make sure we copy the cached install folder + # into our new clone. + git clone --depth=1 https://github.com/microsoft/vcpkg /vcpkg-clone + if [ -e /vcpkg/installed ]; then + mv /vcpkg/installed /vcpkg-clone/ + rm -rf /vcpkg + fi + mv /vcpkg-clone /vcpkg + + ( + cd /vcpkg + ./bootstrap-vcpkg.sh -disableMetrics + + # Make Python3 available for other packages. + ./vcpkg install python3 + ln -sf $(pwd)/installed/x64-linux/tools/python3/python3.[0-9][0-9] /usr/bin/python3 + + ./vcpkg install \ + curl[http2] \ + fontconfig \ + freetype \ + liblzma \ + libpng \ + lzo \ + sdl2 \ + zlib \ + # EOF + ) + echo "::endgroup::" + # The yum variant of fluidsynth depends on all possible audio drivers, # like jack, ALSA, pulseaudio, etc. This is not really useful for us, # as we route the output of fluidsynth back via our sound driver, and - # as such do not use these audio driver outputs at all. So instead, - # we compile fluidsynth ourselves, with as little dependencies as - # possible. This currently means it picks up SDL2, but this is fine, - # as we need SDL2 anyway. + # as such do not use these audio driver outputs at all. + # The vcpkg variant of fluidsynth depends on ALSA. Similar issue here. + # So instead, we compile fluidsynth ourselves, with as few + # dependencies as possible. This currently means it picks up SDL2, but + # this is fine, as we need SDL2 anyway. echo "::group::Install fluidsynth" wget https://github.com/FluidSynth/fluidsynth/archive/v2.1.6.tar.gz tar xf v2.1.6.tar.gz @@ -70,6 +109,7 @@ jobs: echo "::group::CMake" cmake ${GITHUB_WORKSPACE} \ + -DCMAKE_TOOLCHAIN_FILE=/vcpkg/scripts/buildsystems/vcpkg.cmake \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DOPTION_PACKAGE_DEPENDENCIES=ON \ # EOF From 0722bb3bf4195ac4ffcbc9df66ef620b4d8f7a22 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 15 Feb 2023 22:58:43 +0100 Subject: [PATCH 10/30] Change: try to detect the CA file/path for CURL (#10481) The default is given compile-time, not run-time. So libcurl is of no use to us. Current list is kindly borrowed from https://go.dev/src/crypto/x509/root_linux.go --- src/network/core/http_curl.cpp | 65 ++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/network/core/http_curl.cpp b/src/network/core/http_curl.cpp index 1727cf24b3..188d9b7b63 100644 --- a/src/network/core/http_curl.cpp +++ b/src/network/core/http_curl.cpp @@ -11,6 +11,7 @@ #include "../../stdafx.h" #include "../../debug.h" +#include "../../fileio_func.h" #include "../../rev.h" #include "../../thread.h" #include "../network_internal.h" @@ -26,6 +27,24 @@ #include "../../safeguards.h" +#if defined(UNIX) +/** List of certificate bundles, depending on OS. Taken from: https://go.dev/src/crypto/x509/root_linux.go. */ +static auto _certificate_files = { + "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc. + "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6 + "/etc/ssl/ca-bundle.pem", // OpenSUSE + "/etc/pki/tls/cacert.pem", // OpenELEC + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7 + "/etc/ssl/cert.pem", // Alpine Linux +}; +/** List of certificate directories, depending on OS. Taken from: https://go.dev/src/crypto/x509/root_linux.go. */ +static auto _certificate_directories = { + "/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139 + "/etc/pki/tls/certs", // Fedora/RHEL + "/system/etc/security/cacerts", // Android +}; +#endif /* UNIX */ + /** Single HTTP request. */ class NetworkHTTPRequest { public: @@ -61,9 +80,20 @@ static std::atomic _http_thread_exit = false; static std::queue> _http_requests; static std::mutex _http_mutex; static std::condition_variable _http_cv; +#if defined(UNIX) +static std::string _http_ca_file = ""; +static std::string _http_ca_path = ""; +#endif /* UNIX */ /* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const char *data) { +#if defined(UNIX) + if (_http_ca_file.empty() && _http_ca_path.empty()) { + callback->OnFailure(); + return; + } +#endif /* UNIX */ + std::lock_guard lock(_http_mutex); _http_requests.push(std::make_unique(uri, callback, data)); _http_cv.notify_one(); @@ -106,6 +136,14 @@ void HttpThread() curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L); + /* Ensure we validate the certificate and hostname of the server. */ +#if defined(UNIX) + curl_easy_setopt(curl, CURLOPT_CAINFO, _http_ca_file.empty() ? nullptr : _http_ca_file.c_str()); + curl_easy_setopt(curl, CURLOPT_CAPATH, _http_ca_path.empty() ? nullptr : _http_ca_path.c_str()); +#endif /* UNIX */ + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true); + /* Give the connection about 10 seconds to complete. */ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); @@ -159,6 +197,33 @@ void NetworkHTTPInitialize() { curl_global_init(CURL_GLOBAL_DEFAULT); +#if defined(UNIX) + /* Depending on the Linux distro, certificates can either be in + * a bundle or a folder, in a wide range of different locations. + * Try to find what location is used by this OS. */ + for (auto &ca_file : _certificate_files) { + if (FileExists(ca_file)) { + _http_ca_file = ca_file; + break; + } + } + if (_http_ca_file.empty()) { + for (auto &ca_path : _certificate_directories) { + if (FileExists(ca_path)) { + _http_ca_path = ca_path; + break; + } + } + } + Debug(net, 3, "Using certificate file: {}", _http_ca_file.empty() ? "none" : _http_ca_file); + Debug(net, 3, "Using certificate path: {}", _http_ca_path.empty() ? "none" : _http_ca_path); + + /* Tell the user why HTTPS will not be working. */ + if (_http_ca_file.empty() && _http_ca_path.empty()) { + Debug(net, 0, "No certificate files or directories found, HTTPS will not work!"); + } +#endif /* UNIX */ + _http_thread_exit = false; StartNewThread(&_http_thread, "ottd:http", &HttpThread); } From 1c17556f96e6b885c05bf9050f53e73a740ca9c9 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 15 Feb 2023 20:10:30 +0100 Subject: [PATCH 11/30] Codechange: replace instance of char * with std::string --- src/network/core/http.h | 4 ++-- src/network/core/http_curl.cpp | 24 ++++++++---------------- src/network/core/http_none.cpp | 2 +- src/network/core/http_winhttp.cpp | 22 ++++++++++------------ src/network/network_content.cpp | 15 ++------------- 5 files changed, 23 insertions(+), 44 deletions(-) diff --git a/src/network/core/http.h b/src/network/core/http.h index cad503862f..e14ef8f006 100644 --- a/src/network/core/http.h +++ b/src/network/core/http.h @@ -42,9 +42,9 @@ public: * * @param uri the URI to connect to (https://.../..). * @param callback the callback to send data back on. - * @param data optionally, the data we want to send. When set, this will be a POST request, otherwise a GET request. + * @param data the data we want to send. When non-empty, this will be a POST request, otherwise a GET request. */ - static void Connect(const std::string &uri, HTTPCallback *callback, const char *data = nullptr); + static void Connect(const std::string &uri, HTTPCallback *callback, const std::string data = ""); /** * Do the receiving for all HTTP connections. diff --git a/src/network/core/http_curl.cpp b/src/network/core/http_curl.cpp index 188d9b7b63..0694afeac7 100644 --- a/src/network/core/http_curl.cpp +++ b/src/network/core/http_curl.cpp @@ -53,26 +53,18 @@ public: * * @param uri the URI to connect to (https://.../..). * @param callback the callback to send data back on. - * @param data optionally, the data we want to send. When set, this will be a POST request, otherwise a GET request. + * @param data the data we want to send. When non-empty, this will be a POST request, otherwise a GET request. */ - NetworkHTTPRequest(const std::string &uri, HTTPCallback *callback, const char *data = nullptr) : + NetworkHTTPRequest(const std::string &uri, HTTPCallback *callback, const std::string &data) : uri(uri), callback(callback), data(data) { } - /** - * Destructor of the HTTP request. - */ - ~NetworkHTTPRequest() - { - free(this->data); - } - - std::string uri; ///< URI to connect to. - HTTPCallback *callback; ///< Callback to send data back on. - const char *data; ///< Data to send, if any. + const std::string uri; ///< URI to connect to. + HTTPCallback *callback; ///< Callback to send data back on. + const std::string data; ///< Data to send, if any. }; static std::thread _http_thread; @@ -85,7 +77,7 @@ static std::string _http_ca_file = ""; static std::string _http_ca_path = ""; #endif /* UNIX */ -/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const char *data) +/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const std::string data) { #if defined(UNIX) if (_http_ca_file.empty() && _http_ca_path.empty()) { @@ -154,9 +146,9 @@ void HttpThread() curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); /* Prepare POST body and URI. */ - if (request->data != nullptr) { + if (!request->data.empty()) { curl_easy_setopt(curl, CURLOPT_POST, 1L); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request->data); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request->data.c_str()); } curl_easy_setopt(curl, CURLOPT_URL, request->uri.c_str()); diff --git a/src/network/core/http_none.cpp b/src/network/core/http_none.cpp index aae03cbff8..41ba7c8cba 100644 --- a/src/network/core/http_none.cpp +++ b/src/network/core/http_none.cpp @@ -18,7 +18,7 @@ #include "../../safeguards.h" -/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const char *data) +/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const std::string data) { /* No valid HTTP backend was compiled in, so we fail all HTTP requests. */ callback->OnFailure(); diff --git a/src/network/core/http_winhttp.cpp b/src/network/core/http_winhttp.cpp index 209316759f..e689ee0050 100644 --- a/src/network/core/http_winhttp.cpp +++ b/src/network/core/http_winhttp.cpp @@ -25,9 +25,9 @@ static HINTERNET _winhttp_session = nullptr; /** Single HTTP request. */ class NetworkHTTPRequest { private: - std::wstring uri; ///< URI to connect to. - HTTPCallback *callback; ///< Callback to send data back on. - const char *data; ///< Data to send, if any. + const std::wstring uri; ///< URI to connect to. + HTTPCallback *callback; ///< Callback to send data back on. + const std::string data; ///< Data to send, if any. HINTERNET connection = nullptr; ///< Current connection object. HINTERNET request = nullptr; ///< Current request object. @@ -35,7 +35,7 @@ private: int depth = 0; ///< Current redirect depth we are in. public: - NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *callback, const char *data = nullptr); + NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *callback, const std::string &data); ~NetworkHTTPRequest(); @@ -52,9 +52,9 @@ static std::vector _new_http_requests; * * @param uri the URI to connect to (https://.../..). * @param callback the callback to send data back on. - * @param data optionally, the data we want to send. When set, this will be a POST request, otherwise a GET request. + * @param data the data we want to send. When non-empty, this will be a POST request, otherwise a GET request. */ -NetworkHTTPRequest::NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *callback, const char *data) : +NetworkHTTPRequest::NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *callback, const std::string &data) : uri(uri), callback(callback), data(data) @@ -224,7 +224,7 @@ void NetworkHTTPRequest::Connect() return; } - this->request = WinHttpOpenRequest(connection, data == nullptr ? L"GET" : L"POST", url_components.lpszUrlPath, nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, url_components.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0); + this->request = WinHttpOpenRequest(connection, data.empty() ? L"GET" : L"POST", url_components.lpszUrlPath, nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, url_components.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0); if (this->request == nullptr) { WinHttpCloseHandle(this->connection); @@ -235,10 +235,10 @@ void NetworkHTTPRequest::Connect() } /* Send the request (possibly with a payload). */ - if (data == nullptr) { + if (data.empty()) { WinHttpSendRequest(this->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, reinterpret_cast(this)); } else { - WinHttpSendRequest(this->request, L"Content-Type: application/x-www-form-urlencoded\r\n", -1, const_cast(data), static_cast(strlen(data)), static_cast(strlen(data)), reinterpret_cast(this)); + WinHttpSendRequest(this->request, L"Content-Type: application/x-www-form-urlencoded\r\n", -1, const_cast(data.c_str()), static_cast(data.size()), static_cast(data.size()), reinterpret_cast(this)); } } @@ -263,11 +263,9 @@ NetworkHTTPRequest::~NetworkHTTPRequest() WinHttpCloseHandle(this->request); WinHttpCloseHandle(this->connection); } - - free(this->data); } -/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const char *data) +/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const std::string data) { auto request = new NetworkHTTPRequest(std::wstring(uri.begin(), uri.end()), callback, data); request->Connect(); diff --git a/src/network/network_content.cpp b/src/network/network_content.cpp index 79bb4ed052..27a54b84dc 100644 --- a/src/network/network_content.cpp +++ b/src/network/network_content.cpp @@ -337,25 +337,14 @@ void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uin */ void ClientNetworkContentSocketHandler::DownloadSelectedContentHTTP(const ContentIDList &content) { - uint count = (uint)content.size(); - - /* Allocate memory for the whole request. - * Requests are "id\nid\n..." (as strings), so assume the maximum ID, - * which is uint32 so 10 characters long. Then the newlines and - * multiply that all with the count and then add the '\0'. */ - uint bytes = (10 + 1) * count + 1; - char *content_request = MallocT(bytes); - const char *lastof = content_request + bytes - 1; - - char *p = content_request; + std::string content_request; for (const ContentID &id : content) { - p += seprintf(p, lastof, "%d\n", id); + content_request += std::to_string(id) + "\n"; } this->http_response_index = -1; NetworkHTTPSocketHandler::Connect(NetworkContentMirrorUriString(), this, content_request); - /* NetworkHTTPContentConnecter takes over freeing of content_request! */ } /** From dea2dea8814412051b2d9531418e95b66146edee Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 15 Feb 2023 20:51:58 +0100 Subject: [PATCH 12/30] Fix: reset content download progress to zero if falling back to TCP Otherwise this chain of events can happen: - You already have a (partial) file downloaded - You start the download, and HTTP fails - This resets the download progress to the current size of the file - The TCP download starts at a very large value (UINT32_MAX - filesize) It now resets to 0% done when any negative value is being given. As added bonus, we no longer have to query how much was already downloaded. --- src/network/network_content.cpp | 4 +--- src/network/network_content_gui.cpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/network/network_content.cpp b/src/network/network_content.cpp index 27a54b84dc..57f022dd1a 100644 --- a/src/network/network_content.cpp +++ b/src/network/network_content.cpp @@ -586,9 +586,7 @@ void ClientNetworkContentSocketHandler::OnFailure() this->http_response_index = -2; if (this->curFile != nullptr) { - /* Revert the download progress when we are going for the old system. */ - long size = ftell(this->curFile); - if (size > 0) this->OnDownloadProgress(this->curInfo, (int)-size); + this->OnDownloadProgress(this->curInfo, -1); fclose(this->curFile); this->curFile = nullptr; diff --git a/src/network/network_content_gui.cpp b/src/network/network_content_gui.cpp index 1426cd77ec..2f3375f2a5 100644 --- a/src/network/network_content_gui.cpp +++ b/src/network/network_content_gui.cpp @@ -100,7 +100,7 @@ static WindowDesc _network_content_download_status_window_desc( ); BaseNetworkContentDownloadStatusWindow::BaseNetworkContentDownloadStatusWindow(WindowDesc *desc) : - Window(desc), cur_id(UINT32_MAX) + Window(desc), downloaded_bytes(0), downloaded_files(0), cur_id(UINT32_MAX) { _network_content_client.AddCallback(this); _network_content_client.DownloadSelectedContent(this->total_files, this->total_bytes); @@ -174,7 +174,13 @@ void BaseNetworkContentDownloadStatusWindow::OnDownloadProgress(const ContentInf this->downloaded_files++; } - this->downloaded_bytes += bytes; + /* A negative value means we are resetting; for example, when retrying or using a fallback. */ + if (bytes < 0) { + this->downloaded_bytes = 0; + } else { + this->downloaded_bytes += bytes; + } + this->SetDirty(); } From fdfcb09aa37ac3403b1bd3d5e8c043596dede71b Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 15 Feb 2023 20:54:08 +0100 Subject: [PATCH 13/30] Fix #10131: actually cancel downloads when pressing cancel --- src/network/core/http.h | 9 +++++++++ src/network/core/http_curl.cpp | 6 ++++-- src/network/core/http_winhttp.cpp | 31 +++++++++++++++++++++---------- src/network/network_content.cpp | 25 ++++++++++++++++++++----- src/network/network_content.h | 2 ++ 5 files changed, 56 insertions(+), 17 deletions(-) diff --git a/src/network/core/http.h b/src/network/core/http.h index e14ef8f006..78b5be87af 100644 --- a/src/network/core/http.h +++ b/src/network/core/http.h @@ -30,6 +30,15 @@ struct HTTPCallback { */ virtual void OnReceiveData(const char *data, size_t length) = 0; + /** + * Check if there is a request to cancel the transfer. + * + * @return true iff the connection is cancelled. + * @note Cancellations are never instant, and can take a bit of time to be processed. + * The object needs to remain valid until the OnFailure() callback is called. + */ + virtual bool IsCancelled() const = 0; + /** Silentium */ virtual ~HTTPCallback() {} }; diff --git a/src/network/core/http_curl.cpp b/src/network/core/http_curl.cpp index 0694afeac7..3781eb2d4f 100644 --- a/src/network/core/http_curl.cpp +++ b/src/network/core/http_curl.cpp @@ -167,8 +167,10 @@ void HttpThread() * do about this. */ curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, +[](void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) -> int { - return _http_thread_exit ? 1 : 0; + const HTTPCallback *callback = static_cast(userdata); + return (callback->IsCancelled() || _http_thread_exit) ? 1 : 0; }); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, request->callback); /* Perform the request. */ CURLcode res = curl_easy_perform(curl); @@ -177,7 +179,7 @@ void HttpThread() Debug(net, 1, "HTTP request succeeded"); request->callback->OnReceiveData(nullptr, 0); } else { - Debug(net, 0, "HTTP request failed: {}", curl_easy_strerror(res)); + Debug(net, (request->callback->IsCancelled() || _http_thread_exit) ? 1 : 0, "HTTP request failed: {}", curl_easy_strerror(res)); request->callback->OnFailure(); } } diff --git a/src/network/core/http_winhttp.cpp b/src/network/core/http_winhttp.cpp index e689ee0050..9fc28d62cb 100644 --- a/src/network/core/http_winhttp.cpp +++ b/src/network/core/http_winhttp.cpp @@ -29,10 +29,10 @@ private: HTTPCallback *callback; ///< Callback to send data back on. const std::string data; ///< Data to send, if any. - HINTERNET connection = nullptr; ///< Current connection object. - HINTERNET request = nullptr; ///< Current request object. - bool finished = false; ///< Whether we are finished with the request. - int depth = 0; ///< Current redirect depth we are in. + HINTERNET connection = nullptr; ///< Current connection object. + HINTERNET request = nullptr; ///< Current request object. + std::atomic finished = false; ///< Whether we are finished with the request. + int depth = 0; ///< Current redirect depth we are in. public: NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *callback, const std::string &data); @@ -88,6 +88,8 @@ static std::string GetLastErrorAsString() */ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length) { + if (this->finished) return; + switch (code) { case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME: case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED: @@ -108,8 +110,8 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length) /* Make sure we are not in a redirect loop. */ if (this->depth++ > 5) { Debug(net, 0, "HTTP request failed: too many redirects"); - this->callback->OnFailure(); this->finished = true; + this->callback->OnFailure(); return; } break; @@ -130,8 +132,8 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length) /* If there is any error, we simply abort the request. */ if (status_code >= 400) { Debug(net, 0, "HTTP request failed: status-code {}", status_code); - this->callback->OnFailure(); this->finished = true; + this->callback->OnFailure(); return; } @@ -171,20 +173,22 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length) case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE: case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString()); - this->callback->OnFailure(); this->finished = true; + this->callback->OnFailure(); break; default: Debug(net, 0, "HTTP request failed: unexepected callback code 0x{:x}", code); - this->callback->OnFailure(); this->finished = true; + this->callback->OnFailure(); return; } } static void CALLBACK StaticWinHttpCallback(HINTERNET handle, DWORD_PTR context, DWORD code, void *info, DWORD length) { + if (context == 0) return; + NetworkHTTPRequest *request = (NetworkHTTPRequest *)context; request->WinHttpCallback(code, info, length); } @@ -219,8 +223,8 @@ void NetworkHTTPRequest::Connect() this->connection = WinHttpConnect(_winhttp_session, url_components.lpszHostName, url_components.nPort, 0); if (this->connection == nullptr) { Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString()); - this->callback->OnFailure(); this->finished = true; + this->callback->OnFailure(); return; } @@ -229,8 +233,8 @@ void NetworkHTTPRequest::Connect() WinHttpCloseHandle(this->connection); Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString()); - this->callback->OnFailure(); this->finished = true; + this->callback->OnFailure(); return; } @@ -249,6 +253,13 @@ void NetworkHTTPRequest::Connect() */ bool NetworkHTTPRequest::Receive() { + if (this->callback->IsCancelled()) { + Debug(net, 1, "HTTP request failed: cancelled by user"); + this->finished = true; + this->callback->OnFailure(); + return true; + } + return this->finished; } diff --git a/src/network/network_content.cpp b/src/network/network_content.cpp index 57f022dd1a..6ac2cbbd10 100644 --- a/src/network/network_content.cpp +++ b/src/network/network_content.cpp @@ -324,6 +324,8 @@ void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uin /* If there's nothing to download, do nothing. */ if (files == 0) return; + this->isCancelled = false; + if (_settings_client.network.no_http_content_downloads || fallback) { this->DownloadSelectedContentFallback(content); } else { @@ -574,13 +576,14 @@ void ClientNetworkContentSocketHandler::AfterDownload() } } +bool ClientNetworkContentSocketHandler::IsCancelled() const +{ + return this->isCancelled; +} + /* Also called to just clean up the mess. */ void ClientNetworkContentSocketHandler::OnFailure() { - /* If we fail, download the rest via the 'old' system. */ - uint files, bytes; - this->DownloadSelectedContent(files, bytes, true); - this->http_response.clear(); this->http_response.shrink_to_fit(); this->http_response_index = -2; @@ -591,6 +594,13 @@ void ClientNetworkContentSocketHandler::OnFailure() fclose(this->curFile); this->curFile = nullptr; } + + /* If we fail, download the rest via the 'old' system. */ + if (!this->isCancelled) { + uint files, bytes; + + this->DownloadSelectedContent(files, bytes, true); + } } void ClientNetworkContentSocketHandler::OnReceiveData(const char *data, size_t length) @@ -726,7 +736,8 @@ ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() : http_response_index(-2), curFile(nullptr), curInfo(nullptr), - isConnecting(false) + isConnecting(false), + isCancelled(false) { this->lastActivity = std::chrono::steady_clock::now(); } @@ -772,7 +783,10 @@ public: void ClientNetworkContentSocketHandler::Connect() { if (this->sock != INVALID_SOCKET || this->isConnecting) return; + + this->isCancelled = false; this->isConnecting = true; + new NetworkContentConnecter(NetworkContentServerConnectionString()); } @@ -781,6 +795,7 @@ void ClientNetworkContentSocketHandler::Connect() */ NetworkRecvStatus ClientNetworkContentSocketHandler::CloseConnection(bool error) { + this->isCancelled = true; NetworkContentSocketHandler::CloseConnection(); if (this->sock == INVALID_SOCKET) return NETWORK_RECV_STATUS_OKAY; diff --git a/src/network/network_content.h b/src/network/network_content.h index 21d324cab7..8a2877f904 100644 --- a/src/network/network_content.h +++ b/src/network/network_content.h @@ -76,6 +76,7 @@ protected: FILE *curFile; ///< Currently downloaded file ContentInfo *curInfo; ///< Information about the currently downloaded file bool isConnecting; ///< Whether we're connecting + bool isCancelled; ///< Whether the download has been cancelled std::chrono::steady_clock::time_point lastActivity; ///< The last time there was network activity friend class NetworkContentConnecter; @@ -94,6 +95,7 @@ protected: void OnFailure() override; void OnReceiveData(const char *data, size_t length) override; + bool IsCancelled() const override; bool BeforeDownload(); void AfterDownload(); From 27cbb81df5cf97f78ffbab9bd64f6daba6c21bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Guilloux?= Date: Thu, 16 Feb 2023 22:35:51 +0100 Subject: [PATCH 14/30] Fix: [Actions] vcpkg needs pkg-config to build zlib on macOS (#10488) --- .github/workflows/ci-build.yml | 9 +++++++++ .github/workflows/release-macos.yml | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 54b314d9e8..e5b0947315 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -169,6 +169,15 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - name: Install dependencies + env: + HOMEBREW_NO_AUTO_UPDATE: 1 + HOMEBREW_NO_INSTALL_CLEANUP: 1 + run: | + brew install \ + pkg-config \ + # EOF + - name: Prepare cache key id: key run: | diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 8323260501..b7a3201566 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -26,7 +26,10 @@ jobs: HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1 run: | - brew install pandoc + brew install \ + pandoc \ + pkg-config \ + # EOF - name: Prepare cache key id: key From 4072dcff49930b1eb6e2a8489cbce1970f39caec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Guilloux?= Date: Fri, 17 Feb 2023 12:24:51 +0100 Subject: [PATCH 15/30] Fix #10486: [Script] Debug window requires AIs to be started before GS (#10487) --- src/saveload/afterload.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index b22797032e..854bb5b56f 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -544,14 +544,16 @@ static inline bool MayHaveBridgeAbove(TileIndex t) */ static void StartScripts() { - /* Start the GameScript. */ - Game::StartNew(); + /* Script debug window requires AIs to be started before trying to start GameScript. */ /* Start the AIs. */ for (const Company *c : Company::Iterate()) { if (Company::IsValidAiID(c->index)) AI::StartNew(c->index, false); } + /* Start the GameScript. */ + Game::StartNew(); + ShowScriptDebugWindowIfScriptError(); } From 2fdfc38da86d8bbc6486efb1e42d9e7517bde145 Mon Sep 17 00:00:00 2001 From: translators Date: Fri, 17 Feb 2023 18:47:31 +0000 Subject: [PATCH 16/30] Update: Translations from eints korean: 6 changes by telk5093 slovak: 8 changes by legitalk catalan: 3 changes by J0anJosep turkish: 5 changes by EndChapter dutch: 1 change by Afoklala portuguese (brazilian): 8 changes by ericandradex polish: 2 changes by pAter-exe --- src/lang/brazilian_portuguese.txt | 10 ++++++++-- src/lang/catalan.txt | 4 +++- src/lang/dutch.txt | 2 +- src/lang/korean.txt | 8 ++++++-- src/lang/polish.txt | 4 ++-- src/lang/slovak.txt | 10 ++++++++-- src/lang/turkish.txt | 6 +++++- 7 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/lang/brazilian_portuguese.txt b/src/lang/brazilian_portuguese.txt index fb63f4762d..a5c075c828 100644 --- a/src/lang/brazilian_portuguese.txt +++ b/src/lang/brazilian_portuguese.txt @@ -1207,7 +1207,9 @@ STR_CONFIG_SETTING_HORIZONTAL_POS_RIGHT :Direita STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN :Empréstimo Inicial Máximo: {STRING} STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_HELPTEXT :Quantia máxima a ser emprestada para uma companhia (sem levar em conta a inflação) +STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_VALUE :{CURRENCY_LONG} ###setting-zero-is-special +STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_DISABLED :Sem empréstimo {RED}Requer Script de Jogo para fornecer fundos iniciais STR_CONFIG_SETTING_INTEREST_RATE :Taxa de Juros: {STRING} STR_CONFIG_SETTING_INTEREST_RATE_HELPTEXT :Juros de empréstimo; também controla inflação, se ativado @@ -3351,6 +3353,8 @@ STR_NEWGRF_ERROR_MSG_INFO :{SILVER}{STRING STR_NEWGRF_ERROR_MSG_WARNING :{RED}Atenção: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_ERROR :{RED}Erro: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_FATAL :{RED}Erro Fatal: {SILVER}{STRING} +STR_NEWGRF_ERROR_FATAL_POPUP :{WHITE}O NewGRF "{STRING}" retornou um erro fatal:{}{STRING} +STR_NEWGRF_ERROR_POPUP :{WHITE}O NewGRF "{STRING}" retornou um erro fatal:{}{STRING} STR_NEWGRF_ERROR_VERSION_NUMBER :{1:STRING} não irá funcionar com a versão do TTDPatch encontrada pelo OpenTTD STR_NEWGRF_ERROR_DOS_OR_WINDOWS :{1:STRING} funciona na versão {STRING} de TTD STR_NEWGRF_ERROR_UNSET_SWITCH :{1:STRING} é projetado para ser usado com {2:STRING} @@ -4526,7 +4530,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Limpa Li STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Limpar a velocidade máxima de viagem da ordem em destaque. Ctrl+Clique limpa as velocidades para todas as ordens STR_TIMETABLE_RESET_LATENESS :{BLACK}Restabelecer Contador de Atraso -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Restabelecer o contador de atraso,então o veículo estará pontual +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Redefina o contador de atrasos, para que o veículo chegue no horário. Ctrl+Clique redefinirá todo o grupo para que o veículo mais recente chegue no horário e todos os outros cheguem mais cedo STR_TIMETABLE_AUTOFILL :{BLACK}Autopreencher STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Preencher o horário automaticamente com os valores da próxima viagem. Ctrl+Clique para tentar manter os tempos de espera @@ -4621,7 +4625,9 @@ STR_SCREENSHOT_HEIGHTMAP_SCREENSHOT :{BLACK}Captura STR_SCREENSHOT_MINIMAP_SCREENSHOT :{BLACK}Captura de tela do minimapa # Script Parameters -STR_AI_SETTINGS_CAPTION_AI :{WHITE}Parâmetros de IA +STR_AI_SETTINGS_CAPTION :{WHITE}{STRING} Parâmetros +STR_AI_SETTINGS_CAPTION_AI :IA +STR_AI_SETTINGS_CAPTION_GAMESCRIPT :Script de Jogo STR_AI_SETTINGS_CLOSE :{BLACK}Fechar STR_AI_SETTINGS_RESET :{BLACK}Resetar STR_AI_SETTINGS_SETTING :{STRING}: {ORANGE}{STRING} diff --git a/src/lang/catalan.txt b/src/lang/catalan.txt index ba125cfc18..7bf877fafa 100644 --- a/src/lang/catalan.txt +++ b/src/lang/catalan.txt @@ -3353,6 +3353,8 @@ STR_NEWGRF_ERROR_MSG_INFO :{SILVER}{STRING STR_NEWGRF_ERROR_MSG_WARNING :{RED}Alerta: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_ERROR :{RED}Error: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_FATAL :{RED}Fatal: {SILVER}{STRING} +STR_NEWGRF_ERROR_FATAL_POPUP :{WHITE}El NewGRF "{STRING}" ha retornat un error fatal:{}{STRING} +STR_NEWGRF_ERROR_POPUP :{WHITE}El NewGRF "{STRING}" ha retornat un error:{}{STRING} STR_NEWGRF_ERROR_VERSION_NUMBER :{1:STRING} no funcionarà amb la versió TTDPatch informada per l'OpenTTD STR_NEWGRF_ERROR_DOS_OR_WINDOWS :{1:STRING} és per a la versió {2:STRING} del TTD STR_NEWGRF_ERROR_UNSET_SWITCH :{1:STRING} està dissenyat per a fer-se servir amb {2:STRING} @@ -4528,7 +4530,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Esborra STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Esborra la velocitat de viatge màxima de l'ordre seleccionada. Amb Ctrl+clic s'esborra la velocitat per a totes les ordres. STR_TIMETABLE_RESET_LATENESS :{BLACK}Restablir Retard -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Restableix el comptador de retards, de manera que el vehicle serà puntual +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Restableix el comptador de retards, de manera que el vehicle serà puntual. Amb Ctrl+clic, es restableix el grup sencer de manera que el vehicle més endarrerit passa a anar a hora i els altres arribaran abans d'hora. STR_TIMETABLE_AUTOFILL :{BLACK}Autoomple STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Omple automàticament l'horari amb valors del proper viatge. CTRL+clic per a intentar mantenir els temps d'espera. diff --git a/src/lang/dutch.txt b/src/lang/dutch.txt index 57baa224de..aa5b30eb0a 100644 --- a/src/lang/dutch.txt +++ b/src/lang/dutch.txt @@ -4529,7 +4529,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Snelheid STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Verwijder de maximumsnelheid van de gekozen order. Met Ctrl+klik wis je de snelheid voor alle orders STR_TIMETABLE_RESET_LATENESS :{BLACK}Vertragingsteller terugstellen -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Stel de vertragingsteller terug zodat het voertuig op tijd is +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Stel de vertragingsteller terug zodat het voertuig op tijd is. Ctrl+kllik stelt de hele groep terug zodat het laatste voertuig op tijd komt en alle andere te vroeg. STR_TIMETABLE_AUTOFILL :{BLACK}Automatisch vullen STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Vul de dienstregeling automatisch in aan de hand van de volgende reis. Ctrl+klik om te proberen om wachttijden te bewaren diff --git a/src/lang/korean.txt b/src/lang/korean.txt index d4a0dcf9d6..66d968430a 100644 --- a/src/lang/korean.txt +++ b/src/lang/korean.txt @@ -3353,6 +3353,8 @@ STR_NEWGRF_ERROR_MSG_INFO :{SILVER}{STRING STR_NEWGRF_ERROR_MSG_WARNING :{RED}경고: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_ERROR :{RED}오류: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_FATAL :{RED}치명적 오류: {SILVER}{STRING} +STR_NEWGRF_ERROR_FATAL_POPUP :{WHITE}NewGRF "{STRING}"에서 치명적인 오류가 발생했습니다:{}{STRING} +STR_NEWGRF_ERROR_POPUP :{WHITE}NewGRF "{STRING}"에서 오류가 발생했습니다:{}{STRING} STR_NEWGRF_ERROR_VERSION_NUMBER :{1:STRING}{G 1 "은" "는"} OpenTTD에서 보고된 TTDPatch 버전에서 작동하지 않을 것입니다 STR_NEWGRF_ERROR_DOS_OR_WINDOWS :{1:STRING}{G 1 "은" "는"} {2:STRING} 버전의 TTD를 위한 것입니다 STR_NEWGRF_ERROR_UNSET_SWITCH :{1:STRING}{G 1 "은" "는"} {2:STRING}{G 1 "와" "과"} 같이 사용해야 합니다 @@ -4528,7 +4530,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}속력 STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}선택한 경로의 최대 여행 속력 제한값을 초기화합니다. CTRL+클릭하면 모든 경로의 속력을 초기화합니다 STR_TIMETABLE_RESET_LATENESS :{BLACK}지연 시간 초기화 -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}이 차량의 지연 시간값을 초기화하여, 정시운행 상태로 바꿉니다 +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}이 차량의 지연 시간 값을 초기화하여, 정시운행 상태로 바꿉니다. CTRL+클릭하면 전체 그룹을 초기화하여 마지막 차량은 정시에, 나머지 차량은 조착하게 됩니다. STR_TIMETABLE_AUTOFILL :{BLACK}자동 시간 설정 STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}다음 운행시 자동으로 값을 얻어 시간표를 완성합니다. 역에 머무르는 시간값을 유지하려면 CTRL+클릭하세요 @@ -4623,7 +4625,9 @@ STR_SCREENSHOT_HEIGHTMAP_SCREENSHOT :{BLACK}높이 STR_SCREENSHOT_MINIMAP_SCREENSHOT :{BLACK}미니맵 스크린 샷 # Script Parameters -STR_AI_SETTINGS_CAPTION_AI :{WHITE}인공지능 매개 변수 +STR_AI_SETTINGS_CAPTION :{WHITE}{STRING} 매개 변수 +STR_AI_SETTINGS_CAPTION_AI :인공지능 +STR_AI_SETTINGS_CAPTION_GAMESCRIPT :게임 스크립트 STR_AI_SETTINGS_CLOSE :{BLACK}닫기 STR_AI_SETTINGS_RESET :{BLACK}초기화 STR_AI_SETTINGS_SETTING :{STRING}: {ORANGE}{STRING} diff --git a/src/lang/polish.txt b/src/lang/polish.txt index f0ffd90345..2ce044765e 100644 --- a/src/lang/polish.txt +++ b/src/lang/polish.txt @@ -4915,7 +4915,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Wyczyś STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Wyczyść maksymalną prędkość przejazdu w zaznaczonym poleceniu. Ctrl+klik wyczyści prędkość we wszystkich poleceniach STR_TIMETABLE_RESET_LATENESS :{BLACK}Wyzeruj spóźnienia -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Wyzeruj licznik spóźnienia, aby pojazd podróżował zgodnie z rozkładem jazdy +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Wyzeruj licznik spóźnienia, aby pojazd podróżował zgodnie z rozkładem jazdy. Ctrl+klik spowoduje wyzerowanie całej grupy, więc ostatni pojazd będzie punktualny, a wszystkie pozostałe będą przed czasem STR_TIMETABLE_AUTOFILL :{BLACK}Automat. wypełnienie STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Wypełnij automatycznie rozkład jazdy wartościami z następnego przejazdu. Ctrl+klik, aby spróbować utrzymać czasy oczekiwania @@ -5011,7 +5011,7 @@ STR_SCREENSHOT_MINIMAP_SCREENSHOT :{BLACK}Zrzut ek # Script Parameters STR_AI_SETTINGS_CAPTION :{WHITE}{STRING} Parametry -STR_AI_SETTINGS_CAPTION_AI :{WHITE}Parametry SI +STR_AI_SETTINGS_CAPTION_AI :SI STR_AI_SETTINGS_CAPTION_GAMESCRIPT :Game Script STR_AI_SETTINGS_CLOSE :{BLACK}Zamknij STR_AI_SETTINGS_RESET :{BLACK}Resetuj diff --git a/src/lang/slovak.txt b/src/lang/slovak.txt index b16f52e99b..b7b531ddcd 100644 --- a/src/lang/slovak.txt +++ b/src/lang/slovak.txt @@ -1274,7 +1274,9 @@ STR_CONFIG_SETTING_HORIZONTAL_POS_RIGHT :vpravo STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN :Maximálny počiatočný úver: {STRING} STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_HELPTEXT :Maximálna množstvo peňazí ktoré si môže spoločnosť požičať (bez inflácie) +STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_VALUE :{CURRENCY_LONG} ###setting-zero-is-special +STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_DISABLED :Žiadny úver {RED}Vyžaduje Herný skript na poskytnutie počiatočných prostriedkov STR_CONFIG_SETTING_INTEREST_RATE :Úroková sadzba: {STRING} STR_CONFIG_SETTING_INTEREST_RATE_HELPTEXT :Úroková sadzba úveru; kontroluje infláciu ak je povolená @@ -3418,6 +3420,8 @@ STR_NEWGRF_ERROR_MSG_INFO :{SILVER}{STRING STR_NEWGRF_ERROR_MSG_WARNING :{RED}Upozornenie: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_ERROR :{RED}Chyba: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_FATAL :{RED}Kritická chyba: {SILVER}{STRING} +STR_NEWGRF_ERROR_FATAL_POPUP :{WHITE}NewGRF "{STRING}" vrátil fatálnu chybu:{}{STRING} +STR_NEWGRF_ERROR_POPUP :{WHITE}NewGRF "{STRING}" vrátil chybu:{}{STRING} STR_NEWGRF_ERROR_VERSION_NUMBER :{1:STRING} nebude fungovať s TTDPatch verziou nahlásenou OpenTTD. STR_NEWGRF_ERROR_DOS_OR_WINDOWS :{1:STRING} je pre verziu {2:STRING} TTD STR_NEWGRF_ERROR_UNSET_SWITCH :{1:STRING} je navrhnutý pre použitie s {2:STRING} @@ -4593,7 +4597,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Odstrán STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Odstrániť obmedzenie maximánej rýchlosti označeného príkazu. Ctrl+klik vymaže rýchlosť pre všetky príkazy STR_TIMETABLE_RESET_LATENESS :{BLACK}Reset meškania -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Vynulovať počítadlo meškania, takže vozidlo pôjde presne +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Vynulovať počítadlo meškania, takže vozidlo pôjde presne. Ctrl+klik vynuluje celú skupinu, takže posledné vozidlo bude bez meškania a ostatné prídu skôr STR_TIMETABLE_AUTOFILL :{BLACK}Automaticky vyplniť STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Vyplniť časový plán automaticky s hodnotami z nasledujúcej trasy. Ctrl+klik - pokúsiť sa udržať čakacie doby @@ -4688,7 +4692,9 @@ STR_SCREENSHOT_HEIGHTMAP_SCREENSHOT :{BLACK}Snímka STR_SCREENSHOT_MINIMAP_SCREENSHOT :{BLACK}Snímka minimapy # Script Parameters -STR_AI_SETTINGS_CAPTION_AI :{WHITE}Parametre AI +STR_AI_SETTINGS_CAPTION :{WHITE}{STRING} Parametre +STR_AI_SETTINGS_CAPTION_AI :AI +STR_AI_SETTINGS_CAPTION_GAMESCRIPT :Herný skript STR_AI_SETTINGS_CLOSE :{BLACK}Zavrieť STR_AI_SETTINGS_RESET :{BLACK}Resetovať STR_AI_SETTINGS_SETTING :{STRING}: {ORANGE}{STRING} diff --git a/src/lang/turkish.txt b/src/lang/turkish.txt index 19f19110bf..2e081c3e9b 100644 --- a/src/lang/turkish.txt +++ b/src/lang/turkish.txt @@ -3353,6 +3353,8 @@ STR_NEWGRF_ERROR_MSG_INFO :{SILVER}{STRING STR_NEWGRF_ERROR_MSG_WARNING :{RED}Uyarı: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_ERROR :{RED}Hata: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_FATAL :{RED}Ölümcül hata: {SILVER}{STRING} +STR_NEWGRF_ERROR_FATAL_POPUP :{WHITE}NewGRF "{STRING}" bir ölümcül hata bildirdi:{}{STRING} +STR_NEWGRF_ERROR_POPUP :{WHITE}NewGRF "{STRING}" bir ölümcül hata bildirdi:{}{STRING} STR_NEWGRF_ERROR_VERSION_NUMBER :{1:STRING} OpenTTD tarafından belirtilen TTDPatch sürümüyle çalışmayacaktır STR_NEWGRF_ERROR_DOS_OR_WINDOWS :{1:STRING}, TTD'nin {2:STRING} sürümü içindir STR_NEWGRF_ERROR_UNSET_SWITCH :{1:STRING}, {2:STRING} ile kullanılmak için tasarlanmıştır @@ -4528,7 +4530,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Hız Sı STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Seçili emrin azami seyahat hızını sil. Ctrl+Tıklama bütün emirlerin azami hızlarını siler STR_TIMETABLE_RESET_LATENESS :{BLACK}Gecikme sayacını sıfırla -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Geç kalma sayacını sıfırla, böylece araç zamanında gitmiş sayılacak +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Geç kalma sayacını sıfırla, böylece araç zamanında gitmiş sayılacak. Ctrl ile tıklamak bütün grubu sıfırlar böylece en son araç zamanında ve diğer tüm araçlar erken gelmiş sayılacak. STR_TIMETABLE_AUTOFILL :{BLACK}Otomatik doldur STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Zaman tablosunu bir sonraki yolculuktaki değerlerle otomatik doldur Bekleme sürelerini tutmak için Ctrl ile tıklanır @@ -4623,7 +4625,9 @@ STR_SCREENSHOT_HEIGHTMAP_SCREENSHOT :{BLACK}Yüksekl STR_SCREENSHOT_MINIMAP_SCREENSHOT :{BLACK}Küçük harita ekran görüntüsü # Script Parameters +STR_AI_SETTINGS_CAPTION :{WHITE}{STRING} Parametreler STR_AI_SETTINGS_CAPTION_AI :{WHITE}YZ Parametreleri +STR_AI_SETTINGS_CAPTION_GAMESCRIPT :Oyun Betiği STR_AI_SETTINGS_CLOSE :{BLACK}Kapat STR_AI_SETTINGS_RESET :{BLACK}Yeniden başlat STR_AI_SETTINGS_SETTING :{STRING}: {ORANGE}{STRING} From e735370318128e93d57a5d3294bf92551fa33cf8 Mon Sep 17 00:00:00 2001 From: glx22 Date: Thu, 16 Feb 2023 01:17:53 +0100 Subject: [PATCH 17/30] Change: [Script] A ScriptText with too many parameters is now a fatal error It should never happen as adding/setting parameters already checks that anyway. --- src/script/api/ai_changelog.hpp | 3 +++ src/script/api/game_changelog.hpp | 3 +++ src/script/api/script_error.hpp | 6 ------ src/script/api/script_text.cpp | 7 ++++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/script/api/ai_changelog.hpp b/src/script/api/ai_changelog.hpp index 27ad40e79f..9672777ec6 100644 --- a/src/script/api/ai_changelog.hpp +++ b/src/script/api/ai_changelog.hpp @@ -20,6 +20,9 @@ * API additions: * \li AITown::ROAD_LAYOUT_RANDOM * + * API removals: + * \li AIError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore. + * * \b 13.0 * * API additions: diff --git a/src/script/api/game_changelog.hpp b/src/script/api/game_changelog.hpp index 46a8e4904f..79174cf9af 100644 --- a/src/script/api/game_changelog.hpp +++ b/src/script/api/game_changelog.hpp @@ -20,6 +20,9 @@ * API additions: * \li GSTown::ROAD_LAYOUT_RANDOM * + * API removals: + * \li GSError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore. + * * \b 13.0 * * API additions: diff --git a/src/script/api/script_error.hpp b/src/script/api/script_error.hpp index 87d4196fdd..6d3d476f7b 100644 --- a/src/script/api/script_error.hpp +++ b/src/script/api/script_error.hpp @@ -42,10 +42,6 @@ * @param string The string that is checked. */ #define EnforcePreconditionEncodedText(returnval, string) \ - if ((string) == nullptr) { \ - ScriptObject::SetLastError(ScriptError::ERR_PRECONDITION_TOO_MANY_PARAMETERS); \ - return returnval; \ - } \ if (StrEmpty(string)) { \ ScriptObject::SetLastError(ScriptError::ERR_PRECONDITION_FAILED); \ return returnval; \ @@ -94,8 +90,6 @@ public: ERR_PRECONDITION_FAILED, // [] /** A string supplied was too long */ ERR_PRECONDITION_STRING_TOO_LONG, // [] - /** A string had too many parameters */ - ERR_PRECONDITION_TOO_MANY_PARAMETERS, // [] /** The company you use is invalid */ ERR_PRECONDITION_INVALID_COMPANY, // [] /** An error returned by a NewGRF. No possibility to get the exact error in an script readable format */ diff --git a/src/script/api/script_text.cpp b/src/script/api/script_text.cpp index 62980326ce..c9040b1d88 100644 --- a/src/script/api/script_text.cpp +++ b/src/script/api/script_text.cpp @@ -11,6 +11,7 @@ #include "../../string_func.h" #include "../../strings_func.h" #include "script_text.hpp" +#include "../script_fatalerror.hpp" #include "../../table/control_codes.h" #include "table/strings.h" @@ -181,7 +182,8 @@ const char *ScriptText::GetEncodedText() static char buf[1024]; int param_count = 0; this->_GetEncodedText(buf, lastof(buf), param_count); - return (param_count > SCRIPT_TEXT_MAX_PARAMETERS) ? nullptr : buf; + if (param_count > SCRIPT_TEXT_MAX_PARAMETERS) throw Script_FatalError("A string had too many parameters"); + return buf; } char *ScriptText::_GetEncodedText(char *p, char *lastofp, int ¶m_count) @@ -208,8 +210,7 @@ char *ScriptText::_GetEncodedText(char *p, char *lastofp, int ¶m_count) const char *Text::GetDecodedText() { - const char *encoded_text = this->GetEncodedText(); - if (encoded_text == nullptr) return nullptr; + const std::string &encoded_text = this->GetEncodedText(); static char buf[1024]; ::SetDParamStr(0, encoded_text); From 3559576166de5ba0e0d7c2f9a127696b60f6f182 Mon Sep 17 00:00:00 2001 From: glx22 Date: Thu, 16 Feb 2023 02:05:54 +0100 Subject: [PATCH 18/30] Codechange: [Script] Don't expose static buffers outside of ScriptText --- src/script/api/script_basestation.cpp | 2 +- src/script/api/script_company.cpp | 4 ++-- src/script/api/script_error.hpp | 2 +- src/script/api/script_goal.cpp | 16 ++++++---------- src/script/api/script_group.cpp | 2 +- src/script/api/script_industry.cpp | 7 +------ src/script/api/script_industry.hpp | 2 +- src/script/api/script_league.cpp | 17 ++++++++--------- src/script/api/script_news.cpp | 2 +- src/script/api/script_sign.cpp | 4 ++-- src/script/api/script_story_page.cpp | 25 ++++++++++++++++--------- src/script/api/script_text.cpp | 11 +++-------- src/script/api/script_text.hpp | 15 +++++++-------- src/script/api/script_town.cpp | 19 ++++++------------- src/script/api/script_town.hpp | 6 +++--- src/script/api/script_vehicle.cpp | 2 +- 16 files changed, 60 insertions(+), 76 deletions(-) diff --git a/src/script/api/script_basestation.cpp b/src/script/api/script_basestation.cpp index 788d549500..4835edc4bd 100644 --- a/src/script/api/script_basestation.cpp +++ b/src/script/api/script_basestation.cpp @@ -40,7 +40,7 @@ EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY); EnforcePrecondition(false, IsValidBaseStation(station_id)); EnforcePrecondition(false, name != nullptr); - const char *text = name->GetDecodedText(); + const std::string &text = name->GetDecodedText(); EnforcePreconditionEncodedText(false, text); EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_STATION_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG); diff --git a/src/script/api/script_company.cpp b/src/script/api/script_company.cpp index 33c0c0ea48..b35234f5a7 100644 --- a/src/script/api/script_company.cpp +++ b/src/script/api/script_company.cpp @@ -48,7 +48,7 @@ CCountedPtr counter(name); EnforcePrecondition(false, name != nullptr); - const char *text = name->GetDecodedText(); + const std::string &text = name->GetDecodedText(); EnforcePreconditionEncodedText(false, text); EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_COMPANY_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG); @@ -69,7 +69,7 @@ CCountedPtr counter(name); EnforcePrecondition(false, name != nullptr); - const char *text = name->GetDecodedText(); + const std::string &text = name->GetDecodedText(); EnforcePreconditionEncodedText(false, text); EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_PRESIDENT_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG); diff --git a/src/script/api/script_error.hpp b/src/script/api/script_error.hpp index 6d3d476f7b..f4b3832ef5 100644 --- a/src/script/api/script_error.hpp +++ b/src/script/api/script_error.hpp @@ -42,7 +42,7 @@ * @param string The string that is checked. */ #define EnforcePreconditionEncodedText(returnval, string) \ - if (StrEmpty(string)) { \ + if (string.empty()) { \ ScriptObject::SetLastError(ScriptError::ERR_PRECONDITION_FAILED); \ return returnval; \ } diff --git a/src/script/api/script_goal.cpp b/src/script/api/script_goal.cpp index d908f4e720..8260a7d436 100644 --- a/src/script/api/script_goal.cpp +++ b/src/script/api/script_goal.cpp @@ -34,7 +34,7 @@ EnforcePrecondition(GOAL_INVALID, ScriptObject::GetCompany() == OWNER_DEITY); EnforcePrecondition(GOAL_INVALID, goal != nullptr); - const char *text = goal->GetEncodedText(); + const std::string &text = goal->GetEncodedText(); EnforcePreconditionEncodedText(GOAL_INVALID, text); EnforcePrecondition(GOAL_INVALID, company == ScriptCompany::COMPANY_INVALID || ScriptCompany::ResolveCompanyID(company) != ScriptCompany::COMPANY_INVALID); @@ -71,9 +71,10 @@ EnforcePrecondition(false, IsValidGoal(goal_id)); EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY); EnforcePrecondition(false, goal != nullptr); - EnforcePrecondition(false, !StrEmpty(goal->GetEncodedText())); + const std::string &text = goal->GetEncodedText(); + EnforcePreconditionEncodedText(false, text); - return ScriptObject::Command::Do(goal_id, goal->GetEncodedText()); + return ScriptObject::Command::Do(goal_id, text); } /* static */ bool ScriptGoal::SetProgress(GoalID goal_id, Text *progress) @@ -83,12 +84,7 @@ EnforcePrecondition(false, IsValidGoal(goal_id)); EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY); - /* Ensure null as used for empty string. */ - if (progress != nullptr && StrEmpty(progress->GetEncodedText())) { - progress = nullptr; - } - - return ScriptObject::Command::Do(goal_id, progress != nullptr ? std::string{ progress->GetEncodedText() } : std::string{}); + return ScriptObject::Command::Do(goal_id, progress != nullptr ? progress->GetEncodedText() : std::string{}); } /* static */ bool ScriptGoal::SetCompleted(GoalID goal_id, bool completed) @@ -114,7 +110,7 @@ EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY); EnforcePrecondition(false, question != nullptr); - const char *text = question->GetEncodedText(); + const std::string &text = question->GetEncodedText(); EnforcePreconditionEncodedText(false, text); uint min_buttons = (type == QT_QUESTION ? 1 : 0); EnforcePrecondition(false, CountBits(buttons) >= min_buttons && CountBits(buttons) <= 3); diff --git a/src/script/api/script_group.cpp b/src/script/api/script_group.cpp index dbf4c0280e..9910dae3a4 100644 --- a/src/script/api/script_group.cpp +++ b/src/script/api/script_group.cpp @@ -57,7 +57,7 @@ EnforcePrecondition(false, IsValidGroup(group_id)); EnforcePrecondition(false, name != nullptr); - const char *text = name->GetDecodedText(); + const std::string &text = name->GetDecodedText(); EnforcePreconditionEncodedText(false, text); EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_GROUP_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG); diff --git a/src/script/api/script_industry.cpp b/src/script/api/script_industry.cpp index a27ea1a6e3..7ca847f05a 100644 --- a/src/script/api/script_industry.cpp +++ b/src/script/api/script_industry.cpp @@ -53,14 +53,9 @@ { CCountedPtr counter(text); - const char *encoded_text = nullptr; - if (text != nullptr) { - encoded_text = text->GetEncodedText(); - EnforcePreconditionEncodedText(false, encoded_text); - } EnforcePrecondition(false, IsValidIndustry(industry_id)); - return ScriptObject::Command::Do(industry_id, std::string{ encoded_text ? encoded_text : "" }); + return ScriptObject::Command::Do(industry_id, text != nullptr ? text->GetEncodedText() : std::string{}); } /* static */ ScriptIndustry::CargoAcceptState ScriptIndustry::IsCargoAccepted(IndustryID industry_id, CargoID cargo_id) diff --git a/src/script/api/script_industry.hpp b/src/script/api/script_industry.hpp index 95133da0ec..f5e435d2dd 100644 --- a/src/script/api/script_industry.hpp +++ b/src/script/api/script_industry.hpp @@ -84,7 +84,7 @@ public: /** * Set the custom text of an industry, shown in the GUI. * @param industry_id The industry to set the custom text of. - * @param text The text to set it to (can be either a raw string, or a ScriptText object). If null is passed, the text will be removed. + * @param text The text to set it to (can be either a raw string, or a ScriptText object). If null, or an empty string, is passed, the text will be removed. * @pre IsValidIndustry(industry_id). * @return True if the action succeeded. * @api -ai diff --git a/src/script/api/script_league.cpp b/src/script/api/script_league.cpp index d50eff8446..928c9e2971 100644 --- a/src/script/api/script_league.cpp +++ b/src/script/api/script_league.cpp @@ -32,11 +32,11 @@ EnforcePrecondition(LEAGUE_TABLE_INVALID, ScriptObject::GetCompany() == OWNER_DEITY); EnforcePrecondition(LEAGUE_TABLE_INVALID, title != nullptr); - const char *encoded_title = title->GetEncodedText(); + const std::string &encoded_title = title->GetEncodedText(); EnforcePreconditionEncodedText(LEAGUE_TABLE_INVALID, encoded_title); - auto encoded_header = (header != nullptr ? std::string{ header->GetEncodedText() } : std::string{}); - auto encoded_footer = (footer != nullptr ? std::string{ footer->GetEncodedText() } : std::string{}); + const std::string &encoded_header = (header != nullptr ? header->GetEncodedText() : std::string{}); + const std::string &encoded_footer = (footer != nullptr ? footer->GetEncodedText() : std::string{}); if (!ScriptObject::Command::Do(&ScriptInstance::DoCommandReturnLeagueTableID, encoded_title, encoded_header, encoded_footer)) return LEAGUE_TABLE_INVALID; @@ -63,12 +63,11 @@ if (company == ScriptCompany::COMPANY_INVALID) c = INVALID_COMPANY; EnforcePrecondition(LEAGUE_TABLE_ELEMENT_INVALID, text != nullptr); - const char *encoded_text_ptr = text->GetEncodedText(); - EnforcePreconditionEncodedText(LEAGUE_TABLE_ELEMENT_INVALID, encoded_text_ptr); - std::string encoded_text = encoded_text_ptr; // save into string so GetEncodedText can reuse the internal buffer + const std::string &encoded_text = text->GetEncodedText(); + EnforcePreconditionEncodedText(LEAGUE_TABLE_ELEMENT_INVALID, encoded_text); EnforcePrecondition(LEAGUE_TABLE_ELEMENT_INVALID, score != nullptr); - const char *encoded_score = score->GetEncodedText(); + const std::string &encoded_score = score->GetEncodedText(); EnforcePreconditionEncodedText(LEAGUE_TABLE_ELEMENT_INVALID, encoded_score); EnforcePrecondition(LEAGUE_TABLE_ELEMENT_INVALID, IsValidLink(Link((::LinkType)link_type, link_target))); @@ -91,7 +90,7 @@ if (company == ScriptCompany::COMPANY_INVALID) c = INVALID_COMPANY; EnforcePrecondition(false, text != nullptr); - const char *encoded_text = text->GetEncodedText(); + const std::string &encoded_text = text->GetEncodedText(); EnforcePreconditionEncodedText(false, encoded_text); EnforcePrecondition(false, IsValidLink(Link((::LinkType)link_type, link_target))); @@ -107,7 +106,7 @@ EnforcePrecondition(false, IsValidLeagueTableElement(element)); EnforcePrecondition(false, score != nullptr); - const char *encoded_score = score->GetEncodedText(); + const std::string &encoded_score = score->GetEncodedText(); EnforcePreconditionEncodedText(false, encoded_score); return ScriptObject::Command::Do(element, rating, encoded_score); diff --git a/src/script/api/script_news.cpp b/src/script/api/script_news.cpp index 119cbc735c..0939c8a3b4 100644 --- a/src/script/api/script_news.cpp +++ b/src/script/api/script_news.cpp @@ -25,7 +25,7 @@ CCountedPtr counter(text); EnforcePrecondition(false, text != nullptr); - const char *encoded = text->GetEncodedText(); + const std::string &encoded = text->GetEncodedText(); EnforcePreconditionEncodedText(false, encoded); EnforcePrecondition(false, type == NT_ECONOMY || type == NT_SUBSIDIES || type == NT_GENERAL); EnforcePrecondition(false, company == ScriptCompany::COMPANY_INVALID || ScriptCompany::ResolveCompanyID(company) != ScriptCompany::COMPANY_INVALID); diff --git a/src/script/api/script_sign.cpp b/src/script/api/script_sign.cpp index c95093a791..269d3e69dc 100644 --- a/src/script/api/script_sign.cpp +++ b/src/script/api/script_sign.cpp @@ -38,7 +38,7 @@ EnforcePrecondition(false, IsValidSign(sign_id)); EnforcePrecondition(false, name != nullptr); - const char *text = name->GetDecodedText(); + const std::string &text = name->GetDecodedText(); EnforcePreconditionEncodedText(false, text); EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_SIGN_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG); @@ -73,7 +73,7 @@ EnforcePrecondition(INVALID_SIGN, ::IsValidTile(location)); EnforcePrecondition(INVALID_SIGN, name != nullptr); - const char *text = name->GetDecodedText(); + const std::string &text = name->GetDecodedText(); EnforcePreconditionEncodedText(INVALID_SIGN, text); EnforcePreconditionCustomError(INVALID_SIGN, ::Utf8StringLength(text) < MAX_LENGTH_SIGN_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG); diff --git a/src/script/api/script_story_page.cpp b/src/script/api/script_story_page.cpp index f767472e02..970e983a70 100644 --- a/src/script/api/script_story_page.cpp +++ b/src/script/api/script_story_page.cpp @@ -49,7 +49,7 @@ static inline bool StoryPageElementTypeRequiresText(StoryPageElementType type) if (company == ScriptCompany::COMPANY_INVALID) c = INVALID_COMPANY; if (!ScriptObject::Command::Do(&ScriptInstance::DoCommandReturnStoryPageID, - (::CompanyID)c, title != nullptr ? std::string{ title->GetEncodedText() } : std::string{})) return STORY_PAGE_INVALID; + (::CompanyID)c, title != nullptr ? title->GetEncodedText() : std::string{})) return STORY_PAGE_INVALID; /* In case of test-mode, we return StoryPageID 0 */ return (ScriptStoryPage::StoryPageID)0; @@ -63,7 +63,12 @@ static inline bool StoryPageElementTypeRequiresText(StoryPageElementType type) EnforcePrecondition(STORY_PAGE_ELEMENT_INVALID, ScriptObject::GetCompany() == OWNER_DEITY); EnforcePrecondition(STORY_PAGE_ELEMENT_INVALID, IsValidStoryPage(story_page_id)); - EnforcePrecondition(STORY_PAGE_ELEMENT_INVALID, !StoryPageElementTypeRequiresText(btype) || (text != nullptr && !StrEmpty(text->GetEncodedText()))); + std::string encoded_text; + if (StoryPageElementTypeRequiresText(btype)) { + EnforcePrecondition(STORY_PAGE_ELEMENT_INVALID, text != nullptr); + encoded_text = text->GetEncodedText(); + EnforcePreconditionEncodedText(STORY_PAGE_ELEMENT_INVALID, encoded_text); + } EnforcePrecondition(STORY_PAGE_ELEMENT_INVALID, type != SPET_LOCATION || ::IsValidTile(reference)); EnforcePrecondition(STORY_PAGE_ELEMENT_INVALID, type != SPET_GOAL || ScriptGoal::IsValidGoal((ScriptGoal::GoalID)reference)); EnforcePrecondition(STORY_PAGE_ELEMENT_INVALID, type != SPET_GOAL || !(StoryPage::Get(story_page_id)->company == INVALID_COMPANY && Goal::Get(reference)->company != INVALID_COMPANY)); @@ -90,7 +95,7 @@ static inline bool StoryPageElementTypeRequiresText(StoryPageElementType type) reftile, (::StoryPageID)story_page_id, (::StoryPageElementType)type, refid, - StoryPageElementTypeRequiresText(btype) ? std::string{ text->GetEncodedText() } : std::string{})) return STORY_PAGE_ELEMENT_INVALID; + encoded_text)) return STORY_PAGE_ELEMENT_INVALID; /* In case of test-mode, we return StoryPageElementID 0 */ return (ScriptStoryPage::StoryPageElementID)0; @@ -107,7 +112,12 @@ static inline bool StoryPageElementTypeRequiresText(StoryPageElementType type) StoryPage *p = StoryPage::Get(pe->page); ::StoryPageElementType type = pe->type; - EnforcePrecondition(false, !StoryPageElementTypeRequiresText(type) || (text != nullptr && !StrEmpty(text->GetEncodedText()))); + std::string encoded_text; + if (StoryPageElementTypeRequiresText(type)) { + EnforcePrecondition(false, text != nullptr); + encoded_text = text->GetEncodedText(); + EnforcePreconditionEncodedText(false, encoded_text); + } EnforcePrecondition(false, type != ::SPET_LOCATION || ::IsValidTile(reference)); EnforcePrecondition(false, type != ::SPET_GOAL || ScriptGoal::IsValidGoal((ScriptGoal::GoalID)reference)); EnforcePrecondition(false, type != ::SPET_GOAL || !(p->company == INVALID_COMPANY && Goal::Get(reference)->company != INVALID_COMPANY)); @@ -130,10 +140,7 @@ static inline bool StoryPageElementTypeRequiresText(StoryPageElementType type) NOT_REACHED(); } - return ScriptObject::Command::Do(reftile, - story_page_element_id, - refid, - StoryPageElementTypeRequiresText(type) ? std::string{ text->GetEncodedText() } : std::string{}); + return ScriptObject::Command::Do(reftile, story_page_element_id, refid, encoded_text); } /* static */ uint32 ScriptStoryPage::GetPageSortValue(StoryPageID story_page_id) @@ -157,7 +164,7 @@ static inline bool StoryPageElementTypeRequiresText(StoryPageElementType type) EnforcePrecondition(false, IsValidStoryPage(story_page_id)); EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY); - return ScriptObject::Command::Do(story_page_id, title != nullptr ? std::string{ title->GetEncodedText() } : std::string{}); + return ScriptObject::Command::Do(story_page_id, title != nullptr ? title->GetEncodedText() : std::string{}); } /* static */ ScriptCompany::CompanyID ScriptStoryPage::GetCompany(StoryPageID story_page_id) diff --git a/src/script/api/script_text.cpp b/src/script/api/script_text.cpp index c9040b1d88..d01f3b93f1 100644 --- a/src/script/api/script_text.cpp +++ b/src/script/api/script_text.cpp @@ -18,15 +18,10 @@ #include "../../safeguards.h" -RawText::RawText(const char *text) : text(stredup(text)) +RawText::RawText(const char *text) : text(text) { } -RawText::~RawText() -{ - free(this->text); -} - ScriptText::ScriptText(HSQUIRRELVM vm) : ZeroedMemoryAllocator() @@ -177,7 +172,7 @@ SQInteger ScriptText::_set(HSQUIRRELVM vm) return this->_SetParam(k, vm); } -const char *ScriptText::GetEncodedText() +const std::string ScriptText::GetEncodedText() { static char buf[1024]; int param_count = 0; @@ -208,7 +203,7 @@ char *ScriptText::_GetEncodedText(char *p, char *lastofp, int ¶m_count) return p; } -const char *Text::GetDecodedText() +const std::string Text::GetDecodedText() { const std::string &encoded_text = this->GetEncodedText(); diff --git a/src/script/api/script_text.hpp b/src/script/api/script_text.hpp index 64cf2e7993..66ab2bd47d 100644 --- a/src/script/api/script_text.hpp +++ b/src/script/api/script_text.hpp @@ -21,17 +21,17 @@ class Text : public ScriptObject { public: /** * Convert a ScriptText to a normal string. - * @return A string (in a static buffer), or nullptr. + * @return A string. * @api -all */ - virtual const char *GetEncodedText() = 0; + virtual const std::string GetEncodedText() = 0; /** * Convert a #ScriptText into a decoded normal string. - * @return A string (in a static buffer), or nullptr. + * @return A string. * @api -all */ - const char *GetDecodedText(); + const std::string GetDecodedText(); }; /** @@ -41,11 +41,10 @@ public: class RawText : public Text { public: RawText(const char *text); - ~RawText(); - const char *GetEncodedText() override { return this->text; } + const std::string GetEncodedText() override { return this->text; } private: - const char *text; + const std::string text; }; /** @@ -125,7 +124,7 @@ public: /** * @api -all */ - virtual const char *GetEncodedText(); + virtual const std::string GetEncodedText(); private: StringID string; diff --git a/src/script/api/script_town.cpp b/src/script/api/script_town.cpp index 047cbe1a69..a3108f6839 100644 --- a/src/script/api/script_town.cpp +++ b/src/script/api/script_town.cpp @@ -44,29 +44,23 @@ { CCountedPtr counter(name); - const char *text = nullptr; + EnforcePrecondition(false, IsValidTown(town_id)); + std::string text; if (name != nullptr) { text = name->GetDecodedText(); - EnforcePreconditionEncodedText(false, text); EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_TOWN_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG); } - EnforcePrecondition(false, IsValidTown(town_id)); - return ScriptObject::Command::Do(town_id, text != nullptr ? std::string{ text } : std::string{}); + return ScriptObject::Command::Do(town_id, text); } /* static */ bool ScriptTown::SetText(TownID town_id, Text *text) { CCountedPtr counter(text); - const char *encoded_text = nullptr; - if (text != nullptr) { - encoded_text = text->GetEncodedText(); - EnforcePreconditionEncodedText(false, encoded_text); - } EnforcePrecondition(false, IsValidTown(town_id)); - return ScriptObject::Command::Do(town_id, encoded_text != nullptr ? std::string{ encoded_text } : std::string{}); + return ScriptObject::Command::Do(town_id, text != nullptr ? text->GetEncodedText() : std::string{}); } /* static */ int32 ScriptTown::GetPopulation(TownID town_id) @@ -294,10 +288,9 @@ layout = (RoadLayout) (byte)_settings_game.economy.town_layout; } - const char *text = nullptr; + std::string text; if (name != nullptr) { text = name->GetDecodedText(); - EnforcePreconditionEncodedText(false, text); EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_TOWN_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG); } uint32 townnameparts; @@ -306,7 +299,7 @@ return false; } - return ScriptObject::Command::Do(tile, (::TownSize)size, city, (::TownLayout)layout, false, townnameparts, text != nullptr ? std::string{ text } : std::string{}); + return ScriptObject::Command::Do(tile, (::TownSize)size, city, (::TownLayout)layout, false, townnameparts, text); } /* static */ ScriptTown::TownRating ScriptTown::GetRating(TownID town_id, ScriptCompany::CompanyID company_id) diff --git a/src/script/api/script_town.hpp b/src/script/api/script_town.hpp index d64d12ef6b..b9e2791a74 100644 --- a/src/script/api/script_town.hpp +++ b/src/script/api/script_town.hpp @@ -147,7 +147,7 @@ public: /** * Rename a town. * @param town_id The town to rename - * @param name The new name of the town. If null is passed, the town name will be reset to the default name. + * @param name The new name of the town. If null, or an empty string, is passed, the town name will be reset to the default name. * @pre IsValidTown(town_id). * @return True if the action succeeded. * @api -ai @@ -157,7 +157,7 @@ public: /** * Set the custom text of a town, shown in the GUI. * @param town_id The town to set the custom text of. - * @param text The text to set it to (can be either a raw string, or a ScriptText object). If null is passed, the text will be removed. + * @param text The text to set it to (can be either a raw string, or a ScriptText object). If null, or an empty string, is passed, the text will be removed. * @pre IsValidTown(town_id). * @return True if the action succeeded. * @api -ai @@ -401,7 +401,7 @@ public: * @param size The town size of the new town. * @param city True if the new town should be a city. * @param layout The town layout of the new town. - * @param name The name of the new town. Pass null to use a random town name. + * @param name The name of the new town. Pass null, or an empty string, to use a random town name. * @game @pre no company mode in scope || ScriptSettings.GetValue("economy.found_town") != 0. * @ai @pre ScriptSettings.GetValue("economy.found_town") != 0. * @game @pre no company mode in scope || size != TOWN_SIZE_LARGE. diff --git a/src/script/api/script_vehicle.cpp b/src/script/api/script_vehicle.cpp index 851d72f3b8..9450605c25 100644 --- a/src/script/api/script_vehicle.cpp +++ b/src/script/api/script_vehicle.cpp @@ -244,7 +244,7 @@ EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY); EnforcePrecondition(false, IsValidVehicle(vehicle_id)); EnforcePrecondition(false, name != nullptr); - const char *text = name->GetDecodedText(); + const std::string &text = name->GetDecodedText(); EnforcePreconditionEncodedText(false, text); EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_VEHICLE_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG); From 3df9321a658a122f50478bd4639607765168df03 Mon Sep 17 00:00:00 2001 From: SamuXarick <43006711+SamuXarick@users.noreply.github.com> Date: Sat, 21 Jan 2023 22:10:03 +0000 Subject: [PATCH 19/30] Fix: Some Script::IsValidVehicle checks need to be complemented with IsPrimaryVehicle Add: [Script] ScriptVehicle.IsPrimaryVehicle --- src/script/api/ai_changelog.hpp | 1 + src/script/api/game_changelog.hpp | 1 + src/script/api/script_group.cpp | 2 +- src/script/api/script_group.hpp | 2 +- src/script/api/script_order.cpp | 26 +++++++-------- src/script/api/script_order.hpp | 24 ++++++++------ src/script/api/script_stationlist.cpp | 2 +- src/script/api/script_vehicle.cpp | 43 ++++++++++++++---------- src/script/api/script_vehicle.hpp | 46 ++++++++++++++++---------- src/script/api/script_vehiclelist.cpp | 2 +- src/script/api/script_waypointlist.cpp | 2 +- 11 files changed, 87 insertions(+), 64 deletions(-) diff --git a/src/script/api/ai_changelog.hpp b/src/script/api/ai_changelog.hpp index 9672777ec6..21534121cf 100644 --- a/src/script/api/ai_changelog.hpp +++ b/src/script/api/ai_changelog.hpp @@ -19,6 +19,7 @@ * * API additions: * \li AITown::ROAD_LAYOUT_RANDOM + * \li AIVehicle::IsPrimaryVehicle * * API removals: * \li AIError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore. diff --git a/src/script/api/game_changelog.hpp b/src/script/api/game_changelog.hpp index 79174cf9af..bdf211cff7 100644 --- a/src/script/api/game_changelog.hpp +++ b/src/script/api/game_changelog.hpp @@ -19,6 +19,7 @@ * * API additions: * \li GSTown::ROAD_LAYOUT_RANDOM + * \li GSVehicle::IsPrimaryVehicle * * API removals: * \li GSError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore. diff --git a/src/script/api/script_group.cpp b/src/script/api/script_group.cpp index 9910dae3a4..b037a847dd 100644 --- a/src/script/api/script_group.cpp +++ b/src/script/api/script_group.cpp @@ -121,7 +121,7 @@ /* static */ bool ScriptGroup::MoveVehicle(GroupID group_id, VehicleID vehicle_id) { EnforcePrecondition(false, IsValidGroup(group_id) || group_id == GROUP_DEFAULT); - EnforcePrecondition(false, ScriptVehicle::IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); return ScriptObject::Command::Do(group_id, vehicle_id, false); } diff --git a/src/script/api/script_group.hpp b/src/script/api/script_group.hpp index 997bdf3c82..2887b88a5a 100644 --- a/src/script/api/script_group.hpp +++ b/src/script/api/script_group.hpp @@ -147,7 +147,7 @@ public: * @param group_id The group to move the vehicle to. * @param vehicle_id The vehicle to move to the group. * @pre IsValidGroup(group_id) || group_id == GROUP_DEFAULT. - * @pre ScriptVehicle::IsValidVehicle(vehicle_id). + * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @return True if and only if the vehicle was successfully moved to the group. * @note A vehicle can be in only one group at the same time. To remove it from * a group, move it to another or to GROUP_DEFAULT. Moving the vehicle to the diff --git a/src/script/api/script_order.cpp b/src/script/api/script_order.cpp index 44427bb19b..8480cb8c29 100644 --- a/src/script/api/script_order.cpp +++ b/src/script/api/script_order.cpp @@ -50,7 +50,7 @@ static OrderType GetOrderTypeByTile(TileIndex t) /* static */ bool ScriptOrder::IsValidVehicleOrder(VehicleID vehicle_id, OrderPosition order_position) { - return ScriptVehicle::IsValidVehicle(vehicle_id) && order_position >= 0 && (order_position < ::Vehicle::Get(vehicle_id)->GetNumManualOrders() || order_position == ORDER_CURRENT); + return ScriptVehicle::IsPrimaryVehicle(vehicle_id) && order_position >= 0 && (order_position < ::Vehicle::Get(vehicle_id)->GetNumManualOrders() || order_position == ORDER_CURRENT); } /** @@ -158,7 +158,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ bool ScriptOrder::IsCurrentOrderPartOfOrderList(VehicleID vehicle_id) { - if (!ScriptVehicle::IsValidVehicle(vehicle_id)) return false; + if (!ScriptVehicle::IsPrimaryVehicle(vehicle_id)) return false; if (GetOrderCount(vehicle_id) == 0) return false; const Order *order = &::Vehicle::Get(vehicle_id)->current_order; @@ -168,7 +168,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ ScriptOrder::OrderPosition ScriptOrder::ResolveOrderPosition(VehicleID vehicle_id, OrderPosition order_position) { - if (!ScriptVehicle::IsValidVehicle(vehicle_id)) return ORDER_INVALID; + if (!ScriptVehicle::IsPrimaryVehicle(vehicle_id)) return ORDER_INVALID; int num_manual_orders = ::Vehicle::Get(vehicle_id)->GetNumManualOrders(); if (num_manual_orders == 0) return ORDER_INVALID; @@ -236,7 +236,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ int32 ScriptOrder::GetOrderCount(VehicleID vehicle_id) { - return ScriptVehicle::IsValidVehicle(vehicle_id) ? ::Vehicle::Get(vehicle_id)->GetNumManualOrders() : -1; + return ScriptVehicle::IsPrimaryVehicle(vehicle_id) ? ::Vehicle::Get(vehicle_id)->GetNumManualOrders() : -1; } /* static */ TileIndex ScriptOrder::GetOrderDestination(VehicleID vehicle_id, OrderPosition order_position) @@ -443,7 +443,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ bool ScriptOrder::AppendOrder(VehicleID vehicle_id, TileIndex destination, ScriptOrderFlags order_flags) { - EnforcePrecondition(false, ScriptVehicle::IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); EnforcePrecondition(false, AreOrderFlagsValid(destination, order_flags)); return InsertOrder(vehicle_id, (ScriptOrder::OrderPosition)::Vehicle::Get(vehicle_id)->GetNumManualOrders(), destination, order_flags); @@ -451,7 +451,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* static */ bool ScriptOrder::AppendConditionalOrder(VehicleID vehicle_id, OrderPosition jump_to) { - EnforcePrecondition(false, ScriptVehicle::IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, jump_to)); return InsertConditionalOrder(vehicle_id, (ScriptOrder::OrderPosition)::Vehicle::Get(vehicle_id)->GetNumManualOrders(), jump_to); @@ -462,7 +462,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* IsValidVehicleOrder is not good enough because it does not allow appending. */ if (order_position == ORDER_CURRENT) order_position = ScriptOrder::ResolveOrderPosition(vehicle_id, order_position); - EnforcePrecondition(false, ScriptVehicle::IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); EnforcePrecondition(false, order_position >= 0 && order_position <= ::Vehicle::Get(vehicle_id)->GetNumManualOrders()); EnforcePrecondition(false, AreOrderFlagsValid(destination, order_flags)); @@ -516,7 +516,7 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr /* IsValidVehicleOrder is not good enough because it does not allow appending. */ if (order_position == ORDER_CURRENT) order_position = ScriptOrder::ResolveOrderPosition(vehicle_id, order_position); - EnforcePrecondition(false, ScriptVehicle::IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); EnforcePrecondition(false, order_position >= 0 && order_position <= ::Vehicle::Get(vehicle_id)->GetNumManualOrders()); EnforcePrecondition(false, IsValidVehicleOrder(vehicle_id, jump_to) && jump_to != ORDER_CURRENT); @@ -645,23 +645,23 @@ static void _DoCommandReturnSetOrderFlags(class ScriptInstance *instance) /* static */ bool ScriptOrder::CopyOrders(VehicleID vehicle_id, VehicleID main_vehicle_id) { - EnforcePrecondition(false, ScriptVehicle::IsValidVehicle(vehicle_id)); - EnforcePrecondition(false, ScriptVehicle::IsValidVehicle(main_vehicle_id)); + EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); + EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(main_vehicle_id)); return ScriptObject::Command::Do(0, CO_COPY, vehicle_id, main_vehicle_id); } /* static */ bool ScriptOrder::ShareOrders(VehicleID vehicle_id, VehicleID main_vehicle_id) { - EnforcePrecondition(false, ScriptVehicle::IsValidVehicle(vehicle_id)); - EnforcePrecondition(false, ScriptVehicle::IsValidVehicle(main_vehicle_id)); + EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); + EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(main_vehicle_id)); return ScriptObject::Command::Do(0, CO_SHARE, vehicle_id, main_vehicle_id); } /* static */ bool ScriptOrder::UnshareOrders(VehicleID vehicle_id) { - EnforcePrecondition(false, ScriptVehicle::IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); return ScriptObject::Command::Do(0, CO_UNSHARE, vehicle_id, 0); } diff --git a/src/script/api/script_order.hpp b/src/script/api/script_order.hpp index 0086e39ca2..22c4c93239 100644 --- a/src/script/api/script_order.hpp +++ b/src/script/api/script_order.hpp @@ -142,7 +142,7 @@ public: * Checks whether the given order id is valid for the given vehicle. * @param vehicle_id The vehicle to check the order index for. * @param order_position The order index to check. - * @pre ScriptVehicle::IsValidVehicle(vehicle_id). + * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @return True if and only if the order_position is valid for the given vehicle. */ static bool IsValidVehicleOrder(VehicleID vehicle_id, OrderPosition order_position); @@ -207,7 +207,7 @@ public: /** * Checks whether the current order is part of the orderlist. * @param vehicle_id The vehicle to check. - * @pre ScriptVehicle::IsValidVehicle(vehicle_id). + * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @return True if and only if the current order is part of the order list. * @note If the order is a non-'non-stop' order, and the vehicle is currently * (un)loading at a station that is not the final destination, this function @@ -222,7 +222,7 @@ public: * given index does not exist it will return ORDER_INVALID. * @param vehicle_id The vehicle to check the order index for. * @param order_position The order index to resolve. - * @pre ScriptVehicle::IsValidVehicle(vehicle_id). + * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @return The resolved order index. */ static OrderPosition ResolveOrderPosition(VehicleID vehicle_id, OrderPosition order_position); @@ -246,7 +246,7 @@ public: /** * Returns the number of orders for the given vehicle. * @param vehicle_id The vehicle to get the order count of. - * @pre ScriptVehicle::IsValidVehicle(vehicle_id). + * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @return The number of orders for the given vehicle or a negative * value when the vehicle does not exist. */ @@ -432,7 +432,7 @@ public: * @param vehicle_id The vehicle to append the order to. * @param destination The destination of the order. * @param order_flags The flags given to the order. - * @pre ScriptVehicle::IsValidVehicle(vehicle_id). + * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @pre AreOrderFlagsValid(destination, order_flags). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @exception ScriptOrder::ERR_ORDER_TOO_MANY @@ -446,7 +446,7 @@ public: * Appends a conditional order to the end of the vehicle's order list. * @param vehicle_id The vehicle to append the order to. * @param jump_to The OrderPosition to jump to if the condition is true. - * @pre ScriptVehicle::IsValidVehicle(vehicle_id). + * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @pre IsValidVehicleOrder(vehicle_id, jump_to). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @exception ScriptOrder::ERR_ORDER_TOO_MANY @@ -461,6 +461,7 @@ public: * @param order_position The order to place the new order before. * @param destination The destination of the order. * @param order_flags The flags given to the order. + * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id) * @pre IsValidVehicleOrder(vehicle_id, order_position). * @pre AreOrderFlagsValid(destination, order_flags). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY @@ -476,6 +477,7 @@ public: * @param vehicle_id The vehicle to add the order to. * @param order_position The order to place the new order before. * @param jump_to The OrderPosition to jump to if the condition is true. + * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @pre IsValidVehicleOrder(vehicle_id, order_position). * @pre IsValidVehicleOrder(vehicle_id, jump_to). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY @@ -550,8 +552,8 @@ public: * are going to be the orders of the changed vehicle. * @param vehicle_id The vehicle to copy the orders to. * @param main_vehicle_id The vehicle to copy the orders from. - * @pre ScriptVehicle::IsValidVehicle(vehicle_id). - * @pre ScriptVehicle::IsValidVehicle(main_vehicle_id). + * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). + * @pre ScriptVehicle::IsPrimaryVehicle(main_vehicle_id). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @exception ScriptOrder::ERR_ORDER_TOO_MANY * @exception ScriptOrder::ERR_ORDER_AIRCRAFT_NOT_ENOUGH_RANGE @@ -565,8 +567,8 @@ public: * vehicle are going to be the orders of the changed vehicle. * @param vehicle_id The vehicle to add to the shared order list. * @param main_vehicle_id The vehicle to share the orders with. - * @pre ScriptVehicle::IsValidVehicle(vehicle_id). - * @pre ScriptVehicle::IsValidVehicle(main_vehicle_id). + * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). + * @pre ScriptVehicle::IsPrimaryVehicle(main_vehicle_id). * @exception ScriptError::ERR_OWNED_BY_ANOTHER_COMPANY * @exception ScriptOrder::ERR_ORDER_AIRCRAFT_NOT_ENOUGH_RANGE * @return True if and only if the sharing succeeded. @@ -578,7 +580,7 @@ public: * Removes the given vehicle from a shared orders list. * After unsharing orders, the orders list of the vehicle is empty. * @param vehicle_id The vehicle to remove from the shared order list. - * @pre ScriptVehicle::IsValidVehicle(vehicle_id). + * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id). * @return True if and only if the unsharing succeeded. * @api -game */ diff --git a/src/script/api/script_stationlist.cpp b/src/script/api/script_stationlist.cpp index 9b9e67e7a6..590e80aa58 100644 --- a/src/script/api/script_stationlist.cpp +++ b/src/script/api/script_stationlist.cpp @@ -25,7 +25,7 @@ ScriptStationList::ScriptStationList(ScriptStation::StationType station_type) ScriptStationList_Vehicle::ScriptStationList_Vehicle(VehicleID vehicle_id) { - if (!ScriptVehicle::IsValidVehicle(vehicle_id)) return; + if (!ScriptVehicle::IsPrimaryVehicle(vehicle_id)) return; Vehicle *v = ::Vehicle::Get(vehicle_id); diff --git a/src/script/api/script_vehicle.cpp b/src/script/api/script_vehicle.cpp index 9450605c25..515509a2e2 100644 --- a/src/script/api/script_vehicle.cpp +++ b/src/script/api/script_vehicle.cpp @@ -34,6 +34,13 @@ return v != nullptr && (v->owner == ScriptObject::GetCompany() || ScriptObject::GetCompany() == OWNER_DEITY) && (v->IsPrimaryVehicle() || (v->type == VEH_TRAIN && ::Train::From(v)->IsFreeWagon())); } +/* static */ bool ScriptVehicle::IsPrimaryVehicle(VehicleID vehicle_id) +{ + if (!IsValidVehicle(vehicle_id)) return false; + + return ::Vehicle::Get(vehicle_id)->IsPrimaryVehicle(); +} + /* static */ ScriptCompany::CompanyID ScriptVehicle::GetOwner(VehicleID vehicle_id) { if (!IsValidVehicle(vehicle_id)) return ScriptCompany::COMPANY_INVALID; @@ -102,7 +109,7 @@ /* static */ VehicleID ScriptVehicle::CloneVehicle(TileIndex depot, VehicleID vehicle_id, bool share_orders) { EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY); - EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, IsPrimaryVehicle(vehicle_id)); if (!ScriptObject::Command::Do(&ScriptInstance::DoCommandReturnVehicleID, depot, vehicle_id, share_orders)) return VEHICLE_INVALID; @@ -191,7 +198,7 @@ /* static */ bool ScriptVehicle::SendVehicleToDepot(VehicleID vehicle_id) { EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY); - EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, IsPrimaryVehicle(vehicle_id)); return ScriptObject::Command::Do(vehicle_id, DepotCommand::None, {}); } @@ -199,7 +206,7 @@ /* static */ bool ScriptVehicle::SendVehicleToDepotForServicing(VehicleID vehicle_id) { EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY); - EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, IsPrimaryVehicle(vehicle_id)); return ScriptObject::Command::Do(vehicle_id, DepotCommand::Service, {}); } @@ -219,7 +226,7 @@ /* static */ bool ScriptVehicle::StartStopVehicle(VehicleID vehicle_id) { EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY); - EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, IsPrimaryVehicle(vehicle_id)); return ScriptObject::Command::Do(vehicle_id, false); } @@ -227,7 +234,7 @@ /* static */ bool ScriptVehicle::ReverseVehicle(VehicleID vehicle_id) { EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY); - EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, IsPrimaryVehicle(vehicle_id)); EnforcePrecondition(false, ::Vehicle::Get(vehicle_id)->type == VEH_ROAD || ::Vehicle::Get(vehicle_id)->type == VEH_TRAIN); switch (::Vehicle::Get(vehicle_id)->type) { @@ -242,7 +249,7 @@ CCountedPtr counter(name); EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY); - EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, IsPrimaryVehicle(vehicle_id)); EnforcePrecondition(false, name != nullptr); const std::string &text = name->GetDecodedText(); EnforcePreconditionEncodedText(false, text); @@ -286,14 +293,14 @@ /* static */ int32 ScriptVehicle::GetUnitNumber(VehicleID vehicle_id) { - if (!IsValidVehicle(vehicle_id)) return -1; + if (!IsPrimaryVehicle(vehicle_id)) return -1; return ::Vehicle::Get(vehicle_id)->unitnumber; } /* static */ char *ScriptVehicle::GetName(VehicleID vehicle_id) { - if (!IsValidVehicle(vehicle_id)) return nullptr; + if (!IsPrimaryVehicle(vehicle_id)) return nullptr; ::SetDParam(0, vehicle_id); return GetString(STR_VEHICLE_NAME); @@ -320,21 +327,21 @@ /* static */ int32 ScriptVehicle::GetMaxAge(VehicleID vehicle_id) { - if (!IsValidVehicle(vehicle_id)) return -1; + if (!IsPrimaryVehicle(vehicle_id)) return -1; return ::Vehicle::Get(vehicle_id)->max_age; } /* static */ int32 ScriptVehicle::GetAgeLeft(VehicleID vehicle_id) { - if (!IsValidVehicle(vehicle_id)) return -1; + if (!IsPrimaryVehicle(vehicle_id)) return -1; return ::Vehicle::Get(vehicle_id)->max_age - ::Vehicle::Get(vehicle_id)->age; } /* static */ int32 ScriptVehicle::GetCurrentSpeed(VehicleID vehicle_id) { - if (!IsValidVehicle(vehicle_id)) return -1; + if (!IsPrimaryVehicle(vehicle_id)) return -1; const ::Vehicle *v = ::Vehicle::Get(vehicle_id); return (v->vehstatus & (::VS_STOPPED | ::VS_CRASHED)) == 0 ? v->GetDisplaySpeed() : 0; // km-ish/h @@ -357,21 +364,21 @@ /* static */ Money ScriptVehicle::GetRunningCost(VehicleID vehicle_id) { - if (!IsValidVehicle(vehicle_id)) return -1; + if (!IsPrimaryVehicle(vehicle_id)) return -1; return ::Vehicle::Get(vehicle_id)->GetRunningCost() >> 8; } /* static */ Money ScriptVehicle::GetProfitThisYear(VehicleID vehicle_id) { - if (!IsValidVehicle(vehicle_id)) return -1; + if (!IsPrimaryVehicle(vehicle_id)) return -1; return ::Vehicle::Get(vehicle_id)->GetDisplayProfitThisYear(); } /* static */ Money ScriptVehicle::GetProfitLastYear(VehicleID vehicle_id) { - if (!IsValidVehicle(vehicle_id)) return -1; + if (!IsPrimaryVehicle(vehicle_id)) return -1; return ::Vehicle::Get(vehicle_id)->GetDisplayProfitLastYear(); } @@ -432,7 +439,7 @@ /* static */ GroupID ScriptVehicle::GetGroupID(VehicleID vehicle_id) { - if (!IsValidVehicle(vehicle_id)) return ScriptGroup::GROUP_INVALID; + if (!IsPrimaryVehicle(vehicle_id)) return ScriptGroup::GROUP_INVALID; return ::Vehicle::Get(vehicle_id)->group_id; } @@ -452,7 +459,7 @@ /* static */ bool ScriptVehicle::HasSharedOrders(VehicleID vehicle_id) { - if (!IsValidVehicle(vehicle_id)) return false; + if (!IsPrimaryVehicle(vehicle_id)) return false; Vehicle *v = ::Vehicle::Get(vehicle_id); return v->orders != nullptr && v->orders->GetNumVehicles() > 1; @@ -460,7 +467,7 @@ /* static */ int ScriptVehicle::GetReliability(VehicleID vehicle_id) { - if (!IsValidVehicle(vehicle_id)) return -1; + if (!IsPrimaryVehicle(vehicle_id)) return -1; const Vehicle *v = ::Vehicle::Get(vehicle_id); return ::ToPercent16(v->reliability); @@ -468,7 +475,7 @@ /* static */ uint ScriptVehicle::GetMaximumOrderDistance(VehicleID vehicle_id) { - if (!IsValidVehicle(vehicle_id)) return 0; + if (!IsPrimaryVehicle(vehicle_id)) return 0; const ::Vehicle *v = ::Vehicle::Get(vehicle_id); switch (v->type) { diff --git a/src/script/api/script_vehicle.hpp b/src/script/api/script_vehicle.hpp index 02616e8b4d..6041c091d7 100644 --- a/src/script/api/script_vehicle.hpp +++ b/src/script/api/script_vehicle.hpp @@ -97,9 +97,20 @@ public: * Checks whether the given vehicle is valid and owned by you. * @param vehicle_id The vehicle to check. * @return True if and only if the vehicle is valid. + * @note Also returns true when the leading part of the vehicle is a wagon. + * Use IsPrimaryVehicle() to check for a valid vehicle with a leading engine. */ static bool IsValidVehicle(VehicleID vehicle_id); + /** + * Checks whether this is a primary vehicle. + * @param vehicle_id The vehicle to check. + * @pre IsValidVehicle(vehicle_id). + * @return True if the vehicle is a primary vehicle. + * @note Returns false when the leading part of the vehicle is a wagon. + */ + static bool IsPrimaryVehicle(VehicleID vehicle_id); + /** * Get the number of wagons a vehicle has. * @param vehicle_id The vehicle to get the number of wagons from. @@ -112,7 +123,7 @@ public: * Set the name of a vehicle. * @param vehicle_id The vehicle to set the name for. * @param name The name for the vehicle (can be either a raw string, or a ScriptText object). - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @pre name != null && len(name) != 0. * @game @pre Valid ScriptCompanyMode active in scope. * @exception ScriptError::ERR_NAME_IS_NOT_UNIQUE @@ -123,7 +134,7 @@ public: /** * Get the name of a vehicle. * @param vehicle_id The vehicle to get the name of. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @return The name the vehicle has. */ static char *GetName(VehicleID vehicle_id); @@ -166,7 +177,7 @@ public: /** * Get the unitnumber of a vehicle. * @param vehicle_id The vehicle to get the unitnumber of. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @return The unitnumber the vehicle has. */ static int32 GetUnitNumber(VehicleID vehicle_id); @@ -194,7 +205,7 @@ public: /** * Get the maximum age of a vehicle. * @param vehicle_id The vehicle to get the age of. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @return The maximum age the vehicle has. * @note The age is in days. */ @@ -203,7 +214,7 @@ public: /** * Get the age a vehicle has left (maximum - current). * @param vehicle_id The vehicle to get the age of. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @return The age the vehicle has left. * @note The age is in days. */ @@ -212,7 +223,7 @@ public: /** * Get the current speed of a vehicle. * @param vehicle_id The vehicle to get the speed of. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @return The current speed of the vehicle. * @note The speed is in OpenTTD's internal speed unit. * This is mph / 1.6, which is roughly km/h. @@ -231,7 +242,7 @@ public: /** * Get the running cost of this vehicle. * @param vehicle_id The vehicle to get the running cost of. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @return The running cost of the vehicle per year. * @note Cost is per year; divide by 365 to get per day. * @note This is not equal to ScriptEngine::GetRunningCost for Trains, because @@ -242,7 +253,7 @@ public: /** * Get the current profit of a vehicle. * @param vehicle_id The vehicle to get the profit of. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @return The current profit the vehicle has. */ static Money GetProfitThisYear(VehicleID vehicle_id); @@ -250,7 +261,7 @@ public: /** * Get the profit of last year of a vehicle. * @param vehicle_id The vehicle to get the profit of. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @return The profit the vehicle had last year. */ static Money GetProfitLastYear(VehicleID vehicle_id); @@ -363,7 +374,7 @@ public: * @param vehicle_id The vehicle to use as example for the new vehicle. * @param share_orders Should the orders be copied or shared? * @pre The tile 'depot' has a depot on it, allowing 'vehicle_id'-type vehicles. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @game @pre Valid ScriptCompanyMode active in scope. * @exception ScriptVehicle::ERR_VEHICLE_TOO_MANY * @exception ScriptVehicle::ERR_VEHICLE_BUILD_DISABLED @@ -481,7 +492,7 @@ public: * Sends the given vehicle to a depot. If the vehicle has already been * sent to a depot it continues with its normal orders instead. * @param vehicle_id The vehicle to send to a depot. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @game @pre Valid ScriptCompanyMode active in scope. * @exception ScriptVehicle::ERR_VEHICLE_CANNOT_SEND_TO_DEPOT * @return True if the current order was changed. @@ -492,7 +503,7 @@ public: * Sends the given vehicle to a depot for servicing. If the vehicle has * already been sent to a depot it continues with its normal orders instead. * @param vehicle_id The vehicle to send to a depot for servicing. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @game @pre Valid ScriptCompanyMode active in scope. * @exception ScriptVehicle::ERR_VEHICLE_CANNOT_SEND_TO_DEPOT * @return True if the current order was changed. @@ -502,7 +513,7 @@ public: /** * Starts or stops the given vehicle depending on the current state. * @param vehicle_id The vehicle to start/stop. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @game @pre Valid ScriptCompanyMode active in scope. * @exception ScriptVehicle::ERR_VEHICLE_CANNOT_START_STOP * @exception (For aircraft only): ScriptVehicle::ERR_VEHICLE_IN_FLIGHT @@ -514,7 +525,7 @@ public: /** * Turn the given vehicle so it'll drive the other way. * @param vehicle_id The vehicle to turn. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @pre GetVehicleType(vehicle_id) == VT_ROAD || GetVehicleType(vehicle_id) == VT_RAIL. * @game @pre Valid ScriptCompanyMode active in scope. * @return True if and only if the vehicle has started to turn. @@ -555,6 +566,7 @@ public: /** * Get the group of a given vehicle. * @param vehicle_id The vehicle to get the group from. + * @pre IsPrimaryVehicle(vehicle_id). * @return The group of the given vehicle. */ static GroupID GetGroupID(VehicleID vehicle_id); @@ -571,7 +583,7 @@ public: /** * Check if the vehicle has shared orders. * @param vehicle_id The vehicle to check. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @return True if the vehicle has shared orders. */ static bool HasSharedOrders(VehicleID vehicle_id); @@ -579,7 +591,7 @@ public: /** * Get the current reliability of a vehicle. * @param vehicle_id The vehicle to check. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @return The current reliability (0-100%). */ static int GetReliability(VehicleID vehicle_id); @@ -590,7 +602,7 @@ public: * map distances, you may use the result of this function to compare it * with the result of ScriptOrder::GetOrderDistance. * @param vehicle_id The vehicle to get the distance for. - * @pre IsValidVehicle(vehicle_id). + * @pre IsPrimaryVehicle(vehicle_id). * @return The maximum distance between two orders for this vehicle * or 0 if the distance is unlimited. * @note The unit of the order distances is unspecified and should diff --git a/src/script/api/script_vehiclelist.cpp b/src/script/api/script_vehiclelist.cpp index 3a4d2d135b..bd4e6f0c44 100644 --- a/src/script/api/script_vehiclelist.cpp +++ b/src/script/api/script_vehiclelist.cpp @@ -91,7 +91,7 @@ ScriptVehicleList_Depot::ScriptVehicleList_Depot(TileIndex tile) ScriptVehicleList_SharedOrders::ScriptVehicleList_SharedOrders(VehicleID vehicle_id) { - if (!ScriptVehicle::IsValidVehicle(vehicle_id)) return; + if (!ScriptVehicle::IsPrimaryVehicle(vehicle_id)) return; for (const Vehicle *v = Vehicle::Get(vehicle_id)->FirstShared(); v != nullptr; v = v->NextShared()) { this->AddItem(v->index); diff --git a/src/script/api/script_waypointlist.cpp b/src/script/api/script_waypointlist.cpp index 1c0a804d1c..4a4b40b200 100644 --- a/src/script/api/script_waypointlist.cpp +++ b/src/script/api/script_waypointlist.cpp @@ -25,7 +25,7 @@ ScriptWaypointList::ScriptWaypointList(ScriptWaypoint::WaypointType waypoint_typ ScriptWaypointList_Vehicle::ScriptWaypointList_Vehicle(VehicleID vehicle_id) { - if (!ScriptVehicle::IsValidVehicle(vehicle_id)) return; + if (!ScriptVehicle::IsPrimaryVehicle(vehicle_id)) return; const Vehicle *v = ::Vehicle::Get(vehicle_id); From 2376112c77f737727ba1a7e2791cb424f2136175 Mon Sep 17 00:00:00 2001 From: PeterN Date: Sat, 18 Feb 2023 10:50:20 +0000 Subject: [PATCH 20/30] Fix #10477: Not enough space for text due to rounding down (OSX) (#10489) --- src/os/macosx/string_osx.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/os/macosx/string_osx.cpp b/src/os/macosx/string_osx.cpp index f263db3753..89ff8c6bb3 100644 --- a/src/os/macosx/string_osx.cpp +++ b/src/os/macosx/string_osx.cpp @@ -15,6 +15,7 @@ #include "../../fontcache.h" #include "../../zoom_func.h" #include "macos.h" +#include #include @@ -253,7 +254,7 @@ CoreTextParagraphLayout::CoreTextVisualRun::CoreTextVisualRun(CTRunRef run, Font this->positions[i * 2 + 1] = pts[i].y; } } - this->total_advance = (int)CTRunGetTypographicBounds(run, CFRangeMake(0, 0), nullptr, nullptr, nullptr); + this->total_advance = (int)std::ceil(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), nullptr, nullptr, nullptr)); this->positions[this->glyphs.size() * 2] = this->positions[0] + this->total_advance; } From 4a8b8807e620f87b70070521f21a31572e28ac36 Mon Sep 17 00:00:00 2001 From: translators Date: Sat, 18 Feb 2023 18:44:22 +0000 Subject: [PATCH 21/30] Update: Translations from eints vietnamese: 6 changes by KhoiCanDev korean: 1 change by telk5093 --- src/lang/korean.txt | 2 +- src/lang/vietnamese.txt | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lang/korean.txt b/src/lang/korean.txt index 66d968430a..3c54ce74f2 100644 --- a/src/lang/korean.txt +++ b/src/lang/korean.txt @@ -4243,7 +4243,7 @@ STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED :{BLACK}무게: STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE :{BLACK}무게: {LTBLUE}{WEIGHT_SHORT} {BLACK}힘: {LTBLUE}{POWER}{BLACK} 최고 속력: {LTBLUE}{VELOCITY} {BLACK}최고 견인력: {LTBLUE}{FORCE} STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR :{BLACK}올해 이익: {LTBLUE}{CURRENCY_LONG} (작년: {CURRENCY_LONG}) -STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR_MIN_PERFORMANCE :{BLACK}올해 이익: {LTBLUE}{CURRENCY_LONG} (작년: {CURRENCY_LONG}) {BLACK}최소 성취도: {LTBLUE}{POWER_TO_WEIGHT} +STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR_MIN_PERFORMANCE :{BLACK}올해 이익: {LTBLUE}{CURRENCY_LONG} (작년: {CURRENCY_LONG}) {BLACK}최소 성능: {LTBLUE}{POWER_TO_WEIGHT} STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS :{BLACK}신뢰도: {LTBLUE}{COMMA}% {BLACK}최근 점검 이후 고장 횟수: {LTBLUE}{COMMA} STR_VEHICLE_INFO_BUILT_VALUE :{LTBLUE}{ENGINE} {BLACK}생산: {LTBLUE}{NUM}{BLACK} 가격: {LTBLUE}{CURRENCY_LONG} diff --git a/src/lang/vietnamese.txt b/src/lang/vietnamese.txt index 07e00c0127..c74363d550 100644 --- a/src/lang/vietnamese.txt +++ b/src/lang/vietnamese.txt @@ -1206,7 +1206,9 @@ STR_CONFIG_SETTING_HORIZONTAL_POS_RIGHT :Phải STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN :Khoảng vay khởi nghiệp tối đa: {STRING} STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_HELPTEXT :Hạn mức tối đa một công ty có thể vay (không tính lạm phát) +STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_VALUE :{CURRENCY_LONG} ###setting-zero-is-special +STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_DISABLED :Không có khoản vay {RED}Cần Game Script để cung cấp khoảng vốn ban đầu STR_CONFIG_SETTING_INTEREST_RATE :Lãi suất vay: {STRING} STR_CONFIG_SETTING_INTEREST_RATE_HELPTEXT :Lãi xuất vay; ảnh hưởng tới cả lạm phát nếu bật tùy chọn đó @@ -3350,6 +3352,8 @@ STR_NEWGRF_ERROR_MSG_INFO :{SILVER}{STRING STR_NEWGRF_ERROR_MSG_WARNING :{RED}Cảnh báo: {SILVER}{STRING} 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}NewGRF "{STRING}" đã xảy ra một lỗi nghiêm trọng:{}{STRING} +STR_NEWGRF_ERROR_POPUP :{WHITE}NewGRF "{STRING}" đã xảy ra lỗi:{}{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 {2:STRING} của TTD. STR_NEWGRF_ERROR_UNSET_SWITCH :{1:STRING} được thiết kế để dùng với {2:STRING} @@ -4525,7 +4529,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Xóa Gi STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Xóa tốc độ đối đa đối với lộ trình được chọn. Ctrl+Click xoá tốc độ cho mọi lộ trình STR_TIMETABLE_RESET_LATENESS :{BLACK}Lập lại bộ đếm trễ -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Thiết lập lại bộ đếm trễ giờ, để việc di chuyển phương tiện được tính lại đúng đắn +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Thiết lập lại bộ đếm trễ giờ, để phương tiện được tính là đúng giờ. Ctrl+Click sẽ thiết lập lại nguyên nhóm, phương tiện mới nhất sẽ được tính là đúng giờ, và các phương tiện khác được tính là sớm STR_TIMETABLE_AUTOFILL :{BLACK}Tự điền STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Tự điền bảng lịch trình qua việc đo thời gian hành trình kế tiếp. Ctrl+Click để cố giữ thời gian chờ không đổi @@ -4622,6 +4626,7 @@ STR_SCREENSHOT_MINIMAP_SCREENSHOT :{BLACK}Chụp m # Script Parameters STR_AI_SETTINGS_CAPTION :{WHITE}{STRING} Tham số STR_AI_SETTINGS_CAPTION_AI :AI +STR_AI_SETTINGS_CAPTION_GAMESCRIPT :Game Script STR_AI_SETTINGS_CLOSE :{BLACK}Đóng STR_AI_SETTINGS_RESET :{BLACK}Thiết Lập Lại STR_AI_SETTINGS_SETTING :{STRING}: {ORANGE}{STRING} From 9ade3345f75f54d8d0da47a00665104e87bb7e06 Mon Sep 17 00:00:00 2001 From: translators Date: Sun, 19 Feb 2023 18:43:37 +0000 Subject: [PATCH 22/30] Update: Translations from eints english (us): 3 changes by nikolas galician: 1 change by pvillaverde --- src/lang/english_US.txt | 6 +++--- src/lang/galician.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lang/english_US.txt b/src/lang/english_US.txt index fcfbaa63d8..89aa277508 100644 --- a/src/lang/english_US.txt +++ b/src/lang/english_US.txt @@ -37,7 +37,7 @@ STR_CARGO_PLURAL_IRON_ORE :Iron Ore STR_CARGO_PLURAL_STEEL :Steel STR_CARGO_PLURAL_VALUABLES :Valuables STR_CARGO_PLURAL_COPPER_ORE :Copper Ore -STR_CARGO_PLURAL_MAIZE :Maize +STR_CARGO_PLURAL_MAIZE :Corn STR_CARGO_PLURAL_FRUIT :Fruit STR_CARGO_PLURAL_DIAMONDS :Diamonds STR_CARGO_PLURAL_FOOD :Food @@ -71,7 +71,7 @@ STR_CARGO_SINGULAR_IRON_ORE :Iron Ore STR_CARGO_SINGULAR_STEEL :Steel STR_CARGO_SINGULAR_VALUABLES :Valuables STR_CARGO_SINGULAR_COPPER_ORE :Copper Ore -STR_CARGO_SINGULAR_MAIZE :Maize +STR_CARGO_SINGULAR_MAIZE :Corn STR_CARGO_SINGULAR_FRUIT :Fruit STR_CARGO_SINGULAR_DIAMOND :Diamond STR_CARGO_SINGULAR_FOOD :Food @@ -105,7 +105,7 @@ STR_QUANTITY_IRON_ORE :{WEIGHT_LONG} o STR_QUANTITY_STEEL :{WEIGHT_LONG} of steel STR_QUANTITY_VALUABLES :{COMMA}{NBSP}bag{P "" s} of valuables STR_QUANTITY_COPPER_ORE :{WEIGHT_LONG} of copper ore -STR_QUANTITY_MAIZE :{WEIGHT_LONG} of maize +STR_QUANTITY_MAIZE :{WEIGHT_LONG} of corn STR_QUANTITY_FRUIT :{WEIGHT_LONG} of fruit STR_QUANTITY_DIAMONDS :{COMMA}{NBSP}bag{P "" s} of diamonds STR_QUANTITY_FOOD :{WEIGHT_LONG} of food diff --git a/src/lang/galician.txt b/src/lang/galician.txt index 82a9a16510..b57a68816f 100644 --- a/src/lang/galician.txt +++ b/src/lang/galician.txt @@ -2699,7 +2699,7 @@ STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_INCREASE_TOOLTIP :{BLACK}Aumentar # Bridge selection window STR_SELECT_RAIL_BRIDGE_CAPTION :{WHITE}Selecciona ponte ferroviaria STR_SELECT_ROAD_BRIDGE_CAPTION :{WHITE}Selecciona ponte de estrada -STR_SELECT_BRIDGE_SELECTION_TOOLTIP :{BLACK}Selección de pontes - picha na ponte seleccionada para construíla +STR_SELECT_BRIDGE_SELECTION_TOOLTIP :{BLACK}Selección de pontes - pincha na ponte seleccionada para construíla STR_BRIDGE_NAME_SUSPENSION_STEEL :Colgante, aceiro STR_BRIDGE_NAME_GIRDER_STEEL :Vigas, aceiro STR_BRIDGE_NAME_CANTILEVER_STEEL :Voladizo, aceiro From 8778949b6a917fee0dc70dab989489b16c8ab68d Mon Sep 17 00:00:00 2001 From: translators Date: Mon, 20 Feb 2023 18:47:32 +0000 Subject: [PATCH 23/30] Update: Translations from eints english (us): 1 change by nikolas galician: 18 changes by pvillaverde --- src/lang/english_US.txt | 2 +- src/lang/galician.txt | 26 ++++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/lang/english_US.txt b/src/lang/english_US.txt index 89aa277508..c28c3c5494 100644 --- a/src/lang/english_US.txt +++ b/src/lang/english_US.txt @@ -4362,7 +4362,7 @@ STR_ORDER_DROP_REFIT_AUTO_ANY :Available cargo STR_ORDER_SERVICE :{BLACK}Maintenance STR_ORDER_DROP_GO_ALWAYS_DEPOT :Always go -STR_ORDER_DROP_SERVICE_DEPOT :Maintain if needed +STR_ORDER_DROP_SERVICE_DEPOT :Repair if needed STR_ORDER_DROP_HALT_DEPOT :Stop STR_ORDER_SERVICE_TOOLTIP :{BLACK}Skip this order unless maintenance is needed diff --git a/src/lang/galician.txt b/src/lang/galician.txt index b57a68816f..a7a340030c 100644 --- a/src/lang/galician.txt +++ b/src/lang/galician.txt @@ -387,7 +387,7 @@ STR_SCENEDIT_TOOLBAR_TOWN_GENERATION :{BLACK}Xeració STR_SCENEDIT_TOOLBAR_INDUSTRY_GENERATION :{BLACK}Xeración de industrias STR_SCENEDIT_TOOLBAR_ROAD_CONSTRUCTION :{BLACK}Construción de estradas STR_SCENEDIT_TOOLBAR_TRAM_CONSTRUCTION :{BLACK}Construción de tranvía -STR_SCENEDIT_TOOLBAR_PLANT_TREES :{BLACK}Plantar árbores. Shift alterna entre construir/amosar custo estimado +STR_SCENEDIT_TOOLBAR_PLANT_TREES :{BLACK}Plantar árbores. Ctrl selecciona a area diagonalmente. Shift alterna entre construir/amosar custo estimado STR_SCENEDIT_TOOLBAR_PLACE_SIGN :{BLACK}Colocar rótulo STR_SCENEDIT_TOOLBAR_PLACE_OBJECT :{BLACK}Colocar obxecto. Ctrl selecciona a area diagonalmente. Shift alterna entre construir/amosar custo estimado @@ -1207,7 +1207,9 @@ STR_CONFIG_SETTING_HORIZONTAL_POS_RIGHT :Dereita STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN :Crédito máximo inicial: {STRING} STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_HELPTEXT :Cantidade máxima de cartos que unha compañía pode pedir (sen ter en conta a inflación) +STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_VALUE :{CURRENCY_LONG} ###setting-zero-is-special +STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN_DISABLED :Non hai empréstitos. {RED}Require dun script do xogo para ter fondos iniciais STR_CONFIG_SETTING_INTEREST_RATE :Taxa de interés: {STRING} STR_CONFIG_SETTING_INTEREST_RATE_HELPTEXT :A taxa de interés do préstamo; controla tamén a inflación, se está activada @@ -1927,13 +1929,13 @@ STR_CONFIG_SETTING_LINKGRAPH_TIME :Leva {STRING}{N STR_CONFIG_SETTING_LINKGRAPH_TIME_HELPTEXT :Tempo empregado para cada recálculo dunha compoñente do gráfico de ligazóns. Cando comeza un recálculo, creáse un fío que funciona por este número de días. Canto máis pequeno sexa este, é máis probable que o fío non remate cando se supón. Nese intre o xogo para para compensar este retardo. Canto máis longo sexa, máis tempo leva actualizar a distribución cando cambian as rutas. STR_CONFIG_SETTING_DISTRIBUTION_PAX :Modo de distribución para pasaxeiros: {STRING} -STR_CONFIG_SETTING_DISTRIBUTION_PAX_HELPTEXT :"simétrico" singinfica que máis ou menos o mesmo número de pasaxeiros irán dende a estación A cada a estación B e tamén da B cara a A. "asimétrico" significa que calquera número de pasaxeiros pode ir en calquera dirección. "manual" significa que non haberá distribución automática para os pasaxeiros. +STR_CONFIG_SETTING_DISTRIBUTION_PAX_HELPTEXT :"Simétrico" significa que máis ou menos o mesmo número de pasaxeiros irán dende a estación A cada a estación B e tamén da B cara a A. "Asimétrico" significa que calquera número de pasaxeiros pode ir en calquera dirección. "manual" significa que non haberá distribución automática para os pasaxeiros. STR_CONFIG_SETTING_DISTRIBUTION_MAIL :Modo de distribución para correo: {STRING} -STR_CONFIG_SETTING_DISTRIBUTION_MAIL_HELPTEXT :"simétrico" significa que máis ou menos a mesma cantidade de correo vai ser enviada da estación A cara a estación B como da estación B cara a A. "asimétrico" signigica que calquera cantidade de correo pode ser enviado en calquera dirección. "manual" significa que non hai distribución automática para o correo. +STR_CONFIG_SETTING_DISTRIBUTION_MAIL_HELPTEXT :"Simétrico" significa que máis ou menos a mesma cantidade de correo vai ser enviada da estación A cara a estación B como da estación B cara a A. "Asimétrico" signigica que calquera cantidade de correo pode ser enviado en calquera dirección. "Manual" significa que non hai distribución automática para o correo. STR_CONFIG_SETTING_DISTRIBUTION_ARMOURED :Modo de disitribución para o tipo de mercadoría BLINDADO: {STRING} -STR_CONFIG_SETTING_DISTRIBUTION_ARMOURED_HELPTEXT :A calse de mercadoría BLINDADA contén obxectos de valor nos clima morno, diamantes no subtropical ou ouro no clima subártico. Os NewGRFs poden cambiar isto. "simétrico" significa que máis ou menos a mesma cantidade de esta mercadoría será enviadas dende a estación A cara a estación B así como da estación B para a A. "asimétrico" significa que calquera cantidade de esta mercadoría pode ser enviada en calquera dirección. "manual" significa que non haberá distribución automática para esta mercadoría. Recoméndase elixir asimétrico ou manual cando se xoguen mapas subárticos, xa que os bancos non van enviar ouro de volta ás minas. Para climas mornos e subtropicais podes escoller tamén simétrico xa que os bancos retornan valores aos bancos de orixe dalgunha carga de valores. +STR_CONFIG_SETTING_DISTRIBUTION_ARMOURED_HELPTEXT :A clase de mercadoría BLINDADA contén obxectos de valor nos clima morno, diamantes no subtropical ou ouro no clima subártico. Os NewGRFs poden cambiar isto. "Simétrico" significa que máis ou menos a mesma cantidade de esta mercadoría será enviadas dende a estación A cara a estación B así como da estación B para a A. "Asimétrico" significa que calquera cantidade de esta mercadoría pode ser enviada en calquera dirección. "manual" significa que non haberá distribución automática para esta mercadoría. Recoméndase elixir asimétrico ou manual cando se xoguen mapas subárticos ou subtropicais, xa que os bancos só reciben carga nestos climas. Para os climas mornos podes escoller simétrico xa que os bancos enviarán carga de volta o banco orixinal. STR_CONFIG_SETTING_DISTRIBUTION_DEFAULT :Xeito de distribución para outros tipos de mercadoría: {STRING} -STR_CONFIG_SETTING_DISTRIBUTION_DEFAULT_HELPTEXT :"asimétrico" significa que calquera cantidade de mercadorías pode ser enviada en calquera dirección. "manual" significa que non haberá distribución automática para estas mercadorías. +STR_CONFIG_SETTING_DISTRIBUTION_DEFAULT_HELPTEXT :"Asimétrico" significa que calquera cantidade de mercadorías pode ser enviada en calquera dirección. "Manual" significa que non haberá distribución automática para estas mercadorías. ###length 3 STR_CONFIG_SETTING_DISTRIBUTION_MANUAL :manual STR_CONFIG_SETTING_DISTRIBUTION_ASYMMETRIC :asimétrica @@ -2700,6 +2702,10 @@ STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_INCREASE_TOOLTIP :{BLACK}Aumentar STR_SELECT_RAIL_BRIDGE_CAPTION :{WHITE}Selecciona ponte ferroviaria STR_SELECT_ROAD_BRIDGE_CAPTION :{WHITE}Selecciona ponte de estrada STR_SELECT_BRIDGE_SELECTION_TOOLTIP :{BLACK}Selección de pontes - pincha na ponte seleccionada para construíla +STR_SELECT_BRIDGE_INFO_NAME :{GOLD}{STRING} +STR_SELECT_BRIDGE_INFO_NAME_MAX_SPEED :{GOLD}{STRING},{} {VELOCITY} +STR_SELECT_BRIDGE_INFO_NAME_COST :{GOLD}{0:STRING},{} {WHITE}{2:CURRENCY_LONG} +STR_SELECT_BRIDGE_INFO_NAME_MAX_SPEED_COST :{GOLD}{STRING},{} {VELOCITY} {WHITE}{CURRENCY_LONG} STR_BRIDGE_NAME_SUSPENSION_STEEL :Colgante, aceiro STR_BRIDGE_NAME_GIRDER_STEEL :Vigas, aceiro STR_BRIDGE_NAME_CANTILEVER_STEEL :Voladizo, aceiro @@ -2819,7 +2825,7 @@ STR_OBJECT_CLASS_TRNS :Transmisores STR_PLANT_TREE_CAPTION :{WHITE}Árbores STR_PLANT_TREE_TOOLTIP :{BLACK}Selecciona-lo tipo de árbore a plantar. Se xa hai unha árbore no cadro, isto engadirá máis árbores de varios tipos independentemente do tipo seleccionado STR_TREES_RANDOM_TYPE :{BLACK}Árbores de tipo aleatorio -STR_TREES_RANDOM_TYPE_TOOLTIP :{BLACK}Colocar árbores de tipo aleatorio. Shift alterna entre construír e amosa-lo custo estimado +STR_TREES_RANDOM_TYPE_TOOLTIP :{BLACK}Colocar árbores de tipo aleatorio. Ctrl selecciona a area diagonalmente. Shift alterna entre construír e amosa-lo custo estimado STR_TREES_RANDOM_TREES_BUTTON :{BLACK}Árbores aleatorias STR_TREES_RANDOM_TREES_TOOLTIP :{BLACK}Plantar árbores aleatoriamente sobre a paisaxe STR_TREES_MODE_NORMAL_BUTTON :{BLACK}Normal @@ -3347,6 +3353,8 @@ STR_NEWGRF_ERROR_MSG_INFO :{SILVER}{STRING STR_NEWGRF_ERROR_MSG_WARNING :{RED}Coidado: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_ERROR :{RED}Erro: {SILVER}{STRING} STR_NEWGRF_ERROR_MSG_FATAL :{RED}Erro fatal: {SILVER}{STRING} +STR_NEWGRF_ERROR_FATAL_POPUP :{WHITE}O NewGRF "{STRING}" devolveu un erro crítico:{}{STRING} +STR_NEWGRF_ERROR_POPUP :{WHITE}O NewGRF "{STRING}" devolveu un erro:{}{STRING} STR_NEWGRF_ERROR_VERSION_NUMBER :{1:STRING} non funcionará coa versión de TTDPatch reportada por OpenTTD. STR_NEWGRF_ERROR_DOS_OR_WINDOWS :{1:STRING} é para a versión {2:STRING} de TTD. STR_NEWGRF_ERROR_UNSET_SWITCH :{1:STRING} está deseñado para ser usado con {2:STRING} @@ -4522,7 +4530,7 @@ STR_TIMETABLE_CLEAR_SPEED :{BLACK}Borrar o STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Borrar a velocidade máxima da orde seleccionada. Ctrl+Click borra a velocidade para todas as ordes. STR_TIMETABLE_RESET_LATENESS :{BLACK}Reiniciar atraso -STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Reiniciar o contador de atraso, para que o vehículo vaia en hora +STR_TIMETABLE_RESET_LATENESS_TOOLTIP :{BLACK}Reiniciar o contador de atraso, para que o vehículo vaia en hora. Ctrl+Click reiniciará o grupo enteiro de tal xeito que o último vehículo vaia en hora e os demáis máis cedo. STR_TIMETABLE_AUTOFILL :{BLACK}Encher automaticamente STR_TIMETABLE_AUTOFILL_TOOLTIP :{BLACK}Encher o horario automaticamente cos valores da seguinte viaxe. Ctrl+Clic para intentar manter os tempos de espera @@ -4617,7 +4625,9 @@ STR_SCREENSHOT_HEIGHTMAP_SCREENSHOT :{BLACK}Captura STR_SCREENSHOT_MINIMAP_SCREENSHOT :{BLACK}Captura de pantalla do minimapa # Script Parameters -STR_AI_SETTINGS_CAPTION_AI :{WHITE}Parámetros da IA +STR_AI_SETTINGS_CAPTION :{WHITE}{STRING} Parámetros +STR_AI_SETTINGS_CAPTION_AI :AI +STR_AI_SETTINGS_CAPTION_GAMESCRIPT :Script do xogo STR_AI_SETTINGS_CLOSE :{BLACK}Pechar STR_AI_SETTINGS_RESET :{BLACK}Restablecer STR_AI_SETTINGS_SETTING :{STRING}: {ORANGE}{STRING} From 92c755161d6a467d33477bd0041fc720d7d9ba41 Mon Sep 17 00:00:00 2001 From: Nik Nyby Date: Mon, 20 Feb 2023 16:18:25 -0500 Subject: [PATCH 24/30] Fix #10222: Adjust line drawing algorithm (#10491) --- src/blitter/common.hpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/blitter/common.hpp b/src/blitter/common.hpp index cd3b6276a4..1ecd09a01e 100644 --- a/src/blitter/common.hpp +++ b/src/blitter/common.hpp @@ -109,11 +109,6 @@ void Blitter::DrawLineGeneric(int x1, int y1, int x2, int y2, int screen_width, } while (x1 != x2) { - if (dash_count < dash) { - for (int y = y_low; y != y_high; y += stepy) { - if (y >= 0 && y < screen_height) set_pixel(x1, y); - } - } if (frac_low >= 0) { y_low += stepy; frac_low -= dx; @@ -122,6 +117,12 @@ void Blitter::DrawLineGeneric(int x1, int y1, int x2, int y2, int screen_width, y_high += stepy; frac_high -= dx; } + if (dash_count < dash) { + for (int y = y_low; y != y_high; y += stepy) { + if (y >= 0 && y < screen_height) set_pixel(x1, y); + } + } + x1++; frac_low += dy; frac_high += dy; @@ -171,11 +172,6 @@ void Blitter::DrawLineGeneric(int x1, int y1, int x2, int y2, int screen_width, } while (y1 != y2) { - if (dash_count < dash) { - for (int x = x_low; x != x_high; x += stepx) { - if (x >= 0 && x < screen_width) set_pixel(x, y1); - } - } if (frac_low >= 0) { x_low += stepx; frac_low -= dy; @@ -184,6 +180,12 @@ void Blitter::DrawLineGeneric(int x1, int y1, int x2, int y2, int screen_width, x_high += stepx; frac_high -= dy; } + if (dash_count < dash) { + for (int x = x_low; x != x_high; x += stepx) { + if (x >= 0 && x < screen_width) set_pixel(x, y1); + } + } + y1++; frac_low += dx; frac_high += dx; From 7b1fd3e37f49ae307ae074766a9fc57fbb6faa5a Mon Sep 17 00:00:00 2001 From: SamuXarick <43006711+SamuXarick@users.noreply.github.com> Date: Fri, 3 Feb 2023 20:06:29 +0000 Subject: [PATCH 25/30] Fix #10059: [Script] Clamp config item values to int32 Also prevent random_deviation to be below 0. --- src/script/script_info.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/script/script_info.cpp b/src/script/script_info.cpp index d381ae9c2c..dc4e07dee2 100644 --- a/src/script/script_info.cpp +++ b/src/script/script_info.cpp @@ -146,42 +146,42 @@ SQInteger ScriptInfo::AddSetting(HSQUIRRELVM vm) } else if (strcmp(key, "min_value") == 0) { SQInteger res; if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.min_value = res; + config.min_value = ClampToI32(res); items |= 0x004; } else if (strcmp(key, "max_value") == 0) { SQInteger res; if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.max_value = res; + config.max_value = ClampToI32(res); items |= 0x008; } else if (strcmp(key, "easy_value") == 0) { SQInteger res; if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.easy_value = res; + config.easy_value = ClampToI32(res); items |= 0x010; } else if (strcmp(key, "medium_value") == 0) { SQInteger res; if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.medium_value = res; + config.medium_value = ClampToI32(res); items |= 0x020; } else if (strcmp(key, "hard_value") == 0) { SQInteger res; if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.hard_value = res; + config.hard_value = ClampToI32(res); items |= 0x040; } else if (strcmp(key, "random_deviation") == 0) { SQInteger res; if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.random_deviation = res; + config.random_deviation = ClampToI32(abs(res)); items |= 0x200; } else if (strcmp(key, "custom_value") == 0) { SQInteger res; if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.custom_value = res; + config.custom_value = ClampToI32(res); items |= 0x080; } else if (strcmp(key, "step_size") == 0) { SQInteger res; if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; - config.step_size = res; + config.step_size = ClampToI32(res); } else if (strcmp(key, "flags") == 0) { SQInteger res; if (SQ_FAILED(sq_getinteger(vm, -1, &res))) return SQ_ERROR; From fde7028a956e9e56dd9ba66ba8b0d9e2781fb531 Mon Sep 17 00:00:00 2001 From: SamuXarick <43006711+SamuXarick@users.noreply.github.com> Date: Fri, 3 Feb 2023 20:39:06 +0000 Subject: [PATCH 26/30] Fix #10059: [Script] Let custom values on a config item be up to 10 digits + 1 for sign --- src/game/game_gui.cpp | 3 ++- src/script/script_config.hpp | 3 +++ src/script/script_gui.cpp | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/game/game_gui.cpp b/src/game/game_gui.cpp index ad2248a6f5..566a219b20 100644 --- a/src/game/game_gui.cpp +++ b/src/game/game_gui.cpp @@ -21,6 +21,7 @@ #include "game_config.hpp" #include "game_info.hpp" #include "../script/script_gui.h" +#include "../script_config.hpp" #include "../table/strings.h" #include "../safeguards.h" @@ -340,7 +341,7 @@ struct GSConfigWindow : public Window { } else if (!bool_item && !config_item.complete_labels) { /* Display a query box so users can enter a custom value. */ SetDParam(0, old_val); - ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 10, this, CS_NUMERAL, QSF_NONE); + ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, INT32_DIGITS_WITH_SIGN_AND_TERMINATION, this, CS_NUMERAL_SIGNED, QSF_NONE); } this->SetDirty(); break; diff --git a/src/script/script_config.hpp b/src/script/script_config.hpp index f8b2a43dc9..b243f3061f 100644 --- a/src/script/script_config.hpp +++ b/src/script/script_config.hpp @@ -18,6 +18,9 @@ #include "../textfile_gui.h" #include "script_instance.hpp" +/** Maximum of 10 digits for MIN / MAX_INT32, 1 for the sign and 1 for '\0'. */ +static const int INT32_DIGITS_WITH_SIGN_AND_TERMINATION = 10 + 1 + 1; + /** Bitmask of flags for Script settings. */ enum ScriptConfigFlags { SCRIPTCONFIG_NONE = 0x0, ///< No flags set. diff --git a/src/script/script_gui.cpp b/src/script/script_gui.cpp index 468cdecf59..8c9a1abcc7 100644 --- a/src/script/script_gui.cpp +++ b/src/script/script_gui.cpp @@ -25,6 +25,7 @@ #include "script_gui.h" #include "script_log.hpp" #include "script_scanner.hpp" +#include "script_config.hpp" #include "../ai/ai.hpp" #include "../ai/ai_config.hpp" #include "../ai/ai_info.hpp" @@ -497,7 +498,7 @@ struct ScriptSettingsWindow : public Window { } else if (!bool_item && !config_item.complete_labels) { /* Display a query box so users can enter a custom value. */ SetDParam(0, old_val); - ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 10, this, CS_NUMERAL, QSF_NONE); + ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, INT32_DIGITS_WITH_SIGN_AND_TERMINATION, this, CS_NUMERAL_SIGNED, QSF_NONE); } this->SetDirty(); break; From bb2ac8b3c4c41ac75d88ed0a172ebb2477c4728e Mon Sep 17 00:00:00 2001 From: SamuXarick <43006711+SamuXarick@users.noreply.github.com> Date: Fri, 3 Feb 2023 21:35:21 +0000 Subject: [PATCH 27/30] Fix: [Script] Save config item values up to 10 digits + 1 for sign + 1 for termination, enough to fit min and max int --- src/script/script_config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/script_config.cpp b/src/script/script_config.cpp index 738a898a79..7f4cfebb6d 100644 --- a/src/script/script_config.cpp +++ b/src/script/script_config.cpp @@ -216,7 +216,7 @@ std::string ScriptConfig::SettingsToString() const char *s = string; *s = '\0'; for (const auto &item : this->settings) { - char no[10]; + char no[INT32_DIGITS_WITH_SIGN_AND_TERMINATION]; seprintf(no, lastof(no), "%d", item.second); /* Check if the string would fit in the destination */ From 376820c0b62c12219ff4a6b96a5c03f64a8e3ed9 Mon Sep 17 00:00:00 2001 From: SamuXarick <43006711+SamuXarick@users.noreply.github.com> Date: Sat, 4 Feb 2023 11:58:04 +0000 Subject: [PATCH 28/30] Doc: [Script] Update info descriptions --- src/script/api/script_info_docs.hpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/script/api/script_info_docs.hpp b/src/script/api/script_info_docs.hpp index 36d2b859ab..fad4b152df 100644 --- a/src/script/api/script_info_docs.hpp +++ b/src/script/api/script_info_docs.hpp @@ -218,20 +218,27 @@ public: * store the current configuration of Scripts. Required. * - description A single line describing the setting. Required. * - min_value The minimum value of this setting. Required for integer - * settings and not allowed for boolean settings. + * settings and not allowed for boolean settings. The value will be + * clamped in the range [MIN(int32), MAX(int32)] (inclusive). * - max_value The maximum value of this setting. Required for integer - * settings and not allowed for boolean settings. + * settings and not allowed for boolean settings. The value will be + * clamped in the range [MIN(int32), MAX(int32)] (inclusive). * - easy_value The default value if the easy difficulty level - * is selected. Required. + * is selected. Required. The value will be clamped in the range + * [MIN(int32), MAX(int32)] (inclusive). * - medium_value The default value if the medium difficulty level - * is selected. Required. + * is selected. Required. The value will be clamped in the range + * [MIN(int32), MAX(int32)] (inclusive). * - hard_value The default value if the hard difficulty level - * is selected. Required. + * is selected. Required. The value will be clamped in the range + * [MIN(int32), MAX(int32)] (inclusive). * - custom_value The default value if the custom difficulty level - * is selected. Required. + * is selected. Required. The value will be clamped in the range + * [MIN(int32), MAX(int32)] (inclusive). * - random_deviation If this property has a nonzero value, then the * actual value of the setting in game will be randomized in the range * [user_configured_value - random_deviation, user_configured_value + random_deviation] (inclusive). + * random_deviation sign is ignored and the value is clamped in the range [0, MAX(int32)] (inclusive). * Not allowed if the CONFIG_RANDOM flag is set, otherwise optional. * - step_size The increase/decrease of the value every time the user * clicks one of the up/down arrow buttons. Optional, default is 1. From 8351b97f5293caa4b12840e2eec51f74da202409 Mon Sep 17 00:00:00 2001 From: SamuXarick <43006711+SamuXarick@users.noreply.github.com> Date: Sat, 4 Feb 2023 14:24:10 +0000 Subject: [PATCH 29/30] Add: [Script] Labels for negative values of a setting --- src/script/api/script_info_docs.hpp | 5 ++++- src/script/script_info.cpp | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/script/api/script_info_docs.hpp b/src/script/api/script_info_docs.hpp index fad4b152df..cae5852180 100644 --- a/src/script/api/script_info_docs.hpp +++ b/src/script/api/script_info_docs.hpp @@ -254,13 +254,16 @@ public: * user will see the corresponding name. * @param setting_name The name of the setting. * @param value_names A table that maps values to names. The first - * character of every identifier is ignored and the rest should + * character of every identifier is ignored, the second character + * could be '_' to indicate the value is negative, and the rest should * be an integer of the value you define a name for. The value * is a short description of that value. * To define labels for a setting named "competition_level" you could * for example call it like this: * AddLabels("competition_level", {_0 = "no competition", _1 = "some competition", * _2 = "a lot of competition"}); + * Another example, for a setting with a negative value: + * AddLabels("amount", {__1 = "less than one", _0 = "none", _1 = "more than one"}); * * @note This is a function provided by OpenTTD, you don't have to * include it in your Script but should just call it from GetSettings. diff --git a/src/script/script_info.cpp b/src/script/script_info.cpp index dc4e07dee2..79f4982fb6 100644 --- a/src/script/script_info.cpp +++ b/src/script/script_info.cpp @@ -252,7 +252,14 @@ SQInteger ScriptInfo::AddLabels(HSQUIRRELVM vm) if (SQ_FAILED(sq_getstring(vm, -1, &label))) return SQ_ERROR; /* Because squirrel doesn't support identifiers starting with a digit, * we skip the first character. */ - int key = atoi(key_string + 1); + key_string++; + int sign = 1; + if (*key_string == '_') { + /* When the second character is '_', it indicates the value is negative. */ + sign = -1; + key_string++; + } + int key = atoi(key_string) * sign; StrMakeValidInPlace(const_cast(label)); /* !Contains() prevents stredup from leaking. */ From b52b29b1a455d65df0926db9f60bf11b764323a4 Mon Sep 17 00:00:00 2001 From: SamuXarick <43006711+SamuXarick@users.noreply.github.com> Date: Mon, 20 Feb 2023 15:19:39 +0000 Subject: [PATCH 30/30] Change: Avoid crashing to the side of a train When a road vehicle is already running on a multi level crossing, and a train shows up ahead, don't make the road vehicle crash on the side of the train. --- src/road_cmd.cpp | 3 +++ src/train.h | 2 ++ src/train_cmd.cpp | 15 ++++++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 43bb864dc2..b06ac95676 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -26,6 +26,7 @@ #include "effectvehicle_base.h" #include "elrail_func.h" #include "roadveh.h" +#include "train.h" #include "town.h" #include "company_base.h" #include "core/random_func.hpp" @@ -2086,6 +2087,8 @@ static TrackStatus GetTileTrackStatus_Road(TileIndex tile, TransportType mode, u trackdirbits = TrackBitsToTrackdirBits(AxisToTrackBits(axis)); if (IsCrossingBarred(tile)) { red_signals = trackdirbits; + if (TrainOnCrossing(tile)) break; + auto mask_red_signal_bits_if_crossing_barred = [&](TileIndex t, TrackdirBits mask) { if (IsLevelCrossingTile(t) && IsCrossingBarred(t)) red_signals &= mask; }; diff --git a/src/train.h b/src/train.h index 9bc34a0fc4..36c99c7b95 100644 --- a/src/train.h +++ b/src/train.h @@ -65,6 +65,8 @@ int GetTrainStopLocation(StationID station_id, TileIndex tile, const Train *v, i void GetTrainSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); +bool TrainOnCrossing(TileIndex tile); + /** Variables that are cached to improve performance and such */ struct TrainCache { /* Cached wagon override spritegroup */ diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 694fa06a80..7977e375af 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -1658,6 +1658,19 @@ static Vehicle *TrainOnTileEnum(Vehicle *v, void *) return (v->type == VEH_TRAIN) ? v : nullptr; } +/** + * Check if a level crossing tile has a train on it + * @param tile tile to test + * @return true if a train is on the crossing + * @pre tile is a level crossing + */ +bool TrainOnCrossing(TileIndex tile) +{ + assert(IsLevelCrossingTile(tile)); + + return HasVehicleOnPos(tile, nullptr, &TrainOnTileEnum); +} + /** * Checks if a train is approaching a rail-road crossing @@ -1709,7 +1722,7 @@ static bool TrainApproachingCrossing(TileIndex tile) static inline bool CheckLevelCrossing(TileIndex tile) { /* reserved || train on crossing || train approaching crossing */ - return HasCrossingReservation(tile) || HasVehicleOnPos(tile, NULL, &TrainOnTileEnum) || TrainApproachingCrossing(tile); + return HasCrossingReservation(tile) || TrainOnCrossing(tile) || TrainApproachingCrossing(tile); } /**