# HG changeset patch # User Peter Nelson # Date 2024-01-04 20:50:58 # Node ID 5c619adf84b5f2dcfea3c7e0030db4c8e25cadc7 # Parent 0911e61e08fcb671e65a23997f446e498e665942 Change: Don't handle 'missing' string parameters as 0. (#11673) If not enough parameters are supplied for a string, then a value of 0 was used, which could result in incorrect information being displayed. Instead, throw an exception and include an error in the string. diff --git a/src/strings.cpp b/src/strings.cpp --- a/src/strings.cpp +++ b/src/strings.cpp @@ -81,15 +81,13 @@ StringParameter *StringParameters::GetNe { assert(this->next_type == 0 || (SCC_CONTROL_START <= this->next_type && this->next_type <= SCC_CONTROL_END)); if (this->offset >= this->parameters.size()) { - Debug(misc, 0, "Trying to read invalid string parameter"); - return nullptr; + throw std::out_of_range("Trying to read invalid string parameter"); } auto ¶m = this->parameters[this->offset++]; if (param.type != 0 && param.type != this->next_type) { - Debug(misc, 0, "Trying to read string parameter with wrong type"); this->next_type = 0; - return nullptr; + throw std::out_of_range("Trying to read string parameter with wrong type"); } param.type = this->next_type; this->next_type = 0; @@ -903,733 +901,738 @@ static void FormatString(StringBuilder & str_stack.push(str_arg); for (;;) { - while (!str_stack.empty() && (b = Utf8Consume(&str_stack.top())) == '\0') { - str_stack.pop(); - } - if (str_stack.empty()) break; - const char *&str = str_stack.top(); - - if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) { - /* We need to pass some stuff as it might be modified. */ - StringParameters remaining = args.GetRemainingParameters(); - b = RemapNewGRFStringControlCode(b, &str, remaining, dry_run); - if (b == 0) continue; - } - - if (b < SCC_CONTROL_START || b > SCC_CONTROL_END) { - builder.Utf8Encode(b); - continue; - } - - args.SetTypeOfNextParameter(b); - switch (b) { - case SCC_ENCODED: { - ArrayStringParameters<20> sub_args; - - char *p; - uint32_t stringid = std::strtoul(str, &p, 16); - if (*p != ':' && *p != '\0') { - while (*p != '\0') p++; - str = p; - builder += "(invalid SCC_ENCODED)"; - break; - } - if (stringid >= TAB_SIZE_GAMESCRIPT) { - while (*p != '\0') p++; - str = p; - builder += "(invalid StringID)"; - break; - } - - int i = 0; - while (*p != '\0' && i < 20) { - uint64_t param; - const char *s = ++p; - - /* Find the next value */ - bool instring = false; - bool escape = false; - for (;; p++) { - if (*p == '\\') { - escape = true; - continue; - } - if (*p == '"' && escape) { - escape = false; - continue; - } - escape = false; - - if (*p == '"') { - instring = !instring; - continue; - } - if (instring) { - continue; - } - - if (*p == ':') break; - if (*p == '\0') break; - } + try { + while (!str_stack.empty() && (b = Utf8Consume(&str_stack.top())) == '\0') { + str_stack.pop(); + } + if (str_stack.empty()) break; + const char *&str = str_stack.top(); - if (*s != '"') { - /* Check if we want to look up another string */ - char32_t l; - size_t len = Utf8Decode(&l, s); - bool lookup = (l == SCC_ENCODED); - if (lookup) s += len; - - param = std::strtoull(s, &p, 16); - - if (lookup) { - if (param >= TAB_SIZE_GAMESCRIPT) { - while (*p != '\0') p++; - str = p; - builder += "(invalid sub-StringID)"; - break; - } - param = MakeStringID(TEXT_TAB_GAMESCRIPT_START, param); - } - - sub_args.SetParam(i++, param); - } else { - s++; // skip the leading \" - sub_args.SetParam(i++, std::string(s, p - s - 1)); // also skip the trailing \". - } - } - /* If we didn't error out, we can actually print the string. */ - if (*str != '\0') { - str = p; - GetStringWithArgs(builder, MakeStringID(TEXT_TAB_GAMESCRIPT_START, stringid), sub_args, true); - } - break; - } - - case SCC_NEWGRF_STRINL: { - StringID substr = Utf8Consume(&str); - str_stack.push(GetStringPtr(substr)); - break; - } - - case SCC_NEWGRF_PRINT_WORD_STRING_ID: { - StringID substr = args.GetNextParameter(); - str_stack.push(GetStringPtr(substr)); - case_index = next_substr_case_index; - 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 = orig_offset + (byte)*str++; - int gender = 0; - if (!dry_run && args.GetTypeAtOffset(offset) != 0) { - /* Now we need to figure out what text to resolve, i.e. - * what do we need to draw? So get the actual raw string - * first using the control code to get said string. */ - char input[4 + 1]; - char *p = input + Utf8Encode(input, args.GetTypeAtOffset(offset)); - *p = '\0'; - - /* The gender is stored at the start of the formatted string. */ - bool old_sgd = _scan_for_gender_data; - _scan_for_gender_data = true; - std::string buffer; - StringBuilder tmp_builder(buffer); - StringParameters tmp_params = args.GetRemainingParameters(offset); - FormatString(tmp_builder, input, tmp_params); - _scan_for_gender_data = old_sgd; - - /* And determine the 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 = (byte)s[0]; - } - str = ParseStringChoice(str, gender, builder); - break; + if (SCC_NEWGRF_FIRST <= b && b <= SCC_NEWGRF_LAST) { + /* We need to pass some stuff as it might be modified. */ + StringParameters remaining = args.GetRemainingParameters(); + b = RemapNewGRFStringControlCode(b, &str, remaining, dry_run); + if (b == 0) continue; } - /* 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} - if (_scan_for_gender_data) { - builder.Utf8Encode(SCC_GENDER_INDEX); - builder += *str++; - } else { - str++; - } - break; - - case SCC_PLURAL_LIST: { // {P} - int plural_form = *str++; // contains the plural form for this string - size_t offset = orig_offset + (byte)*str++; - int64_t v = args.GetParam(offset); // contains the number that determines plural - str = ParseStringChoice(str, DeterminePluralForm(v, plural_form), builder); - break; - } - - case SCC_ARG_INDEX: { // Move argument pointer - args.SetOffset(orig_offset + (byte)*str++); - 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 = (byte)*str++; - break; - } - - case SCC_SWITCH_CASE: { // {Used to implement case switching} - /* <0x9E> - * Each LEN is printed using 2 bytes in big endian order. */ - uint num = (byte)*str++; - while (num) { - if ((byte)str[0] == case_index) { - /* Found the case, adjust str pointer and continue */ - str += 3; - break; - } - /* Otherwise skip to the next case */ - str += 3 + (str[1] << 8) + str[2]; - num--; - } - break; - } - - case SCC_REVISION: // {REV} - builder += _openttd_revision; - break; - - case SCC_RAW_STRING_POINTER: { // {RAW_STRING} - const char *raw_string = args.GetNextParameterString(); - /* raw_string can be(come) nullptr when the parameter is out of range and 0 is returned instead. */ - if (raw_string == nullptr) { - builder += "(invalid RAW_STRING parameter)"; - break; - } - FormatString(builder, raw_string, args); - break; - } - - case SCC_STRING: {// {STRING} - StringID string_id = args.GetNextParameter(); - if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break; - /* It's prohibited for the included string to consume any arguments. */ - StringParameters tmp_params(args, game_script ? args.GetDataLeft() : 0); - GetStringWithArgs(builder, string_id, tmp_params, next_substr_case_index, game_script); - next_substr_case_index = 0; - break; + if (b < SCC_CONTROL_START || b > SCC_CONTROL_END) { + builder.Utf8Encode(b); + continue; } - case SCC_STRING1: - case SCC_STRING2: - case SCC_STRING3: - case SCC_STRING4: - case SCC_STRING5: - case SCC_STRING6: - case SCC_STRING7: { // {STRING1..7} - /* Strings that consume arguments */ - StringID string_id = args.GetNextParameter(); - if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break; - uint size = b - SCC_STRING1 + 1; - if (game_script && size > args.GetDataLeft()) { - builder += "(too many parameters)"; - } else { - StringParameters sub_args(args, game_script ? args.GetDataLeft() : size); - GetStringWithArgs(builder, string_id, sub_args, next_substr_case_index, game_script); - args.AdvanceOffset(size); - } - next_substr_case_index = 0; - break; - } - - case SCC_COMMA: // {COMMA} - FormatCommaNumber(builder, args.GetNextParameter()); - break; - - case SCC_DECIMAL: { // {DECIMAL} - int64_t number = args.GetNextParameter(); - int digits = args.GetNextParameter(); - FormatCommaNumber(builder, number, digits); - break; - } + args.SetTypeOfNextParameter(b); + switch (b) { + case SCC_ENCODED: { + ArrayStringParameters<20> sub_args; - case SCC_NUM: // {NUM} - FormatNoCommaNumber(builder, args.GetNextParameter()); - break; - - case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM} - int64_t num = args.GetNextParameter(); - FormatZerofillNumber(builder, num, args.GetNextParameter()); - break; - } - - case SCC_HEX: // {HEX} - FormatHexNumber(builder, args.GetNextParameter()); - break; - - case SCC_BYTES: // {BYTES} - FormatBytes(builder, args.GetNextParameter()); - break; - - case SCC_CARGO_TINY: { // {CARGO_TINY} - /* Tiny description of cargotypes. Layout: - * param 1: cargo type - * param 2: cargo count */ - CargoID cargo = args.GetNextParameter(); - if (cargo >= CargoSpec::GetArraySize()) break; - - StringID cargo_str = CargoSpec::Get(cargo)->units_volume; - int64_t amount = 0; - switch (cargo_str) { - case STR_TONS: - amount = _units_weight[_settings_game.locale.units_weight].c.ToDisplay(args.GetNextParameter()); - break; - - case STR_LITERS: - amount = _units_volume[_settings_game.locale.units_volume].c.ToDisplay(args.GetNextParameter()); - break; - - default: { - amount = args.GetNextParameter(); + char *p; + uint32_t stringid = std::strtoul(str, &p, 16); + if (*p != ':' && *p != '\0') { + while (*p != '\0') p++; + str = p; + builder += "(invalid SCC_ENCODED)"; break; } - } - - FormatCommaNumber(builder, amount); - break; - } - - case SCC_CARGO_SHORT: { // {CARGO_SHORT} - /* Short description of cargotypes. Layout: - * param 1: cargo type - * param 2: cargo count */ - CargoID cargo = args.GetNextParameter(); - if (cargo >= CargoSpec::GetArraySize()) break; - - StringID cargo_str = CargoSpec::Get(cargo)->units_volume; - switch (cargo_str) { - case STR_TONS: { - assert(_settings_game.locale.units_weight < lengthof(_units_weight)); - const auto &x = _units_weight[_settings_game.locale.units_weight]; - auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); - FormatString(builder, GetStringPtr(x.l), tmp_params); - break; - } - - case STR_LITERS: { - assert(_settings_game.locale.units_volume < lengthof(_units_volume)); - const auto &x = _units_volume[_settings_game.locale.units_volume]; - auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); - FormatString(builder, GetStringPtr(x.l), tmp_params); + if (stringid >= TAB_SIZE_GAMESCRIPT) { + while (*p != '\0') p++; + str = p; + builder += "(invalid StringID)"; break; } - default: { - auto tmp_params = MakeParameters(args.GetNextParameter()); - GetStringWithArgs(builder, cargo_str, tmp_params); - break; + int i = 0; + while (*p != '\0' && i < 20) { + uint64_t param; + const char *s = ++p; + + /* Find the next value */ + bool instring = false; + bool escape = false; + for (;; p++) { + if (*p == '\\') { + escape = true; + continue; + } + if (*p == '"' && escape) { + escape = false; + continue; + } + escape = false; + + if (*p == '"') { + instring = !instring; + continue; + } + if (instring) { + continue; + } + + if (*p == ':') break; + if (*p == '\0') break; + } + + if (*s != '"') { + /* Check if we want to look up another string */ + char32_t l; + size_t len = Utf8Decode(&l, s); + bool lookup = (l == SCC_ENCODED); + if (lookup) s += len; + + param = std::strtoull(s, &p, 16); + + if (lookup) { + if (param >= TAB_SIZE_GAMESCRIPT) { + while (*p != '\0') p++; + str = p; + builder += "(invalid sub-StringID)"; + break; + } + param = MakeStringID(TEXT_TAB_GAMESCRIPT_START, param); + } + + sub_args.SetParam(i++, param); + } else { + s++; // skip the leading \" + sub_args.SetParam(i++, std::string(s, p - s - 1)); // also skip the trailing \". + } } + /* If we didn't error out, we can actually print the string. */ + if (*str != '\0') { + str = p; + GetStringWithArgs(builder, MakeStringID(TEXT_TAB_GAMESCRIPT_START, stringid), sub_args, true); + } + break; } - break; - } + + case SCC_NEWGRF_STRINL: { + StringID substr = Utf8Consume(&str); + str_stack.push(GetStringPtr(substr)); + break; + } - case SCC_CARGO_LONG: { // {CARGO_LONG} - /* First parameter is cargo type, second parameter is cargo count */ - CargoID cargo = args.GetNextParameter(); - if (IsValidCargoID(cargo) && cargo >= CargoSpec::GetArraySize()) break; + case SCC_NEWGRF_PRINT_WORD_STRING_ID: { + StringID substr = args.GetNextParameter(); + str_stack.push(GetStringPtr(substr)); + case_index = next_substr_case_index; + next_substr_case_index = 0; + break; + } - StringID cargo_str = !IsValidCargoID(cargo) ? STR_QUANTITY_N_A : CargoSpec::Get(cargo)->quantifier; - auto tmp_args = MakeParameters(args.GetNextParameter()); - GetStringWithArgs(builder, cargo_str, tmp_args); - break; - } + + case SCC_GENDER_LIST: { // {G 0 Der Die Das} + /* First read the meta data from the language file. */ + size_t offset = orig_offset + (byte)*str++; + int gender = 0; + if (!dry_run && args.GetTypeAtOffset(offset) != 0) { + /* Now we need to figure out what text to resolve, i.e. + * what do we need to draw? So get the actual raw string + * first using the control code to get said string. */ + char input[4 + 1]; + char *p = input + Utf8Encode(input, args.GetTypeAtOffset(offset)); + *p = '\0'; - case SCC_CARGO_LIST: { // {CARGO_LIST} - CargoTypes cmask = args.GetNextParameter(); - bool first = true; + /* The gender is stored at the start of the formatted string. */ + bool old_sgd = _scan_for_gender_data; + _scan_for_gender_data = true; + std::string buffer; + StringBuilder tmp_builder(buffer); + StringParameters tmp_params = args.GetRemainingParameters(offset); + FormatString(tmp_builder, input, tmp_params); + _scan_for_gender_data = old_sgd; + + /* And determine the 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 = (byte)s[0]; + } + str = ParseStringChoice(str, gender, builder); + break; + } - for (const auto &cs : _sorted_cargo_specs) { - if (!HasBit(cmask, cs->Index())) continue; - - if (first) { - first = false; + /* 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} + if (_scan_for_gender_data) { + builder.Utf8Encode(SCC_GENDER_INDEX); + builder += *str++; } else { - /* Add a comma if this is not the first item */ - builder += ", "; + str++; } + break; - GetStringWithArgs(builder, cs->name, args, next_substr_case_index, game_script); + case SCC_PLURAL_LIST: { // {P} + int plural_form = *str++; // contains the plural form for this string + size_t offset = orig_offset + (byte)*str++; + int64_t v = args.GetParam(offset); // contains the number that determines plural + str = ParseStringChoice(str, DeterminePluralForm(v, plural_form), builder); + break; + } + + case SCC_ARG_INDEX: { // Move argument pointer + args.SetOffset(orig_offset + (byte)*str++); + 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 = (byte)*str++; + break; + } + + case SCC_SWITCH_CASE: { // {Used to implement case switching} + /* <0x9E> + * Each LEN is printed using 2 bytes in big endian order. */ + uint num = (byte)*str++; + while (num) { + if ((byte)str[0] == case_index) { + /* Found the case, adjust str pointer and continue */ + str += 3; + break; + } + /* Otherwise skip to the next case */ + str += 3 + (str[1] << 8) + str[2]; + num--; + } + break; } - /* If first is still true then no cargo is accepted */ - if (first) GetStringWithArgs(builder, STR_JUST_NOTHING, args, next_substr_case_index, game_script); - - next_substr_case_index = 0; - break; - } - - case SCC_CURRENCY_SHORT: // {CURRENCY_SHORT} - FormatGenericCurrency(builder, _currency, args.GetNextParameter(), true); - break; - - case SCC_CURRENCY_LONG: // {CURRENCY_LONG} - FormatGenericCurrency(builder, _currency, args.GetNextParameter(), false); - break; + case SCC_REVISION: // {REV} + builder += _openttd_revision; + break; - case SCC_DATE_TINY: // {DATE_TINY} - FormatTinyOrISODate(builder, args.GetNextParameter(), STR_FORMAT_DATE_TINY); - break; - - case SCC_DATE_SHORT: // {DATE_SHORT} - FormatMonthAndYear(builder, args.GetNextParameter(), next_substr_case_index); - next_substr_case_index = 0; - break; + case SCC_RAW_STRING_POINTER: { // {RAW_STRING} + const char *raw_string = args.GetNextParameterString(); + /* raw_string can be nullptr. */ + if (raw_string == nullptr) { + builder += "(invalid RAW_STRING parameter)"; + break; + } + FormatString(builder, raw_string, args); + break; + } - case SCC_DATE_LONG: // {DATE_LONG} - FormatYmdString(builder, args.GetNextParameter(), next_substr_case_index); - next_substr_case_index = 0; - break; - - case SCC_DATE_ISO: // {DATE_ISO} - FormatTinyOrISODate(builder, args.GetNextParameter(), STR_FORMAT_DATE_ISO); - break; + case SCC_STRING: {// {STRING} + StringID string_id = args.GetNextParameter(); + if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break; + /* It's prohibited for the included string to consume any arguments. */ + StringParameters tmp_params(args, game_script ? args.GetDataLeft() : 0); + GetStringWithArgs(builder, string_id, tmp_params, next_substr_case_index, game_script); + next_substr_case_index = 0; + break; + } - case SCC_FORCE: { // {FORCE} - assert(_settings_game.locale.units_force < lengthof(_units_force)); - const auto &x = _units_force[_settings_game.locale.units_force]; - auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); - FormatString(builder, GetStringPtr(x.s), tmp_params); - break; - } + case SCC_STRING1: + case SCC_STRING2: + case SCC_STRING3: + case SCC_STRING4: + case SCC_STRING5: + case SCC_STRING6: + case SCC_STRING7: { // {STRING1..7} + /* Strings that consume arguments */ + StringID string_id = args.GetNextParameter(); + if (game_script && GetStringTab(string_id) != TEXT_TAB_GAMESCRIPT_START) break; + uint size = b - SCC_STRING1 + 1; + if (game_script && size > args.GetDataLeft()) { + builder += "(too many parameters)"; + } else { + StringParameters sub_args(args, game_script ? args.GetDataLeft() : size); + GetStringWithArgs(builder, string_id, sub_args, next_substr_case_index, game_script); + args.AdvanceOffset(size); + } + next_substr_case_index = 0; + break; + } - case SCC_HEIGHT: { // {HEIGHT} - assert(_settings_game.locale.units_height < lengthof(_units_height)); - const auto &x = _units_height[_settings_game.locale.units_height]; - auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); - FormatString(builder, GetStringPtr(x.s), tmp_params); - break; - } + case SCC_COMMA: // {COMMA} + FormatCommaNumber(builder, args.GetNextParameter()); + break; - case SCC_POWER: { // {POWER} - assert(_settings_game.locale.units_power < lengthof(_units_power)); - const auto &x = _units_power[_settings_game.locale.units_power]; - auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); - FormatString(builder, GetStringPtr(x.s), tmp_params); - break; - } + case SCC_DECIMAL: { // {DECIMAL} + int64_t number = args.GetNextParameter(); + int digits = args.GetNextParameter(); + FormatCommaNumber(builder, number, digits); + break; + } + + case SCC_NUM: // {NUM} + FormatNoCommaNumber(builder, args.GetNextParameter()); + break; - case SCC_POWER_TO_WEIGHT: { // {POWER_TO_WEIGHT} - auto setting = _settings_game.locale.units_power * 3u + _settings_game.locale.units_weight; - assert(setting < lengthof(_units_power_to_weight)); - const auto &x = _units_power_to_weight[setting]; - auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); - FormatString(builder, GetStringPtr(x.s), tmp_params); - break; - } + case SCC_ZEROFILL_NUM: { // {ZEROFILL_NUM} + int64_t num = args.GetNextParameter(); + FormatZerofillNumber(builder, num, args.GetNextParameter()); + break; + } + + case SCC_HEX: // {HEX} + FormatHexNumber(builder, args.GetNextParameter()); + break; + + case SCC_BYTES: // {BYTES} + FormatBytes(builder, args.GetNextParameter()); + break; + + case SCC_CARGO_TINY: { // {CARGO_TINY} + /* Tiny description of cargotypes. Layout: + * param 1: cargo type + * param 2: cargo count */ + CargoID cargo = args.GetNextParameter(); + if (cargo >= CargoSpec::GetArraySize()) break; - case SCC_VELOCITY: { // {VELOCITY} - int64_t arg = args.GetNextParameter(); - // Unpack vehicle type from packed argument to get desired units. - VehicleType vt = static_cast(GB(arg, 56, 8)); - byte units = GetVelocityUnits(vt); - assert(units < lengthof(_units_velocity)); - const auto &x = _units_velocity[units]; - auto tmp_params = MakeParameters(ConvertKmhishSpeedToDisplaySpeed(GB(arg, 0, 56), vt), x.decimal_places); - FormatString(builder, GetStringPtr(x.s), tmp_params); - break; - } + StringID cargo_str = CargoSpec::Get(cargo)->units_volume; + int64_t amount = 0; + switch (cargo_str) { + case STR_TONS: + amount = _units_weight[_settings_game.locale.units_weight].c.ToDisplay(args.GetNextParameter()); + break; + + case STR_LITERS: + amount = _units_volume[_settings_game.locale.units_volume].c.ToDisplay(args.GetNextParameter()); + break; + + default: { + amount = args.GetNextParameter(); + break; + } + } - case SCC_VOLUME_SHORT: { // {VOLUME_SHORT} - assert(_settings_game.locale.units_volume < lengthof(_units_volume)); - const auto &x = _units_volume[_settings_game.locale.units_volume]; - auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); - FormatString(builder, GetStringPtr(x.s), tmp_params); - break; - } + FormatCommaNumber(builder, amount); + break; + } - case SCC_VOLUME_LONG: { // {VOLUME_LONG} - assert(_settings_game.locale.units_volume < lengthof(_units_volume)); - const auto &x = _units_volume[_settings_game.locale.units_volume]; - auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); - FormatString(builder, GetStringPtr(x.l), tmp_params); - break; - } + case SCC_CARGO_SHORT: { // {CARGO_SHORT} + /* Short description of cargotypes. Layout: + * param 1: cargo type + * param 2: cargo count */ + CargoID cargo = args.GetNextParameter(); + if (cargo >= CargoSpec::GetArraySize()) break; - case SCC_WEIGHT_SHORT: { // {WEIGHT_SHORT} - assert(_settings_game.locale.units_weight < lengthof(_units_weight)); - const auto &x = _units_weight[_settings_game.locale.units_weight]; - auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); - FormatString(builder, GetStringPtr(x.s), tmp_params); - break; - } + StringID cargo_str = CargoSpec::Get(cargo)->units_volume; + switch (cargo_str) { + case STR_TONS: { + assert(_settings_game.locale.units_weight < lengthof(_units_weight)); + const auto &x = _units_weight[_settings_game.locale.units_weight]; + auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); + FormatString(builder, GetStringPtr(x.l), tmp_params); + break; + } - case SCC_WEIGHT_LONG: { // {WEIGHT_LONG} - assert(_settings_game.locale.units_weight < lengthof(_units_weight)); - const auto &x = _units_weight[_settings_game.locale.units_weight]; - auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); - FormatString(builder, GetStringPtr(x.l), tmp_params); - break; - } + case STR_LITERS: { + assert(_settings_game.locale.units_volume < lengthof(_units_volume)); + const auto &x = _units_volume[_settings_game.locale.units_volume]; + auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); + FormatString(builder, GetStringPtr(x.l), tmp_params); + break; + } - case SCC_COMPANY_NAME: { // {COMPANY} - const Company *c = Company::GetIfValid(args.GetNextParameter()); - if (c == nullptr) break; - - if (!c->name.empty()) { - auto tmp_params = MakeParameters(c->name); - GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); - } else { - auto tmp_params = MakeParameters(c->name_2); - GetStringWithArgs(builder, c->name_1, tmp_params); + default: { + auto tmp_params = MakeParameters(args.GetNextParameter()); + GetStringWithArgs(builder, cargo_str, tmp_params); + break; + } + } + break; } - break; - } + + case SCC_CARGO_LONG: { // {CARGO_LONG} + /* First parameter is cargo type, second parameter is cargo count */ + CargoID cargo = args.GetNextParameter(); + if (IsValidCargoID(cargo) && cargo >= CargoSpec::GetArraySize()) break; - case SCC_COMPANY_NUM: { // {COMPANY_NUM} - CompanyID company = args.GetNextParameter(); + StringID cargo_str = !IsValidCargoID(cargo) ? STR_QUANTITY_N_A : CargoSpec::Get(cargo)->quantifier; + auto tmp_args = MakeParameters(args.GetNextParameter()); + GetStringWithArgs(builder, cargo_str, tmp_args); + break; + } + + case SCC_CARGO_LIST: { // {CARGO_LIST} + CargoTypes cmask = args.GetNextParameter(); + bool first = true; - /* Nothing is added for AI or inactive companies */ - if (Company::IsValidHumanID(company)) { - auto tmp_params = MakeParameters(company + 1); - GetStringWithArgs(builder, STR_FORMAT_COMPANY_NUM, tmp_params); - } - break; - } + for (const auto &cs : _sorted_cargo_specs) { + if (!HasBit(cmask, cs->Index())) continue; - case SCC_DEPOT_NAME: { // {DEPOT} - VehicleType vt = args.GetNextParameter(); - if (vt == VEH_AIRCRAFT) { - auto tmp_params = MakeParameters(args.GetNextParameter()); - GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_AIRCRAFT, tmp_params); + if (first) { + first = false; + } else { + /* Add a comma if this is not the first item */ + builder += ", "; + } + + GetStringWithArgs(builder, cs->name, args, next_substr_case_index, game_script); + } + + /* If first is still true then no cargo is accepted */ + if (first) GetStringWithArgs(builder, STR_JUST_NOTHING, args, next_substr_case_index, game_script); + + next_substr_case_index = 0; break; } - const Depot *d = Depot::Get(args.GetNextParameter()); - if (!d->name.empty()) { - auto tmp_params = MakeParameters(d->name); - GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); - } else { - auto tmp_params = MakeParameters(d->town->index, d->town_cn + 1); - GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_TRAIN + 2 * vt + (d->town_cn == 0 ? 0 : 1), tmp_params); + case SCC_CURRENCY_SHORT: // {CURRENCY_SHORT} + FormatGenericCurrency(builder, _currency, args.GetNextParameter(), true); + break; + + case SCC_CURRENCY_LONG: // {CURRENCY_LONG} + FormatGenericCurrency(builder, _currency, args.GetNextParameter(), false); + break; + + case SCC_DATE_TINY: // {DATE_TINY} + FormatTinyOrISODate(builder, args.GetNextParameter(), STR_FORMAT_DATE_TINY); + break; + + case SCC_DATE_SHORT: // {DATE_SHORT} + FormatMonthAndYear(builder, args.GetNextParameter(), next_substr_case_index); + next_substr_case_index = 0; + break; + + case SCC_DATE_LONG: // {DATE_LONG} + FormatYmdString(builder, args.GetNextParameter(), next_substr_case_index); + next_substr_case_index = 0; + break; + + case SCC_DATE_ISO: // {DATE_ISO} + FormatTinyOrISODate(builder, args.GetNextParameter(), STR_FORMAT_DATE_ISO); + break; + + case SCC_FORCE: { // {FORCE} + assert(_settings_game.locale.units_force < lengthof(_units_force)); + const auto &x = _units_force[_settings_game.locale.units_force]; + auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); + FormatString(builder, GetStringPtr(x.s), tmp_params); + break; + } + + case SCC_HEIGHT: { // {HEIGHT} + assert(_settings_game.locale.units_height < lengthof(_units_height)); + const auto &x = _units_height[_settings_game.locale.units_height]; + auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); + FormatString(builder, GetStringPtr(x.s), tmp_params); + break; } - break; - } + + case SCC_POWER: { // {POWER} + assert(_settings_game.locale.units_power < lengthof(_units_power)); + const auto &x = _units_power[_settings_game.locale.units_power]; + auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); + FormatString(builder, GetStringPtr(x.s), tmp_params); + break; + } + + case SCC_POWER_TO_WEIGHT: { // {POWER_TO_WEIGHT} + auto setting = _settings_game.locale.units_power * 3u + _settings_game.locale.units_weight; + assert(setting < lengthof(_units_power_to_weight)); + const auto &x = _units_power_to_weight[setting]; + auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); + FormatString(builder, GetStringPtr(x.s), tmp_params); + break; + } - case SCC_ENGINE_NAME: { // {ENGINE} - int64_t arg = args.GetNextParameter(); - const Engine *e = Engine::GetIfValid(static_cast(arg)); - if (e == nullptr) break; + case SCC_VELOCITY: { // {VELOCITY} + int64_t arg = args.GetNextParameter(); + // Unpack vehicle type from packed argument to get desired units. + VehicleType vt = static_cast(GB(arg, 56, 8)); + byte units = GetVelocityUnits(vt); + assert(units < lengthof(_units_velocity)); + const auto &x = _units_velocity[units]; + auto tmp_params = MakeParameters(ConvertKmhishSpeedToDisplaySpeed(GB(arg, 0, 56), vt), x.decimal_places); + FormatString(builder, GetStringPtr(x.s), tmp_params); + break; + } - if (!e->name.empty() && e->IsEnabled()) { - auto tmp_params = MakeParameters(e->name); - GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); + case SCC_VOLUME_SHORT: { // {VOLUME_SHORT} + assert(_settings_game.locale.units_volume < lengthof(_units_volume)); + const auto &x = _units_volume[_settings_game.locale.units_volume]; + auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); + FormatString(builder, GetStringPtr(x.s), tmp_params); + break; + } + + case SCC_VOLUME_LONG: { // {VOLUME_LONG} + assert(_settings_game.locale.units_volume < lengthof(_units_volume)); + const auto &x = _units_volume[_settings_game.locale.units_volume]; + auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); + FormatString(builder, GetStringPtr(x.l), tmp_params); break; } - if (HasBit(e->info.callback_mask, CBM_VEHICLE_NAME)) { - uint16_t callback = GetVehicleCallback(CBID_VEHICLE_NAME, static_cast(arg >> 32), 0, e->index, nullptr); - /* Not calling ErrorUnknownCallbackResult due to being inside string processing. */ - if (callback != CALLBACK_FAILED && callback < 0x400) { - const GRFFile *grffile = e->GetGRF(); - assert(grffile != nullptr); + case SCC_WEIGHT_SHORT: { // {WEIGHT_SHORT} + assert(_settings_game.locale.units_weight < lengthof(_units_weight)); + const auto &x = _units_weight[_settings_game.locale.units_weight]; + auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); + FormatString(builder, GetStringPtr(x.s), tmp_params); + break; + } + + case SCC_WEIGHT_LONG: { // {WEIGHT_LONG} + assert(_settings_game.locale.units_weight < lengthof(_units_weight)); + const auto &x = _units_weight[_settings_game.locale.units_weight]; + auto tmp_params = MakeParameters(x.c.ToDisplay(args.GetNextParameter()), x.decimal_places); + FormatString(builder, GetStringPtr(x.l), tmp_params); + break; + } + + case SCC_COMPANY_NAME: { // {COMPANY} + const Company *c = Company::GetIfValid(args.GetNextParameter()); + if (c == nullptr) break; - StartTextRefStackUsage(grffile, 6); - ArrayStringParameters<6> tmp_params; - GetStringWithArgs(builder, GetGRFStringID(grffile->grfid, 0xD000 + callback), tmp_params); - StopTextRefStackUsage(); + if (!c->name.empty()) { + auto tmp_params = MakeParameters(c->name); + GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); + } else { + auto tmp_params = MakeParameters(c->name_2); + GetStringWithArgs(builder, c->name_1, tmp_params); + } + break; + } - break; + case SCC_COMPANY_NUM: { // {COMPANY_NUM} + CompanyID company = args.GetNextParameter(); + + /* Nothing is added for AI or inactive companies */ + if (Company::IsValidHumanID(company)) { + auto tmp_params = MakeParameters(company + 1); + GetStringWithArgs(builder, STR_FORMAT_COMPANY_NUM, tmp_params); } + break; } - auto tmp_params = ArrayStringParameters<0>(); - GetStringWithArgs(builder, e->info.string_id, tmp_params); - break; - } + case SCC_DEPOT_NAME: { // {DEPOT} + VehicleType vt = args.GetNextParameter(); + if (vt == VEH_AIRCRAFT) { + auto tmp_params = MakeParameters(args.GetNextParameter()); + GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_AIRCRAFT, tmp_params); + break; + } - case SCC_GROUP_NAME: { // {GROUP} - const Group *g = Group::GetIfValid(args.GetNextParameter()); - if (g == nullptr) break; + const Depot *d = Depot::Get(args.GetNextParameter()); + if (!d->name.empty()) { + auto tmp_params = MakeParameters(d->name); + GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); + } else { + auto tmp_params = MakeParameters(d->town->index, d->town_cn + 1); + GetStringWithArgs(builder, STR_FORMAT_DEPOT_NAME_TRAIN + 2 * vt + (d->town_cn == 0 ? 0 : 1), tmp_params); + } + break; + } - if (!g->name.empty()) { - auto tmp_params = MakeParameters(g->name); - GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); - } else { - auto tmp_params = MakeParameters(g->index); - GetStringWithArgs(builder, STR_FORMAT_GROUP_NAME, tmp_params); - } - break; - } + case SCC_ENGINE_NAME: { // {ENGINE} + int64_t arg = args.GetNextParameter(); + const Engine *e = Engine::GetIfValid(static_cast(arg)); + if (e == nullptr) break; + + if (!e->name.empty() && e->IsEnabled()) { + auto tmp_params = MakeParameters(e->name); + GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); + break; + } - case SCC_INDUSTRY_NAME: { // {INDUSTRY} - const Industry *i = Industry::GetIfValid(args.GetNextParameter()); - if (i == nullptr) break; + if (HasBit(e->info.callback_mask, CBM_VEHICLE_NAME)) { + uint16_t callback = GetVehicleCallback(CBID_VEHICLE_NAME, static_cast(arg >> 32), 0, e->index, nullptr); + /* Not calling ErrorUnknownCallbackResult due to being inside string processing. */ + if (callback != CALLBACK_FAILED && callback < 0x400) { + const GRFFile *grffile = e->GetGRF(); + assert(grffile != nullptr); - static bool use_cache = true; - if (use_cache) { // Use cached version if first call - AutoRestoreBackup cache_backup(use_cache, false); - builder += i->GetCachedName(); - } else if (_scan_for_gender_data) { - /* Gender is defined by the industry type. - * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */ + StartTextRefStackUsage(grffile, 6); + ArrayStringParameters<6> tmp_params; + GetStringWithArgs(builder, GetGRFStringID(grffile->grfid, 0xD000 + callback), tmp_params); + StopTextRefStackUsage(); + + break; + } + } + auto tmp_params = ArrayStringParameters<0>(); - FormatString(builder, GetStringPtr(GetIndustrySpec(i->type)->name), tmp_params, next_substr_case_index); - } else { - /* First print the town name and the industry type name. */ - auto tmp_params = MakeParameters(i->town->index, GetIndustrySpec(i->type)->name); - FormatString(builder, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), tmp_params, next_substr_case_index); - } - next_substr_case_index = 0; - break; - } - - case SCC_PRESIDENT_NAME: { // {PRESIDENT_NAME} - const Company *c = Company::GetIfValid(args.GetNextParameter()); - if (c == nullptr) break; - - if (!c->president_name.empty()) { - auto tmp_params = MakeParameters(c->president_name); - GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); - } else { - auto tmp_params = MakeParameters(c->president_name_2); - GetStringWithArgs(builder, c->president_name_1, tmp_params); - } - break; - } - - case SCC_STATION_NAME: { // {STATION} - StationID sid = args.GetNextParameter(); - const Station *st = Station::GetIfValid(sid); - - if (st == nullptr) { - /* The station doesn't exist anymore. The only place where we might - * be "drawing" an invalid station is in the case of cargo that is - * in transit. */ - auto tmp_params = ArrayStringParameters<0>(); - GetStringWithArgs(builder, STR_UNKNOWN_STATION, tmp_params); + GetStringWithArgs(builder, e->info.string_id, tmp_params); break; } - static bool use_cache = true; - if (use_cache) { // Use cached version if first call - AutoRestoreBackup cache_backup(use_cache, false); - builder += st->GetCachedName(); - } else if (!st->name.empty()) { - auto tmp_params = MakeParameters(st->name); - GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); - } else { - StringID string_id = st->string_id; - if (st->indtype != IT_INVALID) { - /* Special case where the industry provides the name for the station */ - const IndustrySpec *indsp = GetIndustrySpec(st->indtype); + case SCC_GROUP_NAME: { // {GROUP} + const Group *g = Group::GetIfValid(args.GetNextParameter()); + if (g == nullptr) break; + + if (!g->name.empty()) { + auto tmp_params = MakeParameters(g->name); + GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); + } else { + auto tmp_params = MakeParameters(g->index); + GetStringWithArgs(builder, STR_FORMAT_GROUP_NAME, tmp_params); + } + break; + } + + case SCC_INDUSTRY_NAME: { // {INDUSTRY} + const Industry *i = Industry::GetIfValid(args.GetNextParameter()); + if (i == nullptr) break; - /* Industry GRFs can change which might remove the station name and - * thus cause very strange things. Here we check for that before we - * actually set the station name. */ - if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) { - string_id = indsp->station_name; - } + static bool use_cache = true; + if (use_cache) { // Use cached version if first call + AutoRestoreBackup cache_backup(use_cache, false); + builder += i->GetCachedName(); + } else if (_scan_for_gender_data) { + /* Gender is defined by the industry type. + * STR_FORMAT_INDUSTRY_NAME may have the town first, so it would result in the gender of the town name */ + auto tmp_params = ArrayStringParameters<0>(); + FormatString(builder, GetStringPtr(GetIndustrySpec(i->type)->name), tmp_params, next_substr_case_index); + } else { + /* First print the town name and the industry type name. */ + auto tmp_params = MakeParameters(i->town->index, GetIndustrySpec(i->type)->name); + FormatString(builder, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), tmp_params, next_substr_case_index); + } + next_substr_case_index = 0; + break; + } + + case SCC_PRESIDENT_NAME: { // {PRESIDENT_NAME} + const Company *c = Company::GetIfValid(args.GetNextParameter()); + if (c == nullptr) break; + + if (!c->president_name.empty()) { + auto tmp_params = MakeParameters(c->president_name); + GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); + } else { + auto tmp_params = MakeParameters(c->president_name_2); + GetStringWithArgs(builder, c->president_name_1, tmp_params); + } + break; + } + + case SCC_STATION_NAME: { // {STATION} + StationID sid = args.GetNextParameter(); + const Station *st = Station::GetIfValid(sid); + + if (st == nullptr) { + /* The station doesn't exist anymore. The only place where we might + * be "drawing" an invalid station is in the case of cargo that is + * in transit. */ + auto tmp_params = ArrayStringParameters<0>(); + GetStringWithArgs(builder, STR_UNKNOWN_STATION, tmp_params); + break; } - auto tmp_params = MakeParameters(STR_TOWN_NAME, st->town->index, st->index); - GetStringWithArgs(builder, string_id, tmp_params); - } - break; - } - - case SCC_TOWN_NAME: { // {TOWN} - const Town *t = Town::GetIfValid(args.GetNextParameter()); - if (t == nullptr) break; + static bool use_cache = true; + if (use_cache) { // Use cached version if first call + AutoRestoreBackup cache_backup(use_cache, false); + builder += st->GetCachedName(); + } else if (!st->name.empty()) { + auto tmp_params = MakeParameters(st->name); + GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); + } else { + StringID string_id = st->string_id; + if (st->indtype != IT_INVALID) { + /* Special case where the industry provides the name for the station */ + const IndustrySpec *indsp = GetIndustrySpec(st->indtype); - static bool use_cache = true; - if (use_cache) { // Use cached version if first call - AutoRestoreBackup cache_backup(use_cache, false); - builder += t->GetCachedName(); - } else if (!t->name.empty()) { - auto tmp_params = MakeParameters(t->name); - GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); - } else { - GetTownName(builder, t); + /* Industry GRFs can change which might remove the station name and + * thus cause very strange things. Here we check for that before we + * actually set the station name. */ + if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) { + string_id = indsp->station_name; + } + } + + auto tmp_params = MakeParameters(STR_TOWN_NAME, st->town->index, st->index); + GetStringWithArgs(builder, string_id, tmp_params); + } + break; } - break; - } - case SCC_WAYPOINT_NAME: { // {WAYPOINT} - Waypoint *wp = Waypoint::GetIfValid(args.GetNextParameter()); - if (wp == nullptr) break; + case SCC_TOWN_NAME: { // {TOWN} + const Town *t = Town::GetIfValid(args.GetNextParameter()); + if (t == nullptr) break; - if (!wp->name.empty()) { - auto tmp_params = MakeParameters(wp->name); - GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); - } else { - auto tmp_params = MakeParameters(wp->town->index, wp->town_cn + 1); - StringID string_id = ((wp->string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME); - if (wp->town_cn != 0) string_id++; - GetStringWithArgs(builder, string_id, tmp_params); + static bool use_cache = true; + if (use_cache) { // Use cached version if first call + AutoRestoreBackup cache_backup(use_cache, false); + builder += t->GetCachedName(); + } else if (!t->name.empty()) { + auto tmp_params = MakeParameters(t->name); + GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); + } else { + GetTownName(builder, t); + } + break; } - break; - } + + case SCC_WAYPOINT_NAME: { // {WAYPOINT} + Waypoint *wp = Waypoint::GetIfValid(args.GetNextParameter()); + if (wp == nullptr) break; - case SCC_VEHICLE_NAME: { // {VEHICLE} - const Vehicle *v = Vehicle::GetIfValid(args.GetNextParameter()); - if (v == nullptr) break; + if (!wp->name.empty()) { + auto tmp_params = MakeParameters(wp->name); + GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); + } else { + auto tmp_params = MakeParameters(wp->town->index, wp->town_cn + 1); + StringID string_id = ((wp->string_id == STR_SV_STNAME_BUOY) ? STR_FORMAT_BUOY_NAME : STR_FORMAT_WAYPOINT_NAME); + if (wp->town_cn != 0) string_id++; + GetStringWithArgs(builder, string_id, tmp_params); + } + break; + } - if (!v->name.empty()) { - auto tmp_params = MakeParameters(v->name); - GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); - } else if (v->group_id != DEFAULT_GROUP) { - /* The vehicle has no name, but is member of a group, so print group name */ - auto tmp_params = MakeParameters(v->group_id, v->unitnumber); - GetStringWithArgs(builder, STR_FORMAT_GROUP_VEHICLE_NAME, tmp_params); - } else { - auto tmp_params = MakeParameters(v->unitnumber); + case SCC_VEHICLE_NAME: { // {VEHICLE} + const Vehicle *v = Vehicle::GetIfValid(args.GetNextParameter()); + if (v == nullptr) break; + + if (!v->name.empty()) { + auto tmp_params = MakeParameters(v->name); + GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); + } else if (v->group_id != DEFAULT_GROUP) { + /* The vehicle has no name, but is member of a group, so print group name */ + auto tmp_params = MakeParameters(v->group_id, v->unitnumber); + GetStringWithArgs(builder, STR_FORMAT_GROUP_VEHICLE_NAME, tmp_params); + } else { + auto tmp_params = MakeParameters(v->unitnumber); - StringID string_id; - switch (v->type) { - default: string_id = STR_INVALID_VEHICLE; break; - case VEH_TRAIN: string_id = STR_SV_TRAIN_NAME; break; - case VEH_ROAD: string_id = STR_SV_ROAD_VEHICLE_NAME; break; - case VEH_SHIP: string_id = STR_SV_SHIP_NAME; break; - case VEH_AIRCRAFT: string_id = STR_SV_AIRCRAFT_NAME; break; + StringID string_id; + switch (v->type) { + default: string_id = STR_INVALID_VEHICLE; break; + case VEH_TRAIN: string_id = STR_SV_TRAIN_NAME; break; + case VEH_ROAD: string_id = STR_SV_ROAD_VEHICLE_NAME; break; + case VEH_SHIP: string_id = STR_SV_SHIP_NAME; break; + case VEH_AIRCRAFT: string_id = STR_SV_AIRCRAFT_NAME; break; + } + + GetStringWithArgs(builder, string_id, tmp_params); } - - GetStringWithArgs(builder, string_id, tmp_params); + break; } - break; - } - case SCC_SIGN_NAME: { // {SIGN} - const Sign *si = Sign::GetIfValid(args.GetNextParameter()); - if (si == nullptr) break; + case SCC_SIGN_NAME: { // {SIGN} + const Sign *si = Sign::GetIfValid(args.GetNextParameter()); + if (si == nullptr) break; - if (!si->name.empty()) { - auto tmp_params = MakeParameters(si->name); - GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); - } else { - auto tmp_params = ArrayStringParameters<0>(); - GetStringWithArgs(builder, STR_DEFAULT_SIGN_NAME, tmp_params); + if (!si->name.empty()) { + auto tmp_params = MakeParameters(si->name); + GetStringWithArgs(builder, STR_JUST_RAW_STRING, tmp_params); + } else { + auto tmp_params = ArrayStringParameters<0>(); + GetStringWithArgs(builder, STR_DEFAULT_SIGN_NAME, tmp_params); + } + break; } - break; - } - case SCC_STATION_FEATURES: { // {STATIONFEATURES} - StationGetSpecialString(builder, args.GetNextParameter()); - break; - } + case SCC_STATION_FEATURES: { // {STATIONFEATURES} + StationGetSpecialString(builder, args.GetNextParameter()); + break; + } - case SCC_COLOUR: { // {COLOUR} - StringControlCode scc = (StringControlCode)(SCC_BLUE + args.GetNextParameter()); - if (IsInsideMM(scc, SCC_BLUE, SCC_COLOUR)) builder.Utf8Encode(scc); - break; + case SCC_COLOUR: { // {COLOUR} + StringControlCode scc = (StringControlCode)(SCC_BLUE + args.GetNextParameter()); + if (IsInsideMM(scc, SCC_BLUE, SCC_COLOUR)) builder.Utf8Encode(scc); + break; + } + + default: + builder.Utf8Encode(b); + break; } - - default: - builder.Utf8Encode(b); - break; + } catch (std::out_of_range &e) { + Debug(misc, 0, "FormatString: {}", e.what()); + builder += "(invalid parameter)"; } } } diff --git a/src/strings_internal.h b/src/strings_internal.h --- a/src/strings_internal.h +++ b/src/strings_internal.h @@ -95,7 +95,7 @@ public: T GetNextParameter() { auto ptr = GetNextParameterPointer(); - return static_cast(ptr == nullptr ? 0 : ptr->data); + return static_cast(ptr->data); } /** @@ -107,7 +107,6 @@ public: const char *GetNextParameterString() { auto ptr = GetNextParameterPointer(); - if (ptr == nullptr) return nullptr; return ptr->string != nullptr ? ptr->string->c_str() : ptr->string_view; }