Compare commits

...

11 Commits

Author SHA1 Message Date
Jonathan G Rennison 3136f08b86 Debug: Fix display of engine cargo age period and reliability decay speed 2 weeks ago
Jonathan G Rennison 33baceaef7 Maintain timer sort invariants when changing period
See: https://github.com/OpenTTD/OpenTTD/issues/12509
3 weeks 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.
3 weeks ago
Jonathan G Rennison d5b8f51bf9 Rename variable to fix Windows header name collision 3 weeks ago
Jonathan G Rennison 71227f61d8 Use MoveFileExW to implement FioRenameFile on Windows
This is to allow renaming over an existing file
3 weeks ago
Jonathan G Rennison 083d91a582 Remove use of shell API function for rename 3 weeks ago
Peter Nelson 653e217bb1 Fix: Signature validation did not close its file. (#12479)
(cherry picked from commit 3316b27496)
3 weeks ago
Peter Nelson 8fdc91bd9f Fix a29766d: Wrong scrolling dropdown list position with RTL. (#12412)
(cherry picked from commit 9750826590)
3 weeks ago
Loïc Guilloux 54093fb8b2 Fix: [Win32] Force font mapper to only use TrueType fonts (#12406)
(cherry picked from commit 11aa3694fa)
3 weeks ago
Jonathan G Rennison 98dc6c3c81 Fix NewGRF byte order when using -q 3 weeks 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)
3 weeks ago

@ -668,7 +668,7 @@ Company *DoStartupNewCompany(DoStartupNewCompanyFlag flags, CompanyID company)
}
/** 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 (_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. */
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)
{
#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
return rename(oldname.c_str(), newname.c_str()) == 0;
#endif

@ -22,12 +22,6 @@
# include <fcntl.h>
#endif
#ifdef _WIN32
# include <windows.h>
# include <shellapi.h>
# include "core/mem_func.hpp"
#endif
#include "safeguards.h"
/**
@ -91,30 +85,9 @@ bool IniFile::SaveToDisk(const std::string &filename)
if (ret != 0) return false;
#endif
#if defined(_WIN32)
/* 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) {
if (!FioRenameFile(file_new, filename)) {
DEBUG(misc, 0, "Renaming %s to %s failed; configuration not saved", file_new.c_str(), filename.c_str());
}
#endif
#ifdef __EMSCRIPTEN__
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
return v->motion_counter;
return v->First()->motion_counter;
case 0x47: { // Vehicle cargo info
/* Format: ccccwwtt

@ -164,7 +164,7 @@ std::string NewGRFProfiler::GetOutputFilename() const
/**
* 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();
});
@ -174,7 +174,7 @@ static TimeoutTimer<TimerGameTick> _profiling_finish_timeout(0, []()
*/
/* 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) {
char md5sum[33];
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.
* @param fs The font size that is going to be cached.
@ -170,7 +166,8 @@ void Win32FontCache::SetFontSize(int pixels)
/* Create GDI font handle. */
this->logfont.lfHeight = -pixels;
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) {
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. */
extern TimeoutTimer<TimerGameTick> _new_competitor_timeout;
_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)) {

@ -61,9 +61,9 @@ static const SaveLoad _date_desc[] = {
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),
/* 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, 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("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.value, SLE_UINT32, SLV_109, SLV_AI_START_DATE),
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_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');
size_t len = fread(text.data(), filesize, 1, f);
FioFCloseFile(f);
if (len != 1) {
Debug(misc, 0, "Failed to validate signature: failed to read file: {}", filename);
return false;

@ -105,8 +105,8 @@ static const NamedSaveLoad _date_desc[] = {
NSL("", SLE_CONDNULL(1, SL_MIN_VERSION, SLV_10)),
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("", SLEG_CONDVAR(_new_competitor_timeout.period, 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(_new_competitor_timeout.period.value, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_109)),
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("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))),
@ -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("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("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_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(4, SLV_10, SLV_120)),
NSL("", SLE_NULL(1)), // _cur_company_tick_index
NSL("", SLE_CONDNULL(2, SL_MIN_VERSION, SLV_109)), // _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
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.value
NSL("", SLE_NULL(1)), // _trees_tick_ctr
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

@ -1701,7 +1701,7 @@ static const OldChunks main_chunk[] = {
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

@ -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));
}
#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");
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) {
StationList nearby = std::move(st->industry->stations_near);
st->industry->stations_near.clear();
for (Station *near : nearby) {
near->RecomputeCatchment(true);
UpdateStationAcceptance(near, true);
for (Station *st_near : nearby) {
st_near->RecomputeCatchment(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->duration_phase_1 + e->duration_phase_2 + e->duration_phase_3);
output.print(buffer);
seprintf(buffer, lastof(buffer), " Reliability: %u, spd_dec: %u, start: %u, max: %u, final: %u",
e->reliability, e->reliability_spd_dec, e->reliability_start, e->reliability_max, e->reliability_final);
seprintf(buffer, lastof(buffer), " Reliability: %u, spd_dec: %u (%u), start: %u, max: %u, final: %u",
e->reliability, e->reliability_spd_dec, e->info.decay_speed, e->reliability_start, e->reliability_max, e->reliability_final);
output.print(buffer);
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);
output.print(buffer);
seprintf(buffer, lastof(buffer), " Cargo ageing: %u, cargo load speed: %u",
e->info.decay_speed, e->info.load_amount);
seprintf(buffer, lastof(buffer), " Cargo age period: %u, cargo load speed: %u",
e->info.cargo_age_period, e->info.load_amount);
output.print(buffer);
seprintf(buffer, lastof(buffer), " Company availability: %X, climates: %X",
e->company_avail, e->info.climates);

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

@ -19,13 +19,13 @@
template<>
void IntervalTimer<TimerGameTick>::Elapsed(TimerGameTick::TElapsed delta)
{
if (this->period == 0) return;
if (this->period.value == 0) return;
this->storage.elapsed += delta;
uint count = 0;
while (this->storage.elapsed >= this->period) {
this->storage.elapsed -= this->period;
while (this->storage.elapsed >= this->period.value) {
this->storage.elapsed -= this->period.value;
count++;
}
@ -38,11 +38,11 @@ template<>
void TimeoutTimer<TimerGameTick>::Elapsed(TimerGameTick::TElapsed delta)
{
if (this->fired) return;
if (this->period == 0) return;
if (this->period.value == 0) return;
this->storage.elapsed += delta;
if (this->storage.elapsed >= this->period) {
if (this->storage.elapsed >= this->period.value) {
this->callback();
this->fired = true;
}
@ -58,7 +58,16 @@ void TimerManager<TimerGameTick>::Elapsed(TimerGameTick::TElapsed delta)
#ifdef WITH_ASSERT
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 */

@ -21,7 +21,34 @@
*/
class TimerGameTick {
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;
struct TStorage {
uint elapsed;

@ -57,6 +57,20 @@ public:
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
/**
* Validate that a new period is actually valid.

@ -154,7 +154,12 @@ struct DropdownWindow : Window {
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->GetWidget<NWidgetStacked>(WID_DM_SHOW_SCROLL)->SetDisplayedPlane(list_dim.height > widget_dim.height ? 0 : SZSP_NONE);

Loading…
Cancel
Save