diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 68f12afc0a..06d9af2185 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -114,6 +114,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 \ @@ -201,6 +202,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 cce0db4038..84d20ccbc0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -43,6 +43,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.yml b/.github/workflows/release.yml index 442b2cf20d..8a54c82713 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -284,31 +284,71 @@ 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 \ libicu-devel \ libpng-devel \ - libpng-devel \ - lzo-devel \ - SDL2-devel \ + perl-IPC-Cmd \ wget \ - xz-devel \ - libzstd-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 @@ -332,6 +372,7 @@ jobs: echo "::group::CMake" cmake ${GITHUB_WORKSPACE} \ + -DCMAKE_TOOLCHAIN_FILE=/vcpkg/scripts/buildsystems/vcpkg.cmake \ -DCMAKE_BUILD_TYPE=Release \ -DOPTION_COMPRESS_DEBUG=ON \ -DOPTION_LTO=ON \ @@ -424,6 +465,7 @@ jobs: git \ make \ openssl \ + libcurl4-openssl-dev \ libfontconfig-dev \ libfluidsynth-dev \ libicu-dev \ @@ -523,6 +565,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 c5f4b158cf..d4f76ae213 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,6 +121,14 @@ find_package(LZO) find_package(ZSTD 1.4) 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) @@ -331,6 +339,10 @@ link_package(LIBLZMA TARGET LibLZMA::LibLZMA ENCOURAGED) link_package(LZO) link_package(ZSTD TARGET ZSTD::ZSTD RECOMMENDED) +if(NOT WIN32 AND NOT EMSCRIPTEN) + link_package(CURL ENCOURAGED) +endif() + if(NOT OPTION_DEDICATED) link_package(Fluidsynth) link_package(SDL) @@ -443,6 +455,7 @@ if(WIN32) imm32 usp10 psapi + winhttp ) endif() diff --git a/COMPILING.md b/COMPILING.md index a7962ccd10..117425886a 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -11,14 +11,17 @@ OpenTTD makes use of the following external libraries: - (optional) liblzo2: (de)compressing of old (pre 0.3.0) savegames - (optional) libzstd: (de)compressing of multiplayer join savegames, if available -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/command.cpp b/src/command.cpp index b2b75766d1..4aa6c367a1 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -116,7 +116,9 @@ CommandProc CmdMassChangeOrder; CommandProc CmdChangeServiceInt; CommandProc CmdBuildIndustry; -CommandProc CmdIndustryCtrl; +CommandProc CmdIndustrySetFlags; +CommandProc CmdIndustrySetExclusivity; +CommandProc CmdIndustrySetText; CommandProc CmdSetCompanyManagerFace; CommandProc CmdSetCompanyColour; @@ -371,7 +373,9 @@ static const Command _command_proc_table[] = { DEF_CMD(CmdChangeServiceInt, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_CHANGE_SERVICE_INT DEF_CMD(CmdBuildIndustry, CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_INDUSTRY - DEF_CMD(CmdIndustryCtrl, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_INDUSTRY_CTRL + DEF_CMD(CmdIndustrySetFlags, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_INDUSTRY_SET_FLAGS + DEF_CMD(CmdIndustrySetExclusivity, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_INDUSTRY_SET_EXCLUSIVITY + DEF_CMD(CmdIndustrySetText, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_INDUSTRY_SET_TEXT DEF_CMD(CmdSetCompanyManagerFace, 0, CMDT_OTHER_MANAGEMENT ), // CMD_SET_COMPANY_MANAGER_FACE DEF_CMD(CmdSetCompanyColour, 0, CMDT_OTHER_MANAGEMENT ), // CMD_SET_COMPANY_COLOUR diff --git a/src/command_type.h b/src/command_type.h index 71412770ec..fab1c4875c 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -341,7 +341,9 @@ enum Commands { 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/crashlog.cpp b/src/crashlog.cpp index b3360c40ca..fe5d2445df 100644 --- a/src/crashlog.cpp +++ b/src/crashlog.cpp @@ -74,6 +74,9 @@ #ifdef WITH_ZLIB # include #endif +#ifdef WITH_CURL +# include +#endif #include "safeguards.h" @@ -359,6 +362,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/game/game_gui.cpp b/src/game/game_gui.cpp index 7e151f91f5..048adc7f55 100644 --- a/src/game/game_gui.cpp +++ b/src/game/game_gui.cpp @@ -22,6 +22,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" @@ -345,7 +346,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/industry.h b/src/industry.h index 8805d17467..be5ac6b819 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 ebf9a3a34d..5a5529f7c4 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -2174,65 +2174,76 @@ CommandCost CmdBuildIndustry(TileIndex tile, DoCommandFlag flags, uint32 p1, uin } /** - * Change industry properties - * @param tile Unused. + * Set industry control flags. * @param flags Type of operation. * @param p1 IndustryID - * @param p2 various bitstuffed elements - * - p2 = (bit 0 - 7) - IndustryAction to perform - * - p2 = (bit 8 - 15) - IndustryControlFlags - * (only used with set control flags) - * - p2 = (bit 16 - 23) - 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 p2 IndustryControlFlags * @return Empty cost or an error. */ -CommandCost CmdIndustryCtrl(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +CommandCost CmdIndustrySetFlags(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { if (_current_company != OWNER_DEITY) return CMD_ERROR; Industry *ind = Industry::GetIfValid(p1); if (ind == nullptr) return CMD_ERROR; - auto action = static_cast(GB(p2, 0, 8)); - - switch (action) { - case IndustryAction::SetControlFlags: { - IndustryControlFlags ctlflags = (IndustryControlFlags)GB(p2, 8, 8) & INDCTL_MASK; + if (flags & DC_EXEC) ind->ctlflags = ((IndustryControlFlags)p2) & INDCTL_MASK; - if (flags & DC_EXEC) ind->ctlflags = ctlflags; + return CommandCost(); +} - break; - } +/** + * Change exclusive consumer or supplier for the industry. + * @param flags Type of operation. + * @param p1 IndustryID + * @param p2 various bitstuffed elements + * - p2 = (bit 0 - 7) - CompanyID to set or INVALID_OWNER (available to everyone) or + * OWNER_NONE (neutral stations only) or OWNER_DEITY (no one) + * - p2 = (bit 8) - Set exclusive consumer if true, supplier if false. + * @return Empty cost or an error. + */ +CommandCost CmdIndustrySetExclusivity(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + if (_current_company != OWNER_DEITY) return CMD_ERROR; - case IndustryAction::SetExclusiveSupplier: - case IndustryAction::SetExclusiveConsumer: { - Owner company_id = (Owner)GB(p2, 16, 8); + Industry *ind = Industry::GetIfValid(p1); + if (ind == nullptr) return CMD_ERROR; - if (company_id != OWNER_NONE && company_id != INVALID_OWNER && company_id != OWNER_DEITY - && !Company::IsValidID(company_id)) return CMD_ERROR; + Owner company_id = (Owner)GB(p2, 0, 8); + bool consumer = HasBit(p2, 8); - if (flags & DC_EXEC) { - if (action == IndustryAction::SetExclusiveSupplier) { - ind->exclusive_supplier = company_id; - } else { - ind->exclusive_consumer = company_id; - } - } + 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::SetText: { - ind->text.clear(); - if (!StrEmpty(text)) ind->text = text; - InvalidateWindowData(WC_INDUSTRY_VIEW, ind->index); - break; - } + return CommandCost(); +} - default: - return CMD_ERROR; +/** + * Change additional industry text. + * @param flags Type of operation. + * @param p1 IndustryID + * @param text - Additional industry text. + * @return Empty cost or an error. + */ +CommandCost CmdIndustrySetText(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + if (_current_company != OWNER_DEITY) return CMD_ERROR; + + Industry *ind = Industry::GetIfValid(p1); + if (ind == nullptr) return CMD_ERROR; + + if (flags & DC_EXEC) { + ind->text.clear(); + if (!StrEmpty(text)) ind->text = text; + InvalidateWindowData(WC_INDUSTRY_VIEW, ind->index); } return CommandCost(); 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/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/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/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/english_US.txt b/src/lang/english_US.txt index df8c5eada6..c28c3c5494 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 @@ -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 @@ -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 diff --git a/src/lang/galician.txt b/src/lang/galician.txt index 82a9a16510..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 @@ -2699,7 +2701,11 @@ 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_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} 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/korean.txt b/src/lang/korean.txt index 0c34853ad6..319ae89897 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 "와" "과"} 같이 사용해야 합니다 @@ -4241,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} @@ -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/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/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/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. 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/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/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/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} 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+клац, щоб спробувати зберегти час очікування 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} 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 9839459888..459481de61 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..78b5be87af --- /dev/null +++ b/src/network/core/http.h @@ -0,0 +1,74 @@ +/* + * 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; + + /** + * 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() {} +}; + +/** 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 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 std::string data = ""); + + /** + * 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..0a4aa14a0a --- /dev/null +++ b/src/network/core/http_curl.cpp @@ -0,0 +1,239 @@ +/* + * 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_fmt.h" +#include "../../fileio_func.h" +#include "../../rev.h" +#include "../../thread.h" +#include "../network_internal.h" + +#include "http.h" + +#include +#include +#include +#include +#include +#include + +#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: + /** + * Create a new HTTP request. + * + * @param uri the URI to connect to (https://.../..). + * @param callback the callback to send data back on. + * @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 std::string &data) : + uri(uri), + callback(callback), + data(data) + { + } + + 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; +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 std::string 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(); +} + +/* static */ void NetworkHTTPSocketHandler::HTTPReceive() +{ +} + +void HttpThread() +{ + CURL *curl = curl_easy_init(); + assert(curl != nullptr); + + for (;;) { + std::unique_lock lock(_http_mutex); + + /* Wait for a new request. */ + while (_http_requests.empty() && !_http_thread_exit) { + _http_cv.wait(lock); + } + if (_http_thread_exit) break; + + std::unique_ptr request = std::move(_http_requests.front()); + _http_requests.pop(); + + /* Release the lock, as we will take a while to process the request. */ + lock.unlock(); + + /* Reset to default settings. */ + curl_easy_reset(curl); + + if (_debug_net_level >= 5) { + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + } + + /* 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); + + /* 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); + + /* Set a buffer of 100KiB, as the default of 16KiB seems a bit small. */ + curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 100L * 1024L); + + /* Fail our call if we don't receive a 2XX return value. */ + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + + /* Prepare POST body and URI. */ + if (!request->data.empty()) { + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request->data.c_str()); + } + 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 { + 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); + + if (res == CURLE_OK) { + Debug(net, 1, "HTTP request succeeded"); + request->callback->OnReceiveData(nullptr, 0); + } else { + Debug(net, (request->callback->IsCancelled() || _http_thread_exit) ? 1 : 0, "HTTP request failed: {}", curl_easy_strerror(res)); + request->callback->OnFailure(); + } + } + + curl_easy_cleanup(curl); +} + +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); +} + +void NetworkHTTPUninitialize() +{ + curl_global_cleanup(); + + _http_thread_exit = true; + + { + std::lock_guard lock(_http_mutex); + _http_cv.notify_one(); + } + + if (_http_thread.joinable()) { + _http_thread.join(); + } +} diff --git a/src/network/core/http_none.cpp b/src/network/core/http_none.cpp new file mode 100644 index 0000000000..41ba7c8cba --- /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 std::string 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..e8eee7d8c1 --- /dev/null +++ b/src/network/core/http_winhttp.cpp @@ -0,0 +1,325 @@ +/* + * 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_fmt.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: + 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. + 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); + + ~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 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 std::string &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) +{ + if (this->finished) return; + + 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->finished = true; + this->callback->OnFailure(); + 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->finished = true; + this->callback->OnFailure(); + 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->finished = true; + this->callback->OnFailure(); + break; + + default: + Debug(net, 0, "HTTP request failed: unexepected callback code 0x{:x}", code); + 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); +} + +/** + * 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->finished = true; + this->callback->OnFailure(); + return; + } + + 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); + + Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString()); + this->finished = true; + this->callback->OnFailure(); + return; + } + + /* Send the request (possibly with a payload). */ + 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.c_str()), static_cast(data.size()), static_cast(data.size()), 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() +{ + if (this->callback->IsCancelled()) { + Debug(net, 1, "HTTP request failed: cancelled by user"); + this->finished = true; + this->callback->OnFailure(); + return true; + } + + 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); + } +} + +/* 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(); + _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 b22de91022..0000000000 --- a/src/network/core/tcp_http.cpp +++ /dev/null @@ -1,325 +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 char *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) -{ - size_t bufferSize = strlen(url) + strlen(host) + strlen(_openttd_revision) + (data == nullptr ? 0 : strlen(data)) + 128; - char *buffer = AllocaM(char, bufferSize); - - DEBUG(net, 5, "[tcp/http] Requesting %s%s", host, url); - if (data != nullptr) { - seprintf(buffer, buffer + bufferSize - 1, "POST %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: OpenTTD/%s\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s\r\n", url, host, _openttd_revision, (int)strlen(data), data); - } else { - seprintf(buffer, buffer + bufferSize - 1, "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: OpenTTD/%s\r\n\r\n", url, host, _openttd_revision); - } - - ssize_t size = strlen(buffer); - ssize_t res = send(this->sock, (const char*)buffer, size, 0); - if (res != 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 %i 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 %s", 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 %s", 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: %s", 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 da7a04ac48..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 char *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.c_str(), 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 0518ba861e..5fe99cadad 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -1356,12 +1356,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 c28d0cc376..ba7aa00a03 100644 --- a/src/network/network_content.cpp +++ b/src/network/network_content.cpp @@ -317,13 +317,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; @@ -337,6 +330,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 { @@ -350,25 +345,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; - new NetworkHTTPContentConnecter(NetworkContentMirrorConnectionString(), this, NETWORK_CONTENT_MIRROR_URL, content_request); - /* NetworkHTTPContentConnecter takes over freeing of content_request! */ + NetworkHTTPSocketHandler::Connect(NetworkContentMirrorUriString(), this, content_request); } /** @@ -598,25 +582,31 @@ 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; 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; } + + /* 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) @@ -752,7 +742,8 @@ ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() : http_response_index(-2), curFile(nullptr), curInfo(nullptr), - isConnecting(false) + isConnecting(false), + isCancelled(false) { this->lastActivity = std::chrono::steady_clock::now(); } @@ -798,7 +789,10 @@ public: void ClientNetworkContentSocketHandler::Connect() { if (this->sock != INVALID_SOCKET || this->isConnecting) return; + + this->isCancelled = false; this->isConnecting = true; + new NetworkContentConnecter(NetworkContentServerConnectionString()); } @@ -807,6 +801,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 c5de5dbe0f..02f1c996d8 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 "../3rdparty/cpp-btree/btree_map.h" /** Vector with content info */ @@ -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; @@ -95,6 +96,7 @@ protected: void OnFailure() override; void OnReceiveData(const char *data, size_t length) override; + bool IsCancelled() const override; bool BeforeDownload(); void AfterDownload(); diff --git a/src/network/network_content_gui.cpp b/src/network/network_content_gui.cpp index 1359799086..9d32a0019c 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); @@ -173,7 +173,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(); } diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index de7aec0d4b..305fcce3cb 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -27,6 +27,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" @@ -2722,6 +2723,8 @@ static TrackStatus GetTileTrackStatus_Road(TileIndex tile, TransportType mode, u }; if (IsCrossingBarred(tile) && !is_non_colliding()) { 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/script/api/ai_changelog.hpp b/src/script/api/ai_changelog.hpp index 27ad40e79f..21534121cf 100644 --- a/src/script/api/ai_changelog.hpp +++ b/src/script/api/ai_changelog.hpp @@ -19,6 +19,10 @@ * * 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. * * \b 13.0 * diff --git a/src/script/api/game_changelog.hpp b/src/script/api/game_changelog.hpp index 46a8e4904f..bdf211cff7 100644 --- a/src/script/api/game_changelog.hpp +++ b/src/script/api/game_changelog.hpp @@ -19,6 +19,10 @@ * * 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. * * \b 13.0 * diff --git a/src/script/api/script_basestation.cpp b/src/script/api/script_basestation.cpp index 860e54c787..ba7c61c348 100644 --- a/src/script/api/script_basestation.cpp +++ b/src/script/api/script_basestation.cpp @@ -38,7 +38,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 36c8e7ca6d..8ba188bf9c 100644 --- a/src/script/api/script_company.cpp +++ b/src/script/api/script_company.cpp @@ -44,7 +44,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); @@ -65,7 +65,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 87d4196fdd..f4b3832ef5 100644 --- a/src/script/api/script_error.hpp +++ b/src/script/api/script_error.hpp @@ -42,11 +42,7 @@ * @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)) { \ + if (string.empty()) { \ 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_goal.cpp b/src/script/api/script_goal.cpp index 02765c10dd..e5dc3ebe56 100644 --- a/src/script/api/script_goal.cpp +++ b/src/script/api/script_goal.cpp @@ -33,7 +33,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); @@ -70,9 +70,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::DoCommand(0, goal_id, 0, CMD_SET_GOAL_TEXT, goal->GetEncodedText()); + return ScriptObject::DoCommand(0, goal_id, 0, CMD_SET_GOAL_TEXT, text); } /* static */ bool ScriptGoal::SetProgress(GoalID goal_id, Text *progress) @@ -82,12 +83,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::DoCommand(0, goal_id, 0, CMD_SET_GOAL_PROGRESS, progress != nullptr ? progress->GetEncodedText() : nullptr); + return ScriptObject::DoCommand(0, goal_id, 0, CMD_SET_GOAL_PROGRESS, progress != nullptr ? progress->GetEncodedText().c_str() : ""); } /* static */ bool ScriptGoal::SetCompleted(GoalID goal_id, bool completed) @@ -113,7 +109,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 a6e2fdee25..a43217cac0 100644 --- a/src/script/api/script_group.cpp +++ b/src/script/api/script_group.cpp @@ -54,7 +54,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); @@ -118,7 +118,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::DoCommand(0, group_id, vehicle_id, CMD_ADD_VEHICLE_GROUP); } 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_industry.cpp b/src/script/api/script_industry.cpp index 6f54fda14c..3ce296d147 100644 --- a/src/script/api/script_industry.cpp +++ b/src/script/api/script_industry.cpp @@ -52,14 +52,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::DoCommand(0, industry_id, static_cast(IndustryAction::SetText), CMD_INDUSTRY_CTRL, encoded_text); + return ScriptObject::DoCommand(0, industry_id, 0, CMD_INDUSTRY_SET_TEXT, text != nullptr ? text->GetEncodedText().c_str() : ""); } /* static */ ScriptIndustry::CargoAcceptState ScriptIndustry::IsCargoAccepted(IndustryID industry_id, CargoID cargo_id) @@ -257,7 +252,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::DoCommand(0, industry_id, 0 | ((control_flags & ::INDCTL_MASK) << 8), CMD_INDUSTRY_CTRL); + return ScriptObject::DoCommand(0, industry_id, (::IndustryControlFlags)control_flags & ::INDCTL_MASK, CMD_INDUSTRY_SET_FLAGS); } /* static */ ScriptCompany::CompanyID ScriptIndustry::GetExclusiveSupplier(IndustryID industry_id) @@ -276,7 +271,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::DoCommand(0, industry_id, 1 | (((uint8)owner) << 16), CMD_INDUSTRY_CTRL); + return ScriptObject::DoCommand(0, industry_id, (1 << 8) | ((uint8)owner), CMD_INDUSTRY_SET_EXCLUSIVITY); } /* static */ ScriptCompany::CompanyID ScriptIndustry::GetExclusiveConsumer(IndustryID industry_id) @@ -295,5 +290,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::DoCommand(0, industry_id, 2 | (((uint8)owner) << 16), CMD_INDUSTRY_CTRL); + return ScriptObject::DoCommand(0, industry_id, ((uint8)owner), CMD_INDUSTRY_SET_EXCLUSIVITY); } 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_info_docs.hpp b/src/script/api/script_info_docs.hpp index 36d2b859ab..cae5852180 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. @@ -247,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/api/script_league.cpp b/src/script/api/script_league.cpp index 9b53f792b9..5c98aa8323 100644 --- a/src/script/api/script_league.cpp +++ b/src/script/api/script_league.cpp @@ -33,13 +33,13 @@ EnforcePrecondition(LEAGUE_TABLE_INVALID, ScriptObject::GetCompany() == OWNER_DEITY); EnforcePrecondition(LEAGUE_TABLE_INVALID, title != nullptr); - const char *encoded_title = title->GetEncodedText(); + std::string encoded_title = title->GetEncodedText(); EnforcePreconditionEncodedText(LEAGUE_TABLE_INVALID, encoded_title); LeagueTableCmdData data; - data.title = encoded_title; - data.header = header->GetEncodedText(); - data.footer = footer->GetEncodedText(); + data.title = std::move(encoded_title); + if (header != nullptr) data.header = header->GetEncodedText(); + if (footer != nullptr) data.footer = footer->GetEncodedText(); if (!ScriptObject::DoCommandEx(0, 0, 0, 0, CMD_CREATE_LEAGUE_TABLE, nullptr, &data, &ScriptInstance::DoCommandReturnLeagueTableID)) return LEAGUE_TABLE_INVALID; @@ -66,12 +66,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))); @@ -98,7 +97,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))); @@ -114,7 +113,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::DoCommandEx(0, element, 0, rating, CMD_UPDATE_LEAGUE_TABLE_ELEMENT_SCORE, encoded_score); diff --git a/src/script/api/script_news.cpp b/src/script/api/script_news.cpp index 678cc69852..578ccc73a4 100644 --- a/src/script/api/script_news.cpp +++ b/src/script/api/script_news.cpp @@ -24,7 +24,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_object.hpp b/src/script/api/script_object.hpp index 9630df42af..8fdad1b0c3 100644 --- a/src/script/api/script_object.hpp +++ b/src/script/api/script_object.hpp @@ -86,9 +86,19 @@ protected: */ static bool DoCommandEx(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint cmd, const char *text = nullptr, const CommandAuxiliaryBase *aux_data = nullptr, Script_SuspendCallbackProc *callback = nullptr); + static bool DoCommandEx(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint cmd, const std::string &text, const CommandAuxiliaryBase *aux_data = nullptr, Script_SuspendCallbackProc *callback = nullptr) + { + return ScriptObject::DoCommandEx(tile, p1, p2, p3, cmd, text.c_str(), aux_data, callback); + } + static bool DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text = nullptr, Script_SuspendCallbackProc *callback = nullptr) { - return ScriptObject::DoCommandEx(tile, p1, p2, 0, cmd, text, 0, callback); + return ScriptObject::DoCommandEx(tile, p1, p2, 0, cmd, text, nullptr, callback); + } + + static bool DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const std::string &text, Script_SuspendCallbackProc *callback = nullptr) + { + return ScriptObject::DoCommandEx(tile, p1, p2, 0, cmd, text.c_str(), nullptr, callback); } /** diff --git a/src/script/api/script_order.cpp b/src/script/api/script_order.cpp index 0533642b1b..d977ecb4fa 100644 --- a/src/script/api/script_order.cpp +++ b/src/script/api/script_order.cpp @@ -49,7 +49,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); } /** @@ -162,7 +162,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; @@ -172,7 +172,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; @@ -240,7 +240,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) @@ -450,7 +450,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); @@ -458,7 +458,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); @@ -469,7 +469,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)); @@ -522,7 +522,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); @@ -653,23 +653,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::DoCommand(0, vehicle_id | CO_COPY << 30, main_vehicle_id, CMD_CLONE_ORDER); } /* 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::DoCommand(0, vehicle_id | CO_SHARE << 30, main_vehicle_id, CMD_CLONE_ORDER); } /* static */ bool ScriptOrder::UnshareOrders(VehicleID vehicle_id) { - EnforcePrecondition(false, ScriptVehicle::IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, ScriptVehicle::IsPrimaryVehicle(vehicle_id)); return ScriptObject::DoCommand(0, vehicle_id | CO_UNSHARE << 30, 0, CMD_CLONE_ORDER); } diff --git a/src/script/api/script_order.hpp b/src/script/api/script_order.hpp index d02fb4b5a3..82b3100d36 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_sign.cpp b/src/script/api/script_sign.cpp index 8908941cf7..5f28d06719 100644 --- a/src/script/api/script_sign.cpp +++ b/src/script/api/script_sign.cpp @@ -37,7 +37,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); @@ -72,7 +72,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_stationlist.cpp b/src/script/api/script_stationlist.cpp index 841a9186e1..99941bb51b 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_story_page.cpp b/src/script/api/script_story_page.cpp index 4ee90e218d..c695616ffe 100644 --- a/src/script/api/script_story_page.cpp +++ b/src/script/api/script_story_page.cpp @@ -51,7 +51,7 @@ static inline bool StoryPageElementTypeRequiresText(StoryPageElementType type) c, 0, CMD_CREATE_STORY_PAGE, - title != nullptr? title->GetEncodedText() : nullptr, + title != nullptr ? title->GetEncodedText().c_str() : "", &ScriptInstance::DoCommandReturnStoryPageID)) return STORY_PAGE_INVALID; /* In case of test-mode, we return StoryPageID 0 */ @@ -66,7 +66,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)); @@ -93,7 +98,7 @@ static inline bool StoryPageElementTypeRequiresText(StoryPageElementType type) story_page_id + (type << 16), refid, CMD_CREATE_STORY_PAGE_ELEMENT, - StoryPageElementTypeRequiresText(btype) ? text->GetEncodedText() : nullptr, + encoded_text, &ScriptInstance::DoCommandReturnStoryPageElementID)) return STORY_PAGE_ELEMENT_INVALID; /* In case of test-mode, we return StoryPageElementID 0 */ @@ -111,7 +116,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)); @@ -138,7 +148,7 @@ static inline bool StoryPageElementTypeRequiresText(StoryPageElementType type) story_page_element_id, refid, CMD_UPDATE_STORY_PAGE_ELEMENT, - StoryPageElementTypeRequiresText(type) ? text->GetEncodedText() : nullptr); + encoded_text); } /* static */ uint32 ScriptStoryPage::GetPageSortValue(StoryPageID story_page_id) @@ -162,7 +172,7 @@ static inline bool StoryPageElementTypeRequiresText(StoryPageElementType type) EnforcePrecondition(false, IsValidStoryPage(story_page_id)); EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY); - return ScriptObject::DoCommand(0, story_page_id, 0, CMD_SET_STORY_PAGE_TITLE, title != nullptr? title->GetEncodedText() : nullptr); + return ScriptObject::DoCommand(0, story_page_id, 0, CMD_SET_STORY_PAGE_TITLE, title != nullptr ? title->GetEncodedText().c_str() : ""); } /* 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 cd30e3a0c1..e9bf26e73e 100644 --- a/src/script/api/script_text.cpp +++ b/src/script/api/script_text.cpp @@ -11,21 +11,17 @@ #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" #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) : string(STR_NULL), params(), parami(), paramt(), paramc(0) @@ -176,12 +172,13 @@ 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; 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) @@ -206,10 +203,9 @@ char *ScriptText::_GetEncodedText(char *p, char *lastofp, int ¶m_count) return p; } -const char *Text::GetDecodedText() +const std::string 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); diff --git a/src/script/api/script_text.hpp b/src/script/api/script_text.hpp index 204aa3ea6a..ebacd70dd7 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 06dff3499b..e1e24b4898 100644 --- a/src/script/api/script_town.cpp +++ b/src/script/api/script_town.cpp @@ -44,13 +44,12 @@ { 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::DoCommand(0, town_id, 0, CMD_RENAME_TOWN, text); } @@ -59,14 +58,9 @@ { 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::DoCommand(::Town::Get(town_id)->xy, town_id, 0, CMD_TOWN_SET_TEXT, encoded_text); + return ScriptObject::DoCommand(::Town::Get(town_id)->xy, town_id, 0, CMD_TOWN_SET_TEXT, text != nullptr ? text->GetEncodedText().c_str() : ""); } /* static */ int32 ScriptTown::GetPopulation(TownID town_id) @@ -296,10 +290,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; @@ -309,7 +302,7 @@ return false; } - return ScriptObject::DoCommand(tile, size | (city ? 1 << 2 : 0) | layout << 3, townnameparts, CMD_FOUND_TOWN, text); + return ScriptObject::DoCommand(tile, size | (city ? 1 << 2 : 0) | layout << 3, townnameparts, CMD_FOUND_TOWN, text.c_str()); } /* 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 c4a9f7ff0e..b386ea080d 100644 --- a/src/script/api/script_vehicle.cpp +++ b/src/script/api/script_vehicle.cpp @@ -31,6 +31,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; @@ -101,7 +108,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::DoCommand(depot, vehicle_id, share_orders, CMD_CLONE_VEHICLE, nullptr, &ScriptInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID; @@ -190,7 +197,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::DoCommand(0, vehicle_id, 0, GetCmdSendToDepot(::Vehicle::Get(vehicle_id))); } @@ -198,7 +205,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::DoCommand(0, vehicle_id | DEPOT_SERVICE, 0, GetCmdSendToDepot(::Vehicle::Get(vehicle_id))); } @@ -218,7 +225,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::DoCommand(0, vehicle_id, 0, CMD_START_STOP_VEHICLE); } @@ -226,7 +233,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) { @@ -241,9 +248,9 @@ CCountedPtr counter(name); EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY); - EnforcePrecondition(false, IsValidVehicle(vehicle_id)); + EnforcePrecondition(false, IsPrimaryVehicle(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); @@ -285,14 +292,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); @@ -319,21 +326,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 @@ -356,21 +363,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(); } @@ -431,7 +438,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; } @@ -451,7 +458,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; @@ -459,7 +466,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); @@ -467,7 +474,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); 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 */ 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 cfb2b6196e..89a1dd90a2 100644 --- a/src/script/script_gui.cpp +++ b/src/script/script_gui.cpp @@ -26,6 +26,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" @@ -506,7 +507,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; diff --git a/src/script/script_info.cpp b/src/script/script_info.cpp index d381ae9c2c..79f4982fb6 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; @@ -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. */ diff --git a/src/train.h b/src/train.h index fe311e06fe..80aa0bf966 100644 --- a/src/train.h +++ b/src/train.h @@ -89,6 +89,8 @@ void DeleteVisibleTrain(Train *v); void CheckBreakdownFlags(Train *v); void GetTrainSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); +bool TrainOnCrossing(TileIndex tile); + inline int GetTrainRealisticBrakingTargetDecelerationLimit(int acceleration_type) { return 120 + (acceleration_type * 48); diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 605490f984..9804626fc4 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2592,6 +2592,19 @@ static Vehicle *TrainOnTileEnum(Vehicle *v, void *) return v; } +/** + * 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, VEH_TRAIN, nullptr, &TrainOnTileEnum); +} + /** * Checks if a train is approaching a rail-road crossing @@ -2641,7 +2654,7 @@ static bool TrainApproachingCrossing(TileIndex tile) static inline bool CheckLevelCrossing(TileIndex tile) { /* reserved || train on crossing || train approaching crossing */ - return HasCrossingReservation(tile) || HasVehicleOnPos(tile, VEH_TRAIN, nullptr, &TrainOnTileEnum) || TrainApproachingCrossing(tile); + return HasCrossingReservation(tile) || TrainOnCrossing(tile) || TrainApproachingCrossing(tile); } /**