#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * * OpenRCT2 is the work of many authors, a full list can be found in contributors.md * For more information, visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * A full copy of the GNU General Public License can be found in licence.txt *****************************************************************************/ #pragma endregion #include "Context.h" #include "core/Util.hpp" #include "Editor.h" #include "EditorObjectSelectionSession.h" #include "Game.h" #include "localisation/Localisation.h" #include "object/ObjectManager.h" #include "object/ObjectRepository.h" #include "OpenRCT2.h" #include "ride/RideData.h" #include "windows/Intent.h" #include "windows/_legacy.h" #include "world/footpath.h" #include "world/LargeScenery.h" bool _maxObjectsWasHit; uint8 * _objectSelectionFlags = nullptr; sint32 _numSelectedObjectsForType[OBJECT_TYPE_COUNT]; static sint32 _numAvailableObjectsForType[OBJECT_TYPE_COUNT]; static void setup_in_use_selection_flags(); static void setup_track_designer_objects(); static void setup_track_manager_objects(); static void window_editor_object_selection_select_required_objects(); static void window_editor_object_selection_select_default_objects(); /** * * rct2: 0x006ABCD1 */ static void setup_track_manager_objects() { sint32 numObjects = (sint32)object_repository_get_items_count(); const ObjectRepositoryItem * items = object_repository_get_items(); for (sint32 i = 0; i < numObjects; i++) { uint8 * selectionFlags = &_objectSelectionFlags[i]; const ObjectRepositoryItem * item = &items[i]; uint8 object_type = item->ObjectEntry.flags & 0xF; if (object_type == OBJECT_TYPE_RIDE) { *selectionFlags |= OBJECT_SELECTION_FLAG_6; for (auto rideType : item->RideType) { if (rideType != RIDE_TYPE_NULL && ride_type_has_flag(rideType, RIDE_TYPE_FLAG_HAS_TRACK)) { *selectionFlags &= ~OBJECT_SELECTION_FLAG_6; break; } } } } } /** * * rct2: 0x006ABC1E */ static void setup_track_designer_objects() { sint32 numObjects = (sint32)object_repository_get_items_count(); const ObjectRepositoryItem * items = object_repository_get_items(); for (sint32 i = 0; i < numObjects; i++) { uint8 * selectionFlags = &_objectSelectionFlags[i]; const ObjectRepositoryItem * item = &items[i]; uint8 objectType = item->ObjectEntry.flags & 0xF; if (objectType == OBJECT_TYPE_RIDE) { *selectionFlags |= OBJECT_SELECTION_FLAG_6; for (uint8 rideType : item->RideType) { if (rideType != RIDE_TYPE_NULL) { if (RideData4[rideType].flags & RIDE_TYPE_FLAG4_SHOW_IN_TRACK_DESIGNER) { *selectionFlags &= ~OBJECT_SELECTION_FLAG_6; break; } } } } } } /** * * rct2: 0x006AA82B */ void setup_in_use_selection_flags() { for (uint8 object_type = 0; object_type < OBJECT_TYPE_COUNT; object_type++){ for (uint16 i = 0; i < object_entry_group_counts[object_type]; i++){ Editor::SelectedObjects[object_type][i] = OBJECT_SELECTION_NOT_SELECTED_OR_REQUIRED; } } for (uint8 object_type = 0; object_type < OBJECT_TYPE_COUNT; object_type++){ for (uint16 i = 0; i < object_entry_group_counts[object_type]; i++){ if (object_entry_groups[object_type].chunks[i] != nullptr) { Editor::SelectedObjects[object_type][i] |= OBJECT_SELECTION_FLAG_2; } } } tile_element_iterator iter; tile_element_iterator_begin(&iter); do { uint16 type; rct_banner* banner; switch (tile_element_get_type(iter.element)) { default: case TILE_ELEMENT_TYPE_SURFACE: case TILE_ELEMENT_TYPE_TRACK: break; case TILE_ELEMENT_TYPE_PATH: type = iter.element->properties.path.type; type >>= 4; assert(type < object_entry_group_counts[OBJECT_TYPE_PATHS]); Editor::SelectedObjects[OBJECT_TYPE_PATHS][type] |= OBJECT_SELECTION_FLAG_SELECTED; if (footpath_element_has_path_scenery(iter.element)) { uint8 path_additions = footpath_element_get_path_scenery_index(iter.element); Editor::SelectedObjects[OBJECT_TYPE_PATH_BITS][path_additions] |= OBJECT_SELECTION_FLAG_SELECTED; } break; case TILE_ELEMENT_TYPE_SMALL_SCENERY: type = iter.element->properties.scenery.type; assert(type < object_entry_group_counts[OBJECT_TYPE_SMALL_SCENERY]); Editor::SelectedObjects[OBJECT_TYPE_SMALL_SCENERY][type] |= OBJECT_SELECTION_FLAG_SELECTED; break; case TILE_ELEMENT_TYPE_ENTRANCE: if (iter.element->properties.entrance.type != ENTRANCE_TYPE_PARK_ENTRANCE) break; // Skip if not the middle part if (iter.element->properties.entrance.index != 0) break; Editor::SelectedObjects[OBJECT_TYPE_PARK_ENTRANCE][0] |= OBJECT_SELECTION_FLAG_SELECTED; type = iter.element->properties.entrance.path_type; assert(type < object_entry_group_counts[OBJECT_TYPE_PATHS]); Editor::SelectedObjects[OBJECT_TYPE_PATHS][type] |= OBJECT_SELECTION_FLAG_SELECTED; break; case TILE_ELEMENT_TYPE_WALL: type = iter.element->properties.wall.type; assert(type < object_entry_group_counts[OBJECT_TYPE_WALLS]); Editor::SelectedObjects[OBJECT_TYPE_WALLS][type] |= OBJECT_SELECTION_FLAG_SELECTED; break; case TILE_ELEMENT_TYPE_LARGE_SCENERY: type = scenery_large_get_type(iter.element); assert(type < object_entry_group_counts[OBJECT_TYPE_LARGE_SCENERY]); Editor::SelectedObjects[OBJECT_TYPE_LARGE_SCENERY][type] |= OBJECT_SELECTION_FLAG_SELECTED; break; case TILE_ELEMENT_TYPE_BANNER: banner = &gBanners[iter.element->properties.banner.index]; type = banner->type; assert(type < object_entry_group_counts[OBJECT_TYPE_BANNERS]); Editor::SelectedObjects[OBJECT_TYPE_BANNERS][type] |= OBJECT_SELECTION_FLAG_SELECTED; break; } } while (tile_element_iterator_next(&iter)); for (uint8 ride_index = 0; ride_index < 0xFF; ride_index++) { Ride* ride = get_ride(ride_index); if (ride->type != RIDE_TYPE_NULL) { uint8 type = ride->subtype; Editor::SelectedObjects[OBJECT_TYPE_RIDE][type] |= OBJECT_SELECTION_FLAG_SELECTED; } } sint32 numObjects = (sint32)object_repository_get_items_count(); const ObjectRepositoryItem * items = object_repository_get_items(); for (sint32 i = 0; i < numObjects; i++) { uint8 *selectionFlags = &_objectSelectionFlags[i]; const ObjectRepositoryItem * item = &items[i]; *selectionFlags &= ~OBJECT_SELECTION_FLAG_IN_USE; uint8 entryType, entryIndex; if (find_object_in_entry_group(&item->ObjectEntry, &entryType, &entryIndex)) { if (Editor::SelectedObjects[entryType][entryIndex] & OBJECT_SELECTION_FLAG_SELECTED) { *selectionFlags |= OBJECT_SELECTION_FLAG_IN_USE | OBJECT_SELECTION_FLAG_SELECTED; } if (Editor::SelectedObjects[entryType][entryIndex] & OBJECT_SELECTION_FLAG_2) { *selectionFlags |= OBJECT_SELECTION_FLAG_SELECTED; } } } } /** * * rct2: 0x006AB211 */ bool sub_6AB211() { sint32 numObjects = (sint32)object_repository_get_items_count(); _objectSelectionFlags = (uint8*)calloc(numObjects, sizeof(uint8)); if (_objectSelectionFlags == nullptr){ log_error("Failed to allocate memory for object flag list."); return false; } for (uint8 objectType = 0; objectType < 11; objectType++) { _numSelectedObjectsForType[objectType] = 0; _numAvailableObjectsForType[objectType] = 0; } const ObjectRepositoryItem * items = object_repository_get_items(); for (sint32 i = 0; i < numObjects; i++) { uint8 objectType = items[i].ObjectEntry.flags & 0xF; _numAvailableObjectsForType[objectType]++; } if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) { setup_track_designer_objects(); } if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) { setup_track_manager_objects(); } setup_in_use_selection_flags(); reset_selected_object_count_and_size(); if (!(gScreenFlags & (SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER))) { window_editor_object_selection_select_required_objects(); // To prevent it breaking in scenario mode. if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) { window_editor_object_selection_select_default_objects(); } } reset_selected_object_count_and_size(); return true; } /** * * rct2: 0x006AB316 */ void editor_object_flags_free() { SafeFree(_objectSelectionFlags); } /** * * rct2: 0x00685791 */ static void remove_selected_objects_from_research(const rct_object_entry* installedObject){ uint8 entry_type, entry_index; if (!find_object_in_entry_group(installedObject, &entry_type, &entry_index)) return; if (entry_type == OBJECT_TYPE_RIDE){ rct_ride_entry* rideEntry = (rct_ride_entry*)object_entry_groups[entry_type].chunks[entry_index]; for (auto rideType : rideEntry->ride_type) { rct_research_item tmp = {}; tmp.type = RESEARCH_ENTRY_TYPE_RIDE; tmp.entryIndex = entry_index; tmp.baseRideType = rideType; research_remove(&tmp); } } else if (entry_type == OBJECT_TYPE_SCENERY_GROUP) { rct_research_item tmp = {}; tmp.type = RESEARCH_ENTRY_TYPE_SCENERY; tmp.entryIndex = entry_index; research_remove(&tmp); } } /** * * rct2: 0x006ABB66 */ void unload_unselected_objects() { sint32 numItems = (sint32)object_repository_get_items_count(); const ObjectRepositoryItem * items = object_repository_get_items(); size_t numObjectsToUnload = 0; rct_object_entry * objectsToUnload = (rct_object_entry *)malloc(numItems * sizeof(rct_object_entry)); for (sint32 i = 0; i < numItems; i++) { if (!(_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_SELECTED)) { const rct_object_entry * entry = &items[i].ObjectEntry; remove_selected_objects_from_research(entry); objectsToUnload[numObjectsToUnload++] = *entry; } } object_manager_unload_objects(objectsToUnload, numObjectsToUnload); free(objectsToUnload); } /** * * rct2: 0x006AA805 */ static void window_editor_object_selection_select_default_objects() { if (_numSelectedObjectsForType[0] == 0) { for (const auto &defaultSelectedObject : DefaultSelectedObjects) { window_editor_object_selection_select_object(0, 7, &defaultSelectedObject); } } } /** * * rct2: 0x006AA7E9 */ static void window_editor_object_selection_select_required_objects() { sint32 i; for (i = 0; i < (sint32)Util::CountOf(RequiredSelectedObjects); i++) window_editor_object_selection_select_object(0, 0xF, &RequiredSelectedObjects[i]); } /** * * rct2: 0x006AA770 */ void reset_selected_object_count_and_size() { for (auto &objectType : _numSelectedObjectsForType) { objectType = 0; } sint32 numObjects = (sint32)object_repository_get_items_count(); const ObjectRepositoryItem * items = object_repository_get_items(); for (sint32 i = 0; i < numObjects; i++) { uint8 objectType = items[i].ObjectEntry.flags & 0xF; if (_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_SELECTED) { _numSelectedObjectsForType[objectType]++; } } } /** * Master objects are objects that are not * optional / required dependants of an * object. */ static void set_object_selection_error(uint8 is_master_object, rct_string_id error_msg){ gGameCommandErrorText = error_msg; if (!is_master_object){ reset_selected_object_count_and_size(); } } /** * * rct2: 0x006AB54F */ sint32 window_editor_object_selection_select_object(uint8 bh, sint32 flags, const rct_object_entry *entry) { sint32 numObjects = (sint32)object_repository_get_items_count(); const ObjectRepositoryItem * item = object_repository_find_object_by_entry(entry); if (item == nullptr) { set_object_selection_error(bh, STR_OBJECT_SELECTION_ERR_OBJECT_DATA_NOT_FOUND); return 0; } // Get repository item index sint32 index = -1; const ObjectRepositoryItem * items = object_repository_get_items(); for (sint32 i = 0; i < numObjects; i++) { if (&items[i] == item) { index = i; } } uint8 * selectionFlags = &_objectSelectionFlags[index]; if (!(flags & 1)) { if (!(*selectionFlags & OBJECT_SELECTION_FLAG_SELECTED)) { return 1; } else if (*selectionFlags & OBJECT_SELECTION_FLAG_IN_USE) { set_object_selection_error(bh, STR_OBJECT_SELECTION_ERR_CURRENTLY_IN_USE); return 0; } else if (*selectionFlags & OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED) { set_object_selection_error(bh, STR_OBJECT_SELECTION_ERR_ALWAYS_REQUIRED); return 0; } uint8 objectType = item->ObjectEntry.flags & 0xF; if (objectType == OBJECT_TYPE_SCENERY_GROUP && (flags & (1 << 2))) { for (sint32 j = 0; j < item->NumThemeObjects; j++) { window_editor_object_selection_select_object(++bh, flags, &item->ThemeObjects[j]); } } _numSelectedObjectsForType[objectType]--; *selectionFlags &= ~OBJECT_SELECTION_FLAG_SELECTED; return 1; } else { if (bh == 0) { if (flags & (1 << 3)) { *selectionFlags |= OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED; } } if (*selectionFlags & OBJECT_SELECTION_FLAG_SELECTED) { return 1; } uint8 objectType = item->ObjectEntry.flags & 0xF; uint16 maxObjects = object_entry_group_counts[objectType]; if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) { maxObjects = 4; } if (maxObjects <= _numSelectedObjectsForType[objectType]) { set_object_selection_error(bh, STR_OBJECT_SELECTION_ERR_TOO_MANY_OF_TYPE_SELECTED); return 0; } if (objectType == OBJECT_TYPE_SCENERY_GROUP && (flags & (1 << 2))) { for (uint16 j = 0; j < item->NumThemeObjects; j++) { if (!window_editor_object_selection_select_object(++bh, flags, &item->ThemeObjects[j])) { _maxObjectsWasHit = true; } } } if (bh != 0 && !(flags & (1 << 1))) { char objectName[64]; object_create_identifier_name(objectName, 64, &item->ObjectEntry); set_format_arg(0, const char *, objectName); set_object_selection_error(bh, STR_OBJECT_SELECTION_ERR_SHOULD_SELECT_X_FIRST); return 0; } if (maxObjects <= _numSelectedObjectsForType[objectType]) { set_object_selection_error(bh, STR_OBJECT_SELECTION_ERR_TOO_MANY_OF_TYPE_SELECTED); return 0; } _numSelectedObjectsForType[objectType]++; *selectionFlags |= OBJECT_SELECTION_FLAG_SELECTED; return 1; } } bool editor_check_object_group_at_least_one_selected(sint32 checkObjectType) { sint32 numObjects = (sint32)object_repository_get_items_count(); const ObjectRepositoryItem * items = object_repository_get_items(); for (sint32 i = 0; i < numObjects; i++) { uint8 objectType = items[i].ObjectEntry.flags & 0x0F; if (checkObjectType == objectType && (_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_SELECTED)) { return true; } } return false; } sint32 editor_remove_unused_objects() { bool createSelectionFlags = (_objectSelectionFlags == nullptr); if (createSelectionFlags && !sub_6AB211()) { return 0; } setup_in_use_selection_flags(); sint32 numObjects = (sint32)object_repository_get_items_count(); const ObjectRepositoryItem * items = object_repository_get_items(); sint32 numUnselectedObjects = 0; for (sint32 i = 0; i < numObjects; i++) { if (!(_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_IN_USE) && !(_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED)) { const ObjectRepositoryItem * item = &items[i]; uint8 objectType = item->ObjectEntry.flags & 0xF; if (objectType == OBJECT_TYPE_PARK_ENTRANCE || objectType == OBJECT_TYPE_SCENARIO_TEXT || objectType == OBJECT_TYPE_WATER || objectType == OBJECT_TYPE_SCENERY_GROUP) { continue; } _numSelectedObjectsForType[objectType]--; _objectSelectionFlags[i] &= ~OBJECT_SELECTION_FLAG_SELECTED; numUnselectedObjects++; } } unload_unselected_objects(); if (createSelectionFlags) { editor_object_flags_free(); } auto intent = Intent(INTENT_ACTION_REFRESH_SCENERY); context_broadcast_intent(&intent); return numUnselectedObjects; }