Merge branch 'master' into jgrpp

# Conflicts:
#	.github/workflows/release-linux.yml
#	.github/workflows/release-macos.yml
#	src/industry_cmd.cpp
#	src/industry_cmd.h
#	src/network/core/http_curl.cpp
#	src/network/core/tcp_http.cpp
#	src/network/core/tcp_http.h
#	src/network/network_content.h
#	src/script/api/script_goal.cpp
#	src/script/api/script_industry.cpp
#	src/script/api/script_league.cpp
#	src/script/api/script_story_page.cpp
#	src/script/api/script_town.cpp
#	src/train.h
#	src/train_cmd.cpp
pull/491/head
Jonathan G Rennison 1 year ago
commit ae7c86c49d

@ -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 \

@ -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 \

@ -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 \

@ -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()

@ -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.

@ -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

@ -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

@ -74,6 +74,9 @@
#ifdef WITH_ZLIB
# include <zlib.h>
#endif
#ifdef WITH_CURL
# include <curl/curl.h>
#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;
}

@ -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;

@ -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.

@ -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<IndustryAction>(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();

@ -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}

@ -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.

@ -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.

@ -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

@ -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

@ -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

@ -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

@ -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}

@ -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

@ -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}

@ -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

@ -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

@ -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.

@ -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+щелчок - не изменять время ожидания.

@ -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}

@ -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

@ -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}

@ -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+клац, щоб спробувати зберегти час очікування

@ -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}

@ -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
)

@ -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");
}

@ -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)

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @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 */

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @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 <atomic>
#include <condition_variable>
#include <curl/curl.h>
#include <memory>
#include <mutex>
#include <queue>
#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<bool> _http_thread_exit = false;
static std::queue<std::unique_ptr<NetworkHTTPRequest>> _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<std::mutex> lock(_http_mutex);
_http_requests.push(std::make_unique<NetworkHTTPRequest>(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<std::mutex> 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<NetworkHTTPRequest> 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<HTTPCallback *>(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<HTTPCallback *>(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<std::mutex> lock(_http_mutex);
_http_cv.notify_one();
}
if (_http_thread.joinable()) {
_http_thread.join();
}
}

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @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()
{
}

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @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 <winhttp.h>
#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<bool> finished = false; ///< Whether we are finished with the request.
int depth = 0; ///< Current redirect depth we are in.
public:
NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *callback, const std::string &data);
~NetworkHTTPRequest();
void Connect();
bool Receive();
void WinHttpCallback(DWORD code, void *info, DWORD length);
};
static std::vector<NetworkHTTPRequest *> _http_requests;
static std::vector<NetworkHTTPRequest *> _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<char>(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<char *>(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<DWORD_PTR>(this));
} else {
WinHttpSendRequest(this->request, L"Content-Type: application/x-www-form-urlencoded\r\n", -1, const_cast<char *>(data.c_str()), static_cast<DWORD>(data.size()), static_cast<DWORD>(data.size()), reinterpret_cast<DWORD_PTR>(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);
}

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @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<NetworkHTTPSocketHandler *> _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<ssize_t>(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<ssize_t>(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++;
}
}

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @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 */

@ -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");

@ -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<char>(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;

@ -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();

@ -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();
}

@ -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;
};

@ -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
*

@ -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
*

@ -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);

@ -44,7 +44,7 @@
CCountedPtr<Text> 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<Text> 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);

@ -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 */

@ -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);

@ -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);
}

@ -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

@ -52,14 +52,9 @@
{
CCountedPtr<Text> 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<uint32>(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);
}

@ -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

@ -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.

@ -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);

@ -24,7 +24,7 @@
CCountedPtr<Text> 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);

@ -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);
}
/**

@ -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);
}

@ -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
*/

@ -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);

@ -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);

@ -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)

@ -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 &param_count)
@ -206,10 +203,9 @@ char *ScriptText::_GetEncodedText(char *p, char *lastofp, int &param_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);

@ -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;

@ -44,13 +44,12 @@
{
CCountedPtr<Text> 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<Text> 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)

@ -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.

@ -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<Text> 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) {

@ -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

@ -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);

@ -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);

@ -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 */

@ -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.

@ -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;

@ -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<char *>(label));
/* !Contains() prevents stredup from leaking. */

@ -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);

@ -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);
}
/**

Loading…
Cancel
Save