From 4721a1f93ecb490fb1ecee768729792f4b8bad91 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sun, 13 Apr 2025 18:00:41 +0100 Subject: [PATCH 001/766] Fix: Don't display badges if the class has no name. (#13994) Unnamed badges are intended to be for internal-use, not for player information. Additionally if there is no name to the class, then is causes problems when user configuration comes. --- src/newgrf_badge.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/newgrf_badge.cpp b/src/newgrf_badge.cpp index 94aa72d966..b1269a02fe 100644 --- a/src/newgrf_badge.cpp +++ b/src/newgrf_badge.cpp @@ -353,6 +353,9 @@ GUIBadgeClasses::GUIBadgeClasses(GrfSpecFeature feature) uint max_column = 0; for (BadgeClassID class_index : used.Classes()) { + const Badge *class_badge = GetClassBadge(class_index); + if (class_badge->name == STR_NULL) continue; + Dimension size = GetBadgeMaximalDimension(class_index, feature); if (size.width == 0) continue; @@ -360,9 +363,7 @@ GUIBadgeClasses::GUIBadgeClasses(GrfSpecFeature feature) bool visible = true; uint sort_order = UINT_MAX; - std::string_view label = GetClassBadge(class_index)->label; - - this->gui_classes.emplace_back(class_index, column, visible, sort_order, size, label); + this->gui_classes.emplace_back(class_index, column, visible, sort_order, size, class_badge->label); if (visible) max_column = std::max(max_column, column); } From 800d6e339d1cdcaa96b4af5118a6046860806365 Mon Sep 17 00:00:00 2001 From: frosch Date: Thu, 27 Mar 2025 18:15:56 +0100 Subject: [PATCH 002/766] Codechange: Add StringConsumer. --- src/core/CMakeLists.txt | 2 + src/core/string_consumer.cpp | 196 ++++++++ src/core/string_consumer.hpp | 895 +++++++++++++++++++++++++++++++++ src/settingsgen/CMakeLists.txt | 1 + src/strgen/CMakeLists.txt | 1 + src/tests/CMakeLists.txt | 1 + src/tests/string_consumer.cpp | 487 ++++++++++++++++++ 7 files changed, 1583 insertions(+) create mode 100644 src/core/string_consumer.cpp create mode 100644 src/core/string_consumer.hpp create mode 100644 src/tests/string_consumer.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c14d10bf39..d21ea8da92 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -25,6 +25,8 @@ add_files( smallstack_type.hpp string_builder.cpp string_builder.hpp + string_consumer.cpp + string_consumer.hpp strong_typedef_type.hpp utf8.cpp utf8.hpp diff --git a/src/core/string_consumer.cpp b/src/core/string_consumer.cpp new file mode 100644 index 0000000000..6f2906676e --- /dev/null +++ b/src/core/string_consumer.cpp @@ -0,0 +1,196 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file string_consumer.cpp Implementation of string parsing. */ + +#include "../stdafx.h" +#include "string_consumer.hpp" + +#include "bitmath_func.hpp" +#include "utf8.hpp" +#include "string_builder.hpp" + +#include "../string_func.h" + +#if defined(STRGEN) || defined(SETTINGSGEN) +#include "../error_func.h" +#else +#include "../debug.h" +#endif + +#include "../safeguards.h" + +const std::string_view StringConsumer::WHITESPACE_NO_NEWLINE = "\t\v\f\r "; +const std::string_view StringConsumer::WHITESPACE_OR_NEWLINE = "\t\n\v\f\r "; + +/* static */ void StringConsumer::LogError(std::string &&msg) +{ +#if defined(STRGEN) || defined(SETTINGSGEN) + FatalErrorI(std::move(msg)); +#else + DebugPrint("misc", 0, std::move(msg)); +#endif +} + +std::optional StringConsumer::PeekUint8() const +{ + if (this->GetBytesLeft() < 1) return std::nullopt; + return static_cast(this->src[this->position]); +} + +std::optional StringConsumer::PeekUint16LE() const +{ + if (this->GetBytesLeft() < 2) return std::nullopt; + return static_cast(this->src[this->position]) | + static_cast(this->src[this->position + 1]) << 8; +} + +std::optional StringConsumer::PeekUint32LE() const +{ + if (this->GetBytesLeft() < 4) return std::nullopt; + return static_cast(this->src[this->position]) | + static_cast(this->src[this->position + 1]) << 8 | + static_cast(this->src[this->position + 2]) << 16 | + static_cast(this->src[this->position + 3]) << 24; +} + +std::optional StringConsumer::PeekUint64LE() const +{ + if (this->GetBytesLeft() < 8) return std::nullopt; + return static_cast(static_cast(this->src[this->position])) | + static_cast(static_cast(this->src[this->position + 1])) << 8 | + static_cast(static_cast(this->src[this->position + 2])) << 16 | + static_cast(static_cast(this->src[this->position + 3])) << 24 | + static_cast(static_cast(this->src[this->position + 4])) << 32 | + static_cast(static_cast(this->src[this->position + 5])) << 40 | + static_cast(static_cast(this->src[this->position + 6])) << 48 | + static_cast(static_cast(this->src[this->position + 7])) << 56; +} + +std::optional StringConsumer::PeekChar() const +{ + auto result = this->PeekUint8(); + if (!result.has_value()) return {}; + return static_cast(*result); +} + +std::pair StringConsumer::PeekUtf8() const +{ + auto buf = this->src.substr(this->position); + return DecodeUtf8(buf); +} + +std::string_view StringConsumer::Peek(size_type len) const +{ + auto buf = this->src.substr(this->position); + if (len == std::string_view::npos) { + len = buf.size(); + } else if (len > buf.size()) { + len = buf.size(); + } + return buf.substr(0, len); +} + +void StringConsumer::Skip(size_type len) +{ + if (len == std::string_view::npos) { + this->position = this->src.size(); + } else if (size_type max_len = GetBytesLeft(); len > max_len) { + LogError(fmt::format("Source buffer too short: {} > {}", len, max_len)); + this->position = this->src.size(); + } else { + this->position += len; + } +} + +StringConsumer::size_type StringConsumer::Find(std::string_view str) const +{ + assert(!str.empty()); + auto buf = this->src.substr(this->position); + return buf.find(str); +} + +StringConsumer::size_type StringConsumer::FindUtf8(char32_t c) const +{ + auto [data, len] = EncodeUtf8(c); + return this->Find({data, len}); +} + +StringConsumer::size_type StringConsumer::FindCharIn(std::string_view chars) const +{ + assert(!chars.empty()); + auto buf = this->src.substr(this->position); + return buf.find_first_of(chars); +} + +StringConsumer::size_type StringConsumer::FindCharNotIn(std::string_view chars) const +{ + assert(!chars.empty()); + auto buf = this->src.substr(this->position); + return buf.find_first_not_of(chars); +} + +std::string_view StringConsumer::PeekUntil(std::string_view str, SeparatorUsage sep) const +{ + assert(!str.empty()); + auto buf = this->src.substr(this->position); + auto len = buf.find(str); + if (len != std::string_view::npos) { + switch (sep) { + case READ_ONE_SEPARATOR: + if (buf.compare(len, str.size(), str) == 0) len += str.size(); + break; + case READ_ALL_SEPARATORS: + while (buf.compare(len, str.size(), str) == 0) len += str.size(); + break; + default: + break; + } + } + return buf.substr(0, len); +} + +std::string_view StringConsumer::PeekUntilUtf8(char32_t c, SeparatorUsage sep) const +{ + auto [data, len] = EncodeUtf8(c); + return PeekUntil({data, len}, sep); +} + +std::string_view StringConsumer::ReadUntilUtf8(char32_t c, SeparatorUsage sep) +{ + auto [data, len] = EncodeUtf8(c); + return ReadUntil({data, len}, sep); +} + +void StringConsumer::SkipUntilUtf8(char32_t c, SeparatorUsage sep) +{ + auto [data, len] = EncodeUtf8(c); + return SkipUntil({data, len}, sep); +} + +void StringConsumer::SkipIntegerBase(int base) +{ + this->SkipIf("-"); + if (base == 0) { + if (this->ReadIf("0x") || this->ReadIf("0X")) { // boolean short-circuit ensures only one prefix is read + base = 16; + } else { + base = 10; + } + } + switch (base) { + default: + assert(false); + break; + case 10: + this->SkipUntilCharNotIn("0123456789"); + break; + case 16: + this->SkipUntilCharNotIn("0123456789abcdefABCDEF"); + break; + } +} diff --git a/src/core/string_consumer.hpp b/src/core/string_consumer.hpp new file mode 100644 index 0000000000..b85bf65eb9 --- /dev/null +++ b/src/core/string_consumer.hpp @@ -0,0 +1,895 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file string_consumer.hpp Parse strings. + */ + +#ifndef STRING_CONSUMER_HPP +#define STRING_CONSUMER_HPP + +#include +#include "format.hpp" + +/** + * Parse data from a string / buffer. + * + * There are generally four operations for each data type: + * - Peek: Check and return validity and value. Do not advance read position. + * - TryRead: Check and return validity and value. Advance reader, if valid. + * - Read: Check validity, return value or fallback-value. Advance reader, even if value is invalid, to avoid deadlocks/stalling. + * - Skip: Discard value. Advance reader, even if value is invalid, to avoid deadlocks/stalling. + */ +class StringConsumer { +public: + using size_type = std::string_view::size_type; + + /** + * Special value for "end of data". + */ + static constexpr size_type npos = std::string_view::npos; + + /** + * ASCII whitespace characters, excluding new-line. + * Usable in FindChar(In|NotIn), (Peek|Read|Skip)(If|Until)Char(In|NotIn) + */ + static const std::string_view WHITESPACE_NO_NEWLINE; + /** + * ASCII whitespace characters, including new-line. + * Usable in FindChar(In|NotIn), (Peek|Read|Skip)(If|Until)Char(In|NotIn) + */ + static const std::string_view WHITESPACE_OR_NEWLINE; + +private: + std::string_view src; + size_type position = 0; + + static void LogError(std::string &&msg); + +public: + /** + * Construct parser with data from string. + */ + explicit StringConsumer(std::string_view src) : src(src) {} + /** + * Construct parser with data from string. + */ + explicit StringConsumer(const std::string &src) : src(src) {} + /** + * Construct parser with data from span. + */ + explicit StringConsumer(std::span src) : src(src.data(), src.size()) {} + /** + * Construct parser with data from buffer. + */ + StringConsumer(const char *src, size_type len) : src(src, len) {} + + /** + * Check whether any bytes left to read. + */ + [[nodiscard]] bool AnyBytesLeft() const noexcept { return this->position < this->src.size(); } + /** + * Get number of bytes left to read. + */ + [[nodiscard]] size_type GetBytesLeft() const noexcept { return this->src.size() - this->position; } + + /** + * Check wheter any bytes were already read. + */ + [[nodiscard]] bool AnyBytesRead() const noexcept { return this->position > 0; } + /** + * Get number of already read bytes. + */ + [[nodiscard]] size_type GetBytesRead() const noexcept { return this->position; } + + /** + * Get the original data, as passed to the constructor. + */ + [[nodiscard]] std::string_view GetOrigData() const noexcept { return this->src; } + /** + * Get already read data. + */ + [[nodiscard]] std::string_view GetReadData() const noexcept { return this->src.substr(0, this->position); } + /** + * Get data left to read. + */ + [[nodiscard]] std::string_view GetLeftData() const noexcept { return this->src.substr(this->position); } + + /** + * Discard all remaining data. + */ + void SkipAll() { this->position = this->src.size(); } + + /** + * Peek binary uint8. + * @return Read integer, std::nullopt if not enough data. + */ + [[nodiscard]] std::optional PeekUint8() const; + /** + * Try to read binary uint8, and then advance reader. + */ + [[nodiscard]] std::optional TryReadUint8() + { + auto value = this->PeekUint8(); + if (value.has_value()) this->SkipUint8(); + return value; + } + /** + * Read binary uint8, and advance reader. + * @param def Default value to return, if not enough data. + * @return Read integer, 'def' if not enough data. + */ + [[nodiscard]] uint8_t ReadUint8(uint8_t def = 0) + { + auto value = this->PeekUint8(); + this->SkipUint8(); // always advance + return value.value_or(def); + } + /** + * Skip binary uint8. + */ + void SkipUint8() { this->Skip(1); } + + /** + * Peek binary int8. + * @return Read integer, std::nullopt if not enough data. + */ + [[nodiscard]] std::optional PeekSint8() const + { + auto result = PeekUint8(); + if (!result.has_value()) return std::nullopt; + return static_cast(*result); + } + /** + * Try to read binary int8, and then advance reader. + */ + [[nodiscard]] std::optional TryReadSint8() + { + auto value = this->PeekSint8(); + if (value.has_value()) this->SkipSint8(); + return value; + } + /** + * Read binary int8, and advance reader. + * @param def Default value to return, if not enough data. + * @return Read integer, 'def' if not enough data. + */ + [[nodiscard]] int8_t ReadSint8(int8_t def = 0) + { + auto value = this->PeekSint8(); + this->SkipSint8(); // always advance + return value.value_or(def); + } + /** + * Skip binary int8. + */ + void SkipSint8() { this->Skip(1); } + + /** + * Peek binary uint16 using little endian. + * @return Read integer, std::nullopt if not enough data. + */ + [[nodiscard]] std::optional PeekUint16LE() const; + /** + * Try to read binary uint16, and then advance reader. + */ + [[nodiscard]] std::optional TryReadUint16LE() + { + auto value = this->PeekUint16LE(); + if (value.has_value()) this->SkipUint16LE(); + return value; + } + /** + * Read binary uint16 using little endian, and advance reader. + * @param def Default value to return, if not enough data. + * @return Read integer, 'def' if not enough data. + * @note The reader is advanced, even if not enough data was present. + */ + [[nodiscard]] uint16_t ReadUint16LE(uint16_t def = 0) + { + auto value = this->PeekUint16LE(); + this->SkipUint16LE(); // always advance + return value.value_or(def); + } + /** + * Skip binary uint16, and advance reader. + * @note The reader is advanced, even if not enough data was present. + */ + void SkipUint16LE() { this->Skip(2); } + + /** + * Peek binary int16 using little endian. + * @return Read integer, std::nullopt if not enough data. + */ + [[nodiscard]] std::optional PeekSint16LE() const + { + auto result = PeekUint16LE(); + if (!result.has_value()) return std::nullopt; + return static_cast(*result); + } + /** + * Try to read binary int16, and then advance reader. + */ + [[nodiscard]] std::optional TryReadSint16LE() + { + auto value = this->PeekSint16LE(); + if (value.has_value()) this->SkipSint16LE(); + return value; + } + /** + * Read binary int16 using little endian, and advance reader. + * @param def Default value to return, if not enough data. + * @return Read integer, 'def' if not enough data. + * @note The reader is advanced, even if not enough data was present. + */ + [[nodiscard]] int16_t ReadSint16LE(int16_t def = 0) + { + auto value = this->PeekSint16LE(); + this->SkipSint16LE(); // always advance + return value.value_or(def); + } + /** + * Skip binary int16, and advance reader. + * @note The reader is advanced, even if not enough data was present. + */ + void SkipSint16LE() { this->Skip(2); } + + /** + * Peek binary uint32 using little endian. + * @return Read integer, std::nullopt if not enough data. + */ + [[nodiscard]] std::optional PeekUint32LE() const; + /** + * Try to read binary uint32, and then advance reader. + */ + [[nodiscard]] std::optional TryReadUint32LE() + { + auto value = this->PeekUint32LE(); + if (value.has_value()) this->SkipUint32LE(); + return value; + } + /** + * Read binary uint32 using little endian, and advance reader. + * @param def Default value to return, if not enough data. + * @return Read integer, 'def' if not enough data. + * @note The reader is advanced, even if not enough data was present. + */ + [[nodiscard]] uint32_t ReadUint32LE(uint32_t def = 0) + { + auto value = this->PeekUint32LE(); + this->SkipUint32LE(); // always advance + return value.value_or(def); + } + /** + * Skip binary uint32, and advance reader. + * @note The reader is advanced, even if not enough data was present. + */ + void SkipUint32LE() { this->Skip(4); } + + /** + * Peek binary int32 using little endian. + * @return Read integer, std::nullopt if not enough data. + */ + [[nodiscard]] std::optional PeekSint32LE() const + { + auto result = PeekUint32LE(); + if (!result.has_value()) return std::nullopt; + return static_cast(*result); + } + /** + * Try to read binary int32, and then advance reader. + */ + [[nodiscard]] std::optional TryReadSint32LE() + { + auto value = this->PeekSint32LE(); + if (value.has_value()) this->SkipSint32LE(); + return value; + } + /** + * Read binary int32 using little endian, and advance reader. + * @param def Default value to return, if not enough data. + * @return Read integer, 'def' if not enough data. + * @note The reader is advanced, even if not enough data was present. + */ + [[nodiscard]] int32_t ReadSint32LE(int32_t def = 0) + { + auto value = this->PeekSint32LE(); + this->SkipSint32LE(); // always advance + return value.value_or(def); + } + /** + * Skip binary int32, and advance reader. + * @note The reader is advanced, even if not enough data was present. + */ + void SkipSint32LE() { this->Skip(4); } + + /** + * Peek binary uint64 using little endian. + * @return Read integer, std::nullopt if not enough data. + */ + [[nodiscard]] std::optional PeekUint64LE() const; + /** + * Try to read binary uint64, and then advance reader. + */ + [[nodiscard]] std::optional TryReadUint64LE() + { + auto value = this->PeekUint64LE(); + if (value.has_value()) this->SkipUint64LE(); + return value; + } + /** + * Read binary uint64 using little endian, and advance reader. + * @param def Default value to return, if not enough data. + * @return Read integer, 'def' if not enough data. + * @note The reader is advanced, even if not enough data was present. + */ + [[nodiscard]] uint64_t ReadUint64LE(uint64_t def = 0) + { + auto value = this->PeekUint64LE(); + this->SkipUint64LE(); // always advance + return value.value_or(def); + } + /** + * Skip binary uint64, and advance reader. + * @note The reader is advanced, even if not enough data was present. + */ + void SkipUint64LE() { this->Skip(8); } + + /** + * Peek binary int64 using little endian. + * @return Read integer, std::nullopt if not enough data. + */ + [[nodiscard]] std::optional PeekSint64LE() const + { + auto result = PeekUint64LE(); + if (!result.has_value()) return std::nullopt; + return static_cast(*result); + } + /** + * Try to read binary int64, and then advance reader. + */ + [[nodiscard]] std::optional TryReadSint64LE() + { + auto value = this->PeekSint64LE(); + if (value.has_value()) this->SkipSint64LE(); + return value; + } + /** + * Read binary int64 using little endian, and advance reader. + * @param def Default value to return, if not enough data. + * @return Read integer, 'def' if not enough data. + * @note The reader is advanced, even if not enough data was present. + */ + [[nodiscard]] int64_t ReadSint64LE(int64_t def = 0) + { + auto value = this->PeekSint64LE(); + this->SkipSint64LE(); // always advance + return value.value_or(def); + } + /** + * Skip binary int64, and advance reader. + * @note The reader is advanced, even if not enough data was present. + */ + void SkipSint64LE() { this->Skip(8); } + + /** + * Peek 8-bit character. + * @return Read char, std::nullopt if not enough data. + */ + [[nodiscard]] std::optional PeekChar() const; + /** + * Try to read a 8-bit character, and then advance reader. + */ + [[nodiscard]] std::optional TryReadChar() + { + auto value = this->PeekChar(); + if (value.has_value()) this->SkipChar(); + return value; + } + /** + * Read 8-bit character, and advance reader. + * @param def Default value to return, if not enough data. + * @return Read character, 'def' if not enough data. + */ + [[nodiscard]] char ReadChar(char def = '?') { + auto value = this->PeekChar(); + this->SkipChar(); // always advance + return value.value_or(def); + } + /** + * Skip 8-bit character, and advance reader. + */ + void SkipChar() { this->Skip(1); } + + /** + * Peek UTF-8 character. + * @return Length and read char, {0, 0} if no valid data. + */ + [[nodiscard]] std::pair PeekUtf8() const; + /** + * Try to read a UTF-8 character, and then advance reader. + */ + [[nodiscard]] std::optional TryReadUtf8() + { + auto [len, value] = this->PeekUtf8(); + if (len == 0) return std::nullopt; + this->Skip(len); + return value; + } + /** + * Read UTF-8 character, and advance reader. + * @param def Default value to return, if no valid data. + * @return Read char, 'def' if no valid data. + * @note The reader is advanced, even if no valid data was present. + */ + [[nodiscard]] char32_t ReadUtf8(char32_t def = '?') + { + auto [len, value] = this->PeekUtf8(); + this->Skip(len > 0 ? len : 1); // advance at least one byte + return len > 0 ? value : def; + } + /** + * Skip UTF-8 character, and advance reader. + * @note The reader is advanced, even if no valid data was present. + * @note This behaves different to Utf8View::iterator. + * Here we do not skip overlong encodings, because we want to + * allow binary data to follow UTF-8 data. + */ + void SkipUtf8() + { + auto len = this->PeekUtf8().first; + this->Skip(len > 0 ? len : 1); // advance at least one byte + } + + /** + * Check whether the next data matches 'str'. + */ + [[nodiscard]] bool PeekIf(std::string_view str) const + { + return this->src.compare(this->position, str.size(), str) == 0; + } + /** + * Check whether the next data matches 'str', and skip it. + */ + [[nodiscard]] bool ReadIf(std::string_view str) + { + bool result = this->PeekIf(str); + if (result) this->Skip(str.size()); + return result; + } + /** + * If the next data matches 'str', then skip it. + */ + void SkipIf(std::string_view str) + { + if (this->PeekIf(str)) this->Skip(str.size()); + } + + /** + * Check whether the next 8-bit char matches 'c'. + */ + [[nodiscard]] bool PeekCharIf(char c) const + { + return this->PeekIf({&c, 1}); + } + /** + * Check whether the next 8-bit char matches 'c', and skip it. + */ + [[nodiscard]] bool ReadCharIf(char c) + { + return this->ReadIf({&c, 1}); + } + /** + * If the next data matches the 8-bit char 'c', then skip it. + */ + void SkipCharIf(char c) + { + return this->SkipIf({&c, 1}); + } + + /** + * Check whether the next UTF-8 char matches 'c'. + */ + [[nodiscard]] bool PeekUtf8If(char32_t c) const + { + auto [len, result] = this->PeekUtf8(); + return len > 0 && result == c; + } + /** + * Check whether the next UTF-8 char matches 'c', and skip it. + */ + [[nodiscard]] bool ReadUtf8If(char32_t c) + { + auto [len, result] = this->PeekUtf8(); + if (len == 0 || result != c) return false; + this->Skip(len); + return true; + } + /** + * If the next data matches the UTF-8 char 'c', then skip it. + */ + void SkipUtf8If(char32_t c) + { + auto [len, result] = this->PeekUtf8(); + if (len > 0 && result == c) { + this->Skip(len); + } + } + + /** + * Peek the next 'len' bytes. + * @param len Bytes to read, 'npos' to read all. + * @return Up to 'len' bytes. + */ + [[nodiscard]] std::string_view Peek(size_type len) const; + /** + * Read the next 'len' bytes, and advance reader. + * @param len Bytes to read, 'npos' to read all. + * @return Up to 'len' bytes. + */ + [[nodiscard]] std::string_view Read(size_type len) + { + auto result = this->Peek(len); + if (len != npos && len != result.size()) { + LogError(fmt::format("Source buffer too short: {} > {}", len, result.size())); + } + this->Skip(result.size()); + return result; + } + /** + * Discard some bytes. + * @param len Number of bytes to skip, 'npos' to skip all. + */ + void Skip(size_type len); + + /** + * Find first occurence of 'str'. + * @return Offset from current reader position. 'npos' if no match found. + */ + [[nodiscard]] size_type Find(std::string_view str) const; + /** + * Find first occurence of 8-bit char 'c'. + * @return Offset from current reader position. 'npos' if no match found. + */ + [[nodiscard]] size_type FindChar(char c) const + { + return this->Find({&c, 1}); + } + /** + * Find first occurence of UTF-8 char 'c'. + * @return Offset from current reader position. 'npos' if no match found. + */ + [[nodiscard]] size_type FindUtf8(char32_t c) const; + + /** + * Find first occurence of any 8-bit char in 'chars'. + * @return Offset from current reader position. 'npos' if no match found. + */ + [[nodiscard]] size_type FindCharIn(std::string_view chars) const; + /** + * Find first occurence of any 8-bit char not in 'chars'. + * @return Offset from current reader position. 'npos' if no match found. + */ + [[nodiscard]] size_type FindCharNotIn(std::string_view chars) const; + + /** + * Check whether the next 8-bit char is in 'chars'. + * @return Matching char, std::nullopt if no match. + */ + [[nodiscard]] std::optional PeekCharIfIn(std::string_view chars) const + { + assert(!chars.empty()); + std::optional c = this->PeekChar(); + if (c.has_value() && chars.find(*c) != std::string_view::npos) return c; + return std::nullopt; + } + /** + * Read next 8-bit char, check whether it is in 'chars', and advance reader. + * @return Matching char, std::nullopt if no match. + */ + [[nodiscard]] std::optional ReadCharIfIn(std::string_view chars) + { + auto result = this->PeekCharIfIn(chars); + if (result.has_value()) this->Skip(1); + return result; + } + /** + * If the next 8-bit char is in 'chars', skip it. + */ + void SkipCharIfIn(std::string_view chars) + { + auto result = this->PeekCharIfIn(chars); + if (result.has_value()) this->Skip(1); + } + + /** + * Check whether the next 8-bit char is not in 'chars'. + * @return Non-matching char, std::nullopt if match. + */ + [[nodiscard]] std::optional PeekCharIfNotIn(std::string_view chars) const + { + assert(!chars.empty()); + std::optional c = this->PeekChar(); + if (c.has_value() && chars.find(*c) == std::string_view::npos) return c; + return std::nullopt; + } + /** + * Read next 8-bit char, check whether it is not in 'chars', and advance reader. + * @return Non-matching char, std::nullopt if match. + */ + [[nodiscard]] std::optional ReadCharIfNotIn(std::string_view chars) + { + auto result = this->PeekCharIfNotIn(chars); + if (result.has_value()) this->Skip(1); + return result; + } + /** + * If the next 8-bit char is not in 'chars', skip it. + */ + void SkipCharIfNotIn(std::string_view chars) + { + auto result = this->PeekCharIfNotIn(chars); + if (result.has_value()) this->Skip(1); + } + + /** + * Peek 8-bit chars, while they are not in 'chars', until they are. + * @return Non-matching chars. + */ + [[nodiscard]] std::string_view PeekUntilCharIn(std::string_view chars) const + { + size_type len = this->FindCharIn(chars); + return this->Peek(len); + } + /** + * Read 8-bit chars, while they are not in 'chars', until they are; and advance reader. + * @return Non-matching chars. + */ + [[nodiscard]] std::string_view ReadUntilCharIn(std::string_view chars) + { + size_type len = this->FindCharIn(chars); + return this->Read(len); + } + /** + * Skip 8-bit chars, while they are not in 'chars', until they are. + */ + void SkipUntilCharIn(std::string_view chars) + { + size_type len = this->FindCharIn(chars); + this->Skip(len); + } + + /** + * Peek 8-bit chars, while they are in 'chars', until they are not. + * @return Matching chars. + */ + [[nodiscard]] std::string_view PeekUntilCharNotIn(std::string_view chars) const + { + size_type len = this->FindCharNotIn(chars); + return this->Peek(len); + } + /** + * Read 8-bit chars, while they are in 'chars', until they are not; and advance reader. + * @return Matching chars. + */ + [[nodiscard]] std::string_view ReadUntilCharNotIn(std::string_view chars) + { + size_type len = this->FindCharNotIn(chars); + return this->Read(len); + } + /** + * Skip 8-bit chars, while they are in 'chars', until they are not. + */ + void SkipUntilCharNotIn(std::string_view chars) + { + size_type len = this->FindCharNotIn(chars); + this->Skip(len); + } + + /** + * Treatment of separator characters. + */ + enum SeparatorUsage { + READ_ALL_SEPARATORS, ///< Read all consecutive separators, and include them all in the result + READ_ONE_SEPARATOR, ///< Read one separator, and include it in the result + KEEP_SEPARATOR, ///< Keep the separator in the data as next value to be read. + SKIP_ONE_SEPARATOR, ///< Read and discard one separator, do not include it in the result. + SKIP_ALL_SEPARATORS, ///< Read and discard all consecutive separators, do not include any in the result. + }; + + /** + * Peek data until the first occurrence of 'str'. + * @param str Separator string. + * @param sep Whether to include/exclude 'str' from the result. + */ + [[nodiscard]] std::string_view PeekUntil(std::string_view str, SeparatorUsage sep) const; + /** + * Read data until the first occurrence of 'str', and advance reader. + * @param str Separator string. + * @param sep Whether to include/exclude 'str' from the result, and/or skip it. + */ + [[nodiscard]] std::string_view ReadUntil(std::string_view str, SeparatorUsage sep) + { + assert(!str.empty()); + auto result = this->PeekUntil(str, sep); + this->Skip(result.size()); + switch (sep) { + default: + break; + case SKIP_ONE_SEPARATOR: + this->SkipIf(str); + break; + case SKIP_ALL_SEPARATORS: + while (this->ReadIf(str)) {} + break; + } + return result; + } + /** + * Skip data until the first occurrence of 'str'. + * @param str Separator string. + * @param sep Whether to also skip 'str'. + */ + void SkipUntil(std::string_view str, SeparatorUsage sep) + { + assert(!str.empty()); + this->Skip(this->Find(str)); + switch (sep) { + default: + break; + case READ_ONE_SEPARATOR: + case SKIP_ONE_SEPARATOR: + this->SkipIf(str); + break; + case READ_ALL_SEPARATORS: + case SKIP_ALL_SEPARATORS: + while (this->ReadIf(str)) {} + break; + } + } + + /** + * Peek data until the first occurrence of 8-bit char 'c'. + * @param c Separator char. + * @param sep Whether to include/exclude 'c' from the result. + */ + [[nodiscard]] std::string_view PeekUntilChar(char c, SeparatorUsage sep) const + { + return this->PeekUntil({&c, 1}, sep); + } + /** + * Read data until the first occurrence of 8-bit char 'c', and advance reader. + * @param c Separator char. + * @param sep Whether to include/exclude 'c' from the result, and/or skip it. + */ + [[nodiscard]] std::string_view ReadUntilChar(char c, SeparatorUsage sep) + { + return this->ReadUntil({&c, 1}, sep); + } + /** + * Skip data until the first occurrence of 8-bit char 'c'. + * @param c Separator char. + * @param sep Whether to also skip 'c'. + */ + void SkipUntilChar(char c, SeparatorUsage sep) + { + this->SkipUntil({&c, 1}, sep); + } + + /** + * Peek data until the first occurrence of UTF-8 char 'c'. + * @param c Separator char. + * @param sep Whether to include/exclude 'c' from the result. + */ + [[nodiscard]] std::string_view PeekUntilUtf8(char32_t c, SeparatorUsage sep) const; + /** + * Read data until the first occurrence of UTF-8 char 'c', and advance reader. + * @param c Separator char. + * @param sep Whether to include/exclude 'c' from the result, and/or skip it. + */ + [[nodiscard]] std::string_view ReadUntilUtf8(char32_t c, SeparatorUsage sep); + /** + * Skip data until the first occurrence of UTF-8 char 'c'. + * @param c Separator char. + * @param sep Whether to also skip 'c'. + */ + void SkipUntilUtf8(char32_t c, SeparatorUsage sep); + +private: + template + [[nodiscard]] static std::pair ParseIntegerBase(std::string_view src, int base, bool log_errors) + { + if (base == 0) { + /* Try positive hex */ + if (src.starts_with("0x") || src.starts_with("0X")) { + auto [len, value] = ParseIntegerBase(src.substr(2), 16, log_errors); + if (len == 0) return {}; + return {len + 2, value}; + } + + /* Try negative hex */ + if (std::is_signed_v && (src.starts_with("-0x") || src.starts_with("-0X"))) { + using Unsigned = std::make_signed_t; + auto [len, uvalue] = ParseIntegerBase(src.substr(3), 16, log_errors); + if (len == 0) return {}; + T value = -uvalue; + if (value > 0) { + if (log_errors) LogError(fmt::format("Integer out of range: '{}'", src.substr(0, len + 3))); + return {}; + } + return {len + 3, value}; + } + + /* Try decimal */ + return ParseIntegerBase(src, 10, log_errors); + } + + T value{}; + assert(base == 10 || base == 16); // we only support these bases when skipping + auto result = std::from_chars(src.data(), src.data() + src.size(), value, base); + auto len = result.ptr - src.data(); + if (result.ec == std::errc::result_out_of_range) { + if (log_errors) LogError(fmt::format("Integer out of range: '{}'+'{}'", src.substr(0, len), src.substr(len, 4))); + return {}; + } + if (result.ec != std::errc{}) { + if (log_errors) LogError(fmt::format("Cannot parse integer: '{}'+'{}'", src.substr(0, len), src.substr(len, 4))); + return {}; + } + return {len, value}; + } + +public: + /** + * Peek and parse an integer in number 'base'. + * If 'base == 0', then a prefix '0x' decides between base 16 or base 10. + * @return Length of string match, and parsed value. + * @note The parser rejects leading whitespace and unary plus. + */ + template + [[nodiscard]] std::pair PeekIntegerBase(int base) const + { + return ParseIntegerBase(this->src.substr(this->position), base, false); + } + /** + * Try to read and parse an integer in number 'base', and then advance the reader. + * If 'base == 0', then a prefix '0x' decides between base 16 or base 10. + * @return Parsed value, if valid. + * @note The parser rejects leading whitespace and unary plus. + */ + template + [[nodiscard]] std::optional TryReadIntegerBase(int base) + { + auto [len, value] = this->PeekIntegerBase(base); + if (len == 0) return std::nullopt; + this->SkipIntegerBase(base); + return value; + } + /** + * Read and parse an integer in number 'base', and advance the reader. + * If 'base == 0', then a prefix '0x' decides between base 16 or base 10. + * @return Parsed value, or 'def' if invalid. + * @note The reader is advanced, even if no valid data was present. + * @note The parser rejects leading whitespace and unary plus. + */ + template + [[nodiscard]] T ReadIntegerBase(int base, T def = 0) + { + auto [len, value] = ParseIntegerBase(this->src.substr(this->position), base, true); + this->SkipIntegerBase(base); // always advance + return len > 0 ? value : def; + } + /** + * Skip an integer in number 'base'. + * If 'base == 0', then a prefix '0x' decides between base 16 or base 10. + * @note The reader is advanced, even if no valid data was present. + * @note The parser rejects leading whitespace and unary plus. + */ + void SkipIntegerBase(int base); +}; + +#endif /* STRING_CONSUMER_HPP */ diff --git a/src/settingsgen/CMakeLists.txt b/src/settingsgen/CMakeLists.txt index c69f755035..220ffb972b 100644 --- a/src/settingsgen/CMakeLists.txt +++ b/src/settingsgen/CMakeLists.txt @@ -11,6 +11,7 @@ if (NOT HOST_BINARY_DIR) ../ini_load.cpp ../string.cpp ../core/string_builder.cpp + ../core/string_consumer.cpp ../core/utf8.cpp ) add_definitions(-DSETTINGSGEN) diff --git a/src/strgen/CMakeLists.txt b/src/strgen/CMakeLists.txt index 425cc1dd1a..40eb11448f 100644 --- a/src/strgen/CMakeLists.txt +++ b/src/strgen/CMakeLists.txt @@ -13,6 +13,7 @@ if (NOT HOST_BINARY_DIR) ../error.cpp ../string.cpp ../core/string_builder.cpp + ../core/string_consumer.cpp ../core/utf8.cpp ) add_definitions(-DSTRGEN) diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 7bfc261e4c..29f97ad205 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -8,6 +8,7 @@ add_test_files( mock_spritecache.cpp mock_spritecache.h string_builder.cpp + string_consumer.cpp string_func.cpp test_main.cpp test_network_crypto.cpp diff --git a/src/tests/string_consumer.cpp b/src/tests/string_consumer.cpp new file mode 100644 index 0000000000..1cf470a0cc --- /dev/null +++ b/src/tests/string_consumer.cpp @@ -0,0 +1,487 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file string_consumer.cpp Test functionality from core/string_consumer. */ + +#include "../stdafx.h" + +#include + +#include "../3rdparty/catch2/catch.hpp" + +#include "../core/string_consumer.hpp" + +#include "../safeguards.h" + +using namespace std::literals; + +TEST_CASE("StringConsumer - basic") +{ + StringConsumer consumer("ab"sv); + CHECK(!consumer.AnyBytesRead()); + CHECK(consumer.GetBytesRead() == 0); + CHECK(consumer.AnyBytesLeft()); + CHECK(consumer.GetBytesLeft() == 2); + CHECK(consumer.GetOrigData() == "ab"); + CHECK(consumer.GetReadData() == ""); + CHECK(consumer.GetLeftData() == "ab"); + consumer.Skip(1); + CHECK(consumer.AnyBytesRead()); + CHECK(consumer.GetBytesRead() == 1); + CHECK(consumer.AnyBytesLeft()); + CHECK(consumer.GetBytesLeft() == 1); + CHECK(consumer.GetOrigData() == "ab"); + CHECK(consumer.GetReadData() == "a"); + CHECK(consumer.GetLeftData() == "b"); + consumer.SkipAll(); + CHECK(consumer.AnyBytesRead()); + CHECK(consumer.GetBytesRead() == 2); + CHECK(!consumer.AnyBytesLeft()); + CHECK(consumer.GetBytesLeft() == 0); + CHECK(consumer.GetOrigData() == "ab"); + CHECK(consumer.GetReadData() == "ab"); + CHECK(consumer.GetLeftData() == ""); + consumer.Skip(1); + CHECK(consumer.AnyBytesRead()); + CHECK(consumer.GetBytesRead() == 2); + CHECK(!consumer.AnyBytesLeft()); + CHECK(consumer.GetBytesLeft() == 0); + CHECK(consumer.GetOrigData() == "ab"); + CHECK(consumer.GetReadData() == "ab"); + CHECK(consumer.GetLeftData() == ""); +} + +TEST_CASE("StringConsumer - binary8") +{ + StringConsumer consumer("\xFF\xFE\xFD\0"sv); + CHECK(consumer.PeekUint8() == 0xFF); + CHECK(consumer.PeekSint8() == -1); + CHECK(consumer.PeekChar() == static_cast(-1)); + consumer.SkipUint8(); + CHECK(consumer.PeekUint8() == 0xFE); + CHECK(consumer.PeekSint8() == -2); + CHECK(consumer.PeekChar() == static_cast(-2)); + CHECK(consumer.ReadUint8() == 0xFE); + CHECK(consumer.PeekUint8() == 0xFD); + CHECK(consumer.PeekSint8() == -3); + CHECK(consumer.PeekChar() == static_cast(-3)); + CHECK(consumer.ReadSint8() == -3); + CHECK(consumer.PeekUint8() == 0); + CHECK(consumer.PeekSint8() == 0); + CHECK(consumer.PeekChar() == 0); + CHECK(consumer.ReadChar() == 0); + CHECK(consumer.PeekUint8() == std::nullopt); + CHECK(consumer.PeekSint8() == std::nullopt); + CHECK(consumer.PeekChar() == std::nullopt); + CHECK(consumer.ReadUint8(42) == 42); + consumer.SkipSint8(); + CHECK(consumer.ReadSint8(42) == 42); + CHECK(consumer.ReadChar(42) == 42); +} + +TEST_CASE("StringConsumer - binary16") +{ + StringConsumer consumer("\xFF\xFF\xFE\xFF\xFD\xFF"sv); + CHECK(consumer.PeekUint16LE() == 0xFFFF); + CHECK(consumer.PeekSint16LE() == -1); + consumer.SkipUint16LE(); + CHECK(consumer.PeekUint16LE() == 0xFFFE); + CHECK(consumer.PeekSint16LE() == -2); + CHECK(consumer.ReadUint16LE() == 0xFFFE); + CHECK(consumer.PeekUint16LE() == 0xFFFD); + CHECK(consumer.PeekSint16LE() == -3); + CHECK(consumer.ReadSint16LE() == -3); + CHECK(consumer.PeekUint16LE() == std::nullopt); + CHECK(consumer.PeekSint16LE() == std::nullopt); + CHECK(consumer.ReadUint16LE(42) == 42); + consumer.SkipSint16LE(); + CHECK(consumer.ReadSint16LE(42) == 42); +} + +TEST_CASE("StringConsumer - binary32") +{ + StringConsumer consumer("\xFF\xFF\xFF\xFF\xFE\xFF\xFF\xFF\xFD\xFF\xFF\xFF"sv); + CHECK(consumer.PeekUint32LE() == 0xFFFFFFFF); + CHECK(consumer.PeekSint32LE() == -1); + consumer.SkipUint32LE(); + CHECK(consumer.PeekUint32LE() == 0xFFFFFFFE); + CHECK(consumer.PeekSint32LE() == -2); + CHECK(consumer.ReadUint32LE() == 0xFFFFFFFE); + CHECK(consumer.PeekUint32LE() == 0xFFFFFFFD); + CHECK(consumer.PeekSint32LE() == -3); + CHECK(consumer.ReadSint32LE() == -3); + CHECK(consumer.PeekUint32LE() == std::nullopt); + CHECK(consumer.PeekSint32LE() == std::nullopt); + CHECK(consumer.ReadUint32LE(42) == 42); + consumer.SkipSint32LE(); + CHECK(consumer.ReadSint32LE(42) == 42); +} + +TEST_CASE("StringConsumer - binary64") +{ + StringConsumer consumer("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFD\xFF\xFF\xFF\xFF\xFF\xFF\xFF"sv); + CHECK(consumer.PeekUint64LE() == 0xFFFFFFFF'FFFFFFFF); + CHECK(consumer.PeekSint64LE() == -1); + consumer.SkipUint64LE(); + CHECK(consumer.PeekUint64LE() == 0xFFFFFFFF'FFFFFFFE); + CHECK(consumer.PeekSint64LE() == -2); + CHECK(consumer.ReadUint64LE() == 0xFFFFFFFF'FFFFFFFE); + CHECK(consumer.PeekUint64LE() == 0xFFFFFFFF'FFFFFFFD); + CHECK(consumer.PeekSint64LE() == -3); + CHECK(consumer.ReadSint64LE() == -3); + CHECK(consumer.PeekUint64LE() == std::nullopt); + CHECK(consumer.PeekSint64LE() == std::nullopt); + CHECK(consumer.ReadUint64LE(42) == 42); + consumer.SkipSint64LE(); + CHECK(consumer.ReadSint64LE(42) == 42); +} + +TEST_CASE("StringConsumer - utf8") +{ + StringConsumer consumer("a\u1234\xFF\xFE""b"sv); + CHECK(consumer.PeekUtf8() == std::pair(1, 'a')); + consumer.SkipUtf8(); + CHECK(consumer.PeekUtf8() == std::pair(3, 0x1234)); + CHECK(consumer.ReadUtf8() == 0x1234); + CHECK(consumer.PeekUint8() == 0xFF); + CHECK(consumer.PeekUtf8() == std::pair(0, 0)); + CHECK(consumer.ReadUtf8() == '?'); + CHECK(consumer.PeekUint8() == 0xFE); + CHECK(consumer.PeekUtf8() == std::pair(0, 0)); + consumer.SkipUtf8(); + CHECK(consumer.PeekUtf8() == std::pair(1, 'b')); + CHECK(consumer.ReadUtf8() == 'b'); + CHECK(!consumer.AnyBytesLeft()); + CHECK(consumer.PeekUtf8() == std::pair(0, 0)); + CHECK(consumer.ReadUtf8() == '?'); +} + +TEST_CASE("StringConsumer - conditions") +{ + StringConsumer consumer("ABCDabcde\u0234@@@gh\0\0\0ij\0\0\0kl"sv); + CHECK(consumer.PeekIf("AB")); + CHECK(consumer.PeekCharIf('A')); + CHECK(consumer.PeekUtf8If('A')); + CHECK(!consumer.PeekIf("CD")); + CHECK(!consumer.ReadIf("CD")); + consumer.SkipIf("CD"); + CHECK(consumer.ReadIf("AB")); + CHECK(consumer.PeekIf("CD")); + consumer.SkipIf("CD"); + CHECK(consumer.Peek(2) == "ab"); + CHECK(consumer.Read(2) == "ab"); + CHECK(consumer.Peek(2) == "cd"); + CHECK(consumer.Find("e\u0234") == 2); + CHECK(consumer.Find("ab") == StringConsumer::npos); + CHECK(consumer.FindChar('e') == 2); + CHECK(consumer.FindChar('a') == StringConsumer::npos); + CHECK(consumer.FindUtf8(0x234) == 3); + CHECK(consumer.FindUtf8(0x1234) == StringConsumer::npos); + consumer.Skip(2); + CHECK(consumer.Peek(3) == "e\u0234"); + CHECK(consumer.PeekUntil("e", StringConsumer::READ_ALL_SEPARATORS) == "e"); + CHECK(consumer.PeekUntil("e", StringConsumer::READ_ONE_SEPARATOR) == "e"); + CHECK(consumer.PeekUntil("e", StringConsumer::KEEP_SEPARATOR) == ""); + CHECK(consumer.PeekUntil("e", StringConsumer::SKIP_ONE_SEPARATOR) == ""); + CHECK(consumer.PeekUntil("e", StringConsumer::SKIP_ALL_SEPARATORS) == ""); + CHECK(consumer.PeekUntil("@", StringConsumer::READ_ALL_SEPARATORS) == "e\u0234@@@"); + CHECK(consumer.PeekUntil("@", StringConsumer::READ_ONE_SEPARATOR) == "e\u0234@"); + CHECK(consumer.PeekUntil("@", StringConsumer::KEEP_SEPARATOR) == "e\u0234"); + CHECK(consumer.PeekUntil("@", StringConsumer::SKIP_ONE_SEPARATOR) == "e\u0234"); + CHECK(consumer.PeekUntil("@", StringConsumer::SKIP_ALL_SEPARATORS) == "e\u0234"); + CHECK(consumer.ReadUntil("@", StringConsumer::KEEP_SEPARATOR) == "e\u0234"); + CHECK(consumer.ReadUntil("@", StringConsumer::READ_ONE_SEPARATOR) == "@"); + CHECK(consumer.ReadUntil("@", StringConsumer::READ_ALL_SEPARATORS) == "@@"); + CHECK(consumer.PeekUntilChar('\0', StringConsumer::READ_ALL_SEPARATORS) == "gh\0\0\0"sv); + CHECK(consumer.PeekUntilChar('\0', StringConsumer::READ_ONE_SEPARATOR) == "gh\0"sv); + CHECK(consumer.PeekUntilChar('\0', StringConsumer::KEEP_SEPARATOR) == "gh"); + CHECK(consumer.PeekUntilChar('\0', StringConsumer::SKIP_ONE_SEPARATOR) == "gh"); + CHECK(consumer.PeekUntilChar('\0', StringConsumer::SKIP_ALL_SEPARATORS) == "gh"); + CHECK(consumer.ReadUntilChar('\0', StringConsumer::READ_ONE_SEPARATOR) == "gh\0"sv); + CHECK(consumer.PeekUntilChar('\0', StringConsumer::READ_ALL_SEPARATORS) == "\0\0"sv); + CHECK(consumer.ReadUntilChar('\0', StringConsumer::SKIP_ONE_SEPARATOR) == ""); + CHECK(consumer.PeekUntilChar('\0', StringConsumer::READ_ALL_SEPARATORS) == "\0"sv); + consumer.SkipUntilUtf8(0, StringConsumer::READ_ALL_SEPARATORS); + CHECK(consumer.PeekUntilUtf8(0, StringConsumer::KEEP_SEPARATOR) == "ij"); + consumer.SkipUntilUtf8(0, StringConsumer::SKIP_ALL_SEPARATORS); + CHECK(consumer.PeekUntilUtf8(0x234, StringConsumer::READ_ALL_SEPARATORS) == "kl"); + CHECK(consumer.PeekUntilUtf8(0x234, StringConsumer::READ_ONE_SEPARATOR) == "kl"); + CHECK(consumer.PeekUntilUtf8(0x234, StringConsumer::KEEP_SEPARATOR) == "kl"); + CHECK(consumer.PeekUntilUtf8(0x234, StringConsumer::SKIP_ONE_SEPARATOR) == "kl"); + CHECK(consumer.PeekUntilUtf8(0x234, StringConsumer::SKIP_ALL_SEPARATORS) == "kl"); + CHECK(consumer.ReadUntilUtf8(0x234, StringConsumer::READ_ALL_SEPARATORS) == "kl"); + CHECK(consumer.PeekUntilUtf8(0x234, StringConsumer::READ_ALL_SEPARATORS) == ""); + CHECK(consumer.PeekUntilUtf8(0x234, StringConsumer::READ_ONE_SEPARATOR) == ""); + CHECK(consumer.PeekUntilUtf8(0x234, StringConsumer::KEEP_SEPARATOR) == ""); + CHECK(consumer.PeekUntilUtf8(0x234, StringConsumer::SKIP_ONE_SEPARATOR) == ""); + CHECK(consumer.PeekUntilUtf8(0x234, StringConsumer::SKIP_ALL_SEPARATORS) == ""); + CHECK(consumer.ReadUntilUtf8(0x234, StringConsumer::READ_ALL_SEPARATORS) == ""); + CHECK(consumer.ReadUntilUtf8(0x234, StringConsumer::READ_ONE_SEPARATOR) == ""); + CHECK(consumer.ReadUntilUtf8(0x234, StringConsumer::KEEP_SEPARATOR) == ""); + CHECK(consumer.ReadUntilUtf8(0x234, StringConsumer::SKIP_ONE_SEPARATOR) == ""); + CHECK(consumer.ReadUntilUtf8(0x234, StringConsumer::SKIP_ALL_SEPARATORS) == ""); + CHECK(consumer.Peek(2) == ""); + CHECK(consumer.Read(2) == ""); +} + +TEST_CASE("StringConsumer - ascii") +{ + StringConsumer consumer("abcdefgh \r\n\tAB \r\n\t"sv); + CHECK(consumer.FindCharIn("dc") == 2); + CHECK(consumer.FindCharIn("xy") == StringConsumer::npos); + CHECK(consumer.FindCharNotIn("ba") == 2); + CHECK(consumer.PeekUntilCharNotIn("ba") == "ab"); + CHECK(consumer.PeekUntilCharNotIn("dc") == ""); + CHECK(consumer.PeekUntilCharIn("ba") == ""); + CHECK(consumer.PeekUntilCharIn("dc") == "ab"); + CHECK(consumer.ReadUntilCharNotIn("dc") == ""); + CHECK(consumer.ReadUntilCharNotIn("ba") == "ab"); + CHECK(consumer.ReadUntilCharIn("dc") == ""); + CHECK(consumer.ReadUntilCharIn("fe") == "cd"); + CHECK(consumer.PeekIf("ef")); + consumer.SkipUntilCharNotIn("ji"); + CHECK(consumer.PeekIf("ef")); + consumer.SkipUntilCharNotIn("fe"); + CHECK(consumer.PeekIf("gh")); + consumer.SkipUntilCharIn("hg"); + CHECK(consumer.PeekIf("gh")); + consumer.SkipUntilCharIn(StringConsumer::WHITESPACE_OR_NEWLINE); + CHECK(consumer.PeekCharIfIn(StringConsumer::WHITESPACE_OR_NEWLINE) == ' '); + CHECK(consumer.ReadCharIfIn(StringConsumer::WHITESPACE_OR_NEWLINE) == ' '); + consumer.SkipCharIfIn(StringConsumer::WHITESPACE_OR_NEWLINE); + CHECK(consumer.PeekUntilCharNotIn(StringConsumer::WHITESPACE_NO_NEWLINE) == "\r"); + CHECK(consumer.ReadUntilCharNotIn(StringConsumer::WHITESPACE_NO_NEWLINE) == "\r"); + consumer.SkipUntilCharNotIn(StringConsumer::WHITESPACE_NO_NEWLINE); + CHECK(consumer.PeekCharIfIn(StringConsumer::WHITESPACE_OR_NEWLINE) == '\n'); + CHECK(consumer.ReadCharIfIn(StringConsumer::WHITESPACE_OR_NEWLINE) == '\n'); + CHECK(consumer.PeekUntilCharNotIn(StringConsumer::WHITESPACE_NO_NEWLINE) == "\t"); + CHECK(consumer.ReadUntilCharNotIn(StringConsumer::WHITESPACE_NO_NEWLINE) == "\t"); + consumer.SkipUntilCharNotIn(StringConsumer::WHITESPACE_NO_NEWLINE); + CHECK(consumer.PeekUntilCharIn(StringConsumer::WHITESPACE_OR_NEWLINE) == "AB"); + CHECK(consumer.ReadUntilCharIn(StringConsumer::WHITESPACE_OR_NEWLINE) == "AB"); + CHECK(consumer.PeekUntilCharNotIn(StringConsumer::WHITESPACE_OR_NEWLINE) == " \r\n\t"); + consumer.SkipUntilCharNotIn(StringConsumer::WHITESPACE_OR_NEWLINE); + CHECK(!consumer.AnyBytesLeft()); +} + +TEST_CASE("StringConsumer - parse int") +{ + StringConsumer consumer("1 a -a -2 -2 ffffFFFF ffffFFFF -1aaaAAAA -1aaaAAAA +3 1234567890123 1234567890123 1234567890123 ffffFFFFffffFFFE ffffFFFFffffFFFE ffffFFFFffffFFFE ffffFFFFffffFFFE -0x1aaaAAAAaaaaAAAA -1234567890123 "sv); + CHECK(consumer.PeekIntegerBase(0) == std::pair(1, 1)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(1, 1)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(1, 1)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(1, 1)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(1, 1)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(1, 1)); + CHECK(consumer.TryReadIntegerBase(10) == 1); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(1, 0xa)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(1, 0xa)); + CHECK(consumer.ReadIntegerBase(16) == 0xa); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(2, -0xa)); + CHECK(consumer.ReadIntegerBase(16) == -0xa); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(2, -2)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(2, -2)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(2, -2)); + CHECK(consumer.TryReadIntegerBase(10) == std::nullopt); + CHECK(consumer.ReadIntegerBase(10) == 0); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(2, -2)); + CHECK(consumer.TryReadIntegerBase(10) == std::nullopt); + CHECK(consumer.ReadIntegerBase(10) == -2); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(16) == std::pair(8, 0xffffffff)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.TryReadIntegerBase(16) == std::nullopt); + CHECK(consumer.ReadIntegerBase(16) == 0); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.TryReadIntegerBase(16) == 0xffffffff); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(9, -0x1aaaaaaa)); + CHECK(consumer.TryReadIntegerBase(16) == std::nullopt); + CHECK(consumer.ReadIntegerBase(16) == 0); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(9, -0x1aaaaaaa)); + CHECK(consumer.TryReadIntegerBase(16) == std::nullopt); + CHECK(consumer.ReadIntegerBase(16) == -0x1aaaaaaa); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + consumer.SkipIntegerBase(10); + CHECK(consumer.ReadUtf8() == '+'); + CHECK(consumer.PeekIntegerBase(10) == std::pair(1, 3)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(1, 3)); + consumer.SkipIntegerBase(10); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(13, 1234567890123)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(13, 1234567890123)); + CHECK(consumer.TryReadIntegerBase(10) == std::nullopt); + CHECK(consumer.ReadIntegerBase(10) == 0); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(13, 1234567890123)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(13, 1234567890123)); + CHECK(consumer.TryReadIntegerBase(10) == std::nullopt); + CHECK(consumer.ReadIntegerBase(10) == 0); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(13, 1234567890123)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(13, 1234567890123)); + CHECK(consumer.ReadIntegerBase(10) == 1234567890123); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(16, 0xffffffff'fffffffe)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.ReadIntegerBase(16) == 0); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(16, 0xffffffff'fffffffe)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.ReadIntegerBase(16) == 0); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(16, 0xffffffff'fffffffe)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.ReadIntegerBase(16) == 0); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(16, 0xffffffff'fffffffe)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.ReadIntegerBase(16) == 0xffffffff'fffffffe); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(2, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(2, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(19, -0x1aaaaaaa'aaaaaaaa)); + CHECK(consumer.ReadIntegerBase(0) == -0x1aaaaaaa'aaaaaaaa); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(14, -1234567890123)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(14, -1234567890123)); + CHECK(consumer.ReadIntegerBase(0) == -1234567890123); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + consumer.SkipIntegerBase(10); + consumer.SkipIntegerBase(10); + consumer.SkipIntegerBase(0); + consumer.SkipIntegerBase(0); + CHECK(consumer.ReadIntegerBase(10, 42) == 42); + CHECK(consumer.ReadIntegerBase(10, 42) == 42); + CHECK(consumer.ReadIntegerBase(10, 42) == 42); + CHECK(consumer.ReadIntegerBase(10, 42) == 42); + CHECK(consumer.ReadIntegerBase(0, 42) == 42); + CHECK(consumer.ReadIntegerBase(0, 42) == 42); + CHECK(consumer.ReadIntegerBase(0, 42) == 42); + CHECK(consumer.ReadIntegerBase(0, 42) == 42); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + consumer.SkipIntegerBase(10); + consumer.SkipIntegerBase(10); + CHECK(consumer.ReadIntegerBase(10, 42) == 42); + CHECK(consumer.ReadIntegerBase(10, 42) == 42); + CHECK(consumer.ReadIntegerBase(10, 42) == 42); + CHECK(consumer.ReadIntegerBase(10, 42) == 42); + CHECK(consumer.ReadIntegerBase(0, 42) == 42); + CHECK(consumer.ReadIntegerBase(0, 42) == 42); + CHECK(consumer.ReadIntegerBase(0, 42) == 42); + CHECK(consumer.ReadIntegerBase(0, 42) == 42); +} + +TEST_CASE("StringConsumer - invalid int") +{ + StringConsumer consumer("x 0x - -0x 0y"sv); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + consumer.SkipIntegerBase(0); + consumer.SkipIntegerBase(10); + consumer.SkipIntegerBase(16); + CHECK(consumer.ReadUtf8() == 'x'); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(1, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(1, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(1, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(1, 0)); + consumer.SkipIntegerBase(0); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + consumer.SkipIntegerBase(0); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(2, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(0, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(2, 0)); + consumer.SkipIntegerBase(0); + CHECK(consumer.ReadUtf8() == ' '); + CHECK(consumer.PeekIntegerBase(0) == std::pair(1, 0)); + CHECK(consumer.PeekIntegerBase(0) == std::pair(1, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(1, 0)); + CHECK(consumer.PeekIntegerBase(10) == std::pair(1, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(1, 0)); + CHECK(consumer.PeekIntegerBase(16) == std::pair(1, 0)); + consumer.SkipIntegerBase(0); + CHECK(consumer.ReadUtf8() == 'y'); +} From 96eee0e8e4421618b4791dfda629cc6fe5d2e513 Mon Sep 17 00:00:00 2001 From: frosch Date: Thu, 27 Mar 2025 19:57:01 +0100 Subject: [PATCH 003/766] Codechange: Base ByteReader on StringConsumer. --- src/newgrf.cpp | 11 +++-- src/newgrf/newgrf_bytereader.cpp | 26 ------------ src/newgrf/newgrf_bytereader.h | 69 +++++++++++++++++++------------- src/string_func.h | 14 ------- 4 files changed, 49 insertions(+), 71 deletions(-) diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 3dc4fca19f..ee440aa464 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -1218,23 +1218,26 @@ struct InvokeGrfActionHandler { * XXX: We consider GRF files trusted. It would be trivial to exploit OTTD by * a crafted invalid GRF file. We should tell that to the user somehow, or * better make this more robust in the future. */ -static void DecodeSpecialSprite(uint8_t *buf, uint num, GrfLoadingStage stage) +static void DecodeSpecialSprite(ReusableBuffer &allocator, uint num, GrfLoadingStage stage) { + uint8_t *buf; auto it = _grf_line_to_action6_sprite_override.find({_cur_gps.grfconfig->ident.grfid, _cur_gps.nfo_line}); if (it == _grf_line_to_action6_sprite_override.end()) { /* No preloaded sprite to work with; read the * pseudo sprite content. */ + buf = allocator.Allocate(num); _cur_gps.file->ReadBlock(buf, num); } else { /* Use the preloaded sprite data. */ buf = it->second.data(); + assert(it->second.size() == num); GrfMsg(7, "DecodeSpecialSprite: Using preloaded pseudo sprite data"); /* Skip the real (original) content of this action. */ _cur_gps.file->SeekTo(num, SEEK_CUR); } - ByteReader br(buf, buf + num); + ByteReader br(buf, num); try { uint8_t action = br.ReadByte(); @@ -1302,7 +1305,7 @@ static void LoadNewGRFFileFromFile(GRFConfig &config, GrfLoadingStage stage, Spr _cur_gps.ClearDataForNextFile(); - ReusableBuffer buf; + ReusableBuffer allocator; while ((num = (grf_container_version >= 2 ? file.ReadDword() : file.ReadWord())) != 0) { uint8_t type = file.ReadByte(); @@ -1317,7 +1320,7 @@ static void LoadNewGRFFileFromFile(GRFConfig &config, GrfLoadingStage stage, Spr break; } - DecodeSpecialSprite(buf.Allocate(num), num, stage); + DecodeSpecialSprite(allocator, num, stage); /* Stop all processing if we are to skip the remaining sprites */ if (_cur_gps.skip_sprites == -1) break; diff --git a/src/newgrf/newgrf_bytereader.cpp b/src/newgrf/newgrf_bytereader.cpp index d09c1dfadc..a1a9585ff2 100644 --- a/src/newgrf/newgrf_bytereader.cpp +++ b/src/newgrf/newgrf_bytereader.cpp @@ -14,17 +14,6 @@ #include "../safeguards.h" -/** - * Read a single DWord (32 bits). - * @note The buffer is NOT advanced. - * @returns Value read from buffer. - */ -uint32_t ByteReader::PeekDWord() -{ - AutoRestoreBackup backup(this->data, this->data); - return this->ReadDWord(); -} - /** * Read a value of the given number of bytes. * @returns Value read from buffer. @@ -40,18 +29,3 @@ uint32_t ByteReader::ReadVarSize(uint8_t size) return 0; } } - -/** - * Read a string. - * @returns Sting read from the buffer. - */ -std::string_view ByteReader::ReadString() -{ - const char *string = reinterpret_cast(this->data); - size_t string_length = ttd_strnlen(string, this->Remaining()); - - /* Skip past the terminating NUL byte if it is present, but not more than remaining. */ - this->Skip(std::min(string_length + 1, this->Remaining())); - - return std::string_view(string, string_length); -} diff --git a/src/newgrf/newgrf_bytereader.h b/src/newgrf/newgrf_bytereader.h index b3ceec7f2b..e298524741 100644 --- a/src/newgrf/newgrf_bytereader.h +++ b/src/newgrf/newgrf_bytereader.h @@ -10,24 +10,21 @@ #ifndef NEWGRF_BYTEREADER_H #define NEWGRF_BYTEREADER_H +#include "../core/string_consumer.hpp" + class OTTDByteReaderSignal { }; /** Class to read from a NewGRF file */ class ByteReader { + StringConsumer consumer; public: - ByteReader(const uint8_t *data, const uint8_t *end) : data(data), end(end) { } + ByteReader(const uint8_t *data, size_t len) : consumer(reinterpret_cast(data), len) { } const uint8_t *ReadBytes(size_t size) { - if (this->data + size >= this->end) { - /* Put data at the end, as would happen if every byte had been individually read. */ - this->data = this->end; - throw OTTDByteReaderSignal(); - } - - const uint8_t *ret = this->data; - this->data += size; - return ret; + auto result = this->consumer.Read(size); + if (result.size() != size) throw OTTDByteReaderSignal(); + return reinterpret_cast(result.data()); } /** @@ -36,8 +33,9 @@ public: */ uint8_t ReadByte() { - if (this->data < this->end) return *this->data++; - throw OTTDByteReaderSignal(); + auto result = this->consumer.TryReadUint8(); + if (!result.has_value()) throw OTTDByteReaderSignal(); + return *result; } /** @@ -46,8 +44,9 @@ public: */ uint16_t ReadWord() { - uint16_t val = this->ReadByte(); - return val | (this->ReadByte() << 8); + auto result = this->consumer.TryReadUint16LE(); + if (!result.has_value()) throw OTTDByteReaderSignal(); + return *result; } /** @@ -66,35 +65,51 @@ public: */ uint32_t ReadDWord() { - uint32_t val = this->ReadWord(); - return val | (this->ReadWord() << 16); + auto result = this->consumer.TryReadUint32LE(); + if (!result.has_value()) throw OTTDByteReaderSignal(); + return *result; + } + + /** + * Read a single DWord (32 bits). + * @note The buffer is NOT advanced. + * @returns Value read from buffer. + */ + uint32_t PeekDWord() + { + auto result = this->consumer.PeekUint32LE(); + if (!result.has_value()) throw OTTDByteReaderSignal(); + return *result; } - uint32_t PeekDWord(); uint32_t ReadVarSize(uint8_t size); - std::string_view ReadString(); + + /** + * Read a NUL-terminated string. + * @returns String read from the buffer. + */ + std::string_view ReadString() + { + /* Terminating NUL may be missing at the end of sprite. */ + return this->consumer.ReadUntilChar('\0', StringConsumer::SKIP_ONE_SEPARATOR); + } size_t Remaining() const { - return this->end - this->data; + return this->consumer.GetBytesLeft(); } bool HasData(size_t count = 1) const { - return this->data + count <= this->end; + return count <= this->consumer.GetBytesLeft(); } void Skip(size_t len) { - this->data += len; - /* It is valid to move the buffer to exactly the end of the data, - * as there may not be any more data read. */ - if (this->data > this->end) throw OTTDByteReaderSignal(); + auto result = this->consumer.Read(len); + if (result.size() != len) throw OTTDByteReaderSignal(); } -private: - const uint8_t *data; ///< Current position within data. - const uint8_t *end; ///< Last position of data. }; #endif /* NEWGRF_BYTEREADER_H */ diff --git a/src/string_func.h b/src/string_func.h index 0952993855..09c8dc18db 100644 --- a/src/string_func.h +++ b/src/string_func.h @@ -70,20 +70,6 @@ inline bool StrEmpty(const char *s) return s == nullptr || s[0] == '\0'; } -/** - * Get the length of a string, within a limited buffer. - * - * @param str The pointer to the first element of the buffer - * @param maxlen The maximum size of the buffer - * @return The length of the string - */ -inline size_t ttd_strnlen(const char *str, size_t maxlen) -{ - const char *t; - for (t = str; static_cast(t - str) < maxlen && *t != '\0'; t++) {} - return t - str; -} - bool IsValidChar(char32_t key, CharSetFilter afilter); size_t Utf8Decode(char32_t *c, const char *s); From 3964d053b5a068cd8a1409c3f328a2264dac7f01 Mon Sep 17 00:00:00 2001 From: frosch Date: Thu, 27 Mar 2025 20:21:42 +0100 Subject: [PATCH 004/766] Codechange: Replace BufState with StringConsumer. --- src/3rdparty/squirrel/squirrel/sqapi.cpp | 37 ++++-------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/src/3rdparty/squirrel/squirrel/sqapi.cpp b/src/3rdparty/squirrel/squirrel/sqapi.cpp index 189fe67329..e530cd23ce 100644 --- a/src/3rdparty/squirrel/squirrel/sqapi.cpp +++ b/src/3rdparty/squirrel/squirrel/sqapi.cpp @@ -18,6 +18,7 @@ #include "sqfuncstate.h" #include "sqclass.h" +#include "../../../core/string_consumer.hpp" #include "../../../string_func.h" #include "../../../safeguards.h" @@ -1253,44 +1254,16 @@ SQRESULT sq_next(HSQUIRRELVM v,SQInteger idx) return SQ_ERROR; } -struct BufState{ - const SQChar *buf; - SQInteger ptr; - SQInteger size; -}; - char32_t buf_lexfeed(SQUserPointer file) { /* Convert an UTF-8 character into a char32_t */ - BufState *buf = (BufState *)file; - const char *p = &buf->buf[buf->ptr]; - - if (buf->size < buf->ptr + 1) return 0; - - /* Read the first character, and get the length based on UTF-8 specs. If invalid, bail out. */ - uint len = Utf8EncodedCharLen(*p); - if (len == 0) { - buf->ptr++; - return -1; - } - - /* Read the remaining bits. */ - if (buf->size < buf->ptr + len) return 0; - buf->ptr += len; - - /* Convert the character, and when definitely invalid, bail out as well. */ - char32_t c; - if (Utf8Decode(&c, p) != len) return -1; - - return c; + StringConsumer &consumer = *reinterpret_cast(file); + return consumer.AnyBytesLeft() ? consumer.ReadUtf8(-1) : 0; } SQRESULT sq_compilebuffer(HSQUIRRELVM v,const SQChar *s,SQInteger size,const SQChar *sourcename,SQBool raiseerror) { - BufState buf; - buf.buf = s; - buf.size = size; - buf.ptr = 0; - return sq_compile(v, buf_lexfeed, &buf, sourcename, raiseerror); + StringConsumer consumer(s, size); + return sq_compile(v, buf_lexfeed, &consumer, sourcename, raiseerror); } void sq_move(HSQUIRRELVM dest,HSQUIRRELVM src,SQInteger idx) From 9bcd3feb1720ce4787411e06d94b5e92871b863d Mon Sep 17 00:00:00 2001 From: frosch Date: Thu, 27 Mar 2025 22:10:47 +0100 Subject: [PATCH 005/766] Codechange: Make SQFile a buffered reader, based on StringConsumer. --- src/script/squirrel.cpp | 79 ++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/src/script/squirrel.cpp b/src/script/squirrel.cpp index af65a993f5..05967deecc 100644 --- a/src/script/squirrel.cpp +++ b/src/script/squirrel.cpp @@ -18,6 +18,8 @@ #include #include <../squirrel/sqpcheader.h> #include <../squirrel/sqvm.h> +#include "../core/math_func.hpp" +#include "../core/string_consumer.hpp" #include "../safeguards.h" @@ -544,52 +546,73 @@ private: FileHandle file; size_t size; size_t pos; + std::string buffer; + StringConsumer consumer; + + size_t ReadInternal(std::span buf) + { + size_t count = buf.size(); + if (this->pos + count > this->size) { + count = this->size - this->pos; + } + if (count > 0) count = fread(buf.data(), 1, count, this->file); + this->pos += count; + return count; + } public: - SQFile(FileHandle file, size_t size) : file(std::move(file)), size(size), pos(0) {} + SQFile(FileHandle file, size_t size) : file(std::move(file)), size(size), pos(0), consumer(buffer) {} - size_t Read(void *buf, size_t elemsize, size_t count) + StringConsumer &GetConsumer(size_t min_size = 64) { - assert(elemsize != 0); - if (this->pos + (elemsize * count) > this->size) { - count = (this->size - this->pos) / elemsize; + if (this->consumer.GetBytesLeft() < min_size && this->pos < this->size) { + this->buffer.erase(0, this->consumer.GetBytesRead()); + + size_t buffer_size = this->buffer.size(); + size_t read_size = Align(min_size - buffer_size, 4096); // read pages of 4096 bytes + // TODO C++23: use std::string::resize_and_overwrite() + this->buffer.resize(buffer_size + read_size); + auto dest = std::span(this->buffer.data(), this->buffer.size()).subspan(buffer_size); + buffer_size += this->ReadInternal(dest); + this->buffer.resize(buffer_size); + + this->consumer = StringConsumer(this->buffer); } - if (count == 0) return 0; - size_t ret = fread(buf, elemsize, count, this->file); - this->pos += ret * elemsize; - return ret; + return this->consumer; + } + + size_t Read(void *buf, size_t max_size) + { + std::span dest(reinterpret_cast(buf), max_size); + + auto view = this->consumer.Read(max_size); + std::copy(view.data(), view.data() + view.size(), dest.data()); + size_t result_size = view.size(); + + if (result_size < max_size) { + assert(!this->consumer.AnyBytesLeft()); + result_size += this->ReadInternal(dest.subspan(result_size)); + } + + return result_size; } }; static char32_t _io_file_lexfeed_ASCII(SQUserPointer file) { - unsigned char c; - if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return c; - return 0; + StringConsumer &consumer = reinterpret_cast(file)->GetConsumer(); + return consumer.TryReadUint8().value_or(0); // read as unsigned, otherwise integer promotion breaks it } static char32_t _io_file_lexfeed_UTF8(SQUserPointer file) { - char buffer[5]; - - /* Read the first character, and get the length based on UTF-8 specs. If invalid, bail out. */ - if (((SQFile *)file)->Read(buffer, sizeof(buffer[0]), 1) != 1) return 0; - uint len = Utf8EncodedCharLen(buffer[0]); - if (len == 0) return -1; - - /* Read the remaining bits. */ - if (len > 1 && ((SQFile *)file)->Read(buffer + 1, sizeof(buffer[0]), len - 1) != len - 1) return 0; - - /* Convert the character, and when definitely invalid, bail out as well. */ - char32_t c; - if (Utf8Decode(&c, buffer) != len) return -1; - - return c; + StringConsumer &consumer = reinterpret_cast(file)->GetConsumer(); + return consumer.AnyBytesLeft() ? consumer.ReadUtf8(-1) : 0; } static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size) { - SQInteger ret = ((SQFile *)file)->Read(buf, 1, size); + SQInteger ret = reinterpret_cast(file)->Read(buf, size); if (ret == 0) return -1; return ret; } From 131b7c71229aa96aaf7a53105cc4bfc4b6600a3c Mon Sep 17 00:00:00 2001 From: frosch Date: Fri, 28 Mar 2025 19:15:18 +0100 Subject: [PATCH 006/766] Codechange: Replace Utf8Decode usages with StringConsumer. --- src/osk_gui.cpp | 12 +++--------- src/saveload/saveload.cpp | 14 +++++++------ src/strings.cpp | 41 +++++++++++++++------------------------ 3 files changed, 27 insertions(+), 40 deletions(-) diff --git a/src/osk_gui.cpp b/src/osk_gui.cpp index 4763321ba9..585c267f9f 100644 --- a/src/osk_gui.cpp +++ b/src/osk_gui.cpp @@ -16,6 +16,7 @@ #include "querystring_gui.h" #include "video/video_driver.hpp" #include "zoom_func.h" +#include "core/string_consumer.hpp" #include "widgets/osk_widget.h" @@ -358,17 +359,10 @@ void GetKeyboardLayout() keyboard[1] = _keyboard_opt[1].empty() ? GetString(STR_OSK_KEYBOARD_LAYOUT_CAPS) : _keyboard_opt[1]; for (uint j = 0; j < 2; j++) { - auto kbd = keyboard[j].begin(); - bool ended = false; + StringConsumer consumer(keyboard[j]); for (uint i = 0; i < OSK_KEYBOARD_ENTRIES; i++) { - _keyboard[j][i] = Utf8Consume(kbd); - /* Be lenient when the last characters are missing (is quite normal) */ - if (_keyboard[j][i] == '\0' || ended) { - ended = true; - _keyboard[j][i] = ' '; - continue; - } + _keyboard[j][i] = consumer.AnyBytesLeft() ? consumer.ReadUtf8() : ' '; if (IsPrintable(_keyboard[j][i])) { errormark[j] += ' '; diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 5b743ba20d..9c7bcc32c6 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -30,6 +30,7 @@ #include "../strings_func.h" #include "../core/endian_func.hpp" #include "../core/string_builder.hpp" +#include "../core/string_consumer.hpp" #include "../vehicle_base.h" #include "../company_func.h" #include "../timer/timer_game_economy.h" @@ -935,13 +936,14 @@ void FixSCCEncoded(std::string &str, bool fix_code) bool in_string = false; // Set if we in a string, between double-quotes. bool need_type = true; // Set if a parameter type needs to be emitted. - for (auto it = std::begin(str); it != std::end(str); /* nothing */) { - size_t len = Utf8EncodedCharLen(*it); - if (len == 0 || it + len > std::end(str)) break; - + StringConsumer consumer(str); + while (consumer.AnyBytesLeft()) { char32_t c; - Utf8Decode(&c, &*it); - it += len; + if (auto r = consumer.TryReadUtf8(); r.has_value()) { + c = *r; + } else { + break; + } if (c == SCC_ENCODED || (fix_code && (c == 0xE028 || c == 0xE02A))) { builder.PutUtf8(SCC_ENCODED); need_type = false; diff --git a/src/strings.cpp b/src/strings.cpp index 68a59c0c9b..438af68e8a 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -39,8 +39,8 @@ #include "core/backup_type.hpp" #include "gfx_layout.h" #include "core/utf8.hpp" +#include "core/string_consumer.hpp" #include -#include #include "table/strings.h" #include "table/control_codes.h" @@ -153,48 +153,39 @@ EncodedString EncodedString::ReplaceParam(size_t param, StringParameter &&data) if (this->empty()) return {}; std::vector params; + StringConsumer consumer(this->string); - /* We need char * for std::from_chars. Iterate the underlying data, as string's own iterators may interfere. */ - const char *p = this->string.data(); - const char *e = this->string.data() + this->string.length(); - - char32_t c = Utf8Consume(p); - if (c != SCC_ENCODED_INTERNAL) return {}; + if (!consumer.ReadUtf8If(SCC_ENCODED_INTERNAL)) return {}; StringID str; - auto result = std::from_chars(p, e, str, 16); - if (result.ec != std::errc()) return {}; - if (result.ptr != e && *result.ptr != SCC_RECORD_SEPARATOR) return {}; - p = result.ptr; + if (auto r = consumer.TryReadIntegerBase(16); r.has_value()) { + str = *r; + } else { + return {}; + } + if (consumer.AnyBytesLeft() && !consumer.ReadUtf8If(SCC_RECORD_SEPARATOR)) return {}; - while (p != e) { - auto s = ++p; + while (consumer.AnyBytesLeft()) { + StringConsumer record(consumer.ReadUntilUtf8(SCC_RECORD_SEPARATOR, StringConsumer::SKIP_ONE_SEPARATOR)); - /* Find end of the parameter. */ - for (; p != e && *p != SCC_RECORD_SEPARATOR; ++p) {} - - if (s == p) { + if (!record.AnyBytesLeft()) { /* This is an empty parameter. */ params.emplace_back(std::monostate{}); continue; } /* Get the parameter type. */ - char32_t parameter_type; - size_t len = Utf8Decode(¶meter_type, s); - s += len; - + char32_t parameter_type = record.ReadUtf8(); switch (parameter_type) { case SCC_ENCODED_NUMERIC: { - uint64_t value; - result = std::from_chars(s, p, value, 16); - if (result.ec != std::errc() || result.ptr != p) return {}; + uint64_t value = record.ReadIntegerBase(16); + assert(!record.AnyBytesLeft()); params.emplace_back(value); break; } case SCC_ENCODED_STRING: { - params.emplace_back(std::string(s, p)); + params.emplace_back(std::string(record.Read(StringConsumer::npos))); break; } From d48bc18721cb889174d9cc5faf80d91c5af20606 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sun, 13 Apr 2025 21:06:09 +0100 Subject: [PATCH 007/766] Codechange: Rename badge_class to class_index and badge_classes to gui_classes internally. (#13995) These names are already used in some places, so this brings a bit of consistency. --- src/newgrf_badge.cpp | 46 ++++++++++++++++++++++---------------------- src/newgrf_badge.h | 8 ++++---- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/newgrf_badge.cpp b/src/newgrf_badge.cpp index b1269a02fe..0ed9cced62 100644 --- a/src/newgrf_badge.cpp +++ b/src/newgrf_badge.cpp @@ -285,7 +285,7 @@ static PalSpriteID GetBadgeSprite(const Badge &badge, GrfSpecFeature feature, st /** * Get the largest badge size (within limits) for a badge class. - * @param badge_class Badge class. + * @param class_index Badge class. * @param feature Feature being used. * @returns Largest base size of the badge class for the feature. */ @@ -439,11 +439,11 @@ int DrawBadgeNameList(Rect r, std::span badges, GrfSpecFeature) { if (badges.empty()) return r.top; - std::set classes; - for (const BadgeID &index : badges) classes.insert(GetBadge(index)->class_index); + std::set class_indexes; + for (const BadgeID &index : badges) class_indexes.insert(GetBadge(index)->class_index); std::string_view list_separator = GetListSeparator(); - for (const BadgeClassID &class_index : classes) { + for (const BadgeClassID &class_index : class_indexes) { const Badge *class_badge = GetClassBadge(class_index); if (class_badge == nullptr || class_badge->name == STR_NULL) continue; @@ -473,23 +473,23 @@ int DrawBadgeNameList(Rect r, std::span badges, GrfSpecFeature) * Draw a badge column group. * @param r rect to draw within. * @param column_group column to draw. - * @param badge_classes badge classes. + * @param gui_classes gui badge classes. * @param badges badges to draw. * @param feature feature being used. * @param introduction_date introduction date of item. * @param remap palette remap to for company-coloured badges. */ -void DrawBadgeColumn(Rect r, int column_group, const GUIBadgeClasses &badge_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, PaletteID remap) +void DrawBadgeColumn(Rect r, int column_group, const GUIBadgeClasses &gui_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, PaletteID remap) { bool rtl = _current_text_dir == TD_RTL; - for (const auto &badge_class : badge_classes.GetClasses()) { - if (badge_class.column_group != column_group) continue; - if (!badge_class.visible) continue; + for (const auto &gc : gui_classes.GetClasses()) { + if (gc.column_group != column_group) continue; + if (!gc.visible) continue; - int width = ScaleGUITrad(badge_class.size.width); + int width = ScaleGUITrad(gc.size.width); for (const BadgeID &index : badges) { const Badge &badge = *GetBadge(index); - if (badge.class_index != badge_class.badge_class) continue; + if (badge.class_index != gc.class_index) continue; PalSpriteID ps = GetBadgeSprite(badge, feature, introduction_date, remap); if (ps.sprite == 0) continue; @@ -507,13 +507,13 @@ template class DropDownBadges : public TBase { public: template - explicit DropDownBadges(const std::shared_ptr &badge_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, Args&&... args) - : TBase(std::forward(args)...), badge_classes(badge_classes), badges(badges), feature(feature), introduction_date(introduction_date) + explicit DropDownBadges(const std::shared_ptr &gui_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, Args&&... args) + : TBase(std::forward(args)...), gui_classes(gui_classes), badges(badges), feature(feature), introduction_date(introduction_date) { - for (const auto &badge_class : badge_classes->GetClasses()) { - if (badge_class.column_group != 0) continue; - dim.width += badge_class.size.width + WidgetDimensions::scaled.hsep_normal; - dim.height = std::max(dim.height, badge_class.size.height); + for (const auto &gc : gui_classes->GetClasses()) { + if (gc.column_group != 0) continue; + dim.width += gc.size.width + WidgetDimensions::scaled.hsep_normal; + dim.height = std::max(dim.height, gc.size.height); } } @@ -524,13 +524,13 @@ public: { bool rtl = TEnd ^ (_current_text_dir == TD_RTL); - DrawBadgeColumn(r.WithWidth(this->dim.width, rtl), 0, *this->badge_classes, this->badges, this->feature, this->introduction_date, PAL_NONE); + DrawBadgeColumn(r.WithWidth(this->dim.width, rtl), 0, *this->gui_classes, this->badges, this->feature, this->introduction_date, PAL_NONE); this->TBase::Draw(full, r.Indent(this->dim.width + WidgetDimensions::scaled.hsep_wide, rtl), sel, bg_colour); } private: - std::shared_ptr badge_classes; + std::shared_ptr gui_classes; const std::span badges; const GrfSpecFeature feature; @@ -543,12 +543,12 @@ private: using DropDownListBadgeItem = DropDownBadges; using DropDownListBadgeIconItem = DropDownBadges; -std::unique_ptr MakeDropDownListBadgeItem(const std::shared_ptr &badge_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, std::string &&str, int value, bool masked, bool shaded) +std::unique_ptr MakeDropDownListBadgeItem(const std::shared_ptr &gui_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, std::string &&str, int value, bool masked, bool shaded) { - return std::make_unique(badge_classes, badges, feature, introduction_date, std::move(str), value, masked, shaded); + return std::make_unique(gui_classes, badges, feature, introduction_date, std::move(str), value, masked, shaded); } -std::unique_ptr MakeDropDownListBadgeIconItem(const std::shared_ptr &badge_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, const Dimension &dim, SpriteID sprite, PaletteID palette, std::string &&str, int value, bool masked, bool shaded) +std::unique_ptr MakeDropDownListBadgeIconItem(const std::shared_ptr &gui_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, const Dimension &dim, SpriteID sprite, PaletteID palette, std::string &&str, int value, bool masked, bool shaded) { - return std::make_unique(badge_classes, badges, feature, introduction_date, dim, sprite, palette, std::move(str), value, masked, shaded); + return std::make_unique(gui_classes, badges, feature, introduction_date, dim, sprite, palette, std::move(str), value, masked, shaded); } diff --git a/src/newgrf_badge.h b/src/newgrf_badge.h index e76baa71d5..b296aa98bc 100644 --- a/src/newgrf_badge.h +++ b/src/newgrf_badge.h @@ -44,15 +44,15 @@ Badge *GetClassBadge(BadgeClassID class_index); class GUIBadgeClasses { public: struct Element { - BadgeClassID badge_class; ///< Badge class index. + BadgeClassID class_index; ///< Badge class index. uint8_t column_group; ///< Column group in UI. 0 = left, 1 = centre, 2 = right. bool visible; ///< Whether this element is visible. uint sort_order; ///< Order of element. Dimension size; ///< Maximal size of this element. std::string_view label; ///< Class label (string owned by the class badge) - constexpr Element(BadgeClassID badge_class, uint8_t column_group, bool visible, uint sort_order, Dimension size, std::string_view label) : - badge_class(badge_class), column_group(column_group), visible(visible), sort_order(sort_order), size(size), label(label) {} + constexpr Element(BadgeClassID class_index, uint8_t column_group, bool visible, uint sort_order, Dimension size, std::string_view label) : + class_index(class_index), column_group(column_group), visible(visible), sort_order(sort_order), size(size), label(label) {} }; GUIBadgeClasses() = default; @@ -70,7 +70,7 @@ private: }; int DrawBadgeNameList(Rect r, std::span badges, GrfSpecFeature feature); -void DrawBadgeColumn(Rect r, int column_group, const GUIBadgeClasses &badge_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, PaletteID remap); +void DrawBadgeColumn(Rect r, int column_group, const GUIBadgeClasses &gui_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, PaletteID remap); uint32_t GetBadgeVariableResult(const struct GRFFile &grffile, std::span badges, uint32_t parameter); From 3dacf46870a711591d3251517adcfe09fba8b3fe Mon Sep 17 00:00:00 2001 From: translators Date: Mon, 14 Apr 2025 04:44:46 +0000 Subject: [PATCH 008/766] Update: Translations from eints vietnamese: 7 changes by KhoiCanDev --- src/lang/vietnamese.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lang/vietnamese.txt b/src/lang/vietnamese.txt index f3525df674..8154410b2d 100644 --- a/src/lang/vietnamese.txt +++ b/src/lang/vietnamese.txt @@ -1210,7 +1210,7 @@ STR_CITY_APPROVAL_TOLERANT :Vừa phải STR_CITY_APPROVAL_HOSTILE :Khó khăn STR_CITY_APPROVAL_PERMISSIVE :Dễ dãi (không có tác dụng với hoạt động xây dựng của công ty) -STR_WARNING_NO_SUITABLE_AI :{WHITE}Không có AI nào phù hợp...{}Bạn có thể tải các AI từ mục 'Online Content' +STR_WARNING_NO_SUITABLE_AI :{WHITE}Không có AI nào phù hợp...{}{}Bạn có thể tải các AI từ mục 'Nội dung trực tuyến' # Settings tree window STR_CONFIG_SETTING_TREE_CAPTION :{WHITE}Thiết lập @@ -2854,6 +2854,8 @@ STR_HOUSE_PICKER_CLASS_ZONE3 :Phía ngoài ng STR_HOUSE_PICKER_CLASS_ZONE4 :Phía trong ngoại ô STR_HOUSE_PICKER_CLASS_ZONE5 :Nội thành +STR_HOUSE_PICKER_PROTECT_OFF :Tắt +STR_HOUSE_PICKER_PROTECT_ON :Bật STR_STATION_CLASS_DFLT :Mặc định STR_STATION_CLASS_DFLT_STATION :Trạm mặc định @@ -4016,6 +4018,10 @@ STR_INDUSTRY_VIEW_PRODUCES_N_CARGO :{BLACK}Sản l STR_INDUSTRY_VIEW_CARGO_LIST_EXTENSION :, {STRING}{STRING} STR_INDUSTRY_VIEW_REQUIRES :{BLACK}Cần cung cấp: +STR_INDUSTRY_VIEW_ACCEPT_CARGO_SUFFIX :{YELLOW}{0:STRING}{BLACK}{3:STRING} +STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_SUFFIX :{YELLOW}{0:STRING}{BLACK}: {1:CARGO_SHORT} đang chờ{3:STRING} +STR_INDUSTRY_VIEW_ACCEPT_CARGO_NOSUFFIX :{YELLOW}{0:STRING} +STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_NOSUFFIX :{YELLOW}{0:STRING}{BLACK}: {1:CARGO_SHORT} đang chờ STR_CONFIG_GAME_PRODUCTION :{WHITE}Thay đổi sản lượng (bội số của 8, max 2040) STR_CONFIG_GAME_PRODUCTION_LEVEL :{WHITE}Thay đổi mức sản lượng (theo phần trăm, cao nhất 800%) From 069edc1a4bbcd5a284a6d1e450de77f2f4216ce4 Mon Sep 17 00:00:00 2001 From: John Taylor Date: Mon, 14 Apr 2025 16:57:07 +0200 Subject: [PATCH 009/766] Fix #13954: Plotting graphs with limited data to the right. (#13956) --- src/graph_gui.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/graph_gui.cpp b/src/graph_gui.cpp index e3b89744b4..7cb36d9dbe 100644 --- a/src/graph_gui.cpp +++ b/src/graph_gui.cpp @@ -442,10 +442,14 @@ protected: y += y_sep; } + x = rtl ? r.right : r.left; + y = r.bottom + ScaleGUITrad(2); + + /* if there are not enough datapoints to fill the graph, align to the right */ + x += (this->num_vert_lines - this->num_on_x_axis) * x_sep; + /* Draw x-axis labels and markings for graphs based on financial quarters and years. */ if (this->draw_dates) { - x = rtl ? r.right : r.left; - y = r.bottom + ScaleGUITrad(2); TimerGameEconomy::Month month = this->month; TimerGameEconomy::Year year = this->year; for (int i = 0; i < this->num_on_x_axis; i++) { @@ -471,9 +475,6 @@ protected: } } else { /* Draw x-axis labels for graphs not based on quarterly performance (cargo payment rates, and all graphs when using wallclock units). */ - x = rtl ? r.right : r.left; - y = r.bottom + ScaleGUITrad(2); - int16_t iterator; uint16_t label; if (this->x_values_reversed) { @@ -512,6 +513,9 @@ protected: x = r.left + (x_sep / 2); } + /* if there are not enough datapoints to fill the graph, align to the right */ + x += (this->num_vert_lines - this->num_on_x_axis) * x_sep; + uint prev_x = INVALID_DATAPOINT_POS; uint prev_y = INVALID_DATAPOINT_POS; From 98efd3c96e4564ad40d680022edb43b25b3272d0 Mon Sep 17 00:00:00 2001 From: frosch Date: Fri, 28 Mar 2025 16:18:24 +0100 Subject: [PATCH 010/766] Codefix #13872: Use StringConsumer in TranslateTTDPatchCodes. --- src/newgrf_text.cpp | 58 ++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/src/newgrf_text.cpp b/src/newgrf_text.cpp index 3deb92a221..fdb09ace1c 100644 --- a/src/newgrf_text.cpp +++ b/src/newgrf_text.cpp @@ -28,6 +28,7 @@ #include "debug.h" #include "core/alloc_type.hpp" #include "core/string_builder.hpp" +#include "core/string_consumer.hpp" #include "language.h" #include "table/strings.h" @@ -238,17 +239,10 @@ std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool all /* Empty input string? Nothing to do here. */ if (str.empty()) return {}; - std::string_view::const_iterator src = str.cbegin(); + StringConsumer consumer(str); /* Is this an unicode string? */ - bool unicode = false; - char32_t marker; - size_t len = Utf8Decode(&marker, &*src); - - if (marker == NFO_UTF8_IDENTIFIER) { - unicode = true; - src += len; - } + bool unicode = consumer.ReadUtf8If(NFO_UTF8_IDENTIFIER); /* Helper variable for a possible (string) mapping of plural/gender and cases. */ std::optional mapping_pg, mapping_c; @@ -256,29 +250,27 @@ std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool all std::string dest; StringBuilder builder(dest); - while (src != str.cend()) { + while (consumer.AnyBytesLeft()) { char32_t c; - - if (unicode && Utf8EncodedCharLen(*src) != 0) { - c = Utf8Consume(src); + if (auto u = unicode ? consumer.TryReadUtf8() : std::nullopt; u.has_value()) { + c = *u; /* 'Magic' range of control codes. */ - if (GB(c, 8, 8) == 0xE0) { - c = GB(c, 0, 8); + if (0xE000 <= c && c <= 0xE0FF) { + c -= 0xE000; } else if (c >= 0x20) { if (!IsValidChar(c, CS_ALPHANUMERAL)) c = '?'; builder.PutUtf8(c); continue; } } else { - c = static_cast(*src++); + c = consumer.ReadUint8(); // read as unsigned, otherwise integer promotion breaks it } - + assert(c <= 0xFF); if (c == '\0') break; switch (c) { case 0x01: - if (*src == '\0') goto string_end; - src++; + consumer.SkipUint8(); builder.PutChar(' '); break; case 0x0A: break; @@ -292,8 +284,8 @@ std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool all case 0x0E: builder.PutUtf8(SCC_TINYFONT); break; case 0x0F: builder.PutUtf8(SCC_BIGFONT); break; case 0x1F: - if (src[0] == '\0' || src[1] == '\0') goto string_end; - src += 2; + consumer.SkipUint8(); + consumer.SkipUint8(); builder.PutChar(' '); break; case 0x7B: @@ -304,10 +296,7 @@ std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool all case 0x80: builder.PutUtf8(byte80); break; case 0x81: { - if (src[0] == '\0' || src[1] == '\0') goto string_end; - uint16_t string; - string = static_cast(*src++); - string |= static_cast(*src++) << 8; + uint16_t string = consumer.ReadUint16LE(); builder.PutUtf8(SCC_NEWGRF_STRINL); builder.PutUtf8(MapGRFStringID(grfid, GRFStringID{string})); break; @@ -337,7 +326,7 @@ std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool all case 0x98: builder.PutUtf8(SCC_BLACK); break; case 0x9A: { - int code = *src++; + uint8_t code = consumer.ReadUint8(); switch (code) { case 0x00: goto string_end; case 0x01: builder.PutUtf8(SCC_NEWGRF_PRINT_QWORD_CURRENCY); break; @@ -350,9 +339,7 @@ std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool all * support this. As such it is not implemented in OpenTTD. */ case 0x03: { - if (src[0] == '\0' || src[1] == '\0') goto string_end; - uint16_t tmp = static_cast(*src++); - tmp |= static_cast(*src++) << 8; + uint16_t tmp = consumer.ReadUint16LE(); builder.PutUtf8(SCC_NEWGRF_PUSH_WORD); builder.PutUtf8(tmp); break; @@ -367,9 +354,8 @@ std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool all case 0x0E: case 0x0F: { - if (str[0] == '\0') goto string_end; const LanguageMap *lm = LanguageMap::GetLanguageMap(grfid, language_id); - int index = *src++; + int index = consumer.ReadUint8(); int mapped = lm != nullptr ? lm->GetMapping(index, code == 0x0E) : -1; if (mapped >= 0) { builder.PutUtf8(code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE); @@ -380,14 +366,13 @@ std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool all case 0x10: case 0x11: - if (str[0] == '\0') goto string_end; if (!mapping_pg.has_value() && !mapping_c.has_value()) { - if (code == 0x10) src++; // Skip the index + if (code == 0x10) consumer.SkipUint8(); // Skip the index GrfMsg(1, "choice list {} marker found when not expected", code == 0x10 ? "next" : "default"); break; } else { auto &mapping = mapping_pg ? mapping_pg : mapping_c; - int index = (code == 0x10 ? *src++ : 0); + int index = (code == 0x10 ? consumer.ReadUint8() : 0); if (mapping->strings.find(index) != mapping->strings.end()) { GrfMsg(1, "duplicate choice list string, ignoring"); } else { @@ -416,14 +401,13 @@ std::string TranslateTTDPatchCodes(uint32_t grfid, uint8_t language_id, bool all case 0x14: case 0x15: { auto &mapping = code == 0x14 ? mapping_c : mapping_pg; - if (src[0] == '\0') goto string_end; /* Case mapping can have nested plural/gender mapping. Otherwise nesting is invalid. */ if (mapping.has_value() || mapping_pg.has_value()) { GrfMsg(1, "choice lists can't be stacked, it's going to get messy now..."); - if (code != 0x14) src++; + if (code != 0x14) consumer.SkipUint8(); } else { static const StringControlCode mp[] = { SCC_GENDER_LIST, SCC_SWITCH_CASE, SCC_PLURAL_LIST }; - mapping.emplace(mp[code - 0x13], code == 0x14 ? 0 : *src++); + mapping.emplace(mp[code - 0x13], code == 0x14 ? 0 : consumer.ReadUint8()); } break; } From 15a17d832fff6acec1a2a736054490ecc060c853 Mon Sep 17 00:00:00 2001 From: frosch Date: Fri, 28 Mar 2025 16:31:16 +0100 Subject: [PATCH 011/766] Codechange: Use StringConsumer in HandleNewGRFStringControlCodes. --- src/newgrf_text.cpp | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/newgrf_text.cpp b/src/newgrf_text.cpp index fdb09ace1c..a23d94a988 100644 --- a/src/newgrf_text.cpp +++ b/src/newgrf_text.cpp @@ -746,11 +746,11 @@ static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &s /** * Process NewGRF string control code instructions. * @param scc The string control code that has been read. - * @param str The string that we are reading from. + * @param consumer The string that we are reading from. * @param stack The TextRefStack. * @param[out] params Output parameters */ -static void ProcessNewGRFStringControlCode(char32_t scc, const char *&str, TextRefStack &stack, std::vector ¶ms) +static void ProcessNewGRFStringControlCode(char32_t scc, StringConsumer &consumer, TextRefStack &stack, std::vector ¶ms) { /* There is data on the NewGRF text stack, and we want to move them to OpenTTD's string stack. * After this call, a new call is made with `modify_parameters` set to false when the string is finally formatted. */ @@ -758,33 +758,35 @@ static void ProcessNewGRFStringControlCode(char32_t scc, const char *&str, TextR default: return; case SCC_PLURAL_LIST: - ++str; // plural form + consumer.SkipUint8(); // plural form [[fallthrough]]; case SCC_GENDER_LIST: { - ++str; // offset + consumer.SkipUint8(); // offset /* plural and gender choices cannot contain any string commands, so just skip the whole thing */ - uint num = static_cast(*str++); + uint num = consumer.ReadUint8(); uint total_len = 0; for (uint i = 0; i != num; i++) { - total_len += static_cast(*str++); + total_len += consumer.ReadUint8(); } - str += total_len; + consumer.Skip(total_len); break; } case SCC_SWITCH_CASE: { /* skip all cases and continue with default case */ - uint num = static_cast(*str++); + uint num = consumer.ReadUint8(); for (uint i = 0; i != num; i++) { - str += 3 + static_cast(str[1]) + (static_cast(str[2]) << 8); + consumer.SkipUint8(); + auto len = consumer.ReadUint16LE(); + consumer.Skip(len); } - str += 2; // length of default + consumer.SkipUint16LE(); // length of default break; } case SCC_GENDER_INDEX: case SCC_SET_CASE: - ++str; + consumer.SkipUint8(); break; case SCC_ARG_INDEX: @@ -824,7 +826,7 @@ static void ProcessNewGRFStringControlCode(char32_t scc, const char *&str, TextR case SCC_NEWGRF_DISCARD_WORD: stack.PopUnsignedWord(); break; case SCC_NEWGRF_ROTATE_TOP_4_WORDS: stack.RotateTop4Words(); break; - case SCC_NEWGRF_PUSH_WORD: stack.PushWord(Utf8Consume(&str)); break; + case SCC_NEWGRF_PUSH_WORD: stack.PushWord(consumer.ReadUtf8(0)); break; case SCC_NEWGRF_PRINT_WORD_CARGO_LONG: case SCC_NEWGRF_PRINT_WORD_CARGO_SHORT: @@ -834,7 +836,7 @@ static void ProcessNewGRFStringControlCode(char32_t scc, const char *&str, TextR break; case SCC_NEWGRF_STRINL: { - StringID stringid = Utf8Consume(str); + StringID stringid = consumer.ReadUtf8(STR_NULL); /* We also need to handle the substring's stack usage. */ HandleNewGRFStringControlCodes(GetStringPtr(stringid), stack, params); break; @@ -947,10 +949,10 @@ char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str) */ static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector ¶ms) { - for (const char *p = str.data(), *end = str.data() + str.size(); p < end; /* nothing */) { - char32_t scc; - p += Utf8Decode(&scc, p); - ProcessNewGRFStringControlCode(scc, p, stack, params); + StringConsumer consumer(str); + while (consumer.AnyBytesLeft()) { + char32_t scc = consumer.ReadUtf8(); + ProcessNewGRFStringControlCode(scc, consumer, stack, params); } } From f5ffd4789bca560dc7910b94a287c9dfd3513eb7 Mon Sep 17 00:00:00 2001 From: frosch Date: Fri, 28 Mar 2025 17:38:11 +0100 Subject: [PATCH 012/766] Codechange: Use StringConsumer in FormatString. --- src/newgrf_text.cpp | 6 +- src/strings.cpp | 146 +++++++++++++++++++---------------------- src/strings_internal.h | 3 +- 3 files changed, 73 insertions(+), 82 deletions(-) diff --git a/src/newgrf_text.cpp b/src/newgrf_text.cpp index a23d94a988..7d17af6b13 100644 --- a/src/newgrf_text.cpp +++ b/src/newgrf_text.cpp @@ -861,10 +861,10 @@ static void ProcessNewGRFStringControlCode(char32_t scc, StringConsumer &consume /** * Emit OpenTTD's internal string code for the different NewGRF string codes. * @param scc NewGRF string code. - * @param[in,out] str String iterator, moved forward if SCC_NEWGRF_PUSH_WORD is found. + * @param consumer String consumer, moved forward if SCC_NEWGRF_PUSH_WORD is found. * @returns String code to use. */ -char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str) +char32_t RemapNewGRFStringControlCode(char32_t scc, StringConsumer &consumer) { switch (scc) { default: @@ -932,7 +932,7 @@ char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str) /* These NewGRF string codes modify the NewGRF stack or otherwise do not map to OpenTTD string codes. */ case SCC_NEWGRF_PUSH_WORD: - Utf8Consume(str); + consumer.SkipUtf8(); return 0; case SCC_NEWGRF_DISCARD_WORD: diff --git a/src/strings.cpp b/src/strings.cpp index 438af68e8a..d0d65f9d40 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -644,7 +644,7 @@ static void FormatGenericCurrency(StringBuilder &builder, const CurrencySpec *sp * @param plural_form The plural form we want an index for. * @return The plural index for the given form. */ -static int DeterminePluralForm(int64_t count, int plural_form) +static int DeterminePluralForm(int64_t count, uint plural_form) { /* The absolute value determines plurality */ uint64_t n = abs(count); @@ -765,22 +765,25 @@ static int DeterminePluralForm(int64_t count, int plural_form) } } -static const char *ParseStringChoice(const char *b, uint form, StringBuilder &builder) +static void ParseStringChoice(StringConsumer &consumer, uint form, StringBuilder &builder) { /* {Length of each string} {each string} */ - uint n = (uint8_t)*b++; - size_t form_offset = 0, form_len = 0, total_len = 0; + uint n = consumer.ReadUint8(); + size_t form_pre = 0, form_len = 0, form_post = 0; for (uint i = 0; i != n; i++) { - uint len = (uint8_t)*b++; - if (i == form) { - form_offset = total_len; + uint len = consumer.ReadUint8(); + if (i < form) { + form_pre += len; + } else if (i > form) { + form_post += len; + } else { form_len = len; } - total_len += len; } - builder += std::string_view(b + form_offset, form_len); - return b + total_len; + consumer.Skip(form_pre); + builder += consumer.Read(form_len); + consumer.Skip(form_post); } /** Helper for unit conversion. */ @@ -982,67 +985,59 @@ uint ConvertDisplaySpeedToKmhishSpeed(uint speed, VehicleType type) /** * Decodes an encoded string during FormatString. - * @param str The buffer of the encoded string. + * @param consumer The encoded string. * @param game_script Set if decoding a GameScript-encoded string. This affects how string IDs are handled. * @param builder The string builder to write the string to. - * @returns Updated position position in input buffer. */ -static const char *DecodeEncodedString(const char *str, bool game_script, StringBuilder &builder) +static void DecodeEncodedString(StringConsumer &consumer, bool game_script, StringBuilder &builder) { std::vector sub_args; - char *p; - StringIndexInTab id(std::strtoul(str, &p, 16)); - if (*p != SCC_RECORD_SEPARATOR && *p != '\0') { - while (*p != '\0') p++; + StringIndexInTab id(consumer.ReadIntegerBase(16)); + if (consumer.AnyBytesLeft() && !consumer.ReadUtf8If(SCC_RECORD_SEPARATOR)) { + consumer.SkipAll(); builder += "(invalid SCC_ENCODED)"; - return p; + return; } if (game_script && id >= TAB_SIZE_GAMESCRIPT) { - while (*p != '\0') p++; + consumer.SkipAll(); builder += "(invalid StringID)"; - return p; + return; } - while (*p != '\0') { - /* The start of parameter. */ - const char *s = ++p; + while (consumer.AnyBytesLeft()) { + StringConsumer record(consumer.ReadUntilUtf8(SCC_RECORD_SEPARATOR, StringConsumer::SKIP_ONE_SEPARATOR)); - /* Find end of the parameter. */ - for (; *p != '\0' && *p != SCC_RECORD_SEPARATOR; ++p) {} - - if (s == p) { + if (!record.AnyBytesLeft()) { /* This is an empty parameter. */ sub_args.emplace_back(std::monostate{}); continue; } /* Get the parameter type. */ - char32_t parameter_type; - size_t len = Utf8Decode(¶meter_type, s); - s += len; - + char32_t parameter_type = record.ReadUtf8(); switch (parameter_type) { case SCC_ENCODED: { - uint64_t param = std::strtoull(s, &p, 16); + uint64_t param = record.ReadIntegerBase(16); if (param >= TAB_SIZE_GAMESCRIPT) { - while (*p != '\0') p++; builder += "(invalid sub-StringID)"; - return p; + return; } + assert(!record.AnyBytesLeft()); param = MakeStringID(TEXT_TAB_GAMESCRIPT_START, StringIndexInTab(param)); sub_args.emplace_back(param); break; } case SCC_ENCODED_NUMERIC: { - uint64_t param = std::strtoull(s, &p, 16); + uint64_t param = record.ReadIntegerBase(16); + assert(!record.AnyBytesLeft()); sub_args.emplace_back(param); break; } case SCC_ENCODED_STRING: { - sub_args.emplace_back(std::string(s, p - s)); + sub_args.emplace_back(std::string(record.Read(StringConsumer::npos))); break; } @@ -1055,8 +1050,6 @@ static const char *DecodeEncodedString(const char *str, bool game_script, String StringID stringid = game_script ? MakeStringID(TEXT_TAB_GAMESCRIPT_START, id) : StringID{id.base()}; GetStringWithArgs(builder, stringid, sub_args, true); - - return p; } /** @@ -1087,13 +1080,12 @@ static void FormatString(StringBuilder &builder, std::string_view str_arg, Strin } uint next_substr_case_index = 0; struct StrStackItem { - const char *str; - const char *end; + StringConsumer consumer; size_t first_param_offset; uint case_index; StrStackItem(std::string_view view, size_t first_param_offset, uint case_index) - : str(view.data()), end(view.data() + view.size()), first_param_offset(first_param_offset), case_index(case_index) + : consumer(view), first_param_offset(first_param_offset), case_index(case_index) {} }; std::stack> str_stack; @@ -1101,19 +1093,19 @@ static void FormatString(StringBuilder &builder, std::string_view str_arg, Strin for (;;) { try { - while (!str_stack.empty() && str_stack.top().str >= str_stack.top().end) { + while (!str_stack.empty() && !str_stack.top().consumer.AnyBytesLeft()) { str_stack.pop(); } if (str_stack.empty()) break; - const char *&str = str_stack.top().str; + StringConsumer &consumer = str_stack.top().consumer; const size_t ref_param_offset = str_stack.top().first_param_offset; const uint case_index = str_stack.top().case_index; - char32_t b = Utf8Consume(&str); + char32_t b = consumer.ReadUtf8(); assert(b != 0); if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) { /* We need to pass some stuff as it might be modified. */ - b = RemapNewGRFStringControlCode(b, &str); + b = RemapNewGRFStringControlCode(b, consumer); if (b == 0) continue; } @@ -1126,13 +1118,13 @@ static void FormatString(StringBuilder &builder, std::string_view str_arg, Strin switch (b) { case SCC_ENCODED: case SCC_ENCODED_INTERNAL: - str = DecodeEncodedString(str, b == SCC_ENCODED, builder); + DecodeEncodedString(consumer, b == SCC_ENCODED, builder); break; case SCC_NEWGRF_STRINL: { - StringID substr = Utf8Consume(&str); + StringID substr = consumer.ReadUtf8(STR_NULL); std::string_view ptr = GetStringPtr(substr); - str_stack.emplace(ptr, args.GetOffset(), next_substr_case_index); // this may invalidate "str" + str_stack.emplace(ptr, args.GetOffset(), next_substr_case_index); // this may invalidate "consumer" next_substr_case_index = 0; break; } @@ -1140,15 +1132,15 @@ static void FormatString(StringBuilder &builder, std::string_view str_arg, Strin case SCC_NEWGRF_PRINT_WORD_STRING_ID: { StringID substr = args.GetNextParameter(); std::string_view ptr = GetStringPtr(substr); - str_stack.emplace(ptr, args.GetOffset(), next_substr_case_index); // this may invalidate "str" + str_stack.emplace(ptr, args.GetOffset(), next_substr_case_index); // this may invalidate "consumer" next_substr_case_index = 0; break; } case SCC_GENDER_LIST: { // {G 0 Der Die Das} /* First read the meta data from the language file. */ - size_t offset = ref_param_offset + (uint8_t)*str++; - int gender = 0; + size_t offset = ref_param_offset + consumer.ReadUint8(); + uint8_t gender = 0; if (offset >= args.GetNumParameters()) { /* The offset may come from an external NewGRF, and be invalid. */ builder += "(invalid GENDER parameter)"; @@ -1170,37 +1162,38 @@ static void FormatString(StringBuilder &builder, std::string_view str_arg, Strin FormatString(tmp_builder, input, tmp_params); } - /* The gender is stored at the start of the formatted string. */ - const char *s = buffer.c_str(); - char32_t c = Utf8Consume(&s); - /* Does this string have a gender, if so, set it */ - if (c == SCC_GENDER_INDEX) gender = (uint8_t)s[0]; + /* The gender is stored at the start of the formatted string. + * Does this string have a gender, if so, set it. */ + StringConsumer gender_consumer(buffer); + if (gender_consumer.ReadUtf8If(SCC_GENDER_INDEX)) { + gender = gender_consumer.ReadUint8(); + } } - str = ParseStringChoice(str, gender, builder); + ParseStringChoice(consumer, gender, builder); break; } /* This sets up the gender for the string. * We just ignore this one. It's used in {G 0 Der Die Das} to determine the case. */ - case SCC_GENDER_INDEX: // {GENDER 0} + case SCC_GENDER_INDEX: { // {GENDER 0} + uint8_t gender = consumer.ReadUint8(); if (_scan_for_gender_data) { builder.PutUtf8(SCC_GENDER_INDEX); - builder.PutUint8(*str++); - } else { - str++; + builder.PutUint8(gender); } break; + } case SCC_PLURAL_LIST: { // {P} - int plural_form = *str++; // contains the plural form for this string - size_t offset = ref_param_offset + (uint8_t)*str++; + uint8_t plural_form = consumer.ReadUint8(); // contains the plural form for this string + size_t offset = ref_param_offset + consumer.ReadUint8(); const uint64_t *v = nullptr; /* The offset may come from an external NewGRF, and be invalid. */ if (offset < args.GetNumParameters()) { v = std::get_if(&args.GetParam(offset)); // contains the number that determines plural } if (v != nullptr) { - str = ParseStringChoice(str, DeterminePluralForm(static_cast(*v), plural_form), builder); + ParseStringChoice(consumer, DeterminePluralForm(static_cast(*v), plural_form), builder); } else { builder += "(invalid PLURAL parameter)"; } @@ -1208,38 +1201,35 @@ static void FormatString(StringBuilder &builder, std::string_view str_arg, Strin } case SCC_ARG_INDEX: { // Move argument pointer - args.SetOffset(ref_param_offset + (uint8_t)*str++); + args.SetOffset(ref_param_offset + consumer.ReadUint8()); break; } case SCC_SET_CASE: { // {SET_CASE} /* This is a pseudo command, it's outputted when someone does {STRING.ack} * The modifier is added to all subsequent GetStringWithArgs that accept the modifier. */ - next_substr_case_index = (uint8_t)*str++; + next_substr_case_index = consumer.ReadUint8(); break; } case SCC_SWITCH_CASE: { // {Used to implement case switching} /* <0x9E> * Each LEN is printed using 2 bytes in little endian order. */ - uint num = (uint8_t)*str++; + uint num = consumer.ReadUint8(); std::optional found; for (; num > 0; --num) { - uint8_t index = static_cast(str[0]); - uint16_t len = static_cast(str[1]) + (static_cast(str[2]) << 8); - str += 3; + uint8_t index = consumer.ReadUint8(); + uint16_t len = consumer.ReadUint16LE(); + auto case_str = consumer.Read(len); if (index == case_index) { /* Found the case */ - found.emplace(str, len); + found = case_str; } - str += len; } - uint16_t default_len = static_cast(str[0]) + (static_cast(str[1]) << 8); - str += 2; - if (!found.has_value()) found.emplace(str, default_len); - str += default_len; - assert(str <= str_stack.top().end); - str_stack.emplace(*found, ref_param_offset, case_index); // this may invalidate "str" + uint16_t default_len = consumer.ReadUint16LE(); + auto default_str = consumer.Read(default_len); + if (!found.has_value()) found = default_str; + str_stack.emplace(*found, ref_param_offset, case_index); // this may invalidate "consumer" break; } diff --git a/src/strings_internal.h b/src/strings_internal.h index 47e4329bad..c15a09a0c3 100644 --- a/src/strings_internal.h +++ b/src/strings_internal.h @@ -13,6 +13,7 @@ #include "strings_func.h" #include "string_func.h" #include "core/string_builder.hpp" +#include "core/string_consumer.hpp" class StringParameters { protected: @@ -214,6 +215,6 @@ void GenerateTownNameString(StringBuilder &builder, size_t lang, uint32_t seed); void GetTownName(StringBuilder &builder, const struct Town *t); void GRFTownNameGenerate(StringBuilder &builder, uint32_t grfid, uint16_t gen, uint32_t seed); -char32_t RemapNewGRFStringControlCode(char32_t scc, const char **str); +char32_t RemapNewGRFStringControlCode(char32_t scc, StringConsumer &consumer); #endif /* STRINGS_INTERNAL_H */ From dc21fae18e06b5f8f70553bdc942f30194152fe9 Mon Sep 17 00:00:00 2001 From: frosch Date: Thu, 3 Apr 2025 20:50:51 +0200 Subject: [PATCH 013/766] Codechange: Add InPlaceReplacement to couple StringConsumer and Builder on the same buffer. --- src/core/CMakeLists.txt | 2 + src/core/string_inplace.cpp | 63 ++++++++++++++++++++ src/core/string_inplace.hpp | 105 +++++++++++++++++++++++++++++++++ src/settingsgen/CMakeLists.txt | 1 + src/strgen/CMakeLists.txt | 1 + src/tests/CMakeLists.txt | 1 + src/tests/string_inplace.cpp | 57 ++++++++++++++++++ 7 files changed, 230 insertions(+) create mode 100644 src/core/string_inplace.cpp create mode 100644 src/core/string_inplace.hpp create mode 100644 src/tests/string_inplace.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d21ea8da92..448090585d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -27,6 +27,8 @@ add_files( string_builder.hpp string_consumer.cpp string_consumer.hpp + string_inplace.cpp + string_inplace.hpp strong_typedef_type.hpp utf8.cpp utf8.hpp diff --git a/src/core/string_inplace.cpp b/src/core/string_inplace.cpp new file mode 100644 index 0000000000..248fe8d130 --- /dev/null +++ b/src/core/string_inplace.cpp @@ -0,0 +1,63 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file string_inplace.cpp Inplace-replacement of textual and binary data. + */ + +#include "../stdafx.h" +#include "string_inplace.hpp" +#include "../safeguards.h" + +/** + * Check whether any unused bytes are left between the Builder and Consumer position. + */ +[[nodiscard]] bool InPlaceBuilder::AnyBytesUnused() const noexcept +{ + return this->consumer.GetBytesRead() > this->position; +} + +/** + * Get number of unused bytes left between the Builder and Consumer position. + */ +[[nodiscard]] InPlaceBuilder::size_type InPlaceBuilder::GetBytesUnused() const noexcept +{ + return this->consumer.GetBytesRead() - this->position; +} + +/** + * Append buffer. + */ +void InPlaceBuilder::PutBuffer(const char *str, size_type len) +{ + auto unused = this->GetBytesUnused(); + if (len > unused) NOT_REACHED(); + std::copy(str, str + len, this->dest.data() + this->position); + this->position += len; +} + +/** + * Create coupled Consumer+Builder pair. + * @param buffer Data to consume and replace. + * @note The lifetime of the buffer must exceed the lifetime of both the Consumer and the Builder. + */ +InPlaceReplacement::InPlaceReplacement(std::span buffer) + : consumer(buffer), builder(buffer, consumer) +{ +} + +InPlaceReplacement::InPlaceReplacement(const InPlaceReplacement &src) + : consumer(src.consumer), builder(src.builder, consumer) +{ +} + +InPlaceReplacement& InPlaceReplacement::operator=(const InPlaceReplacement &src) +{ + this->consumer = src.consumer; + this->builder.AssignBuffer(src.builder); + return *this; +} diff --git a/src/core/string_inplace.hpp b/src/core/string_inplace.hpp new file mode 100644 index 0000000000..855ae55896 --- /dev/null +++ b/src/core/string_inplace.hpp @@ -0,0 +1,105 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file string_inplace.hpp Inplace-replacement of textual and binary data. + */ + +#ifndef STRING_INPLACE_HPP +#define STRING_INPLACE_HPP + +#include "string_builder.hpp" +#include "string_consumer.hpp" + +/** + * Builder implementation for InPlaceReplacement. + */ +class InPlaceBuilder final : public BaseStringBuilder +{ + std::span dest; + size_type position = 0; + const StringConsumer &consumer; + + friend class InPlaceReplacement; + explicit InPlaceBuilder(std::span dest, const StringConsumer &consumer) : dest(dest), consumer(consumer) {} + InPlaceBuilder(const InPlaceBuilder &src, const StringConsumer &consumer) : dest(src.dest), position(src.position), consumer(consumer) {} + void AssignBuffer(const InPlaceBuilder &src) { this->dest = src.dest; this->position = src.position; } +public: + InPlaceBuilder(const InPlaceBuilder &) = delete; + InPlaceBuilder& operator=(const InPlaceBuilder &) = delete; + + /** + * Check whether any bytes have been written. + */ + [[nodiscard]] bool AnyBytesWritten() const noexcept { return this->position != 0; } + /** + * Get number of already written bytes. + */ + [[nodiscard]] size_type GetBytesWritten() const noexcept { return this->position; } + /** + * Get already written data. + */ + [[nodiscard]] std::string_view GetWrittenData() const noexcept { return {this->dest.data(), this->position}; } + + [[nodiscard]] bool AnyBytesUnused() const noexcept; + [[nodiscard]] size_type GetBytesUnused() const noexcept; + + using BaseStringBuilder::PutBuffer; + void PutBuffer(const char *str, size_type len) override; + + /** + * Implementation of std::back_insert_iterator for non-growing destination buffer. + */ + class back_insert_iterator { + InPlaceBuilder *parent = nullptr; + public: + using value_type = void; + using difference_type = void; + using iterator_category = std::output_iterator_tag; + using pointer = void; + using reference = void; + + back_insert_iterator(InPlaceBuilder &parent) : parent(&parent) {} + + back_insert_iterator &operator++() { return *this; } + back_insert_iterator operator++(int) { return *this; } + back_insert_iterator &operator*() { return *this; } + + back_insert_iterator &operator=(char value) + { + this->parent->PutChar(value); + return *this; + } + }; + /** + * Create a back-insert-iterator. + */ + back_insert_iterator back_inserter() + { + return back_insert_iterator(*this); + } +}; + +/** + * Compose data into a fixed size buffer, which is consumed at the same time. + * - The Consumer reads data from a buffer. + * - The Builder writes data to the buffer, replacing already consumed data. + * - The Builder asserts, if it overtakes the consumer. + */ +class InPlaceReplacement +{ +public: + StringConsumer consumer; ///< Consumer from shared buffer + InPlaceBuilder builder; ///< Builder into shared buffer + +public: + InPlaceReplacement(std::span buffer); + InPlaceReplacement(const InPlaceReplacement &src); + InPlaceReplacement& operator=(const InPlaceReplacement &src); +}; + +#endif /* STRING_INPLACE_HPP */ diff --git a/src/settingsgen/CMakeLists.txt b/src/settingsgen/CMakeLists.txt index 220ffb972b..7f7c1854b1 100644 --- a/src/settingsgen/CMakeLists.txt +++ b/src/settingsgen/CMakeLists.txt @@ -12,6 +12,7 @@ if (NOT HOST_BINARY_DIR) ../string.cpp ../core/string_builder.cpp ../core/string_consumer.cpp + ../core/string_inplace.cpp ../core/utf8.cpp ) add_definitions(-DSETTINGSGEN) diff --git a/src/strgen/CMakeLists.txt b/src/strgen/CMakeLists.txt index 40eb11448f..64d1905089 100644 --- a/src/strgen/CMakeLists.txt +++ b/src/strgen/CMakeLists.txt @@ -14,6 +14,7 @@ if (NOT HOST_BINARY_DIR) ../string.cpp ../core/string_builder.cpp ../core/string_consumer.cpp + ../core/string_inplace.cpp ../core/utf8.cpp ) add_definitions(-DSTRGEN) diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 29f97ad205..94579147fa 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -9,6 +9,7 @@ add_test_files( mock_spritecache.h string_builder.cpp string_consumer.cpp + string_inplace.cpp string_func.cpp test_main.cpp test_network_crypto.cpp diff --git a/src/tests/string_inplace.cpp b/src/tests/string_inplace.cpp new file mode 100644 index 0000000000..275c2c1fd3 --- /dev/null +++ b/src/tests/string_inplace.cpp @@ -0,0 +1,57 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file string_inplace.cpp Test functionality from core/string_inplace. */ + +#include "../stdafx.h" +#include "../3rdparty/catch2/catch.hpp" +#include "../core/string_inplace.hpp" +#include "../safeguards.h" + +using namespace std::literals; + +TEST_CASE("InPlaceReplacement") +{ + std::array buffer{1, 2, 3, 4}; + InPlaceReplacement inplace(buffer); + + CHECK(!inplace.builder.AnyBytesWritten()); + CHECK(inplace.builder.GetBytesWritten() == 0); + CHECK(inplace.builder.GetWrittenData() == ""sv); + CHECK(!inplace.builder.AnyBytesUnused()); + CHECK(inplace.builder.GetBytesUnused() == 0); + CHECK(!inplace.consumer.AnyBytesRead()); + CHECK(inplace.consumer.GetBytesRead() == 0); + CHECK(inplace.consumer.AnyBytesLeft()); + CHECK(inplace.consumer.GetBytesLeft() == 4); + + CHECK(inplace.consumer.ReadUint16LE() == 0x201); + + CHECK(inplace.builder.GetBytesWritten() == 0); + CHECK(inplace.builder.GetBytesUnused() == 2); + CHECK(inplace.consumer.GetBytesRead() == 2); + CHECK(inplace.consumer.GetBytesLeft() == 2); + + inplace.builder.PutUint8(11); + + CHECK(inplace.builder.GetBytesWritten() == 1); + CHECK(inplace.builder.GetBytesUnused() == 1); + CHECK(inplace.consumer.GetBytesRead() == 2); + CHECK(inplace.consumer.GetBytesLeft() == 2); + + inplace.builder.PutUint8(12); + + CHECK(inplace.builder.GetBytesWritten() == 2); + CHECK(inplace.builder.GetBytesUnused() == 0); + CHECK(inplace.consumer.GetBytesRead() == 2); + CHECK(inplace.consumer.GetBytesLeft() == 2); + + CHECK(buffer[0] == 11); + CHECK(buffer[1] == 12); + CHECK(buffer[2] == 3); + CHECK(buffer[3] == 4); +} From b81a35ea89f02248b187268e00449e1f668bbd9b Mon Sep 17 00:00:00 2001 From: frosch Date: Tue, 1 Apr 2025 19:14:30 +0200 Subject: [PATCH 014/766] Codechange: Use StringConsumer and Builder in StrMakeValid and StrValid. --- src/string.cpp | 137 ++++++++++++++++--------------------------------- 1 file changed, 43 insertions(+), 94 deletions(-) diff --git a/src/string.cpp b/src/string.cpp index 2fadbb2e30..4032a7f34f 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -14,6 +14,7 @@ #include "string_func.h" #include "string_base.h" #include "core/utf8.hpp" +#include "core/string_inplace.hpp" #include "table/control_codes.h" @@ -108,76 +109,40 @@ static bool IsSccEncodedCode(char32_t c) } /** - * Copies the valid (UTF-8) characters from \c str up to \c last to the \c dst. + * Copies the valid (UTF-8) characters from \c consumer to the \c builder. * Depending on the \c settings invalid characters can be replaced with a * question mark, as well as determining what characters are deemed invalid. * - * It is allowed for \c dst to be the same as \c src, in which case the string - * is make valid in place. - * @param dst The destination to write to. - * @param str The string to validate. - * @param last The last valid character of str. + * @param builder The destination to write to. + * @param consumer The string to validate. * @param settings The settings for the string validation. */ -template -static void StrMakeValid(T &dst, const char *str, const char *last, StringValidationSettings settings) +template +static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings) { /* Assume the ABSOLUTE WORST to be in str as it comes from the outside. */ - - while (str <= last && *str != '\0') { - size_t len = Utf8EncodedCharLen(*str); - char32_t c; - /* If the first byte does not look like the first byte of an encoded - * character, i.e. encoded length is 0, then this byte is definitely bad - * and it should be skipped. - * When the first byte looks like the first byte of an encoded character, - * then the remaining bytes in the string are checked whether the whole - * encoded character can be there. If that is not the case, this byte is - * skipped. - * Finally we attempt to decode the encoded character, which does certain - * extra validations to see whether the correct number of bytes were used - * to encode the character. If that is not the case, the byte is probably - * invalid and it is skipped. We could emit a question mark, but then the - * logic below cannot just copy bytes, it would need to re-encode the - * decoded characters as the length in bytes may have changed. - * - * The goals here is to get as much valid Utf8 encoded characters from the - * source string to the destination string. - * - * Note: a multi-byte encoded termination ('\0') will trigger the encoded - * char length and the decoded length to differ, so it will be ignored as - * invalid character data. If it were to reach the termination, then we - * would also reach the "last" byte of the string and a normal '\0' - * termination will be placed after it. - */ - if (len == 0 || str + len > last + 1 || len != Utf8Decode(&c, str)) { + while (consumer.AnyBytesLeft()) { + auto c = consumer.TryReadUtf8(); + if (!c.has_value()) { /* Maybe the next byte is still a valid character? */ - str++; + consumer.Skip(1); continue; } + if (*c == 0) break; - if ((IsPrintable(c) && (c < SCC_SPRITE_START || c > SCC_SPRITE_END)) || (settings.Test(StringValidationSetting::AllowControlCode) && IsSccEncodedCode(c))) { - /* Copy the character back. Even if dst is current the same as str - * (i.e. no characters have been changed) this is quicker than - * moving the pointers ahead by len */ - do { - *dst++ = *str++; - } while (--len != 0); - } else if (settings.Test(StringValidationSetting::AllowNewline) && c == '\n') { - *dst++ = *str++; - } else { - if (settings.Test(StringValidationSetting::AllowNewline) && c == '\r' && str[1] == '\n') { - str += len; - continue; - } - str += len; - if (settings.Test(StringValidationSetting::ReplaceTabCrNlWithSpace) && (c == '\r' || c == '\n' || c == '\t')) { - /* Replace the tab, carriage return or newline with a space. */ - *dst++ = ' '; - } else if (settings.Test(StringValidationSetting::ReplaceWithQuestionMark)) { - /* Replace the undesirable character with a question mark */ - *dst++ = '?'; - } + if ((IsPrintable(*c) && (*c < SCC_SPRITE_START || *c > SCC_SPRITE_END)) || + (settings.Test(StringValidationSetting::AllowControlCode) && IsSccEncodedCode(*c)) || + (settings.Test(StringValidationSetting::AllowNewline) && *c == '\n')) { + builder.PutUtf8(*c); + } else if (settings.Test(StringValidationSetting::AllowNewline) && *c == '\r' && consumer.PeekCharIf('\n')) { + /* Skip \r, if followed by \n */ + /* continue */ + } else if (settings.Test(StringValidationSetting::ReplaceTabCrNlWithSpace) && (*c == '\r' || *c == '\n' || *c == '\t')) { + /* Replace the tab, carriage return or newline with a space. */ + builder.PutChar(' '); + } else if (settings.Test(StringValidationSetting::ReplaceWithQuestionMark)) { + /* Replace the undesirable character with a question mark */ + builder.PutChar('?'); } } @@ -193,9 +158,10 @@ static void StrMakeValid(T &dst, const char *str, const char *last, StringValida */ void StrMakeValidInPlace(char *str, StringValidationSettings settings) { - char *dst = str; - StrMakeValid(dst, str, str + strlen(str), settings); - *dst = '\0'; + InPlaceReplacement inplace(std::span(str, strlen(str))); + StrMakeValid(inplace.builder, inplace.consumer, settings); + /* Add NUL terminator, if we ended up with less bytes than before */ + if (inplace.builder.AnyBytesUnused()) inplace.builder.PutChar('\0'); } /** @@ -209,11 +175,9 @@ void StrMakeValidInPlace(std::string &str, StringValidationSettings settings) { if (str.empty()) return; - char *buf = str.data(); - char *last = buf + str.size() - 1; - char *dst = buf; - StrMakeValid(dst, buf, last, settings); - str.erase(dst - buf, std::string::npos); + InPlaceReplacement inplace(std::span(str.data(), str.size())); + StrMakeValid(inplace.builder, inplace.consumer, settings); + str.erase(inplace.builder.GetBytesWritten(), std::string::npos); } /** @@ -225,16 +189,11 @@ void StrMakeValidInPlace(std::string &str, StringValidationSettings settings) */ std::string StrMakeValid(std::string_view str, StringValidationSettings settings) { - if (str.empty()) return {}; - - auto buf = str.data(); - auto last = buf + str.size() - 1; - - std::ostringstream dst; - std::ostreambuf_iterator dst_iter(dst); - StrMakeValid(dst_iter, buf, last, settings); - - return dst.str(); + std::string result; + StringBuilder builder(result); + StringConsumer consumer(str); + StrMakeValid(builder, consumer, settings); + return result; } /** @@ -248,27 +207,17 @@ std::string StrMakeValid(std::string_view str, StringValidationSettings settings bool StrValid(std::span str) { /* Assume the ABSOLUTE WORST to be in str as it comes from the outside. */ - auto it = std::begin(str); - auto last = std::prev(std::end(str)); - - while (it <= last && *it != '\0') { - size_t len = Utf8EncodedCharLen(*it); - /* Encoded length is 0 if the character isn't known. - * The length check is needed to prevent Utf8Decode to read - * over the terminating '\0' if that happens to be placed - * within the encoding of an UTF8 character. */ - if (len == 0 || it + len > last) return false; - - char32_t c; - len = Utf8Decode(&c, &*it); - if (!IsPrintable(c) || (c >= SCC_SPRITE_START && c <= SCC_SPRITE_END)) { + StringConsumer consumer(str); + while (consumer.AnyBytesLeft()) { + auto c = consumer.TryReadUtf8(); + if (!c.has_value()) return false; // invalid codepoint + if (*c == 0) return true; // NUL termination + if (!IsPrintable(*c) || (*c >= SCC_SPRITE_START && *c <= SCC_SPRITE_END)) { return false; } - - it += len; } - return *it == '\0'; + return false; // missing NUL termination } /** From b27fd83ff1aea2c1586ed6310cdcfd3e513ebd24 Mon Sep 17 00:00:00 2001 From: frosch Date: Mon, 31 Mar 2025 17:30:54 +0200 Subject: [PATCH 015/766] Codechange: Parse translation strings using StringConsumer. --- src/game/game_text.cpp | 2 +- src/strgen/strgen.cpp | 8 +- src/strgen/strgen.h | 5 +- src/strgen/strgen_base.cpp | 174 ++++++++++++++----------------------- src/table/strgen_tables.h | 8 +- 5 files changed, 75 insertions(+), 122 deletions(-) diff --git a/src/game/game_text.cpp b/src/game/game_text.cpp index 5a7aa0365c..07182ad712 100644 --- a/src/game/game_text.cpp +++ b/src/game/game_text.cpp @@ -267,7 +267,7 @@ static void ExtractStringParams(const StringData &data, StringParamsList ¶ms if (ls != nullptr) { StringParams ¶m = params.emplace_back(); - ParsedCommandStruct pcs = ExtractCommandString(ls->english.c_str(), false); + ParsedCommandStruct pcs = ExtractCommandString(ls->english, false); for (auto it = pcs.consuming_commands.begin(); it != pcs.consuming_commands.end(); it++) { if (*it == nullptr) { diff --git a/src/strgen/strgen.cpp b/src/strgen/strgen.cpp index c86c0c1029..b54da8a7b9 100644 --- a/src/strgen/strgen.cpp +++ b/src/strgen/strgen.cpp @@ -146,10 +146,10 @@ void FileStringReader::HandlePragma(char *str, LanguagePackHeader &lang) lang.newgrflangid = static_cast(langid); } else if (!memcmp(str, "gender ", 7)) { if (this->master) FatalError("Genders are not allowed in the base translation."); - const char *buf = str + 7; + StringConsumer consumer(std::string_view(str + 7)); for (;;) { - auto s = ParseWord(&buf); + auto s = ParseWord(consumer); if (!s.has_value()) break; if (lang.num_genders >= MAX_NUM_GENDERS) FatalError("Too many genders, max {}", MAX_NUM_GENDERS); @@ -158,10 +158,10 @@ void FileStringReader::HandlePragma(char *str, LanguagePackHeader &lang) } } else if (!memcmp(str, "case ", 5)) { if (this->master) FatalError("Cases are not allowed in the base translation."); - const char *buf = str + 5; + StringConsumer consumer(std::string_view(str + 5)); for (;;) { - auto s = ParseWord(&buf); + auto s = ParseWord(consumer); if (!s.has_value()) break; if (lang.num_cases >= MAX_NUM_CASES) FatalError("Too many cases, max {}", MAX_NUM_CASES); diff --git a/src/strgen/strgen.h b/src/strgen/strgen.h index 78a6c8def7..18e007f71e 100644 --- a/src/strgen/strgen.h +++ b/src/strgen/strgen.h @@ -10,6 +10,7 @@ #ifndef STRGEN_H #define STRGEN_H +#include "../core/string_consumer.hpp" #include "../language.h" #include "../3rdparty/fmt/format.h" @@ -144,7 +145,7 @@ struct ParsedCommandStruct { }; const CmdStruct *TranslateCmdForCompare(const CmdStruct *a); -ParsedCommandStruct ExtractCommandString(const char *s, bool warnings); +ParsedCommandStruct ExtractCommandString(std::string_view s, bool warnings); void StrgenWarningI(const std::string &msg); void StrgenErrorI(const std::string &msg); @@ -152,7 +153,7 @@ void StrgenErrorI(const std::string &msg); #define StrgenWarning(format_string, ...) StrgenWarningI(fmt::format(FMT_STRING(format_string) __VA_OPT__(,) __VA_ARGS__)) #define StrgenError(format_string, ...) StrgenErrorI(fmt::format(FMT_STRING(format_string) __VA_OPT__(,) __VA_ARGS__)) #define StrgenFatal(format_string, ...) StrgenFatalI(fmt::format(FMT_STRING(format_string) __VA_OPT__(,) __VA_ARGS__)) -std::optional ParseWord(const char **buf); +std::optional ParseWord(StringConsumer &consumer); /** Global state shared between strgen.cpp, game_text.cpp and strgen_base.cpp */ struct StrgenState { diff --git a/src/strgen/strgen_base.cpp b/src/strgen/strgen_base.cpp index 534003ec16..aa831c1945 100644 --- a/src/strgen/strgen_base.cpp +++ b/src/strgen/strgen_base.cpp @@ -33,7 +33,7 @@ struct ParsedCommandString { std::optional argno; std::optional casei; }; -static ParsedCommandString ParseCommandString(const char **str); +static ParsedCommandString ParseCommandString(StringConsumer &consumer); static size_t TranslateArgumentIdx(size_t arg, size_t offset = 0); /** @@ -139,9 +139,9 @@ uint32_t StringData::Version() const hash = (hash & 1 ? hash >> 1 ^ 0xDEADBEEF : hash >> 1); hash = VersionHashStr(hash, ls->name); - const char *s = ls->english.c_str(); + StringConsumer consumer(ls->english); ParsedCommandString cs; - while ((cs = ParseCommandString(&s)).cmd != nullptr) { + while ((cs = ParseCommandString(consumer)).cmd != nullptr) { if (cs.cmd->flags.Test(CmdFlag::DontCount)) continue; hash ^= (cs.cmd - _cmd_structs) * 0x1234567; @@ -188,62 +188,41 @@ static size_t Utf8Validate(const char *s) return 0; } -void EmitSingleChar(StringBuilder &builder, const char *buf, char32_t value) +void EmitSingleChar(StringBuilder &builder, std::string_view param, char32_t value) { - if (*buf != '\0') StrgenWarning("Ignoring trailing letters in command"); + if (!param.empty()) StrgenWarning("Ignoring trailing letters in command"); builder.PutUtf8(value); } /* The plural specifier looks like * {NUM} {PLURAL passenger passengers} then it picks either passenger/passengers depending on the count in NUM */ -static std::pair, std::optional> ParseRelNum(const char **buf) +static std::pair, std::optional> ParseRelNum(StringConsumer &consumer) { - const char *s = *buf; - char *end; - - while (*s == ' ' || *s == '\t') s++; - size_t v = std::strtoul(s, &end, 0); - if (end == s) return {}; + consumer.SkipUntilCharNotIn(StringConsumer::WHITESPACE_NO_NEWLINE); + std::optional v = consumer.TryReadIntegerBase(10); std::optional offset; - if (*end == ':') { + if (v.has_value() && consumer.ReadCharIf(':')) { /* Take the Nth within */ - s = end + 1; - offset = std::strtoul(s, &end, 0); - if (end == s) return {}; + offset = consumer.TryReadIntegerBase(10); + if (!offset.has_value()) StrgenFatal("Expected number for substring parameter"); } - *buf = end; return {v, offset}; } /* Parse out the next word, or nullptr */ -std::optional ParseWord(const char **buf) +std::optional ParseWord(StringConsumer &consumer) { - const char *s = *buf; + consumer.SkipUntilCharNotIn(StringConsumer::WHITESPACE_NO_NEWLINE); + if (!consumer.AnyBytesLeft()) return {}; - while (*s == ' ' || *s == '\t') s++; - if (*s == '\0') return {}; - - if (*s == '"') { - const char *begin = ++s; + if (consumer.ReadCharIf('"')) { /* parse until next " or NUL */ - for (;;) { - if (*s == '\0') StrgenFatal("Unterminated quotes"); - if (*s == '"') { - *buf = s + 1; - return std::string_view(begin, s - begin); - } - s++; - } + auto result = consumer.ReadUntilChar('"', StringConsumer::KEEP_SEPARATOR); + if (!consumer.ReadCharIf('"')) StrgenFatal("Unterminated quotes"); + return result; } else { /* proceed until whitespace or NUL */ - const char *begin = s; - for (;;) { - if (*s == '\0' || *s == ' ' || *s == '\t') { - *buf = s; - return std::string_view(begin, s - begin); - } - s++; - } + return consumer.ReadUntilCharIn(StringConsumer::WHITESPACE_NO_NEWLINE); } } @@ -262,10 +241,12 @@ static void EmitWordList(StringBuilder &builder, const std::vector } } -void EmitPlural(StringBuilder &builder, const char *buf, char32_t) +void EmitPlural(StringBuilder &builder, std::string_view param, char32_t) { + StringConsumer consumer(param); + /* Parse out the number, if one exists. Otherwise default to prev arg. */ - auto [argidx, offset] = ParseRelNum(&buf); + auto [argidx, offset] = ParseRelNum(consumer); if (!argidx.has_value()) { if (_cur_argidx == 0) StrgenFatal("Plural choice needs positional reference"); argidx = _cur_argidx - 1; @@ -283,7 +264,7 @@ void EmitPlural(StringBuilder &builder, const char *buf, char32_t) /* Parse each string */ std::vector words; for (;;) { - auto word = ParseWord(&buf); + auto word = ParseWord(consumer); if (!word.has_value()) break; words.emplace_back(*word); } @@ -315,14 +296,14 @@ void EmitPlural(StringBuilder &builder, const char *buf, char32_t) EmitWordList(builder, words); } -void EmitGender(StringBuilder &builder, const char *buf, char32_t) +void EmitGender(StringBuilder &builder, std::string_view param, char32_t) { - if (buf[0] == '=') { - buf++; - + StringConsumer consumer(param); + if (consumer.ReadCharIf('=')) { /* This is a {G=DER} command */ - auto nw = _strgen.lang.GetGenderIndex(buf); - if (nw >= MAX_NUM_GENDERS) StrgenFatal("G argument '{}' invalid", buf); + auto gender = consumer.Read(StringConsumer::npos); + auto nw = _strgen.lang.GetGenderIndex(gender); + if (nw >= MAX_NUM_GENDERS) StrgenFatal("G argument '{}' invalid", gender); /* now nw contains the gender index */ builder.PutUtf8(SCC_GENDER_INDEX); @@ -330,7 +311,7 @@ void EmitGender(StringBuilder &builder, const char *buf, char32_t) } else { /* This is a {G 0 foo bar two} command. * If no relative number exists, default to +0 */ - auto [argidx, offset] = ParseRelNum(&buf); + auto [argidx, offset] = ParseRelNum(consumer); if (!argidx.has_value()) argidx = _cur_argidx; if (!offset.has_value()) offset = 0; @@ -341,7 +322,7 @@ void EmitGender(StringBuilder &builder, const char *buf, char32_t) std::vector words; for (;;) { - auto word = ParseWord(&buf); + auto word = ParseWord(consumer); if (!word.has_value()) break; words.emplace_back(*word); } @@ -370,74 +351,45 @@ static uint8_t ResolveCaseName(std::string_view str) } /* returns cmd == nullptr on eof */ -static ParsedCommandString ParseCommandString(const char **str) +static ParsedCommandString ParseCommandString(StringConsumer &consumer) { ParsedCommandString result; - const char *s = *str; /* Scan to the next command, exit if there's no next command. */ - for (; *s != '{'; s++) { - if (*s == '\0') return {}; - } - s++; // Skip past the { + consumer.SkipUntilChar('{', StringConsumer::KEEP_SEPARATOR); + if (!consumer.ReadCharIf('{')) return {}; - if (*s >= '0' && *s <= '9') { - char *end; - - result.argno = std::strtoul(s, &end, 0); - if (*end != ':') StrgenFatal("missing arg #"); - s = end + 1; + if (auto argno = consumer.TryReadIntegerBase(10); argno.has_value()) { + result.argno = argno; + if (!consumer.ReadCharIf(':')) StrgenFatal("missing arg #"); } /* parse command name */ - const char *start = s; - char c; - do { - c = *s++; - } while (c != '}' && c != ' ' && c != '=' && c != '.' && c != 0); - - std::string_view command(start, s - start - 1); + auto command = consumer.ReadUntilCharIn("} =."); result.cmd = FindCmd(command); if (result.cmd == nullptr) { StrgenError("Undefined command '{}'", command); return {}; } - if (c == '.') { - const char *casep = s; - + /* parse case */ + if (consumer.ReadCharIf('.')) { if (!result.cmd->flags.Test(CmdFlag::Case)) { StrgenFatal("Command '{}' can't have a case", result.cmd->cmd); } - do { - c = *s++; - } while (c != '}' && c != ' ' && c != '\0'); - result.casei = ResolveCaseName(std::string_view(casep, s - casep - 1)); + auto casep = consumer.ReadUntilCharIn("} "); + result.casei = ResolveCaseName(casep); } - if (c == '\0') { - StrgenError("Missing }} from command '{}'", start); + /* parse params */ + result.param = consumer.ReadUntilChar('}', StringConsumer::KEEP_SEPARATOR); + + if (!consumer.ReadCharIf('}')) { + StrgenError("Missing }} from command '{}'", result.cmd->cmd); return {}; } - if (c != '}') { - if (c == '=') s--; - /* copy params */ - start = s; - for (;;) { - c = *s++; - if (c == '}') break; - if (c == '\0') { - StrgenError("Missing }} from command '{}'", start); - return {}; - } - result.param += c; - } - } - - *str = s; - return result; } @@ -453,14 +405,15 @@ StringReader::StringReader(StringData &data, const std::string &file, bool maste { } -ParsedCommandStruct ExtractCommandString(const char *s, bool) +ParsedCommandStruct ExtractCommandString(std::string_view s, bool) { ParsedCommandStruct p; + StringConsumer consumer(s); size_t argidx = 0; for (;;) { /* read until next command from a. */ - auto cs = ParseCommandString(&s); + auto cs = ParseCommandString(consumer); if (cs.cmd == nullptr) break; @@ -499,7 +452,7 @@ const CmdStruct *TranslateCmdForCompare(const CmdStruct *a) return a; } -static bool CheckCommandsMatch(const char *a, const char *b, const char *name) +static bool CheckCommandsMatch(std::string_view a, std::string_view b, std::string_view name) { /* If we're not translating, i.e. we're compiling the base language, * it is pointless to do all these checks as it'll always be correct. @@ -629,7 +582,7 @@ void StringReader::HandleString(char *str) } /* make sure that the commands match */ - if (!CheckCommandsMatch(s, ent->english.c_str(), str)) return; + if (!CheckCommandsMatch(s, ent->english, str)) return; if (casep != nullptr) { ent->translated_cases.emplace_back(ResolveCaseName(casep), s); @@ -735,20 +688,19 @@ static void PutArgidxCommand(StringBuilder &builder) builder.PutUint8(static_cast(TranslateArgumentIdx(_cur_argidx))); } -static std::string PutCommandString(const char *str) +static std::string PutCommandString(std::string_view str) { std::string result; StringBuilder builder(result); + StringConsumer consumer(str); _cur_argidx = 0; - while (*str != '\0') { + for (;;) { /* Process characters as they are until we encounter a { */ - if (*str != '{') { - builder.PutChar(*str++); - continue; - } + builder.Put(consumer.ReadUntilChar('{', StringConsumer::KEEP_SEPARATOR)); + if (!consumer.AnyBytesLeft()) break; - auto cs = ParseCommandString(&str); + auto cs = ParseCommandString(consumer); auto *cmd = cs.cmd; if (cmd == nullptr) break; @@ -772,7 +724,7 @@ static std::string PutCommandString(const char *str) } } - cmd->proc(builder, cs.param.c_str(), cmd->value); + cmd->proc(builder, cs.param, cmd->value); } return result; } @@ -848,7 +800,7 @@ void LanguageWriter::WriteLang(const StringData &data) } /* Extract the strings and stuff from the english command string */ - _cur_pcs = ExtractCommandString(ls->english.c_str(), false); + _cur_pcs = ExtractCommandString(ls->english, false); _translated = !ls->translated_cases.empty() || !ls->translated.empty(); const std::string &cmdp = _translated ? ls->translated : ls->english; @@ -863,7 +815,7 @@ void LanguageWriter::WriteLang(const StringData &data) /* Write each case */ for (const Case &c : ls->translated_cases) { - auto case_str = PutCommandString(c.string.c_str()); + auto case_str = PutCommandString(c.string); builder.PutUint8(c.caseidx); builder.PutUint16LE(static_cast(case_str.size())); builder.Put(case_str); @@ -871,7 +823,7 @@ void LanguageWriter::WriteLang(const StringData &data) } std::string def_str; - if (!cmdp.empty()) def_str = PutCommandString(cmdp.c_str()); + if (!cmdp.empty()) def_str = PutCommandString(cmdp); if (!ls->translated_cases.empty()) { builder.PutUint16LE(static_cast(def_str.size())); } diff --git a/src/table/strgen_tables.h b/src/table/strgen_tables.h index bd57fa795d..2791e1982a 100644 --- a/src/table/strgen_tables.h +++ b/src/table/strgen_tables.h @@ -17,7 +17,7 @@ enum class CmdFlag : uint8_t { using CmdFlags = EnumBitSet; class StringBuilder; -typedef void (*ParseCmdProc)(StringBuilder &builder, const char *buf, char32_t value); +typedef void (*ParseCmdProc)(StringBuilder &builder, std::string_view param, char32_t value); struct CmdStruct { std::string_view cmd; @@ -28,9 +28,9 @@ struct CmdStruct { CmdFlags flags; }; -extern void EmitSingleChar(StringBuilder &builder, const char *buf, char32_t value); -extern void EmitPlural(StringBuilder &builder, const char *buf, char32_t value); -extern void EmitGender(StringBuilder &builder, const char *buf, char32_t value); +extern void EmitSingleChar(StringBuilder &builder, std::string_view param, char32_t value); +extern void EmitPlural(StringBuilder &builder, std::string_view param, char32_t value); +extern void EmitGender(StringBuilder &builder, std::string_view param, char32_t value); static const CmdStruct _cmd_structs[] = { /* Font size */ From bf8a241f691658257c435df5f40cfb8e282850ba Mon Sep 17 00:00:00 2001 From: frosch Date: Mon, 31 Mar 2025 17:32:05 +0200 Subject: [PATCH 016/766] Codechange: Parse translation files using StringConsumer. --- src/strgen/strgen.cpp | 71 +++++++++--------- src/strgen/strgen.h | 8 +-- src/strgen/strgen_base.cpp | 143 +++++++++++++++---------------------- 3 files changed, 96 insertions(+), 126 deletions(-) diff --git a/src/strgen/strgen.cpp b/src/strgen/strgen.cpp index b54da8a7b9..a926f0d1f9 100644 --- a/src/strgen/strgen.cpp +++ b/src/strgen/strgen.cpp @@ -91,7 +91,7 @@ struct FileStringReader : StringReader { return result; } - void HandlePragma(char *str, LanguagePackHeader &lang) override; + void HandlePragma(std::string_view str, LanguagePackHeader &lang) override; void ParseFile() override { @@ -103,51 +103,50 @@ struct FileStringReader : StringReader { } }; -void FileStringReader::HandlePragma(char *str, LanguagePackHeader &lang) +void FileStringReader::HandlePragma(std::string_view str, LanguagePackHeader &lang) { - if (!memcmp(str, "id ", 3)) { - this->data.next_string_id = std::strtoul(str + 3, nullptr, 0); - } else if (!memcmp(str, "name ", 5)) { - strecpy(lang.name, str + 5); - } else if (!memcmp(str, "ownname ", 8)) { - strecpy(lang.own_name, str + 8); - } else if (!memcmp(str, "isocode ", 8)) { - strecpy(lang.isocode, str + 8); - } else if (!memcmp(str, "textdir ", 8)) { - if (!memcmp(str + 8, "ltr", 3)) { + StringConsumer consumer(str); + auto name = consumer.ReadUntilChar(' ', StringConsumer::SKIP_ALL_SEPARATORS); + if (name == "id") { + this->data.next_string_id = consumer.ReadIntegerBase(0); + } else if (name == "name") { + strecpy(lang.name, consumer.Read(StringConsumer::npos)); + } else if (name == "ownname") { + strecpy(lang.own_name, consumer.Read(StringConsumer::npos)); + } else if (name == "isocode") { + strecpy(lang.isocode, consumer.Read(StringConsumer::npos)); + } else if (name == "textdir") { + auto dir = consumer.Read(StringConsumer::npos); + if (dir == "ltr") { lang.text_dir = TD_LTR; - } else if (!memcmp(str + 8, "rtl", 3)) { + } else if (dir == "rtl") { lang.text_dir = TD_RTL; } else { - FatalError("Invalid textdir {}", str + 8); + FatalError("Invalid textdir {}", dir); } - } else if (!memcmp(str, "digitsep ", 9)) { - str += 9; - strecpy(lang.digit_group_separator, strcmp(str, "{NBSP}") == 0 ? NBSP : str); - } else if (!memcmp(str, "digitsepcur ", 12)) { - str += 12; - strecpy(lang.digit_group_separator_currency, strcmp(str, "{NBSP}") == 0 ? NBSP : str); - } else if (!memcmp(str, "decimalsep ", 11)) { - str += 11; - strecpy(lang.digit_decimal_separator, strcmp(str, "{NBSP}") == 0 ? NBSP : str); - } else if (!memcmp(str, "winlangid ", 10)) { - const char *buf = str + 10; - long langid = std::strtol(buf, nullptr, 16); + } else if (name == "digitsep") { + auto sep = consumer.Read(StringConsumer::npos); + strecpy(lang.digit_group_separator, sep == "{NBSP}" ? NBSP : sep); + } else if (name == "digitsepcur") { + auto sep = consumer.Read(StringConsumer::npos); + strecpy(lang.digit_group_separator_currency, sep == "{NBSP}" ? NBSP : sep); + } else if (name == "decimalsep") { + auto sep = consumer.Read(StringConsumer::npos); + strecpy(lang.digit_decimal_separator, sep == "{NBSP}" ? NBSP : sep); + } else if (name == "winlangid") { + auto langid = consumer.ReadIntegerBase(0); if (langid > UINT16_MAX || langid < 0) { - FatalError("Invalid winlangid {}", buf); + FatalError("Invalid winlangid {}", langid); } lang.winlangid = static_cast(langid); - } else if (!memcmp(str, "grflangid ", 10)) { - const char *buf = str + 10; - long langid = std::strtol(buf, nullptr, 16); + } else if (name == "grflangid") { + auto langid = consumer.ReadIntegerBase(0); if (langid >= 0x7F || langid < 0) { - FatalError("Invalid grflangid {}", buf); + FatalError("Invalid grflangid {}", langid); } lang.newgrflangid = static_cast(langid); - } else if (!memcmp(str, "gender ", 7)) { + } else if (name == "gender") { if (this->master) FatalError("Genders are not allowed in the base translation."); - StringConsumer consumer(std::string_view(str + 7)); - for (;;) { auto s = ParseWord(consumer); @@ -156,10 +155,8 @@ void FileStringReader::HandlePragma(char *str, LanguagePackHeader &lang) s->copy(lang.genders[lang.num_genders], CASE_GENDER_LEN - 1); lang.num_genders++; } - } else if (!memcmp(str, "case ", 5)) { + } else if (name == "case") { if (this->master) FatalError("Cases are not allowed in the base translation."); - StringConsumer consumer(std::string_view(str + 5)); - for (;;) { auto s = ParseWord(consumer); diff --git a/src/strgen/strgen.h b/src/strgen/strgen.h index 18e007f71e..c57b803d28 100644 --- a/src/strgen/strgen.h +++ b/src/strgen/strgen.h @@ -22,7 +22,7 @@ struct Case { uint8_t caseidx; ///< The index of the case. std::string string; ///< The translation of the case. - Case(uint8_t caseidx, const std::string &string); + Case(uint8_t caseidx, std::string_view string); }; /** Information about a single string. */ @@ -34,7 +34,7 @@ struct LangString { size_t line; ///< Line of string in source-file. std::vector translated_cases; ///< Cases of the translation. - LangString(const std::string &name, const std::string &english, size_t index, size_t line); + LangString(std::string_view name, std::string_view english, size_t index, size_t line); void FreeTranslation(); }; @@ -63,7 +63,7 @@ struct StringReader { StringReader(StringData &data, const std::string &file, bool master, bool translation); virtual ~StringReader() = default; - void HandleString(char *str); + void HandleString(std::string_view str); /** * Read a single line from the source of strings. @@ -75,7 +75,7 @@ struct StringReader { * Handle the pragma of the file. * @param str The pragma string to parse. */ - virtual void HandlePragma(char *str, LanguagePackHeader &lang); + virtual void HandlePragma(std::string_view str, LanguagePackHeader &lang); /** * Start parsing the file. diff --git a/src/strgen/strgen_base.cpp b/src/strgen/strgen_base.cpp index aa831c1945..4a08dcafae 100644 --- a/src/strgen/strgen_base.cpp +++ b/src/strgen/strgen_base.cpp @@ -41,7 +41,7 @@ static size_t TranslateArgumentIdx(size_t arg, size_t offset = 0); * @param caseidx The index of the case. * @param string The translation of the case. */ -Case::Case(uint8_t caseidx, const std::string &string) : +Case::Case(uint8_t caseidx, std::string_view string) : caseidx(caseidx), string(string) { } @@ -53,7 +53,7 @@ Case::Case(uint8_t caseidx, const std::string &string) : * @param index The index in the string table. * @param line The line this string was found on. */ -LangString::LangString(const std::string &name, const std::string &english, size_t index, size_t line) : +LangString::LangString(std::string_view name, std::string_view english, size_t index, size_t line) : name(name), english(english), index(index), line(line) { } @@ -164,30 +164,6 @@ size_t StringData::CountInUse(size_t tab) const return count; } -static size_t Utf8Validate(const char *s) -{ - char32_t c; - - if (!HasBit(s[0], 7)) { - /* 1 byte */ - return 1; - } else if (GB(s[0], 5, 3) == 6 && IsUtf8Part(s[1])) { - /* 2 bytes */ - c = GB(s[0], 0, 5) << 6 | GB(s[1], 0, 6); - if (c >= 0x80) return 2; - } else if (GB(s[0], 4, 4) == 14 && IsUtf8Part(s[1]) && IsUtf8Part(s[2])) { - /* 3 bytes */ - c = GB(s[0], 0, 4) << 12 | GB(s[1], 0, 6) << 6 | GB(s[2], 0, 6); - if (c >= 0x800) return 3; - } else if (GB(s[0], 3, 5) == 30 && IsUtf8Part(s[1]) && IsUtf8Part(s[2]) && IsUtf8Part(s[3])) { - /* 4 bytes */ - c = GB(s[0], 0, 3) << 18 | GB(s[1], 0, 6) << 12 | GB(s[2], 0, 6) << 6 | GB(s[3], 0, 6); - if (c >= 0x10000 && c <= 0x10FFFF) return 4; - } - - return 0; -} - void EmitSingleChar(StringBuilder &builder, std::string_view param, char32_t value) { if (!param.empty()) StrgenWarning("Ignoring trailing letters in command"); @@ -503,91 +479,92 @@ static bool CheckCommandsMatch(std::string_view a, std::string_view b, std::stri return result; } -void StringReader::HandleString(char *str) +[[nodiscard]] static std::string_view StripTrailingWhitespace(std::string_view str) { - if (*str == '#') { - if (str[1] == '#' && str[2] != '#') this->HandlePragma(str + 2, _strgen.lang); - return; + auto len = str.find_last_not_of("\r\n "); + if (len == std::string_view::npos) return {}; + return str.substr(0, len + 1); +} + +void StringReader::HandleString(std::string_view src) +{ + /* Ignore blank lines */ + if (src.empty()) return; + + StringConsumer consumer(src); + if (consumer.ReadCharIf('#')) { + if (consumer.ReadCharIf('#') && !consumer.ReadCharIf('#')) this->HandlePragma(consumer.Read(StringConsumer::npos), _strgen.lang); + return; // ignore comments } - /* Ignore comments & blank lines */ - if (*str == ';' || *str == ' ' || *str == '\0') return; - - char *s = strchr(str, ':'); - if (s == nullptr) { + /* Read string name */ + std::string_view str_name = StripTrailingWhitespace(consumer.ReadUntilChar(':', StringConsumer::KEEP_SEPARATOR)); + if (!consumer.ReadCharIf(':')) { StrgenError("Line has no ':' delimiter"); return; } - char *t; - /* Trim spaces. - * After this str points to the command name, and s points to the command contents */ - for (t = s; t > str && (t[-1] == ' ' || t[-1] == '\t'); t--) {} - *t = 0; - s++; - - /* Check string is valid UTF-8 */ - const char *tmp; - for (tmp = s; *tmp != '\0';) { - size_t len = Utf8Validate(tmp); - if (len == 0) StrgenFatal("Invalid UTF-8 sequence in '{}'", s); - - char32_t c; - Utf8Decode(&c, tmp); - if (c <= 0x001F || // ASCII control character range - c == 0x200B || // Zero width space - (c >= 0xE000 && c <= 0xF8FF) || // Private range - (c >= 0xFFF0 && c <= 0xFFFF)) { // Specials range - StrgenFatal("Unwanted UTF-8 character U+{:04X} in sequence '{}'", static_cast(c), s); - } - - tmp += len; + /* Read string case */ + std::optional casep; + if (auto index = str_name.find("."); index != std::string_view::npos) { + casep = str_name.substr(index + 1); + str_name = str_name.substr(0, index); } - /* Check if the string has a case.. - * The syntax for cases is IDENTNAME.case */ - char *casep = strchr(str, '.'); - if (casep != nullptr) *casep++ = '\0'; + /* Read string data */ + std::string_view value = consumer.Read(StringConsumer::npos); + + /* Check string is valid UTF-8 */ + for (StringConsumer validation_consumer(value); validation_consumer.AnyBytesLeft(); ) { + auto c = validation_consumer.TryReadUtf8(); + if (!c.has_value()) StrgenFatal("Invalid UTF-8 sequence in '{}'", value); + if (*c <= 0x001F || // ASCII control character range + *c == 0x200B || // Zero width space + (*c >= 0xE000 && *c <= 0xF8FF) || // Private range + (*c >= 0xFFF0 && *c <= 0xFFFF)) { // Specials range + StrgenFatal("Unwanted UTF-8 character U+{:04X} in sequence '{}'", static_cast(*c), value); + } + } /* Check if this string already exists.. */ - LangString *ent = this->data.Find(str); + LangString *ent = this->data.Find(std::string(str_name)); if (this->master) { - if (casep != nullptr) { + if (casep.has_value()) { StrgenError("Cases in the base translation are not supported."); return; } if (ent != nullptr) { - StrgenError("String name '{}' is used multiple times", str); + StrgenError("String name '{}' is used multiple times", str_name); return; } if (this->data.strings[this->data.next_string_id] != nullptr) { - StrgenError("String ID 0x{:X} for '{}' already in use by '{}'", this->data.next_string_id, str, this->data.strings[this->data.next_string_id]->name); + StrgenError("String ID 0x{:X} for '{}' already in use by '{}'", this->data.next_string_id, str_name, this->data.strings[this->data.next_string_id]->name); return; } /* Allocate a new LangString */ - this->data.Add(std::make_unique(str, s, this->data.next_string_id++, _strgen.cur_line)); + this->data.Add(std::make_unique(str_name, value, this->data.next_string_id++, _strgen.cur_line)); } else { if (ent == nullptr) { - StrgenWarning("String name '{}' does not exist in master file", str); + StrgenWarning("String name '{}' does not exist in master file", str_name); return; } - if (!ent->translated.empty() && casep == nullptr) { - StrgenError("String name '{}' is used multiple times", str); + if (!ent->translated.empty() && !casep.has_value()) { + StrgenError("String name '{}' is used multiple times", str_name); return; } /* make sure that the commands match */ - if (!CheckCommandsMatch(s, ent->english, str)) return; + if (!CheckCommandsMatch(value, ent->english, str_name)) return; - if (casep != nullptr) { - ent->translated_cases.emplace_back(ResolveCaseName(casep), s); + if (casep.has_value()) { + ent->translated_cases.emplace_back(ResolveCaseName(*casep), value); } else { - ent->translated = s; + ent->translated = value; /* If the string was translated, use the line from the * translated language so errors in the translated file * are properly referenced to. */ @@ -596,23 +573,20 @@ void StringReader::HandleString(char *str) } } -void StringReader::HandlePragma(char *str, LanguagePackHeader &lang) +void StringReader::HandlePragma(std::string_view str, LanguagePackHeader &lang) { - if (!memcmp(str, "plural ", 7)) { - lang.plural_form = atoi(str + 7); + StringConsumer consumer(str); + auto name = consumer.ReadUntilChar(' ', StringConsumer::SKIP_ALL_SEPARATORS); + if (name == "plural") { + lang.plural_form = consumer.ReadIntegerBase(10); if (lang.plural_form >= lengthof(_plural_forms)) { StrgenFatal("Invalid pluralform {}", lang.plural_form); } } else { - StrgenFatal("unknown pragma '{}'", str); + StrgenFatal("unknown pragma '{}'", name); } } -static void StripTrailingWhitespace(std::string &str) -{ - str.erase(str.find_last_not_of("\r\n ") + 1); -} - void StringReader::ParseFile() { _strgen.warnings = _strgen.errors = 0; @@ -631,8 +605,7 @@ void StringReader::ParseFile() std::optional line = this->ReadLine(); if (!line.has_value()) return; - StripTrailingWhitespace(line.value()); - this->HandleString(line.value().data()); + this->HandleString(StripTrailingWhitespace(line.value())); _strgen.cur_line++; } From 588fedb5db2ba0c160d27fe2a3cc851d2e40e2b5 Mon Sep 17 00:00:00 2001 From: frosch Date: Tue, 1 Apr 2025 18:21:34 +0200 Subject: [PATCH 017/766] Codechange: Remove unused Utf8Consume, Utf8Decode, Utf8EncodedCharLen. --- src/string.cpp | 42 ------------------------------------------ src/string_func.h | 38 -------------------------------------- 2 files changed, 80 deletions(-) diff --git a/src/string.cpp b/src/string.cpp index 4032a7f34f..a9d72efab9 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -377,48 +377,6 @@ bool IsValidChar(char32_t key, CharSetFilter afilter) } } - -/* UTF-8 handling routines */ - - -/** - * Decode and consume the next UTF-8 encoded character. - * @param c Buffer to place decoded character. - * @param s Character stream to retrieve character from. - * @return Number of characters in the sequence. - */ -size_t Utf8Decode(char32_t *c, const char *s) -{ - assert(c != nullptr); - - if (!HasBit(s[0], 7)) { - /* Single byte character: 0xxxxxxx */ - *c = s[0]; - return 1; - } else if (GB(s[0], 5, 3) == 6) { - if (IsUtf8Part(s[1])) { - /* Double byte character: 110xxxxx 10xxxxxx */ - *c = GB(s[0], 0, 5) << 6 | GB(s[1], 0, 6); - if (*c >= 0x80) return 2; - } - } else if (GB(s[0], 4, 4) == 14) { - if (IsUtf8Part(s[1]) && IsUtf8Part(s[2])) { - /* Triple byte character: 1110xxxx 10xxxxxx 10xxxxxx */ - *c = GB(s[0], 0, 4) << 12 | GB(s[1], 0, 6) << 6 | GB(s[2], 0, 6); - if (*c >= 0x800) return 3; - } - } else if (GB(s[0], 3, 5) == 30) { - if (IsUtf8Part(s[1]) && IsUtf8Part(s[2]) && IsUtf8Part(s[3])) { - /* 4 byte character: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ - *c = GB(s[0], 0, 3) << 18 | GB(s[1], 0, 6) << 12 | GB(s[2], 0, 6) << 6 | GB(s[3], 0, 6); - if (*c >= 0x10000 && *c <= 0x10FFFF) return 4; - } - } - - *c = '?'; - return 1; -} - /** * Test if a unicode character is considered garbage to be skipped. * @param c Character to test. diff --git a/src/string_func.h b/src/string_func.h index 09c8dc18db..c7e8910542 100644 --- a/src/string_func.h +++ b/src/string_func.h @@ -72,44 +72,6 @@ inline bool StrEmpty(const char *s) bool IsValidChar(char32_t key, CharSetFilter afilter); -size_t Utf8Decode(char32_t *c, const char *s); -/* std::string_view::iterator might be char *, in which case we do not want this templated variant to be taken. */ -template requires (!std::is_same_v && (std::is_same_v || std::is_same_v)) -inline size_t Utf8Decode(char32_t *c, T &s) { return Utf8Decode(c, &*s); } - -inline char32_t Utf8Consume(const char **s) -{ - char32_t c; - *s += Utf8Decode(&c, *s); - return c; -} - -template -inline char32_t Utf8Consume(Titr &s) -{ - char32_t c; - s += Utf8Decode(&c, &*s); - return c; -} - -/** - * Return the length of an UTF-8 encoded value based on a single char. This - * char should be the first byte of the UTF-8 encoding. If not, or encoding - * is invalid, return value is 0 - * @param c char to query length of - * @return requested size - */ -inline int8_t Utf8EncodedCharLen(char c) -{ - if (GB(c, 3, 5) == 0x1E) return 4; - if (GB(c, 4, 4) == 0x0E) return 3; - if (GB(c, 5, 3) == 0x06) return 2; - if (GB(c, 7, 1) == 0x00) return 1; - - /* Invalid UTF8 start encoding */ - return 0; -} - /* Check if the given character is part of a UTF8 sequence */ inline bool IsUtf8Part(char c) From d2c9828b4c4259709a7138ad10286ccb8fdacc32 Mon Sep 17 00:00:00 2001 From: frosch Date: Wed, 2 Apr 2025 10:04:46 +0200 Subject: [PATCH 018/766] Codechange: Move IsUtf8Part to only file using it. --- src/core/utf8.hpp | 8 +++++++- src/string_func.h | 7 ------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/core/utf8.hpp b/src/core/utf8.hpp index db1354ef24..26e6305ba9 100644 --- a/src/core/utf8.hpp +++ b/src/core/utf8.hpp @@ -13,11 +13,17 @@ #define UTF8_HPP #include -#include "../string_func.h" +#include "bitmath_func.hpp" [[nodiscard]] std::pair EncodeUtf8(char32_t c); [[nodiscard]] std::pair DecodeUtf8(std::string_view buf); +/* Check if the given character is part of a UTF8 sequence */ +inline bool IsUtf8Part(char c) +{ + return GB(c, 6, 2) == 2; +} + /** * Constant span of UTF-8 encoded data. */ diff --git a/src/string_func.h b/src/string_func.h index c7e8910542..9fa5636204 100644 --- a/src/string_func.h +++ b/src/string_func.h @@ -72,13 +72,6 @@ inline bool StrEmpty(const char *s) bool IsValidChar(char32_t key, CharSetFilter afilter); - -/* Check if the given character is part of a UTF8 sequence */ -inline bool IsUtf8Part(char c) -{ - return GB(c, 6, 2) == 2; -} - size_t Utf8StringLength(std::string_view str); /** From fc20ce301a39f4a2d4a1e62b50cd2c8b8728be49 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Mon, 14 Apr 2025 20:50:02 +0200 Subject: [PATCH 019/766] Doc: Missing changelog entry for 15.0-beta2. (#14001) --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index e70a8b5069..5dbd8bf0a4 100644 --- a/changelog.md +++ b/changelog.md @@ -20,6 +20,7 @@ - Add: Ukrainian Hryvnia currency (#12877) - Add: Convert 32bpp-only sprites to 8bpp when needed (#11602) - Change: [Script] Start GS (but don't run it) when generating world in scenario editor (#13961) +- Change: [Script] Add vehicle owner to crash event (#13878) - Change: Make tree placement at world generation look more organic (#13515) - Change: [MacOS] Put the icon in a rounded rectangle (#13446) - Change: [Script] GetWaypointID to return the StationID of any waypoint (#13407) From 5008568dfc56571f2badc4b677aa30f4f8e255dc Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Mon, 14 Apr 2025 23:55:40 +0100 Subject: [PATCH 020/766] Codechange: Rename CenterBounds to CentreBounds and move to geometry header. (#14002) --- src/aircraft_gui.cpp | 2 +- src/airport_gui.cpp | 2 +- src/build_vehicle_gui.cpp | 2 +- src/company_gui.cpp | 6 +++--- src/core/geometry_type.hpp | 11 +++++++++++ src/dropdown_common_type.h | 4 ++-- src/genworld_gui.cpp | 2 +- src/gfx.cpp | 4 ++-- src/gfx_func.h | 12 ------------ src/graph_gui.cpp | 10 +++++----- src/industry_gui.cpp | 2 +- src/linkgraph/linkgraph_gui.cpp | 6 +++--- src/misc_gui.cpp | 6 +++--- src/network/network_content_gui.cpp | 2 +- src/network/network_gui.cpp | 8 ++++---- src/newgrf_debug_gui.cpp | 2 +- src/newgrf_gui.cpp | 4 ++-- src/news_gui.cpp | 2 +- src/rail_gui.cpp | 2 +- src/roadveh_gui.cpp | 2 +- src/script/script_gui.cpp | 2 +- src/ship_gui.cpp | 2 +- src/smallmap_gui.cpp | 4 ++-- src/statusbar_gui.cpp | 4 ++-- src/subsidy_gui.cpp | 2 +- src/train_gui.cpp | 2 +- src/tree_gui.cpp | 2 +- src/vehicle_gui.cpp | 8 ++++---- src/widget.cpp | 10 +++++----- 29 files changed, 63 insertions(+), 64 deletions(-) diff --git a/src/aircraft_gui.cpp b/src/aircraft_gui.cpp index 1ba0a311d5..5b7d67582b 100644 --- a/src/aircraft_gui.cpp +++ b/src/aircraft_gui.cpp @@ -82,7 +82,7 @@ void DrawAircraftImage(const Vehicle *v, const Rect &r, VehicleID selection, Eng int x_offs = UnScaleGUI(rect.left); int x = rtl ? r.right - width - x_offs : r.left - x_offs; /* This magic -1 offset is related to the sprite_y_offsets in build_vehicle_gui.cpp */ - int y = ScaleSpriteTrad(-1) + CenterBounds(r.top, r.bottom, 0); + int y = ScaleSpriteTrad(-1) + CentreBounds(r.top, r.bottom, 0); bool helicopter = v->subtype == AIR_HELICOPTER; int heli_offs = 0; diff --git a/src/airport_gui.cpp b/src/airport_gui.cpp index 6954317887..5b2464e975 100644 --- a/src/airport_gui.cpp +++ b/src/airport_gui.cpp @@ -401,7 +401,7 @@ public: case WID_AP_AIRPORT_SPRITE: if (this->preview_sprite != 0) { Dimension d = GetSpriteSize(this->preview_sprite); - DrawSprite(this->preview_sprite, COMPANY_SPRITE_COLOUR(_local_company), CenterBounds(r.left, r.right, d.width), CenterBounds(r.top, r.bottom, d.height)); + DrawSprite(this->preview_sprite, COMPANY_SPRITE_COLOUR(_local_company), CentreBounds(r.left, r.right, d.width), CentreBounds(r.top, r.bottom, d.height)); } break; diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index ad0f55141e..e2a5c7cdf0 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -995,7 +995,7 @@ void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_li if (lvl < item.indent) tx += level_width; } /* Draw our node in the tree. */ - int ycentre = CenterBounds(textr.top, textr.bottom, WidgetDimensions::scaled.fullbevel.top); + int ycentre = CentreBounds(textr.top, textr.bottom, WidgetDimensions::scaled.fullbevel.top); if (!HasBit(item.level_mask, item.indent)) GfxDrawLine(tx, ir.top, tx, ycentre, linecolour, WidgetDimensions::scaled.fullbevel.top); GfxDrawLine(tx, ycentre, tx + offset - (rtl ? -1 : 1), ycentre, linecolour, WidgetDimensions::scaled.fullbevel.top); } diff --git a/src/company_gui.cpp b/src/company_gui.cpp index 0c171330e2..01544dc527 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -1127,8 +1127,8 @@ void DrawCompanyManagerFace(CompanyManagerFace cmf, Colours colour, const Rect & /* Determine offset from centre of drawing rect. */ Dimension d = GetSpriteSize(SPR_GRADIENT); - int x = CenterBounds(r.left, r.right, d.width); - int y = CenterBounds(r.top, r.bottom, d.height); + int x = CentreBounds(r.left, r.right, d.width); + int y = CentreBounds(r.top, r.bottom, d.height); bool has_moustache = !HasBit(ge, GENDER_FEMALE) && GetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE, ge) != 0; bool has_tie_earring = !HasBit(ge, GENDER_FEMALE) || GetCompanyManagerFaceBits(cmf, CMFV_HAS_TIE_EARRING, ge) != 0; @@ -2373,7 +2373,7 @@ struct CompanyWindow : Window Point offset; Dimension d = GetSpriteSize(SPR_VEH_BUS_SW_VIEW, &offset); d.height -= offset.y; - DrawSprite(SPR_VEH_BUS_SW_VIEW, COMPANY_SPRITE_COLOUR(c->index), r.left - offset.x, CenterBounds(r.top, r.bottom, d.height) - offset.y); + DrawSprite(SPR_VEH_BUS_SW_VIEW, COMPANY_SPRITE_COLOUR(c->index), r.left - offset.x, CentreBounds(r.top, r.bottom, d.height) - offset.y); break; } diff --git a/src/core/geometry_type.hpp b/src/core/geometry_type.hpp index 9149ab2c3c..ddb3398ade 100644 --- a/src/core/geometry_type.hpp +++ b/src/core/geometry_type.hpp @@ -16,6 +16,17 @@ # define Point OTTD_Point #endif /* __APPLE__ */ +/** + * Determine where to position a centred object. + * @param min The top or left coordinate. + * @param max The bottom or right coordinate. + * @param size The height or width of the object to draw. + * @return Offset of where to position the object. + */ +inline int CentreBounds(int min, int max, int size) +{ + return (min + max - size + 1) / 2; +} /** Coordinates of a point in 2D */ struct Point { diff --git a/src/dropdown_common_type.h b/src/dropdown_common_type.h index 5193348464..04cb7665f6 100644 --- a/src/dropdown_common_type.h +++ b/src/dropdown_common_type.h @@ -36,7 +36,7 @@ public: uint8_t c1 = GetColourGradient(bg_colour, SHADE_DARK); uint8_t c2 = GetColourGradient(bg_colour, SHADE_LIGHTEST); - int mid = CenterBounds(full.top, full.bottom, 0); + int mid = CentreBounds(full.top, full.bottom, 0); GfxFillRect(full.left, mid - WidgetDimensions::scaled.bevel.bottom, full.right, mid - 1, c1); GfxFillRect(full.left, mid, full.right, mid + WidgetDimensions::scaled.bevel.top - 1, c2); } @@ -126,7 +126,7 @@ public: { bool rtl = TEnd ^ (_current_text_dir == TD_RTL); Rect ir = r.WithWidth(this->dbounds.width, rtl); - DrawSprite(this->sprite, this->palette, CenterBounds(ir.left, ir.right, this->dsprite.width), CenterBounds(r.top, r.bottom, this->dsprite.height)); + DrawSprite(this->sprite, this->palette, CentreBounds(ir.left, ir.right, this->dsprite.width), CentreBounds(r.top, r.bottom, this->dsprite.height)); this->TBase::Draw(full, r.Indent(this->dbounds.width + WidgetDimensions::scaled.hsep_normal, rtl), sel, bg_colour); } }; diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp index 52218c2380..a646638cff 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -1393,7 +1393,7 @@ struct GenerateProgressWindow : public Window { DrawFrameRect(r, COLOUR_GREY, {FrameFlag::BorderOnly, FrameFlag::Lowered}); Rect br = r.Shrink(WidgetDimensions::scaled.bevel); DrawFrameRect(br.WithWidth(br.Width() * GenWorldStatus::percent / 100, _current_text_dir == TD_RTL), COLOUR_MAUVE, {}); - DrawString(br.left, br.right, CenterBounds(br.top, br.bottom, GetCharacterHeight(FS_NORMAL)), + DrawString(br.left, br.right, CentreBounds(br.top, br.bottom, GetCharacterHeight(FS_NORMAL)), GetString(STR_GENERATION_PROGRESS, GenWorldStatus::percent), TC_FROMSTRING, SA_HOR_CENTER); break; } diff --git a/src/gfx.cpp b/src/gfx.cpp index f116343b00..13d9776d12 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -907,8 +907,8 @@ void DrawCharCentered(char32_t c, const Rect &r, TextColour colour) { SetColourRemap(colour); GfxMainBlitter(GetGlyph(FS_NORMAL, c), - CenterBounds(r.left, r.right, GetCharacterWidth(FS_NORMAL, c)), - CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), + CentreBounds(r.left, r.right, GetCharacterWidth(FS_NORMAL, c)), + CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), BlitterMode::ColourRemap); } diff --git a/src/gfx_func.h b/src/gfx_func.h index d7febfdc74..72654a7b8c 100644 --- a/src/gfx_func.h +++ b/src/gfx_func.h @@ -156,18 +156,6 @@ inline bool FillDrawPixelInfo(DrawPixelInfo *n, const Rect &r) return FillDrawPixelInfo(n, r.left, r.top, r.Width(), r.Height()); } -/** - * Determine where to draw a centred object inside a widget. - * @param min The top or left coordinate. - * @param max The bottom or right coordinate. - * @param size The height or width of the object to draw. - * @return Offset of where to start drawing the object. - */ -inline int CenterBounds(int min, int max, int size) -{ - return (min + max - size + 1) / 2; -} - /* window.cpp */ void DrawOverlappedWindowForAll(int left, int top, int right, int bottom); diff --git a/src/graph_gui.cpp b/src/graph_gui.cpp index 7cb36d9dbe..011e1d41c2 100644 --- a/src/graph_gui.cpp +++ b/src/graph_gui.cpp @@ -73,10 +73,10 @@ struct GraphLegendWindow : Window { const Rect ir = r.Shrink(WidgetDimensions::scaled.framerect); Dimension d = GetSpriteSize(SPR_COMPANY_ICON); - DrawCompanyIcon(cid, rtl ? ir.right - d.width : ir.left, CenterBounds(ir.top, ir.bottom, d.height)); + DrawCompanyIcon(cid, rtl ? ir.right - d.width : ir.left, CentreBounds(ir.top, ir.bottom, d.height)); const Rect tr = ir.Indent(d.width + WidgetDimensions::scaled.hsep_normal, rtl); - DrawString(tr.left, tr.right, CenterBounds(tr.top, tr.bottom, GetCharacterHeight(FS_NORMAL)), GetString(STR_COMPANY_NAME_COMPANY_NUM, cid, cid), _legend_excluded_companies.Test(cid) ? TC_BLACK : TC_WHITE); + DrawString(tr.left, tr.right, CentreBounds(tr.top, tr.bottom, GetCharacterHeight(FS_NORMAL)), GetString(STR_COMPANY_NAME_COMPANY_NUM, cid, cid), _legend_excluded_companies.Test(cid) ? TC_BLACK : TC_WHITE); } void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override @@ -1385,7 +1385,7 @@ struct PerformanceRatingDetailWindow : Window { if (this->IsWidgetDisabled(widget)) return; CompanyID cid = (CompanyID)(widget - WID_PRD_COMPANY_FIRST); Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON); - DrawCompanyIcon(cid, CenterBounds(r.left, r.right, sprite_size.width), CenterBounds(r.top, r.bottom, sprite_size.height)); + DrawCompanyIcon(cid, CentreBounds(r.left, r.right, sprite_size.width), CentreBounds(r.top, r.bottom, sprite_size.height)); return; } @@ -1408,8 +1408,8 @@ struct PerformanceRatingDetailWindow : Window { needed = SCORE_MAX; } - uint bar_top = CenterBounds(r.top, r.bottom, this->bar_height); - uint text_top = CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)); + uint bar_top = CentreBounds(r.top, r.bottom, this->bar_height); + uint text_top = CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)); DrawString(this->score_info_left, this->score_info_right, text_top, STR_PERFORMANCE_DETAIL_VEHICLES + score_type); diff --git a/src/industry_gui.cpp b/src/industry_gui.cpp index bbe73b9327..6beb9bdaf9 100644 --- a/src/industry_gui.cpp +++ b/src/industry_gui.cpp @@ -864,7 +864,7 @@ public: SpriteID icon = CargoSpec::Get(cargo_type)->GetCargoIcon(); Dimension d = GetSpriteSize(icon); Rect ir = r.WithWidth(this->cargo_icon_size.width, rtl).WithHeight(GetCharacterHeight(FS_NORMAL)); - DrawSprite(icon, PAL_NONE, CenterBounds(ir.left, ir.right, d.width), CenterBounds(ir.top, ir.bottom, this->cargo_icon_size.height)); + DrawSprite(icon, PAL_NONE, CentreBounds(ir.left, ir.right, d.width), CentreBounds(ir.top, ir.bottom, this->cargo_icon_size.height)); } std::string GetAcceptedCargoString(const Industry::AcceptedCargo &ac, const CargoSuffix &suffix) const diff --git a/src/linkgraph/linkgraph_gui.cpp b/src/linkgraph/linkgraph_gui.cpp index 61c25550c0..57ab1dfaf0 100644 --- a/src/linkgraph/linkgraph_gui.cpp +++ b/src/linkgraph/linkgraph_gui.cpp @@ -604,7 +604,7 @@ void LinkGraphLegendWindow::DrawWidget(const Rect &r, WidgetID widget) const if (this->IsWidgetDisabled(widget)) return; CompanyID cid = (CompanyID)(widget - WID_LGL_COMPANY_FIRST); Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON); - DrawCompanyIcon(cid, CenterBounds(br.left, br.right, sprite_size.width), CenterBounds(br.top, br.bottom, sprite_size.height)); + DrawCompanyIcon(cid, CentreBounds(br.left, br.right, sprite_size.width), CentreBounds(br.top, br.bottom, sprite_size.height)); } if (IsInsideMM(widget, WID_LGL_SATURATION_FIRST, WID_LGL_SATURATION_LAST + 1)) { uint8_t colour = LinkGraphOverlay::LINK_COLOURS[_settings_client.gui.linkgraph_colours][widget - WID_LGL_SATURATION_FIRST]; @@ -618,13 +618,13 @@ void LinkGraphLegendWindow::DrawWidget(const Rect &r, WidgetID widget) const str = STR_LINKGRAPH_LEGEND_SATURATED; } if (str != STR_NULL) { - DrawString(br.left, br.right, CenterBounds(br.top, br.bottom, GetCharacterHeight(FS_SMALL)), str, GetContrastColour(colour) | TC_FORCED, SA_HOR_CENTER, false, FS_SMALL); + DrawString(br.left, br.right, CentreBounds(br.top, br.bottom, GetCharacterHeight(FS_SMALL)), str, GetContrastColour(colour) | TC_FORCED, SA_HOR_CENTER, false, FS_SMALL); } } if (IsInsideMM(widget, WID_LGL_CARGO_FIRST, WID_LGL_CARGO_LAST + 1)) { const CargoSpec *cargo = _sorted_cargo_specs[widget - WID_LGL_CARGO_FIRST]; GfxFillRect(br, cargo->legend_colour); - DrawString(br.left, br.right, CenterBounds(br.top, br.bottom, GetCharacterHeight(FS_SMALL)), cargo->abbrev, GetContrastColour(cargo->legend_colour, 73), SA_HOR_CENTER, false, FS_SMALL); + DrawString(br.left, br.right, CentreBounds(br.top, br.bottom, GetCharacterHeight(FS_SMALL)), cargo->abbrev, GetContrastColour(cargo->legend_colour, 73), SA_HOR_CENTER, false, FS_SMALL); } } diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp index bae49d57f3..eefdf37a29 100644 --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -767,14 +767,14 @@ void QueryString::DrawEditBox(const Window *w, WidgetID wid) const /* If we have a marked area, draw a background highlight. */ if (tb->marklength != 0) GfxFillRect(fr.left + tb->markxoffs, fr.top, fr.left + tb->markxoffs + tb->marklength - 1, fr.bottom, PC_GREY); - DrawString(fr.left, fr.right, CenterBounds(fr.top, fr.bottom, GetCharacterHeight(FS_NORMAL)), tb->GetText(), TC_YELLOW); + DrawString(fr.left, fr.right, CentreBounds(fr.top, fr.bottom, GetCharacterHeight(FS_NORMAL)), tb->GetText(), TC_YELLOW); bool focussed = w->IsWidgetGloballyFocused(wid) || IsOSKOpenedFor(w, wid); if (focussed && tb->caret) { int caret_width = GetCaretWidth(); if (rtl) { - DrawString(fr.right - tb->pixels + tb->caretxoffs - caret_width, fr.right - tb->pixels + tb->caretxoffs, CenterBounds(fr.top, fr.bottom, GetCharacterHeight(FS_NORMAL)), "_", TC_WHITE); + DrawString(fr.right - tb->pixels + tb->caretxoffs - caret_width, fr.right - tb->pixels + tb->caretxoffs, CentreBounds(fr.top, fr.bottom, GetCharacterHeight(FS_NORMAL)), "_", TC_WHITE); } else { - DrawString(fr.left + tb->caretxoffs, fr.left + tb->caretxoffs + caret_width, CenterBounds(fr.top, fr.bottom, GetCharacterHeight(FS_NORMAL)), "_", TC_WHITE); + DrawString(fr.left + tb->caretxoffs, fr.left + tb->caretxoffs + caret_width, CentreBounds(fr.top, fr.bottom, GetCharacterHeight(FS_NORMAL)), "_", TC_WHITE); } } } diff --git a/src/network/network_content_gui.cpp b/src/network/network_content_gui.cpp index de77cc6dd7..5974f0f1bb 100644 --- a/src/network/network_content_gui.cpp +++ b/src/network/network_content_gui.cpp @@ -145,7 +145,7 @@ void BaseNetworkContentDownloadStatusWindow::DrawWidget(const Rect &r, WidgetID DrawFrameRect(r, COLOUR_GREY, {FrameFlag::BorderOnly, FrameFlag::Lowered}); Rect ir = r.Shrink(WidgetDimensions::scaled.bevel); DrawFrameRect(ir.WithWidth((uint64_t)ir.Width() * this->downloaded_bytes / this->total_bytes, _current_text_dir == TD_RTL), COLOUR_MAUVE, {}); - DrawString(ir.left, ir.right, CenterBounds(ir.top, ir.bottom, GetCharacterHeight(FS_NORMAL)), + DrawString(ir.left, ir.right, CentreBounds(ir.top, ir.bottom, GetCharacterHeight(FS_NORMAL)), GetString(STR_CONTENT_DOWNLOAD_PROGRESS_SIZE, this->downloaded_bytes, this->total_bytes, this->downloaded_bytes * 100LL / this->total_bytes), TC_FROMSTRING, SA_HOR_CENTER); break; diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index cb5c5cb4a2..a5da3540d1 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -1900,10 +1900,10 @@ public: void DrawCompany(CompanyID company_id, const Rect &r, uint &line) const { bool rtl = _current_text_dir == TD_RTL; - int text_y_offset = CenterBounds(0, this->line_height, GetCharacterHeight(FS_NORMAL)); + int text_y_offset = CentreBounds(0, this->line_height, GetCharacterHeight(FS_NORMAL)); Dimension d = GetSpriteSize(SPR_COMPANY_ICON); - int offset = CenterBounds(0, this->line_height, d.height); + int offset = CentreBounds(0, this->line_height, d.height); uint line_start = this->vscroll->GetPosition(); uint line_end = line_start + this->vscroll->GetCapacity(); @@ -1961,7 +1961,7 @@ public: if (player_icon != 0) { Dimension d2 = GetSpriteSize(player_icon); - int offset_y = CenterBounds(0, this->line_height, d2.height); + int offset_y = CentreBounds(0, this->line_height, d2.height); DrawSprite(player_icon, PALETTE_TO_GREY, rtl ? tr.right - d2.width : tr.left, y + offset_y); tr = tr.Indent(d2.width + WidgetDimensions::scaled.hsep_normal, rtl); } @@ -2075,7 +2075,7 @@ struct NetworkJoinStatusWindow : Window { break; } DrawFrameRect(ir.WithWidth(ir.Width() * progress / 100, _current_text_dir == TD_RTL), COLOUR_MAUVE, {}); - DrawString(ir.left, ir.right, CenterBounds(ir.top, ir.bottom, GetCharacterHeight(FS_NORMAL)), STR_NETWORK_CONNECTING_1 + _network_join_status, TC_FROMSTRING, SA_HOR_CENTER); + DrawString(ir.left, ir.right, CentreBounds(ir.top, ir.bottom, GetCharacterHeight(FS_NORMAL)), STR_NETWORK_CONNECTING_1 + _network_join_status, TC_FROMSTRING, SA_HOR_CENTER); break; } diff --git a/src/newgrf_debug_gui.cpp b/src/newgrf_debug_gui.cpp index 40fabd2e0c..1f83160969 100644 --- a/src/newgrf_debug_gui.cpp +++ b/src/newgrf_debug_gui.cpp @@ -412,7 +412,7 @@ struct NewGRFInspectWindow : Window { GrfSpecFeature f = GetFeatureNum(this->window_number); int h = GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height; - int y = CenterBounds(br.top, br.bottom, h); + int y = CentreBounds(br.top, br.bottom, h); DrawVehicleImage(v->First(), br, VehicleID::Invalid(), EIT_IN_DETAILS, skip); /* Highlight the articulated part (this is different to the whole-vehicle highlighting of DrawVehicleImage */ diff --git a/src/newgrf_gui.cpp b/src/newgrf_gui.cpp index 2b145522d3..6c206a93fe 100644 --- a/src/newgrf_gui.cpp +++ b/src/newgrf_gui.cpp @@ -908,7 +908,7 @@ struct NewGRFWindow : public Window, NewGRFScanCallback { case WID_NS_NEWGRF_INFO_TITLE: { /* Create the nice grayish rectangle at the details top. */ GfxFillRect(r.Shrink(WidgetDimensions::scaled.bevel), PC_DARK_BLUE); - DrawString(r.left, r.right, CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), STR_NEWGRF_SETTINGS_INFO_TITLE, TC_FROMSTRING, SA_HOR_CENTER); + DrawString(r.left, r.right, CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), STR_NEWGRF_SETTINGS_INFO_TITLE, TC_FROMSTRING, SA_HOR_CENTER); break; } @@ -2194,7 +2194,7 @@ struct ScanProgressWindow : public Window { Rect ir = r.Shrink(WidgetDimensions::scaled.bevel); uint percent = scanned * 100 / std::max(1U, _settings_client.gui.last_newgrf_count); DrawFrameRect(ir.WithWidth(ir.Width() * percent / 100, _current_text_dir == TD_RTL), COLOUR_MAUVE, {}); - DrawString(ir.left, ir.right, CenterBounds(ir.top, ir.bottom, GetCharacterHeight(FS_NORMAL)), GetString(STR_GENERATION_PROGRESS, percent), TC_FROMSTRING, SA_HOR_CENTER); + DrawString(ir.left, ir.right, CentreBounds(ir.top, ir.bottom, GetCharacterHeight(FS_NORMAL)), GetString(STR_GENERATION_PROGRESS, percent), TC_FROMSTRING, SA_HOR_CENTER); break; } diff --git a/src/news_gui.cpp b/src/news_gui.cpp index f9ca7a3601..8445579b74 100644 --- a/src/news_gui.cpp +++ b/src/news_gui.cpp @@ -550,7 +550,7 @@ struct NewsWindow : Window { case WID_N_VEH_SPR: { assert(std::holds_alternative(ni->ref1)); EngineID engine = std::get(this->ni->ref1); - DrawVehicleEngine(r.left, r.right, CenterBounds(r.left, r.right, 0), CenterBounds(r.top, r.bottom, 0), engine, GetEnginePalette(engine, _local_company), EIT_PREVIEW); + DrawVehicleEngine(r.left, r.right, CentreBounds(r.left, r.right, 0), CentreBounds(r.top, r.bottom, 0), engine, GetEnginePalette(engine, _local_company), EIT_PREVIEW); GfxFillRect(r, PALETTE_NEWSPAPER, FILLRECT_RECOLOUR); break; } diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 09c551a344..08d8b38d7f 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -1465,7 +1465,7 @@ private: Point offset; Dimension sprite_size = GetSpriteSize(image, &offset); Rect ir = r.Shrink(WidgetDimensions::scaled.imgbtn); - int x = CenterBounds(ir.left, ir.right, sprite_size.width - offset.x) - offset.x; // centered + int x = CentreBounds(ir.left, ir.right, sprite_size.width - offset.x) - offset.x; // centered int y = ir.top - sig_sprite_bottom_offset + (ir.Height() + sig_sprite_size.height) / 2; // aligned to bottom diff --git a/src/roadveh_gui.cpp b/src/roadveh_gui.cpp index 4a98d58dfa..b7fedf2495 100644 --- a/src/roadveh_gui.cpp +++ b/src/roadveh_gui.cpp @@ -152,6 +152,6 @@ void DrawRoadVehImage(const Vehicle *v, const Rect &r, VehicleID selection, Engi if (v->index == selection) { int height = ScaleSpriteTrad(12); Rect hr = {(rtl ? px : 0), 0, (rtl ? max_width : px) - 1, height - 1}; - DrawFrameRect(hr.Translate(r.left, CenterBounds(r.top, r.bottom, height)).Expand(WidgetDimensions::scaled.bevel), COLOUR_WHITE, FrameFlag::BorderOnly); + DrawFrameRect(hr.Translate(r.left, CentreBounds(r.top, r.bottom, height)).Expand(WidgetDimensions::scaled.bevel), COLOUR_WHITE, FrameFlag::BorderOnly); } } diff --git a/src/script/script_gui.cpp b/src/script/script_gui.cpp index 28c9f2f379..d5353dfa82 100644 --- a/src/script/script_gui.cpp +++ b/src/script/script_gui.cpp @@ -857,7 +857,7 @@ struct ScriptDebugWindow : public Window { if (this->IsWidgetDisabled(widget)) return; CompanyID cid = (CompanyID)(widget - start); Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON); - DrawCompanyIcon(cid, CenterBounds(r.left, r.right, sprite_size.width), CenterBounds(r.top, r.bottom, sprite_size.height)); + DrawCompanyIcon(cid, CentreBounds(r.left, r.right, sprite_size.width), CentreBounds(r.top, r.bottom, sprite_size.height)); } /** diff --git a/src/ship_gui.cpp b/src/ship_gui.cpp index 7edccb0be6..7bdedb82dc 100644 --- a/src/ship_gui.cpp +++ b/src/ship_gui.cpp @@ -41,7 +41,7 @@ void DrawShipImage(const Vehicle *v, const Rect &r, VehicleID selection, EngineI int x_offs = UnScaleGUI(rect.left); int x = rtl ? r.right - width - x_offs : r.left - x_offs; /* This magic -1 offset is related to the sprite_y_offsets in build_vehicle_gui.cpp */ - int y = ScaleSpriteTrad(-1) + CenterBounds(r.top, r.bottom, 0); + int y = ScaleSpriteTrad(-1) + CentreBounds(r.top, r.bottom, 0); seq.Draw(x, y, GetVehiclePalette(v), false); if (v->cargo_cap > 0) DrawCargoIconOverlay(x, y, v->cargo_type); diff --git a/src/smallmap_gui.cpp b/src/smallmap_gui.cpp index e73183481e..eb4521be95 100644 --- a/src/smallmap_gui.cpp +++ b/src/smallmap_gui.cpp @@ -1495,8 +1495,8 @@ public: */ Point GetStationMiddle(const Station *st) const { - int x = CenterBounds(st->rect.left, st->rect.right, 0); - int y = CenterBounds(st->rect.top, st->rect.bottom, 0); + int x = CentreBounds(st->rect.left, st->rect.right, 0); + int y = CentreBounds(st->rect.top, st->rect.bottom, 0); Point ret = this->RemapTile(x, y); /* Same magic 3 as in DrawVehicles; that's where I got it from. diff --git a/src/statusbar_gui.cpp b/src/statusbar_gui.cpp index 7944176499..7dbde04b44 100644 --- a/src/statusbar_gui.cpp +++ b/src/statusbar_gui.cpp @@ -106,7 +106,7 @@ struct StatusBarWindow : Window { void DrawWidget(const Rect &r, WidgetID widget) const override { Rect tr = r.Shrink(WidgetDimensions::scaled.framerect, RectPadding::zero); - tr.top = CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)); + tr.top = CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)); switch (widget) { case WID_S_LEFT: /* Draw the date */ @@ -155,7 +155,7 @@ struct StatusBarWindow : Window { if (!this->reminder_timeout.HasFired()) { Dimension icon_size = GetSpriteSize(SPR_UNREAD_NEWS); - DrawSprite(SPR_UNREAD_NEWS, PAL_NONE, tr.right - icon_size.width, CenterBounds(r.top, r.bottom, icon_size.height)); + DrawSprite(SPR_UNREAD_NEWS, PAL_NONE, tr.right - icon_size.width, CentreBounds(r.top, r.bottom, icon_size.height)); } break; } diff --git a/src/subsidy_gui.cpp b/src/subsidy_gui.cpp index 520063d198..5f065166ef 100644 --- a/src/subsidy_gui.cpp +++ b/src/subsidy_gui.cpp @@ -150,7 +150,7 @@ struct SubsidyListWindow : Window { SpriteID icon = CargoSpec::Get(cargo_type)->GetCargoIcon(); Dimension d = GetSpriteSize(icon); Rect ir = r.WithWidth(this->cargo_icon_size.width, rtl).WithHeight(GetCharacterHeight(FS_NORMAL)); - DrawSprite(icon, PAL_NONE, CenterBounds(ir.left, ir.right, d.width), CenterBounds(ir.top, ir.bottom, this->cargo_icon_size.height) + y_offset); + DrawSprite(icon, PAL_NONE, CentreBounds(ir.left, ir.right, d.width), CentreBounds(ir.top, ir.bottom, this->cargo_icon_size.height) + y_offset); } void DrawWidget(const Rect &r, WidgetID widget) const override diff --git a/src/train_gui.cpp b/src/train_gui.cpp index a0c5804e56..4a96056f58 100644 --- a/src/train_gui.cpp +++ b/src/train_gui.cpp @@ -167,7 +167,7 @@ void DrawTrainImage(const Train *v, const Rect &r, VehicleID selection, EngineIm * the next engine after the highlight could overlap it. */ int height = ScaleSpriteTrad(12); Rect hr = {highlight_l, 0, highlight_r, height - 1}; - DrawFrameRect(hr.Translate(r.left, CenterBounds(r.top, r.bottom, height)).Expand(WidgetDimensions::scaled.bevel), COLOUR_WHITE, FrameFlag::BorderOnly); + DrawFrameRect(hr.Translate(r.left, CentreBounds(r.top, r.bottom, height)).Expand(WidgetDimensions::scaled.bevel), COLOUR_WHITE, FrameFlag::BorderOnly); } } diff --git a/src/tree_gui.cpp b/src/tree_gui.cpp index e177146cd9..109fd08e18 100644 --- a/src/tree_gui.cpp +++ b/src/tree_gui.cpp @@ -167,7 +167,7 @@ public: if (widget >= WID_BT_TYPE_BUTTON_FIRST) { const int index = widget - WID_BT_TYPE_BUTTON_FIRST; /* Trees "grow" in the centre on the bottom line of the buttons */ - DrawSprite(tree_sprites[index].sprite, tree_sprites[index].pal, CenterBounds(r.left, r.right, 0), r.bottom - ScaleGUITrad(BUTTON_BOTTOM_OFFSET)); + DrawSprite(tree_sprites[index].sprite, tree_sprites[index].pal, CentreBounds(r.left, r.right, 0), r.bottom - ScaleGUITrad(BUTTON_BOTTOM_OFFSET)); } } diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 83553cad0f..d78ba5723b 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -1058,7 +1058,7 @@ struct RefitWindow : public Window { /* Determine top & bottom position of the highlight.*/ const int height = ScaleSpriteTrad(12); - const int highlight_top = CenterBounds(r.top, r.bottom, height); + const int highlight_top = CentreBounds(r.top, r.bottom, height); const int highlight_bottom = highlight_top + height - 1; for (Train *u = Train::From(v); u != nullptr; u = u->Next()) { @@ -2667,14 +2667,14 @@ struct VehicleDetailsWindow : Window { /* We're using wallclock units. Show minutes since last serviced. */ if (TimerGameEconomy::UsingWallclockUnits()) { int minutes_since_serviced = (TimerGameEconomy::date - v->date_of_last_service).base() / EconomyTime::DAYS_IN_ECONOMY_MONTH; - DrawString(tr.left, tr.right, CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), + DrawString(tr.left, tr.right, CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), GetString(v->ServiceIntervalIsPercent() ? STR_VEHICLE_DETAILS_SERVICING_INTERVAL_PERCENT : STR_VEHICLE_DETAILS_SERVICING_INTERVAL_MINUTES, v->GetServiceInterval(), STR_VEHICLE_DETAILS_LAST_SERVICE_MINUTES_AGO, minutes_since_serviced)); break; } /* We're using calendar dates. Show the date of last service. */ - DrawString(tr.left, tr.right, CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), + DrawString(tr.left, tr.right, CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), GetString(v->ServiceIntervalIsPercent() ? STR_VEHICLE_DETAILS_SERVICING_INTERVAL_PERCENT : STR_VEHICLE_DETAILS_SERVICING_INTERVAL_DAYS, v->GetServiceInterval(), STR_VEHICLE_DETAILS_LAST_SERVICE_DATE, v->date_of_last_service)); break; @@ -3212,7 +3212,7 @@ public: TextColour text_colour = TC_FROMSTRING; std::string str = GetVehicleStatusString(v, text_colour); - DrawString(tr.left, tr.right, CenterBounds(tr.top, tr.bottom, GetCharacterHeight(FS_NORMAL)), str, text_colour, SA_HOR_CENTER); + DrawString(tr.left, tr.right, CentreBounds(tr.top, tr.bottom, GetCharacterHeight(FS_NORMAL)), str, text_colour, SA_HOR_CENTER); } void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override diff --git a/src/widget.cpp b/src/widget.cpp index 2609362796..cde3eba7bf 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -123,13 +123,13 @@ static inline Point GetAlignedPosition(const Rect &r, const Dimension &d, String if (!(align & SA_FORCE) && _current_text_dir == TD_RTL && (align & SA_HOR_MASK) != SA_HOR_CENTER) align ^= SA_RIGHT; switch (align & SA_HOR_MASK) { case SA_LEFT: p.x = r.left; break; - case SA_HOR_CENTER: p.x = CenterBounds(r.left, r.right, d.width); break; + case SA_HOR_CENTER: p.x = CentreBounds(r.left, r.right, d.width); break; case SA_RIGHT: p.x = r.right + 1 - d.width; break; default: NOT_REACHED(); } switch (align & SA_VERT_MASK) { case SA_TOP: p.y = r.top; break; - case SA_VERT_CENTER: p.y = CenterBounds(r.top, r.bottom, d.height); break; + case SA_VERT_CENTER: p.y = CentreBounds(r.top, r.bottom, d.height); break; case SA_BOTTOM: p.y = r.bottom + 1 - d.height; break; default: NOT_REACHED(); } @@ -676,7 +676,7 @@ static inline void DrawCloseBox(const Rect &r, Colours colour) d.width -= offset.x; d.height -= offset.y; int s = ScaleSpriteTrad(1); /* Offset to account for shadow of SPR_CLOSEBOX */ - DrawSprite(SPR_CLOSEBOX, (colour != COLOUR_WHITE ? TC_BLACK : TC_SILVER) | (1U << PALETTE_TEXT_RECOLOUR), CenterBounds(r.left, r.right, d.width - s) - offset.x, CenterBounds(r.top, r.bottom, d.height - s) - offset.y); + DrawSprite(SPR_CLOSEBOX, (colour != COLOUR_WHITE ? TC_BLACK : TC_SILVER) | (1U << PALETTE_TEXT_RECOLOUR), CentreBounds(r.left, r.right, d.width - s) - offset.x, CentreBounds(r.top, r.bottom, d.height - s) - offset.y); } /** @@ -727,13 +727,13 @@ static inline void DrawButtonDropdown(const Rect &r, Colours colour, bool clicke DrawFrameRect(r.left, r.top, r.right - dd_width, r.bottom, colour, clicked_button ? FrameFlag::Lowered : FrameFlags{}); DrawImageButtons(r.WithWidth(dd_width, true), WWT_DROPDOWN, colour, clicked_dropdown, SPR_ARROW_DOWN, SA_CENTER); if (!str.empty()) { - DrawString(r.left + WidgetDimensions::scaled.dropdowntext.left, r.right - dd_width - WidgetDimensions::scaled.dropdowntext.right, CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), str, TC_BLACK, align); + DrawString(r.left + WidgetDimensions::scaled.dropdowntext.left, r.right - dd_width - WidgetDimensions::scaled.dropdowntext.right, CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), str, TC_BLACK, align); } } else { DrawFrameRect(r.left + dd_width, r.top, r.right, r.bottom, colour, clicked_button ? FrameFlag::Lowered : FrameFlags{}); DrawImageButtons(r.WithWidth(dd_width, false), WWT_DROPDOWN, colour, clicked_dropdown, SPR_ARROW_DOWN, SA_CENTER); if (!str.empty()) { - DrawString(r.left + dd_width + WidgetDimensions::scaled.dropdowntext.left, r.right - WidgetDimensions::scaled.dropdowntext.right, CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), str, TC_BLACK, align); + DrawString(r.left + dd_width + WidgetDimensions::scaled.dropdowntext.left, r.right - WidgetDimensions::scaled.dropdowntext.right, CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), str, TC_BLACK, align); } } } From 5fad6897827199854fb5e11fd3f58ef4c353ba90 Mon Sep 17 00:00:00 2001 From: translators Date: Tue, 15 Apr 2025 04:44:48 +0000 Subject: [PATCH 021/766] Update: Translations from eints norwegian (bokmal): 1 change by eriksorngard --- src/lang/norwegian_bokmal.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/norwegian_bokmal.txt b/src/lang/norwegian_bokmal.txt index cc728cb0fa..3a0d7e0e86 100644 --- a/src/lang/norwegian_bokmal.txt +++ b/src/lang/norwegian_bokmal.txt @@ -2022,7 +2022,7 @@ STR_CONFIG_SETTING_SOFT_LIMIT :Største antall STR_CONFIG_SETTING_SOFT_LIMIT_HELPTEXT :Antall ikke-klebrige åpne vinduer før gamle vinduer automatisk blir lukket for å gi plass til nye vinduer STR_CONFIG_SETTING_SOFT_LIMIT_VALUE :{COMMA} ###setting-zero-is-special -STR_CONFIG_SETTING_SOFT_LIMIT_DISABLED :deaktivert +STR_CONFIG_SETTING_SOFT_LIMIT_DISABLED :Deaktivert STR_CONFIG_SETTING_ZOOM_MIN :Største zoom-inn nivå: {STRING} STR_CONFIG_SETTING_ZOOM_MIN_HELPTEXT :Største forstørrelsesnivå for synsfelt. Vær oppmerksom på at aktivering av høyere forstørrelsesnivå øker minnekravet From 301b209b87246417dc556ecbec33bb7a8e563c16 Mon Sep 17 00:00:00 2001 From: frosch Date: Tue, 15 Apr 2025 14:53:29 +0200 Subject: [PATCH 022/766] Fix: [NewGRF] Roadstop animation/randomisation was not triggered on vehicle arrival. (#14003) --- src/roadveh_cmd.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 069c4fbbb7..b56e8b772d 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -37,6 +37,7 @@ #include "framerate_type.h" #include "roadveh_cmd.h" #include "road_cmd.h" +#include "newgrf_roadstop.h" #include "table/strings.h" @@ -1453,6 +1454,8 @@ again: v->last_station_visited = st->index; RoadVehArrivesAt(v, st); v->BeginLoading(); + TriggerRoadStopRandomisation(st, v->tile, RSRT_VEH_ARRIVES); + TriggerRoadStopAnimation(st, v->tile, SAT_TRAIN_ARRIVES); } return false; } @@ -1515,6 +1518,8 @@ again: if (IsDriveThroughStopTile(v->tile) || (v->current_order.IsType(OT_GOTO_STATION) && v->current_order.GetDestination() == st->index)) { RoadVehArrivesAt(v, st); v->BeginLoading(); + TriggerRoadStopRandomisation(st, v->tile, RSRT_VEH_ARRIVES); + TriggerRoadStopAnimation(st, v->tile, SAT_TRAIN_ARRIVES); return false; } } else { From 47f0f4dd9efbd26486b1b813ae0f54b91e82c67b Mon Sep 17 00:00:00 2001 From: frosch Date: Tue, 15 Apr 2025 17:13:10 +0200 Subject: [PATCH 023/766] Fix: [NewGRF] Animation speed properties of houses and stations had wrong default. --- src/newgrf_station.h | 2 +- src/table/town_land.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/newgrf_station.h b/src/newgrf_station.h index bc66263700..1c3f5d8609 100644 --- a/src/newgrf_station.h +++ b/src/newgrf_station.h @@ -119,7 +119,7 @@ struct StationSpec : NewGRFSpecBase { disallowed_platforms(0), disallowed_lengths(0), cargo_threshold(0), cargo_triggers(0), callback_mask(0), flags(0), - animation({0, 0, 0, 0}) {} + animation({0, 0, 2, 0}) {} /** * Properties related the the grf file. * NUM_CARGO real cargo plus three pseudo cargo sprite groups. diff --git a/src/table/town_land.h b/src/table/town_land.h index 7b2f8c4b8e..ab682e8f51 100644 --- a/src/table/town_land.h +++ b/src/table/town_land.h @@ -1814,7 +1814,7 @@ static_assert(lengthof(_town_draw_tile_data) == (NEW_HOUSE_OFFSET) * 4 * 4); {ca1, ca2, ca3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \ {INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO}, \ bf, ba, true, GRFFileProps(INVALID_HOUSE_ID), HouseCallbackMasks{}, {COLOUR_BEGIN, COLOUR_BEGIN, COLOUR_BEGIN, COLOUR_BEGIN}, \ - 16, HouseExtraFlags{}, HOUSE_NO_CLASS, {0, 2, 0, 0}, 0, 0, 0, {}, {cg1, cg2, cg3}, } + 16, HouseExtraFlags{}, HOUSE_NO_CLASS, {0, 0, 2, 0}, 0, 0, 0, {}, {cg1, cg2, cg3}, } /** House specifications from original data */ extern const HouseSpec _original_house_specs[] = { /** From 03ed59a004fd43d0851de297f00d59bb0283aced Mon Sep 17 00:00:00 2001 From: frosch Date: Tue, 15 Apr 2025 17:11:17 +0200 Subject: [PATCH 024/766] Codechange: Turn AnimationStatus into an enum class. --- src/industry_cmd.cpp | 4 ++-- src/newgrf/newgrf_act0_airports.cpp | 4 ++-- src/newgrf/newgrf_act0_houses.cpp | 9 +++++---- src/newgrf/newgrf_act0_industries.cpp | 2 +- src/newgrf/newgrf_act0_objects.cpp | 2 +- src/newgrf/newgrf_act0_roadstops.cpp | 2 +- src/newgrf/newgrf_act0_stations.cpp | 2 +- src/newgrf_animation_base.h | 2 +- src/newgrf_animation_type.h | 16 +++++++++------- src/newgrf_object.h | 3 ++- src/newgrf_station.h | 4 ++-- src/station_cmd.cpp | 2 +- src/table/airporttiles.h | 4 ++-- src/table/build_industry.h | 2 +- src/table/object_land.h | 2 +- src/table/town_land.h | 2 +- 16 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index 1c2d79476d..42b8a6fd7f 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -696,7 +696,7 @@ static void AnimateTile_Industry(TileIndex tile) { IndustryGfx gfx = GetIndustryGfx(tile); - if (GetIndustryTileSpec(gfx)->animation.status != ANIM_STATUS_NO_ANIMATION) { + if (GetIndustryTileSpec(gfx)->animation.status != AnimationStatus::NoAnimation) { AnimateNewIndustryTile(tile); return; } @@ -1955,7 +1955,7 @@ static void DoCreateNewIndustry(Industry *i, TileIndex tile, IndustryType type, /* it->gfx is stored in the map. But the translated ID cur_gfx is the interesting one */ IndustryGfx cur_gfx = GetTranslatedIndustryTileID(it.gfx); const IndustryTileSpec *its = GetIndustryTileSpec(cur_gfx); - if (its->animation.status != ANIM_STATUS_NO_ANIMATION) AddAnimatedTile(cur_tile); + if (its->animation.status != AnimationStatus::NoAnimation) AddAnimatedTile(cur_tile); } } diff --git a/src/newgrf/newgrf_act0_airports.cpp b/src/newgrf/newgrf_act0_airports.cpp index 0063a78361..407b980a06 100644 --- a/src/newgrf/newgrf_act0_airports.cpp +++ b/src/newgrf/newgrf_act0_airports.cpp @@ -210,7 +210,7 @@ static ChangeInfoResult AirportTilesChangeInfo(uint first, uint last, int prop, tsp->enabled = true; - tsp->animation.status = ANIM_STATUS_NO_ANIMATION; + tsp->animation = AnimationInfo{}; tsp->grf_prop.local_id = id; tsp->grf_prop.subst_id = subs_id; @@ -239,7 +239,7 @@ static ChangeInfoResult AirportTilesChangeInfo(uint first, uint last, int prop, case 0x0F: // Animation information tsp->animation.frames = buf.ReadByte(); - tsp->animation.status = buf.ReadByte(); + tsp->animation.status = static_cast(buf.ReadByte()); break; case 0x10: // Animation speed diff --git a/src/newgrf/newgrf_act0_houses.cpp b/src/newgrf/newgrf_act0_houses.cpp index 86b5c83910..d512666fdf 100644 --- a/src/newgrf/newgrf_act0_houses.cpp +++ b/src/newgrf/newgrf_act0_houses.cpp @@ -253,11 +253,12 @@ static ChangeInfoResult TownHouseChangeInfo(uint first, uint last, int prop, Byt housespec->extra_flags = static_cast(buf.ReadByte()); break; - case 0x1A: // Animation frames - housespec->animation.frames = buf.ReadByte(); - housespec->animation.status = GB(housespec->animation.frames, 7, 1); - SB(housespec->animation.frames, 7, 1, 0); + case 0x1A: { // Animation frames + uint8_t info = buf.ReadByte(); + housespec->animation.frames = GB(info, 0, 7); + housespec->animation.status = HasBit(info, 7) ? AnimationStatus::Looping : AnimationStatus::NonLooping; break; + } case 0x1B: // Animation speed housespec->animation.speed = Clamp(buf.ReadByte(), 2, 16); diff --git a/src/newgrf/newgrf_act0_industries.cpp b/src/newgrf/newgrf_act0_industries.cpp index 5f46bc3d2f..bb20156748 100644 --- a/src/newgrf/newgrf_act0_industries.cpp +++ b/src/newgrf/newgrf_act0_industries.cpp @@ -152,7 +152,7 @@ static ChangeInfoResult IndustrytilesChangeInfo(uint first, uint last, int prop, case 0x0F: // Animation information tsp->animation.frames = buf.ReadByte(); - tsp->animation.status = buf.ReadByte(); + tsp->animation.status = static_cast(buf.ReadByte()); break; case 0x10: // Animation speed diff --git a/src/newgrf/newgrf_act0_objects.cpp b/src/newgrf/newgrf_act0_objects.cpp index fad95d7911..4b76d3620a 100644 --- a/src/newgrf/newgrf_act0_objects.cpp +++ b/src/newgrf/newgrf_act0_objects.cpp @@ -151,7 +151,7 @@ static ChangeInfoResult ObjectChangeInfo(uint first, uint last, int prop, ByteRe case 0x11: // Animation info spec->animation.frames = buf.ReadByte(); - spec->animation.status = buf.ReadByte(); + spec->animation.status = static_cast(buf.ReadByte()); break; case 0x12: // Animation speed diff --git a/src/newgrf/newgrf_act0_roadstops.cpp b/src/newgrf/newgrf_act0_roadstops.cpp index 3da28acf04..0a64a273a2 100644 --- a/src/newgrf/newgrf_act0_roadstops.cpp +++ b/src/newgrf/newgrf_act0_roadstops.cpp @@ -115,7 +115,7 @@ static ChangeInfoResult RoadStopChangeInfo(uint first, uint last, int prop, Byte case 0x0E: // Animation info rs->animation.frames = buf.ReadByte(); - rs->animation.status = buf.ReadByte(); + rs->animation.status = static_cast(buf.ReadByte()); break; case 0x0F: // Animation speed diff --git a/src/newgrf/newgrf_act0_stations.cpp b/src/newgrf/newgrf_act0_stations.cpp index 3041060040..38da11b566 100644 --- a/src/newgrf/newgrf_act0_stations.cpp +++ b/src/newgrf/newgrf_act0_stations.cpp @@ -235,7 +235,7 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR case 0x16: // Animation info statspec->animation.frames = buf.ReadByte(); - statspec->animation.status = buf.ReadByte(); + statspec->animation.status = static_cast(buf.ReadByte()); break; case 0x17: // Animation speed diff --git a/src/newgrf_animation_base.h b/src/newgrf_animation_base.h index c1c266f93d..c601179a70 100644 --- a/src/newgrf_animation_base.h +++ b/src/newgrf_animation_base.h @@ -103,7 +103,7 @@ struct AnimationBase { if (!frame_set_by_callback) { if (frame < num_frames) { frame++; - } else if (frame == num_frames && spec->animation.status == ANIM_STATUS_LOOPING) { + } else if (frame == num_frames && spec->animation.status == AnimationStatus::Looping) { /* This animation loops, so start again from the beginning */ frame = 0; } else { diff --git a/src/newgrf_animation_type.h b/src/newgrf_animation_type.h index 6835111bfb..ec1354b3fb 100644 --- a/src/newgrf_animation_type.h +++ b/src/newgrf_animation_type.h @@ -10,16 +10,18 @@ #ifndef NEWGRF_ANIMATION_TYPE_H #define NEWGRF_ANIMATION_TYPE_H -static const uint8_t ANIM_STATUS_NON_LOOPING = 0x00; ///< Animation is not looping. -static const uint8_t ANIM_STATUS_LOOPING = 0x01; ///< Animation is looping. -static const uint8_t ANIM_STATUS_NO_ANIMATION = 0xFF; ///< There is no animation. +enum class AnimationStatus : uint8_t { + NonLooping = 0x00, ///< Animation is not looping. + Looping = 0x01, ///< Animation is looping. + NoAnimation = 0xFF, ///< There is no animation. +}; /** Information about animation. */ struct AnimationInfo { - uint8_t frames; ///< The number of frames. - uint8_t status; ///< Status; 0: no looping, 1: looping, 0xFF: no animation. - uint8_t speed; ///< The speed, i.e. the amount of time between frames. - uint16_t triggers; ///< The triggers that trigger animation. + uint8_t frames = 0; ///< The number of frames. + AnimationStatus status = AnimationStatus::NoAnimation; ///< Status. + uint8_t speed = 2; ///< The speed: time between frames = 2^speed ticks. + uint16_t triggers = 0; ///< The triggers that trigger animation. }; /** Animation triggers for station. */ diff --git a/src/newgrf_object.h b/src/newgrf_object.h index 74cf12e629..c965de8aed 100644 --- a/src/newgrf_object.h +++ b/src/newgrf_object.h @@ -60,7 +60,8 @@ DECLARE_INCREMENT_DECREMENT_OPERATORS(ObjectClassID) struct ObjectSpec : NewGRFSpecBase { /* 2 because of the "normal" and "buy" sprite stacks. */ FixedGRFFileProps<2> grf_prop; ///< Properties related the the grf file - AnimationInfo animation; ///< Information about the animation. + /* Animation speed default differs from other features */ + AnimationInfo animation{0, AnimationStatus::NoAnimation, 0, 0}; ///< Information about the animation. StringID name; ///< The name for this object. LandscapeTypes climate; ///< In which climates is this object available? diff --git a/src/newgrf_station.h b/src/newgrf_station.h index 1c3f5d8609..36d1b9125b 100644 --- a/src/newgrf_station.h +++ b/src/newgrf_station.h @@ -118,8 +118,8 @@ struct StationSpec : NewGRFSpecBase { StationSpec() : name(0), disallowed_platforms(0), disallowed_lengths(0), cargo_threshold(0), cargo_triggers(0), - callback_mask(0), flags(0), - animation({0, 0, 2, 0}) {} + callback_mask(0), flags(0) + {} /** * Properties related the the grf file. * NUM_CARGO real cargo plus three pseudo cargo sprite groups. diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index ad5e88dd96..070f2ceb55 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -2617,7 +2617,7 @@ CommandCost CmdBuildAirport(DoCommandFlags flags, TileIndex tile, uint8_t airpor SetStationTileRandomBits(t, GB(Random(), 0, 4)); st->airport.Add(iter); - if (AirportTileSpec::Get(GetTranslatedAirportTileID(iter.GetStationGfx()))->animation.status != ANIM_STATUS_NO_ANIMATION) AddAnimatedTile(t); + if (AirportTileSpec::Get(GetTranslatedAirportTileID(iter.GetStationGfx()))->animation.status != AnimationStatus::NoAnimation) AddAnimatedTile(t); } /* Only call the animation trigger after all tiles have been built */ diff --git a/src/table/airporttiles.h b/src/table/airporttiles.h index 1776ea0505..0003b91065 100644 --- a/src/table/airporttiles.h +++ b/src/table/airporttiles.h @@ -11,9 +11,9 @@ #define AIRPORTTILES_H /** Writes all airport tile properties in the AirportTile struct */ -#define AT(num_frames, anim_speed) {{num_frames, ANIM_STATUS_LOOPING, anim_speed, 0}, STR_NULL, AirportTileCallbackMasks{}, 0, true, GRFFileProps(INVALID_AIRPORTTILE), {}} +#define AT(num_frames, anim_speed) {{num_frames, AnimationStatus::Looping, anim_speed, 0}, STR_NULL, AirportTileCallbackMasks{}, 0, true, GRFFileProps(INVALID_AIRPORTTILE), {}} /** Writes an airport tile without animation in the AirportTile struct */ -#define AT_NOANIM {{0, ANIM_STATUS_NO_ANIMATION, 2, 0}, STR_NULL, AirportTileCallbackMasks{}, 0, true, GRFFileProps(INVALID_AIRPORTTILE), {}} +#define AT_NOANIM {AnimationInfo{}, STR_NULL, AirportTileCallbackMasks{}, 0, true, GRFFileProps(INVALID_AIRPORTTILE), {}} /** * All default airport tiles. diff --git a/src/table/build_industry.h b/src/table/build_industry.h index 258c368170..1f2608d10e 100644 --- a/src/table/build_industry.h +++ b/src/table/build_industry.h @@ -1533,7 +1533,7 @@ static const IndustrySpec _origin_industry_specs[NEW_INDUSTRYOFFSET] = { */ #define MT(ca1, c1, ca2, c2, ca3, c3, sl, a1, a2, a3) { \ {INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO}, \ - {ca1, ca2, ca3}, sl, a1, a2, a3, IndustryTileCallbackMasks{}, {0, ANIM_STATUS_NO_ANIMATION, 2, 0}, IndustryTileSpecialFlags{}, true, GRFFileProps(INVALID_INDUSTRYTILE), {}, {c1, c2, c3} \ + {ca1, ca2, ca3}, sl, a1, a2, a3, IndustryTileCallbackMasks{}, AnimationInfo{}, IndustryTileSpecialFlags{}, true, GRFFileProps(INVALID_INDUSTRYTILE), {}, {c1, c2, c3} \ } static const IndustryTileSpec _origin_industry_tile_specs[NEW_INDUSTRYTILEOFFSET] = { /* Coal Mine */ diff --git a/src/table/object_land.h b/src/table/object_land.h index 39574a82c6..0ea2ab8eae 100644 --- a/src/table/object_land.h +++ b/src/table/object_land.h @@ -104,7 +104,7 @@ static const DrawTileSpriteSpan _object_hq[] = { #undef TILE_SPRITE_LINE #undef TILE_SPRITE_LINE_NOTHING -#define M(name, size, build_cost_multiplier, clear_cost_multiplier, height, climate, gen_amount, flags) {{INVALID_OBJECT_CLASS, 0}, FixedGRFFileProps<2>{}, {0, 0, 0, 0}, name, climate, size, build_cost_multiplier, clear_cost_multiplier, TimerGameCalendar::Date{}, CalendarTime::MAX_DATE + 1, flags, ObjectCallbackMasks{}, height, 1, gen_amount, {}} +#define M(name, size, build_cost_multiplier, clear_cost_multiplier, height, climate, gen_amount, flags) {{INVALID_OBJECT_CLASS, 0}, FixedGRFFileProps<2>{}, AnimationInfo{}, name, climate, size, build_cost_multiplier, clear_cost_multiplier, TimerGameCalendar::Date{}, CalendarTime::MAX_DATE + 1, flags, ObjectCallbackMasks{}, height, 1, gen_amount, {}} /* Climates * T = Temperate diff --git a/src/table/town_land.h b/src/table/town_land.h index ab682e8f51..776fb23231 100644 --- a/src/table/town_land.h +++ b/src/table/town_land.h @@ -1814,7 +1814,7 @@ static_assert(lengthof(_town_draw_tile_data) == (NEW_HOUSE_OFFSET) * 4 * 4); {ca1, ca2, ca3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \ {INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO, INVALID_CARGO}, \ bf, ba, true, GRFFileProps(INVALID_HOUSE_ID), HouseCallbackMasks{}, {COLOUR_BEGIN, COLOUR_BEGIN, COLOUR_BEGIN, COLOUR_BEGIN}, \ - 16, HouseExtraFlags{}, HOUSE_NO_CLASS, {0, 0, 2, 0}, 0, 0, 0, {}, {cg1, cg2, cg3}, } + 16, HouseExtraFlags{}, HOUSE_NO_CLASS, AnimationInfo{}, 0, 0, 0, {}, {cg1, cg2, cg3}, } /** House specifications from original data */ extern const HouseSpec _original_house_specs[] = { /** From fa0814e257f692fcfd5b53dcadd5b283bf9fa2b7 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 15 Apr 2025 21:57:13 +0100 Subject: [PATCH 025/766] Codefix: Codestyle consistency fixes. (#14008) --- src/core/string_builder.hpp | 5 ++--- src/core/string_consumer.hpp | 8 ++++---- src/core/string_inplace.hpp | 6 ++---- src/string.cpp | 2 +- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/core/string_builder.hpp b/src/core/string_builder.hpp index 77fba335fd..c6058bab05 100644 --- a/src/core/string_builder.hpp +++ b/src/core/string_builder.hpp @@ -53,7 +53,7 @@ public: /** * Append integer 'value' in given number 'base'. */ - template + template void PutIntegerBase(T value, int base) { std::array buf; @@ -67,8 +67,7 @@ public: /** * Compose data into a growing std::string. */ -class StringBuilder final : public BaseStringBuilder -{ +class StringBuilder final : public BaseStringBuilder { std::string *dest; public: /** diff --git a/src/core/string_consumer.hpp b/src/core/string_consumer.hpp index b85bf65eb9..e4e9145fa3 100644 --- a/src/core/string_consumer.hpp +++ b/src/core/string_consumer.hpp @@ -800,7 +800,7 @@ public: void SkipUntilUtf8(char32_t c, SeparatorUsage sep); private: - template + template [[nodiscard]] static std::pair ParseIntegerBase(std::string_view src, int base, bool log_errors) { if (base == 0) { @@ -850,7 +850,7 @@ public: * @return Length of string match, and parsed value. * @note The parser rejects leading whitespace and unary plus. */ - template + template [[nodiscard]] std::pair PeekIntegerBase(int base) const { return ParseIntegerBase(this->src.substr(this->position), base, false); @@ -861,7 +861,7 @@ public: * @return Parsed value, if valid. * @note The parser rejects leading whitespace and unary plus. */ - template + template [[nodiscard]] std::optional TryReadIntegerBase(int base) { auto [len, value] = this->PeekIntegerBase(base); @@ -876,7 +876,7 @@ public: * @note The reader is advanced, even if no valid data was present. * @note The parser rejects leading whitespace and unary plus. */ - template + template [[nodiscard]] T ReadIntegerBase(int base, T def = 0) { auto [len, value] = ParseIntegerBase(this->src.substr(this->position), base, true); diff --git a/src/core/string_inplace.hpp b/src/core/string_inplace.hpp index 855ae55896..eaf16752b0 100644 --- a/src/core/string_inplace.hpp +++ b/src/core/string_inplace.hpp @@ -18,8 +18,7 @@ /** * Builder implementation for InPlaceReplacement. */ -class InPlaceBuilder final : public BaseStringBuilder -{ +class InPlaceBuilder final : public BaseStringBuilder { std::span dest; size_type position = 0; const StringConsumer &consumer; @@ -90,8 +89,7 @@ public: * - The Builder writes data to the buffer, replacing already consumed data. * - The Builder asserts, if it overtakes the consumer. */ -class InPlaceReplacement -{ +class InPlaceReplacement { public: StringConsumer consumer; ///< Consumer from shared buffer InPlaceBuilder builder; ///< Builder into shared buffer diff --git a/src/string.cpp b/src/string.cpp index a9d72efab9..21cd8f0ced 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -117,7 +117,7 @@ static bool IsSccEncodedCode(char32_t c) * @param consumer The string to validate. * @param settings The settings for the string validation. */ -template +template static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings) { /* Assume the ABSOLUTE WORST to be in str as it comes from the outside. */ From b57f3262ecb7c7269e20d29eb08863f20c3e821f Mon Sep 17 00:00:00 2001 From: translators Date: Wed, 16 Apr 2025 04:44:48 +0000 Subject: [PATCH 026/766] Update: Translations from eints spanish: 101 changes by MontyMontana --- src/lang/spanish.txt | 158 +++++++++++++++++++++++++++---------------- 1 file changed, 101 insertions(+), 57 deletions(-) diff --git a/src/lang/spanish.txt b/src/lang/spanish.txt index 0b1b39c029..4cc56411b5 100644 --- a/src/lang/spanish.txt +++ b/src/lang/spanish.txt @@ -453,6 +453,12 @@ STR_SETTINGS_MENU_SANDBOX_OPTIONS :Opciones sandbo STR_SETTINGS_MENU_TRANSPARENCY_OPTIONS :Opciones de transparencia STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED :Ver nombres de municipios STR_SETTINGS_MENU_STATION_NAMES_DISPLAYED :Ver nombres de estaciones +STR_SETTINGS_MENU_STATION_NAMES_TRAIN :Estaciones de tren +STR_SETTINGS_MENU_STATION_NAMES_LORRY :Estaciones de camión +STR_SETTINGS_MENU_STATION_NAMES_BUS :Estaciones de autobús +STR_SETTINGS_MENU_STATION_NAMES_SHIP :Muelles +STR_SETTINGS_MENU_STATION_NAMES_PLANE :Aeropuertos +STR_SETTINGS_MENU_STATION_NAMES_GHOST :Vestigios STR_SETTINGS_MENU_WAYPOINTS_DISPLAYED :Ver puntos de ruta STR_SETTINGS_MENU_SIGNS_DISPLAYED :Ver letreros STR_SETTINGS_MENU_SHOW_COMPETITOR_SIGNS :Letreros y nombres de competidor mostrados @@ -928,7 +934,7 @@ STR_NEWS_SHOW_VEHICLE_GROUP_TOOLTIP :{BLACK}Abre la STR_NEWS_STATION_NO_LONGER_ACCEPTS_CARGO_LIST :{WHITE}{STATION} ya no acepta: {CARGO_LIST} STR_NEWS_STATION_NOW_ACCEPTS_CARGO_LIST :{WHITE}{STATION} ahora acepta: {CARGO_LIST} -STR_NEWS_OFFER_OF_SUBSIDY_EXPIRED :{BIG_FONT}{BLACK}Oferta de subvención terminada:{}{}{STRING} de {STRING} a {STRING} ya no conlleva una subvención +STR_NEWS_OFFER_OF_SUBSIDY_EXPIRED :{BIG_FONT}{BLACK}Oferta de subvención vencida:{}{}{STRING} de {STRING} a {STRING} ya no conlleva una subvención STR_NEWS_SUBSIDY_WITHDRAWN_SERVICE :{BIG_FONT}{BLACK}Subvención retirada:{}{}El servicio de {STRING} desde {STRING} a {STRING} ya no está subvencionado STR_NEWS_SERVICE_SUBSIDY_OFFERED :{BIG_FONT}{BLACK}Se ofrece subvención:{}{}¡Al primer servicio de {STRING} que cubra la línea desde {STRING} a {STRING} se le otorgará una subvención durante {UNITS_YEARS_OR_MINUTES} por parte de las autoridades locales! ###length 4 @@ -1018,6 +1024,7 @@ STR_GAME_OPTIONS_CURRENCY_IDR :Rupia indonesia STR_GAME_OPTIONS_CURRENCY_MYR :Ringgit malasio STR_GAME_OPTIONS_CURRENCY_LVL :Lats letón STR_GAME_OPTIONS_CURRENCY_PTE :Escudo portugués +STR_GAME_OPTIONS_CURRENCY_UAH :Grivna ucraniana STR_GAME_OPTIONS_AUTOSAVE_FRAME :{BLACK}Autoguardado STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_TOOLTIP :{BLACK}Selección del intervalo entre guardados automáticos del juego @@ -1211,7 +1218,7 @@ STR_CITY_APPROVAL_TOLERANT :Tolerante STR_CITY_APPROVAL_HOSTILE :Hostil STR_CITY_APPROVAL_PERMISSIVE :Permisiva (sin efecto sobre las acciones de las compañías) -STR_WARNING_NO_SUITABLE_AI :{WHITE}No se han encontrado IA apropiadas...{}Puedes descargar IA a través del sistema de 'Contenido Online' +STR_WARNING_NO_SUITABLE_AI :{WHITE}No se han encontrado IA apropiadas...{}Puedes descargar IA a través del sistema de 'Contenido en línea' # Settings tree window STR_CONFIG_SETTING_TREE_CAPTION :{WHITE}Configuración @@ -1980,7 +1987,12 @@ STR_CONFIG_SETTING_TOWN_FOUNDING_FORBIDDEN :Prohibido STR_CONFIG_SETTING_TOWN_FOUNDING_ALLOWED :Permitido STR_CONFIG_SETTING_TOWN_FOUNDING_ALLOWED_CUSTOM_LAYOUT :Permitido, patrón de carreteras personalizado +STR_CONFIG_SETTING_HOUSE_PLACER :Colocación individual de casas: {STRING} +STR_CONFIG_SETTING_HOUSE_PLACER_HELPTEXT :Activar esta opción permite a los jugadores colocar casas manualmente ###length 3 +STR_CONFIG_SETTING_HOUSE_PLACER_FORBIDDEN :Prohibido +STR_CONFIG_SETTING_HOUSE_PLACER_ALLOWED :Permitido +STR_CONFIG_SETTING_HOUSE_PLACER_FULLY_CONSTRUCTED :Permitido, construcción completa STR_CONFIG_SETTING_TOWN_CARGOGENMODE :Generación de carga en municipios: {STRING} STR_CONFIG_SETTING_TOWN_CARGOGENMODE_HELPTEXT :Cantidad de carga que es producida por las casas del municipio, en relación a la población total del municipio.{}Crecimiento cuadrático: Un municipio de doble tamaño genera el cuádruple de pasajeros.{}Crecimiento lineal: Un municipio de doble tamaño genera el doble de pasajeros @@ -2009,7 +2021,7 @@ STR_CONFIG_SETTING_SOFT_LIMIT :Máximo número STR_CONFIG_SETTING_SOFT_LIMIT_HELPTEXT :Número máximo de ventanas sin anclar que pueden estar abiertas antes de que las más antiguas se cierren automáticamente para dejar paso a las nuevas STR_CONFIG_SETTING_SOFT_LIMIT_VALUE :{COMMA} ###setting-zero-is-special -STR_CONFIG_SETTING_SOFT_LIMIT_DISABLED :deshabilitado +STR_CONFIG_SETTING_SOFT_LIMIT_DISABLED :Deshabilitado STR_CONFIG_SETTING_ZOOM_MIN :Máximo acercamiento: {STRING} STR_CONFIG_SETTING_ZOOM_MIN_HELPTEXT :Nivel máximo de acercamiento para las ventanas de vista y la vista principal. Los niveles altos de acercamiento incrementan el consumo de memoria del juego @@ -2194,7 +2206,7 @@ STR_INTRO_HIGHSCORE :{BLACK}Tabla de STR_INTRO_HELP :{BLACK}Ayuda y Manuales STR_INTRO_CONFIG_SETTINGS_TREE :{BLACK}Configuración STR_INTRO_NEWGRF_SETTINGS :{BLACK}Configuración NewGRF -STR_INTRO_ONLINE_CONTENT :{BLACK}Contenido Online +STR_INTRO_ONLINE_CONTENT :{BLACK}Contenido en línea STR_INTRO_AI_SETTINGS :{BLACK}Configuración de IA STR_INTRO_GAMESCRIPT_SETTINGS :{BLACK}Scripts de Juego STR_INTRO_QUIT :{BLACK}Salir @@ -2243,6 +2255,7 @@ STR_HELP_WINDOW_README :{BLACK}Léeme STR_HELP_WINDOW_CHANGELOG :{BLACK}Registro de cambios STR_HELP_WINDOW_KNOWN_BUGS :{BLACK}Fallos conocidos STR_HELP_WINDOW_LICENSE :{BLACK}Licencia +STR_HELP_WINDOW_FONTS :{BLACK}Fuentes STR_HELP_WINDOW_MAIN_WEBSITE :{BLACK}OpenTTD STR_HELP_WINDOW_MANUAL_WIKI :{BLACK}Manual / Wiki STR_HELP_WINDOW_BUGTRACKER :{BLACK}Informar de un problema @@ -2631,13 +2644,14 @@ STR_NETWORK_SERVER_MESSAGE_GAME_REASON_LINK_GRAPH :esperando actua STR_NETWORK_MESSAGE_CLIENT_LEAVING :abandonando STR_NETWORK_MESSAGE_CLIENT_JOINED :*** {STRING} se ha unido a la partida STR_NETWORK_MESSAGE_CLIENT_JOINED_ID :*** {STRING} se ha unido a la partida (Cliente #{NUM}) +STR_NETWORK_MESSAGE_CLIENT_COMPANY_JOIN :*** {STRING} se ha unido a {STRING} STR_NETWORK_MESSAGE_CLIENT_COMPANY_SPECTATE :*** {STRING} se ha unido como espectador STR_NETWORK_MESSAGE_CLIENT_COMPANY_NEW :*** {STRING} ha creado una empresa nueva (#{NUM}) STR_NETWORK_MESSAGE_CLIENT_LEFT :*** {STRING} ha abandonado el juego ({STRING}) STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} ha cambiado su nombre a {STRING} STR_NETWORK_MESSAGE_GIVE_MONEY :*** {STRING} ha transferido {CURRENCY_LONG} a {STRING} STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}El servidor ha cerrado la sesión -STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}El servidor está reiniciando...{}Espera por favor... +STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}El servidor se está reiniciando...{}{}Espera por favor... STR_NETWORK_MESSAGE_KICKED :*** {STRING} ha sido expulsado. Razón: ({STRING}) STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED :{WHITE}El registro del servidor ha fallado @@ -2646,7 +2660,7 @@ STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}Tu servi STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL :{WHITE}Los demás jugadores no podrán conectarse a tu servidor # Content downloading window -STR_CONTENT_TITLE :{WHITE}Descarga de contenido +STR_CONTENT_TITLE :{WHITE}Contenido en línea STR_CONTENT_TYPE_CAPTION :{BLACK}Tipo STR_CONTENT_TYPE_CAPTION_TOOLTIP :{BLACK}Tipo de contenido STR_CONTENT_NAME_CAPTION :{BLACK}Nombre @@ -2845,6 +2859,7 @@ STR_HOUSE_PICKER_YEARS_FROM :{BLACK}Años: { STR_HOUSE_PICKER_YEARS_UNTIL :{BLACK}Años: {ORANGE}Hasta {NUM} STR_HOUSE_PICKER_SIZE :{BLACK}Tamaño: {ORANGE}{NUM}x{NUM} casillas STR_HOUSE_PICKER_CARGO_ACCEPTED :{BLACK}Carga aceptada: {ORANGE} +STR_HOUSE_PICKER_CARGO_PRODUCED :{BLACK}Carga producida: {ORANGE}{CARGO_LIST} STR_HOUSE_PICKER_CLASS_ZONE1 :Afueras STR_HOUSE_PICKER_CLASS_ZONE2 :Periferia @@ -2852,6 +2867,10 @@ STR_HOUSE_PICKER_CLASS_ZONE3 :Suburbios exter STR_HOUSE_PICKER_CLASS_ZONE4 :Suburbios interiores STR_HOUSE_PICKER_CLASS_ZONE5 :Centro urbano +STR_HOUSE_PICKER_PROTECT_TITLE :Evitar mejoras +STR_HOUSE_PICKER_PROTECT_TOOLTIP :Selecciona si esta casa estará protegida de reemplazos cuando el municipio crezca +STR_HOUSE_PICKER_PROTECT_OFF :No +STR_HOUSE_PICKER_PROTECT_ON :Sí STR_STATION_CLASS_DFLT :Por defecto STR_STATION_CLASS_DFLT_STATION :Estación por defecto @@ -2885,6 +2904,7 @@ STR_SELECT_ROAD_BRIDGE_CAPTION :{WHITE}Selecci STR_SELECT_BRIDGE_SELECTION_TOOLTIP :{BLACK}Selección de puente - Clica en el puente elegido para construirlo STR_SELECT_BRIDGE_INFO_NAME :{GOLD}{STRING} STR_SELECT_BRIDGE_INFO_NAME_MAX_SPEED :{GOLD}{STRING},{} {VELOCITY} +STR_SELECT_BRIDGE_INFO_NAME_COST :{GOLD}{STRING},{} {WHITE}{CURRENCY_LONG} STR_SELECT_BRIDGE_INFO_NAME_MAX_SPEED_COST :{GOLD}{STRING},{} {VELOCITY} {WHITE}{CURRENCY_LONG} STR_BRIDGE_NAME_SUSPENSION_STEEL :Colgante, Acero STR_BRIDGE_NAME_GIRDER_STEEL :Tirantes, Acero @@ -2943,7 +2963,7 @@ STR_STATION_BUILD_CARGO_TRAM_ORIENTATION_TOOLTIP :{BLACK}Seleccio # Waterways toolbar (last two for SE only) STR_WATERWAYS_TOOLBAR_CAPTION :{WHITE}Construcción de Canales STR_WATERWAYS_TOOLBAR_CAPTION_SE :{WHITE}Canales -STR_WATERWAYS_TOOLBAR_BUILD_CANALS_TOOLTIP :{BLACK}Construye canales. Con Mayús sólo muestra una estimación del coste +STR_WATERWAYS_TOOLBAR_BUILD_CANALS_TOOLTIP :{BLACK}Construye canales. Ctrl+clic+arrastrar permite seleccionar un área diagonalmente. Con Mayús sólo muestra una estimación del coste STR_WATERWAYS_TOOLBAR_BUILD_LOCKS_TOOLTIP :{BLACK}Construye esclusas. Con Mayús sólo muestra una estimación del coste STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP :{BLACK}Contruye un astillero (para comprar y dar mantenimiento a barcos). Con Mayús sólo muestra una estimación del coste STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP :{BLACK}Construye muelles. Ctrl+clic para seleccionar otra estación a la que unirlo. Con Mayús sólo muestra una estimación del coste @@ -3128,6 +3148,8 @@ STR_LANG_AREA_INFORMATION_TRAM_TYPE :{BLACK}Tipo de STR_LANG_AREA_INFORMATION_RAIL_SPEED_LIMIT :{BLACK}Límite de velocidad de ferrocarril: {LTBLUE}{VELOCITY} STR_LANG_AREA_INFORMATION_ROAD_SPEED_LIMIT :{BLACK}Límite de velocidad de carretera: {LTBLUE}{VELOCITY} STR_LANG_AREA_INFORMATION_TRAM_SPEED_LIMIT :{BLACK}Límite de velocidad de tranvía: {LTBLUE}{VELOCITY} +STR_LAND_AREA_INFORMATION_TOWN_CAN_UPGRADE :{BLACK}El municipio puede reemplazarlo: {LTBLUE}Sí +STR_LAND_AREA_INFORMATION_TOWN_CANNOT_UPGRADE :{BLACK}El municipio puede reemplazarlo: {LTBLUE}No # Description of land area of different tiles STR_LAI_CLEAR_DESCRIPTION_ROCKS :Rocas @@ -3136,6 +3158,9 @@ STR_LAI_CLEAR_DESCRIPTION_BARE_LAND :Terreno desnudo STR_LAI_CLEAR_DESCRIPTION_GRASS :Pastos STR_LAI_CLEAR_DESCRIPTION_FIELDS :Campos STR_LAI_CLEAR_DESCRIPTION_DESERT :Desierto +STR_LAI_CLEAR_DESCRIPTION_SNOWY_ROCKS :Rocas cubiertas con nieve +STR_LAI_CLEAR_DESCRIPTION_SNOWY_ROUGH_LAND :Terreno irregular cubierto con nieve +STR_LAI_CLEAR_DESCRIPTION_SNOWY_GRASS :Hierba cubierta de nieve STR_LAI_RAIL_DESCRIPTION_TRACK :Vía de ferrocarril STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_SIGNALS :Vía de ferrocarril con señales de bloque @@ -3607,17 +3632,17 @@ STR_NEWGRF_LIST_COMPATIBLE :{YELLOW}Archivo STR_NEWGRF_LIST_MISSING :{RED}Faltan archivos # NewGRF 'it's broken' warnings -STR_NEWGRF_BROKEN :{WHITE}Es probable que el funcionamiento del NewGRF '{0:STRING}' cause desincronizaciones o fallos -STR_NEWGRF_BROKEN_POWERED_WAGON :{WHITE}Ha cambiado el estado 'vagón motorizado' '{1:ENGINE}' cuando no estaba dentro de un depósito -STR_NEWGRF_BROKEN_VEHICLE_LENGTH :{WHITE}Ha cambiado la longitud del vehículo '{1:ENGINE}' cuando no estaba dentro de un depósito -STR_NEWGRF_BROKEN_CAPACITY :{WHITE}Ha cambiado la capacidad del vehículo '{1:ENGINE}' fuera de un depósito o mientras no se estaba reformando +STR_NEWGRF_BROKEN :{WHITE}Es probable que el funcionamiento del NewGRF '{PUSH_COLOUR}{0:STRING}{POP_COLOUR}' cause desincronizaciones o fallos +STR_NEWGRF_BROKEN_POWERED_WAGON :{WHITE}Ha cambiado el estado 'vagón motorizado' '{PUSH_COLOUR}{1:ENGINE}{POP_COLOUR}' cuando no estaba dentro de un depósito +STR_NEWGRF_BROKEN_VEHICLE_LENGTH :{WHITE}Ha cambiado la longitud del vehículo '{PUSH_COLOUR}{1:ENGINE}{POP_COLOUR}' cuando no estaba dentro de un depósito +STR_NEWGRF_BROKEN_CAPACITY :{WHITE}Ha cambiado la capacidad del vehículo '{PUSH_COLOUR}{1:ENGINE}{POP_COLOUR}' cuando no estaba dentro de un depósito o mientras no se estaba reformando STR_BROKEN_VEHICLE_LENGTH :{WHITE}El tren '{VEHICLE}' perteneciente a '{COMPANY}' tiene una longitud incorrecta. Esto puede deberse a problemas con los NewGRF. El juego puede desincronizarse o fallar por completo -STR_NEWGRF_BUGGY :{WHITE}El NewGRF '{0:STRING}' da información incorrecta -STR_NEWGRF_BUGGY_ARTICULATED_CARGO :{WHITE}La información de carga/reforma para '{1:ENGINE}' difiere de la de lista de compra después de la construcción. Esto puede causar que la renovación/reemplazo automático no haga la reforma correcta -STR_NEWGRF_BUGGY_ENDLESS_PRODUCTION_CALLBACK :{WHITE}'{1:STRING}' ha causado un bucle sin fin en la 'callback' de producción +STR_NEWGRF_BUGGY :{WHITE}El NewGRF '{PUSH_COLOUR}{0:STRING}{POP_COLOUR}' da información incorrecta +STR_NEWGRF_BUGGY_ARTICULATED_CARGO :{WHITE}La información de carga/reforma para '{PUSH_COLOUR}{1:ENGINE}{POP_COLOUR}' difiere de la de lista de compra después de la construcción. Esto puede causar que la renovación/reemplazo automático no haga la reforma correcta +STR_NEWGRF_BUGGY_ENDLESS_PRODUCTION_CALLBACK :{WHITE}'{PUSH_COLOUR}{1:STRING}{POP_COLOUR}' ha causado un bucle sin fin en la 'callback' de producción STR_NEWGRF_BUGGY_UNKNOWN_CALLBACK_RESULT :{WHITE}'Callback' {1:HEX} ha devuelto el resultado desconocido o inválido {2:HEX} -STR_NEWGRF_BUGGY_INVALID_CARGO_PRODUCTION_CALLBACK :{WHITE}'{1:STRING}' ha devuelto tipo de carga inválida en el 'callback' de producción en {2:HEX} +STR_NEWGRF_BUGGY_INVALID_CARGO_PRODUCTION_CALLBACK :{WHITE}'{PUSH_COLOUR}{1:STRING}{POP_COLOUR}' ha devuelto tipo de carga inválida en el 'callback' de producción en {2:HEX} # 'User removed essential NewGRFs'-placeholders for stuff without specs STR_NEWGRF_INVALID_CARGO : @@ -4013,6 +4038,10 @@ STR_INDUSTRY_VIEW_PRODUCES_N_CARGO :{BLACK}Produce: STR_INDUSTRY_VIEW_CARGO_LIST_EXTENSION :, {STRING}{STRING} STR_INDUSTRY_VIEW_REQUIRES :{BLACK}Necesita: +STR_INDUSTRY_VIEW_ACCEPT_CARGO_SUFFIX :{YELLOW}{0:STRING}{BLACK}{3:STRING} +STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_SUFFIX :{YELLOW}{0:STRING}{BLACK}: {1:CARGO_SHORT} esperando{3:STRING} +STR_INDUSTRY_VIEW_ACCEPT_CARGO_NOSUFFIX :{YELLOW}{0:STRING} +STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_NOSUFFIX :{YELLOW}{0:STRING}{BLACK}: {1:CARGO_SHORT} en espera STR_CONFIG_GAME_PRODUCTION :{WHITE}Cambiar producción (múltiplos de 8, máximo 2040) STR_CONFIG_GAME_PRODUCTION_LEVEL :{WHITE}Cambiar nivel de producción (porcentaje, hasta un máximo de 800%) @@ -4670,54 +4699,56 @@ STR_ORDER_ROAD_VEHICLE_DEPOT :Depósito de ve STR_ORDER_SHIP_DEPOT :Astillero de barcos ###next-name-looks-similar +STR_ORDER_GO_TO_NEAREST_HANGAR_FORMAT :{STRING}l hangar más cercano +STR_ORDER_GO_TO_NEAREST_DEPOT_FORMAT :{STRING}l {STRING} más cercano STR_ORDER_GO_TO_DEPOT_FORMAT :{STRING} {DEPOT} -STR_ORDER_REFIT_ORDER :(Reformar a {STRING}) -STR_ORDER_REFIT_STOP_ORDER :(Reformar a {STRING} y detenerse) -STR_ORDER_STOP_ORDER :(Detenerse) +STR_ORDER_REFIT_ORDER :{SPACE}(Reformar a {STRING}) +STR_ORDER_REFIT_STOP_ORDER :{SPACE}(Reformar a {STRING} y detenerse) +STR_ORDER_STOP_ORDER :{SPACE}(Detenerse) -STR_ORDER_WAIT_TO_UNBUNCH :(Esperar para distanciarse) +STR_ORDER_WAIT_TO_UNBUNCH :{SPACE}(Esperar para distanciarse) STR_ORDER_GO_TO_STATION :{STRING} {STATION} STR_ORDER_GO_TO_STATION_CAN_T_USE_STATION :{PUSH_COLOUR}{RED}(No puede usar la estación){POP_COLOUR} {STRING} {STATION} -STR_ORDER_IMPLICIT :(Implícita) +STR_ORDER_IMPLICIT :{SPACE}(Implícita) -STR_ORDER_FULL_LOAD :(Carga completa) -STR_ORDER_FULL_LOAD_ANY :(Carga completa cualquier carga) -STR_ORDER_NO_LOAD :(Sin carga) -STR_ORDER_UNLOAD :(Descargar y tomar carga) -STR_ORDER_UNLOAD_FULL_LOAD :(Descargar y esperar a carga completa) -STR_ORDER_UNLOAD_FULL_LOAD_ANY :(Descargar y esperar a cualquier carga completa) -STR_ORDER_UNLOAD_NO_LOAD :(Descargar y dejar vacío) -STR_ORDER_TRANSFER :(Transferir y tomar carga) -STR_ORDER_TRANSFER_FULL_LOAD :(Transferir y esperar a carga completa) -STR_ORDER_TRANSFER_FULL_LOAD_ANY :(Transferir y esperar a cualquier carga completa) -STR_ORDER_TRANSFER_NO_LOAD :(Transferir y dejar vacío) -STR_ORDER_NO_UNLOAD :(No descargar y tomar carga) -STR_ORDER_NO_UNLOAD_FULL_LOAD :(No descargar y esperar a carga completa) -STR_ORDER_NO_UNLOAD_FULL_LOAD_ANY :(No descargar y esperar a cualquier carga completa) -STR_ORDER_NO_UNLOAD_NO_LOAD :(No descargar y no cargar) +STR_ORDER_FULL_LOAD :{SPACE}(Carga completa) +STR_ORDER_FULL_LOAD_ANY :{SPACE}(Carga completa cualquier carga) +STR_ORDER_NO_LOAD :{SPACE}(Sin carga) +STR_ORDER_UNLOAD :{SPACE}(Descargar y tomar carga) +STR_ORDER_UNLOAD_FULL_LOAD :{SPACE}(Descargar y esperar a carga completa) +STR_ORDER_UNLOAD_FULL_LOAD_ANY :{SPACE}(Descargar y esperar a cualquier carga completa) +STR_ORDER_UNLOAD_NO_LOAD :{SPACE}(Descargar y dejar vacío) +STR_ORDER_TRANSFER :{SPACE}(Transferir y cargar) +STR_ORDER_TRANSFER_FULL_LOAD :{SPACE}(Transferir y esperar a carga completa) +STR_ORDER_TRANSFER_FULL_LOAD_ANY :{SPACE}(Transferir y esperar a cualquier carga completa) +STR_ORDER_TRANSFER_NO_LOAD :{SPACE}(Transferir y dejar vacío) +STR_ORDER_NO_UNLOAD :{SPACE}(No descargar y tomar carga) +STR_ORDER_NO_UNLOAD_FULL_LOAD :{SPACE}(No descargar y esperar a carga completa) +STR_ORDER_NO_UNLOAD_FULL_LOAD_ANY :{SPACE}(No descargar y esperar a cualquier carga completa) +STR_ORDER_NO_UNLOAD_NO_LOAD :{SPACE}(No descargar y no cargar) -STR_ORDER_AUTO_REFIT :(Reforma a {STRING}) -STR_ORDER_FULL_LOAD_REFIT :(Carga completa con reforma a {STRING}) -STR_ORDER_FULL_LOAD_ANY_REFIT :(Cualquier carga completa con reforma a {STRING}) -STR_ORDER_UNLOAD_REFIT :(Descargar y tomar carga con reforma a {STRING}) -STR_ORDER_UNLOAD_FULL_LOAD_REFIT :(Descargar y esperar carga completa con reforma a {STRING}) -STR_ORDER_UNLOAD_FULL_LOAD_ANY_REFIT :(Descargar y esperar a cualquier carga completa con reforma a {STRING}) -STR_ORDER_TRANSFER_REFIT :(Transferir y tomar carga con reforma a {STRING}) -STR_ORDER_TRANSFER_FULL_LOAD_REFIT :(Transferir y esperar a carga completa con reforma a {STRING}) -STR_ORDER_TRANSFER_FULL_LOAD_ANY_REFIT :(Transferir y esperar a cualquier carga completa con reforma a {STRING}) -STR_ORDER_NO_UNLOAD_REFIT :(No descargar y tomar carga con reforma a {STRING}) -STR_ORDER_NO_UNLOAD_FULL_LOAD_REFIT :(No descargar y esperar a carga completa con reforma a {STRING}) -STR_ORDER_NO_UNLOAD_FULL_LOAD_ANY_REFIT :(No descargar y esperar a cualquier carga completa con reforma a {STRING}) +STR_ORDER_AUTO_REFIT :{SPACE}(Reforma a {STRING}) +STR_ORDER_FULL_LOAD_REFIT :{SPACE}(Carga completa con reforma a {STRING}) +STR_ORDER_FULL_LOAD_ANY_REFIT :{SPACE}(Cualquier carga completa con reforma a {STRING}) +STR_ORDER_UNLOAD_REFIT :{SPACE}(Descargar y tomar carga con reforma a {STRING}) +STR_ORDER_UNLOAD_FULL_LOAD_REFIT :{SPACE}(Descargar y esperar carga completa con reforma a {STRING}) +STR_ORDER_UNLOAD_FULL_LOAD_ANY_REFIT :{SPACE}(Descargar y esperar a cualquier carga completa con reforma a {STRING}) +STR_ORDER_TRANSFER_REFIT :{SPACE}(Transferir y cargar con reforma a {STRING}) +STR_ORDER_TRANSFER_FULL_LOAD_REFIT :{SPACE}(Transferir y esperar a carga completa con reforma a {STRING}) +STR_ORDER_TRANSFER_FULL_LOAD_ANY_REFIT :{SPACE}(Transferir y esperar a cualquier carga completa con reforma a {STRING}) +STR_ORDER_NO_UNLOAD_REFIT :{SPACE}(No descargar y tomar carga con reforma a {STRING}) +STR_ORDER_NO_UNLOAD_FULL_LOAD_REFIT :{SPACE}(No descargar y esperar a carga completa con reforma a {STRING}) +STR_ORDER_NO_UNLOAD_FULL_LOAD_ANY_REFIT :{SPACE}(No descargar y esperar a cualquier carga completa con reforma a {STRING}) STR_ORDER_AUTO_REFIT_ANY :carga disponible ###length 3 -STR_ORDER_STOP_LOCATION_NEAR_END :[extremo cercano] -STR_ORDER_STOP_LOCATION_MIDDLE :[centro] -STR_ORDER_STOP_LOCATION_FAR_END :[extremo lejano] +STR_ORDER_STOP_LOCATION_NEAR_END :{SPACE}[extremo cercano] +STR_ORDER_STOP_LOCATION_MIDDLE :{SPACE}[centro] +STR_ORDER_STOP_LOCATION_FAR_END :{SPACE}[extremo lejano] STR_ORDER_OUT_OF_RANGE :{RED} (El próximo destino está fuera de alcance) @@ -4742,8 +4773,8 @@ STR_TIMETABLE_TRAVEL_FOR :Viajar durante STR_TIMETABLE_TRAVEL_FOR_SPEED :Viajar durante {STRING} a {VELOCITY} como máximo STR_TIMETABLE_TRAVEL_FOR_ESTIMATED :Viajar (durante {STRING}, sin programar) STR_TIMETABLE_TRAVEL_FOR_SPEED_ESTIMATED :Viajar (durante {STRING}, sin programar) a {VELOCITY} como máximo -STR_TIMETABLE_STAY_FOR_ESTIMATED :(permanecer {STRING}, sin programar) -STR_TIMETABLE_AND_TRAVEL_FOR_ESTIMATED :(viajar durante {STRING}, sin programar) +STR_TIMETABLE_STAY_FOR_ESTIMATED :{SPACE}(permanecer {STRING}, sin programar) +STR_TIMETABLE_AND_TRAVEL_FOR_ESTIMATED :{SPACE}(viajar durante {STRING}, sin programar) STR_TIMETABLE_STAY_FOR :y permanecer {STRING} STR_TIMETABLE_AND_TRAVEL_FOR :y viajar durante {STRING} @@ -4764,12 +4795,14 @@ STR_TIMETABLE_START_SECONDS_QUERY :Segundos hasta STR_TIMETABLE_CHANGE_TIME :{BLACK}Modificar duración STR_TIMETABLE_WAIT_TIME_TOOLTIP :{BLACK}Modifica la duración esperada de la orden seleccionada. Ctrl+clic establece la duración para todas las órdenes +STR_TIMETABLE_CHANGE_TIME_QUERY :Cambiar duración STR_TIMETABLE_CLEAR_TIME :{BLACK}Borrar duración STR_TIMETABLE_CLEAR_TIME_TOOLTIP :{BLACK}Borra la duración de la orden resaltada.Ctrl+clic borra la duración de todas las órdenes STR_TIMETABLE_CHANGE_SPEED :{BLACK}Modificar límite de velocidad STR_TIMETABLE_CHANGE_SPEED_TOOLTIP :{BLACK}Cambia el límite de velocidad de la orden resaltada. Ctrl+clic establece el límite para todas las órdenes +STR_TIMETABLE_CHANGE_SPEED_QUERY :Cambiar límite de velocidad STR_TIMETABLE_CLEAR_SPEED :{BLACK}Borrar límite de velocidad STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Borra el límite de velocidad de la orden resaltada. Ctrl+clic borra el límite de todas las órdenes @@ -4819,7 +4852,7 @@ STR_AI_DEBUG_SELECT_AI_TOOLTIP :{BLACK}Muestra STR_AI_GAME_SCRIPT :{BLACK}Script de Juego STR_AI_GAME_SCRIPT_TOOLTIP :{BLACK}Analizar el registro del script de juego. Ctrl+click para abrir en una nueva ventana -STR_ERROR_AI_NO_AI_FOUND :No se encontró ninguna IA apropiada para cargar.{}Ésta es una IA por defecto que no realiza acción alguna.{}Puedes descargar nuevas IA mediante el sistema de 'Contenido Online' +STR_ERROR_AI_NO_AI_FOUND :No se encontró ninguna IA apropiada para cargar.{}Ésta es una IA por defecto que no realiza acción alguna.{}Puedes descargar nuevas IA mediante el sistema de 'Contenido en línea' STR_ERROR_AI_PLEASE_REPORT_CRASH :{WHITE}Uno de los scripts ejecutados ha fallado. Por favor, informa del fallo al autor del script con una captura de la ventana de depuración de script/IA STR_ERROR_AI_DEBUG_SERVER_ONLY :{YELLOW}La ventana de depuración de scripts solo está disponible para el servidor @@ -4874,8 +4907,8 @@ STR_SCREENSHOT_HEIGHTMAP_SCREENSHOT :{BLACK}Mapa de STR_SCREENSHOT_MINIMAP_SCREENSHOT :{BLACK}Minimapa # Script Parameters -STR_AI_SETTINGS_CAPTION_AI :{WHITE}IA -STR_AI_SETTINGS_CAPTION_GAMESCRIPT :Script de juego +STR_AI_SETTINGS_CAPTION_AI :{WHITE}Parámetros de IA +STR_AI_SETTINGS_CAPTION_GAMESCRIPT :{WHITE}Parámetros del script de juego STR_AI_SETTINGS_CLOSE :{BLACK}Cerrar STR_AI_SETTINGS_RESET :{BLACK}Reiniciar STR_AI_SETTINGS_SETTING :{STRING}: {ORANGE}{STRING} @@ -4936,7 +4969,7 @@ STR_GAME_SAVELOAD_NOT_AVAILABLE : STR_WARNING_LOADGAME_REMOVED_TRAMS :{WHITE}La partida se guardó en una versión sin soporte para tranvías. Todos los tranvías han sido eliminados # Map generation messages -STR_ERROR_COULD_NOT_CREATE_TOWN :{WHITE}Generación de mapa abortada...{}... no hay localizaciones apropiadas para municipios +STR_ERROR_COULD_NOT_CREATE_TOWN :{WHITE}Generación de mapa abortada...{}{}... no hay localizaciones apropiadas para municipios STR_ERROR_NO_TOWN_IN_SCENARIO :{WHITE}... no hay municipios en el escenario STR_ERROR_PNGMAP :{WHITE}No se pudo cargar mapa de alturas PNG... @@ -4965,6 +4998,7 @@ STR_ERROR_SCREENSHOT_FAILED :{WHITE}¡Captur # Error message titles STR_ERROR_MESSAGE_CAPTION :{YELLOW}Mensaje +STR_ERROR_MESSAGE_CAPTION_OTHER_COMPANY :{YELLOW}Mensaje de {COMPANY} # Generic construction errors STR_ERROR_OFF_EDGE_OF_MAP :{WHITE}Fuera del borde del mapa @@ -4983,6 +5017,7 @@ STR_ERROR_TERRAFORM_LIMIT_REACHED :{WHITE}... lím STR_ERROR_CLEARING_LIMIT_REACHED :{WHITE}... límite de casillas despejadas alcanzado STR_ERROR_TREE_PLANT_LIMIT_REACHED :{WHITE}... limite de plantación de árboles alcanzado STR_ERROR_NAME_MUST_BE_UNIQUE :{WHITE}El nombre debe ser único +STR_ERROR_GENERIC_OBJECT_IN_THE_WAY :{WHITE}{STRING} en medio STR_ERROR_NOT_ALLOWED_WHILE_PAUSED :{WHITE}No permitido en pausa # Local authority errors @@ -5025,7 +5060,7 @@ STR_ERROR_TOO_CLOSE_TO_ANOTHER_TOWN :{WHITE}... dema STR_ERROR_TOO_MANY_TOWNS :{WHITE}... demasiados municipios STR_ERROR_NO_SPACE_FOR_TOWN :{WHITE}... no hay más espacio en el mapa STR_ERROR_ROAD_WORKS_IN_PROGRESS :{WHITE}Obras de carretera en progreso -STR_ERROR_TOWN_CAN_T_DELETE :{WHITE}No se puede borrar este municipio...{}Quedan estaciones o depósitos relacionados con él, o una propiedad suya no puede ser retirada +STR_ERROR_TOWN_CAN_T_DELETE :{WHITE}No se puede borrar este municipio...{}{}Quedan estaciones o depósitos relacionados con él, o una propiedad suya no puede ser retirada STR_ERROR_STATUE_NO_SUITABLE_PLACE :{WHITE}... no existe un lugar apropiado para una estatua en el centro de este municipio STR_ERROR_CAN_T_BUILD_HOUSE :{WHITE}No se puede construir edificio... @@ -5299,8 +5334,16 @@ STR_ERROR_CAN_T_SELL_SHIP :{WHITE}No se pu STR_ERROR_CAN_T_SELL_AIRCRAFT :{WHITE}No se puede vender la aeronave... ###length VEHICLE_TYPES +STR_ERROR_CAN_T_SELL_ALL_TRAIN :{WHITE}No se pueden vender todos los trenes... +STR_ERROR_CAN_T_SELL_ALL_ROAD_VEHICLE :{WHITE}No se pueden vender todos los vehículos de carretera... +STR_ERROR_CAN_T_SELL_ALL_SHIP :{WHITE}No se pueden vender todos los barcos... +STR_ERROR_CAN_T_SELL_ALL_AIRCRAFT :{WHITE}No se pueden vender todas las aeronaves... ###length VEHICLE_TYPES +STR_ERROR_CAN_T_AUTOREPLACE_TRAIN :{WHITE}No se pueden reemplazar automáticamente los trenes... +STR_ERROR_CAN_T_AUTOREPLACE_ROAD_VEHICLE :{WHITE}No se pueden reemplazar automáticamente los vehículos de carretera... +STR_ERROR_CAN_T_AUTOREPLACE_SHIP :{WHITE}No se pueden reemplazar automáticamente los barcos... +STR_ERROR_CAN_T_AUTOREPLACE_AIRCRAFT :{WHITE}No se pueden reemplazar automáticamente las aeronaves... STR_ERROR_TOO_MANY_VEHICLES_IN_GAME :{WHITE}Demasiados vehículos en el juego STR_ERROR_CAN_T_CHANGE_SERVICING :{WHITE}No se puede cambiar intervalo de mantenimiento... @@ -5882,3 +5925,4 @@ STR_SHIP :{BLACK}{SHIP} STR_TOOLBAR_RAILTYPE_VELOCITY :{STRING} ({VELOCITY}) +STR_BADGE_NAME_LIST :{STRING}: {GOLD}{STRING} From f9ab492e645288d308d0406fbb4235f3da8bc88e Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 15 Apr 2025 19:39:55 +0100 Subject: [PATCH 027/766] Codechange: Pass HouseSpec by reference to IsHouseSpecValid(). --- src/newgrf.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/newgrf.cpp b/src/newgrf.cpp index ee440aa464..4ec1f05dc3 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -945,40 +945,40 @@ void FinaliseCargoArray() * @param filename The filename of the newgrf this house was defined in. * @return Whether the given housespec is valid. */ -static bool IsHouseSpecValid(HouseSpec *hs, const HouseSpec *next1, const HouseSpec *next2, const HouseSpec *next3, const std::string &filename) +static bool IsHouseSpecValid(HouseSpec &hs, const HouseSpec *next1, const HouseSpec *next2, const HouseSpec *next3, const std::string &filename) { - if ((hs->building_flags.Any(BUILDING_HAS_2_TILES) && + if ((hs.building_flags.Any(BUILDING_HAS_2_TILES) && (next1 == nullptr || !next1->enabled || next1->building_flags.Any(BUILDING_HAS_1_TILE))) || - (hs->building_flags.Any(BUILDING_HAS_4_TILES) && + (hs.building_flags.Any(BUILDING_HAS_4_TILES) && (next2 == nullptr || !next2->enabled || next2->building_flags.Any(BUILDING_HAS_1_TILE) || next3 == nullptr || !next3->enabled || next3->building_flags.Any(BUILDING_HAS_1_TILE)))) { - hs->enabled = false; - if (!filename.empty()) Debug(grf, 1, "FinaliseHouseArray: {} defines house {} as multitile, but no suitable tiles follow. Disabling house.", filename, hs->grf_prop.local_id); + hs.enabled = false; + if (!filename.empty()) Debug(grf, 1, "FinaliseHouseArray: {} defines house {} as multitile, but no suitable tiles follow. Disabling house.", filename, hs.grf_prop.local_id); return false; } /* Some places sum population by only counting north tiles. Other places use all tiles causing desyncs. * As the newgrf specs define population to be zero for non-north tiles, we just disable the offending house. * If you want to allow non-zero populations somewhen, make sure to sum the population of all tiles in all places. */ - if ((hs->building_flags.Any(BUILDING_HAS_2_TILES) && next1->population != 0) || - (hs->building_flags.Any(BUILDING_HAS_4_TILES) && (next2->population != 0 || next3->population != 0))) { - hs->enabled = false; - if (!filename.empty()) Debug(grf, 1, "FinaliseHouseArray: {} defines multitile house {} with non-zero population on additional tiles. Disabling house.", filename, hs->grf_prop.local_id); + if ((hs.building_flags.Any(BUILDING_HAS_2_TILES) && next1->population != 0) || + (hs.building_flags.Any(BUILDING_HAS_4_TILES) && (next2->population != 0 || next3->population != 0))) { + hs.enabled = false; + if (!filename.empty()) Debug(grf, 1, "FinaliseHouseArray: {} defines multitile house {} with non-zero population on additional tiles. Disabling house.", filename, hs.grf_prop.local_id); return false; } /* Substitute type is also used for override, and having an override with a different size causes crashes. * This check should only be done for NewGRF houses because grf_prop.subst_id is not set for original houses.*/ - if (!filename.empty() && (hs->building_flags & BUILDING_HAS_1_TILE) != (HouseSpec::Get(hs->grf_prop.subst_id)->building_flags & BUILDING_HAS_1_TILE)) { - hs->enabled = false; - Debug(grf, 1, "FinaliseHouseArray: {} defines house {} with different house size then it's substitute type. Disabling house.", filename, hs->grf_prop.local_id); + if (!filename.empty() && (hs.building_flags & BUILDING_HAS_1_TILE) != (HouseSpec::Get(hs.grf_prop.subst_id)->building_flags & BUILDING_HAS_1_TILE)) { + hs.enabled = false; + Debug(grf, 1, "FinaliseHouseArray: {} defines house {} with different house size then it's substitute type. Disabling house.", filename, hs.grf_prop.local_id); return false; } /* Make sure that additional parts of multitile houses are not available. */ - if (!hs->building_flags.Any(BUILDING_HAS_1_TILE) && (hs->building_availability & HZ_ZONALL) != 0 && (hs->building_availability & HZ_CLIMALL) != 0) { - hs->enabled = false; - if (!filename.empty()) Debug(grf, 1, "FinaliseHouseArray: {} defines house {} without a size but marked it as available. Disabling house.", filename, hs->grf_prop.local_id); + if (!hs.building_flags.Any(BUILDING_HAS_1_TILE) && (hs.building_availability & HZ_ZONALL) != 0 && (hs.building_availability & HZ_CLIMALL) != 0) { + hs.enabled = false; + if (!filename.empty()) Debug(grf, 1, "FinaliseHouseArray: {} defines house {} without a size but marked it as available. Disabling house.", filename, hs.grf_prop.local_id); return false; } @@ -1040,7 +1040,7 @@ static void FinaliseHouseArray() const HouseSpec *next2 = (i + 2 < num_houses ? file.housespec[i + 2].get() : nullptr); const HouseSpec *next3 = (i + 3 < num_houses ? file.housespec[i + 3].get() : nullptr); - if (!IsHouseSpecValid(hs, next1, next2, next3, file.filename)) continue; + if (!IsHouseSpecValid(*hs, next1, next2, next3, file.filename)) continue; _house_mngr.SetEntitySpec(hs); } @@ -1054,7 +1054,7 @@ static void FinaliseHouseArray() /* We need to check all houses again to we are sure that multitile houses * did get consecutive IDs and none of the parts are missing. */ - if (!IsHouseSpecValid(hs, next1, next2, next3, std::string{})) { + if (!IsHouseSpecValid(*hs, next1, next2, next3, std::string{})) { /* GetHouseNorthPart checks 3 houses that are directly before * it in the house pool. If any of those houses have multi-tile * flags set it assumes it's part of a multitile house. Since From a281ac4674c81cf2bf4b5bc8627a4fb3d9405701 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 15 Apr 2025 19:42:04 +0100 Subject: [PATCH 028/766] Codefix: Information for NewGRF specs remained duplicated after loading. After loading airports+tiles, industries+tiles, houses and objects, their specs are copied from the NewGRF's loading storage to the final global storage. Instead, move the specs to the their new storage, and clear the NewGRF's storage once done. (Stations and RoadStops are different, and the NewGRF's storage is the final storage location.) --- src/newgrf.cpp | 46 ++++++++++++++++++++++++++----------- src/newgrf_airport.cpp | 8 +++---- src/newgrf_airport.h | 2 ++ src/newgrf_airporttiles.cpp | 8 +++---- src/newgrf_airporttiles.h | 2 +- src/newgrf_commons.cpp | 34 +++++++++++++-------------- src/newgrf_commons.h | 12 +++++----- 7 files changed, 67 insertions(+), 45 deletions(-) diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 4ec1f05dc3..224e6ca6fc 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -1027,12 +1027,12 @@ static void FinaliseHouseArray() * On the other hand, why 1930? Just 'fix' the houses with the lowest * minimum introduction date to 0. */ - for (const auto &file : _grf_files) { + for (auto &file : _grf_files) { if (file.housespec.empty()) continue; size_t num_houses = file.housespec.size(); for (size_t i = 0; i < num_houses; i++) { - HouseSpec *hs = file.housespec[i].get(); + auto &hs = file.housespec[i]; if (hs == nullptr) continue; @@ -1042,8 +1042,12 @@ static void FinaliseHouseArray() if (!IsHouseSpecValid(*hs, next1, next2, next3, file.filename)) continue; - _house_mngr.SetEntitySpec(hs); + _house_mngr.SetEntitySpec(std::move(*hs)); } + + /* Won't be used again */ + file.housespec.clear(); + file.housespec.shrink_to_fit(); } for (size_t i = 0; i < HouseSpec::Specs().size(); i++) { @@ -1096,18 +1100,24 @@ static void FinaliseHouseArray() */ static void FinaliseIndustriesArray() { - for (const auto &file : _grf_files) { - for (const auto &indsp : file.industryspec) { + for (auto &file : _grf_files) { + for (auto &indsp : file.industryspec) { if (indsp == nullptr || !indsp->enabled) continue; - _industry_mngr.SetEntitySpec(indsp.get()); + _industry_mngr.SetEntitySpec(std::move(*indsp)); } - for (const auto &indtsp : file.indtspec) { + for (auto &indtsp : file.indtspec) { if (indtsp != nullptr) { - _industile_mngr.SetEntitySpec(indtsp.get()); + _industile_mngr.SetEntitySpec(std::move(*indtsp)); } } + + /* Won't be used again */ + file.industryspec.clear(); + file.industryspec.shrink_to_fit(); + file.indtspec.clear(); + file.indtspec.shrink_to_fit(); } for (auto &indsp : _industry_specs) { @@ -1144,12 +1154,16 @@ static void FinaliseIndustriesArray() */ static void FinaliseObjectsArray() { - for (const auto &file : _grf_files) { + for (auto &file : _grf_files) { for (auto &objectspec : file.objectspec) { if (objectspec != nullptr && objectspec->grf_prop.HasGrfFile() && objectspec->IsEnabled()) { - _object_mngr.SetEntitySpec(objectspec.get()); + _object_mngr.SetEntitySpec(std::move(*objectspec)); } } + + /* Won't be used again */ + file.objectspec.clear(); + file.objectspec.shrink_to_fit(); } ObjectSpec::BindToClasses(); @@ -1162,18 +1176,24 @@ static void FinaliseObjectsArray() */ static void FinaliseAirportsArray() { - for (const auto &file : _grf_files) { + for (auto &file : _grf_files) { for (auto &as : file.airportspec) { if (as != nullptr && as->enabled) { - _airport_mngr.SetEntitySpec(as.get()); + _airport_mngr.SetEntitySpec(std::move(*as)); } } for (auto &ats : file.airtspec) { if (ats != nullptr && ats->enabled) { - _airporttile_mngr.SetEntitySpec(ats.get()); + _airporttile_mngr.SetEntitySpec(std::move(*ats)); } } + + /* Won't be used again */ + file.airportspec.clear(); + file.airportspec.shrink_to_fit(); + file.airtspec.clear(); + file.airtspec.shrink_to_fit(); } } diff --git a/src/newgrf_airport.cpp b/src/newgrf_airport.cpp index fdd5fc3739..a22e87bb9d 100644 --- a/src/newgrf_airport.cpp +++ b/src/newgrf_airport.cpp @@ -134,22 +134,22 @@ void BindAirportSpecs() } -void AirportOverrideManager::SetEntitySpec(AirportSpec *as) +void AirportOverrideManager::SetEntitySpec(AirportSpec &&as) { - uint8_t airport_id = this->AddEntityID(as->grf_prop.local_id, as->grf_prop.grfid, as->grf_prop.subst_id); + uint8_t airport_id = this->AddEntityID(as.grf_prop.local_id, as.grf_prop.grfid, as.grf_prop.subst_id); if (airport_id == this->invalid_id) { GrfMsg(1, "Airport.SetEntitySpec: Too many airports allocated. Ignoring."); return; } - *AirportSpec::GetWithoutOverride(airport_id) = *as; + AirportSpec::specs[airport_id] = std::move(as); /* Now add the overrides. */ for (int i = 0; i < this->max_offset; i++) { AirportSpec *overridden_as = AirportSpec::GetWithoutOverride(i); - if (this->entity_overrides[i] != as->grf_prop.local_id || this->grfid_overrides[i] != as->grf_prop.grfid) continue; + if (this->entity_overrides[i] != AirportSpec::specs[airport_id].grf_prop.local_id || this->grfid_overrides[i] != AirportSpec::specs[airport_id].grf_prop.grfid) continue; overridden_as->grf_prop.override_id = airport_id; overridden_as->enabled = false; diff --git a/src/newgrf_airport.h b/src/newgrf_airport.h index f9faa33921..59e811b80e 100644 --- a/src/newgrf_airport.h +++ b/src/newgrf_airport.h @@ -141,6 +141,8 @@ struct AirportSpec : NewGRFSpecBase { private: static AirportSpec specs[NUM_AIRPORTS]; ///< Specs of the airports. + + friend void AirportOverrideManager::SetEntitySpec(AirportSpec &&as); }; /** Information related to airport classes. */ diff --git a/src/newgrf_airporttiles.cpp b/src/newgrf_airporttiles.cpp index f47e9e4af6..eea0f118c3 100644 --- a/src/newgrf_airporttiles.cpp +++ b/src/newgrf_airporttiles.cpp @@ -66,22 +66,22 @@ void AirportTileSpec::ResetAirportTiles() _airporttile_mngr.ResetOverride(); } -void AirportTileOverrideManager::SetEntitySpec(const AirportTileSpec *airpts) +void AirportTileOverrideManager::SetEntitySpec(AirportTileSpec &&airpts) { - StationGfx airpt_id = this->AddEntityID(airpts->grf_prop.local_id, airpts->grf_prop.grfid, airpts->grf_prop.subst_id); + StationGfx airpt_id = this->AddEntityID(airpts.grf_prop.local_id, airpts.grf_prop.grfid, airpts.grf_prop.subst_id); if (airpt_id == this->invalid_id) { GrfMsg(1, "AirportTile.SetEntitySpec: Too many airport tiles allocated. Ignoring."); return; } - AirportTileSpec::tiles[airpt_id] = *airpts; + AirportTileSpec::tiles[airpt_id] = std::move(airpts); /* Now add the overrides. */ for (int i = 0; i < this->max_offset; i++) { AirportTileSpec *overridden_airpts = &AirportTileSpec::tiles[i]; - if (this->entity_overrides[i] != airpts->grf_prop.local_id || this->grfid_overrides[i] != airpts->grf_prop.grfid) continue; + if (this->entity_overrides[i] != AirportTileSpec::tiles[airpt_id].grf_prop.local_id || this->grfid_overrides[i] != AirportTileSpec::tiles[airpt_id].grf_prop.grfid) continue; overridden_airpts->grf_prop.override_id = airpt_id; overridden_airpts->enabled = false; diff --git a/src/newgrf_airporttiles.h b/src/newgrf_airporttiles.h index 1f2d989c33..68a62d1b07 100644 --- a/src/newgrf_airporttiles.h +++ b/src/newgrf_airporttiles.h @@ -84,7 +84,7 @@ struct AirportTileSpec { private: static AirportTileSpec tiles[NUM_AIRPORTTILES]; - friend void AirportTileOverrideManager::SetEntitySpec(const AirportTileSpec *airpts); + friend void AirportTileOverrideManager::SetEntitySpec(AirportTileSpec &&airpts); }; void AnimateAirportTile(TileIndex tile); diff --git a/src/newgrf_commons.cpp b/src/newgrf_commons.cpp index 6ed612ca81..a46b5477d6 100644 --- a/src/newgrf_commons.cpp +++ b/src/newgrf_commons.cpp @@ -157,9 +157,9 @@ uint16_t OverrideManagerBase::GetSubstituteID(uint16_t entity_id) const * It will find itself the proper slot on which it will go * @param hs HouseSpec read from the grf file, ready for inclusion */ -void HouseOverrideManager::SetEntitySpec(const HouseSpec *hs) +void HouseOverrideManager::SetEntitySpec(HouseSpec &&hs) { - HouseID house_id = this->AddEntityID(hs->grf_prop.local_id, hs->grf_prop.grfid, hs->grf_prop.subst_id); + HouseID house_id = this->AddEntityID(hs.grf_prop.local_id, hs.grf_prop.grfid, hs.grf_prop.subst_id); if (house_id == this->invalid_id) { GrfMsg(1, "House.SetEntitySpec: Too many houses allocated. Ignoring."); @@ -170,13 +170,13 @@ void HouseOverrideManager::SetEntitySpec(const HouseSpec *hs) /* Now that we know we can use the given id, copy the spec to its final destination. */ if (house_id >= house_specs.size()) house_specs.resize(house_id + 1); - house_specs[house_id] = *hs; + house_specs[house_id] = std::move(hs); /* Now add the overrides. */ for (int i = 0; i < this->max_offset; i++) { HouseSpec *overridden_hs = HouseSpec::Get(i); - if (this->entity_overrides[i] != hs->grf_prop.local_id || this->grfid_overrides[i] != hs->grf_prop.grfid) continue; + if (this->entity_overrides[i] != house_specs[house_id].grf_prop.local_id || this->grfid_overrides[i] != house_specs[house_id].grf_prop.grfid) continue; overridden_hs->grf_prop.override_id = house_id; this->entity_overrides[i] = this->invalid_id; @@ -245,18 +245,18 @@ uint16_t IndustryOverrideManager::AddEntityID(uint16_t grf_local_id, uint32_t gr * checking what is available * @param inds Industryspec that comes from the grf decoding process */ -void IndustryOverrideManager::SetEntitySpec(IndustrySpec *inds) +void IndustryOverrideManager::SetEntitySpec(IndustrySpec &&inds) { /* First step : We need to find if this industry is already specified in the savegame data. */ - IndustryType ind_id = this->GetID(inds->grf_prop.local_id, inds->grf_prop.grfid); + IndustryType ind_id = this->GetID(inds.grf_prop.local_id, inds.grf_prop.grfid); if (ind_id == this->invalid_id) { /* Not found. * Or it has already been overridden, so you've lost your place. * Or it is a simple substitute. * We need to find a free available slot */ - ind_id = this->AddEntityID(inds->grf_prop.local_id, inds->grf_prop.grfid, inds->grf_prop.subst_id); - inds->grf_prop.override_id = this->invalid_id; // make sure it will not be detected as overridden + ind_id = this->AddEntityID(inds.grf_prop.local_id, inds.grf_prop.grfid, inds.grf_prop.subst_id); + inds.grf_prop.override_id = this->invalid_id; // make sure it will not be detected as overridden } if (ind_id == this->invalid_id) { @@ -265,27 +265,27 @@ void IndustryOverrideManager::SetEntitySpec(IndustrySpec *inds) } /* Now that we know we can use the given id, copy the spec to its final destination... */ - _industry_specs[ind_id] = *inds; + _industry_specs[ind_id] = std::move(inds); /* ... and mark it as usable*/ _industry_specs[ind_id].enabled = true; } -void IndustryTileOverrideManager::SetEntitySpec(const IndustryTileSpec *its) +void IndustryTileOverrideManager::SetEntitySpec(IndustryTileSpec &&its) { - IndustryGfx indt_id = this->AddEntityID(its->grf_prop.local_id, its->grf_prop.grfid, its->grf_prop.subst_id); + IndustryGfx indt_id = this->AddEntityID(its.grf_prop.local_id, its.grf_prop.grfid, its.grf_prop.subst_id); if (indt_id == this->invalid_id) { GrfMsg(1, "IndustryTile.SetEntitySpec: Too many industry tiles allocated. Ignoring."); return; } - _industry_tile_specs[indt_id] = *its; + _industry_tile_specs[indt_id] = std::move(its); /* Now add the overrides. */ for (int i = 0; i < this->max_offset; i++) { IndustryTileSpec *overridden_its = &_industry_tile_specs[i]; - if (this->entity_overrides[i] != its->grf_prop.local_id || this->grfid_overrides[i] != its->grf_prop.grfid) continue; + if (this->entity_overrides[i] != _industry_tile_specs[indt_id].grf_prop.local_id || this->grfid_overrides[i] != _industry_tile_specs[indt_id].grf_prop.grfid) continue; overridden_its->grf_prop.override_id = indt_id; overridden_its->enabled = false; @@ -300,17 +300,17 @@ void IndustryTileOverrideManager::SetEntitySpec(const IndustryTileSpec *its) * checking what is available * @param spec ObjectSpec that comes from the grf decoding process */ -void ObjectOverrideManager::SetEntitySpec(ObjectSpec *spec) +void ObjectOverrideManager::SetEntitySpec(ObjectSpec &&spec) { /* First step : We need to find if this object is already specified in the savegame data. */ - ObjectType type = this->GetID(spec->grf_prop.local_id, spec->grf_prop.grfid); + ObjectType type = this->GetID(spec.grf_prop.local_id, spec.grf_prop.grfid); if (type == this->invalid_id) { /* Not found. * Or it has already been overridden, so you've lost your place. * Or it is a simple substitute. * We need to find a free available slot */ - type = this->AddEntityID(spec->grf_prop.local_id, spec->grf_prop.grfid, OBJECT_TRANSMITTER); + type = this->AddEntityID(spec.grf_prop.local_id, spec.grf_prop.grfid, OBJECT_TRANSMITTER); } if (type == this->invalid_id) { @@ -322,7 +322,7 @@ void ObjectOverrideManager::SetEntitySpec(ObjectSpec *spec) /* Now that we know we can use the given id, copy the spec to its final destination. */ if (type >= _object_specs.size()) _object_specs.resize(type + 1); - _object_specs[type] = *spec; + _object_specs[type] = std::move(spec); } /** diff --git a/src/newgrf_commons.h b/src/newgrf_commons.h index 37ecc134e7..d553bd6e49 100644 --- a/src/newgrf_commons.h +++ b/src/newgrf_commons.h @@ -210,7 +210,7 @@ public: HouseOverrideManager(uint16_t offset, uint16_t maximum, uint16_t invalid) : OverrideManagerBase(offset, maximum, invalid) {} - void SetEntitySpec(const HouseSpec *hs); + void SetEntitySpec(HouseSpec &&hs); }; @@ -223,7 +223,7 @@ public: uint16_t AddEntityID(uint16_t grf_local_id, uint32_t grfid, uint16_t substitute_id) override; uint16_t GetID(uint16_t grf_local_id, uint32_t grfid) const override; - void SetEntitySpec(IndustrySpec *inds); + void SetEntitySpec(IndustrySpec &&inds); }; @@ -235,7 +235,7 @@ public: IndustryTileOverrideManager(uint16_t offset, uint16_t maximum, uint16_t invalid) : OverrideManagerBase(offset, maximum, invalid) {} - void SetEntitySpec(const IndustryTileSpec *indts); + void SetEntitySpec(IndustryTileSpec &&indts); }; struct AirportSpec; @@ -244,7 +244,7 @@ public: AirportOverrideManager(uint16_t offset, uint16_t maximum, uint16_t invalid) : OverrideManagerBase(offset, maximum, invalid) {} - void SetEntitySpec(AirportSpec *inds); + void SetEntitySpec(AirportSpec &&inds); }; struct AirportTileSpec; @@ -255,7 +255,7 @@ public: AirportTileOverrideManager(uint16_t offset, uint16_t maximum, uint16_t invalid) : OverrideManagerBase(offset, maximum, invalid) {} - void SetEntitySpec(const AirportTileSpec *ats); + void SetEntitySpec(AirportTileSpec &&ats); }; struct ObjectSpec; @@ -266,7 +266,7 @@ public: ObjectOverrideManager(uint16_t offset, uint16_t maximum, uint16_t invalid) : OverrideManagerBase(offset, maximum, invalid) {} - void SetEntitySpec(ObjectSpec *spec); + void SetEntitySpec(ObjectSpec &&spec); }; extern HouseOverrideManager _house_mngr; From 6ea10edef8e5fb56db8cbe95540dbc1da0487650 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Wed, 16 Apr 2025 19:43:45 +0100 Subject: [PATCH 029/766] Codechange: Use std::unique_ptrs for handling station cargo display. (#14009) Replaces manual management of raw pointers. --- src/station_gui.cpp | 175 +++++++++++++++++++++----------------------- 1 file changed, 84 insertions(+), 91 deletions(-) diff --git a/src/station_gui.cpp b/src/station_gui.cpp index 160f1d1eb1..fa35efccb5 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -889,9 +889,13 @@ enum class CargoSortType : uint8_t { class CargoSorter { public: + using is_transparent = void; CargoSorter(CargoSortType t = CargoSortType::StationID, SortOrder o = SO_ASCENDING) : type(t), order(o) {} CargoSortType GetSortType() {return this->type;} - bool operator()(const CargoDataEntry *cd1, const CargoDataEntry *cd2) const; + bool operator()(const CargoDataEntry &cd1, const CargoDataEntry &cd2) const; + bool operator()(const CargoDataEntry &cd1, const std::unique_ptr &cd2) const { return this->operator()(cd1, *cd2); } + bool operator()(const std::unique_ptr &cd1, const CargoDataEntry &cd2) const { return this->operator()(*cd1, cd2); } + bool operator()(const std::unique_ptr &cd1, const std::unique_ptr &cd2) const { return this->operator()(*cd1, *cd2); } private: CargoSortType type; @@ -899,11 +903,11 @@ private: template bool SortId(Tid st1, Tid st2) const; - bool SortCount(const CargoDataEntry *cd1, const CargoDataEntry *cd2) const; - bool SortStation (StationID st1, StationID st2) const; + bool SortCount(const CargoDataEntry &cd1, const CargoDataEntry &cd2) const; + bool SortStation(StationID st1, StationID st2) const; }; -typedef std::set CargoDataSet; +typedef std::set, CargoSorter> CargoDataSet; /** * A cargo data entry representing one possible row in the station view window's @@ -920,7 +924,7 @@ public: * @param station ID of the station for which an entry shall be created or retrieved * @return a child entry associated with the given station. */ - CargoDataEntry *InsertOrRetrieve(StationID station) + CargoDataEntry &InsertOrRetrieve(StationID station) { return this->InsertOrRetrieve(station); } @@ -930,7 +934,7 @@ public: * @param cargo type of the cargo for which an entry shall be created or retrieved * @return a child entry associated with the given cargo. */ - CargoDataEntry *InsertOrRetrieve(CargoType cargo) + CargoDataEntry &InsertOrRetrieve(CargoType cargo) { return this->InsertOrRetrieve(cargo); } @@ -944,7 +948,7 @@ public: void Remove(StationID station) { CargoDataEntry t(station); - this->Remove(&t); + this->Remove(t); } /** @@ -954,7 +958,7 @@ public: void Remove(CargoType cargo) { CargoDataEntry t(cargo); - this->Remove(&t); + this->Remove(t); } /** @@ -965,7 +969,7 @@ public: CargoDataEntry *Retrieve(StationID station) const { CargoDataEntry t(station); - return this->Retrieve(this->children->find(&t)); + return this->Retrieve(this->children->find(t)); } /** @@ -976,7 +980,7 @@ public: CargoDataEntry *Retrieve(CargoType cargo) const { CargoDataEntry t(cargo); - return this->Retrieve(this->children->find(&t)); + return this->Retrieve(this->children->find(t)); } void Resort(CargoSortType type, SortOrder order); @@ -1027,19 +1031,19 @@ public: void SetTransfers(bool value) { this->transfers = value; } void Clear(); -private: CargoDataEntry(StationID station, uint count, CargoDataEntry *parent); CargoDataEntry(CargoType cargo, uint count, CargoDataEntry *parent); CargoDataEntry(StationID station); CargoDataEntry(CargoType cargo); +private: CargoDataEntry *Retrieve(CargoDataSet::iterator i) const; template - CargoDataEntry *InsertOrRetrieve(Tid s); + CargoDataEntry &InsertOrRetrieve(Tid s); - void Remove(CargoDataEntry *entry); + void Remove(CargoDataEntry &entry); void IncrementSize(); CargoDataEntry *parent; ///< the parent of this entry. @@ -1052,7 +1056,7 @@ private: }; uint num_children; ///< the number of subentries belonging to this entry. uint count; ///< sum of counts of all children or amount of cargo for this entry. - CargoDataSet *children; ///< the children of this entry. + std::unique_ptr children; ///< the children of this entry. }; CargoDataEntry::CargoDataEntry() : @@ -1060,7 +1064,7 @@ CargoDataEntry::CargoDataEntry() : station(StationID::Invalid()), num_children(0), count(0), - children(new CargoDataSet(CargoSorter(CargoSortType::CargoType))) + children(std::make_unique(CargoSorter(CargoSortType::CargoType))) {} CargoDataEntry::CargoDataEntry(CargoType cargo, uint count, CargoDataEntry *parent) : @@ -1068,7 +1072,7 @@ CargoDataEntry::CargoDataEntry(CargoType cargo, uint count, CargoDataEntry *pare cargo(cargo), num_children(0), count(count), - children(new CargoDataSet) + children(std::make_unique()) {} CargoDataEntry::CargoDataEntry(StationID station, uint count, CargoDataEntry *parent) : @@ -1076,7 +1080,7 @@ CargoDataEntry::CargoDataEntry(StationID station, uint count, CargoDataEntry *pa station(station), num_children(0), count(count), - children(new CargoDataSet) + children(std::make_unique()) {} CargoDataEntry::CargoDataEntry(StationID station) : @@ -1098,7 +1102,6 @@ CargoDataEntry::CargoDataEntry(CargoType cargo) : CargoDataEntry::~CargoDataEntry() { this->Clear(); - delete this->children; } /** @@ -1106,13 +1109,7 @@ CargoDataEntry::~CargoDataEntry() */ void CargoDataEntry::Clear() { - if (this->children != nullptr) { - for (auto &it : *this->children) { - assert(it != this); - delete it; - } - this->children->clear(); - } + if (this->children != nullptr) this->children->clear(); if (this->parent != nullptr) this->parent->count -= this->count; this->count = 0; this->num_children = 0; @@ -1124,13 +1121,10 @@ void CargoDataEntry::Clear() * which only contains the ID of the entry to be removed. In this case child is * not deleted. */ -void CargoDataEntry::Remove(CargoDataEntry *entry) +void CargoDataEntry::Remove(CargoDataEntry &entry) { CargoDataSet::iterator i = this->children->find(entry); - if (i != this->children->end()) { - delete *i; - this->children->erase(i); - } + if (i != this->children->end()) this->children->erase(i); } /** @@ -1140,17 +1134,16 @@ void CargoDataEntry::Remove(CargoDataEntry *entry) * @return the new or retrieved subentry */ template -CargoDataEntry *CargoDataEntry::InsertOrRetrieve(Tid child_id) +CargoDataEntry &CargoDataEntry::InsertOrRetrieve(Tid child_id) { CargoDataEntry tmp(child_id); - CargoDataSet::iterator i = this->children->find(&tmp); + CargoDataSet::iterator i = this->children->find(tmp); if (i == this->children->end()) { IncrementSize(); - return *(this->children->insert(new CargoDataEntry(child_id, 0, this)).first); + return **(this->children->insert(std::make_unique(child_id, 0, this)).first); } else { - CargoDataEntry *ret = *i; assert(this->children->value_comp().GetSortType() != CargoSortType::Count); - return ret; + return **i; } } @@ -1176,9 +1169,9 @@ void CargoDataEntry::IncrementSize() void CargoDataEntry::Resort(CargoSortType type, SortOrder order) { - CargoDataSet *new_subs = new CargoDataSet(this->children->begin(), this->children->end(), CargoSorter(type, order)); - delete this->children; - this->children = new_subs; + auto new_children = std::make_unique(CargoSorter(type, order)); + new_children->merge(*this->children); + this->children = std::move(new_children); } CargoDataEntry *CargoDataEntry::Retrieve(CargoDataSet::iterator i) const @@ -1187,21 +1180,21 @@ CargoDataEntry *CargoDataEntry::Retrieve(CargoDataSet::iterator i) const return nullptr; } else { assert(this->children->value_comp().GetSortType() != CargoSortType::Count); - return *i; + return i->get(); } } -bool CargoSorter::operator()(const CargoDataEntry *cd1, const CargoDataEntry *cd2) const +bool CargoSorter::operator()(const CargoDataEntry &cd1, const CargoDataEntry &cd2) const { switch (this->type) { case CargoSortType::StationID: - return this->SortId(cd1->GetStation(), cd2->GetStation()); + return this->SortId(cd1.GetStation(), cd2.GetStation()); case CargoSortType::CargoType: - return this->SortId(cd1->GetCargo(), cd2->GetCargo()); + return this->SortId(cd1.GetCargo(), cd2.GetCargo()); case CargoSortType::Count: return this->SortCount(cd1, cd2); case CargoSortType::StationString: - return this->SortStation(cd1->GetStation(), cd2->GetStation()); + return this->SortStation(cd1.GetStation(), cd2.GetStation()); default: NOT_REACHED(); } @@ -1213,12 +1206,12 @@ bool CargoSorter::SortId(Tid st1, Tid st2) const return (this->order == SO_ASCENDING) ? st1 < st2 : st2 < st1; } -bool CargoSorter::SortCount(const CargoDataEntry *cd1, const CargoDataEntry *cd2) const +bool CargoSorter::SortCount(const CargoDataEntry &cd1, const CargoDataEntry &cd2) const { - uint c1 = cd1->GetCount(); - uint c2 = cd2->GetCount(); + uint c1 = cd1.GetCount(); + uint c2 = cd2.GetCount(); if (c1 == c2) { - return this->SortStation(cd1->GetStation(), cd2->GetStation()); + return this->SortStation(cd1.GetStation(), cd2.GetStation()); } else if (this->order == SO_ASCENDING) { return c1 < c2; } else { @@ -1392,25 +1385,25 @@ struct StationViewWindow : public Window { switch (groupings[i]) { case GR_CARGO: assert(i == 0); - data = data->InsertOrRetrieve(cargo); + data = &data->InsertOrRetrieve(cargo); data->SetTransfers(source != this->window_number); expand = expand->Retrieve(cargo); break; case GR_SOURCE: if (auto_distributed || source != this->window_number) { - data = data->InsertOrRetrieve(source); + data = &data->InsertOrRetrieve(source); expand = expand->Retrieve(source); } break; case GR_NEXT: if (auto_distributed) { - data = data->InsertOrRetrieve(next); + data = &data->InsertOrRetrieve(next); expand = expand->Retrieve(next); } break; case GR_DESTINATION: if (auto_distributed) { - data = data->InsertOrRetrieve(dest); + data = &data->InsertOrRetrieve(dest); expand = expand->Retrieve(dest); } break; @@ -1498,7 +1491,7 @@ struct StationViewWindow : public Window { /* Draw waiting cargo. */ NWidgetBase *nwi = this->GetWidget(WID_SV_WAITING); Rect waiting_rect = nwi->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect); - this->DrawEntries(&cargo, waiting_rect, pos, maxrows, 0); + this->DrawEntries(cargo, waiting_rect, pos, maxrows, 0); scroll_to_row = INT_MAX; } } @@ -1521,20 +1514,20 @@ struct StationViewWindow : public Window { void RecalcDestinations(CargoType cargo) { const Station *st = Station::Get(this->window_number); - CargoDataEntry *entry = cached_destinations.InsertOrRetrieve(cargo); - entry->Clear(); + CargoDataEntry &entry = cached_destinations.InsertOrRetrieve(cargo); + entry.Clear(); if (!st->goods[cargo].HasData()) return; for (const auto &it : st->goods[cargo].GetData().flows) { StationID from = it.first; - CargoDataEntry *source_entry = entry->InsertOrRetrieve(from); + CargoDataEntry &source_entry = entry.InsertOrRetrieve(from); uint32_t prev_count = 0; for (const auto &flow_it : *it.second.GetShares()) { StationID via = flow_it.second; - CargoDataEntry *via_entry = source_entry->InsertOrRetrieve(via); + CargoDataEntry &via_entry = source_entry.InsertOrRetrieve(via); if (via == this->window_number) { - via_entry->InsertOrRetrieve(via)->Update(flow_it.first - prev_count); + via_entry.InsertOrRetrieve(via).Update(flow_it.first - prev_count); } else { EstimateDestinations(cargo, from, via, flow_it.first - prev_count, via_entry); } @@ -1552,7 +1545,7 @@ struct StationViewWindow : public Window { * @param count Size of the batch of cargo. * @param dest CargoDataEntry to save the results in. */ - void EstimateDestinations(CargoType cargo, StationID source, StationID next, uint count, CargoDataEntry *dest) + void EstimateDestinations(CargoType cargo, StationID source, StationID next, uint count, CargoDataEntry &dest) { if (Station::IsValidID(next) && Station::IsValidID(source)) { GoodsEntry &ge = Station::Get(next)->goods[cargo]; @@ -1565,19 +1558,19 @@ struct StationViewWindow : public Window { const FlowStat::SharesMap *shares = map_it->second.GetShares(); uint32_t prev_count = 0; for (FlowStat::SharesMap::const_iterator i = shares->begin(); i != shares->end(); ++i) { - tmp.InsertOrRetrieve(i->second)->Update(i->first - prev_count); + tmp.InsertOrRetrieve(i->second).Update(i->first - prev_count); prev_count = i->first; } } if (tmp.GetCount() == 0) { - dest->InsertOrRetrieve(StationID::Invalid())->Update(count); + dest.InsertOrRetrieve(StationID::Invalid()).Update(count); } else { uint sum_estimated = 0; while (sum_estimated < count) { for (CargoDataSet::iterator i = tmp.Begin(); i != tmp.End() && sum_estimated < count; ++i) { - CargoDataEntry *child = *i; - uint estimate = DivideApprox(child->GetCount() * count, tmp.GetCount()); + CargoDataEntry &child = **i; + uint estimate = DivideApprox(child.GetCount() * count, tmp.GetCount()); if (estimate == 0) estimate = 1; sum_estimated += estimate; @@ -1587,10 +1580,10 @@ struct StationViewWindow : public Window { } if (estimate > 0) { - if (child->GetStation() == next) { - dest->InsertOrRetrieve(next)->Update(estimate); + if (child.GetStation() == next) { + dest.InsertOrRetrieve(next).Update(estimate); } else { - EstimateDestinations(cargo, source, child->GetStation(), estimate, dest); + EstimateDestinations(cargo, source, child.GetStation(), estimate, dest); } } } @@ -1598,7 +1591,7 @@ struct StationViewWindow : public Window { } } } else { - dest->InsertOrRetrieve(StationID::Invalid())->Update(count); + dest.InsertOrRetrieve(StationID::Invalid()).Update(count); } } @@ -1618,8 +1611,8 @@ struct StationViewWindow : public Window { for (FlowStat::SharesMap::const_iterator flow_it = shares->begin(); flow_it != shares->end(); ++flow_it) { const CargoDataEntry *via_entry = source_entry->Retrieve(flow_it->second); for (CargoDataSet::iterator dest_it = via_entry->Begin(); dest_it != via_entry->End(); ++dest_it) { - CargoDataEntry *dest_entry = *dest_it; - ShowCargo(entry, cargo, from, flow_it->second, dest_entry->GetStation(), dest_entry->GetCount()); + CargoDataEntry &dest_entry = **dest_it; + ShowCargo(entry, cargo, from, flow_it->second, dest_entry.GetStation(), dest_entry.GetCount()); } } } @@ -1652,7 +1645,7 @@ struct StationViewWindow : public Window { uint remaining = cp->Count(); for (CargoDataSet::iterator dest_it = via_entry->Begin(); dest_it != via_entry->End();) { - CargoDataEntry *dest_entry = *dest_it; + CargoDataEntry &dest_entry = **dest_it; /* Advance iterator here instead of in the for statement to test whether this is the last entry */ ++dest_it; @@ -1664,10 +1657,10 @@ struct StationViewWindow : public Window { * not matching GoodsEntry::TotalCount() */ val = remaining; } else { - val = std::min(remaining, DivideApprox(cp->Count() * dest_entry->GetCount(), via_entry->GetCount())); + val = std::min(remaining, DivideApprox(cp->Count() * dest_entry.GetCount(), via_entry->GetCount())); remaining -= val; } - this->ShowCargo(entry, cargo, cp->GetFirstStation(), next, dest_entry->GetStation(), val); + this->ShowCargo(entry, cargo, cp->GetFirstStation(), next, dest_entry.GetStation(), val); } } this->ShowCargo(entry, cargo, NEW_STATION, NEW_STATION, NEW_STATION, packets.ReservedCount()); @@ -1701,16 +1694,16 @@ struct StationViewWindow : public Window { * Mark a specific row, characterized by its CargoDataEntry, as expanded. * @param entry The row to be marked as expanded. */ - void SetDisplayedRow(const CargoDataEntry *entry) + void SetDisplayedRow(const CargoDataEntry &entry) { std::list stations; - const CargoDataEntry *parent = entry->GetParent(); + const CargoDataEntry *parent = entry.GetParent(); if (parent->GetParent() == nullptr) { - this->displayed_rows.push_back(RowDisplay(&this->expanded_rows, entry->GetCargo())); + this->displayed_rows.push_back(RowDisplay(&this->expanded_rows, entry.GetCargo())); return; } - StationID next = entry->GetStation(); + StationID next = entry.GetStation(); while (parent->GetParent()->GetParent() != nullptr) { stations.push_back(parent->GetStation()); parent = parent->GetParent(); @@ -1764,9 +1757,9 @@ struct StationViewWindow : public Window { * @param column The "column" the entry will be shown in. * @return either STR_STATION_VIEW_VIA or STR_STATION_VIEW_NONSTOP. */ - StringID SearchNonStop(CargoDataEntry *cd, StationID station, int column) + StringID SearchNonStop(CargoDataEntry &cd, StationID station, int column) { - CargoDataEntry *parent = cd->GetParent(); + CargoDataEntry *parent = cd.GetParent(); for (int i = column - 1; i > 0; --i) { if (this->groupings[i] == GR_DESTINATION) { if (parent->GetStation() == station) { @@ -1779,9 +1772,9 @@ struct StationViewWindow : public Window { } if (this->groupings[column + 1] == GR_DESTINATION) { - CargoDataSet::iterator begin = cd->Begin(); - CargoDataSet::iterator end = cd->End(); - if (begin != end && ++(cd->Begin()) == end && (*(begin))->GetStation() == station) { + CargoDataSet::iterator begin = cd.Begin(); + CargoDataSet::iterator end = cd.End(); + if (begin != end && ++(cd.Begin()) == end && (*(begin))->GetStation() == station) { return STR_STATION_VIEW_NONSTOP; } else { return STR_STATION_VIEW_VIA; @@ -1801,20 +1794,20 @@ struct StationViewWindow : public Window { * @param cargo Current cargo being drawn (if cargo column has been passed). * @return row (in "pos" counting) after the one we have last drawn to. */ - int DrawEntries(CargoDataEntry *entry, const Rect &r, int pos, int maxrows, int column, CargoType cargo = INVALID_CARGO) + int DrawEntries(CargoDataEntry &entry, const Rect &r, int pos, int maxrows, int column, CargoType cargo = INVALID_CARGO) { if (this->sortings[column] == CargoSortType::AsGrouping) { if (this->groupings[column] != GR_CARGO) { - entry->Resort(CargoSortType::StationString, this->sort_orders[column]); + entry.Resort(CargoSortType::StationString, this->sort_orders[column]); } } else { - entry->Resort(CargoSortType::Count, this->sort_orders[column]); + entry.Resort(CargoSortType::Count, this->sort_orders[column]); } - for (CargoDataSet::iterator i = entry->Begin(); i != entry->End(); ++i) { - CargoDataEntry *cd = *i; + for (CargoDataSet::iterator i = entry.Begin(); i != entry.End(); ++i) { + CargoDataEntry &cd = **i; Grouping grouping = this->groupings[column]; - if (grouping == GR_CARGO) cargo = cd->GetCargo(); + if (grouping == GR_CARGO) cargo = cd.GetCargo(); bool auto_distributed = _settings_game.linkgraph.GetDistributionType(cargo) != DT_MANUAL; if (pos > -maxrows && pos <= 0) { @@ -1823,10 +1816,10 @@ struct StationViewWindow : public Window { int y = r.top - pos * GetCharacterHeight(FS_NORMAL); if (this->groupings[column] == GR_CARGO) { str = STR_STATION_VIEW_WAITING_CARGO; - DrawCargoIcons(cd->GetCargo(), cd->GetCount(), r.left + this->expand_shrink_width, r.right - this->expand_shrink_width, y); + DrawCargoIcons(cd.GetCargo(), cd.GetCount(), r.left + this->expand_shrink_width, r.right - this->expand_shrink_width, y); } else { if (!auto_distributed) grouping = GR_SOURCE; - station = cd->GetStation(); + station = cd.GetStation(); str = this->GetGroupingString(grouping, station); if (grouping == GR_NEXT && str == STR_STATION_VIEW_VIA) str = this->SearchNonStop(cd, station, column); @@ -1839,11 +1832,11 @@ struct StationViewWindow : public Window { Rect text = r.Indent(column * WidgetDimensions::scaled.hsep_indent, rtl).Indent(this->expand_shrink_width, !rtl); Rect shrink = r.WithWidth(this->expand_shrink_width, !rtl); - DrawString(text.left, text.right, y, GetString(str, cargo, cd->GetCount(), station)); + DrawString(text.left, text.right, y, GetString(str, cargo, cd.GetCount(), station)); if (column < NUM_COLUMNS - 1) { const char *sym = nullptr; - if (cd->GetNumChildren() > 0) { + if (cd.GetNumChildren() > 0) { sym = "-"; } else if (auto_distributed && str != STR_STATION_VIEW_RESERVED) { sym = "+"; @@ -1852,7 +1845,7 @@ struct StationViewWindow : public Window { const GoodsEntry &ge = Station::Get(this->window_number)->goods[cargo]; if (ge.HasData()) { const StationCargoList &cargo_list = ge.GetData().cargo; - if (grouping == GR_CARGO && (cargo_list.ReservedCount() > 0 || cd->HasTransfers())) { + if (grouping == GR_CARGO && (cargo_list.ReservedCount() > 0 || cd.HasTransfers())) { sym = "+"; } } From ea0817390f1999a6867517b5ec466e5b1e47eca9 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 15 Apr 2025 23:07:24 +0100 Subject: [PATCH 030/766] Codechange: Use EnumBitSet for CzechAllowFlags. --- src/table/townname.h | 139 ++++++++++++++++++++++--------------------- src/townname.cpp | 4 +- 2 files changed, 72 insertions(+), 71 deletions(-) diff --git a/src/table/townname.h b/src/table/townname.h index e2ff9279be..149c528efa 100644 --- a/src/table/townname.h +++ b/src/table/townname.h @@ -1727,14 +1727,15 @@ static const char * const _name_czech_patmod[][3] = { /* This way the substantive can choose only some adjectives/endings: * At least one of these flags must be satisfied: */ -enum CzechAllow : uint8_t { - CZA_SHORT = 1, - CZA_MIDDLE = 2, - CZA_LONG = 4, - CZA_ALL = CZA_SHORT | CZA_MIDDLE | CZA_LONG, +enum class CzechAllowFlag : uint8_t { + Short, + Middle, + Long, }; -DECLARE_ENUM_AS_BIT_SET(CzechAllow) +using CzechAllowFlags = EnumBitSet; + +static constexpr CzechAllowFlags CZA_ALL = {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}; /* All these flags must be satisfied (in the stem->others direction): */ enum CzechChoose : uint8_t { @@ -1749,7 +1750,7 @@ DECLARE_ENUM_AS_BIT_SET(CzechChoose) struct CzechNameSubst { CzechGender gender; - CzechAllow allow; + CzechAllowFlags allow; CzechChoose choose; const char *name; }; @@ -1834,48 +1835,48 @@ static const CzechNameSubst _name_czech_subst_full[] = { /* TODO: More stems needed. --pasky */ static const CzechNameSubst _name_czech_subst_stem[] = { - { CZG_SMASC, CZA_MIDDLE, CZC_COLOR, "Kostel" }, - { CZG_SMASC, CZA_MIDDLE, CZC_COLOR, "Kl\u00e1\u0161ter" }, - { CZG_SMASC, CZA_SHORT, CZC_COLOR, "Lhot" }, - { CZG_SFEM, CZA_SHORT, CZC_COLOR, "Lhot" }, - { CZG_SFEM, CZA_SHORT, CZC_COLOR, "Hur" }, - { CZG_FREE, CZA_MIDDLE | CZA_LONG, CZC_NONE, "Sedl" }, - { CZG_FREE, CZA_SHORT | CZA_MIDDLE | CZA_LONG, CZC_COLOR, "Hrad" }, - { CZG_NFREE, CZA_MIDDLE, CZC_NONE, "Pras" }, - { CZG_NFREE, CZA_MIDDLE, CZC_NONE, "Ba\u017e" }, - { CZG_NFREE, CZA_MIDDLE, CZC_NONE, "Tes" }, - { CZG_NFREE, CZA_MIDDLE, CZC_NONE, "U\u017e" }, - { CZG_NFREE, CZA_MIDDLE | CZA_LONG, CZC_POSTFIX, "B\u0159" }, - { CZG_NFREE, CZA_MIDDLE | CZA_LONG, CZC_NONE, "Vod" }, - { CZG_NFREE, CZA_MIDDLE | CZA_LONG, CZC_NONE, "Jan" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Prach" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Kunr" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Strak" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "V\u00edt" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Vy\u0161" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "\u017dat" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "\u017der" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "St\u0159ed" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Harv" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Pruh" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Tach" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "P\u00edsn" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Jin" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Jes" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Jar" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Sok" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Hod" }, - { CZG_NFREE, CZA_LONG, CZC_NONE, "Net" }, - { CZG_FREE, CZA_LONG, CZC_NONE, "Pra\u017e" }, - { CZG_FREE, CZA_LONG, CZC_NONE, "Nerat" }, - { CZG_FREE, CZA_LONG, CZC_NONE, "Kral" }, - { CZG_FREE, CZA_LONG, CZC_NONE, "Hut" }, - { CZG_FREE, CZA_LONG, CZC_NOPOSTFIX, "Pan" }, - { CZG_FREE, CZA_SHORT | CZA_MIDDLE | CZA_LONG, CZC_NOPOSTFIX, "Odst\u0159ed" }, - { CZG_FREE, CZA_SHORT | CZA_MIDDLE | CZA_LONG, CZC_COLOR, "Mrat" }, - { CZG_FREE, CZA_LONG, CZC_COLOR, "Hlav" }, - { CZG_FREE, CZA_SHORT | CZA_MIDDLE, CZC_NONE, "M\u011b\u0159" }, - { CZG_FREE, CZA_MIDDLE | CZA_LONG, CZC_NONE, "Lip" }, + { CZG_SMASC, { CzechAllowFlag::Middle }, CZC_COLOR, "Kostel" }, + { CZG_SMASC, { CzechAllowFlag::Middle }, CZC_COLOR, "Kl\u00e1\u0161ter" }, + { CZG_SMASC, {CzechAllowFlag::Short }, CZC_COLOR, "Lhot" }, + { CZG_SFEM, {CzechAllowFlag::Short }, CZC_COLOR, "Lhot" }, + { CZG_SFEM, {CzechAllowFlag::Short }, CZC_COLOR, "Hur" }, + { CZG_FREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_NONE, "Sedl" }, + { CZG_FREE, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_COLOR, "Hrad" }, + { CZG_NFREE, { CzechAllowFlag::Middle }, CZC_NONE, "Pras" }, + { CZG_NFREE, { CzechAllowFlag::Middle }, CZC_NONE, "Ba\u017e" }, + { CZG_NFREE, { CzechAllowFlag::Middle }, CZC_NONE, "Tes" }, + { CZG_NFREE, { CzechAllowFlag::Middle }, CZC_NONE, "U\u017e" }, + { CZG_NFREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_POSTFIX, "B\u0159" }, + { CZG_NFREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_NONE, "Vod" }, + { CZG_NFREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_NONE, "Jan" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Prach" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Kunr" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Strak" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "V\u00edt" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Vy\u0161" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "\u017dat" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "\u017der" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "St\u0159ed" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Harv" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Pruh" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Tach" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "P\u00edsn" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Jin" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Jes" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Jar" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Sok" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Hod" }, + { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Net" }, + { CZG_FREE, { CzechAllowFlag::Long}, CZC_NONE, "Pra\u017e" }, + { CZG_FREE, { CzechAllowFlag::Long}, CZC_NONE, "Nerat" }, + { CZG_FREE, { CzechAllowFlag::Long}, CZC_NONE, "Kral" }, + { CZG_FREE, { CzechAllowFlag::Long}, CZC_NONE, "Hut" }, + { CZG_FREE, { CzechAllowFlag::Long}, CZC_NOPOSTFIX, "Pan" }, + { CZG_FREE, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_NOPOSTFIX, "Odst\u0159ed" }, + { CZG_FREE, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_COLOR, "Mrat" }, + { CZG_FREE, { CzechAllowFlag::Long}, CZC_COLOR, "Hlav" }, + { CZG_FREE, {CzechAllowFlag::Short, CzechAllowFlag::Middle }, CZC_NONE, "M\u011b\u0159" }, + { CZG_FREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_NONE, "Lip" }, }; /* Optional postfix inserted between stem and ending. */ @@ -1887,26 +1888,26 @@ static const char * const _name_czech_subst_postfix[] = { /* This array must have the both neutral genders at the end! */ static const CzechNameSubst _name_czech_subst_ending[] = { - { CZG_SMASC, CZA_SHORT | CZA_MIDDLE, CZC_ANY, "ec" }, - { CZG_SMASC, CZA_SHORT | CZA_MIDDLE, CZC_ANY, "\u00edn" }, - { CZG_SMASC, CZA_SHORT | CZA_MIDDLE | CZA_LONG, CZC_ANY, "ov" }, - { CZG_SMASC, CZA_SHORT | CZA_LONG, CZC_ANY, "kov" }, - { CZG_SMASC, CZA_LONG, CZC_POSTFIX, "\u00edn" }, - { CZG_SMASC, CZA_LONG, CZC_POSTFIX, "n\u00edk" }, - { CZG_SMASC, CZA_LONG, CZC_ANY, "burk" }, - { CZG_SFEM, CZA_SHORT, CZC_ANY, "ka" }, - { CZG_SFEM, CZA_MIDDLE, CZC_ANY, "inka" }, - { CZG_SFEM, CZA_MIDDLE, CZC_ANY, "n\u00e1" }, - { CZG_SFEM, CZA_LONG, CZC_ANY, "ava" }, - { CZG_PMASC, CZA_LONG, CZC_POSTFIX, "\u00edky" }, - { CZG_PMASC, CZA_LONG, CZC_ANY, "upy" }, - { CZG_PMASC, CZA_LONG, CZC_ANY, "olupy" }, - { CZG_PFEM, CZA_LONG, CZC_ANY, "avy" }, - { CZG_PFEM, CZA_SHORT | CZA_MIDDLE | CZA_LONG, CZC_ANY, "ice" }, - { CZG_PFEM, CZA_SHORT | CZA_MIDDLE | CZA_LONG, CZC_ANY, "i\u010dky" }, - { CZG_PNEUT, CZA_SHORT | CZA_MIDDLE, CZC_ANY, "na" }, - { CZG_SNEUT, CZA_SHORT | CZA_MIDDLE, CZC_ANY, "no" }, - { CZG_SNEUT, CZA_LONG, CZC_ANY, "i\u0161t\u011b" }, + { CZG_SMASC, {CzechAllowFlag::Short, CzechAllowFlag::Middle }, CZC_ANY, "ec" }, + { CZG_SMASC, {CzechAllowFlag::Short, CzechAllowFlag::Middle }, CZC_ANY, "\u00edn" }, + { CZG_SMASC, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_ANY, "ov" }, + { CZG_SMASC, {CzechAllowFlag::Short, CzechAllowFlag::Long}, CZC_ANY, "kov" }, + { CZG_SMASC, { CzechAllowFlag::Long}, CZC_POSTFIX, "\u00edn" }, + { CZG_SMASC, { CzechAllowFlag::Long}, CZC_POSTFIX, "n\u00edk" }, + { CZG_SMASC, { CzechAllowFlag::Long}, CZC_ANY, "burk" }, + { CZG_SFEM, {CzechAllowFlag::Short }, CZC_ANY, "ka" }, + { CZG_SFEM, { CzechAllowFlag::Middle }, CZC_ANY, "inka" }, + { CZG_SFEM, { CzechAllowFlag::Middle }, CZC_ANY, "n\u00e1" }, + { CZG_SFEM, { CzechAllowFlag::Long}, CZC_ANY, "ava" }, + { CZG_PMASC, { CzechAllowFlag::Long}, CZC_POSTFIX, "\u00edky" }, + { CZG_PMASC, { CzechAllowFlag::Long}, CZC_ANY, "upy" }, + { CZG_PMASC, { CzechAllowFlag::Long}, CZC_ANY, "olupy" }, + { CZG_PFEM, { CzechAllowFlag::Long}, CZC_ANY, "avy" }, + { CZG_PFEM, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_ANY, "ice" }, + { CZG_PFEM, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_ANY, "i\u010dky" }, + { CZG_PNEUT, {CzechAllowFlag::Short, CzechAllowFlag::Middle }, CZC_ANY, "na" }, + { CZG_SNEUT, {CzechAllowFlag::Short, CzechAllowFlag::Middle }, CZC_ANY, "no" }, + { CZG_SNEUT, { CzechAllowFlag::Long}, CZC_ANY, "i\u0161t\u011b" }, }; static const char * const _name_czech_suffix[] = { diff --git a/src/townname.cpp b/src/townname.cpp index b19bfef79e..7f118db394 100644 --- a/src/townname.cpp +++ b/src/townname.cpp @@ -603,7 +603,7 @@ static void MakeCzechTownName(StringBuilder &builder, uint32_t seed) /* The select criteria. */ CzechGender gender; CzechChoose choose; - CzechAllow allow; + CzechAllowFlags allow; if (do_prefix) prefix = SeedModChance(5, std::size(_name_czech_adj) * 12, seed) / 12; if (do_suffix) suffix = SeedModChance(7, std::size(_name_czech_suffix), seed); @@ -671,7 +671,7 @@ static void MakeCzechTownName(StringBuilder &builder, uint32_t seed) for (ending = ending_start; ending <= ending_stop; ending++) { const CzechNameSubst *e = &_name_czech_subst_ending[ending]; - if ((e->choose & choose) == choose && (e->allow & allow) != 0) { + if ((e->choose & choose) == choose && e->allow.Any(allow)) { map[i++] = ending; } } From 378ea52ac60a782e1aeaa3863adc46534ad90a5f Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 15 Apr 2025 23:17:41 +0100 Subject: [PATCH 031/766] Codechange: Use EnumBitSet for CzechChooseFlags. --- src/table/townname.h | 164 +++++++++++++++++++++---------------------- src/townname.cpp | 10 +-- 2 files changed, 87 insertions(+), 87 deletions(-) diff --git a/src/table/townname.h b/src/table/townname.h index 149c528efa..685e888a97 100644 --- a/src/table/townname.h +++ b/src/table/townname.h @@ -1738,26 +1738,26 @@ using CzechAllowFlags = EnumBitSet; static constexpr CzechAllowFlags CZA_ALL = {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}; /* All these flags must be satisfied (in the stem->others direction): */ -enum CzechChoose : uint8_t { - CZC_NONE = 0, // No requirements. - CZC_COLOR = 1, - CZC_POSTFIX = 2, // Matched if postfix was inserted. - CZC_NOPOSTFIX = 4, // Matched if no postfix was inserted. - CZC_ANY = CZC_COLOR | CZC_POSTFIX | CZC_NOPOSTFIX, +enum class CzechChooseFlag : uint8_t { + Colour, + Postfix, // Matched if postfix was inserted. + NoPostfix, // Matched if no postfix was inserted. }; -DECLARE_ENUM_AS_BIT_SET(CzechChoose) +using CzechChooseFlags = EnumBitSet; + +static constexpr CzechChooseFlags CZC_ANY = {CzechChooseFlag::Colour, CzechChooseFlag::Postfix, CzechChooseFlag::NoPostfix}; struct CzechNameSubst { CzechGender gender; CzechAllowFlags allow; - CzechChoose choose; + CzechChooseFlags choose; const char *name; }; struct CzechNameAdj { CzechPattern pattern; - CzechChoose choose; + CzechChooseFlags choose; const char *name; }; @@ -1792,18 +1792,18 @@ static const CzechNameAdj _name_czech_adj[] = { { CZP_MLADY, CZC_ANY, "Kamenn" }, { CZP_MLADY, CZC_ANY, "Cihlov" }, { CZP_MLADY, CZC_ANY, "Divn" }, - { CZP_MLADY, CZC_COLOR, "\u010cerven" }, - { CZP_MLADY, CZC_COLOR, "\u010cerven" }, - { CZP_MLADY, CZC_COLOR, "\u010cerven" }, - { CZP_MLADY, CZC_COLOR, "Zelen" }, - { CZP_MLADY, CZC_COLOR, "\u017dlut" }, - { CZP_MLADY, CZC_COLOR, "Siv" }, - { CZP_MLADY, CZC_COLOR, "\u0160ed" }, - { CZP_MLADY, CZC_COLOR, "B\u00edl" }, - { CZP_MLADY, CZC_COLOR, "B\u00edl" }, - { CZP_MLADY, CZC_COLOR, "Modr" }, - { CZP_MLADY, CZC_COLOR, "R\u016f\u017eov" }, - { CZP_MLADY, CZC_COLOR, "\u010cern" }, + { CZP_MLADY, CzechChooseFlag::Colour, "\u010cerven" }, + { CZP_MLADY, CzechChooseFlag::Colour, "\u010cerven" }, + { CZP_MLADY, CzechChooseFlag::Colour, "\u010cerven" }, + { CZP_MLADY, CzechChooseFlag::Colour, "Zelen" }, + { CZP_MLADY, CzechChooseFlag::Colour, "\u017dlut" }, + { CZP_MLADY, CzechChooseFlag::Colour, "Siv" }, + { CZP_MLADY, CzechChooseFlag::Colour, "\u0160ed" }, + { CZP_MLADY, CzechChooseFlag::Colour, "B\u00edl" }, + { CZP_MLADY, CzechChooseFlag::Colour, "B\u00edl" }, + { CZP_MLADY, CzechChooseFlag::Colour, "Modr" }, + { CZP_MLADY, CzechChooseFlag::Colour, "R\u016f\u017eov" }, + { CZP_MLADY, CzechChooseFlag::Colour, "\u010cern" }, { CZP_PRIVL, CZC_ANY, "Kr\u00e1l" }, { CZP_PRIVL, CZC_ANY, "Jan" }, { CZP_PRIVL, CZC_ANY, "Karl" }, @@ -1815,68 +1815,68 @@ static const CzechNameAdj _name_czech_adj[] = { /* Considered a stem for choose/allow matching purposes. */ static const CzechNameSubst _name_czech_subst_full[] = { - { CZG_SMASC, CZA_ALL, CZC_COLOR, "Sedlec" }, - { CZG_SMASC, CZA_ALL, CZC_COLOR, "Brod" }, - { CZG_SMASC, CZA_ALL, CZC_COLOR, "Brod" }, - { CZG_SMASC, CZA_ALL, CZC_NONE, "\u00daval" }, - { CZG_SMASC, CZA_ALL, CZC_COLOR, "\u017d\u010f\u00e1r" }, - { CZG_SMASC, CZA_ALL, CZC_COLOR, "Smrk" }, - { CZG_SFEM, CZA_ALL, CZC_COLOR, "Hora" }, - { CZG_SFEM, CZA_ALL, CZC_COLOR, "Lhota" }, - { CZG_SFEM, CZA_ALL, CZC_COLOR, "Lhota" }, - { CZG_SFEM, CZA_ALL, CZC_COLOR, "Hlava" }, - { CZG_SFEM, CZA_ALL, CZC_COLOR, "L\u00edpa" }, - { CZG_SNEUT, CZA_ALL, CZC_COLOR, "Pole" }, - { CZG_SNEUT, CZA_ALL, CZC_COLOR, "\u00dadol\u00ed" }, - { CZG_PMASC, CZA_ALL, CZC_NONE, "\u00davaly" }, - { CZG_PFEM, CZA_ALL, CZC_COLOR, "Luka" }, - { CZG_PNEUT, CZA_ALL, CZC_COLOR, "Pole" }, + { CZG_SMASC, CZA_ALL, CzechChooseFlag::Colour, "Sedlec" }, + { CZG_SMASC, CZA_ALL, CzechChooseFlag::Colour, "Brod" }, + { CZG_SMASC, CZA_ALL, CzechChooseFlag::Colour, "Brod" }, + { CZG_SMASC, CZA_ALL, {}, "\u00daval" }, + { CZG_SMASC, CZA_ALL, CzechChooseFlag::Colour, "\u017d\u010f\u00e1r" }, + { CZG_SMASC, CZA_ALL, CzechChooseFlag::Colour, "Smrk" }, + { CZG_SFEM, CZA_ALL, CzechChooseFlag::Colour, "Hora" }, + { CZG_SFEM, CZA_ALL, CzechChooseFlag::Colour, "Lhota" }, + { CZG_SFEM, CZA_ALL, CzechChooseFlag::Colour, "Lhota" }, + { CZG_SFEM, CZA_ALL, CzechChooseFlag::Colour, "Hlava" }, + { CZG_SFEM, CZA_ALL, CzechChooseFlag::Colour, "L\u00edpa" }, + { CZG_SNEUT, CZA_ALL, CzechChooseFlag::Colour, "Pole" }, + { CZG_SNEUT, CZA_ALL, CzechChooseFlag::Colour, "\u00dadol\u00ed" }, + { CZG_PMASC, CZA_ALL, {}, "\u00davaly" }, + { CZG_PFEM, CZA_ALL, CzechChooseFlag::Colour, "Luka" }, + { CZG_PNEUT, CZA_ALL, CzechChooseFlag::Colour, "Pole" }, }; /* TODO: More stems needed. --pasky */ static const CzechNameSubst _name_czech_subst_stem[] = { - { CZG_SMASC, { CzechAllowFlag::Middle }, CZC_COLOR, "Kostel" }, - { CZG_SMASC, { CzechAllowFlag::Middle }, CZC_COLOR, "Kl\u00e1\u0161ter" }, - { CZG_SMASC, {CzechAllowFlag::Short }, CZC_COLOR, "Lhot" }, - { CZG_SFEM, {CzechAllowFlag::Short }, CZC_COLOR, "Lhot" }, - { CZG_SFEM, {CzechAllowFlag::Short }, CZC_COLOR, "Hur" }, - { CZG_FREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_NONE, "Sedl" }, - { CZG_FREE, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_COLOR, "Hrad" }, - { CZG_NFREE, { CzechAllowFlag::Middle }, CZC_NONE, "Pras" }, - { CZG_NFREE, { CzechAllowFlag::Middle }, CZC_NONE, "Ba\u017e" }, - { CZG_NFREE, { CzechAllowFlag::Middle }, CZC_NONE, "Tes" }, - { CZG_NFREE, { CzechAllowFlag::Middle }, CZC_NONE, "U\u017e" }, - { CZG_NFREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_POSTFIX, "B\u0159" }, - { CZG_NFREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_NONE, "Vod" }, - { CZG_NFREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_NONE, "Jan" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Prach" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Kunr" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Strak" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "V\u00edt" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Vy\u0161" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "\u017dat" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "\u017der" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "St\u0159ed" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Harv" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Pruh" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Tach" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "P\u00edsn" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Jin" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Jes" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Jar" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Sok" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Hod" }, - { CZG_NFREE, { CzechAllowFlag::Long}, CZC_NONE, "Net" }, - { CZG_FREE, { CzechAllowFlag::Long}, CZC_NONE, "Pra\u017e" }, - { CZG_FREE, { CzechAllowFlag::Long}, CZC_NONE, "Nerat" }, - { CZG_FREE, { CzechAllowFlag::Long}, CZC_NONE, "Kral" }, - { CZG_FREE, { CzechAllowFlag::Long}, CZC_NONE, "Hut" }, - { CZG_FREE, { CzechAllowFlag::Long}, CZC_NOPOSTFIX, "Pan" }, - { CZG_FREE, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_NOPOSTFIX, "Odst\u0159ed" }, - { CZG_FREE, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_COLOR, "Mrat" }, - { CZG_FREE, { CzechAllowFlag::Long}, CZC_COLOR, "Hlav" }, - { CZG_FREE, {CzechAllowFlag::Short, CzechAllowFlag::Middle }, CZC_NONE, "M\u011b\u0159" }, - { CZG_FREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_NONE, "Lip" }, + { CZG_SMASC, { CzechAllowFlag::Middle }, CzechChooseFlag::Colour, "Kostel" }, + { CZG_SMASC, { CzechAllowFlag::Middle }, CzechChooseFlag::Colour, "Kl\u00e1\u0161ter" }, + { CZG_SMASC, {CzechAllowFlag::Short }, CzechChooseFlag::Colour, "Lhot" }, + { CZG_SFEM, {CzechAllowFlag::Short }, CzechChooseFlag::Colour, "Lhot" }, + { CZG_SFEM, {CzechAllowFlag::Short }, CzechChooseFlag::Colour, "Hur" }, + { CZG_FREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, {}, "Sedl" }, + { CZG_FREE, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CzechChooseFlag::Colour, "Hrad" }, + { CZG_NFREE, { CzechAllowFlag::Middle }, {}, "Pras" }, + { CZG_NFREE, { CzechAllowFlag::Middle }, {}, "Ba\u017e" }, + { CZG_NFREE, { CzechAllowFlag::Middle }, {}, "Tes" }, + { CZG_NFREE, { CzechAllowFlag::Middle }, {}, "U\u017e" }, + { CZG_NFREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, CzechChooseFlag::Postfix, "B\u0159" }, + { CZG_NFREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, {}, "Vod" }, + { CZG_NFREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, {}, "Jan" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Prach" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Kunr" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Strak" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "V\u00edt" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Vy\u0161" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "\u017dat" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "\u017der" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "St\u0159ed" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Harv" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Pruh" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Tach" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "P\u00edsn" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Jin" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Jes" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Jar" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Sok" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Hod" }, + { CZG_NFREE, { CzechAllowFlag::Long}, {}, "Net" }, + { CZG_FREE, { CzechAllowFlag::Long}, {}, "Pra\u017e" }, + { CZG_FREE, { CzechAllowFlag::Long}, {}, "Nerat" }, + { CZG_FREE, { CzechAllowFlag::Long}, {}, "Kral" }, + { CZG_FREE, { CzechAllowFlag::Long}, {}, "Hut" }, + { CZG_FREE, { CzechAllowFlag::Long}, CzechChooseFlag::NoPostfix, "Pan" }, + { CZG_FREE, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CzechChooseFlag::NoPostfix, "Odst\u0159ed" }, + { CZG_FREE, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CzechChooseFlag::Colour, "Mrat" }, + { CZG_FREE, { CzechAllowFlag::Long}, CzechChooseFlag::Colour, "Hlav" }, + { CZG_FREE, {CzechAllowFlag::Short, CzechAllowFlag::Middle }, {}, "M\u011b\u0159" }, + { CZG_FREE, { CzechAllowFlag::Middle, CzechAllowFlag::Long}, {}, "Lip" }, }; /* Optional postfix inserted between stem and ending. */ @@ -1892,14 +1892,14 @@ static const CzechNameSubst _name_czech_subst_ending[] = { { CZG_SMASC, {CzechAllowFlag::Short, CzechAllowFlag::Middle }, CZC_ANY, "\u00edn" }, { CZG_SMASC, {CzechAllowFlag::Short, CzechAllowFlag::Middle, CzechAllowFlag::Long}, CZC_ANY, "ov" }, { CZG_SMASC, {CzechAllowFlag::Short, CzechAllowFlag::Long}, CZC_ANY, "kov" }, - { CZG_SMASC, { CzechAllowFlag::Long}, CZC_POSTFIX, "\u00edn" }, - { CZG_SMASC, { CzechAllowFlag::Long}, CZC_POSTFIX, "n\u00edk" }, + { CZG_SMASC, { CzechAllowFlag::Long}, CzechChooseFlag::Postfix, "\u00edn" }, + { CZG_SMASC, { CzechAllowFlag::Long}, CzechChooseFlag::Postfix, "n\u00edk" }, { CZG_SMASC, { CzechAllowFlag::Long}, CZC_ANY, "burk" }, { CZG_SFEM, {CzechAllowFlag::Short }, CZC_ANY, "ka" }, { CZG_SFEM, { CzechAllowFlag::Middle }, CZC_ANY, "inka" }, { CZG_SFEM, { CzechAllowFlag::Middle }, CZC_ANY, "n\u00e1" }, { CZG_SFEM, { CzechAllowFlag::Long}, CZC_ANY, "ava" }, - { CZG_PMASC, { CzechAllowFlag::Long}, CZC_POSTFIX, "\u00edky" }, + { CZG_PMASC, { CzechAllowFlag::Long}, CzechChooseFlag::Postfix, "\u00edky" }, { CZG_PMASC, { CzechAllowFlag::Long}, CZC_ANY, "upy" }, { CZG_PMASC, { CzechAllowFlag::Long}, CZC_ANY, "olupy" }, { CZG_PFEM, { CzechAllowFlag::Long}, CZC_ANY, "avy" }, diff --git a/src/townname.cpp b/src/townname.cpp index 7f118db394..0b6a4380f1 100644 --- a/src/townname.cpp +++ b/src/townname.cpp @@ -602,7 +602,7 @@ static void MakeCzechTownName(StringBuilder &builder, uint32_t seed) /* The select criteria. */ CzechGender gender; - CzechChoose choose; + CzechChooseFlags choose; CzechAllowFlags allow; if (do_prefix) prefix = SeedModChance(5, std::size(_name_czech_adj) * 12, seed) / 12; @@ -632,18 +632,18 @@ static void MakeCzechTownName(StringBuilder &builder, uint32_t seed) /* Load the postfix (1:1 chance that a postfix will be inserted) */ postfix = SeedModChance(14, std::size(_name_czech_subst_postfix) * 2, seed); - if (choose & CZC_POSTFIX) { + if (choose.Test(CzechChooseFlag::Postfix)) { /* Always get a real postfix. */ postfix %= std::size(_name_czech_subst_postfix); } - if (choose & CZC_NOPOSTFIX) { + if (choose.Test(CzechChooseFlag::NoPostfix)) { /* Always drop a postfix. */ postfix += std::size(_name_czech_subst_postfix); } if (postfix < std::size(_name_czech_subst_postfix)) { - choose |= CZC_POSTFIX; + choose.Set(CzechChooseFlag::Postfix); } else { - choose |= CZC_NOPOSTFIX; + choose.Set(CzechChooseFlag::NoPostfix); } /* Localize the array segment containing a good gender */ From 1f83ea41fcedf8ecf5fe4d232b78b56f51f5d362 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Wed, 16 Apr 2025 22:27:36 +0100 Subject: [PATCH 032/766] Fix: [NewGRF] PrepareLayout worked on a copy of the data resulting in bad sprite layouts. (#14013) --- src/newgrf_commons.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/newgrf_commons.cpp b/src/newgrf_commons.cpp index a46b5477d6..3d28b360ad 100644 --- a/src/newgrf_commons.cpp +++ b/src/newgrf_commons.cpp @@ -617,7 +617,7 @@ uint32_t NewGRFSpriteLayout::PrepareLayout(uint32_t orig_offset, uint32_t newgrf * and apply the default sprite offsets (unless disabled). */ const TileLayoutRegisters *regs = this->registers.empty() ? nullptr : this->registers.data(); bool ground = true; - for (DrawTileSeqStruct result : result_seq) { + for (DrawTileSeqStruct &result : result_seq) { TileLayoutFlags flags = TLF_NOTHING; if (regs != nullptr) flags = regs->flags; From 421f202539858282cb42c953676b9e210afc7b5e Mon Sep 17 00:00:00 2001 From: translators Date: Thu, 17 Apr 2025 04:45:26 +0000 Subject: [PATCH 033/766] Update: Translations from eints luxembourgish: 103 changes by phreeze83 latvian: 1 change by lexuslatvia --- src/lang/latvian.txt | 2 +- src/lang/luxembourgish.txt | 161 ++++++++++++++++++++++++------------- 2 files changed, 104 insertions(+), 59 deletions(-) diff --git a/src/lang/latvian.txt b/src/lang/latvian.txt index 14be5c60bc..af11240a97 100644 --- a/src/lang/latvian.txt +++ b/src/lang/latvian.txt @@ -2022,7 +2022,7 @@ STR_CONFIG_SETTING_SOFT_LIMIT :Maksimālais lo STR_CONFIG_SETTING_SOFT_LIMIT_HELPTEXT :Atvērtu nepielipušo logu skaits, pirms tie tiek automātiski aizvērti lai atbrīvotu vietu jauniem logiem STR_CONFIG_SETTING_SOFT_LIMIT_VALUE :{COMMA} ###setting-zero-is-special -STR_CONFIG_SETTING_SOFT_LIMIT_DISABLED :izslēgts +STR_CONFIG_SETTING_SOFT_LIMIT_DISABLED :Izslēgts STR_CONFIG_SETTING_ZOOM_MIN :Maksimālais pietuvinājuma līmenis: {STRING} STR_CONFIG_SETTING_ZOOM_MIN_HELPTEXT :Maksimālais pietuvinājuma līmenis skatvietām. Ir jāievēro ka paaugstinot pietuvinājuma līmeni, pieaug operatīvās atmiņas izlietojums diff --git a/src/lang/luxembourgish.txt b/src/lang/luxembourgish.txt index 2941add4f2..7e070b41a3 100644 --- a/src/lang/luxembourgish.txt +++ b/src/lang/luxembourgish.txt @@ -452,6 +452,12 @@ STR_SETTINGS_MENU_SANDBOX_OPTIONS :Sandbox Optioun STR_SETTINGS_MENU_TRANSPARENCY_OPTIONS :Transparenz Optiounen STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED :Stiednimm uweisen STR_SETTINGS_MENU_STATION_NAMES_DISPLAYED :Statiounsnimm uweisen +STR_SETTINGS_MENU_STATION_NAMES_TRAIN :Garen +STR_SETTINGS_MENU_STATION_NAMES_LORRY :Camionsgaren +STR_SETTINGS_MENU_STATION_NAMES_BUS :Busarrêten +STR_SETTINGS_MENU_STATION_NAMES_SHIP :Hafen +STR_SETTINGS_MENU_STATION_NAMES_PLANE :Fluchhäfen +STR_SETTINGS_MENU_STATION_NAMES_GHOST :Geescht STR_SETTINGS_MENU_WAYPOINTS_DISPLAYED :Weepunktnimm uweisen STR_SETTINGS_MENU_SIGNS_DISPLAYED :Schëlder uweisen STR_SETTINGS_MENU_SHOW_COMPETITOR_SIGNS :Géignerschëlder an Nimm uweisen @@ -1017,6 +1023,7 @@ STR_GAME_OPTIONS_CURRENCY_IDR :Indonesesch Rup STR_GAME_OPTIONS_CURRENCY_MYR :Malaysesche Ringgit STR_GAME_OPTIONS_CURRENCY_LVL :Lettesch Lat STR_GAME_OPTIONS_CURRENCY_PTE :Portugieseschen Escudo +STR_GAME_OPTIONS_CURRENCY_UAH :Ukrainesch Hryvnia STR_GAME_OPTIONS_AUTOSAVE_FRAME :{BLACK}Autospäicheren STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_TOOLTIP :{BLACK}Wiel den Intervall aus fir d'Autospäicherung @@ -1210,7 +1217,7 @@ STR_CITY_APPROVAL_TOLERANT :Tolerant STR_CITY_APPROVAL_HOSTILE :Feindlech STR_CITY_APPROVAL_PERMISSIVE :Fräizügeg (keng Auswierkung op Firmenactiounen) -STR_WARNING_NO_SUITABLE_AI :{WHITE}Keng KI fonnt...{}KI kënnen iwwert den 'Online Content' system downgeload ginn +STR_WARNING_NO_SUITABLE_AI :{WHITE}Keng KI fonnt...{}{}KI kënnen iwwert den 'Online Content' system downgeload ginn # Settings tree window STR_CONFIG_SETTING_TREE_CAPTION :{WHITE}Astellungen @@ -1923,7 +1930,8 @@ STR_CONFIG_SETTING_ENDING_YEAR_ZERO :Nie STR_CONFIG_SETTING_ECONOMY_TYPE :Wiertschaftstyp: {STRING} ###length 2 -STR_CONFIG_SETTING_ECONOMY_TYPE_HELPTEXT :"Glaat" suergt fir méi oft a kleng Produktiounsännerungen. "Agefruer" stoppt jeglech Ännerungen an der Wiertschaft an Industrieschléissungen. Dës Astellunge kinnten keen Afloss hunn, wann en NewGRF benotzt gëtt +STR_CONFIG_SETTING_ECONOMY_TYPE_HELPTEXT :"Glaat" suergt fir méi oft a kleng Produktiounsännerungen. "Agefruer" stoppt jeglech Ännerungen an der Wiertschaft an Industrieschléissungen. Dës Astellunge kinnten keen Afloss hunn, wann en NewGRF benotzt gëtt.{}{}Industrieproduktioun pro Mount kinnt nach emmer variabel ausgesinn wann d'Ekonomie agefruer ass, wëll d'Produktioun a fixen Intervalle ugefrot gëtt an dat net exakt de equivalent zu de Méint ass +STR_CONFIG_SETTING_ECONOMY_TYPE_HELPTEXT_PERIOD :"Glaat" suergt fir méi oft a kleng Produktiounsännerungen. "Agefruer" stoppt jeglech Ännerungen an der Wiertschaft an Industrieschléissungen. Dës Astellunge kinnten keen Afloss hunn, wann en NewGRF benotzt gëtt.{}{}Industrieproduktioun pro Minut kinnt nach emmer variabel ausgesinn wann d'Ekonomie agefruer ass, wëll d'Produktioun a fixen Intervalle ugefrot gëtt an dat net exakt de equivalent zu Minuten ass ###length 3 STR_CONFIG_SETTING_ECONOMY_TYPE_ORIGINAL :Original @@ -1966,19 +1974,24 @@ STR_CONFIG_SETTING_TOWN_LAYOUT_RANDOM :Zoufälleg STR_CONFIG_SETTING_ALLOW_TOWN_ROADS :Stied däerfen Stroossen bauen: {STRING} STR_CONFIG_SETTING_ALLOW_TOWN_ROADS_HELPTEXT :Erlaabt Stied Stroossen ze bauen fir ze wuessen. Ausschalten fir d'Stiedréid dorun ze hënneren fir Stroossen selwer ze bauen STR_CONFIG_SETTING_ALLOW_TOWN_LEVEL_CROSSINGS :Stied dierfe Barrière bauen: {STRING} -STR_CONFIG_SETTING_ALLOW_TOWN_LEVEL_CROSSINGS_HELPTEXT :Wann dës Astellung ugeschalt ass, kënnen Stied Stroossen iwwert Schinne bauen +STR_CONFIG_SETTING_ALLOW_TOWN_LEVEL_CROSSINGS_HELPTEXT :Wann dës Astellung ugeschalt ass, kënne Stied Stroossen iwwert Schinne bauen STR_CONFIG_SETTING_NOISE_LEVEL :Limitéier de Bau vu Fluchhäfen geméiss dem Kaméidisniveau: {STRING} STR_CONFIG_SETTING_NOISE_LEVEL_HELPTEXT :Erlaabt de Stied de Bau vu Fluchhäfen opgront vum Kaméidisniveau ze blockéiren, wat sech op d'Gréisst vun der Stad an d'Gréisst an Distanz vum Fluchhafe baséiert. Wann dat ausgeschalt ass, erlaabt d'Stad zwee Fluchhäfen ausser d'Gemengenastellung ass "Fräizügeg" STR_CONFIG_SETTING_TOWN_FOUNDING :Stiedgrënnung am Spill: {STRING} -STR_CONFIG_SETTING_TOWN_FOUNDING_HELPTEXT :Wann dës Astellung ugeschalt ass, kënnen Spiller nei Stied am Spill grënnen +STR_CONFIG_SETTING_TOWN_FOUNDING_HELPTEXT :Wann dës Astellung ugeschalt ass, kënne Spiller nei Stied am Spill grënnen ###length 3 STR_CONFIG_SETTING_TOWN_FOUNDING_FORBIDDEN :Verbueden STR_CONFIG_SETTING_TOWN_FOUNDING_ALLOWED :Erlaabt STR_CONFIG_SETTING_TOWN_FOUNDING_ALLOWED_CUSTOM_LAYOUT :Erlaabt, eegene Stad-Layout +STR_CONFIG_SETTING_HOUSE_PLACER :Individuell Haiser plazéiren: {STRING} +STR_CONFIG_SETTING_HOUSE_PLACER_HELPTEXT :Wann des Astellung ugeschalt ass, kënne Spiller Haiser manuell plazéiren ###length 3 +STR_CONFIG_SETTING_HOUSE_PLACER_FORBIDDEN :Verbueden +STR_CONFIG_SETTING_HOUSE_PLACER_ALLOWED :Erlabt +STR_CONFIG_SETTING_HOUSE_PLACER_FULLY_CONSTRUCTED :Erlabt, fäerdeg gebaut STR_CONFIG_SETTING_TOWN_CARGOGENMODE :Duerfwuerengeneratioun: {STRING} STR_CONFIG_SETTING_TOWN_CARGOGENMODE_HELPTEXT :Wéivill Wuere produzéiert ginn, relativ zur Bevölkerung vum Duerf.{}Quadratesche Wuesstum: En duebel sou grousst Duerf, generéiert véier mol souvill Passagéier.{}Lineare Wuesstum: En duebel sou grousst Duerf, generéiert duebel souvill Passagéier @@ -2007,7 +2020,7 @@ STR_CONFIG_SETTING_SOFT_LIMIT :Maximal Unzuel STR_CONFIG_SETTING_SOFT_LIMIT_HELPTEXT :Unzuel un net-gepinnten openen Fënster befier al Fënsteren automatesch zougemaach ginn fir nei Plaz ze man fir nei Fënsteren STR_CONFIG_SETTING_SOFT_LIMIT_VALUE :{COMMA} ###setting-zero-is-special -STR_CONFIG_SETTING_SOFT_LIMIT_DISABLED :ausgeschalt +STR_CONFIG_SETTING_SOFT_LIMIT_DISABLED :Ausgeschalt STR_CONFIG_SETTING_ZOOM_MIN :Maximale Razoom Level: {STRING} STR_CONFIG_SETTING_ZOOM_MIN_HELPTEXT :Maximal Razoomstuf fir Usiichtsfënsteren. Et gëtt méi Späicher gebraucht wann d'Stufen ze grouss ginn @@ -2192,7 +2205,7 @@ STR_INTRO_HIGHSCORE :{BLACK}Beschtel STR_INTRO_HELP :{BLACK}Hëllef & Handbuch STR_INTRO_CONFIG_SETTINGS_TREE :{BLACK}Astellungen STR_INTRO_NEWGRF_SETTINGS :{BLACK}NewGRF Astellungen -STR_INTRO_ONLINE_CONTENT :{BLACK}Check Online Inhalt +STR_INTRO_ONLINE_CONTENT :{BLACK}Online Content STR_INTRO_AI_SETTINGS :{BLACK}KI Astellungen STR_INTRO_GAMESCRIPT_SETTINGS :{BLACK}Spillscriptastellungen STR_INTRO_QUIT :{BLACK}Eraus @@ -2241,6 +2254,7 @@ STR_HELP_WINDOW_README :{BLACK}Readme STR_HELP_WINDOW_CHANGELOG :{BLACK}Changelog STR_HELP_WINDOW_KNOWN_BUGS :{BLACK}Bekannte Bugs STR_HELP_WINDOW_LICENSE :{BLACK}Lizenz +STR_HELP_WINDOW_FONTS :{BLACK}Schrëften STR_HELP_WINDOW_MAIN_WEBSITE :{BLACK}OpenTTD STR_HELP_WINDOW_MANUAL_WIKI :{BLACK}Handbuch / Wiki STR_HELP_WINDOW_BUGTRACKER :{BLACK}E Bug melden @@ -2629,13 +2643,14 @@ STR_NETWORK_SERVER_MESSAGE_GAME_REASON_LINK_GRAPH :warden op d'Akt STR_NETWORK_MESSAGE_CLIENT_LEAVING :verloossen STR_NETWORK_MESSAGE_CLIENT_JOINED :*** {STRING} ass dem Spill bäigetrueden STR_NETWORK_MESSAGE_CLIENT_JOINED_ID :*** {STRING} ass an d'Spill komm (Client #{NUM}) +STR_NETWORK_MESSAGE_CLIENT_COMPANY_JOIN :*** {STRING} ass bei d'Firma {STRING} gaang STR_NETWORK_MESSAGE_CLIENT_COMPANY_SPECTATE :*** {STRING} ass als Zuschauer do STR_NETWORK_MESSAGE_CLIENT_COMPANY_NEW :*** {STRING} huet eng nei Firma gegrënnt (#{NUM}) STR_NETWORK_MESSAGE_CLIENT_LEFT :*** {STRING} huet d'Spill verlooss ({STRING}) STR_NETWORK_MESSAGE_NAME_CHANGE :*** {STRING} huet säin Numm op {STRING} gewiesselt STR_NETWORK_MESSAGE_GIVE_MONEY :*** {STRING} huet {CURRENCY_LONG} der Firma {STRING} ginn STR_NETWORK_MESSAGE_SERVER_SHUTDOWN :{WHITE}De Server huet d'Sessioun zougemaach -STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}De Server gëtt nei gestart...{}W.e.g. waarden... +STR_NETWORK_MESSAGE_SERVER_REBOOT :{WHITE}De Server gëtt nei gestart...{}{}W.e.g. waarden... STR_NETWORK_MESSAGE_KICKED :*** {STRING} gouf gekickt. Grond: ({STRING}) STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED :{WHITE}Server Registration huet net geklappt @@ -2644,7 +2659,7 @@ STR_NETWORK_ERROR_COORDINATOR_ISOLATED :{WHITE}De Serve STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL :{WHITE}Aner Spiller wäerte sech net op de Server verbanne kënnen # Content downloading window -STR_CONTENT_TITLE :{WHITE}Lueden Inhalt erof +STR_CONTENT_TITLE :{WHITE}Online Content STR_CONTENT_TYPE_CAPTION :{BLACK}Typ STR_CONTENT_TYPE_CAPTION_TOOLTIP :{BLACK}Typ vum Inhalt STR_CONTENT_NAME_CAPTION :{BLACK}Numm @@ -2843,6 +2858,7 @@ STR_HOUSE_PICKER_YEARS_FROM :{BLACK}Joer: {O STR_HOUSE_PICKER_YEARS_UNTIL :{BLACK}Joer: {ORANGE}Bis {NUM} STR_HOUSE_PICKER_SIZE :{BLACK}Gréisst: {ORANGE}{NUM}x{NUM} Felder STR_HOUSE_PICKER_CARGO_ACCEPTED :{BLACK}Akzeptéiert Wuer: {ORANGE} +STR_HOUSE_PICKER_CARGO_PRODUCED :{BLACK}Produzéiert Wuer: {ORANGE}{CARGO_LIST} STR_HOUSE_PICKER_CLASS_ZONE1 :Rand STR_HOUSE_PICKER_CLASS_ZONE2 :Ausseberäich @@ -2850,6 +2866,10 @@ STR_HOUSE_PICKER_CLASS_ZONE3 :Aner Viruerter STR_HOUSE_PICKER_CLASS_ZONE4 :Bannescht Viruerter STR_HOUSE_PICKER_CLASS_ZONE5 :Stadzentrum +STR_HOUSE_PICKER_PROTECT_TITLE :Upgrades verhënneren +STR_HOUSE_PICKER_PROTECT_TOOLTIP :Wiel aus ob dëst Haus firun engem Ersetze geschützt gëtt wann d'Stad wiisst +STR_HOUSE_PICKER_PROTECT_OFF :Aus +STR_HOUSE_PICKER_PROTECT_ON :Un STR_STATION_CLASS_DFLT :Standard STR_STATION_CLASS_DFLT_STATION :Standardstatioun @@ -2883,6 +2903,7 @@ STR_SELECT_ROAD_BRIDGE_CAPTION :{WHITE}Stroosse STR_SELECT_BRIDGE_SELECTION_TOOLTIP :{BLACK}Bréckenauswiel - Wiel deng Bréck aus STR_SELECT_BRIDGE_INFO_NAME :{GOLD}{STRING} STR_SELECT_BRIDGE_INFO_NAME_MAX_SPEED :{GOLD}{STRING},{} {VELOCITY} +STR_SELECT_BRIDGE_INFO_NAME_COST :{GOLD}{STRING},{} {WHITE}{CURRENCY_LONG} STR_SELECT_BRIDGE_INFO_NAME_MAX_SPEED_COST :{GOLD}{STRING},{} {VELOCITY} {WHITE}{CURRENCY_LONG} STR_BRIDGE_NAME_SUSPENSION_STEEL :Stolen Hängebréck STR_BRIDGE_NAME_GIRDER_STEEL :Stoldréier Bréck @@ -2941,7 +2962,7 @@ STR_STATION_BUILD_CARGO_TRAM_ORIENTATION_TOOLTIP :{BLACK}Wueren-T # Waterways toolbar (last two for SE only) STR_WATERWAYS_TOOLBAR_CAPTION :{WHITE}Waasserstroosse bauen STR_WATERWAYS_TOOLBAR_CAPTION_SE :{WHITE}Waasserstroossen -STR_WATERWAYS_TOOLBAR_BUILD_CANALS_TOOLTIP :{BLACK}Kanäl bauen. Dréck Shift fir nëmmen déi ongeféier Käschten unzeweisen +STR_WATERWAYS_TOOLBAR_BUILD_CANALS_TOOLTIP :{BLACK}Kanäl bauen. Ctrl+Klick+Zéien fir wielt d'Areal diagonal aus. Dréck Shift fir nëmmen déi ongeféier Käschten unzeweisen STR_WATERWAYS_TOOLBAR_BUILD_LOCKS_TOOLTIP :{BLACK}Schleise bauen. Dréck Shift fir nëmmen déi ongeféier Käschten unzeweisen STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP :{BLACK}Schëffsschapp bauen (fir Schëffer ze bauen an ze reparéiren). Dréck Shift fir nëmmen déi ongeféier Käschten unzeweisen STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP :{BLACK}Schëffsdock bauen. Ctrl aktivéiert ubaue vu Statiounen. Shift wiesselt tëscht bauen/ongeféier Käschten uweisen @@ -3126,6 +3147,8 @@ STR_LANG_AREA_INFORMATION_TRAM_TYPE :{BLACK}Tramtyp: STR_LANG_AREA_INFORMATION_RAIL_SPEED_LIMIT :{BLACK}Schinne-Geschw.-Limit: {LTBLUE}{VELOCITY} STR_LANG_AREA_INFORMATION_ROAD_SPEED_LIMIT :{BLACK}Stroosse-Geschw.-Limit: {LTBLUE}{VELOCITY} STR_LANG_AREA_INFORMATION_TRAM_SPEED_LIMIT :{BLACK}Tram-Geschw.-Limit: {LTBLUE}{VELOCITY} +STR_LAND_AREA_INFORMATION_TOWN_CAN_UPGRADE :{BLACK}Stad kann upgraden: {LTBLUE}Jo +STR_LAND_AREA_INFORMATION_TOWN_CANNOT_UPGRADE :{BLACK}Stad kann upgraden: {LTBLUE}Nee # Description of land area of different tiles STR_LAI_CLEAR_DESCRIPTION_ROCKS :Fielsen @@ -3134,6 +3157,9 @@ STR_LAI_CLEAR_DESCRIPTION_BARE_LAND :Eidelt Land STR_LAI_CLEAR_DESCRIPTION_GRASS :Gras STR_LAI_CLEAR_DESCRIPTION_FIELDS :Felder STR_LAI_CLEAR_DESCRIPTION_DESERT :Wüst +STR_LAI_CLEAR_DESCRIPTION_SNOWY_ROCKS :Schnéibedeckt Fielsen +STR_LAI_CLEAR_DESCRIPTION_SNOWY_ROUGH_LAND :Schnéibedeckt knubbelegt Land +STR_LAI_CLEAR_DESCRIPTION_SNOWY_GRASS :Schnéibedeckt Gras STR_LAI_RAIL_DESCRIPTION_TRACK :Schinn STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_NORMAL_SIGNALS :Schinn mat Blocksignaler @@ -3605,17 +3631,17 @@ STR_NEWGRF_LIST_COMPATIBLE :{YELLOW}Kompati STR_NEWGRF_LIST_MISSING :{RED}Dateien feelen # NewGRF 'it's broken' warnings -STR_NEWGRF_BROKEN :{WHITE}Verhalen vun der NewGRF '{0:STRING}' kann Desyncs oder Crashen verursachen -STR_NEWGRF_BROKEN_POWERED_WAGON :{WHITE}Ännert den Zoustand fir '{1:ENGINE}' wann net an engem Schapp -STR_NEWGRF_BROKEN_VEHICLE_LENGTH :{WHITE}Ännert d'Gefierlängt fir '{1:ENGINE}' wann net an engem Schapp -STR_NEWGRF_BROKEN_CAPACITY :{WHITE}Geännerte Gefierkapazitéit vun '{1:ENGINE}' wann net an engem Schapp oder amgaang emgebaut ze ginn +STR_NEWGRF_BROKEN :{WHITE}Verhalen vun der NewGRF '{PUSH_COLOUR}{0:STRING}{POP_COLOUR}' kann Desyncs oder Crashen verursachen +STR_NEWGRF_BROKEN_POWERED_WAGON :{WHITE}Ännert den Zoustand fir '{PUSH_COLOUR}{1:ENGINE}{POP_COLOUR}' wann net an engem Schapp +STR_NEWGRF_BROKEN_VEHICLE_LENGTH :{WHITE}Ännert d'Gefierlängt fir '{PUSH_COLOUR}{1:ENGINE}{POP_COLOUR}' wann net an engem Schapp +STR_NEWGRF_BROKEN_CAPACITY :{WHITE}Geännerte Gefierkapazitéit vun '{PUSH_COLOUR}{1:ENGINE}{POP_COLOUR}' wann net an engem Schapp oder amgaang emgebaut ze ginn STR_BROKEN_VEHICLE_LENGTH :{WHITE}Zuch'{VEHICLE}' vun der Firma '{COMPANY}' huet eng falsch Längt. Et kënnt wahrscheinlech wéinst den NewGRFs. Spill kann desyncroniséiren oder ofstierzen -STR_NEWGRF_BUGGY :{WHITE}NewGRF '{0:STRING}' huet Fehlinformatiounen -STR_NEWGRF_BUGGY_ARTICULATED_CARGO :{WHITE}Cargo/refit Informatioun fir '{1:ENGINE}' ass anescht wéi an der Kaflëscht no der Constructioun. Dëst kann en Autoerneirung/-ersetzen Feeler oprufen -STR_NEWGRF_BUGGY_ENDLESS_PRODUCTION_CALLBACK :{WHITE}'{1:STRING}' huet eng Endlosschläif am Production callback verursaacht +STR_NEWGRF_BUGGY :{WHITE}NewGRF '{PUSH_COLOUR}{0:STRING}{POP_COLOUR}' huet Fehlinformatiounen +STR_NEWGRF_BUGGY_ARTICULATED_CARGO :{WHITE}Cargo/refit Informatioun fir '{PUSH_COLOUR}{1:ENGINE}{POP_COLOUR}' ass anescht wéi an der Kaflëscht no der Constructioun. Dëst kann en Autoerneirung/-ersetzen Feeler oprufen +STR_NEWGRF_BUGGY_ENDLESS_PRODUCTION_CALLBACK :{WHITE}'{PUSH_COLOUR}{1:STRING}{POP_COLOUR}' huet eng Endlosschläif am Production callback verursaacht STR_NEWGRF_BUGGY_UNKNOWN_CALLBACK_RESULT :{WHITE}Callback {1:HEX} huet en onbekannten/invalid Resultat {2:HEX} -STR_NEWGRF_BUGGY_INVALID_CARGO_PRODUCTION_CALLBACK :{WHITE}'{1:STRING}' huet en ongültegen Wuerentyp am Production-callback bei {2:HEX} +STR_NEWGRF_BUGGY_INVALID_CARGO_PRODUCTION_CALLBACK :{WHITE}'{PUSH_COLOUR}{1:STRING}{POP_COLOUR}' huet en ongültegen Wuerentyp am Production-callback bei {2:HEX} # 'User removed essential NewGRFs'-placeholders for stuff without specs STR_NEWGRF_INVALID_CARGO : @@ -4011,6 +4037,10 @@ STR_INDUSTRY_VIEW_PRODUCES_N_CARGO :{BLACK}Produzé STR_INDUSTRY_VIEW_CARGO_LIST_EXTENSION :, {STRING}{STRING} STR_INDUSTRY_VIEW_REQUIRES :{BLACK}Brauch: +STR_INDUSTRY_VIEW_ACCEPT_CARGO_SUFFIX :{YELLOW}{0:STRING}{BLACK}{3:STRING} +STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_SUFFIX :{YELLOW}{0:STRING}{BLACK}: {1:CARGO_SHORT} warden{3:STRING} +STR_INDUSTRY_VIEW_ACCEPT_CARGO_NOSUFFIX :{YELLOW}{0:STRING} +STR_INDUSTRY_VIEW_ACCEPT_CARGO_AMOUNT_NOSUFFIX :{YELLOW}{0:STRING}{BLACK}: {1:CARGO_SHORT} warden STR_CONFIG_GAME_PRODUCTION :{WHITE}D'Produktioun änneren (Multipel vun 8, bis op 2040) STR_CONFIG_GAME_PRODUCTION_LEVEL :{WHITE}Änner de Produktiounslevel (Prozenter, bis zu 800%) @@ -4668,54 +4698,56 @@ STR_ORDER_ROAD_VEHICLE_DEPOT :Stroossegefier- STR_ORDER_SHIP_DEPOT :Schëffswerft ###next-name-looks-similar +STR_ORDER_GO_TO_NEAREST_HANGAR_FORMAT :{STRING} den noosten Hangar +STR_ORDER_GO_TO_NEAREST_DEPOT_FORMAT :{STRING} den noosten {STRING} STR_ORDER_GO_TO_DEPOT_FORMAT :{STRING} {DEPOT} -STR_ORDER_REFIT_ORDER :(Ëmbau op {STRING}) -STR_ORDER_REFIT_STOP_ORDER :(Ëmbau op {STRING} an stoppen) -STR_ORDER_STOP_ORDER :(Stop) +STR_ORDER_REFIT_ORDER :{SPACE}(Ëmbau op {STRING}) +STR_ORDER_REFIT_STOP_ORDER :{SPACE}(Ëmbau op {STRING} an stoppen) +STR_ORDER_STOP_ORDER :{SPACE}(Stop) -STR_ORDER_WAIT_TO_UNBUNCH :(Warden fir ze verspreeën) +STR_ORDER_WAIT_TO_UNBUNCH :{SPACE}(Warden fir ze verspreeën) STR_ORDER_GO_TO_STATION :{STRING} {STATION} STR_ORDER_GO_TO_STATION_CAN_T_USE_STATION :{PUSH_COLOUR}{RED}(Statioun kann net benotzt ginn){POP_COLOUR} {STRING} {STATION} -STR_ORDER_IMPLICIT :(Implizit) +STR_ORDER_IMPLICIT :{SPACE}(Implizit) -STR_ORDER_FULL_LOAD :(Voll lueden) -STR_ORDER_FULL_LOAD_ANY :(Voll lueden mat all Wuer) -STR_ORDER_NO_LOAD :(Net lueden) -STR_ORDER_UNLOAD :(Entlueden an Luedung huelen) -STR_ORDER_UNLOAD_FULL_LOAD :(Entlueden an erem voll lueden) -STR_ORDER_UNLOAD_FULL_LOAD_ANY :(Entlueden an mat all Wuer voll lueden) -STR_ORDER_UNLOAD_NO_LOAD :(Entlueden an eidel loosen) -STR_ORDER_TRANSFER :(Transferéieren an Lueden) -STR_ORDER_TRANSFER_FULL_LOAD :(Transferéieren an voll lueden) -STR_ORDER_TRANSFER_FULL_LOAD_ANY :(Transferéieren an mat all Wuer voll lueden) -STR_ORDER_TRANSFER_NO_LOAD :(Transferéieren an eidel loosen) -STR_ORDER_NO_UNLOAD :(Net entlueden an Wueren lueden) -STR_ORDER_NO_UNLOAD_FULL_LOAD :(Net entlueden an op voll Beluedung waarden) -STR_ORDER_NO_UNLOAD_FULL_LOAD_ANY :(Net entlueden an op iergendeng voll Beluedung waarden) -STR_ORDER_NO_UNLOAD_NO_LOAD :(Keen Ent- an Belueden) +STR_ORDER_FULL_LOAD :{SPACE}(Voll lueden) +STR_ORDER_FULL_LOAD_ANY :{SPACE}(Voll lueden mat all Wuer) +STR_ORDER_NO_LOAD :{SPACE}(Net lueden) +STR_ORDER_UNLOAD :{SPACE}(Entlueden an Luedung huelen) +STR_ORDER_UNLOAD_FULL_LOAD :{SPACE}(Entlueden an erem voll lueden) +STR_ORDER_UNLOAD_FULL_LOAD_ANY :{SPACE}(Entlueden an mat all Wuer voll lueden) +STR_ORDER_UNLOAD_NO_LOAD :{SPACE}(Entlueden an eidel loosen) +STR_ORDER_TRANSFER :{SPACE}(Transferéieren an Lueden) +STR_ORDER_TRANSFER_FULL_LOAD :{SPACE}(Transferéieren an voll lueden) +STR_ORDER_TRANSFER_FULL_LOAD_ANY :{SPACE}(Transferéieren an mat all Wuer voll lueden) +STR_ORDER_TRANSFER_NO_LOAD :{SPACE}(Transferéieren an eidel loosen) +STR_ORDER_NO_UNLOAD :{SPACE}(Net entlueden an Wueren lueden) +STR_ORDER_NO_UNLOAD_FULL_LOAD :{SPACE}(Net entlueden an op voll Beluedung waarden) +STR_ORDER_NO_UNLOAD_FULL_LOAD_ANY :{SPACE}(Net entlueden an op iergendeng voll Beluedung waarden) +STR_ORDER_NO_UNLOAD_NO_LOAD :{SPACE}(Keen Ent- an Belueden) -STR_ORDER_AUTO_REFIT :(Embauen op {STRING}) -STR_ORDER_FULL_LOAD_REFIT :(Voll lueden mat ëmbauen op {STRING}) -STR_ORDER_FULL_LOAD_ANY_REFIT :(Voll lueden mat all Wueren mat ëmbauen op {STRING}) -STR_ORDER_UNLOAD_REFIT :(Entlueden an Wueren lueden mat Embauen op {STRING}) -STR_ORDER_UNLOAD_FULL_LOAD_REFIT :(Entlueden an op voll Luedung waarden mat Embauen op {STRING}) -STR_ORDER_UNLOAD_FULL_LOAD_ANY_REFIT :(Entlueden an waard op iergendeng Volluedung mat Embauen op {STRING}) -STR_ORDER_TRANSFER_REFIT :(Transfer an lued Wueren mat Auto-Embauen zu {STRING}) -STR_ORDER_TRANSFER_FULL_LOAD_REFIT :(Transfer an waard op Vollueden mat Auto-Embauen op {STRING}) -STR_ORDER_TRANSFER_FULL_LOAD_ANY_REFIT :(Transfer an waard op iergendeng Volluedung mat Auto-Embauen op {STRING}) -STR_ORDER_NO_UNLOAD_REFIT :(Keen Entlueden an huel Wueren mat Auto-Embauen op {STRING}) -STR_ORDER_NO_UNLOAD_FULL_LOAD_REFIT :(Keen Entlueden an waard op Vollueden mat Auto-Embauen op {STRING}) -STR_ORDER_NO_UNLOAD_FULL_LOAD_ANY_REFIT :(Keen Entlueden an waard op iergendend Volluedung mat Auto-Embauen op {STRING}) +STR_ORDER_AUTO_REFIT :{SPACE}(Embauen op {STRING}) +STR_ORDER_FULL_LOAD_REFIT :{SPACE}(Voll lueden mat ëmbauen op {STRING}) +STR_ORDER_FULL_LOAD_ANY_REFIT :{SPACE}(Voll lueden mat all Wueren mat ëmbauen op {STRING}) +STR_ORDER_UNLOAD_REFIT :{SPACE}(Entlueden an Wueren lueden mat Embauen op {STRING}) +STR_ORDER_UNLOAD_FULL_LOAD_REFIT :{SPACE}(Entlueden an op voll Luedung waarden mat Embauen op {STRING}) +STR_ORDER_UNLOAD_FULL_LOAD_ANY_REFIT :{SPACE}(Entlueden an waard op iergendeng Volluedung mat Embauen op {STRING}) +STR_ORDER_TRANSFER_REFIT :{SPACE}(Transfer an lued Wueren mat Auto-Embauen zu {STRING}) +STR_ORDER_TRANSFER_FULL_LOAD_REFIT :{SPACE}(Transfer an waard op Vollueden mat Auto-Embauen op {STRING}) +STR_ORDER_TRANSFER_FULL_LOAD_ANY_REFIT :{SPACE}(Transfer an waard op iergendeng Volluedung mat Auto-Embauen op {STRING}) +STR_ORDER_NO_UNLOAD_REFIT :{SPACE}(Keen Entlueden an huel Wueren mat Auto-Embauen op {STRING}) +STR_ORDER_NO_UNLOAD_FULL_LOAD_REFIT :{SPACE}(Keen Entlueden an waard op Vollueden mat Auto-Embauen op {STRING}) +STR_ORDER_NO_UNLOAD_FULL_LOAD_ANY_REFIT :{SPACE}(Keen Entlueden an waard op iergendend Volluedung mat Auto-Embauen op {STRING}) STR_ORDER_AUTO_REFIT_ANY :verfügbar Wueren ###length 3 -STR_ORDER_STOP_LOCATION_NEAR_END :[noosten Enn] -STR_ORDER_STOP_LOCATION_MIDDLE :[Mëtt] -STR_ORDER_STOP_LOCATION_FAR_END :[wäit Enn] +STR_ORDER_STOP_LOCATION_NEAR_END :{SPACE}[no um Enn] +STR_ORDER_STOP_LOCATION_MIDDLE :{SPACE}[Mëtt] +STR_ORDER_STOP_LOCATION_FAR_END :{SPACE}[wäit Enn] STR_ORDER_OUT_OF_RANGE :{RED} (Nächst Destinatioun ass ze wäit fort) @@ -4740,8 +4772,8 @@ STR_TIMETABLE_TRAVEL_FOR :Ënnerwee währ STR_TIMETABLE_TRAVEL_FOR_SPEED :Fiert während {STRING} mat maximal {VELOCITY} STR_TIMETABLE_TRAVEL_FOR_ESTIMATED :Fuer (während {STRING}, ouni Zäitplang) STR_TIMETABLE_TRAVEL_FOR_SPEED_ESTIMATED :Fuer (während {STRING}, ouni Zäitplang) mat maximal {VELOCITY} -STR_TIMETABLE_STAY_FOR_ESTIMATED :(bleif während {STRING}, ouni Zäitplang) -STR_TIMETABLE_AND_TRAVEL_FOR_ESTIMATED :(fuer während {STRING}, ouni Zäitplang) +STR_TIMETABLE_STAY_FOR_ESTIMATED :{SPACE}(waard {STRING}, ouni Zäitplang) +STR_TIMETABLE_AND_TRAVEL_FOR_ESTIMATED :{SPACE}(fuer während {STRING}, ouni Zäitplang) STR_TIMETABLE_STAY_FOR :an bleif fir {STRING} STR_TIMETABLE_AND_TRAVEL_FOR :an ënnerwee während {STRING} @@ -4762,12 +4794,14 @@ STR_TIMETABLE_START_SECONDS_QUERY :Sekonne bis den STR_TIMETABLE_CHANGE_TIME :{BLACK}Zäit wiesselen STR_TIMETABLE_WAIT_TIME_TOOLTIP :{BLACK}Änner Zäit déi den ugewielten Optrag brauche soll. Ctrl+Klick ännert d'Zäit fir all Opträg +STR_TIMETABLE_CHANGE_TIME_QUERY :Zäit änneren STR_TIMETABLE_CLEAR_TIME :{BLACK}Zäit läschen STR_TIMETABLE_CLEAR_TIME_TOOLTIP :{BLACK}Läsch Zäit fir de gewielten Optrag. Ctrl+Klick läscht d'Zäite fir all opträg STR_TIMETABLE_CHANGE_SPEED :{BLACK}Änner de Geschwindegkeetslimit STR_TIMETABLE_CHANGE_SPEED_TOOLTIP :{BLACK}Änner d'maximal Reesgeschwindëgkeet fir de gewielten Optrag. Ctrl+Klick ännert d'Geschwindegkeet fir all Opträg +STR_TIMETABLE_CHANGE_SPEED_QUERY :Geschwindegkeetslimit änneren STR_TIMETABLE_CLEAR_SPEED :{BLACK}Geschwindegkeetslimit läschen STR_TIMETABLE_CLEAR_SPEED_TOOLTIP :{BLACK}Maximal Reesgeschwindegkeet vum gewielten Optrag läschen. Ctrl+Klick läscht d'Geschwindegkeet fir all Opträg @@ -4873,7 +4907,7 @@ STR_SCREENSHOT_MINIMAP_SCREENSHOT :{BLACK}Minimap- # Script Parameters STR_AI_SETTINGS_CAPTION_AI :KI -STR_AI_SETTINGS_CAPTION_GAMESCRIPT :Spill-Script +STR_AI_SETTINGS_CAPTION_GAMESCRIPT :{WHITE}Spill-Script-Parameter STR_AI_SETTINGS_CLOSE :{BLACK}Zoumaachen STR_AI_SETTINGS_RESET :{BLACK}Reset STR_AI_SETTINGS_SETTING :{STRING}: {ORANGE}{STRING} @@ -4934,7 +4968,7 @@ STR_GAME_SAVELOAD_NOT_AVAILABLE : STR_WARNING_LOADGAME_REMOVED_TRAMS :{WHITE}Spill gouf an enger Versioun ouni Tram support gesaved. All Tram gouf wechgeholl # Map generation messages -STR_ERROR_COULD_NOT_CREATE_TOWN :{WHITE}Kaartenerstellung ofgebrach...{}... keng passend Stadplazen +STR_ERROR_COULD_NOT_CREATE_TOWN :{WHITE}Kaartenerstellung ofgebrach...{}{}... keng passend Stadplazen STR_ERROR_NO_TOWN_IN_SCENARIO :{WHITE}... et ass keng Stad an dësem Szenario STR_ERROR_PNGMAP :{WHITE}Kann d'Landschaft net vum PNG lueden... @@ -4963,6 +4997,7 @@ STR_ERROR_SCREENSHOT_FAILED :{WHITE}Screensh # Error message titles STR_ERROR_MESSAGE_CAPTION :{YELLOW}Meldung +STR_ERROR_MESSAGE_CAPTION_OTHER_COMPANY :{YELLOW}Matdeelung vun {COMPANY} # Generic construction errors STR_ERROR_OFF_EDGE_OF_MAP :{WHITE}Ausserhalb vun der Kaart @@ -4981,6 +5016,7 @@ STR_ERROR_TERRAFORM_LIMIT_REACHED :{WHITE}... Limi STR_ERROR_CLEARING_LIMIT_REACHED :{WHITE}... Limit fir d'Raume vu Felder erreecht STR_ERROR_TREE_PLANT_LIMIT_REACHED :{WHITE}... Limit fir Beem ze planzen erreecht STR_ERROR_NAME_MUST_BE_UNIQUE :{WHITE}Numm muss eenzegarteg sinn +STR_ERROR_GENERIC_OBJECT_IN_THE_WAY :{WHITE}{STRING} am Wee STR_ERROR_NOT_ALLOWED_WHILE_PAUSED :{WHITE}Net erlaabt an der Paus # Local authority errors @@ -5023,7 +5059,7 @@ STR_ERROR_TOO_CLOSE_TO_ANOTHER_TOWN :{WHITE}... ze n STR_ERROR_TOO_MANY_TOWNS :{WHITE}... ze vill Stied STR_ERROR_NO_SPACE_FOR_TOWN :{WHITE}... et ass keng Plaz méi op der Kaart STR_ERROR_ROAD_WORKS_IN_PROGRESS :{WHITE}Stroossenarbeschten amgaangen -STR_ERROR_TOWN_CAN_T_DELETE :{WHITE}Kann des Stad net läschen...{}Eng Statioun oder Schapp huet den Numm vun dëser Stad oder en Stéck dat der Stad gehéiert kann net ewechgeholl ginn +STR_ERROR_TOWN_CAN_T_DELETE :{WHITE}Kann des Stad net läschen...{}{}Eng Statioun oder Schapp huet den Numm vun dëser Stad oder en Stéck dat der Stad gehéiert kann net ewechgeholl ginn STR_ERROR_STATUE_NO_SUITABLE_PLACE :{WHITE}... et gëtt keng gëeegent Plaz fir eng Statu am Stadzentrum STR_ERROR_CAN_T_BUILD_HOUSE :{WHITE}Kann d'Haus net bauen... @@ -5297,8 +5333,16 @@ STR_ERROR_CAN_T_SELL_SHIP :{WHITE}Kann Sch STR_ERROR_CAN_T_SELL_AIRCRAFT :{WHITE}Kann de Fliger net verkafen... ###length VEHICLE_TYPES +STR_ERROR_CAN_T_SELL_ALL_TRAIN :{WHITE}Kann net all Zich verkafen... +STR_ERROR_CAN_T_SELL_ALL_ROAD_VEHICLE :{WHITE}Kann net all Gefierer verkafen... +STR_ERROR_CAN_T_SELL_ALL_SHIP :{WHITE}Kann net all Schëffer verkafen... +STR_ERROR_CAN_T_SELL_ALL_AIRCRAFT :{WHITE}Kann all Fligeren net verkafen... ###length VEHICLE_TYPES +STR_ERROR_CAN_T_AUTOREPLACE_TRAIN :{WHITE}Kann Zich net automatesch ersetzen... +STR_ERROR_CAN_T_AUTOREPLACE_ROAD_VEHICLE :{WHITE}Ka Gefierer net automatesch ersetzen... +STR_ERROR_CAN_T_AUTOREPLACE_SHIP :{WHITE}Ka Schëffer net automatesch ersetzen... +STR_ERROR_CAN_T_AUTOREPLACE_AIRCRAFT :{WHITE}Ka Fliger net automatesch ersetzen... STR_ERROR_TOO_MANY_VEHICLES_IN_GAME :{WHITE}Ze vill Gefierer am Spill STR_ERROR_CAN_T_CHANGE_SERVICING :{WHITE}De Service-Intervall kann net geännert ginn... @@ -5818,7 +5862,7 @@ STR_SAVEGAME_NAME_DEFAULT :{COMPANY}, {STR STR_SAVEGAME_NAME_SPECTATOR :Zuschauer, {1:STRING} # Viewport strings -STR_VIEWPORT_TOWN_POP :{WHITE}{TOWN} ({COMMA}) +STR_VIEWPORT_TOWN_POP :{TOWN} ({COMMA}) STR_VIEWPORT_STATION :{STATION} {STATION_FEATURES} # Simple strings to get specific types of data @@ -5880,3 +5924,4 @@ STR_SHIP :{BLACK}{SHIP} STR_TOOLBAR_RAILTYPE_VELOCITY :{STRING} ({VELOCITY}) +STR_BADGE_NAME_LIST :{STRING}: {GOLD}{STRING} From 5aed046d11a38fc4a5c48c79cb5023424ae68b06 Mon Sep 17 00:00:00 2001 From: frosch Date: Thu, 17 Apr 2025 12:15:49 +0200 Subject: [PATCH 034/766] Fix: [NewGRF] Strange things happened, when using the synchronised tile loop animation trigger for houses. (#14011) --- src/newgrf_house.cpp | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/newgrf_house.cpp b/src/newgrf_house.cpp index 3203688e43..99cbf7b0e1 100644 --- a/src/newgrf_house.cpp +++ b/src/newgrf_house.cpp @@ -628,12 +628,20 @@ bool CanDeleteHouse(TileIndex tile) } } -static void AnimationControl(TileIndex tile, uint16_t random_bits) +/** + * Call the tile loop animation trigger for houses, if enabled. + * @param tile House tile + * @param sync Whether to call the synchronized or the unsynchronized trigger. + * @param random_bits Shared random bits for the synchronized trigger. + */ +static void AnimationControl(TileIndex tile, bool sync, uint16_t random_bits) { const HouseSpec *hs = HouseSpec::Get(GetHouseType(tile)); - if (hs->callback_mask.Test(HouseCallbackMask::AnimationStartStop)) { - uint32_t param = hs->extra_flags.Test(HouseExtraFlag::SynchronisedCallback1B) ? (GB(Random(), 0, 16) | random_bits << 16) : Random(); + /* Check whether the matching trigger is enabled */ + if (hs->callback_mask.Test(HouseCallbackMask::AnimationStartStop) && + hs->extra_flags.Test(HouseExtraFlag::SynchronisedCallback1B) == sync) { + uint32_t param = sync ? (GB(Random(), 0, 16) | random_bits << 16) : Random(); HouseAnimationBase::ChangeAnimationFrame(CBID_HOUSE_ANIMATION_START_STOP, hs, Town::GetByTile(tile), tile, param, 0); } } @@ -650,21 +658,16 @@ bool NewHouseTileLoop(TileIndex tile) TriggerHouse(tile, HOUSE_TRIGGER_TILE_LOOP); if (hs->building_flags.Any(BUILDING_HAS_1_TILE)) TriggerHouse(tile, HOUSE_TRIGGER_TILE_LOOP_TOP); - if (hs->callback_mask.Test(HouseCallbackMask::AnimationStartStop)) { - /* If this house is marked as having a synchronised callback, all the - * tiles will have the callback called at once, rather than when the - * tile loop reaches them. This should only be enabled for the northern - * tile, or strange things will happen (here, and in TTDPatch). */ - if (hs->extra_flags.Test(HouseExtraFlag::SynchronisedCallback1B)) { - uint16_t random = GB(Random(), 0, 16); + /* Call the unsynchronized tile loop trigger */ + AnimationControl(tile, false, 0); - if (hs->building_flags.Any(BUILDING_HAS_1_TILE)) AnimationControl(tile, random); - if (hs->building_flags.Any(BUILDING_2_TILES_Y)) AnimationControl(TileAddXY(tile, 0, 1), random); - if (hs->building_flags.Any(BUILDING_2_TILES_X)) AnimationControl(TileAddXY(tile, 1, 0), random); - if (hs->building_flags.Any(BUILDING_HAS_4_TILES)) AnimationControl(TileAddXY(tile, 1, 1), random); - } else { - AnimationControl(tile, 0); - } + /* Call the synchronized tile loop trigger, if this is the north tile */ + if (hs->building_flags.Any(BUILDING_HAS_1_TILE)) { + uint16_t random = GB(Random(), 0, 16); + AnimationControl(tile, true, random); + if (hs->building_flags.Any(BUILDING_2_TILES_Y)) AnimationControl(TileAddXY(tile, 0, 1), true, random); + if (hs->building_flags.Any(BUILDING_2_TILES_X)) AnimationControl(TileAddXY(tile, 1, 0), true, random); + if (hs->building_flags.Any(BUILDING_HAS_4_TILES)) AnimationControl(TileAddXY(tile, 1, 1), true, random); } /* Check callback 21, which determines if a house should be destroyed. */ From 31e716449dfa512222246e318ee3018ff2014f91 Mon Sep 17 00:00:00 2001 From: frosch Date: Fri, 18 Apr 2025 15:06:46 +0200 Subject: [PATCH 035/766] Codechange: Change StationNameInformation to not use macros. (#14020) --- src/station_cmd.cpp | 134 +++++++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 59 deletions(-) diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 070f2ceb55..e563c1de28 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -210,8 +210,6 @@ static bool CMSATree(TileIndex tile) return IsTileType(tile, MP_TREES); } -#define M(x) ((x) - STR_SV_STNAME) - enum StationNaming : uint8_t { STATIONNAMING_RAIL, STATIONNAMING_ROAD, @@ -223,8 +221,20 @@ enum StationNaming : uint8_t { /** Information to handle station action 0 property 24 correctly */ struct StationNameInformation { - uint32_t free_names; ///< Current bitset of free names (we can remove names). + std::bitset used_names; ///< Used default station suffixes. std::bitset indtypes; ///< Bit set indicating when an industry type has been found. + + bool IsAvailable(StringID str) const + { + assert(IsInsideMM(str, STR_SV_STNAME, STR_SV_STNAME_FALLBACK)); + return !this->used_names.test(str - STR_SV_STNAME); + } + + void SetUsed(StringID str) + { + assert(IsInsideMM(str, STR_SV_STNAME, STR_SV_STNAME_FALLBACK)); + this->used_names.set(str - STR_SV_STNAME); + } }; /** @@ -247,25 +257,16 @@ static bool FindNearIndustryName(TileIndex tile, void *user_data) /* In all cases if an industry that provides a name is found two of * the standard names will be disabled. */ - sni->free_names &= ~(1 << M(STR_SV_STNAME_OILFIELD) | 1 << M(STR_SV_STNAME_MINES)); + sni->SetUsed(STR_SV_STNAME_OILFIELD); + sni->SetUsed(STR_SV_STNAME_MINES); return !sni->indtypes[indtype]; } static StringID GenerateStationName(Station *st, TileIndex tile, StationNaming name_class) { - static const uint32_t _gen_station_name_bits[] = { - 0, // STATIONNAMING_RAIL - 0, // STATIONNAMING_ROAD - 1U << M(STR_SV_STNAME_AIRPORT), // STATIONNAMING_AIRPORT - 1U << M(STR_SV_STNAME_OILFIELD), // STATIONNAMING_OILRIG - 1U << M(STR_SV_STNAME_DOCKS), // STATIONNAMING_DOCK - 1U << M(STR_SV_STNAME_HELIPORT), // STATIONNAMING_HELIPORT - }; - const Town *t = st->town; StationNameInformation sni{}; - sni.free_names = UINT32_MAX; for (const Station *s : Station::Iterate()) { if (s != st && s->town == t) { @@ -281,12 +282,10 @@ static StringID GenerateStationName(Station *st, TileIndex tile, StationNaming n } continue; } - uint str = M(s->string_id); - if (str <= 0x20) { - if (str == M(STR_SV_STNAME_FOREST)) { - str = M(STR_SV_STNAME_WOODS); - } - ClrBit(sni.free_names, str); + if (IsInsideMM(s->string_id, STR_SV_STNAME, STR_SV_STNAME_FALLBACK)) { + auto str = s->string_id; + if (str == STR_SV_STNAME_FOREST) str = STR_SV_STNAME_WOODS; + sni.SetUsed(str); } } } @@ -306,11 +305,25 @@ static StringID GenerateStationName(Station *st, TileIndex tile, StationNaming n /* Oil rigs/mines name could be marked not free by looking for a near by industry. */ /* check default names */ - uint32_t tmp = sni.free_names & _gen_station_name_bits[name_class]; - if (tmp != 0) return STR_SV_STNAME + FindFirstBit(tmp); + switch (name_class) { + case STATIONNAMING_AIRPORT: + if (sni.IsAvailable(STR_SV_STNAME_AIRPORT)) return STR_SV_STNAME_AIRPORT; + break; + case STATIONNAMING_OILRIG: + if (sni.IsAvailable(STR_SV_STNAME_OILFIELD)) return STR_SV_STNAME_OILFIELD; + break; + case STATIONNAMING_DOCK: + if (sni.IsAvailable(STR_SV_STNAME_DOCKS)) return STR_SV_STNAME_DOCKS; + break; + case STATIONNAMING_HELIPORT: + if (sni.IsAvailable(STR_SV_STNAME_HELIPORT)) return STR_SV_STNAME_HELIPORT; + break; + default: + break; + }; /* check mine? */ - if (HasBit(sni.free_names, M(STR_SV_STNAME_MINES))) { + if (sni.IsAvailable(STR_SV_STNAME_MINES)) { if (CountMapSquareAround(tile, CMSAMine) >= 2) { return STR_SV_STNAME_MINES; } @@ -318,20 +331,19 @@ static StringID GenerateStationName(Station *st, TileIndex tile, StationNaming n /* check close enough to town to get central as name? */ if (DistanceMax(tile, t->xy) < 8) { - if (HasBit(sni.free_names, M(STR_SV_STNAME))) return STR_SV_STNAME; - - if (HasBit(sni.free_names, M(STR_SV_STNAME_CENTRAL))) return STR_SV_STNAME_CENTRAL; + if (sni.IsAvailable(STR_SV_STNAME)) return STR_SV_STNAME; + if (sni.IsAvailable(STR_SV_STNAME_CENTRAL)) return STR_SV_STNAME_CENTRAL; } /* Check lakeside */ - if (HasBit(sni.free_names, M(STR_SV_STNAME_LAKESIDE)) && + if (sni.IsAvailable(STR_SV_STNAME_LAKESIDE) && DistanceFromEdge(tile) < 20 && CountMapSquareAround(tile, CMSAWater) >= 5) { return STR_SV_STNAME_LAKESIDE; } /* Check woods */ - if (HasBit(sni.free_names, M(STR_SV_STNAME_WOODS)) && ( + if (sni.IsAvailable(STR_SV_STNAME_WOODS) && ( CountMapSquareAround(tile, CMSATree) >= 8 || CountMapSquareAround(tile, IsTileForestIndustry) >= 2) ) { @@ -342,43 +354,47 @@ static StringID GenerateStationName(Station *st, TileIndex tile, StationNaming n int z = GetTileZ(tile); int z2 = GetTileZ(t->xy); if (z < z2) { - if (HasBit(sni.free_names, M(STR_SV_STNAME_VALLEY))) return STR_SV_STNAME_VALLEY; + if (sni.IsAvailable(STR_SV_STNAME_VALLEY)) return STR_SV_STNAME_VALLEY; } else if (z > z2) { - if (HasBit(sni.free_names, M(STR_SV_STNAME_HEIGHTS))) return STR_SV_STNAME_HEIGHTS; + if (sni.IsAvailable(STR_SV_STNAME_HEIGHTS)) return STR_SV_STNAME_HEIGHTS; } /* check direction compared to town */ - static const int8_t _direction_and_table[] = { - ~( (1 << M(STR_SV_STNAME_WEST)) | (1 << M(STR_SV_STNAME_EAST)) | (1 << M(STR_SV_STNAME_NORTH)) ), - ~( (1 << M(STR_SV_STNAME_SOUTH)) | (1 << M(STR_SV_STNAME_WEST)) | (1 << M(STR_SV_STNAME_NORTH)) ), - ~( (1 << M(STR_SV_STNAME_SOUTH)) | (1 << M(STR_SV_STNAME_EAST)) | (1 << M(STR_SV_STNAME_NORTH)) ), - ~( (1 << M(STR_SV_STNAME_SOUTH)) | (1 << M(STR_SV_STNAME_WEST)) | (1 << M(STR_SV_STNAME_EAST)) ), + if (TileX(tile) < TileX(t->xy)) { + sni.SetUsed(STR_SV_STNAME_SOUTH); + sni.SetUsed(STR_SV_STNAME_WEST); + } else { + sni.SetUsed(STR_SV_STNAME_NORTH); + sni.SetUsed(STR_SV_STNAME_EAST); + } + if (TileY(tile) < TileY(t->xy)) { + sni.SetUsed(STR_SV_STNAME_SOUTH); + sni.SetUsed(STR_SV_STNAME_EAST); + } else { + sni.SetUsed(STR_SV_STNAME_NORTH); + sni.SetUsed(STR_SV_STNAME_WEST); + } + + /** Remaining station names that can be used when a more specific name has not been used. */ + static const StringID fallback_names[] = { + STR_SV_STNAME_NORTH, + STR_SV_STNAME_SOUTH, + STR_SV_STNAME_EAST, + STR_SV_STNAME_WEST, + STR_SV_STNAME_TRANSFER, + STR_SV_STNAME_HALT, + STR_SV_STNAME_EXCHANGE, + STR_SV_STNAME_ANNEXE, + STR_SV_STNAME_SIDINGS, + STR_SV_STNAME_BRANCH, + STR_SV_STNAME_UPPER, + STR_SV_STNAME_LOWER, }; - - sni.free_names &= _direction_and_table[ - (TileX(tile) < TileX(t->xy)) + - (TileY(tile) < TileY(t->xy)) * 2]; - - /** Bitmask of remaining station names that can be used when a more specific name has not been used. */ - static const uint32_t fallback_names = ( - (1U << M(STR_SV_STNAME_NORTH)) | - (1U << M(STR_SV_STNAME_SOUTH)) | - (1U << M(STR_SV_STNAME_EAST)) | - (1U << M(STR_SV_STNAME_WEST)) | - (1U << M(STR_SV_STNAME_TRANSFER)) | - (1U << M(STR_SV_STNAME_HALT)) | - (1U << M(STR_SV_STNAME_EXCHANGE)) | - (1U << M(STR_SV_STNAME_ANNEXE)) | - (1U << M(STR_SV_STNAME_SIDINGS)) | - (1U << M(STR_SV_STNAME_BRANCH)) | - (1U << M(STR_SV_STNAME_UPPER)) | - (1U << M(STR_SV_STNAME_LOWER)) - ); - - sni.free_names &= fallback_names; - return (sni.free_names == 0) ? STR_SV_STNAME_FALLBACK : (STR_SV_STNAME + FindFirstBit(sni.free_names)); + for (auto str : fallback_names) { + if (sni.IsAvailable(str)) return str; + } + return STR_SV_STNAME_FALLBACK; } -#undef M /** * Find the closest deleted station of the current company From 2c59838acb974cdea17da184283f8e4621be45ee Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Fri, 18 Apr 2025 14:07:57 +0100 Subject: [PATCH 036/766] Codechange: (re)set multiple bitset flags in one call. (#14017) --- src/newgrf.cpp | 2 +- src/newgrf/newgrf_act0_houses.cpp | 2 +- src/object_cmd.cpp | 2 +- src/pathfinder/yapf/yapf_costrail.hpp | 3 +-- src/script/api/script_tilelist.cpp | 4 ++-- src/station_cmd.cpp | 2 +- src/vehicle_cmd.cpp | 2 +- src/viewport.cpp | 2 +- src/waypoint_cmd.cpp | 2 +- src/window.cpp | 2 +- 10 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 224e6ca6fc..34febb2d90 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -909,7 +909,7 @@ static void FinaliseEngineArray() } if (e->info.variant_id != EngineID::Invalid()) { - Engine::Get(e->info.variant_id)->display_flags.Set(EngineDisplayFlag::HasVariants).Set(EngineDisplayFlag::IsFolded); + Engine::Get(e->info.variant_id)->display_flags.Set({EngineDisplayFlag::HasVariants, EngineDisplayFlag::IsFolded}); } } } diff --git a/src/newgrf/newgrf_act0_houses.cpp b/src/newgrf/newgrf_act0_houses.cpp index d512666fdf..c73ff6fd6e 100644 --- a/src/newgrf/newgrf_act0_houses.cpp +++ b/src/newgrf/newgrf_act0_houses.cpp @@ -145,7 +145,7 @@ static ChangeInfoResult TownHouseChangeInfo(uint first, uint last, int prop, Byt housespec->random_colour[3] = COLOUR_GREEN; /* House flags 40 and 80 are exceptions; these flags are never set automatically. */ - housespec->building_flags.Reset(BuildingFlag::IsChurch).Reset(BuildingFlag::IsStadium); + housespec->building_flags.Reset({BuildingFlag::IsChurch, BuildingFlag::IsStadium}); /* Make sure that the third cargo type is valid in this * climate. This can cause problems when copying the properties diff --git a/src/object_cmd.cpp b/src/object_cmd.cpp index 94af931e8a..f3032be4ac 100644 --- a/src/object_cmd.cpp +++ b/src/object_cmd.cpp @@ -240,7 +240,7 @@ CommandCost CmdBuildObject(DoCommandFlags flags, TileIndex tile, ObjectType type if (!IsWaterTile(t)) { /* Normal water tiles don't have to be cleared. For all other tile types clear * the tile but leave the water. */ - cost.AddCost(Command::Do(DoCommandFlags{flags}.Reset(DoCommandFlag::NoWater).Reset(DoCommandFlag::Execute), t)); + cost.AddCost(Command::Do(DoCommandFlags{flags}.Reset({DoCommandFlag::NoWater, DoCommandFlag::Execute}), t)); } else { /* Can't build on water owned by another company. */ Owner o = GetTileOwner(t); diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index 65e0c59648..1c2b1e61fb 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -517,8 +517,7 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th end_segment_reason.Set(EndSegmentReason::SafeTile); } else if (HasSignalOnTrackdir(next.tile, ReverseTrackdir(next.td)) && GetSignalType(next.tile, TrackdirToTrack(next.td)) == SIGTYPE_PBS_ONEWAY) { /* Possible safe tile, but not so good as it's the back of a signal... */ - end_segment_reason.Set(EndSegmentReason::SafeTile); - end_segment_reason.Set(EndSegmentReason::DeadEnd); + end_segment_reason.Set({EndSegmentReason::SafeTile, EndSegmentReason::DeadEnd}); extra_cost += Yapf().PfGetSettings().rail_lastred_exit_penalty; } } diff --git a/src/script/api/script_tilelist.cpp b/src/script/api/script_tilelist.cpp index 83596d22bc..4eefa8daca 100644 --- a/src/script/api/script_tilelist.cpp +++ b/src/script/api/script_tilelist.cpp @@ -144,8 +144,8 @@ ScriptTileList_StationType::ScriptTileList_StationType(StationID station_id, Scr if ((station_type & ScriptStation::STATION_TRAIN) != 0) station_types.Set(::StationType::Rail); if ((station_type & ScriptStation::STATION_TRUCK_STOP) != 0) station_types.Set(::StationType::Truck); if ((station_type & ScriptStation::STATION_BUS_STOP) != 0) station_types.Set(::StationType::Bus); - if ((station_type & ScriptStation::STATION_AIRPORT) != 0) station_types.Set(::StationType::Airport).Set(::StationType::Oilrig); - if ((station_type & ScriptStation::STATION_DOCK) != 0) station_types.Set(::StationType::Dock).Set(::StationType::Oilrig); + if ((station_type & ScriptStation::STATION_AIRPORT) != 0) station_types.Set({::StationType::Airport, ::StationType::Oilrig}); + if ((station_type & ScriptStation::STATION_DOCK) != 0) station_types.Set({::StationType::Dock, ::StationType::Oilrig}); TileArea ta(::TileXY(rect->left, rect->top), rect->Width(), rect->Height()); for (TileIndex cur_tile : ta) { diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index e563c1de28..ab76fd4788 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -2308,7 +2308,7 @@ CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlags flags, int rep /* if we deleted the whole waypoint, delete the road facility. */ if (wp->road_waypoint_area.tile == INVALID_TILE) { - wp->facilities.Reset(StationFacility::BusStop).Reset(StationFacility::TruckStop); + wp->facilities.Reset({StationFacility::BusStop, StationFacility::TruckStop}); SetWindowWidgetDirty(WC_STATION_VIEW, wp->index, WID_SV_ROADVEHS); wp->UpdateVirtCoord(); DeleteStationIfEmpty(wp); diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index 89bae13ccd..ad41d7f17c 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -139,7 +139,7 @@ std::tuple CmdBuildVehicle(D /* If we are refitting we need to temporarily purchase the vehicle to be able to * test it. */ DoCommandFlags subflags = flags; - if (refitting && !flags.Test(DoCommandFlag::Execute)) subflags.Set(DoCommandFlag::Execute).Set(DoCommandFlag::AutoReplace); + if (refitting && !flags.Test(DoCommandFlag::Execute)) subflags.Set({DoCommandFlag::Execute, DoCommandFlag::AutoReplace}); /* Vehicle construction needs random bits, so we have to save the random * seeds to prevent desyncs. */ diff --git a/src/viewport.cpp b/src/viewport.cpp index 01d6c70897..7950b7d4a2 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -1352,7 +1352,7 @@ static Rect ExpandRectWithViewportSignMargins(Rect r, ZoomLevel zoom) static void ViewportAddTownStrings(DrawPixelInfo *dpi, const std::vector &towns, bool small) { ViewportStringFlags flags{}; - if (small) flags.Set(ViewportStringFlag::Small).Set(ViewportStringFlag::Shadow); + if (small) flags.Set({ViewportStringFlag::Small, ViewportStringFlag::Shadow}); StringID stringid = !small && _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP : STR_TOWN_NAME; for (const Town *t : towns) { diff --git a/src/waypoint_cmd.cpp b/src/waypoint_cmd.cpp index 559d2e55a3..acd461abba 100644 --- a/src/waypoint_cmd.cpp +++ b/src/waypoint_cmd.cpp @@ -409,7 +409,7 @@ CommandCost CmdBuildRoadWaypoint(DoCommandFlags flags, TileIndex start_tile, Axi } wp->delete_ctr = 0; - wp->facilities.Set(StationFacility::BusStop).Set(StationFacility::TruckStop); + wp->facilities.Set({StationFacility::BusStop, StationFacility::TruckStop}); wp->build_date = TimerGameCalendar::date; wp->string_id = STR_SV_STNAME_WAYPOINT; diff --git a/src/window.cpp b/src/window.cpp index 0e36096d2a..7b3a694ab3 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1837,7 +1837,7 @@ static void DecreaseWindowCounters() if (nwid->type == NWID_HSCROLLBAR || nwid->type == NWID_VSCROLLBAR) { NWidgetScrollbar *sb = static_cast(nwid); if (sb->disp_flags.Any({NWidgetDisplayFlag::ScrollbarUp, NWidgetDisplayFlag::ScrollbarDown})) { - sb->disp_flags.Reset(NWidgetDisplayFlag::ScrollbarUp).Reset(NWidgetDisplayFlag::ScrollbarDown); + sb->disp_flags.Reset({NWidgetDisplayFlag::ScrollbarUp, NWidgetDisplayFlag::ScrollbarDown}); w->mouse_capture_widget = -1; sb->SetDirty(w); } From b862d4937f35f803a09cfa5211aee214c6df92c0 Mon Sep 17 00:00:00 2001 From: frosch Date: Fri, 18 Apr 2025 15:19:28 +0200 Subject: [PATCH 037/766] Codechange: Turn custom vehicle spritenums into enum, and use them consistently. (#14022) --- src/aircraft_cmd.cpp | 6 +++--- src/depot_gui.cpp | 2 +- src/newgrf/newgrf_act0_aircraft.cpp | 4 ++-- src/newgrf/newgrf_act0_roadvehs.cpp | 4 ++-- src/newgrf/newgrf_act0_ships.cpp | 4 ++-- src/newgrf/newgrf_act0_trains.cpp | 2 +- src/newgrf/newgrf_internal_vehicle.h | 4 ++-- src/newgrf_engine.cpp | 4 ++-- src/roadveh_cmd.cpp | 7 ++++--- src/ship_cmd.cpp | 4 ++-- src/train_cmd.cpp | 11 ++++++----- src/vehicle_func.h | 15 ++++++++++++--- 12 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 78597e3a30..82d9af94b4 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -175,7 +175,7 @@ void Aircraft::GetImage(Direction direction, EngineImageType image_type, Vehicle { uint8_t spritenum = this->spritenum; - if (is_custom_sprite(spritenum)) { + if (IsCustomVehicleSpriteNum(spritenum)) { GetCustomVehicleSprite(this, direction, image_type, result); if (result->IsValid()) return; @@ -191,7 +191,7 @@ void GetRotorImage(const Aircraft *v, EngineImageType image_type, VehicleSpriteS assert(v->subtype == AIR_HELICOPTER); const Aircraft *w = v->Next()->Next(); - if (is_custom_sprite(v->spritenum)) { + if (IsCustomVehicleSpriteNum(v->spritenum)) { GetCustomRotorSprite(v, image_type, result); if (result->IsValid()) return; } @@ -205,7 +205,7 @@ static void GetAircraftIcon(EngineID engine, EngineImageType image_type, Vehicle const Engine *e = Engine::Get(engine); uint8_t spritenum = e->u.air.image_index; - if (is_custom_sprite(spritenum)) { + if (IsCustomVehicleSpriteNum(spritenum)) { GetCustomVehicleIcon(engine, DIR_W, image_type, result); if (result->IsValid()) return; diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp index cd2b28111a..1aabc6bcc2 100644 --- a/src/depot_gui.cpp +++ b/src/depot_gui.cpp @@ -228,7 +228,7 @@ void InitDepotWindowBlockSizes() if (!e->IsEnabled()) continue; uint w = TRAININFO_DEFAULT_VEHICLE_WIDTH; - if (e->GetGRF() != nullptr && is_custom_sprite(e->u.rail.image_index)) { + if (e->GetGRF() != nullptr && IsCustomVehicleSpriteNum(e->u.rail.image_index)) { w = e->GetGRF()->traininfo_vehicle_width; if (w != VEHICLEINFO_FULL_VEHICLE_WIDTH) { /* Hopeless. diff --git a/src/newgrf/newgrf_act0_aircraft.cpp b/src/newgrf/newgrf_act0_aircraft.cpp index 1c614d0d32..d246544784 100644 --- a/src/newgrf/newgrf_act0_aircraft.cpp +++ b/src/newgrf/newgrf_act0_aircraft.cpp @@ -43,9 +43,9 @@ static ChangeInfoResult AircraftVehicleChangeInfo(uint first, uint last, int pro uint8_t orig_spriteid = spriteid; /* aircraft have different custom id in the GRF file */ - if (spriteid == 0xFF) spriteid = 0xFD; + if (spriteid == 0xFF) spriteid = CUSTOM_VEHICLE_SPRITENUM; - if (spriteid < 0xFD) spriteid >>= 1; + if (spriteid < CUSTOM_VEHICLE_SPRITENUM) spriteid >>= 1; if (IsValidNewGRFImageIndex(spriteid)) { avi->image_index = spriteid; diff --git a/src/newgrf/newgrf_act0_roadvehs.cpp b/src/newgrf/newgrf_act0_roadvehs.cpp index aba759f8f7..e8cf67f6bb 100644 --- a/src/newgrf/newgrf_act0_roadvehs.cpp +++ b/src/newgrf/newgrf_act0_roadvehs.cpp @@ -62,9 +62,9 @@ static ChangeInfoResult RoadVehicleChangeInfo(uint first, uint last, int prop, B uint8_t orig_spriteid = spriteid; /* cars have different custom id in the GRF file */ - if (spriteid == 0xFF) spriteid = 0xFD; + if (spriteid == 0xFF) spriteid = CUSTOM_VEHICLE_SPRITENUM; - if (spriteid < 0xFD) spriteid >>= 1; + if (spriteid < CUSTOM_VEHICLE_SPRITENUM) spriteid >>= 1; if (IsValidNewGRFImageIndex(spriteid)) { rvi->image_index = spriteid; diff --git a/src/newgrf/newgrf_act0_ships.cpp b/src/newgrf/newgrf_act0_ships.cpp index f24be94bda..0e1bfe0b17 100644 --- a/src/newgrf/newgrf_act0_ships.cpp +++ b/src/newgrf/newgrf_act0_ships.cpp @@ -45,9 +45,9 @@ static ChangeInfoResult ShipVehicleChangeInfo(uint first, uint last, int prop, B uint8_t orig_spriteid = spriteid; /* ships have different custom id in the GRF file */ - if (spriteid == 0xFF) spriteid = 0xFD; + if (spriteid == 0xFF) spriteid = CUSTOM_VEHICLE_SPRITENUM; - if (spriteid < 0xFD) spriteid >>= 1; + if (spriteid < CUSTOM_VEHICLE_SPRITENUM) spriteid >>= 1; if (IsValidNewGRFImageIndex(spriteid)) { svi->image_index = spriteid; diff --git a/src/newgrf/newgrf_act0_trains.cpp b/src/newgrf/newgrf_act0_trains.cpp index 29fa3e473a..445dbf294b 100644 --- a/src/newgrf/newgrf_act0_trains.cpp +++ b/src/newgrf/newgrf_act0_trains.cpp @@ -98,7 +98,7 @@ ChangeInfoResult RailVehicleChangeInfo(uint first, uint last, int prop, ByteRead /* TTD sprite IDs point to a location in a 16bit array, but we use it * as an array index, so we need it to be half the original value. */ - if (spriteid < 0xFD) spriteid >>= 1; + if (spriteid < CUSTOM_VEHICLE_SPRITENUM) spriteid >>= 1; if (IsValidNewGRFImageIndex(spriteid)) { rvi->image_index = spriteid; diff --git a/src/newgrf/newgrf_internal_vehicle.h b/src/newgrf/newgrf_internal_vehicle.h index 53526e7526..2e1abf3b70 100644 --- a/src/newgrf/newgrf_internal_vehicle.h +++ b/src/newgrf/newgrf_internal_vehicle.h @@ -58,12 +58,12 @@ void ConvertTTDBasePrice(uint32_t base_pointer, const char *error_location, Pric * Helper to check whether an image index is valid for a particular NewGRF vehicle. * @tparam T The type of vehicle. * @param image_index The image index to check. - * @return True iff the image index is valid, or 0xFD (use new graphics). + * @return True iff the image index is valid, or CUSTOM_VEHICLE_SPRITENUM (use new graphics). */ template static inline bool IsValidNewGRFImageIndex(uint8_t image_index) { - return image_index == 0xFD || IsValidImageIndex(image_index); + return image_index == CUSTOM_VEHICLE_SPRITENUM || IsValidImageIndex(image_index); } ChangeInfoResult CommonVehicleChangeInfo(EngineInfo *ei, int prop, ByteReader &buf); diff --git a/src/newgrf_engine.cpp b/src/newgrf_engine.cpp index 88dfd74b33..62158dca6e 100644 --- a/src/newgrf_engine.cpp +++ b/src/newgrf_engine.cpp @@ -838,8 +838,8 @@ static uint32_t VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *objec case 0x46: return v->GetEngine()->grf_prop.local_id; case 0x47: return GB(v->GetEngine()->grf_prop.local_id, 8, 8); case 0x48: - if (v->type != VEH_TRAIN || v->spritenum != 0xFD) return v->spritenum; - return HasBit(Train::From(v)->flags, VRF_REVERSE_DIRECTION) ? 0xFE : 0xFD; + if (v->type != VEH_TRAIN || v->spritenum != CUSTOM_VEHICLE_SPRITENUM) return v->spritenum; + return HasBit(Train::From(v)->flags, VRF_REVERSE_DIRECTION) ? CUSTOM_VEHICLE_SPRITENUM_REVERSED : CUSTOM_VEHICLE_SPRITENUM; case 0x49: return v->day_counter; case 0x4A: return v->breakdowns_since_last_service; diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index b56e8b772d..71649eaa5c 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -107,7 +107,7 @@ static void GetRoadVehIcon(EngineID engine, EngineImageType image_type, VehicleS const Engine *e = Engine::Get(engine); uint8_t spritenum = e->u.road.image_index; - if (is_custom_sprite(spritenum)) { + if (IsCustomVehicleSpriteNum(spritenum)) { GetCustomVehicleIcon(engine, DIR_W, image_type, result); if (result->IsValid()) return; @@ -122,8 +122,9 @@ void RoadVehicle::GetImage(Direction direction, EngineImageType image_type, Vehi { uint8_t spritenum = this->spritenum; - if (is_custom_sprite(spritenum)) { - GetCustomVehicleSprite(this, (Direction)(direction + 4 * IS_CUSTOM_SECONDHEAD_SPRITE(spritenum)), image_type, result); + if (IsCustomVehicleSpriteNum(spritenum)) { + if (spritenum == CUSTOM_VEHICLE_SPRITENUM_REVERSED) direction = ReverseDir(direction); + GetCustomVehicleSprite(this, direction, image_type, result); if (result->IsValid()) return; spritenum = this->GetEngine()->original_image_index; diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index badc781ab0..2ed052c76f 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -83,7 +83,7 @@ static void GetShipIcon(EngineID engine, EngineImageType image_type, VehicleSpri const Engine *e = Engine::Get(engine); uint8_t spritenum = e->u.ship.image_index; - if (is_custom_sprite(spritenum)) { + if (IsCustomVehicleSpriteNum(spritenum)) { GetCustomVehicleIcon(engine, DIR_W, image_type, result); if (result->IsValid()) return; @@ -137,7 +137,7 @@ void Ship::GetImage(Direction direction, EngineImageType image_type, VehicleSpri if (image_type == EIT_ON_MAP) direction = this->rotation; - if (is_custom_sprite(spritenum)) { + if (IsCustomVehicleSpriteNum(spritenum)) { GetCustomVehicleSprite(this, direction, image_type, result); if (result->IsValid()) return; diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index b7b10f543e..3cc2755b69 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -441,7 +441,7 @@ int Train::GetCursorImageOffset() const int reference_width = TRAININFO_DEFAULT_VEHICLE_WIDTH; const Engine *e = this->GetEngine(); - if (e->GetGRF() != nullptr && is_custom_sprite(e->u.rail.image_index)) { + if (e->GetGRF() != nullptr && IsCustomVehicleSpriteNum(e->u.rail.image_index)) { reference_width = e->GetGRF()->traininfo_vehicle_width; } @@ -461,7 +461,7 @@ int Train::GetDisplayImageWidth(Point *offset) const int vehicle_pitch = 0; const Engine *e = this->GetEngine(); - if (e->GetGRF() != nullptr && is_custom_sprite(e->u.rail.image_index)) { + if (e->GetGRF() != nullptr && IsCustomVehicleSpriteNum(e->u.rail.image_index)) { reference_width = e->GetGRF()->traininfo_vehicle_width; vehicle_pitch = e->GetGRF()->traininfo_vehicle_pitch; } @@ -495,8 +495,9 @@ void Train::GetImage(Direction direction, EngineImageType image_type, VehicleSpr if (HasBit(this->flags, VRF_REVERSE_DIRECTION)) direction = ReverseDir(direction); - if (is_custom_sprite(spritenum)) { - GetCustomVehicleSprite(this, (Direction)(direction + 4 * IS_CUSTOM_SECONDHEAD_SPRITE(spritenum)), image_type, result); + if (IsCustomVehicleSpriteNum(spritenum)) { + if (spritenum == CUSTOM_VEHICLE_SPRITENUM_REVERSED) direction = ReverseDir(direction); + GetCustomVehicleSprite(this, direction, image_type, result); if (result->IsValid()) return; spritenum = this->GetEngine()->original_image_index; @@ -516,7 +517,7 @@ static void GetRailIcon(EngineID engine, bool rear_head, int &y, EngineImageType Direction dir = rear_head ? DIR_E : DIR_W; uint8_t spritenum = e->u.rail.image_index; - if (is_custom_sprite(spritenum)) { + if (IsCustomVehicleSpriteNum(spritenum)) { GetCustomVehicleIcon(engine, dir, image_type, result); if (result->IsValid()) { if (e->GetGRF() != nullptr) { diff --git a/src/vehicle_func.h b/src/vehicle_func.h index e5207e969e..33701feae2 100644 --- a/src/vehicle_func.h +++ b/src/vehicle_func.h @@ -21,9 +21,18 @@ #include "track_type.h" #include "livery.h" -#define is_custom_sprite(x) (x >= 0xFD) -#define IS_CUSTOM_FIRSTHEAD_SPRITE(x) (x == 0xFD) -#define IS_CUSTOM_SECONDHEAD_SPRITE(x) (x == 0xFE) +/** + * Special values for Vehicle::spritenum and (Aircraft|Rail|Road|Ship)VehicleInfo::image_index + */ +enum CustomVehicleSpriteNum { + CUSTOM_VEHICLE_SPRITENUM = 0xFD, ///< Vehicle sprite from NewGRF + CUSTOM_VEHICLE_SPRITENUM_REVERSED = 0xFE, ///< Vehicle sprite from NewGRF with reverse driving direction (from articulation callback) +}; + +static inline bool IsCustomVehicleSpriteNum(uint8_t spritenum) +{ + return spritenum >= CUSTOM_VEHICLE_SPRITENUM; +} static const TimerGameEconomy::Date VEHICLE_PROFIT_MIN_AGE{CalendarTime::DAYS_IN_YEAR * 2}; ///< Only vehicles older than this have a meaningful profit. static const Money VEHICLE_PROFIT_THRESHOLD = 10000; ///< Threshold for a vehicle to be considered making good profit. From 26db4ccf09f4615747aa92942f05cadb12edbb07 Mon Sep 17 00:00:00 2001 From: frosch Date: Fri, 18 Apr 2025 15:20:55 +0200 Subject: [PATCH 038/766] Codechange: Turn bit-stuffed FiosType enum into a struct. (#14019) --- src/console_cmds.cpp | 14 ++++---- src/fileio_type.h | 60 +++++++++------------------------- src/fios.cpp | 20 ++++-------- src/fios_gui.cpp | 12 +++---- src/genworld_gui.cpp | 2 +- src/landscape.cpp | 2 +- src/network/network_client.cpp | 2 +- src/network/network_server.cpp | 2 +- src/openttd.cpp | 18 +++++----- src/saveload/afterload.cpp | 2 +- src/saveload/saveload.cpp | 22 +++---------- src/saveload/saveload.h | 6 ++-- src/saveload/signs_sl.cpp | 2 +- 13 files changed, 59 insertions(+), 105 deletions(-) diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index cb091832ff..948f1c0aef 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -462,7 +462,7 @@ DEF_CONSOLE_CMD(ConLoad) _console_file_list_savegame.ValidateFileList(); const FiosItem *item = _console_file_list_savegame.FindItem(file); if (item != nullptr) { - if (GetAbstractFileType(item->type) == FT_SAVEGAME) { + if (item->type.abstract == FT_SAVEGAME) { _switch_mode = SM_LOAD_GAME; _file_to_saveload.Set(*item); } else { @@ -488,7 +488,7 @@ DEF_CONSOLE_CMD(ConLoadScenario) _console_file_list_scenario.ValidateFileList(); const FiosItem *item = _console_file_list_scenario.FindItem(file); if (item != nullptr) { - if (GetAbstractFileType(item->type) == FT_SCENARIO) { + if (item->type.abstract == FT_SCENARIO) { _switch_mode = SM_LOAD_GAME; _file_to_saveload.Set(*item); } else { @@ -514,7 +514,7 @@ DEF_CONSOLE_CMD(ConLoadHeightmap) _console_file_list_heightmap.ValidateFileList(); const FiosItem *item = _console_file_list_heightmap.FindItem(file); if (item != nullptr) { - if (GetAbstractFileType(item->type) == FT_HEIGHTMAP) { + if (item->type.abstract == FT_HEIGHTMAP) { _switch_mode = SM_START_HEIGHTMAP; _file_to_saveload.Set(*item); } else { @@ -614,8 +614,10 @@ DEF_CONSOLE_CMD(ConChangeDirectory) _console_file_list_savegame.ValidateFileList(true); const FiosItem *item = _console_file_list_savegame.FindItem(file); if (item != nullptr) { - switch (item->type) { - case FIOS_TYPE_DIR: case FIOS_TYPE_DRIVE: case FIOS_TYPE_PARENT: + switch (item->type.detailed) { + case DFT_FIOS_DIR: + case DFT_FIOS_DRIVE: + case DFT_FIOS_PARENT: FiosBrowseTo(item); break; default: IConsolePrint(CC_ERROR, "{}: Not a directory.", file); @@ -1307,7 +1309,7 @@ DEF_CONSOLE_CMD(ConReload) return true; } - if (_file_to_saveload.abstract_ftype == FT_NONE || _file_to_saveload.abstract_ftype == FT_INVALID) { + if (_file_to_saveload.ftype.abstract == FT_NONE || _file_to_saveload.ftype.abstract == FT_INVALID) { IConsolePrint(CC_ERROR, "No game loaded to reload."); return true; } diff --git a/src/fileio_type.h b/src/fileio_type.h index 46d1fbfdca..54e47aeff4 100644 --- a/src/fileio_type.h +++ b/src/fileio_type.h @@ -21,8 +21,6 @@ enum AbstractFileType : uint8_t { FT_TOWN_DATA, ///< town data file FT_INVALID = 7, ///< Invalid or unknown file type. - FT_NUMBITS = 3, ///< Number of bits required for storing a #AbstractFileType value. - FT_MASK = (1 << FT_NUMBITS) - 1, ///< Bitmask for extracting an abstract file type. }; /** Kinds of files in each #AbstractFileType. */ @@ -58,56 +56,30 @@ enum SaveLoadOperation : uint8_t { SLO_INVALID, ///< Unknown file operation. }; -/** - * Construct an enum value for #FiosType as a combination of an abstract and a detailed file type. - * @param abstract Abstract file type (one of #AbstractFileType). - * @param detailed Detailed file type (one of #DetailedFileType). - */ -#define MAKE_FIOS_TYPE(abstract, detailed) ((abstract) | ((detailed) << FT_NUMBITS)) - /** * Elements of a file system that are recognized. - * Values are a combination of #AbstractFileType and #DetailedFileType. - * @see GetAbstractFileType GetDetailedFileType */ -enum FiosType : uint16_t { - FIOS_TYPE_DRIVE = MAKE_FIOS_TYPE(FT_NONE, DFT_FIOS_DRIVE), - FIOS_TYPE_PARENT = MAKE_FIOS_TYPE(FT_NONE, DFT_FIOS_PARENT), - FIOS_TYPE_DIR = MAKE_FIOS_TYPE(FT_NONE, DFT_FIOS_DIR), - FIOS_TYPE_DIRECT = MAKE_FIOS_TYPE(FT_NONE, DFT_FIOS_DIRECT), +struct FiosType { + AbstractFileType abstract; ///< Abstract file type. + DetailedFileType detailed; ///< Detailed file type. - FIOS_TYPE_FILE = MAKE_FIOS_TYPE(FT_SAVEGAME, DFT_GAME_FILE), - FIOS_TYPE_OLDFILE = MAKE_FIOS_TYPE(FT_SAVEGAME, DFT_OLD_GAME_FILE), - FIOS_TYPE_SCENARIO = MAKE_FIOS_TYPE(FT_SCENARIO, DFT_GAME_FILE), - FIOS_TYPE_OLD_SCENARIO = MAKE_FIOS_TYPE(FT_SCENARIO, DFT_OLD_GAME_FILE), - FIOS_TYPE_PNG = MAKE_FIOS_TYPE(FT_HEIGHTMAP, DFT_HEIGHTMAP_PNG), - FIOS_TYPE_BMP = MAKE_FIOS_TYPE(FT_HEIGHTMAP, DFT_HEIGHTMAP_BMP), - FIOS_TYPE_JSON = MAKE_FIOS_TYPE(FT_TOWN_DATA, DFT_TOWN_DATA_JSON), - - FIOS_TYPE_INVALID = MAKE_FIOS_TYPE(FT_INVALID, DFT_INVALID), + constexpr bool operator==(const FiosType &) const noexcept = default; }; -#undef MAKE_FIOS_TYPE +constexpr FiosType FIOS_TYPE_DRIVE{FT_NONE, DFT_FIOS_DRIVE}; +constexpr FiosType FIOS_TYPE_PARENT{FT_NONE, DFT_FIOS_PARENT}; +constexpr FiosType FIOS_TYPE_DIR{FT_NONE, DFT_FIOS_DIR}; +constexpr FiosType FIOS_TYPE_DIRECT{FT_NONE, DFT_FIOS_DIRECT}; -/** - * Extract the abstract file type from a #FiosType. - * @param fios_type Type to query. - * @return The Abstract file type of the \a fios_type. - */ -inline AbstractFileType GetAbstractFileType(FiosType fios_type) -{ - return static_cast(static_cast(fios_type) & FT_MASK); -} +constexpr FiosType FIOS_TYPE_FILE{FT_SAVEGAME, DFT_GAME_FILE}; +constexpr FiosType FIOS_TYPE_OLDFILE{FT_SAVEGAME, DFT_OLD_GAME_FILE}; +constexpr FiosType FIOS_TYPE_SCENARIO{FT_SCENARIO, DFT_GAME_FILE}; +constexpr FiosType FIOS_TYPE_OLD_SCENARIO{FT_SCENARIO, DFT_OLD_GAME_FILE}; +constexpr FiosType FIOS_TYPE_PNG{FT_HEIGHTMAP, DFT_HEIGHTMAP_PNG}; +constexpr FiosType FIOS_TYPE_BMP{FT_HEIGHTMAP, DFT_HEIGHTMAP_BMP}; +constexpr FiosType FIOS_TYPE_JSON{FT_TOWN_DATA, DFT_TOWN_DATA_JSON}; -/** - * Extract the detailed file type from a #FiosType. - * @param fios_type Type to query. - * @return The Detailed file type of the \a fios_type. - */ -inline DetailedFileType GetDetailedFileType(FiosType fios_type) -{ - return static_cast(fios_type >> FT_NUMBITS); -} +constexpr FiosType FIOS_TYPE_INVALID{FT_INVALID, DFT_INVALID}; /** * The different kinds of subdirectories OpenTTD uses diff --git a/src/fios.cpp b/src/fios.cpp index 9206aa101a..4e74c2e246 100644 --- a/src/fios.cpp +++ b/src/fios.cpp @@ -142,18 +142,18 @@ std::string FiosGetCurrentPath() */ bool FiosBrowseTo(const FiosItem *item) { - switch (item->type) { - case FIOS_TYPE_DRIVE: + switch (item->type.detailed) { + case DFT_FIOS_DRIVE: #if defined(_WIN32) assert(_fios_path != nullptr); *_fios_path = std::string{ item->title, 0, 1 } + ":" PATHSEP; #endif break; - case FIOS_TYPE_INVALID: + case DFT_INVALID: break; - case FIOS_TYPE_PARENT: { + case DFT_FIOS_PARENT: { assert(_fios_path != nullptr); auto s = _fios_path->find_last_of(PATHSEPCHAR); if (s != std::string::npos && s != 0) { @@ -167,24 +167,18 @@ bool FiosBrowseTo(const FiosItem *item) break; } - case FIOS_TYPE_DIR: + case DFT_FIOS_DIR: assert(_fios_path != nullptr); *_fios_path += item->name; *_fios_path += PATHSEP; break; - case FIOS_TYPE_DIRECT: + case DFT_FIOS_DIRECT: assert(_fios_path != nullptr); *_fios_path = item->name; break; - case FIOS_TYPE_FILE: - case FIOS_TYPE_OLDFILE: - case FIOS_TYPE_SCENARIO: - case FIOS_TYPE_OLD_SCENARIO: - case FIOS_TYPE_PNG: - case FIOS_TYPE_BMP: - case FIOS_TYPE_JSON: + default: return false; } diff --git a/src/fios_gui.cpp b/src/fios_gui.cpp index e3ff376497..6bf9210eee 100644 --- a/src/fios_gui.cpp +++ b/src/fios_gui.cpp @@ -309,10 +309,10 @@ static void SortSaveGameList(FileList &file_list) * Only sort savegames/scenarios, not directories */ for (const auto &item : file_list) { - switch (item.type) { - case FIOS_TYPE_DIR: sort_start++; break; - case FIOS_TYPE_PARENT: sort_start++; break; - case FIOS_TYPE_DRIVE: sort_end++; break; + switch (item.type.detailed) { + case DFT_FIOS_DIR: sort_start++; break; + case DFT_FIOS_PARENT: sort_start++; break; + case DFT_FIOS_DRIVE: sort_end++; break; default: break; } } @@ -514,7 +514,7 @@ public: } else if (item == this->highlighted) { GfxFillRect(br.left, tr.top, br.right, tr.bottom, PC_VERY_DARK_BLUE); } - DrawString(tr, item->title, _fios_colours[GetDetailedFileType(item->type)]); + DrawString(tr, item->title, _fios_colours[item->type.detailed]); tr = tr.Translate(0, this->resize.step_height); } break; @@ -719,7 +719,7 @@ public: this->selected = file; _load_check_data.Clear(); - if (GetDetailedFileType(file->type) == DFT_GAME_FILE) { + if (file->type.detailed == DFT_GAME_FILE) { /* Other detailed file types cannot be checked before. */ SaveOrLoad(file->name, SLO_CHECK, DFT_GAME_FILE, NO_DIRECTORY, false); } diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp index a646638cff..854ae3d226 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -997,7 +997,7 @@ static void _ShowGenerateLandscape(GenerateLandscapeWindowMode mode) if (mode == GLWM_HEIGHTMAP) { /* If the function returns negative, it means there was a problem loading the heightmap */ - if (!GetHeightmapDimensions(_file_to_saveload.detail_ftype, _file_to_saveload.name.c_str(), &x, &y)) return; + if (!GetHeightmapDimensions(_file_to_saveload.ftype.detailed, _file_to_saveload.name.c_str(), &x, &y)) return; } WindowDesc &desc = (mode == GLWM_HEIGHTMAP) ? _heightmap_load_desc : _generate_landscape_desc; diff --git a/src/landscape.cpp b/src/landscape.cpp index 702889a922..02a61ddfc0 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -1543,7 +1543,7 @@ bool GenerateLandscape(uint8_t mode) if (mode == GWM_HEIGHTMAP) { SetGeneratingWorldProgress(GWP_LANDSCAPE, steps + GLS_HEIGHTMAP); - if (!LoadHeightmap(_file_to_saveload.detail_ftype, _file_to_saveload.name.c_str())) { + if (!LoadHeightmap(_file_to_saveload.ftype.detailed, _file_to_saveload.name.c_str())) { return false; } IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 928b01f828..c662f9f6d3 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -823,7 +823,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_MAP_DONE(Packet ClearErrorMessages(); /* Set the abstract filetype. This is read during savegame load. */ - _file_to_saveload.SetMode(SLO_LOAD, FT_SAVEGAME, DFT_GAME_FILE); + _file_to_saveload.SetMode(FIOS_TYPE_FILE, SLO_LOAD); bool load_success = SafeLoad({}, SLO_LOAD, DFT_GAME_FILE, GM_NORMAL, NO_DIRECTORY, this->savegame); this->savegame = nullptr; diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index 6247807a39..5eb29ad2a0 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -1814,7 +1814,7 @@ void NetworkServer_Tick(bool send_frame) static void NetworkRestartMap() { _settings_newgame.game_creation.generation_seed = GENERATE_NEW_SEED; - switch (_file_to_saveload.abstract_ftype) { + switch (_file_to_saveload.ftype.abstract) { case FT_SAVEGAME: case FT_SCENARIO: _switch_mode = SM_LOAD_GAME; diff --git a/src/openttd.cpp b/src/openttd.cpp index 6d5dd18ad9..d095f23b01 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -579,14 +579,14 @@ int openttd_main(std::span arguments) } /* Allow for '-e' before or after '-g'. */ - switch (GetAbstractFileType(ft)) { + switch (ft.abstract) { case FT_SAVEGAME: _switch_mode = (_switch_mode == SM_EDITOR ? SM_LOAD_SCENARIO : SM_LOAD_GAME); break; case FT_SCENARIO: _switch_mode = (_switch_mode == SM_EDITOR ? SM_LOAD_SCENARIO : SM_LOAD_GAME); break; case FT_HEIGHTMAP: _switch_mode = (_switch_mode == SM_EDITOR ? SM_LOAD_HEIGHTMAP : SM_START_HEIGHTMAP); break; default: break; } - _file_to_saveload.SetMode(SLO_LOAD, GetAbstractFileType(ft), GetDetailedFileType(ft)); + _file_to_saveload.SetMode(ft, SLO_LOAD); break; } @@ -886,7 +886,7 @@ static void MakeNewGame(bool from_heightmap, bool reset_settings) _game_mode = GM_NORMAL; if (!from_heightmap) { /* "reload" command needs to know what mode we were in. */ - _file_to_saveload.SetMode(SLO_INVALID, FT_INVALID, DFT_INVALID); + _file_to_saveload.SetMode(FIOS_TYPE_INVALID, SLO_INVALID); } ResetGRFConfig(true); @@ -904,7 +904,7 @@ static void MakeNewEditorWorld() { _game_mode = GM_EDITOR; /* "reload" command needs to know what mode we were in. */ - _file_to_saveload.SetMode(SLO_INVALID, FT_INVALID, DFT_INVALID); + _file_to_saveload.SetMode(FIOS_TYPE_INVALID, SLO_INVALID); ResetGRFConfig(true); @@ -1054,12 +1054,12 @@ void SwitchToMode(SwitchMode new_mode) break; case SM_RELOADGAME: // Reload with what-ever started the game - if (_file_to_saveload.abstract_ftype == FT_SAVEGAME || _file_to_saveload.abstract_ftype == FT_SCENARIO) { + if (_file_to_saveload.ftype.abstract == FT_SAVEGAME || _file_to_saveload.ftype.abstract == FT_SCENARIO) { /* Reload current savegame/scenario */ _switch_mode = _game_mode == GM_EDITOR ? SM_LOAD_SCENARIO : SM_LOAD_GAME; SwitchToMode(_switch_mode); break; - } else if (_file_to_saveload.abstract_ftype == FT_HEIGHTMAP) { + } else if (_file_to_saveload.ftype.abstract == FT_HEIGHTMAP) { /* Restart current heightmap */ _switch_mode = _game_mode == GM_EDITOR ? SM_LOAD_HEIGHTMAP : SM_RESTART_HEIGHTMAP; SwitchToMode(_switch_mode); @@ -1084,10 +1084,10 @@ void SwitchToMode(SwitchMode new_mode) ResetGRFConfig(true); ResetWindowSystem(); - if (!SafeLoad(_file_to_saveload.name, _file_to_saveload.file_op, _file_to_saveload.detail_ftype, GM_NORMAL, NO_DIRECTORY)) { + if (!SafeLoad(_file_to_saveload.name, _file_to_saveload.file_op, _file_to_saveload.ftype.detailed, GM_NORMAL, NO_DIRECTORY)) { ShowErrorMessage(GetSaveLoadErrorType(), GetSaveLoadErrorMessage(), WL_CRITICAL); } else { - if (_file_to_saveload.abstract_ftype == FT_SCENARIO) { + if (_file_to_saveload.ftype.abstract == FT_SCENARIO) { OnStartScenario(); } OnStartGame(_network_dedicated); @@ -1120,7 +1120,7 @@ void SwitchToMode(SwitchMode new_mode) break; case SM_LOAD_SCENARIO: { // Load scenario from scenario editor - if (SafeLoad(_file_to_saveload.name, _file_to_saveload.file_op, _file_to_saveload.detail_ftype, GM_EDITOR, NO_DIRECTORY)) { + if (SafeLoad(_file_to_saveload.name, _file_to_saveload.file_op, _file_to_saveload.ftype.detailed, GM_EDITOR, NO_DIRECTORY)) { SetLocalCompany(OWNER_NONE); GenerateSavegameId(); _settings_newgame.game_creation.starting_year = TimerGameCalendar::year; diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 23de2d9de1..9fca094948 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -256,7 +256,7 @@ static void InitializeWindowsAndCaches() /* For each company, verify (while loading a scenario) that the inauguration date is the current year and set it * accordingly if it is not the case. No need to set it on companies that are not been used already, * thus the MIN_YEAR (which is really nothing more than Zero, initialized value) test */ - if (_file_to_saveload.abstract_ftype == FT_SCENARIO && c->inaugurated_year != EconomyTime::MIN_YEAR) { + if (_file_to_saveload.ftype.abstract == FT_SCENARIO && c->inaugurated_year != EconomyTime::MIN_YEAR) { c->inaugurated_year = TimerGameEconomy::year; } } diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 9c7bcc32c6..d6f2536f67 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -3370,33 +3370,21 @@ std::string GenerateDefaultSaveName() return filename; } -/** - * Set the mode and file type of the file to save or load based on the type of file entry at the file system. - * @param ft Type of file entry of the file system. - */ -void FileToSaveLoad::SetMode(FiosType ft) -{ - this->SetMode(SLO_LOAD, GetAbstractFileType(ft), GetDetailedFileType(ft)); -} - /** * Set the mode and file type of the file to save or load. + * @param ft File type. * @param fop File operation being performed. - * @param aft Abstract file type. - * @param dft Detailed file type. */ -void FileToSaveLoad::SetMode(SaveLoadOperation fop, AbstractFileType aft, DetailedFileType dft) +void FileToSaveLoad::SetMode(const FiosType &ft, SaveLoadOperation fop) { - if (aft == FT_INVALID || aft == FT_NONE) { + if (ft.abstract == FT_INVALID || ft.abstract == FT_NONE) { this->file_op = SLO_INVALID; - this->detail_ftype = DFT_INVALID; - this->abstract_ftype = FT_INVALID; + this->ftype = FIOS_TYPE_INVALID; return; } this->file_op = fop; - this->detail_ftype = dft; - this->abstract_ftype = aft; + this->ftype = ft; } /** diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index b665661d7d..246088625e 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -414,13 +414,11 @@ enum SaveOrLoadResult : uint8_t { /** Deals with the type of the savegame, independent of extension */ struct FileToSaveLoad { SaveLoadOperation file_op; ///< File operation to perform. - DetailedFileType detail_ftype; ///< Concrete file type (PNG, BMP, old save, etc). - AbstractFileType abstract_ftype; ///< Abstract type of file (scenario, heightmap, etc). + FiosType ftype; ///< File type. std::string name; ///< Name of the file. std::string title; ///< Internal name of the game. - void SetMode(FiosType ft); - void SetMode(SaveLoadOperation fop, AbstractFileType aft, DetailedFileType dft); + void SetMode(const FiosType &ft, SaveLoadOperation fop = SLO_LOAD); void Set(const FiosItem &item); }; diff --git a/src/saveload/signs_sl.cpp b/src/saveload/signs_sl.cpp index 698bd9f176..e8c1061374 100644 --- a/src/saveload/signs_sl.cpp +++ b/src/saveload/signs_sl.cpp @@ -62,7 +62,7 @@ struct SIGNChunkHandler : ChunkHandler { } /* Signs placed in scenario editor shall now be OWNER_DEITY */ - if (IsSavegameVersionBefore(SLV_171) && si->owner == OWNER_NONE && _file_to_saveload.abstract_ftype == FT_SCENARIO) { + if (IsSavegameVersionBefore(SLV_171) && si->owner == OWNER_NONE && _file_to_saveload.ftype.abstract == FT_SCENARIO) { si->owner = OWNER_DEITY; } } From 252376ce3eb6ffd4b9cd1b7ebbdb257d4b96cac8 Mon Sep 17 00:00:00 2001 From: frosch Date: Fri, 18 Apr 2025 15:24:34 +0200 Subject: [PATCH 039/766] Codechange: Rename DrawRoadAsSnowDesert to DrawRoadAsSnowOrDesert to be consistent with other functions. --- src/road_cmd.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 2d9a6d2de5..6e23f63c59 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -1356,7 +1356,7 @@ static uint GetRoadSpriteOffset(Slope slope, RoadBits bits) * @param roadside What sort of road this is * @return True if snow/desert road sprites should be used. */ -static bool DrawRoadAsSnowDesert(bool snow_or_desert, Roadside roadside) +static bool DrawRoadAsSnowOrDesert(bool snow_or_desert, Roadside roadside) { return (snow_or_desert && !(_settings_game.game_creation.landscape == LandscapeType::Tropic && HasGrfMiscBit(GrfMiscBit::DesertPavedRoads) && @@ -1556,7 +1556,7 @@ static SpriteID GetRoadGroundSprite(const TileInfo *ti, Roadside roadside, const { /* Draw bare ground sprite if no road or road uses overlay system. */ if (rti == nullptr || rti->UsesOverlay()) { - if (DrawRoadAsSnowDesert(snow_or_desert, roadside)) { + if (DrawRoadAsSnowOrDesert(snow_or_desert, roadside)) { return SPR_FLAT_SNOW_DESERT_TILE + SlopeToSpriteOffset(ti->tileh); } @@ -1571,7 +1571,7 @@ static SpriteID GetRoadGroundSprite(const TileInfo *ti, Roadside roadside, const /* Draw original road base sprite */ SpriteID image = SPR_ROAD_Y + offset; - if (DrawRoadAsSnowDesert(snow_or_desert, roadside)) { + if (DrawRoadAsSnowOrDesert(snow_or_desert, roadside)) { image += 19; } else { switch (roadside) { @@ -1720,7 +1720,7 @@ static void DrawTile_Road(TileInfo *ti) SpriteID image = SPR_ROAD_Y + axis; Roadside roadside = GetRoadside(ti->tile); - if (DrawRoadAsSnowDesert(IsOnSnow(ti->tile), roadside)) { + if (DrawRoadAsSnowOrDesert(IsOnSnow(ti->tile), roadside)) { image += 19; } else { switch (roadside) { @@ -1736,7 +1736,7 @@ static void DrawTile_Road(TileInfo *ti) if (IsCrossingBarred(ti->tile)) image += 2; Roadside roadside = GetRoadside(ti->tile); - if (DrawRoadAsSnowDesert(IsOnSnow(ti->tile), roadside)) { + if (DrawRoadAsSnowOrDesert(IsOnSnow(ti->tile), roadside)) { image += 8; } else { switch (roadside) { From 42deccc4f541b7e4aa12e80395fadd11096b5dc9 Mon Sep 17 00:00:00 2001 From: frosch Date: Thu, 17 Apr 2025 13:41:18 +0200 Subject: [PATCH 040/766] Codechange: Merge (IsOn|Toggle)(Snow|Desert) into (IsOn|Toggle)SnowOrDesert. --- src/newgrf_commons.cpp | 2 +- src/road_cmd.cpp | 14 +++++++------- src/road_map.h | 8 ++------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/newgrf_commons.cpp b/src/newgrf_commons.cpp index 3d28b360ad..3b03f253ff 100644 --- a/src/newgrf_commons.cpp +++ b/src/newgrf_commons.cpp @@ -357,7 +357,7 @@ uint32_t GetTerrainType(TileIndex tile, TileContext context) case MP_ROAD: /* During map generation the snowstate may not be valid yet, as the tileloop may not have run yet. */ if (_generating_world) goto genworld; // we do not care about foundations here - has_snow = IsOnSnow(tile); + has_snow = IsOnSnowOrDesert(tile); break; case MP_TREES: { diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 6e23f63c59..c12587c90e 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -1628,7 +1628,7 @@ static void DrawRoadBits(TileInfo *ti) /* DrawFoundation() modifies ti. */ } - DrawRoadGroundSprites(ti, road, tram, road_rti, tram_rti, GetRoadside(ti->tile), IsOnSnow(ti->tile)); + DrawRoadGroundSprites(ti, road, tram, road_rti, tram_rti, GetRoadside(ti->tile), IsOnSnowOrDesert(ti->tile)); /* Draw one way */ if (road_rti != nullptr) { @@ -1720,7 +1720,7 @@ static void DrawTile_Road(TileInfo *ti) SpriteID image = SPR_ROAD_Y + axis; Roadside roadside = GetRoadside(ti->tile); - if (DrawRoadAsSnowOrDesert(IsOnSnow(ti->tile), roadside)) { + if (DrawRoadAsSnowOrDesert(IsOnSnowOrDesert(ti->tile), roadside)) { image += 19; } else { switch (roadside) { @@ -1736,7 +1736,7 @@ static void DrawTile_Road(TileInfo *ti) if (IsCrossingBarred(ti->tile)) image += 2; Roadside roadside = GetRoadside(ti->tile); - if (DrawRoadAsSnowOrDesert(IsOnSnow(ti->tile), roadside)) { + if (DrawRoadAsSnowOrDesert(IsOnSnowOrDesert(ti->tile), roadside)) { image += 8; } else { switch (roadside) { @@ -1975,16 +1975,16 @@ static void TileLoop_Road(TileIndex tile) case LandscapeType::Arctic: { /* Roads on flat foundations use the snow level of the height they are elevated to. All others use the snow level of their minimum height. */ int tile_z = (std::get(GetFoundationSlope(tile)) == SLOPE_FLAT) ? GetTileMaxZ(tile) : GetTileZ(tile); - if (IsOnSnow(tile) != (tile_z > GetSnowLine())) { - ToggleSnow(tile); + if (IsOnSnowOrDesert(tile) != (tile_z > GetSnowLine())) { + ToggleSnowOrDesert(tile); MarkTileDirtyByTile(tile); } break; } case LandscapeType::Tropic: - if (GetTropicZone(tile) == TROPICZONE_DESERT && !IsOnDesert(tile)) { - ToggleDesert(tile); + if (GetTropicZone(tile) == TROPICZONE_DESERT && !IsOnSnowOrDesert(tile)) { + ToggleSnowOrDesert(tile); MarkTileDirtyByTile(tile); } break; diff --git a/src/road_map.h b/src/road_map.h index 4d53bfdbe5..3ee5de72c1 100644 --- a/src/road_map.h +++ b/src/road_map.h @@ -433,25 +433,21 @@ inline void BarCrossing(Tile t) SetCrossingBarred(t, true); } -/** Check if a road tile has snow/desert. */ -#define IsOnDesert IsOnSnow /** * Check if a road tile has snow/desert. * @param t The tile to query. * @return True if the tile has snow/desert. */ -inline bool IsOnSnow(Tile t) +inline bool IsOnSnowOrDesert(Tile t) { return HasBit(t.m7(), 5); } -/** Toggle the snow/desert state of a road tile. */ -#define ToggleDesert ToggleSnow /** * Toggle the snow/desert state of a road tile. * @param t The tile to change. */ -inline void ToggleSnow(Tile t) +inline void ToggleSnowOrDesert(Tile t) { ToggleBit(t.m7(), 5); } From f82e172610970a1fd4aac889c4fef1e3ba285bc5 Mon Sep 17 00:00:00 2001 From: frosch Date: Thu, 17 Apr 2025 12:20:55 +0200 Subject: [PATCH 041/766] Codechange: Expand and remove DEF_CONSOLE_CMD and DEF_CONSOLE_HOOK macros. --- src/console_cmds.cpp | 184 +++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 96 deletions(-) diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 948f1c0aef..1a7e1d6908 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -105,11 +105,6 @@ static ConsoleFileList _console_file_list_savegame{FT_SAVEGAME, true}; ///< File static ConsoleFileList _console_file_list_scenario{FT_SCENARIO, false}; ///< File storage cache for scenarios. static ConsoleFileList _console_file_list_heightmap{FT_HEIGHTMAP, false}; ///< File storage cache for heightmaps. -/* console command defines */ -#define DEF_CONSOLE_CMD(function) static bool function([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) -#define DEF_CONSOLE_HOOK(function) static ConsoleHookResult function(bool echo) - - /**************** * command hooks ****************/ @@ -131,7 +126,7 @@ static inline bool NetworkAvailable(bool echo) * Check whether we are a server. * @return Are we a server? True when yes, false otherwise. */ -DEF_CONSOLE_HOOK(ConHookServerOnly) +static ConsoleHookResult ConHookServerOnly(bool echo) { if (!NetworkAvailable(echo)) return CHR_DISALLOW; @@ -146,7 +141,7 @@ DEF_CONSOLE_HOOK(ConHookServerOnly) * Check whether we are a client in a network game. * @return Are we a client in a network game? True when yes, false otherwise. */ -DEF_CONSOLE_HOOK(ConHookClientOnly) +static ConsoleHookResult ConHookClientOnly(bool echo) { if (!NetworkAvailable(echo)) return CHR_DISALLOW; @@ -161,7 +156,7 @@ DEF_CONSOLE_HOOK(ConHookClientOnly) * Check whether we are in a multiplayer game. * @return True when we are client or server in a network game. */ -DEF_CONSOLE_HOOK(ConHookNeedNetwork) +static ConsoleHookResult ConHookNeedNetwork(bool echo) { if (!NetworkAvailable(echo)) return CHR_DISALLOW; @@ -176,7 +171,7 @@ DEF_CONSOLE_HOOK(ConHookNeedNetwork) * Check whether we are in a multiplayer game and are playing, i.e. we are not the dedicated server. * @return Are we a client or non-dedicated server in a network game? True when yes, false otherwise. */ -DEF_CONSOLE_HOOK(ConHookNeedNonDedicatedNetwork) +static ConsoleHookResult ConHookNeedNonDedicatedNetwork(bool echo) { if (!NetworkAvailable(echo)) return CHR_DISALLOW; @@ -191,7 +186,7 @@ DEF_CONSOLE_HOOK(ConHookNeedNonDedicatedNetwork) * Check whether we are in singleplayer mode. * @return True when no network is active. */ -DEF_CONSOLE_HOOK(ConHookNoNetwork) +static ConsoleHookResult ConHookNoNetwork(bool echo) { if (_networking) { if (echo) IConsolePrint(CC_ERROR, "This command is forbidden in multiplayer."); @@ -204,7 +199,7 @@ DEF_CONSOLE_HOOK(ConHookNoNetwork) * Check if are either in singleplayer or a server. * @return True iff we are either in singleplayer or a server. */ -DEF_CONSOLE_HOOK(ConHookServerOrNoNetwork) +static ConsoleHookResult ConHookServerOrNoNetwork(bool echo) { if (_networking && !_network_server) { if (echo) IConsolePrint(CC_ERROR, "This command is only available to a network server."); @@ -213,7 +208,7 @@ DEF_CONSOLE_HOOK(ConHookServerOrNoNetwork) return CHR_ALLOW; } -DEF_CONSOLE_HOOK(ConHookNewGRFDeveloperTool) +static ConsoleHookResult ConHookNewGRFDeveloperTool(bool echo) { if (_settings_client.gui.newgrf_developer_tools) { if (_game_mode == GM_MENU) { @@ -229,7 +224,7 @@ DEF_CONSOLE_HOOK(ConHookNewGRFDeveloperTool) * Reset status of all engines. * @return Will always succeed. */ -DEF_CONSOLE_CMD(ConResetEngines) +static bool ConResetEngines([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Reset status data of all engines. This might solve some issues with 'lost' engines. Usage: 'resetengines'."); @@ -245,7 +240,7 @@ DEF_CONSOLE_CMD(ConResetEngines) * @return Will always return true. * @note Resetting the pool only succeeds when there are no vehicles ingame. */ -DEF_CONSOLE_CMD(ConResetEnginePool) +static bool ConResetEnginePool([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Reset NewGRF allocations of engine slots. This will remove invalid engine definitions, and might make default engines available again."); @@ -271,7 +266,7 @@ DEF_CONSOLE_CMD(ConResetEnginePool) * param tile number. * @return True when the tile is reset or the help on usage was printed (0 or two parameters). */ -DEF_CONSOLE_CMD(ConResetTile) +static bool ConResetTile([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Reset a tile to bare land. Usage: 'resettile '."); @@ -296,7 +291,7 @@ DEF_CONSOLE_CMD(ConResetTile) * param level As defined by ZoomLevel and as limited by zoom_min/zoom_max from GUISettings. * @return True when either console help was shown or a proper amount of parameters given. */ -DEF_CONSOLE_CMD(ConZoomToLevel) +static bool ConZoomToLevel([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { switch (argc) { case 0: @@ -355,7 +350,7 @@ DEF_CONSOLE_CMD(ConZoomToLevel) * and y coordinates. * @return True when either console help was shown or a proper amount of parameters given. */ -DEF_CONSOLE_CMD(ConScrollToTile) +static bool ConScrollToTile([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Center the screen on a given tile."); @@ -409,7 +404,7 @@ DEF_CONSOLE_CMD(ConScrollToTile) * param filename the filename to save the map to. * @return True when help was displayed or the file attempted to be saved. */ -DEF_CONSOLE_CMD(ConSave) +static bool ConSave([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Save the current game. Usage: 'save '."); @@ -436,7 +431,7 @@ DEF_CONSOLE_CMD(ConSave) * Explicitly save the configuration. * @return True. */ -DEF_CONSOLE_CMD(ConSaveConfig) +static bool ConSaveConfig([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Saves the configuration for new games to the configuration file, typically 'openttd.cfg'."); @@ -449,7 +444,7 @@ DEF_CONSOLE_CMD(ConSaveConfig) return true; } -DEF_CONSOLE_CMD(ConLoad) +static bool ConLoad([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Load a game by name or index. Usage: 'load '."); @@ -475,7 +470,7 @@ DEF_CONSOLE_CMD(ConLoad) return true; } -DEF_CONSOLE_CMD(ConLoadScenario) +static bool ConLoadScenario([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Load a scenario by name or index. Usage: 'load_scenario '."); @@ -501,7 +496,7 @@ DEF_CONSOLE_CMD(ConLoadScenario) return true; } -DEF_CONSOLE_CMD(ConLoadHeightmap) +static bool ConLoadHeightmap([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Load a heightmap by name or index. Usage: 'load_heightmap '."); @@ -527,7 +522,7 @@ DEF_CONSOLE_CMD(ConLoadHeightmap) return true; } -DEF_CONSOLE_CMD(ConRemove) +static bool ConRemove([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Remove a savegame by name or index. Usage: 'rm '."); @@ -553,7 +548,7 @@ DEF_CONSOLE_CMD(ConRemove) /* List all the files in the current dir via console */ -DEF_CONSOLE_CMD(ConListFiles) +static bool ConListFiles([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "List all loadable savegames and directories in the current dir via console. Usage: 'ls | dir'."); @@ -569,7 +564,7 @@ DEF_CONSOLE_CMD(ConListFiles) } /* List all the scenarios */ -DEF_CONSOLE_CMD(ConListScenarios) +static bool ConListScenarios([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "List all loadable scenarios. Usage: 'list_scenarios'."); @@ -585,7 +580,7 @@ DEF_CONSOLE_CMD(ConListScenarios) } /* List all the heightmaps */ -DEF_CONSOLE_CMD(ConListHeightmaps) +static bool ConListHeightmaps([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "List all loadable heightmaps. Usage: 'list_heightmaps'."); @@ -601,7 +596,7 @@ DEF_CONSOLE_CMD(ConListHeightmaps) } /* Change the dir via console */ -DEF_CONSOLE_CMD(ConChangeDirectory) +static bool ConChangeDirectory([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Change the dir via console. Usage: 'cd '."); @@ -630,7 +625,7 @@ DEF_CONSOLE_CMD(ConChangeDirectory) return true; } -DEF_CONSOLE_CMD(ConPrintWorkingDirectory) +static bool ConPrintWorkingDirectory([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Print out the current working directory. Usage: 'pwd'."); @@ -645,7 +640,7 @@ DEF_CONSOLE_CMD(ConPrintWorkingDirectory) return true; } -DEF_CONSOLE_CMD(ConClearBuffer) +static bool ConClearBuffer([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Clear the console buffer. Usage: 'clear'."); @@ -705,7 +700,7 @@ static bool ConKickOrBan(const char *argv, bool ban, const std::string &reason) return true; } -DEF_CONSOLE_CMD(ConKick) +static bool ConKick([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Kick a client from a network game. Usage: 'kick []'."); @@ -728,7 +723,7 @@ DEF_CONSOLE_CMD(ConKick) } } -DEF_CONSOLE_CMD(ConBan) +static bool ConBan([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Ban a client from a network game. Usage: 'ban []'."); @@ -752,7 +747,7 @@ DEF_CONSOLE_CMD(ConBan) } } -DEF_CONSOLE_CMD(ConUnBan) +static bool ConUnBan([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Unban a client from a network game. Usage: 'unban '."); @@ -784,7 +779,7 @@ DEF_CONSOLE_CMD(ConUnBan) return true; } -DEF_CONSOLE_CMD(ConBanList) +static bool ConBanList([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "List the IP's of banned clients: Usage 'banlist'."); @@ -802,7 +797,7 @@ DEF_CONSOLE_CMD(ConBanList) return true; } -DEF_CONSOLE_CMD(ConPauseGame) +static bool ConPauseGame([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Pause a network game. Usage: 'pause'."); @@ -824,7 +819,7 @@ DEF_CONSOLE_CMD(ConPauseGame) return true; } -DEF_CONSOLE_CMD(ConUnpauseGame) +static bool ConUnpauseGame([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Unpause a network game. Usage: 'unpause'."); @@ -850,7 +845,7 @@ DEF_CONSOLE_CMD(ConUnpauseGame) return true; } -DEF_CONSOLE_CMD(ConRcon) +static bool ConRcon([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Remote control the server from another client. Usage: 'rcon '."); @@ -869,7 +864,7 @@ DEF_CONSOLE_CMD(ConRcon) return true; } -DEF_CONSOLE_CMD(ConStatus) +static bool ConStatus([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "List the status of all clients connected to the server. Usage 'status'."); @@ -880,7 +875,7 @@ DEF_CONSOLE_CMD(ConStatus) return true; } -DEF_CONSOLE_CMD(ConServerInfo) +static bool ConServerInfo([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "List current and maximum client/company limits. Usage 'server_info'."); @@ -896,7 +891,7 @@ DEF_CONSOLE_CMD(ConServerInfo) return true; } -DEF_CONSOLE_CMD(ConClientNickChange) +static bool ConClientNickChange([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc != 3) { IConsolePrint(CC_HELP, "Change the nickname of a connected client. Usage: 'client_name '."); @@ -930,7 +925,7 @@ DEF_CONSOLE_CMD(ConClientNickChange) return true; } -DEF_CONSOLE_CMD(ConJoinCompany) +static bool ConJoinCompany([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc < 2) { IConsolePrint(CC_HELP, "Request joining another company. Usage: 'join '."); @@ -977,7 +972,7 @@ DEF_CONSOLE_CMD(ConJoinCompany) return true; } -DEF_CONSOLE_CMD(ConMoveClient) +static bool ConMoveClient([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc < 3) { IConsolePrint(CC_HELP, "Move a client to another company. Usage: 'move '."); @@ -1020,7 +1015,7 @@ DEF_CONSOLE_CMD(ConMoveClient) return true; } -DEF_CONSOLE_CMD(ConResetCompany) +static bool ConResetCompany([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Remove an idle company from the game. Usage: 'reset_company '."); @@ -1061,7 +1056,7 @@ DEF_CONSOLE_CMD(ConResetCompany) return true; } -DEF_CONSOLE_CMD(ConNetworkClients) +static bool ConNetworkClients([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Get a list of connected clients including their ID, name, company-id, and IP. Usage: 'clients'."); @@ -1073,7 +1068,7 @@ DEF_CONSOLE_CMD(ConNetworkClients) return true; } -DEF_CONSOLE_CMD(ConNetworkReconnect) +static bool ConNetworkReconnect([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Reconnect to server to which you were connected last time. Usage: 'reconnect []'."); @@ -1104,7 +1099,7 @@ DEF_CONSOLE_CMD(ConNetworkReconnect) return NetworkClientConnectGame(_settings_client.network.last_joined, playas); } -DEF_CONSOLE_CMD(ConNetworkConnect) +static bool ConNetworkConnect([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Connect to a remote OTTD server and join the game. Usage: 'connect '."); @@ -1122,7 +1117,7 @@ DEF_CONSOLE_CMD(ConNetworkConnect) * script file console commands *********************************/ -DEF_CONSOLE_CMD(ConExec) +static bool ConExec([[maybe_unused]] uint8_t argc, [[maybe_unused]] char *argv[]) { if (argc == 0) { IConsolePrint(CC_HELP, "Execute a local script file. Usage: 'exec