Merge branch 'master' into jgrpp

# Conflicts:
#	src/cheat_gui.cpp
#	src/command.cpp
#	src/command_func.h
#	src/company_base.h
#	src/debug.cpp
#	src/debug.h
#	src/economy.cpp
#	src/engine_type.h
#	src/graph_gui.cpp
#	src/misc_cmd.cpp
#	src/misc_cmd.h
#	src/network/core/os_abstraction.cpp
#	src/openttd.cpp
#	src/saveload/saveload.cpp
#	src/saveload/saveload.h
#	src/settings_type.h
#	src/ship_cmd.cpp
#	src/stdafx.h
#	src/tests/bitmath_func.cpp
#	src/town_cmd.cpp
#	src/town_gui.cpp
pull/661/head
Jonathan G Rennison 3 months ago
commit 838b166726

@ -23,16 +23,28 @@ install(TARGETS openttd
COMPONENT Runtime COMPONENT Runtime
) )
install(DIRECTORY if (NOT EMSCRIPTEN)
${CMAKE_BINARY_DIR}/lang # Emscripten embeds these files in openttd.data.
${CMAKE_BINARY_DIR}/baseset # See CMakeLists.txt in the root.
${CMAKE_BINARY_DIR}/ai install(DIRECTORY
${CMAKE_BINARY_DIR}/game ${CMAKE_BINARY_DIR}/lang
${CMAKE_SOURCE_DIR}/bin/scripts ${CMAKE_BINARY_DIR}/baseset
DESTINATION ${DATA_DESTINATION_DIR} ${CMAKE_BINARY_DIR}/ai
COMPONENT language_files ${CMAKE_BINARY_DIR}/game
REGEX "ai/[^\.]+$" EXCLUDE # Ignore subdirs in ai dir ${CMAKE_SOURCE_DIR}/bin/scripts
) DESTINATION ${DATA_DESTINATION_DIR}
COMPONENT language_files
REGEX "ai/[^\.]+$" EXCLUDE # Ignore subdirs in ai dir
)
else()
install(FILES
${CMAKE_BINARY_DIR}/openttd.js
${CMAKE_BINARY_DIR}/openttd.wasm
${CMAKE_BINARY_DIR}/openttd.data
DESTINATION ${BINARY_DESTINATION_DIR}
COMPONENT Runtime
)
endif()
install(FILES install(FILES
${CMAKE_SOURCE_DIR}/COPYING.md ${CMAKE_SOURCE_DIR}/COPYING.md
@ -80,7 +92,7 @@ if(OPTION_INSTALL_FHS)
COMPONENT manual) COMPONENT manual)
endif() endif()
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN)
install(DIRECTORY install(DIRECTORY
${CMAKE_BINARY_DIR}/media/icons ${CMAKE_BINARY_DIR}/media/icons
${CMAKE_BINARY_DIR}/media/pixmaps ${CMAKE_BINARY_DIR}/media/pixmaps

@ -34,7 +34,6 @@ execute_process(COMMAND ${OPENTTD_EXECUTABLE}
-mnull -mnull
-vnull:ticks=30000 -vnull:ticks=30000
-d script=2 -d script=2
-d misc=9
-Q -Q
OUTPUT_VARIABLE REGRESSION_OUTPUT OUTPUT_VARIABLE REGRESSION_OUTPUT
ERROR_VARIABLE REGRESSION_RESULT ERROR_VARIABLE REGRESSION_RESULT
@ -58,16 +57,24 @@ string(REPLACE "0x0x0" "0x00000000" REGRESSION_RESULT "${REGRESSION_RESULT}")
string(REPLACE "\\" "/" REGRESSION_RESULT "${REGRESSION_RESULT}") string(REPLACE "\\" "/" REGRESSION_RESULT "${REGRESSION_RESULT}")
# Remove timestamps if any # Remove timestamps if any
string(REGEX REPLACE "\[[0-9-]+ [0-9:]+\] " "" REGRESSION_RESULT "${REGRESSION_RESULT}") string(REGEX REPLACE "\\\[[0-9-]+ [0-9:]+\\\] " "" REGRESSION_RESULT "${REGRESSION_RESULT}")
# Remove log level
string(REGEX REPLACE "\\\[script:[0-9]\\\]" "" REGRESSION_RESULT "${REGRESSION_RESULT}")
# Convert the output to a format that is expected (and more readable) by result.txt # Convert the output to a format that is expected (and more readable) by result.txt
string(REPLACE "\ndbg: [script]" "\n" REGRESSION_RESULT "${REGRESSION_RESULT}") string(REPLACE "\ndbg: " "\n" REGRESSION_RESULT "${REGRESSION_RESULT}")
string(REPLACE "\n " "\nERROR: " REGRESSION_RESULT "${REGRESSION_RESULT}") string(REPLACE "\n " "\nERROR: " REGRESSION_RESULT "${REGRESSION_RESULT}")
string(REPLACE "\nERROR: [1] " "\n" REGRESSION_RESULT "${REGRESSION_RESULT}") string(REPLACE "\nERROR: [1] " "\n" REGRESSION_RESULT "${REGRESSION_RESULT}")
string(REPLACE "\n[P] " "\n" REGRESSION_RESULT "${REGRESSION_RESULT}") string(REPLACE "\n[P] " "\n" REGRESSION_RESULT "${REGRESSION_RESULT}")
string(REPLACE "\n[S] " "\n" REGRESSION_RESULT "${REGRESSION_RESULT}") string(REPLACE "\n[S] " "\n" REGRESSION_RESULT "${REGRESSION_RESULT}")
string(REGEX REPLACE "dbg: ([^\n]*)\n?" "" REGRESSION_RESULT "${REGRESSION_RESULT}") string(REGEX REPLACE "dbg: ([^\n]*)\n?" "" REGRESSION_RESULT "${REGRESSION_RESULT}")
# Remove duplicate script info
string(REGEX REPLACE "ERROR: Registering([^\n]*)\n?" "" REGRESSION_RESULT "${REGRESSION_RESULT}")
string(REGEX REPLACE "ERROR: [12]([^\n]*)\n?" "" REGRESSION_RESULT "${REGRESSION_RESULT}")
string(REGEX REPLACE "ERROR: The first([^\n]*)\n?" "" REGRESSION_RESULT "${REGRESSION_RESULT}")
# Read the expected result # Read the expected result
file(READ ai/${REGRESSION_TEST}/result.txt REGRESSION_EXPECTED) file(READ ai/${REGRESSION_TEST}/result.txt REGRESSION_EXPECTED)
@ -91,7 +98,7 @@ foreach(RESULT IN LISTS REGRESSION_RESULT)
if(NOT RESULT STREQUAL EXPECTED) if(NOT RESULT STREQUAL EXPECTED)
message("${ARGC}: - ${EXPECTED}") message("${ARGC}: - ${EXPECTED}")
message("${ARGC}: + ${RESULT}'") message("${ARGC}: + ${RESULT}")
set(ERROR YES) set(ERROR YES)
endif() endif()
endforeach() endforeach()

@ -1,4 +1,3 @@
--TestInit-- --TestInit--
Ops: 9988 Ops: 9988
TickTest: 1 TickTest: 1

@ -1,4 +1,3 @@
--StationList-- --StationList--
Count(): 5 Count(): 5
Location ListDump: Location ListDump:

@ -50,6 +50,7 @@
/* Load default data and store the name in the settings */ /* Load default data and store the name in the settings */
config->Change(info->GetName(), -1, false, true); config->Change(info->GetName(), -1, false, true);
} }
if (rerandomise_ai) config->AddRandomDeviation();
config->AnchorUnchangeableSettings(); config->AnchorUnchangeableSettings();
Backup<CompanyID> cur_company(_current_company, company, FILE_LINE); Backup<CompanyID> cur_company(_current_company, company, FILE_LINE);

@ -180,8 +180,16 @@ struct AIConfigWindow : public Window {
} else { } else {
text = STR_AI_CONFIG_RANDOM_AI; text = STR_AI_CONFIG_RANDOM_AI;
} }
DrawString(tr, text,
(this->selected_slot == i) ? TC_WHITE : (IsEditable((CompanyID)i) ? TC_ORANGE : TC_SILVER)); TextColour tc = TC_SILVER;
if (this->selected_slot == i) {
tc = TC_WHITE;
} else if (IsEditable((CompanyID)i)) {
tc = TC_ORANGE;
} else if (Company::IsValidAiID(i)) {
tc = TC_GREEN;
}
DrawString(tr, text, tc);
tr.top += this->line_height; tr.top += this->line_height;
} }
break; break;
@ -228,7 +236,7 @@ struct AIConfigWindow : public Window {
case WID_AIC_LIST: { // Select a slot case WID_AIC_LIST: { // Select a slot
this->selected_slot = (CompanyID)this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget); this->selected_slot = (CompanyID)this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget);
this->InvalidateData(); this->InvalidateData();
if (click_count > 1 && this->selected_slot != INVALID_COMPANY) ShowScriptListWindow((CompanyID)this->selected_slot, _ctrl_pressed); if (click_count > 1 && IsEditable(this->selected_slot)) ShowScriptListWindow((CompanyID)this->selected_slot, _ctrl_pressed);
break; break;
} }
@ -258,7 +266,7 @@ struct AIConfigWindow : public Window {
} }
case WID_AIC_CHANGE: // choose other AI case WID_AIC_CHANGE: // choose other AI
ShowScriptListWindow((CompanyID)this->selected_slot, _ctrl_pressed); if (IsEditable(this->selected_slot)) ShowScriptListWindow((CompanyID)this->selected_slot, _ctrl_pressed);
break; break;
case WID_AIC_CONFIGURE: // change the settings for an AI case WID_AIC_CONFIGURE: // change the settings for an AI
@ -282,7 +290,7 @@ struct AIConfigWindow : public Window {
*/ */
void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
{ {
if (!IsEditable(this->selected_slot)) { if (!IsEditable(this->selected_slot) && !Company::IsValidAiID(this->selected_slot)) {
this->selected_slot = INVALID_COMPANY; this->selected_slot = INVALID_COMPANY;
} }
@ -294,10 +302,10 @@ struct AIConfigWindow : public Window {
this->SetWidgetDisabledState(WID_AIC_INCREASE_NUMBER, GetGameSettings().difficulty.max_no_competitors == MAX_COMPANIES - 1); this->SetWidgetDisabledState(WID_AIC_INCREASE_NUMBER, GetGameSettings().difficulty.max_no_competitors == MAX_COMPANIES - 1);
this->SetWidgetDisabledState(WID_AIC_DECREASE_INTERVAL, GetGameSettings().difficulty.competitors_interval == MIN_COMPETITORS_INTERVAL); this->SetWidgetDisabledState(WID_AIC_DECREASE_INTERVAL, GetGameSettings().difficulty.competitors_interval == MIN_COMPETITORS_INTERVAL);
this->SetWidgetDisabledState(WID_AIC_INCREASE_INTERVAL, GetGameSettings().difficulty.competitors_interval == MAX_COMPETITORS_INTERVAL); this->SetWidgetDisabledState(WID_AIC_INCREASE_INTERVAL, GetGameSettings().difficulty.competitors_interval == MAX_COMPETITORS_INTERVAL);
this->SetWidgetDisabledState(WID_AIC_CHANGE, this->selected_slot == INVALID_COMPANY); this->SetWidgetDisabledState(WID_AIC_CHANGE, !IsEditable(this->selected_slot));
this->SetWidgetDisabledState(WID_AIC_CONFIGURE, this->selected_slot == INVALID_COMPANY || config->GetConfigList()->empty()); this->SetWidgetDisabledState(WID_AIC_CONFIGURE, this->selected_slot == INVALID_COMPANY || config->GetConfigList()->empty());
this->SetWidgetDisabledState(WID_AIC_MOVE_UP, this->selected_slot == INVALID_COMPANY || !IsEditable((CompanyID)(this->selected_slot - 1))); this->SetWidgetDisabledState(WID_AIC_MOVE_UP, !IsEditable(this->selected_slot) || !IsEditable((CompanyID)(this->selected_slot - 1)));
this->SetWidgetDisabledState(WID_AIC_MOVE_DOWN, this->selected_slot == INVALID_COMPANY || !IsEditable((CompanyID)(this->selected_slot + 1))); this->SetWidgetDisabledState(WID_AIC_MOVE_DOWN, !IsEditable(this->selected_slot) || !IsEditable((CompanyID)(this->selected_slot + 1)));
this->SetWidgetDisabledState(WID_AIC_OPEN_URL, this->selected_slot == INVALID_COMPANY || config->GetInfo() == nullptr || config->GetInfo()->GetURL().empty()); this->SetWidgetDisabledState(WID_AIC_OPEN_URL, this->selected_slot == INVALID_COMPANY || config->GetInfo() == nullptr || config->GetInfo()->GetURL().empty());
for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) { for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) {

@ -45,14 +45,14 @@ template <> const char *GetClassName<AIInfo, ScriptType::AI>() { return "AIInfo"
SQAIInfo.DefSQAdvancedMethod(engine, &AIInfo::AddSetting, "AddSetting"); SQAIInfo.DefSQAdvancedMethod(engine, &AIInfo::AddSetting, "AddSetting");
SQAIInfo.DefSQAdvancedMethod(engine, &AIInfo::AddLabels, "AddLabels"); SQAIInfo.DefSQAdvancedMethod(engine, &AIInfo::AddLabels, "AddLabels");
SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_NONE, "CONFIG_NONE"); SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_NONE, "CONFIG_NONE");
SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_RANDOM, "CONFIG_RANDOM"); SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_NONE, "CONFIG_RANDOM"); // Deprecated, mapped to NONE.
SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_BOOLEAN, "CONFIG_BOOLEAN"); SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_BOOLEAN, "CONFIG_BOOLEAN");
SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_INGAME, "CONFIG_INGAME"); SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_INGAME, "CONFIG_INGAME");
SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_DEVELOPER, "CONFIG_DEVELOPER"); SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_DEVELOPER, "CONFIG_DEVELOPER");
/* Pre 1.2 had an AI prefix */ /* Pre 1.2 had an AI prefix */
SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_NONE, "AICONFIG_NONE"); SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_NONE, "AICONFIG_NONE");
SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_RANDOM, "AICONFIG_RANDOM"); SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_NONE, "AICONFIG_RANDOM"); // Deprecated, mapped to NONE.
SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_BOOLEAN, "AICONFIG_BOOLEAN"); SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_BOOLEAN, "AICONFIG_BOOLEAN");
SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_INGAME, "AICONFIG_INGAME"); SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_INGAME, "AICONFIG_INGAME");

@ -129,6 +129,7 @@ CommandProc CmdSetCompanyColour;
CommandProc CmdIncreaseLoan; CommandProc CmdIncreaseLoan;
CommandProc CmdDecreaseLoan; CommandProc CmdDecreaseLoan;
CommandProcEx CmdSetCompanyMaxLoan;
CommandProc CmdWantEnginePreview; CommandProc CmdWantEnginePreview;
CommandProc CmdEngineCtrl; CommandProc CmdEngineCtrl;
@ -397,6 +398,7 @@ static const Command _command_proc_table[] = {
DEF_CMD(CmdIncreaseLoan, 0, CMDT_MONEY_MANAGEMENT ), // CMD_INCREASE_LOAN DEF_CMD(CmdIncreaseLoan, 0, CMDT_MONEY_MANAGEMENT ), // CMD_INCREASE_LOAN
DEF_CMD(CmdDecreaseLoan, 0, CMDT_MONEY_MANAGEMENT ), // CMD_DECREASE_LOAN DEF_CMD(CmdDecreaseLoan, 0, CMDT_MONEY_MANAGEMENT ), // CMD_DECREASE_LOAN
DEF_CMD(CmdSetCompanyMaxLoan, CMD_DEITY, CMDT_MONEY_MANAGEMENT ), // CMD_SET_COMPANY_MAX_LOAN
DEF_CMD(CmdWantEnginePreview, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_WANT_ENGINE_PREVIEW DEF_CMD(CmdWantEnginePreview, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_WANT_ENGINE_PREVIEW
DEF_CMD(CmdEngineCtrl, CMD_DEITY, CMDT_VEHICLE_MANAGEMENT ), // CMD_ENGINE_CTRL DEF_CMD(CmdEngineCtrl, CMD_DEITY, CMDT_VEHICLE_MANAGEMENT ), // CMD_ENGINE_CTRL
@ -732,7 +734,7 @@ char *DumpCommandLog(char *buffer, const char *last, std::function<char *(char *
return buffer; return buffer;
} }
/*! /**
* This function range-checks a cmd, and checks if the cmd is not nullptr * This function range-checks a cmd, and checks if the cmd is not nullptr
* *
* @param cmd The integer value of a command * @param cmd The integer value of a command
@ -745,7 +747,7 @@ bool IsValidCommand(uint32_t cmd)
return cmd < lengthof(_command_proc_table) && _command_proc_table[cmd].proc != nullptr; return cmd < lengthof(_command_proc_table) && _command_proc_table[cmd].proc != nullptr;
} }
/*! /**
* This function mask the parameter with CMD_ID_MASK and returns * This function mask the parameter with CMD_ID_MASK and returns
* the flags which belongs to the given command. * the flags which belongs to the given command.
* *
@ -759,7 +761,7 @@ CommandFlags GetCommandFlags(uint32_t cmd)
return _command_proc_table[cmd & CMD_ID_MASK].flags; return _command_proc_table[cmd & CMD_ID_MASK].flags;
} }
/*! /**
* This function mask the parameter with CMD_ID_MASK and returns * This function mask the parameter with CMD_ID_MASK and returns
* the name which belongs to the given command. * the name which belongs to the given command.
* *
@ -819,7 +821,7 @@ private:
char buffer[64]; char buffer[64];
}; };
/*! /**
* This function executes a given command with the parameters from the #CommandProc parameter list. * This function executes a given command with the parameters from the #CommandProc parameter list.
* Depending on the flags parameter it execute or test a command. * Depending on the flags parameter it execute or test a command.
* *
@ -889,20 +891,6 @@ error:
return res; return res;
} }
/*!
* This functions returns the money which can be used to execute a command.
* This is either the money of the current company or INT64_MAX if there
* is no such a company "at the moment" like the server itself.
*
* @return The available money of a company or INT64_MAX
*/
Money GetAvailableMoneyForCommand()
{
CompanyID company = _current_company;
if (!Company::IsValidID(company)) return INT64_MAX;
return Company::Get(company)->money;
}
static void DebugLogCommandLogEntry(const CommandLogEntry &entry) static void DebugLogCommandLogEntry(const CommandLogEntry &entry)
{ {
if (_debug_command_level <= 0) return; if (_debug_command_level <= 0) return;
@ -910,7 +898,7 @@ static void DebugLogCommandLogEntry(const CommandLogEntry &entry)
char buffer[256]; char buffer[256];
char *b = buffer; char *b = buffer;
DumpSubCommandLogEntry(b, lastof(buffer), entry); DumpSubCommandLogEntry(b, lastof(buffer), entry);
debug_print("command", buffer); debug_print("command", 0, buffer);
} }
static void AppendCommandLogEntry(const CommandCost &res, TileIndex tile, uint32_t p1, uint32_t p2, uint64_t p3, uint32_t cmd, CommandLogEntryFlag log_flags, const char *text) static void AppendCommandLogEntry(const CommandCost &res, TileIndex tile, uint32_t p1, uint32_t p2, uint64_t p3, uint32_t cmd, CommandLogEntryFlag log_flags, const char *text)
@ -949,7 +937,7 @@ static void AppendCommandLogEntry(const CommandCost &res, TileIndex tile, uint32
cmd_log.count++; cmd_log.count++;
} }
/*! /**
* Toplevel network safe docommand function for the current company. Must not be called recursively. * Toplevel network safe docommand function for the current company. Must not be called recursively.
* The callback is called when the command succeeded or failed. The parameters * The callback is called when the command succeeded or failed. The parameters
* \a tile, \a p1, and \a p2 are from the #CommandProc function. The parameter \a cmd is the command to execute. * \a tile, \a p1, and \a p2 are from the #CommandProc function. The parameter \a cmd is the command to execute.
@ -1107,7 +1095,7 @@ void EnqueueDoCommandP(CommandContainer cmd)
*/ */
#define return_dcpi(cmd) { _docommand_recursive = 0; return cmd; } #define return_dcpi(cmd) { _docommand_recursive = 0; return cmd; }
/*! /**
* Helper function for the toplevel network safe docommand function for the current company. * Helper function for the toplevel network safe docommand function for the current company.
* *
* @param tile The tile to perform a command on (see #CommandProc) * @param tile The tile to perform a command on (see #CommandProc)
@ -1206,7 +1194,7 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32_t p1, uint32_t p2, uint64_
std::string dbg_info = stdstr_fmt("%s: %s; company: %02x; tile: %06x (%u x %u); p1: %08x; p2: %08x; p3: " OTTD_PRINTFHEX64PAD "; cmd: %08x; %s <%s> (%s)", std::string dbg_info = stdstr_fmt("%s: %s; company: %02x; tile: %06x (%u x %u); p1: %08x; p2: %08x; p3: " OTTD_PRINTFHEX64PAD "; cmd: %08x; %s <%s> (%s)",
prefix, debug_date_dumper().HexDate(), (int)_current_company, tile, TileX(tile), TileY(tile), p1, p2, p3, prefix, debug_date_dumper().HexDate(), (int)_current_company, tile, TileX(tile), TileY(tile), p1, p2, p3,
cmd & ~CMD_NETWORK_COMMAND, text_buf.c_str(), aux_str.c_str(), GetCommandName(cmd)); cmd & ~CMD_NETWORK_COMMAND, text_buf.c_str(), aux_str.c_str(), GetCommandName(cmd));
debug_print("desync", dbg_info.c_str()); debug_print("desync", 1, dbg_info.c_str());
} }
}; };

@ -65,7 +65,6 @@ extern Money _additional_cash_required;
bool IsValidCommand(uint32_t cmd); bool IsValidCommand(uint32_t cmd);
CommandFlags GetCommandFlags(uint32_t cmd); CommandFlags GetCommandFlags(uint32_t cmd);
const char *GetCommandName(uint32_t cmd); const char *GetCommandName(uint32_t cmd);
Money GetAvailableMoneyForCommand();
bool IsCommandAllowedWhilePaused(uint32_t cmd); bool IsCommandAllowedWhilePaused(uint32_t cmd);
/** /**

@ -348,6 +348,7 @@ enum Commands {
CMD_INCREASE_LOAN, ///< increase the loan from the bank CMD_INCREASE_LOAN, ///< increase the loan from the bank
CMD_DECREASE_LOAN, ///< decrease the loan from the bank CMD_DECREASE_LOAN, ///< decrease the loan from the bank
CMD_SET_COMPANY_MAX_LOAN, ///< sets the max loan for the company
CMD_WANT_ENGINE_PREVIEW, ///< confirm the preview of an engine CMD_WANT_ENGINE_PREVIEW, ///< confirm the preview of an engine
CMD_ENGINE_CTRL, ///< control availability of the engine for companies CMD_ENGINE_CTRL, ///< control availability of the engine for companies

@ -19,6 +19,8 @@
#include <array> #include <array>
#include <string> #include <string>
static const Money COMPANY_MAX_LOAN_DEFAULT = INT64_MIN;
/** Statistics about the economy. */ /** Statistics about the economy. */
struct CompanyEconomyEntry { struct CompanyEconomyEntry {
Money income; ///< The amount of income. Money income; ///< The amount of income.
@ -60,7 +62,6 @@ DECLARE_ENUM_AS_BIT_SET(CompanyBankruptcyFlags)
typedef Pool<Company, CompanyID, 1, MAX_COMPANIES> CompanyPool; typedef Pool<Company, CompanyID, 1, MAX_COMPANIES> CompanyPool;
extern CompanyPool _company_pool; extern CompanyPool _company_pool;
/** Statically loadable part of Company pool item */ /** Statically loadable part of Company pool item */
struct CompanyProperties { struct CompanyProperties {
uint32_t name_2; ///< Parameter of #name_1. uint32_t name_2; ///< Parameter of #name_1.
@ -76,6 +77,7 @@ struct CompanyProperties {
Money money; ///< Money owned by the company. Money money; ///< Money owned by the company.
byte money_fraction; ///< Fraction of money of the company, too small to represent in #money. byte money_fraction; ///< Fraction of money of the company, too small to represent in #money.
Money current_loan; ///< Amount of money borrowed from the bank. Money current_loan; ///< Amount of money borrowed from the bank.
Money max_loan; ///< Max allowed amount of the loan or COMPANY_MAX_LOAN_DEFAULT.
Colours colour; ///< Company colour. Colours colour; ///< Company colour.
@ -120,8 +122,8 @@ struct CompanyProperties {
// TODO: Change some of these member variables to use relevant INVALID_xxx constants // TODO: Change some of these member variables to use relevant INVALID_xxx constants
CompanyProperties() CompanyProperties()
: name_2(0), name_1(0), president_name_1(0), president_name_2(0), : name_2(0), name_1(0), president_name_1(0), president_name_2(0),
face(0), money(0), money_fraction(0), current_loan(0), colour(COLOUR_BEGIN), block_preview(0), face(0), money(0), money_fraction(0), current_loan(0), max_loan(COMPANY_MAX_LOAN_DEFAULT), colour(COLOUR_BEGIN),
location_of_HQ(0), last_build_coordinate(0), share_owners(), inaugurated_year(0), block_preview(0), location_of_HQ(0), last_build_coordinate(0), share_owners(), inaugurated_year(0),
months_of_bankruptcy(0), bankrupt_last_asked(INVALID_COMPANY), bankrupt_flags(CBRF_NONE), bankrupt_asked(0), bankrupt_timeout(0), bankrupt_value(0), months_of_bankruptcy(0), bankrupt_last_asked(INVALID_COMPANY), bankrupt_flags(CBRF_NONE), bankrupt_asked(0), bankrupt_timeout(0), bankrupt_value(0),
terraform_limit(0), clear_limit(0), tree_limit(0), purchase_land_limit(0), build_object_limit(0), is_ai(false), engine_renew_list(nullptr) {} terraform_limit(0), clear_limit(0), tree_limit(0), purchase_land_limit(0), build_object_limit(0), is_ai(false), engine_renew_list(nullptr) {}
}; };
@ -141,6 +143,8 @@ struct Company : CompanyPool::PoolItem<&_company_pool>, CompanyProperties {
CompanyInfrastructure infrastructure; ///< NOSAVE: Counts of company owned infrastructure. CompanyInfrastructure infrastructure; ///< NOSAVE: Counts of company owned infrastructure.
Money GetMaxLoan() const;
/** /**
* Is this company a valid company, controlled by the computer (a NoAI program)? * Is this company a valid company, controlled by the computer (a NoAI program)?
* @param index Index in the pool. * @param index Index in the pool.

@ -109,6 +109,16 @@ void Company::PostDestructor(size_t index)
InvalidateWindowData(WC_ERRMSG, 0); InvalidateWindowData(WC_ERRMSG, 0);
} }
/**
* Calculate the max allowed loan for this company.
* @return the max loan amount.
*/
Money Company::GetMaxLoan() const
{
if (this->max_loan == COMPANY_MAX_LOAN_DEFAULT) return _economy.max_loan;
return this->max_loan;
}
/** /**
* Sets the local company and updates the settings that are set on a * Sets the local company and updates the settings that are set on a
* per-company basis to reflect the core's state in the GUI. * per-company basis to reflect the core's state in the GUI.
@ -216,20 +226,48 @@ void InvalidateCompanyWindows(const Company *company)
SetWindowDirty(WC_FINANCES, cid); SetWindowDirty(WC_FINANCES, cid);
} }
/**
* Get the amount of money that a company has available, or INT64_MAX
* if there is no such valid company.
*
* @param company Company to check
* @return The available money of the company or INT64_MAX
*/
Money GetAvailableMoney(CompanyID company)
{
if (_settings_game.difficulty.infinite_money) return INT64_MAX;
if (!Company::IsValidID(company)) return INT64_MAX;
return Company::Get(company)->money;
}
/**
* This functions returns the money which can be used to execute a command.
* This is either the money of the current company, or INT64_MAX if infinite money
* is enabled or there is no such a company "at the moment" like the server itself.
*
* @return The available money of the current company or INT64_MAX
*/
Money GetAvailableMoneyForCommand()
{
return GetAvailableMoney(_current_company);
}
/** /**
* Verify whether the company can pay the bill. * Verify whether the company can pay the bill.
* @param[in,out] cost Money to pay, is changed to an error if the company does not have enough money. * @param[in,out] cost Money to pay, is changed to an error if the company does not have enough money.
* @return Function returns \c true if the company has enough money, else it returns \c false. * @return Function returns \c true if the company has enough money or infinite money is enabled,
* else it returns \c false.
*/ */
bool CheckCompanyHasMoney(CommandCost &cost) bool CheckCompanyHasMoney(CommandCost &cost)
{ {
if (cost.GetCost() > 0) { if (cost.GetCost() <= 0) return true;
const Company *c = Company::GetIfValid(_current_company); if (_settings_game.difficulty.infinite_money) return true;
if (c != nullptr && cost.GetCost() > c->money) {
SetDParam(0, cost.GetCost()); const Company *c = Company::GetIfValid(_current_company);
cost.MakeError(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY); if (c != nullptr && cost.GetCost() > c->money) {
return false; SetDParam(0, cost.GetCost());
} cost.MakeError(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY);
return false;
} }
return true; return true;
} }

@ -26,6 +26,8 @@ void CompanyAdminBankrupt(CompanyID company_id);
void UpdateLandscapingLimits(); void UpdateLandscapingLimits();
void UpdateCompanyLiveries(Company *c); void UpdateCompanyLiveries(Company *c);
Money GetAvailableMoney(CompanyID company);
Money GetAvailableMoneyForCommand();
bool CheckCompanyHasMoney(CommandCost &cost); bool CheckCompanyHasMoney(CommandCost &cost);
void SubtractMoneyFromCompany(const CommandCost &cost); void SubtractMoneyFromCompany(const CommandCost &cost);
void SubtractMoneyFromCompanyFract(CompanyID company, const CommandCost &cost); void SubtractMoneyFromCompanyFract(CompanyID company, const CommandCost &cost);

@ -373,9 +373,11 @@ struct CompanyFinancesWindow : Window {
SetDParam(0, _settings_game.difficulty.initial_interest); SetDParam(0, _settings_game.difficulty.initial_interest);
break; break;
case WID_CF_MAXLOAN_VALUE: case WID_CF_MAXLOAN_VALUE: {
SetDParam(0, _economy.max_loan); const Company *c = Company::Get((CompanyID)this->window_number);
SetDParam(0, c->GetMaxLoan());
break; break;
}
case WID_CF_INCREASE_LOAN: case WID_CF_INCREASE_LOAN:
case WID_CF_REPAY_LOAN: case WID_CF_REPAY_LOAN:
@ -473,7 +475,7 @@ struct CompanyFinancesWindow : Window {
} }
const Company *c = Company::Get(company); const Company *c = Company::Get(company);
this->SetWidgetDisabledState(WID_CF_INCREASE_LOAN, c->current_loan == _economy.max_loan); // Borrow button only shows when there is any more money to loan. this->SetWidgetDisabledState(WID_CF_INCREASE_LOAN, c->current_loan >= c->GetMaxLoan()); // Borrow button only shows when there is any more money to loan.
this->SetWidgetDisabledState(WID_CF_REPAY_LOAN, company != _local_company || c->current_loan == 0); // Repay button only shows when there is any more money to repay. this->SetWidgetDisabledState(WID_CF_REPAY_LOAN, company != _local_company || c->current_loan == 0); // Repay button only shows when there is any more money to repay.
} }

@ -36,8 +36,9 @@
/** Element in the queue of debug messages that have to be passed to either NetworkAdminConsole or IConsolePrint.*/ /** Element in the queue of debug messages that have to be passed to either NetworkAdminConsole or IConsolePrint.*/
struct QueuedDebugItem { struct QueuedDebugItem {
std::string level; ///< The used debug level. std::string category; ///< The used debug category.
std::string message; ///< The actual formatted message. int level; ///< The used debug level.
std::string message; ///< The actual formatted message.
}; };
std::atomic<bool> _debug_remote_console; ///< Whether we need to send data to either NetworkAdminConsole or IConsolePrint. std::atomic<bool> _debug_remote_console; ///< Whether we need to send data to either NetworkAdminConsole or IConsolePrint.
std::mutex _debug_remote_console_mutex; ///< Mutex to guard the queue of debug messages for either NetworkAdminConsole or IConsolePrint. std::mutex _debug_remote_console_mutex; ///< Mutex to guard the queue of debug messages for either NetworkAdminConsole or IConsolePrint.
@ -136,15 +137,16 @@ char *DumpDebugFacilityNames(char *buf, char *last)
/** /**
* Internal function for outputting the debug line. * Internal function for outputting the debug line.
* @param dbg Debug category. * @param dbg Debug category.
* @param level Debug level.
* @param buf Text line to output. * @param buf Text line to output.
*/ */
void debug_print(const char *dbg, const char *buf) void debug_print(const char *dbg, int level, const char *buf)
{ {
if (strcmp(dbg, "desync") == 0) { if (strcmp(dbg, "desync") == 0) {
static FILE *f = FioFOpenFile("commands-out.log", "wb", AUTOSAVE_DIR); static FILE *f = FioFOpenFile("commands-out.log", "wb", AUTOSAVE_DIR);
if (f != nullptr) { if (f != nullptr) {
fprintf(f, "%s%s\n", log_prefix().GetLogPrefix(), buf); fprintf(f, "%s%s\n", log_prefix().GetLogPrefix(true), buf);
fflush(f); fflush(f);
} }
#ifdef RANDOM_DEBUG #ifdef RANDOM_DEBUG
@ -178,7 +180,7 @@ void debug_print(const char *dbg, const char *buf)
} }
char buffer[512]; char buffer[512];
seprintf(buffer, lastof(buffer), "%sdbg: [%s] %s\n", log_prefix().GetLogPrefix(), dbg, buf); seprintf(buffer, lastof(buffer), "%sdbg: [%s:%d] %s\n", log_prefix().GetLogPrefix(), dbg, level, buf);
str_strip_colours(buffer); str_strip_colours(buffer);
@ -197,10 +199,10 @@ void debug_print(const char *dbg, const char *buf)
/* Only add to the queue when there is at least one consumer of the data. */ /* Only add to the queue when there is at least one consumer of the data. */
if (IsNonGameThread()) { if (IsNonGameThread()) {
std::lock_guard<std::mutex> lock(_debug_remote_console_mutex); std::lock_guard<std::mutex> lock(_debug_remote_console_mutex);
_debug_remote_console_queue.push_back({ dbg, buf }); _debug_remote_console_queue.push_back({ dbg, level, buf });
} else { } else {
NetworkAdminConsole(dbg, buf); NetworkAdminConsole(dbg, buf);
if (_settings_client.gui.developer >= 2) IConsolePrintF(CC_DEBUG, "dbg: [%s] %s", dbg, buf); if (_settings_client.gui.developer >= 2) IConsolePrintF(CC_DEBUG, "dbg: [%s:%d] %s", dbg, level, buf);
} }
} }
} }
@ -211,7 +213,7 @@ void debug_print(const char *dbg, const char *buf)
* @param dbg Debug category. * @param dbg Debug category.
* @param format Text string a la printf, with optional arguments. * @param format Text string a la printf, with optional arguments.
*/ */
void CDECL debug(const char *dbg, const char *format, ...) void CDECL debug(const char *dbg, int level, const char *format, ...)
{ {
char buf[1024]; char buf[1024];
@ -220,7 +222,7 @@ void CDECL debug(const char *dbg, const char *format, ...)
vseprintf(buf, lastof(buf), format, va); vseprintf(buf, lastof(buf), format, va);
va_end(va); va_end(va);
debug_print(dbg, buf); debug_print(dbg, level, buf);
} }
/** /**
@ -306,13 +308,16 @@ std::string GetDebugString()
} }
/** /**
* Get the prefix for logs; if show_date_in_logs is enabled it returns * Get the prefix for logs.
* the date, otherwise it returns nothing. *
* @return the prefix for logs (do not free), never nullptr * If show_date_in_logs or \p force is enabled it returns
* the date, otherwise it returns an empty string.
*
* @return the prefix for logs (do not free), never nullptr.
*/ */
const char *log_prefix::GetLogPrefix() const char *log_prefix::GetLogPrefix(bool force)
{ {
if (_settings_client.gui.show_date_in_logs) { if (force || _settings_client.gui.show_date_in_logs) {
LocalTime::Format(this->buffer, lastof(this->buffer), "[%Y-%m-%d %H:%M:%S] "); LocalTime::Format(this->buffer, lastof(this->buffer), "[%Y-%m-%d %H:%M:%S] ");
} else { } else {
this->buffer[0] = '\0'; this->buffer[0] = '\0';
@ -430,8 +435,8 @@ void DebugSendRemoteMessages()
} }
for (auto &item : _debug_remote_console_queue_spare) { for (auto &item : _debug_remote_console_queue_spare) {
NetworkAdminConsole(item.level.c_str(), item.message.c_str()); NetworkAdminConsole(item.category.c_str(), item.message.c_str());
if (_settings_client.gui.developer >= 2) IConsolePrintF(CC_DEBUG, "dbg: [%s] %s", item.level.c_str(), item.message.c_str()); if (_settings_client.gui.developer >= 2) IConsolePrintF(CC_DEBUG, "dbg: [%s:%d] %s", item.category.c_str(), item.level, item.message.c_str());
} }
_debug_remote_console_queue_spare.clear(); _debug_remote_console_queue_spare.clear();

@ -33,7 +33,7 @@
* @param name Category * @param name Category
* @param level Debugging level, higher levels means more detailed information. * @param level Debugging level, higher levels means more detailed information.
*/ */
#define DEBUG(name, level, ...) if ((level) == 0 || _debug_ ## name ## _level >= (level)) debug(#name, __VA_ARGS__) #define DEBUG(name, level, ...) do { if ((level) == 0 || _debug_ ## name ## _level >= (level)) debug(#name, level, __VA_ARGS__); } while (false)
extern int _debug_driver_level; extern int _debug_driver_level;
extern int _debug_grf_level; extern int _debug_grf_level;
@ -64,8 +64,8 @@ extern std::string _loadgame_DBGL_data;
extern bool _save_DBGC_data; extern bool _save_DBGC_data;
extern std::string _loadgame_DBGC_data; extern std::string _loadgame_DBGC_data;
void CDECL debug(const char *dbg, const char *format, ...) WARN_FORMAT(2, 3); void CDECL debug(const char *dbg, int level, const char *format, ...) WARN_FORMAT(3, 4);
void debug_print(const char *dbg, const char *buf); void debug_print(const char *dbg, int level, const char *buf);
char *DumpDebugFacilityNames(char *buf, char *last); char *DumpDebugFacilityNames(char *buf, char *last);
void SetDebugString(const char *s, void (*error_func)(const char *)); void SetDebugString(const char *s, void (*error_func)(const char *));
@ -117,7 +117,7 @@ inline void ShowInfoI(const std::string &str)
} }
struct log_prefix { struct log_prefix {
const char *GetLogPrefix(); const char *GetLogPrefix(bool force = false);
private: private:
char buffer[24]; char buffer[24];

@ -21,7 +21,7 @@
* @param level The maximum debug level this message should be shown at. When the debug level for this category is set lower, then the message will not be shown. * @param level The maximum debug level this message should be shown at. When the debug level for this category is set lower, then the message will not be shown.
* @param format_string The formatting string of the message. * @param format_string The formatting string of the message.
*/ */
#define Debug(name, level, format_string, ...) if ((level) == 0 || _debug_ ## name ## _level >= (level)) debug_print(#name, fmt::format(FMT_STRING(format_string), ## __VA_ARGS__).c_str()) #define Debug(name, level, format_string, ...) do { if ((level) == 0 || _debug_ ## name ## _level >= (level)) debug_print(#name, level, fmt::format(FMT_STRING(format_string), ## __VA_ARGS__).c_str()); } while (false)
void NORETURN usererror_str(const char *msg); void NORETURN usererror_str(const char *msg);
void NORETURN fatalerror_str(const char *msg); void NORETURN fatalerror_str(const char *msg);

@ -680,9 +680,12 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner)
*/ */
static void CompanyCheckBankrupt(Company *c) static void CompanyCheckBankrupt(Company *c)
{ {
/* If "Infinite money" setting is on, companies should not go bankrupt. */
if (_settings_game.difficulty.infinite_money) return;
/* If the company has money again, it does not go bankrupt */ /* If the company has money again, it does not go bankrupt */
if (c->bankrupt_flags & CBRF_SALE) return; if (c->bankrupt_flags & CBRF_SALE) return;
if (c->money - c->current_loan >= -_economy.max_loan) { if (c->money - c->current_loan >= -c->GetMaxLoan()) {
int previous_months_of_bankruptcy = CeilDiv(c->months_of_bankruptcy, 3); int previous_months_of_bankruptcy = CeilDiv(c->months_of_bankruptcy, 3);
c->months_of_bankruptcy = 0; c->months_of_bankruptcy = 0;
c->bankrupt_asked = 0; c->bankrupt_asked = 0;
@ -952,17 +955,21 @@ static void CompaniesPayInterest()
/* Over a year the paid interest should be "loan * interest percentage", /* Over a year the paid interest should be "loan * interest percentage",
* but... as that number is likely not dividable by 12 (pay each month), * but... as that number is likely not dividable by 12 (pay each month),
* one needs to account for that in the monthly fee calculations. * one needs to account for that in the monthly fee calculations.
*
* To easily calculate what one should pay "this" month, you calculate * To easily calculate what one should pay "this" month, you calculate
* what (total) should have been paid up to this month and you subtract * what (total) should have been paid up to this month and you subtract
* whatever has been paid in the previous months. This will mean one month * whatever has been paid in the previous months. This will mean one month
* it'll be a bit more and the other it'll be a bit less than the average * it'll be a bit more and the other it'll be a bit less than the average
* monthly fee, but on average it will be exact. * monthly fee, but on average it will be exact.
*
* In order to prevent cheating or abuse (just not paying interest by not * In order to prevent cheating or abuse (just not paying interest by not
* taking a loan we make companies pay interest on negative cash as well * taking a loan) we make companies pay interest on negative cash as well,
* except if infinite money is enabled.
*/ */
Money yearly_fee = c->current_loan * _economy.interest_rate / 100; Money yearly_fee = c->current_loan * _economy.interest_rate / 100;
if (c->money < 0) { Money available_money = GetAvailableMoney(c->index);
yearly_fee += -c->money *_economy.interest_rate / 100; if (available_money < 0) {
yearly_fee += -available_money * _economy.interest_rate / 100;
} }
Money up_to_previous_month = yearly_fee * EconTime::CurMonth() / 12; Money up_to_previous_month = yearly_fee * EconTime::CurMonth() / 12;
Money up_to_this_month = yearly_fee * (EconTime::CurMonth() + 1) / 12; Money up_to_this_month = yearly_fee * (EconTime::CurMonth() + 1) / 12;

@ -205,6 +205,8 @@ struct PriceBaseSpec {
static const int LOAN_INTERVAL = 10000; static const int LOAN_INTERVAL = 10000;
/** The size of loan for a new company, in British Pounds! */ /** The size of loan for a new company, in British Pounds! */
static const int64_t INITIAL_LOAN = 100000; static const int64_t INITIAL_LOAN = 100000;
/** The max amount possible to configure for a max loan of a company. */
static const int64_t MAX_LOAN_LIMIT = 2000000000;
/** /**
* Maximum inflation (including fractional part) without causing overflows in int64_t price computations. * Maximum inflation (including fractional part) without causing overflows in int64_t price computations.

@ -85,6 +85,8 @@ Engine::Engine(VehicleType type, EngineID base)
if (type == VEH_ROAD) this->u.road.tractive_effort = 0x4C; if (type == VEH_ROAD) this->u.road.tractive_effort = 0x4C;
/* Aircraft must have INVALID_CARGO as default, as there is no property */ /* Aircraft must have INVALID_CARGO as default, as there is no property */
if (type == VEH_AIRCRAFT) this->info.cargo_type = INVALID_CARGO; if (type == VEH_AIRCRAFT) this->info.cargo_type = INVALID_CARGO;
/* Ships must have a non-zero acceleration. */
if (type == VEH_SHIP) this->u.ship.acceleration = 1;
/* Set visual effect to the default value */ /* Set visual effect to the default value */
switch (type) { switch (type) {
case VEH_TRAIN: this->u.rail.visual_effect = VE_DEFAULT; break; case VEH_TRAIN: this->u.rail.visual_effect = VE_DEFAULT; break;

@ -67,6 +67,7 @@ struct RailVehicleInfo {
struct ShipVehicleInfo { struct ShipVehicleInfo {
byte image_index; byte image_index;
byte cost_factor; byte cost_factor;
uint8_t acceleration; ///< Acceleration (1 unit = 1/3.2 mph per tick = 0.5 km-ish/h per tick)
uint16_t max_speed; ///< Maximum speed (1 unit = 1/3.2 mph = 0.5 km-ish/h) uint16_t max_speed; ///< Maximum speed (1 unit = 1/3.2 mph = 0.5 km-ish/h)
uint16_t capacity; uint16_t capacity;
byte running_cost; byte running_cost;

@ -211,26 +211,20 @@ public:
return pt; return pt;
} }
/* Find the free screen space between the main toolbar at the top, and the statusbar at the bottom. constexpr int distance_to_cursor = 200;
* Add a fixed distance 20 to make it less cluttered.
*/ /* Position the error window just above the cursor. This makes the
int scr_top = GetMainViewTop() + 20; * error window clearly visible, without being in the way of what
int scr_bot = GetMainViewBottom() - 20; * the user is doing. */
Point pt;
Point pt = RemapCoords(this->position.x, this->position.y, GetSlopePixelZOutsideMap(this->position.x, this->position.y)); pt.x = _cursor.pos.x - sm_width / 2;
const Viewport *vp = GetMainWindow()->viewport; pt.y = _cursor.pos.y - (distance_to_cursor + sm_height);
if (this->face == INVALID_COMPANY) {
/* move x pos to opposite corner */ if (pt.y < GetMainViewTop()) {
pt.x = UnScaleByZoom(pt.x - vp->virtual_left, vp->zoom) + vp->left; /* Window didn't fit above cursor, so place it below. */
pt.x = (pt.x < (_screen.width >> 1)) ? _screen.width - sm_width - 20 : 20; // Stay 20 pixels away from the edge of the screen. pt.y = _cursor.pos.y + distance_to_cursor;
/* move y pos to opposite corner */
pt.y = UnScaleByZoom(pt.y - vp->virtual_top, vp->zoom) + vp->top;
pt.y = (pt.y < (_screen.height >> 1)) ? scr_bot - sm_height : scr_top;
} else {
pt.x = std::min(std::max(UnScaleByZoom(pt.x - vp->virtual_left, vp->zoom) + vp->left - (sm_width / 2), 0), _screen.width - sm_width);
pt.y = std::min(std::max(UnScaleByZoom(pt.y - vp->virtual_top, vp->zoom) + vp->top - (sm_height / 2), scr_top), scr_bot - sm_height);
} }
return pt; return pt;
} }

@ -31,8 +31,9 @@ public:
/** /**
* Start up a new GameScript. * Start up a new GameScript.
* @param randomise Whether to randomise the configured GameScript.
*/ */
static void StartNew(); static void StartNew(bool randomise = true);
/** /**
* Uninitialize the Game system. * Uninitialize the Game system.

@ -69,7 +69,7 @@
} }
} }
/* static */ void Game::StartNew() /* static */ void Game::StartNew(bool randomise)
{ {
if (Game::instance != nullptr) return; if (Game::instance != nullptr) return;
@ -83,6 +83,7 @@
GameInfo *info = config->GetInfo(); GameInfo *info = config->GetInfo();
if (info == nullptr) return; if (info == nullptr) return;
if (randomise) config->AddRandomDeviation();
config->AnchorUnchangeableSettings(); config->AnchorUnchangeableSettings();
Backup<CompanyID> cur_company(_current_company, FILE_LINE); Backup<CompanyID> cur_company(_current_company, FILE_LINE);

@ -206,10 +206,18 @@ struct GSConfigWindow : public Window {
TextColour colour; TextColour colour;
uint idx = 0; uint idx = 0;
if (config_item.description.empty()) { if (config_item.description.empty()) {
str = STR_JUST_STRING1; if (Game::GetInstance() == nullptr && config_item.random_deviation != 0) {
str = STR_AI_SETTINGS_JUST_DEVIATION;
} else {
str = STR_JUST_STRING1;
}
colour = TC_ORANGE; colour = TC_ORANGE;
} else { } else {
str = STR_AI_SETTINGS_SETTING; if (Game::GetInstance() == nullptr && config_item.random_deviation != 0) {
str = STR_AI_SETTINGS_SETTING_DEVIATION;
} else {
str = STR_AI_SETTINGS_SETTING;
}
colour = TC_LIGHT_BLUE; colour = TC_LIGHT_BLUE;
SetDParamStr(idx++, config_item.description); SetDParamStr(idx++, config_item.description);
} }
@ -223,13 +231,36 @@ struct GSConfigWindow : public Window {
} else { } else {
DrawArrowButtons(br.left, y + button_y_offset, COLOUR_YELLOW, (this->clicked_button == i) ? 1 + (this->clicked_increase != rtl) : 0, editable && current_value > config_item.min_value, editable && current_value < config_item.max_value); DrawArrowButtons(br.left, y + button_y_offset, COLOUR_YELLOW, (this->clicked_button == i) ? 1 + (this->clicked_increase != rtl) : 0, editable && current_value > config_item.min_value, editable && current_value < config_item.max_value);
} }
auto config_iterator = config_item.labels.find(current_value);
if (config_iterator != config_item.labels.end()) { if (Game::GetInstance() != nullptr || config_item.random_deviation == 0) {
SetDParam(idx++, STR_JUST_RAW_STRING); auto config_iterator = config_item.labels.find(current_value);
SetDParamStr(idx++, config_iterator->second); if (config_iterator != config_item.labels.end()) {
SetDParam(idx++, STR_JUST_RAW_STRING);
SetDParamStr(idx++, config_iterator->second);
} else {
SetDParam(idx++, STR_JUST_INT);
SetDParam(idx++, current_value);
}
} else { } else {
SetDParam(idx++, STR_JUST_INT); int min_deviated = std::max(config_item.min_value, current_value - config_item.random_deviation);
SetDParam(idx++, current_value); auto config_iterator = config_item.labels.find(min_deviated);
if (config_iterator != config_item.labels.end()) {
SetDParam(idx++, STR_JUST_RAW_STRING);
SetDParamStr(idx++, config_iterator->second);
} else {
SetDParam(idx++, STR_JUST_INT);
SetDParam(idx++, min_deviated);
}
int max_deviated = std::min(config_item.max_value, current_value + config_item.random_deviation);
config_iterator = config_item.labels.find(max_deviated);
if (config_iterator != config_item.labels.end()) {
SetDParam(idx++, STR_JUST_RAW_STRING);
SetDParamStr(idx++, config_iterator->second);
} else {
SetDParam(idx++, STR_JUST_INT);
SetDParam(idx++, max_deviated);
}
} }
} }

@ -42,7 +42,7 @@ template <> const char *GetClassName<GameInfo, ScriptType::GS>() { return "GSInf
SQGSInfo.DefSQAdvancedMethod(engine, &GameInfo::AddSetting, "AddSetting"); SQGSInfo.DefSQAdvancedMethod(engine, &GameInfo::AddSetting, "AddSetting");
SQGSInfo.DefSQAdvancedMethod(engine, &GameInfo::AddLabels, "AddLabels"); SQGSInfo.DefSQAdvancedMethod(engine, &GameInfo::AddLabels, "AddLabels");
SQGSInfo.DefSQConst(engine, SCRIPTCONFIG_NONE, "CONFIG_NONE"); SQGSInfo.DefSQConst(engine, SCRIPTCONFIG_NONE, "CONFIG_NONE");
SQGSInfo.DefSQConst(engine, SCRIPTCONFIG_RANDOM, "CONFIG_RANDOM"); SQGSInfo.DefSQConst(engine, SCRIPTCONFIG_NONE, "CONFIG_RANDOM"); // Deprecated, mapped to NONE.
SQGSInfo.DefSQConst(engine, SCRIPTCONFIG_BOOLEAN, "CONFIG_BOOLEAN"); SQGSInfo.DefSQConst(engine, SCRIPTCONFIG_BOOLEAN, "CONFIG_BOOLEAN");
SQGSInfo.DefSQConst(engine, SCRIPTCONFIG_INGAME, "CONFIG_INGAME"); SQGSInfo.DefSQConst(engine, SCRIPTCONFIG_INGAME, "CONFIG_INGAME");
SQGSInfo.DefSQConst(engine, SCRIPTCONFIG_DEVELOPER, "CONFIG_DEVELOPER"); SQGSInfo.DefSQConst(engine, SCRIPTCONFIG_DEVELOPER, "CONFIG_DEVELOPER");

@ -325,7 +325,7 @@ void GenerateWorld(GenWorldMode mode, uint size_x, uint size_y, bool reset_setti
_settings_game.construction.map_height_limit = std::max(MAP_HEIGHT_LIMIT_AUTO_MINIMUM, std::min(MAX_MAP_HEIGHT_LIMIT, estimated_height + MAP_HEIGHT_LIMIT_AUTO_CEILING_ROOM)); _settings_game.construction.map_height_limit = std::max(MAP_HEIGHT_LIMIT_AUTO_MINIMUM, std::min(MAX_MAP_HEIGHT_LIMIT, estimated_height + MAP_HEIGHT_LIMIT_AUTO_CEILING_ROOM));
} }
if (_settings_game.game_creation.generation_seed == GENERATE_NEW_SEED) _settings_game.game_creation.generation_seed = _settings_newgame.game_creation.generation_seed = InteractiveRandom(); if (_settings_game.game_creation.generation_seed == GENERATE_NEW_SEED) _settings_game.game_creation.generation_seed = InteractiveRandom();
/* Load the right landscape stuff, and the NewGRFs! */ /* Load the right landscape stuff, and the NewGRFs! */
GfxLoadSprites(); GfxLoadSprites();

@ -37,11 +37,14 @@
static CompanyMask _legend_excluded_companies; static CompanyMask _legend_excluded_companies;
static CargoTypes _legend_excluded_cargo; static CargoTypes _legend_excluded_cargo;
uint8_t _cargo_payment_x_mode;
/* Apparently these don't play well with enums. */ /* Apparently these don't play well with enums. */
static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX); // Value used for a datapoint that shouldn't be drawn. static const OverflowSafeInt64 INVALID_DATAPOINT(INT64_MAX); // Value used for a datapoint that shouldn't be drawn.
static const uint INVALID_DATAPOINT_POS = UINT_MAX; // Used to determine if the previous point was drawn. static const uint INVALID_DATAPOINT_POS = UINT_MAX; // Used to determine if the previous point was drawn.
uint8_t _cargo_payment_x_mode; constexpr double INT64_MAX_IN_DOUBLE = static_cast<double>(INT64_MAX - 512); ///< The biggest double that when cast to int64_t still fits in a int64_t.
static_assert(static_cast<int64_t>(INT64_MAX_IN_DOUBLE) < INT64_MAX);
/****************/ /****************/
/* GRAPH LEGEND */ /* GRAPH LEGEND */
@ -227,16 +230,16 @@ protected:
} }
} }
/* Prevent showing values too close to the graph limits. */
current_interval.highest = (11 * current_interval.highest) / 10;
current_interval.lowest = (11 * current_interval.lowest) / 10;
/* Always include zero in the shown range. */ /* Always include zero in the shown range. */
double abs_lower = (current_interval.lowest > 0) ? 0 : (double)abs(current_interval.lowest); double abs_lower = (current_interval.lowest > 0) ? 0 : (double)abs(current_interval.lowest);
double abs_higher = (current_interval.highest < 0) ? 0 : (double)current_interval.highest; double abs_higher = (current_interval.highest < 0) ? 0 : (double)current_interval.highest;
/* Prevent showing values too close to the graph limits. */
abs_higher = (11.0 * abs_higher) / 10.0;
abs_lower = (11.0 * abs_lower) / 10.0;
int num_pos_grids; int num_pos_grids;
int64_t grid_size; OverflowSafeInt64 grid_size;
if (abs_lower != 0 || abs_higher != 0) { if (abs_lower != 0 || abs_higher != 0) {
/* The number of grids to reserve for the positive part is: */ /* The number of grids to reserve for the positive part is: */
@ -247,8 +250,19 @@ protected:
if (num_pos_grids == num_hori_lines && abs_lower != 0) num_pos_grids--; if (num_pos_grids == num_hori_lines && abs_lower != 0) num_pos_grids--;
/* Get the required grid size for each side and use the maximum one. */ /* Get the required grid size for each side and use the maximum one. */
int64_t grid_size_higher = (abs_higher > 0) ? ((int64_t)abs_higher + num_pos_grids - 1) / num_pos_grids : 0;
int64_t grid_size_lower = (abs_lower > 0) ? ((int64_t)abs_lower + num_hori_lines - num_pos_grids - 1) / (num_hori_lines - num_pos_grids) : 0; OverflowSafeInt64 grid_size_higher = 0;
if (abs_higher > 0) {
grid_size_higher = abs_higher > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_higher);
grid_size_higher = (grid_size_higher + num_pos_grids - 1) / num_pos_grids;
}
OverflowSafeInt64 grid_size_lower = 0;
if (abs_lower > 0) {
grid_size_lower = abs_lower > INT64_MAX_IN_DOUBLE ? INT64_MAX : static_cast<int64_t>(abs_lower);
grid_size_lower = (grid_size_lower + num_hori_lines - num_pos_grids - 1) / (num_hori_lines - num_pos_grids);
}
grid_size = std::max(grid_size_higher, grid_size_lower); grid_size = std::max(grid_size_higher, grid_size_lower);
} else { } else {
/* If both values are zero, show an empty graph. */ /* If both values are zero, show an empty graph. */
@ -637,7 +651,13 @@ public:
if (c != nullptr) { if (c != nullptr) {
this->colours[numd] = _colour_gradient[c->colour][6]; this->colours[numd] = _colour_gradient[c->colour][6];
for (int j = this->num_on_x_axis, i = 0; --j >= 0;) { for (int j = this->num_on_x_axis, i = 0; --j >= 0;) {
this->cost[numd][i] = (j >= c->num_valid_stat_ent) ? INVALID_DATAPOINT : GetGraphData(c, j); if (j >= c->num_valid_stat_ent) {
this->cost[numd][i] = INVALID_DATAPOINT;
} else {
/* Ensure we never assign INVALID_DATAPOINT, as that has another meaning.
* Instead, use the value just under it. Hopefully nobody will notice. */
this->cost[numd][i] = std::min(GetGraphData(c, j), INVALID_DATAPOINT - 1);
}
i++; i++;
} }
} }

@ -46,9 +46,9 @@ static_assert((LOAN_INTERVAL & 3) == 0);
CommandCost CmdIncreaseLoan(TileIndex tile, DoCommandFlag flags, uint32_t p1, uint32_t p2, const char *text) CommandCost CmdIncreaseLoan(TileIndex tile, DoCommandFlag flags, uint32_t p1, uint32_t p2, const char *text)
{ {
Company *c = Company::Get(_current_company); Company *c = Company::Get(_current_company);
Money max_loan = c->GetMaxLoan();
if (c->current_loan >= _economy.max_loan) { if (c->current_loan >= max_loan) {
SetDParam(0, _economy.max_loan); SetDParam(0, max_loan);
return_cmd_error(STR_ERROR_MAXIMUM_PERMITTED_LOAN); return_cmd_error(STR_ERROR_MAXIMUM_PERMITTED_LOAN);
} }
@ -59,11 +59,11 @@ CommandCost CmdIncreaseLoan(TileIndex tile, DoCommandFlag flags, uint32_t p1, ui
loan = LOAN_INTERVAL; loan = LOAN_INTERVAL;
break; break;
case 1: // Take a loan as big as possible case 1: // Take a loan as big as possible
loan = _economy.max_loan - c->current_loan; loan = max_loan - c->current_loan;
break; break;
case 2: // Take the given amount of loan case 2: // Take the given amount of loan
loan = ((uint64_t)p1 << 32) | (p2 & 0xFFFFFFFC); loan = ((uint64_t)p1 << 32) | (p2 & 0xFFFFFFFC);
if (loan < LOAN_INTERVAL || c->current_loan + loan > _economy.max_loan || loan % LOAN_INTERVAL != 0) return CMD_ERROR; if (loan < LOAN_INTERVAL || c->current_loan + loan > max_loan || loan % LOAN_INTERVAL != 0) return CMD_ERROR;
break; break;
} }
@ -106,7 +106,7 @@ CommandCost CmdDecreaseLoan(TileIndex tile, DoCommandFlag flags, uint32_t p1, ui
loan = std::min(c->current_loan, (Money)LOAN_INTERVAL); loan = std::min(c->current_loan, (Money)LOAN_INTERVAL);
break; break;
case 1: // Pay back as much as possible case 1: // Pay back as much as possible
loan = std::max(std::min(c->current_loan, c->money), (Money)LOAN_INTERVAL); loan = std::max(std::min(c->current_loan, GetAvailableMoneyForCommand()), (Money)LOAN_INTERVAL);
loan -= loan % LOAN_INTERVAL; loan -= loan % LOAN_INTERVAL;
break; break;
case 2: // Repay the given amount of loan case 2: // Repay the given amount of loan
@ -115,7 +115,7 @@ CommandCost CmdDecreaseLoan(TileIndex tile, DoCommandFlag flags, uint32_t p1, ui
break; break;
} }
if (c->money < loan) { if (GetAvailableMoneyForCommand() < loan) {
SetDParam(0, loan); SetDParam(0, loan);
return_cmd_error(STR_ERROR_CURRENCY_REQUIRED); return_cmd_error(STR_ERROR_CURRENCY_REQUIRED);
} }
@ -128,6 +128,38 @@ CommandCost CmdDecreaseLoan(TileIndex tile, DoCommandFlag flags, uint32_t p1, ui
return CommandCost(); return CommandCost();
} }
/**
* Sets the max loan amount of your company. Does not respect the global loan setting.
* @param tile unused
* @param flags operation to perform
* @param p1 the company ID.
* @param p2 unused
* @param p3 the new max loan amount, will be rounded down to the multitude of LOAN_INTERVAL. If set to COMPANY_MAX_LOAN_DEFAULT reset the max loan to default(global) value.
* @param text unused
* @return zero cost or an error
*/
CommandCost CmdSetCompanyMaxLoan(TileIndex tile, DoCommandFlag flags, uint32_t p1, uint32_t p2, uint64_t p3, const char *text, const CommandAuxiliaryBase *aux_data)
{
if (_current_company != OWNER_DEITY) return CMD_ERROR;
Money amount = (Money)p3;
if (amount != COMPANY_MAX_LOAN_DEFAULT) {
if (amount < 0 || amount > (Money)MAX_LOAN_LIMIT) return CMD_ERROR;
}
Company *c = Company::GetIfValid((CompanyID)p1);
if (c == nullptr) return CMD_ERROR;
if (flags & DC_EXEC) {
/* Round the amount down to a multiple of LOAN_INTERVAL. */
if (amount != COMPANY_MAX_LOAN_DEFAULT) amount -= (int64_t)amount % LOAN_INTERVAL;
c->max_loan = amount;
InvalidateCompanyWindows(c);
}
return CommandCost();
}
/** /**
* In case of an unsafe unpause, we want the * In case of an unsafe unpause, we want the
* user to confirm that it might crash. * user to confirm that it might crash.

@ -73,15 +73,16 @@ NetworkHTTPRequest::NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *ca
static std::string GetLastErrorAsString() static std::string GetLastErrorAsString()
{ {
char buffer[512]; wchar_t buffer[512];
DWORD error_code = GetLastError(); DWORD error_code = GetLastError();
if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandleA("winhttp.dll"), error_code, if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandle(L"winhttp.dll"), error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), nullptr) == 0) { MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, lengthof(buffer), nullptr) == 0) {
return fmt::format("unknown error {}", error_code); return fmt::format("unknown error {}", error_code);
} }
return buffer; return FS2OTTD(buffer);
} }
/** /**

@ -80,12 +80,15 @@ const char *NetworkError::AsString() const
{ {
if (this->message.empty()) { if (this->message.empty()) {
#if defined(_WIN32) #if defined(_WIN32)
char buffer[512]; wchar_t buffer[512];
if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, this->error, if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, this->error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), nullptr) == 0) { MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, lengthof(buffer), nullptr) == 0) {
seprintf(buffer, lastof(buffer), "Unknown error %d", this->error); char errbuffer[32];
seprintf(errbuffer, lastof(errbuffer), "Unknown error %d", this->error);
this->message.assign(errbuffer);
} else {
this->message.assign(FS2OTTD(buffer));
} }
this->message.assign(buffer);
#else #else
/* Make strerror thread safe by locking access to it. There is a thread safe strerror_r, however /* Make strerror thread safe by locking access to it. There is a thread safe strerror_r, however
* the non-POSIX variant is available due to defining _GNU_SOURCE meaning it is not portable. * the non-POSIX variant is available due to defining _GNU_SOURCE meaning it is not portable.

@ -1688,6 +1688,18 @@ public:
void UpdateWidgetSize(WidgetID widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize) override void UpdateWidgetSize(WidgetID widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize) override
{ {
switch (widget) { switch (widget) {
case WID_CL_SERVER_NAME:
case WID_CL_CLIENT_NAME:
if (widget == WID_CL_SERVER_NAME) {
SetDParamStr(0, _network_server ? _settings_client.network.server_name : _network_server_name);
} else {
const NetworkClientInfo *own_ci = NetworkClientInfo::GetByClientID(_network_own_client_id);
SetDParamStr(0, own_ci != nullptr ? own_ci->client_name : _settings_client.network.client_name);
}
*size = GetStringBoundingBox(STR_JUST_RAW_STRING);
size->width = std::min(size->width, static_cast<uint>(ScaleGUITrad(200))); // By default, don't open the window too wide.
break;
case WID_CL_SERVER_VISIBILITY: case WID_CL_SERVER_VISIBILITY:
*size = maxdim(maxdim(GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_LOCAL), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_PUBLIC)), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY)); *size = maxdim(maxdim(GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_LOCAL), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_PUBLIC)), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY));
size->width += padding.width; size->width += padding.width;

@ -1542,7 +1542,7 @@ static ChangeInfoResult ShipVehicleChangeInfo(uint engine, int numinfo, int prop
svi->cost_factor = buf->ReadByte(); svi->cost_factor = buf->ReadByte();
break; break;
case PROP_SHIP_SPEED: // 0x0B Speed (1 unit is 0.5 km-ish/h) case PROP_SHIP_SPEED: // 0x0B Speed (1 unit is 0.5 km-ish/h). Use 0x23 to achieve higher speeds.
svi->max_speed = buf->ReadByte(); svi->max_speed = buf->ReadByte();
break; break;
@ -1671,6 +1671,14 @@ static ChangeInfoResult ShipVehicleChangeInfo(uint engine, int numinfo, int prop
SB(ei->callback_mask, 8, 8, buf->ReadByte()); SB(ei->callback_mask, 8, 8, buf->ReadByte());
break; break;
case 0x23: // Speed (1 unit is 0.5 km-ish/h)
svi->max_speed = buf->ReadWord();
break;
case 0x24: // Acceleration (1 unit is 0.5 km-ish/h per tick)
svi->acceleration = std::max<uint8_t>(1, buf->ReadByte());
break;
default: default:
ret = CommonVehicleChangeInfo(ei, prop, mapping_entry, buf); ret = CommonVehicleChangeInfo(ei, prop, mapping_entry, buf);
break; break;

@ -10,6 +10,7 @@
#include "stdafx.h" #include "stdafx.h"
#include "landscape.h" #include "landscape.h"
#include "command_func.h" #include "command_func.h"
#include "company_func.h"
#include "viewport_func.h" #include "viewport_func.h"
#include "company_base.h" #include "company_base.h"
#include "town.h" #include "town.h"

@ -678,7 +678,7 @@ struct AfterNewGRFScan : NewGRFScanCallback {
SetEffectVolume(_settings_client.music.effect_vol); SetEffectVolume(_settings_client.music.effect_vol);
if (startyear != CalTime::INVALID_YEAR) IConsoleSetSetting("game_creation.starting_year", startyear.base()); if (startyear != CalTime::INVALID_YEAR) IConsoleSetSetting("game_creation.starting_year", startyear.base());
if (generation_seed != GENERATE_NEW_SEED) _settings_newgame.game_creation.generation_seed = generation_seed; _settings_newgame.game_creation.generation_seed = generation_seed;
if (!dedicated_host.empty()) { if (!dedicated_host.empty()) {
_network_bind_list.clear(); _network_bind_list.clear();

@ -20,13 +20,13 @@ static std::string GetLoadError()
{ {
auto error_code = GetLastError(); auto error_code = GetLastError();
char buffer[512]; wchar_t buffer[512];
if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error_code, if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), nullptr) == 0) { MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, lengthof(buffer), nullptr) == 0) {
return fmt::format("Unknown error {}", error_code); return fmt::format("Unknown error {}", error_code);
} }
return buffer; return FS2OTTD(buffer);
} }
void *LibraryLoader::OpenLibrary(const std::string &filename) void *LibraryLoader::OpenLibrary(const std::string &filename)

@ -3957,7 +3957,9 @@ void DrawTrackBits(TileInfo *ti, TrackBits track)
static void DrawSignals(TileIndex tile, TrackBits rails, const RailTypeInfo *rti) static void DrawSignals(TileIndex tile, TrackBits rails, const RailTypeInfo *rti)
{ {
#define MAYBE_DRAW_SIGNAL(x, y, z, t) if (IsSignalPresent(tile, x)) DrawSingleSignal(tile, rti, t, GetSingleSignalState(tile, x), y, z) auto MAYBE_DRAW_SIGNAL = [&](byte signalbit, SignalOffsets image, uint pos, Track track) {
if (IsSignalPresent(tile, signalbit)) DrawSingleSignal(tile, rti, track, GetSingleSignalState(tile, signalbit), image, pos);
};
if (!(rails & TRACK_BIT_Y)) { if (!(rails & TRACK_BIT_Y)) {
if (!(rails & TRACK_BIT_X)) { if (!(rails & TRACK_BIT_X)) {

@ -13,6 +13,7 @@
#include "road_internal.h" #include "road_internal.h"
#include "viewport_func.h" #include "viewport_func.h"
#include "command_func.h" #include "command_func.h"
#include "company_func.h"
#include "pathfinder/yapf/yapf_cache.h" #include "pathfinder/yapf/yapf_cache.h"
#include "depot_base.h" #include "depot_base.h"
#include "newgrf.h" #include "newgrf.h"

@ -653,7 +653,7 @@ static void StartScripts()
} }
/* Start the GameScript. */ /* Start the GameScript. */
Game::StartNew(); Game::StartNew(false);
ShowScriptDebugWindowIfScriptError(); ShowScriptDebugWindowIfScriptError();
} }
@ -4360,6 +4360,19 @@ bool AfterLoadGame()
} }
} }
if (IsSavegameVersionBefore(SLV_SHIP_ACCELERATION) && SlXvIsFeatureMissing(XSLFI_SHIP_ACCELERATION)) {
/* NewGRF acceleration information was added to ships. */
for (Ship *s : Ship::Iterate()) {
if (s->acceleration == 0) s->acceleration = ShipVehInfo(s->engine_type)->acceleration;
}
}
if (IsSavegameVersionBefore(SLV_MAX_LOAN_FOR_COMPANY)) {
for (Company *c : Company::Iterate()) {
c->max_loan = COMPANY_MAX_LOAN_DEFAULT;
}
}
for (Company *c : Company::Iterate()) { for (Company *c : Company::Iterate()) {
UpdateCompanyLiveries(c); UpdateCompanyLiveries(c);
} }

@ -249,6 +249,7 @@ static const SaveLoad _company_desc[] = {
SLE_CONDVAR(CompanyProperties, current_loan, SLE_VAR_I64 | SLE_FILE_I32, SL_MIN_VERSION, SLV_65), SLE_CONDVAR(CompanyProperties, current_loan, SLE_VAR_I64 | SLE_FILE_I32, SL_MIN_VERSION, SLV_65),
SLE_CONDVAR(CompanyProperties, current_loan, SLE_INT64, SLV_65, SL_MAX_VERSION), SLE_CONDVAR(CompanyProperties, current_loan, SLE_INT64, SLV_65, SL_MAX_VERSION),
SLE_CONDVAR(CompanyProperties, max_loan, SLE_INT64, SLV_MAX_LOAN_FOR_COMPANY, SL_MAX_VERSION),
SLE_VAR(CompanyProperties, colour, SLE_UINT8), SLE_VAR(CompanyProperties, colour, SLE_UINT8),
SLE_VAR(CompanyProperties, money_fraction, SLE_UINT8), SLE_VAR(CompanyProperties, money_fraction, SLE_UINT8),

@ -24,6 +24,7 @@
* *
* API removals: * API removals:
* \li AIError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore. * \li AIError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore.
* \li AIInfo::CONFIG_RANDOM, no longer used.
* *
* Other changes: * Other changes:
* \li AIGroupList accepts an optional filter function * \li AIGroupList accepts an optional filter function

@ -47,6 +47,8 @@
* \li GSCompany::SetAutoRenewStatus * \li GSCompany::SetAutoRenewStatus
* \li GSCompany::SetAutoRenewMonths * \li GSCompany::SetAutoRenewMonths
* \li GSCompany::SetAutoRenewMoney * \li GSCompany::SetAutoRenewMoney
* \li GSCompany::SetMaxLoanAmountForCompany
* \li GSCompany::ResetMaxLoanAmountForCompany
* \li GSGameSettings::IsDisabledVehicleType * \li GSGameSettings::IsDisabledVehicleType
* \li GSGroup::GroupID * \li GSGroup::GroupID
* \li GSGroup::IsValidGroup * \li GSGroup::IsValidGroup
@ -88,6 +90,7 @@
* *
* API removals: * API removals:
* \li GSError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore. * \li GSError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore.
* \li AIInfo::CONFIG_RANDOM, no longer used.
* *
* Other changes: * Other changes:
* \li GSGroupList accepts an optional filter function * \li GSGroupList accepts an optional filter function

@ -186,7 +186,7 @@
company = ResolveCompanyID(company); company = ResolveCompanyID(company);
if (company == COMPANY_INVALID) return -1; if (company == COMPANY_INVALID) return -1;
return ::Company::Get(company)->money; return GetAvailableMoney((::CompanyID)company);
} }
/* static */ Money ScriptCompany::GetLoanAmount() /* static */ Money ScriptCompany::GetLoanAmount()
@ -199,7 +199,32 @@
/* static */ Money ScriptCompany::GetMaxLoanAmount() /* static */ Money ScriptCompany::GetMaxLoanAmount()
{ {
return _economy.max_loan; if (ScriptCompanyMode::IsDeity()) return _economy.max_loan;
ScriptCompany::CompanyID company = ResolveCompanyID(COMPANY_SELF);
if (company == COMPANY_INVALID) return -1;
return ::Company::Get(company)->GetMaxLoan();
}
/* static */ bool ScriptCompany::SetMaxLoanAmountForCompany(CompanyID company, Money amount)
{
EnforceDeityMode(false);
EnforcePrecondition(false, amount >= 0 && amount <= (Money)MAX_LOAN_LIMIT);
company = ResolveCompanyID(company);
EnforcePrecondition(false, company != COMPANY_INVALID);
return ScriptObject::DoCommandEx(0, company, 0, (uint64_t)amount, CMD_SET_COMPANY_MAX_LOAN);
}
/* static */ bool ScriptCompany::ResetMaxLoanAmountForCompany(CompanyID company)
{
EnforceDeityMode(false);
company = ResolveCompanyID(company);
EnforcePrecondition(false, company != COMPANY_INVALID);
return ScriptObject::DoCommandEx(0, company, 0, (uint64_t)COMPANY_MAX_LOAN_DEFAULT, CMD_SET_COMPANY_MAX_LOAN);
} }
/* static */ Money ScriptCompany::GetLoanInterval() /* static */ Money ScriptCompany::GetLoanInterval()

@ -219,12 +219,38 @@ public:
static Money GetLoanAmount(); static Money GetLoanAmount();
/** /**
* Gets the maximum amount your company can loan. * Gets the maximum amount your company can loan. In deity mode returns the global max loan.
* @return The maximum amount your company can loan. * @return The maximum amount your company can loan.
* @post GetLoanInterval() is always a multiplier of the return value. * @post GetLoanInterval() is always a multiplier of the return value.
*/ */
static Money GetMaxLoanAmount(); static Money GetMaxLoanAmount();
/**
* Sets the max amount of money company can loan.
* @param company The company ID.
* @param amount Max loan amount. Will be rounded down to a multiple of GetLoanInterval().
* @return True, if the max loan was changed.
* @pre ScriptCompanyMode::IsDeity().
* @pre amount >= 0.
* @pre ResolveCompanyID(company) != COMPANY_INVALID.
* @note You need to create your own news message to inform about max loan change.
* @note Max loan value set with this method is not affected by inflation.
* @api -ai
*/
static bool SetMaxLoanAmountForCompany(CompanyID company, Money amount);
/**
* Makes the max amount of money company can loan follow the global max loan setting.
* @param company The company ID.
* @return True, if the max loan was reset.
* @pre ScriptCompanyMode::IsDeity().
* @pre amount >= 0 && amount <= MAX_LOAN_LIMIT.
* @pre ResolveCompanyID(company) != COMPANY_INVALID.
* @note You need to create your own news message to inform about max loan change.
* @api -ai
*/
static bool ResetMaxLoanAmountForCompany(CompanyID company);
/** /**
* Gets the interval/loan step. * Gets the interval/loan step.
* @return The loan step. * @return The loan step.

@ -203,7 +203,6 @@ public:
/** Miscellaneous flags for Script settings. */ /** Miscellaneous flags for Script settings. */
enum ScriptConfigFlags { enum ScriptConfigFlags {
CONFIG_NONE, ///< Normal setting. CONFIG_NONE, ///< Normal setting.
CONFIG_RANDOM, ///< When randomizing the Script, pick any value between min_value and max_value (inclusive).
CONFIG_BOOLEAN, ///< This value is a boolean (either 0 (false) or 1 (true) ). CONFIG_BOOLEAN, ///< This value is a boolean (either 0 (false) or 1 (true) ).
CONFIG_INGAME, ///< This setting can be changed while the Script is running. CONFIG_INGAME, ///< This setting can be changed while the Script is running.
CONFIG_DEVELOPER, ///< This setting will only be visible when the Script development tools are active. CONFIG_DEVELOPER, ///< This setting will only be visible when the Script development tools are active.
@ -236,10 +235,11 @@ public:
* is selected. Required. The value will be clamped in the range * is selected. Required. The value will be clamped in the range
* [MIN(int32_t), MAX(int32_t)] (inclusive). * [MIN(int32_t), MAX(int32_t)] (inclusive).
* - random_deviation If this property has a nonzero value, then the * - random_deviation If this property has a nonzero value, then the
* actual value of the setting in game will be randomized in the range * actual value of the setting in game will be randomised in the range
* [user_configured_value - random_deviation, user_configured_value + random_deviation] (inclusive). * [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_t)] (inclusive). * random_deviation sign is ignored and the value is clamped in the range [0, MAX(int32_t)] (inclusive).
* Not allowed if the CONFIG_RANDOM flag is set, otherwise optional. * The randomisation will happen just before the Script start.
* Not allowed if the CONFIG_BOOLEAN flag is set, otherwise optional.
* - step_size The increase/decrease of the value every time the user * - step_size The increase/decrease of the value every time the user
* clicks one of the up/down arrow buttons. Optional, default is 1. * clicks one of the up/down arrow buttons. Optional, default is 1.
* - flags Bitmask of some flags, see ScriptConfigFlags. Required. * - flags Bitmask of some flags, see ScriptConfigFlags. Required.

@ -33,18 +33,6 @@ void ScriptConfig::Change(std::optional<const std::string> name, int version, bo
this->to_load_data.reset(); this->to_load_data.reset();
this->ClearConfigList(); this->ClearConfigList();
if (_game_mode == GM_NORMAL && this->info != nullptr) {
/* If we're in an existing game and the Script is changed, set all settings
* for the Script that have the random flag to a random value. */
for (const auto &item : *this->info->GetConfigList()) {
if (item.flags & SCRIPTCONFIG_RANDOM) {
this->SetSetting(item.name, ScriptObject::GetRandomizer(OWNER_NONE).Next(item.max_value + 1 - item.min_value) + item.min_value);
}
}
this->AddRandomDeviation();
}
} }
ScriptConfig::ScriptConfig(const ScriptConfig *config) ScriptConfig::ScriptConfig(const ScriptConfig *config)
@ -58,9 +46,6 @@ ScriptConfig::ScriptConfig(const ScriptConfig *config)
for (const auto &item : config->settings) { for (const auto &item : config->settings) {
this->settings[item.first] = item.second; this->settings[item.first] = item.second;
} }
/* Virtual functions get called statically in constructors, so make it explicit to remove any confusion. */
this->ScriptConfig::AddRandomDeviation();
} }
ScriptConfig::~ScriptConfig() ScriptConfig::~ScriptConfig()

@ -23,7 +23,7 @@ static const int INT32_DIGITS_WITH_SIGN_AND_TERMINATION = 10 + 1 + 1;
/** Bitmask of flags for Script settings. */ /** Bitmask of flags for Script settings. */
enum ScriptConfigFlags { enum ScriptConfigFlags {
SCRIPTCONFIG_NONE = 0x0, ///< No flags set. SCRIPTCONFIG_NONE = 0x0, ///< No flags set.
SCRIPTCONFIG_RANDOM = 0x1, ///< When randomizing the Script, pick any value between min_value and max_value when on custom difficulty setting. // Unused flag 0x1.
SCRIPTCONFIG_BOOLEAN = 0x2, ///< This value is a boolean (either 0 (false) or 1 (true) ). SCRIPTCONFIG_BOOLEAN = 0x2, ///< This value is a boolean (either 0 (false) or 1 (true) ).
SCRIPTCONFIG_INGAME = 0x4, ///< This setting can be changed while the Script is running. SCRIPTCONFIG_INGAME = 0x4, ///< This setting can be changed while the Script is running.
SCRIPTCONFIG_DEVELOPER = 0x8, ///< This setting will only be visible when the Script development tools are active. SCRIPTCONFIG_DEVELOPER = 0x8, ///< This setting will only be visible when the Script development tools are active.

@ -395,10 +395,18 @@ struct ScriptSettingsWindow : public Window {
TextColour colour; TextColour colour;
uint idx = 0; uint idx = 0;
if (config_item.description.empty()) { if (config_item.description.empty()) {
str = STR_JUST_STRING1; if (this->slot != OWNER_DEITY && !Company::IsValidID(this->slot) && config_item.random_deviation != 0) {
str = STR_AI_SETTINGS_JUST_DEVIATION;
} else {
str = STR_JUST_STRING1;
}
colour = TC_ORANGE; colour = TC_ORANGE;
} else { } else {
str = STR_AI_SETTINGS_SETTING; if (this->slot != OWNER_DEITY && !Company::IsValidID(this->slot) && config_item.random_deviation != 0) {
str = STR_AI_SETTINGS_SETTING_DEVIATION;
} else {
str = STR_AI_SETTINGS_SETTING;
}
colour = TC_LIGHT_BLUE; colour = TC_LIGHT_BLUE;
SetDParamStr(idx++, config_item.description); SetDParamStr(idx++, config_item.description);
} }
@ -413,13 +421,35 @@ struct ScriptSettingsWindow : public Window {
DrawArrowButtons(br.left, y + button_y_offset, COLOUR_YELLOW, (this->clicked_button == i) ? 1 + (this->clicked_increase != rtl) : 0, editable && current_value > config_item.min_value, editable && current_value < config_item.max_value); DrawArrowButtons(br.left, y + button_y_offset, COLOUR_YELLOW, (this->clicked_button == i) ? 1 + (this->clicked_increase != rtl) : 0, editable && current_value > config_item.min_value, editable && current_value < config_item.max_value);
} }
auto config_iterator = config_item.labels.find(current_value); if (this->slot == OWNER_DEITY || Company::IsValidID(this->slot) || config_item.random_deviation == 0) {
if (config_iterator != config_item.labels.end()) { auto config_iterator = config_item.labels.find(current_value);
SetDParam(idx++, STR_JUST_RAW_STRING); if (config_iterator != config_item.labels.end()) {
SetDParamStr(idx++, config_iterator->second); SetDParam(idx++, STR_JUST_RAW_STRING);
SetDParamStr(idx++, config_iterator->second);
} else {
SetDParam(idx++, STR_JUST_INT);
SetDParam(idx++, current_value);
}
} else { } else {
SetDParam(idx++, STR_JUST_INT); int min_deviated = std::max(config_item.min_value, current_value - config_item.random_deviation);
SetDParam(idx++, current_value); auto config_iterator = config_item.labels.find(min_deviated);
if (config_iterator != config_item.labels.end()) {
SetDParam(idx++, STR_JUST_RAW_STRING);
SetDParamStr(idx++, config_iterator->second);
} else {
SetDParam(idx++, STR_JUST_INT);
SetDParam(idx++, min_deviated);
}
int max_deviated = std::min(config_item.max_value, current_value + config_item.random_deviation);
config_iterator = config_item.labels.find(max_deviated);
if (config_iterator != config_item.labels.end()) {
SetDParam(idx++, STR_JUST_RAW_STRING);
SetDParamStr(idx++, config_iterator->second);
} else {
SetDParam(idx++, STR_JUST_INT);
SetDParam(idx++, max_deviated);
}
} }
} }

@ -162,12 +162,13 @@ SQInteger ScriptInfo::AddSetting(HSQUIRRELVM vm)
} }
sq_pop(vm, 1); sq_pop(vm, 1);
/* Don't allow both random_deviation and SCRIPTCONFIG_RANDOM to /* Don't allow both random_deviation and SCRIPTCONFIG_BOOLEAN to
* be set for the same config item. */ * be set for the same config item. */
if ((items & 0x200) != 0 && (config.flags & SCRIPTCONFIG_RANDOM) != 0) { if ((items & 0x200) != 0 && (config.flags & SCRIPTCONFIG_BOOLEAN) != 0) {
this->engine->ThrowError("Setting both random_deviation and SCRIPTCONFIG_RANDOM is not allowed"); this->engine->ThrowError("setting both random_deviation and CONFIG_BOOLEAN is not allowed");
return SQ_ERROR; return SQ_ERROR;
} }
/* Reset the bit for random_deviation as it's optional. */ /* Reset the bit for random_deviation as it's optional. */
items &= ~0x200; items &= ~0x200;

@ -219,8 +219,10 @@ enum IniFileVersion : uint32_t {
IFV_GAME_TYPE, ///< 2 PR#9515 Convert server_advertise to server_game_type. IFV_GAME_TYPE, ///< 2 PR#9515 Convert server_advertise to server_game_type.
IFV_LINKGRAPH_SECONDS, ///< 3 PR#10610 Store linkgraph update intervals in seconds instead of days. IFV_LINKGRAPH_SECONDS, ///< 3 PR#10610 Store linkgraph update intervals in seconds instead of days.
IFV_NETWORK_PRIVATE_SETTINGS, ///< 4 PR#10762 Move no_http_content_downloads / use_relay_service to private settings. IFV_NETWORK_PRIVATE_SETTINGS, ///< 4 PR#10762 Move no_http_content_downloads / use_relay_service to private settings.
IFV_AUTOSAVE_RENAME, ///< 5 PR#11143 Renamed values of autosave to be in minutes. IFV_AUTOSAVE_RENAME, ///< 5 PR#11143 Renamed values of autosave to be in minutes.
IFV_RIGHT_CLICK_CLOSE, ///< 6 PR#10204 Add alternative right click to close windows setting. IFV_RIGHT_CLICK_CLOSE, ///< 6 PR#10204 Add alternative right click to close windows setting.
IFV_REMOVE_GENERATION_SEED, ///< 7 PR#11927 Remove "generation_seed" from configuration.
IFV_MAX_VERSION, ///< Highest possible ini-file version. IFV_MAX_VERSION, ///< Highest possible ini-file version.
}; };
@ -2988,6 +2990,13 @@ void SaveToConfig(SaveToConfigFlags flags)
} }
} }
if (generic_version < IFV_REMOVE_GENERATION_SEED) {
IniGroup *game_creation = generic_ini.GetGroup("game_creation");
if (game_creation != nullptr) {
game_creation->RemoveItem("generation_seed");
}
}
/* These variables are migrated from generic ini to private ini now. */ /* These variables are migrated from generic ini to private ini now. */
if (generic_version < IFV_NETWORK_PRIVATE_SETTINGS) { if (generic_version < IFV_NETWORK_PRIVATE_SETTINGS) {
IniGroup *network = generic_ini.GetGroup("network"); IniGroup *network = generic_ini.GetGroup("network");

@ -2417,6 +2417,7 @@ static SettingsContainer &GetSettingsTree()
SettingsPage *accounting = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCOUNTING)); SettingsPage *accounting = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCOUNTING));
{ {
accounting->Add(new SettingEntry("difficulty.infinite_money"));
accounting->Add(new SettingEntry("economy.inflation")); accounting->Add(new SettingEntry("economy.inflation"));
accounting->Add(new SettingEntry("economy.inflation_fixed_dates")); accounting->Add(new SettingEntry("economy.inflation_fixed_dates"));
accounting->Add(new SettingEntry("difficulty.initial_interest")); accounting->Add(new SettingEntry("difficulty.initial_interest"));

@ -116,6 +116,7 @@ struct DifficultySettings {
bool line_reverse_mode; ///< reversing at stations or not bool line_reverse_mode; ///< reversing at stations or not
bool disasters; ///< are disasters enabled bool disasters; ///< are disasters enabled
byte town_council_tolerance; ///< minimum required town ratings to be allowed to demolish stuff byte town_council_tolerance; ///< minimum required town ratings to be allowed to demolish stuff
bool infinite_money; ///< whether spending money despite negative balance is allowed
bool money_cheat_in_multiplayer; ///< is the money cheat permitted for non-admin multiplayer clients bool money_cheat_in_multiplayer; ///< is the money cheat permitted for non-admin multiplayer clients
bool rename_towns_in_multiplayer; ///< is renaming towns permitted for non-admin multiplayer clients bool rename_towns_in_multiplayer; ///< is renaming towns permitted for non-admin multiplayer clients
bool override_town_settings_in_multiplayer; ///< is overriding town settings permitted for non-admin multiplayer clients bool override_town_settings_in_multiplayer; ///< is overriding town settings permitted for non-admin multiplayer clients

@ -529,7 +529,7 @@ static uint ShipAccelerate(Vehicle *v)
{ {
uint speed; uint speed;
speed = std::min<uint>(v->cur_speed + 1, Ship::From(v)->GetEffectiveMaxSpeed()); speed = std::min<uint>(v->cur_speed + v->acceleration, Ship::From(v)->GetEffectiveMaxSpeed());
speed = std::min<uint>(speed, v->current_order.GetMaxSpeed() * 2); speed = std::min<uint>(speed, v->current_order.GetMaxSpeed() * 2);
if (v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_LOW_POWER && v->cur_speed > (v->breakdown_severity * ShipVehInfo(v->engine_type)->max_speed) >> 8) { if (v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_LOW_POWER && v->cur_speed > (v->breakdown_severity * ShipVehInfo(v->engine_type)->max_speed) >> 8) {
@ -972,6 +972,8 @@ static void ShipController(Ship *v)
uint number_of_steps = ShipAccelerate(v); uint number_of_steps = ShipAccelerate(v);
if (number_of_steps == 0 && v->current_order.IsType(OT_LEAVESTATION)) number_of_steps = 1; if (number_of_steps == 0 && v->current_order.IsType(OT_LEAVESTATION)) number_of_steps = 1;
for (uint i = 0; i < number_of_steps; ++i) { for (uint i = 0; i < number_of_steps; ++i) {
if (ShipMoveUpDownOnLock(v)) return;
GetNewVehiclePosResult gp = GetNewVehiclePos(v); GetNewVehiclePosResult gp = GetNewVehiclePos(v);
if (v->state != TRACK_BIT_WORMHOLE) { if (v->state != TRACK_BIT_WORMHOLE) {
/* Not on a bridge */ /* Not on a bridge */
@ -1202,6 +1204,7 @@ CommandCost CmdBuildShip(TileIndex tile, DoCommandFlag flags, const Engine *e, V
v->sprite_seq.Set(SPR_IMG_QUERY); v->sprite_seq.Set(SPR_IMG_QUERY);
v->random_bits = Random(); v->random_bits = Random();
v->acceleration = svi->acceleration;
v->UpdateCache(); v->UpdateCache();
if (e->flags & ENGINE_EXCLUSIVE_PREVIEW) SetBit(v->vehicle_flags, VF_BUILT_AS_PROTOTYPE); if (e->flags & ENGINE_EXCLUSIVE_PREVIEW) SetBit(v->vehicle_flags, VF_BUILT_AS_PROTOTYPE);

@ -208,6 +208,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = {
{ XSLFI_SAVEGAME_ID, XSCF_NULL, 1, 1, "slv_savegame_id", nullptr, nullptr, nullptr }, { XSLFI_SAVEGAME_ID, XSCF_NULL, 1, 1, "slv_savegame_id", nullptr, nullptr, nullptr },
{ XSLFI_NEWGRF_LAST_SERVICE, XSCF_NULL, 1, 1, "slv_newgrf_last_service", nullptr, nullptr, nullptr }, { XSLFI_NEWGRF_LAST_SERVICE, XSCF_NULL, 1, 1, "slv_newgrf_last_service", nullptr, nullptr, nullptr },
{ XSLFI_CARGO_TRAVELLED, XSCF_NULL, 1, 1, "slv_cargo_travelled", nullptr, nullptr, nullptr }, { XSLFI_CARGO_TRAVELLED, XSCF_NULL, 1, 1, "slv_cargo_travelled", nullptr, nullptr, nullptr },
{ XSLFI_SHIP_ACCELERATION, XSCF_NULL, 1, 1, "slv_ship_acceleration", nullptr, nullptr, nullptr },
{ XSLFI_TABLE_PATS, XSCF_NULL, 1, 1, "table_pats", nullptr, nullptr, nullptr }, { XSLFI_TABLE_PATS, XSCF_NULL, 1, 1, "table_pats", nullptr, nullptr, nullptr },
{ XSLFI_TABLE_MISC_SL, XSCF_NULL, 1, 1, "table_misc_sl", nullptr, nullptr, nullptr }, { XSLFI_TABLE_MISC_SL, XSCF_NULL, 1, 1, "table_misc_sl", nullptr, nullptr, nullptr },

@ -157,6 +157,7 @@ enum SlXvFeatureIndex {
XSLFI_SAVEGAME_ID, ///< See: SLV_SAVEGAME_ID (PR #10719) XSLFI_SAVEGAME_ID, ///< See: SLV_SAVEGAME_ID (PR #10719)
XSLFI_NEWGRF_LAST_SERVICE, ///< See: SLV_NEWGRF_LAST_SERVICE (PR #11124) XSLFI_NEWGRF_LAST_SERVICE, ///< See: SLV_NEWGRF_LAST_SERVICE (PR #11124)
XSLFI_CARGO_TRAVELLED, ///< See: SLV_CARGO_TRAVELLED (PR #11283) XSLFI_CARGO_TRAVELLED, ///< See: SLV_CARGO_TRAVELLED (PR #11283)
XSLFI_SHIP_ACCELERATION, ///< See: SLV_SHIP_ACCELERATION (PR #10734)
XSLFI_TABLE_PATS, ///< Use upstream table format for PATS XSLFI_TABLE_PATS, ///< Use upstream table format for PATS
XSLFI_TABLE_MISC_SL, ///< Use upstream table format for miscellaneous chunks, so far: DATE, VIEW, MAPS XSLFI_TABLE_MISC_SL, ///< Use upstream table format for miscellaneous chunks, so far: DATE, VIEW, MAPS

@ -46,6 +46,7 @@
#include "../error.h" #include "../error.h"
#include "../scope.h" #include "../scope.h"
#include "../core/ring_buffer.hpp" #include "../core/ring_buffer.hpp"
#include "../timer/timer_game_tick.h"
#include <atomic> #include <atomic>
#include <string> #include <string>
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
@ -4115,14 +4116,23 @@ std::string GenerateDefaultSaveName()
SetDParam(0, cid); SetDParam(0, cid);
/* Insert current date */ /* We show the current game time differently depending on the timekeeping units used by this game. */
switch (_settings_client.gui.date_format_in_default_names) { if (EconTime::UsingWallclockUnits() && CalTime::IsCalendarFrozen()) {
case 0: SetDParam(1, STR_JUST_DATE_LONG); break; /* Insert time played. */
case 1: SetDParam(1, STR_JUST_DATE_TINY); break; const auto play_time = _scaled_tick_counter / TICKS_PER_SECOND;
case 2: SetDParam(1, STR_JUST_DATE_ISO); break; SetDParam(1, STR_SAVEGAME_DURATION_REALTIME);
default: NOT_REACHED(); SetDParam(2, play_time / 60 / 60);
SetDParam(3, (play_time / 60) % 60);
} else {
/* Insert current date */
switch (_settings_client.gui.date_format_in_default_names) {
case 0: SetDParam(1, STR_JUST_DATE_LONG); break;
case 1: SetDParam(1, STR_JUST_DATE_TINY); break;
case 2: SetDParam(1, STR_JUST_DATE_ISO); break;
default: NOT_REACHED();
}
SetDParam(2, CalTime::CurDate());
} }
SetDParam(2, CalTime::CurDate());
/* Get the correct string (special string for when there's not company) */ /* Get the correct string (special string for when there's not company) */
std::string filename = GetString(!Company::IsValidID(cid) ? STR_SAVEGAME_NAME_SPECTATOR : STR_SAVEGAME_NAME_DEFAULT); std::string filename = GetString(!Company::IsValidID(cid) ? STR_SAVEGAME_NAME_SPECTATOR : STR_SAVEGAME_NAME_DEFAULT);

@ -383,6 +383,8 @@ enum SaveLoadVersion : uint16_t {
SLV_ECONOMY_DATE, ///< 326 PR#10700 Split calendar and economy timers and dates. SLV_ECONOMY_DATE, ///< 326 PR#10700 Split calendar and economy timers and dates.
SLV_ECONOMY_MODE_TIMEKEEPING_UNITS, ///< 327 PR#11341 Mode to display economy measurements in wallclock units. SLV_ECONOMY_MODE_TIMEKEEPING_UNITS, ///< 327 PR#11341 Mode to display economy measurements in wallclock units.
SLV_CALENDAR_SUB_DATE_FRACT, ///< 328 PR#11428 Add sub_date_fract to measure calendar days. SLV_CALENDAR_SUB_DATE_FRACT, ///< 328 PR#11428 Add sub_date_fract to measure calendar days.
SLV_SHIP_ACCELERATION, ///< 329 PR#10734 Start using Vehicle's acceleration field for ships too.
SLV_MAX_LOAN_FOR_COMPANY, ///< 330 PR#11224 Separate max loan for each company.
SL_MAX_VERSION, ///< Highest possible saveload version SL_MAX_VERSION, ///< Highest possible saveload version

@ -162,6 +162,8 @@ struct StatusBarWindow : Window {
case WID_S_RIGHT: { case WID_S_RIGHT: {
if (_local_company == COMPANY_SPECTATOR) { if (_local_company == COMPANY_SPECTATOR) {
DrawString(tr, STR_STATUSBAR_SPECTATOR, TC_FROMSTRING, SA_HOR_CENTER); DrawString(tr, STR_STATUSBAR_SPECTATOR, TC_FROMSTRING, SA_HOR_CENTER);
} else if (_settings_game.difficulty.infinite_money) {
DrawString(tr, STR_STATUSBAR_INFINITE_MONEY, TC_FROMSTRING, SA_HOR_CENTER);
} else { } else {
/* Draw company money, if any */ /* Draw company money, if any */
const Company *c = Company::GetIfValid(_local_company); const Company *c = Company::GetIfValid(_local_company);

@ -450,11 +450,11 @@ const char *assert_tile_info(uint32_t tile);
/* Asserts are enabled if NDEBUG isn't defined or WITH_ASSERT is defined. */ /* Asserts are enabled if NDEBUG isn't defined or WITH_ASSERT is defined. */
#if !defined(NDEBUG) || defined(WITH_ASSERT) #if !defined(NDEBUG) || defined(WITH_ASSERT)
# undef assert # undef assert
# define assert(expression) if (unlikely(!(expression))) error("Assertion failed at line %i of %s: %s", __LINE__, __FILE__, #expression); # define assert(expression) do { if (unlikely(!(expression))) error("Assertion failed at line %i of %s: %s", __LINE__, __FILE__, #expression); } while (false)
# define assert_msg(expression, ...) if (unlikely(!(expression))) assert_msg_error(__LINE__, __FILE__, #expression, nullptr, __VA_ARGS__); # define assert_msg(expression, ...) do { if (unlikely(!(expression))) assert_msg_error(__LINE__, __FILE__, #expression, nullptr, __VA_ARGS__); } while (false)
# define assert_msg_tile(expression, tile, ...) if (unlikely(!(expression))) assert_msg_error(__LINE__, __FILE__, #expression, assert_tile_info(tile), __VA_ARGS__); # define assert_msg_tile(expression, tile, ...) do { if (unlikely(!(expression))) assert_msg_error(__LINE__, __FILE__, #expression, assert_tile_info(tile), __VA_ARGS__); } while (false)
# define assert_tile(expression, tile) if (unlikely(!(expression))) error("Assertion failed at line %i of %s: %s\n\t%s", __LINE__, __FILE__, #expression, assert_tile_info(tile)); # define assert_tile(expression, tile) do { if (unlikely(!(expression))) error("Assertion failed at line %i of %s: %s\n\t%s", __LINE__, __FILE__, #expression, assert_tile_info(tile)); } while (false)
# define assert_str(expression, str) if (unlikely(!(expression))) assert_str_error(__LINE__, __FILE__, #expression, str); # define assert_str(expression, str) do { if (unlikely(!(expression))) assert_str_error(__LINE__, __FILE__, #expression, str); } while (false)
#else #else
# undef assert # undef assert
# define assert(expression) # define assert(expression)

@ -522,7 +522,6 @@ static void FormatGenericCurrency(StringBuilder builder, const CurrencySpec *spe
/* We are going to make number absolute for printing, so /* We are going to make number absolute for printing, so
* keep this piece of data as we need it later on */ * keep this piece of data as we need it later on */
bool negative = number < 0; bool negative = number < 0;
const char *multiplier = "";
number *= spec->rate; number *= spec->rate;
@ -539,16 +538,24 @@ static void FormatGenericCurrency(StringBuilder builder, const CurrencySpec *spe
* The only remaining value is 1 (suffix), so everything that is not 1 */ * The only remaining value is 1 (suffix), so everything that is not 1 */
if (spec->symbol_pos != 1) builder += spec->prefix; if (spec->symbol_pos != 1) builder += spec->prefix;
/* for huge numbers, compact the number into k or M */ StringID number_str = STR_NULL;
/* For huge numbers, compact the number. */
if (compact) { if (compact) {
/* Take care of the 'k' rounding. Having 1 000 000 k /* Take care of the thousand rounding. Having 1 000 000 k
* and 1 000 M is inconsistent, so always use 1 000 M. */ * and 1 000 M is inconsistent, so always use 1 000 M. */
if (number >= 1000000000 - 500) { if (number >= Money(1'000'000'000'000'000) - 500'000'000) {
number = (number + 500000) / 1000000; number = (number + Money(500'000'000'000)) / Money(1'000'000'000'000);
multiplier = NBSP "M"; number_str = STR_CURRENCY_SHORT_TERA;
} else if (number >= 1000000) { } else if (number >= Money(1'000'000'000'000) - 500'000) {
number = (number + 500) / 1000; number = (number + 500'000'000) / 1'000'000'000;
multiplier = NBSP "k"; number_str = STR_CURRENCY_SHORT_GIGA;
} else if (number >= 1'000'000'000 - 500) {
number = (number + 500'000) / 1'000'000;
number_str = STR_CURRENCY_SHORT_MEGA;
} else if (number >= 1'000'000) {
number = (number + 500) / 1'000;
number_str = STR_CURRENCY_SHORT_KILO;
} }
} }
@ -556,7 +563,10 @@ static void FormatGenericCurrency(StringBuilder builder, const CurrencySpec *spe
if (StrEmpty(separator)) separator = _currency->separator.c_str(); if (StrEmpty(separator)) separator = _currency->separator.c_str();
if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator_currency; if (StrEmpty(separator)) separator = _langpack.langpack->digit_group_separator_currency;
FormatNumber(builder, number, separator); FormatNumber(builder, number, separator);
builder += multiplier; if (number_str != STR_NULL) {
auto tmp_params = ArrayStringParameters<0>();
FormatString(builder, GetStringPtr(number_str), tmp_params);
}
/* Add suffix part, following symbol_pos specification. /* Add suffix part, following symbol_pos specification.
* Here, it can can be either 1 (suffix) or 2 (both prefix and suffix). * Here, it can can be either 1 (suffix) or 2 (both prefix and suffix).

@ -557,29 +557,30 @@ static const RailVehicleInfo _orig_rail_vehicle_info[] = {
* @see ShipVehicleInfo * @see ShipVehicleInfo
* @param a image_index * @param a image_index
* @param b cost_factor * @param b cost_factor
* @param c max_speed (1 unit = 1/3.2 mph = 0.5 km-ish/h) * @param c acceleration (1 unit = 1/3.2 mph per tick = 0.5 km-ish/h per tick)
* @param d capacity (persons, bags, tons, pieces, items, cubic metres, ...) * @param d max_speed (1 unit = 1/3.2 mph = 0.5 km-ish/h)
* @param e running_cost * @param e capacity (persons, bags, tons, pieces, items, cubic metres, ...)
* @param f sound effect * @param f running_cost
* @param g refittable * @param g sound effect
* @param h refittable
*/ */
#define SVI(a, b, c, d, e, f, g) { a, b, c, d, e, f, g, VE_DEFAULT, 0, 0 } #define SVI(a, b, c, d, e, f, g, h) { a, b, c, d, e, f, g, h, VE_DEFAULT, 0, 0 }
static const ShipVehicleInfo _orig_ship_vehicle_info[] = { static const ShipVehicleInfo _orig_ship_vehicle_info[] = {
/* image_index capacity refittable /* image_index max_speed sfx refittable
* | cost_factor running_cost | * | cost_factor capacity | |
* | | max_speed | sfx | * | | acceleration running_cost |
* | | | | | | | */ * | | | | | | | | */
SVI( 1, 160, 48, 220, 140, SND_06_DEPARTURE_CARGO_SHIP, 0 ), // 0 MPS Oil Tanker SVI( 1, 160, 1, 48, 220, 140, SND_06_DEPARTURE_CARGO_SHIP, 0 ), // 0 MPS Oil Tanker
SVI( 1, 176, 80, 350, 125, SND_06_DEPARTURE_CARGO_SHIP, 0 ), // 1 CS-Inc. Oil Tanker SVI( 1, 176, 1, 80, 350, 125, SND_06_DEPARTURE_CARGO_SHIP, 0 ), // 1 CS-Inc. Oil Tanker
SVI( 2, 96, 64, 100, 90, SND_07_DEPARTURE_FERRY, 0 ), // 2 MPS Passenger Ferry SVI( 2, 96, 1, 64, 100, 90, SND_07_DEPARTURE_FERRY, 0 ), // 2 MPS Passenger Ferry
SVI( 2, 112, 128, 130, 80, SND_07_DEPARTURE_FERRY, 0 ), // 3 FFP Passenger Ferry SVI( 2, 112, 1, 128, 130, 80, SND_07_DEPARTURE_FERRY, 0 ), // 3 FFP Passenger Ferry
SVI( 3, 148, 224, 100, 190, SND_07_DEPARTURE_FERRY, 0 ), // 4 Bakewell 300 Hovercraft SVI( 3, 148, 1, 224, 100, 190, SND_07_DEPARTURE_FERRY, 0 ), // 4 Bakewell 300 Hovercraft
SVI( 2, 96, 64, 100, 90, SND_07_DEPARTURE_FERRY, 0 ), // 5 Chugger-Chug Passenger Ferry SVI( 2, 96, 1, 64, 100, 90, SND_07_DEPARTURE_FERRY, 0 ), // 5 Chugger-Chug Passenger Ferry
SVI( 2, 112, 128, 130, 80, SND_07_DEPARTURE_FERRY, 0 ), // 6 Shivershake Passenger Ferry SVI( 2, 112, 1, 128, 130, 80, SND_07_DEPARTURE_FERRY, 0 ), // 6 Shivershake Passenger Ferry
SVI( 0, 128, 48, 160, 150, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 7 Yate Cargo ship SVI( 0, 128, 1, 48, 160, 150, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 7 Yate Cargo ship
SVI( 0, 144, 80, 190, 113, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 8 Bakewell Cargo ship SVI( 0, 144, 1, 80, 190, 113, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 8 Bakewell Cargo ship
SVI( 0, 128, 48, 160, 150, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 9 Mightymover Cargo ship SVI( 0, 128, 1, 48, 160, 150, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 9 Mightymover Cargo ship
SVI( 0, 144, 80, 190, 113, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 10 Powernaut Cargo ship SVI( 0, 144, 1, 80, 190, 113, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 10 Powernaut Cargo ship
}; };
#undef SVI #undef SVI

@ -135,7 +135,7 @@ from = SLV_97
flags = SF_NEWGAME_ONLY | SF_SCENEDIT_TOO | SF_GUI_CURRENCY | SF_GUI_0_IS_SPECIAL flags = SF_NEWGAME_ONLY | SF_SCENEDIT_TOO | SF_GUI_CURRENCY | SF_GUI_0_IS_SPECIAL
def = 300000 def = 300000
min = LOAN_INTERVAL min = LOAN_INTERVAL
max = 2000000000 max = MAX_LOAN_LIMIT
pre_cb = [](auto &new_value) { new_value = (new_value + LOAN_INTERVAL / 2) / LOAN_INTERVAL * LOAN_INTERVAL; return true; } pre_cb = [](auto &new_value) { new_value = (new_value + LOAN_INTERVAL / 2) / LOAN_INTERVAL * LOAN_INTERVAL; return true; }
interval = LOAN_INTERVAL interval = LOAN_INTERVAL
str = STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN str = STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN
@ -371,3 +371,11 @@ min = 0
max = 3 max = 3
cat = SC_BASIC cat = SC_BASIC
[SDT_BOOL]
var = difficulty.infinite_money
def = false
str = STR_CONFIG_SETTING_INFINITE_MONEY
strhelp = STR_CONFIG_SETTING_INFINITE_MONEY_HELPTEXT
cat = SC_BASIC
post_cb = [](auto) { SetWindowDirty(WC_STATUS_BAR, 0); }

@ -238,6 +238,7 @@ strval = STR_VARIETY_NONE
var = game_creation.generation_seed var = game_creation.generation_seed
type = SLE_UINT32 type = SLE_UINT32
from = SLV_30 from = SLV_30
flags = SF_NOT_IN_CONFIG
def = GENERATE_NEW_SEED def = GENERATE_NEW_SEED
min = 0 min = 0
max = UINT32_MAX max = UINT32_MAX

@ -5,7 +5,7 @@
* 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/>. * 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 bitmath_func_test.cpp Test functionality from core/bitmath_func. */ /** @file bitmath_func.cpp Test functionality from core/bitmath_func. */
#include "../stdafx.h" #include "../stdafx.h"

@ -19,6 +19,7 @@
#include "viewport_kdtree.h" #include "viewport_kdtree.h"
#include "cmd_helper.h" #include "cmd_helper.h"
#include "command_func.h" #include "command_func.h"
#include "company_func.h"
#include "industry.h" #include "industry.h"
#include "station_base.h" #include "station_base.h"
#include "waypoint_base.h" #include "waypoint_base.h"
@ -1546,7 +1547,7 @@ static inline bool RoadTypesAllowHouseHere(TileIndex t)
TileIndex cur_tile = t + ToTileIndexDiff(*ptr); TileIndex cur_tile = t + ToTileIndexDiff(*ptr);
if (!IsValidTile(cur_tile)) continue; if (!IsValidTile(cur_tile)) continue;
if (!(IsTileType(cur_tile, MP_ROAD) || IsTileType(cur_tile, MP_STATION))) continue; if (!(IsTileType(cur_tile, MP_ROAD) || IsAnyRoadStopTile(cur_tile))) continue;
allow = true; allow = true;
RoadType road_rt = GetRoadTypeRoad(cur_tile); RoadType road_rt = GetRoadTypeRoad(cur_tile);
@ -3863,7 +3864,7 @@ uint GetMaskOfTownActions(int *nump, CompanyID cid, const Town *t)
if (cid != COMPANY_SPECTATOR && !(_settings_game.economy.bribe && t->unwanted[cid])) { if (cid != COMPANY_SPECTATOR && !(_settings_game.economy.bribe && t->unwanted[cid])) {
/* Things worth more than this are not shown */ /* Things worth more than this are not shown */
Money avail = Company::Get(cid)->money + _price[PR_STATION_VALUE] * 200; Money avail = GetAvailableMoney(cid) + _price[PR_STATION_VALUE] * 200;
/* Check the action bits for validity and /* Check the action bits for validity and
* if they are valid add them */ * if they are valid add them */

@ -277,7 +277,7 @@ bool Vehicle::NeedsServicing() const
* There are a lot more reasons for autoreplace to fail than we can test here reasonably. */ * There are a lot more reasons for autoreplace to fail than we can test here reasonably. */
bool pending_replace = false; bool pending_replace = false;
Money needed_money = c->settings.engine_renew_money; Money needed_money = c->settings.engine_renew_money;
if (needed_money > c->money) return false; if (needed_money > GetAvailableMoney(c->index)) return false;
for (const Vehicle *v = this; v != nullptr; v = (v->type == VEH_TRAIN) ? Train::From(v)->GetNextUnit() : nullptr) { for (const Vehicle *v = this; v != nullptr; v = (v->type == VEH_TRAIN) ? Train::From(v)->GetNextUnit() : nullptr) {
bool replace_when_old = false; bool replace_when_old = false;
@ -331,7 +331,7 @@ bool Vehicle::NeedsServicing() const
* We want 2*(the price of the new vehicle) without looking at the value of the vehicle we are going to sell. */ * We want 2*(the price of the new vehicle) without looking at the value of the vehicle we are going to sell. */
pending_replace = true; pending_replace = true;
needed_money += 2 * Engine::Get(new_engine)->GetCost(); needed_money += 2 * Engine::Get(new_engine)->GetCost();
if (needed_money > c->money) return false; if (needed_money > GetAvailableMoney(c->index)) return false;
} }
return pending_replace; return pending_replace;

Loading…
Cancel
Save