diff --git a/src/string.cpp b/src/string.cpp index 9803cc9b5a..8266b8527e 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -100,6 +100,7 @@ static bool IsSccEncodedCode(char32_t c) switch (c) { case SCC_RECORD_SEPARATOR: case SCC_ENCODED: + case SCC_ENCODED_INTERNAL: case SCC_ENCODED_NUMERIC: case SCC_ENCODED_STRING: return true; diff --git a/src/strings.cpp b/src/strings.cpp index 9255b9d618..7979ab984d 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -95,6 +95,65 @@ const StringParameter &StringParameters::GetNextParameterReference() return param; } +/** + * Encode a string with no parameters into an encoded string. + * @param str The StringID to format. + * @returns The encoded string. + */ +EncodedString GetEncodedString(StringID str) +{ + return GetEncodedStringWithArgs(str, {}); +} + +/** + * Encode a string with its parameters into an encoded string. + * The encoded string can be stored and decoded later without requiring parameters to be stored separately. + * @param str The StringID to format. + * @param params The parameters of the string. + * @returns The encoded string. + */ +EncodedString GetEncodedStringWithArgs(StringID str, std::span params) +{ + std::string result; + auto output = std::back_inserter(result); + Utf8Encode(output, SCC_ENCODED_INTERNAL); + fmt::format_to(output, "{:X}", str); + + struct visitor { + std::back_insert_iterator &output; + + void operator()(const std::monostate &) {} + + void operator()(const uint64_t &arg) + { + Utf8Encode(output, SCC_ENCODED_NUMERIC); + fmt::format_to(this->output, "{:X}", arg); + } + + void operator()(const std::string &value) + { + Utf8Encode(output, SCC_ENCODED_STRING); + fmt::format_to(this->output, "{}", value); + } + }; + + visitor v{output}; + for (const auto ¶m : params) { + *output = SCC_RECORD_SEPARATOR; + std::visit(v, param.data); + } + + return EncodedString{std::move(result)}; +} + +/** + * Decode the encoded string. + * @returns Decoded raw string. + */ +std::string EncodedString::GetDecodedString() const +{ + return GetString(STR_JUST_RAW_STRING, this->string); +} /** * Set a string parameter \a v at index \a n in the global string parameter array. @@ -956,10 +1015,11 @@ uint ConvertDisplaySpeedToKmhishSpeed(uint speed, VehicleType type) /** * Decodes an encoded string during FormatString. * @param str The buffer of 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, StringBuilder &builder) +static const char *DecodeEncodedString(const char *str, bool game_script, StringBuilder &builder) { ArrayStringParameters<20> sub_args; @@ -970,7 +1030,7 @@ static const char *DecodeEncodedString(const char *str, StringBuilder &builder) builder += "(invalid SCC_ENCODED)"; return p; } - if (id >= TAB_SIZE_GAMESCRIPT) { + if (game_script && id >= TAB_SIZE_GAMESCRIPT) { while (*p != '\0') p++; builder += "(invalid StringID)"; return p; @@ -984,6 +1044,12 @@ static const char *DecodeEncodedString(const char *str, StringBuilder &builder) /* Find end of the parameter. */ for (; *p != '\0' && *p != SCC_RECORD_SEPARATOR; ++p) {} + if (s == p) { + /* This is an empty parameter. */ + sub_args.SetParam(i++, std::monostate{}); + continue; + } + /* Get the parameter type. */ char32_t parameter_type; size_t len = Utf8Decode(¶meter_type, s); @@ -1014,13 +1080,13 @@ static const char *DecodeEncodedString(const char *str, StringBuilder &builder) } default: - /* Skip unknown parameter. */ - i++; + /* Unknown parameter, make it blank. */ + sub_args.SetParam(i++, std::monostate{}); break; } } - StringID stringid = MakeStringID(TEXT_TAB_GAMESCRIPT_START, id); + StringID stringid = game_script ? MakeStringID(TEXT_TAB_GAMESCRIPT_START, id) : StringID{id.base()}; GetStringWithArgs(builder, stringid, sub_args, true); return p; @@ -1092,7 +1158,8 @@ static void FormatString(StringBuilder &builder, const char *str_arg, StringPara args.SetTypeOfNextParameter(b); switch (b) { case SCC_ENCODED: - str = DecodeEncodedString(str, builder); + case SCC_ENCODED_INTERNAL: + str = DecodeEncodedString(str, b == SCC_ENCODED, builder); break; case SCC_NEWGRF_STRINL: { diff --git a/src/strings_func.h b/src/strings_func.h index 2748ff6758..307d57e7bc 100644 --- a/src/strings_func.h +++ b/src/strings_func.h @@ -135,6 +135,22 @@ std::string GetString(StringID string, Args &&... args) return GetStringWithArgs(string, params); } +EncodedString GetEncodedString(StringID str); +EncodedString GetEncodedStringWithArgs(StringID str, std::span params); + +/** + * Get an encoded string with parameters. + * @param string String ID to encode. + * @param args The parameters to set. + * @return The encoded string. + */ +template +EncodedString GetEncodedString(StringID string, const Args&... args) +{ + auto params = MakeParameters(std::forward(args)...); + return GetEncodedStringWithArgs(string, params); +} + /** * A searcher for missing glyphs. */ diff --git a/src/strings_type.h b/src/strings_type.h index d749ac7960..567fd04e7a 100644 --- a/src/strings_type.h +++ b/src/strings_type.h @@ -92,4 +92,27 @@ struct StringParameter { inline StringParameter(const ConvertibleThroughBase auto &data) : data(static_cast(data.base())), type(0) {} }; +/** + * Container for an encoded string, created by GetEncodedString. + */ +class EncodedString { +public: + EncodedString() = default; + + auto operator<=>(const EncodedString &) const = default; + + std::string GetDecodedString() const; + + inline void clear() { this->string.clear(); } + inline bool empty() const { return this->string.empty(); } + +private: + std::string string; ///< The encoded string. + + /* An EncodedString can only be created by GetEncodedStringWithArgs(). */ + explicit EncodedString(std::string &&string) : string(std::move(string)) {} + + friend EncodedString GetEncodedStringWithArgs(StringID str, std::span params); +}; + #endif /* STRINGS_TYPE_H */ diff --git a/src/table/control_codes.h b/src/table/control_codes.h index 5e2e4ea9c6..d6db08ad31 100644 --- a/src/table/control_codes.h +++ b/src/table/control_codes.h @@ -25,7 +25,7 @@ enum StringControlCode : uint16_t { /* All SCC_ENCODED* control codes must have stable ids are they are stored in strings that are saved in savegames. */ SCC_ENCODED = SCC_CONTROL_START, ///< Encoded string marker and sub-string parameter. - SCC_ENCODED_RESERVED, ///< Reserved for future non-GS encoded strings. + SCC_ENCODED_INTERNAL, ///< Encoded text from OpenTTD. SCC_ENCODED_NUMERIC, ///< Encoded numeric parameter. SCC_ENCODED_STRING, ///< Encoded string parameter.