diff --git a/include/core/map.h b/include/core/map.h index db81fc93..22c21d96 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -48,12 +48,9 @@ public: static QString mapConstantFromName(const QString &name); QString expectedConstantName() const { return Map::mapConstantFromName(m_name); } - void setLayout(Layout *layout); + void setLayout(Layout *layout) { m_layout = layout; } Layout* layout() const { return m_layout; } - void setLayoutId(const QString &layoutId) { m_layoutId = layoutId; } - QString layoutId() const { return m_layoutId; } - int getWidth() const; int getHeight() const; int getBorderWidth() const; @@ -71,10 +68,12 @@ public: void setNeedsHealLocation(bool needsHealLocation) { m_needsHealLocation = needsHealLocation; } void setIsPersistedToFile(bool persistedToFile) { m_isPersistedToFile = persistedToFile; } void setHasUnsavedDataChanges(bool unsavedDataChanges) { m_hasUnsavedDataChanges = unsavedDataChanges; } + void setLoaded(bool loaded) { m_loaded = loaded; } bool needsHealLocation() const { return m_needsHealLocation; } bool isPersistedToFile() const { return m_isPersistedToFile; } bool hasUnsavedDataChanges() const { return m_hasUnsavedDataChanges; } + bool loaded() const { return m_loaded; } void resetEvents(); QList getEvents(Event::Group group = Event::Group::None) const; @@ -109,7 +108,6 @@ public: private: QString m_name; QString m_constantName; - QString m_layoutId; QString m_sharedEventsMap = ""; QString m_sharedScriptsMap = ""; @@ -123,6 +121,7 @@ private: bool m_hasUnsavedDataChanges = false; bool m_needsHealLocation = false; bool m_scriptsLoaded = false; + bool m_loaded = false; QMap> m_events; QSet m_ownedEvents; // for memory management diff --git a/include/project.h b/include/project.h index f7a4b5cc..d393fbbb 100644 --- a/include/project.h +++ b/include/project.h @@ -37,9 +37,6 @@ public: QStringList healLocationSaveOrder; QMap> healLocations; QMap mapConstantsToMapNames; - QMap mapNamesToMapConstants; - QMap mapNameToLayoutId; - QMap mapNameToMapSectionName; QString layoutsLabel; QStringList layoutIds; QStringList layoutIdsMaster; @@ -81,7 +78,7 @@ public: void set_root(QString); - void clearMapCache(); + void clearMaps(); void clearTilesetCache(); void clearMapLayouts(); void clearEventGraphics(); @@ -90,9 +87,10 @@ public: bool sanityCheck(); bool load(); - QMap mapCache; - Map* loadMap(QString); - Map* getMap(QString); + Map* loadMap(const QString &mapName); + + // Note: This does not guarantee the map is loaded. + Map* getMap(const QString &mapName) { return this->maps.value(mapName); } QMap tilesetCache; Tileset* loadTileset(QString, Tileset *tileset = nullptr); @@ -111,7 +109,10 @@ public: bool readMapGroups(); void addNewMapGroup(const QString &groupName); - QString mapNameToMapGroup(const QString &mapName); + QString mapNameToMapGroup(const QString &mapName) const; + QString getMapConstant(const QString &mapName, const QString &defaultValue = QString()) const; + QString getMapLayoutId(const QString &mapName, const QString &defaultValue = QString()) const; + QString getMapLocation(const QString &mapName, const QString &defaultValue = QString()) const; struct NewMapSettings { QString name; @@ -253,10 +254,11 @@ public: static QString getMapGroupPrefix(); private: - QMap mapSectionDisplayNames; + QHash mapSectionDisplayNames; QMap modifiedFileTimestamps; QMap facingDirections; - QMap speciesToIconPath; + QHash speciesToIconPath; + QHash maps; const QRegularExpression re_gbapalExtension; const QRegularExpression re_bppExtension; diff --git a/src/core/events.cpp b/src/core/events.cpp index 9186e5c0..48c28c8f 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -285,7 +285,7 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { cloneJson["y"] = this->getY(); cloneJson["target_local_id"] = this->getTargetID(); const QString mapName = this->getTargetMap(); - cloneJson["target_map"] = project->mapNamesToMapConstants.value(mapName, mapName); + cloneJson["target_map"] = project->getMapConstant(mapName, mapName); this->addCustomAttributesTo(&cloneJson); return cloneJson; @@ -333,7 +333,7 @@ QSet CloneObjectEvent::getExpectedFields() { void CloneObjectEvent::loadPixmap(Project *project) { // Try to get the targeted object to clone int eventIndex = this->targetID - 1; - Map *clonedMap = project->getMap(this->targetMap); + Map *clonedMap = project->loadMap(this->targetMap); Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, eventIndex) : nullptr; if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) { @@ -380,7 +380,7 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) { warpJson["y"] = this->getY(); warpJson["elevation"] = this->getElevation(); const QString mapName = this->getDestinationMap(); - warpJson["dest_map"] = project->mapNamesToMapConstants.value(mapName, mapName); + warpJson["dest_map"] = project->getMapConstant(mapName, mapName); warpJson["dest_warp_id"] = this->getDestinationWarpID(); this->addCustomAttributesTo(&warpJson); @@ -839,7 +839,7 @@ OrderedJson::object HealLocationEvent::buildEventJson(Project *project) { healLocationJson["y"] = this->getY(); if (projectConfig.healLocationRespawnDataEnabled) { const QString mapName = this->getRespawnMapName(); - healLocationJson["respawn_map"] = project->mapNamesToMapConstants.value(mapName, mapName); + healLocationJson["respawn_map"] = project->getMapConstant(mapName, mapName); healLocationJson["respawn_npc"] = this->getRespawnNPC(); } diff --git a/src/core/map.cpp b/src/core/map.cpp index 7335c5bb..dc328cf0 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -23,7 +23,6 @@ Map::Map(QObject *parent) : QObject(parent) Map::Map(const Map &other, QObject *parent) : Map(parent) { m_name = other.m_name; m_constantName = other.m_constantName; - m_layoutId = other.m_layoutId; m_sharedEventsMap = other.m_sharedEventsMap; m_sharedScriptsMap = other.m_sharedScriptsMap; m_customAttributes = other.m_customAttributes; @@ -48,14 +47,6 @@ Map::~Map() { deleteConnections(); } -// Note: Map does not take ownership of layout -void Map::setLayout(Layout *layout) { - m_layout = layout; - if (layout) { - m_layoutId = layout->id; - } -} - // We don't enforce this for existing maps, but for creating new maps we need to formulaically generate a new MAP_NAME ID. QString Map::mapConstantFromName(const QString &name) { return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + Util::toDefineCase(name); diff --git a/src/core/mapconnection.cpp b/src/core/mapconnection.cpp index db2755e9..c478003b 100644 --- a/src/core/mapconnection.cpp +++ b/src/core/mapconnection.cpp @@ -53,7 +53,7 @@ void MapConnection::markMapEdited() { } Map* MapConnection::getMap(const QString& mapName) const { - return project ? project->getMap(mapName) : nullptr; + return project ? project->loadMap(mapName) : nullptr; } Map* MapConnection::targetMap() const { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ed7e7d77..85db9697 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1384,7 +1384,7 @@ void MainWindow::openNewMapDialog() { } void MainWindow::openDuplicateMapDialog(const QString &mapName) { - const Map *map = this->editor->project->getMap(mapName); + const Map *map = this->editor->project->loadMap(mapName); if (map) { auto dialog = new NewMapDialog(this->editor->project, map, this); dialog->open(); diff --git a/src/project.cpp b/src/project.cpp index 6590aac7..6b24ffb7 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -42,7 +42,7 @@ Project::Project(QObject *parent) : Project::~Project() { - clearMapCache(); + clearMaps(); clearTilesetCache(); clearMapLayouts(); clearEventGraphics(); @@ -127,9 +127,9 @@ QString Project::getProjectTitle() const { } } -void Project::clearMapCache() { - qDeleteAll(this->mapCache); - this->mapCache.clear(); +void Project::clearMaps() { + qDeleteAll(this->maps); + this->maps.clear(); } void Project::clearTilesetCache() { @@ -137,31 +137,18 @@ void Project::clearTilesetCache() { this->tilesetCache.clear(); } -Map* Project::loadMap(QString mapName) { - if (mapName == getDynamicMapName()) +Map* Project::loadMap(const QString &mapName) { + Map* map = this->maps.value(mapName); + if (!map) return nullptr; - Map *map; - if (mapCache.contains(mapName)) { - map = mapCache.value(mapName); - // TODO: uncomment when undo/redo history is fully implemented for all actions. - if (true/*map->hasUnsavedChanges()*/) { - return map; - } - } else { - map = new Map; - map->setName(mapName); - } + if (map->loaded()) + return map; - if (!(loadMapData(map) && loadMapLayout(map))){ - delete map; + if (!(loadMapData(map) && loadMapLayout(map))) return nullptr; - } - // If the map's MAPSEC value in the header changes, update our global array to keep it in sync. - connect(map->header(), &MapHeader::locationChanged, [this, map] { this->mapNameToMapSectionName.insert(map->name(), map->header()->location()); }); - - mapCache.insert(mapName, map); + map->setLoaded(true); emit mapLoaded(map); return map; } @@ -235,11 +222,18 @@ bool Project::loadMapData(Map* map) { // We should already know the map constant ID from the initial project launch, but we'll ensure it's correct here anyway. map->setConstantName(ParseUtil::jsonToQString(mapObj["id"])); - this->mapNamesToMapConstants.insert(map->name(), map->constantName()); this->mapConstantsToMapNames.insert(map->constantName(), map->name()); + const QString layoutId = ParseUtil::jsonToQString(mapObj["layout"]); + Layout* layout = this->mapLayouts.value(layoutId); + if (!layout) { + // We've already verified layout IDs on project launch and ignored maps with invalid IDs, so this shouldn't happen. + logError(QString("Cannot load map with unknown layout ID '%1'").arg(layoutId)); + return false; + } + map->setLayout(layout); + map->header()->setSong(ParseUtil::jsonToQString(mapObj["music"])); - map->setLayoutId(ParseUtil::jsonToQString(mapObj["layout"])); map->header()->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"])); map->header()->setRequiresFlash(ParseUtil::jsonToBool(mapObj["requires_flash"])); map->header()->setWeather(ParseUtil::jsonToQString(mapObj["weather"])); @@ -361,12 +355,9 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t this->mapNames.insert(mapNamePos, map->name()); this->groupNameToMapNames[settings.group].append(map->name()); this->mapConstantsToMapNames.insert(map->constantName(), map->name()); - this->mapNamesToMapConstants.insert(map->name(), map->constantName()); - this->mapNameToLayoutId.insert(map->name(), map->layoutId()); - this->mapNameToMapSectionName.insert(map->name(), map->header()->location()); map->setIsPersistedToFile(false); - this->mapCache.insert(map->name(), map); + this->maps.insert(map->name(), map); emit mapCreated(map, settings.group); @@ -446,18 +437,7 @@ Layout *Project::loadLayout(QString layoutId) { } bool Project::loadMapLayout(Map* map) { - if (!map->isPersistedToFile()) { - return true; - } - - Layout *layout = this->mapLayouts.value(map->layoutId()); - if (!layout) { - logError(QString("Map '%1' has an unknown layout '%2'").arg(map->name()).arg(map->layoutId())); - return false; - } - map->setLayout(layout); - - if (map->hasUnsavedChanges()) { + if (!map->isPersistedToFile() || map->hasUnsavedChanges()) { return true; } else { return loadLayout(map->layout()); @@ -667,7 +647,7 @@ void Project::saveMapGroups() { for (const auto &groupName : this->groupNames) { OrderedJson::array groupArr; for (const auto &mapName : this->groupNameToMapNames.value(groupName)) { - if (this->mapCache.value(mapName) && !this->mapCache.value(mapName)->isPersistedToFile()) { + if (this->maps.value(mapName) && !this->maps.value(mapName)->isPersistedToFile()) { // This is a new map that hasn't been saved yet, don't add it to the global map groups list yet. continue; } @@ -1127,7 +1107,7 @@ void Project::writeBlockdata(QString path, const Blockdata &blockdata) { } void Project::saveAll() { - for (auto map : this->mapCache) { + for (auto map : this->maps) { saveMap(map, true); // Avoid double-saving the layouts } for (auto layout : this->mapLayouts) { @@ -1137,6 +1117,8 @@ void Project::saveAll() { } void Project::saveMap(Map *map, bool skipLayout) { + if (!map || !map->loaded()) return; + // Create/Modify a few collateral files for brand new maps. const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_map_folders) + map->name(); const QString fullPath = QString("%1/%2").arg(this->root).arg(folderPath); @@ -1198,7 +1180,7 @@ void Project::saveMap(Map *map, bool skipLayout) { OrderedJson::array connectionsArr; for (const auto &connection : connections) { OrderedJson::object connectionObj; - connectionObj["map"] = this->mapNamesToMapConstants.value(connection->targetMapName(), connection->targetMapName()); + connectionObj["map"] = getMapConstant(connection->targetMapName(), connection->targetMapName()); connectionObj["offset"] = connection->offset(); connectionObj["direction"] = connection->direction(); connectionsArr.append(connectionObj); @@ -1548,15 +1530,6 @@ Blockdata Project::readBlockdata(QString path, bool *ok) { return blockdata; } -Map* Project::getMap(QString map_name) { - if (mapCache.contains(map_name)) { - return mapCache.value(map_name); - } else { - Map *map = loadMap(map_name); - return map; - } -} - Tileset* Project::getTileset(QString label, bool forceLoad) { Tileset *existingTileset = nullptr; if (tilesetCache.contains(label)) { @@ -1760,8 +1733,8 @@ bool Project::readWildMonData() { } bool Project::readMapGroups() { + clearMaps(); this->mapConstantsToMapNames.clear(); - this->mapNamesToMapConstants.clear(); this->mapNames.clear(); this->groupNames.clear(); this->groupNameToMapNames.clear(); @@ -1780,8 +1753,10 @@ bool Project::readMapGroups() { QJsonObject mapGroupsObj = mapGroupsDoc.object(); QJsonArray mapGroupOrder = mapGroupsObj["group_order"].toArray(); + // Save special "Dynamic" constant const QString dynamicMapName = getDynamicMapName(); - const QString dynamicMapConstant = getDynamicMapDefineName(); + this->mapConstantsToMapNames.insert(getDynamicMapDefineName(), dynamicMapName); + this->mapNames.append(dynamicMapName); // Process the map group lists QStringList failedMapNames; @@ -1793,11 +1768,6 @@ bool Project::readMapGroups() { // Process the names in this map group for (int j = 0; j < mapNamesJson.size(); j++) { const QString mapName = ParseUtil::jsonToQString(mapNamesJson.at(j)); - if (mapName == dynamicMapName) { - logWarn(QString("Ignoring map with reserved name '%1'.").arg(mapName)); - failedMapNames.append(mapName); - continue; - } if (this->mapNames.contains(mapName)) { logWarn(QString("Ignoring repeated map name '%1'.").arg(mapName)); failedMapNames.append(mapName); @@ -1819,11 +1789,6 @@ bool Project::readMapGroups() { failedMapNames.append(mapName); continue; } - if (mapConstant == dynamicMapConstant) { - logWarn(QString("Ignoring map with reserved \"id\" value '%1'.").arg(mapName)); - failedMapNames.append(mapName); - continue; - } const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); if (!mapConstant.startsWith(expectedPrefix)) { logWarn(QString("Map '%1' has invalid \"id\" value '%2' and will be ignored. Value must begin with '%3'.").arg(mapName).arg(mapConstant).arg(expectedPrefix)); @@ -1855,22 +1820,28 @@ bool Project::readMapGroups() { logWarn(QString("Map '%1' has unknown \"region_map_section\" value '%2'.").arg(mapName).arg(mapSectionName)); } - // Success, save the constants to the project + // Success, create the Map object + auto map = new Map; + map->setName(mapName); + map->setConstantName(mapConstant); + map->setLayout(this->mapLayouts.value(layoutId)); + map->header()->setLocation(mapSectionName); + this->maps.insert(mapName, map); + this->mapNames.append(mapName); this->groupNameToMapNames[groupName].append(mapName); this->mapConstantsToMapNames.insert(mapConstant, mapName); - this->mapNamesToMapConstants.insert(mapName, mapConstant); - this->mapNameToLayoutId.insert(mapName, layoutId); - this->mapNameToMapSectionName.insert(mapName, mapSectionName); } } + // TODO: This might be ok now that we have layout-only mode? if (this->groupNames.isEmpty()) { logError(QString("Failed to find any map groups in %1").arg(filepath)); return false; } - if (this->mapNames.isEmpty()) { - logError(QString("Failed to find any map names in %1").arg(filepath)); + // TODO: This might be ok now that we have layout-only mode? + if (this->maps.isEmpty()) { + logError(QString("Failed to find any maps in %1").arg(filepath)); return false; } @@ -1880,11 +1851,6 @@ bool Project::readMapGroups() { emit mapsExcluded(failedMapNames); } - // Save special "Dynamic" constant - this->mapConstantsToMapNames.insert(dynamicMapConstant, dynamicMapName); - this->mapNamesToMapConstants.insert(dynamicMapName, dynamicMapConstant); - this->mapNames.append(dynamicMapName); - return true; } @@ -1899,7 +1865,7 @@ void Project::addNewMapGroup(const QString &groupName) { emit mapGroupAdded(groupName); } -QString Project::mapNameToMapGroup(const QString &mapName) { +QString Project::mapNameToMapGroup(const QString &mapName) const { for (auto it = this->groupNameToMapNames.constBegin(); it != this->groupNameToMapNames.constEnd(); it++) { const QStringList mapNames = it.value(); if (mapNames.contains(mapName)) { @@ -1909,6 +1875,23 @@ QString Project::mapNameToMapGroup(const QString &mapName) { return QString(); } +QString Project::getMapConstant(const QString &mapName, const QString &defaultValue) const { + if (mapName == getDynamicMapName()) return getDynamicMapDefineName(); + + Map* map = this->maps.value(mapName); + return map ? map->constantName() : defaultValue; +} + +QString Project::getMapLayoutId(const QString &mapName, const QString &defaultValue) const { + Map* map = this->maps.value(mapName); + return (map && map->layout()) ? map->layout()->id : defaultValue; +} + +QString Project::getMapLocation(const QString &mapName, const QString &defaultValue) const { + Map* map = this->maps.value(mapName); + return map ? map->header()->location() : defaultValue; +} + // When we ask the user to provide a new identifier for something (like a map name or MAPSEC id) // we use this to make sure that it doesn't collide with any known identifiers first. // Porymap knows of many more identifiers than this, but for simplicity we only check the lists that users can add to via Porymap. @@ -1937,7 +1920,8 @@ bool Project::isIdentifierUnique(const QString &identifier) const { if (this->encounterGroupLabels.contains(identifier)) return false; // Check event IDs - for (const auto &map : this->mapCache) { + for (const auto &map : this->maps) { + if (!map->loaded()) continue; auto events = map->getEvents(); for (const auto &event : events) { QString idName = event->getIdName(); @@ -3202,16 +3186,14 @@ bool Project::hasUnsavedChanges() { return true; // Check layouts for unsaved changes - for (auto i = this->mapLayouts.constBegin(); i != this->mapLayouts.constEnd(); i++) { - auto layout = i.value(); - if (layout && layout->hasUnsavedChanges()) + for (const auto &layout : this->mapLayouts) { + if (layout->hasUnsavedChanges()) return true; } - // Check loaded maps for unsaved changes - for (auto i = this->mapCache.constBegin(); i != this->mapCache.constEnd(); i++) { - auto map = i.value(); - if (map && map->hasUnsavedChanges()) + // Check maps for unsaved changes + for (const auto &map : this->maps) { + if (map->hasUnsavedChanges()) return true; } return false; diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index a8c330d2..1bdc43aa 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -104,7 +104,7 @@ QStandardItem *MapListModel::createMapItem(const QString &mapName, QStandardItem map->setData(mapName, MapListUserRoles::NameRole); map->setData("map_name", MapListUserRoles::TypeRole); map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren); - map->setToolTip(this->project->mapNamesToMapConstants.value(mapName)); + map->setToolTip(this->project->getMapConstant(mapName)); this->mapItems.insert(mapName, map); return map; } @@ -164,10 +164,10 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { if (name == this->activeItemName) return this->mapOpenedIcon; - const Map* map = this->project->mapCache.value(name); - if (!map) + const Map* map = this->project->getMap(name); + if (!map || !map->loaded()) return this->mapGrayIcon; - return map->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; + return map->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; } else if (type == this->folderTypeName) { // Decorating map folder in the map list return item->hasChildren() ? this->mapFolderIcon : this->emptyMapFolderIcon; @@ -446,7 +446,7 @@ MapLocationModel::MapLocationModel(Project *project, QObject *parent) : MapListM insertMapFolderItem(idName); } for (const auto &mapName : this->project->mapNames) { - insertMapItem(mapName, this->project->mapNameToMapSectionName.value(mapName)); + insertMapItem(mapName, this->project->getMapLocation(mapName)); } } @@ -470,7 +470,7 @@ LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListMod insertMapFolderItem(layoutId); } for (const auto &mapName : this->project->mapNames) { - insertMapItem(mapName, this->project->mapNameToLayoutId.value(mapName)); + insertMapItem(mapName, this->project->getMapLayoutId(mapName)); } }