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 2 months ago
commit 838b166726

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

@ -34,7 +34,6 @@ execute_process(COMMAND ${OPENTTD_EXECUTABLE}
-mnull
-vnull:ticks=30000
-d script=2
-d misc=9
-Q
OUTPUT_VARIABLE REGRESSION_OUTPUT
ERROR_VARIABLE REGRESSION_RESULT
@ -58,16 +57,24 @@ string(REPLACE "0x0x0" "0x00000000" REGRESSION_RESULT "${REGRESSION_RESULT}")
string(REPLACE "\\" "/" REGRESSION_RESULT "${REGRESSION_RESULT}")
# 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
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 "\nERROR: [1] " "\n" REGRESSION_RESULT "${REGRESSION_RESULT}")
string(REPLACE "\n[P] " "\n" REGRESSION_RESULT "${REGRESSION_RESULT}")
string(REPLACE "\n[S] " "\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
file(READ ai/${REGRESSION_TEST}/result.txt REGRESSION_EXPECTED)
@ -91,7 +98,7 @@ foreach(RESULT IN LISTS REGRESSION_RESULT)
if(NOT RESULT STREQUAL EXPECTED)
message("${ARGC}: - ${EXPECTED}")
message("${ARGC}: + ${RESULT}'")
message("${ARGC}: + ${RESULT}")
set(ERROR YES)
endif()
endforeach()

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

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

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

@ -180,8 +180,16 @@ struct AIConfigWindow : public Window {
} else {
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;
}
break;
@ -228,7 +236,7 @@ struct AIConfigWindow : public Window {
case WID_AIC_LIST: { // Select a slot
this->selected_slot = (CompanyID)this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget);
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;
}
@ -258,7 +266,7 @@ struct AIConfigWindow : public Window {
}
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;
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
{
if (!IsEditable(this->selected_slot)) {
if (!IsEditable(this->selected_slot) && !Company::IsValidAiID(this->selected_slot)) {
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_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_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_MOVE_UP, this->selected_slot == INVALID_COMPANY || !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_UP, !IsEditable(this->selected_slot) || !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());
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::AddLabels, "AddLabels");
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_INGAME, "CONFIG_INGAME");
SQAIInfo.DefSQConst(engine, SCRIPTCONFIG_DEVELOPER, "CONFIG_DEVELOPER");
/* Pre 1.2 had an AI prefix */
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_INGAME, "AICONFIG_INGAME");

@ -129,6 +129,7 @@ CommandProc CmdSetCompanyColour;
CommandProc CmdIncreaseLoan;
CommandProc CmdDecreaseLoan;
CommandProcEx CmdSetCompanyMaxLoan;
CommandProc CmdWantEnginePreview;
CommandProc CmdEngineCtrl;
@ -397,6 +398,7 @@ static const Command _command_proc_table[] = {
DEF_CMD(CmdIncreaseLoan, 0, CMDT_MONEY_MANAGEMENT ), // CMD_INCREASE_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(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;
}
/*!
/**
* This function range-checks a cmd, and checks if the cmd is not nullptr
*
* @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;
}
/*!
/**
* This function mask the parameter with CMD_ID_MASK and returns
* 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;
}
/*!
/**
* This function mask the parameter with CMD_ID_MASK and returns
* the name which belongs to the given command.
*
@ -819,7 +821,7 @@ private:
char buffer[64];
};
/*!
/**
* 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.
*
@ -889,20 +891,6 @@ error:
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)
{
if (_debug_command_level <= 0) return;
@ -910,7 +898,7 @@ static void DebugLogCommandLogEntry(const CommandLogEntry &entry)
char buffer[256];
char *b = buffer;
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)
@ -949,7 +937,7 @@ static void AppendCommandLogEntry(const CommandCost &res, TileIndex tile, uint32
cmd_log.count++;
}
/*!
/**
* 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
* \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; }
/*!
/**
* Helper function for the toplevel network safe docommand function for the current company.
*
* @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)",
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));
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);
CommandFlags GetCommandFlags(uint32_t cmd);
const char *GetCommandName(uint32_t cmd);
Money GetAvailableMoneyForCommand();
bool IsCommandAllowedWhilePaused(uint32_t cmd);
/**

@ -348,6 +348,7 @@ enum Commands {
CMD_INCREASE_LOAN, ///< increase 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_ENGINE_CTRL, ///< control availability of the engine for companies

@ -19,6 +19,8 @@
#include <array>
#include <string>
static const Money COMPANY_MAX_LOAN_DEFAULT = INT64_MIN;
/** Statistics about the economy. */
struct CompanyEconomyEntry {
Money income; ///< The amount of income.
@ -60,7 +62,6 @@ DECLARE_ENUM_AS_BIT_SET(CompanyBankruptcyFlags)
typedef Pool<Company, CompanyID, 1, MAX_COMPANIES> CompanyPool;
extern CompanyPool _company_pool;
/** Statically loadable part of Company pool item */
struct CompanyProperties {
uint32_t name_2; ///< Parameter of #name_1.
@ -76,6 +77,7 @@ struct CompanyProperties {
Money money; ///< Money owned by the company.
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 max_loan; ///< Max allowed amount of the loan or COMPANY_MAX_LOAN_DEFAULT.
Colours colour; ///< Company colour.
@ -120,8 +122,8 @@ struct CompanyProperties {
// TODO: Change some of these member variables to use relevant INVALID_xxx constants
CompanyProperties()
: 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),
location_of_HQ(0), last_build_coordinate(0), share_owners(), inaugurated_year(0),
face(0), money(0), money_fraction(0), current_loan(0), max_loan(COMPANY_MAX_LOAN_DEFAULT), colour(COLOUR_BEGIN),
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),
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.
Money GetMaxLoan() const;
/**
* Is this company a valid company, controlled by the computer (a NoAI program)?
* @param index Index in the pool.

@ -109,6 +109,16 @@ void Company::PostDestructor(size_t index)
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
* 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);
}
/**
* 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.
* @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)
{
if (cost.GetCost() > 0) {
const Company *c = Company::GetIfValid(_current_company);
if (c != nullptr && cost.GetCost() > c->money) {
SetDParam(0, cost.GetCost());
cost.MakeError(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY);
return false;
}
if (cost.GetCost() <= 0) return true;
if (_settings_game.difficulty.infinite_money) return true;
const Company *c = Company::GetIfValid(_current_company);
if (c != nullptr && cost.GetCost() > c->money) {
SetDParam(0, cost.GetCost());
cost.MakeError(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY);
return false;
}
return true;
}

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

@ -373,9 +373,11 @@ struct CompanyFinancesWindow : Window {
SetDParam(0, _settings_game.difficulty.initial_interest);
break;
case WID_CF_MAXLOAN_VALUE:
SetDParam(0, _economy.max_loan);
case WID_CF_MAXLOAN_VALUE: {
const Company *c = Company::Get((CompanyID)this->window_number);
SetDParam(0, c->GetMaxLoan());
break;
}
case WID_CF_INCREASE_LOAN:
case WID_CF_REPAY_LOAN:
@ -473,7 +475,7 @@ struct CompanyFinancesWindow : Window {
}
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.
}

@ -36,8 +36,9 @@
/** Element in the queue of debug messages that have to be passed to either NetworkAdminConsole or IConsolePrint.*/
struct QueuedDebugItem {
std::string level; ///< The used debug level.
std::string message; ///< The actual formatted message.
std::string category; ///< The used debug category.
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::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.
* @param dbg Debug category.
* @param level Debug level.
* @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) {
static FILE *f = FioFOpenFile("commands-out.log", "wb", AUTOSAVE_DIR);
if (f != nullptr) {
fprintf(f, "%s%s\n", log_prefix().GetLogPrefix(), buf);
fprintf(f, "%s%s\n", log_prefix().GetLogPrefix(true), buf);
fflush(f);
}
#ifdef RANDOM_DEBUG
@ -178,7 +180,7 @@ void debug_print(const char *dbg, const char *buf)
}
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);
@ -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. */
if (IsNonGameThread()) {
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 {
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 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];
@ -220,7 +222,7 @@ void CDECL debug(const char *dbg, const char *format, ...)
vseprintf(buf, lastof(buf), format, 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
* the date, otherwise it returns nothing.
* @return the prefix for logs (do not free), never nullptr
* Get the prefix for logs.
*
* 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] ");
} else {
this->buffer[0] = '\0';
@ -430,8 +435,8 @@ void DebugSendRemoteMessages()
}
for (auto &item : _debug_remote_console_queue_spare) {
NetworkAdminConsole(item.level.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());
NetworkAdminConsole(item.category.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();

@ -33,7 +33,7 @@
* @param name Category
* @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_grf_level;
@ -64,8 +64,8 @@ extern std::string _loadgame_DBGL_data;
extern bool _save_DBGC_data;
extern std::string _loadgame_DBGC_data;
void CDECL debug(const char *dbg, const char *format, ...) WARN_FORMAT(2, 3);
void debug_print(const char *dbg, const char *buf);
void CDECL debug(const char *dbg, int level, const char *format, ...) WARN_FORMAT(3, 4);
void debug_print(const char *dbg, int level, const char *buf);
char *DumpDebugFacilityNames(char *buf, char *last);
void SetDebugString(const char *s, void (*error_func)(const char *));
@ -117,7 +117,7 @@ inline void ShowInfoI(const std::string &str)
}
struct log_prefix {
const char *GetLogPrefix();
const char *GetLogPrefix(bool force = false);
private:
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 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 fatalerror_str(const char *msg);

@ -680,9 +680,12 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner)
*/
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 (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);
c->months_of_bankruptcy = 0;
c->bankrupt_asked = 0;
@ -952,17 +955,21 @@ static void CompaniesPayInterest()
/* Over a year the paid interest should be "loan * interest percentage",
* but... as that number is likely not dividable by 12 (pay each month),
* one needs to account for that in the monthly fee calculations.
*
* To easily calculate what one should pay "this" month, you calculate
* 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
* 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.
*
* 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;
if (c->money < 0) {
yearly_fee += -c->money *_economy.interest_rate / 100;
Money available_money = GetAvailableMoney(c->index);
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_this_month = yearly_fee * (EconTime::CurMonth() + 1) / 12;

@ -205,6 +205,8 @@ struct PriceBaseSpec {
static const int LOAN_INTERVAL = 10000;
/** The size of loan for a new company, in British Pounds! */
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.

@ -85,6 +85,8 @@ Engine::Engine(VehicleType type, EngineID base)
if (type == VEH_ROAD) this->u.road.tractive_effort = 0x4C;
/* Aircraft must have INVALID_CARGO as default, as there is no property */
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 */
switch (type) {
case VEH_TRAIN: this->u.rail.visual_effect = VE_DEFAULT; break;

@ -67,6 +67,7 @@ struct RailVehicleInfo {
struct ShipVehicleInfo {
byte image_index;
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 capacity;
byte running_cost;

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

@ -31,8 +31,9 @@ public:
/**
* 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.

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

@ -206,10 +206,18 @@ struct GSConfigWindow : public Window {
TextColour colour;
uint idx = 0;
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;
} 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;
SetDParamStr(idx++, config_item.description);
}
@ -223,13 +231,36 @@ struct GSConfigWindow : public Window {
} 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);
}
auto config_iterator = config_item.labels.find(current_value);
if (config_iterator != config_item.labels.end()) {
SetDParam(idx++, STR_JUST_RAW_STRING);
SetDParamStr(idx++, config_iterator->second);
if (Game::GetInstance() != nullptr || config_item.random_deviation == 0) {
auto config_iterator = config_item.labels.find(current_value);
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 {
SetDParam(idx++, STR_JUST_INT);
SetDParam(idx++, current_value);
int min_deviated = std::max(config_item.min_value, current_value - config_item.random_deviation);
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::AddLabels, "AddLabels");
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_INGAME, "CONFIG_INGAME");
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));
}
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! */
GfxLoadSprites();

@ -37,11 +37,14 @@
static CompanyMask _legend_excluded_companies;
static CargoTypes _legend_excluded_cargo;
uint8_t _cargo_payment_x_mode;
/* 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 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 */
@ -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. */
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;
/* 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;
int64_t grid_size;
OverflowSafeInt64 grid_size;
if (abs_lower != 0 || abs_higher != 0) {
/* 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--;
/* 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);
} else {
/* If both values are zero, show an empty graph. */
@ -637,7 +651,13 @@ public:
if (c != nullptr) {
this->colours[numd] = _colour_gradient[c->colour][6];
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++;
}
}

@ -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)
{
Company *c = Company::Get(_current_company);
if (c->current_loan >= _economy.max_loan) {
SetDParam(0, _economy.max_loan);
Money max_loan = c->GetMaxLoan();
if (c->current_loan >= max_loan) {
SetDParam(0, max_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;
break;
case 1: // Take a loan as big as possible
loan = _economy.max_loan - c->current_loan;
loan = max_loan - c->current_loan;
break;
case 2: // Take the given amount of loan
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;
}
@ -106,7 +106,7 @@ CommandCost CmdDecreaseLoan(TileIndex tile, DoCommandFlag flags, uint32_t p1, ui
loan = std::min(c->current_loan, (Money)LOAN_INTERVAL);
break;
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;
break;
case 2: // Repay the given amount of loan
@ -115,7 +115,7 @@ CommandCost CmdDecreaseLoan(TileIndex tile, DoCommandFlag flags, uint32_t p1, ui
break;
}
if (c->money < loan) {
if (GetAvailableMoneyForCommand() < loan) {
SetDParam(0, loan);
return_cmd_error(STR_ERROR_CURRENCY_REQUIRED);
}
@ -128,6 +128,38 @@ CommandCost CmdDecreaseLoan(TileIndex tile, DoCommandFlag flags, uint32_t p1, ui
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
* user to confirm that it might crash.

@ -73,15 +73,16 @@ NetworkHTTPRequest::NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *ca
static std::string GetLastErrorAsString()
{
char buffer[512];
wchar_t buffer[512];
DWORD error_code = GetLastError();
if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandleA("winhttp.dll"), error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), nullptr) == 0) {
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, lengthof(buffer), nullptr) == 0) {
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 defined(_WIN32)
char buffer[512];
if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, this->error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), nullptr) == 0) {
seprintf(buffer, lastof(buffer), "Unknown error %d", this->error);
wchar_t buffer[512];
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, this->error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, lengthof(buffer), nullptr) == 0) {
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
/* 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.

@ -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
{
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:
*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;

@ -1542,7 +1542,7 @@ static ChangeInfoResult ShipVehicleChangeInfo(uint engine, int numinfo, int prop
svi->cost_factor = buf->ReadByte();
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();
break;
@ -1671,6 +1671,14 @@ static ChangeInfoResult ShipVehicleChangeInfo(uint engine, int numinfo, int prop
SB(ei->callback_mask, 8, 8, buf->ReadByte());
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:
ret = CommonVehicleChangeInfo(ei, prop, mapping_entry, buf);
break;

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

@ -678,7 +678,7 @@ struct AfterNewGRFScan : NewGRFScanCallback {
SetEffectVolume(_settings_client.music.effect_vol);
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()) {
_network_bind_list.clear();

@ -20,13 +20,13 @@ static std::string GetLoadError()
{
auto error_code = GetLastError();
char buffer[512];
if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), nullptr) == 0) {
wchar_t buffer[512];
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, lengthof(buffer), nullptr) == 0) {
return fmt::format("Unknown error {}", error_code);
}
return buffer;
return FS2OTTD(buffer);
}
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)
{
#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_X)) {

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

@ -653,7 +653,7 @@ static void StartScripts()
}
/* Start the GameScript. */
Game::StartNew();
Game::StartNew(false);
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()) {
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_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, money_fraction, SLE_UINT8),

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

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

@ -186,7 +186,7 @@
company = ResolveCompanyID(company);
if (company == COMPANY_INVALID) return -1;
return ::Company::Get(company)->money;
return GetAvailableMoney((::CompanyID)company);
}
/* static */ Money ScriptCompany::GetLoanAmount()
@ -199,7 +199,32 @@
/* 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()

@ -219,12 +219,38 @@ public:
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.
* @post GetLoanInterval() is always a multiplier of the return value.
*/
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.
* @return The loan step.

@ -203,7 +203,6 @@ public:
/** Miscellaneous flags for Script settings. */
enum ScriptConfigFlags {
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_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.
@ -236,10 +235,11 @@ public:
* is selected. Required. The value will be clamped in the range
* [MIN(int32_t), MAX(int32_t)] (inclusive).
* - random_deviation If this property has a nonzero value, then the
* actual value of the setting in game will be randomized in the range
* actual value of the setting in game will be randomised in the range
* [user_configured_value - random_deviation, user_configured_value + random_deviation] (inclusive).
* random_deviation sign is ignored and the value is clamped in the range [0, MAX(int32_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
* clicks one of the up/down arrow buttons. Optional, default is 1.
* - 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->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)
@ -58,9 +46,6 @@ ScriptConfig::ScriptConfig(const ScriptConfig *config)
for (const auto &item : config->settings) {
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()

@ -23,7 +23,7 @@ static const int INT32_DIGITS_WITH_SIGN_AND_TERMINATION = 10 + 1 + 1;
/** Bitmask of flags for Script settings. */
enum ScriptConfigFlags {
SCRIPTCONFIG_NONE = 0x0, ///< No flags set.
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_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.

@ -395,10 +395,18 @@ struct ScriptSettingsWindow : public Window {
TextColour colour;
uint idx = 0;
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;
} 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;
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);
}
auto config_iterator = config_item.labels.find(current_value);
if (config_iterator != config_item.labels.end()) {
SetDParam(idx++, STR_JUST_RAW_STRING);
SetDParamStr(idx++, config_iterator->second);
if (this->slot == OWNER_DEITY || Company::IsValidID(this->slot) || config_item.random_deviation == 0) {
auto config_iterator = config_item.labels.find(current_value);
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 {
SetDParam(idx++, STR_JUST_INT);
SetDParam(idx++, current_value);
int min_deviated = std::max(config_item.min_value, current_value - config_item.random_deviation);
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);
/* 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. */
if ((items & 0x200) != 0 && (config.flags & SCRIPTCONFIG_RANDOM) != 0) {
this->engine->ThrowError("Setting both random_deviation and SCRIPTCONFIG_RANDOM is not allowed");
if ((items & 0x200) != 0 && (config.flags & SCRIPTCONFIG_BOOLEAN) != 0) {
this->engine->ThrowError("setting both random_deviation and CONFIG_BOOLEAN is not allowed");
return SQ_ERROR;
}
/* Reset the bit for random_deviation as it's optional. */
items &= ~0x200;

@ -219,8 +219,10 @@ enum IniFileVersion : uint32_t {
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_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_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.
};
@ -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. */
if (generic_version < IFV_NETWORK_PRIVATE_SETTINGS) {
IniGroup *network = generic_ini.GetGroup("network");

@ -2417,6 +2417,7 @@ static SettingsContainer &GetSettingsTree()
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_fixed_dates"));
accounting->Add(new SettingEntry("difficulty.initial_interest"));

@ -116,6 +116,7 @@ struct DifficultySettings {
bool line_reverse_mode; ///< reversing at stations or not
bool disasters; ///< are disasters enabled
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 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

@ -529,7 +529,7 @@ static uint ShipAccelerate(Vehicle *v)
{
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);
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);
if (number_of_steps == 0 && v->current_order.IsType(OT_LEAVESTATION)) number_of_steps = 1;
for (uint i = 0; i < number_of_steps; ++i) {
if (ShipMoveUpDownOnLock(v)) return;
GetNewVehiclePosResult gp = GetNewVehiclePos(v);
if (v->state != TRACK_BIT_WORMHOLE) {
/* 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->random_bits = Random();
v->acceleration = svi->acceleration;
v->UpdateCache();
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_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_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_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_NEWGRF_LAST_SERVICE, ///< See: SLV_NEWGRF_LAST_SERVICE (PR #11124)
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_MISC_SL, ///< Use upstream table format for miscellaneous chunks, so far: DATE, VIEW, MAPS

@ -46,6 +46,7 @@
#include "../error.h"
#include "../scope.h"
#include "../core/ring_buffer.hpp"
#include "../timer/timer_game_tick.h"
#include <atomic>
#include <string>
#ifdef __EMSCRIPTEN__
@ -4115,14 +4116,23 @@ std::string GenerateDefaultSaveName()
SetDParam(0, cid);
/* 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();
/* We show the current game time differently depending on the timekeeping units used by this game. */
if (EconTime::UsingWallclockUnits() && CalTime::IsCalendarFrozen()) {
/* Insert time played. */
const auto play_time = _scaled_tick_counter / TICKS_PER_SECOND;
SetDParam(1, STR_SAVEGAME_DURATION_REALTIME);
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) */
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_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_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

@ -162,6 +162,8 @@ struct StatusBarWindow : Window {
case WID_S_RIGHT: {
if (_local_company == COMPANY_SPECTATOR) {
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 {
/* Draw company money, if any */
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. */
#if !defined(NDEBUG) || defined(WITH_ASSERT)
# undef assert
# define assert(expression) if (unlikely(!(expression))) error("Assertion failed at line %i of %s: %s", __LINE__, __FILE__, #expression);
# define assert_msg(expression, ...) if (unlikely(!(expression))) assert_msg_error(__LINE__, __FILE__, #expression, nullptr, __VA_ARGS__);
# define assert_msg_tile(expression, tile, ...) if (unlikely(!(expression))) assert_msg_error(__LINE__, __FILE__, #expression, assert_tile_info(tile), __VA_ARGS__);
# 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_str(expression, str) if (unlikely(!(expression))) assert_str_error(__LINE__, __FILE__, #expression, str);
# 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, ...) do { if (unlikely(!(expression))) assert_msg_error(__LINE__, __FILE__, #expression, nullptr, __VA_ARGS__); } while (false)
# 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) 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) do { if (unlikely(!(expression))) assert_str_error(__LINE__, __FILE__, #expression, str); } while (false)
#else
# undef assert
# 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
* keep this piece of data as we need it later on */
bool negative = number < 0;
const char *multiplier = "";
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 */
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) {
/* 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. */
if (number >= 1000000000 - 500) {
number = (number + 500000) / 1000000;
multiplier = NBSP "M";
} else if (number >= 1000000) {
number = (number + 500) / 1000;
multiplier = NBSP "k";
if (number >= Money(1'000'000'000'000'000) - 500'000'000) {
number = (number + Money(500'000'000'000)) / Money(1'000'000'000'000);
number_str = STR_CURRENCY_SHORT_TERA;
} else if (number >= Money(1'000'000'000'000) - 500'000) {
number = (number + 500'000'000) / 1'000'000'000;
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 = _langpack.langpack->digit_group_separator_currency;
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.
* 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
* @param a image_index
* @param b cost_factor
* @param c max_speed (1 unit = 1/3.2 mph = 0.5 km-ish/h)
* @param d capacity (persons, bags, tons, pieces, items, cubic metres, ...)
* @param e running_cost
* @param f sound effect
* @param g refittable
* @param c acceleration (1 unit = 1/3.2 mph per tick = 0.5 km-ish/h per tick)
* @param d max_speed (1 unit = 1/3.2 mph = 0.5 km-ish/h)
* @param e capacity (persons, bags, tons, pieces, items, cubic metres, ...)
* @param f running_cost
* @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[] = {
/* image_index capacity refittable
* | cost_factor running_cost |
* | | max_speed | sfx |
* | | | | | | | */
SVI( 1, 160, 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( 2, 96, 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( 3, 148, 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, 112, 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, 144, 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, 144, 80, 190, 113, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 10 Powernaut Cargo ship
/* image_index max_speed sfx refittable
* | cost_factor capacity | |
* | | acceleration running_cost |
* | | | | | | | | */
SVI( 1, 160, 1, 48, 220, 140, SND_06_DEPARTURE_CARGO_SHIP, 0 ), // 0 MPS Oil Tanker
SVI( 1, 176, 1, 80, 350, 125, SND_06_DEPARTURE_CARGO_SHIP, 0 ), // 1 CS-Inc. Oil Tanker
SVI( 2, 96, 1, 64, 100, 90, SND_07_DEPARTURE_FERRY, 0 ), // 2 MPS Passenger Ferry
SVI( 2, 112, 1, 128, 130, 80, SND_07_DEPARTURE_FERRY, 0 ), // 3 FFP Passenger Ferry
SVI( 3, 148, 1, 224, 100, 190, SND_07_DEPARTURE_FERRY, 0 ), // 4 Bakewell 300 Hovercraft
SVI( 2, 96, 1, 64, 100, 90, SND_07_DEPARTURE_FERRY, 0 ), // 5 Chugger-Chug Passenger Ferry
SVI( 2, 112, 1, 128, 130, 80, SND_07_DEPARTURE_FERRY, 0 ), // 6 Shivershake Passenger Ferry
SVI( 0, 128, 1, 48, 160, 150, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 7 Yate Cargo ship
SVI( 0, 144, 1, 80, 190, 113, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 8 Bakewell Cargo ship
SVI( 0, 128, 1, 48, 160, 150, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 9 Mightymover Cargo ship
SVI( 0, 144, 1, 80, 190, 113, SND_06_DEPARTURE_CARGO_SHIP, 1 ), // 10 Powernaut Cargo ship
};
#undef SVI

@ -135,7 +135,7 @@ from = SLV_97
flags = SF_NEWGAME_ONLY | SF_SCENEDIT_TOO | SF_GUI_CURRENCY | SF_GUI_0_IS_SPECIAL
def = 300000
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; }
interval = LOAN_INTERVAL
str = STR_CONFIG_SETTING_MAXIMUM_INITIAL_LOAN
@ -371,3 +371,11 @@ min = 0
max = 3
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
type = SLE_UINT32
from = SLV_30
flags = SF_NOT_IN_CONFIG
def = GENERATE_NEW_SEED
min = 0
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/>.
*/
/** @file bitmath_func_test.cpp Test functionality from core/bitmath_func. */
/** @file bitmath_func.cpp Test functionality from core/bitmath_func. */
#include "../stdafx.h"

@ -19,6 +19,7 @@
#include "viewport_kdtree.h"
#include "cmd_helper.h"
#include "command_func.h"
#include "company_func.h"
#include "industry.h"
#include "station_base.h"
#include "waypoint_base.h"
@ -1546,7 +1547,7 @@ static inline bool RoadTypesAllowHouseHere(TileIndex t)
TileIndex cur_tile = t + ToTileIndexDiff(*ptr);
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;
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])) {
/* 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
* 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. */
bool pending_replace = false;
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) {
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. */
pending_replace = true;
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;

Loading…
Cancel
Save