diff --git a/include/config.h b/include/config.h index eb9c9ac3..2ca56fd1 100644 --- a/include/config.h +++ b/include/config.h @@ -214,6 +214,8 @@ enum ProjectIdentifier { define_pals_total, define_tiles_per_metatile, define_map_size, + define_map_offset_width, + define_map_offset_height, define_mask_metatile, define_mask_collision, define_mask_elevation, diff --git a/include/project.h b/include/project.h index 9a031c0d..fe1c48f2 100644 --- a/include/project.h +++ b/include/project.h @@ -240,24 +240,27 @@ public: static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + int getMapDataSize(int width, int height) const; + int getMaxMapDataSize() const { return this->maxMapDataSize; } + int getMaxMapWidth() const; + int getMaxMapHeight() const; + bool mapDimensionsValid(int width, int height) const; + bool calculateDefaultMapSize(); + int getDefaultMapDimension() const { return this->defaultMapDimension; } + QSize getMapSizeAddition() const { return this->mapSizeAddition; } + + int getMaxEvents(Event::Group group) const; + static QString getEmptyMapDefineName(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); static QString getEmptySpeciesName(); - static int getNumTilesPrimary(); - static int getNumTilesTotal(); - static int getNumMetatilesPrimary(); - static int getNumMetatilesTotal(); - static int getNumPalettesPrimary(); - static int getNumPalettesTotal(); - static int getMaxMapDataSize(); - static int getDefaultMapDimension(); - static int getMaxMapWidth(); - static int getMaxMapHeight(); - static int getMapDataSize(int width, int height); - static bool mapDimensionsValid(int width, int height); - bool calculateDefaultMapSize(); - int getMaxEvents(Event::Group group); + static int getNumTilesPrimary() { return num_tiles_primary; } + static int getNumTilesTotal() { return num_tiles_total; } + static int getNumMetatilesPrimary() { return num_metatiles_primary; } + static int getNumMetatilesTotal() { return Block::getMaxMetatileId() + 1; } + static int getNumPalettesPrimary(){ return num_pals_primary; } + static int getNumPalettesTotal() { return num_pals_total; } static QString getEmptyMapsecName(); static QString getMapGroupPrefix(); @@ -302,15 +305,19 @@ private: QString findSpeciesIconPath(const QStringList &names) const; - int maxEventsPerGroup; int maxObjectEvents; + QSize mapSizeAddition; + int maxMapDataSize; + int defaultMapDimension; + + // TODO: These really shouldn't be static, they're specific to a single project. + // We're making an assumption here that we only have one project open at a single time + // (which is true, but then if that's the case we should have some global Project instance instead) static int num_tiles_primary; static int num_tiles_total; static int num_metatiles_primary; static int num_pals_primary; static int num_pals_total; - static int max_map_data_size; - static int default_map_dimension; signals: void fileChanged(const QString &filepath); diff --git a/src/config.cpp b/src/config.cpp index 42a59149..fcd7f83b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -89,6 +89,8 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::define_pals_total, {"define_pals_total", "NUM_PALS_TOTAL"}}, {ProjectIdentifier::define_tiles_per_metatile, {"define_tiles_per_metatile", "NUM_TILES_PER_METATILE"}}, {ProjectIdentifier::define_map_size, {"define_map_size", "MAX_MAP_DATA_SIZE"}}, + {ProjectIdentifier::define_map_offset_width, {"define_map_offset_width", "MAP_OFFSET_W"}}, + {ProjectIdentifier::define_map_offset_height, {"define_map_offset_height", "MAP_OFFSET_H"}}, {ProjectIdentifier::define_mask_metatile, {"define_mask_metatile", "MAPGRID_METATILE_ID_MASK"}}, {ProjectIdentifier::define_mask_collision, {"define_mask_collision", "MAPGRID_COLLISION_MASK"}}, {ProjectIdentifier::define_mask_elevation, {"define_mask_elevation", "MAPGRID_ELEVATION_MASK"}}, diff --git a/src/project.cpp b/src/project.cpp index bbbc052c..d33063e0 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -29,8 +29,6 @@ int Project::num_tiles_total = 1024; int Project::num_metatiles_primary = 512; int Project::num_pals_primary = 6; int Project::num_pals_total = 13; -int Project::max_map_data_size = 10240; // 0x2800 -int Project::default_map_dimension = 20; Project::Project(QObject *parent) : QObject(parent), @@ -2109,7 +2107,12 @@ bool Project::readFieldmapProperties() { const QString numPalsTotalName = projectConfig.getIdentifier(ProjectIdentifier::define_pals_total); const QString maxMapSizeName = projectConfig.getIdentifier(ProjectIdentifier::define_map_size); const QString numTilesPerMetatileName = projectConfig.getIdentifier(ProjectIdentifier::define_tiles_per_metatile); - const QSet names = { + const QString mapOffsetWidthName = projectConfig.getIdentifier(ProjectIdentifier::define_map_offset_width); + const QString mapOffsetHeightName = projectConfig.getIdentifier(ProjectIdentifier::define_map_offset_height); + + const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); + fileWatcher.addPath(root + "/" + filename); + const QMap defines = parser.readCDefinesByName(filename, { numTilesPrimaryName, numTilesTotalName, numMetatilesPrimaryName, @@ -2117,10 +2120,9 @@ bool Project::readFieldmapProperties() { numPalsTotalName, maxMapSizeName, numTilesPerMetatileName, - }; - const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); - fileWatcher.addPath(root + "/" + filename); - const QMap defines = parser.readCDefinesByName(filename, names); + mapOffsetWidthName, + mapOffsetHeightName, + }); auto loadDefine = [defines](const QString name, int * dest, int min, int max) { auto it = defines.find(name); @@ -2146,25 +2148,35 @@ bool Project::readFieldmapProperties() { // we don't actually know what the maximum number of metatiles is. loadDefine(numMetatilesPrimaryName, &Project::num_metatiles_primary, 1, 0xFFFF - 1); + int w = 15, h = 14; // Default values of MAP_OFFSET_W, MAP_OFFSET_H + loadDefine(mapOffsetWidthName, &w, 0, INT_MAX); + loadDefine(mapOffsetHeightName, &h, 0, INT_MAX); + this->mapSizeAddition = QSize(w, h); + + this->maxMapDataSize = 10240; // Default value of MAX_MAP_DATA_SIZE + this->defaultMapDimension = 20; // Arbitrary default of 20x20. auto it = defines.find(maxMapSizeName); if (it != defines.end()) { int min = getMapDataSize(1, 1); if (it.value() >= min) { - Project::max_map_data_size = it.value(); - calculateDefaultMapSize(); + this->maxMapDataSize = it.value(); + if (getMapDataSize(this->defaultMapDimension, this->defaultMapDimension) > this->maxMapDataSize) { + // The specified map size is too small to use the default map dimensions. + // Calculate the largest square map size that we can use instead. + this->defaultMapDimension = qFloor((qSqrt(4 * this->maxMapDataSize + 1) - (w + h)) / 2); + } } else { - // must be large enough to support a 1x1 map - logWarn(QString("Value for map property '%1' is %2, must be at least %3. Using default (%4) instead.") + logWarn(QString("Value for map property '%1' of %2 is too small to support a 1x1 map. Must be at least %3. Using default (%4) instead.") .arg(maxMapSizeName) .arg(it.value()) .arg(min) - .arg(Project::max_map_data_size)); + .arg(this->maxMapDataSize)); } } else { logWarn(QString("Value for map property '%1' not found. Using default (%2) instead.") .arg(maxMapSizeName) - .arg(Project::max_map_data_size)); + .arg(this->maxMapDataSize)); } it = defines.find(numTilesPerMetatileName); @@ -3112,91 +3124,28 @@ QPixmap Project::getSpeciesIcon(const QString &species) { return pixmap; } -int Project::getNumTilesPrimary() -{ - return Project::num_tiles_primary; +int Project::getMapDataSize(int width, int height) const { + return (width + this->mapSizeAddition.width()) + * (height + this->mapSizeAddition.height()); } -int Project::getNumTilesTotal() -{ - return Project::num_tiles_total; +int Project::getMaxMapWidth() const { + return (getMaxMapDataSize() / (1 + this->mapSizeAddition.height())) - this->mapSizeAddition.width(); } -int Project::getNumMetatilesPrimary() -{ - return Project::num_metatiles_primary; +int Project::getMaxMapHeight() const { + return (getMaxMapDataSize() / (1 + this->mapSizeAddition.width())) - this->mapSizeAddition.height(); } -int Project::getNumMetatilesTotal() -{ - return Block::getMaxMetatileId() + 1; -} - -int Project::getNumPalettesPrimary() -{ - return Project::num_pals_primary; -} - -int Project::getNumPalettesTotal() -{ - return Project::num_pals_total; -} - -int Project::getMaxMapDataSize() -{ - return Project::max_map_data_size; -} - -int Project::getMapDataSize(int width, int height) -{ - // + 15 and + 14 come from fieldmap.c in pokeruby/pokeemerald/pokefirered. - return (width + 15) * (height + 14); -} - -int Project::getDefaultMapDimension() -{ - return Project::default_map_dimension; -} - -int Project::getMaxMapWidth() -{ - return (getMaxMapDataSize() / (1 + 14)) - 15; -} - -int Project::getMaxMapHeight() -{ - return (getMaxMapDataSize() / (1 + 15)) - 14; -} - -bool Project::mapDimensionsValid(int width, int height) { +bool Project::mapDimensionsValid(int width, int height) const { return getMapDataSize(width, height) <= getMaxMapDataSize(); } -// Get largest possible square dimensions for a map up to maximum of 20x20 (arbitrary) -bool Project::calculateDefaultMapSize(){ - int max = getMaxMapDataSize(); - - if (max >= getMapDataSize(20, 20)) { - default_map_dimension = 20; - } else if (max >= getMapDataSize(1, 1)) { - // Below equation derived from max >= (x + 15) * (x + 14) - // x^2 + 29x + (210 - max), then complete the square and simplify - default_map_dimension = qFloor((qSqrt(4 * getMaxMapDataSize() + 1) - 29) / 2); - } else { - logError(QString("'%1' of %2 is too small to support a 1x1 map. Must be at least %3.") - .arg(projectConfig.getIdentifier(ProjectIdentifier::define_map_size)) - .arg(max) - .arg(getMapDataSize(1, 1))); - return false; - } - return true; -} - // Object events have their own limit specified by ProjectIdentifier::define_obj_event_count. // The default value for this is 64. All events (object events included) are also limited by // the data types of the event counters in the project. This would normally be u8, so the limit is 255. // We let the users tell us this limit in case they change these data types. -int Project::getMaxEvents(Event::Group group) { +int Project::getMaxEvents(Event::Group group) const { if (group == Event::Group::Object) return qMin(this->maxObjectEvents, projectConfig.maxEventsPerGroup); return projectConfig.maxEventsPerGroup; diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 0ee3316e..08bfc9c5 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -227,7 +227,7 @@ int MainWindow::getHeight() { void MainWindow::setDimensions(int width, int height) { if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(width, height)) + if (this->editor->project && !this->editor->project->mapDimensionsValid(width, height)) return; this->editor->layout->setDimensions(width, height); this->tryCommitMapChanges(true); @@ -237,7 +237,7 @@ void MainWindow::setDimensions(int width, int height) { void MainWindow::setWidth(int width) { if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(width, this->editor->layout->getHeight())) + if (this->editor->project && !this->editor->project->mapDimensionsValid(width, this->editor->layout->getHeight())) return; this->editor->layout->setDimensions(width, this->editor->layout->getHeight()); this->tryCommitMapChanges(true); @@ -247,7 +247,7 @@ void MainWindow::setWidth(int width) { void MainWindow::setHeight(int height) { if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(this->editor->layout->getWidth(), height)) + if (this->editor->project && !this->editor->project->mapDimensionsValid(this->editor->layout->getWidth(), height)) return; this->editor->layout->setDimensions(this->editor->layout->getWidth(), height); this->tryCommitMapChanges(true); diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index aa8178ce..b88b4f3f 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -86,17 +86,14 @@ bool NewLayoutForm::validateMapDimensions() { int size = m_project->getMapDataSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); int maxSize = m_project->getMaxMapDataSize(); - // TODO: Get from project - const int additionalWidth = 15; - const int additionalHeight = 14; - QString errorText; if (size > maxSize) { + QSize addition = m_project->getMapSizeAddition(); errorText = QString("The specified width and height are too large.\n" "The maximum map width and height is the following: (width + %1) * (height + %2) <= %3\n" "The specified map width and height was: (%4 + %1) * (%5 + %2) = %6") - .arg(additionalWidth) - .arg(additionalHeight) + .arg(addition.width()) + .arg(addition.height()) .arg(maxSize) .arg(ui->spinBox_MapWidth->value()) .arg(ui->spinBox_MapHeight->value()) diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 5629d8e9..00790ca8 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -145,15 +145,18 @@ void ResizeLayoutPopup::setupLayoutView() { // Upper limits: maximum metatiles in a map formula: // max = (width + 15) * (height + 14) // This limit can be found in fieldmap.c in pokeruby/pokeemerald/pokefirered. - int numMetatiles = editor->project->getMapDataSize(rect.width() / 16, rect.height() / 16); - int maxMetatiles = editor->project->getMaxMapDataSize(); - if (numMetatiles > maxMetatiles) { - QString errorText = QString("The maximum layout width and height is the following: (width + 15) * (height + 14) <= %1\n" - "The specified layout width and height was: (%2 + 15) * (%3 + 14) = %4") - .arg(maxMetatiles) + int size = editor->project->getMapDataSize(rect.width() / 16, rect.height() / 16); + int maxSize = editor->project->getMaxMapDataSize(); + if (size > maxSize) { + QSize addition = editor->project->getMapSizeAddition(); + QString errorText = QString("The maximum layout width and height is the following: (width + %1) * (height + %2) <= %3\n" + "The specified layout width and height was: (%4 + %1) * (%5 + %2) = %6") + .arg(addition.width()) + .arg(addition.height()) + .arg(maxSize) .arg(rect.width() / 16) .arg(rect.height() / 16) - .arg(numMetatiles); + .arg(size); QMessageBox warning; warning.setIcon(QMessageBox::Warning); warning.setText("The specified width and height are too large.");