ProjectConfig::getKeyValueMap() {
map.insert("collision_sheet_path", this->collisionSheetPath);
map.insert("collision_sheet_width", QString::number(this->collisionSheetWidth));
map.insert("collision_sheet_height", QString::number(this->collisionSheetHeight));
+ map.insert("warp_behaviors", this->warpBehaviors.join(","));
+ map.insert("warp_behavior_warning_disabled", QString::number(this->warpBehaviorWarningDisabled));
+
return map;
}
@@ -1254,6 +1294,24 @@ int ProjectConfig::getCollisionSheetHeight() {
return this->collisionSheetHeight;
}
+void ProjectConfig::setWarpBehaviors(const QStringList &behaviors) {
+ this->warpBehaviors = behaviors;
+ this->save();
+}
+
+QStringList ProjectConfig::getWarpBehaviors() {
+ return this->warpBehaviors;
+}
+
+void ProjectConfig::setWarpBehaviorWarningDisabled(bool disabled) {
+ this->warpBehaviorWarningDisabled = disabled;
+ this->save();
+}
+
+bool ProjectConfig::getWarpBehaviorWarningDisabled() {
+ return this->warpBehaviorWarningDisabled;
+}
+
UserConfig userConfig;
diff --git a/src/editor.cpp b/src/editor.cpp
index 5c3bd7b3..b64dfb09 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -106,7 +106,6 @@ void Editor::setEditingMap() {
this->cursorMapTileRect->setActive(true);
setMapEditingButtonsEnabled(true);
- this->constructBehaviorValueList(); // TODO: Remove
}
void Editor::setEditingCollision() {
@@ -1989,58 +1988,11 @@ void Editor::redrawObject(DraggablePixmapItem *item) {
}
}
-/* TODO: Add tab to PSE for warp behaviors, containing:
- - A description of what it means
- - A check box to enable/disable the warning
- - An editable list of warp behavior names
-
-*/
-// TODO: Relocate to file handling the "warp behaviors" window, make static list below a config default
-const QStringList warpBehaviorNames = {
- "MB_NORTH_ARROW_WARP",
- "MB_SOUTH_ARROW_WARP",
- "MB_WEST_ARROW_WARP",
- "MB_EAST_ARROW_WARP",
- "MB_STAIRS_OUTSIDE_ABANDONED_SHIP",
- "MB_WATER_SOUTH_ARROW_WARP",
- "MB_SHOAL_CAVE_ENTRANCE",
- "MB_SECRET_BASE_SPOT_RED_CAVE_OPEN",
- "MB_SECRET_BASE_SPOT_BROWN_CAVE_OPEN",
- "MB_SECRET_BASE_SPOT_YELLOW_CAVE_OPEN",
- "MB_SECRET_BASE_SPOT_BLUE_CAVE_OPEN",
- "MB_SECRET_BASE_SPOT_TREE_LEFT_OPEN",
- "MB_SECRET_BASE_SPOT_TREE_RIGHT_OPEN",
- "MB_SECRET_BASE_SPOT_SHRUB_OPEN",
- "MB_ANIMATED_DOOR",
- "MB_NON_ANIMATED_DOOR",
- "MB_PETALBURG_GYM_DOOR",
- "MB_WATER_DOOR",
- "MB_LADDER",
- "MB_UP_ESCALATOR",
- "MB_DOWN_ESCALATOR",
- "MB_DEEP_SOUTH_WARP",
- "MB_LAVARIDGE_GYM_B1F_WARP",
- "MB_LAVARIDGE_GYM_1F_WARP",
- "MB_AQUA_HIDEOUT_WARP",
- "MB_MT_PYRE_HOLE",
- "MB_MOSSDEEP_GYM_WARP",
- "MB_BRIDGE_OVER_OCEAN",
-};
-QSet warpBehaviorValues;
-void Editor::constructBehaviorValueList() {
- if (!project) return;
- warpBehaviorValues.clear();
- //warpBehaviorNames.removeDuplicates(); // TODO: Uncomment when list is actually dynamic
- for (auto name : warpBehaviorNames) {
- if (project->metatileBehaviorMap.contains(name)) {
- warpBehaviorValues.insert(project->metatileBehaviorMap[name]);
- }
- }
-}
-
// Warp events display a warning if they're not positioned on a metatile with a warp behavior.
void Editor::updateWarpEventWarning(Event *event) {
- if (!map || !event || event->getEventType() != Event::Type::Warp)
+ if (projectConfig.getWarpBehaviorWarningDisabled())
+ return;
+ if (!project || !map || !event || event->getEventType() != Event::Type::Warp)
return;
Block block;
Metatile * metatile = nullptr;
@@ -2049,16 +2001,18 @@ void Editor::updateWarpEventWarning(Event *event) {
metatile = Tileset::getMetatile(block.metatileId(), map->layout->tileset_primary, map->layout->tileset_secondary);
}
// metatile may be null if the warp is in the map border. Display the warning in this case
- bool validWarpBehavior = metatile && warpBehaviorValues.contains(metatile->behavior());
+ bool validWarpBehavior = metatile && project->warpBehaviorValues.contains(metatile->behavior());
warpEvent->setWarningEnabled(!validWarpBehavior);
}
// The warp event behavior warning is updated whenever the event moves or the event selection changes.
// It does not respond to changes in the underlying metatile. To capture the common case of a user painting
-// metatiles on the Map tab then returning to the Events tab we update the warings for all selected warp
+// metatiles on the Map tab then returning to the Events tab we update the warnings for all selected warp
// events when the Events tab is opened. This does not cover the case where metatiles are painted while
// still on the Events tab, such as by Undo/Redo or the scripting API.
void Editor::updateWarpEventWarnings() {
+ if (projectConfig.getWarpBehaviorWarningDisabled())
+ return;
if (selected_events) {
for (auto selection : *selected_events)
updateWarpEventWarning(selection->event);
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 355fcdb3..73158270 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -2744,6 +2744,33 @@ void MainWindow::on_actionProject_Settings_triggered() {
this->openProjectSettingsEditor(porymapConfig.getProjectSettingsTab());
}
+void MainWindow::onWarpBehaviorWarningClicked() {
+ static const QString text = QString(
+ "By default, Warp Events only function as exits if they're positioned on a metatile "
+ "whose Metatile Behavior is treated specially in your project's code."
+ );
+ static const QString informative = QString(
+ ""
+ "For instance, most floor metatiles in a cave have the behavior MB_CAVE, but the floor space in front of an "
+ "exit will have MB_SOUTH_ARROW_WARP, which is treated specially and will allow a Warp Event to warp the player. "
+ "You can see in the status bar what behavior a metatile has when you mouse over it, or by selecting it in the Tileset Editor."
+ "
"
+ "Note: Not all Warp Events that show this warning are incorrect! For example some warps may function "
+ "as a 1-way entrance, and others may have the metatile underneath them changed programmatically."
+ "
"
+ "You can disable this warning or edit the list of behaviors that silence this warning under Options -> Project Settings..."
+ "
"
+ );
+ QMessageBox msgBox(QMessageBox::Information, "porymap", text, QMessageBox::Close, this);
+ QPushButton *settings = msgBox.addButton("Open Settings...", QMessageBox::ActionRole);
+ msgBox.setDefaultButton(QMessageBox::Close);
+ msgBox.setTextFormat(Qt::RichText);
+ msgBox.setInformativeText(informative);
+ msgBox.exec();
+ if (msgBox.clickedButton() == settings)
+ this->openProjectSettingsEditor(ProjectSettingsEditor::eventsTab);
+}
+
void MainWindow::on_actionCustom_Scripts_triggered() {
if (!this->customScriptsEditor)
initCustomScriptsEditor();
diff --git a/src/project.cpp b/src/project.cpp
index 3e487efa..856bf2a2 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -2291,6 +2291,17 @@ bool Project::readMetatileBehaviors() {
for (QString defineName : this->metatileBehaviorMap.keys()) {
this->metatileBehaviorMapInverse.insert(this->metatileBehaviorMap[defineName], defineName);
}
+
+ // Construct warp behavior value list for the warp metatile behavior warning
+ const QStringList warpBehaviorNames = projectConfig.getWarpBehaviors();
+ this->warpBehaviorValues.clear();
+ for (auto name : warpBehaviorNames) {
+ if (this->metatileBehaviorMap.contains(name)) {
+ int value = this->metatileBehaviorMap.value(name);
+ this->warpBehaviorValues.insert(static_cast(value));
+ }
+ }
+
return true;
}
diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp
index 7da0a977..3002569a 100644
--- a/src/ui/eventframes.cpp
+++ b/src/ui/eventframes.cpp
@@ -525,9 +525,7 @@ void WarpFrame::connectSignals(MainWindow *window) {
// warning
this->warning->disconnect();
- connect(this->warning, &QPushButton::clicked, [window]() {
- window->openProjectSettingsEditor(ProjectSettingsEditor::warpBehaviorsTab);
- });
+ connect(this->warning, &QPushButton::clicked, window, &MainWindow::onWarpBehaviorWarningClicked);
}
void WarpFrame::initialize() {
diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp
index 44930598..09a94981 100644
--- a/src/ui/projectsettingseditor.cpp
+++ b/src/ui/projectsettingseditor.cpp
@@ -10,7 +10,7 @@
Editor for the settings in a user's porymap.project.cfg file (and 'use_encounter_json' in porymap.user.cfg).
*/
-const int ProjectSettingsEditor::warpBehaviorsTab = 4;
+const int ProjectSettingsEditor::eventsTab = 3;
ProjectSettingsEditor::ProjectSettingsEditor(QWidget *parent, Project *project) :
QMainWindow(parent),
@@ -45,6 +45,8 @@ void ProjectSettingsEditor::connectSignals() {
this->setBorderMetatileIds(customSize, this->getBorderMetatileIds(!customSize));
this->setBorderMetatilesUi(customSize);
});
+ connect(ui->button_AddWarpBehavior, &QAbstractButton::clicked, [this](bool) { this->updateWarpBehaviorsList(true); });
+ connect(ui->button_RemoveWarpBehavior, &QAbstractButton::clicked, [this](bool) { this->updateWarpBehaviorsList(false); });
// Connect file selection buttons
connect(ui->button_ChoosePrefabs, &QAbstractButton::clicked, [this](bool) { this->choosePrefabsFile(); });
@@ -56,6 +58,7 @@ void ProjectSettingsEditor::connectSignals() {
connect(ui->button_HealspotsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_HealspotsIcon); });
connect(ui->button_PokemonIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_PokemonIcon); });
+
// Display a warning if a mask value overlaps with another mask in its group.
connect(ui->spinBox_MetatileIdMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateBlockMaskOverlapWarning);
connect(ui->spinBox_CollisionMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateBlockMaskOverlapWarning);
@@ -67,7 +70,8 @@ void ProjectSettingsEditor::connectSignals() {
// Record that there are unsaved changes if any of the settings are modified
for (auto combo : ui->centralwidget->findChildren()){
- if (combo != ui->comboBox_IconSpecies) // Changes to the icon species combo box are just for info display, don't mark as unsaved
+ // Changes to these two combo boxes are just for info display, don't mark as unsaved
+ if (combo != ui->comboBox_IconSpecies && combo != ui->comboBox_WarpBehaviors)
connect(combo, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::markEdited);
}
for (auto checkBox : ui->centralwidget->findChildren())
@@ -92,17 +96,25 @@ void ProjectSettingsEditor::initUi() {
ui->comboBox_DefaultPrimaryTileset->addItems(project->primaryTilesetLabels);
ui->comboBox_DefaultSecondaryTileset->addItems(project->secondaryTilesetLabels);
ui->comboBox_IconSpecies->addItems(project->speciesToIconPath.keys());
+ ui->comboBox_WarpBehaviors->addItems(project->metatileBehaviorMap.keys());
}
ui->comboBox_BaseGameVersion->addItems(ProjectConfig::versionStrings);
ui->comboBox_AttributesSize->addItems({"1", "2", "4"});
// Validate that the border metatiles text is a comma-separated list of metatile values
static const QString regex_Hex = "(0[xX])?[A-Fa-f0-9]+";
- static const QRegularExpression expression(QString("^(%1,)*%1$").arg(regex_Hex)); // Comma-separated list of hex values
- QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression);
- ui->lineEdit_BorderMetatiles->setValidator(validator);
+ static const QRegularExpression expression_HexList(QString("^(%1,)*%1$").arg(regex_Hex)); // Comma-separated list of hex values
+ QRegularExpressionValidator *validator_HexList = new QRegularExpressionValidator(expression_HexList);
+ ui->lineEdit_BorderMetatiles->setValidator(validator_HexList);
this->setBorderMetatilesUi(projectConfig.getUseCustomBorderSize());
+ // Validate that the text added to the warp behavior list could be a valid define
+ // (we don't care whether it actually is a metatile behavior define)
+ static const QRegularExpression expression_Word("^[A-Za-z0-9_]*$");
+ QRegularExpressionValidator *validator_Word = new QRegularExpressionValidator(expression_Word);
+ ui->comboBox_WarpBehaviors->setValidator(validator_Word);
+ ui->textEdit_WarpBehaviors->setTextColor(Qt::gray);
+
// Set spin box limits
uint16_t maxMetatileId = Block::getMaxMetatileId();
ui->spinBox_FillMetatile->setMaximum(maxMetatileId);
@@ -272,6 +284,38 @@ void ProjectSettingsEditor::updatePokemonIconPath(const QString &newSpecies) {
this->prevIconSpecies = newSpecies;
}
+QStringList ProjectSettingsEditor::getWarpBehaviorsList() {
+ return ui->textEdit_WarpBehaviors->toPlainText().split("\n");
+}
+
+void ProjectSettingsEditor::setWarpBehaviorsList(QStringList list) {
+ ui->textEdit_WarpBehaviors->setText(list.join("\n"));
+}
+
+void ProjectSettingsEditor::updateWarpBehaviorsList(bool adding) {
+ const QString input = ui->comboBox_WarpBehaviors->currentText();
+ if (input.isEmpty())
+ return;
+
+ QStringList list = this->getWarpBehaviorsList();
+ int pos = list.indexOf(input);
+
+ if (adding && pos < 0) {
+ // Add text to list
+ list.prepend(input);
+ } else if (!adding && pos >= 0) {
+ // Remove text from list
+ list.removeAt(pos);
+ } else {
+ // Trying to add text already in list,
+ // or trying to remove text not in list
+ return;
+ }
+
+ this->setWarpBehaviorsList(list);
+ this->hasUnsavedChanges = true;
+}
+
void ProjectSettingsEditor::createProjectPathsTable() {
auto pathPairs = ProjectConfig::defaultPaths.values();
for (auto pathPair : pathPairs) {
@@ -366,6 +410,7 @@ void ProjectSettingsEditor::refresh() {
ui->checkBox_EnableCustomBorderSize->setChecked(projectConfig.getUseCustomBorderSize());
ui->checkBox_OutputCallback->setChecked(projectConfig.getTilesetsHaveCallback());
ui->checkBox_OutputIsCompressed->setChecked(projectConfig.getTilesetsHaveIsCompressed());
+ ui->checkBox_DisableWarning->setChecked(projectConfig.getWarpBehaviorWarningDisabled());
// Set spin box values
ui->spinBox_Elevation->setValue(projectConfig.getDefaultElevation());
@@ -396,6 +441,7 @@ void ProjectSettingsEditor::refresh() {
ui->lineEdit_HealspotsIcon->setText(projectConfig.getEventIconPath(Event::Group::Heal));
for (auto lineEdit : ui->scrollAreaContents_ProjectPaths->findChildren())
lineEdit->setText(projectConfig.getFilePath(lineEdit->objectName(), true));
+ this->setWarpBehaviorsList(projectConfig.getWarpBehaviors());
this->refreshing = false; // Allow signals
}
@@ -429,6 +475,7 @@ void ProjectSettingsEditor::save() {
projectConfig.setUseCustomBorderSize(ui->checkBox_EnableCustomBorderSize->isChecked());
projectConfig.setTilesetsHaveCallback(ui->checkBox_OutputCallback->isChecked());
projectConfig.setTilesetsHaveIsCompressed(ui->checkBox_OutputIsCompressed->isChecked());
+ projectConfig.setWarpBehaviorWarningDisabled(ui->checkBox_DisableWarning->isChecked());
// Save spin box settings
projectConfig.setDefaultElevation(ui->spinBox_Elevation->value());
@@ -455,6 +502,8 @@ void ProjectSettingsEditor::save() {
for (auto lineEdit : ui->scrollAreaContents_ProjectPaths->findChildren())
projectConfig.setFilePath(lineEdit->objectName(), lineEdit->text());
+ projectConfig.setWarpBehaviors(this->getWarpBehaviorsList());
+
// Save border metatile IDs
projectConfig.setNewMapBorderMetatileIds(this->getBorderMetatileIds(ui->checkBox_EnableCustomBorderSize->isChecked()));