diff --git a/distribution/changelog.txt b/distribution/changelog.txt index d1fb7b809e..c921a159a9 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -15,6 +15,7 @@ - Fix: [#14316] Closing the Track Designs Manager window causes broken state. - Fix: [#14649] ImageImporter incorrectly remaps colours outside the RCT2 palette. - Fix: [#14667] “Extreme Hawaiian Island” has unpurchaseable land tiles (original bug). +- Fix: [#14741] Crash when exiting OpenRCT2 on macOS. - Fix: [#15096] Crash when placing entrances in the scenario editor near the map corner. - Fix: [#15136] Exported SV6 files cause vanilla RCT2 to hang. - Fix: [#15142] ToonTowner's mine roofs were moved into the pirate theme scenery group instead of the mine theme scenery group. @@ -25,15 +26,18 @@ - Fix: [#15197] Cannot place flat ride after removing it in construction window. - Fix: [#15199] Construction window is not closed when a ride gets demolished. - Fix: [#15213] Freeze when hovering over Reverse Freefall Coaster in Russian. +- Fix: [#15227] Crash on exit after hovering over water types in the Object Selection window. - Fix: [#15255] Tile Inspector shows banner information on walls that do not contain one. - Fix: [#15257] Chat icon shows in scenario/track editor. Other icons don't disable when deactivated in options menu. - Fix: [#15289] Unexpected behavior with duplicated banners which also caused desyncs in multiplayer. - Fix: [#15451] Guest list name filter remains after group selection. +- Fix: [#15377] Entrance/exit ghost doesn't work on different stations without touching them first. - Fix: [#15476] Crash when placing/clearing small scenery. - Fix: [#15487] Map animations do not work correctly when loading an exported SV6 file in vanilla RCT2. - Fix: [#15496] Crash in paint_swinging_inverter_ship_structure(). - Fix: [#15503] Freeze when doing specific coaster merges with block brakes. - Fix: [#15514] Two different “quit to menu” menu items are available in track designer and track design manager. +- Fix: [#15560] Memory leak due to OpenGL Renderer not releasing a texture. - Improved: [#3417] Crash dumps are now placed in their own folder. - Improved: [#13524] macOS arm64 native (universal) app - Improved: [#15538] Software rendering can now draw in parallel when Multithreading is enabled. diff --git a/src/openrct2-ui/drawing/engines/opengl/SwapFramebuffer.cpp b/src/openrct2-ui/drawing/engines/opengl/SwapFramebuffer.cpp index fb7098183c..39c88b9dec 100644 --- a/src/openrct2-ui/drawing/engines/opengl/SwapFramebuffer.cpp +++ b/src/openrct2-ui/drawing/engines/opengl/SwapFramebuffer.cpp @@ -27,6 +27,11 @@ SwapFramebuffer::SwapFramebuffer(int32_t width, int32_t height) glClearBufferfv(GL_DEPTH, 0, depthValueTransparent); } +SwapFramebuffer::~SwapFramebuffer() +{ + glDeleteTextures(1, &_backDepth); +} + void SwapFramebuffer::ApplyTransparency(ApplyTransparencyShader& shader, GLuint paletteTex) { _mixFramebuffer.Bind(); diff --git a/src/openrct2-ui/drawing/engines/opengl/SwapFramebuffer.h b/src/openrct2-ui/drawing/engines/opengl/SwapFramebuffer.h index 5880e254f9..44d1659a3b 100644 --- a/src/openrct2-ui/drawing/engines/opengl/SwapFramebuffer.h +++ b/src/openrct2-ui/drawing/engines/opengl/SwapFramebuffer.h @@ -33,6 +33,7 @@ private: public: SwapFramebuffer(int32_t width, int32_t height); + ~SwapFramebuffer(); const OpenGLFramebuffer& GetFinalFramebuffer() const { diff --git a/src/openrct2-ui/windows/EditorObjectSelection.cpp b/src/openrct2-ui/windows/EditorObjectSelection.cpp index 9d92776635..0cdb5171df 100644 --- a/src/openrct2-ui/windows/EditorObjectSelection.cpp +++ b/src/openrct2-ui/windows/EditorObjectSelection.cpp @@ -753,7 +753,10 @@ static void window_editor_object_selection_scroll_mouseover( w->selected_list_item = selectedObject; if (_loadedObject != nullptr) + { _loadedObject->Unload(); + _loadedObject = nullptr; + } if (selectedObject != -1) { diff --git a/src/openrct2-ui/windows/ObjectLoadError.cpp b/src/openrct2-ui/windows/ObjectLoadError.cpp index 3222e91434..ecce57249d 100644 --- a/src/openrct2-ui/windows/ObjectLoadError.cpp +++ b/src/openrct2-ui/windows/ObjectLoadError.cpp @@ -284,322 +284,294 @@ static rct_widget window_object_load_error_widgets[] = { #endif WIDGETS_END, }; - -static void window_object_load_error_close(rct_window *w); -static void window_object_load_error_update(rct_window *w); -static void window_object_load_error_mouseup(rct_window *w, rct_widgetindex widgetIndex); -static void window_object_load_error_scrollgetsize(rct_window *w, int32_t scrollIndex, int32_t *width, int32_t *height); -static void window_object_load_error_scrollmouseover(rct_window *w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords); -static void window_object_load_error_scrollmousedown(rct_window *w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords); -static void window_object_load_error_paint(rct_window *w, rct_drawpixelinfo *dpi); -static void window_object_load_error_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, int32_t scrollIndex); -#ifndef DISABLE_HTTP -static void window_object_load_error_download_all(rct_window* w); -static void window_object_load_error_update_list(rct_window* w); -#endif - -static rct_window_event_list window_object_load_error_events([](auto& events) -{ - events.close = &window_object_load_error_close; - events.mouse_up = &window_object_load_error_mouseup; - events.update = &window_object_load_error_update; - events.get_scroll_size = &window_object_load_error_scrollgetsize; - events.scroll_mousedown = &window_object_load_error_scrollmousedown; - events.scroll_mouseover = &window_object_load_error_scrollmouseover; - events.paint = &window_object_load_error_paint; - events.scroll_paint = &window_object_load_error_scrollpaint; -}); // clang-format on -static std::vector _invalid_entries; -static int32_t highlighted_index = -1; -static std::string file_path; -#ifndef DISABLE_HTTP -static ObjectDownloader _objDownloader; -static bool _updatedListAfterDownload; -#endif - /** * Returns an rct_string_id that represents an rct_object_entry's type. * * Could possibly be moved out of the window file if other * uses exist and a suitable location is found. */ -static rct_string_id get_object_type_string(ObjectType type) +static constexpr rct_string_id GetStringFromObjectType(const ObjectType type) { - rct_string_id result; switch (type) { case ObjectType::Ride: - result = STR_OBJECT_SELECTION_RIDE_VEHICLES_ATTRACTIONS; - break; + return STR_OBJECT_SELECTION_RIDE_VEHICLES_ATTRACTIONS; case ObjectType::SmallScenery: - result = STR_OBJECT_SELECTION_SMALL_SCENERY; - break; + return STR_OBJECT_SELECTION_SMALL_SCENERY; case ObjectType::LargeScenery: - result = STR_OBJECT_SELECTION_LARGE_SCENERY; - break; + return STR_OBJECT_SELECTION_LARGE_SCENERY; case ObjectType::Walls: - result = STR_OBJECT_SELECTION_WALLS_FENCES; - break; + return STR_OBJECT_SELECTION_WALLS_FENCES; case ObjectType::Banners: - result = STR_OBJECT_SELECTION_PATH_SIGNS; - break; + return STR_OBJECT_SELECTION_PATH_SIGNS; case ObjectType::Paths: - result = STR_OBJECT_SELECTION_FOOTPATHS; - break; + return STR_OBJECT_SELECTION_FOOTPATHS; case ObjectType::PathBits: - result = STR_OBJECT_SELECTION_PATH_EXTRAS; - break; + return STR_OBJECT_SELECTION_PATH_EXTRAS; case ObjectType::SceneryGroup: - result = STR_OBJECT_SELECTION_SCENERY_GROUPS; - break; + return STR_OBJECT_SELECTION_SCENERY_GROUPS; case ObjectType::ParkEntrance: - result = STR_OBJECT_SELECTION_PARK_ENTRANCE; - break; + return STR_OBJECT_SELECTION_PARK_ENTRANCE; case ObjectType::Water: - result = STR_OBJECT_SELECTION_WATER; - break; + return STR_OBJECT_SELECTION_WATER; default: - result = STR_UNKNOWN_OBJECT_TYPE; + return STR_UNKNOWN_OBJECT_TYPE; } - return result; } -/** - * Returns a newline-separated string listing all object names. - * Used for placing all names on the clipboard. - */ -static void copy_object_names_to_clipboard(rct_window* w) +class ObjectLoadErrorWindow final : public rct_window { - std::stringstream stream; - for (uint16_t i = 0; i < w->no_list_items; i++) +private: + std::vector _invalidEntries; + int32_t _highlightedIndex = -1; + std::string _filePath; +#ifndef DISABLE_HTTP + ObjectDownloader _objDownloader; + bool _updatedListAfterDownload{}; + + void DownloadAllObjects() { - const auto& entry = _invalid_entries[i]; - stream << entry.GetName(); - stream << PLATFORM_NEWLINE; + if (!_objDownloader.IsDownloading()) + { + _updatedListAfterDownload = false; + _objDownloader.Begin(_invalidEntries); + } } - auto clip = stream.str(); - OpenRCT2::GetContext()->GetUiContext()->SetClipboardText(clip.c_str()); -} + void UpdateObjectList() + { + const auto entries = _objDownloader.GetDownloadedEntries(); + for (auto& de : entries) + { + _invalidEntries.erase( + std::remove_if( + _invalidEntries.begin(), _invalidEntries.end(), + [de](const ObjectEntryDescriptor& e) { return de.GetName() == e.GetName(); }), + _invalidEntries.end()); + } + no_list_items = static_cast(_invalidEntries.size()); + } +#endif + + /** + * Returns a newline-separated string listing all object names. + * Used for placing all names on the clipboard. + */ + void CopyObjectNamesToClipboard() + { + std::stringstream stream; + for (uint16_t i = 0; i < no_list_items; i++) + { + const auto& entry = _invalidEntries[i]; + stream << entry.GetName(); + stream << PLATFORM_NEWLINE; + } + + const auto clip = stream.str(); + OpenRCT2::GetContext()->GetUiContext()->SetClipboardText(clip.c_str()); + } + + void SelectObjectFromList(const int32_t index) + { + if (index < 0 || index > no_list_items) + { + selected_list_item = -1; + } + else + { + selected_list_item = index; + } + widget_invalidate(this, WIDX_SCROLL); + } + +public: + void OnOpen() override + { + widgets = window_object_load_error_widgets; + enabled_widgets = (1ULL << WIDX_CLOSE) | (1ULL << WIDX_COPY_CURRENT) | (1ULL << WIDX_COPY_ALL) + | (1ULL << WIDX_DOWNLOAD_ALL); + + WindowInitScrollWidgets(this); + colours[0] = COLOUR_LIGHT_BLUE; + colours[1] = COLOUR_LIGHT_BLUE; + colours[2] = COLOUR_LIGHT_BLUE; + } + + void OnClose() override + { + _invalidEntries.clear(); + _invalidEntries.shrink_to_fit(); + } + + void OnMouseUp(const rct_widgetindex widgetIndex) override + { + switch (widgetIndex) + { + case WIDX_CLOSE: + window_close(this); + return; + case WIDX_COPY_CURRENT: + if (selected_list_item > -1 && selected_list_item < no_list_items) + { + const auto name = std::string(_invalidEntries[selected_list_item].GetName()); + OpenRCT2::GetContext()->GetUiContext()->SetClipboardText(name.c_str()); + } + break; + case WIDX_COPY_ALL: + CopyObjectNamesToClipboard(); + break; +#ifndef DISABLE_HTTP + case WIDX_DOWNLOAD_ALL: + DownloadAllObjects(); + break; +#endif + } + } + + void OnUpdate() override + { + frame_no++; + + // Check if the mouse is hovering over the list + if (!WidgetIsHighlighted(this, WIDX_SCROLL)) + { + _highlightedIndex = -1; + widget_invalidate(this, WIDX_SCROLL); + } + +#ifndef DISABLE_HTTP + _objDownloader.Update(); + + // Remove downloaded objects from our invalid entry list + if (_objDownloader.IsDownloading()) + { + // Don't do this too often as it isn't particularly efficient + if (frame_no % 64 == 0) + { + UpdateObjectList(); + } + } + else if (!_updatedListAfterDownload) + { + UpdateObjectList(); + _updatedListAfterDownload = true; + } +#endif + } + + ScreenSize OnScrollGetSize(const int32_t scrollIndex) override + { + return ScreenSize(0, no_list_items * SCROLLABLE_ROW_HEIGHT); + } + + void OnScrollMouseDown(const int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override + { + const auto selectedItem = screenCoords.y / SCROLLABLE_ROW_HEIGHT; + SelectObjectFromList(selectedItem); + } + + void OnScrollMouseOver(const int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override + { + // Highlight item that the cursor is over, or remove highlighting if none + const auto selectedItem = screenCoords.y / SCROLLABLE_ROW_HEIGHT; + if (selectedItem < 0 || selectedItem >= no_list_items) + _highlightedIndex = -1; + else + _highlightedIndex = selectedItem; + + widget_invalidate(this, WIDX_SCROLL); + } + + void OnDraw(rct_drawpixelinfo& dpi) override + { + WindowDrawWidgets(this, &dpi); + + // Draw explanatory message + auto ft = Formatter(); + ft.Add(STR_OBJECT_ERROR_WINDOW_EXPLANATION); + DrawTextWrapped(&dpi, windowPos + ScreenCoordsXY{ 5, 18 }, WW - 10, STR_BLACK_STRING, ft); + + // Draw file name + ft = Formatter(); + ft.Add(STR_OBJECT_ERROR_WINDOW_FILE); + ft.Add(_filePath.c_str()); + DrawTextEllipsised(&dpi, { windowPos.x + 5, windowPos.y + 43 }, WW - 5, STR_BLACK_STRING, ft); + } + + void OnScrollDraw(const int32_t scrollIndex, rct_drawpixelinfo& dpi) override + { + auto dpiCoords = ScreenCoordsXY{ dpi.x, dpi.y }; + gfx_fill_rect( + &dpi, { dpiCoords, dpiCoords + ScreenCoordsXY{ dpi.width - 1, dpi.height - 1 } }, ColourMapA[colours[1]].mid_light); + const int32_t listWidth = widgets[WIDX_SCROLL].width(); + + for (int32_t i = 0; i < no_list_items; i++) + { + ScreenCoordsXY screenCoords; + screenCoords.y = i * SCROLLABLE_ROW_HEIGHT; + if (screenCoords.y > dpi.y + dpi.height) + break; + + if (screenCoords.y + SCROLLABLE_ROW_HEIGHT < dpi.y) + continue; + + const auto screenRect = ScreenRect{ { 0, screenCoords.y }, + { listWidth, screenCoords.y + SCROLLABLE_ROW_HEIGHT - 1 } }; + // If hovering over item, change the color and fill the backdrop. + if (i == selected_list_item) + gfx_fill_rect(&dpi, screenRect, ColourMapA[colours[1]].darker); + else if (i == _highlightedIndex) + gfx_fill_rect(&dpi, screenRect, ColourMapA[colours[1]].mid_dark); + else if ((i & 1) != 0) // odd / even check + gfx_fill_rect(&dpi, screenRect, ColourMapA[colours[1]].light); + + // Draw the actual object entry's name... + screenCoords.x = NAME_COL_LEFT - 3; + + const auto& entry = _invalidEntries[i]; + + auto name = entry.GetName(); + char buffer[256]; + String::Set(buffer, sizeof(buffer), name.data(), name.size()); + gfx_draw_string(&dpi, screenCoords, buffer, { COLOUR_DARK_GREEN }); + + if (entry.Generation == ObjectGeneration::DAT) + { + // ... source game ... + const auto sourceStringId = object_manager_get_source_game_string(entry.Entry.GetSourceGame()); + DrawTextBasic(&dpi, { SOURCE_COL_LEFT - 3, screenCoords.y }, sourceStringId, {}, { COLOUR_DARK_GREEN }); + } + + // ... and type + const auto type = GetStringFromObjectType(entry.GetType()); + DrawTextBasic(&dpi, { TYPE_COL_LEFT - 3, screenCoords.y }, type, {}, { COLOUR_DARK_GREEN }); + } + } + + void Initialise(utf8* path, const size_t numMissingObjects, const ObjectEntryDescriptor* missingObjects) + { + _invalidEntries = std::vector(missingObjects, missingObjects + numMissingObjects); + + // Refresh list items and path + no_list_items = static_cast(numMissingObjects); + _filePath = path; + + Invalidate(); + } +}; rct_window* window_object_load_error_open(utf8* path, size_t numMissingObjects, const ObjectEntryDescriptor* missingObjects) { - _invalid_entries = std::vector(missingObjects, missingObjects + numMissingObjects); - // Check if window is already open - rct_window* window = window_bring_to_front_by_class(WC_OBJECT_LOAD_ERROR); + auto* window = window_bring_to_front_by_class(WC_OBJECT_LOAD_ERROR); if (window == nullptr) { - window = WindowCreateCentred(WW, WH, &window_object_load_error_events, WC_OBJECT_LOAD_ERROR, 0); - - window->widgets = window_object_load_error_widgets; - window->enabled_widgets = (1ULL << WIDX_CLOSE) | (1ULL << WIDX_COPY_CURRENT) | (1ULL << WIDX_COPY_ALL) - | (1ULL << WIDX_DOWNLOAD_ALL); - - WindowInitScrollWidgets(window); - window->colours[0] = COLOUR_LIGHT_BLUE; - window->colours[1] = COLOUR_LIGHT_BLUE; - window->colours[2] = COLOUR_LIGHT_BLUE; + window = WindowCreate(WC_OBJECT_LOAD_ERROR, WW, WH, 0); } - // Refresh list items and path - window->no_list_items = static_cast(numMissingObjects); - file_path = path; + static_cast(window)->Initialise(path, numMissingObjects, missingObjects); - window->Invalidate(); return window; } - -static void window_object_load_error_close(rct_window* w) -{ - _invalid_entries.clear(); - _invalid_entries.shrink_to_fit(); -} - -static void window_object_load_error_update(rct_window* w) -{ - w->frame_no++; - - // Check if the mouse is hovering over the list - if (!WidgetIsHighlighted(w, WIDX_SCROLL)) - { - highlighted_index = -1; - widget_invalidate(w, WIDX_SCROLL); - } - -#ifndef DISABLE_HTTP - _objDownloader.Update(); - - // Remove downloaded objects from our invalid entry list - if (_objDownloader.IsDownloading()) - { - // Don't do this too often as it isn't particularly efficient - if (w->frame_no % 64 == 0) - { - window_object_load_error_update_list(w); - } - } - else if (!_updatedListAfterDownload) - { - window_object_load_error_update_list(w); - _updatedListAfterDownload = true; - } -#endif -} - -static void window_object_load_error_mouseup(rct_window* w, rct_widgetindex widgetIndex) -{ - switch (widgetIndex) - { - case WIDX_CLOSE: - window_close(w); - break; - case WIDX_COPY_CURRENT: - if (w->selected_list_item > -1 && w->selected_list_item < w->no_list_items) - { - auto name = std::string(_invalid_entries[w->selected_list_item].GetName()); - OpenRCT2::GetContext()->GetUiContext()->SetClipboardText(name.c_str()); - } - break; - case WIDX_COPY_ALL: - copy_object_names_to_clipboard(w); - break; -#ifndef DISABLE_HTTP - case WIDX_DOWNLOAD_ALL: - window_object_load_error_download_all(w); - break; -#endif - } -} - -static void window_object_load_error_scrollmouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords) -{ - // Highlight item that the cursor is over, or remove highlighting if none - int32_t selected_item; - selected_item = screenCoords.y / SCROLLABLE_ROW_HEIGHT; - if (selected_item < 0 || selected_item >= w->no_list_items) - highlighted_index = -1; - else - highlighted_index = selected_item; - - widget_invalidate(w, WIDX_SCROLL); -} - -static void window_object_load_error_select_element_from_list(rct_window* w, int32_t index) -{ - if (index < 0 || index > w->no_list_items) - { - w->selected_list_item = -1; - } - else - { - w->selected_list_item = index; - } - widget_invalidate(w, WIDX_SCROLL); -} - -static void window_object_load_error_scrollmousedown(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords) -{ - int32_t selected_item; - selected_item = screenCoords.y / SCROLLABLE_ROW_HEIGHT; - window_object_load_error_select_element_from_list(w, selected_item); -} - -static void window_object_load_error_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height) -{ - *height = w->no_list_items * SCROLLABLE_ROW_HEIGHT; -} - -static void window_object_load_error_paint(rct_window* w, rct_drawpixelinfo* dpi) -{ - WindowDrawWidgets(w, dpi); - - // Draw explanatory message - auto ft = Formatter(); - ft.Add(STR_OBJECT_ERROR_WINDOW_EXPLANATION); - DrawTextWrapped(dpi, w->windowPos + ScreenCoordsXY{ 5, 18 }, WW - 10, STR_BLACK_STRING, ft); - - // Draw file name - ft = Formatter(); - ft.Add(STR_OBJECT_ERROR_WINDOW_FILE); - ft.Add(file_path.c_str()); - DrawTextEllipsised(dpi, { w->windowPos.x + 5, w->windowPos.y + 43 }, WW - 5, STR_BLACK_STRING, ft); -} - -static void window_object_load_error_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex) -{ - auto dpiCoords = ScreenCoordsXY{ dpi->x, dpi->y }; - gfx_fill_rect( - dpi, { dpiCoords, dpiCoords + ScreenCoordsXY{ dpi->width - 1, dpi->height - 1 } }, ColourMapA[w->colours[1]].mid_light); - const int32_t list_width = w->widgets[WIDX_SCROLL].width(); - - for (int32_t i = 0; i < w->no_list_items; i++) - { - ScreenCoordsXY screenCoords; - screenCoords.y = i * SCROLLABLE_ROW_HEIGHT; - if (screenCoords.y > dpi->y + dpi->height) - break; - - if (screenCoords.y + SCROLLABLE_ROW_HEIGHT < dpi->y) - continue; - - auto screenRect = ScreenRect{ { 0, screenCoords.y }, { list_width, screenCoords.y + SCROLLABLE_ROW_HEIGHT - 1 } }; - // If hovering over item, change the color and fill the backdrop. - if (i == w->selected_list_item) - gfx_fill_rect(dpi, screenRect, ColourMapA[w->colours[1]].darker); - else if (i == highlighted_index) - gfx_fill_rect(dpi, screenRect, ColourMapA[w->colours[1]].mid_dark); - else if ((i & 1) != 0) // odd / even check - gfx_fill_rect(dpi, screenRect, ColourMapA[w->colours[1]].light); - - // Draw the actual object entry's name... - screenCoords.x = NAME_COL_LEFT - 3; - - const auto& entry = _invalid_entries[i]; - - auto name = entry.GetName(); - char buffer[256]; - String::Set(buffer, sizeof(buffer), name.data(), name.size()); - gfx_draw_string(dpi, screenCoords, buffer, { COLOUR_DARK_GREEN }); - - if (entry.Generation == ObjectGeneration::DAT) - { - // ... source game ... - rct_string_id sourceStringId = object_manager_get_source_game_string(entry.Entry.GetSourceGame()); - DrawTextBasic(dpi, { SOURCE_COL_LEFT - 3, screenCoords.y }, sourceStringId, {}, { COLOUR_DARK_GREEN }); - } - - // ... and type - rct_string_id type = get_object_type_string(entry.GetType()); - DrawTextBasic(dpi, { TYPE_COL_LEFT - 3, screenCoords.y }, type, {}, { COLOUR_DARK_GREEN }); - } -} - -#ifndef DISABLE_HTTP - -static void window_object_load_error_download_all(rct_window* w) -{ - if (!_objDownloader.IsDownloading()) - { - _updatedListAfterDownload = false; - _objDownloader.Begin(_invalid_entries); - } -} - -static void window_object_load_error_update_list(rct_window* w) -{ - auto entries = _objDownloader.GetDownloadedEntries(); - for (auto& de : entries) - { - _invalid_entries.erase( - std::remove_if( - _invalid_entries.begin(), _invalid_entries.end(), - [de](const ObjectEntryDescriptor& e) { return de.GetName() == e.GetName(); }), - _invalid_entries.end()); - w->no_list_items = static_cast(_invalid_entries.size()); - } -} - -#endif diff --git a/src/openrct2-ui/windows/TrackList.cpp b/src/openrct2-ui/windows/TrackList.cpp index 03045137d2..6cdc7e4e29 100644 --- a/src/openrct2-ui/windows/TrackList.cpp +++ b/src/openrct2-ui/windows/TrackList.cpp @@ -60,58 +60,693 @@ static rct_widget window_track_list_widgets[] = { WIDGETS_END, }; -static void window_track_list_close(rct_window *w); -static void window_track_list_mouseup(rct_window *w, rct_widgetindex widgetIndex); -static void window_track_list_update(rct_window *w); -static void window_track_list_scrollgetsize(rct_window *w, int32_t scrollIndex, int32_t *width, int32_t *height); -static void window_track_list_scrollmousedown(rct_window *w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords); -static void window_track_list_scrollmouseover(rct_window *w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords); -static void window_track_list_textinput(rct_window *w, rct_widgetindex widgetIndex, char *text); -static void window_track_list_invalidate(rct_window *w); -static void window_track_list_paint(rct_window *w, rct_drawpixelinfo *dpi); -static void window_track_list_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, int32_t scrollIndex); - -static rct_window_event_list window_track_list_events([](auto& events) -{ - events.close = &window_track_list_close; - events.mouse_up = &window_track_list_mouseup; - events.update = &window_track_list_update; - events.get_scroll_size = &window_track_list_scrollgetsize; - events.scroll_mousedown = &window_track_list_scrollmousedown; - events.scroll_mouseover = &window_track_list_scrollmouseover; - events.text_input = &window_track_list_textinput; - events.invalidate = &window_track_list_invalidate; - events.paint = &window_track_list_paint; - events.scroll_paint = &window_track_list_scrollpaint; -}); // clang-format on constexpr uint16_t TRACK_DESIGN_INDEX_UNLOADED = UINT16_MAX; RideSelection _window_track_list_item; -static std::vector _trackDesigns; -static utf8 _filterString[USER_STRING_MAX_LENGTH]; -static std::vector _filteredTrackIds; -static uint16_t _loadedTrackDesignIndex; -static std::unique_ptr _loadedTrackDesign; -static std::vector _trackDesignPreviewPixels; +class TrackListWindow final : public Window +{ +private: + std::vector _trackDesigns; + utf8 _filterString[USER_STRING_MAX_LENGTH]; + std::vector _filteredTrackIds; + uint16_t _loadedTrackDesignIndex; + std::unique_ptr _loadedTrackDesign; + std::vector _trackDesignPreviewPixels; -static void track_list_load_designs(RideSelection item); -static bool track_list_load_design_for_preview(utf8* path); + void FilterList() + { + _filteredTrackIds.clear(); -/** - * - * rct2: 0x006CF1A2 - */ -rct_window* window_track_list_open(RideSelection item) + // Nothing to filter, so fill the list with all indices + if (String::LengthOf(_filterString) == 0) + { + for (uint16_t i = 0; i < _trackDesigns.size(); i++) + _filteredTrackIds.push_back(i); + + return; + } + + // Convert filter to lowercase + utf8 filterStringLower[sizeof(_filterString)]; + String::Set(filterStringLower, sizeof(filterStringLower), _filterString); + for (int32_t i = 0; filterStringLower[i] != '\0'; i++) + filterStringLower[i] = static_cast(tolower(filterStringLower[i])); + + // Fill the set with indices for tracks that match the filter + for (uint16_t i = 0; i < _trackDesigns.size(); i++) + { + utf8 trackNameLower[USER_STRING_MAX_LENGTH]; + String::Set(trackNameLower, sizeof(trackNameLower), _trackDesigns[i].name); + for (int32_t j = 0; trackNameLower[j] != '\0'; j++) + trackNameLower[j] = static_cast(tolower(trackNameLower[j])); + + if (strstr(trackNameLower, filterStringLower) != nullptr) + { + _filteredTrackIds.push_back(i); + } + } + } + + void SelectFromList(int32_t listIndex) + { + OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click1, 0, this->windowPos.x + (this->width / 2)); + if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)) + { + if (listIndex == 0) + { + Close(); + ride_construct_new(_window_track_list_item); + return; + } + listIndex--; + } + + // Displays a message if the ride can't load, fix #4080 + if (_loadedTrackDesign == nullptr) + { + context_show_error(STR_CANT_BUILD_THIS_HERE, STR_TRACK_LOAD_FAILED_ERROR, {}); + return; + } + + if (_loadedTrackDesign->track_flags & TRACK_DESIGN_FLAG_SCENERY_UNAVAILABLE) + { + gTrackDesignSceneryToggle = true; + } + + uint16_t trackDesignIndex = _filteredTrackIds[listIndex]; + track_design_file_ref* tdRef = &_trackDesigns[trackDesignIndex]; + if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) + { + auto intent = Intent(WC_MANAGE_TRACK_DESIGN); + intent.putExtra(INTENT_EXTRA_TRACK_DESIGN, tdRef); + context_open_intent(&intent); + } + else + { + if (_loadedTrackDesignIndex != TRACK_DESIGN_INDEX_UNLOADED + && (_loadedTrackDesign->track_flags & TRACK_DESIGN_FLAG_VEHICLE_UNAVAILABLE)) + { + context_show_error(STR_THIS_DESIGN_WILL_BE_BUILT_WITH_AN_ALTERNATIVE_VEHICLE_TYPE, STR_NONE, {}); + } + + auto intent = Intent(WC_TRACK_DESIGN_PLACE); + intent.putExtra(INTENT_EXTRA_TRACK_DESIGN, tdRef); + context_open_intent(&intent); + } + } + + int32_t GetListItemFromPosition(const ScreenCoordsXY& screenCoords) + { + size_t maxItems = _filteredTrackIds.size(); + if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)) + { + // Extra item: custom design + maxItems++; + } + + int32_t index = screenCoords.y / SCROLLABLE_ROW_HEIGHT; + if (index < 0 || static_cast(index) >= maxItems) + { + index = -1; + } + return index; + } + + void LoadDesignsList(RideSelection item) + { + auto repo = OpenRCT2::GetContext()->GetTrackDesignRepository(); + std::string entryName; + if (item.Type < 0x80) + { + if (GetRideTypeDescriptor(item.Type).HasFlag(RIDE_TYPE_FLAG_LIST_VEHICLES_SEPARATELY)) + { + entryName = get_ride_entry_name(item.EntryIndex); + } + } + _trackDesigns = repo->GetItemsForObjectEntry(item.Type, entryName); + + FilterList(); + } + + bool LoadDesignPreview(utf8* path) + { + _loadedTrackDesign = track_design_open(path); + if (_loadedTrackDesign != nullptr) + { + track_design_draw_preview(_loadedTrackDesign.get(), _trackDesignPreviewPixels.data()); + return true; + } + return false; + } + +public: + void OnOpen() override + { + String::Set(_filterString, sizeof(_filterString), ""); + window_track_list_widgets[WIDX_FILTER_STRING].string = _filterString; + widgets = window_track_list_widgets; + enabled_widgets = (1ULL << WIDX_CLOSE) | (1ULL << WIDX_BACK) | (1ULL << WIDX_FILTER_STRING) + | (1ULL << WIDX_FILTER_CLEAR) | (1ULL << WIDX_ROTATE) | (1ULL << WIDX_TOGGLE_SCENERY); + + WindowInitScrollWidgets(this); + track_list.track_list_being_updated = false; + track_list.reload_track_designs = false; + // Start with first track highlighted + selected_list_item = 0; + if (_trackDesigns.size() != 0 && !(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)) + { + selected_list_item = 1; + } + gTrackDesignSceneryToggle = false; + window_push_others_right(this); + _currentTrackPieceDirection = 2; + _trackDesignPreviewPixels.resize(4 * TRACK_PREVIEW_IMAGE_SIZE); + + _loadedTrackDesign = nullptr; + _loadedTrackDesignIndex = TRACK_DESIGN_INDEX_UNLOADED; + } + + void OnClose() override + { + // Dispose track design and preview + _loadedTrackDesign = nullptr; + _trackDesignPreviewPixels.clear(); + _trackDesignPreviewPixels.shrink_to_fit(); + + // Dispose track list + for (auto& trackDesign : _trackDesigns) + { + free(trackDesign.name); + free(trackDesign.path); + } + _trackDesigns.clear(); + + // If gScreenAge is zero, we're already in the process + // of loading the track manager, so we shouldn't try + // to do it again. Otherwise, this window will get + // another close signal from the track manager load function, + // try to load the track manager again, and an infinite loop will result. + if ((gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) && gScreenAge != 0) + { + Close(); + Editor::LoadTrackManager(); + } + } + + void OnMouseUp(const rct_widgetindex widgetIndex) override + { + switch (widgetIndex) + { + case WIDX_CLOSE: + Close(); + break; + case WIDX_ROTATE: + _currentTrackPieceDirection++; + _currentTrackPieceDirection %= 4; + Invalidate(); + break; + case WIDX_TOGGLE_SCENERY: + gTrackDesignSceneryToggle = !gTrackDesignSceneryToggle; + _loadedTrackDesignIndex = TRACK_DESIGN_INDEX_UNLOADED; + Invalidate(); + break; + case WIDX_BACK: + Close(); + if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)) + { + context_open_window(WC_CONSTRUCT_RIDE); + } + break; + case WIDX_FILTER_STRING: + window_start_textbox(this, widgetIndex, STR_STRING, _filterString, sizeof(_filterString)); // TODO check this + // out + break; + case WIDX_FILTER_CLEAR: + // Keep the highlighted item selected + if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) + { + if (selected_list_item != -1 && _filteredTrackIds.size() > static_cast(selected_list_item)) + selected_list_item = _filteredTrackIds[selected_list_item]; + else + selected_list_item = -1; + } + else + { + if (selected_list_item != 0) + selected_list_item = _filteredTrackIds[selected_list_item - 1] + 1; + } + + String::Set(_filterString, sizeof(_filterString), ""); + FilterList(); + Invalidate(); + break; + } + } + + ScreenSize OnScrollGetSize(const int32_t scrollIndex) override + { + size_t numItems = _filteredTrackIds.size(); + if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)) + { + // Extra item: custom design + numItems++; + } + int32_t scrollHeight = static_cast(numItems * SCROLLABLE_ROW_HEIGHT); + + return { width, scrollHeight }; + } + + void OnScrollMouseDown(const int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override + { + if (!track_list.track_list_being_updated) + { + int32_t i = GetListItemFromPosition(screenCoords); + if (i != -1) + { + SelectFromList(i); + } + } + } + + void OnScrollMouseOver(const int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override + { + if (!track_list.track_list_being_updated) + { + int32_t i = GetListItemFromPosition(screenCoords); + if (i != -1 && selected_list_item != i) + { + selected_list_item = i; + Invalidate(); + } + } + } + + void OnTextInput(const rct_widgetindex widgetIndex, std::string_view text) override + { + if (widgetIndex != WIDX_FILTER_STRING || text.empty()) + return; + + if (String::Equals(_filterString, std::string(text).c_str())) + return; + + String::Set(_filterString, sizeof(_filterString), std::string(text).c_str()); + + FilterList(); + + scrolls->v_top = 0; + + Invalidate(); + } + + void OnPrepareDraw() override + { + rct_string_id stringId = STR_NONE; + rct_ride_entry* entry = get_ride_entry(_window_track_list_item.EntryIndex); + + if (entry != nullptr) + { + RideNaming rideName = get_ride_naming(_window_track_list_item.Type, entry); + stringId = rideName.Name; + } + + Formatter::Common().Add(stringId); + if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) + { + window_track_list_widgets[WIDX_TITLE].text = STR_TRACK_DESIGNS; + window_track_list_widgets[WIDX_TRACK_LIST].tooltip = STR_CLICK_ON_DESIGN_TO_RENAME_OR_DELETE_IT; + } + else + { + window_track_list_widgets[WIDX_TITLE].text = STR_SELECT_DESIGN; + window_track_list_widgets[WIDX_TRACK_LIST].tooltip = STR_CLICK_ON_DESIGN_TO_BUILD_IT_TIP; + } + + if ((gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) || selected_list_item != 0) + { + pressed_widgets |= 1ULL << WIDX_TRACK_PREVIEW; + disabled_widgets &= ~(1ULL << WIDX_TRACK_PREVIEW); + window_track_list_widgets[WIDX_ROTATE].type = WindowWidgetType::FlatBtn; + window_track_list_widgets[WIDX_TOGGLE_SCENERY].type = WindowWidgetType::FlatBtn; + if (gTrackDesignSceneryToggle) + { + pressed_widgets &= ~(1ULL << WIDX_TOGGLE_SCENERY); + } + else + { + pressed_widgets |= (1ULL << WIDX_TOGGLE_SCENERY); + } + } + else + { + pressed_widgets &= ~(1ULL << WIDX_TRACK_PREVIEW); + disabled_widgets |= (1ULL << WIDX_TRACK_PREVIEW); + window_track_list_widgets[WIDX_ROTATE].type = WindowWidgetType::Empty; + window_track_list_widgets[WIDX_TOGGLE_SCENERY].type = WindowWidgetType::Empty; + } + + // When debugging tools are on, shift everything up a bit to make room for displaying the path. + const int32_t bottomMargin = gConfigGeneral.debugging_tools ? (WINDOW_PADDING + DEBUG_PATH_HEIGHT) : WINDOW_PADDING; + window_track_list_widgets[WIDX_TRACK_LIST].bottom = height - bottomMargin; + window_track_list_widgets[WIDX_ROTATE].bottom = height - bottomMargin; + window_track_list_widgets[WIDX_ROTATE].top = window_track_list_widgets[WIDX_ROTATE].bottom + - ROTATE_AND_SCENERY_BUTTON_SIZE; + window_track_list_widgets[WIDX_TOGGLE_SCENERY].bottom = window_track_list_widgets[WIDX_ROTATE].top; + window_track_list_widgets[WIDX_TOGGLE_SCENERY].top = window_track_list_widgets[WIDX_TOGGLE_SCENERY].bottom + - ROTATE_AND_SCENERY_BUTTON_SIZE; + } + + void OnUpdate() override + { + if (gCurrentTextBox.window.classification == classification && gCurrentTextBox.window.number == number) + { + window_update_textbox_caret(); + widget_invalidate(this, WIDX_FILTER_STRING); // TODO Check this + } + + if (track_list.reload_track_designs) + { + LoadDesignsList(_window_track_list_item); + selected_list_item = 0; + Invalidate(); + track_list.reload_track_designs = false; + } + } + + void OnDraw(rct_drawpixelinfo& dpi) override + { + DrawWidgets(dpi); + + int32_t listItemIndex = selected_list_item; + if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) + { + if (_trackDesigns.empty() || listItemIndex == -1) + return; + } + else + { + if (listItemIndex == 0) + return; + + // Because the first item in the list is "Build a custom design", lower the index by one + listItemIndex--; + } + + int32_t trackIndex = _filteredTrackIds[listItemIndex]; + + // Track preview + auto& tdWidget = widgets[WIDX_TRACK_PREVIEW]; + int32_t colour = ColourMapA[colours[0]].darkest; + utf8* path = _trackDesigns[trackIndex].path; + + // Show track file path (in debug mode) + if (gConfigGeneral.debugging_tools) + { + utf8 pathBuffer[MAX_PATH]; + const utf8* pathPtr = pathBuffer; + shorten_path(pathBuffer, sizeof(pathBuffer), path, width, FontSpriteBase::MEDIUM); + auto ft = Formatter(); + ft.Add(pathPtr); + DrawTextBasic( + &dpi, windowPos + ScreenCoordsXY{ 0, height - DEBUG_PATH_HEIGHT - 3 }, STR_STRING, ft, + { colours[1] }); // TODO Check dpi + } + + auto screenPos = windowPos + ScreenCoordsXY{ tdWidget.left + 1, tdWidget.top + 1 }; + gfx_fill_rect(&dpi, { screenPos, screenPos + ScreenCoordsXY{ 369, 216 } }, colour); // TODO Check dpi + + if (_loadedTrackDesignIndex != trackIndex) + { + if (LoadDesignPreview(path)) + { + _loadedTrackDesignIndex = trackIndex; + } + else + { + _loadedTrackDesignIndex = TRACK_DESIGN_INDEX_UNLOADED; + } + } + + if (!_loadedTrackDesign) + { + return; + } + + auto trackPreview = screenPos; + screenPos = windowPos + ScreenCoordsXY{ tdWidget.midX(), tdWidget.midY() }; + + rct_g1_element g1temp = {}; + g1temp.offset = _trackDesignPreviewPixels.data() + (_currentTrackPieceDirection * TRACK_PREVIEW_IMAGE_SIZE); + g1temp.width = 370; + g1temp.height = 217; + g1temp.flags = G1_FLAG_BMP; + gfx_set_g1_element(SPR_TEMP, &g1temp); + drawing_engine_invalidate_image(SPR_TEMP); + gfx_draw_sprite(&dpi, ImageId(SPR_TEMP), trackPreview); + + screenPos.y = windowPos.y + tdWidget.bottom - 12; + + // Warnings + if ((_loadedTrackDesign->track_flags & TRACK_DESIGN_FLAG_VEHICLE_UNAVAILABLE) + && !(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)) + { + // Vehicle design not available + DrawTextEllipsised(&dpi, screenPos, 368, STR_VEHICLE_DESIGN_UNAVAILABLE, {}, { TextAlignment::CENTRE }); + screenPos.y -= SCROLLABLE_ROW_HEIGHT; + } + + if (_loadedTrackDesign->track_flags & TRACK_DESIGN_FLAG_SCENERY_UNAVAILABLE) + { + if (!gTrackDesignSceneryToggle) + { + // Scenery not available + DrawTextEllipsised( + &dpi, screenPos, 368, STR_DESIGN_INCLUDES_SCENERY_WHICH_IS_UNAVAILABLE, {}, { TextAlignment::CENTRE }); + screenPos.y -= SCROLLABLE_ROW_HEIGHT; + } + } + + // Track design name + auto ft = Formatter(); + ft.Add(_trackDesigns[trackIndex].name); + DrawTextEllipsised(&dpi, screenPos, 368, STR_TRACK_PREVIEW_NAME_FORMAT, ft, { TextAlignment::CENTRE }); + + // Information + screenPos = windowPos + ScreenCoordsXY{ tdWidget.left + 1, tdWidget.bottom + 2 }; + + // Stats + ft = Formatter(); + ft.Add(_loadedTrackDesign->excitement * 10); + DrawTextBasic(&dpi, screenPos, STR_TRACK_LIST_EXCITEMENT_RATING, ft); + screenPos.y += LIST_ROW_HEIGHT; + + ft = Formatter(); + ft.Add(_loadedTrackDesign->intensity * 10); + DrawTextBasic(&dpi, screenPos, STR_TRACK_LIST_INTENSITY_RATING, ft); + screenPos.y += LIST_ROW_HEIGHT; + + ft = Formatter(); + ft.Add(_loadedTrackDesign->nausea * 10); + DrawTextBasic(&dpi, screenPos, STR_TRACK_LIST_NAUSEA_RATING, ft); + screenPos.y += LIST_ROW_HEIGHT + 4; + + // Information for tracked rides. + if (GetRideTypeDescriptor(_loadedTrackDesign->type).HasFlag(RIDE_TYPE_FLAG_HAS_TRACK)) + { + if (_loadedTrackDesign->type != RIDE_TYPE_MAZE) + { + if (_loadedTrackDesign->type == RIDE_TYPE_MINI_GOLF) + { + // Holes + ft = Formatter(); + ft.Add(_loadedTrackDesign->holes & 0x1F); + DrawTextBasic(&dpi, screenPos, STR_HOLES, ft); + screenPos.y += LIST_ROW_HEIGHT; + } + else + { + // Maximum speed + ft = Formatter(); + ft.Add(((_loadedTrackDesign->max_speed << 16) * 9) >> 18); + DrawTextBasic(&dpi, screenPos, STR_MAX_SPEED, ft); + screenPos.y += LIST_ROW_HEIGHT; + + // Average speed + ft = Formatter(); + ft.Add(((_loadedTrackDesign->average_speed << 16) * 9) >> 18); + DrawTextBasic(&dpi, screenPos, STR_AVERAGE_SPEED, ft); + screenPos.y += LIST_ROW_HEIGHT; + } + + // Ride length + ft = Formatter(); + ft.Add(STR_RIDE_LENGTH_ENTRY); + ft.Add(_loadedTrackDesign->ride_length); + DrawTextEllipsised(&dpi, screenPos, 214, STR_TRACK_LIST_RIDE_LENGTH, ft); + screenPos.y += LIST_ROW_HEIGHT; + } + + if (GetRideTypeDescriptor(_loadedTrackDesign->type).HasFlag(RIDE_TYPE_FLAG_HAS_G_FORCES)) + { + // Maximum positive vertical Gs + ft = Formatter(); + ft.Add(_loadedTrackDesign->max_positive_vertical_g * 32); + DrawTextBasic(&dpi, screenPos, STR_MAX_POSITIVE_VERTICAL_G, ft); + screenPos.y += LIST_ROW_HEIGHT; + + // Maximum negative vertical Gs + ft = Formatter(); + ft.Add(_loadedTrackDesign->max_negative_vertical_g * 32); + DrawTextBasic(&dpi, screenPos, STR_MAX_NEGATIVE_VERTICAL_G, ft); + screenPos.y += LIST_ROW_HEIGHT; + + // Maximum lateral Gs + ft = Formatter(); + ft.Add(_loadedTrackDesign->max_lateral_g * 32); + DrawTextBasic(&dpi, screenPos, STR_MAX_LATERAL_G, ft); + screenPos.y += LIST_ROW_HEIGHT; + + if (_loadedTrackDesign->total_air_time != 0) + { + // Total air time + ft = Formatter(); + ft.Add(_loadedTrackDesign->total_air_time * 25); + DrawTextBasic(&dpi, screenPos, STR_TOTAL_AIR_TIME, ft); + screenPos.y += LIST_ROW_HEIGHT; + } + } + + if (GetRideTypeDescriptor(_loadedTrackDesign->type).HasFlag(RIDE_TYPE_FLAG_HAS_DROPS)) + { + // Drops + ft = Formatter(); + ft.Add(_loadedTrackDesign->drops & 0x3F); + DrawTextBasic(&dpi, screenPos, STR_DROPS, ft); + screenPos.y += LIST_ROW_HEIGHT; + + // Drop height is multiplied by 0.75 + ft = Formatter(); + ft.Add((_loadedTrackDesign->highest_drop_height * 3) / 4); + DrawTextBasic(&dpi, screenPos, STR_HIGHEST_DROP_HEIGHT, ft); + screenPos.y += LIST_ROW_HEIGHT; + } + + if (_loadedTrackDesign->type != RIDE_TYPE_MINI_GOLF) + { + uint16_t inversions = _loadedTrackDesign->inversions & 0x1F; + if (inversions != 0) + { + ft = Formatter(); + ft.Add(inversions); + // Inversions + DrawTextBasic(&dpi, screenPos, STR_INVERSIONS, ft); + screenPos.y += LIST_ROW_HEIGHT; + } + } + screenPos.y += 4; + } + + if (_loadedTrackDesign->space_required_x != 0xFF) + { + // Space required + ft = Formatter(); + ft.Add(_loadedTrackDesign->space_required_x); + ft.Add(_loadedTrackDesign->space_required_y); + DrawTextBasic(&dpi, screenPos, STR_TRACK_LIST_SPACE_REQUIRED, ft); + screenPos.y += LIST_ROW_HEIGHT; + } + + if (_loadedTrackDesign->cost != 0) + { + ft = Formatter(); + ft.Add(_loadedTrackDesign->cost); + DrawTextBasic(&dpi, screenPos, STR_TRACK_LIST_COST_AROUND, ft); + } + } + + void OnScrollDraw(const int32_t scrollIndex, rct_drawpixelinfo& dpi) override + { + uint8_t paletteIndex = ColourMapA[colours[0]].mid_light; + gfx_clear(&dpi, paletteIndex); + + auto screenCoords = ScreenCoordsXY{ 0, 0 }; + size_t listIndex = 0; + if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) + { + if (_trackDesigns.empty()) + { + // No track designs + DrawTextBasic(&dpi, screenCoords - ScreenCoordsXY{ 0, 1 }, STR_NO_TRACK_DESIGNS_OF_THIS_TYPE); + return; + } + } + else + { + // Build custom track item + rct_string_id stringId; + if (listIndex == static_cast(selected_list_item)) + { + // Highlight + gfx_filter_rect( + &dpi, screenCoords.x, screenCoords.y, width, screenCoords.y + SCROLLABLE_ROW_HEIGHT - 1, + FilterPaletteID::PaletteDarken1); + stringId = STR_WINDOW_COLOUR_2_STRINGID; + } + else + { + stringId = STR_BLACK_STRING; + } + + auto ft = Formatter(); + ft.Add(STR_BUILD_CUSTOM_DESIGN); + DrawTextBasic(&dpi, screenCoords - ScreenCoordsXY{ 0, 1 }, stringId, ft); + screenCoords.y += SCROLLABLE_ROW_HEIGHT; + listIndex++; + } + + for (auto i : _filteredTrackIds) + { + if (screenCoords.y + SCROLLABLE_ROW_HEIGHT >= dpi.y && screenCoords.y < dpi.y + dpi.height) + { + rct_string_id stringId; + if (listIndex == static_cast(selected_list_item)) + { + // Highlight + gfx_filter_rect( + &dpi, screenCoords.x, screenCoords.y, width, screenCoords.y + SCROLLABLE_ROW_HEIGHT - 1, + FilterPaletteID::PaletteDarken1); + stringId = STR_WINDOW_COLOUR_2_STRINGID; + } + else + { + stringId = STR_BLACK_STRING; + } + + // Draw track name + auto ft = Formatter(); + ft.Add(STR_TRACK_LIST_NAME_FORMAT); + ft.Add(_trackDesigns[i].name); + DrawTextBasic(&dpi, screenCoords - ScreenCoordsXY{ 0, 1 }, stringId, ft); + } + + screenCoords.y += SCROLLABLE_ROW_HEIGHT; + listIndex++; + } + } + + bool SetRideSelection(const RideSelection item) + { + _window_track_list_item = item; + LoadDesignsList(item); + return true; + } +}; + +rct_window* window_track_list_open(const RideSelection item) { window_close_construction_windows(); - _window_track_list_item = item; - - String::Set(_filterString, sizeof(_filterString), ""); - track_list_load_designs(item); - ScreenCoordsXY screenPos{}; if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) { @@ -123,700 +758,7 @@ rct_window* window_track_list_open(RideSelection item) { screenPos = { 0, TOP_TOOLBAR_HEIGHT + 2 }; } - - rct_window* w = WindowCreate(screenPos, 600, WH, &window_track_list_events, WC_TRACK_DESIGN_LIST, 0); - - window_track_list_widgets[WIDX_FILTER_STRING].string = _filterString; - w->widgets = window_track_list_widgets; - w->enabled_widgets = (1ULL << WIDX_CLOSE) | (1ULL << WIDX_BACK) | (1ULL << WIDX_FILTER_STRING) | (1ULL << WIDX_FILTER_CLEAR) - | (1ULL << WIDX_ROTATE) | (1ULL << WIDX_TOGGLE_SCENERY); - - WindowInitScrollWidgets(w); - w->track_list.track_list_being_updated = false; - w->track_list.reload_track_designs = false; - - // Start with first track highlighted - w->selected_list_item = 0; - if (_trackDesigns.size() != 0 && !(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)) - { - w->selected_list_item = 1; - } - - gTrackDesignSceneryToggle = false; - window_push_others_right(w); - _currentTrackPieceDirection = 2; - - _trackDesignPreviewPixels.resize(4 * TRACK_PREVIEW_IMAGE_SIZE); - - _loadedTrackDesign = nullptr; - _loadedTrackDesignIndex = TRACK_DESIGN_INDEX_UNLOADED; - + auto* w = WindowCreate(WC_TRACK_DESIGN_LIST, WW, WH, 0); + w->SetRideSelection(item); return w; } - -static void window_track_list_filter_list() -{ - _filteredTrackIds.clear(); - - // Nothing to filter, so fill the list with all indices - if (String::LengthOf(_filterString) == 0) - { - for (uint16_t i = 0; i < _trackDesigns.size(); i++) - _filteredTrackIds.push_back(i); - - return; - } - - // Convert filter to lowercase - utf8 filterStringLower[sizeof(_filterString)]; - String::Set(filterStringLower, sizeof(filterStringLower), _filterString); - for (int32_t i = 0; filterStringLower[i] != '\0'; i++) - filterStringLower[i] = static_cast(tolower(filterStringLower[i])); - - // Fill the set with indices for tracks that match the filter - for (uint16_t i = 0; i < _trackDesigns.size(); i++) - { - utf8 trackNameLower[USER_STRING_MAX_LENGTH]; - String::Set(trackNameLower, sizeof(trackNameLower), _trackDesigns[i].name); - for (int32_t j = 0; trackNameLower[j] != '\0'; j++) - trackNameLower[j] = static_cast(tolower(trackNameLower[j])); - - if (strstr(trackNameLower, filterStringLower) != nullptr) - { - _filteredTrackIds.push_back(i); - } - } -} - -/** - * - * rct2: 0x006CFD76 - */ -static void window_track_list_close(rct_window* w) -{ - // Dispose track design and preview - _loadedTrackDesign = nullptr; - _trackDesignPreviewPixels.clear(); - _trackDesignPreviewPixels.shrink_to_fit(); - - // Dispose track list - for (auto& trackDesign : _trackDesigns) - { - free(trackDesign.name); - free(trackDesign.path); - } - _trackDesigns.clear(); - - // If gScreenAge is zero, we're already in the process - // of loading the track manager, so we shouldn't try - // to do it again. Otherwise, this window will get - // another close signal from the track manager load function, - // try to load the track manager again, and an infinite loop will result. - if ((gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) && gScreenAge != 0) - { - window_close_by_number(WC_MANAGE_TRACK_DESIGN, w->number); - window_close_by_number(WC_TRACK_DELETE_PROMPT, w->number); - Editor::LoadTrackManager(); - } -} - -/** - * - * rct2: 0x006CFB82 - */ -static void window_track_list_select(rct_window* w, int32_t listIndex) -{ - OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click1, 0, w->windowPos.x + (w->width / 2)); - if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)) - { - if (listIndex == 0) - { - window_close(w); - ride_construct_new(_window_track_list_item); - return; - } - listIndex--; - } - - // Displays a message if the ride can't load, fix #4080 - if (_loadedTrackDesign == nullptr) - { - context_show_error(STR_CANT_BUILD_THIS_HERE, STR_TRACK_LOAD_FAILED_ERROR, {}); - return; - } - - if (_loadedTrackDesign->track_flags & TRACK_DESIGN_FLAG_SCENERY_UNAVAILABLE) - { - gTrackDesignSceneryToggle = true; - } - - uint16_t trackDesignIndex = _filteredTrackIds[listIndex]; - track_design_file_ref* tdRef = &_trackDesigns[trackDesignIndex]; - if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) - { - auto intent = Intent(WC_MANAGE_TRACK_DESIGN); - intent.putExtra(INTENT_EXTRA_TRACK_DESIGN, tdRef); - context_open_intent(&intent); - } - else - { - if (_loadedTrackDesignIndex != TRACK_DESIGN_INDEX_UNLOADED - && (_loadedTrackDesign->track_flags & TRACK_DESIGN_FLAG_VEHICLE_UNAVAILABLE)) - { - context_show_error(STR_THIS_DESIGN_WILL_BE_BUILT_WITH_AN_ALTERNATIVE_VEHICLE_TYPE, STR_NONE, {}); - } - - auto intent = Intent(WC_TRACK_DESIGN_PLACE); - intent.putExtra(INTENT_EXTRA_TRACK_DESIGN, tdRef); - context_open_intent(&intent); - } -} - -static int32_t window_track_list_get_list_item_index_from_position(const ScreenCoordsXY& screenCoords) -{ - size_t maxItems = _filteredTrackIds.size(); - if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)) - { - // Extra item: custom design - maxItems++; - } - - int32_t index = screenCoords.y / SCROLLABLE_ROW_HEIGHT; - if (index < 0 || static_cast(index) >= maxItems) - { - index = -1; - } - return index; -} - -/** - * - * rct2: 0x006CFA31 - */ -static void window_track_list_mouseup(rct_window* w, rct_widgetindex widgetIndex) -{ - switch (widgetIndex) - { - case WIDX_CLOSE: - window_close(w); - break; - case WIDX_ROTATE: - _currentTrackPieceDirection++; - _currentTrackPieceDirection %= 4; - w->Invalidate(); - break; - case WIDX_TOGGLE_SCENERY: - gTrackDesignSceneryToggle = !gTrackDesignSceneryToggle; - _loadedTrackDesignIndex = TRACK_DESIGN_INDEX_UNLOADED; - w->Invalidate(); - break; - case WIDX_BACK: - window_close(w); - if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)) - { - context_open_window(WC_CONSTRUCT_RIDE); - } - break; - case WIDX_FILTER_STRING: - window_start_textbox(w, widgetIndex, STR_STRING, _filterString, sizeof(_filterString)); - break; - case WIDX_FILTER_CLEAR: - // Keep the highlighted item selected - if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) - { - if (w->selected_list_item != -1 && _filteredTrackIds.size() > static_cast(w->selected_list_item)) - w->selected_list_item = _filteredTrackIds[w->selected_list_item]; - else - w->selected_list_item = -1; - } - else - { - if (w->selected_list_item != 0) - w->selected_list_item = _filteredTrackIds[w->selected_list_item - 1] + 1; - } - - String::Set(_filterString, sizeof(_filterString), ""); - window_track_list_filter_list(); - w->Invalidate(); - break; - } -} - -/** - * - * rct2: 0x006CFAB0 - */ -static void window_track_list_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height) -{ - size_t numItems = _filteredTrackIds.size(); - if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)) - { - // Extra item: custom design - numItems++; - } - - *height = static_cast(numItems * SCROLLABLE_ROW_HEIGHT); -} - -/** - * - * rct2: 0x006CFB39 - */ -static void window_track_list_scrollmousedown(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords) -{ - if (!w->track_list.track_list_being_updated) - { - int32_t i = window_track_list_get_list_item_index_from_position(screenCoords); - if (i != -1) - { - window_track_list_select(w, i); - } - } -} - -/** - * - * rct2: 0x006CFAD7 - */ -static void window_track_list_scrollmouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords) -{ - if (!w->track_list.track_list_being_updated) - { - int32_t i = window_track_list_get_list_item_index_from_position(screenCoords); - if (i != -1 && w->selected_list_item != i) - { - w->selected_list_item = i; - w->Invalidate(); - } - } -} - -static void window_track_list_textinput(rct_window* w, rct_widgetindex widgetIndex, char* text) -{ - if (widgetIndex != WIDX_FILTER_STRING || text == nullptr) - return; - - if (String::Equals(_filterString, text)) - return; - - String::Set(_filterString, sizeof(_filterString), text); - - window_track_list_filter_list(); - - w->scrolls->v_top = 0; - - w->Invalidate(); -} - -static void window_track_list_update(rct_window* w) -{ - if (gCurrentTextBox.window.classification == w->classification && gCurrentTextBox.window.number == w->number) - { - window_update_textbox_caret(); - widget_invalidate(w, WIDX_FILTER_STRING); - } - - if (w->track_list.reload_track_designs) - { - track_list_load_designs(_window_track_list_item); - w->selected_list_item = 0; - w->Invalidate(); - w->track_list.reload_track_designs = false; - } -} - -/** - * - * rct2: 0x006CF2D6 - */ -static void window_track_list_invalidate(rct_window* w) -{ - rct_string_id stringId = STR_NONE; - rct_ride_entry* entry = get_ride_entry(_window_track_list_item.EntryIndex); - - if (entry != nullptr) - { - RideNaming rideName = get_ride_naming(_window_track_list_item.Type, entry); - stringId = rideName.Name; - } - - Formatter::Common().Add(stringId); - if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) - { - window_track_list_widgets[WIDX_TITLE].text = STR_TRACK_DESIGNS; - window_track_list_widgets[WIDX_TRACK_LIST].tooltip = STR_CLICK_ON_DESIGN_TO_RENAME_OR_DELETE_IT; - } - else - { - window_track_list_widgets[WIDX_TITLE].text = STR_SELECT_DESIGN; - window_track_list_widgets[WIDX_TRACK_LIST].tooltip = STR_CLICK_ON_DESIGN_TO_BUILD_IT_TIP; - } - - if ((gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) || w->selected_list_item != 0) - { - w->pressed_widgets |= 1ULL << WIDX_TRACK_PREVIEW; - w->disabled_widgets &= ~(1ULL << WIDX_TRACK_PREVIEW); - window_track_list_widgets[WIDX_ROTATE].type = WindowWidgetType::FlatBtn; - window_track_list_widgets[WIDX_TOGGLE_SCENERY].type = WindowWidgetType::FlatBtn; - if (gTrackDesignSceneryToggle) - { - w->pressed_widgets &= ~(1ULL << WIDX_TOGGLE_SCENERY); - } - else - { - w->pressed_widgets |= (1ULL << WIDX_TOGGLE_SCENERY); - } - } - else - { - w->pressed_widgets &= ~(1ULL << WIDX_TRACK_PREVIEW); - w->disabled_widgets |= (1ULL << WIDX_TRACK_PREVIEW); - window_track_list_widgets[WIDX_ROTATE].type = WindowWidgetType::Empty; - window_track_list_widgets[WIDX_TOGGLE_SCENERY].type = WindowWidgetType::Empty; - } - - // When debugging tools are on, shift everything up a bit to make room for displaying the path. - const int32_t bottomMargin = gConfigGeneral.debugging_tools ? (WINDOW_PADDING + DEBUG_PATH_HEIGHT) : WINDOW_PADDING; - window_track_list_widgets[WIDX_TRACK_LIST].bottom = w->height - bottomMargin; - window_track_list_widgets[WIDX_ROTATE].bottom = w->height - bottomMargin; - window_track_list_widgets[WIDX_ROTATE].top = window_track_list_widgets[WIDX_ROTATE].bottom - ROTATE_AND_SCENERY_BUTTON_SIZE; - window_track_list_widgets[WIDX_TOGGLE_SCENERY].bottom = window_track_list_widgets[WIDX_ROTATE].top; - window_track_list_widgets[WIDX_TOGGLE_SCENERY].top = window_track_list_widgets[WIDX_TOGGLE_SCENERY].bottom - - ROTATE_AND_SCENERY_BUTTON_SIZE; -} - -/** - * - * rct2: 0x006CF387 - */ -static void window_track_list_paint(rct_window* w, rct_drawpixelinfo* dpi) -{ - WindowDrawWidgets(w, dpi); - - int32_t listItemIndex = w->selected_list_item; - if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) - { - if (_trackDesigns.empty() || listItemIndex == -1) - return; - } - else - { - if (listItemIndex == 0) - return; - - // Because the first item in the list is "Build a custom design", lower the index by one - listItemIndex--; - } - - int32_t trackIndex = _filteredTrackIds[listItemIndex]; - - // Track preview - int32_t colour; - rct_widget* widget = &window_track_list_widgets[WIDX_TRACK_PREVIEW]; - colour = ColourMapA[w->colours[0]].darkest; - utf8* path = _trackDesigns[trackIndex].path; - - // Show track file path (in debug mode) - if (gConfigGeneral.debugging_tools) - { - utf8 pathBuffer[MAX_PATH]; - const utf8* pathPtr = pathBuffer; - shorten_path(pathBuffer, sizeof(pathBuffer), path, w->width, FontSpriteBase::MEDIUM); - auto ft = Formatter(); - ft.Add(pathPtr); - DrawTextBasic( - dpi, w->windowPos + ScreenCoordsXY{ 0, w->height - DEBUG_PATH_HEIGHT - 3 }, STR_STRING, ft, { w->colours[1] }); - } - - auto screenPos = w->windowPos + ScreenCoordsXY{ widget->left + 1, widget->top + 1 }; - gfx_fill_rect(dpi, { screenPos, screenPos + ScreenCoordsXY{ 369, 216 } }, colour); - - if (_loadedTrackDesignIndex != trackIndex) - { - if (track_list_load_design_for_preview(path)) - { - _loadedTrackDesignIndex = trackIndex; - } - else - { - _loadedTrackDesignIndex = TRACK_DESIGN_INDEX_UNLOADED; - } - } - - if (!_loadedTrackDesign) - { - return; - } - - auto trackPreview = screenPos; - screenPos = w->windowPos + ScreenCoordsXY{ widget->midX(), widget->midY() }; - - rct_g1_element g1temp = {}; - g1temp.offset = _trackDesignPreviewPixels.data() + (_currentTrackPieceDirection * TRACK_PREVIEW_IMAGE_SIZE); - g1temp.width = 370; - g1temp.height = 217; - g1temp.flags = G1_FLAG_BMP; - gfx_set_g1_element(SPR_TEMP, &g1temp); - drawing_engine_invalidate_image(SPR_TEMP); - gfx_draw_sprite(dpi, ImageId(SPR_TEMP), trackPreview); - - screenPos.y = w->windowPos.y + widget->bottom - 12; - - // Warnings - if ((_loadedTrackDesign->track_flags & TRACK_DESIGN_FLAG_VEHICLE_UNAVAILABLE) - && !(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)) - { - // Vehicle design not available - DrawTextEllipsised(dpi, screenPos, 368, STR_VEHICLE_DESIGN_UNAVAILABLE, {}, { TextAlignment::CENTRE }); - screenPos.y -= SCROLLABLE_ROW_HEIGHT; - } - - if (_loadedTrackDesign->track_flags & TRACK_DESIGN_FLAG_SCENERY_UNAVAILABLE) - { - if (!gTrackDesignSceneryToggle) - { - // Scenery not available - DrawTextEllipsised( - dpi, screenPos, 368, STR_DESIGN_INCLUDES_SCENERY_WHICH_IS_UNAVAILABLE, {}, { TextAlignment::CENTRE }); - screenPos.y -= SCROLLABLE_ROW_HEIGHT; - } - } - - // Track design name - auto ft = Formatter(); - ft.Add(_trackDesigns[trackIndex].name); - DrawTextEllipsised(dpi, screenPos, 368, STR_TRACK_PREVIEW_NAME_FORMAT, ft, { TextAlignment::CENTRE }); - - // Information - screenPos = w->windowPos + ScreenCoordsXY{ widget->left + 1, widget->bottom + 2 }; - - // Stats - ft = Formatter(); - ft.Add(_loadedTrackDesign->excitement * 10); - DrawTextBasic(dpi, screenPos, STR_TRACK_LIST_EXCITEMENT_RATING, ft); - screenPos.y += LIST_ROW_HEIGHT; - - ft = Formatter(); - ft.Add(_loadedTrackDesign->intensity * 10); - DrawTextBasic(dpi, screenPos, STR_TRACK_LIST_INTENSITY_RATING, ft); - screenPos.y += LIST_ROW_HEIGHT; - - ft = Formatter(); - ft.Add(_loadedTrackDesign->nausea * 10); - DrawTextBasic(dpi, screenPos, STR_TRACK_LIST_NAUSEA_RATING, ft); - screenPos.y += LIST_ROW_HEIGHT + 4; - - // Information for tracked rides. - if (GetRideTypeDescriptor(_loadedTrackDesign->type).HasFlag(RIDE_TYPE_FLAG_HAS_TRACK)) - { - if (_loadedTrackDesign->type != RIDE_TYPE_MAZE) - { - if (_loadedTrackDesign->type == RIDE_TYPE_MINI_GOLF) - { - // Holes - ft = Formatter(); - ft.Add(_loadedTrackDesign->holes & 0x1F); - DrawTextBasic(dpi, screenPos, STR_HOLES, ft); - screenPos.y += LIST_ROW_HEIGHT; - } - else - { - // Maximum speed - ft = Formatter(); - ft.Add(((_loadedTrackDesign->max_speed << 16) * 9) >> 18); - DrawTextBasic(dpi, screenPos, STR_MAX_SPEED, ft); - screenPos.y += LIST_ROW_HEIGHT; - - // Average speed - ft = Formatter(); - ft.Add(((_loadedTrackDesign->average_speed << 16) * 9) >> 18); - DrawTextBasic(dpi, screenPos, STR_AVERAGE_SPEED, ft); - screenPos.y += LIST_ROW_HEIGHT; - } - - // Ride length - ft = Formatter(); - ft.Add(STR_RIDE_LENGTH_ENTRY); - ft.Add(_loadedTrackDesign->ride_length); - DrawTextEllipsised(dpi, screenPos, 214, STR_TRACK_LIST_RIDE_LENGTH, ft); - screenPos.y += LIST_ROW_HEIGHT; - } - - if (GetRideTypeDescriptor(_loadedTrackDesign->type).HasFlag(RIDE_TYPE_FLAG_HAS_G_FORCES)) - { - // Maximum positive vertical Gs - ft = Formatter(); - ft.Add(_loadedTrackDesign->max_positive_vertical_g * 32); - DrawTextBasic(dpi, screenPos, STR_MAX_POSITIVE_VERTICAL_G, ft); - screenPos.y += LIST_ROW_HEIGHT; - - // Maximum negative vertical Gs - ft = Formatter(); - ft.Add(_loadedTrackDesign->max_negative_vertical_g * 32); - DrawTextBasic(dpi, screenPos, STR_MAX_NEGATIVE_VERTICAL_G, ft); - screenPos.y += LIST_ROW_HEIGHT; - - // Maximum lateral Gs - ft = Formatter(); - ft.Add(_loadedTrackDesign->max_lateral_g * 32); - DrawTextBasic(dpi, screenPos, STR_MAX_LATERAL_G, ft); - screenPos.y += LIST_ROW_HEIGHT; - - if (_loadedTrackDesign->total_air_time != 0) - { - // Total air time - ft = Formatter(); - ft.Add(_loadedTrackDesign->total_air_time * 25); - DrawTextBasic(dpi, screenPos, STR_TOTAL_AIR_TIME, ft); - screenPos.y += LIST_ROW_HEIGHT; - } - } - - if (GetRideTypeDescriptor(_loadedTrackDesign->type).HasFlag(RIDE_TYPE_FLAG_HAS_DROPS)) - { - // Drops - ft = Formatter(); - ft.Add(_loadedTrackDesign->drops & 0x3F); - DrawTextBasic(dpi, screenPos, STR_DROPS, ft); - screenPos.y += LIST_ROW_HEIGHT; - - // Drop height is multiplied by 0.75 - ft = Formatter(); - ft.Add((_loadedTrackDesign->highest_drop_height * 3) / 4); - DrawTextBasic(dpi, screenPos, STR_HIGHEST_DROP_HEIGHT, ft); - screenPos.y += LIST_ROW_HEIGHT; - } - - if (_loadedTrackDesign->type != RIDE_TYPE_MINI_GOLF) - { - uint16_t inversions = _loadedTrackDesign->inversions & 0x1F; - if (inversions != 0) - { - ft = Formatter(); - ft.Add(inversions); - // Inversions - DrawTextBasic(dpi, screenPos, STR_INVERSIONS, ft); - screenPos.y += LIST_ROW_HEIGHT; - } - } - screenPos.y += 4; - } - - if (_loadedTrackDesign->space_required_x != 0xFF) - { - // Space required - ft = Formatter(); - ft.Add(_loadedTrackDesign->space_required_x); - ft.Add(_loadedTrackDesign->space_required_y); - DrawTextBasic(dpi, screenPos, STR_TRACK_LIST_SPACE_REQUIRED, ft); - screenPos.y += LIST_ROW_HEIGHT; - } - - if (_loadedTrackDesign->cost != 0) - { - ft = Formatter(); - ft.Add(_loadedTrackDesign->cost); - DrawTextBasic(dpi, screenPos, STR_TRACK_LIST_COST_AROUND, ft); - } -} - -/** - * - * rct2: 0x006CF8CD - */ -static void window_track_list_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex) -{ - uint8_t paletteIndex = ColourMapA[w->colours[0]].mid_light; - gfx_clear(dpi, paletteIndex); - - auto screenCoords = ScreenCoordsXY{ 0, 0 }; - size_t listIndex = 0; - if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) - { - if (_trackDesigns.empty()) - { - // No track designs - DrawTextBasic(dpi, screenCoords - ScreenCoordsXY{ 0, 1 }, STR_NO_TRACK_DESIGNS_OF_THIS_TYPE); - return; - } - } - else - { - // Build custom track item - rct_string_id stringId; - if (listIndex == static_cast(w->selected_list_item)) - { - // Highlight - gfx_filter_rect( - dpi, screenCoords.x, screenCoords.y, w->width, screenCoords.y + SCROLLABLE_ROW_HEIGHT - 1, - FilterPaletteID::PaletteDarken1); - stringId = STR_WINDOW_COLOUR_2_STRINGID; - } - else - { - stringId = STR_BLACK_STRING; - } - - auto ft = Formatter(); - ft.Add(STR_BUILD_CUSTOM_DESIGN); - DrawTextBasic(dpi, screenCoords - ScreenCoordsXY{ 0, 1 }, stringId, ft); - screenCoords.y += SCROLLABLE_ROW_HEIGHT; - listIndex++; - } - - for (auto i : _filteredTrackIds) - { - if (screenCoords.y + SCROLLABLE_ROW_HEIGHT >= dpi->y && screenCoords.y < dpi->y + dpi->height) - { - rct_string_id stringId; - if (listIndex == static_cast(w->selected_list_item)) - { - // Highlight - gfx_filter_rect( - dpi, screenCoords.x, screenCoords.y, w->width, screenCoords.y + SCROLLABLE_ROW_HEIGHT - 1, - FilterPaletteID::PaletteDarken1); - stringId = STR_WINDOW_COLOUR_2_STRINGID; - } - else - { - stringId = STR_BLACK_STRING; - } - - // Draw track name - auto ft = Formatter(); - ft.Add(STR_TRACK_LIST_NAME_FORMAT); - ft.Add(_trackDesigns[i].name); - DrawTextBasic(dpi, screenCoords - ScreenCoordsXY{ 0, 1 }, stringId, ft); - } - - screenCoords.y += SCROLLABLE_ROW_HEIGHT; - listIndex++; - } -} - -static void track_list_load_designs(RideSelection item) -{ - auto repo = OpenRCT2::GetContext()->GetTrackDesignRepository(); - std::string entryName; - if (item.Type < 0x80) - { - if (GetRideTypeDescriptor(item.Type).HasFlag(RIDE_TYPE_FLAG_LIST_VEHICLES_SEPARATELY)) - { - entryName = get_ride_entry_name(item.EntryIndex); - } - } - _trackDesigns = repo->GetItemsForObjectEntry(item.Type, entryName); - - window_track_list_filter_list(); -} - -static bool track_list_load_design_for_preview(utf8* path) -{ - _loadedTrackDesign = track_design_open(path); - if (_loadedTrackDesign != nullptr) - { - track_design_draw_preview(_loadedTrackDesign.get(), _trackDesignPreviewPixels.data()); - return true; - } - return false; -} diff --git a/src/openrct2/object/LargeSceneryObject.cpp b/src/openrct2/object/LargeSceneryObject.cpp index fa96ac5535..1ca0146744 100644 --- a/src/openrct2/object/LargeSceneryObject.cpp +++ b/src/openrct2/object/LargeSceneryObject.cpp @@ -98,7 +98,7 @@ void LargeSceneryObject::Unload() gfx_object_free_images(_baseImageId, GetImageTable().GetCount()); _legacyType.name = 0; - _legacyType.image = 0; + _baseImageId = _legacyType.image = 0; } void LargeSceneryObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t height) const diff --git a/src/openrct2/object/WaterObject.cpp b/src/openrct2/object/WaterObject.cpp index 0ea6ff11a8..3fd438619e 100644 --- a/src/openrct2/object/WaterObject.cpp +++ b/src/openrct2/object/WaterObject.cpp @@ -46,6 +46,7 @@ void WaterObject::Unload() language_free_object_string(_legacyType.string_idx); _legacyType.string_idx = 0; + _legacyType.image_id = 0; } void WaterObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t height) const diff --git a/src/openrct2/paint/Painter.cpp b/src/openrct2/paint/Painter.cpp index 46ccdd559d..f831f9cb56 100644 --- a/src/openrct2/paint/Painter.cpp +++ b/src/openrct2/paint/Painter.cpp @@ -170,3 +170,12 @@ void Painter::ReleaseSession(paint_session* session) session->PaintEntryChain.Clear(); _freePaintSessions.push_back(session); } + +Painter::~Painter() +{ + for (auto&& session : _paintSessionPool) + { + ReleaseSession(session.get()); + } + _paintSessionPool.clear(); +} diff --git a/src/openrct2/paint/Painter.h b/src/openrct2/paint/Painter.h index d776f6f557..7bcc9ba2f0 100644 --- a/src/openrct2/paint/Painter.h +++ b/src/openrct2/paint/Painter.h @@ -49,6 +49,7 @@ namespace OpenRCT2 paint_session* CreateSession(rct_drawpixelinfo* dpi, uint32_t viewFlags); void ReleaseSession(paint_session* session); + ~Painter(); private: void PaintReplayNotice(rct_drawpixelinfo* dpi, const char* text); diff --git a/src/openrct2/ride/RideConstruction.cpp b/src/openrct2/ride/RideConstruction.cpp index 18fafd2572..90dc3fd58f 100644 --- a/src/openrct2/ride/RideConstruction.cpp +++ b/src/openrct2/ride/RideConstruction.cpp @@ -1202,43 +1202,6 @@ money32 set_operating_setting_nested(ride_id_t rideId, RideSetSetting setting, u return res->Error == GameActions::Status::Ok ? 0 : MONEY32_UNDEFINED; } -// Finds the direction a ride entrance should face for an NxN station. -// Direction is valid if queryLocation is directly adjacent to this station. -static Direction GetRideEntranceDirection(const CoordsXY& queryLocation, CoordsXY entranceMin, CoordsXY entranceMax) -{ - entranceMin -= CoordsXY(32, 32); - entranceMax += CoordsXY(32, 32); - if (queryLocation.x == entranceMin.x) - { - if (queryLocation.y > entranceMin.y && queryLocation.y < entranceMax.y) - { - return 0; - } - } - if (queryLocation.y == entranceMax.y) - { - if (queryLocation.x > entranceMin.x && queryLocation.x < entranceMax.x) - { - return 1; - } - } - if (queryLocation.x == entranceMax.x) - { - if (queryLocation.y > entranceMin.y && queryLocation.y < entranceMax.y) - { - return 2; - } - } - if (queryLocation.y == entranceMin.y) - { - if (queryLocation.x > entranceMin.x && queryLocation.x < entranceMax.x) - { - return 3; - } - } - return INVALID_DIRECTION; -} - /** * * rct2: 0x006CCF70 @@ -1253,18 +1216,19 @@ CoordsXYZD ride_get_entrance_or_exit_position_from_screen_position(const ScreenC { if (info.Element->GetType() == TILE_ELEMENT_TYPE_TRACK) { - if (info.Element->AsTrack()->GetRideIndex() == gRideEntranceExitPlaceRideIndex) + const auto* trackElement = info.Element->AsTrack(); + if (trackElement->GetRideIndex() == gRideEntranceExitPlaceRideIndex) { - const auto& ted = GetTrackElementDescriptor(info.Element->AsTrack()->GetTrackType()); + const auto& ted = GetTrackElementDescriptor(trackElement->GetTrackType()); if (ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN) { - if (info.Element->AsTrack()->GetTrackType() == TrackElemType::Maze) + if (trackElement->GetTrackType() == TrackElemType::Maze) { gRideEntranceExitPlaceStationIndex = 0; } else { - gRideEntranceExitPlaceStationIndex = info.Element->AsTrack()->GetStationIndex(); + gRideEntranceExitPlaceStationIndex = trackElement->GetStationIndex(); } } } @@ -1302,132 +1266,67 @@ CoordsXYZD ride_get_entrance_or_exit_position_from_screen_position(const ScreenC return entranceExitCoords; } - // flat rides - if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_SINGLE_PIECE_STATION)) + // find the quadrant the mouse is hovering over - that's the direction to start searching for a station TileElement + Direction startDirection = 0; + auto mapX = (coordsAtHeight->x & 0x1F) - 16; + auto mapY = (coordsAtHeight->y & 0x1F) - 16; + if (std::abs(mapX) < std::abs(mapY)) { - // find the quadrant the mouse is hovering over - that's the direction to start searching for a station TileElement - Direction startDirection = 0; - auto mapX = (coordsAtHeight->x & 0x1F) - 16; - auto mapY = (coordsAtHeight->y & 0x1F) - 16; - if (std::abs(mapX) < std::abs(mapY)) - { - startDirection = mapY < 0 ? 3 : 1; - } - else - { - startDirection = mapX < 0 ? 0 : 2; - } - // check all 4 directions, starting with the mouse's quadrant - for (uint8_t directionIncrement = 0; directionIncrement < 4; directionIncrement++) - { - entranceExitCoords.direction = (startDirection + directionIncrement) & 3; - // search for TrackElement one tile over, shifted in the search direction - auto nextLocation = entranceExitCoords; - nextLocation += CoordsDirectionDelta[entranceExitCoords.direction]; - if (map_is_location_valid(nextLocation)) - { - // iterate over every element in the tile until we find what we want - auto tileElement = map_get_first_element_at(nextLocation); - if (tileElement == nullptr) - continue; - do - { - if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK) - continue; - if (tileElement->GetBaseZ() != stationBaseZ) - continue; - if (tileElement->AsTrack()->GetRideIndex() != gRideEntranceExitPlaceRideIndex) - continue; - if (tileElement->AsTrack()->GetTrackType() == TrackElemType::Maze) - { - // if it's a maze, it can place the entrance and exit immediately - entranceExitCoords.direction = direction_reverse(entranceExitCoords.direction); - gRideEntranceExitPlaceDirection = entranceExitCoords.direction; - return entranceExitCoords; - } - if (tileElement->AsTrack()->GetStationIndex() != gRideEntranceExitPlaceStationIndex) - continue; - // if it's not a maze, the sequence properties for the TrackElement must be found to determine if an - // entrance can be placed on that side - - // get the ride entrance's side relative to the TrackElement - Direction direction = (direction_reverse(entranceExitCoords.direction) - tileElement->GetDirection()) & 3; - const auto& ted = GetTrackElementDescriptor(tileElement->AsTrack()->GetTrackType()); - if (ted.SequenceProperties[tileElement->AsTrack()->GetSequenceIndex()] & (1 << direction)) - { - // if that side of the TrackElement supports stations, the ride entrance is valid and faces away from - // the station - entranceExitCoords.direction = direction_reverse(entranceExitCoords.direction); - gRideEntranceExitPlaceDirection = entranceExitCoords.direction; - return entranceExitCoords; - } - } while (!(tileElement++)->IsLastForTile()); - } - } - gRideEntranceExitPlaceDirection = INVALID_DIRECTION; + startDirection = mapY < 0 ? 3 : 1; } - // tracked rides excluding tower rides else { - // create a 2-point rectangle. The rectangle will have a width or height of 0 depending on station direction - CoordsXY entranceMin = { stationStart }; - CoordsXY entranceMax = { stationStart }; - - // get the beginning of the station TrackElement - auto tileElement = ride_get_station_start_track_element(ride, gRideEntranceExitPlaceStationIndex); - if (tileElement == nullptr) + startDirection = mapX < 0 ? 0 : 2; + } + // check all 4 directions, starting with the mouse's quadrant + for (uint8_t directionIncrement = 0; directionIncrement < 4; directionIncrement++) + { + entranceExitCoords.direction = (startDirection + directionIncrement) & 3; + // search for TrackElement one tile over, shifted in the search direction + auto nextLocation = entranceExitCoords; + nextLocation += CoordsDirectionDelta[entranceExitCoords.direction]; + if (map_is_location_valid(nextLocation)) { - entranceExitCoords.SetNull(); - return entranceExitCoords; - } - auto stationDirection = tileElement->GetDirection(); - entranceExitCoords.direction = stationDirection; - - auto next = entranceMax; - // find additional station TrackElements and extend the line of valid entrance locations - while (true) - { - entranceMax = next; - next -= CoordsDirectionDelta[stationDirection]; - tileElement = map_get_first_element_at(next); + // iterate over every element in the tile until we find what we want + auto* tileElement = map_get_first_element_at(nextLocation); if (tileElement == nullptr) - break; - bool goToNextTile = false; - + continue; do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK) continue; - if (tileElement->AsTrack()->GetRideIndex() != gRideEntranceExitPlaceRideIndex) + if (tileElement->GetBaseZ() != stationBaseZ) continue; - if (tileElement->AsTrack()->GetStationIndex() != gRideEntranceExitPlaceStationIndex) + auto* trackElement = tileElement->AsTrack(); + if (trackElement->GetRideIndex() != gRideEntranceExitPlaceRideIndex) continue; - - if (tileElement->AsTrack()->IsStation()) + if (trackElement->GetTrackType() == TrackElemType::Maze) { - goToNextTile = true; + // if it's a maze, it can place the entrance and exit immediately + entranceExitCoords.direction = direction_reverse(entranceExitCoords.direction); + gRideEntranceExitPlaceDirection = entranceExitCoords.direction; + return entranceExitCoords; } - } while (!goToNextTile && !(tileElement++)->IsLastForTile()); + // if it's not a maze, the sequence properties for the TrackElement must be found to determine if an + // entrance can be placed on that side - if (!goToNextTile) - break; - } - // swap variables for valid ranges - if (entranceMin.x > entranceMax.x) - std::swap(entranceMin.x, entranceMax.x); - if (entranceMin.y > entranceMax.y) - std::swap(entranceMin.y, entranceMax.y); + gRideEntranceExitPlaceStationIndex = trackElement->GetStationIndex(); - // get the station direction from its position - auto direction = GetRideEntranceDirection(entranceExitCoords, entranceMin, entranceMax); - - if (direction != INVALID_DIRECTION && direction != stationDirection && direction != direction_reverse(stationDirection)) - { - entranceExitCoords.direction = direction; - gRideEntranceExitPlaceDirection = entranceExitCoords.direction; - return entranceExitCoords; + // get the ride entrance's side relative to the TrackElement + Direction direction = (direction_reverse(entranceExitCoords.direction) - tileElement->GetDirection()) & 3; + const auto& ted = GetTrackElementDescriptor(trackElement->GetTrackType()); + if (ted.SequenceProperties[trackElement->GetSequenceIndex()] & (1 << direction)) + { + // if that side of the TrackElement supports stations, the ride entrance is valid and faces away from + // the station + entranceExitCoords.direction = direction_reverse(entranceExitCoords.direction); + gRideEntranceExitPlaceDirection = entranceExitCoords.direction; + return entranceExitCoords; + } + } while (!(tileElement++)->IsLastForTile()); } } + gRideEntranceExitPlaceDirection = INVALID_DIRECTION; return entranceExitCoords; }