diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 91ed1f38a3..9a836c503a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -273,6 +273,9 @@ add_files( newgrf_airporttiles.h newgrf_animation_base.h newgrf_animation_type.h + newgrf_badge.cpp + newgrf_badge.h + newgrf_badge_type.h newgrf_callbacks.h newgrf_canal.cpp newgrf_canal.h diff --git a/src/airport_gui.cpp b/src/airport_gui.cpp index 3c7811c35b..1617dce6d4 100644 --- a/src/airport_gui.cpp +++ b/src/airport_gui.cpp @@ -21,6 +21,7 @@ #include "company_base.h" #include "station_type.h" #include "newgrf_airport.h" +#include "newgrf_badge.h" #include "newgrf_callbacks.h" #include "dropdown_type.h" #include "dropdown_func.h" @@ -445,6 +446,7 @@ public: } /* strings such as 'Size' and 'Coverage Area' */ + r.top = DrawBadgeNameList(r, as->badges, GSF_AIRPORTS) + WidgetDimensions::scaled.vsep_normal; r.top = DrawStationCoverageAreaText(r, SCT_ALL, rad, false) + WidgetDimensions::scaled.vsep_normal; r.top = DrawStationCoverageAreaText(r, SCT_ALL, rad, true); } diff --git a/src/autoreplace_gui.cpp b/src/autoreplace_gui.cpp index c8f8e0cb96..3d0e15c821 100644 --- a/src/autoreplace_gui.cpp +++ b/src/autoreplace_gui.cpp @@ -10,6 +10,7 @@ #include "stdafx.h" #include "command_func.h" #include "vehicle_gui.h" +#include "newgrf_badge.h" #include "newgrf_engine.h" #include "rail.h" #include "road.h" @@ -89,6 +90,7 @@ class ReplaceVehicleWindow : public Window { RailType sel_railtype; ///< Type of rail tracks selected. #INVALID_RAILTYPE to show all. RoadType sel_roadtype; ///< Type of road selected. #INVALID_ROADTYPE to show all. Scrollbar *vscroll[2]; + GUIBadgeClasses badge_classes; /** * Figure out if an engine should be added to a list. @@ -276,6 +278,8 @@ public: this->sel_engine[1] = EngineID::Invalid(); this->show_hidden_engines = _engine_sort_show_hidden_engines[vehicletype]; + this->badge_classes = GUIBadgeClasses(static_cast(GSF_TRAINS + vehicletype)); + this->CreateNestedTree(); this->vscroll[0] = this->GetScrollbar(WID_RV_LEFT_SCROLLBAR); this->vscroll[1] = this->GetScrollbar(WID_RV_RIGHT_SCROLLBAR); @@ -463,7 +467,7 @@ public: int side = (widget == WID_RV_LEFT_MATRIX) ? 0 : 1; /* Do the actual drawing */ - DrawEngineList(this->window_number, r, this->engines[side], *this->vscroll[side], this->sel_engine[side], side == 0, this->sel_group); + DrawEngineList(this->window_number, r, this->engines[side], *this->vscroll[side], this->sel_engine[side], side == 0, this->sel_group, this->badge_classes); break; } } diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 81204d510e..9704f8ccf5 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -17,6 +17,7 @@ #include "command_func.h" #include "company_func.h" #include "vehicle_gui.h" +#include "newgrf_badge.h" #include "newgrf_engine.h" #include "newgrf_text.h" #include "group.h" @@ -974,6 +975,8 @@ int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number, if (refittable) y = ShowRefitOptionsList(left, right, y, engine_number); + y = DrawBadgeNameList({left, y, right, INT16_MAX}, e->badges, static_cast(GSF_TRAINS + e->type)); + /* Additional text from NewGRF */ y = ShowAdditionalText(left, right, y, engine_number); @@ -988,6 +991,11 @@ int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number, return y; } +static void DrawEngineBadgeColumn(const Rect &r, int column_group, const GUIBadgeClasses &badge_classes, const Engine *e, PaletteID remap) +{ + DrawBadgeColumn(r, column_group, badge_classes, e->badges, static_cast(GSF_TRAINS + e->type), e->info.base_intro, remap); +} + /** * Engine drawing loop * @param type Type of vehicle (VEH_*) @@ -998,9 +1006,9 @@ int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number, * @param show_count Whether to show the amount of engines or not * @param selected_group the group to list the engines of */ -void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_list, const Scrollbar &sb, EngineID selected_id, bool show_count, GroupID selected_group) +void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_list, const Scrollbar &sb, EngineID selected_id, bool show_count, GroupID selected_group, const GUIBadgeClasses &badge_classes) { - static const int sprite_y_offsets[] = { -1, -1, -2, -2 }; + static const std::array sprite_y_offsets = { 0, 0, -1, -1 }; auto [first, last] = sb.GetVisibleRangeIterators(eng_list); @@ -1012,7 +1020,9 @@ void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_li int circle_width = std::max(GetScaledSpriteSize(SPR_CIRCLE_FOLDED).width, GetScaledSpriteSize(SPR_CIRCLE_UNFOLDED).width); int linecolour = GetColourGradient(COLOUR_ORANGE, SHADE_NORMAL); - Rect ir = r.WithHeight(step_size).Shrink(WidgetDimensions::scaled.matrix); + auto badge_column_widths = badge_classes.GetColumnWidths(); + + Rect ir = r.WithHeight(step_size).Shrink(WidgetDimensions::scaled.matrix, RectPadding::zero); int sprite_y_offset = ScaleSpriteTrad(sprite_y_offsets[type]) + ir.Height() / 2; Dimension replace_icon = {0, 0}; @@ -1030,44 +1040,88 @@ void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_li count_width = GetStringBoundingBox(STR_JUST_COMMA, FS_SMALL).width; } - Rect tr = ir.Indent(circle_width + WidgetDimensions::scaled.hsep_normal + sprite_width + WidgetDimensions::scaled.hsep_wide, rtl); // Name position - Rect cr = tr.Indent(replace_icon.width + WidgetDimensions::scaled.hsep_wide, !rtl).WithWidth(count_width, !rtl); // Count position - Rect rr = tr.WithWidth(replace_icon.width, !rtl); // Replace icon position - if (show_count) tr = tr.Indent(count_width + WidgetDimensions::scaled.hsep_normal + replace_icon.width + WidgetDimensions::scaled.hsep_wide, !rtl); - - int normal_text_y_offset = (ir.Height() - GetCharacterHeight(FS_NORMAL)) / 2; - int small_text_y_offset = ir.Height() - GetCharacterHeight(FS_SMALL); - int replace_icon_y_offset = (ir.Height() - replace_icon.height) / 2; + const int text_row_height = ir.Shrink(WidgetDimensions::scaled.matrix).Height(); + const int normal_text_y_offset = (text_row_height - GetCharacterHeight(FS_NORMAL)) / 2; + const int small_text_y_offset = text_row_height - GetCharacterHeight(FS_SMALL); const int offset = (rtl ? -circle_width : circle_width) / 2; const int level_width = rtl ? -WidgetDimensions::scaled.hsep_indent : WidgetDimensions::scaled.hsep_indent; - int y = ir.top; for (auto it = first; it != last; ++it) { const auto &item = *it; + const Engine *e = Engine::Get(item.engine_id); + uint indent = item.indent * WidgetDimensions::scaled.hsep_indent; bool has_variants = item.flags.Test(EngineDisplayFlag::HasVariants); bool is_folded = item.flags.Test(EngineDisplayFlag::IsFolded); bool shaded = item.flags.Test(EngineDisplayFlag::Shaded); + Rect textr = ir.Shrink(WidgetDimensions::scaled.matrix); + Rect tr = ir.Indent(indent, rtl); + if (item.indent > 0) { /* Draw tree continuation lines. */ int tx = (rtl ? ir.right : ir.left) + offset; - int ty = y - WidgetDimensions::scaled.matrix.top; for (uint lvl = 1; lvl <= item.indent; ++lvl) { - if (HasBit(item.level_mask, lvl)) GfxDrawLine(tx, ty, tx, ty + step_size - 1, linecolour, WidgetDimensions::scaled.fullbevel.top); + if (HasBit(item.level_mask, lvl)) GfxDrawLine(tx, ir.top, tx, ir.bottom, linecolour, WidgetDimensions::scaled.fullbevel.top); if (lvl < item.indent) tx += level_width; } /* Draw our node in the tree. */ - int ycentre = y + normal_text_y_offset + GetCharacterHeight(FS_NORMAL) / 2 - 1; - if (!HasBit(item.level_mask, item.indent)) GfxDrawLine(tx, ty, tx, ycentre, linecolour, WidgetDimensions::scaled.fullbevel.top); + int ycentre = CenterBounds(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); } + if (has_variants) { + Rect fr = tr.WithWidth(circle_width, rtl); + DrawSpriteIgnorePadding(is_folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED, PAL_NONE, {fr.left, textr.top, fr.right, textr.bottom}, SA_CENTER); + } + + tr = tr.Indent(circle_width + WidgetDimensions::scaled.hsep_normal, rtl); + /* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */ const uint num_engines = GetGroupNumEngines(_local_company, selected_group, item.engine_id); + const PaletteID pal = (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(item.engine_id, _local_company); + + if (badge_column_widths.size() >= 1 && badge_column_widths[0] > 0) { + Rect br = tr.WithWidth(badge_column_widths[0], rtl); + DrawEngineBadgeColumn(br, 0, badge_classes, e, pal); + tr = tr.Indent(badge_column_widths[0], rtl); + } + + int sprite_x = tr.WithWidth(sprite_width, rtl).left + sprite_left; + DrawVehicleEngine(r.left, r.right, sprite_x, tr.top + sprite_y_offset, item.engine_id, pal, EIT_PURCHASE); + + tr = tr.Indent(sprite_width + WidgetDimensions::scaled.hsep_wide, rtl); + + if (badge_column_widths.size() >= 2 && badge_column_widths[1] > 0) { + Rect br = tr.WithWidth(badge_column_widths[1], rtl); + DrawEngineBadgeColumn(br, 1, badge_classes, e, pal); + tr = tr.Indent(badge_column_widths[1], rtl); + } + + if (show_count) { + /* Rect for replace-protection icon. */ + Rect rr = tr.WithWidth(replace_icon.width, !rtl); + tr = tr.Indent(replace_icon.width + WidgetDimensions::scaled.hsep_normal, !rtl); + /* Rect for engine type count text. */ + Rect cr = tr.WithWidth(count_width, !rtl); + tr = tr.Indent(count_width + WidgetDimensions::scaled.hsep_normal, !rtl); + + SetDParam(0, num_engines); + DrawString(cr.left, cr.right, textr.top + small_text_y_offset, STR_JUST_COMMA, TC_BLACK, SA_RIGHT | SA_FORCE, false, FS_SMALL); + + if (EngineHasReplacementForCompany(Company::Get(_local_company), item.engine_id, selected_group)) { + DrawSpriteIgnorePadding(SPR_GROUP_REPLACE_ACTIVE, num_engines == 0 ? PALETTE_CRASH : PAL_NONE, rr, SA_CENTER); + } + } + + if (badge_column_widths.size() >= 3 && badge_column_widths[2] > 0) { + Rect br = tr.WithWidth(badge_column_widths[2], !rtl).Indent(WidgetDimensions::scaled.hsep_wide, rtl); + DrawEngineBadgeColumn(br, 2, badge_classes, e, pal); + tr = tr.Indent(badge_column_widths[2], !rtl); + } - const Engine *e = Engine::Get(item.engine_id); bool hidden = e->company_hidden.Test(_local_company); StringID str = hidden ? STR_HIDDEN_ENGINE_NAME : STR_ENGINE_NAME; TextColour tc = (item.engine_id == selected_id) ? TC_WHITE : ((hidden | shaded) ? (TC_GREY | TC_FORCED | TC_NO_SHADE) : TC_BLACK); @@ -1078,20 +1132,9 @@ void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_li } else { SetDParam(0, PackEngineNameDParam(item.engine_id, EngineNameContext::PurchaseList, item.indent)); } - Rect itr = tr.Indent(indent, rtl); - DrawString(itr.left, itr.right, y + normal_text_y_offset, str, tc); - int sprite_x = ir.Indent(indent + circle_width + WidgetDimensions::scaled.hsep_normal, rtl).WithWidth(sprite_width, rtl).left + sprite_left; - DrawVehicleEngine(r.left, r.right, sprite_x, y + sprite_y_offset, item.engine_id, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(item.engine_id, _local_company), EIT_PURCHASE); - if (show_count) { - SetDParam(0, num_engines); - DrawString(cr.left, cr.right, y + small_text_y_offset, STR_JUST_COMMA, TC_BLACK, SA_RIGHT | SA_FORCE, false, FS_SMALL); - if (EngineHasReplacementForCompany(Company::Get(_local_company), item.engine_id, selected_group)) DrawSprite(SPR_GROUP_REPLACE_ACTIVE, num_engines == 0 ? PALETTE_CRASH : PAL_NONE, rr.left, y + replace_icon_y_offset); - } - if (has_variants) { - Rect fr = ir.Indent(indent, rtl).WithWidth(circle_width, rtl); - DrawSpriteIgnorePadding(is_folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED, PAL_NONE, {fr.left, y, fr.right, y + ir.Height() - 1}, SA_CENTER); - } - y += step_size; + DrawString(tr.left, tr.right, textr.top + normal_text_y_offset, str, tc); + + ir = ir.Translate(0, step_size); } } @@ -1179,6 +1222,7 @@ struct BuildVehicleWindow : Window { int details_height; ///< Minimal needed height of the details panels, in text lines (found so far). Scrollbar *vscroll; TestedEngineDetails te; ///< Tested cost and capacity after refit. + GUIBadgeClasses badge_classes; StringFilter string_filter; ///< Filter for vehicle name QueryString vehicle_editbox; ///< Filter editbox @@ -1197,6 +1241,11 @@ struct BuildVehicleWindow : Window { } } + void BuildBadgeClasses() + { + this->badge_classes = GUIBadgeClasses(static_cast(GSF_TRAINS + this->vehicle_type)); + } + BuildVehicleWindow(WindowDesc &desc, TileIndex tile, VehicleType type) : Window(desc), vehicle_editbox(MAX_LENGTH_VEHICLE_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_VEHICLE_NAME_CHARS) { this->vehicle_type = type; @@ -1209,6 +1258,8 @@ struct BuildVehicleWindow : Window { this->descending_sort_order = _engine_sort_last_order[type]; this->show_hidden_engines = _engine_sort_show_hidden_engines[type]; + this->BuildBadgeClasses(); + this->UpdateFilterByTile(); this->CreateNestedTree(); @@ -1764,7 +1815,7 @@ struct BuildVehicleWindow : Window { case WID_BV_LIST: resize.height = GetEngineListHeight(this->vehicle_type); size.height = 3 * resize.height; - size.width = std::max(size.width, GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_left + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_right + 165) + padding.width; + size.width = std::max(size.width, this->badge_classes.GetTotalColumnsWidth() + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_left + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_right + 165) + padding.width; break; case WID_BV_PANEL: @@ -1810,7 +1861,8 @@ struct BuildVehicleWindow : Window { *this->vscroll, this->sel_engine, false, - DEFAULT_GROUP + DEFAULT_GROUP, + this->badge_classes ); break; diff --git a/src/engine_base.h b/src/engine_base.h index 61b40bc6d2..7f9857b404 100644 --- a/src/engine_base.h +++ b/src/engine_base.h @@ -82,6 +82,7 @@ struct Engine : EnginePool::PoolItem<&_engine_pool> { */ VariableGRFFileProps grf_prop; std::vector overrides; + std::vector badges; Engine() {} Engine(VehicleType type, uint16_t local_id); diff --git a/src/engine_gui.h b/src/engine_gui.h index b536f400ab..5eb9816b09 100644 --- a/src/engine_gui.h +++ b/src/engine_gui.h @@ -12,6 +12,7 @@ #include "engine_type.h" #include "group_type.h" +#include "newgrf_badge.h" #include "sortlist_type.h" #include "gfx_type.h" #include "vehicle_type.h" @@ -52,7 +53,7 @@ extern EngList_SortTypeFunction * const _engine_sort_functions[][11]; /* Functions in build_vehicle_gui.cpp */ uint GetEngineListHeight(VehicleType type); void DisplayVehicleSortDropDown(Window *w, VehicleType vehicle_type, int selected, WidgetID button); -void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_list, const Scrollbar &sb, EngineID selected_id, bool show_count, GroupID selected_group); +void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_list, const Scrollbar &sb, EngineID selected_id, bool show_count, GroupID selected_group, const GUIBadgeClasses &badge_classes); void GUIEngineListAddChildren(GUIEngineList &dst, const GUIEngineList &src, EngineID parent = EngineID::Invalid(), uint8_t indent = 0); #endif /* ENGINE_GUI_H */ diff --git a/src/engine_type.h b/src/engine_type.h index 145d34497e..3648f39a6d 100644 --- a/src/engine_type.h +++ b/src/engine_type.h @@ -19,6 +19,7 @@ #include "timer/timer_game_calendar.h" #include "sound_type.h" #include "strings_type.h" +#include "newgrf_badge_type.h" /** Unique identification number of an engine. */ using EngineID = PoolID; diff --git a/src/house.h b/src/house.h index 3a799b0865..18439ec58f 100644 --- a/src/house.h +++ b/src/house.h @@ -14,6 +14,7 @@ #include "timer/timer_game_calendar.h" #include "house_type.h" #include "newgrf_animation_type.h" +#include "newgrf_badge_type.h" #include "newgrf_callbacks.h" #include "newgrf_commons.h" @@ -115,6 +116,7 @@ struct HouseSpec { uint8_t processing_time; ///< Periodic refresh multiplier uint8_t minimum_life; ///< The minimum number of years this house will survive before the town rebuilds it CargoTypes watched_cargoes; ///< Cargo types watched for acceptance. + std::vector badges; CargoLabel accepts_cargo_label[HOUSE_ORIGINAL_NUM_ACCEPTS]; ///< input landscape cargo slots diff --git a/src/industry_gui.cpp b/src/industry_gui.cpp index 47934a62d8..165a3f4e57 100644 --- a/src/industry_gui.cpp +++ b/src/industry_gui.cpp @@ -19,6 +19,7 @@ #include "industry.h" #include "town.h" #include "cheat_type.h" +#include "newgrf_badge.h" #include "newgrf_industries.h" #include "newgrf_text.h" #include "newgrf_debug.h" @@ -579,6 +580,8 @@ public: cargostring = this->MakeCargoListString(indsp->produced_cargo, cargo_suffix, STR_INDUSTRY_VIEW_PRODUCES_N_CARGO); ir.top = DrawStringMultiLine(ir, cargostring); + ir.top = DrawBadgeNameList(ir, indsp->badges, GSF_INDUSTRIES); + /* Get the additional purchase info text, if it has not already been queried. */ if (indsp->callback_mask.Test(IndustryCallbackMask::FundMoreText)) { uint16_t callback_res = GetIndustryCallback(CBID_INDUSTRY_FUND_MORE_TEXT, 0, 0, nullptr, this->selected_type, INVALID_TILE); diff --git a/src/industrytype.h b/src/industrytype.h index a7a39afc2f..4283fb4f1c 100644 --- a/src/industrytype.h +++ b/src/industrytype.h @@ -16,6 +16,7 @@ #include "landscape_type.h" #include "cargo_type.h" #include "newgrf_animation_type.h" +#include "newgrf_badge_type.h" #include "newgrf_callbacks.h" #include "newgrf_commons.h" @@ -132,6 +133,7 @@ struct IndustrySpec { bool enabled; ///< entity still available (by default true).newgrf can disable it, though GRFFileProps grf_prop; ///< properties related to the grf file std::vector random_sounds; ///< Random sounds; + std::vector badges; std::array, INDUSTRY_ORIGINAL_NUM_OUTPUTS> produced_cargo_label; ///< Cargo labels of produced cargo for default industries. std::array, INDUSTRY_ORIGINAL_NUM_INPUTS> accepts_cargo_label; ///< Cargo labels of accepted cargo for default industries. @@ -164,6 +166,7 @@ struct IndustryTileSpec { IndustryTileSpecialFlags special_flags; ///< Bitmask of extra flags used by the tile bool enabled; ///< entity still available (by default true).newgrf can disable it, though GRFFileProps grf_prop; ///< properties related to the grf file + std::vector badges; std::array, INDUSTRY_ORIGINAL_NUM_INPUTS> accepts_cargo_label; ///< Cargo labels of accepted cargo for default industry tiles. }; diff --git a/src/lang/english.txt b/src/lang/english.txt index fb87508540..1c475cfc8c 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5925,3 +5925,5 @@ STR_PLANE :{BLACK}{PLANE} STR_SHIP :{BLACK}{SHIP} STR_TOOLBAR_RAILTYPE_VELOCITY :{STRING} ({VELOCITY}) + +STR_BADGE_NAME_LIST :{STRING}: {GOLD}{RAW_STRING} diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 151b447de6..34a48170f8 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -22,6 +22,8 @@ #include "fontcache.h" #include "currency.h" #include "landscape.h" +#include "newgrf_badge.h" +#include "newgrf_badge_type.h" #include "newgrf_cargo.h" #include "newgrf_house.h" #include "newgrf_sound.h" @@ -1049,6 +1051,50 @@ static ChangeInfoResult CommonVehicleChangeInfo(EngineInfo *ei, int prop, ByteRe return CIR_SUCCESS; } +/** + * Skip a list of badges. + * @param buf Buffer reader containing list of badges to skip. + */ +static void SkipBadgeList(ByteReader &buf) +{ + uint16_t count = buf.ReadWord(); + while (count-- > 0) { + buf.ReadWord(); + } +} + +/** + * Read a list of badges. + * @param buf Buffer reader containing list of badges to read. + * @param feature The feature of the badge list. + * @returns list of badges. + */ +static std::vector ReadBadgeList(ByteReader &buf, GrfSpecFeature feature) +{ + uint16_t count = buf.ReadWord(); + + std::vector badges; + badges.reserve(count); + + while (count-- > 0) { + uint16_t local_index = buf.ReadWord(); + if (local_index >= std::size(_cur.grffile->badge_list)) { + GrfMsg(1, "ReadBadgeList: Badge label {} out of range (max {}), skipping.", local_index, std::size(_cur.grffile->badge_list) - 1); + continue; + } + + BadgeID index = _cur.grffile->badge_list[local_index]; + + /* Is badge already present? */ + if (std::ranges::find(badges, index) != std::end(badges)) continue; + + badges.push_back(index); + MarkBadgeSeen(index, feature); + } + + return badges; +} + /** * Define properties for rail vehicles * @param first Local ID of the first vehicle. @@ -1354,6 +1400,10 @@ static ChangeInfoResult RailVehicleChangeInfo(uint first, uint last, int prop, B _gted[e->index].cargo_allowed_required = CargoClasses{buf.ReadWord()}; break; + case 0x33: // Badge list + e->badges = ReadBadgeList(buf, GSF_TRAINS); + break; + default: ret = CommonVehicleChangeInfo(ei, prop, buf); break; @@ -1565,6 +1615,10 @@ static ChangeInfoResult RoadVehicleChangeInfo(uint first, uint last, int prop, B _gted[e->index].cargo_allowed_required = CargoClasses{buf.ReadWord()}; break; + case 0x2A: // Badge list + e->badges = ReadBadgeList(buf, GSF_ROADVEHICLES); + break; + default: ret = CommonVehicleChangeInfo(ei, prop, buf); break; @@ -1762,6 +1816,10 @@ static ChangeInfoResult ShipVehicleChangeInfo(uint first, uint last, int prop, B _gted[e->index].cargo_allowed_required = CargoClasses{buf.ReadWord()}; break; + case 0x26: // Badge list + e->badges = ReadBadgeList(buf, GSF_SHIPS); + break; + default: ret = CommonVehicleChangeInfo(ei, prop, buf); break; @@ -1937,6 +1995,10 @@ static ChangeInfoResult AircraftVehicleChangeInfo(uint first, uint last, int pro _gted[e->index].cargo_allowed_required = CargoClasses{buf.ReadWord()}; break; + case 0x24: // Badge list + e->badges = ReadBadgeList(buf, GSF_AIRCRAFT); + break; + default: ret = CommonVehicleChangeInfo(ei, prop, buf); break; @@ -2217,6 +2279,10 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR break; } + case 0x1F: // Badge list + statspec->badges = ReadBadgeList(buf, GSF_STATIONS); + break; + default: ret = CIR_UNKNOWN; break; @@ -2698,6 +2764,10 @@ static ChangeInfoResult TownHouseChangeInfo(uint first, uint last, int prop, Byt break; } + case 0x24: // Badge list + housespec->badges = ReadBadgeList(buf, GSF_HOUSES); + break; + default: ret = CIR_UNKNOWN; break; @@ -2760,6 +2830,23 @@ static ChangeInfoResult LoadTranslationTable(uint first, uint last, ByteReader & return CIR_SUCCESS; } +static ChangeInfoResult LoadBadgeTranslationTable(uint first, uint last, ByteReader &buf, std::vector &translation_table, const char *name) +{ + if (first != 0 && first != std::size(translation_table)) { + GrfMsg(1, "LoadBadgeTranslationTable: {} translation table must start at zero or {}", name, std::size(translation_table)); + return CIR_INVALID_ID; + } + + if (first == 0) translation_table.clear(); + translation_table.reserve(last); + for (uint id = first; id < last; ++id) { + std::string_view label = buf.ReadString(); + translation_table.push_back(GetOrCreateBadge(label).index); + } + + return CIR_SUCCESS; +} + /** * Helper to read a DWord worth of bytes from the reader * and to return it as a valid string. @@ -2797,6 +2884,9 @@ static ChangeInfoResult GlobalVarChangeInfo(uint first, uint last, int prop, Byt case 0x17: // Tram type translation table; loading during both reservation and activation stage (in case it is selected depending on defined tramtypes) return LoadTranslationTable(first, last, buf, [](GRFFile &grf) -> std::vector & { return grf.tramtype_list; }, "Tram type"); + case 0x18: // Badge translation table + return LoadBadgeTranslationTable(first, last, buf, _cur.grffile->badge_list, "Badge"); + default: break; } @@ -3020,6 +3110,9 @@ static ChangeInfoResult GlobalVarReserveInfo(uint first, uint last, int prop, By case 0x17: // Tram type translation table; loading during both reservation and activation stage (in case it is selected depending on defined tramtypes) return LoadTranslationTable(first, last, buf, [](GRFFile &grf) -> std::vector & { return grf.tramtype_list; }, "Tram type"); + case 0x18: // Badge translation table + return LoadBadgeTranslationTable(first, last, buf, _cur.grffile->badge_list, "Badge"); + default: break; } @@ -3455,6 +3548,10 @@ static ChangeInfoResult IndustrytilesChangeInfo(uint first, uint last, int prop, break; } + case 0x14: // Badge list + tsp->badges = ReadBadgeList(buf, GSF_INDUSTRYTILES); + break; + default: ret = CIR_UNKNOWN; break; @@ -3548,6 +3645,10 @@ static ChangeInfoResult IgnoreIndustryProperty(int prop, ByteReader &buf) break; } + case 0x29: // Badge list + SkipBadgeList(buf); + break; + default: ret = CIR_UNKNOWN; break; @@ -3951,6 +4052,10 @@ static ChangeInfoResult IndustriesChangeInfo(uint first, uint last, int prop, By break; } + case 0x29: // Badge list + indsp->badges = ReadBadgeList(buf, GSF_INDUSTRIES); + break; + default: ret = CIR_UNKNOWN; break; @@ -4106,6 +4211,10 @@ static ChangeInfoResult AirportChangeInfo(uint first, uint last, int prop, ByteR as->maintenance_cost = buf.ReadWord(); break; + case 0x12: // Badge list + as->badges = ReadBadgeList(buf, GSF_AIRPORTS); + break; + default: ret = CIR_UNKNOWN; break; @@ -4152,6 +4261,10 @@ static ChangeInfoResult IgnoreObjectProperty(uint prop, ByteReader &buf) buf.ReadDWord(); break; + case 0x19: // Badge list + SkipBadgeList(buf); + break; + default: ret = CIR_UNKNOWN; break; @@ -4281,6 +4394,10 @@ static ChangeInfoResult ObjectChangeInfo(uint first, uint last, int prop, ByteRe spec->generate_amount = buf.ReadByte(); break; + case 0x19: // Badge list + spec->badges = ReadBadgeList(buf, GSF_OBJECTS); + break; + default: ret = CIR_UNKNOWN; break; @@ -4419,6 +4536,10 @@ static ChangeInfoResult RailTypeChangeInfo(uint first, uint last, int prop, Byte for (int j = buf.ReadByte(); j != 0; j--) buf.ReadDWord(); break; + case 0x1E: // Badge list + rti->badges = ReadBadgeList(buf, GSF_RAILTYPES); + break; + default: ret = CIR_UNKNOWN; break; @@ -4499,6 +4620,10 @@ static ChangeInfoResult RailTypeReserveInfo(uint first, uint last, int prop, Byt buf.ReadDWord(); break; + case 0x1E: // Badge list + SkipBadgeList(buf); + break; + default: ret = CIR_UNKNOWN; break; @@ -4624,6 +4749,10 @@ static ChangeInfoResult RoadTypeChangeInfo(uint first, uint last, int prop, Byte for (int j = buf.ReadByte(); j != 0; j--) buf.ReadDWord(); break; + case 0x1E: // Badge list + rti->badges = ReadBadgeList(buf, GSF_ROADTYPES); + break; + default: ret = CIR_UNKNOWN; break; @@ -4713,6 +4842,10 @@ static ChangeInfoResult RoadTypeReserveInfo(uint first, uint last, int prop, Byt buf.ReadDWord(); break; + case 0x1E: // Badge list + SkipBadgeList(buf); + break; + default: ret = CIR_UNKNOWN; break; @@ -4808,6 +4941,10 @@ static ChangeInfoResult AirportTilesChangeInfo(uint first, uint last, int prop, tsp->animation.triggers = buf.ReadByte(); break; + case 0x12: // Badge list + tsp->badges = ReadBadgeList(buf, GSF_TRAMTYPES); + break; + default: ret = CIR_UNKNOWN; break; @@ -4849,6 +4986,10 @@ static ChangeInfoResult IgnoreRoadStopProperty(uint prop, ByteReader &buf) buf.ReadDWord(); break; + case 0x16: // Badge list + SkipBadgeList(buf); + break; + default: ret = CIR_UNKNOWN; break; @@ -4857,6 +4998,45 @@ static ChangeInfoResult IgnoreRoadStopProperty(uint prop, ByteReader &buf) return ret; } +static ChangeInfoResult BadgeChangeInfo(uint first, uint last, int prop, ByteReader &buf) +{ + ChangeInfoResult ret = CIR_SUCCESS; + + if (last >= UINT16_MAX) { + GrfMsg(1, "BadgeChangeInfo: Tag {} is invalid, max {}, ignoring", last, UINT16_MAX - 1); + return CIR_INVALID_ID; + } + + for (uint id = first; id < last; ++id) { + auto it = _cur.grffile->badge_map.find(id); + if (prop != 0x08 && it == std::end(_cur.grffile->badge_map)) { + GrfMsg(1, "BadgeChangeInfo: Attempt to modify undefined tag {}, ignoring", id); + return CIR_INVALID_ID; + } + + Badge *badge = nullptr; + if (prop != 0x08) badge = GetBadge(it->second); + + switch (prop) { + case 0x08: { // Label + std::string_view label = buf.ReadString(); + _cur.grffile->badge_map[id] = GetOrCreateBadge(label).index; + break; + } + + case 0x09: // Flags + badge->flags = static_cast(buf.ReadDWord()); + break; + + default: + ret = CIR_UNKNOWN; + break; + } + } + + return ret; +} + static ChangeInfoResult RoadStopChangeInfo(uint first, uint last, int prop, ByteReader &buf) { ChangeInfoResult ret = CIR_SUCCESS; @@ -4935,6 +5115,10 @@ static ChangeInfoResult RoadStopChangeInfo(uint first, uint last, int prop, Byte rs->clear_cost_multiplier = buf.ReadByte(); break; + case 0x16: // Badge list + rs->badges = ReadBadgeList(buf, GSF_ROADSTOPS); + break; + default: ret = CIR_UNKNOWN; break; @@ -5009,6 +5193,7 @@ static void FeatureChangeInfo(ByteReader &buf) /* GSF_ROADTYPES */ RoadTypeChangeInfo, /* GSF_TRAMTYPES */ TramTypeChangeInfo, /* GSF_ROADSTOPS */ RoadStopChangeInfo, + /* GSF_BADGES */ BadgeChangeInfo, }; static_assert(GSF_END == std::size(handler)); @@ -5439,6 +5624,7 @@ static void NewSpriteGroup(ByteReader &buf) case GSF_RAILTYPES: case GSF_ROADTYPES: case GSF_TRAMTYPES: + case GSF_BADGES: { uint8_t num_loaded = type; uint8_t num_loading = buf.ReadByte(); @@ -6211,6 +6397,56 @@ static void RoadStopMapSpriteGroup(ByteReader &buf, uint8_t idcount) } } +static void BadgeMapSpriteGroup(ByteReader &buf, uint8_t idcount) +{ + if (_cur.grffile->badge_map.empty()) { + GrfMsg(1, "BadgeMapSpriteGroup: No badges defined, skipping"); + return; + } + + std::vector local_ids; + local_ids.reserve(idcount); + for (uint i = 0; i < idcount; i++) { + local_ids.push_back(buf.ReadExtendedByte()); + } + + uint8_t cidcount = buf.ReadByte(); + for (uint c = 0; c < cidcount; c++) { + uint8_t ctype = buf.ReadByte(); + uint16_t groupid = buf.ReadWord(); + if (!IsValidGroupID(groupid, "BadgeMapSpriteGroup")) continue; + + if (ctype >= GSF_END) continue; + + for (const auto &local_id : local_ids) { + auto found = _cur.grffile->badge_map.find(local_id); + if (found == std::end(_cur.grffile->badge_map)) { + GrfMsg(1, "BadgeMapSpriteGroup: Badge {} undefined, skipping", local_id); + continue; + } + + auto &badge = *GetBadge(found->second); + badge.grf_prop.SetSpriteGroup(ctype, _cur.spritegroups[groupid]); + } + } + + uint16_t groupid = buf.ReadWord(); + if (!IsValidGroupID(groupid, "BadgeMapSpriteGroup")) return; + + for (auto &local_id : local_ids) { + auto found = _cur.grffile->badge_map.find(local_id); + if (found == std::end(_cur.grffile->badge_map)) { + GrfMsg(1, "BadgeMapSpriteGroup: Badge {} undefined, skipping", local_id); + continue; + } + + auto &badge = *GetBadge(found->second); + badge.grf_prop.SetSpriteGroup(GSF_END, _cur.spritegroups[groupid]); + badge.grf_prop.grffile = _cur.grffile; + badge.grf_prop.local_id = local_id; + } +} + /* Action 0x03 */ static void FeatureMapSpriteGroup(ByteReader &buf) { @@ -6314,6 +6550,10 @@ static void FeatureMapSpriteGroup(ByteReader &buf) RoadStopMapSpriteGroup(buf, idcount); return; + case GSF_BADGES: + BadgeMapSpriteGroup(buf, idcount); + break; + default: GrfMsg(1, "FeatureMapSpriteGroup: Unsupported feature 0x{:02X}, skipping", feature); return; @@ -6353,7 +6593,7 @@ static void FeatureNewName(ByteReader &buf) uint16_t id; if (generic) { id = buf.ReadWord(); - } else if (feature <= GSF_AIRCRAFT) { + } else if (feature <= GSF_AIRCRAFT || feature == GSF_BADGES) { id = buf.ReadExtendedByte(); } else { id = buf.ReadByte(); @@ -6388,6 +6628,21 @@ static void FeatureNewName(ByteReader &buf) } break; + case GSF_BADGES: { + if (!generic) { + auto found = _cur.grffile->badge_map.find(id); + if (found == std::end(_cur.grffile->badge_map)) { + GrfMsg(1, "FeatureNewName: Attempt to name undefined badge 0x{:X}, ignoring", id); + } else { + Badge &badge = *GetBadge(found->second); + badge.name = AddGRFString(_cur.grffile->grfid, GRFStringID{feature_overlay | id}, lang, true, false, name, STR_UNDEFINED); + } + } else { + AddGRFString(_cur.grffile->grfid, GRFStringID{id}, lang, new_scheme, true, name, STR_UNDEFINED); + } + break; + } + default: if (IsInsideMM(id, 0xD000, 0xD400) || IsInsideMM(id, 0xD800, 0x10000)) { AddGRFString(_cur.grffile->grfid, GRFStringID{id}, lang, new_scheme, true, name, STR_UNDEFINED); @@ -8811,6 +9066,8 @@ void ResetNewGRFData() CleanUpStrings(); CleanUpGRFTownNames(); + ResetBadges(); + /* Copy/reset original engine info data */ SetupEngines(); @@ -9253,6 +9510,12 @@ static void FinaliseEngineArray() if (!e->info.climates.Test(_settings_game.game_creation.landscape)) continue; + switch (e->type) { + case VEH_TRAIN: AppendCopyableBadgeList(e->badges, GetRailTypeInfo(e->u.rail.railtype)->badges, GSF_TRAINS); break; + case VEH_ROAD: AppendCopyableBadgeList(e->badges, GetRoadTypeInfo(e->u.road.roadtype)->badges, GSF_ROADVEHICLES); break; + default: break; + } + /* Skip wagons, there livery is defined via the engine */ if (e->type != VEH_TRAIN || e->u.rail.railveh_type != RAILVEH_WAGON) { LiveryScheme ls = GetEngineLiveryScheme(e->index, EngineID::Invalid(), nullptr); @@ -9944,6 +10207,42 @@ static void FinalisePriceBaseMultipliers() } } +template +void AddBadgeToSpecs(T &specs, GrfSpecFeature feature, Badge &badge) +{ + for (auto &spec : specs) { + if (spec == nullptr) continue; + spec->badges.push_back(badge.index); + badge.features.Set(feature); + } +} + +/** Finish up applying badges to things */ +static void FinaliseBadges() +{ + for (GRFFile * const file : _grf_files) { + Badge *badge = GetBadgeByLabel(fmt::format("newgrf/{:08x}", std::byteswap(file->grfid))); + if (badge == nullptr) continue; + + for (Engine *e : Engine::Iterate()) { + if (e->grf_prop.grffile != file) continue; + e->badges.push_back(badge->index); + badge->features.Set(static_cast(GSF_TRAINS + e->type)); + } + + AddBadgeToSpecs(file->stations, GSF_STATIONS, *badge); + AddBadgeToSpecs(file->housespec, GSF_HOUSES, *badge); + AddBadgeToSpecs(file->industryspec, GSF_INDUSTRIES, *badge); + AddBadgeToSpecs(file->indtspec, GSF_INDUSTRYTILES, *badge); + AddBadgeToSpecs(file->objectspec, GSF_OBJECTS, *badge); + AddBadgeToSpecs(file->airportspec, GSF_AIRPORTS, *badge); + AddBadgeToSpecs(file->airtspec, GSF_AIRPORTTILES, *badge); + AddBadgeToSpecs(file->roadstops, GSF_ROADSTOPS, *badge); + } + + ApplyBadgeFeaturesToClassBadges(); +} + extern void InitGRFTownGeneratorNames(); /** Finish loading NewGRFs and execute needed post-processing */ @@ -9961,6 +10260,8 @@ static void AfterLoadGRFs() /* Clear the action 6 override sprites. */ _grf_line_to_action6_sprite_override.clear(); + FinaliseBadges(); + /* Polish cargoes */ FinaliseCargoArray(); diff --git a/src/newgrf.h b/src/newgrf.h index b0e0852ddd..481b7d5c0a 100644 --- a/src/newgrf.h +++ b/src/newgrf.h @@ -14,6 +14,7 @@ #include "rail_type.h" #include "road_type.h" #include "fileio_type.h" +#include "newgrf_badge_type.h" #include "newgrf_callbacks.h" #include "newgrf_text_type.h" #include "core/bitmath_func.hpp" @@ -87,6 +88,7 @@ enum GrfSpecFeature : uint8_t { GSF_ROADTYPES, GSF_TRAMTYPES, GSF_ROADSTOPS, + GSF_BADGES, GSF_END, GSF_FAKE_TOWNS = GSF_END, ///< Fake town GrfSpecFeature for NewGRF debugging (parent scope) @@ -94,6 +96,7 @@ enum GrfSpecFeature : uint8_t { GSF_INVALID = 0xFF, ///< An invalid spec feature }; +using GrfSpecFeatures = EnumBitSet; static const uint32_t INVALID_GRFID = 0xFFFFFFFF; @@ -130,6 +133,9 @@ struct GRFFile : ZeroedMemoryAllocator { std::vector cargo_list; ///< Cargo translation table (local ID -> label) std::array cargo_map{}; ///< Inverse cargo translation table (CargoType -> local ID) + std::vector badge_list; ///< Badge translation table (local index -> global index) + std::unordered_map badge_map; + std::vector railtype_list; ///< Railtype translation table std::array railtype_map{}; diff --git a/src/newgrf_airport.cpp b/src/newgrf_airport.cpp index fa1def20ae..7d5ad89f1c 100644 --- a/src/newgrf_airport.cpp +++ b/src/newgrf_airport.cpp @@ -10,6 +10,7 @@ #include "stdafx.h" #include "debug.h" #include "timer/timer_game_calendar.h" +#include "newgrf_badge.h" #include "newgrf_spritegroup.h" #include "newgrf_text.h" #include "station_base.h" @@ -158,6 +159,8 @@ void AirportOverrideManager::SetEntitySpec(AirportSpec *as) { switch (variable) { case 0x40: return this->layout; + + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->spec->badges, parameter); } if (this->st == nullptr) { diff --git a/src/newgrf_airport.h b/src/newgrf_airport.h index 6e65899ae5..f9faa33921 100644 --- a/src/newgrf_airport.h +++ b/src/newgrf_airport.h @@ -12,6 +12,7 @@ #include "airport.h" #include "timer/timer_game_calendar.h" +#include "newgrf_badge_type.h" #include "newgrf_class.h" #include "newgrf_commons.h" #include "newgrf_spritegroup.h" @@ -119,6 +120,7 @@ struct AirportSpec : NewGRFSpecBase { /* Newgrf data */ bool enabled; ///< Entity still available (by default true). Newgrf can disable it, though. struct GRFFileProps grf_prop; ///< Properties related to the grf file. + std::vector badges; static const AirportSpec *Get(uint8_t type); static AirportSpec *GetWithoutOverride(uint8_t type); diff --git a/src/newgrf_airporttiles.cpp b/src/newgrf_airporttiles.cpp index 23a012bb34..8f5650d69e 100644 --- a/src/newgrf_airporttiles.cpp +++ b/src/newgrf_airporttiles.cpp @@ -10,6 +10,7 @@ #include "stdafx.h" #include "debug.h" #include "newgrf_airporttiles.h" +#include "newgrf_badge.h" #include "newgrf_spritegroup.h" #include "newgrf_sound.h" #include "station_base.h" @@ -190,6 +191,8 @@ static uint32_t GetAirportTileIDAtOffset(TileIndex tile, const Station *st, uint /* Get airport tile ID at offset */ case 0x62: return GetAirportTileIDAtOffset(GetNearbyTile(parameter, this->tile), this->st, this->ro.grffile->grfid); + + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->ats->badges, parameter); } Debug(grf, 1, "Unhandled airport tile variable 0x{:X}", variable); diff --git a/src/newgrf_airporttiles.h b/src/newgrf_airporttiles.h index 592e367bc3..1f2d989c33 100644 --- a/src/newgrf_airporttiles.h +++ b/src/newgrf_airporttiles.h @@ -13,6 +13,7 @@ #include "airport.h" #include "station_map.h" #include "newgrf_animation_type.h" +#include "newgrf_badge_type.h" #include "newgrf_callbacks.h" #include "newgrf_commons.h" #include "newgrf_spritegroup.h" @@ -73,6 +74,7 @@ struct AirportTileSpec { uint8_t animation_special_flags; ///< Extra flags to influence the animation bool enabled; ///< entity still available (by default true). newgrf can disable it, though GRFFileProps grf_prop; ///< properties related the the grf file + std::vector badges; static const AirportTileSpec *Get(StationGfx gfx); static const AirportTileSpec *GetByTile(TileIndex tile); diff --git a/src/newgrf_badge.cpp b/src/newgrf_badge.cpp new file mode 100644 index 0000000000..5807f162ef --- /dev/null +++ b/src/newgrf_badge.cpp @@ -0,0 +1,516 @@ + /* + * 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 newgrf_badge.cpp Functionality for NewGRF badges. */ + +#include "stdafx.h" +#include "dropdown_type.h" +#include "dropdown_common_type.h" +#include "newgrf.h" +#include "newgrf_badge.h" +#include "newgrf_badge_type.h" +#include "newgrf_spritegroup.h" +#include "strings_func.h" +#include "timer/timer_game_calendar.h" +#include "window_gui.h" +#include "zoom_func.h" + +#include "table/strings.h" + +#include "safeguards.h" + +/** Separator to identify badge classes from a label. */ +static constexpr char BADGE_CLASS_SEPARATOR = '/'; + +/** Global state for badge definitions. */ +class Badges { +public: + std::vector classes; ///< List of known badge classes. + std::vector specs; ///< List of known badges. +}; + +/** Static instance of badge state. */ +static Badges _badges = {}; + +/** + * Assign a BadgeClassID to the given badge. + * @param index Badge ID of badge that should be assigned. + * @returns new or existing BadgeClassID. + */ +static BadgeClassID GetOrCreateBadgeClass(BadgeID index) +{ + auto it = std::ranges::find(_badges.classes, index); + if (it == std::end(_badges.classes)) { + it = _badges.classes.emplace(it, index); + } + + return static_cast(std::distance(std::begin(_badges.classes), it)); +} + +/** + * Reset badges to the default state. + */ +void ResetBadges() +{ + _badges = {}; +} + +/** + * Register a badge label and return its global index. + * @param label Badge label to register. + * @returns Global index of the badge. + */ +Badge &GetOrCreateBadge(std::string_view label) +{ + /* Check if the label exists. */ + auto it = std::ranges::find(_badges.specs, label, &Badge::label); + if (it != std::end(_badges.specs)) return *it; + + BadgeClassID class_index; + + /* Extract class. */ + auto sep = label.find_first_of(BADGE_CLASS_SEPARATOR); + if (sep != std::string_view::npos) { + /* There is a separator, find (and create if necessary) the class label. */ + class_index = GetOrCreateBadge(label.substr(0, sep)).class_index; + it = std::end(_badges.specs); + } + + BadgeID index = BadgeID(std::distance(std::begin(_badges.specs), it)); + if (sep == std::string_view::npos) { + /* There is no separator, so this badge is a class badge. */ + class_index = GetOrCreateBadgeClass(index); + } + + it = _badges.specs.emplace(it, label, index, class_index); + return *it; +} + +/** + * Get a badge if it exists. + * @param index Index of badge. + * @returns Badge with specified index, or nullptr if it does not exist. + */ +Badge *GetBadge(BadgeID index) +{ + if (index.base() >= std::size(_badges.specs)) return nullptr; + return &_badges.specs[index.base()]; +} + +/** + * Get a badge by label if it exists. + * @param label Label of badge. + * @returns Badge with specified label, or nullptr if it does not exist. + */ +Badge *GetBadgeByLabel(std::string_view label) +{ + auto it = std::ranges::find(_badges.specs, label, &Badge::label); + if (it == std::end(_badges.specs)) return nullptr; + + return &*it; +} + +/** + * Get the badge class of a badge label. + * @param label Label to get class of. + * @returns Badge class index of label. + */ +Badge *GetClassBadge(BadgeClassID class_index) +{ + if (class_index.base() >= std::size(_badges.classes)) return nullptr; + return GetBadge(_badges.classes[class_index.base()]); +} + +/** Resolver for a badge scope. */ +struct BadgeScopeResolver : public ScopeResolver { + const Badge &badge; + const std::optional introduction_date; + + /** + * Scope resolver of a badge. + * @param ro Surrounding resolver. + * @param badge Badge to resolve. + * @param introduction_date Introduction date of entity. + */ + BadgeScopeResolver(ResolverObject &ro, const Badge &badge, const std::optional introduction_date) + : ScopeResolver(ro), badge(badge), introduction_date(introduction_date) { } + + uint32_t GetVariable(uint8_t variable, [[maybe_unused]] uint32_t parameter, bool &available) const override; +}; + +/* virtual */ uint32_t BadgeScopeResolver::GetVariable(uint8_t variable, [[maybe_unused]] uint32_t parameter, bool &available) const +{ + switch (variable) { + case 0x40: + if (this->introduction_date.has_value()) return this->introduction_date->base(); + return TimerGameCalendar::date.base(); + + default: break; + } + + available = false; + return UINT_MAX; +} + +/** Resolver of badges. */ +struct BadgeResolverObject : public ResolverObject { + BadgeScopeResolver self_scope; + + BadgeResolverObject(const Badge &badge, GrfSpecFeature feature, std::optional introduction_date, CallbackID callback = CBID_NO_CALLBACK, uint32_t callback_param1 = 0, uint32_t callback_param2 = 0); + + ScopeResolver *GetScope(VarSpriteGroupScope scope = VSG_SCOPE_SELF, uint8_t relative = 0) override + { + switch (scope) { + case VSG_SCOPE_SELF: return &this->self_scope; + default: return ResolverObject::GetScope(scope, relative); + } + } + + GrfSpecFeature GetFeature() const override; + uint32_t GetDebugID() const override; +}; + +GrfSpecFeature BadgeResolverObject::GetFeature() const +{ + return GSF_BADGES; +} + +uint32_t BadgeResolverObject::GetDebugID() const +{ + return this->self_scope.badge.index.base(); +} + +/** + * Constructor of the badge resolver. + * @param badge Badge being resolved. + * @param feature GRF feature being used. + * @param introduction_date Optional introduction date of entity. + * @param callback Callback ID. + * @param callback_param1 First parameter (var 10) of the callback. + * @param callback_param2 Second parameter (var 18) of the callback. + */ +BadgeResolverObject::BadgeResolverObject(const Badge &badge, GrfSpecFeature feature, std::optional introduction_date, CallbackID callback, uint32_t callback_param1, uint32_t callback_param2) + : ResolverObject(badge.grf_prop.grffile, callback, callback_param1, callback_param2), self_scope(*this, badge, introduction_date) +{ + assert(feature <= GSF_END); + this->root_spritegroup = this->self_scope.badge.grf_prop.GetSpriteGroup(feature); + if (this->root_spritegroup == nullptr) this->root_spritegroup = this->self_scope.badge.grf_prop.GetSpriteGroup(GSF_END); +} + +/** + * Test for a matching badge in a list of badges, returning the number of matching bits. + * @param grffile GRF file of the current varaction. + * @param badges List of badges to test. + * @param parameter GRF-local badge index. + * @returns true iff the badge is present. + */ +uint32_t GetBadgeVariableResult(const GRFFile &grffile, std::span badges, uint32_t parameter) +{ + if (parameter >= std::size(grffile.badge_list)) return UINT_MAX; + + BadgeID index = grffile.badge_list[parameter]; + return std::ranges::find(badges, index) != std::end(badges); +} + +/** + * Mark a badge a seen (used) by a feature. + */ +void MarkBadgeSeen(BadgeID index, GrfSpecFeature feature) +{ + Badge *b = GetBadge(index); + assert(b != nullptr); + b->features.Set(feature); +} + +/** + * Append copyable badges from a list onto another. + * Badges must exist and be marked with the Copy flag. + * @param dst Destination badge list. + * @param src Source badge list. + * @param feature Feature of list. + */ +void AppendCopyableBadgeList(std::vector &dst, std::span src, GrfSpecFeature feature) +{ + for (const BadgeID &index : src) { + /* Is badge already present? */ + if (std::ranges::find(dst, index) != std::end(dst)) continue; + + /* Is badge copyable? */ + Badge *badge = GetBadge(index); + if (badge == nullptr) continue; + if (!badge->flags.Test(BadgeFlag::Copy)) continue; + + dst.push_back(index); + badge->features.Set(feature); + } +} + +/** Apply features from all badges to their badge classes. */ +void ApplyBadgeFeaturesToClassBadges() +{ + for (const Badge &badge : _badges.specs) { + Badge *class_badge = GetClassBadge(badge.class_index); + assert(class_badge != nullptr); + class_badge->features.Set(badge.features); + } +} + +static constexpr uint MAX_BADGE_HEIGHT = 12; ///< Maximal height of a badge sprite. +static constexpr uint MAX_BADGE_WIDTH = MAX_BADGE_HEIGHT * 2; ///< Maximal width. + +/** + * Get sprite for the given badge. + * @param badge Badge being queried. + * @param feature GRF feature being used. + * @param introduction_date Introduction date of the item, if it has one. + * @param remap Palette remap to use if the flag is company-coloured. + * @returns Custom sprite to draw, or \c 0 if not available. + */ +static PalSpriteID GetBadgeSprite(const Badge &badge, GrfSpecFeature feature, std::optional introduction_date, PaletteID remap) +{ + BadgeResolverObject object(badge, feature, introduction_date); + const SpriteGroup *group = object.Resolve(); + if (group == nullptr) return {0, PAL_NONE}; + + PaletteID pal = badge.flags.Test(BadgeFlag::UseCompanyColour) ? remap : PAL_NONE; + + return {group->GetResult(), pal}; +} + +/** + * Get the largest badge size (within limits) for a badge class. + * @param badge_class Badge class. + * @param feature Feature being used. + * @returns Largest base size of the badge class for the feature. + */ +static Dimension GetBadgeMaximalDimension(BadgeClassID class_index, GrfSpecFeature feature) +{ + Dimension d = { 0, MAX_BADGE_HEIGHT }; + + for (const auto &badge : _badges.specs) { + if (badge.class_index != class_index) continue; + + PalSpriteID ps = GetBadgeSprite(badge, feature, std::nullopt, PAL_NONE); + if (ps.sprite == 0) continue; + + d.width = std::max(d.width, GetSpriteSize(ps.sprite, nullptr, ZOOM_LVL_NORMAL).width); + if (d.width > MAX_BADGE_WIDTH) break; + } + + d.width = std::min(d.width, MAX_BADGE_WIDTH); + return d; +} + +/** Utility class to create a list of badge classes used by a feature. */ +class UsedBadgeClasses { +public: + /** + * Create a list of used badge classes for a feature. + * @param feature GRF feature being used. + */ + explicit UsedBadgeClasses(GrfSpecFeature feature) + { + for (auto index : _badges.classes) { + Badge *class_badge = GetBadge(index); + if (!class_badge->features.Test(feature)) continue; + + this->classes.push_back(class_badge->class_index); + } + + std::ranges::sort(this->classes, [](const BadgeClassID &a, const BadgeClassID &b) + { + return GetClassBadge(a)->label < GetClassBadge(b)->label; + }); + } + + std::span Classes() const { return this->classes; } + +private: + std::vector classes; ///< List of badge classes. +}; + +static bool operator<(const GUIBadgeClasses::Element &a, const GUIBadgeClasses::Element &b) +{ + if (a.column_group != b.column_group) return a.column_group < b.column_group; + if (a.sort_order != b.sort_order) return a.sort_order < b.sort_order; + return a.label < b.label; +} + +/** + * Construct of list of badge classes and column groups to display. + * @param feature feature being used. + */ +GUIBadgeClasses::GUIBadgeClasses(GrfSpecFeature feature) +{ + /* Get list of classes used by feature. */ + UsedBadgeClasses used(feature); + + uint max_column = 0; + for (BadgeClassID class_index : used.Classes()) { + Dimension size = GetBadgeMaximalDimension(class_index, feature); + if (size.width == 0) continue; + + uint8_t column = 0; + 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); + if (visible) max_column = std::max(max_column, column); + } + + std::sort(std::begin(this->gui_classes), std::end(this->gui_classes)); + + /* Determine total width of visible badge columns. */ + this->column_widths.resize(max_column + 1); + for (const auto &el : this->gui_classes) { + if (!el.visible) continue; + this->column_widths[el.column_group] += ScaleGUITrad(el.size.width) + WidgetDimensions::scaled.hsep_normal; + } + + /* Replace trailing `hsep_normal` spacer with wider `hsep_wide` spacer. */ + for (uint &badge_width : this->column_widths) { + if (badge_width == 0) continue; + badge_width = badge_width - WidgetDimensions::scaled.hsep_normal + WidgetDimensions::scaled.hsep_wide; + } +} + +/** + * Get total width of all columns. + * @returns sum of all column widths. + */ +uint GUIBadgeClasses::GetTotalColumnsWidth() const +{ + return std::accumulate(std::begin(this->column_widths), std::end(this->column_widths), 0U); +} + +/** + * Draw names for a list of badge labels. + * @param r Rect to draw in. + * @param badges List of badges. + * @param feature GRF feature being used. + * @returns Vertical position after drawing is complete. + */ +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); + + for (const BadgeClassID &class_index : classes) { + const Badge *class_badge = GetClassBadge(class_index); + if (class_badge == nullptr || class_badge->name == STR_NULL) continue; + + std::string s; + for (const BadgeID &index : badges) { + const Badge *badge = GetBadge(index); + if (badge == nullptr || badge->name == STR_NULL) continue; + if (badge->class_index != class_index) continue; + + if (!s.empty()) { + if (badge->flags.Test(BadgeFlag::NameListFirstOnly)) continue; + s += ", "; + } + AppendStringInPlace(s, badge->name); + if (badge->flags.Test(BadgeFlag::NameListStop)) break; + } + + if (s.empty()) continue; + + SetDParam(0, class_badge->name); + SetDParamStr(1, std::move(s)); + r.top = DrawStringMultiLine(r, STR_BADGE_NAME_LIST, TC_BLACK); + } + + return r.top; +} + +/** + * Draw a badge column group. + * @param r rect to draw within. + * @param column_group column to draw. + * @param badge_classes 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) +{ + 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; + + int width = ScaleGUITrad(badge_class.size.width); + for (const BadgeID &index : badges) { + const Badge &badge = *GetBadge(index); + if (badge.class_index != badge_class.badge_class) continue; + + PalSpriteID ps = GetBadgeSprite(badge, feature, introduction_date, remap); + if (ps.sprite == 0) continue; + + DrawSpriteIgnorePadding(ps.sprite, ps.pal, r.WithWidth(width, rtl), SA_CENTER); + break; + } + + r = r.Indent(width + WidgetDimensions::scaled.hsep_normal, rtl); + } +} + +/** Drop down element that draws a list of badges. */ +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) + { + 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); + } + } + + uint Height() const override { return std::max(this->dim.height, this->TBase::Height()); } + uint Width() const override { return this->dim.width + WidgetDimensions::scaled.hsep_wide + this->TBase::Width(); } + + void Draw(const Rect &full, const Rect &r, bool sel, Colours bg_colour) const override + { + 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); + + this->TBase::Draw(full, r.Indent(this->dim.width + WidgetDimensions::scaled.hsep_wide, rtl), sel, bg_colour); + } + +private: + std::shared_ptr badge_classes; + + const std::span badges; + const GrfSpecFeature feature; + const std::optional introduction_date; + + Dimension dim{}; + +}; + +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, StringID str, int value, bool masked, bool shaded) +{ + return std::make_unique(badge_classes, badges, feature, introduction_date, 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, StringID str, int value, bool masked, bool shaded) +{ + return std::make_unique(badge_classes, badges, feature, introduction_date, dim, sprite, palette, str, value, masked, shaded); +} diff --git a/src/newgrf_badge.h b/src/newgrf_badge.h new file mode 100644 index 0000000000..d0328169de --- /dev/null +++ b/src/newgrf_badge.h @@ -0,0 +1,80 @@ +/* + * 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 newgrf_badge.h Functions related to NewGRF badges. */ + +#ifndef NEWGRF_BADGE_H +#define NEWGRF_BADGE_H + +#include "dropdown_type.h" +#include "newgrf.h" +#include "newgrf_badge_type.h" +#include "newgrf_commons.h" +#include "strings_type.h" +#include "timer/timer_game_calendar.h" + +class Badge { +public: + std::string label; ///< Label of badge. + BadgeID index; ///< Index assigned to badge. + BadgeClassID class_index; ///< Index of class this badge belongs to. + BadgeFlags flags = {}; ///< Display flags + StringID name = 0; ///< Short name. + GrfSpecFeatures features{}; ///< Bitmask of which features use this badge. + VariableGRFFileProps grf_prop; ///< Sprite information. + + Badge(std::string_view label, BadgeID index, BadgeClassID class_index) : label(label), index(index), class_index(class_index) {} +}; + +void ResetBadges(); + +Badge &GetOrCreateBadge(std::string_view label); +void MarkBadgeSeen(BadgeID index, GrfSpecFeature feature); +void AppendCopyableBadgeList(std::vector &dst, std::span src, GrfSpecFeature feature); +void ApplyBadgeFeaturesToClassBadges(); + +Badge *GetBadge(BadgeID index); +Badge *GetBadgeByLabel(std::string_view label); +Badge *GetClassBadge(BadgeClassID class_index); + +class GUIBadgeClasses { +public: + struct Element { + BadgeClassID badge_class; ///< 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) {} + }; + + GUIBadgeClasses() = default; + explicit GUIBadgeClasses(GrfSpecFeature feature); + + inline std::span GetClasses() const { return this->gui_classes; } + + inline std::span GetColumnWidths() const { return this->column_widths; } + + uint GetTotalColumnsWidth() const; + +private: + std::vector gui_classes{}; + std::vector column_widths{}; +}; + +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); + +uint32_t GetBadgeVariableResult(const struct GRFFile &grffile, std::span badges, uint32_t parameter); + +std::unique_ptr MakeDropDownListBadgeItem(const std::shared_ptr &gui_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, StringID str, int value, bool masked = false, bool shaded = false); +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, StringID str, int value, bool masked = false, bool shaded = false); + +#endif /* NEWGRF_BADGE_H */ diff --git a/src/newgrf_badge_type.h b/src/newgrf_badge_type.h new file mode 100644 index 0000000000..54810aca27 --- /dev/null +++ b/src/newgrf_badge_type.h @@ -0,0 +1,27 @@ +/* + * 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 newgrf_badge_type.h Types related to NewGRF badges. */ + +#ifndef NEWGRF_BADGE_TYPE_H +#define NEWGRF_BADGE_TYPE_H + +#include "core/enum_type.hpp" +#include "core/strong_typedef_type.hpp" + +using BadgeID = StrongType::Typedef; +using BadgeClassID = StrongType::Typedef; + +enum class BadgeFlag : uint8_t { + Copy = 0, ///< Copy badge to related things. + NameListStop = 1, ///< Stop adding names to the name list after this badge. + NameListFirstOnly = 2, ///< Don't add this name to the name list if not first. + UseCompanyColour = 3, ///< Apply company colour palette to this badge. +}; +using BadgeFlags = EnumBitSet; + +#endif /* NEWGRF_BADGE_TYPE_H */ diff --git a/src/newgrf_engine.cpp b/src/newgrf_engine.cpp index 401f05e622..fa02ecf4f2 100644 --- a/src/newgrf_engine.cpp +++ b/src/newgrf_engine.cpp @@ -12,6 +12,7 @@ #include "train.h" #include "roadveh.h" #include "company_func.h" +#include "newgrf_badge.h" #include "newgrf_cargo.h" #include "newgrf_spritegroup.h" #include "timer/timer_game_calendar.h" @@ -692,6 +693,8 @@ static uint32_t VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *objec default: return 0x00; } + case 0x7A: return GetBadgeVariableResult(*object->ro.grffile, v->GetEngine()->badges, parameter); + case 0xFE: case 0xFF: { uint16_t modflags = 0; @@ -959,6 +962,9 @@ static uint32_t VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *objec case 0x48: return Engine::Get(this->self_type)->flags.base(); // Vehicle Type Info case 0x49: return TimerGameCalendar::year.base(); // 'Long' format build year case 0x4B: return TimerGameCalendar::date.base(); // Long date of last service + + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, Engine::Get(this->self_type)->badges, parameter); + case 0x92: return ClampTo(TimerGameCalendar::date - CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR); // Date of last service case 0x93: return GB(ClampTo(TimerGameCalendar::date - CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR), 8, 8); case 0xC4: return (Clamp(TimerGameCalendar::year, CalendarTime::ORIGINAL_BASE_YEAR, CalendarTime::ORIGINAL_MAX_YEAR) - CalendarTime::ORIGINAL_BASE_YEAR).base(); // Build year diff --git a/src/newgrf_house.cpp b/src/newgrf_house.cpp index 0059012dd6..4d99affbbc 100644 --- a/src/newgrf_house.cpp +++ b/src/newgrf_house.cpp @@ -10,6 +10,7 @@ #include "stdafx.h" #include "debug.h" #include "landscape.h" +#include "newgrf_badge.h" #include "newgrf_house.h" #include "newgrf_spritegroup.h" #include "newgrf_town.h" @@ -391,6 +392,8 @@ static uint32_t GetDistanceFromNearbyHouse(uint8_t parameter, TileIndex tile, Ho case 0x65: return 0; case 0x66: return 0xFFFFFFFF; /* Class and ID of nearby house. */ case 0x67: return 0; + + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, HouseSpec::Get(this->house_id)->badges, parameter); } Debug(grf, 1, "Unhandled house variable 0x{:X}", variable); @@ -507,6 +510,8 @@ static uint32_t GetDistanceFromNearbyHouse(uint8_t parameter, TileIndex tile, Ho * in case the newgrf was removed. */ return _house_mngr.GetGRFID(house_id); } + + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, HouseSpec::Get(this->house_id)->badges, parameter); } Debug(grf, 1, "Unhandled house variable 0x{:X}", variable); diff --git a/src/newgrf_industries.cpp b/src/newgrf_industries.cpp index cdf3a844c4..fa19d8f1f7 100644 --- a/src/newgrf_industries.cpp +++ b/src/newgrf_industries.cpp @@ -10,6 +10,7 @@ #include "stdafx.h" #include "debug.h" #include "industry.h" +#include "newgrf_badge.h" #include "newgrf_industries.h" #include "newgrf_town.h" #include "newgrf_cargo.h" @@ -166,6 +167,8 @@ static uint32_t GetCountAndDistanceOfClosestInstance(uint8_t param_setID, uint8_ /* Variables available during construction check. */ switch (variable) { + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, GetIndustrySpec(this->type)->badges, parameter); + case 0x80: return this->tile.base(); case 0x81: return GB(this->tile.base(), 8, 8); @@ -350,6 +353,8 @@ static uint32_t GetCountAndDistanceOfClosestInstance(uint8_t param_setID, uint8_ NOT_REACHED(); } + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, GetIndustrySpec(this->type)->badges, parameter); + /* Get a variable from the persistent storage */ case 0x7C: return (this->industry->psa != nullptr) ? this->industry->psa->GetValue(parameter) : 0; diff --git a/src/newgrf_industrytiles.cpp b/src/newgrf_industrytiles.cpp index c2d86fb63e..60988f6e1d 100644 --- a/src/newgrf_industrytiles.cpp +++ b/src/newgrf_industrytiles.cpp @@ -10,6 +10,7 @@ #include "stdafx.h" #include "debug.h" #include "landscape.h" +#include "newgrf_badge.h" #include "newgrf_industrytiles.h" #include "newgrf_sound.h" #include "industry.h" @@ -91,6 +92,8 @@ uint32_t GetRelativePosition(TileIndex tile, TileIndex ind_tile) /* Get industry tile ID at offset */ case 0x62: return GetIndustryIDAtOffset(GetNearbyTile(parameter, this->tile), this->industry, this->ro.grffile->grfid); + + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, GetIndustryTileSpec(GetIndustryGfx(this->tile))->badges, parameter); } Debug(grf, 1, "Unhandled industry tile variable 0x{:X}", variable); diff --git a/src/newgrf_object.cpp b/src/newgrf_object.cpp index 3604a62b31..90a483a8f7 100644 --- a/src/newgrf_object.cpp +++ b/src/newgrf_object.cpp @@ -12,6 +12,7 @@ #include "company_func.h" #include "debug.h" #include "genworld.h" +#include "newgrf_badge.h" #include "newgrf_object.h" #include "newgrf_class_func.h" #include "newgrf_sound.h" @@ -286,6 +287,8 @@ static uint32_t GetCountAndDistanceOfClosestInstance(uint8_t local_id, uint32_t /* Object view */ case 0x48: return this->view; + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->spec->badges, parameter); + /* * Disallow the rest: * 0x40: Relative position is passed as parameter during construction. @@ -356,6 +359,8 @@ static uint32_t GetCountAndDistanceOfClosestInstance(uint8_t local_id, uint32_t /* Count of object, distance of closest instance */ case 0x64: return GetCountAndDistanceOfClosestInstance(parameter, this->ro.grffile->grfid, this->tile, this->obj); + + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->spec->badges, parameter); } unhandled: diff --git a/src/newgrf_object.h b/src/newgrf_object.h index 4b00bb6da9..74cf12e629 100644 --- a/src/newgrf_object.h +++ b/src/newgrf_object.h @@ -17,6 +17,7 @@ #include "timer/timer_game_calendar.h" #include "object_type.h" #include "newgrf_animation_type.h" +#include "newgrf_badge_type.h" #include "newgrf_class.h" #include "newgrf_commons.h" @@ -73,6 +74,7 @@ struct ObjectSpec : NewGRFSpecBase { uint8_t height; ///< The height of this structure, in heightlevels; max MAX_TILE_HEIGHT. uint8_t views; ///< The number of views. uint8_t generate_amount; ///< Number of objects which are attempted to be generated per 256^2 map during world generation. + std::vector badges; /** * Test if this object is enabled. diff --git a/src/newgrf_roadstop.cpp b/src/newgrf_roadstop.cpp index 853d2e8313..7ebec4340d 100644 --- a/src/newgrf_roadstop.cpp +++ b/src/newgrf_roadstop.cpp @@ -11,6 +11,7 @@ #include "debug.h" #include "station_base.h" #include "roadstop_base.h" +#include "newgrf_badge.h" #include "newgrf_roadstop.h" #include "newgrf_class_func.h" #include "newgrf_cargo.h" @@ -200,6 +201,8 @@ uint32_t RoadStopScopeResolver::GetVariable(uint8_t variable, [[maybe_unused]] u return 0xFFFE; } + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->roadstopspec->badges, parameter); + case 0xF0: return this->st == nullptr ? 0 : this->st->facilities.base(); // facilities case 0xFA: return ClampTo((this->st == nullptr ? TimerGameCalendar::date : this->st->build_date) - CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR); // build date diff --git a/src/newgrf_roadstop.h b/src/newgrf_roadstop.h index dd2e32bebf..b429f6c22c 100644 --- a/src/newgrf_roadstop.h +++ b/src/newgrf_roadstop.h @@ -14,6 +14,7 @@ #include "newgrf_animation_type.h" #include "newgrf_spritegroup.h" +#include "newgrf_badge_type.h" #include "newgrf_callbacks.h" #include "newgrf_class.h" #include "newgrf_commons.h" @@ -158,6 +159,8 @@ struct RoadStopSpec : NewGRFSpecBase { uint8_t build_cost_multiplier = 16; ///< Build cost multiplier per tile. uint8_t clear_cost_multiplier = 16; ///< Clear cost multiplier per tile. + std::vector badges; + /** * Get the cost for building a road stop of this type. * @return The cost for building. diff --git a/src/newgrf_station.cpp b/src/newgrf_station.cpp index 9ce7e6c2fc..0b56a0ddc6 100644 --- a/src/newgrf_station.cpp +++ b/src/newgrf_station.cpp @@ -12,6 +12,7 @@ #include "station_base.h" #include "waypoint_base.h" #include "roadstop_base.h" +#include "newgrf_badge.h" #include "newgrf_cargo.h" #include "newgrf_station.h" #include "newgrf_spritegroup.h" @@ -292,6 +293,8 @@ TownScopeResolver *StationResolverObject::GetTown() } break; + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->statspec->badges, parameter); + case 0xFA: return ClampTo(TimerGameCalendar::date - CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR); // Build date, clamped to a 16 bit value } @@ -393,6 +396,8 @@ TownScopeResolver *StationResolverObject::GetTown() return 0xFFFE; } + case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->statspec->badges, parameter); + /* General station variables */ case 0x82: return 50; case 0x84: return this->st->string_id; diff --git a/src/newgrf_station.h b/src/newgrf_station.h index 40c6cee9e2..bc66263700 100644 --- a/src/newgrf_station.h +++ b/src/newgrf_station.h @@ -12,6 +12,7 @@ #include "core/enum_type.hpp" #include "newgrf_animation_type.h" +#include "newgrf_badge_type.h" #include "newgrf_callbacks.h" #include "newgrf_class.h" #include "newgrf_commons.h" @@ -173,6 +174,8 @@ struct StationSpec : NewGRFSpecBase { /** Custom platform layouts, keyed by platform and length combined. */ std::unordered_map> layouts; + + std::vector badges; }; /** Class containing information relating to station classes. */ diff --git a/src/object_gui.cpp b/src/object_gui.cpp index 3e21135dae..78f23766f0 100644 --- a/src/object_gui.cpp +++ b/src/object_gui.cpp @@ -12,6 +12,7 @@ #include "company_func.h" #include "hotkeys.h" #include "newgrf.h" +#include "newgrf_badge.h" #include "newgrf_object.h" #include "newgrf_text.h" #include "object.h" @@ -45,6 +46,8 @@ class ObjectPickerCallbacks : public PickerCallbacksNewGRFClass { public: ObjectPickerCallbacks() : PickerCallbacksNewGRFClass("fav_objects") {} + GrfSpecFeature GetFeature() const override { return GSF_OBJECTS; } + StringID GetClassTooltip() const override { return STR_PICKER_OBJECT_CLASS_TOOLTIP; } StringID GetTypeTooltip() const override { return STR_PICKER_OBJECT_TYPE_TOOLTIP; } @@ -77,6 +80,13 @@ public: return (spec == nullptr || !spec->IsEverAvailable()) ? INVALID_STRING_ID : spec->name; } + std::span GetTypeBadges(int cls_id, int id) const override + { + const auto *spec = this->GetSpec(cls_id, id); + if (spec == nullptr || !spec->IsEverAvailable()) return {}; + return spec->badges; + } + bool IsTypeAvailable(int cls_id, int id) const override { const auto *spec = this->GetSpec(cls_id, id); @@ -222,6 +232,11 @@ public: const ObjectSpec *spec = objclass->GetSpec(_object_gui.sel_type); if (spec == nullptr) break; + Rect tr = r; + const int bottom = tr.bottom; + tr.bottom = INT16_MAX; + tr.top = DrawBadgeNameList(tr, spec->badges, GSF_OBJECTS); + /* Get the extra message for the GUI */ if (spec->callback_mask.Test(ObjectCallbackMask::FundMoreText)) { uint16_t callback_res = GetObjectCallback(CBID_OBJECT_FUND_MORE_TEXT, 0, 0, spec, nullptr, INVALID_TILE, _object_gui.sel_view); @@ -235,17 +250,19 @@ public: /* Use all the available space left from where we stand up to the * end of the window. We ALSO enlarge the window if needed, so we * can 'go' wild with the bottom of the window. */ - int y = DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, message, TC_ORANGE) - r.top - 1; + tr.top = DrawStringMultiLine(tr, message, TC_ORANGE); StopTextRefStackUsage(); - if (y > this->info_height) { - BuildObjectWindow *bow = const_cast(this); - bow->info_height = y; - bow->ReInit(); - } } } } } + + if (tr.top > bottom) { + BuildObjectWindow *bow = const_cast(this); + bow->info_height += tr.top - bottom; + bow->ReInit(); + } + break; } diff --git a/src/picker_gui.cpp b/src/picker_gui.cpp index d4a3e185ff..0940d7e107 100644 --- a/src/picker_gui.cpp +++ b/src/picker_gui.cpp @@ -9,9 +9,11 @@ #include "stdafx.h" #include "core/backup_type.hpp" +#include "company_func.h" #include "gui.h" #include "hotkeys.h" #include "ini_type.h" +#include "newgrf_badge.h" #include "picker_gui.h" #include "querystring_gui.h" #include "settings_type.h" @@ -234,6 +236,8 @@ void PickerWindow::ConstructWindow() this->FinishInitNested(this->window_number); + this->badge_classes = GUIBadgeClasses(this->callbacks.GetFeature()); + this->InvalidateData(PICKER_INVALIDATION_ALL); } @@ -301,6 +305,12 @@ void PickerWindow::DrawWidget(const Rect &r, WidgetID widget) const int y = (ir.Height() + ScaleSpriteTrad(PREVIEW_HEIGHT)) / 2 - ScaleSpriteTrad(PREVIEW_BOTTOM); this->callbacks.DrawType(x, y, item.class_index, item.index); + + int by = ir.Height() - ScaleGUITrad(12); + + GrfSpecFeature feature = this->callbacks.GetFeature(); + DrawBadgeColumn({0, by, ir.Width() - 1, ir.Height() - 1}, 0, this->badge_classes, this->callbacks.GetTypeBadges(item.class_index, item.index), feature, std::nullopt, PAL_NONE); + if (this->callbacks.saved.contains(item)) { DrawSprite(SPR_BLOT, PALETTE_TO_YELLOW, 0, 0); } diff --git a/src/picker_gui.h b/src/picker_gui.h index 5b342f3cfb..4f0060deff 100644 --- a/src/picker_gui.h +++ b/src/picker_gui.h @@ -10,6 +10,7 @@ #ifndef PICKER_GUI_H #define PICKER_GUI_H +#include "newgrf_badge.h" #include "querystring_gui.h" #include "sortlist_type.h" #include "stringfilter_type.h" @@ -41,6 +42,7 @@ public: virtual void Close(int) { } + virtual GrfSpecFeature GetFeature() const = 0; /** Should picker class/type selection be enabled? */ virtual bool IsActive() const = 0; /** Are there multiple classes to chose from? */ @@ -72,6 +74,8 @@ public: virtual PickerItem GetPickerItem(int cls_id, int id) const = 0; /** Get the item of a type. */ virtual StringID GetTypeName(int cls_id, int id) const = 0; + /** Get the item of a type. */ + virtual std::span GetTypeBadges(int cls_id, int id) const = 0; /** Test if an item is currently buildable. */ virtual bool IsTypeAvailable(int cls_id, int id) const = 0; /** Draw preview image of an item. */ @@ -213,6 +217,8 @@ private: void EnsureSelectedTypeIsValid(); void EnsureSelectedTypeIsVisible(); + GUIBadgeClasses badge_classes; + IntervalTimer yearly_interval = {{TimerGameCalendar::YEAR, TimerGameCalendar::Priority::NONE}, [this](auto) { this->SetDirty(); }}; diff --git a/src/rail.h b/src/rail.h index cff9922647..20ca41f7a6 100644 --- a/src/rail.h +++ b/src/rail.h @@ -21,6 +21,7 @@ #include "timer/timer_game_calendar.h" #include "signal_type.h" #include "settings_type.h" +#include "newgrf_badge_type.h" /** Railtype flag bit numbers. */ enum class RailTypeFlag : uint8_t { @@ -270,6 +271,8 @@ public: */ const SpriteGroup *group[RTSG_END]; + std::vector badges; + inline bool UsesOverlay() const { return this->group[RTSG_GROUND] != nullptr; diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 0b45e65d3e..4891d199b4 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -17,6 +17,7 @@ #include "viewport_func.h" #include "command_func.h" #include "waypoint_func.h" +#include "newgrf_badge.h" #include "newgrf_station.h" #include "company_base.h" #include "strings_func.h" @@ -969,6 +970,8 @@ class StationPickerCallbacks : public PickerCallbacksNewGRFClass { public: StationPickerCallbacks() : PickerCallbacksNewGRFClass("fav_stations") {} + GrfSpecFeature GetFeature() const override { return GSF_STATIONS; } + StringID GetClassTooltip() const override { return STR_PICKER_STATION_CLASS_TOOLTIP; } StringID GetTypeTooltip() const override { return STR_PICKER_STATION_TYPE_TOOLTIP; } @@ -1007,6 +1010,13 @@ public: return (spec == nullptr) ? STR_STATION_CLASS_DFLT_STATION : spec->name; } + std::span GetTypeBadges(int cls_id, int id) const override + { + const auto *spec = this->GetSpec(cls_id, id); + if (spec == nullptr) return {}; + return spec->badges; + } + bool IsTypeAvailable(int cls_id, int id) const override { return IsStationAvailable(this->GetSpec(cls_id, id)); @@ -1151,6 +1161,7 @@ public: Rect r = this->GetWidget(WID_BRAS_COVERAGE_TEXTS)->GetCurrentRect(); const int bottom = r.bottom; r.bottom = INT_MAX; // Allow overflow as we want to know the required height. + if (statspec != nullptr) r.top = DrawBadgeNameList(r, statspec->badges, GSF_STATIONS); r.top = DrawStationCoverageAreaText(r, SCT_ALL, rad, false) + WidgetDimensions::scaled.vsep_normal; r.top = DrawStationCoverageAreaText(r, SCT_ALL, rad, true); /* Resize background if the window is too small. @@ -1780,6 +1791,8 @@ class WaypointPickerCallbacks : public PickerCallbacksNewGRFClass public: WaypointPickerCallbacks() : PickerCallbacksNewGRFClass("fav_waypoints") {} + GrfSpecFeature GetFeature() const override { return GSF_STATIONS; } + StringID GetClassTooltip() const override { return STR_PICKER_WAYPOINT_CLASS_TOOLTIP; } StringID GetTypeTooltip() const override { return STR_PICKER_WAYPOINT_TYPE_TOOLTIP; } @@ -1819,6 +1832,13 @@ public: return (spec == nullptr) ? STR_STATION_CLASS_WAYP_WAYPOINT : spec->name; } + std::span GetTypeBadges(int cls_id, int id) const override + { + const auto *spec = this->GetSpec(cls_id, id); + if (spec == nullptr) return {}; + return spec->badges; + } + bool IsTypeAvailable(int cls_id, int id) const override { return IsStationAvailable(this->GetSpec(cls_id, id)); @@ -2039,6 +2059,9 @@ DropDownList GetRailTypeDropDownList(bool for_replacement, bool all_option) } } + /* Shared list so that each item can take ownership. */ + auto badge_class_list = std::make_shared(GSF_RAILTYPES); + for (const auto &rt : _sorted_railtypes) { /* If it's not used ever, don't show it to the user. */ if (!HasBit(used_railtypes, rt)) continue; @@ -2048,10 +2071,10 @@ DropDownList GetRailTypeDropDownList(bool for_replacement, bool all_option) SetDParam(0, rti->strings.menu_text); SetDParam(1, rti->max_speed); if (for_replacement) { - list.push_back(MakeDropDownListStringItem(rti->strings.replace_text, rt, !HasBit(avail_railtypes, rt))); + list.push_back(MakeDropDownListBadgeItem(badge_class_list, rti->badges, GSF_RAILTYPES, rti->introduction_date, rti->strings.replace_text, rt, !HasBit(avail_railtypes, rt))); } else { StringID str = rti->max_speed > 0 ? STR_TOOLBAR_RAILTYPE_VELOCITY : STR_JUST_STRING; - list.push_back(MakeDropDownListIconItem(d, rti->gui_sprites.build_x_rail, PAL_NONE, str, rt, !HasBit(avail_railtypes, rt))); + list.push_back(MakeDropDownListBadgeIconItem(badge_class_list, rti->badges, GSF_RAILTYPES, rti->introduction_date, d, rti->gui_sprites.build_x_rail, PAL_NONE, str, rt, !HasBit(avail_railtypes, rt))); } } diff --git a/src/road.h b/src/road.h index bb301d2137..ea9f67393a 100644 --- a/src/road.h +++ b/src/road.h @@ -17,6 +17,7 @@ #include "timer/timer_game_calendar.h" #include "core/enum_type.hpp" #include "newgrf.h" +#include "newgrf_badge_type.h" #include "economy_func.h" @@ -181,6 +182,8 @@ public: */ const SpriteGroup *group[ROTSG_END]; + std::vector badges; + inline bool UsesOverlay() const { return this->group[ROTSG_GROUND] != nullptr; diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 9410035b52..5700bc1426 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -39,6 +39,7 @@ #include "waypoint_cmd.h" #include "road_cmd.h" #include "tunnelbridge_cmd.h" +#include "newgrf_badge.h" #include "newgrf_roadstop.h" #include "picker_gui.h" #include "timer/timer.h" @@ -1184,6 +1185,8 @@ class RoadStopPickerCallbacks : public PickerCallbacksNewGRFClass public: RoadStopPickerCallbacks(const std::string &ini_group) : PickerCallbacksNewGRFClass(ini_group) {} + GrfSpecFeature GetFeature() const override { return GSF_ROADSTOPS; } + StringID GetClassTooltip() const override; StringID GetTypeTooltip() const override; @@ -1231,6 +1234,14 @@ public: return (spec == nullptr) ? STR_STATION_CLASS_DFLT_ROADSTOP : spec->name; } + std::span GetTypeBadges(int cls_id, int id) const override + { + const auto *spec = this->GetSpec(cls_id, id); + if (!IsRoadStopEverAvailable(spec, roadstoptype == RoadStopType::Bus ? StationType::Bus : StationType::Truck)) return {}; + if (spec == nullptr) return {}; + return spec->badges; + } + bool IsTypeAvailable(int cls_id, int id) const override { const auto *spec = this->GetSpec(cls_id, id); @@ -1347,6 +1358,8 @@ public: void OnPaint() override { + const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui.sel_class)->GetSpec(_roadstop_gui.sel_type); + this->DrawWidgets(); int rad = _settings_game.station.modified_catchment ? ((this->window_class == WC_BUS_STATION) ? CA_BUS : CA_TRUCK) : CA_UNMODIFIED; @@ -1363,6 +1376,7 @@ public: Rect r = this->GetWidget(WID_BROS_ACCEPTANCE)->GetCurrentRect(); const int bottom = r.bottom; r.bottom = INT_MAX; // Allow overflow as we want to know the required height. + if (spec != nullptr) r.top = DrawBadgeNameList(r, spec->badges, GSF_ROADSTOPS); r.top = DrawStationCoverageAreaText(r, sct, rad, false) + WidgetDimensions::scaled.vsep_normal; r.top = DrawStationCoverageAreaText(r, sct, rad, true); /* Resize background if the window is too small. @@ -1603,6 +1617,8 @@ class RoadWaypointPickerCallbacks : public PickerCallbacksNewGRFClass("fav_road_waypoints") {} + GrfSpecFeature GetFeature() const override { return GSF_ROADSTOPS; } + StringID GetClassTooltip() const override { return STR_PICKER_WAYPOINT_CLASS_TOOLTIP; } StringID GetTypeTooltip() const override { return STR_PICKER_WAYPOINT_TYPE_TOOLTIP; } @@ -1642,6 +1658,13 @@ public: return (spec == nullptr) ? STR_STATION_CLASS_WAYP_WAYPOINT : spec->name; } + std::span GetTypeBadges(int cls_id, int id) const override + { + const auto *spec = this->GetSpec(cls_id, id); + if (spec == nullptr) return {}; + return spec->badges; + } + bool IsTypeAvailable(int cls_id, int id) const override { return IsRoadStopAvailable(this->GetSpec(cls_id, id), StationType::RoadWaypoint); @@ -1765,6 +1788,9 @@ DropDownList GetRoadTypeDropDownList(RoadTramTypes rtts, bool for_replacement, b } } + /* Shared list so that each item can take ownership. */ + auto badge_class_list = std::make_shared(GSF_ROADTYPES); + for (const auto &rt : _sorted_roadtypes) { /* If it's not used ever, don't show it to the user. */ if (!HasBit(used_roadtypes, rt)) continue; @@ -1774,10 +1800,10 @@ DropDownList GetRoadTypeDropDownList(RoadTramTypes rtts, bool for_replacement, b SetDParam(0, rti->strings.menu_text); SetDParam(1, rti->max_speed / 2); if (for_replacement) { - list.push_back(MakeDropDownListStringItem(rti->strings.replace_text, rt, !HasBit(avail_roadtypes, rt))); + list.push_back(MakeDropDownListBadgeItem(badge_class_list, rti->badges, GSF_ROADTYPES, rti->introduction_date, rti->strings.replace_text, rt, !HasBit(avail_roadtypes, rt))); } else { StringID str = rti->max_speed > 0 ? STR_TOOLBAR_RAILTYPE_VELOCITY : STR_JUST_STRING; - list.push_back(MakeDropDownListIconItem(d, rti->gui_sprites.build_x_road, PAL_NONE, str, rt, !HasBit(avail_roadtypes, rt))); + list.push_back(MakeDropDownListBadgeIconItem(badge_class_list, rti->badges, GSF_ROADTYPES, rti->introduction_date, d, rti->gui_sprites.build_x_road, PAL_NONE, str, rt, !HasBit(avail_roadtypes, rt))); } } @@ -1808,6 +1834,10 @@ DropDownList GetScenRoadTypeDropDownList(RoadTramTypes rtts) const RoadTypeInfo *rti = GetRoadTypeInfo(rt); d = maxdim(d, GetSpriteSize(rti->gui_sprites.build_x_road)); } + + /* Shared list so that each item can take ownership. */ + auto badge_class_list = std::make_shared(GSF_ROADTYPES); + for (const auto &rt : _sorted_roadtypes) { if (!HasBit(used_roadtypes, rt)) continue; @@ -1816,7 +1846,7 @@ DropDownList GetScenRoadTypeDropDownList(RoadTramTypes rtts) SetDParam(0, rti->strings.menu_text); SetDParam(1, rti->max_speed / 2); StringID str = rti->max_speed > 0 ? STR_TOOLBAR_RAILTYPE_VELOCITY : STR_JUST_STRING; - list.push_back(MakeDropDownListIconItem(d, rti->gui_sprites.build_x_road, PAL_NONE, str, rt, !HasBit(avail_roadtypes, rt))); + list.push_back(MakeDropDownListBadgeIconItem(badge_class_list, rti->badges, GSF_ROADTYPES, rti->introduction_date, d, rti->gui_sprites.build_x_road, PAL_NONE, str, rt, !HasBit(avail_roadtypes, rt))); } if (list.empty()) { diff --git a/src/table/airport_defaults.h b/src/table/airport_defaults.h index b683f1210a..1212046f9e 100644 --- a/src/table/airport_defaults.h +++ b/src/table/airport_defaults.h @@ -378,7 +378,7 @@ static const std::initializer_list _tile_table_helistation = /** General AirportSpec definition. */ #define AS_GENERIC(fsm, layouts, depots, size_x, size_y, noise, catchment, min_year, max_year, maint_cost, ttdpatch_type, class_id, name, preview, enabled) \ - {{class_id, 0}, fsm, layouts, depots, size_x, size_y, noise, catchment, TimerGameCalendar::Year{min_year}, TimerGameCalendar::Year{max_year}, name, ttdpatch_type, preview, maint_cost, enabled, GRFFileProps(AT_INVALID)} + {{class_id, 0}, fsm, layouts, depots, size_x, size_y, noise, catchment, TimerGameCalendar::Year{min_year}, TimerGameCalendar::Year{max_year}, name, ttdpatch_type, preview, maint_cost, enabled, GRFFileProps(AT_INVALID), {}} /** AirportSpec definition for airports without any depot. */ #define AS_ND(ap_name, size_x, size_y, min_year, max_year, catchment, noise, maint_cost, ttdpatch_type, class_id, name, preview) \ diff --git a/src/table/airporttiles.h b/src/table/airporttiles.h index ed4bc799ab..1776ea0505 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, ANIM_STATUS_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 {{0, ANIM_STATUS_NO_ANIMATION, 2, 0}, 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 325afdcc01..258c368170 100644 --- a/src/table/build_industry.h +++ b/src/table/build_industry.h @@ -1133,7 +1133,7 @@ enum IndustryTypes : uint8_t { {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}, \ {{im1, 0}, {im2, 0}, {im3, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}, \ pr, clim, bev, col, in, intx, s1, s2, s3, STR_UNDEFINED, {ai1, ai2, ai3, ai4}, {ag1, ag2, ag3, ag4}, \ - IndustryCallbackMasks{}, true, GRFFileProps(IT_INVALID), snd, \ + IndustryCallbackMasks{}, true, GRFFileProps(IT_INVALID), snd, {}, \ {{p1, p2}}, {{a1, a2, a3}}} /* Format: tile table count and sounds table @@ -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{}, {0, ANIM_STATUS_NO_ANIMATION, 2, 0}, IndustryTileSpecialFlags{}, true, GRFFileProps(INVALID_INDUSTRYTILE), {}, {c1, c2, c3} \ } static const IndustryTileSpec _origin_industry_tile_specs[NEW_INDUSTRYTILEOFFSET] = { /* Coal Mine */ diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index 86a119bcf7..cb042e7974 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -724,5 +724,6 @@ static const NIFeature * const _nifeatures[] = { &_nif_tramtype, // GSF_TRAMTYPES &_nif_roadstop, // GSF_ROADSTOPS &_nif_town, // GSF_FAKE_TOWNS + nullptr, }; static_assert(lengthof(_nifeatures) == GSF_FAKE_END); diff --git a/src/table/object_land.h b/src/table/object_land.h index 37da639dc3..39574a82c6 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>{}, {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, {}} /* Climates * T = Temperate diff --git a/src/table/railtypes.h b/src/table/railtypes.h index 4ed5b33921..a4f80ca302 100644 --- a/src/table/railtypes.h +++ b/src/table/railtypes.h @@ -112,6 +112,7 @@ static const RailTypeInfo _original_railtypes[] = { { nullptr }, { nullptr }, + {}, }, /** Electrified railway */ @@ -213,6 +214,7 @@ static const RailTypeInfo _original_railtypes[] = { { nullptr }, { nullptr }, + {}, }, /** Monorail */ @@ -310,6 +312,7 @@ static const RailTypeInfo _original_railtypes[] = { { nullptr }, { nullptr }, + {}, }, /** Maglev */ @@ -407,6 +410,7 @@ static const RailTypeInfo _original_railtypes[] = { { nullptr }, { nullptr }, + {}, }, }; diff --git a/src/table/roadtypes.h b/src/table/roadtypes.h index 57c1156cfa..99553bbf2e 100644 --- a/src/table/roadtypes.h +++ b/src/table/roadtypes.h @@ -95,6 +95,7 @@ static const RoadTypeInfo _original_roadtypes[] = { { nullptr }, { nullptr }, + {}, }, /* Electrified Tram */ @@ -175,6 +176,7 @@ static const RoadTypeInfo _original_roadtypes[] = { { nullptr }, { nullptr }, + {}, }, }; diff --git a/src/table/town_land.h b/src/table/town_land.h index aa744d8802..7b2f8c4b8e 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, 2, 0, 0}, 0, 0, 0, {}, {cg1, cg2, cg3}, } /** House specifications from original data */ extern const HouseSpec _original_house_specs[] = { /** diff --git a/src/town_gui.cpp b/src/town_gui.cpp index d04c045304..72e1ddd2f0 100644 --- a/src/town_gui.cpp +++ b/src/town_gui.cpp @@ -1524,6 +1524,8 @@ public: STR_HOUSE_PICKER_CLASS_ZONE5, }; + GrfSpecFeature GetFeature() const override { return GSF_HOUSES; } + StringID GetClassTooltip() const override { return STR_PICKER_HOUSE_CLASS_TOOLTIP; } StringID GetTypeTooltip() const override { return STR_PICKER_HOUSE_TYPE_TOOLTIP; } bool IsActive() const override { return true; } @@ -1574,6 +1576,21 @@ public: return GetHouseName(spec); } + std::span GetTypeBadges(int cls_id, int id) const override + { + const auto *spec = HouseSpec::Get(id); + if (spec == nullptr) return {}; + if (!spec->enabled) return {}; + if ((spec->building_availability & climate_mask) == 0) return {}; + if (!HasBit(spec->building_availability, cls_id)) return {}; + for (int i = 0; i < cls_id; i++) { + /* Don't include if it's already included in an earlier zone. */ + if (HasBit(spec->building_availability, i)) return {}; + } + + return spec->badges; + } + bool IsTypeAvailable(int, int id) const override { const HouseSpec *hs = HouseSpec::Get(id);