OpenRCT2/src/openrct2/EditorObjectSelectionSessio...

713 lines
24 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*****************************************************************************
* Copyright (c) 2014-2024 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 "GameState.h"
#include "OpenRCT2.h"
#include "drawing/Drawing.h"
#include "localisation/Formatter.h"
#include "localisation/Localisation.h"
#include "management/Research.h"
#include "object/DefaultObjects.h"
#include "object/FootpathEntry.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/Scenery.h"
#include <iterator>
#include <vector>
std::optional<StringId> _gSceneryGroupPartialSelectError;
std::vector<uint8_t> _objectSelectionFlags;
uint32_t _numSelectedObjectsForType[EnumValue(ObjectType::Count)];
static int32_t _numAvailableObjectsForType[EnumValue(ObjectType::Count)];
static void SetupInUseSelectionFlags();
static void SetupTrackDesignerObjects();
static void SetupTrackManagerObjects();
static void WindowEditorObjectSelectionSelectDefaultObjects();
static void SelectDesignerObjects();
static void ReplaceSelectedWaterPalette(const ObjectRepositoryItem* item);
/**
* Master objects are objects that are not optional / required dependants of an object.
*/
static constexpr ResultWithMessage ObjectSelectionError(bool isMasterObject, StringId message)
{
if (!isMasterObject)
ResetSelectedObjectCountAndSize();
return { false, message };
}
/**
*
* rct2: 0x006ABCD1
*/
static void SetupTrackManagerObjects()
{
int32_t numObjects = static_cast<int32_t>(ObjectRepositoryGetItemsCount());
const ObjectRepositoryItem* items = ObjectRepositoryGetItems();
for (int32_t i = 0; i < numObjects; i++)
{
uint8_t* selectionFlags = &_objectSelectionFlags[i];
const ObjectRepositoryItem* item = &items[i];
if (item->Type == ObjectType::Ride)
{
*selectionFlags |= ObjectSelectionFlags::Flag6;
for (auto rideType : item->RideInfo.RideType)
{
if (GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_HAS_TRACK))
{
*selectionFlags &= ~ObjectSelectionFlags::Flag6;
break;
}
}
}
}
}
/**
*
* rct2: 0x006ABC1E
*/
static void SetupTrackDesignerObjects()
{
int32_t numObjects = static_cast<int32_t>(ObjectRepositoryGetItemsCount());
const ObjectRepositoryItem* items = ObjectRepositoryGetItems();
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 |= ObjectSelectionFlags::Flag6;
for (auto rideType : item->RideInfo.RideType)
{
if (rideType != RIDE_TYPE_NULL)
{
if (GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_SHOW_IN_TRACK_DESIGNER))
{
*selectionFlags &= ~ObjectSelectionFlags::Flag6;
break;
}
}
}
}
}
}
/**
*
* rct2: 0x006AA82B
*/
void SetupInUseSelectionFlags()
{
auto& objectMgr = OpenRCT2::GetContext()->GetObjectManager();
for (auto objectType : getTransientObjectTypes())
{
for (auto i = 0u; i < getObjectEntryGroupCount(objectType); i++)
{
Editor::ClearSelectedObject(static_cast<ObjectType>(objectType), i, ObjectSelectionFlags::AllFlags);
auto loadedObj = objectMgr.GetLoadedObject(static_cast<ObjectType>(objectType), i);
if (loadedObj != nullptr)
{
Editor::SetSelectedObject(static_cast<ObjectType>(objectType), i, ObjectSelectionFlags::Selected);
}
}
}
TileElementIterator iter;
TileElementIteratorBegin(&iter);
do
{
ObjectEntryIndex type;
switch (iter.element->GetType())
{
default:
case TileElementType::Surface:
{
auto surfaceEl = iter.element->AsSurface();
auto surfaceIndex = surfaceEl->GetSurfaceObjectIndex();
auto edgeIndex = surfaceEl->GetEdgeObjectIndex();
Editor::SetSelectedObject(ObjectType::TerrainSurface, surfaceIndex, ObjectSelectionFlags::InUse);
Editor::SetSelectedObject(ObjectType::TerrainEdge, edgeIndex, ObjectSelectionFlags::InUse);
break;
}
case TileElementType::Track:
break;
case TileElementType::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, ObjectSelectionFlags::InUse);
Editor::SetSelectedObject(ObjectType::FootpathRailings, railingEntryIndex, ObjectSelectionFlags::InUse);
}
else
{
Editor::SetSelectedObject(ObjectType::Paths, legacyPathEntryIndex, ObjectSelectionFlags::InUse);
}
if (footpathEl->HasAddition())
{
auto pathAdditionEntryIndex = footpathEl->GetAdditionEntryIndex();
Editor::SetSelectedObject(ObjectType::PathAdditions, pathAdditionEntryIndex, ObjectSelectionFlags::InUse);
}
break;
}
case TileElementType::SmallScenery:
type = iter.element->AsSmallScenery()->GetEntryIndex();
Editor::SetSelectedObject(ObjectType::SmallScenery, type, ObjectSelectionFlags::InUse);
break;
case TileElementType::Entrance:
{
auto parkEntranceEl = iter.element->AsEntrance();
if (parkEntranceEl->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE)
break;
Editor::SetSelectedObject(ObjectType::ParkEntrance, 0, ObjectSelectionFlags::InUse);
// 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, ObjectSelectionFlags::InUse);
}
else
{
Editor::SetSelectedObject(ObjectType::Paths, legacyPathEntryIndex, ObjectSelectionFlags::InUse);
}
break;
}
case TileElementType::Wall:
type = iter.element->AsWall()->GetEntryIndex();
Editor::SetSelectedObject(ObjectType::Walls, type, ObjectSelectionFlags::InUse);
break;
case TileElementType::LargeScenery:
type = iter.element->AsLargeScenery()->GetEntryIndex();
Editor::SetSelectedObject(ObjectType::LargeScenery, type, ObjectSelectionFlags::InUse);
break;
case TileElementType::Banner:
{
auto banner = iter.element->AsBanner()->GetBanner();
if (banner != nullptr)
{
type = banner->type;
Editor::SetSelectedObject(ObjectType::Banners, type, ObjectSelectionFlags::InUse);
}
break;
}
}
} while (TileElementIteratorNext(&iter));
for (auto& ride : GetRideManager())
{
Editor::SetSelectedObject(ObjectType::Ride, ride.subtype, ObjectSelectionFlags::InUse);
Editor::SetSelectedObject(ObjectType::Station, ride.entrance_style, ObjectSelectionFlags::InUse);
Editor::SetSelectedObject(ObjectType::Music, ride.music, ObjectSelectionFlags::InUse);
}
// 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, ObjectSelectionFlags::InUse);
}
}
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, ObjectSelectionFlags::InUse);
}
}
auto numObjects = ObjectRepositoryGetItemsCount();
const auto* items = ObjectRepositoryGetItems();
for (size_t i = 0; i < numObjects; i++)
{
auto* selectionFlags = &_objectSelectionFlags[i];
const auto* item = &items[i];
*selectionFlags &= ~ObjectSelectionFlags::InUse;
if (item->LoadedObject != nullptr)
{
auto objectType = item->LoadedObject->GetObjectType();
auto entryIndex = objectMgr.GetLoadedObjectEntryIndex(item->LoadedObject.get());
*selectionFlags |= Editor::GetSelectedObjectFlags(objectType, entryIndex);
}
}
}
/**
*
* rct2: 0x006AB211
*/
void Sub6AB211()
{
int32_t numObjects = static_cast<int32_t>(ObjectRepositoryGetItemsCount());
_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 = ObjectRepositoryGetItems();
for (int32_t i = 0; i < numObjects; i++)
{
ObjectType objectType = items[i].Type;
_numAvailableObjectsForType[EnumValue(objectType)]++;
}
if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER)
{
SetupTrackDesignerObjects();
}
if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)
{
SetupTrackManagerObjects();
}
SetupInUseSelectionFlags();
ResetSelectedObjectCountAndSize();
if (!(gScreenFlags & (SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER)))
{
// To prevent it breaking in scenario mode.
if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
{
WindowEditorObjectSelectionSelectDefaultObjects();
}
}
ResetSelectedObjectCountAndSize();
}
/**
*
* rct2: 0x006AB316
*/
void EditorObjectFlagsFree()
{
_objectSelectionFlags.clear();
_objectSelectionFlags.shrink_to_fit();
}
/**
*
* rct2: 0x00685791
*/
static void RemoveSelectedObjectsFromResearch(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 = GetRideEntryByIndex(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;
tmp.baseRideType = 0;
ResearchRemove(tmp);
break;
}
default:
break;
}
}
}
/**
*
* rct2: 0x006ABB66
*/
void UnloadUnselectedObjects()
{
auto numItems = static_cast<int32_t>(ObjectRepositoryGetItemsCount());
const auto* items = ObjectRepositoryGetItems();
std::vector<ObjectEntryDescriptor> objectsToUnload;
for (int32_t i = 0; i < numItems; i++)
{
if (!(_objectSelectionFlags[i] & ObjectSelectionFlags::Selected))
{
auto descriptor = ObjectEntryDescriptor(items[i]);
if (!IsIntransientObjectType(items[i].Type))
{
RemoveSelectedObjectsFromResearch(descriptor);
objectsToUnload.push_back(descriptor);
}
}
}
ObjectManagerUnloadObjects(objectsToUnload);
}
/**
*
* rct2: 0x006AA805
*/
static void WindowEditorObjectSelectionSelectDefaultObjects()
{
if (_numSelectedObjectsForType[0] == 0)
{
for (auto defaultSelectedObject : DefaultSelectedObjects)
{
WindowEditorObjectSelectionSelectObject(
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)
{
WindowEditorObjectSelectionSelectObject(
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)
{
LoadPalette();
}
else
{
LOG_ERROR("Failed to load selected palette %s", std::string(newPaletteEntry.GetName()).c_str());
}
}
/**
*
* rct2: 0x006AA770
*/
void ResetSelectedObjectCountAndSize()
{
for (auto& objectType : _numSelectedObjectsForType)
{
objectType = 0;
}
int32_t numObjects = static_cast<int32_t>(ObjectRepositoryGetItemsCount());
const ObjectRepositoryItem* items = ObjectRepositoryGetItems();
for (int32_t i = 0; i < numObjects; i++)
{
ObjectType objectType = items[i].Type;
if (_objectSelectionFlags[i] & ObjectSelectionFlags::Selected)
{
_numSelectedObjectsForType[EnumValue(objectType)]++;
}
}
}
void FinishObjectSelection()
{
auto& gameState = OpenRCT2::GetGameState();
if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER)
{
SetEveryRideTypeInvented();
SetEveryRideEntryInvented();
auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
gameState.LastEntranceStyle = objManager.GetLoadedObjectEntryIndex("rct2.station.plain");
if (gameState.LastEntranceStyle == OBJECT_ENTRY_INDEX_NULL)
{
gameState.LastEntranceStyle = 0;
}
gameState.EditorStep = EditorStep::RollercoasterDesigner;
GfxInvalidateScreen();
}
else
{
SetAllSceneryItemsInvented();
ScenerySetDefaultPlacementConfiguration();
gameState.EditorStep = EditorStep::LandscapeEditor;
GfxInvalidateScreen();
}
}
/**
*
* rct2: 0x006AB54F
*/
ResultWithMessage WindowEditorObjectSelectionSelectObject(
uint8_t isMasterObject, int32_t flags, const ObjectRepositoryItem* item)
{
if (item == nullptr)
{
return ObjectSelectionError(isMasterObject, STR_OBJECT_SELECTION_ERR_OBJECT_DATA_NOT_FOUND);
}
int32_t numObjects = static_cast<int32_t>(ObjectRepositoryGetItemsCount());
// Get repository item index
int32_t index = -1;
const ObjectRepositoryItem* items = ObjectRepositoryGetItems();
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 & ObjectSelectionFlags::Selected))
{
return { true };
}
if (*selectionFlags & ObjectSelectionFlags::InUse)
{
return ObjectSelectionError(isMasterObject, STR_OBJECT_SELECTION_ERR_CURRENTLY_IN_USE);
}
if (*selectionFlags & ObjectSelectionFlags::AlwaysRequired)
{
return ObjectSelectionError(isMasterObject, STR_OBJECT_SELECTION_ERR_ALWAYS_REQUIRED);
}
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)
{
WindowEditorObjectSelectionSelectObject(++isMasterObject, flags, sgEntry);
}
}
_numSelectedObjectsForType[EnumValue(objectType)]--;
*selectionFlags &= ~ObjectSelectionFlags::Selected;
return { true };
}
if (isMasterObject == 0)
{
if (flags & INPUT_FLAG_EDITOR_OBJECT_ALWAYS_REQUIRED)
{
*selectionFlags |= ObjectSelectionFlags::AlwaysRequired;
}
}
if (*selectionFlags & ObjectSelectionFlags::Selected)
{
return { true };
}
if (item->Flags & ObjectItemFlags::IsCompatibilityObject)
{
return ObjectSelectionError(isMasterObject, STR_OBJECT_SELECTION_ERR_COMPAT_OBJECT);
}
ObjectType objectType = item->Type;
auto maxObjects = getObjectEntryGroupCount(objectType);
if (maxObjects <= _numSelectedObjectsForType[EnumValue(objectType)])
{
return ObjectSelectionError(isMasterObject, STR_OBJECT_SELECTION_ERR_TOO_MANY_OF_TYPE_SELECTED);
}
if (objectType == ObjectType::SceneryGroup && (flags & INPUT_FLAG_EDITOR_OBJECT_SELECT_OBJECTS_IN_SCENERY_GROUP))
{
for (const auto& sgEntry : item->SceneryGroupInfo.Entries)
{
const auto selectionResult = WindowEditorObjectSelectionSelectObject(++isMasterObject, flags, sgEntry);
if (!selectionResult.Successful)
{
_gSceneryGroupPartialSelectError = selectionResult.Message;
LOG_ERROR("Could not find object: %s", std::string(sgEntry.GetName()).c_str());
}
}
}
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];
ObjectCreateIdentifierName(objectName, 64, &item->ObjectEntry);
auto ft = Formatter::Common();
ft.Add<const char*>(objectName);
return ObjectSelectionError(isMasterObject, STR_OBJECT_SELECTION_ERR_SHOULD_SELECT_X_FIRST);
}
if (maxObjects <= _numSelectedObjectsForType[EnumValue(objectType)])
{
return ObjectSelectionError(isMasterObject, STR_OBJECT_SELECTION_ERR_TOO_MANY_OF_TYPE_SELECTED);
}
_numSelectedObjectsForType[EnumValue(objectType)]++;
*selectionFlags |= ObjectSelectionFlags::Selected;
return { true };
}
ResultWithMessage WindowEditorObjectSelectionSelectObject(
uint8_t isMasterObject, int32_t flags, const ObjectEntryDescriptor& descriptor)
{
auto& objectRepository = OpenRCT2::GetContext()->GetObjectRepository();
const auto* item = objectRepository.FindObject(descriptor);
return WindowEditorObjectSelectionSelectObject(isMasterObject, flags, item);
}
bool EditorCheckObjectGroupAtLeastOneSelected(ObjectType checkObjectType)
{
auto numObjects = std::min(ObjectRepositoryGetItemsCount(), _objectSelectionFlags.size());
const ObjectRepositoryItem* items = ObjectRepositoryGetItems();
for (size_t i = 0; i < numObjects; i++)
{
auto objectType = items[i].Type;
if (checkObjectType == objectType && (_objectSelectionFlags[i] & ObjectSelectionFlags::Selected))
{
return true;
}
}
return false;
}
bool EditorCheckObjectGroupAtLeastOneSurfaceSelected(bool queue)
{
auto numObjects = std::min(ObjectRepositoryGetItemsCount(), _objectSelectionFlags.size());
const auto* items = ObjectRepositoryGetItems();
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] & ObjectSelectionFlags::Selected)
&& queue == isQueue)
{
return true;
}
}
return false;
}
int32_t EditorRemoveUnusedObjects()
{
Sub6AB211();
SetupInUseSelectionFlags();
int32_t numObjects = static_cast<int32_t>(ObjectRepositoryGetItemsCount());
const ObjectRepositoryItem* items = ObjectRepositoryGetItems();
int32_t numUnselectedObjects = 0;
for (int32_t i = 0; i < numObjects; i++)
{
if (_objectSelectionFlags[i] & ObjectSelectionFlags::Selected)
{
if (!(_objectSelectionFlags[i] & ObjectSelectionFlags::InUse)
&& !(_objectSelectionFlags[i] & ObjectSelectionFlags::AlwaysRequired))
{
const ObjectRepositoryItem* item = &items[i];
ObjectType objectType = item->Type;
if (ObjectTypeIsIntransient(objectType))
continue;
// These object types require exactly one object to be selected at all times.
// Removing that object can badly break the game state.
if (objectType == ObjectType::ParkEntrance || objectType == ObjectType::Water)
continue;
// Its hard to determine exactly if a scenery group is used, so do not remove these automatically.
if (objectType == ObjectType::SceneryGroup)
continue;
_numSelectedObjectsForType[EnumValue(objectType)]--;
_objectSelectionFlags[i] &= ~ObjectSelectionFlags::Selected;
numUnselectedObjects++;
}
}
}
UnloadUnselectedObjects();
EditorObjectFlagsFree();
auto intent = Intent(INTENT_ACTION_REFRESH_SCENERY);
ContextBroadcastIntent(&intent);
return numUnselectedObjects;
}