34#include "table/strings.h"
68 if (m.newgrf_id == newgrf_id)
return m.openttd_id;
83 if (m.openttd_id == openttd_id)
return m.newgrf_id;
113 if (this->strings.find(0) == this->strings.end()) {
116 GrfMsg(1,
"choice list misses default value");
117 this->strings[0] = std::string();
126 builder += this->strings[0];
132 if (this->type == SCC_SWITCH_CASE) {
143 if (this->strings.find(lm->
GetReverseMapping(i,
false)) != this->strings.end()) count++;
147 auto add_case = [&](std::string_view str) {
153 builder += str.substr(0, len);
159 if (this->strings.find(idx) == this->strings.end())
continue;
160 auto &str = this->strings[idx];
169 add_case(this->strings[0]);
171 if (this->type == SCC_PLURAL_LIST) {
181 builder.
PutUint8(this->offset - 0x80);
184 int count = (this->type == SCC_GENDER_LIST ?
_current_language->num_genders : LANGUAGE_MAX_PLURAL_FORMS);
188 for (
int i = 0; i < count; i++) {
189 int idx = (this->type == SCC_GENDER_LIST ? lm->
GetReverseMapping(i,
true) : i + 1);
190 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0];
191 size_t len = str.size();
192 if (len > UINT8_MAX) GrfMsg(1,
"choice list string is too long");
197 for (
int i = 0; i < count; i++) {
198 int idx = (this->type == SCC_GENDER_LIST ? lm->
GetReverseMapping(i,
true) : i + 1);
199 const auto &str = this->strings[this->strings.find(idx) != this->strings.end() ? idx : 0];
201 builder += str.substr(0, len);
219 if (str.empty())
return {};
227 std::optional<UnmappedChoiceList> mapping_pg, mapping_c;
228 std::optional<std::reference_wrapper<std::string>> dest_c;
234 if (
auto u = unicode ? consumer.
TryReadUtf8() : std::nullopt; u.has_value()) {
237 if (0xE000 <= c && c <= 0xE0FF) {
239 }
else if (c >= 0x20) {
248 if (c ==
'\0')
break;
257 if (allow_newlines) {
260 GrfMsg(1,
"Detected newline in string that does not allow one");
275 case 0x80: builder.
PutUtf8(byte80);
break;
289 case 0x88: builder.
PutUtf8(SCC_BLUE);
break;
290 case 0x89: builder.
PutUtf8(SCC_SILVER);
break;
291 case 0x8A: builder.
PutUtf8(SCC_GOLD);
break;
292 case 0x8B: builder.
PutUtf8(SCC_RED);
break;
293 case 0x8C: builder.
PutUtf8(SCC_PURPLE);
break;
294 case 0x8D: builder.
PutUtf8(SCC_LTBROWN);
break;
295 case 0x8E: builder.
PutUtf8(SCC_ORANGE);
break;
296 case 0x8F: builder.
PutUtf8(SCC_GREEN);
break;
297 case 0x90: builder.
PutUtf8(SCC_YELLOW);
break;
298 case 0x91: builder.
PutUtf8(SCC_DKGREEN);
break;
299 case 0x92: builder.
PutUtf8(SCC_CREAM);
break;
300 case 0x93: builder.
PutUtf8(SCC_BROWN);
break;
301 case 0x94: builder.
PutUtf8(SCC_WHITE);
break;
302 case 0x95: builder.
PutUtf8(SCC_LTBLUE);
break;
303 case 0x96: builder.
PutUtf8(SCC_GRAY);
break;
304 case 0x97: builder.
PutUtf8(SCC_DKBLUE);
break;
305 case 0x98: builder.
PutUtf8(SCC_BLACK);
break;
310 case 0x00:
goto string_end;
338 int mapped = lm !=
nullptr ? lm->
GetMapping(index, code == 0x0E) : -1;
340 builder.
PutUtf8(code == 0x0E ? SCC_GENDER_INDEX : SCC_SET_CASE);
341 builder.
PutUtf8(code == 0x0E ? mapped : mapped + 1);
348 if (!mapping_pg.has_value() && !mapping_c.has_value()) {
350 GrfMsg(1,
"choice list {} marker found when not expected", code == 0x10 ?
"next" :
"default");
353 auto &mapping = mapping_pg ? mapping_pg : mapping_c;
354 int index = (code == 0x10 ? consumer.
ReadUint8() : 0);
355 if (mapping->strings.find(index) != mapping->strings.end()) {
356 GrfMsg(1,
"duplicate choice list string, ignoring");
359 if (!mapping_pg) dest_c = mapping->strings[index];
365 if (!mapping_pg.has_value() && !mapping_c.has_value()) {
366 GrfMsg(1,
"choice list end marker found when not expected");
368 auto &mapping = mapping_pg ? mapping_pg : mapping_c;
369 auto &new_dest = mapping_pg && dest_c ? dest_c->get() : dest;
372 if (!mapping_pg) dest_c.reset();
382 auto &mapping = code == 0x14 ? mapping_c : mapping_pg;
384 if (mapping.has_value() || mapping_pg.has_value()) {
385 GrfMsg(1,
"choice lists can't be stacked, it's going to get messy now...");
388 static const StringControlCode mp[] = { SCC_GENDER_LIST, SCC_SWITCH_CASE, SCC_PLURAL_LIST };
389 mapping.emplace(mp[code - 0x13], code == 0x14 ? 0 : consumer.
ReadUint8());
406 case 0x1F: builder.
PutUtf8(SCC_PUSH_COLOUR);
break;
407 case 0x20: builder.
PutUtf8(SCC_POP_COLOUR);
break;
412 GrfMsg(1,
"missing handler for extended format code");
418 case 0x9B: builder.
PutUtf8(SCC_TOWN);
break;
419 case 0x9C: builder.
PutUtf8(SCC_CITY);
break;
420 case 0x9E: builder.
PutUtf8(0x20AC);
break;
421 case 0x9F: builder.
PutUtf8(0x0178);
break;
422 case 0xA0: builder.
PutUtf8(SCC_UP_ARROW);
break;
423 case 0xAA: builder.
PutUtf8(SCC_DOWN_ARROW);
break;
424 case 0xAC: builder.
PutUtf8(SCC_CHECKMARK);
break;
425 case 0xAD: builder.
PutUtf8(SCC_CROSS);
break;
426 case 0xAF: builder.
PutUtf8(SCC_RIGHT_ARROW);
break;
427 case 0xB4: builder.
PutUtf8(SCC_TRAIN);
break;
428 case 0xB5: builder.
PutUtf8(SCC_LORRY);
break;
429 case 0xB6: builder.
PutUtf8(SCC_BUS);
break;
430 case 0xB7: builder.
PutUtf8(SCC_PLANE);
break;
431 case 0xB8: builder.
PutUtf8(SCC_SHIP);
break;
432 case 0xB9: builder.
PutUtf8(SCC_SUPERSCRIPT_M1);
break;
433 case 0xBC: builder.
PutUtf8(SCC_SMALL_UP_ARROW);
break;
434 case 0xBD: builder.
PutUtf8(SCC_SMALL_DOWN_ARROW);
break;
444 if (mapping_pg.has_value() || mapping_c.has_value()) {
445 GrfMsg(1,
"choice list was incomplete, the whole list is ignored");
460 for (
auto &text : list) {
461 if (text.langid == langid) {
462 text.text = text_to_add;
468 list.emplace_back(langid, std::string{text_to_add});
496 if (list ==
nullptr) list = std::make_shared<GRFTextList>();
508 if (list ==
nullptr) list = std::make_shared<GRFTextList>();
524 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](
const GRFTextEntry &grf_text) {
return grf_text.grfid == grfid && grf_text.stringid == stringid; });
525 if (it == std::end(_grf_text)) {
530 it = _grf_text.emplace(std::end(_grf_text));
532 it->stringid = stringid;
533 it->def_string = def_string;
558 if (new_scheme)
return AddGRFString(grfid, stringid,
static_cast<GRFLanguage>(langid_to_add), allow_newlines, text_to_add, def_string);
572 if (old_lang.
Test(lang)) ret =
AddGRFString(grfid, stringid, lang, allow_newlines, text_to_add, def_string);
585 auto it = std::ranges::find_if(_grf_text, [&grfid, &stringid](
const GRFTextEntry &grf_text) {
return grf_text.grfid == grfid && grf_text.stringid == stringid; });
586 if (it != std::end(_grf_text)) {
591 return STR_UNDEFINED;
605 std::optional<std::string_view> default_text;
608 for (
const auto &text : text_list) {
614 default_text = text.text;
641 assert(stringid.base() < _grf_text.size());
642 assert(_grf_text[stringid].grfid != 0);
645 if (str.has_value())
return *str;
648 return GetStringPtr(_grf_text[stringid].def_string);
651bool CheckGrfLangID(uint8_t lang_id, uint8_t grf_version)
653 if (grf_version < 7) {
678 std::vector<uint8_t> stack;
680 const GRFFile *grffile =
nullptr;
682 TextRefStack(
const GRFFile *grffile, std::span<const int32_t> textstack) : grffile(grffile)
684 this->stack.reserve(textstack.size() * 4 + 6);
685 for (int32_t value : std::ranges::reverse_view(textstack)) {
686 this->PushDWord(value);
690 uint8_t PopUnsignedByte()
692 if (this->stack.empty())
return 0;
693 auto res = this->stack.back();
694 this->stack.pop_back();
697 int8_t PopSignedByte() {
return (int8_t)this->PopUnsignedByte(); }
699 uint16_t PopUnsignedWord()
701 uint16_t val = this->PopUnsignedByte();
702 return val | (this->PopUnsignedByte() << 8);
704 int16_t PopSignedWord() {
return (int32_t)this->PopUnsignedWord(); }
706 uint32_t PopUnsignedDWord()
708 uint32_t val = this->PopUnsignedWord();
709 return val | (this->PopUnsignedWord() << 16);
711 int32_t PopSignedDWord() {
return (int32_t)this->PopUnsignedDWord(); }
713 uint64_t PopUnsignedQWord()
715 uint64_t val = this->PopUnsignedDWord();
716 return val | (((uint64_t)this->PopUnsignedDWord()) << 32);
718 int64_t PopSignedQWord() {
return (int64_t)this->PopUnsignedQWord(); }
723 auto w1 = this->PopUnsignedWord();
724 auto w2 = this->PopUnsignedWord();
725 auto w3 = this->PopUnsignedWord();
726 auto w4 = this->PopUnsignedWord();
733 void PushByte(uint8_t b)
735 this->stack.push_back(b);
738 void PushWord(uint16_t w)
740 this->PushByte(
GB(w, 8, 8));
741 this->PushByte(
GB(w, 0, 8));
744 void PushDWord(uint32_t dw)
746 this->PushWord(
GB(dw, 16, 16));
747 this->PushWord(
GB(dw, 0, 16));
767 case SCC_PLURAL_LIST:
770 case SCC_GENDER_LIST: {
775 for (uint i = 0; i != num; i++) {
778 consumer.
Skip(total_len);
782 case SCC_SWITCH_CASE: {
785 for (uint i = 0; i != num; i++) {
794 case SCC_GENDER_INDEX:
842 params.emplace_back(stack.PopUnsignedWord());
854 params.emplace_back(stringid);
862 params.emplace_back(cargo <
NUM_CARGO ? 1ULL << cargo : 0);
894 return SCC_CURRENCY_LONG;
898 return SCC_DATE_LONG;
902 return SCC_DATE_SHORT;
908 return SCC_VOLUME_LONG;
911 return SCC_VOLUME_SHORT;
914 return SCC_WEIGHT_LONG;
917 return SCC_WEIGHT_SHORT;
926 return SCC_CARGO_LONG;
929 return SCC_CARGO_SHORT;
932 return SCC_CARGO_TINY;
935 return SCC_CARGO_LIST;
938 return SCC_STATION_NAME;
977 auto str = GetStringPtr(stringid);
979 std::vector<StringParameter> params;
static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
constexpr enable_if_t< is_integral_v< T >, T > byteswap(T x) noexcept
Custom implementation of std::byteswap; remove once we build with C++23.
static constexpr CargoType NUM_CARGO
Maximum number of cargo types in a game.
CargoType
Cargo slots to indicate a cargo type within a game.
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr bool Any(const Timpl &other) const
Test if any of the given values are set.
void PutUtf8(char32_t c)
Append UTF-8 char.
void PutUint16LE(uint16_t value)
Append binary uint16 using little endian.
void PutChar(char c)
Append 8-bit char.
void PutUint8(uint8_t value)
Append binary uint8.
Compose data into a growing std::string.
Parse data from a string / buffer.
char32_t ReadUtf8(char32_t def='?')
Read UTF-8 character, and advance reader.
void SkipUtf8()
Skip UTF-8 character, and advance reader.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
uint8_t ReadUint8(uint8_t def=0)
Read binary uint8, and advance reader.
void SkipUint16LE()
Skip binary uint16, and advance reader.
uint16_t ReadUint16LE(uint16_t def=0)
Read binary uint16 using little endian, and advance reader.
void SkipUint8()
Skip binary uint8.
std::optional< char32_t > TryReadUtf8()
Try to read a UTF-8 character, and then advance reader.
bool ReadUtf8If(char32_t c)
Check whether the next UTF-8 char matches 'c', and skip it.
void Skip(size_type len)
Discard some bytes.
static constexpr TimerGame< struct Calendar >::Date DAYS_TILL_ORIGINAL_BASE_YEAR
A sort-of mixin that implements 'at(pos)' and 'operator[](pos)' only for a specific type.
Control codes that are embedded in the translation strings.
StringControlCode
List of string control codes used for string formatting, displaying, and by strgen to generate the la...
@ SCC_NEWGRF_PRINT_WORD_VOLUME_SHORT
9A 19: Read 2 bytes from the stack as short signed volume
@ SCC_NEWGRF_PRINT_DWORD_CURRENCY
8F: Read 4 bytes from the stack as currency
@ SCC_NEWGRF_PRINT_WORD_HEX
9A 07: Read 2 bytes from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_WEIGHT_SHORT
9A 1A: Read 2 bytes from the stack as short unsigned weight
@ SCC_NEWGRF_ROTATE_TOP_4_WORDS
86: Rotate the top 4 words of the stack (W4 W1 W2 W3)
@ SCC_TINYFONT
Switch to small font.
@ SCC_NEWGRF_PRINT_DWORD_DATE_LONG
9A 16: Read 4 bytes from the stack as base 0 date
@ SCC_NEWGRF_PRINT_DWORD_HEX
9A 08: Read 4 bytes from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_DATE_SHORT
83: Read 2 bytes from the stack as base 1920 date
@ SCC_BIGFONT
Switch to large font.
@ SCC_NEWGRF_PRINT_BYTE_HEX
9A 06: Read 1 byte from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_UNSIGNED
7E: Read 2 bytes from the stack as unsigned value
@ SCC_NEWGRF_PRINT_BYTE_SIGNED
7D: Read 1 byte from the stack as signed value
@ SCC_NEWGRF_PRINT_WORD_CARGO_NAME
9A 1E: Read 2 bytes from the stack as cargo name
@ SCC_NEWGRF_PRINT_DWORD_SIGNED
7B: Read 4 bytes from the stack
@ SCC_NEWGRF_STRINL
Inline another string at the current position, StringID is encoded in the string.
@ SCC_NEWGRF_PRINT_WORD_CARGO_SHORT
9A 1C: Read 2 + 2 bytes from the stack as cargo type (translated) and unsigned cargo amount
@ SCC_NEWGRF_PRINT_WORD_STATION_NAME
9A 0C: Read 2 bytes from the stack as station name
@ SCC_NEWGRF_PRINT_WORD_DATE_LONG
82: Read 2 bytes from the stack as base 1920 date
@ SCC_NEWGRF_PRINT_WORD_SIGNED
7C: Read 2 bytes from the stack as signed value
@ SCC_NEWGRF_PRINT_WORD_POWER
9A 18: Read 2 bytes from the stack as unsigned power
@ SCC_NEWGRF_PUSH_WORD
9A 03: Pushes 2 bytes onto the stack
@ SCC_NEWGRF_PRINT_WORD_STRING_ID
81: Read 2 bytes from the stack as String ID
@ SCC_NEWGRF_PRINT_WORD_VOLUME_LONG
87: Read 2 bytes from the stack as long signed volume
@ SCC_NEWGRF_DISCARD_WORD
85: Discard the next two bytes
@ SCC_NEWGRF_PRINT_DWORD_FORCE
9A 21: Read 4 bytes from the stack as unsigned force
@ SCC_NEWGRF_PRINT_WORD_CARGO_TINY
9A 1D: Read 2 + 2 bytes from the stack as cargo type (translated) and unsigned cargo amount
@ SCC_NEWGRF_PRINT_QWORD_HEX
9A 0B: Read 8 bytes from the stack and print it as hex
@ SCC_NEWGRF_PRINT_WORD_CARGO_LONG
9A 1B: Read 2 + 2 bytes from the stack as cargo type (translated) and unsigned cargo amount
@ SCC_NEWGRF_PRINT_WORD_SPEED
84: Read 2 bytes from the stack as signed speed
@ SCC_NEWGRF_PRINT_DWORD_DATE_SHORT
9A 17: Read 4 bytes from the stack as base 0 date
@ SCC_NEWGRF_PRINT_WORD_WEIGHT_LONG
9A 0D: Read 2 bytes from the stack as long unsigned weight
@ SCC_NEWGRF_PRINT_QWORD_CURRENCY
9A 01: Read 8 bytes from the stack as currency
Functions related to debugging.
Information about languages and their files.
const LanguageMetadata * _current_language
The currently loaded language.
constexpr To ClampTo(From value)
Clamp the given value down to lie within the requested type.
Base for the NewGRF implementation.
StringID MapGRFStringID(GrfID grfid, GRFStringID str)
Used when setting an object's property to map to the GRF's strings while taking in consideration the ...
CargoType GetCargoTranslation(uint8_t cargo, const GRFFile *grffile, bool usebit)
Translate a GRF-local cargo slot/bitnum into a CargoType.
Cargo support for NewGRFs.
Functionality related to the temporary and persistent storage arrays for NewGRFs.
void CleanUpStrings()
House cleaning.
std::string_view GetGRFStringPtr(StringIndexInTab stringid)
Get a C-string from a stringid set by a newgrf.
static void HandleNewGRFStringControlCodes(std::string_view str, TextRefStack &stack, std::vector< StringParameter > ¶ms)
Handle control codes in a NewGRF string, processing the stack and filling parameters.
EnumBitSet< GRFLanguage, uint8_t > OldGRFLanguages
The bitmask of the old GRF languages, which are conveniently the same as the first few GRFLanguages.
std::vector< StringParameter > GetGRFStringTextStackParameters(const GRFFile *grffile, StringID stringid, std::span< const int32_t > textstack)
Process the text ref stack for a GRF String and return its parameters.
std::string GetGRFStringWithTextStack(const struct GRFFile *grffile, GRFStringID grfstringid, std::span< const int32_t > textstack)
Format a GRF string using the text ref stack for parameters.
std::string TranslateTTDPatchCodes(GrfID grfid, GRFLanguage language_id, bool allow_newlines, std::string_view str, StringControlCode byte80)
Translate TTDPatch string codes into something OpenTTD can handle (better).
std::optional< std::string_view > GetGRFStringFromGRFText(const GRFTextList &text_list)
Get a C-string from a GRFText-list.
StringID GetGRFStringID(GrfID grfid, GRFStringID stringid)
Returns the index for this stringid associated with its grfID.
static void AddGRFTextToList(GRFTextList &list, GRFLanguage langid, std::string_view text_to_add)
Add a new text to a GRFText list.
static void ProcessNewGRFStringControlCode(char32_t scc, StringConsumer &consumer, TextRefStack &stack, std::vector< StringParameter > ¶ms)
Process NewGRF string control code instructions.
char32_t RemapNewGRFStringControlCode(char32_t scc, StringConsumer &consumer)
Emit OpenTTD's internal string code for the different NewGRF string codes.
static StringID AddGRFString(GrfID grfid, GRFStringID stringid, GRFLanguage langid_to_add, bool allow_newlines, std::string_view text_to_add, StringID def_string)
Add the new read string into our structure.
Header of Action 04 "universal holder" structure and functions.
GRFLanguage
Incomplete enumeration of the (New)GRF languages.
@ Unspecified
No specific language.
@ English
English (English UK).
@ American
American (English US).
StrongType::Typedef< uint32_t, struct GRFStringIDTag, StrongType::Compare, StrongType::Integer > GRFStringID
Type for GRF-internal string IDs.
std::shared_ptr< GRFTextList > GRFTextWrapper
Reference counted wrapper around a GRFText pointer.
std::vector< GRFText > GRFTextList
A GRF text with a list of translations.
static const char32_t NFO_UTF8_IDENTIFIER
This character (thorn) indicates a unicode string to NFO.
uint32_t GrfID
The unique identifier of a NewGRF.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
bool IsValidChar(char32_t key, CharSetFilter afilter)
Only allow certain keys.
Compose strings from textual and binary data.
Functions related to low-level strings.
@ CS_ALPHANUMERAL
Both numeric and alphabetic and spaces and stuff.
void GetStringWithArgs(StringBuilder &builder, StringID string, StringParameters &args, uint case_index, bool game_script)
Get a parsed string with most special stringcodes replaced by the string parameters.
StringID MakeStringID(StringTab tab, StringIndexInTab index)
Create a StringID.
Types and functions related to the internal workings of formatting OpenTTD's strings.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
StrongType::Typedef< uint32_t, struct StringIndexInTabTag, StrongType::Compare, StrongType::Integer > StringIndexInTab
The index/offset of a string within a StringTab.
static const StringID INVALID_STRING_ID
Constant representing an invalid string (16bit in case it is used in savegames).
static const uint TAB_SIZE_NEWGRF
Number of strings for NewGRFs.
@ TEXT_TAB_NEWGRF_START
Start of NewGRF supplied strings.
Dynamic data of a loaded NewGRF.
Holder of the above structure.
Mapping between NewGRF and OpenTTD IDs.
Mapping of language data between a NewGRF and OpenTTD.
std::vector< Mapping > gender_map
Mapping of NewGRF and OpenTTD IDs for genders.
int GetReverseMapping(int openttd_id, bool gender) const
Get the mapping from OpenTTD's internal ID to the NewGRF supplied ID.
int plural_form
The plural form used for this language.
static const LanguageMap * GetLanguageMap(GrfID grfid, GRFLanguage language_id)
Get the language map associated with a given NewGRF and language.
std::vector< Mapping > case_map
Mapping of NewGRF and OpenTTD IDs for cases.
int GetMapping(int newgrf_id, bool gender) const
Get the mapping from the NewGRF supplied ID to OpenTTD's internal ID.
void RotateTop4Words()
Rotate the top four words down: W1, W2, W3, W4 -> W4, W1, W2, W3.
StringControlCode type
The type of choice list.
int offset
The offset for the plural/gender form.
std::map< int, std::string > strings
Mapping of NewGRF supplied ID to the different strings in the choice list.
UnmappedChoiceList(StringControlCode type, int offset)
Initialise the mapping.
void Flush(const LanguageMap *lm, std::string &dest)
Flush this choice list into the destination string.
Definition of the game-calendar-timer.