OpenRCT2/src/openrct2/EditorObjectSelectionSessio...

700 lines
23 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2020 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 "EditorObjectSelectionSession.h"
#include "Context.h"
#include "Editor.h"
#include "Game.h"
#include "OpenRCT2.h"
#include "drawing/Drawing.h"
#include "localisation/Localisation.h"
#include "management/Research.h"
#include "object/DefaultObjects.h"
#include "object/ObjectList.h"
#include "object/ObjectManager.h"
#include "object/ObjectRepository.h"
#include "ride/RideData.h"
#include "ride/TrainManager.h"
#include "ride/Vehicle.h"
#include "scenario/Scenario.h"
#include "windows/Intent.h"
#include "world/Footpath.h"
#include "world/LargeScenery.h"
#include <iterator>
#include <vector>
bool _maxObjectsWasHit;
std::vector<uint8_t> _objectSelectionFlags;
int32_t _numSelectedObjectsForType[EnumValue(ObjectType::Count)];
static int32_t _numAvailableObjectsForType[EnumValue(ObjectType::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_default_objects();
static void SelectDesignerObjects();
static void ReplaceSelectedWaterPalette(const ObjectRepositoryItem* item);
/**
*
* rct2: 0x006ABCD1
*/
static void setup_track_manager_objects()
{
int32_t numObjects = static_cast<int32_t>(object_repository_get_items_count());
const ObjectRepositoryItem* items = object_repository_get_items();
for (int32_t i = 0; i < numObjects; i++)
{
uint8_t* selectionFlags = &_objectSelectionFlags[i];
const ObjectRepositoryItem* item = &items[i];
if (item->Type == ObjectType::Ride)
{
*selectionFlags |= OBJECT_SELECTION_FLAG_6;
for (auto rideType : item->RideInfo.RideType)
{
if (GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_HAS_TRACK))
{
*selectionFlags &= ~OBJECT_SELECTION_FLAG_6;
break;
}
}
}
}
}
/**
*
* rct2: 0x006ABC1E
*/
static void setup_track_designer_objects()
{
int32_t numObjects = static_cast<int32_t>(object_repository_get_items_count());
const ObjectRepositoryItem* items = object_repository_get_items();
SelectDesignerObjects();
for (int32_t i = 0; i < numObjects; i++)
{
uint8_t* selectionFlags = &_objectSelectionFlags[i];
const ObjectRepositoryItem* item = &items[i];
if (item->Type == ObjectType::Ride)
{
*selectionFlags |= OBJECT_SELECTION_FLAG_6;
for (uint8_t rideType : item->RideInfo.RideType)
{
if (rideType != RIDE_TYPE_NULL)
{
if (GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_SHOW_IN_TRACK_DESIGNER))
{
*selectionFlags &= ~OBJECT_SELECTION_FLAG_6;
break;
}
}
}
}
}
}
/**
*
* rct2: 0x006AA82B
*/
void setup_in_use_selection_flags()
{
auto& objectMgr = OpenRCT2::GetContext()->GetObjectManager();
for (uint8_t objectType = 0; objectType < EnumValue(ObjectType::Count); objectType++)
{
for (int32_t i = 0; i < object_entry_group_counts[objectType]; i++)
{
Editor::ClearSelectedObject(static_cast<ObjectType>(objectType), i, OBJECT_SELECTION_FLAG_ALL);
auto loadedObj = objectMgr.GetLoadedObject(static_cast<ObjectType>(objectType), i);
if (loadedObj != nullptr)
{
Editor::SetSelectedObject(static_cast<ObjectType>(objectType), i, OBJECT_SELECTION_FLAG_2);
}
}
}
tile_element_iterator iter;
tile_element_iterator_begin(&iter);
do
{
ObjectEntryIndex type;
switch (iter.element->GetType())
{
default:
case TILE_ELEMENT_TYPE_SURFACE:
{
auto surfaceEl = iter.element->AsSurface();
auto surfaceIndex = surfaceEl->GetSurfaceStyle();
auto edgeIndex = surfaceEl->GetEdgeStyle();
Editor::SetSelectedObject(ObjectType::TerrainSurface, surfaceIndex, OBJECT_SELECTION_FLAG_SELECTED);
Editor::SetSelectedObject(ObjectType::TerrainEdge, edgeIndex, OBJECT_SELECTION_FLAG_SELECTED);
break;
}
case TILE_ELEMENT_TYPE_TRACK:
break;
case TILE_ELEMENT_TYPE_PATH:
{
auto footpathEl = iter.element->AsPath();
auto legacyPathEntryIndex = footpathEl->GetLegacyPathEntryIndex();
if (legacyPathEntryIndex == OBJECT_ENTRY_INDEX_NULL)
{
auto surfaceEntryIndex = footpathEl->GetSurfaceEntryIndex();
auto railingEntryIndex = footpathEl->GetRailingsEntryIndex();
Editor::SetSelectedObject(ObjectType::FootpathSurface, surfaceEntryIndex, OBJECT_SELECTION_FLAG_SELECTED);
Editor::SetSelectedObject(ObjectType::FootpathRailings, railingEntryIndex, OBJECT_SELECTION_FLAG_SELECTED);
}
else
{
Editor::SetSelectedObject(ObjectType::Paths, legacyPathEntryIndex, OBJECT_SELECTION_FLAG_SELECTED);
}
if (footpathEl->HasAddition())
{
auto pathAdditionEntryIndex = footpathEl->GetAdditionEntryIndex();
Editor::SetSelectedObject(ObjectType::PathBits, pathAdditionEntryIndex, OBJECT_SELECTION_FLAG_SELECTED);
}
break;
}
case TILE_ELEMENT_TYPE_SMALL_SCENERY:
type = iter.element->AsSmallScenery()->GetEntryIndex();
Editor::SetSelectedObject(ObjectType::SmallScenery, type, OBJECT_SELECTION_FLAG_SELECTED);
break;
case TILE_ELEMENT_TYPE_ENTRANCE:
{
auto parkEntranceEl = iter.element->AsEntrance();
if (parkEntranceEl->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE)
break;
Editor::SetSelectedObject(ObjectType::ParkEntrance, 0, OBJECT_SELECTION_FLAG_SELECTED);
// Skip if not the middle part
if (parkEntranceEl->GetSequenceIndex() != 0)
break;
auto legacyPathEntryIndex = parkEntranceEl->GetLegacyPathEntryIndex();
if (legacyPathEntryIndex == OBJECT_ENTRY_INDEX_NULL)
{
auto surfaceEntryIndex = parkEntranceEl->GetSurfaceEntryIndex();
Editor::SetSelectedObject(ObjectType::FootpathSurface, surfaceEntryIndex, OBJECT_SELECTION_FLAG_SELECTED);
}
else
{
Editor::SetSelectedObject(ObjectType::Paths, legacyPathEntryIndex, OBJECT_SELECTION_FLAG_SELECTED);
}
break;
}
case TILE_ELEMENT_TYPE_WALL:
type = iter.element->AsWall()->GetEntryIndex();
Editor::SetSelectedObject(ObjectType::Walls, type, OBJECT_SELECTION_FLAG_SELECTED);
break;
case TILE_ELEMENT_TYPE_LARGE_SCENERY:
type = iter.element->AsLargeScenery()->GetEntryIndex();
Editor::SetSelectedObject(ObjectType::LargeScenery, type, OBJECT_SELECTION_FLAG_SELECTED);
break;
case TILE_ELEMENT_TYPE_BANNER:
{
auto banner = iter.element->AsBanner()->GetBanner();
if (banner != nullptr)
{
type = banner->type;
Editor::SetSelectedObject(ObjectType::Banners, type, OBJECT_SELECTION_FLAG_SELECTED);
}
break;
}
}
} while (tile_element_iterator_next(&iter));
for (auto& ride : GetRideManager())
{
Editor::SetSelectedObject(ObjectType::Ride, ride.subtype, OBJECT_SELECTION_FLAG_SELECTED);
Editor::SetSelectedObject(ObjectType::Station, ride.entrance_style, OBJECT_SELECTION_FLAG_SELECTED);
Editor::SetSelectedObject(ObjectType::Music, ride.music, OBJECT_SELECTION_FLAG_SELECTED);
}
// Apply selected object status for hacked vehicles that may not have an associated ride
for (auto* vehicle : TrainManager::View())
{
ObjectEntryIndex type = vehicle->ride_subtype;
if (type != OBJECT_ENTRY_INDEX_NULL) // cable lifts use index null. Ignore them
{
Editor::SetSelectedObject(ObjectType::Ride, type, OBJECT_SELECTION_FLAG_SELECTED);
}
}
for (auto vehicle : EntityList<Vehicle>())
{
ObjectEntryIndex type = vehicle->ride_subtype;
if (type != OBJECT_ENTRY_INDEX_NULL) // cable lifts use index null. Ignore them
{
Editor::SetSelectedObject(ObjectType::Ride, type, OBJECT_SELECTION_FLAG_SELECTED);
}
}
auto numObjects = object_repository_get_items_count();
const auto* items = object_repository_get_items();
for (size_t i = 0; i < numObjects; i++)
{
auto* selectionFlags = &_objectSelectionFlags[i];
const auto* item = &items[i];
*selectionFlags &= ~OBJECT_SELECTION_FLAG_IN_USE;
if (item->LoadedObject != nullptr)
{
auto objectType = item->LoadedObject->GetObjectType();
auto entryIndex = objectMgr.GetLoadedObjectEntryIndex(item->LoadedObject.get());
auto flags = Editor::GetSelectedObjectFlags(objectType, entryIndex);
if (flags & OBJECT_SELECTION_FLAG_SELECTED)
{
*selectionFlags |= OBJECT_SELECTION_FLAG_IN_USE | OBJECT_SELECTION_FLAG_SELECTED;
}
if (flags & OBJECT_SELECTION_FLAG_2)
{
*selectionFlags |= OBJECT_SELECTION_FLAG_SELECTED;
}
}
}
}
/**
*
* rct2: 0x006AB211
*/
void sub_6AB211()
{
int32_t numObjects = static_cast<int32_t>(object_repository_get_items_count());
_objectSelectionFlags = std::vector<uint8_t>(numObjects);
for (uint8_t objectType = 0; objectType < EnumValue(ObjectType::Count); objectType++)
{
_numSelectedObjectsForType[objectType] = 0;
_numAvailableObjectsForType[objectType] = 0;
}
const ObjectRepositoryItem* items = object_repository_get_items();
for (int32_t i = 0; i < numObjects; i++)
{
ObjectType objectType = items[i].Type;
_numAvailableObjectsForType[EnumValue(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)))
{
// 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();
}
/**
*
* rct2: 0x006AB316
*/
void editor_object_flags_free()
{
_objectSelectionFlags.clear();
_objectSelectionFlags.shrink_to_fit();
}
/**
*
* rct2: 0x00685791
*/
static void remove_selected_objects_from_research(const ObjectEntryDescriptor& descriptor)
{
auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
auto obj = objManager.GetLoadedObject(descriptor);
if (obj != nullptr)
{
auto entryIndex = objManager.GetLoadedObjectEntryIndex(obj);
switch (obj->GetObjectType())
{
case ObjectType::Ride:
{
auto rideEntry = get_ride_entry(entryIndex);
for (auto rideType : rideEntry->ride_type)
{
ResearchItem tmp = {};
tmp.type = Research::EntryType::Ride;
tmp.entryIndex = entryIndex;
tmp.baseRideType = rideType;
ResearchRemove(tmp);
}
break;
}
case ObjectType::SceneryGroup:
{
ResearchItem tmp = {};
tmp.type = Research::EntryType::Scenery;
tmp.entryIndex = entryIndex;
ResearchRemove(tmp);
break;
}
default:
break;
}
}
}
/**
*
* rct2: 0x006ABB66
*/
void unload_unselected_objects()
{
auto numItems = static_cast<int32_t>(object_repository_get_items_count());
const auto* items = object_repository_get_items();
std::vector<ObjectEntryDescriptor> objectsToUnload;
for (int32_t i = 0; i < numItems; i++)
{
if (!(_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_SELECTED))
{
auto descriptor = ObjectEntryDescriptor(items[i]);
remove_selected_objects_from_research(descriptor);
objectsToUnload.push_back(descriptor);
}
}
object_manager_unload_objects(objectsToUnload);
}
/**
*
* rct2: 0x006AA805
*/
static void window_editor_object_selection_select_default_objects()
{
if (_numSelectedObjectsForType[0] == 0)
{
for (auto defaultSelectedObject : DefaultSelectedObjects)
{
window_editor_object_selection_select_object(
0,
INPUT_FLAG_EDITOR_OBJECT_SELECT | INPUT_FLAG_EDITOR_OBJECT_1
| INPUT_FLAG_EDITOR_OBJECT_SELECT_OBJECTS_IN_SCENERY_GROUP,
ObjectEntryDescriptor(defaultSelectedObject));
}
}
}
static void SelectDesignerObjects()
{
if (_numSelectedObjectsForType[0] == 0)
{
for (auto designerSelectedObject : DesignerSelectedObjects)
{
window_editor_object_selection_select_object(
0,
INPUT_FLAG_EDITOR_OBJECT_SELECT | INPUT_FLAG_EDITOR_OBJECT_1
| INPUT_FLAG_EDITOR_OBJECT_SELECT_OBJECTS_IN_SCENERY_GROUP,
ObjectEntryDescriptor(designerSelectedObject));
}
}
}
/**
* Replaces the previously selected water palette with the palette in the specified item immediately.
*/
static void ReplaceSelectedWaterPalette(const ObjectRepositoryItem* item)
{
auto& objectManager = OpenRCT2::GetContext()->GetObjectManager();
auto* oldPalette = objectManager.GetLoadedObject(ObjectType::Water, 0);
if (oldPalette != nullptr)
{
const std::vector<ObjectEntryDescriptor> oldEntries = { oldPalette->GetDescriptor() };
objectManager.UnloadObjects(oldEntries);
}
auto newPaletteEntry = ObjectEntryDescriptor(*item);
if (objectManager.GetLoadedObject(newPaletteEntry) != nullptr || objectManager.LoadObject(newPaletteEntry) != nullptr)
{
load_palette();
}
else
{
log_error("Failed to load selected palette %s", std::string(newPaletteEntry.GetName()).c_str());
}
}
/**
*
* rct2: 0x006AA770
*/
void reset_selected_object_count_and_size()
{
for (auto& objectType : _numSelectedObjectsForType)
{
objectType = 0;
}
int32_t numObjects = static_cast<int32_t>(object_repository_get_items_count());
const ObjectRepositoryItem* items = object_repository_get_items();
for (int32_t i = 0; i < numObjects; i++)
{
ObjectType objectType = items[i].Type;
if (_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_SELECTED)
{
_numSelectedObjectsForType[EnumValue(objectType)]++;
}
}
}
void finish_object_selection()
{
if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER)
{
set_every_ride_type_invented();
set_every_ride_entry_invented();
gEditorStep = EditorStep::RollercoasterDesigner;
gfx_invalidate_screen();
}
else
{
set_all_scenery_items_invented();
scenery_set_default_placement_configuration();
gEditorStep = EditorStep::LandscapeEditor;
gfx_invalidate_screen();
}
}
/**
* Master objects are objects that are not
* optional / required dependants of an
* object.
*/
static void set_object_selection_error(uint8_t is_master_object, rct_string_id error_msg)
{
gGameCommandErrorText = error_msg;
if (!is_master_object)
{
reset_selected_object_count_and_size();
}
}
/**
*
* rct2: 0x006AB54F
*/
bool window_editor_object_selection_select_object(uint8_t isMasterObject, int32_t flags, const ObjectRepositoryItem* item)
{
if (item == nullptr)
{
set_object_selection_error(isMasterObject, STR_OBJECT_SELECTION_ERR_OBJECT_DATA_NOT_FOUND);
return false;
}
int32_t numObjects = static_cast<int32_t>(object_repository_get_items_count());
// Get repository item index
int32_t index = -1;
const ObjectRepositoryItem* items = object_repository_get_items();
for (int32_t i = 0; i < numObjects; i++)
{
if (&items[i] == item)
{
index = i;
}
}
uint8_t* selectionFlags = &_objectSelectionFlags[index];
if (!(flags & INPUT_FLAG_EDITOR_OBJECT_SELECT))
{
if (!(*selectionFlags & OBJECT_SELECTION_FLAG_SELECTED))
{
return true;
}
if (*selectionFlags & OBJECT_SELECTION_FLAG_IN_USE)
{
set_object_selection_error(isMasterObject, STR_OBJECT_SELECTION_ERR_CURRENTLY_IN_USE);
return false;
}
if (*selectionFlags & OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED)
{
set_object_selection_error(isMasterObject, STR_OBJECT_SELECTION_ERR_ALWAYS_REQUIRED);
return false;
}
ObjectType objectType = item->Type;
if (objectType == ObjectType::SceneryGroup && (flags & INPUT_FLAG_EDITOR_OBJECT_SELECT_OBJECTS_IN_SCENERY_GROUP))
{
for (const auto& sgEntry : item->SceneryGroupInfo.Entries)
{
window_editor_object_selection_select_object(++isMasterObject, flags, sgEntry);
}
}
_numSelectedObjectsForType[EnumValue(objectType)]--;
*selectionFlags &= ~OBJECT_SELECTION_FLAG_SELECTED;
return true;
}
if (isMasterObject == 0)
{
if (flags & INPUT_FLAG_EDITOR_OBJECT_ALWAYS_REQUIRED)
{
*selectionFlags |= OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED;
}
}
if (*selectionFlags & OBJECT_SELECTION_FLAG_SELECTED)
{
return true;
}
ObjectType objectType = item->Type;
uint16_t maxObjects = object_entry_group_counts[EnumValue(objectType)];
if (maxObjects <= _numSelectedObjectsForType[EnumValue(objectType)])
{
set_object_selection_error(isMasterObject, STR_OBJECT_SELECTION_ERR_TOO_MANY_OF_TYPE_SELECTED);
return false;
}
if (objectType == ObjectType::SceneryGroup && (flags & INPUT_FLAG_EDITOR_OBJECT_SELECT_OBJECTS_IN_SCENERY_GROUP))
{
for (const auto& sgEntry : item->SceneryGroupInfo.Entries)
{
if (!window_editor_object_selection_select_object(++isMasterObject, flags, sgEntry))
{
_maxObjectsWasHit = true;
}
}
}
else if (objectType == ObjectType::Water)
{
// Replace old palette with newly selected palette immediately.
ReplaceSelectedWaterPalette(item);
}
if (isMasterObject != 0 && !(flags & INPUT_FLAG_EDITOR_OBJECT_1))
{
char objectName[64];
object_create_identifier_name(objectName, 64, &item->ObjectEntry);
auto ft = Formatter::Common();
ft.Add<const char*>(objectName);
set_object_selection_error(isMasterObject, STR_OBJECT_SELECTION_ERR_SHOULD_SELECT_X_FIRST);
return false;
}
if (maxObjects <= _numSelectedObjectsForType[EnumValue(objectType)])
{
set_object_selection_error(isMasterObject, STR_OBJECT_SELECTION_ERR_TOO_MANY_OF_TYPE_SELECTED);
return false;
}
_numSelectedObjectsForType[EnumValue(objectType)]++;
*selectionFlags |= OBJECT_SELECTION_FLAG_SELECTED;
return true;
}
bool window_editor_object_selection_select_object(
uint8_t isMasterObject, int32_t flags, const ObjectEntryDescriptor& descriptor)
{
auto& objectRepository = OpenRCT2::GetContext()->GetObjectRepository();
const auto* item = objectRepository.FindObject(descriptor);
return window_editor_object_selection_select_object(isMasterObject, flags, item);
}
bool editor_check_object_group_at_least_one_selected(ObjectType checkObjectType)
{
auto numObjects = std::min(object_repository_get_items_count(), _objectSelectionFlags.size());
const ObjectRepositoryItem* items = object_repository_get_items();
for (size_t i = 0; i < numObjects; i++)
{
auto objectType = items[i].Type;
if (checkObjectType == objectType && (_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_SELECTED))
{
return true;
}
}
return false;
}
bool editor_check_object_group_at_least_one_surface_selected(bool queue)
{
auto numObjects = std::min(object_repository_get_items_count(), _objectSelectionFlags.size());
const auto* items = object_repository_get_items();
for (size_t i = 0; i < numObjects; i++)
{
const auto& ori = items[i];
auto isQueue = (ori.FootpathSurfaceInfo.Flags & FOOTPATH_ENTRY_FLAG_IS_QUEUE) != 0;
if (ori.Type == ObjectType::FootpathSurface && (_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_SELECTED)
&& queue == isQueue)
{
return true;
}
}
return false;
}
int32_t editor_remove_unused_objects()
{
sub_6AB211();
setup_in_use_selection_flags();
int32_t numObjects = static_cast<int32_t>(object_repository_get_items_count());
const ObjectRepositoryItem* items = object_repository_get_items();
int32_t numUnselectedObjects = 0;
for (int32_t i = 0; i < numObjects; i++)
{
if (_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_SELECTED)
{
if (!(_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_IN_USE)
&& !(_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED))
{
const ObjectRepositoryItem* item = &items[i];
ObjectType objectType = item->Type;
if (objectType >= ObjectType::SceneryGroup)
{
continue;
}
_numSelectedObjectsForType[EnumValue(objectType)]--;
_objectSelectionFlags[i] &= ~OBJECT_SELECTION_FLAG_SELECTED;
numUnselectedObjects++;
}
}
}
unload_unselected_objects();
editor_object_flags_free();
auto intent = Intent(INTENT_ACTION_REFRESH_SCENERY);
context_broadcast_intent(&intent);
return numUnselectedObjects;
}