OpenRCT2/src/openrct2-ui/windows/EditorObjectSelection.cpp

1616 lines
61 KiB
C++
Raw Normal View History

/*****************************************************************************
* Copyright (c) 2014-2023 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include <algorithm>
2018-01-04 00:36:33 +01:00
#include <cctype>
2018-06-22 23:21:44 +02:00
#include <openrct2-ui/interface/Dropdown.h>
#include <openrct2-ui/interface/Widget.h>
#include <openrct2-ui/windows/Window.h>
#include <openrct2/Context.h>
#include <openrct2/Editor.h>
2017-12-05 13:55:33 +01:00
#include <openrct2/EditorObjectSelectionSession.h>
#include <openrct2/Game.h>
2018-06-22 23:21:44 +02:00
#include <openrct2/OpenRCT2.h>
Split actions hpp files into separate h and cpp files (#13548) * Split up SmallSceneryPlace/Remove Added undo function for Remove Scenery * Refactor: Balloon and Banner actions hpp=>h/cpp * Refactor: rename all action *.hpp files to *.cpp This is preparation for separation in later commits. Note that without the complete set of commits in this branch, the code will not build. * Refactor Clear, Climate, Custom, and Footpath actions hpp=>h/cpp * VSCode: add src subdirectories to includePath * Refactor Guest actions hpp=>h/cpp * Refactor Land actions hpp=>h/cpp * Refactor LargeScenery actions hpp=>h/cpp * Refactor Load, Maze, Network actions hpp=>h/cpp * Refactor Park actions hpp=>h/cpp * Refactor/style: move private function declarations in actions *.h Previous action .h files included private function declarations with private member variables, before public function declarations. This commit re-orders the header files to the following order: - public member variables - private member variables - public functions - private functions * Refactor Pause action hpp=>h/cpp * Refactor Peep, Place, Player actions hpp=>h/cpp * Refactor Ride actions hpp=>h/cpp * Refactor Scenario, Set*, Sign* actions hpp=>h/cpp * Refactor SmallScenerySetColourAction hpp=>h/cpp * Refactor Staff actions hpp=>h/cpp * Refactor Surface, Tile, Track* actions hpp=>h/cpp * Refactor Wall and Water actions hpp=>h/cpp * Fix various includes and other compile errors Update includes for tests. Move static function declarations to .h files Add explicit includes to various files that were previously implicit (the required header was a nested include in an action hpp file, and the action .h file does not include that header) Move RideSetStatus string enum to the cpp file to avoid unused imports * Xcode: modify project file for actions refactor * Cleanup whitespace and end-of-file newlines Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2020-12-10 07:39:10 +01:00
#include <openrct2/actions/LoadOrQuitAction.h>
2018-06-22 23:21:44 +02:00
#include <openrct2/audio/audio.h>
#include <openrct2/config/Config.h>
2022-02-26 17:20:07 +01:00
#include <openrct2/core/Path.hpp>
2018-06-22 23:21:44 +02:00
#include <openrct2/core/String.hpp>
#include <openrct2/drawing/Drawing.h>
2021-12-12 00:06:06 +01:00
#include <openrct2/localisation/Formatter.h>
2018-01-06 18:32:25 +01:00
#include <openrct2/localisation/Localisation.h>
#include <openrct2/object/MusicObject.h>
#include <openrct2/object/ObjectList.h>
#include <openrct2/object/ObjectManager.h>
#include <openrct2/object/ObjectRepository.h>
2017-12-05 13:55:33 +01:00
#include <openrct2/object/RideObject.h>
#include <openrct2/object/SceneryGroupObject.h>
#include <openrct2/platform/Platform.h>
#include <openrct2/ride/RideData.h>
2018-08-29 23:29:45 +02:00
#include <openrct2/scenario/Scenario.h>
#include <openrct2/sprites.h>
2021-07-29 13:32:48 +02:00
#include <openrct2/title/TitleScreen.h>
2017-12-13 13:02:24 +01:00
#include <openrct2/util/Util.h>
#include <openrct2/windows/Intent.h>
2018-06-22 23:21:44 +02:00
#include <string>
#include <vector>
2015-09-10 23:16:41 +02:00
2018-01-31 13:07:20 +01:00
enum
{
FILTER_RCT1 = (1 << 0),
FILTER_AA = (1 << 1),
FILTER_LL = (1 << 2),
FILTER_RCT2 = (1 << 3),
FILTER_WW = (1 << 4),
FILTER_TT = (1 << 5),
FILTER_OO = (1 << 6),
FILTER_CUSTOM = (1 << 7),
FILTER_RIDE_TRANSPORT = (1 << 8),
FILTER_RIDE_GENTLE = (1 << 9),
FILTER_RIDE_COASTER = (1 << 10),
FILTER_RIDE_THRILL = (1 << 11),
FILTER_RIDE_WATER = (1 << 12),
FILTER_RIDE_STALL = (1 << 13),
FILTER_SELECTED = (1 << 14),
FILTER_NONSELECTED = (1 << 15),
FILTER_RIDES = FILTER_RIDE_TRANSPORT | FILTER_RIDE_GENTLE | FILTER_RIDE_COASTER | FILTER_RIDE_THRILL | FILTER_RIDE_WATER
| FILTER_RIDE_STALL,
FILTER_ALL = FILTER_RIDES | FILTER_RCT1 | FILTER_AA | FILTER_LL | FILTER_RCT2 | FILTER_WW | FILTER_TT | FILTER_OO
| FILTER_CUSTOM | FILTER_SELECTED | FILTER_NONSELECTED,
2017-08-02 00:20:32 +02:00
};
enum
{
RIDE_SORT_TYPE,
RIDE_SORT_RIDE
};
enum
{
DDIX_FILTER_RCT1,
DDIX_FILTER_AA,
DDIX_FILTER_LL,
DDIX_FILTER_RCT2,
DDIX_FILTER_WW,
DDIX_FILTER_TT,
DDIX_FILTER_OO,
DDIX_FILTER_CUSTOM,
DDIX_FILTER_SEPARATOR,
DDIX_FILTER_SELECTED,
DDIX_FILTER_NONSELECTED,
};
struct ObjectListItem
{
const ObjectRepositoryItem* repositoryItem;
std::unique_ptr<RideFilters> filter;
uint8_t* flags;
};
static constexpr uint8_t _numSourceGameItems = 8;
2018-01-31 13:07:20 +01:00
static uint32_t _filter_flags;
static uint16_t _filter_object_counts[EnumValue(ObjectType::Count)];
static char _filter_string[MAX_PATH];
#define _FILTER_ALL ((_filter_flags & FILTER_ALL) == FILTER_ALL)
2018-01-31 13:07:20 +01:00
#define _FILTER_RCT1 (_filter_flags & FILTER_RCT1)
#define _FILTER_AA (_filter_flags & FILTER_AA)
#define _FILTER_LL (_filter_flags & FILTER_LL)
#define _FILTER_RCT2 (_filter_flags & FILTER_RCT2)
#define _FILTER_WW (_filter_flags & FILTER_WW)
#define _FILTER_TT (_filter_flags & FILTER_TT)
2018-01-31 13:07:20 +01:00
#define _FILTER_OO (_filter_flags & FILTER_OO)
#define _FILTER_CUSTOM (_filter_flags & FILTER_CUSTOM)
2016-06-21 20:41:07 +02:00
#define _FILTER_SELECTED (_filter_flags & FILTER_SELECTED)
#define _FILTER_NONSELECTED (_filter_flags & FILTER_NONSELECTED)
2022-07-31 14:22:58 +02:00
static constexpr const StringId WINDOW_TITLE = STR_OBJECT_SELECTION;
2020-05-05 22:26:14 +02:00
static constexpr const int32_t WH = 400;
static constexpr const int32_t WW = 755;
struct ObjectPageDesc
{
2022-07-31 14:22:58 +02:00
StringId Caption;
uint32_t Image;
bool IsAdvanced;
};
// clang-format off
// Order of which the object tabs are displayed.
static constexpr const ObjectPageDesc ObjectSelectionPages[] = {
{ STR_OBJECT_SELECTION_RIDE_VEHICLES_ATTRACTIONS, SPR_TAB_RIDE_16, false },
{ STR_OBJECT_SELECTION_STATIONS, SPR_G2_RIDE_STATION_TAB, true },
{ STR_OBJECT_SELECTION_MUSIC, SPR_TAB_MUSIC_0, true },
{ STR_OBJECT_SELECTION_SCENERY_GROUPS, SPR_TAB_SCENERY_STATUES, false },
{ STR_OBJECT_SELECTION_SMALL_SCENERY, SPR_TAB_SCENERY_TREES, true },
{ STR_OBJECT_SELECTION_LARGE_SCENERY, SPR_TAB_SCENERY_URBAN, true },
{ STR_OBJECT_SELECTION_WALLS_FENCES, SPR_TAB_SCENERY_WALLS, true },
{ STR_OBJECT_SELECTION_FOOTPATH_SURFACES, SPR_G2_PATH_SURFACE_TAB, false },
{ STR_OBJECT_SELECTION_FOOTPATH_RAILINGS, SPR_G2_PATH_RAILINGS_TAB, false },
{ STR_OBJECT_SELECTION_FOOTPATHS, SPR_G2_LEGACY_PATH_TAB, true },
{ STR_OBJECT_SELECTION_PATH_EXTRAS, SPR_TAB_SCENERY_PATH_ITEMS, false },
{ STR_OBJECT_SELECTION_PATH_SIGNS, SPR_TAB_SCENERY_SIGNAGE, true },
{ STR_OBJECT_SELECTION_PARK_ENTRANCE, SPR_TAB_PARK, false },
{ STR_OBJECT_SELECTION_TERRAIN_SURFACES, SPR_G2_TAB_LAND, true },
{ STR_OBJECT_SELECTION_TERRAIN_EDGES, SPR_G2_TERRAIN_EDGE_TAB, true },
{ STR_OBJECT_SELECTION_WATER, SPR_TAB_WATER, false },
};
// clang-format on
// Order of which the contents of each tab is displayed.
ObjectType static TabOrder[] = {
ObjectType::Ride, ObjectType::Station, ObjectType::Music,
ObjectType::SceneryGroup, ObjectType::SmallScenery, ObjectType::LargeScenery,
ObjectType::Walls, ObjectType::FootpathSurface, ObjectType::FootpathRailings,
ObjectType::Paths, ObjectType::PathBits, ObjectType::Banners,
ObjectType::ParkEntrance, ObjectType::TerrainSurface, ObjectType::TerrainEdge,
ObjectType::Water,
};
#pragma region Widgets
enum WINDOW_EDITOR_OBJECT_SELECTION_WIDGET_IDX
{
WIDX_BACKGROUND,
WIDX_TITLE,
WIDX_CLOSE,
WIDX_TAB_CONTENT_PANEL,
WIDX_ADVANCED,
WIDX_LIST,
WIDX_PREVIEW,
WIDX_INSTALL_TRACK,
WIDX_FILTER_DROPDOWN,
WIDX_FILTER_TEXT_BOX,
WIDX_FILTER_CLEAR_BUTTON,
WIDX_FILTER_RIDE_TAB_FRAME,
WIDX_FILTER_RIDE_TAB_ALL,
WIDX_FILTER_RIDE_TAB_TRANSPORT,
WIDX_FILTER_RIDE_TAB_GENTLE,
WIDX_FILTER_RIDE_TAB_COASTER,
WIDX_FILTER_RIDE_TAB_THRILL,
WIDX_FILTER_RIDE_TAB_WATER,
WIDX_FILTER_RIDE_TAB_STALL,
WIDX_LIST_SORT_TYPE,
WIDX_LIST_SORT_RIDE,
WIDX_TAB_1,
};
validate_global_widx(WC_EDITOR_OBJECT_SELECTION, WIDX_TAB_1);
static bool _window_editor_object_selection_widgets_initialised;
// clang-format off
2022-12-24 16:50:29 +01:00
static std::vector<Widget> _window_editor_object_selection_widgets = {
2020-05-09 16:44:21 +02:00
WINDOW_SHIM(WINDOW_TITLE, WW, WH),
2021-11-14 17:31:01 +01:00
MakeWidget({ 0, 43}, {WW, 357}, WindowWidgetType::Resize, WindowColour::Secondary ),
MakeWidget({470, 22}, {122, 14}, WindowWidgetType::Button, WindowColour::Primary, STR_OBJECT_SELECTION_ADVANCED, STR_OBJECT_SELECTION_ADVANCED_TIP),
MakeWidget({ 4, 60}, {288, 327}, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_VERTICAL ),
MakeWidget({391, 45}, {114, 114}, WindowWidgetType::FlatBtn, WindowColour::Secondary ),
MakeWidget({470, 22}, {122, 14}, WindowWidgetType::Button, WindowColour::Primary, STR_INSTALL_NEW_TRACK_DESIGN, STR_INSTALL_NEW_TRACK_DESIGN_TIP ),
MakeWidget({350, 22}, {114, 14}, WindowWidgetType::Button, WindowColour::Primary, STR_OBJECT_FILTER, STR_OBJECT_FILTER_TIP ),
MakeWidget({ 4, 45}, {211, 14}, WindowWidgetType::TextBox, WindowColour::Secondary ),
MakeWidget({218, 45}, { 70, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_OBJECT_SEARCH_CLEAR ),
MakeWidget({ 3, 73}, {285, 4}, WindowWidgetType::ImgBtn, WindowColour::Secondary ),
MakeTab ({ 3, 47}, STR_OBJECT_FILTER_ALL_RIDES_TIP ),
MakeTab ({ 34, 47}, STR_TRANSPORT_RIDES_TIP ),
MakeTab ({ 65, 47}, STR_GENTLE_RIDES_TIP ),
MakeTab ({ 96, 47}, STR_ROLLER_COASTERS_TIP ),
MakeTab ({127, 47}, STR_THRILL_RIDES_TIP ),
MakeTab ({158, 47}, STR_WATER_RIDES_TIP ),
MakeTab ({189, 47}, STR_SHOPS_STALLS_TIP ),
MakeWidget({ 4, 80}, {145, 14}, WindowWidgetType::TableHeader, WindowColour::Secondary ),
MakeWidget({149, 80}, {143, 14}, WindowWidgetType::TableHeader, WindowColour::Secondary ),
MakeTab ({ 3, 17}, STR_STRING_DEFINED_TOOLTIP ),
// Copied object type times...
2021-09-26 11:11:42 +02:00
WIDGETS_END,
};
// clang-format on
#pragma endregion
static constexpr const int32_t window_editor_object_selection_animation_loops[] = {
20, // All
32, // Transport
10, // Gentle
72, // Coaster
24, // Thrill
28, // Water
16, // Stall
};
static constexpr const int32_t window_editor_object_selection_animation_divisor[] = {
4, // All
8, // Transport
2, // Gentle
4, // Coaster
4, // Thrill
4, // Water
2, // Stall
};
2022-07-31 14:22:58 +02:00
static StringId GetRideTypeStringId(const ObjectRepositoryItem* item);
static bool VisibleListSortRideType(const ObjectListItem& a, const ObjectListItem& b);
static bool VisibleListSortRideName(const ObjectListItem& a, const ObjectListItem& b);
void EditorLoadSelectedObjects();
2017-12-05 13:55:33 +01:00
class EditorObjectSelectionWindow final : public Window
2018-06-22 23:21:44 +02:00
{
using sortFunc_t = bool (*)(const ObjectListItem&, const ObjectListItem&);
private:
std::vector<ObjectListItem> _listItems;
int32_t _listSortType = RIDE_SORT_TYPE;
bool _listSortDescending = false;
std::unique_ptr<Object> _loadedObject;
public:
/**
*
* rct2: 0x006AA64E
*/
void OnOpen() override
{
InitWidgets();
2023-01-17 19:05:14 +01:00
Sub6AB211();
ResetSelectedObjectCountAndSize();
widgets[WIDX_FILTER_TEXT_BOX].string = _filter_string;
_filter_flags = gConfigInterface.ObjectSelectionFilterFlags;
std::fill_n(_filter_string, sizeof(_filter_string), 0x00);
WindowInitScrollWidgets(*this);
selected_tab = 0;
selected_list_item = -1;
min_width = WW;
min_height = WH;
max_width = 1200;
max_height = 1000;
_listSortType = RIDE_SORT_TYPE;
_listSortDescending = false;
VisibleListRefresh();
}
/**
*
* rct2: 0x006AB199
*/
void OnClose() override
{
UnloadUnselectedObjects();
EditorLoadSelectedObjects();
EditorObjectFlagsFree();
if (_loadedObject != nullptr)
_loadedObject->Unload();
if (gScreenFlags & SCREEN_FLAGS_EDITOR)
2018-06-22 23:21:44 +02:00
{
ResearchPopulateListRandom();
}
else
{
// Used for in-game object selection cheat
// This resets the ride selection list and resets research to 0 on current item
gSilentResearch = true;
ResearchResetCurrentItem();
gSilentResearch = false;
}
auto intent = Intent(INTENT_ACTION_REFRESH_NEW_RIDES);
2022-11-06 21:49:07 +01:00
ContextBroadcastIntent(&intent);
VisibleListDispose();
intent = Intent(INTENT_ACTION_REFRESH_SCENERY);
2022-11-06 21:49:07 +01:00
ContextBroadcastIntent(&intent);
}
void OnUpdate() override
{
if (gCurrentTextBox.window.classification == classification && gCurrentTextBox.window.number == number)
{
WindowUpdateTextboxCaret();
WidgetInvalidate(*this, WIDX_FILTER_TEXT_BOX);
}
2022-08-21 18:49:23 +02:00
for (WidgetIndex i = WIDX_FILTER_RIDE_TAB_TRANSPORT; i <= WIDX_FILTER_RIDE_TAB_STALL; i++)
{
if (!IsWidgetPressed(i))
continue;
frame_no++;
if (frame_no >= window_editor_object_selection_animation_loops[i - WIDX_FILTER_RIDE_TAB_TRANSPORT])
frame_no = 0;
WidgetInvalidate(*this, i);
break;
}
}
/**
*
* rct2: 0x006AAFAB
*/
2022-08-21 18:49:23 +02:00
void OnMouseUp(WidgetIndex widgetIndex) override
{
switch (widgetIndex)
{
case WIDX_CLOSE:
WindowClose(*this);
if (gScreenFlags & SCREEN_FLAGS_EDITOR)
{
FinishObjectSelection();
}
if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)
{
GameNotifyMapChange();
GameUnloadScripts();
TitleLoad();
}
break;
case WIDX_FILTER_RIDE_TAB_ALL:
_filter_flags |= FILTER_RIDES;
gConfigInterface.ObjectSelectionFilterFlags = _filter_flags;
ConfigSaveDefault();
FilterUpdateCounts();
VisibleListRefresh();
selected_list_item = -1;
scrolls[0].v_top = 0;
Invalidate();
break;
case WIDX_FILTER_RIDE_TAB_TRANSPORT:
case WIDX_FILTER_RIDE_TAB_GENTLE:
case WIDX_FILTER_RIDE_TAB_COASTER:
case WIDX_FILTER_RIDE_TAB_THRILL:
case WIDX_FILTER_RIDE_TAB_WATER:
case WIDX_FILTER_RIDE_TAB_STALL:
_filter_flags &= ~FILTER_RIDES;
_filter_flags |= (1 << (widgetIndex - WIDX_FILTER_RIDE_TAB_TRANSPORT + _numSourceGameItems));
gConfigInterface.ObjectSelectionFilterFlags = _filter_flags;
ConfigSaveDefault();
FilterUpdateCounts();
VisibleListRefresh();
selected_list_item = -1;
scrolls[0].v_top = 0;
frame_no = 0;
Invalidate();
break;
case WIDX_ADVANCED:
list_information_type ^= 1;
Invalidate();
break;
case WIDX_INSTALL_TRACK:
{
if (selected_list_item != -1)
{
selected_list_item = -1;
}
Invalidate();
auto intent = Intent(WindowClass::Loadsave);
intent.PutExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_LOAD | LOADSAVETYPE_TRACK);
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
break;
}
case WIDX_FILTER_TEXT_BOX:
WindowStartTextbox(*this, widgetIndex, STR_STRING, _filter_string, sizeof(_filter_string));
break;
case WIDX_FILTER_CLEAR_BUTTON:
std::fill_n(_filter_string, sizeof(_filter_string), 0x00);
FilterUpdateCounts();
scrolls->v_top = 0;
VisibleListRefresh();
Invalidate();
break;
case WIDX_LIST_SORT_TYPE:
if (_listSortType == RIDE_SORT_TYPE)
{
_listSortDescending = !_listSortDescending;
}
else
{
_listSortType = RIDE_SORT_TYPE;
_listSortDescending = false;
}
VisibleListRefresh();
break;
case WIDX_LIST_SORT_RIDE:
if (_listSortType == RIDE_SORT_RIDE)
{
_listSortDescending = !_listSortDescending;
}
else
{
_listSortType = RIDE_SORT_RIDE;
_listSortDescending = false;
}
VisibleListRefresh();
break;
default:
if (widgetIndex >= WIDX_TAB_1
&& static_cast<size_t>(widgetIndex) < WIDX_TAB_1 + std::size(ObjectSelectionPages))
{
SetPage(widgetIndex - WIDX_TAB_1);
}
break;
}
}
void OnResize() override
{
WindowSetResize(*this, WW, WH, 1200, 1000);
}
2022-08-21 18:49:23 +02:00
void OnMouseDown(WidgetIndex widgetIndex) override
{
int32_t numSelectionItems = 0;
switch (widgetIndex)
{
case WIDX_FILTER_DROPDOWN:
gDropdownItems[DDIX_FILTER_RCT1].Format = STR_TOGGLE_OPTION;
gDropdownItems[DDIX_FILTER_AA].Format = STR_TOGGLE_OPTION;
gDropdownItems[DDIX_FILTER_LL].Format = STR_TOGGLE_OPTION;
gDropdownItems[DDIX_FILTER_RCT2].Format = STR_TOGGLE_OPTION;
gDropdownItems[DDIX_FILTER_WW].Format = STR_TOGGLE_OPTION;
gDropdownItems[DDIX_FILTER_TT].Format = STR_TOGGLE_OPTION;
gDropdownItems[DDIX_FILTER_OO].Format = STR_TOGGLE_OPTION;
gDropdownItems[DDIX_FILTER_CUSTOM].Format = STR_TOGGLE_OPTION;
gDropdownItems[DDIX_FILTER_RCT1].Args = STR_SCENARIO_CATEGORY_RCT1;
gDropdownItems[DDIX_FILTER_AA].Args = STR_SCENARIO_CATEGORY_RCT1_AA;
gDropdownItems[DDIX_FILTER_LL].Args = STR_SCENARIO_CATEGORY_RCT1_LL;
gDropdownItems[DDIX_FILTER_RCT2].Args = STR_ROLLERCOASTER_TYCOON_2_DROPDOWN;
gDropdownItems[DDIX_FILTER_WW].Args = STR_OBJECT_FILTER_WW;
gDropdownItems[DDIX_FILTER_TT].Args = STR_OBJECT_FILTER_TT;
gDropdownItems[DDIX_FILTER_OO].Args = STR_OBJECT_FILTER_OPENRCT2_OFFICIAL;
gDropdownItems[DDIX_FILTER_CUSTOM].Args = STR_OBJECT_FILTER_CUSTOM;
// Track manager cannot select multiple, so only show selection filters if not in track manager
if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER))
{
numSelectionItems = 3;
gDropdownItems[DDIX_FILTER_SEPARATOR].Format = 0;
gDropdownItems[DDIX_FILTER_SELECTED].Format = STR_TOGGLE_OPTION;
gDropdownItems[DDIX_FILTER_NONSELECTED].Format = STR_TOGGLE_OPTION;
gDropdownItems[DDIX_FILTER_SEPARATOR].Args = STR_NONE;
gDropdownItems[DDIX_FILTER_SELECTED].Args = STR_SELECTED_ONLY;
gDropdownItems[DDIX_FILTER_NONSELECTED].Args = STR_NON_SELECTED_ONLY;
}
WindowDropdownShowText(
{ windowPos.x + widgets[widgetIndex].left, windowPos.y + widgets[widgetIndex].top },
widgets[widgetIndex].height() + 1, colours[widgets[widgetIndex].colour], Dropdown::Flag::StayOpen,
_numSourceGameItems + numSelectionItems);
for (int32_t i = 0; i < _numSourceGameItems; i++)
{
if (_filter_flags & (1 << i))
{
Dropdown::SetChecked(i, true);
}
}
if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER))
{
Dropdown::SetChecked(DDIX_FILTER_SELECTED, _FILTER_SELECTED != 0);
Dropdown::SetChecked(DDIX_FILTER_NONSELECTED, _FILTER_NONSELECTED != 0);
}
break;
}
}
2022-08-21 18:49:23 +02:00
void OnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex) override
2018-06-22 23:21:44 +02:00
{
if (dropdownIndex == -1)
return;
switch (widgetIndex)
{
case WIDX_FILTER_DROPDOWN:
if (dropdownIndex == DDIX_FILTER_SELECTED)
{
_filter_flags ^= FILTER_SELECTED;
_filter_flags &= ~FILTER_NONSELECTED;
}
else if (dropdownIndex == DDIX_FILTER_NONSELECTED)
{
_filter_flags ^= FILTER_NONSELECTED;
_filter_flags &= ~FILTER_SELECTED;
}
else
{
_filter_flags ^= (1 << dropdownIndex);
}
gConfigInterface.ObjectSelectionFilterFlags = _filter_flags;
ConfigSaveDefault();
FilterUpdateCounts();
scrolls->v_top = 0;
VisibleListRefresh();
Invalidate();
break;
}
}
/**
*
* rct2: 0x006AB031
*/
ScreenSize OnScrollGetSize(int32_t scrollIndex) override
2018-06-22 23:21:44 +02:00
{
const auto newHeight = static_cast<int32_t>(_listItems.size() * SCROLLABLE_ROW_HEIGHT);
return { 0, newHeight };
}
2017-10-07 01:28:00 +02:00
/**
*
* rct2: 0x006AB0B6
*/
void OnScrollMouseDown(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override
{
// Used for in-game object selection cheat to prevent crashing the game
// when windows attempt to draw objects that don't exist any more
WindowCloseAllExceptClass(WindowClass::EditorObjectSelection);
int32_t selected_object = GetObjectFromObjectSelection(GetSelectedObjectType(), screenCoords.y);
if (selected_object == -1)
return;
2017-11-11 15:03:59 +01:00
ObjectListItem* listItem = &_listItems[selected_object];
uint8_t object_selection_flags = *listItem->flags;
2022-01-19 18:41:24 +01:00
if (object_selection_flags & ObjectSelectionFlags::Flag6)
return;
Invalidate();
2022-11-06 21:49:07 +01:00
const CursorState* state = ContextGetCursorState();
OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click1, 0, state->position.x);
if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)
{
const auto objectSelectResult = WindowEditorObjectSelectionSelectObject(
0, INPUT_FLAG_EDITOR_OBJECT_SELECT, listItem->repositoryItem);
if (!objectSelectResult.Successful)
return;
2018-06-22 23:21:44 +02:00
auto& objRepository = OpenRCT2::GetContext()->GetObjectRepository();
_loadedObject = objRepository.LoadObject(listItem->repositoryItem);
auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
objManager.LoadObject(_loadedObject.get()->GetIdentifier());
// This function calls window_track_list_open
ManageTracks();
Close();
return;
}
uint32_t inputFlags = INPUT_FLAG_EDITOR_OBJECT_1 | INPUT_FLAG_EDITOR_OBJECT_SELECT_OBJECTS_IN_SCENERY_GROUP;
// If already selected
2022-01-19 18:41:24 +01:00
if (!(object_selection_flags & ObjectSelectionFlags::Selected))
inputFlags |= INPUT_FLAG_EDITOR_OBJECT_SELECT;
_gSceneryGroupPartialSelectError = std::nullopt;
const auto objectSelectResult = WindowEditorObjectSelectionSelectObject(0, inputFlags, listItem->repositoryItem);
if (!objectSelectResult.Successful)
2017-09-12 00:04:03 +02:00
{
2022-07-31 14:22:58 +02:00
StringId error_title = (inputFlags & INPUT_FLAG_EDITOR_OBJECT_SELECT) ? STR_UNABLE_TO_SELECT_THIS_OBJECT
: STR_UNABLE_TO_DE_SELECT_THIS_OBJECT;
2022-11-06 21:49:07 +01:00
ContextShowError(error_title, objectSelectResult.Message, {});
return;
}
if (_FILTER_SELECTED || _FILTER_NONSELECTED)
{
FilterUpdateCounts();
VisibleListRefresh();
Invalidate();
}
if (_gSceneryGroupPartialSelectError.has_value())
{
const auto errorMessage = _gSceneryGroupPartialSelectError.value();
if (errorMessage == STR_OBJECT_SELECTION_ERR_TOO_MANY_OF_TYPE_SELECTED)
{
2022-11-06 21:49:07 +01:00
ContextShowError(
STR_WARNING_TOO_MANY_OBJECTS_SELECTED, STR_NOT_ALL_OBJECTS_IN_THIS_SCENERY_GROUP_COULD_BE_SELECTED, {});
}
else
{
2022-11-06 21:49:07 +01:00
ContextShowError(
errorMessage, STR_NOT_ALL_OBJECTS_IN_THIS_SCENERY_GROUP_COULD_BE_SELECTED, Formatter::Common());
}
}
}
/**
*
* rct2: 0x006AB079
*/
void OnScrollMouseOver(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override
{
int32_t selectedObject = GetObjectFromObjectSelection(GetSelectedObjectType(), screenCoords.y);
if (selectedObject != -1)
{
ObjectListItem* listItem = &_listItems[selectedObject];
uint8_t objectSelectionFlags = *listItem->flags;
2022-01-19 18:41:24 +01:00
if (objectSelectionFlags & ObjectSelectionFlags::Flag6)
2018-06-22 23:21:44 +02:00
{
selectedObject = -1;
2018-06-22 23:21:44 +02:00
}
}
if (selectedObject != selected_list_item)
{
selected_list_item = selectedObject;
if (_loadedObject != nullptr)
2018-06-22 23:21:44 +02:00
{
_loadedObject->Unload();
_loadedObject = nullptr;
2018-06-22 23:21:44 +02:00
}
if (selectedObject != -1)
2018-06-22 23:21:44 +02:00
{
auto listItem = &_listItems[selectedObject];
auto& objRepository = OpenRCT2::GetContext()->GetObjectRepository();
_loadedObject = objRepository.LoadObject(listItem->repositoryItem);
if (_loadedObject != nullptr)
{
_loadedObject->Load();
}
2018-06-22 23:21:44 +02:00
}
Invalidate();
}
}
void OnScrollDraw(int32_t scrollIndex, DrawPixelInfo& dpi) override
2018-06-22 23:21:44 +02:00
{
// ScrollPaint
ScreenCoordsXY screenCoords;
bool ridePage = (GetSelectedObjectType() == ObjectType::Ride);
uint8_t paletteIndex = ColourMapA[colours[1]].mid_light;
GfxClear(&dpi, paletteIndex);
screenCoords.y = 0;
for (size_t i = 0; i < _listItems.size(); i++)
{
const auto& listItem = _listItems[i];
if (screenCoords.y + SCROLLABLE_ROW_HEIGHT >= dpi.y && screenCoords.y <= dpi.y + dpi.height)
{
// Draw checkbox
if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) && !(*listItem.flags & 0x20))
GfxFillRectInset(&dpi, { { 2, screenCoords.y }, { 11, screenCoords.y + 10 } }, colours[1], INSET_RECT_F_E0);
// Highlight background
2022-01-19 18:41:24 +01:00
auto highlighted = i == static_cast<size_t>(selected_list_item)
&& !(*listItem.flags & ObjectSelectionFlags::Flag6);
if (highlighted)
2018-06-22 23:21:44 +02:00
{
auto bottom = screenCoords.y + (SCROLLABLE_ROW_HEIGHT - 1);
GfxFilterRect(&dpi, { 0, screenCoords.y, width, bottom }, FilterPaletteID::PaletteDarken1);
2018-06-22 23:21:44 +02:00
}
// Draw checkmark
2022-01-19 18:41:24 +01:00
if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) && (*listItem.flags & ObjectSelectionFlags::Selected))
{
screenCoords.x = 2;
auto darkness = highlighted ? TextDarkness::ExtraDark : TextDarkness::Dark;
colour_t colour2 = NOT_TRANSLUCENT(colours[1]);
2022-01-19 18:41:24 +01:00
if (*listItem.flags & (ObjectSelectionFlags::InUse | ObjectSelectionFlags::AlwaysRequired))
colour2 |= COLOUR_FLAG_INSET;
GfxDrawString(
2023-02-24 22:05:07 +01:00
dpi, screenCoords, static_cast<const char*>(CheckBoxMarkString),
{ static_cast<colour_t>(colour2), FontStyle::Medium, darkness });
}
screenCoords.x = gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER ? 0 : 15;
auto bufferWithColour = strcpy(gCommonStringFormatBuffer, highlighted ? "{WINDOW_COLOUR_2}" : "{BLACK}");
auto buffer = strchr(bufferWithColour, '\0');
colour_t colour = COLOUR_BLACK;
auto darkness = TextDarkness::Regular;
2022-01-19 18:41:24 +01:00
if (*listItem.flags & ObjectSelectionFlags::Flag6)
{
colour = colours[1] & 0x7F;
darkness = TextDarkness::Dark;
}
int32_t width_limit = widgets[WIDX_LIST].width() - screenCoords.x;
if (ridePage)
{
width_limit /= 2;
// Draw ride type
2022-07-31 14:22:58 +02:00
StringId rideTypeStringId = GetRideTypeStringId(listItem.repositoryItem);
SafeStrCpy(buffer, LanguageGetString(rideTypeStringId), 256 - (buffer - bufferWithColour));
auto ft = Formatter();
ft.Add<const char*>(gCommonStringFormatBuffer);
DrawTextEllipsised(
2023-02-24 22:05:07 +01:00
dpi, screenCoords, width_limit - 15, STR_STRING, ft, { colour, FontStyle::Medium, darkness });
screenCoords.x = widgets[WIDX_LIST_SORT_RIDE].left - widgets[WIDX_LIST].left;
}
// Draw text
SafeStrCpy(buffer, listItem.repositoryItem->Name.c_str(), 256 - (buffer - bufferWithColour));
if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)
{
while (*buffer != 0 && *buffer != 9)
buffer++;
*buffer = 0;
}
auto ft = Formatter();
ft.Add<const char*>(gCommonStringFormatBuffer);
2023-02-24 22:05:07 +01:00
DrawTextEllipsised(dpi, screenCoords, width_limit, STR_STRING, ft, { colour, FontStyle::Medium, darkness });
}
screenCoords.y += SCROLLABLE_ROW_HEIGHT;
}
}
/**
*
* rct2: 0x006AB058
*/
2022-08-21 18:49:23 +02:00
OpenRCT2String OnTooltip(const WidgetIndex widgetIndex, const StringId fallback) override
{
if (widgetIndex >= WIDX_TAB_1 && static_cast<size_t>(widgetIndex) < WIDX_TAB_1 + std::size(ObjectSelectionPages))
{
auto ft = Formatter();
2022-07-31 14:22:58 +02:00
ft.Add<StringId>(ObjectSelectionPages[(widgetIndex - WIDX_TAB_1)].Caption);
return { fallback, ft };
}
return { fallback, {} };
}
2022-08-21 18:49:23 +02:00
void OnTextInput(WidgetIndex widgetIndex, std::string_view text) override
2018-06-22 23:21:44 +02:00
{
if (widgetIndex != WIDX_FILTER_TEXT_BOX)
return;
std::string tempText = text.data();
const char* c = tempText.c_str();
if (strcmp(_filter_string, c) == 0)
return;
SafeStrCpy(_filter_string, c, sizeof(_filter_string));
FilterUpdateCounts();
scrolls->v_top = 0;
VisibleListRefresh();
Invalidate();
}
void OnPrepareDraw() override
2018-06-22 23:21:44 +02:00
{
// Resize widgets
2022-11-06 10:29:40 +01:00
ResizeFrameWithPage();
widgets[WIDX_ADVANCED].left = width - 130;
widgets[WIDX_ADVANCED].right = width - 9;
widgets[WIDX_LIST].right = width - 309;
widgets[WIDX_LIST].bottom = height - 14;
widgets[WIDX_PREVIEW].left = width - 209;
widgets[WIDX_PREVIEW].right = width - 96;
widgets[WIDX_INSTALL_TRACK].left = width - 130;
widgets[WIDX_INSTALL_TRACK].right = width - 9;
widgets[WIDX_FILTER_DROPDOWN].left = width - 250;
widgets[WIDX_FILTER_DROPDOWN].right = width - 137;
// Set pressed widgets
2022-10-17 19:21:18 +02:00
pressed_widgets |= 1uLL << WIDX_PREVIEW;
SetPressedTab();
if (list_information_type & 1)
2022-10-17 19:21:18 +02:00
pressed_widgets |= (1uLL << WIDX_ADVANCED);
else
2022-10-17 19:21:18 +02:00
pressed_widgets &= ~(1uLL << WIDX_ADVANCED);
// Set window title and buttons
auto ft = Formatter::Common();
2022-07-31 14:22:58 +02:00
ft.Add<StringId>(ObjectSelectionPages[selected_tab].Caption);
auto& titleWidget = widgets[WIDX_TITLE];
auto& installTrackWidget = widgets[WIDX_INSTALL_TRACK];
if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)
2018-06-22 23:21:44 +02:00
{
titleWidget.text = STR_TRACK_DESIGNS_MANAGER_SELECT_RIDE_TYPE;
installTrackWidget.type = WindowWidgetType::Button;
}
else if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER)
{
titleWidget.text = STR_ROLLER_COASTER_DESIGNER_SELECT_RIDE_TYPES_VEHICLES;
installTrackWidget.type = WindowWidgetType::Empty;
}
else
{
titleWidget.text = STR_OBJECT_SELECTION;
installTrackWidget.type = WindowWidgetType::Empty;
}
// Align tabs, hide advanced ones
bool advancedMode = (list_information_type & 1) != 0;
int32_t x = 3;
for (size_t i = 0; i < std::size(ObjectSelectionPages); i++)
2018-06-22 23:21:44 +02:00
{
auto& widget = widgets[WIDX_TAB_1 + i];
if ((!advancedMode && ObjectSelectionPages[i].IsAdvanced)
|| ObjectSelectionPages[i].Image == static_cast<uint32_t>(SPR_NONE))
{
widget.type = WindowWidgetType::Empty;
}
else
{
widget.type = WindowWidgetType::Tab;
widget.left = x;
widget.right = x + 30;
x += 31;
}
}
if (gScreenFlags & (SCREEN_FLAGS_TRACK_MANAGER | SCREEN_FLAGS_TRACK_DESIGNER))
{
widgets[WIDX_ADVANCED].type = WindowWidgetType::Empty;
for (size_t i = 1; i < std::size(ObjectSelectionPages); i++)
{
widgets[WIDX_TAB_1 + i].type = WindowWidgetType::Empty;
}
x = 150;
}
else
{
widgets[WIDX_ADVANCED].type = WindowWidgetType::Button;
x = 300;
}
widgets[WIDX_FILTER_DROPDOWN].type = WindowWidgetType::Button;
widgets[WIDX_LIST].right = width - (WW - 587) - x;
widgets[WIDX_PREVIEW].left = width - (WW - 537) - (x / 2);
widgets[WIDX_PREVIEW].right = widgets[WIDX_PREVIEW].left + 113;
widgets[WIDX_FILTER_RIDE_TAB_FRAME].right = widgets[WIDX_LIST].right;
bool ridePage = (GetSelectedObjectType() == ObjectType::Ride);
widgets[WIDX_LIST].top = (ridePage ? 118 : 60);
widgets[WIDX_FILTER_TEXT_BOX].right = widgets[WIDX_LIST].right - 77;
widgets[WIDX_FILTER_TEXT_BOX].top = (ridePage ? 79 : 45);
widgets[WIDX_FILTER_TEXT_BOX].bottom = (ridePage ? 92 : 58);
widgets[WIDX_FILTER_CLEAR_BUTTON].left = widgets[WIDX_LIST].right - 73;
widgets[WIDX_FILTER_CLEAR_BUTTON].right = widgets[WIDX_LIST].right;
widgets[WIDX_FILTER_CLEAR_BUTTON].top = (ridePage ? 79 : 45);
widgets[WIDX_FILTER_CLEAR_BUTTON].bottom = (ridePage ? 92 : 58);
if (ridePage)
{
for (int32_t i = WIDX_FILTER_RIDE_TAB_ALL; i <= WIDX_FILTER_RIDE_TAB_STALL; i++)
pressed_widgets &= ~(1 << i);
if ((_filter_flags & FILTER_RIDES) == FILTER_RIDES)
2022-10-17 19:21:18 +02:00
pressed_widgets |= (1uLL << WIDX_FILTER_RIDE_TAB_ALL);
else
{
for (int32_t i = 0; i < 6; i++)
{
if (_filter_flags & (1 << (_numSourceGameItems + i)))
2022-10-17 19:21:18 +02:00
pressed_widgets |= 1uLL << (WIDX_FILTER_RIDE_TAB_TRANSPORT + i);
}
}
widgets[WIDX_FILTER_RIDE_TAB_FRAME].type = WindowWidgetType::ImgBtn;
for (int32_t i = WIDX_FILTER_RIDE_TAB_ALL; i <= WIDX_FILTER_RIDE_TAB_STALL; i++)
widgets[i].type = WindowWidgetType::Tab;
int32_t width_limit = (widgets[WIDX_LIST].width() - 15) / 2;
widgets[WIDX_LIST_SORT_TYPE].type = WindowWidgetType::TableHeader;
widgets[WIDX_LIST_SORT_TYPE].top = widgets[WIDX_FILTER_TEXT_BOX].bottom + 3;
widgets[WIDX_LIST_SORT_TYPE].bottom = widgets[WIDX_LIST_SORT_TYPE].top + 13;
widgets[WIDX_LIST_SORT_TYPE].left = 4;
widgets[WIDX_LIST_SORT_TYPE].right = widgets[WIDX_LIST_SORT_TYPE].left + width_limit;
widgets[WIDX_LIST_SORT_RIDE].type = WindowWidgetType::TableHeader;
widgets[WIDX_LIST_SORT_RIDE].top = widgets[WIDX_LIST_SORT_TYPE].top;
widgets[WIDX_LIST_SORT_RIDE].bottom = widgets[WIDX_LIST_SORT_TYPE].bottom;
widgets[WIDX_LIST_SORT_RIDE].left = widgets[WIDX_LIST_SORT_TYPE].right + 1;
widgets[WIDX_LIST_SORT_RIDE].right = widgets[WIDX_LIST].right;
widgets[WIDX_LIST].top = widgets[WIDX_LIST_SORT_TYPE].bottom + 2;
}
else
{
for (int32_t i = WIDX_FILTER_RIDE_TAB_FRAME; i <= WIDX_FILTER_RIDE_TAB_STALL; i++)
widgets[i].type = WindowWidgetType::Empty;
widgets[WIDX_LIST_SORT_TYPE].type = WindowWidgetType::Empty;
widgets[WIDX_LIST_SORT_RIDE].type = WindowWidgetType::Empty;
}
}
void OnDraw(DrawPixelInfo& dpi) override
{
int32_t _width;
DrawWidgets(dpi);
// Draw tabs
for (size_t i = 0; i < std::size(ObjectSelectionPages); i++)
{
const auto& widget = widgets[WIDX_TAB_1 + i];
if (widget.type != WindowWidgetType::Empty)
{
auto image = ImageId(ObjectSelectionPages[i].Image);
auto screenPos = windowPos + ScreenCoordsXY{ widget.left, widget.top };
GfxDrawSprite(&dpi, image, screenPos);
}
}
const int32_t ride_tabs[] = {
SPR_TAB_RIDE_16, SPR_TAB_RIDES_TRANSPORT_0, SPR_TAB_RIDES_GENTLE_0, SPR_TAB_RIDES_ROLLER_COASTERS_0,
SPR_TAB_RIDES_THRILL_0, SPR_TAB_RIDES_WATER_0, SPR_TAB_RIDES_SHOP_0, SPR_TAB_FINANCES_RESEARCH_0,
};
const int32_t ThrillRidesTabAnimationSequence[] = {
5, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0,
};
// Draw ride tabs
if (GetSelectedObjectType() == ObjectType::Ride)
{
for (int32_t i = 0; i < 7; i++)
{
const auto& widget = widgets[WIDX_FILTER_RIDE_TAB_ALL + i];
if (widget.type == WindowWidgetType::Empty)
continue;
int32_t spriteIndex = ride_tabs[i];
int32_t frame = 0;
if (i != 0 && IsWidgetPressed(WIDX_FILTER_RIDE_TAB_ALL + i))
{
frame = frame_no / window_editor_object_selection_animation_divisor[i - 1];
}
spriteIndex += (i == 4 ? ThrillRidesTabAnimationSequence[frame] : frame);
auto screenPos = windowPos + ScreenCoordsXY{ widget.left, widget.top };
GfxDrawSprite(&dpi, ImageId(spriteIndex, colours[1]), screenPos);
}
}
// Preview background
const auto& previewWidget = widgets[WIDX_PREVIEW];
GfxFillRect(
&dpi,
{ windowPos + ScreenCoordsXY{ previewWidget.left + 1, previewWidget.top + 1 },
windowPos + ScreenCoordsXY{ previewWidget.right - 1, previewWidget.bottom - 1 } },
ColourMapA[colours[1]].darkest);
// Draw number of selected items
if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER))
{
auto screenPos = windowPos + ScreenCoordsXY{ 3, height - 13 };
int32_t numSelected = _numSelectedObjectsForType[EnumValue(GetSelectedObjectType())];
int32_t totalSelectable = object_entry_group_counts[EnumValue(GetSelectedObjectType())];
auto ft = Formatter();
ft.Add<uint16_t>(numSelected);
ft.Add<uint16_t>(totalSelectable);
2023-02-24 22:05:07 +01:00
DrawTextBasic(dpi, screenPos, STR_OBJECT_SELECTION_SELECTION_SIZE, ft);
}
// Draw sort button text
const auto& listSortTypeWidget = widgets[WIDX_LIST_SORT_TYPE];
if (listSortTypeWidget.type != WindowWidgetType::Empty)
{
auto ft = Formatter();
2022-07-31 14:22:58 +02:00
auto stringId = _listSortType == RIDE_SORT_TYPE ? static_cast<StringId>(_listSortDescending ? STR_DOWN : STR_UP)
: STR_NONE;
ft.Add<StringId>(stringId);
auto screenPos = windowPos + ScreenCoordsXY{ listSortTypeWidget.left + 1, listSortTypeWidget.top + 1 };
2023-02-24 22:05:07 +01:00
DrawTextEllipsised(dpi, screenPos, listSortTypeWidget.width(), STR_OBJECTS_SORT_TYPE, ft, { colours[1] });
}
const auto& listSortRideWidget = widgets[WIDX_LIST_SORT_RIDE];
if (listSortRideWidget.type != WindowWidgetType::Empty)
{
auto ft = Formatter();
2022-07-31 14:22:58 +02:00
auto stringId = _listSortType == RIDE_SORT_RIDE ? static_cast<StringId>(_listSortDescending ? STR_DOWN : STR_UP)
: STR_NONE;
ft.Add<StringId>(stringId);
auto screenPos = windowPos + ScreenCoordsXY{ listSortRideWidget.left + 1, listSortRideWidget.top + 1 };
2023-02-24 22:05:07 +01:00
DrawTextEllipsised(dpi, screenPos, listSortRideWidget.width(), STR_OBJECTS_SORT_RIDE, ft, { colours[1] });
}
if (selected_list_item == -1 || _loadedObject == nullptr)
return;
ObjectListItem* listItem = &_listItems[selected_list_item];
// Draw preview
{
DrawPixelInfo clipDPI;
auto screenPos = windowPos + ScreenCoordsXY{ previewWidget.left + 1, previewWidget.top + 1 };
_width = previewWidget.width() - 1;
int32_t _height = previewWidget.height() - 1;
if (ClipDrawPixelInfo(&clipDPI, &dpi, screenPos, _width, _height))
{
_loadedObject->DrawPreview(clipDPI, _width, _height);
}
}
// Draw name of object
{
auto screenPos = windowPos + ScreenCoordsXY{ previewWidget.midX() + 1, previewWidget.bottom + 3 };
_width = this->width - widgets[WIDX_LIST].right - 6;
auto ft = Formatter();
2022-07-31 14:22:58 +02:00
ft.Add<StringId>(STR_STRING);
ft.Add<const char*>(listItem->repositoryItem->Name.c_str());
2023-02-24 22:05:07 +01:00
DrawTextEllipsised(dpi, screenPos, _width, STR_WINDOW_COLOUR_2_STRINGID, ft, { TextAlignment::CENTRE });
}
DrawDescriptions(&dpi);
DrawDebugData(&dpi);
}
private:
void InitWidgets()
{
auto& targetWidgets = _window_editor_object_selection_widgets;
if (!_window_editor_object_selection_widgets_initialised)
{
_window_editor_object_selection_widgets_initialised = true;
auto tabWidget = targetWidgets[targetWidgets.size() - 2];
for (size_t i = 1; i < std::size(ObjectSelectionPages); i++)
{
targetWidgets.insert(targetWidgets.end() - 1, tabWidget);
}
}
widgets = targetWidgets.data();
}
void SetPage(int32_t _page)
{
if (selected_tab == _page)
return;
selected_tab = _page;
selected_list_item = -1;
scrolls[0].v_top = 0;
frame_no = 0;
if (_page == EnumValue(ObjectType::Ride))
{
_listSortType = RIDE_SORT_TYPE;
_listSortDescending = false;
}
else
{
_listSortType = RIDE_SORT_RIDE;
_listSortDescending = false;
}
VisibleListRefresh();
Invalidate();
}
void VisibleListRefresh()
{
int32_t numObjects = static_cast<int32_t>(ObjectRepositoryGetItemsCount());
VisibleListDispose();
selected_list_item = -1;
const ObjectRepositoryItem* items = ObjectRepositoryGetItems();
for (int32_t i = 0; i < numObjects; i++)
{
uint8_t selectionFlags = _objectSelectionFlags[i];
const ObjectRepositoryItem* item = &items[i];
2022-01-19 18:41:24 +01:00
if (item->Type == GetSelectedObjectType() && !(selectionFlags & ObjectSelectionFlags::Flag6) && FilterSource(item)
&& FilterString(*item) && FilterChunks(item) && FilterSelected(selectionFlags))
2018-06-22 23:21:44 +02:00
{
auto filter = std::make_unique<RideFilters>();
filter->category[0] = 0;
filter->category[1] = 0;
filter->ride_type = 0;
ObjectListItem currentListItem;
currentListItem.repositoryItem = item;
currentListItem.filter = std::move(filter);
currentListItem.flags = &_objectSelectionFlags[i];
_listItems.push_back(std::move(currentListItem));
}
}
if (_listItems.empty())
{
VisibleListDispose();
}
else
{
sortFunc_t sortFunc = nullptr;
switch (_listSortType)
{
case RIDE_SORT_TYPE:
sortFunc = VisibleListSortRideType;
break;
case RIDE_SORT_RIDE:
sortFunc = VisibleListSortRideName;
break;
default:
LOG_WARNING("Wrong sort type %d, leaving list as-is.", _listSortType);
break;
}
if (sortFunc != nullptr)
{
std::sort(_listItems.begin(), _listItems.end(), sortFunc);
if (_listSortDescending)
{
std::reverse(_listItems.begin(), _listItems.end());
}
}
}
Invalidate();
}
void VisibleListDispose()
{
_listItems.clear();
_listItems.shrink_to_fit();
}
void DrawDescriptions(DrawPixelInfo* dpi)
2018-06-22 23:21:44 +02:00
{
const auto& widget = widgets[WIDX_PREVIEW];
auto screenPos = windowPos + ScreenCoordsXY{ widgets[WIDX_LIST].right + 4, widget.bottom + 23 };
auto _width2 = windowPos.x + this->width - screenPos.x - 4;
auto description = ObjectGetDescription(_loadedObject.get());
if (!description.empty())
{
auto ft = Formatter();
2022-07-31 14:22:58 +02:00
ft.Add<StringId>(STR_STRING);
ft.Add<const char*>(description.c_str());
2023-02-24 22:05:07 +01:00
screenPos.y += DrawTextWrapped(*dpi, screenPos, _width2, STR_WINDOW_COLOUR_2_STRINGID, ft) + LIST_ROW_HEIGHT;
}
if (GetSelectedObjectType() == ObjectType::Ride)
{
auto* rideObject = reinterpret_cast<RideObject*>(_loadedObject.get());
const auto* rideEntry = reinterpret_cast<RideObjectEntry*>(rideObject->GetLegacyData());
if (rideEntry->shop_item[0] != ShopItem::None)
{
std::string sells = "";
for (size_t i = 0; i < std::size(rideEntry->shop_item); i++)
{
if (rideEntry->shop_item[i] == ShopItem::None)
continue;
if (!sells.empty())
sells += ", ";
sells += LanguageGetString(GetShopItemDescriptor(rideEntry->shop_item[i]).Naming.Plural);
}
auto ft = Formatter();
ft.Add<const char*>(sells.c_str());
2023-02-24 22:05:07 +01:00
screenPos.y += DrawTextWrapped(*dpi, screenPos, _width2, STR_RIDE_OBJECT_SHOP_SELLS, ft) + 2;
}
}
else if (GetSelectedObjectType() == ObjectType::SceneryGroup)
{
const auto* sceneryGroupObject = reinterpret_cast<SceneryGroupObject*>(_loadedObject.get());
auto ft = Formatter();
ft.Add<uint16_t>(sceneryGroupObject->GetNumIncludedObjects());
2023-02-24 22:05:07 +01:00
screenPos.y += DrawTextWrapped(*dpi, screenPos, _width2, STR_INCLUDES_X_OBJECTS, ft) + 2;
}
else if (GetSelectedObjectType() == ObjectType::Music)
{
2023-02-24 22:05:07 +01:00
screenPos.y += DrawTextWrapped(*dpi, screenPos, _width2, STR_MUSIC_OBJECT_TRACK_HEADER) + 2;
const auto* musicObject = reinterpret_cast<MusicObject*>(_loadedObject.get());
for (size_t i = 0; i < musicObject->GetTrackCount(); i++)
{
const auto* track = musicObject->GetTrack(i);
if (track->Name.empty())
continue;
auto stringId = track->Composer.empty() ? STR_MUSIC_OBJECT_TRACK_LIST_ITEM
: STR_MUSIC_OBJECT_TRACK_LIST_ITEM_WITH_COMPOSER;
auto ft = Formatter();
ft.Add<const char*>(track->Name.c_str());
ft.Add<const char*>(track->Composer.c_str());
2023-02-24 22:05:07 +01:00
screenPos.y += DrawTextWrapped(*dpi, screenPos + ScreenCoordsXY{ 10, 0 }, _width2, stringId, ft);
}
}
}
void DrawDebugData(DrawPixelInfo* dpi)
2018-06-22 23:21:44 +02:00
{
ObjectListItem* listItem = &_listItems[selected_list_item];
auto screenPos = windowPos + ScreenCoordsXY{ width - 5, height - (LIST_ROW_HEIGHT * 6) };
// Draw fallback image warning
if (_loadedObject && _loadedObject->UsesFallbackImages())
{
2023-02-24 22:05:07 +01:00
DrawTextBasic(*dpi, screenPos, STR_OBJECT_USES_FALLBACK_IMAGES, {}, { COLOUR_WHITE, TextAlignment::RIGHT });
}
screenPos.y += LIST_ROW_HEIGHT;
// Draw ride type.
if (GetSelectedObjectType() == ObjectType::Ride)
{
auto stringId = GetRideTypeStringId(listItem->repositoryItem);
2023-02-24 22:05:07 +01:00
DrawTextBasic(*dpi, screenPos, stringId, {}, { COLOUR_WHITE, TextAlignment::RIGHT });
}
screenPos.y += LIST_ROW_HEIGHT;
// Draw object source
auto stringId = ObjectManagerGetSourceGameString(listItem->repositoryItem->GetFirstSourceGame());
2023-02-24 22:05:07 +01:00
DrawTextBasic(*dpi, screenPos, stringId, {}, { COLOUR_WHITE, TextAlignment::RIGHT });
screenPos.y += LIST_ROW_HEIGHT;
2022-02-26 17:20:07 +01:00
// Draw object filename
{
2022-02-26 17:20:07 +01:00
auto path = Path::GetFileName(listItem->repositoryItem->Path);
auto ft = Formatter();
2022-07-31 14:22:58 +02:00
ft.Add<StringId>(STR_STRING);
2022-02-26 17:20:07 +01:00
ft.Add<const utf8*>(path.c_str());
DrawTextBasic(
2023-02-24 22:05:07 +01:00
*dpi, { windowPos.x + this->width - 5, screenPos.y }, STR_WINDOW_COLOUR_2_STRINGID, ft,
{ COLOUR_BLACK, TextAlignment::RIGHT });
screenPos.y += LIST_ROW_HEIGHT;
}
// Draw object author (will be blank space if no author in file or a non JSON object)
2018-06-22 23:21:44 +02:00
{
auto ft = Formatter();
std::string authorsString;
for (size_t i = 0; i < listItem->repositoryItem->Authors.size(); i++)
{
if (i > 0)
{
authorsString.append(", ");
}
authorsString.append(listItem->repositoryItem->Authors[i]);
}
2022-07-31 14:22:58 +02:00
ft.Add<StringId>(STR_STRING);
ft.Add<const char*>(authorsString.c_str());
DrawTextEllipsised(
2023-02-24 22:05:07 +01:00
*dpi, { windowPos.x + width - 5, screenPos.y }, width - widgets[WIDX_LIST].right - 4,
STR_WINDOW_COLOUR_2_STRINGID, ft, { TextAlignment::RIGHT });
}
}
bool FilterSelected(uint8_t objectFlag)
{
// Track Manager has no concept of selection filtering, so always return true
if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)
{
return true;
}
if (_FILTER_SELECTED == _FILTER_NONSELECTED)
{
return true;
}
2022-01-19 18:41:24 +01:00
if (_FILTER_SELECTED && objectFlag & ObjectSelectionFlags::Selected)
{
return true;
}
2022-01-19 18:41:24 +01:00
if (_FILTER_NONSELECTED && !(objectFlag & ObjectSelectionFlags::Selected))
{
return true;
}
return false;
}
static bool IsFilterInName(const ObjectRepositoryItem& item, std::string_view filter)
{
return String::Contains(item.Name, filter, true);
}
static bool IsFilterInRideType(const ObjectRepositoryItem& item, std::string_view filter)
{
if (item.Type == ObjectType::Ride)
{
auto rideTypeName = LanguageGetString(GetRideTypeStringId(&item));
if (String::Contains(rideTypeName, filter, true))
return true;
}
return false;
}
static bool IsFilterInFilename(const ObjectRepositoryItem& item, std::string_view filter)
{
return String::Contains(item.Path, filter, true);
}
static bool IsFilterInAuthor(const ObjectRepositoryItem& item, std::string_view filter)
{
for (auto& author : item.Authors)
{
if (String::Contains(author, filter, true))
{
return true;
}
}
return false;
}
bool FilterString(const ObjectRepositoryItem& item)
{
// Nothing to search for
std::string_view filter = _filter_string;
if (filter.empty())
return true;
return IsFilterInName(item, filter) || IsFilterInRideType(item, filter) || IsFilterInFilename(item, filter)
|| IsFilterInAuthor(item, filter);
}
bool SourcesMatch(ObjectSourceGame source)
{
// clang-format off
return (_FILTER_RCT1 && source == ObjectSourceGame::RCT1) ||
(_FILTER_AA && source == ObjectSourceGame::AddedAttractions) ||
(_FILTER_LL && source == ObjectSourceGame::LoopyLandscapes) ||
(_FILTER_RCT2 && source == ObjectSourceGame::RCT2) ||
(_FILTER_WW && source == ObjectSourceGame::WackyWorlds) ||
(_FILTER_TT && source == ObjectSourceGame::TimeTwister) ||
(_FILTER_OO && source == ObjectSourceGame::OpenRCT2Official) ||
(_FILTER_CUSTOM &&
source != ObjectSourceGame::RCT1 &&
source != ObjectSourceGame::AddedAttractions &&
source != ObjectSourceGame::LoopyLandscapes &&
source != ObjectSourceGame::RCT2 &&
source != ObjectSourceGame::WackyWorlds &&
source != ObjectSourceGame::TimeTwister &&
source != ObjectSourceGame::OpenRCT2Official);
// clang-format on
}
bool FilterSource(const ObjectRepositoryItem* item)
{
if (_FILTER_ALL)
return true;
for (auto source : item->Sources)
{
if (SourcesMatch(source))
return true;
}
return false;
}
bool FilterChunks(const ObjectRepositoryItem* item)
{
if (item->Type == ObjectType::Ride)
{
ride_type_t rideType = 0;
for (int32_t i = 0; i < RCT2::ObjectLimits::MaxRideTypesPerRideEntry; i++)
2018-06-22 23:21:44 +02:00
{
if (item->RideInfo.RideType[i] != RIDE_TYPE_NULL)
{
rideType = item->RideInfo.RideType[i];
break;
}
}
return (_filter_flags & (1 << (GetRideTypeDescriptor(rideType).Category + _numSourceGameItems))) != 0;
}
return true;
}
void FilterUpdateCounts()
{
if (!_FILTER_ALL || _filter_string[0] != '\0')
{
const auto& selectionFlags = _objectSelectionFlags;
std::fill(std::begin(_filter_object_counts), std::end(_filter_object_counts), 0);
size_t numObjects = ObjectRepositoryGetItemsCount();
const ObjectRepositoryItem* items = ObjectRepositoryGetItems();
for (size_t i = 0; i < numObjects; i++)
2018-06-22 23:21:44 +02:00
{
const ObjectRepositoryItem* item = &items[i];
if (FilterSource(item) && FilterString(*item) && FilterChunks(item) && FilterSelected(selectionFlags[i]))
{
_filter_object_counts[EnumValue(item->Type)]++;
}
}
}
}
std::string ObjectGetDescription(const Object* object)
{
switch (object->GetObjectType())
{
case ObjectType::Ride:
2018-06-22 23:21:44 +02:00
{
const RideObject* rideObject = static_cast<const RideObject*>(object);
return rideObject->GetDescription();
}
default:
return "";
}
}
ObjectType GetSelectedObjectType()
2018-06-22 23:21:44 +02:00
{
const bool inBounds = selected_tab >= 0 && selected_tab < static_cast<int16_t>(std::size(TabOrder));
return inBounds ? TabOrder[selected_tab] : ObjectType::Ride;
2018-06-22 23:21:44 +02:00
}
/**
* Takes the y coordinate of the clicked on scroll list
* and converts this into an object selection.
* Returns the position in the list.
* Object_selection_flags, installed_entry also populated
*
* rct2: 0x006AA703
*/
int32_t GetObjectFromObjectSelection(ObjectType object_type, int32_t y)
2018-06-22 23:21:44 +02:00
{
int32_t listItemIndex = y / SCROLLABLE_ROW_HEIGHT;
if (listItemIndex < 0 || static_cast<size_t>(listItemIndex) >= _listItems.size())
return -1;
return listItemIndex;
}
void SetPressedTab()
{
for (size_t i = 0; i < std::size(ObjectSelectionPages); i++)
{
pressed_widgets &= ~(1ull << (WIDX_TAB_1 + i));
}
pressed_widgets |= 1LL << (WIDX_TAB_1 + selected_tab);
}
/**
*
* rct2: 0x006D33E2
*/
void ManageTracks()
{
SetEveryRideTypeInvented();
SetEveryRideEntryInvented();
gEditorStep = EditorStep::DesignsManager;
int32_t entry_index = 0;
for (; ObjectEntryGetChunk(ObjectType::Ride, entry_index) == nullptr; entry_index++)
;
2023-02-05 16:52:47 +01:00
const auto* rideEntry = GetRideEntryByIndex(entry_index);
auto rideType = rideEntry->GetFirstNonNullRideType();
auto intent = Intent(WindowClass::TrackDesignList);
intent.PutExtra(INTENT_EXTRA_RIDE_TYPE, rideType);
intent.PutExtra(INTENT_EXTRA_RIDE_ENTRY_INDEX, entry_index);
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
}
};
/**
*
* rct2: 0x006AA64E
*/
WindowBase* WindowEditorObjectSelectionOpen()
{
return WindowFocusOrCreate<EditorObjectSelectionWindow>(
WindowClass::EditorObjectSelection, 755, 400, WF_10 | WF_RESIZABLE | WF_CENTRE_SCREEN);
}
static bool VisibleListSortRideName(const ObjectListItem& a, const ObjectListItem& b)
{
auto nameA = a.repositoryItem->Name.c_str();
auto nameB = b.repositoryItem->Name.c_str();
return strcmp(nameA, nameB) < 0;
}
static bool VisibleListSortRideType(const ObjectListItem& a, const ObjectListItem& b)
{
auto rideTypeA = LanguageGetString(GetRideTypeStringId(a.repositoryItem));
auto rideTypeB = LanguageGetString(GetRideTypeStringId(b.repositoryItem));
int32_t result = String::Compare(rideTypeA, rideTypeB);
return result != 0 ? result < 0 : VisibleListSortRideName(a, b);
}
2022-07-31 14:22:58 +02:00
static StringId GetRideTypeStringId(const ObjectRepositoryItem* item)
{
2022-07-31 14:22:58 +02:00
StringId result = STR_NONE;
for (int32_t i = 0; i < RCT2::ObjectLimits::MaxRideTypesPerRideEntry; i++)
{
auto rideType = item->RideInfo.RideType[i];
if (rideType != RIDE_TYPE_NULL)
{
result = GetRideTypeDescriptor(rideType).Naming.Name;
break;
}
}
return result;
}
/**
2015-10-20 20:16:30 +02:00
*
* rct2: 0x006ABBBE
*/
void EditorLoadSelectedObjects()
{
auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
int32_t numItems = static_cast<int32_t>(ObjectRepositoryGetItemsCount());
const ObjectRepositoryItem* items = ObjectRepositoryGetItems();
bool showFallbackWarning = false;
2018-06-22 23:21:44 +02:00
for (int32_t i = 0; i < numItems; i++)
{
2022-01-19 18:41:24 +01:00
if (_objectSelectionFlags[i] & ObjectSelectionFlags::Selected)
2018-06-22 23:21:44 +02:00
{
const auto* item = &items[i];
auto descriptor = ObjectEntryDescriptor(*item);
const auto* loadedObject = objManager.GetLoadedObject(descriptor);
2018-06-22 23:21:44 +02:00
if (loadedObject == nullptr)
{
loadedObject = objManager.LoadObject(descriptor);
2018-06-22 23:21:44 +02:00
if (loadedObject == nullptr)
{
LOG_ERROR("Failed to load entry %s", std::string(descriptor.GetName()).c_str());
}
2018-06-22 23:21:44 +02:00
else if (!(gScreenFlags & SCREEN_FLAGS_EDITOR))
{
// Defaults selected items to researched (if in-game)
auto objectType = loadedObject->GetObjectType();
auto entryIndex = ObjectManagerGetLoadedObjectEntryIndex(loadedObject);
if (objectType == ObjectType::Ride)
2018-06-22 23:21:44 +02:00
{
const auto* rideEntry = GetRideEntryByIndex(entryIndex);
auto rideType = rideEntry->GetFirstNonNullRideType();
ResearchCategory category = static_cast<ResearchCategory>(GetRideTypeDescriptor(rideType).Category);
ResearchInsertRideEntry(rideType, entryIndex, category, true);
}
else if (objectType == ObjectType::SceneryGroup)
2018-06-22 23:21:44 +02:00
{
ResearchInsertSceneryGroupEntry(entryIndex, true);
}
if (loadedObject->UsesFallbackImages())
{
showFallbackWarning = true;
}
}
}
}
}
if (_numSelectedObjectsForType[EnumValue(ObjectType::Water)] == 0)
{
// Reloads the default cyan water palette if no palette was selected.
LoadPalette();
}
if (showFallbackWarning)
2022-11-06 21:49:07 +01:00
ContextShowError(STR_OBJECT_SELECTION_FALLBACK_IMAGES_WARNING, STR_EMPTY, Formatter::Common());
}