Przeglądaj źródła

Add mod management into map editor

nordsoft 2 lat temu
rodzic
commit
e4c147db16

+ 17 - 1
mapeditor/mainwindow.cpp

@@ -333,7 +333,23 @@ bool MainWindow::openMap(const QString & filenameSelect)
 	CMapService mapService;
 	try
 	{
-		controller.setMap(mapService.loadMap(resId));
+		if(auto header = mapService.loadMapHeader(resId))
+		{
+			auto missingMods = CMapService::verifyMapHeaderMods(*header);
+			CModHandler::Incompatibility::ModList modList;
+			for(const auto & m : missingMods)
+				modList.push_back({m.first, m.second.toString()});
+			
+			if(!modList.empty())
+				throw CModHandler::Incompatibility(std::move(modList));
+			
+			controller.setMap(mapService.loadMap(resId));
+		}
+	}
+	catch(const CModHandler::Incompatibility & e)
+	{
+		QMessageBox::warning(this, "Mods requiered", e.what());
+		return false;
 	}
 	catch(const std::exception & e)
 	{

+ 30 - 0
mapeditor/mapcontroller.cpp

@@ -554,3 +554,33 @@ void MapController::redo()
 	sceneForceUpdate(); //TODO: use smart invalidation (setDirty)
 	main->mapChanged();
 }
+
+ModCompatibilityInfo MapController::modAssessmentAll()
+{
+	ModCompatibilityInfo result;
+	for(auto primaryID : VLC->objtypeh->knownObjects())
+	{
+		for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
+		{
+			auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
+			auto modName = QString::fromStdString(handler->getJsonKey()).split(":").at(0).toStdString();
+			if(modName != "core")
+				result[modName] = VLC->modh->getModInfo(modName).version;
+		}
+	}
+	return result;
+}
+
+ModCompatibilityInfo MapController::modAssessmentMap(const CMap & map)
+{
+	ModCompatibilityInfo result;
+	for(auto obj : map.objects)
+	{
+		auto handler = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID);
+		auto modName = QString::fromStdString(handler->getJsonKey()).split(":").at(0).toStdString();
+		if(modName != "core")
+			result[modName] = VLC->modh->getModInfo(modName).version;
+	}
+	//TODO: terrains?
+	return result;
+}

+ 3 - 0
mapeditor/mapcontroller.h

@@ -54,6 +54,9 @@ public:
 	bool discardObject(int level) const;
 	void createObject(int level, CGObjectInstance * obj) const;
 	bool canPlaceObject(int level, CGObjectInstance * obj, QString & error) const;
+	
+	static ModCompatibilityInfo modAssessmentAll();
+	static ModCompatibilityInfo modAssessmentMap(const CMap & map);
 
 	void undo();
 	void redo();

+ 128 - 1
mapeditor/mapsettings.cpp

@@ -17,8 +17,10 @@
 #include "../lib/CArtHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CGeneralTextHandler.h"
+#include "../lib/CModHandler.h"
 #include "../lib/mapObjects/CGHeroInstance.h"
 #include "../lib/mapObjects/MiscObjects.h"
+#include "../lib/mapping/CMapService.h"
 #include "../lib/StringConstants.h"
 #include "inspector/townbulidingswidget.h" //to convert BuildingID to string
 
@@ -82,6 +84,14 @@ std::vector<JsonNode> linearJsonArray(const JsonNode & json)
 	return result;
 }
 
+void traverseNode(QTreeWidgetItem * item, std::function<void(QTreeWidgetItem*)> action)
+{
+	// Do something with item
+	action(item);
+	for (int i = 0; i < item->childCount(); ++i)
+		traverseNode(item->child(i), action);
+}
+
 MapSettings::MapSettings(MapController & ctrl, QWidget *parent) :
 	QDialog(parent),
 	ui(new Ui::MapSettings),
@@ -98,7 +108,6 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) :
 	
 	show();
 	
-	
 	for(int i = 0; i < controller.map()->allowedAbilities.size(); ++i)
 	{
 		auto * item = new QListWidgetItem(QString::fromStdString(VLC->skillh->objects[i]->getNameTranslated()));
@@ -387,6 +396,61 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) :
 			}
 		}
 	}
+	
+	//mods management
+	//collect all active mods
+	QMap<QString, QTreeWidgetItem*> addedMods;
+	QSet<QString> modsToProcess;
+	ui->treeMods->blockSignals(true);
+	
+	auto createModTreeWidgetItem = [&](QTreeWidgetItem * parent, const CModInfo & modInfo)
+	{
+		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, 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);
+		return item;
+	};
+	
+	for(const auto & modName : VLC->modh->getActiveMods())
+	{
+		QString qmodName = QString::fromStdString(modName);
+		if(qmodName.split(".").size() == 1)
+		{
+			const auto & modInfo = VLC->modh->getModInfo(modName);
+			addedMods[qmodName] = createModTreeWidgetItem(nullptr, modInfo);
+			ui->treeMods->addTopLevelItem(addedMods[qmodName]);
+		}
+		else
+		{
+			modsToProcess.insert(qmodName);
+		}
+	}
+	
+	for(auto qmodIter = modsToProcess.begin(); qmodIter != modsToProcess.end();)
+	{
+		auto qmodName = *qmodIter;
+		auto pieces = qmodName.split(".");
+		assert(pieces.size() > 1);
+		
+		QString qs;
+		for(int i = 0; i < pieces.size() - 1; ++i)
+			qs += pieces[i];
+		
+		if(addedMods.count(qs))
+		{
+			const auto & modInfo = VLC->modh->getModInfo(qmodName.toStdString());
+			addedMods[qmodName] = createModTreeWidgetItem(addedMods[qs], modInfo);
+			modsToProcess.erase(qmodIter);
+			qmodIter = modsToProcess.begin();
+		}
+		else
+			++qmodIter;
+	}
+	ui->treeMods->blockSignals(false);
 }
 
 MapSettings::~MapSettings()
@@ -430,6 +494,22 @@ std::string MapSettings::getMonsterName(int monsterObjectIdx)
 	return name;
 }
 
+void MapSettings::updateModWidgetBasedOnMods(const ModCompatibilityInfo & mods)
+{
+	//Mod management
+	auto widgetAction = [&](QTreeWidgetItem * item)
+	{
+		auto modName = item->data(0, Qt::UserRole).toString().toStdString();
+		item->setCheckState(0, mods.count(modName) ? Qt::Checked : Qt::Unchecked);
+	};
+	
+	for (int i = 0; i < ui->treeMods->topLevelItemCount(); ++i)
+	{
+		QTreeWidgetItem *item = ui->treeMods->topLevelItem(i);
+		traverseNode(item, widgetAction);
+	}
+}
+
 void MapSettings::on_pushButton_clicked()
 {
 	controller.map()->name = ui->mapNameEdit->text().toStdString();
@@ -705,6 +785,23 @@ void MapSettings::on_pushButton_clicked()
 		controller.map()->triggeredEvents.push_back(specialDefeat);
 	}
 	
+	//Mod management
+	auto widgetAction = [&](QTreeWidgetItem * item)
+	{
+		if(item->checkState(0) == Qt::Checked)
+		{
+			auto modName = item->data(0, Qt::UserRole).toString().toStdString();
+			controller.map()->mods[modName] = VLC->modh->getModInfo(modName).version;
+		}
+	};
+	
+	controller.map()->mods.clear();
+	for (int i = 0; i < ui->treeMods->topLevelItemCount(); ++i)
+	{
+		QTreeWidgetItem *item = ui->treeMods->topLevelItem(i);
+		traverseNode(item, widgetAction);
+	}
+	
 	close();
 }
 
@@ -887,3 +984,33 @@ void MapSettings::on_heroLevelLimitCheck_toggled(bool checked)
 	ui->heroLevelLimit->setEnabled(checked);
 }
 
+void MapSettings::on_modResolution_map_clicked()
+{
+	updateModWidgetBasedOnMods(MapController::modAssessmentMap(*controller.map()));
+}
+
+
+void MapSettings::on_modResolution_full_clicked()
+{
+	updateModWidgetBasedOnMods(MapController::modAssessmentAll());
+}
+
+void MapSettings::on_treeMods_itemChanged(QTreeWidgetItem *item, int column)
+{
+	//set state for children
+	for (int i = 0; i < item->childCount(); ++i)
+		item->child(i)->setCheckState(0, item->checkState(0));
+	
+	//set state for parent
+	ui->treeMods->blockSignals(true);
+	if(item->checkState(0) == Qt::Checked)
+	{
+		while(item->parent())
+		{
+			item->parent()->setCheckState(0, Qt::Checked);
+			item = item->parent();
+		}
+	}
+	ui->treeMods->blockSignals(false);
+}
+

+ 8 - 0
mapeditor/mapsettings.h

@@ -34,12 +34,20 @@ private slots:
 
 	void on_heroLevelLimitCheck_toggled(bool checked);
 
+	void on_modResolution_map_clicked();
+
+	void on_modResolution_full_clicked();
+
+	void on_treeMods_itemChanged(QTreeWidgetItem *item, int column);
+
 private:
 	
 	std::string getTownName(int townObjectIdx);
 	std::string getHeroName(int townObjectIdx);
 	std::string getMonsterName(int townObjectIdx);
 	
+	void updateModWidgetBasedOnMods(const ModCompatibilityInfo & mods);
+	
 	template<class T>
 	std::vector<int> getObjectIndexes() const
 	{

+ 80 - 9
mapeditor/mapsettings.ui

@@ -9,7 +9,7 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>470</width>
+    <width>543</width>
     <height>494</height>
    </rect>
   </property>
@@ -23,14 +23,7 @@
    <string>Map settings</string>
   </property>
   <layout class="QGridLayout" name="gridLayout">
-   <item row="2" column="1">
-    <widget class="QPushButton" name="pushButton">
-     <property name="text">
-      <string>Ok</string>
-     </property>
-    </widget>
-   </item>
-   <item row="1" column="1">
+   <item row="3" column="1">
     <widget class="QTabWidget" name="tabWidget">
      <property name="currentIndex">
       <number>0</number>
@@ -139,6 +132,77 @@
        </item>
       </layout>
      </widget>
+     <widget class="QWidget" name="tab_9">
+      <attribute name="title">
+       <string>Mods</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_8">
+       <item>
+        <widget class="QLabel" name="label_6">
+         <property name="text">
+          <string>Mandatory mods for playing this map</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QTreeWidget" name="treeMods">
+         <property name="sizeAdjustPolicy">
+          <enum>QAbstractScrollArea::AdjustIgnored</enum>
+         </property>
+         <attribute name="headerDefaultSectionSize">
+          <number>320</number>
+         </attribute>
+         <column>
+          <property name="text">
+           <string>Mod name</string>
+          </property>
+         </column>
+         <column>
+          <property name="text">
+           <string>Version</string>
+          </property>
+         </column>
+        </widget>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_3">
+         <item>
+          <widget class="QLabel" name="label_5">
+           <property name="text">
+            <string>Automatic assignment</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="modResolution_map">
+           <property name="toolTip">
+            <string>Set required mods based on objects placed on the map. This method may cause problems if you have customized rewards, garrisons, etc from mods</string>
+           </property>
+           <property name="text">
+            <string>Map objects mods</string>
+           </property>
+           <property name="autoDefault">
+            <bool>false</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="modResolution_full">
+           <property name="toolTip">
+            <string>Set all mods having a game content as mandatory</string>
+           </property>
+           <property name="text">
+            <string>Full content mods</string>
+           </property>
+           <property name="autoDefault">
+            <bool>false</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
      <widget class="QWidget" name="tab_6">
       <attribute name="title">
        <string>Events</string>
@@ -369,6 +433,13 @@
      </widget>
     </widget>
    </item>
+   <item row="4" column="1">
+    <widget class="QPushButton" name="pushButton">
+     <property name="text">
+      <string>Ok</string>
+     </property>
+    </widget>
+   </item>
   </layout>
  </widget>
  <resources/>

+ 10 - 0
mapeditor/validator.cpp

@@ -10,6 +10,7 @@
 
 #include "StdInc.h"
 #include "validator.h"
+#include "mapcontroller.h"
 #include "ui_validator.h"
 #include "../lib/mapObjects/MapObjects.h"
 #include "../lib/CHeroHandler.h"
@@ -158,6 +159,15 @@ std::list<Validator::Issue> Validator::validate(const CMap * map)
 			issues.emplace_back("Map name is not specified", false);
 		if(map->description.empty())
 			issues.emplace_back("Map description is not specified", false);
+		
+		//verificationfor mods
+		for(auto & mod : MapController::modAssessmentMap(*map))
+		{
+			if(!map->mods.count(mod.first))
+			{
+				issues.emplace_back(QString("Map contains object from mod \"%1\", but doesn't require it").arg(QString::fromStdString(VLC->modh->getModInfo(mod.first).name)), true);
+			}
+		}
 	}
 	catch(const std::exception & e)
 	{

+ 1 - 1
mapeditor/windownewmap.cpp

@@ -264,7 +264,7 @@ void WindowNewMap::on_okButton_clicked()
 		nmap = f.get();
 	}
 	
-
+	nmap->mods = MapController::modAssessmentAll();
 	static_cast<MainWindow*>(parent())->controller.setMap(std::move(nmap));
 	static_cast<MainWindow*>(parent())->initializeMap(true);
 	close();