From 6a10da15ca4a55d5b1c62c7b5db67c76d66db4e5 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 26 Jun 2021 11:12:20 +0200 Subject: [PATCH] Allow filtering the vehicle list by station or cargo (#997) --- CHANGELOG.md | 1 + data/language/en-GB.yml | 9 + src/OpenLoco/Localisation/StringIds.h | 9 + src/OpenLoco/Windows/VehicleList.cpp | 259 ++++++++++++++++++++++++-- 4 files changed, 267 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f46fcfca..1aa18d1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ 21.05+ (???) ------------------------------------------------------------------------ +- Feature: [#856] Allow filtering the vehicle list by station or cargo type. - Fix: [#982] Incorrect rating calculation for cargo causing penalty for fast vehicles. - Fix: [#1008] Inability to decrease max altitude for trees in landscape editor. - Fix: [#1016] Incorrect detection of station causing incorrect smoke sounds. diff --git a/data/language/en-GB.yml b/data/language/en-GB.yml index 674a3a76..a24f57d7 100644 --- a/data/language/en-GB.yml +++ b/data/language/en-GB.yml @@ -2261,3 +2261,12 @@ strings: 2206: "+10% everywhere" 2207: "Min everywhere" 2208: "Max everywhere" + 2209: "All vehicles" + 2210: "Stopping at station" + 2211: "Transporting cargo" + 2212: "{MOVE_X 10}Carrying {STRINGID} {SPRITE}" + 2213: "ยป{MOVE_X 10}Carrying {STRINGID} {SPRITE}" + 2214: "No station selected" + 2215: "No cargo type selected" + 2216: "{SMALLFONT}{COLOUR BLACK}Open a station window to filter by station" + 2217: "{SMALLFONT}{COLOUR BLACK}Select a cargo type from the list of available cargo" diff --git a/src/OpenLoco/Localisation/StringIds.h b/src/OpenLoco/Localisation/StringIds.h index 5eeceec1..f90139ba 100644 --- a/src/OpenLoco/Localisation/StringIds.h +++ b/src/OpenLoco/Localisation/StringIds.h @@ -1606,4 +1606,13 @@ namespace OpenLoco::StringIds constexpr string_id cheat_ratings_plus_10pct = 2206; constexpr string_id cheat_ratings_to_min = 2207; constexpr string_id cheat_ratings_to_max = 2208; + constexpr string_id all_vehicles = 2209; + constexpr string_id stopping_at_station = 2210; + constexpr string_id transporting_cargo = 2211; + constexpr string_id carrying_cargoid_sprite = 2212; + constexpr string_id carrying_cargoid_sprite_selected = 2213; + constexpr string_id no_station_selected = 2214; + constexpr string_id no_cargo_selected = 2215; + constexpr string_id tooltip_open_station_window_to_filter = 2216; + constexpr string_id tooltip_select_cargo_type = 2217; } diff --git a/src/OpenLoco/Windows/VehicleList.cpp b/src/OpenLoco/Windows/VehicleList.cpp index 57a72596..f9834a1a 100644 --- a/src/OpenLoco/Windows/VehicleList.cpp +++ b/src/OpenLoco/Windows/VehicleList.cpp @@ -7,6 +7,7 @@ #include "../Interop/Interop.hpp" #include "../Localisation/FormatArguments.hpp" #include "../Localisation/StringIds.h" +#include "../Objects/CargoObject.h" #include "../Objects/CompetitorObject.h" #include "../Objects/InterfaceSkinObject.h" #include "../OpenLoco.h" @@ -50,6 +51,10 @@ namespace OpenLoco::Ui::Windows::VehicleList sort_age, sort_reliability, scrollview, + filter_type, + filter_type_btn, + cargo_type, + cargo_type_btn, }; Widget _widgets[] = { @@ -69,11 +74,17 @@ namespace OpenLoco::Ui::Windows::VehicleList makeWidget({ 414, 43 }, { 65, 12 }, WidgetType::wt_14, WindowColour::secondary, StringIds::null, StringIds::tooltip_sort_by_age), makeWidget({ 479, 43 }, { 67, 12 }, WidgetType::wt_14, WindowColour::secondary, StringIds::null, StringIds::tooltip_sort_by_reliability), makeWidget({ 3, 56 }, { 544, 138 }, WidgetType::scrollview, WindowColour::secondary, Scrollbars::vertical), + makeDropdownWidgets({ 280 - 16, 200 }, { 120, 12 }, WidgetType::wt_18, WindowColour::secondary, StringIds::empty), + makeDropdownWidgets({ 402 - 16, 200 }, { 150, 12 }, WidgetType::wt_18, WindowColour::secondary, StringIds::empty), widgetEnd() }; + // clang-format off constexpr uint16_t _tabWidgets = (1 << Widx::tab_trains) | (1 << Widx::tab_buses) | (1 << Widx::tab_trucks) | (1 << Widx::tab_trams) | (1 << Widx::tab_aircraft) | (1 << Widx::tab_ships); - constexpr uint64_t _enabledWidgets = (1 << Widx::close_button) | _tabWidgets | (1 << Widx::company_select) | (1 << Widx::sort_name) | (1 << Widx::sort_profit) | (1 << Widx::sort_age) | (1 << Widx::sort_reliability) | (1 << Widx::scrollview); + constexpr uint64_t _enabledWidgets = (1ULL << Widx::close_button) | _tabWidgets | (1ULL << Widx::company_select) | + (1ULL << Widx::sort_name) | (1ULL << Widx::sort_profit) | (1ULL << Widx::sort_age) | (1ULL << Widx::sort_reliability) | + (1ULL << Widx::scrollview) | (1ULL << Widx::filter_type) | (1ULL << Widx::filter_type_btn) | (1ULL << Widx::cargo_type) | (1ULL << Widx::cargo_type_btn); + // clang-format on enum SortMode : uint16_t { @@ -83,6 +94,13 @@ namespace OpenLoco::Ui::Windows::VehicleList Reliability, }; + enum FilterMode : uint8_t + { + allVehicles, + stoppingAt, + transportingCargo, + }; + static const uint8_t row_heights[] = { 28, 28, @@ -95,9 +113,74 @@ namespace OpenLoco::Ui::Windows::VehicleList static Widx getTabFromType(VehicleType type); static void initEvents(); + constexpr bool isStationFilterActive(const Window* self, bool checkSelection = true) + { + return self->var_88A == static_cast(FilterMode::stoppingAt) && (!checkSelection || self->var_88C != -1); + } + + constexpr bool isCargoFilterActive(const Window* self, bool checkSelection = true) + { + return self->var_88A == static_cast(FilterMode::transportingCargo) && (!checkSelection || self->var_88C != -1); + } + + static bool refreshActiveStation(Window* self) + { + if (!isStationFilterActive(self, false)) + return false; + + auto stationWindow = WindowManager::find(WindowType::station); + if (stationWindow != nullptr) + { + self->var_88C = stationWindow->number; + return true; + } + else + { + self->var_88C = -1; + return false; + } + } + + using Vehicles::VehicleHead; + + static bool vehicleStopsAtActiveStation(const VehicleHead* head, StationId_t filterStationId) + { + auto orders = Vehicles::OrderRingView(head->orderTableOffset); + for (auto& order : orders) + { + auto* stationOrder = order.as(); + if (stationOrder == nullptr) + continue; + + const auto stationId = stationOrder->getStation(); + if (stationId == filterStationId) + return true; + } + return false; + } + + static bool vehicleIsTransportingCargo(const VehicleHead* head, int16_t filterCargoId) + { + auto orders = Vehicles::OrderRingView(head->orderTableOffset); + for (auto& order : orders) + { + Vehicles::OrderCargo* cargoOrder = order.as(); + if (cargoOrder == nullptr) + cargoOrder = order.as(); + if (cargoOrder == nullptr) + continue; + + const auto cargoId = cargoOrder->getCargo(); + if (cargoId == filterCargoId) + return true; + } + return false; + } + // 0x004C1D4F static void refreshVehicleList(Window* self) { + refreshActiveStation(self); self->row_count = 0; for (auto vehicle : EntityManager::VehicleList()) { @@ -107,12 +190,16 @@ namespace OpenLoco::Ui::Windows::VehicleList if (vehicle->owner != self->number) continue; + if (isStationFilterActive(self) && !vehicleStopsAtActiveStation(vehicle, self->var_88C)) + continue; + + if (isCargoFilterActive(self) && !vehicleIsTransportingCargo(vehicle, self->var_88C)) + continue; + vehicle->var_0C &= ~Vehicles::Flags0C::sorted; } } - using Vehicles::VehicleHead; - // 0x004C1E4F static bool orderByName(const VehicleHead& lhs, const VehicleHead& rhs) { @@ -190,6 +277,12 @@ namespace OpenLoco::Ui::Windows::VehicleList if (vehicle->var_0C & Vehicles::Flags0C::sorted) continue; + if (isStationFilterActive(self) && !vehicleStopsAtActiveStation(vehicle, self->var_88C)) + continue; + + if (isCargoFilterActive(self) && !vehicleIsTransportingCargo(vehicle, self->var_88C)) + continue; + if (insertId == -1) { insertId = vehicle->id; @@ -363,6 +456,8 @@ namespace OpenLoco::Ui::Windows::VehicleList self->sort_mode = 0; self->var_83C = 0; self->row_hover = -1; + self->var_88A = static_cast(FilterMode::allVehicles); + self->var_88C = -1; refreshVehicleList(self); @@ -474,6 +569,33 @@ namespace OpenLoco::Ui::Windows::VehicleList self->widgets[Widx::sort_age].text = self->sort_mode == SortMode::Age ? StringIds::table_header_age_desc : StringIds::table_header_age; self->widgets[Widx::sort_reliability].text = self->sort_mode == SortMode::Reliability ? StringIds::table_header_reliability_desc : StringIds::table_header_reliability; + // Reposition filter dropdowns + self->widgets[Widx::filter_type].top = self->height - 13; + self->widgets[Widx::filter_type].bottom = self->height - 2; + + self->widgets[Widx::filter_type_btn].top = self->height - 12; + self->widgets[Widx::filter_type_btn].bottom = self->height - 3; + + self->widgets[Widx::cargo_type].top = self->height - 13; + self->widgets[Widx::cargo_type].bottom = self->height - 2; + + self->widgets[Widx::cargo_type_btn].top = self->height - 12; + self->widgets[Widx::cargo_type_btn].bottom = self->height - 3; + + // Disable cargo dropdown if not applicable + if (self->var_88A != FilterMode::transportingCargo) + self->disabled_widgets |= (1 << Widx::cargo_type) | (1 << Widx::cargo_type_btn); + else + self->disabled_widgets &= ~((1 << Widx::cargo_type) | (1 << Widx::cargo_type_btn)); + + // Set appropriate tooltip + static constexpr std::array filterTooltipByType = { + StringIds::null, + StringIds::tooltip_open_station_window_to_filter, + StringIds::tooltip_select_cargo_type, + }; + self->widgets[Widx::cargo_type_btn].tooltip = filterTooltipByType[self->var_88A]; + setTransportTypeTabs(self); } @@ -500,11 +622,71 @@ namespace OpenLoco::Ui::Windows::VehicleList { StringIds::num_ships_singular, StringIds::num_ships_plural }, }; - auto& footerStringPair = typeToFooterStringIds[self->current_tab]; - string_id footerStringId = self->var_83C == 1 ? footerStringPair.first : footerStringPair.second; + FormatArguments args = {}; - auto args = FormatArguments::common(footerStringId, self->var_83C); - Gfx::drawString_494B3F(*context, self->x + 3, self->y + self->height - 13, Colour::black, StringIds::black_stringid, &args); + { + auto& footerStringPair = typeToFooterStringIds[self->current_tab]; + string_id footerStringId = self->var_83C == 1 ? footerStringPair.first : footerStringPair.second; + + args = FormatArguments::common(footerStringId, self->var_83C); + Gfx::drawString_494B3F(*context, self->x + 3, self->y + self->height - 13, Colour::black, StringIds::black_stringid, &args); + } + + static constexpr std::array typeToFilterStringIds{ + StringIds::all_vehicles, + StringIds::stopping_at_station, + StringIds::transporting_cargo, + }; + + { + // Show current filter type + string_id filter = typeToFilterStringIds[self->var_88A]; + args = FormatArguments::common(filter); + auto* widget = &self->widgets[Widx::filter_type]; + Gfx::drawString_494BBF(*context, self->x + widget->left + 1, self->y + widget->top, widget->width() - 15, Colour::black, StringIds::wcolour2_stringid, &args); + } + + auto* widget = &self->widgets[Widx::cargo_type]; + auto xPos = self->x + widget->left + 1; + bool filterActive = false; + + if (isStationFilterActive(self, false)) + { + filterActive = true; + if (self->var_88C != -1) + { + auto station = StationManager::get(self->var_88C); + args = FormatArguments::common(station->name, station->town); + } + else + { + args = FormatArguments::common(StringIds::no_station_selected); + } + } + + else if (isCargoFilterActive(self, false)) + { + filterActive = true; + if (self->var_88C != -1) + { + // Show current cargo + auto cargoObj = ObjectManager::get(self->var_88C); + args = FormatArguments::common(StringIds::carrying_cargoid_sprite, cargoObj->name, cargoObj->unit_inline_sprite); + + // NB: the -9 in the xpos is to compensate for a hack due to the cargo dropdown limitation (only three args per item) + xPos = self->x + widget->left - 9; + } + else + { + args = FormatArguments::common(StringIds::no_cargo_selected); + } + } + + if (filterActive) + { + // Draw filter text as prepared + Gfx::drawString_494BBF(*context, xPos, self->y + widget->top, widget->width() - 15, Colour::black, StringIds::wcolour2_stringid, &args); + } } // 0x004B6D43 @@ -688,14 +870,49 @@ namespace OpenLoco::Ui::Windows::VehicleList { if (widgetIndex == Widx::company_select) Dropdown::populateCompanySelect(self, &self->widgets[widgetIndex]); + + else if (widgetIndex == Widx::filter_type_btn) + { + Widget dropdown = self->widgets[Widx::filter_type]; + Dropdown::show(self->x + dropdown.left, self->y + dropdown.top, dropdown.width() - 4, dropdown.height(), self->getColour(WindowColour::secondary), 3, 0x80); + + Dropdown::add(0, StringIds::dropdown_stringid, StringIds::all_vehicles); + Dropdown::add(1, StringIds::dropdown_stringid, StringIds::stopping_at_station); + Dropdown::add(2, StringIds::dropdown_stringid, StringIds::transporting_cargo); + Dropdown::setItemSelected(self->var_88A); + } + else if (widgetIndex == Widx::cargo_type_btn) + { + auto index = 0; + auto selectedIndex = -1; + for (uint16_t cargoId = 0; cargoId < ObjectManager::getMaxObjects(ObjectType::cargo); ++cargoId) + { + auto cargoObj = ObjectManager::get(cargoId); + if (cargoObj == nullptr) + continue; + + FormatArguments args{}; + args.push(cargoObj->name); + args.push(cargoObj->unit_inline_sprite); + args.push(cargoId); + Dropdown::add(index, StringIds::carrying_cargoid_sprite, args); + + if (index == self->var_88C) + selectedIndex = index; + + index++; + } + + Widget dropdown = self->widgets[Widx::cargo_type]; + Dropdown::showText(self->x + dropdown.left, self->y + dropdown.top, dropdown.width() - 4, dropdown.height(), self->getColour(WindowColour::secondary), index, 0); + if (selectedIndex != -1) + Dropdown::setItemSelected(selectedIndex); + } } // 0x004C243F - static void onDropdown(Ui::Window* self, WidgetIndex_t widgetIndex, int16_t itemIndex) + static void onCompanyDropdown(Ui::Window* self, int16_t itemIndex) { - if (widgetIndex != Widx::company_select) - return; - if (itemIndex == -1) return; @@ -728,6 +945,26 @@ namespace OpenLoco::Ui::Windows::VehicleList self->invalidate(); } + static void onDropdown(Ui::Window* self, WidgetIndex_t widgetIndex, int16_t itemIndex) + { + if (widgetIndex == Widx::company_select) + return onCompanyDropdown(self, itemIndex); + + if (widgetIndex == filter_type_btn && itemIndex != -1) + { + if (self->var_88A != itemIndex) + { + self->var_88A = itemIndex; + self->var_88C = -1; + } + } + + else if (widgetIndex == cargo_type_btn && itemIndex != -1) + { + self->var_88C = Dropdown::getItemArgument(itemIndex, 3); + } + } + // 0x004C24CA static std::optional tooltip(Window* self, WidgetIndex_t widgetIndex) {