浏览代码

Merge pull request #5909 from Laserlicht/template_editor_object

Template editor - object & townHints selection
Ivan Savenko 3 月之前
父节点
当前提交
7fcb0246fb

+ 1 - 0
lib/mapObjects/CompoundMapObjectID.h

@@ -18,6 +18,7 @@ struct DLL_LINKAGE CompoundMapObjectID
 	si32 primaryID;
 	si32 primaryID;
 	si32 secondaryID;
 	si32 secondaryID;
 
 
+	CompoundMapObjectID() : primaryID(0), secondaryID(0) {}
 	CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {};
 	CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {};
 
 
 	bool operator<(const CompoundMapObjectID& other) const
 	bool operator<(const CompoundMapObjectID& other) const

+ 4 - 0
lib/rmg/ObjectConfig.h

@@ -16,6 +16,10 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 
 class DLL_LINKAGE ObjectConfig
 class DLL_LINKAGE ObjectConfig
 {
 {
+#ifdef ENABLE_TEMPLATE_EDITOR
+	friend class ObjectSelector;
+#endif
+
 public:
 public:
 
 
 	enum class EObjectCategory
 	enum class EObjectCategory

+ 6 - 0
mapeditor/CMakeLists.txt

@@ -61,6 +61,8 @@ if(ENABLE_TEMPLATE_EDITOR)
 		templateeditor/factionselector.cpp
 		templateeditor/factionselector.cpp
 		templateeditor/mineselector.cpp
 		templateeditor/mineselector.cpp
 		templateeditor/treasureselector.cpp
 		templateeditor/treasureselector.cpp
+		templateeditor/objectselector.cpp
+		templateeditor/townhintselector.cpp
 	)
 	)
 endif()
 endif()
 
 
@@ -128,6 +130,8 @@ if(ENABLE_TEMPLATE_EDITOR)
 		templateeditor/factionselector.h
 		templateeditor/factionselector.h
 		templateeditor/mineselector.h
 		templateeditor/mineselector.h
 		templateeditor/treasureselector.h
 		templateeditor/treasureselector.h
+		templateeditor/objectselector.h
+		templateeditor/townhintselector.h
 	)
 	)
 endif()
 endif()
 
 
@@ -173,6 +177,8 @@ if(ENABLE_TEMPLATE_EDITOR)
 		templateeditor/factionselector.ui
 		templateeditor/factionselector.ui
 		templateeditor/mineselector.ui
 		templateeditor/mineselector.ui
 		templateeditor/treasureselector.ui
 		templateeditor/treasureselector.ui
+		templateeditor/objectselector.ui
+		templateeditor/townhintselector.ui
 	)
 	)
 endif()
 endif()
 
 

+ 5 - 5
mapeditor/mainwindow.cpp

@@ -988,7 +988,7 @@ void MainWindow::on_actionLevel_triggered()
 
 
 void MainWindow::on_actionUndo_triggered()
 void MainWindow::on_actionUndo_triggered()
 {
 {
-	QString str("Undo clicked");
+	QString str(tr("Undo clicked"));
 	statusBar()->showMessage(str, 1000);
 	statusBar()->showMessage(str, 1000);
 
 
 	if (controller.map())
 	if (controller.map())
@@ -999,7 +999,7 @@ void MainWindow::on_actionUndo_triggered()
 
 
 void MainWindow::on_actionRedo_triggered()
 void MainWindow::on_actionRedo_triggered()
 {
 {
-	QString str("Redo clicked");
+	QString str(tr("Redo clicked"));
 	displayStatus(str);
 	displayStatus(str);
 
 
 	if (controller.map())
 	if (controller.map())
@@ -1010,7 +1010,7 @@ void MainWindow::on_actionRedo_triggered()
 
 
 void MainWindow::on_actionPass_triggered(bool checked)
 void MainWindow::on_actionPass_triggered(bool checked)
 {
 {
-	QString str("Passability clicked");
+	QString str(tr("Passability clicked"));
 	displayStatus(str);
 	displayStatus(str);
 
 
 	if(controller.map())
 	if(controller.map())
@@ -1023,7 +1023,7 @@ void MainWindow::on_actionPass_triggered(bool checked)
 
 
 void MainWindow::on_actionGrid_triggered(bool checked)
 void MainWindow::on_actionGrid_triggered(bool checked)
 {
 {
-	QString str("Grid clicked");
+	QString str(tr("Grid clicked"));
 	displayStatus(str);
 	displayStatus(str);
 
 
 	if(controller.map())
 	if(controller.map())
@@ -1104,7 +1104,7 @@ void MainWindow::on_filter_textChanged(const QString &arg1)
 
 
 void MainWindow::on_actionFill_triggered()
 void MainWindow::on_actionFill_triggered()
 {
 {
-	QString str("Fill clicked");
+	QString str(tr("Fill clicked"));
 	displayStatus(str);
 	displayStatus(str);
 
 
 	if(!controller.map())
 	if(!controller.map())

+ 321 - 0
mapeditor/templateeditor/objectselector.cpp

@@ -0,0 +1,321 @@
+/*
+ * objectselector.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+
+#include "objectselector.h"
+#include "ui_objectselector.h"
+
+#include "../mainwindow.h"
+#include "../mapcontroller.h"
+
+#include "../../lib/mapping/CMap.h"
+#include "../../lib/rmg/ObjectConfig.h"
+#include "../../lib/GameLibrary.h"
+#include "../../lib/mapObjects/CGObjectInstance.h"
+#include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
+#include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
+
+ObjectSelector::ObjectSelector(ObjectConfig & obj) :
+	ui(new Ui::ObjectSelector),
+	obj(obj),
+	advObjects(getAdventureMapItems())
+{
+	ui->setupUi(this);
+
+	setWindowTitle(tr("Object Selector"));
+	
+	setWindowModality(Qt::ApplicationModal);
+
+	fillBannedObjectCategories();
+	fillBannedObjects();
+	fillCustomObjects();
+
+	show();
+}
+
+QMainWindow* getMainWindow()
+{
+	foreach (QWidget *w, qApp->topLevelWidgets())
+		if (QMainWindow* mainWin = qobject_cast<QMainWindow*>(w))
+			return mainWin;
+	return nullptr;
+}
+
+std::map<CompoundMapObjectID, QString> ObjectSelector::getAdventureMapItems()
+{
+	std::map<CompoundMapObjectID, QString> objects;
+	auto& controller = qobject_cast<MainWindow *>(getMainWindow())->controller;
+
+	auto knownObjects = LIBRARY->objtypeh->knownObjects();
+	for(auto & id : knownObjects)
+	{
+		auto knownSubObjects = LIBRARY->objtypeh->knownSubObjects(id);
+		for(auto & subId : knownSubObjects)
+		{
+			auto objId = CompoundMapObjectID(id, subId);
+			auto factory = LIBRARY->objtypeh->getHandlerFor(id, subId);
+			auto templates = factory->getTemplates();
+			
+			QString name{};
+			try
+			{
+				auto templ = templates.at(0);
+				auto temporaryObj(factory->create(controller.getCallback(), templ));
+				QString translated = QString::fromStdString(temporaryObj->getObjectName().c_str());
+				name = translated;
+			}
+			catch(...) {}
+
+			if(name.isEmpty())
+			{
+				auto subGroupName = QString::fromStdString(LIBRARY->objtypeh->getObjectName(id, subId));
+				name = subGroupName;
+			}
+
+			if(!name.isEmpty())
+				objects[objId] = name;
+		}
+	}
+
+	return objects;
+}
+
+void ObjectSelector::fillBannedObjectCategories()
+{
+	ui->tableWidgetBannedObjectCategories->setColumnCount(2);
+	ui->tableWidgetBannedObjectCategories->setRowCount(obj.bannedObjectCategories.size() + 1);
+	ui->tableWidgetBannedObjectCategories->setHorizontalHeaderLabels({tr("Category"), tr("Action")});
+
+	auto addRow = [this](ObjectConfig::EObjectCategory category, int row){
+		std::map<ObjectConfig::EObjectCategory, QString> values = {
+			{ ObjectConfig::EObjectCategory::OTHER, tr("Other") },
+			{ ObjectConfig::EObjectCategory::ALL, tr("All") },
+			{ ObjectConfig::EObjectCategory::NONE, tr("None") },
+			{ ObjectConfig::EObjectCategory::CREATURE_BANK, tr("Creature bank") },
+			{ ObjectConfig::EObjectCategory::BONUS, tr("Bonus") },
+			{ ObjectConfig::EObjectCategory::DWELLING, tr("Dwelling") },
+			{ ObjectConfig::EObjectCategory::RESOURCE, tr("Resource") },
+			{ ObjectConfig::EObjectCategory::RESOURCE_GENERATOR, tr("Resource generator") },
+			{ ObjectConfig::EObjectCategory::SPELL_SCROLL, tr("Spell scroll") },
+			{ ObjectConfig::EObjectCategory::RANDOM_ARTIFACT, tr("Random artifact") },
+			{ ObjectConfig::EObjectCategory::PANDORAS_BOX, tr("Pandoras box") },
+			{ ObjectConfig::EObjectCategory::QUEST_ARTIFACT, tr("Quest artifact") },
+			{ ObjectConfig::EObjectCategory::SEER_HUT, tr("Seer hut") }
+		};
+		QComboBox *combo = new QComboBox();
+		for(auto & item : values)
+    		combo->addItem(item.second, QVariant(static_cast<int>(item.first)));
+
+		int index = combo->findData(static_cast<int>(category));
+		if (index != -1)
+			combo->setCurrentIndex(index);
+
+		ui->tableWidgetBannedObjectCategories->setCellWidget(row, 0, combo);
+
+		auto deleteButton = new QPushButton(tr("Delete"));
+		ui->tableWidgetBannedObjectCategories->setCellWidget(row, 1, deleteButton);
+		connect(deleteButton, &QPushButton::clicked, this, [this, deleteButton]() {
+			for (int r = 0; r < ui->tableWidgetBannedObjectCategories->rowCount(); ++r) {
+				if (ui->tableWidgetBannedObjectCategories->cellWidget(r, 1) == deleteButton) {
+					ui->tableWidgetBannedObjectCategories->removeRow(r);
+					break;
+				}
+			}
+		});
+	};
+
+	for (int row = 0; row < obj.bannedObjectCategories.size(); ++row)
+		addRow(obj.bannedObjectCategories[row], row);
+
+	auto addButton = new QPushButton("Add");
+	ui->tableWidgetBannedObjectCategories->setCellWidget(ui->tableWidgetBannedObjectCategories->rowCount() - 1, 1, addButton);
+	connect(addButton, &QPushButton::clicked, this, [this, addRow]() {
+		ui->tableWidgetBannedObjectCategories->insertRow(ui->tableWidgetBannedObjectCategories->rowCount() - 1);
+		addRow(ObjectConfig::EObjectCategory::ALL, ui->tableWidgetBannedObjectCategories->rowCount() - 2);
+	});
+
+	ui->tableWidgetBannedObjectCategories->resizeColumnsToContents();
+	ui->tableWidgetBannedObjectCategories->setColumnWidth(0, 300);
+}
+
+void ObjectSelector::getBannedObjectCategories()
+{
+	obj.bannedObjectCategories.clear();
+	for (int row = 0; row < ui->tableWidgetBannedObjectCategories->rowCount() - 1; ++row)
+	{
+		auto val = static_cast<ObjectConfig::EObjectCategory>(static_cast<QComboBox *>(ui->tableWidgetBannedObjectCategories->cellWidget(row, 0))->currentData().toInt());
+		if(vstd::contains(obj.bannedObjectCategories, val))
+			obj.bannedObjectCategories.push_back(val);
+	}
+}
+
+void ObjectSelector::fillBannedObjects()
+{
+	ui->tableWidgetBannedObjects->setColumnCount(2);
+	ui->tableWidgetBannedObjects->setRowCount(obj.bannedObjects.size() + 1);
+	ui->tableWidgetBannedObjects->setHorizontalHeaderLabels({tr("Object"), tr("Action")});
+
+	auto addRow = [this](CompoundMapObjectID obj, int row){
+		QComboBox *combo = new QComboBox();
+		for(auto & item : advObjects)
+    		combo->addItem(item.second, QVariant::fromValue(item.first));
+
+		int index = combo->findData(QVariant::fromValue(obj));
+		if (index != -1)
+			combo->setCurrentIndex(index);
+		
+		combo->setEditable(true);
+		QCompleter* completer = new QCompleter(combo);
+		completer->setModel(combo->model());
+		completer->setCompletionMode(QCompleter::PopupCompletion);
+		completer->setFilterMode(Qt::MatchContains);
+		combo->setCompleter(completer);
+
+		ui->tableWidgetBannedObjects->setCellWidget(row, 0, combo);
+
+		auto deleteButton = new QPushButton(tr("Delete"));
+		ui->tableWidgetBannedObjects->setCellWidget(row, 1, deleteButton);
+		connect(deleteButton, &QPushButton::clicked, this, [this, deleteButton]() {
+			for (int r = 0; r < ui->tableWidgetBannedObjects->rowCount(); ++r) {
+				if (ui->tableWidgetBannedObjects->cellWidget(r, 1) == deleteButton) {
+					ui->tableWidgetBannedObjects->removeRow(r);
+					break;
+				}
+			}
+		});
+	};
+
+	for (int row = 0; row < obj.bannedObjects.size(); ++row)
+		addRow(obj.bannedObjects[row], row);
+
+	auto addButton = new QPushButton("Add");
+	ui->tableWidgetBannedObjects->setCellWidget(ui->tableWidgetBannedObjects->rowCount() - 1, 1, addButton);
+	connect(addButton, &QPushButton::clicked, this, [this, addRow]() {
+		ui->tableWidgetBannedObjects->insertRow(ui->tableWidgetBannedObjects->rowCount() - 1);
+		addRow((*advObjects.begin()).first, ui->tableWidgetBannedObjects->rowCount() - 2);
+	});
+
+	ui->tableWidgetBannedObjects->resizeColumnsToContents();
+	ui->tableWidgetBannedObjects->setColumnWidth(0, 300);
+}
+
+void ObjectSelector::getBannedObjects()
+{
+	obj.bannedObjects.clear();
+	for (int row = 0; row < ui->tableWidgetBannedObjects->rowCount() - 1; ++row)
+	{
+		auto val = static_cast<QComboBox *>(ui->tableWidgetBannedObjects->cellWidget(row, 0))->currentData().value<CompoundMapObjectID>();
+		obj.bannedObjects.push_back(val);
+	}
+}
+
+void ObjectSelector::fillCustomObjects()
+{
+	ui->tableWidgetObjects->setColumnCount(5);
+	ui->tableWidgetObjects->setRowCount(obj.customObjects.size() + 1);
+	ui->tableWidgetObjects->setHorizontalHeaderLabels({tr("Object"), tr("Value"), tr("Probability"), tr("Max per zone"), tr("Action")});
+
+	auto addRow = [this](CompoundMapObjectID obj, ui32 value, ui16 probability, ui32 maxPerZone, int row){
+		QComboBox *combo = new QComboBox();
+		for(auto & item : advObjects)
+    		combo->addItem(item.second, QVariant::fromValue(item.first));
+
+		int index = combo->findData(QVariant::fromValue(obj));
+		if (index != -1)
+			combo->setCurrentIndex(index);
+		
+		combo->setEditable(true);
+		QCompleter* completer = new QCompleter(combo);
+		completer->setModel(combo->model());
+		completer->setCompletionMode(QCompleter::PopupCompletion);
+		completer->setFilterMode(Qt::MatchContains);
+		combo->setCompleter(completer);
+
+		ui->tableWidgetObjects->setCellWidget(row, 0, combo);
+
+		QSpinBox *spinValue = new QSpinBox();
+        spinValue->setRange(0, 10000);
+        spinValue->setValue(value);
+		ui->tableWidgetObjects->setCellWidget(row, 1, spinValue);
+
+		QSpinBox *spinProbability = new QSpinBox();
+        spinProbability->setRange(0, 1000);
+        spinProbability->setValue(probability);
+		ui->tableWidgetObjects->setCellWidget(row, 2, spinProbability);
+
+		QSpinBox *spinMaxPerZone = new QSpinBox();
+        spinMaxPerZone->setRange(0, 100);
+        spinMaxPerZone->setValue(maxPerZone);
+		ui->tableWidgetObjects->setCellWidget(row, 3, spinMaxPerZone);
+
+		auto deleteButton = new QPushButton(tr("Delete"));
+		ui->tableWidgetObjects->setCellWidget(row, 4, deleteButton);
+		connect(deleteButton, &QPushButton::clicked, this, [this, deleteButton]() {
+			for (int r = 0; r < ui->tableWidgetObjects->rowCount(); ++r) {
+				if (ui->tableWidgetObjects->cellWidget(r, 4) == deleteButton) {
+					ui->tableWidgetObjects->removeRow(r);
+					break;
+				}
+			}
+		});
+	};
+
+	for (int row = 0; row < obj.customObjects.size(); ++row)
+		addRow(obj.customObjects[row].getCompoundID(), obj.customObjects[row].value, obj.customObjects[row].probability, obj.customObjects[row].maxPerZone, row);
+
+	auto addButton = new QPushButton("Add");
+	ui->tableWidgetObjects->setCellWidget(ui->tableWidgetObjects->rowCount() - 1, 4, addButton);
+	connect(addButton, &QPushButton::clicked, this, [this, addRow]() {
+		ui->tableWidgetObjects->insertRow(ui->tableWidgetObjects->rowCount() - 1);
+		addRow((*advObjects.begin()).first, 0, 0, 1, ui->tableWidgetObjects->rowCount() - 2);
+	});
+
+	ui->tableWidgetObjects->resizeColumnsToContents();
+	ui->tableWidgetObjects->setColumnWidth(0, 300);
+}
+
+void ObjectSelector::getCustomObjects()
+{
+	obj.customObjects.clear();
+	for (int row = 0; row < ui->tableWidgetObjects->rowCount() - 1; ++row)
+	{
+		auto id = static_cast<QComboBox *>(ui->tableWidgetObjects->cellWidget(row, 0))->currentData().value<CompoundMapObjectID>();
+		auto value = static_cast<QSpinBox *>(ui->tableWidgetObjects->cellWidget(row, 1))->value();
+		auto probability = static_cast<QSpinBox *>(ui->tableWidgetObjects->cellWidget(row, 2))->value();
+		auto maxPerZone = static_cast<QSpinBox *>(ui->tableWidgetObjects->cellWidget(row, 3))->value();
+		auto info = ObjectInfo(id);
+		info.value = value;
+		info.probability = probability;
+		info.maxPerZone = maxPerZone;
+		obj.customObjects.push_back(info);
+	}
+}
+
+void ObjectSelector::showObjectSelector(ObjectConfig & obj)
+{
+	auto * dialog = new ObjectSelector(obj);
+	dialog->setAttribute(Qt::WA_DeleteOnClose);
+	dialog->exec();
+}
+
+void ObjectSelector::on_buttonBoxResult_accepted()
+{
+	getBannedObjectCategories();
+	getBannedObjects();
+	getCustomObjects();
+
+    close();
+}
+
+void ObjectSelector::on_buttonBoxResult_rejected()
+{
+    close();
+}

+ 48 - 0
mapeditor/templateeditor/objectselector.h

@@ -0,0 +1,48 @@
+/*
+ * objectselector.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+#include <QWidget>
+
+#include "../../lib/rmg/CRmgTemplate.h"
+
+Q_DECLARE_METATYPE(CompoundMapObjectID);
+
+namespace Ui {
+class ObjectSelector;
+}
+
+class ObjectSelector : public QDialog
+{
+	Q_OBJECT
+
+public:
+	explicit ObjectSelector(ObjectConfig & obj);
+
+	static void showObjectSelector(ObjectConfig & obj);
+
+private slots:
+	void on_buttonBoxResult_accepted();
+    void on_buttonBoxResult_rejected();
+
+private:
+	Ui::ObjectSelector *ui;
+
+	ObjectConfig & obj;
+	
+	std::map<CompoundMapObjectID, QString> advObjects;
+	std::map<CompoundMapObjectID, QString> getAdventureMapItems();
+
+	void fillBannedObjectCategories();
+	void getBannedObjectCategories();
+	void fillBannedObjects();
+	void getBannedObjects();
+	void fillCustomObjects();
+	void getCustomObjects();
+};

+ 58 - 0
mapeditor/templateeditor/objectselector.ui

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ObjectSelector</class>
+ <widget class="QDialog" name="ObjectSelector">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>750</width>
+    <height>480</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Select Objects</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="labelObjects">
+     <property name="text">
+      <string>Objects</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTableWidget" name="tableWidgetObjects"/>
+   </item>
+   <item>
+    <widget class="QLabel" name="labelBannedObjects">
+     <property name="text">
+      <string>Banned Objects</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTableWidget" name="tableWidgetBannedObjects"/>
+   </item>
+   <item>
+    <widget class="QLabel" name="labelBannedObjectCategories">
+     <property name="text">
+      <string>Banned Object Categories</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTableWidget" name="tableWidgetBannedObjectCategories"/>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBoxResult">
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 4 - 4
mapeditor/templateeditor/templateeditor.cpp

@@ -16,6 +16,8 @@
 #include "factionselector.h"
 #include "factionselector.h"
 #include "mineselector.h"
 #include "mineselector.h"
 #include "treasureselector.h"
 #include "treasureselector.h"
+#include "objectselector.h"
+#include "townhintselector.h"
 #include "GeometryAlgorithm.h"
 #include "GeometryAlgorithm.h"
 
 
 #include "../helper.h"
 #include "../helper.h"
@@ -1032,8 +1034,7 @@ void TemplateEditor::on_pushButtonBannedTowns_clicked()
 
 
 void TemplateEditor::on_pushButtonTownHints_clicked()
 void TemplateEditor::on_pushButtonTownHints_clicked()
 {
 {
-	//TODO: Implement dialog
-	QMessageBox::critical(this, tr("Error"), tr("Not implemented yet!"));
+	TownHintSelector::showTownHintSelector(templates[selectedTemplate]->getZones().at(selectedZone)->townHints);
 }
 }
 
 
 void TemplateEditor::on_pushButtonAllowedMonsters_clicked()
 void TemplateEditor::on_pushButtonAllowedMonsters_clicked()
@@ -1059,6 +1060,5 @@ void TemplateEditor::on_pushButtonMines_clicked()
 
 
 void TemplateEditor::on_pushButtonCustomObjects_clicked()
 void TemplateEditor::on_pushButtonCustomObjects_clicked()
 {
 {
-	//TODO: Implement dialog
-	QMessageBox::critical(this, tr("Error"), tr("Not implemented yet!"));
+	ObjectSelector::showObjectSelector(templates[selectedTemplate]->getZones().at(selectedZone)->objectConfig);
 }
 }

+ 156 - 0
mapeditor/templateeditor/townhintselector.cpp

@@ -0,0 +1,156 @@
+/*
+ * townhintselector.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+
+#include "townhintselector.h"
+#include "ui_townhintselector.h"
+
+enum modes { UNKNOWN, LIKE_ZONE, NOT_LIKE_ZONE, RELATED_TO_ZONE_TERRAIN };
+
+TownHintSelector::TownHintSelector(std::vector<rmg::ZoneOptions::CTownHints> & townHints) :
+	ui(new Ui::TownHintSelector),
+	townHints(townHints)
+{
+	ui->setupUi(this);
+
+	setWindowTitle(tr("Town hint Selector"));
+	
+	setWindowModality(Qt::ApplicationModal);
+
+	ui->tableWidgetTownHints->setColumnCount(3);
+	ui->tableWidgetTownHints->setRowCount(townHints.size() + 1);
+	ui->tableWidgetTownHints->setHorizontalHeaderLabels({tr("Type"), tr("Value"), tr("Action")});
+	
+	std::map<modes, QString> values = {
+		{ LIKE_ZONE, tr("Like Zone") },
+		{ NOT_LIKE_ZONE, tr("Not like zone (comma separated)") },
+		{ RELATED_TO_ZONE_TERRAIN, tr("Related to zone terrain") }
+	};
+
+	auto addRow = [this, values](int mode, std::vector<TRmgTemplateZoneId> zones, int row){
+		QComboBox *combo = new QComboBox();
+		for(auto & item : values)
+    		combo->addItem(item.second, QVariant(static_cast<int>(item.first)));
+
+		int index = combo->findData(static_cast<int>(mode));
+		if (index != -1)
+			combo->setCurrentIndex(index);
+
+		ui->tableWidgetTownHints->setCellWidget(row, 0, combo);
+
+		std::vector<std::string> values(zones.size());
+		std::transform(zones.begin(), zones.end(), values.begin(), [](const int val) { return std::to_string(val); });
+		std::string valuesText = boost::algorithm::join(values, ",");
+
+		QLineEdit *lineEdit = new QLineEdit;
+		QRegularExpression regex("^\\d+(,\\d+)*$");
+		QRegularExpressionValidator *validator = new QRegularExpressionValidator(regex, lineEdit);
+		lineEdit->setValidator(validator);
+		lineEdit->setText(QString::fromStdString(valuesText));
+		ui->tableWidgetTownHints->setCellWidget(row, 1, lineEdit);
+
+		auto deleteButton = new QPushButton(tr("Delete"));
+		ui->tableWidgetTownHints->setCellWidget(row, 2, deleteButton);
+		connect(deleteButton, &QPushButton::clicked, this, [this, deleteButton]() {
+			for (int r = 0; r < ui->tableWidgetTownHints->rowCount(); ++r) {
+				if (ui->tableWidgetTownHints->cellWidget(r, 2) == deleteButton) {
+					ui->tableWidgetTownHints->removeRow(r);
+					break;
+				}
+			}
+		});
+	};
+
+	for (int row = 0; row < townHints.size(); ++row)
+	{
+		int mode = UNKNOWN;
+		std::vector<TRmgTemplateZoneId> zones;
+		if(townHints[row].likeZone != rmg::ZoneOptions::NO_ZONE)
+		{
+			mode = LIKE_ZONE;
+			zones = { townHints[row].likeZone };
+		}
+		else if(!townHints[row].notLikeZone.empty())
+		{
+			mode = NOT_LIKE_ZONE;
+			zones = townHints[row].notLikeZone;
+			
+		}
+		else if(townHints[row].relatedToZoneTerrain != rmg::ZoneOptions::NO_ZONE)
+		{
+			mode = RELATED_TO_ZONE_TERRAIN;
+			zones = { townHints[row].relatedToZoneTerrain };
+			
+		}
+
+		assert(mode != UNKNOWN);
+
+		addRow(mode, zones, row);
+	}
+
+	auto addButton = new QPushButton("Add");
+	ui->tableWidgetTownHints->setCellWidget(ui->tableWidgetTownHints->rowCount() - 1, 2, addButton);
+	connect(addButton, &QPushButton::clicked, this, [this, addRow]() {
+		ui->tableWidgetTownHints->insertRow(ui->tableWidgetTownHints->rowCount() - 1);
+		addRow(LIKE_ZONE, std::vector<TRmgTemplateZoneId>({ 0 }), ui->tableWidgetTownHints->rowCount() - 2);
+	});
+
+	ui->tableWidgetTownHints->resizeColumnsToContents();
+	ui->tableWidgetTownHints->setColumnWidth(0, 300);
+	ui->tableWidgetTownHints->setColumnWidth(1, 150);
+
+	show();
+}
+
+void TownHintSelector::showTownHintSelector(std::vector<rmg::ZoneOptions::CTownHints> & townHints)
+{
+	auto * dialog = new TownHintSelector(townHints);
+	dialog->setAttribute(Qt::WA_DeleteOnClose);
+	dialog->exec();
+}
+
+void TownHintSelector::on_buttonBoxResult_accepted()
+{
+	townHints.clear();
+	for (int row = 0; row < ui->tableWidgetTownHints->rowCount() - 1; ++row) // iterate over all rows except the add button row
+	{
+		auto mode = static_cast<modes>(static_cast<QComboBox *>(ui->tableWidgetTownHints->cellWidget(row, 0))->currentData().toInt());
+		auto text = static_cast<QLineEdit *>(ui->tableWidgetTownHints->cellWidget(row, 1))->text().toStdString();
+		std::vector<std::string> values;
+		boost::split(values, text, boost::is_any_of(","));
+		if (!values.empty() && values.back().empty()) // remove "no number" after last comma; other cases are covered by regex
+    		values.pop_back();
+		std::vector<int> numValues(values.size());
+		std::transform(values.begin(), values.end(), numValues.begin(), [](const std::string& str) { return std::stoi(str); });
+		
+		rmg::ZoneOptions::CTownHints hint;
+		switch (mode)
+		{
+		case LIKE_ZONE:
+			hint.likeZone = numValues.at(0);
+			break;
+		case NOT_LIKE_ZONE:
+			hint.notLikeZone = numValues;
+			break;
+		case RELATED_TO_ZONE_TERRAIN:
+			hint.relatedToZoneTerrain = numValues.at(0);
+			break;
+		}
+		townHints.push_back(hint);
+	}
+
+    close();
+}
+
+void TownHintSelector::on_buttonBoxResult_rejected()
+{
+    close();
+}

+ 36 - 0
mapeditor/templateeditor/townhintselector.h

@@ -0,0 +1,36 @@
+/*
+ * townhintselector.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+#include <QWidget>
+
+#include "../../lib/rmg/CRmgTemplate.h"
+
+namespace Ui {
+class TownHintSelector;
+}
+
+class TownHintSelector : public QDialog
+{
+	Q_OBJECT
+
+public:
+	explicit TownHintSelector(std::vector<rmg::ZoneOptions::CTownHints> & townHints);
+
+	static void showTownHintSelector(std::vector<rmg::ZoneOptions::CTownHints> & townHints);
+
+private slots:
+	void on_buttonBoxResult_accepted();
+    void on_buttonBoxResult_rejected();
+
+private:
+	Ui::TownHintSelector *ui;
+
+	std::vector<rmg::ZoneOptions::CTownHints> & townHints;
+};

+ 38 - 0
mapeditor/templateeditor/townhintselector.ui

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TownHintSelector</class>
+ <widget class="QDialog" name="townHintSelector">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>329</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Select Town hints</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="labelTownHints">
+     <property name="text">
+      <string>Town hints</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTableWidget" name="tableWidgetTownHints"/>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBoxResult">
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 1 - 1
mapeditor/templateeditor/treasureselector.cpp

@@ -47,7 +47,7 @@ TreasureSelector::TreasureSelector(std::vector<CTreasureInfo> & treasures) :
 		spinBoxDensity->setValue(density);
 		spinBoxDensity->setValue(density);
 		ui->tableWidgetTreasures->setCellWidget(row, 2, spinBoxDensity);
 		ui->tableWidgetTreasures->setCellWidget(row, 2, spinBoxDensity);
 
 
-		auto deleteButton = new QPushButton("Delete");
+		auto deleteButton = new QPushButton(tr("Delete"));
 		ui->tableWidgetTreasures->setCellWidget(row, 3, deleteButton);
 		ui->tableWidgetTreasures->setCellWidget(row, 3, deleteButton);
 		connect(deleteButton, &QPushButton::clicked, this, [this, deleteButton]() {
 		connect(deleteButton, &QPushButton::clicked, this, [this, deleteButton]() {
 			for (int r = 0; r < ui->tableWidgetTreasures->rowCount(); ++r) {
 			for (int r = 0; r < ui->tableWidgetTreasures->rowCount(); ++r) {

+ 2 - 2
mapeditor/translation/german.ts

@@ -212,7 +212,7 @@
     <message>
     <message>
         <location filename="../campaigneditor/campaignproperties.ui" line="120"/>
         <location filename="../campaigneditor/campaignproperties.ui" line="120"/>
         <source>Regions Preset</source>
         <source>Regions Preset</source>
-        <translation>Regionan-Voreinstellung</translation>
+        <translation>Regionen-Voreinstellung</translation>
     </message>
     </message>
     <message>
     <message>
         <location filename="../campaigneditor/campaignproperties.ui" line="137"/>
         <location filename="../campaigneditor/campaignproperties.ui" line="137"/>
@@ -3383,7 +3383,7 @@ Fügen Sie sie zu den erforderlichen Mods unter Karte → Allgemeine Einstellung
     <message>
     <message>
         <location filename="../templateeditor/templateeditor.ui" line="1110"/>
         <location filename="../templateeditor/templateeditor.ui" line="1110"/>
         <source>Banned towns</source>
         <source>Banned towns</source>
-        <translation>Verbotene STädte</translation>
+        <translation>Verbotene Städte</translation>
     </message>
     </message>
     <message>
     <message>
         <location filename="../templateeditor/templateeditor.ui" line="1117"/>
         <location filename="../templateeditor/templateeditor.ui" line="1117"/>