diff --git a/editor.cpp b/editor.cpp index ac470c02..4a56cdb3 100755 --- a/editor.cpp +++ b/editor.cpp @@ -26,13 +26,15 @@ void Editor::save() { void Editor::undo() { if (current_view) { - ((MapPixmapItem*)current_view)->undo(); + map->undo(); + map_item->draw(); } } void Editor::redo() { if (current_view) { - ((MapPixmapItem*)current_view)->redo(); + map->redo(); + map_item->draw(); } } @@ -58,6 +60,7 @@ void Editor::setEditingMap() { void Editor::setEditingCollision() { current_view = collision_item; if (collision_item) { + displayMapConnections(); collision_item->draw(); collision_item->setVisible(true); setConnectionsVisibility(true); @@ -318,8 +321,8 @@ void Editor::setMap(QString map_name) { } if (project) { map = project->loadMap(map_name); - displayMap(); selected_events->clear(); + displayMap(); updateSelectedEvents(); } } @@ -879,9 +882,6 @@ void MetatilesPixmapItem::updateSelection(QPointF pos, Qt::MouseButton button) { map->paint_tile_index = baseTileY * 8 + baseTileX; map->paint_tile_width = abs(map->paint_metatile_initial_x - x) + 1; map->paint_tile_height = abs(map->paint_metatile_initial_y - y) + 1; - map->smart_paths_enabled = button == Qt::RightButton - && map->paint_tile_width == 3 - && map->paint_tile_height == 3; emit map->paintTileChanged(map); } } @@ -1029,7 +1029,7 @@ void MapPixmapItem::paint(QGraphicsSceneMouseEvent *event) { int x = (int)(pos.x()) / 16; int y = (int)(pos.y()) / 16; - if (map->smart_paths_enabled) { + if (map->smart_paths_enabled && map->paint_tile_width == 3 && map->paint_tile_height == 3) { paintSmartPath(x, y); } else { paintNormal(x, y); @@ -1157,11 +1157,163 @@ void MapPixmapItem::floodFill(QGraphicsSceneMouseEvent *event) { QPointF pos = event->pos(); int x = (int)(pos.x()) / 16; int y = (int)(pos.y()) / 16; - map->floodFill(x, y, map->getSelectedBlockIndex(map->paint_tile_index)); + Block *block = map->getBlock(x, y); + int tile = map->getSelectedBlockIndex(map->paint_tile_index); + if (block && block->tile != tile) { + if (map->smart_paths_enabled && map->paint_tile_width == 3 && map->paint_tile_height == 3) + this->_floodFillSmartPath(x, y); + else + this->_floodFill(x, y); + } + + if (event->type() == QEvent::GraphicsSceneMouseRelease) { + map->commit(); + } draw(); } } +void MapPixmapItem::_floodFill(int initialX, int initialY) { + QList todo; + todo.append(QPoint(initialX, initialY)); + while (todo.length()) { + QPoint point = todo.takeAt(0); + int x = point.x(); + int y = point.y(); + + Block *block = map->getBlock(x, y); + if (block == NULL) { + continue; + } + + int xDiff = x - initialX; + int yDiff = y - initialY; + int i = xDiff % map->paint_tile_width; + int j = yDiff % map->paint_tile_height; + if (i < 0) i = map->paint_tile_width + i; + if (j < 0) j = map->paint_tile_height + j; + int tile = map->getSelectedBlockIndex(map->paint_tile_index + i + (j * 8)); + uint old_tile = block->tile; + if (old_tile == tile) { + continue; + } + + block->tile = tile; + map->_setBlock(x, y, *block); + if ((block = map->getBlock(x + 1, y)) && block->tile == old_tile) { + todo.append(QPoint(x + 1, y)); + } + if ((block = map->getBlock(x - 1, y)) && block->tile == old_tile) { + todo.append(QPoint(x - 1, y)); + } + if ((block = map->getBlock(x, y + 1)) && block->tile == old_tile) { + todo.append(QPoint(x, y + 1)); + } + if ((block = map->getBlock(x, y - 1)) && block->tile == old_tile) { + todo.append(QPoint(x, y - 1)); + } + } +} + +void MapPixmapItem::_floodFillSmartPath(int initialX, int initialY) { + // Smart path should never be enabled without a 3x3 block selection. + if (map->paint_tile_width != 3 || map->paint_tile_height != 3) return; + + // Shift to the middle tile of the smart path selection. + int openTile = map->paint_tile_index + 8 + 1; + + // Flood fill the region with the open tile. + QList todo; + todo.append(QPoint(initialX, initialY)); + while (todo.length()) { + QPoint point = todo.takeAt(0); + int x = point.x(); + int y = point.y(); + + Block *block = map->getBlock(x, y); + if (block == NULL) { + continue; + } + + int tile = map->getSelectedBlockIndex(openTile); + uint old_tile = block->tile; + if (old_tile == tile) { + continue; + } + + block->tile = tile; + map->_setBlock(x, y, *block); + if ((block = map->getBlock(x + 1, y)) && block->tile == old_tile) { + todo.append(QPoint(x + 1, y)); + } + if ((block = map->getBlock(x - 1, y)) && block->tile == old_tile) { + todo.append(QPoint(x - 1, y)); + } + if ((block = map->getBlock(x, y + 1)) && block->tile == old_tile) { + todo.append(QPoint(x, y + 1)); + } + if ((block = map->getBlock(x, y - 1)) && block->tile == old_tile) { + todo.append(QPoint(x, y - 1)); + } + } + + // Go back and resolve the flood-filled edge tiles. + // Mark tiles as visited while we go. + bool visited[map->getWidth() * map->getHeight()]; + for (int i = 0; i < sizeof visited; i++) + visited[i] = false; + + todo.append(QPoint(initialX, initialY)); + while (todo.length()) { + QPoint point = todo.takeAt(0); + int x = point.x(); + int y = point.y(); + visited[x + y * map->getWidth()] = true; + + Block *block = map->getBlock(x, y); + if (block == NULL) { + continue; + } + + int id = 0; + Block *top = map->getBlock(x, y - 1); + Block *right = map->getBlock(x + 1, y); + Block *bottom = map->getBlock(x, y + 1); + Block *left = map->getBlock(x - 1, y); + + // Get marching squares value, to determine which tile to use. + if (top && IS_SMART_PATH_TILE(top)) + id += 1; + if (right && IS_SMART_PATH_TILE(right)) + id += 2; + if (bottom && IS_SMART_PATH_TILE(bottom)) + id += 4; + if (left && IS_SMART_PATH_TILE(left)) + id += 8; + + block->tile = map->getSelectedBlockIndex(map->paint_tile_index + smartPathTable[id]); + map->_setBlock(x, y, *block); + + // Visit neighbors if they are smart-path tiles, and don't revisit any. + if (!visited[x + 1 + y * map->getWidth()] && (block = map->getBlock(x + 1, y)) && IS_SMART_PATH_TILE(block)) { + todo.append(QPoint(x + 1, y)); + visited[x + 1 + y * map->getWidth()] = true; + } + if (!visited[x - 1 + y * map->getWidth()] && (block = map->getBlock(x - 1, y)) && IS_SMART_PATH_TILE(block)) { + todo.append(QPoint(x - 1, y)); + visited[x - 1 + y * map->getWidth()] = true; + } + if (!visited[x + (y + 1) * map->getWidth()] && (block = map->getBlock(x, y + 1)) && IS_SMART_PATH_TILE(block)) { + todo.append(QPoint(x, y + 1)); + visited[x + (y + 1) * map->getWidth()] = true; + } + if (!visited[x + (y - 1) * map->getWidth()] && (block = map->getBlock(x, y - 1)) && IS_SMART_PATH_TILE(block)) { + todo.append(QPoint(x, y - 1)); + visited[x + (y - 1) * map->getWidth()] = true; + } + } +} + void MapPixmapItem::pick(QGraphicsSceneMouseEvent *event) { QPointF pos = event->pos(); int x = (int)(pos.x()) / 16; @@ -1215,20 +1367,6 @@ void MapPixmapItem::draw(bool ignoreCache) { } } -void MapPixmapItem::undo() { - if (map) { - map->undo(); - draw(); - } -} - -void MapPixmapItem::redo() { - if (map) { - map->redo(); - draw(); - } -} - void MapPixmapItem::updateCurHoveredTile(QPointF pos) { int x = ((int)pos.x()) / 16; int y = ((int)pos.y()) / 16; diff --git a/editor.h b/editor.h index b93f9bd5..c096d1a5 100755 --- a/editor.h +++ b/editor.h @@ -250,10 +250,10 @@ public: QList selection; virtual void paint(QGraphicsSceneMouseEvent*); virtual void floodFill(QGraphicsSceneMouseEvent*); + void _floodFill(int x, int y); + void _floodFillSmartPath(int initialX, int initialY); virtual void pick(QGraphicsSceneMouseEvent*); virtual void select(QGraphicsSceneMouseEvent*); - virtual void undo(); - virtual void redo(); virtual void draw(bool ignoreCache = false); private: diff --git a/mainwindow.cpp b/mainwindow.cpp index 697963cd..87254a63 100755 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), @@ -147,7 +149,22 @@ void MainWindow::setMap(QString map_name) { return; } editor->setMap(map_name); + redrawMapScene(); + displayMapProperties(); + setWindowTitle(map_name + " - " + editor->project->getProjectTitle() + " - pretmap"); + + connect(editor->map, SIGNAL(mapChanged(Map*)), this, SLOT(onMapChanged(Map *))); + connect(editor->map, SIGNAL(mapNeedsRedrawing(Map*)), this, SLOT(onMapNeedsRedrawing(Map *))); + connect(editor->map, SIGNAL(statusBarMessage(QString)), this, SLOT(setStatusBarMessage(QString))); + + setRecentMap(map_name); + updateMapList(); +} + +void MainWindow::redrawMapScene() +{ + editor->displayMap(); on_tabWidget_currentChanged(ui->tabWidget->currentIndex()); ui->graphicsView_Map->setScene(editor->scene); @@ -177,16 +194,6 @@ void MainWindow::setMap(QString map_name) { ui->graphicsView_Elevation->setScene(editor->scene_elevation_metatiles); //ui->graphicsView_Elevation->setSceneRect(editor->scene_elevation_metatiles->sceneRect()); ui->graphicsView_Elevation->setFixedSize(editor->elevation_metatiles_item->pixmap().width() + 2, editor->elevation_metatiles_item->pixmap().height() + 2); - - displayMapProperties(); - - setWindowTitle(map_name + " - " + editor->project->getProjectTitle() + " - pretmap"); - - connect(editor->map, SIGNAL(mapChanged(Map*)), this, SLOT(onMapChanged(Map *))); - connect(editor->map, SIGNAL(statusBarMessage(QString)), this, SLOT(setStatusBarMessage(QString))); - - setRecentMap(map_name); - updateMapList(); } void MainWindow::setRecentMap(QString map_name) { @@ -785,6 +792,10 @@ void MainWindow::onMapChanged(Map *map) { updateMapList(); } +void MainWindow::onMapNeedsRedrawing(Map *map) { + redrawMapScene(); +} + void MainWindow::on_action_Export_Map_Image_triggered() { QString defaultFilepath = QString("%1/%2.png").arg(editor->project->root).arg(editor->map->name); @@ -838,3 +849,68 @@ void MainWindow::on_comboBox_SecondaryTileset_activated(const QString &tilesetLa { editor->updateSecondaryTileset(tilesetLabel); } + +void MainWindow::on_pushButton_clicked() +{ + QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + dialog.setWindowTitle("Change Map Dimensions"); + dialog.setWindowModality(Qt::NonModal); + + QFormLayout form(&dialog); + + QSpinBox *widthSpinBox = new QSpinBox(); + QSpinBox *heightSpinBox = new QSpinBox(); + widthSpinBox->setMinimum(1); + heightSpinBox->setMinimum(1); + // See below for explanation of maximum map dimensions + widthSpinBox->setMaximum(0x1E7); + heightSpinBox->setMaximum(0x1D1); + widthSpinBox->setValue(editor->map->getWidth()); + heightSpinBox->setValue(editor->map->getHeight()); + form.addRow(new QLabel("Width"), widthSpinBox); + form.addRow(new QLabel("Height"), heightSpinBox); + + QLabel *errorLabel = new QLabel(); + QPalette errorPalette; + errorPalette.setColor(QPalette::WindowText, Qt::red); + errorLabel->setPalette(errorPalette); + errorLabel->setVisible(false); + + QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); + form.addRow(&buttonBox); + connect(&buttonBox, &QDialogButtonBox::accepted, [&dialog, &widthSpinBox, &heightSpinBox, &errorLabel](){ + // Ensure width and height are an acceptable size. + // The maximum number of metatiles in a map is the following: + // max = (width + 15) * (height + 14) + // This limit can be found in fieldmap.c in pokeruby/pokeemerald. + int realWidth = widthSpinBox->value() + 15; + int realHeight = heightSpinBox->value() + 14; + int numMetatiles = realWidth * realHeight; + if (numMetatiles <= 0x2800) { + dialog.accept(); + } else { + QString errorText = QString("Error: The specified width and height are too large.\n" + "The maximum width and height is the following: (width + 15) * (height + 14) <= 10240\n" + "The specified width and height was: (%1 + 15) * (%2 + 14) = %3") + .arg(widthSpinBox->value()) + .arg(heightSpinBox->value()) + .arg(numMetatiles); + errorLabel->setText(errorText); + errorLabel->setVisible(true); + } + }); + connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject())); + + form.addRow(errorLabel); + + if (dialog.exec() == QDialog::Accepted) { + editor->map->setDimensions(widthSpinBox->value(), heightSpinBox->value()); + editor->map->commit(); + onMapNeedsRedrawing(editor->map); + } +} + +void MainWindow::on_checkBox_smartPaths_stateChanged(int selected) +{ + editor->map->smart_paths_enabled = selected == Qt::Checked; +} diff --git a/mainwindow.h b/mainwindow.h index 88f08661..04b4cdb6 100755 --- a/mainwindow.h +++ b/mainwindow.h @@ -38,6 +38,7 @@ private slots: void onLoadMapRequested(QString, QString); void onMapChanged(Map *map); + void onMapNeedsRedrawing(Map *map); void on_action_Save_triggered(); void on_tabWidget_2_currentChanged(int index); @@ -93,6 +94,10 @@ private slots: void on_comboBox_SecondaryTileset_activated(const QString &arg1); + void on_pushButton_clicked(); + + void on_checkBox_smartPaths_stateChanged(int selected); + private: Ui::MainWindow *ui; QStandardItemModel *mapListModel; @@ -100,6 +105,7 @@ private: Editor *editor = NULL; QIcon* mapIcon; void setMap(QString); + void redrawMapScene(); void loadDataStructures(); void populateMapList(); QString getExistingDirectory(QString); diff --git a/mainwindow.ui b/mainwindow.ui index 77405325..59f41023 100755 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -228,10 +228,20 @@ - + margin-left: 10px + + Smart Paths + + + + + + + + Show Grid @@ -250,6 +260,13 @@ + + + + Change Dimensions + + + @@ -291,7 +308,7 @@ 0 0 436 - 621 + 620 diff --git a/map.cpp b/map.cpp index 2c1189c3..9c76674f 100755 --- a/map.cpp +++ b/map.cpp @@ -6,6 +6,7 @@ #include #include + Map::Map(QObject *parent) : QObject(parent) { paint_tile_index = 1; @@ -276,6 +277,7 @@ QPixmap Map::render(bool ignoreCache = false) { cacheBlockdata(); pixmap = pixmap.fromImage(image); } + return pixmap; } @@ -387,7 +389,7 @@ void Map::drawSelection(int i, int w, int selectionWidth, int selectionHeight, Q int y = i / w; painter->save(); - QColor penColor = smart_paths_enabled ? QColor(0xff, 0x0, 0xff) : QColor(0xff, 0xff, 0xff); + QColor penColor = QColor(0xff, 0xff, 0xff); painter->setPen(penColor); int rectWidth = selectionWidth * 16; int rectHeight = selectionHeight * 16; @@ -427,6 +429,36 @@ QPixmap Map::renderMetatiles() { return QPixmap::fromImage(image); } +void Map::setNewDimensionsBlockdata(int newWidth, int newHeight) { + int oldWidth = getWidth(); + int oldHeight = getHeight(); + + Blockdata* newBlockData = new Blockdata; + + for (int y = 0; y < newHeight; y++) + for (int x = 0; x < newWidth; x++) { + if (x < oldWidth && y < oldHeight) { + int index = y * oldWidth + x; + newBlockData->addBlock(layout->blockdata->blocks->value(index)); + } else { + newBlockData->addBlock(0); + } + } + + layout->blockdata->copyFrom(newBlockData); +} + +void Map::setDimensions(int newWidth, int newHeight, bool setNewBlockdata) { + if (setNewBlockdata) { + setNewDimensionsBlockdata(newWidth, newHeight); + } + + layout->width = QString::number(newWidth); + layout->height = QString::number(newHeight); + + emit mapChanged(this); +} + Block* Map::getBlock(int x, int y) { if (layout->blockdata && layout->blockdata->blocks) { if (x >= 0 && x < getWidth()) @@ -445,38 +477,6 @@ void Map::_setBlock(int x, int y, Block block) { } } -void Map::_floodFill(int x, int y, uint tile) { - QList todo; - todo.append(QPoint(x, y)); - while (todo.length()) { - QPoint point = todo.takeAt(0); - x = point.x(); - y = point.y(); - Block *block = getBlock(x, y); - if (block == NULL) { - continue; - } - uint old_tile = block->tile; - if (old_tile == tile) { - continue; - } - block->tile = tile; - _setBlock(x, y, *block); - if ((block = getBlock(x + 1, y)) && block->tile == old_tile) { - todo.append(QPoint(x + 1, y)); - } - if ((block = getBlock(x - 1, y)) && block->tile == old_tile) { - todo.append(QPoint(x - 1, y)); - } - if ((block = getBlock(x, y + 1)) && block->tile == old_tile) { - todo.append(QPoint(x, y + 1)); - } - if ((block = getBlock(x, y - 1)) && block->tile == old_tile) { - todo.append(QPoint(x, y - 1)); - } - } -} - void Map::_floodFillCollision(int x, int y, uint collision) { QList todo; todo.append(QPoint(x, y)); @@ -578,29 +578,48 @@ void Map::_floodFillCollisionElevation(int x, int y, uint collision, uint elevat void Map::undo() { + HistoryItem *commit = history.back(); + if (!commit) + return; + if (layout->blockdata) { - Blockdata *commit = history.back(); - if (commit != NULL) { - layout->blockdata->copyFrom(commit); - emit mapChanged(this); + layout->blockdata->copyFrom(commit->metatiles); + if (commit->layoutWidth != this->getWidth() || commit->layoutHeight != this->getHeight()) + { + this->setDimensions(commit->layoutWidth, commit->layoutHeight, false); + emit mapNeedsRedrawing(this); } + + emit mapChanged(this); } } void Map::redo() { + HistoryItem *commit = history.next(); + if (!commit) + return; + if (layout->blockdata) { - Blockdata *commit = history.next(); - if (commit != NULL) { - layout->blockdata->copyFrom(commit); - emit mapChanged(this); + layout->blockdata->copyFrom(commit->metatiles); + if (commit->layoutWidth != this->getWidth() || commit->layoutHeight != this->getHeight()) + { + this->setDimensions(commit->layoutWidth, commit->layoutHeight, false); + emit mapNeedsRedrawing(this); } + + emit mapChanged(this); } } void Map::commit() { if (layout->blockdata) { - if (!layout->blockdata->equals(history.current())) { - Blockdata* commit = layout->blockdata->copy(); + HistoryItem *item = history.current(); + bool atCurrentHistory = item + && layout->blockdata->equals(item->metatiles) + && this->getWidth() == item->layoutWidth + && this->getHeight() == item->layoutHeight; + if (!atCurrentHistory) { + HistoryItem *commit = new HistoryItem(layout->blockdata->copy(), this->getWidth(), this->getHeight()); history.push(commit); emit mapChanged(this); } @@ -615,14 +634,6 @@ void Map::setBlock(int x, int y, Block block) { } } -void Map::floodFill(int x, int y, uint tile) { - Block *block = getBlock(x, y); - if (block && block->tile != tile) { - _floodFill(x, y, tile); - commit(); - } -} - void Map::floodFillCollision(int x, int y, uint collision) { Block *block = getBlock(x, y); if (block && block->collision != collision) { diff --git a/map.h b/map.h index a98359a1..1270ce3b 100755 --- a/map.h +++ b/map.h @@ -10,6 +10,17 @@ #include #include +class HistoryItem { +public: + Blockdata *metatiles; + short layoutWidth; + short layoutHeight; + HistoryItem(Blockdata *metatiles_, short layoutWidth_, short layoutHeight_) { + this->metatiles = metatiles_; + this->layoutWidth = layoutWidth_; + this->layoutHeight = layoutHeight_; + } +}; template class History { @@ -173,8 +184,6 @@ public: void setBlock(int x, int y, Block block); void _setBlock(int x, int y, Block block); - void floodFill(int x, int y, uint tile); - void _floodFill(int x, int y, uint tile); void floodFillCollision(int x, int y, uint collision); void _floodFillCollision(int x, int y, uint collision); void floodFillElevation(int x, int y, uint elevation); @@ -182,7 +191,7 @@ public: void floodFillCollisionElevation(int x, int y, uint collision, uint elevation); void _floodFillCollisionElevation(int x, int y, uint collision, uint elevation); - History history; + History history; void undo(); void redo(); void commit(); @@ -194,6 +203,8 @@ public: QList connections; QPixmap renderConnection(Connection); + void setNewDimensionsBlockdata(int newWidth, int newHeight); + void setDimensions(int newWidth, int newHeight, bool setNewBlockData = true); QPixmap renderBorder(); void cacheBorder(); @@ -212,6 +223,7 @@ signals: void paintTileChanged(Map *map); void paintCollisionChanged(Map *map); void mapChanged(Map *map); + void mapNeedsRedrawing(Map *map); void statusBarMessage(QString); public slots: