Browse Source

Merge pull request #2807 from Nordsoft91/editor-improvements-1.4

Nordsoft91 2 years ago
parent
commit
9998b16050
45 changed files with 1410 additions and 674 deletions
  1. 5 2
      lib/mapObjects/CGHeroInstance.cpp
  2. 3 0
      mapeditor/CMakeLists.txt
  3. 1 1
      mapeditor/StdInc.h
  4. 3 9
      mapeditor/inspector/armywidget.cpp
  5. 1 1
      mapeditor/inspector/armywidget.h
  6. 112 169
      mapeditor/inspector/armywidget.ui
  7. 149 0
      mapeditor/inspector/heroskillswidget.cpp
  8. 59 0
      mapeditor/inspector/heroskillswidget.h
  9. 174 0
      mapeditor/inspector/heroskillswidget.ui
  10. 151 189
      mapeditor/inspector/inspector.cpp
  11. 10 7
      mapeditor/inspector/inspector.h
  12. 3 0
      mapeditor/inspector/messagewidget.ui
  13. 6 0
      mapeditor/inspector/questwidget.ui
  14. 1 6
      mapeditor/inspector/rewardswidget.cpp
  15. 6 0
      mapeditor/inspector/rewardswidget.ui
  16. 0 4
      mapeditor/inspector/townbulidingswidget.cpp
  17. 3 0
      mapeditor/inspector/townbulidingswidget.ui
  18. 2 2
      mapeditor/mainwindow.cpp
  19. 3 0
      mapeditor/mainwindow.ui
  20. 2 1
      mapeditor/mapcontroller.cpp
  21. 10 5
      mapeditor/mapsettings/abstractsettings.cpp
  22. 11 8
      mapeditor/mapsettings/abstractsettings.h
  23. 7 5
      mapeditor/mapsettings/eventsettings.cpp
  24. 2 2
      mapeditor/mapsettings/eventsettings.h
  25. 18 16
      mapeditor/mapsettings/generalsettings.cpp
  26. 2 2
      mapeditor/mapsettings/generalsettings.h
  27. 84 24
      mapeditor/mapsettings/loseconditions.cpp
  28. 7 3
      mapeditor/mapsettings/loseconditions.h
  29. 5 1
      mapeditor/mapsettings/loseconditions.ui
  30. 12 12
      mapeditor/mapsettings/mapsettings.cpp
  31. 7 7
      mapeditor/mapsettings/modsettings.cpp
  32. 2 3
      mapeditor/mapsettings/modsettings.h
  33. 7 5
      mapeditor/mapsettings/rumorsettings.cpp
  34. 2 2
      mapeditor/mapsettings/rumorsettings.h
  35. 162 50
      mapeditor/mapsettings/victoryconditions.cpp
  36. 7 3
      mapeditor/mapsettings/victoryconditions.h
  37. 5 1
      mapeditor/mapsettings/victoryconditions.ui
  38. 25 1
      mapeditor/mapview.cpp
  39. 1 0
      mapeditor/mapview.h
  40. 51 3
      mapeditor/playerparams.cpp
  41. 5 1
      mapeditor/playerparams.h
  42. 171 127
      mapeditor/playerparams.ui
  43. 80 0
      mapeditor/scenelayer.cpp
  44. 32 1
      mapeditor/scenelayer.h
  45. 1 1
      mapeditor/validator.cpp

+ 5 - 2
lib/mapObjects/CGHeroInstance.cpp

@@ -999,8 +999,11 @@ int32_t CGHeroInstance::getSpellCost(const spells::Spell * sp) const
 
 void CGHeroInstance::pushPrimSkill( PrimarySkill which, int val )
 {
-	assert(!hasBonus(Selector::typeSubtype(BonusType::PRIMARY_SKILL, static_cast<int>(which))
-						.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL))));
+	auto sel = Selector::typeSubtype(BonusType::PRIMARY_SKILL, static_cast<int>(which))
+		.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
+	if(hasBonus(sel))
+		removeBonuses(sel);
+		
 	addNewBonus(std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::HERO_BASE_SKILL, val, id.getNum(), static_cast<int>(which)));
 }
 

+ 3 - 0
mapeditor/CMakeLists.txt

@@ -32,6 +32,7 @@ set(editor_SRCS
 		inspector/messagewidget.cpp
 		inspector/rewardswidget.cpp
 		inspector/questwidget.cpp
+		inspector/heroskillswidget.cpp
 		resourceExtractor/ResourceConverter.cpp
 )
 
@@ -68,6 +69,7 @@ set(editor_HEADERS
 		inspector/messagewidget.h
 		inspector/rewardswidget.h
 		inspector/questwidget.h
+		inspector/heroskillswidget.h
 		resourceExtractor/ResourceConverter.h
 )
 
@@ -91,6 +93,7 @@ set(editor_FORMS
 		inspector/messagewidget.ui
 		inspector/rewardswidget.ui
 		inspector/questwidget.ui
+		inspector/heroskillswidget.ui
 )
 
 set(editor_TS

+ 1 - 1
mapeditor/StdInc.h

@@ -2,7 +2,7 @@
 
 #include "../Global.h"
 
-#define VCMI_EDITOR_VERSION "0.1"
+#define VCMI_EDITOR_VERSION "0.2"
 #define VCMI_EDITOR_NAME "VCMI Map Editor"
 
 #include <QtWidgets>

+ 3 - 9
mapeditor/inspector/armywidget.cpp

@@ -31,7 +31,6 @@ ArmyWidget::ArmyWidget(CArmedInstance & a, QWidget *parent) :
 	
 	for(int i = 0; i < TOTAL_SLOTS; ++i)
 	{
-		uiCounts[i]->setText("1");
 		uiSlots[i]->addItem("");
 		uiSlots[i]->setItemData(0, -1);
 		
@@ -64,7 +63,7 @@ void ArmyWidget::obtainData()
 		{
 			auto * creature = army.getCreature(SlotID(i));
 			uiSlots[i]->setCurrentIndex(searchItemIndex(i, creature->getId()));
-			uiCounts[i]->setText(QString::number(army.getStackCount(SlotID(i))));
+			uiCounts[i]->setValue(army.getStackCount(SlotID(i)));
 		}
 	}
 	
@@ -88,7 +87,7 @@ bool ArmyWidget::commitChanges()
 		else
 		{
 			isArmed = true;
-			int amount = uiCounts[i]->text().toInt();
+			int amount = uiCounts[i]->value();
 			if(amount)
 			{
 				army.setCreature(SlotID(i), creId, amount);
@@ -138,12 +137,7 @@ void ArmyDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, cons
 {
 	if(auto * ed = qobject_cast<ArmyWidget *>(editor))
 	{
-		auto isArmed = ed->commitChanges();
-		model->setData(index, "dummy");
-		if(isArmed)
-			model->setData(index, "HAS ARMY");
-		else
-			model->setData(index, "");
+		ed->commitChanges();
 	}
 	else
 	{

+ 1 - 1
mapeditor/inspector/armywidget.h

@@ -35,7 +35,7 @@ private:
 	
 	Ui::ArmyWidget *ui;
 	CArmedInstance & army;
-	std::array<QLineEdit*, TOTAL_SLOTS> uiCounts;
+	std::array<QSpinBox*, TOTAL_SLOTS> uiCounts;
 	std::array<QComboBox*, TOTAL_SLOTS> uiSlots;
 };
 

+ 112 - 169
mapeditor/inspector/armywidget.ui

@@ -2,6 +2,9 @@
 <ui version="4.0">
  <class>ArmyWidget</class>
  <widget class="QDialog" name="ArmyWidget">
+  <property name="windowModality">
+   <enum>Qt::NonModal</enum>
+  </property>
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -19,9 +22,12 @@
   <property name="windowTitle">
    <string>Army settings</string>
   </property>
+  <property name="modal">
+   <bool>true</bool>
+  </property>
   <layout class="QGridLayout" name="gridLayout">
-   <item row="6" column="0">
-    <widget class="QComboBox" name="slot6">
+   <item row="5" column="0">
+    <widget class="QComboBox" name="slot5">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
        <horstretch>0</horstretch>
@@ -40,6 +46,16 @@
      </property>
     </widget>
    </item>
+   <item row="6" column="0">
+    <widget class="QComboBox" name="slot6">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+    </widget>
+   </item>
    <item row="2" column="0">
     <widget class="QComboBox" name="slot2">
      <property name="sizePolicy">
@@ -50,103 +66,46 @@
      </property>
     </widget>
    </item>
-   <item row="6" column="1">
-    <widget class="QLineEdit" name="count6">
+   <item row="4" column="0">
+    <widget class="QComboBox" name="slot4">
      <property name="sizePolicy">
-      <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
-     <property name="minimumSize">
-      <size>
-       <width>30</width>
-       <height>0</height>
-      </size>
-     </property>
-     <property name="maximumSize">
-      <size>
-       <width>50</width>
-       <height>16777215</height>
-      </size>
-     </property>
-     <property name="inputMethodHints">
-      <set>Qt::ImhDigitsOnly</set>
-     </property>
     </widget>
    </item>
-   <item row="2" column="1">
-    <widget class="QLineEdit" name="count2">
+   <item row="0" column="0">
+    <widget class="QComboBox" name="slot0">
      <property name="sizePolicy">
-      <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
-     <property name="minimumSize">
-      <size>
-       <width>30</width>
-       <height>0</height>
-      </size>
-     </property>
-     <property name="maximumSize">
-      <size>
-       <width>50</width>
-       <height>16777215</height>
-      </size>
-     </property>
-     <property name="inputMethodHints">
-      <set>Qt::ImhDigitsOnly</set>
-     </property>
     </widget>
    </item>
-   <item row="1" column="1">
-    <widget class="QLineEdit" name="count1">
+   <item row="1" column="0">
+    <widget class="QComboBox" name="slot1">
      <property name="sizePolicy">
-      <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
-     <property name="minimumSize">
-      <size>
-       <width>30</width>
-       <height>0</height>
-      </size>
-     </property>
-     <property name="maximumSize">
-      <size>
-       <width>50</width>
-       <height>16777215</height>
-      </size>
-     </property>
-     <property name="inputMethodHints">
-      <set>Qt::ImhDigitsOnly</set>
-     </property>
     </widget>
    </item>
-   <item row="5" column="1">
-    <widget class="QLineEdit" name="count5">
+   <item row="8" column="0">
+    <widget class="QRadioButton" name="formationTight">
      <property name="sizePolicy">
-      <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
-     <property name="minimumSize">
-      <size>
-       <width>30</width>
-       <height>0</height>
-      </size>
-     </property>
-     <property name="maximumSize">
-      <size>
-       <width>50</width>
-       <height>16777215</height>
-      </size>
-     </property>
-     <property name="inputMethodHints">
-      <set>Qt::ImhDigitsOnly</set>
+     <property name="text">
+      <string>Tight formation</string>
      </property>
     </widget>
    </item>
@@ -163,131 +122,115 @@
      </property>
     </widget>
    </item>
-   <item row="3" column="1">
-    <widget class="QLineEdit" name="count3">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
+   <item row="0" column="1">
+    <widget class="QSpinBox" name="count0">
+     <property name="readOnly">
+      <bool>false</bool>
      </property>
-     <property name="minimumSize">
-      <size>
-       <width>30</width>
-       <height>0</height>
-      </size>
+     <property name="buttonSymbols">
+      <enum>QAbstractSpinBox::PlusMinus</enum>
      </property>
-     <property name="maximumSize">
-      <size>
-       <width>50</width>
-       <height>16777215</height>
-      </size>
+     <property name="accelerated">
+      <bool>true</bool>
      </property>
-     <property name="inputMethodHints">
-      <set>Qt::ImhDigitsOnly</set>
+     <property name="maximum">
+      <number>9999</number>
      </property>
     </widget>
    </item>
-   <item row="1" column="0">
-    <widget class="QComboBox" name="slot1">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
+   <item row="1" column="1">
+    <widget class="QSpinBox" name="count1">
+     <property name="readOnly">
+      <bool>false</bool>
      </property>
-    </widget>
-   </item>
-   <item row="0" column="0">
-    <widget class="QComboBox" name="slot0">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
+     <property name="buttonSymbols">
+      <enum>QAbstractSpinBox::PlusMinus</enum>
+     </property>
+     <property name="accelerated">
+      <bool>true</bool>
+     </property>
+     <property name="maximum">
+      <number>9999</number>
      </property>
     </widget>
    </item>
-   <item row="4" column="0">
-    <widget class="QComboBox" name="slot4">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
+   <item row="2" column="1">
+    <widget class="QSpinBox" name="count2">
+     <property name="readOnly">
+      <bool>false</bool>
+     </property>
+     <property name="buttonSymbols">
+      <enum>QAbstractSpinBox::PlusMinus</enum>
+     </property>
+     <property name="accelerated">
+      <bool>true</bool>
+     </property>
+     <property name="maximum">
+      <number>9999</number>
      </property>
     </widget>
    </item>
-   <item row="4" column="1">
-    <widget class="QLineEdit" name="count4">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
+   <item row="3" column="1">
+    <widget class="QSpinBox" name="count3">
+     <property name="readOnly">
+      <bool>false</bool>
      </property>
-     <property name="minimumSize">
-      <size>
-       <width>30</width>
-       <height>0</height>
-      </size>
+     <property name="buttonSymbols">
+      <enum>QAbstractSpinBox::PlusMinus</enum>
      </property>
-     <property name="maximumSize">
-      <size>
-       <width>50</width>
-       <height>16777215</height>
-      </size>
+     <property name="accelerated">
+      <bool>true</bool>
      </property>
-     <property name="inputMethodHints">
-      <set>Qt::ImhDigitsOnly</set>
+     <property name="maximum">
+      <number>9999</number>
      </property>
     </widget>
    </item>
-   <item row="5" column="0">
-    <widget class="QComboBox" name="slot5">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
+   <item row="4" column="1">
+    <widget class="QSpinBox" name="count4">
+     <property name="readOnly">
+      <bool>false</bool>
+     </property>
+     <property name="buttonSymbols">
+      <enum>QAbstractSpinBox::PlusMinus</enum>
+     </property>
+     <property name="accelerated">
+      <bool>true</bool>
+     </property>
+     <property name="maximum">
+      <number>9999</number>
      </property>
     </widget>
    </item>
-   <item row="0" column="1">
-    <widget class="QLineEdit" name="count0">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
+   <item row="5" column="1">
+    <widget class="QSpinBox" name="count5">
+     <property name="readOnly">
+      <bool>false</bool>
      </property>
-     <property name="minimumSize">
-      <size>
-       <width>30</width>
-       <height>0</height>
-      </size>
+     <property name="buttonSymbols">
+      <enum>QAbstractSpinBox::PlusMinus</enum>
      </property>
-     <property name="maximumSize">
-      <size>
-       <width>50</width>
-       <height>16777215</height>
-      </size>
+     <property name="accelerated">
+      <bool>true</bool>
      </property>
-     <property name="inputMethodHints">
-      <set>Qt::ImhDigitsOnly</set>
+     <property name="maximum">
+      <number>9999</number>
      </property>
     </widget>
    </item>
-   <item row="8" column="0">
-    <widget class="QRadioButton" name="formationTight">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
+   <item row="6" column="1">
+    <widget class="QSpinBox" name="count6">
+     <property name="readOnly">
+      <bool>false</bool>
      </property>
-     <property name="text">
-      <string>Tight formation</string>
+     <property name="buttonSymbols">
+      <enum>QAbstractSpinBox::PlusMinus</enum>
+     </property>
+     <property name="accelerated">
+      <bool>true</bool>
+     </property>
+     <property name="maximum">
+      <number>9999</number>
      </property>
     </widget>
    </item>

+ 149 - 0
mapeditor/inspector/heroskillswidget.cpp

@@ -0,0 +1,149 @@
+/*
+ * heroskillswidget.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 "heroskillswidget.h"
+#include "ui_heroskillswidget.h"
+#include "../../lib/constants/StringConstants.h"
+#include "../../lib/CSkillHandler.h"
+#include "inspector.h"
+
+static QList<std::pair<QString, QVariant>> LevelIdentifiers
+{
+	{QObject::tr("Beginner"), QVariant::fromValue(1)},
+	{QObject::tr("Advanced"), QVariant::fromValue(2)},
+	{QObject::tr("Expert"), QVariant::fromValue(3)},
+};
+
+HeroSkillsWidget::HeroSkillsWidget(CGHeroInstance & h, QWidget *parent) :
+	QDialog(parent),
+	ui(new Ui::HeroSkillsWidget),
+	hero(h)
+{
+	ui->setupUi(this);
+	
+	ui->labelAttack->setText(QString::fromStdString(NPrimarySkill::names[0]));
+	ui->labelDefence->setText(QString::fromStdString(NPrimarySkill::names[1]));
+	ui->labelPower->setText(QString::fromStdString(NPrimarySkill::names[2]));
+	ui->labelKnowledge->setText(QString::fromStdString(NPrimarySkill::names[3]));
+	
+	auto * delegate = new InspectorDelegate;
+	for(auto s : VLC->skillh->objects)
+		delegate->options.push_back({QString::fromStdString(s->getNameTranslated()), QVariant::fromValue(s->getId().getNum())});
+	ui->skills->setItemDelegateForColumn(0, delegate);
+	
+	delegate = new InspectorDelegate;
+	delegate->options = LevelIdentifiers;
+	ui->skills->setItemDelegateForColumn(1, delegate);
+}
+
+HeroSkillsWidget::~HeroSkillsWidget()
+{
+	delete ui;
+}
+
+void HeroSkillsWidget::on_addButton_clicked()
+{
+	ui->skills->setRowCount(ui->skills->rowCount() + 1);
+}
+
+void HeroSkillsWidget::on_removeButton_clicked()
+{
+	ui->skills->removeRow(ui->skills->currentRow());
+}
+
+void HeroSkillsWidget::on_checkBox_toggled(bool checked)
+{
+	ui->skills->setEnabled(checked);
+	ui->addButton->setEnabled(checked);
+	ui->removeButton->setEnabled(checked);
+}
+
+void HeroSkillsWidget::obtainData()
+{
+	ui->attack->setValue(hero.getPrimSkillLevel(PrimarySkill::ATTACK));
+	ui->defence->setValue(hero.getPrimSkillLevel(PrimarySkill::DEFENSE));
+	ui->power->setValue(hero.getPrimSkillLevel(PrimarySkill::SPELL_POWER));
+	ui->knowledge->setValue(hero.getPrimSkillLevel(PrimarySkill::KNOWLEDGE));
+	
+	if(!hero.secSkills.empty() && hero.secSkills.front().first.getNum() == -1)
+		return;
+	
+	ui->checkBox->setChecked(true);
+	ui->skills->setRowCount(hero.secSkills.size());
+	
+	int i = 0;
+	for(auto & s : hero.secSkills)
+	{
+		auto * itemSkill = new QTableWidgetItem;
+		itemSkill->setText(QString::fromStdString(VLC->skillh->getById(s.first)->getNameTranslated()));
+		itemSkill->setData(Qt::UserRole, QVariant::fromValue(s.first.getNum()));
+		ui->skills->setItem(i, 0, itemSkill);
+		
+		auto * itemLevel = new QTableWidgetItem;
+		itemLevel->setText(LevelIdentifiers[s.second - 1].first);
+		itemLevel->setData(Qt::UserRole, LevelIdentifiers[s.second - 1].second);
+		ui->skills->setItem(i++, 1, itemLevel);
+	}
+}
+
+void HeroSkillsWidget::commitChanges()
+{
+	hero.pushPrimSkill(PrimarySkill::ATTACK, ui->attack->value());
+	hero.pushPrimSkill(PrimarySkill::DEFENSE, ui->defence->value());
+	hero.pushPrimSkill(PrimarySkill::SPELL_POWER, ui->power->value());
+	hero.pushPrimSkill(PrimarySkill::KNOWLEDGE, ui->knowledge->value());
+	
+	hero.secSkills.clear();
+	
+	if(!ui->checkBox->isChecked())
+	{
+		hero.secSkills.push_back(std::make_pair(SecondarySkill(-1), -1));
+		return;
+	}
+	
+	for(int i = 0; i < ui->skills->rowCount(); ++i)
+	{
+		if(ui->skills->item(i, 0) && ui->skills->item(i, 1))
+			hero.secSkills.push_back(std::make_pair(SecondarySkill(ui->skills->item(i, 0)->data(Qt::UserRole).toInt()), ui->skills->item(i, 1)->data(Qt::UserRole).toInt()));
+	}
+}
+
+HeroSkillsDelegate::HeroSkillsDelegate(CGHeroInstance & h): hero(h), QStyledItemDelegate()
+{
+}
+
+QWidget * HeroSkillsDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+	return new HeroSkillsWidget(hero, parent);
+}
+
+void HeroSkillsDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
+{
+	if(auto * ed = qobject_cast<HeroSkillsWidget *>(editor))
+	{
+		ed->obtainData();
+	}
+	else
+	{
+		QStyledItemDelegate::setEditorData(editor, index);
+	}
+}
+
+void HeroSkillsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
+{
+	if(auto * ed = qobject_cast<HeroSkillsWidget *>(editor))
+	{
+		ed->commitChanges();
+	}
+	else
+	{
+		QStyledItemDelegate::setModelData(editor, model, index);
+	}
+}

+ 59 - 0
mapeditor/inspector/heroskillswidget.h

@@ -0,0 +1,59 @@
+/*
+ * heroskillswidget.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 <QDialog>
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+namespace Ui {
+class HeroSkillsWidget;
+}
+
+class HeroSkillsWidget : public QDialog
+{
+	Q_OBJECT
+
+public:
+	explicit HeroSkillsWidget(CGHeroInstance &, QWidget *parent = nullptr);
+	~HeroSkillsWidget();
+	
+	void obtainData();
+	void commitChanges();
+
+private slots:
+	void on_addButton_clicked();
+
+	void on_removeButton_clicked();
+
+	void on_checkBox_toggled(bool checked);
+
+private:
+	Ui::HeroSkillsWidget *ui;
+	
+	CGHeroInstance & hero;
+	
+	std::set<int> occupiedSkills;
+};
+
+class HeroSkillsDelegate : public QStyledItemDelegate
+{
+	Q_OBJECT
+public:
+	using QStyledItemDelegate::QStyledItemDelegate;
+	
+	HeroSkillsDelegate(CGHeroInstance &);
+	
+	QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+	void setEditorData(QWidget *editor, const QModelIndex &index) const override;
+	void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
+	
+private:
+	CGHeroInstance & hero;
+};

+ 174 - 0
mapeditor/inspector/heroskillswidget.ui

@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>HeroSkillsWidget</class>
+ <widget class="QDialog" name="HeroSkillsWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>464</width>
+    <height>301</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Hero skills</string>
+  </property>
+  <property name="modal">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <property name="spacing">
+    <number>4</number>
+   </property>
+   <item>
+    <layout class="QHBoxLayout" name="statsLayout">
+     <item>
+      <widget class="QLabel" name="labelAttack">
+       <property name="text">
+        <string>TextLabel</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QSpinBox" name="attack"/>
+     </item>
+     <item>
+      <widget class="QLabel" name="labelDefence">
+       <property name="text">
+        <string>TextLabel</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QSpinBox" name="defence"/>
+     </item>
+     <item>
+      <widget class="QLabel" name="labelPower">
+       <property name="text">
+        <string>TextLabel</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QSpinBox" name="power"/>
+     </item>
+     <item>
+      <widget class="QLabel" name="labelKnowledge">
+       <property name="text">
+        <string>TextLabel</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QSpinBox" name="knowledge"/>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <property name="topMargin">
+      <number>0</number>
+     </property>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="addButton">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>90</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="text">
+        <string>Add</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="removeButton">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>90</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="text">
+        <string>Remove</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QTableWidget" name="skills">
+     <property name="enabled">
+      <bool>false</bool>
+     </property>
+     <property name="horizontalScrollBarPolicy">
+      <enum>Qt::ScrollBarAlwaysOff</enum>
+     </property>
+     <property name="sizeAdjustPolicy">
+      <enum>QAbstractScrollArea::AdjustToContents</enum>
+     </property>
+     <property name="selectionMode">
+      <enum>QAbstractItemView::SingleSelection</enum>
+     </property>
+     <property name="selectionBehavior">
+      <enum>QAbstractItemView::SelectRows</enum>
+     </property>
+     <attribute name="horizontalHeaderVisible">
+      <bool>false</bool>
+     </attribute>
+     <attribute name="horizontalHeaderDefaultSectionSize">
+      <number>120</number>
+     </attribute>
+     <attribute name="horizontalHeaderStretchLastSection">
+      <bool>true</bool>
+     </attribute>
+     <attribute name="verticalHeaderVisible">
+      <bool>false</bool>
+     </attribute>
+     <attribute name="verticalHeaderDefaultSectionSize">
+      <number>21</number>
+     </attribute>
+     <column>
+      <property name="text">
+       <string>Skill</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Level</string>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item>
+    <widget class="QCheckBox" name="checkBox">
+     <property name="text">
+      <string>Customize skills</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 151 - 189
mapeditor/inspector/inspector.cpp

@@ -18,12 +18,37 @@
 #include "../lib/mapObjectConstructors/CObjectClassesHandler.h"
 #include "../lib/mapObjects/ObjectTemplate.h"
 #include "../lib/mapping/CMap.h"
+#include "../lib/constants/StringConstants.h"
 
 #include "townbulidingswidget.h"
 #include "armywidget.h"
 #include "messagewidget.h"
 #include "rewardswidget.h"
 #include "questwidget.h"
+#include "heroskillswidget.h"
+
+static QList<std::pair<QString, QVariant>> MissionIdentifiers
+{
+	{QObject::tr("None"), QVariant::fromValue(int(CQuest::Emission::MISSION_NONE))},
+	{QObject::tr("Reach level"), QVariant::fromValue(int(CQuest::Emission::MISSION_LEVEL))},
+	{QObject::tr("Stats"), QVariant::fromValue(int(CQuest::Emission::MISSION_PRIMARY_STAT))},
+	{QObject::tr("Kill hero"), QVariant::fromValue(int(CQuest::Emission::MISSION_KILL_HERO))},
+	{QObject::tr("Kill monster"), QVariant::fromValue(int(CQuest::Emission::MISSION_KILL_CREATURE))},
+	{QObject::tr("Artifact"), QVariant::fromValue(int(CQuest::Emission::MISSION_ART))},
+	{QObject::tr("Army"), QVariant::fromValue(int(CQuest::Emission::MISSION_ARMY))},
+	{QObject::tr("Resources"), QVariant::fromValue(int(CQuest::Emission::MISSION_RESOURCES))},
+	{QObject::tr("Hero"), QVariant::fromValue(int(CQuest::Emission::MISSION_HERO))},
+	{QObject::tr("Player"), QVariant::fromValue(int(CQuest::Emission::MISSION_PLAYER))},
+};
+
+static QList<std::pair<QString, QVariant>> CharacterIdentifiers
+{
+	{QObject::tr("Compliant"), QVariant::fromValue(int(CGCreature::Character::COMPLIANT))},
+	{QObject::tr("Friendly"), QVariant::fromValue(int(CGCreature::Character::FRIENDLY))},
+	{QObject::tr("Agressive"), QVariant::fromValue(int(CGCreature::Character::AGRESSIVE))},
+	{QObject::tr("Hostile"), QVariant::fromValue(int(CGCreature::Character::HOSTILE))},
+	{QObject::tr("Savage"), QVariant::fromValue(int(CGCreature::Character::SAVAGE))},
+};
 
 //===============IMPLEMENT OBJECT INITIALIZATION FUNCTIONS================
 Initializer::Initializer(CGObjectInstance * o, const PlayerColor & pl) : defaultPlayer(pl)
@@ -47,14 +72,6 @@ Initializer::Initializer(CGObjectInstance * o, const PlayerColor & pl) : default
 	//INIT_OBJ_TYPE(CGSeerHut);
 }
 
-bool stringToBool(const QString & s)
-{
-	if(s == "TRUE")
-		return true;
-	//if(s == "FALSE")
-	return false;
-}
-
 void Initializer::initialize(CArmedInstance * o)
 {
 	if(!o) return;
@@ -135,12 +152,14 @@ void Initializer::initialize(CGHeroInstance * o)
 		}
 	}
 	
-	if(!o->type)
-		o->type = VLC->heroh->objects.at(o->subID);
-	
-	o->gender = o->type->gender;
-	o->portrait = o->type->imageIndex;
-	o->randomizeArmy(o->type->heroClass->faction);
+	if(o->type)
+	{
+		//	o->type = VLC->heroh->objects.at(o->subID);
+		
+		o->gender = o->type->gender;
+		o->portrait = o->type->imageIndex;
+		o->randomizeArmy(o->type->heroClass->faction);
+	}
 }
 
 void Initializer::initialize(CGTownInstance * o)
@@ -234,7 +253,7 @@ void Inspector::updateProperties(CGGarrison * o)
 	if(!o) return;
 	
 	addProperty("Owner", o->tempOwner, false);
-	addProperty("Removable units", o->removableUnits, InspectorDelegate::boolDelegate(), false);
+	addProperty("Removable units", o->removableUnits, false);
 }
 
 void Inspector::updateProperties(CGShipyard * o)
@@ -250,17 +269,21 @@ void Inspector::updateProperties(CGHeroInstance * o)
 	
 	addProperty("Owner", o->tempOwner, o->ID == Obj::PRISON); //field is not editable for prison
 	addProperty<int>("Experience", o->exp, false);
-	addProperty("Hero class", o->type->heroClass->getNameTranslated(), true);
+	addProperty("Hero class", o->type ? o->type->heroClass->getNameTranslated() : "", true);
 	
-	{ //Sex
+	{ //Gender
 		auto * delegate = new InspectorDelegate;
-		delegate->options << "MALE" << "FEMALE";
+		delegate->options = {{"MALE", QVariant::fromValue(int(EHeroGender::MALE))}, {"FEMALE", QVariant::fromValue(int(EHeroGender::FEMALE))}};
 		addProperty<std::string>("Gender", (o->gender == EHeroGender::FEMALE ? "FEMALE" : "MALE"), delegate , false);
 	}
 	addProperty("Name", o->nameCustom, false);
 	addProperty("Biography", o->biographyCustom, new MessageDelegate, false);
 	addProperty("Portrait", o->portrait, false);
 	
+	auto * delegate = new HeroSkillsDelegate(*o);
+	addProperty("Skills", PropertyEditorPlaceholder(), delegate, false);
+	
+	if(o->type)
 	{ //Hero type
 		auto * delegate = new InspectorDelegate;
 		for(int i = 0; i < VLC->heroh->objects.size(); ++i)
@@ -268,7 +291,9 @@ void Inspector::updateProperties(CGHeroInstance * o)
 			if(map->allowedHeroes.at(i))
 			{
 				if(o->ID == Obj::PRISON || (o->type && VLC->heroh->objects[i]->heroClass->getIndex() == o->type->heroClass->getIndex()))
-					delegate->options << QObject::tr(VLC->heroh->objects[i]->getNameTranslated().c_str());
+				{
+					delegate->options.push_back({QObject::tr(VLC->heroh->objects[i]->getNameTranslated().c_str()), QVariant::fromValue(VLC->heroh->objects[i]->getId().getNum())});
+				}
 			}
 		}
 		addProperty("Hero type", o->type->getNameTranslated(), delegate, false);
@@ -301,7 +326,7 @@ void Inspector::updateProperties(CGArtifact * o)
 			for(auto spell : VLC->spellh->objects)
 			{
 				if(map->allowedSpells.at(spell->id))
-					delegate->options << QObject::tr(spell->getNameTranslated().c_str());
+					delegate->options.push_back({QObject::tr(spell->getNameTranslated().c_str()), QVariant::fromValue(int(spell->getId()))});
 			}
 			addProperty("Spell", VLC->spellh->getById(spellId)->getNameTranslated(), delegate, false);
 		}
@@ -339,11 +364,11 @@ void Inspector::updateProperties(CGCreature * o)
 	addProperty("Message", o->message, false);
 	{ //Character
 		auto * delegate = new InspectorDelegate;
-		delegate->options << "COMPLIANT" << "FRIENDLY" << "AGRESSIVE" << "HOSTILE" << "SAVAGE";
+		delegate->options = CharacterIdentifiers;
 		addProperty<CGCreature::Character>("Character", (CGCreature::Character)o->character, delegate, false);
 	}
-	addProperty("Never flees", o->neverFlees, InspectorDelegate::boolDelegate(), false);
-	addProperty("Not growing", o->notGrowingTeam, InspectorDelegate::boolDelegate(), false);
+	addProperty("Never flees", o->neverFlees, false);
+	addProperty("Not growing", o->notGrowingTeam, false);
 	addProperty("Artifact reward", o->gainedArtifact); //TODO: implement in setProperty
 	addProperty("Army", PropertyEditorPlaceholder(), true);
 	addProperty("Amount", o->stacks[SlotID(0)]->count, false);
@@ -364,9 +389,9 @@ void Inspector::updateProperties(CGEvent * o)
 {
 	if(!o) return;
 	
-	addProperty("Remove after", o->removeAfterVisit, InspectorDelegate::boolDelegate(), false);
-	addProperty("Human trigger", o->humanActivate, InspectorDelegate::boolDelegate(), false);
-	addProperty("Cpu trigger", o->computerActivate, InspectorDelegate::boolDelegate(), false);
+	addProperty("Remove after", o->removeAfterVisit, false);
+	addProperty("Human trigger", o->humanActivate, false);
+	addProperty("Cpu trigger", o->computerActivate, false);
 	//ui8 availableFor; //players whom this event is available for
 }
 
@@ -376,7 +401,7 @@ void Inspector::updateProperties(CGSeerHut * o)
 
 	{ //Mission type
 		auto * delegate = new InspectorDelegate;
-		delegate->options << "Reach level" << "Stats" << "Kill hero" << "Kill creature" << "Artifact" << "Army" << "Resources" << "Hero" << "Player";
+		delegate->options = MissionIdentifiers;
 		addProperty<CQuest::Emission>("Mission type", o->quest->missionType, delegate, false);
 	}
 	
@@ -415,10 +440,10 @@ void Inspector::updateProperties()
 	}
 	
 	auto * delegate = new InspectorDelegate();
-	delegate->options << "NEUTRAL";
+	delegate->options.push_back({QObject::tr("neutral"), QVariant::fromValue(PlayerColor::NEUTRAL.getNum())});
 	for(int p = 0; p < map->players.size(); ++p)
 		if(map->players[p].canAnyonePlay())
-			delegate->options << QString("PLAYER %1").arg(p);
+			delegate->options.push_back({QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[p]), QVariant::fromValue(PlayerColor(p).getNum())});
 	addProperty("Owner", obj->tempOwner, delegate, true);
 	
 	UPDATE_OBJ_PROPERTIES(CArmedInstance);
@@ -441,20 +466,30 @@ void Inspector::updateProperties()
 }
 
 //===============IMPLEMENT PROPERTY UPDATE================================
+void Inspector::setProperty(const QString & key, const QTableWidgetItem * item)
+{
+	if(!item->data(Qt::UserRole).isNull())
+	{
+		setProperty(key, item->data(Qt::UserRole));
+		return;
+	}
+	
+	if(item->flags() & Qt::ItemIsUserCheckable)
+	{
+		setProperty(key, QVariant::fromValue(item->checkState() == Qt::Checked));
+		return;
+	}
+	
+	setProperty(key, item->text());
+}
+
 void Inspector::setProperty(const QString & key, const QVariant & value)
 {
 	if(!obj)
 		return;
 	
 	if(key == "Owner")
-	{
-		PlayerColor owner(value.toString().mid(6).toInt()); //receiving PLAYER N, N has index 6
-		if(value == "NEUTRAL")
-			owner = PlayerColor::NEUTRAL;
-		if(value == "UNFLAGGABLE")
-			owner = PlayerColor::UNFLAGGABLE;
-		obj->tempOwner = owner;
-	}
+		obj->tempOwner = PlayerColor(value.toInt());
 	
 	SET_PROPERTIES(CArmedInstance);
 	SET_PROPERTIES(CGTownInstance);
@@ -496,13 +531,13 @@ void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & v
 	if(!o) return;
 	
 	if(key == "Remove after")
-		o->removeAfterVisit = stringToBool(value.toString());
+		o->removeAfterVisit = value.toBool();
 	
 	if(key == "Human trigger")
-		o->humanActivate = stringToBool(value.toString());
+		o->humanActivate = value.toBool();
 	
 	if(key == "Cpu trigger")
-		o->computerActivate = stringToBool(value.toString());
+		o->computerActivate = value.toBool();
 }
 
 void Inspector::setProperty(CGTownInstance * o, const QString & key, const QVariant & value)
@@ -538,14 +573,7 @@ void Inspector::setProperty(CGArtifact * o, const QString & key, const QVariant
 	
 	if(o->storedArtifact && key == "Spell")
 	{
-		for(auto spell : VLC->spellh->objects)
-		{
-			if(spell->getNameTranslated() == value.toString().toStdString())
-			{
-				o->storedArtifact = ArtifactUtils::createScroll(spell->getId());
-				break;
-			}
-		}
+		o->storedArtifact = ArtifactUtils::createScroll(SpellID(value.toInt()));
 	}
 }
 
@@ -559,7 +587,7 @@ void Inspector::setProperty(CGGarrison * o, const QString & key, const QVariant
 	if(!o) return;
 	
 	if(key == "Removable units")
-		o->removableUnits = stringToBool(value.toString());
+		o->removableUnits = value.toBool();
 }
 
 void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVariant & value)
@@ -567,13 +595,13 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari
 	if(!o) return;
 	
 	if(key == "Gender")
-		o->gender = value.toString() == "MALE" ? EHeroGender::MALE : EHeroGender::FEMALE;
+		o->gender = EHeroGender(value.toInt());
 	
 	if(key == "Name")
 		o->nameCustom = value.toString().toStdString();
 	
 	if(key == "Experience")
-		o->exp = value.toInt();
+		o->exp = value.toString().toInt();
 	
 	if(key == "Hero type")
 	{
@@ -609,23 +637,11 @@ void Inspector::setProperty(CGCreature * o, const QString & key, const QVariant
 	if(key == "Message")
 		o->message = value.toString().toStdString();
 	if(key == "Character")
-	{
-		//COMPLIANT = 0, FRIENDLY = 1, AGRESSIVE = 2, HOSTILE = 3, SAVAGE = 4
-		if(value == "COMPLIANT")
-			o->character = CGCreature::Character::COMPLIANT;
-		if(value == "FRIENDLY")
-			o->character = CGCreature::Character::FRIENDLY;
-		if(value == "AGRESSIVE")
-			o->character = CGCreature::Character::AGRESSIVE;
-		if(value == "HOSTILE")
-			o->character = CGCreature::Character::HOSTILE;
-		if(value == "SAVAGE")
-			o->character = CGCreature::Character::SAVAGE;
-	}
+		o->character = CGCreature::Character(value.toInt());
 	if(key == "Never flees")
-		o->neverFlees = stringToBool(value.toString());
+		o->neverFlees = value.toBool();
 	if(key == "Not growing")
-		o->notGrowingTeam = stringToBool(value.toString());
+		o->notGrowingTeam = value.toBool();
 	if(key == "Amount")
 		o->stacks[SlotID(0)]->count = value.toString().toInt();
 }
@@ -635,27 +651,7 @@ void Inspector::setProperty(CGSeerHut * o, const QString & key, const QVariant &
 	if(!o) return;
 	
 	if(key == "Mission type")
-	{
-		if(value == "Reach level")
-			o->quest->missionType = CQuest::Emission::MISSION_LEVEL;
-		if(value == "Stats")
-			o->quest->missionType = CQuest::Emission::MISSION_PRIMARY_STAT;
-		if(value == "Kill hero")
-			o->quest->missionType = CQuest::Emission::MISSION_KILL_HERO;
-		if(value == "Kill creature")
-			o->quest->missionType = CQuest::Emission::MISSION_KILL_CREATURE;
-		if(value == "Artifact")
-			o->quest->missionType = CQuest::Emission::MISSION_ART;
-		if(value == "Army")
-			o->quest->missionType = CQuest::Emission::MISSION_ARMY;
-		if(value == "Resources")
-			o->quest->missionType = CQuest::Emission::MISSION_RESOURCES;
-		if(value == "Hero")
-			o->quest->missionType = CQuest::Emission::MISSION_HERO;
-		if(value == "Player")
-			o->quest->missionType = CQuest::Emission::MISSION_PLAYER;
-	}
-	
+		o->quest->missionType = CQuest::Emission(value.toInt());
 	if(key == "First visit text")
 		o->quest->firstVisitText = value.toString().toStdString();
 	if(key == "Next visit text")
@@ -668,29 +664,40 @@ void Inspector::setProperty(CGSeerHut * o, const QString & key, const QVariant &
 //===============IMPLEMENT PROPERTY VALUE TYPE============================
 QTableWidgetItem * Inspector::addProperty(CGObjectInstance * value)
 {
-	return new QTableWidgetItem(QString::number(data_cast<CGObjectInstance>(value)));
+	auto * item = new QTableWidgetItem(QString::number(data_cast<CGObjectInstance>(value)));
+	item->setFlags(Qt::NoItemFlags);
+	return item;
 }
 
 QTableWidgetItem * Inspector::addProperty(Inspector::PropertyEditorPlaceholder value)
 {
-	auto item = new QTableWidgetItem("");
-	item->setData(Qt::UserRole, QString("PropertyEditor"));
+	auto item = new QTableWidgetItem("...");
+	item->setFlags(Qt::NoItemFlags);
 	return item;
 }
 
 QTableWidgetItem * Inspector::addProperty(unsigned int value)
 {
-	return new QTableWidgetItem(QString::number(value));
+	auto * item = new QTableWidgetItem(QString::number(value));
+	item->setFlags(Qt::NoItemFlags);
+	//item->setData(Qt::UserRole, QVariant::fromValue(value));
+	return item;
 }
 
 QTableWidgetItem * Inspector::addProperty(int value)
 {
-	return new QTableWidgetItem(QString::number(value));
+	auto * item = new QTableWidgetItem(QString::number(value));
+	item->setFlags(Qt::NoItemFlags);
+	//item->setData(Qt::UserRole, QVariant::fromValue(value));
+	return item;
 }
 
 QTableWidgetItem * Inspector::addProperty(bool value)
 {
-	return new QTableWidgetItem(value ? "TRUE" : "FALSE");
+	auto item = new QTableWidgetItem;
+	item->setFlags(Qt::ItemIsUserCheckable);
+	item->setCheckState(value ? Qt::Checked : Qt::Unchecked);
+	return item;
 }
 
 QTableWidgetItem * Inspector::addProperty(const std::string & value)
@@ -700,118 +707,75 @@ QTableWidgetItem * Inspector::addProperty(const std::string & value)
 
 QTableWidgetItem * Inspector::addProperty(const QString & value)
 {
-	return new QTableWidgetItem(value);
+	auto * item = new QTableWidgetItem(value);
+	item->setFlags(Qt::NoItemFlags);
+	return item;
 }
 
 QTableWidgetItem * Inspector::addProperty(const int3 & value)
 {
-	return new QTableWidgetItem(QString("(%1, %2, %3)").arg(value.x, value.y, value.z));
+	auto * item = new QTableWidgetItem(QString("(%1, %2, %3)").arg(value.x, value.y, value.z));
+	item->setFlags(Qt::NoItemFlags);
+	return item;
 }
 
 QTableWidgetItem * Inspector::addProperty(const PlayerColor & value)
 {
-	auto str = QString("PLAYER %1").arg(value.getNum());
+	auto str = QObject::tr("UNFLAGGABLE");
 	if(value == PlayerColor::NEUTRAL)
-		str = "NEUTRAL";
-	if(value == PlayerColor::UNFLAGGABLE)
-		str = "UNFLAGGABLE";
-	return new QTableWidgetItem(str);
+		str = QObject::tr("neutral");
+	
+	if(value.isValidPlayer())
+		str = QString::fromStdString(GameConstants::PLAYER_COLOR_NAMES[value]);
+	
+	auto * item = new QTableWidgetItem(str);
+	item->setFlags(Qt::NoItemFlags);
+	item->setData(Qt::UserRole, QVariant::fromValue(value.getNum()));
+	return item;
 }
 
 QTableWidgetItem * Inspector::addProperty(const GameResID & value)
 {
-	QString str;
-	switch (value.toEnum()) {
-		case EGameResID::WOOD:
-			str = "WOOD";
-			break;
-		case EGameResID::ORE:
-			str = "ORE";
-			break;
-		case EGameResID::SULFUR:
-			str = "SULFUR";
-			break;
-		case EGameResID::GEMS:
-			str = "GEMS";
-			break;
-		case EGameResID::MERCURY:
-			str = "MERCURY";
-			break;
-		case EGameResID::CRYSTAL:
-			str = "CRYSTAL";
-			break;
-		case EGameResID::GOLD:
-			str = "GOLD";
-			break;
-		default:
-			break;
-	}
-	return new QTableWidgetItem(str);
+	auto * item = new QTableWidgetItem(QString::fromStdString(GameConstants::RESOURCE_NAMES[value.toEnum()]));
+	item->setFlags(Qt::NoItemFlags);
+	item->setData(Qt::UserRole, QVariant::fromValue(value.getNum()));
+	return item;
 }
 
 QTableWidgetItem * Inspector::addProperty(CGCreature::Character value)
 {
-	QString str;
-	switch (value) {
-		case CGCreature::Character::COMPLIANT:
-			str = "COMPLIANT";
-			break;
-		case CGCreature::Character::FRIENDLY:
-			str = "FRIENDLY";
-			break;
-		case CGCreature::Character::AGRESSIVE:
-			str = "AGRESSIVE";
-			break;
-		case CGCreature::Character::HOSTILE:
-			str = "HOSTILE";
-			break;
-		case CGCreature::Character::SAVAGE:
-			str = "SAVAGE";
-			break;
-		default:
+	auto * item = new QTableWidgetItem;
+	item->setFlags(Qt::NoItemFlags);
+	item->setData(Qt::UserRole, QVariant::fromValue(int(value)));
+	
+	for(auto & i : CharacterIdentifiers)
+	{
+		if(i.second.toInt() == value)
+		{
+			item->setText(i.first);
 			break;
+		}
 	}
-	return new QTableWidgetItem(str);
+	
+	return item;
 }
 
 QTableWidgetItem * Inspector::addProperty(CQuest::Emission value)
 {
-	QString str;
-	switch (value) {
-		case CQuest::Emission::MISSION_LEVEL:
-			str = "Reach level";
-			break;
-		case CQuest::Emission::MISSION_PRIMARY_STAT:
-			str = "Stats";
-			break;
-		case CQuest::Emission::MISSION_KILL_HERO:
-			str = "Kill hero";
-			break;
-		case CQuest::Emission::MISSION_KILL_CREATURE:
-			str = "Kill creature";
-			break;
-		case CQuest::Emission::MISSION_ART:
-			str = "Artifact";
-			break;
-		case CQuest::Emission::MISSION_ARMY:
-			str = "Army";
-			break;
-		case CQuest::Emission::MISSION_RESOURCES:
-			str = "Resources";
-			break;
-		case CQuest::Emission::MISSION_HERO:
-			str = "Hero";
-			break;
-		case CQuest::Emission::MISSION_PLAYER:
-			str = "Player";
-			break;
-		case CQuest::Emission::MISSION_KEYMASTER:
-			str = "Key master";
-			break;
-		default:
+	auto * item = new QTableWidgetItem;
+	item->setFlags(Qt::NoItemFlags);
+	item->setData(Qt::UserRole, QVariant::fromValue(int(value)));
+	
+	for(auto & i : MissionIdentifiers)
+	{
+		if(i.second.toInt() == value)
+		{
+			item->setText(i.first);
 			break;
+		}
 	}
-	return new QTableWidgetItem(str);
+	
+	return item;
 }
 
 //========================================================================
@@ -824,13 +788,6 @@ Inspector::Inspector(CMap * m, CGObjectInstance * o, QTableWidget * t): obj(o),
  * Delegates
  */
 
-InspectorDelegate * InspectorDelegate::boolDelegate()
-{
-	auto * d = new InspectorDelegate;
-	d->options << "TRUE" << "FALSE";
-	return d;
-}
-
 QWidget * InspectorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
 {
 	return new QComboBox(parent);
@@ -840,7 +797,11 @@ void InspectorDelegate::setEditorData(QWidget *editor, const QModelIndex &index)
 {
 	if(QComboBox *ed = qobject_cast<QComboBox *>(editor))
 	{
-		ed->addItems(options);
+		for(auto & i : options)
+		{
+			ed->addItem(i.first);
+			ed->setItemData(ed->count() - 1, i.second);
+		}
 	}
 	else
 	{
@@ -855,7 +816,8 @@ void InspectorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
 		if(!options.isEmpty())
 		{
 			QMap<int, QVariant> data;
-			data[0] = options[ed->currentIndex()];
+			data[Qt::DisplayRole] = options[ed->currentIndex()].first;
+			data[Qt::UserRole] = options[ed->currentIndex()].second;
 			model->setItemData(index, data);
 		}
 	}

+ 10 - 7
mapeditor/inspector/inspector.h

@@ -96,6 +96,8 @@ protected:
 public:
 	Inspector(CMap *, CGObjectInstance *, QTableWidget *);
 
+	void setProperty(const QString & key, const QTableWidgetItem * item);
+	
 	void setProperty(const QString & key, const QVariant & value);
 
 	void updateProperties();
@@ -106,8 +108,10 @@ protected:
 	void addProperty(const QString & key, const T & value, QAbstractItemDelegate * delegate, bool restricted)
 	{
 		auto * itemValue = addProperty(value);
-		if(restricted)
-			itemValue->setFlags(Qt::NoItemFlags);
+		if(!restricted)
+			itemValue->setFlags(itemValue->flags() | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+		if(!(itemValue->flags() & Qt::ItemIsUserCheckable))
+			itemValue->setFlags(itemValue->flags() | Qt::ItemIsEditable);
 		
 		QTableWidgetItem * itemKey = nullptr;
 		if(keyItems.contains(key))
@@ -120,15 +124,16 @@ protected:
 		else
 		{
 			itemKey = new QTableWidgetItem(key);
-			itemKey->setFlags(Qt::NoItemFlags);
 			keyItems[key] = itemKey;
 			
 			table->setRowCount(row + 1);
 			table->setItem(row, 0, itemKey);
 			table->setItem(row, 1, itemValue);
-			table->setItemDelegateForRow(row, delegate);
+			if(delegate)
+				table->setItemDelegateForRow(row, delegate);
 			++row;
 		}
+		itemKey->setFlags(restricted ? Qt::NoItemFlags : Qt::ItemIsEnabled);
 	}
 	
 	template<class T>
@@ -152,14 +157,12 @@ class InspectorDelegate : public QStyledItemDelegate
 {
 	Q_OBJECT
 public:
-	static InspectorDelegate * boolDelegate();
-	
 	using QStyledItemDelegate::QStyledItemDelegate;
 
 	QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
 	void setEditorData(QWidget *editor, const QModelIndex &index) const override;
 	void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
 	
-	QStringList options;
+	QList<std::pair<QString, QVariant>> options;
 };
 

+ 3 - 0
mapeditor/inspector/messagewidget.ui

@@ -2,6 +2,9 @@
 <ui version="4.0">
  <class>MessageWidget</class>
  <widget class="QDialog" name="MessageWidget">
+  <property name="windowModality">
+   <enum>Qt::NonModal</enum>
+  </property>
   <property name="geometry">
    <rect>
     <x>0</x>

+ 6 - 0
mapeditor/inspector/questwidget.ui

@@ -2,6 +2,9 @@
 <ui version="4.0">
  <class>QuestWidget</class>
  <widget class="QDialog" name="QuestWidget">
+  <property name="windowModality">
+   <enum>Qt::NonModal</enum>
+  </property>
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -13,6 +16,9 @@
   <property name="windowTitle">
    <string>Mission goal</string>
   </property>
+  <property name="modal">
+   <bool>true</bool>
+  </property>
   <layout class="QHBoxLayout" name="horizontalLayout">
    <item>
     <widget class="QComboBox" name="targetId">

+ 1 - 6
mapeditor/inspector/rewardswidget.cpp

@@ -396,12 +396,7 @@ void RewardsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, c
 {
 	if(auto * ed = qobject_cast<RewardsWidget *>(editor))
 	{
-		auto hasReward = ed->commitChanges();
-		model->setData(index, "dummy");
-		if(hasReward)
-			model->setData(index, "HAS REWARD");
-		else
-			model->setData(index, "");
+		ed->commitChanges();
 	}
 	else
 	{

+ 6 - 0
mapeditor/inspector/rewardswidget.ui

@@ -2,6 +2,9 @@
 <ui version="4.0">
  <class>RewardsWidget</class>
  <widget class="QDialog" name="RewardsWidget">
+  <property name="windowModality">
+   <enum>Qt::NonModal</enum>
+  </property>
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -13,6 +16,9 @@
   <property name="windowTitle">
    <string>Rewards</string>
   </property>
+  <property name="modal">
+   <bool>true</bool>
+  </property>
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="1">
     <widget class="QPushButton" name="buttonRemove">

+ 0 - 4
mapeditor/inspector/townbulidingswidget.cpp

@@ -246,10 +246,6 @@ void TownBuildingsDelegate::setModelData(QWidget *editor, QAbstractItemModel *mo
 	{
 		town.forbiddenBuildings = ed->getForbiddenBuildings();
 		town.builtBuildings = ed->getBuiltBuildings();
-		
-		auto data = model->itemData(index);
-		model->setData(index, "dummy");
-		model->setItemData(index, data); //dummy change to trigger signal
 	}
 	else
 	{

+ 3 - 0
mapeditor/inspector/townbulidingswidget.ui

@@ -2,6 +2,9 @@
 <ui version="4.0">
  <class>TownBulidingsWidget</class>
  <widget class="QDialog" name="TownBulidingsWidget">
+  <property name="windowModality">
+   <enum>Qt::NonModal</enum>
+  </property>
   <property name="geometry">
    <rect>
     <x>0</x>

+ 2 - 2
mapeditor/mainwindow.cpp

@@ -996,7 +996,7 @@ void MainWindow::loadInspector(CGObjectInstance * obj, bool switchTab)
 
 void MainWindow::on_inspectorWidget_itemChanged(QTableWidgetItem *item)
 {
-	if(!item->isSelected())
+	if(!item->isSelected() && !(item->flags() & Qt::ItemIsUserCheckable))
 		return;
 
 	int r = item->row();
@@ -1015,7 +1015,7 @@ void MainWindow::on_inspectorWidget_itemChanged(QTableWidgetItem *item)
 
 	//set parameter
 	Inspector inspector(controller.map(), obj, tableWidget);
-	inspector.setProperty(param, item->text());
+	inspector.setProperty(param, item);
 	controller.commitObjectChange(mapLevel);
 }
 

+ 3 - 0
mapeditor/mainwindow.ui

@@ -390,6 +390,9 @@
            <property name="columnCount">
             <number>2</number>
            </property>
+           <attribute name="horizontalHeaderStretchLastSection">
+            <bool>true</bool>
+           </attribute>
            <attribute name="verticalHeaderVisible">
             <bool>false</bool>
            </attribute>

+ 2 - 1
mapeditor/mapcontroller.cpp

@@ -145,7 +145,8 @@ void MapController::repairMap()
 				nih->subID = 0;
 			}
 			
-			nih->type = type;
+			if(obj->ID != Obj::RANDOM_HERO)
+				nih->type = type;
 			
 			if(nih->ID == Obj::HERO) //not prison
 				nih->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();

+ 10 - 5
mapeditor/mapsettings/abstractsettings.cpp

@@ -10,6 +10,7 @@
 
 #include "StdInc.h"
 #include "abstractsettings.h"
+#include "../mapcontroller.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGCreature.h"
 #include "../../lib/CTownHandler.h"
@@ -82,16 +83,20 @@ AbstractSettings::AbstractSettings(QWidget *parent)
 
 }
 
+void AbstractSettings::initialize(MapController & c)
+{
+	controller = &c;
+}
+
 std::string AbstractSettings::getTownName(const CMap & map, int objectIdx)
 {
 	std::string name;
 	if(auto town = dynamic_cast<const CGTownInstance*>(map.objects[objectIdx].get()))
 	{
-		auto * ctown = town->town;
-		if(!ctown)
-			ctown = VLC->townh->randomTown;
-
-		name = ctown->faction ? town->getObjectName() : town->getNameTranslated() + ", (random)";
+		name = town->getNameTranslated();
+		
+		if(name.empty())
+			name = town->getTown()->faction->getNameTranslated();
 	}
 	return name;
 }

+ 11 - 8
mapeditor/mapsettings/abstractsettings.h

@@ -20,6 +20,8 @@ QString expiredDate(int date);
 int3 posFromJson(const JsonNode & json);
 std::vector<JsonNode> linearJsonArray(const JsonNode & json);
 
+class MapController;
+
 class AbstractSettings : public QWidget
 {
 	Q_OBJECT
@@ -27,17 +29,17 @@ public:
 	explicit AbstractSettings(QWidget *parent = nullptr);
 	virtual ~AbstractSettings() = default;
 
-	virtual void initialize(const CMap & map) = 0;
-	virtual void update(CMap & map) = 0;
+	virtual void initialize(MapController & controller);
+	virtual void update() = 0;
 
-	std::string getTownName(const CMap & map, int objectIdx);
-	std::string getHeroName(const CMap & map, int objectIdx);
-	std::string getMonsterName(const CMap & map, int objectIdx);
+	static std::string getTownName(const CMap & map, int objectIdx);
+	static std::string getHeroName(const CMap & map, int objectIdx);
+	static std::string getMonsterName(const CMap & map, int objectIdx);
 
 	static JsonNode conditionToJson(const EventCondition & event);
 
 	template<class T>
-	std::vector<int> getObjectIndexes(const CMap & map) const
+	static std::vector<int> getObjectIndexes(const CMap & map)
 	{
 		std::vector<int> result;
 		for(int i = 0; i < map.objects.size(); ++i)
@@ -49,7 +51,7 @@ public:
 	}
 
 	template<class T>
-	int getObjectByPos(const CMap & map, const int3 & pos)
+	static int getObjectByPos(const CMap & map, const int3 & pos)
 	{
 		for(int i = 0; i < map.objects.size(); ++i)
 		{
@@ -62,6 +64,7 @@ public:
 		return -1;
 	}
 
-signals:
+protected:
+	MapController * controller = nullptr;
 
 };

+ 7 - 5
mapeditor/mapsettings/eventsettings.cpp

@@ -11,6 +11,7 @@
 #include "eventsettings.h"
 #include "timedevent.h"
 #include "ui_eventsettings.h"
+#include "../mapcontroller.h"
 #include "../../lib/mapping/CMapDefines.h"
 #include "../../lib/constants/NumericConstants.h"
 #include "../../lib/constants/StringConstants.h"
@@ -73,9 +74,10 @@ EventSettings::~EventSettings()
 	delete ui;
 }
 
-void EventSettings::initialize(const CMap & map)
+void EventSettings::initialize(MapController & c)
 {
-	for(const auto & event : map.events)
+	AbstractSettings::initialize(c);
+	for(const auto & event : controller->map()->events)
 	{
 		auto * item = new QListWidgetItem(QString::fromStdString(event.name));
 		item->setData(Qt::UserRole, toVariant(event));
@@ -83,13 +85,13 @@ void EventSettings::initialize(const CMap & map)
 	}
 }
 
-void EventSettings::update(CMap & map)
+void EventSettings::update()
 {
-	map.events.clear();
+	controller->map()->events.clear();
 	for(int i = 0; i < ui->eventsList->count(); ++i)
 	{
 		const auto * item = ui->eventsList->item(i);
-		map.events.push_back(eventFromVariant(item->data(Qt::UserRole)));
+		controller->map()->events.push_back(eventFromVariant(item->data(Qt::UserRole)));
 	}
 }
 

+ 2 - 2
mapeditor/mapsettings/eventsettings.h

@@ -23,8 +23,8 @@ public:
 	explicit EventSettings(QWidget *parent = nullptr);
 	~EventSettings();
 
-	void initialize(const CMap & map) override;
-	void update(CMap & map) override;
+	void initialize(MapController & map) override;
+	void update() override;
 
 private slots:
 	void on_timedEventAdd_clicked();

+ 18 - 16
mapeditor/mapsettings/generalsettings.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 #include "generalsettings.h"
 #include "ui_generalsettings.h"
+#include "../mapcontroller.h"
 
 GeneralSettings::GeneralSettings(QWidget *parent) :
 	AbstractSettings(parent),
@@ -23,15 +24,16 @@ GeneralSettings::~GeneralSettings()
 	delete ui;
 }
 
-void GeneralSettings::initialize(const CMap & map)
+void GeneralSettings::initialize(MapController & c)
 {
-	ui->mapNameEdit->setText(tr(map.name.c_str()));
-	ui->mapDescriptionEdit->setPlainText(tr(map.description.c_str()));
-	ui->heroLevelLimit->setValue(map.levelLimit);
-	ui->heroLevelLimitCheck->setChecked(map.levelLimit);
+	AbstractSettings::initialize(c);
+	ui->mapNameEdit->setText(tr(controller->map()->name.c_str()));
+	ui->mapDescriptionEdit->setPlainText(tr(controller->map()->description.c_str()));
+	ui->heroLevelLimit->setValue(controller->map()->levelLimit);
+	ui->heroLevelLimitCheck->setChecked(controller->map()->levelLimit);
 
 	//set difficulty
-	switch(map.difficulty)
+	switch(controller->map()->difficulty)
 	{
 		case 0:
 			ui->diffRadio1->setChecked(true);
@@ -55,21 +57,21 @@ void GeneralSettings::initialize(const CMap & map)
 	};
 }
 
-void GeneralSettings::update(CMap & map)
+void GeneralSettings::update()
 {
-	map.name = ui->mapNameEdit->text().toStdString();
-	map.description = ui->mapDescriptionEdit->toPlainText().toStdString();
+	controller->map()->name = ui->mapNameEdit->text().toStdString();
+	controller->map()->description = ui->mapDescriptionEdit->toPlainText().toStdString();
 	if(ui->heroLevelLimitCheck->isChecked())
-		map.levelLimit = ui->heroLevelLimit->value();
+		controller->map()->levelLimit = ui->heroLevelLimit->value();
 	else
-		map.levelLimit = 0;
+		controller->map()->levelLimit = 0;
 
 	//set difficulty
-	if(ui->diffRadio1->isChecked()) map.difficulty = 0;
-	if(ui->diffRadio2->isChecked()) map.difficulty = 1;
-	if(ui->diffRadio3->isChecked()) map.difficulty = 2;
-	if(ui->diffRadio4->isChecked()) map.difficulty = 3;
-	if(ui->diffRadio5->isChecked()) map.difficulty = 4;
+	if(ui->diffRadio1->isChecked()) controller->map()->difficulty = 0;
+	if(ui->diffRadio2->isChecked()) controller->map()->difficulty = 1;
+	if(ui->diffRadio3->isChecked()) controller->map()->difficulty = 2;
+	if(ui->diffRadio4->isChecked()) controller->map()->difficulty = 3;
+	if(ui->diffRadio5->isChecked()) controller->map()->difficulty = 4;
 }
 
 void GeneralSettings::on_heroLevelLimitCheck_toggled(bool checked)

+ 2 - 2
mapeditor/mapsettings/generalsettings.h

@@ -23,8 +23,8 @@ public:
 	explicit GeneralSettings(QWidget *parent = nullptr);
 	~GeneralSettings();
 
-	void initialize(const CMap & map) override;
-	void update(CMap & map) override;
+	void initialize(MapController & map) override;
+	void update() override;
 
 private slots:
 	void on_heroLevelLimitCheck_toggled(bool checked);

+ 84 - 24
mapeditor/mapsettings/loseconditions.cpp

@@ -10,7 +10,7 @@
 #include "StdInc.h"
 #include "loseconditions.h"
 #include "ui_loseconditions.h"
-
+#include "../mapcontroller.h"
 #include "../lib/CGeneralTextHandler.h"
 
 LoseConditions::LoseConditions(QWidget *parent) :
@@ -25,12 +25,12 @@ LoseConditions::~LoseConditions()
 	delete ui;
 }
 
-void LoseConditions::initialize(const CMap & map)
+void LoseConditions::initialize(MapController & c)
 {
-	mapPointer = &map;
+	AbstractSettings::initialize(c);
 
 	//loss messages
-	ui->defeatMessageEdit->setText(QString::fromStdString(map.defeatMessage.toString()));
+	ui->defeatMessageEdit->setText(QString::fromStdString(controller->map()->defeatMessage.toString()));
 
 	//loss conditions
 	const std::array<std::string, 5> conditionStringsLose = {
@@ -47,7 +47,7 @@ void LoseConditions::initialize(const CMap & map)
 	}
 	ui->standardLoseCheck->setChecked(false);
 
-	for(auto & ev : map.triggeredEvents)
+	for(auto & ev : controller->map()->triggeredEvents)
 	{
 		if(ev.effect.type == EventEffect::DEFEAT)
 		{
@@ -68,7 +68,7 @@ void LoseConditions::initialize(const CMap & map)
 							{
 								ui->loseComboBox->setCurrentIndex(1);
 								assert(loseTypeWidget);
-								int townIdx = getObjectByPos<const CGTownInstance>(*mapPointer, posFromJson(json["position"]));
+								int townIdx = getObjectByPos<const CGTownInstance>(*controller->map(), posFromJson(json["position"]));
 								if(townIdx >= 0)
 								{
 									auto idx = loseTypeWidget->findData(townIdx);
@@ -79,7 +79,7 @@ void LoseConditions::initialize(const CMap & map)
 							{
 								ui->loseComboBox->setCurrentIndex(2);
 								assert(loseTypeWidget);
-								int heroIdx = getObjectByPos<const CGHeroInstance>(*mapPointer, posFromJson(json["position"]));
+								int heroIdx = getObjectByPos<const CGHeroInstance>(*controller->map(), posFromJson(json["position"]));
 								if(heroIdx >= 0)
 								{
 									auto idx = loseTypeWidget->findData(heroIdx);
@@ -114,10 +114,10 @@ void LoseConditions::initialize(const CMap & map)
 	}
 }
 
-void LoseConditions::update(CMap & map)
+void LoseConditions::update()
 {
 	//loss messages
-	map.defeatMessage = MetaString::createFromRawString(ui->defeatMessageEdit->text().toStdString());
+	controller->map()->defeatMessage = MetaString::createFromRawString(ui->defeatMessageEdit->text().toStdString());
 
 	//loss conditions
 	EventCondition defeatCondition(EventCondition::DAYS_WITHOUT_TOWN);
@@ -135,9 +135,9 @@ void LoseConditions::update(CMap & map)
 	//DEFEAT
 	if(ui->loseComboBox->currentIndex() == 0)
 	{
-		map.triggeredEvents.push_back(standardDefeat);
-		map.defeatIconIndex = 3;
-		map.defeatMessage.appendTextID("core.lcdesc.0");
+		controller->map()->triggeredEvents.push_back(standardDefeat);
+		controller->map()->defeatIconIndex = 3;
+		controller->map()->defeatMessage = MetaString::createFromTextID("core.lcdesc.0");
 	}
 	else
 	{
@@ -148,7 +148,7 @@ void LoseConditions::update(CMap & map)
 		specialDefeat.identifier = "specialDefeat";
 		specialDefeat.description.clear(); // TODO: display in quest window
 
-		map.defeatIconIndex = lossCondition;
+		controller->map()->defeatIconIndex = lossCondition;
 
 		switch(lossCondition)
 		{
@@ -158,11 +158,11 @@ void LoseConditions::update(CMap & map)
 				cond.objectType = Obj::TOWN;
 				assert(loseTypeWidget);
 				int townIdx = loseTypeWidget->currentData().toInt();
-				cond.position = map.objects[townIdx]->pos;
+				cond.position = controller->map()->objects[townIdx]->pos;
 				noneOf.expressions.push_back(cond);
 				specialDefeat.onFulfill.appendTextID("core.genrltxt.251");
 				specialDefeat.trigger = EventExpression(noneOf);
-				map.defeatMessage.appendTextID("core.lcdesc.1");
+				controller->map()->defeatMessage = MetaString::createFromTextID("core.lcdesc.1");
 				break;
 			}
 
@@ -172,11 +172,11 @@ void LoseConditions::update(CMap & map)
 				cond.objectType = Obj::HERO;
 				assert(loseTypeWidget);
 				int townIdx = loseTypeWidget->currentData().toInt();
-				cond.position = map.objects[townIdx]->pos;
+				cond.position = controller->map()->objects[townIdx]->pos;
 				noneOf.expressions.push_back(cond);
 				specialDefeat.onFulfill.appendTextID("core.genrltxt.253");
 				specialDefeat.trigger = EventExpression(noneOf);
-				map.defeatMessage.appendTextID("core.lcdesc.2");
+				controller->map()->defeatMessage = MetaString::createFromTextID("core.lcdesc.2");
 				break;
 			}
 
@@ -186,7 +186,7 @@ void LoseConditions::update(CMap & map)
 				cond.value = expiredDate(loseValueWidget->text());
 				specialDefeat.onFulfill.appendTextID("core.genrltxt.254");
 				specialDefeat.trigger = EventExpression(cond);
-				map.defeatMessage.appendTextID("core.lcdesc.3");
+				controller->map()->defeatMessage = MetaString::createFromTextID("core.lcdesc.3");
 				break;
 			}
 
@@ -210,9 +210,9 @@ void LoseConditions::update(CMap & map)
 
 		if(ui->standardLoseCheck->isChecked())
 		{
-			map.triggeredEvents.push_back(standardDefeat);
+			controller->map()->triggeredEvents.push_back(standardDefeat);
 		}
-		map.triggeredEvents.push_back(specialDefeat);
+		controller->map()->triggeredEvents.push_back(specialDefeat);
 	}
 
 }
@@ -222,9 +222,11 @@ void LoseConditions::on_loseComboBox_currentIndexChanged(int index)
 	delete loseTypeWidget;
 	delete loseValueWidget;
 	delete loseSelectWidget;
+	delete pickObjectButton;
 	loseTypeWidget = nullptr;
 	loseValueWidget = nullptr;
 	loseSelectWidget = nullptr;
+	pickObjectButton = nullptr;
 
 	if(index == 0)
 	{
@@ -240,16 +242,22 @@ void LoseConditions::on_loseComboBox_currentIndexChanged(int index)
 		case 0: {  //EventCondition::CONTROL (Obj::TOWN)
 			loseTypeWidget = new QComboBox;
 			ui->loseParamsLayout->addWidget(loseTypeWidget);
-			for(int i : getObjectIndexes<const CGTownInstance>(*mapPointer))
-				loseTypeWidget->addItem(tr(getTownName(*mapPointer, i).c_str()), QVariant::fromValue(i));
+			for(int i : getObjectIndexes<const CGTownInstance>(*controller->map()))
+				loseTypeWidget->addItem(tr(getTownName(*controller->map(), i).c_str()), QVariant::fromValue(i));
+			pickObjectButton = new QToolButton;
+			connect(pickObjectButton, &QToolButton::clicked, this, &LoseConditions::onObjectSelect);
+			ui->loseParamsLayout->addWidget(pickObjectButton);
 			break;
 		}
 
 		case 1: { //EventCondition::CONTROL (Obj::HERO)
 			loseTypeWidget = new QComboBox;
 			ui->loseParamsLayout->addWidget(loseTypeWidget);
-			for(int i : getObjectIndexes<const CGHeroInstance>(*mapPointer))
-				loseTypeWidget->addItem(tr(getHeroName(*mapPointer, i).c_str()), QVariant::fromValue(i));
+			for(int i : getObjectIndexes<const CGHeroInstance>(*controller->map()))
+				loseTypeWidget->addItem(tr(getHeroName(*controller->map(), i).c_str()), QVariant::fromValue(i));
+			pickObjectButton = new QToolButton;
+			connect(pickObjectButton, &QToolButton::clicked, this, &LoseConditions::onObjectSelect);
+			ui->loseParamsLayout->addWidget(pickObjectButton);
 			break;
 		}
 
@@ -269,3 +277,55 @@ void LoseConditions::on_loseComboBox_currentIndexChanged(int index)
 	}
 }
 
+void LoseConditions::onObjectSelect()
+{
+	int loseCondition = ui->loseComboBox->currentIndex() - 1;
+	for(int lvl : {0, 1})
+	{
+		auto & l = controller->scene(lvl)->objectPickerView;
+		switch(loseCondition)
+		{
+			case 0: {  //EventCondition::CONTROL (Obj::TOWN)
+				l.highlight<const CGTownInstance>();
+				break;
+			}
+				
+			case 1: { //EventCondition::CONTROL (Obj::HERO)
+				l.highlight<const CGHeroInstance>();
+				break;
+			}
+			default:
+				return;
+		}
+		l.update();
+		QObject::connect(&l, &ObjectPickerLayer::selectionMade, this, &LoseConditions::onObjectPicked);
+	}
+	
+	dynamic_cast<QWidget*>(parent()->parent()->parent()->parent()->parent()->parent()->parent())->hide();
+}
+
+void LoseConditions::onObjectPicked(const CGObjectInstance * obj)
+{
+	dynamic_cast<QWidget*>(parent()->parent()->parent()->parent()->parent()->parent()->parent())->show();
+	
+	for(int lvl : {0, 1})
+	{
+		auto & l = controller->scene(lvl)->objectPickerView;
+		l.clear();
+		l.update();
+		QObject::disconnect(&l, &ObjectPickerLayer::selectionMade, this, &LoseConditions::onObjectPicked);
+	}
+	
+	if(!obj) //discarded
+		return;
+	
+	for(int i = 0; i < loseTypeWidget->count(); ++i)
+	{
+		auto data = controller->map()->objects.at(loseTypeWidget->itemData(i).toInt());
+		if(data == obj)
+		{
+			loseTypeWidget->setCurrentIndex(i);
+			break;
+		}
+	}
+}

+ 7 - 3
mapeditor/mapsettings/loseconditions.h

@@ -23,18 +23,22 @@ public:
 	explicit LoseConditions(QWidget *parent = nullptr);
 	~LoseConditions();
 
-	void initialize(const CMap & map) override;
-	void update(CMap & map) override;
+	void initialize(MapController & map) override;
+	void update() override;
+	
+public slots:
+	void onObjectSelect();
+	void onObjectPicked(const CGObjectInstance *);
 
 private slots:
 	void on_loseComboBox_currentIndexChanged(int index);
 
 private:
 	Ui::LoseConditions *ui;
-	const CMap * mapPointer = nullptr;
 
 	QComboBox * loseTypeWidget = nullptr;
 	QComboBox * loseSelectWidget = nullptr;
 	QLineEdit * loseValueWidget = nullptr;
+	QToolButton * pickObjectButton = nullptr;
 };
 

+ 5 - 1
mapeditor/mapsettings/loseconditions.ui

@@ -42,7 +42,11 @@
       </widget>
      </item>
      <item>
-      <widget class="QLineEdit" name="defeatMessageEdit"/>
+      <widget class="QLineEdit" name="defeatMessageEdit">
+       <property name="readOnly">
+        <bool>true</bool>
+       </property>
+      </widget>
      </item>
     </layout>
    </item>

+ 12 - 12
mapeditor/mapsettings/mapsettings.cpp

@@ -63,12 +63,12 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) :
 		ui->listHeroes->addItem(item);
 	}
 
-	ui->general->initialize(*controller.map());
-	ui->mods->initialize(*controller.map());
-	ui->victory->initialize(*controller.map());
-	ui->lose->initialize(*controller.map());
-	ui->events->initialize(*controller.map());
-	ui->rumors->initialize(*controller.map());
+	ui->general->initialize(controller);
+	ui->mods->initialize(controller);
+	ui->victory->initialize(controller);
+	ui->lose->initialize(controller);
+	ui->events->initialize(controller);
+	ui->rumors->initialize(controller);
 }
 
 MapSettings::~MapSettings()
@@ -94,12 +94,12 @@ void MapSettings::on_pushButton_clicked()
 
 	controller.map()->triggeredEvents.clear();
 
-	ui->general->update(*controller.map());
-	ui->mods->update(*controller.map());
-	ui->victory->update(*controller.map());
-	ui->lose->update(*controller.map());
-	ui->events->update(*controller.map());
-	ui->rumors->update(*controller.map());
+	ui->general->update();
+	ui->mods->update();
+	ui->victory->update();
+	ui->lose->update();
+	ui->events->update();
+	ui->rumors->update();
 
 	controller.commitChangeWithoutRedraw();
 

+ 7 - 7
mapeditor/mapsettings/modsettings.cpp

@@ -35,9 +35,9 @@ ModSettings::~ModSettings()
 	delete ui;
 }
 
-void ModSettings::initialize(const CMap & map)
+void ModSettings::initialize(MapController & c)
 {
-	mapPointer = &map;
+	AbstractSettings::initialize(c);
 
 	//mods management
 	//collect all active mods
@@ -50,7 +50,7 @@ void ModSettings::initialize(const CMap & map)
 		auto item = new QTreeWidgetItem(parent, {QString::fromStdString(modInfo.name), QString::fromStdString(modInfo.version.toString())});
 		item->setData(0, Qt::UserRole, QVariant(QString::fromStdString(modInfo.identifier)));
 		item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
-		item->setCheckState(0, map.mods.count(modInfo.identifier) ? Qt::Checked : Qt::Unchecked);
+		item->setCheckState(0, controller->map()->mods.count(modInfo.identifier) ? Qt::Checked : Qt::Unchecked);
 		//set parent check
 		if(parent && item->checkState(0) == Qt::Checked)
 			parent->setCheckState(0, Qt::Checked);
@@ -96,7 +96,7 @@ void ModSettings::initialize(const CMap & map)
 	ui->treeMods->blockSignals(false);
 }
 
-void ModSettings::update(CMap & map)
+void ModSettings::update()
 {
 	//Mod management
 	auto widgetAction = [&](QTreeWidgetItem * item)
@@ -104,11 +104,11 @@ void ModSettings::update(CMap & map)
 		if(item->checkState(0) == Qt::Checked)
 		{
 			auto modName = item->data(0, Qt::UserRole).toString().toStdString();
-			map.mods[modName] = VLC->modh->getModInfo(modName).version;
+			controller->map()->mods[modName] = VLC->modh->getModInfo(modName).version;
 		}
 	};
 
-	map.mods.clear();
+	controller->map()->mods.clear();
 	for (int i = 0; i < ui->treeMods->topLevelItemCount(); ++i)
 	{
 		QTreeWidgetItem *item = ui->treeMods->topLevelItem(i);
@@ -134,7 +134,7 @@ void ModSettings::updateModWidgetBasedOnMods(const ModCompatibilityInfo & mods)
 
 void ModSettings::on_modResolution_map_clicked()
 {
-	updateModWidgetBasedOnMods(MapController::modAssessmentMap(*mapPointer));
+	updateModWidgetBasedOnMods(MapController::modAssessmentMap(*controller->map()));
 }
 
 

+ 2 - 3
mapeditor/mapsettings/modsettings.h

@@ -23,8 +23,8 @@ public:
 	explicit ModSettings(QWidget *parent = nullptr);
 	~ModSettings();
 
-	void initialize(const CMap & map) override;
-	void update(CMap & map) override;
+	void initialize(MapController & map) override;
+	void update() override;
 
 private slots:
 	void on_modResolution_map_clicked();
@@ -38,5 +38,4 @@ private:
 
 private:
 	Ui::ModSettings *ui;
-	const CMap * mapPointer = nullptr;
 };

+ 7 - 5
mapeditor/mapsettings/rumorsettings.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 #include "rumorsettings.h"
 #include "ui_rumorsettings.h"
+#include "../mapcontroller.h"
 
 RumorSettings::RumorSettings(QWidget *parent) :
 	AbstractSettings(parent),
@@ -23,9 +24,10 @@ RumorSettings::~RumorSettings()
 	delete ui;
 }
 
-void RumorSettings::initialize(const CMap & map)
+void RumorSettings::initialize(MapController & c)
 {
-	for(auto & rumor : map.rumors)
+	AbstractSettings::initialize(c);
+	for(auto & rumor : controller->map()->rumors)
 	{
 		auto * item = new QListWidgetItem(QString::fromStdString(rumor.name));
 		item->setData(Qt::UserRole, QVariant(QString::fromStdString(rumor.text)));
@@ -34,15 +36,15 @@ void RumorSettings::initialize(const CMap & map)
 	}
 }
 
-void RumorSettings::update(CMap & map)
+void RumorSettings::update()
 {
-	map.rumors.clear();
+	controller->map()->rumors.clear();
 	for(int i = 0; i < ui->rumors->count(); ++i)
 	{
 		Rumor rumor;
 		rumor.name = ui->rumors->item(i)->text().toStdString();
 		rumor.text = ui->rumors->item(i)->data(Qt::UserRole).toString().toStdString();
-		map.rumors.push_back(rumor);
+		controller->map()->rumors.push_back(rumor);
 	}
 }
 

+ 2 - 2
mapeditor/mapsettings/rumorsettings.h

@@ -23,8 +23,8 @@ public:
 	explicit RumorSettings(QWidget *parent = nullptr);
 	~RumorSettings();
 
-	void initialize(const CMap & map) override;
-	void update(CMap & map) override;
+	void initialize(MapController & map) override;
+	void update() override;
 
 private slots:
 	void on_message_textChanged();

+ 162 - 50
mapeditor/mapsettings/victoryconditions.cpp

@@ -10,9 +10,10 @@
 #include "StdInc.h"
 #include "victoryconditions.h"
 #include "ui_victoryconditions.h"
-
+#include "../mapcontroller.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/constants/StringConstants.h"
+#include "../../lib/mapObjects/CGCreature.h"
 
 #include "../inspector/townbulidingswidget.h" //to convert BuildingID to string
 
@@ -23,15 +24,15 @@ VictoryConditions::VictoryConditions(QWidget *parent) :
 	ui->setupUi(this);
 }
 
-void VictoryConditions::initialize(const CMap & map)
+void VictoryConditions::initialize(MapController & c)
 {
-	mapPointer = &map;
+	AbstractSettings::initialize(c);
 
 	//victory message
-	ui->victoryMessageEdit->setText(QString::fromStdString(map.victoryMessage.toString()));
+	ui->victoryMessageEdit->setText(QString::fromStdString(controller->map()->victoryMessage.toString()));
 
 	//victory conditions
-	const std::array<std::string, 8> conditionStringsWin = {
+	const std::array<std::string, 9> conditionStringsWin = {
 		QT_TR_NOOP("No special victory"),
 		QT_TR_NOOP("Capture artifact"),
 		QT_TR_NOOP("Hire creatures"),
@@ -39,7 +40,8 @@ void VictoryConditions::initialize(const CMap & map)
 		QT_TR_NOOP("Construct building"),
 		QT_TR_NOOP("Capture town"),
 		QT_TR_NOOP("Defeat hero"),
-		QT_TR_NOOP("Transport artifact")
+		QT_TR_NOOP("Transport artifact"),
+		QT_TR_NOOP("Kill monster")
 	};
 
 	for(auto & s : conditionStringsWin)
@@ -49,7 +51,7 @@ void VictoryConditions::initialize(const CMap & map)
 	ui->standardVictoryCheck->setChecked(false);
 	ui->onlyForHumansCheck->setChecked(false);
 
-	for(auto & ev : map.triggeredEvents)
+	for(auto & ev : controller->map()->triggeredEvents)
 	{
 		if(ev.effect.type == EventEffect::VICTORY)
 		{
@@ -98,7 +100,7 @@ void VictoryConditions::initialize(const CMap & map)
 							assert(victorySelectWidget);
 							auto idx = victoryTypeWidget->findData(int(json["objectType"].Integer()));
 							victoryTypeWidget->setCurrentIndex(idx);
-							int townIdx = getObjectByPos<const CGTownInstance>(*mapPointer, posFromJson(json["position"]));
+							int townIdx = getObjectByPos<const CGTownInstance>(*controller->map(), posFromJson(json["position"]));
 							if(townIdx >= 0)
 							{
 								auto idx = victorySelectWidget->findData(townIdx);
@@ -112,7 +114,7 @@ void VictoryConditions::initialize(const CMap & map)
 							assert(victoryTypeWidget);
 							if(json["objectType"].Integer() == Obj::TOWN)
 							{
-								int townIdx = getObjectByPos<const CGTownInstance>(*mapPointer, posFromJson(json["position"]));
+								int townIdx = getObjectByPos<const CGTownInstance>(*controller->map(), posFromJson(json["position"]));
 								if(townIdx >= 0)
 								{
 									auto idx = victoryTypeWidget->findData(townIdx);
@@ -124,18 +126,28 @@ void VictoryConditions::initialize(const CMap & map)
 						}
 
 						case EventCondition::DESTROY: {
-							ui->victoryComboBox->setCurrentIndex(6);
-							assert(victoryTypeWidget);
 							if(json["objectType"].Integer() == Obj::HERO)
 							{
-								int heroIdx = getObjectByPos<const CGHeroInstance>(*mapPointer, posFromJson(json["position"]));
+								ui->victoryComboBox->setCurrentIndex(6);
+								assert(victoryTypeWidget);
+								int heroIdx = getObjectByPos<const CGHeroInstance>(*controller->map(), posFromJson(json["position"]));
 								if(heroIdx >= 0)
 								{
 									auto idx = victoryTypeWidget->findData(heroIdx);
 									victoryTypeWidget->setCurrentIndex(idx);
 								}
 							}
-							//TODO: support control other objects (monsters)
+							if(json["objectType"].Integer() == Obj::MONSTER)
+							{
+								ui->victoryComboBox->setCurrentIndex(8);
+								assert(victoryTypeWidget);
+								int monsterIdx = getObjectByPos<const CGCreature>(*controller->map(), posFromJson(json["position"]));
+								if(monsterIdx >= 0)
+								{
+									auto idx = victoryTypeWidget->findData(monsterIdx);
+									victoryTypeWidget->setCurrentIndex(idx);
+								}
+							}
 							break;
 						}
 
@@ -144,7 +156,7 @@ void VictoryConditions::initialize(const CMap & map)
 							assert(victoryTypeWidget);
 							assert(victorySelectWidget);
 							victoryTypeWidget->setCurrentIndex(json["objectType"].Integer());
-							int townIdx = getObjectByPos<const CGTownInstance>(*mapPointer, posFromJson(json["position"]));
+							int townIdx = getObjectByPos<const CGTownInstance>(*controller->map(), posFromJson(json["position"]));
 							if(townIdx >= 0)
 							{
 								auto idx = victorySelectWidget->findData(townIdx);
@@ -164,10 +176,10 @@ void VictoryConditions::initialize(const CMap & map)
 	}
 }
 
-void VictoryConditions::update(CMap & map)
+void VictoryConditions::update()
 {
 	//victory messages
-	map.victoryMessage = MetaString::createFromRawString(ui->victoryMessageEdit->text().toStdString());
+	controller->map()->victoryMessage = MetaString::createFromRawString(ui->victoryMessageEdit->text().toStdString());
 
 	//victory conditions
 	EventCondition victoryCondition(EventCondition::STANDARD_WIN);
@@ -184,9 +196,9 @@ void VictoryConditions::update(CMap & map)
 	//VICTORY
 	if(ui->victoryComboBox->currentIndex() == 0)
 	{
-		map.triggeredEvents.push_back(standardVictory);
-		map.victoryIconIndex = 11;
-		map.victoryMessage.appendTextID(VLC->generaltexth->victoryConditions[0]);
+		controller->map()->triggeredEvents.push_back(standardVictory);
+		controller->map()->victoryIconIndex = 11;
+		controller->map()->victoryMessage = MetaString::createFromTextID("core.vcdesc.0");
 	}
 	else
 	{
@@ -197,8 +209,8 @@ void VictoryConditions::update(CMap & map)
 		specialVictory.identifier = "specialVictory";
 		specialVictory.description.clear(); // TODO: display in quest window
 
-		map.victoryIconIndex = vicCondition;
-		map.victoryMessage.appendTextID(VLC->generaltexth->victoryConditions[size_t(vicCondition) + 1]);
+		controller->map()->victoryIconIndex = vicCondition;
+		controller->map()->victoryMessage = MetaString::createFromTextID("core.vcdesc." + std::to_string(vicCondition + 1));
 
 		switch(vicCondition)
 		{
@@ -240,7 +252,7 @@ void VictoryConditions::update(CMap & map)
 				cond.objectType = victoryTypeWidget->currentData().toInt();
 				int townIdx = victorySelectWidget->currentData().toInt();
 				if(townIdx > -1)
-					cond.position = map.objects[townIdx]->pos;
+					cond.position = controller->map()->objects[townIdx]->pos;
 				specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.283");
 				specialVictory.onFulfill.appendTextID("core.genrltxt.282");
 				specialVictory.trigger = EventExpression(cond);
@@ -252,7 +264,7 @@ void VictoryConditions::update(CMap & map)
 				assert(victoryTypeWidget);
 				cond.objectType = Obj::TOWN;
 				int townIdx = victoryTypeWidget->currentData().toInt();
-				cond.position = map.objects[townIdx]->pos;
+				cond.position = controller->map()->objects[townIdx]->pos;
 				specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.250");
 				specialVictory.onFulfill.appendTextID("core.genrltxt.249");
 				specialVictory.trigger = EventExpression(cond);
@@ -264,7 +276,7 @@ void VictoryConditions::update(CMap & map)
 				assert(victoryTypeWidget);
 				cond.objectType = Obj::HERO;
 				int heroIdx = victoryTypeWidget->currentData().toInt();
-				cond.position = map.objects[heroIdx]->pos;
+				cond.position = controller->map()->objects[heroIdx]->pos;
 				specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.253");
 				specialVictory.onFulfill.appendTextID("core.genrltxt.252");
 				specialVictory.trigger = EventExpression(cond);
@@ -277,12 +289,24 @@ void VictoryConditions::update(CMap & map)
 				cond.objectType = victoryTypeWidget->currentData().toInt();
 				int townIdx = victorySelectWidget->currentData().toInt();
 				if(townIdx > -1)
-					cond.position = map.objects[townIdx]->pos;
+					cond.position = controller->map()->objects[townIdx]->pos;
 				specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.293");
 				specialVictory.onFulfill.appendTextID("core.genrltxt.292");
 				specialVictory.trigger = EventExpression(cond);
 				break;
 			}
+				
+			case 7: {
+				EventCondition cond(EventCondition::DESTROY);
+				assert(victoryTypeWidget);
+				cond.objectType = Obj::MONSTER;
+				int monsterIdx = victoryTypeWidget->currentData().toInt();
+				cond.position = controller->map()->objects[monsterIdx]->pos;
+				specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.287");
+				specialVictory.onFulfill.appendTextID("core.genrltxt.286");
+				specialVictory.trigger = EventExpression(cond);
+				break;
+			}
 
 		}
 
@@ -300,11 +324,11 @@ void VictoryConditions::update(CMap & map)
 		// if normal victory allowed - add one more quest
 		if(ui->standardVictoryCheck->isChecked())
 		{
-			map.victoryMessage.appendRawString(" / ");
-			map.victoryMessage.appendTextID(VLC->generaltexth->victoryConditions[0]);
-			map.triggeredEvents.push_back(standardVictory);
+			controller->map()->victoryMessage.appendRawString(" / ");
+			controller->map()->victoryMessage.appendTextID("core.vcdesc.0");
+			controller->map()->triggeredEvents.push_back(standardVictory);
 		}
-		map.triggeredEvents.push_back(specialVictory);
+		controller->map()->triggeredEvents.push_back(specialVictory);
 	}
 }
 
@@ -318,9 +342,11 @@ void VictoryConditions::on_victoryComboBox_currentIndexChanged(int index)
 	delete victoryTypeWidget;
 	delete victoryValueWidget;
 	delete victorySelectWidget;
+	delete pickObjectButton;
 	victoryTypeWidget = nullptr;
 	victoryValueWidget = nullptr;
 	victorySelectWidget = nullptr;
+	pickObjectButton = nullptr;
 
 	if(index == 0)
 	{
@@ -339,7 +365,7 @@ void VictoryConditions::on_victoryComboBox_currentIndexChanged(int index)
 		case 0: { //EventCondition::HAVE_ARTIFACT
 			victoryTypeWidget = new QComboBox;
 			ui->victoryParamsLayout->addWidget(victoryTypeWidget);
-			for(int i = 0; i < mapPointer->allowedArtifact.size(); ++i)
+			for(int i = 0; i < controller->map()->allowedArtifact.size(); ++i)
 				victoryTypeWidget->addItem(QString::fromStdString(VLC->arth->objects[i]->getNameTranslated()), QVariant::fromValue(i));
 			break;
 		}
@@ -383,53 +409,139 @@ void VictoryConditions::on_victoryComboBox_currentIndexChanged(int index)
 			victorySelectWidget = new QComboBox;
 			ui->victoryParamsLayout->addWidget(victorySelectWidget);
 			victorySelectWidget->addItem("Any town", QVariant::fromValue(-1));
-			for(int i : getObjectIndexes<const CGTownInstance>(*mapPointer))
-				victorySelectWidget->addItem(getTownName(*mapPointer, i).c_str(), QVariant::fromValue(i));
+			for(int i : getObjectIndexes<const CGTownInstance>(*controller->map()))
+				victorySelectWidget->addItem(getTownName(*controller->map(), i).c_str(), QVariant::fromValue(i));
+			
+			pickObjectButton = new QToolButton;
+			connect(pickObjectButton, &QToolButton::clicked, this, &VictoryConditions::onObjectSelect);
+			ui->victoryParamsLayout->addWidget(pickObjectButton);
 			break;
 		}
 
 		case 4: { //EventCondition::CONTROL (Obj::TOWN)
 			victoryTypeWidget = new QComboBox;
 			ui->victoryParamsLayout->addWidget(victoryTypeWidget);
-			for(int i : getObjectIndexes<const CGTownInstance>(*mapPointer))
-				victoryTypeWidget->addItem(tr(getTownName(*mapPointer, i).c_str()), QVariant::fromValue(i));
+			for(int i : getObjectIndexes<const CGTownInstance>(*controller->map()))
+				victoryTypeWidget->addItem(tr(getTownName(*controller->map(), i).c_str()), QVariant::fromValue(i));
+			pickObjectButton = new QToolButton;
+			connect(pickObjectButton, &QToolButton::clicked, this, &VictoryConditions::onObjectSelect);
+			ui->victoryParamsLayout->addWidget(pickObjectButton);
 			break;
 		}
 
 		case 5: { //EventCondition::DESTROY (Obj::HERO)
 			victoryTypeWidget = new QComboBox;
 			ui->victoryParamsLayout->addWidget(victoryTypeWidget);
-			for(int i : getObjectIndexes<const CGHeroInstance>(*mapPointer))
-				victoryTypeWidget->addItem(tr(getHeroName(*mapPointer, i).c_str()), QVariant::fromValue(i));
+			for(int i : getObjectIndexes<const CGHeroInstance>(*controller->map()))
+				victoryTypeWidget->addItem(tr(getHeroName(*controller->map(), i).c_str()), QVariant::fromValue(i));
+			pickObjectButton = new QToolButton;
+			connect(pickObjectButton, &QToolButton::clicked, this, &VictoryConditions::onObjectSelect);
+			ui->victoryParamsLayout->addWidget(pickObjectButton);
 			break;
 		}
 
 		case 6: { //EventCondition::TRANSPORT (Obj::ARTEFACT)
 			victoryTypeWidget = new QComboBox;
 			ui->victoryParamsLayout->addWidget(victoryTypeWidget);
-			for(int i = 0; i < mapPointer->allowedArtifact.size(); ++i)
+			for(int i = 0; i < controller->map()->allowedArtifact.size(); ++i)
 				victoryTypeWidget->addItem(QString::fromStdString(VLC->arth->objects[i]->getNameTranslated()), QVariant::fromValue(i));
-
+			
 			victorySelectWidget = new QComboBox;
 			ui->victoryParamsLayout->addWidget(victorySelectWidget);
-			for(int i : getObjectIndexes<const CGTownInstance>(*mapPointer))
-				victorySelectWidget->addItem(tr(getTownName(*mapPointer, i).c_str()), QVariant::fromValue(i));
+			for(int i : getObjectIndexes<const CGTownInstance>(*controller->map()))
+				victorySelectWidget->addItem(tr(getTownName(*controller->map(), i).c_str()), QVariant::fromValue(i));
+			pickObjectButton = new QToolButton;
+			connect(pickObjectButton, &QToolButton::clicked, this, &VictoryConditions::onObjectSelect);
+			ui->victoryParamsLayout->addWidget(pickObjectButton);
 			break;
 		}
-
-
-		//TODO: support this vectory type
-		// in order to do that, need to implement finding creature by position
-		// selecting from map would be the best user experience
-		/*case 7: { //EventCondition::DESTROY (Obj::MONSTER)
+			
+		case 7: { //EventCondition::DESTROY (Obj::MONSTER)
 			victoryTypeWidget = new QComboBox;
-			ui->loseParamsLayout->addWidget(victoryTypeWidget);
-			for(int i : getObjectIndexes<const CGCreature>(*mapPointer))
-				victoryTypeWidget->addItem(tr(getMonsterName(i).c_str()), QVariant::fromValue(i));
+			ui->victoryParamsLayout->addWidget(victoryTypeWidget);
+			for(int i : getObjectIndexes<const CGCreature>(*controller->map()))
+				victoryTypeWidget->addItem(tr(getMonsterName(*controller->map(), i).c_str()), QVariant::fromValue(i));
+			pickObjectButton = new QToolButton;
+			connect(pickObjectButton, &QToolButton::clicked, this, &VictoryConditions::onObjectSelect);
+			ui->victoryParamsLayout->addWidget(pickObjectButton);
 			break;
-		}*/
+		}
+	}
+}
 
 
+void VictoryConditions::onObjectSelect()
+{
+	int vicConditions = ui->victoryComboBox->currentIndex() - 1;
+	for(int lvl : {0, 1})
+	{
+		auto & l = controller->scene(lvl)->objectPickerView;
+		switch(vicConditions)
+		{
+			case 3: { //EventCondition::HAVE_BUILDING
+				l.highlight<const CGTownInstance>();
+				break;
+			}
+				
+			case 4: { //EventCondition::CONTROL (Obj::TOWN)
+				l.highlight<const CGTownInstance>();
+				break;
+			}
+				
+			case 5: { //EventCondition::DESTROY (Obj::HERO)
+				l.highlight<const CGHeroInstance>();
+				break;
+			}
+				
+			case 6: { //EventCondition::TRANSPORT (Obj::ARTEFACT)
+				l.highlight<const CGTownInstance>();
+				break;
+			}
+				
+			case 7: { //EventCondition::DESTROY (Obj::MONSTER)
+				l.highlight<const CGCreature>();
+				break;
+			}
+			default:
+				return;
+		}
+		l.update();
+		QObject::connect(&l, &ObjectPickerLayer::selectionMade, this, &VictoryConditions::onObjectPicked);
 	}
+	
+	dynamic_cast<QWidget*>(parent()->parent()->parent()->parent()->parent()->parent()->parent())->hide();
 }
 
+void VictoryConditions::onObjectPicked(const CGObjectInstance * obj)
+{
+	dynamic_cast<QWidget*>(parent()->parent()->parent()->parent()->parent()->parent()->parent())->show();
+	
+	for(int lvl : {0, 1})
+	{
+		auto & l = controller->scene(lvl)->objectPickerView;
+		l.clear();
+		l.update();
+		QObject::disconnect(&l, &ObjectPickerLayer::selectionMade, this, &VictoryConditions::onObjectPicked);
+	}
+	
+	if(!obj) //discarded
+		return;
+	
+	int vicConditions = ui->victoryComboBox->currentIndex() - 1;
+	QComboBox * w = victoryTypeWidget;
+	if(vicConditions == 3 || vicConditions == 6)
+		w = victorySelectWidget;
+	
+	for(int i = 0; i < w->count(); ++i)
+	{
+		if(w->itemData(i).toInt() < 0)
+			continue;
+		
+		auto data = controller->map()->objects.at(w->itemData(i).toInt());
+		if(data == obj)
+		{
+			w->setCurrentIndex(i);
+			break;
+		}
+	}
+}

+ 7 - 3
mapeditor/mapsettings/victoryconditions.h

@@ -23,17 +23,21 @@ public:
 	explicit VictoryConditions(QWidget *parent = nullptr);
 	~VictoryConditions();
 
-	void initialize(const CMap & map) override;
-	void update(CMap & map) override;
+	void initialize(MapController & map) override;
+	void update() override;
+	
+public slots:
+	void onObjectSelect();
+	void onObjectPicked(const CGObjectInstance *);
 
 private slots:
 	void on_victoryComboBox_currentIndexChanged(int index);
 
 private:
 	Ui::VictoryConditions *ui;
-	const CMap * mapPointer = nullptr;
 
 	QComboBox * victoryTypeWidget = nullptr;
 	QComboBox * victorySelectWidget = nullptr;
 	QLineEdit * victoryValueWidget = nullptr;
+	QToolButton * pickObjectButton = nullptr;
 };

+ 5 - 1
mapeditor/mapsettings/victoryconditions.ui

@@ -42,7 +42,11 @@
       </widget>
      </item>
      <item>
-      <widget class="QLineEdit" name="victoryMessageEdit"/>
+      <widget class="QLineEdit" name="victoryMessageEdit">
+       <property name="readOnly">
+        <bool>true</bool>
+       </property>
+      </widget>
      </item>
     </layout>
    </item>

+ 25 - 1
mapeditor/mapview.cpp

@@ -198,6 +198,9 @@ void MapView::mousePressEvent(QMouseEvent *event)
 	auto * sc = static_cast<MapScene*>(scene());
 	if(!sc || !controller->map())
 		return;
+	
+	if(sc->objectPickerView.isVisible())
+		return;
 
 	mouseStart = mapToScene(event->pos());
 	tileStart = tilePrev = int3(mouseStart.x() / 32, mouseStart.y() / 32, sc->level);
@@ -338,6 +341,22 @@ void MapView::mouseReleaseEvent(QMouseEvent *event)
 	
 	if(rubberBand)
 		rubberBand->hide();
+	
+	if(sc->objectPickerView.isVisible())
+	{
+		if(event->button() == Qt::RightButton)
+			sc->objectPickerView.discard();
+		
+		if(event->button() == Qt::LeftButton)
+		{
+			mouseStart = mapToScene(event->pos());
+			tileStart = tilePrev = int3(mouseStart.x() / 32, mouseStart.y() / 32, sc->level);
+			if(auto * pickedObject = sc->selectionObjectsView.selectObjectAt(tileStart.x, tileStart.y))
+				sc->objectPickerView.select(pickedObject);
+		}
+		
+		return;
+	}
 
 	switch(selectionTool)
 	{
@@ -397,6 +416,7 @@ void MapView::mouseReleaseEvent(QMouseEvent *event)
 		bool tab = false;
 		if(sc->selectionObjectsView.selectionMode == SelectionObjectsLayer::MOVEMENT)
 		{
+			tab = sc->selectionObjectsView.shift.isNull();
 			controller->commitObjectShift(sc->level);
 		}
 		else
@@ -405,7 +425,6 @@ void MapView::mouseReleaseEvent(QMouseEvent *event)
 			sc->selectionObjectsView.shift = QPoint(0, 0);
 			sc->selectionObjectsView.draw();
 			tab = true;
-			//check if we have only one object
 		}
 		auto selection = sc->selectionObjectsView.getSelection();
 		if(selection.size() == 1)
@@ -463,7 +482,9 @@ void MapView::dropEvent(QDropEvent * event)
 		QString errorMsg;
 		if(controller->canPlaceObject(sc->level, sc->selectionObjectsView.newObject, errorMsg))
 		{
+			auto * obj = sc->selectionObjectsView.newObject;
 			controller->commitObjectCreate(sc->level);
+			emit openObjectProperties(obj, false);
 		}
 		else
 		{
@@ -545,6 +566,7 @@ MapScene::MapScene(int lvl):
 	terrainView(this),
 	objectsView(this),
 	selectionObjectsView(this),
+	objectPickerView(this),
 	isTerrainSelected(false),
 	isObjectSelected(false)
 {
@@ -560,6 +582,7 @@ std::list<AbstractLayer *> MapScene::getAbstractLayers()
 		&objectsView,
 		&gridView,
 		&passabilityView,
+		&objectPickerView,
 		&selectionTerrainView,
 		&selectionObjectsView
 	};
@@ -573,6 +596,7 @@ void MapScene::updateViews()
 	objectsView.show(true);
 	selectionTerrainView.show(true);
 	selectionObjectsView.show(true);
+	objectPickerView.show(true);
 }
 
 void MapScene::terrainSelected(bool anythingSelected)

+ 1 - 0
mapeditor/mapview.h

@@ -66,6 +66,7 @@ public:
 	TerrainLayer terrainView;
 	ObjectsLayer objectsView;
 	SelectionObjectsLayer selectionObjectsView;
+	ObjectPickerLayer objectPickerView;
 
 signals:
 	void selected(bool anything);

+ 51 - 3
mapeditor/playerparams.cpp

@@ -11,6 +11,7 @@
 #include "StdInc.h"
 #include "playerparams.h"
 #include "ui_playerparams.h"
+#include "mapsettings/abstractsettings.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/constants/StringConstants.h"
 
@@ -87,7 +88,8 @@ PlayerParams::PlayerParams(MapController & ctrl, int playerId, QWidget *parent)
 			{
 				if(playerInfo.hasMainTown && playerInfo.posOfMainTown == town->pos)
 					foundMainTown = townIndex;
-				const auto name = ctown->faction ? town->getObjectName() : town->getNameTranslated() + ", (random)";
+				
+				const auto name = AbstractSettings::getTownName(*controller.map(), i);
 				ui->mainTown->addItem(tr(name.c_str()), QVariant::fromValue(i));
 				++townIndex;
 			}
@@ -149,8 +151,7 @@ void PlayerParams::allowedFactionsCheck(QListWidgetItem * item)
 		playerInfo.allowedFactions.erase(FactionID(item->data(Qt::UserRole).toInt()));
 }
 
-
-void PlayerParams::on_mainTown_activated(int index)
+void PlayerParams::on_mainTown_currentIndexChanged(int index)
 {
 	if(index == 0) //default
 	{
@@ -186,3 +187,50 @@ void PlayerParams::on_playerColorCombo_activated(int index)
 	}
 }
 
+
+void PlayerParams::on_townSelect_clicked()
+{
+	auto pred = [this](const CGObjectInstance * obj) -> bool
+	{
+		if(auto town = dynamic_cast<const CGTownInstance*>(obj))
+			return town->getOwner().getNum() == playerColor;
+		return false;
+	};
+	
+	for(int lvl : {0, 1})
+	{
+		auto & l = controller.scene(lvl)->objectPickerView;
+		l.highlight(pred);
+		l.update();
+		QObject::connect(&l, &ObjectPickerLayer::selectionMade, this, &PlayerParams::onTownPicked);
+	}
+	
+	dynamic_cast<QWidget*>(parent()->parent()->parent()->parent())->hide();
+}
+
+void PlayerParams::onTownPicked(const CGObjectInstance * obj)
+{
+	dynamic_cast<QWidget*>(parent()->parent()->parent()->parent())->show();
+	
+	for(int lvl : {0, 1})
+	{
+		auto & l = controller.scene(lvl)->objectPickerView;
+		l.clear();
+		l.update();
+		QObject::disconnect(&l, &ObjectPickerLayer::selectionMade, this, &PlayerParams::onTownPicked);
+	}
+	
+	if(!obj) //discarded
+		return;
+	
+	for(int i = 0; i < ui->mainTown->count(); ++i)
+	{
+		auto town = controller.map()->objects.at(ui->mainTown->itemData(i).toInt());
+		if(town == obj)
+		{
+			ui->mainTown->setCurrentIndex(i);
+			break;
+		}
+	}
+}
+

+ 5 - 1
mapeditor/playerparams.h

@@ -28,13 +28,15 @@ public:
 
 	PlayerInfo playerInfo;
 	int playerColor;
+	
+	void onTownPicked(const CGObjectInstance *);
 
 private slots:
 	void on_radioHuman_toggled(bool checked);
 
 	void on_radioCpu_toggled(bool checked);
 
-	void on_mainTown_activated(int index);
+	void on_mainTown_currentIndexChanged(int index);
 
 	void on_generateHero_stateChanged(int arg1);
 
@@ -46,6 +48,8 @@ private slots:
 
 	void on_playerColorCombo_activated(int index);
 
+	void on_townSelect_clicked();
+
 private:
 	Ui::PlayerParams *ui;
 	

+ 171 - 127
mapeditor/playerparams.ui

@@ -6,7 +6,7 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>505</width>
+    <width>614</width>
     <height>160</height>
    </rect>
   </property>
@@ -25,7 +25,7 @@
   <property name="windowTitle">
    <string/>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
+  <layout class="QHBoxLayout" name="horizontalLayout">
    <property name="leftMargin">
     <number>0</number>
    </property>
@@ -49,136 +49,180 @@
      <property name="title">
       <string/>
      </property>
-     <layout class="QGridLayout" name="gridLayout">
-      <item row="4" column="0">
-       <widget class="QComboBox" name="teamId">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-       </widget>
-      </item>
-      <item row="4" column="1">
-       <widget class="QCheckBox" name="generateHero">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="text">
-         <string>Generate hero at main</string>
-        </property>
-       </widget>
-      </item>
-      <item row="4" column="2">
-       <widget class="QCheckBox" name="randomFaction">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="text">
-         <string>Random faction</string>
-        </property>
-       </widget>
-      </item>
-      <item row="3" column="0">
-       <widget class="QLabel" name="label_2">
-        <property name="text">
-         <string>Team</string>
-        </property>
-       </widget>
-      </item>
-      <item row="2" column="0">
-       <widget class="QRadioButton" name="radioCpu">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="text">
-         <string>CPU only</string>
-        </property>
-       </widget>
-      </item>
-      <item row="1" column="0">
-       <widget class="QRadioButton" name="radioHuman">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="text">
-         <string>Human/CPU</string>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="2" rowspan="4">
-       <widget class="QListWidget" name="allowedFactions">
-        <property name="enabled">
-         <bool>true</bool>
-        </property>
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="focusPolicy">
-         <enum>Qt::ClickFocus</enum>
-        </property>
-        <property name="editTriggers">
-         <set>QAbstractItemView::NoEditTriggers</set>
-        </property>
-        <property name="selectionMode">
-         <enum>QAbstractItemView::NoSelection</enum>
-        </property>
-       </widget>
-      </item>
-      <item row="3" column="1">
-       <widget class="QComboBox" name="mainTown">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
+     <layout class="QHBoxLayout" name="horizontalLayout_2">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <layout class="QVBoxLayout" name="verticalLayout_2">
         <item>
-         <property name="text">
-          <string>(default)</string>
-         </property>
+         <widget class="QRadioButton" name="radioCpu">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="text">
+           <string>CPU only</string>
+          </property>
+         </widget>
         </item>
-       </widget>
-      </item>
-      <item row="2" column="1">
-       <widget class="QLabel" name="label_3">
-        <property name="text">
-         <string>Main town</string>
-        </property>
-       </widget>
+        <item>
+         <widget class="QRadioButton" name="radioHuman">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="text">
+           <string>Human/CPU</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLabel" name="label_2">
+          <property name="text">
+           <string>Team</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QComboBox" name="teamId">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+         </widget>
+        </item>
+       </layout>
       </item>
-      <item row="1" column="1">
-       <widget class="QComboBox" name="playerColorCombo">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-       </widget>
+      <item>
+       <layout class="QVBoxLayout" name="verticalLayout_3">
+        <item>
+         <widget class="QLabel" name="label">
+          <property name="text">
+           <string>Color</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QComboBox" name="playerColorCombo">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLabel" name="label_3">
+          <property name="text">
+           <string>Main town</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout_3">
+          <property name="spacing">
+           <number>0</number>
+          </property>
+          <property name="topMargin">
+           <number>0</number>
+          </property>
+          <item>
+           <widget class="QComboBox" name="mainTown">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <item>
+             <property name="text">
+              <string>(default)</string>
+             </property>
+            </item>
+           </widget>
+          </item>
+          <item>
+           <widget class="QToolButton" name="townSelect">
+            <property name="text">
+             <string>...</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <widget class="QCheckBox" name="generateHero">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="text">
+           <string>Generate hero at main</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
       </item>
-      <item row="0" column="1">
-       <widget class="QLabel" name="label">
-        <property name="text">
-         <string>Color</string>
+      <item>
+       <layout class="QVBoxLayout" name="verticalLayout_4">
+        <property name="leftMargin">
+         <number>0</number>
         </property>
-       </widget>
+        <item>
+         <widget class="QListWidget" name="allowedFactions">
+          <property name="enabled">
+           <bool>true</bool>
+          </property>
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="focusPolicy">
+           <enum>Qt::ClickFocus</enum>
+          </property>
+          <property name="editTriggers">
+           <set>QAbstractItemView::NoEditTriggers</set>
+          </property>
+          <property name="selectionMode">
+           <enum>QAbstractItemView::NoSelection</enum>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QCheckBox" name="randomFaction">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="text">
+           <string>Random faction</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
       </item>
      </layout>
     </widget>

+ 80 - 0
mapeditor/scenelayer.cpp

@@ -112,6 +112,86 @@ void PassabilityLayer::update()
 	redraw();
 }
 
+ObjectPickerLayer::ObjectPickerLayer(MapSceneBase * s): AbstractLayer(s)
+{
+}
+
+void ObjectPickerLayer::highlight(std::function<bool(const CGObjectInstance *)> predicate)
+{
+	if(!map)
+		return;
+	
+	if(scene->level == 0 || map->twoLevel)
+	{
+		for(int j = 0; j < map->height; ++j)
+		{
+			for(int i = 0; i < map->width; ++i)
+			{
+				auto tl = map->getTile(int3(i, j, scene->level));
+				auto * obj = tl.topVisitableObj();
+				if(!obj && !tl.blockingObjects.empty())
+					obj = tl.blockingObjects.front();
+				
+				if(obj && predicate(obj))
+					possibleObjects.insert(obj);
+			}
+		}
+	}
+	
+	isActive = true;
+}
+
+bool ObjectPickerLayer::isVisible() const
+{
+	return isShown && isActive;
+}
+
+void ObjectPickerLayer::clear()
+{
+	possibleObjects.clear();
+	isActive = false;
+}
+
+void ObjectPickerLayer::update()
+{
+	if(!map)
+		return;
+	
+	pixmap.reset(new QPixmap(map->width * 32, map->height * 32));
+	pixmap->fill(Qt::transparent);
+	if(isActive)
+		pixmap->fill(QColor(255, 255, 255, 128));
+	
+	
+	QPainter painter(pixmap.get());
+	painter.setCompositionMode(QPainter::CompositionMode_Source);
+	for(auto * obj : possibleObjects)
+	{
+		if(obj->pos.z != scene->level)
+			continue;
+		
+		for(auto & pos : obj->getBlockedPos())
+			painter.fillRect(pos.x * 32, pos.y * 32, 32, 32, QColor(255, 211, 0, 64));
+	}
+	painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
+	redraw();
+}
+
+void ObjectPickerLayer::select(const CGObjectInstance * obj)
+{
+	if(obj && possibleObjects.count(obj))
+	{
+		clear();
+		emit selectionMade(obj);
+	}
+}
+
+void ObjectPickerLayer::discard()
+{
+	clear();
+	emit selectionMade(nullptr);
+}
+
 SelectionTerrainLayer::SelectionTerrainLayer(MapSceneBase * s): AbstractLayer(s)
 {
 }

+ 32 - 1
mapeditor/scenelayer.h

@@ -116,7 +116,7 @@ public:
 	
 	void update() override;
 	
-	void draw(bool onlyDirty = true); //TODO: implement dirty
+	void draw(bool onlyDirty = true);
 	
 	void setDirty(int x, int y);
 	void setDirty(const CGObjectInstance * object);
@@ -127,6 +127,37 @@ private:
 };
 
 
+class ObjectPickerLayer: public AbstractLayer
+{
+	Q_OBJECT
+public:
+	ObjectPickerLayer(MapSceneBase * s);
+	
+	void update() override;
+	bool isVisible() const;
+	
+	template<class T>
+	void highlight()
+	{
+		highlight([](const CGObjectInstance * o){ return dynamic_cast<T*>(o); });
+	}
+	
+	void highlight(std::function<bool(const CGObjectInstance *)> predicate);
+	
+	void clear();
+	
+	void select(const CGObjectInstance *);
+	void discard();
+	
+signals:
+	void selectionMade(const CGObjectInstance *);
+	
+private:
+	bool isActive = false;
+	std::set<const CGObjectInstance *> possibleObjects;
+};
+
+
 class SelectionObjectsLayer: public AbstractLayer
 {
 	Q_OBJECT

+ 1 - 1
mapeditor/validator.cpp

@@ -131,7 +131,7 @@ std::list<Validator::Issue> Validator::validate(const CMap * map)
 					if(!allHeroesOnMap.insert(ins->type).second)
 						issues.emplace_back(QString(tr("Hero %1 has duplicate on map")).arg(ins->type->getNameTranslated().c_str()), false);
 				}
-				else
+				else if(ins->ID != Obj::RANDOM_HERO)
 					issues.emplace_back(QString(tr("Hero %1 has an empty type and must be removed")).arg(ins->instanceName.c_str()), true);
 			}