mirror of https://github.com/OpenRCT2/OpenRCT2.git
636 lines
20 KiB
C++
636 lines
20 KiB
C++
#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 <algorithm>
|
|
#include <array>
|
|
#include <memory>
|
|
#include <unordered_set>
|
|
#include "../core/Console.hpp"
|
|
#include "../core/Memory.hpp"
|
|
#include "../localisation/StringIds.h"
|
|
#include "FootpathItemObject.h"
|
|
#include "LargeSceneryObject.h"
|
|
#include "Object.h"
|
|
#include "ObjectManager.h"
|
|
#include "ObjectRepository.h"
|
|
#include "SceneryGroupObject.h"
|
|
#include "SmallSceneryObject.h"
|
|
#include "WallObject.h"
|
|
|
|
#include "ObjectList.h"
|
|
|
|
class ObjectManager final : public IObjectManager
|
|
{
|
|
private:
|
|
IObjectRepository * _objectRepository;
|
|
std::vector<Object *> _loadedObjects;
|
|
|
|
public:
|
|
explicit ObjectManager(IObjectRepository * objectRepository)
|
|
{
|
|
Guard::ArgumentNotNull(objectRepository);
|
|
|
|
_objectRepository = objectRepository;
|
|
_loadedObjects.resize(OBJECT_ENTRY_COUNT);
|
|
|
|
UpdateSceneryGroupIndexes();
|
|
reset_type_to_ride_entry_index_map();
|
|
}
|
|
|
|
~ObjectManager() override
|
|
{
|
|
UnloadAll();
|
|
}
|
|
|
|
Object * GetLoadedObject(size_t index) override
|
|
{
|
|
if (index >= OBJECT_ENTRY_COUNT)
|
|
{
|
|
#ifdef DEBUG
|
|
log_warning("Object index %u exceeds maximum of %d.", index, OBJECT_ENTRY_COUNT);
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
if (index >= _loadedObjects.size())
|
|
{
|
|
return nullptr;
|
|
}
|
|
return _loadedObjects[index];
|
|
}
|
|
|
|
Object * GetLoadedObject(sint32 objectType, size_t index) override
|
|
{
|
|
if (index >= (size_t)object_entry_group_counts[objectType])
|
|
{
|
|
#ifdef DEBUG
|
|
log_warning("Object index %u exceeds maximum for type %d.", index, objectType);
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
size_t objectIndex = index;
|
|
for (sint32 i = 0; i < objectType; i++)
|
|
{
|
|
objectIndex += object_entry_group_counts[i];
|
|
}
|
|
return GetLoadedObject(objectIndex);
|
|
}
|
|
|
|
Object * GetLoadedObject(const rct_object_entry * entry) override
|
|
{
|
|
Object * loadedObject = nullptr;
|
|
const ObjectRepositoryItem * ori = _objectRepository->FindObject(entry);
|
|
if (ori != nullptr)
|
|
{
|
|
loadedObject = ori->LoadedObject;
|
|
}
|
|
return loadedObject;
|
|
}
|
|
|
|
uint8 GetLoadedObjectEntryIndex(const Object * object) override
|
|
{
|
|
uint8 result = UINT8_MAX;
|
|
size_t index = GetLoadedObjectIndex(object);
|
|
if (index != SIZE_MAX)
|
|
{
|
|
get_type_entry_index(index, nullptr, &result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Object * LoadObject(const rct_object_entry * entry) override
|
|
{
|
|
Object * loadedObject = nullptr;
|
|
const ObjectRepositoryItem * ori = _objectRepository->FindObject(entry);
|
|
if (ori != nullptr)
|
|
{
|
|
loadedObject = ori->LoadedObject;
|
|
if (loadedObject == nullptr)
|
|
{
|
|
uint8 objectType = object_entry_get_type(entry);
|
|
sint32 slot = FindSpareSlot(objectType);
|
|
if (slot != -1)
|
|
{
|
|
loadedObject = GetOrLoadObject(ori);
|
|
if (loadedObject != nullptr)
|
|
{
|
|
if (_loadedObjects.size() <= (size_t)slot)
|
|
{
|
|
_loadedObjects.resize(slot + 1);
|
|
}
|
|
_loadedObjects[slot] = loadedObject;
|
|
UpdateSceneryGroupIndexes();
|
|
reset_type_to_ride_entry_index_map();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return loadedObject;
|
|
}
|
|
|
|
bool LoadObjects(const rct_object_entry * entries, size_t count) override
|
|
{
|
|
// Find all the required objects
|
|
bool missingObjects;
|
|
auto requiredObjects = GetRequiredObjects(entries, &missingObjects);
|
|
if (missingObjects)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Create a new list of loaded objects
|
|
size_t numNewLoadedObjects = 0;
|
|
auto loadedObjects = LoadObjects(requiredObjects, &numNewLoadedObjects);
|
|
|
|
if (!std::get<0>(loadedObjects))
|
|
{
|
|
UnloadAll();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
SetNewLoadedObjectList(std::get<1>(loadedObjects));
|
|
UpdateSceneryGroupIndexes();
|
|
reset_type_to_ride_entry_index_map();
|
|
log_verbose("%u / %u new objects loaded", numNewLoadedObjects, requiredObjects.size());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void UnloadObjects(const rct_object_entry * entries, size_t count) override
|
|
{
|
|
// TODO there are two performance issues here:
|
|
// - FindObject for every entry which is a dictionary lookup
|
|
// - GetLoadedObjectIndex for every entry which enumerates _loadedList
|
|
|
|
size_t numObjectsUnloaded = 0;
|
|
for (size_t i = 0; i < count; i++)
|
|
{
|
|
const rct_object_entry * entry = &entries[i];
|
|
const ObjectRepositoryItem * ori = _objectRepository->FindObject(entry);
|
|
if (ori != nullptr)
|
|
{
|
|
Object * loadedObject = ori->LoadedObject;
|
|
if (loadedObject != nullptr)
|
|
{
|
|
UnloadObject(loadedObject);
|
|
numObjectsUnloaded++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (numObjectsUnloaded > 0)
|
|
{
|
|
UpdateSceneryGroupIndexes();
|
|
reset_type_to_ride_entry_index_map();
|
|
}
|
|
}
|
|
|
|
void UnloadAll() override
|
|
{
|
|
for (auto object : _loadedObjects)
|
|
{
|
|
UnloadObject(object);
|
|
}
|
|
UpdateSceneryGroupIndexes();
|
|
reset_type_to_ride_entry_index_map();
|
|
}
|
|
|
|
void ResetObjects() override
|
|
{
|
|
for (auto loadedObject : _loadedObjects)
|
|
{
|
|
if (loadedObject != nullptr)
|
|
{
|
|
loadedObject->Unload();
|
|
loadedObject->Load();
|
|
}
|
|
}
|
|
UpdateSceneryGroupIndexes();
|
|
reset_type_to_ride_entry_index_map();
|
|
}
|
|
|
|
std::vector<const ObjectRepositoryItem *> GetPackableObjects() override
|
|
{
|
|
std::vector<const ObjectRepositoryItem *> objects;
|
|
size_t numObjects = _objectRepository->GetNumObjects();
|
|
for (size_t i = 0; i < numObjects; i++)
|
|
{
|
|
const ObjectRepositoryItem * item = &_objectRepository->GetObjects()[i];
|
|
if (item->LoadedObject != nullptr &&
|
|
item->LoadedObject->GetLegacyData() != nullptr &&
|
|
IsObjectCustom(item))
|
|
{
|
|
objects.push_back(item);
|
|
}
|
|
}
|
|
return objects;
|
|
}
|
|
|
|
static rct_string_id GetObjectSourceGameString(const rct_object_entry * entry)
|
|
{
|
|
switch (object_entry_get_source_game(entry))
|
|
{
|
|
case OBJECT_SOURCE_RCT1:
|
|
return STR_SCENARIO_CATEGORY_RCT1;
|
|
case OBJECT_SOURCE_ADDED_ATTRACTIONS:
|
|
return STR_SCENARIO_CATEGORY_RCT1_AA;
|
|
case OBJECT_SOURCE_LOOPY_LANDSCAPES:
|
|
return STR_SCENARIO_CATEGORY_RCT1_LL;
|
|
case OBJECT_SOURCE_RCT2:
|
|
return STR_ROLLERCOASTER_TYCOON_2_DROPDOWN;
|
|
case OBJECT_SOURCE_WACKY_WORLDS:
|
|
return STR_OBJECT_FILTER_WW;
|
|
case OBJECT_SOURCE_TIME_TWISTER:
|
|
return STR_OBJECT_FILTER_TT;
|
|
case OBJECT_SOURCE_OPENRCT2_OFFICIAL:
|
|
return STR_OBJECT_FILTER_OPENRCT2_OFFICIAL;
|
|
default:
|
|
return STR_OBJECT_FILTER_CUSTOM;
|
|
}
|
|
}
|
|
|
|
private:
|
|
sint32 FindSpareSlot(uint8 objectType)
|
|
{
|
|
sint32 firstIndex = GetIndexFromTypeEntry(objectType, 0);
|
|
sint32 endIndex = firstIndex + object_entry_group_counts[objectType];
|
|
for (sint32 i = firstIndex; i < endIndex; i++)
|
|
{
|
|
if (_loadedObjects.size() > (size_t)i && _loadedObjects[i] == nullptr)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
size_t GetLoadedObjectIndex(const Object * object)
|
|
{
|
|
Guard::ArgumentNotNull(object, GUARD_LINE);
|
|
|
|
auto result = std::numeric_limits<size_t>().max();
|
|
auto it = std::find(_loadedObjects.begin(), _loadedObjects.end(), object);
|
|
if (it != _loadedObjects.end())
|
|
{
|
|
result = std::distance(_loadedObjects.begin(), it);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void SetNewLoadedObjectList(const std::vector<Object *> &newLoadedObjects)
|
|
{
|
|
if (newLoadedObjects.size() == 0)
|
|
{
|
|
UnloadAll();
|
|
}
|
|
else
|
|
{
|
|
UnloadObjectsExcept(newLoadedObjects);
|
|
}
|
|
_loadedObjects = newLoadedObjects;
|
|
}
|
|
|
|
void UnloadObject(Object * object)
|
|
{
|
|
if (object != nullptr)
|
|
{
|
|
// TODO try to prevent doing a repository search
|
|
const ObjectRepositoryItem * ori = _objectRepository->FindObject(object->GetObjectEntry());
|
|
if (ori != nullptr)
|
|
{
|
|
_objectRepository->UnregisterLoadedObject(ori, object);
|
|
}
|
|
|
|
// Because it's possible to have the same loaded object for multiple
|
|
// slots, we have to make sure find and set all of them to nullptr
|
|
for (auto &obj : _loadedObjects)
|
|
{
|
|
if (obj == object)
|
|
{
|
|
obj = nullptr;
|
|
}
|
|
}
|
|
|
|
object->Unload();
|
|
delete object;
|
|
}
|
|
}
|
|
|
|
void UnloadObjectsExcept(const std::vector<Object *> &newLoadedObjects)
|
|
{
|
|
// Build a hash set for quick checking
|
|
auto exceptSet = std::unordered_set<Object *>();
|
|
for (auto object : newLoadedObjects)
|
|
{
|
|
if (object != nullptr)
|
|
{
|
|
exceptSet.insert(object);
|
|
}
|
|
}
|
|
|
|
// Unload objects that are not in the hash set
|
|
size_t totalObjectsLoaded = 0;
|
|
size_t numObjectsUnloaded = 0;
|
|
for (auto object : _loadedObjects)
|
|
{
|
|
if (object != nullptr)
|
|
{
|
|
totalObjectsLoaded++;
|
|
if (exceptSet.find(object) == exceptSet.end())
|
|
{
|
|
UnloadObject(object);
|
|
numObjectsUnloaded++;
|
|
}
|
|
}
|
|
}
|
|
|
|
log_verbose("%u / %u objects unloaded", numObjectsUnloaded, totalObjectsLoaded);
|
|
}
|
|
|
|
void UpdateSceneryGroupIndexes()
|
|
{
|
|
for (auto loadedObject : _loadedObjects)
|
|
{
|
|
if (loadedObject != nullptr)
|
|
{
|
|
rct_scenery_entry * sceneryEntry;
|
|
switch (loadedObject->GetObjectType()) {
|
|
case OBJECT_TYPE_SMALL_SCENERY:
|
|
sceneryEntry = (rct_scenery_entry *)loadedObject->GetLegacyData();
|
|
sceneryEntry->small_scenery.scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject);
|
|
break;
|
|
case OBJECT_TYPE_LARGE_SCENERY:
|
|
sceneryEntry = (rct_scenery_entry *)loadedObject->GetLegacyData();
|
|
sceneryEntry->large_scenery.scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject);
|
|
break;
|
|
case OBJECT_TYPE_WALLS:
|
|
sceneryEntry = (rct_scenery_entry *)loadedObject->GetLegacyData();
|
|
sceneryEntry->wall.scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject);
|
|
break;
|
|
case OBJECT_TYPE_BANNERS:
|
|
sceneryEntry = (rct_scenery_entry *)loadedObject->GetLegacyData();
|
|
sceneryEntry->banner.scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject);
|
|
break;
|
|
case OBJECT_TYPE_PATH_BITS:
|
|
sceneryEntry = (rct_scenery_entry *)loadedObject->GetLegacyData();
|
|
sceneryEntry->path_bit.scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject);
|
|
break;
|
|
case OBJECT_TYPE_SCENERY_GROUP:
|
|
auto sgObject = dynamic_cast<SceneryGroupObject *>(loadedObject);
|
|
sgObject->UpdateEntryIndexes();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// HACK Scenery window will lose its tabs after changing the scenery group indexing
|
|
// for now just close it, but it will be better to later tell it to invalidate the tabs
|
|
window_close_by_class(WC_SCENERY);
|
|
}
|
|
|
|
uint8 GetPrimarySceneryGroupEntryIndex(Object * loadedObject)
|
|
{
|
|
auto sceneryObject = dynamic_cast<SceneryObject *>(loadedObject);
|
|
const rct_object_entry * primarySGEntry = sceneryObject->GetPrimarySceneryGroup();
|
|
Object * sgObject = GetLoadedObject(primarySGEntry);
|
|
|
|
uint8 entryIndex = 255;
|
|
if (sgObject != nullptr)
|
|
{
|
|
entryIndex = GetLoadedObjectEntryIndex(sgObject);
|
|
}
|
|
return entryIndex;
|
|
}
|
|
|
|
rct_object_entry* DuplicateObjectEntry(const rct_object_entry* original)
|
|
{
|
|
rct_object_entry * duplicate = Memory::Allocate<rct_object_entry>(sizeof(rct_object_entry));
|
|
duplicate->checksum = original->checksum;
|
|
strncpy(duplicate->name, original->name, 8);
|
|
duplicate->flags = original->flags;
|
|
return duplicate;
|
|
}
|
|
|
|
std::vector<rct_object_entry> GetInvalidObjects(const rct_object_entry * entries) override
|
|
{
|
|
std::vector<rct_object_entry> invalidEntries;
|
|
invalidEntries.reserve(OBJECT_ENTRY_COUNT);
|
|
for (sint32 i = 0; i < OBJECT_ENTRY_COUNT; i++)
|
|
{
|
|
auto entry = entries[i];
|
|
const ObjectRepositoryItem * ori = nullptr;
|
|
if (object_entry_is_empty(&entry))
|
|
{
|
|
entry = { 0 };
|
|
continue;
|
|
}
|
|
|
|
ori = _objectRepository->FindObject(&entry);
|
|
if (ori == nullptr)
|
|
{
|
|
if (object_entry_get_type(&entry) != OBJECT_TYPE_SCENARIO_TEXT)
|
|
{
|
|
invalidEntries.push_back(entry);
|
|
ReportMissingObject(&entry);
|
|
}
|
|
else
|
|
{
|
|
log_info("Ignoring missing STEX entry.");
|
|
entry = { 0 };
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Object * loadedObject = nullptr;
|
|
loadedObject = ori->LoadedObject;
|
|
if (loadedObject == nullptr)
|
|
{
|
|
loadedObject = _objectRepository->LoadObject(ori);
|
|
if (loadedObject == nullptr)
|
|
{
|
|
invalidEntries.push_back(entry);
|
|
ReportObjectLoadProblem(&entry);
|
|
}
|
|
delete loadedObject;
|
|
}
|
|
}
|
|
}
|
|
return invalidEntries;
|
|
}
|
|
|
|
std::vector<const ObjectRepositoryItem *> GetRequiredObjects(const rct_object_entry * entries, bool * missingObjects)
|
|
{
|
|
std::vector<const ObjectRepositoryItem *> requiredObjects;
|
|
*missingObjects = false;
|
|
for (sint32 i = 0; i < OBJECT_ENTRY_COUNT; i++)
|
|
{
|
|
const rct_object_entry * entry = &entries[i];
|
|
const ObjectRepositoryItem * ori = nullptr;
|
|
if (!object_entry_is_empty(entry))
|
|
{
|
|
ori = _objectRepository->FindObject(entry);
|
|
if (ori == nullptr && object_entry_get_type(entry) != OBJECT_TYPE_SCENARIO_TEXT)
|
|
{
|
|
*missingObjects = true;
|
|
ReportMissingObject(entry);
|
|
}
|
|
}
|
|
requiredObjects.push_back(ori);
|
|
}
|
|
return requiredObjects;
|
|
}
|
|
|
|
std::pair<bool, std::vector<Object *>> LoadObjects(std::vector<const ObjectRepositoryItem *> &requiredObjects, size_t * outNewObjectsLoaded)
|
|
{
|
|
size_t newObjectsLoaded = 0;
|
|
std::vector<Object *> loadedObjects;
|
|
loadedObjects.reserve(OBJECT_ENTRY_COUNT);
|
|
for (auto ori : requiredObjects)
|
|
{
|
|
Object * loadedObject = nullptr;
|
|
if (ori != nullptr)
|
|
{
|
|
loadedObject = ori->LoadedObject;
|
|
if (loadedObject == nullptr)
|
|
{
|
|
loadedObject = GetOrLoadObject(ori);
|
|
if (loadedObject == nullptr)
|
|
{
|
|
ReportObjectLoadProblem(&ori->ObjectEntry);
|
|
return std::make_pair(false, std::vector<Object *>());
|
|
} else {
|
|
newObjectsLoaded++;
|
|
}
|
|
}
|
|
}
|
|
loadedObjects.push_back(loadedObject);
|
|
}
|
|
if (outNewObjectsLoaded != nullptr)
|
|
{
|
|
*outNewObjectsLoaded = newObjectsLoaded;
|
|
}
|
|
return std::make_pair(true, loadedObjects);
|
|
}
|
|
|
|
Object * GetOrLoadObject(const ObjectRepositoryItem * ori)
|
|
{
|
|
Object * loadedObject = ori->LoadedObject;
|
|
if (loadedObject == nullptr)
|
|
{
|
|
// Try to load object
|
|
loadedObject = _objectRepository->LoadObject(ori);
|
|
if (loadedObject != nullptr)
|
|
{
|
|
loadedObject->Load();
|
|
|
|
// Connect the ori to the registered object
|
|
_objectRepository->RegisterLoadedObject(ori, loadedObject);
|
|
}
|
|
}
|
|
return loadedObject;
|
|
}
|
|
|
|
static void ReportMissingObject(const rct_object_entry * entry)
|
|
{
|
|
utf8 objName[DAT_NAME_LENGTH + 1] = { 0 };
|
|
std::copy_n(entry->name, DAT_NAME_LENGTH, objName);
|
|
Console::Error::WriteLine("[%s] Object not found.", objName);
|
|
}
|
|
|
|
void ReportObjectLoadProblem(const rct_object_entry * entry)
|
|
{
|
|
utf8 objName[DAT_NAME_LENGTH + 1] = { 0 };
|
|
std::copy_n(entry->name, DAT_NAME_LENGTH, objName);
|
|
Console::Error::WriteLine("[%s] Object could not be loaded.", objName);
|
|
}
|
|
|
|
static sint32 GetIndexFromTypeEntry(uint8 objectType, uint8 entryIndex)
|
|
{
|
|
sint32 result = 0;
|
|
for (uint8 i = 0; i < objectType; i++)
|
|
{
|
|
result += object_entry_group_counts[i];
|
|
}
|
|
result += entryIndex;
|
|
return result;
|
|
}
|
|
};
|
|
|
|
static ObjectManager * _objectManager = nullptr;
|
|
|
|
IObjectManager * CreateObjectManager(IObjectRepository * objectRepository)
|
|
{
|
|
_objectManager = new ObjectManager(objectRepository);
|
|
return _objectManager;
|
|
}
|
|
|
|
IObjectManager * GetObjectManager()
|
|
{
|
|
return _objectManager;
|
|
}
|
|
|
|
void * object_manager_get_loaded_object_by_index(size_t index)
|
|
{
|
|
IObjectManager * objectManager = GetObjectManager();
|
|
Object * loadedObject = objectManager->GetLoadedObject(index);
|
|
return (void *)loadedObject;
|
|
}
|
|
|
|
void * object_manager_get_loaded_object(const rct_object_entry * entry)
|
|
{
|
|
IObjectManager * objectManager = GetObjectManager();
|
|
Object * loadedObject = objectManager->GetLoadedObject(entry);
|
|
return (void *)loadedObject;
|
|
}
|
|
|
|
uint8 object_manager_get_loaded_object_entry_index(const void * loadedObject)
|
|
{
|
|
IObjectManager * objectManager = GetObjectManager();
|
|
const Object * object = static_cast<const Object *>(loadedObject);
|
|
uint8 entryIndex = objectManager->GetLoadedObjectEntryIndex(object);
|
|
return entryIndex;
|
|
}
|
|
|
|
void * object_manager_load_object(const rct_object_entry * entry)
|
|
{
|
|
IObjectManager * objectManager = GetObjectManager();
|
|
Object * loadedObject = objectManager->LoadObject(entry);
|
|
return (void *)loadedObject;
|
|
}
|
|
|
|
void object_manager_unload_objects(const rct_object_entry * entries, size_t count)
|
|
{
|
|
IObjectManager * objectManager = GetObjectManager();
|
|
objectManager->UnloadObjects(entries, count);
|
|
}
|
|
|
|
void object_manager_unload_all_objects()
|
|
{
|
|
IObjectManager * objectManager = GetObjectManager();
|
|
if (objectManager != nullptr)
|
|
{
|
|
objectManager->UnloadAll();
|
|
}
|
|
}
|
|
|
|
rct_string_id object_manager_get_source_game_string(const rct_object_entry * entry)
|
|
{
|
|
return ObjectManager::GetObjectSourceGameString(entry);
|
|
}
|
|
|