Compare commits

..

11 Commits

Author SHA1 Message Date
Jonathan G Rennison 3136f08b86 Debug: Fix display of engine cargo age period and reliability decay speed 1 month ago
Jonathan G Rennison 33baceaef7 Maintain timer sort invariants when changing period
See: https://github.com/OpenTTD/OpenTTD/issues/12509
1 month ago
Jonathan G Rennison 674642f9cc Add a priority field to TimerGameTick::TPeriod
Use this as the primary sort key for TimerGameTick::TPeriod,
to avoid container sort order changes on timer period saveload.
1 month ago
Jonathan G Rennison d5b8f51bf9 Rename variable to fix Windows header name collision 1 month ago
Jonathan G Rennison 71227f61d8 Use MoveFileExW to implement FioRenameFile on Windows
This is to allow renaming over an existing file
1 month ago
Jonathan G Rennison 083d91a582 Remove use of shell API function for rename 1 month ago
Peter Nelson 653e217bb1 Fix: Signature validation did not close its file. (#12479)
(cherry picked from commit 3316b27496)
1 month ago
Peter Nelson 8fdc91bd9f Fix a29766d: Wrong scrolling dropdown list position with RTL. (#12412)
(cherry picked from commit 9750826590)
1 month ago
Loïc Guilloux 54093fb8b2 Fix: [Win32] Force font mapper to only use TrueType fonts (#12406)
(cherry picked from commit 11aa3694fa)
1 month ago
Jonathan G Rennison 98dc6c3c81 Fix NewGRF byte order when using -q 1 month ago
Peter Nelson ae16df2d61 Fix #12497: Add workaround for motion_counter being implemented correctly.
#12229 stopped updating motion_counter for non-engine parts of trains, and in doing so accidentally followed the spec for NewGRF var 46, which breaks NewGRFs that used to... accidentally work.

Make var 46 return motion_counter of the first engine, regardless of self or parent scope. This means var 46 is always in sync with the head engine, and avoids further changes to when motion_counter is updated.

(cherry picked from commit 9539b02455f672e11f3ac32302a00cffa5507770)
1 month ago

@ -668,7 +668,7 @@ Company *DoStartupNewCompany(DoStartupNewCompanyFlag flags, CompanyID company)
} }
/** Start a new competitor company if possible. */ /** Start a new competitor company if possible. */
TimeoutTimer<TimerGameTick> _new_competitor_timeout(0, []() { TimeoutTimer<TimerGameTick> _new_competitor_timeout({ TimerGameTick::Priority::COMPETITOR_TIMEOUT, 0 }, []() {
if (_game_mode == GM_MENU || !AI::CanStartNew()) return; if (_game_mode == GM_MENU || !AI::CanStartNew()) return;
if (_networking && Company::GetNumItems() >= _settings_client.network.max_companies) return; if (_networking && Company::GetNumItems() >= _settings_client.network.max_companies) return;
@ -850,7 +850,7 @@ void OnTick_Companies(bool main_tick)
/* Randomize a bit when the AI is actually going to start; ranges from 87.5% .. 112.5% of indicated value. */ /* Randomize a bit when the AI is actually going to start; ranges from 87.5% .. 112.5% of indicated value. */
timeout += ScriptObject::GetRandomizer(OWNER_NONE).Next(timeout / 4) - timeout / 8; timeout += ScriptObject::GetRandomizer(OWNER_NONE).Next(timeout / 4) - timeout / 8;
_new_competitor_timeout.Reset(std::max(1, timeout)); _new_competitor_timeout.Reset({ TimerGameTick::Priority::COMPETITOR_TIMEOUT, static_cast<uint>(std::max(1, timeout)) });
} }
} }

@ -383,7 +383,7 @@ void FioCreateDirectory(const std::string &name)
bool FioRenameFile(const std::string &oldname, const std::string &newname) bool FioRenameFile(const std::string &oldname, const std::string &newname)
{ {
#if defined(_WIN32) #if defined(_WIN32)
return _wrename(OTTD2FS(oldname).c_str(), OTTD2FS(newname).c_str()) == 0; return MoveFileExW(OTTD2FS(oldname).c_str(), OTTD2FS(newname).c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) != 0;
#else #else
return rename(oldname.c_str(), newname.c_str()) == 0; return rename(oldname.c_str(), newname.c_str()) == 0;
#endif #endif

@ -22,12 +22,6 @@
# include <fcntl.h> # include <fcntl.h>
#endif #endif
#ifdef _WIN32
# include <windows.h>
# include <shellapi.h>
# include "core/mem_func.hpp"
#endif
#include "safeguards.h" #include "safeguards.h"
/** /**
@ -91,30 +85,9 @@ bool IniFile::SaveToDisk(const std::string &filename)
if (ret != 0) return false; if (ret != 0) return false;
#endif #endif
#if defined(_WIN32) if (!FioRenameFile(file_new, filename)) {
/* Allocate space for one more \0 character. */
wchar_t tfilename[MAX_PATH + 1], tfile_new[MAX_PATH + 1];
wcsncpy(tfilename, OTTD2FS(filename).c_str(), MAX_PATH);
wcsncpy(tfile_new, OTTD2FS(file_new).c_str(), MAX_PATH);
/* SHFileOperation wants a double '\0' terminated string. */
tfilename[MAX_PATH - 1] = '\0';
tfile_new[MAX_PATH - 1] = '\0';
tfilename[wcslen(tfilename) + 1] = '\0';
tfile_new[wcslen(tfile_new) + 1] = '\0';
/* Rename file without any user confirmation. */
SHFILEOPSTRUCT shfopt;
MemSetT(&shfopt, 0);
shfopt.wFunc = FO_MOVE;
shfopt.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_SILENT;
shfopt.pFrom = tfile_new;
shfopt.pTo = tfilename;
SHFileOperation(&shfopt);
#else
if (rename(file_new.c_str(), filename.c_str()) < 0) {
DEBUG(misc, 0, "Renaming %s to %s failed; configuration not saved", file_new.c_str(), filename.c_str()); DEBUG(misc, 0, "Renaming %s to %s failed; configuration not saved", file_new.c_str(), filename.c_str());
} }
#endif
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs()); EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());

@ -680,7 +680,7 @@ static uint32_t VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *objec
} }
case 0x46: // Motion counter case 0x46: // Motion counter
return v->motion_counter; return v->First()->motion_counter;
case 0x47: { // Vehicle cargo info case 0x47: { // Vehicle cargo info
/* Format: ccccwwtt /* Format: ccccwwtt

@ -164,7 +164,7 @@ std::string NewGRFProfiler::GetOutputFilename() const
/** /**
* Check whether profiling is active and should be finished. * Check whether profiling is active and should be finished.
*/ */
static TimeoutTimer<TimerGameTick> _profiling_finish_timeout(0, []() static TimeoutTimer<TimerGameTick> _profiling_finish_timeout({ TimerGameTick::Priority::NONE, 0 }, []()
{ {
NewGRFProfiler::FinishAll(); NewGRFProfiler::FinishAll();
}); });
@ -174,7 +174,7 @@ static TimeoutTimer<TimerGameTick> _profiling_finish_timeout(0, []()
*/ */
/* static */ void NewGRFProfiler::StartTimer(uint64_t ticks) /* static */ void NewGRFProfiler::StartTimer(uint64_t ticks)
{ {
_profiling_finish_timeout.Reset(ticks); _profiling_finish_timeout.Reset({ TimerGameTick::Priority::NONE, static_cast<uint>(ticks) });
} }
/** /**

@ -396,7 +396,7 @@ static void WriteSavegameInfo(const char *name)
for (GRFConfig *c = _load_check_data.grfconfig; c != nullptr; c = c->next) { for (GRFConfig *c = _load_check_data.grfconfig; c != nullptr; c = c->next) {
char md5sum[33]; char md5sum[33];
md5sumToString(md5sum, lastof(md5sum), HasBit(c->flags, GCF_COMPATIBLE) ? c->original_md5sum : c->ident.md5sum); md5sumToString(md5sum, lastof(md5sum), HasBit(c->flags, GCF_COMPATIBLE) ? c->original_md5sum : c->ident.md5sum);
p += seprintf(p, lastof(buf), "%08X %s %s\n", c->ident.grfid, md5sum, c->filename.c_str()); p += seprintf(p, lastof(buf), "%08X %s %s\n", BSWAP32(c->ident.grfid), md5sum, c->filename.c_str());
} }
} }

@ -115,10 +115,6 @@ bool SetFallbackFont(FontCacheSettings *settings, const std::string &, int winla
} }
#ifndef ANTIALIASED_QUALITY
#define ANTIALIASED_QUALITY 4
#endif
/** /**
* Create a new Win32FontCache. * Create a new Win32FontCache.
* @param fs The font size that is going to be cached. * @param fs The font size that is going to be cached.
@ -170,7 +166,8 @@ void Win32FontCache::SetFontSize(int pixels)
/* Create GDI font handle. */ /* Create GDI font handle. */
this->logfont.lfHeight = -pixels; this->logfont.lfHeight = -pixels;
this->logfont.lfWidth = 0; this->logfont.lfWidth = 0;
this->logfont.lfOutPrecision = ANTIALIASED_QUALITY; this->logfont.lfOutPrecision = OUT_TT_ONLY_PRECIS;
this->logfont.lfQuality = ANTIALIASED_QUALITY;
if (this->font != nullptr) { if (this->font != nullptr) {
SelectObject(dc, this->old_font); SelectObject(dc, this->old_font);

@ -4410,7 +4410,7 @@ bool AfterLoadGame()
/* We did load the "period" of the timer, but not the fired/elapsed. We can deduce that here. */ /* We did load the "period" of the timer, but not the fired/elapsed. We can deduce that here. */
extern TimeoutTimer<TimerGameTick> _new_competitor_timeout; extern TimeoutTimer<TimerGameTick> _new_competitor_timeout;
_new_competitor_timeout.storage.elapsed = 0; _new_competitor_timeout.storage.elapsed = 0;
_new_competitor_timeout.fired = _new_competitor_timeout.period == 0; _new_competitor_timeout.fired = _new_competitor_timeout.period.value == 0;
} }
if (SlXvIsFeatureMissing(XSLFI_SAVEGAME_ID) && IsSavegameVersionBefore(SLV_SAVEGAME_ID)) { if (SlXvIsFeatureMissing(XSLFI_SAVEGAME_ID) && IsSavegameVersionBefore(SLV_SAVEGAME_ID)) {

@ -61,9 +61,9 @@ static const SaveLoad _date_desc[] = {
SLEG_CONDVAR("pause_mode", _pause_mode, SLE_UINT8, SLV_4, SL_MAX_VERSION), SLEG_CONDVAR("pause_mode", _pause_mode, SLE_UINT8, SLV_4, SL_MAX_VERSION),
SLEG_CONDSSTR("id", _game_session_stats.savegame_id, SLE_STR, SLV_SAVEGAME_ID, SL_MAX_VERSION), SLEG_CONDSSTR("id", _game_session_stats.savegame_id, SLE_STR, SLV_SAVEGAME_ID, SL_MAX_VERSION),
/* For older savegames, we load the current value as the "period"; afterload will set the "fired" and "elapsed". */ /* For older savegames, we load the current value as the "period"; afterload will set the "fired" and "elapsed". */
SLEG_CONDVAR("next_competitor_start", _new_competitor_timeout.period, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_109), SLEG_CONDVAR("next_competitor_start", _new_competitor_timeout.period.value, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_109),
SLEG_CONDVAR("next_competitor_start", _new_competitor_timeout.period, SLE_UINT32, SLV_109, SLV_AI_START_DATE), SLEG_CONDVAR("next_competitor_start", _new_competitor_timeout.period.value, SLE_UINT32, SLV_109, SLV_AI_START_DATE),
SLEG_CONDVAR("competitors_interval", _new_competitor_timeout.period, SLE_UINT32, SLV_AI_START_DATE, SL_MAX_VERSION), SLEG_CONDVAR("competitors_interval", _new_competitor_timeout.period.value, SLE_UINT32, SLV_AI_START_DATE, SL_MAX_VERSION),
SLEG_CONDVAR("competitors_interval_elapsed", _new_competitor_timeout.storage.elapsed, SLE_UINT32, SLV_AI_START_DATE, SL_MAX_VERSION), SLEG_CONDVAR("competitors_interval_elapsed", _new_competitor_timeout.storage.elapsed, SLE_UINT32, SLV_AI_START_DATE, SL_MAX_VERSION),
SLEG_CONDVAR("competitors_interval_fired", _new_competitor_timeout.fired, SLE_BOOL, SLV_AI_START_DATE, SL_MAX_VERSION), SLEG_CONDVAR("competitors_interval_fired", _new_competitor_timeout.fired, SLE_BOOL, SLV_AI_START_DATE, SL_MAX_VERSION),
}; };

@ -206,6 +206,7 @@ static bool _ValidateSignatureFile(const std::string &filename)
std::string text(filesize, '\0'); std::string text(filesize, '\0');
size_t len = fread(text.data(), filesize, 1, f); size_t len = fread(text.data(), filesize, 1, f);
FioFCloseFile(f);
if (len != 1) { if (len != 1) {
Debug(misc, 0, "Failed to validate signature: failed to read file: {}", filename); Debug(misc, 0, "Failed to validate signature: failed to read file: {}", filename);
return false; return false;

@ -105,8 +105,8 @@ static const NamedSaveLoad _date_desc[] = {
NSL("", SLE_CONDNULL(1, SL_MIN_VERSION, SLV_10)), NSL("", SLE_CONDNULL(1, SL_MIN_VERSION, SLV_10)),
NSL("", SLE_CONDNULL(4, SLV_10, SLV_120)), NSL("", SLE_CONDNULL(4, SLV_10, SLV_120)),
NSL("company_tick_counter", SLEG_VAR(_cur_company_tick_index, SLE_FILE_U8 | SLE_VAR_U32)), NSL("company_tick_counter", SLEG_VAR(_cur_company_tick_index, SLE_FILE_U8 | SLE_VAR_U32)),
NSL("", SLEG_CONDVAR(_new_competitor_timeout.period, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_109)), NSL("", SLEG_CONDVAR(_new_competitor_timeout.period.value, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_109)),
NSL("", SLEG_CONDVAR_X(_new_competitor_timeout.period, SLE_UINT32, SLV_109, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AI_START_DATE, 0, 0))), NSL("", SLEG_CONDVAR_X(_new_competitor_timeout.period.value, SLE_UINT32, SLV_109, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AI_START_DATE, 0, 0))),
NSL("trees_tick_counter", SLEG_VAR(_trees_tick_ctr, SLE_UINT8)), NSL("trees_tick_counter", SLEG_VAR(_trees_tick_ctr, SLE_UINT8)),
NSL("pause_mode", SLEG_CONDVAR(_pause_mode, SLE_UINT8, SLV_4, SL_MAX_VERSION)), NSL("pause_mode", SLEG_CONDVAR(_pause_mode, SLE_UINT8, SLV_4, SL_MAX_VERSION)),
NSL("game_events_overall", SLEG_CONDVAR_X(_game_events_overall, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_GAME_EVENTS))), NSL("game_events_overall", SLEG_CONDVAR_X(_game_events_overall, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_GAME_EVENTS))),
@ -115,7 +115,7 @@ static const NamedSaveLoad _date_desc[] = {
NSL("aspect_cfg_hash", SLEG_CONDVAR_X(_aspect_cfg_hash, SLE_UINT64, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_REALISTIC_TRAIN_BRAKING, 7))), NSL("aspect_cfg_hash", SLEG_CONDVAR_X(_aspect_cfg_hash, SLE_UINT64, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_REALISTIC_TRAIN_BRAKING, 7))),
NSL("aux_tileloop_tile", SLEG_CONDVAR_X(_aux_tileloop_tile, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AUX_TILE_LOOP))), NSL("aux_tileloop_tile", SLEG_CONDVAR_X(_aux_tileloop_tile, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AUX_TILE_LOOP))),
NSL("", SLE_CONDNULL(4, SLV_11, SLV_120)), NSL("", SLE_CONDNULL(4, SLV_11, SLV_120)),
NSL("competitors_interval", SLEG_CONDVAR_X(_new_competitor_timeout.period, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AI_START_DATE))), NSL("competitors_interval", SLEG_CONDVAR_X(_new_competitor_timeout.period.value, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AI_START_DATE))),
NSL("competitors_interval_elapsed", SLEG_CONDVAR_X(_new_competitor_timeout.storage.elapsed, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AI_START_DATE))), NSL("competitors_interval_elapsed", SLEG_CONDVAR_X(_new_competitor_timeout.storage.elapsed, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AI_START_DATE))),
NSL("competitors_interval_fired", SLEG_CONDVAR_X(_new_competitor_timeout.fired, SLE_BOOL, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AI_START_DATE))), NSL("competitors_interval_fired", SLEG_CONDVAR_X(_new_competitor_timeout.fired, SLE_BOOL, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AI_START_DATE))),
@ -151,8 +151,8 @@ static const NamedSaveLoad _date_check_desc[] = {
NSL("", SLE_CONDNULL(1, SL_MIN_VERSION, SLV_10)), NSL("", SLE_CONDNULL(1, SL_MIN_VERSION, SLV_10)),
NSL("", SLE_CONDNULL(4, SLV_10, SLV_120)), NSL("", SLE_CONDNULL(4, SLV_10, SLV_120)),
NSL("", SLE_NULL(1)), // _cur_company_tick_index NSL("", SLE_NULL(1)), // _cur_company_tick_index
NSL("", SLE_CONDNULL(2, SL_MIN_VERSION, SLV_109)), // _new_competitor_timeout.period NSL("", SLE_CONDNULL(2, SL_MIN_VERSION, SLV_109)), // _new_competitor_timeout.period.value
NSL("", SLE_CONDNULL_X(4, SLV_109, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AI_START_DATE, 0, 0))), // _new_competitor_timeout.period NSL("", SLE_CONDNULL_X(4, SLV_109, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AI_START_DATE, 0, 0))), // _new_competitor_timeout.period.value
NSL("", SLE_NULL(1)), // _trees_tick_ctr NSL("", SLE_NULL(1)), // _trees_tick_ctr
NSL("", SLE_CONDNULL(1, SLV_4, SL_MAX_VERSION)), // _pause_mode NSL("", SLE_CONDNULL(1, SLV_4, SL_MAX_VERSION)), // _pause_mode
NSL("", SLE_CONDNULL_X(4, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_GAME_EVENTS))), // _game_events_overall NSL("", SLE_CONDNULL_X(4, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_GAME_EVENTS))), // _game_events_overall

@ -1701,7 +1701,7 @@ static const OldChunks main_chunk[] = {
OCL_ASSERT( OC_TTO, 0x496CE ), OCL_ASSERT( OC_TTO, 0x496CE ),
OCL_VAR ( OC_FILE_U16 | OC_VAR_U32, 1, &_new_competitor_timeout.period ), OCL_VAR ( OC_FILE_U16 | OC_VAR_U32, 1, &_new_competitor_timeout.period.value ),
OCL_CNULL( OC_TTO, 2 ), ///< available monorail bitmask OCL_CNULL( OC_TTO, 2 ), ///< available monorail bitmask

@ -2908,11 +2908,6 @@ struct FileWriter : SaveFilter {
SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE, stdstr_fmt("Temporary save file does not have expected file size: " PRINTF_SIZE " != " PRINTF_SIZE, (size_t)st.st_size, save_size)); SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE, stdstr_fmt("Temporary save file does not have expected file size: " PRINTF_SIZE " != " PRINTF_SIZE, (size_t)st.st_size, save_size));
} }
#if defined(_WIN32)
/* Renaming over an existing file is not supported on Windows, manually unlink the target filename first */
unlink(this->target_name.c_str());
#endif
if (!FioRenameFile(this->temp_name, this->target_name)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE, "Failed to rename temporary save file to target name"); if (!FioRenameFile(this->temp_name, this->target_name)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE, "Failed to rename temporary save file to target name");
this->temp_name.clear(); // Now no need to unlink temporary name this->temp_name.clear(); // Now no need to unlink temporary name
} }

@ -5088,9 +5088,9 @@ void BuildOilRig(TileIndex tile)
if (_settings_game.station.serve_neutral_industries) { if (_settings_game.station.serve_neutral_industries) {
StationList nearby = std::move(st->industry->stations_near); StationList nearby = std::move(st->industry->stations_near);
st->industry->stations_near.clear(); st->industry->stations_near.clear();
for (Station *near : nearby) { for (Station *st_near : nearby) {
near->RecomputeCatchment(true); st_near->RecomputeCatchment(true);
UpdateStationAcceptance(near, true); UpdateStationAcceptance(st_near, true);
} }
} }

@ -627,14 +627,14 @@ class NIHVehicle : public NIHelper {
e->age, e->info.base_life.base(), e->duration_phase_1, e->duration_phase_2, e->duration_phase_3, e->age, e->info.base_life.base(), e->duration_phase_1, e->duration_phase_2, e->duration_phase_3,
e->duration_phase_1 + e->duration_phase_2 + e->duration_phase_3); e->duration_phase_1 + e->duration_phase_2 + e->duration_phase_3);
output.print(buffer); output.print(buffer);
seprintf(buffer, lastof(buffer), " Reliability: %u, spd_dec: %u, start: %u, max: %u, final: %u", seprintf(buffer, lastof(buffer), " Reliability: %u, spd_dec: %u (%u), start: %u, max: %u, final: %u",
e->reliability, e->reliability_spd_dec, e->reliability_start, e->reliability_max, e->reliability_final); e->reliability, e->reliability_spd_dec, e->info.decay_speed, e->reliability_start, e->reliability_max, e->reliability_final);
output.print(buffer); output.print(buffer);
seprintf(buffer, lastof(buffer), " Cargo type: %u, refit mask: 0x" OTTD_PRINTFHEX64 ", refit cost: %u", seprintf(buffer, lastof(buffer), " Cargo type: %u, refit mask: 0x" OTTD_PRINTFHEX64 ", refit cost: %u",
e->info.cargo_type, e->info.refit_mask, e->info.refit_cost); e->info.cargo_type, e->info.refit_mask, e->info.refit_cost);
output.print(buffer); output.print(buffer);
seprintf(buffer, lastof(buffer), " Cargo ageing: %u, cargo load speed: %u", seprintf(buffer, lastof(buffer), " Cargo age period: %u, cargo load speed: %u",
e->info.decay_speed, e->info.load_amount); e->info.cargo_age_period, e->info.load_amount);
output.print(buffer); output.print(buffer);
seprintf(buffer, lastof(buffer), " Company availability: %X, climates: %X", seprintf(buffer, lastof(buffer), " Company availability: %X, climates: %X",
e->company_avail, e->info.climates); e->company_avail, e->info.climates);

@ -99,7 +99,7 @@ public:
*/ */
void SetInterval(const TPeriod interval, bool reset = true) void SetInterval(const TPeriod interval, bool reset = true)
{ {
this->period = interval; TimerManager<TTimerType>::ChangeRegisteredTimerPeriod(*this, interval);
if (reset) this->storage = {}; if (reset) this->storage = {};
} }
@ -151,7 +151,7 @@ public:
*/ */
void Reset(const TPeriod timeout) void Reset(const TPeriod timeout)
{ {
this->period = timeout; TimerManager<TTimerType>::ChangeRegisteredTimerPeriod(*this, timeout);
this->fired = false; this->fired = false;
this->storage = {}; this->storage = {};
} }

@ -19,13 +19,13 @@
template<> template<>
void IntervalTimer<TimerGameTick>::Elapsed(TimerGameTick::TElapsed delta) void IntervalTimer<TimerGameTick>::Elapsed(TimerGameTick::TElapsed delta)
{ {
if (this->period == 0) return; if (this->period.value == 0) return;
this->storage.elapsed += delta; this->storage.elapsed += delta;
uint count = 0; uint count = 0;
while (this->storage.elapsed >= this->period) { while (this->storage.elapsed >= this->period.value) {
this->storage.elapsed -= this->period; this->storage.elapsed -= this->period.value;
count++; count++;
} }
@ -38,11 +38,11 @@ template<>
void TimeoutTimer<TimerGameTick>::Elapsed(TimerGameTick::TElapsed delta) void TimeoutTimer<TimerGameTick>::Elapsed(TimerGameTick::TElapsed delta)
{ {
if (this->fired) return; if (this->fired) return;
if (this->period == 0) return; if (this->period.value == 0) return;
this->storage.elapsed += delta; this->storage.elapsed += delta;
if (this->storage.elapsed >= this->period) { if (this->storage.elapsed >= this->period.value) {
this->callback(); this->callback();
this->fired = true; this->fired = true;
} }
@ -58,7 +58,16 @@ void TimerManager<TimerGameTick>::Elapsed(TimerGameTick::TElapsed delta)
#ifdef WITH_ASSERT #ifdef WITH_ASSERT
template<> template<>
void TimerManager<TimerGameTick>::Validate(TimerGameTick::TPeriod) void TimerManager<TimerGameTick>::Validate(TimerGameTick::TPeriod period)
{ {
if (period.priority == TimerGameTick::Priority::NONE) return;
/* Validate we didn't make a developer error and scheduled more than one
* entry on the same priority. There can only be one timer on
* a specific priority, to ensure we are deterministic, and to avoid
* container sort order invariant issues with timer period saveload. */
for (const auto &timer : TimerManager<TimerGameTick>::GetTimers()) {
assert(timer->period.priority != period.priority);
}
} }
#endif /* WITH_ASSERT */ #endif /* WITH_ASSERT */

@ -21,7 +21,34 @@
*/ */
class TimerGameTick { class TimerGameTick {
public: public:
using TPeriod = uint; enum Priority {
NONE, ///< These timers can be executed in any order; the order is not relevant.
/* For all other priorities, the order is important.
* For safety, you can only setup a single timer on a single priority. */
COMPETITOR_TIMEOUT,
};
struct TPeriod {
Priority priority;
uint value;
TPeriod(Priority priority, uint value) : priority(priority), value(value)
{}
bool operator < (const TPeriod &other) const
{
/* Sort by priority before value, such that changes in value for priorities other than NONE do not change the container order */
if (this->priority != other.priority) return this->priority < other.priority;
return this->value < other.value;
}
bool operator == (const TPeriod &other) const
{
return this->priority == other.priority && this->value == other.value;
}
};
using TElapsed = uint; using TElapsed = uint;
struct TStorage { struct TStorage {
uint elapsed; uint elapsed;

@ -57,6 +57,20 @@ public:
GetTimers().erase(&timer); GetTimers().erase(&timer);
} }
/**
* Change the period of a registered timer.
*
* @param timer The timer to change the period of.
* @param new_period The new period value.
*/
static void ChangeRegisteredTimerPeriod(BaseTimer<TTimerType> &timer, TPeriod new_period)
{
/* Unregistration and re-registration is necessary because the period is used as the sort key in base_timer_sorter */
UnregisterTimer(timer);
timer.period = new_period;
RegisterTimer(timer);
}
#ifdef WITH_ASSERT #ifdef WITH_ASSERT
/** /**
* Validate that a new period is actually valid. * Validate that a new period is actually valid.

@ -154,7 +154,12 @@ struct DropdownWindow : Window {
this->position.y = button_rect.bottom + 1; this->position.y = button_rect.bottom + 1;
} }
this->position.x = (_current_text_dir == TD_RTL) ? button_rect.right + 1 - (int)widget_dim.width : button_rect.left; if (_current_text_dir == TD_RTL) {
/* In case the list is wider than the parent button, the list should be right aligned to the button and overflow to the left. */
this->position.x = button_rect.right + 1 - (int)(widget_dim.width + (list_dim.height > widget_dim.height ? NWidgetScrollbar::GetVerticalDimension().width : 0));
} else {
this->position.x = button_rect.left;
}
this->items_dim = widget_dim; this->items_dim = widget_dim;
this->GetWidget<NWidgetStacked>(WID_DM_SHOW_SCROLL)->SetDisplayedPlane(list_dim.height > widget_dim.height ? 0 : SZSP_NONE); this->GetWidget<NWidgetStacked>(WID_DM_SHOW_SCROLL)->SetDisplayedPlane(list_dim.height > widget_dim.height ? 0 : SZSP_NONE);

Loading…
Cancel
Save