Răsfoiți Sursa

bigfixing & new file: launcher/jsonutils.cpp
- launcher uses json parser from vcmi lib instead of one from Qt #1469
- fixed abilities overrides for some creatures #1476
- fixed hero portraits in seer huts #1402
- ttf fonts will render text in utf-8 mode. Not really useful at this point
- new settings entry, available in launcher: encoding. Unused for now.

Ivan Savenko 12 ani în urmă
părinte
comite
208df34fc2

+ 1 - 1
Global.h

@@ -632,7 +632,7 @@ namespace vstd
 	template <typename Container>
 	typename Container::const_reference atOrDefault(const Container &r, size_t index, const typename Container::const_reference &defaultValue)
 	{
-		if(isValidIndex(r, index))
+		if(index < r.size())
 			return r[index];
 		
 		return defaultValue;

+ 1 - 1
Mods/WoG/config/wog/creatures.json

@@ -174,7 +174,7 @@
 		"faction": "fortress",
 		"abilities" : 
 		{
-			"SHOOTER" : null
+			"SHOOTING_ARMY" : null
 		},
 		"graphics" :
 		{

+ 3 - 3
client/GUIClasses.cpp

@@ -943,7 +943,7 @@ size_t CComponent::getIndex()
 	case morale:     return val+3;
 	case luck:       return val+3;
 	case building:   return val;
-	case hero:       return CGI->heroh->heroes[subtype]->imageIndex;
+	case hero:       return subtype;
 	case flag:       return subtype;
 	}
 	assert(0);
@@ -965,7 +965,7 @@ std::string CComponent::getDescription()
 	case morale:     return CGI->generaltexth->heroscrn[ 4 - (val>0) + (val<0)];
 	case luck:       return CGI->generaltexth->heroscrn[ 7 - (val>0) + (val<0)];
 	case building:   return CGI->townh->factions[subtype]->town->buildings[BuildingID(val)]->Description();
-	case hero:       return CGI->heroh->heroes[subtype]->name;
+	case hero:       return "";
 	case flag:       return "";
 	}
 	assert(0);
@@ -1007,7 +1007,7 @@ std::string CComponent::getSubtitleInternal()
 	case morale:     return "";
 	case luck:       return "";
 	case building:   return CGI->townh->factions[subtype]->town->buildings[BuildingID(val)]->Name();
-	case hero:       return CGI->heroh->heroes[subtype]->name;
+	case hero:       return "";
 	case flag:       return CGI->generaltexth->capColors[subtype];
 	}
 	assert(0);

+ 2 - 2
client/gui/Fonts.cpp

@@ -270,9 +270,9 @@ void CTrueTypeFont::renderText(SDL_Surface * surface, const std::string & data,
 	{
 		SDL_Surface * rendered;
 		if (blended)
-			rendered = TTF_RenderText_Blended(font.get(), data.c_str(), color);
+			rendered = TTF_RenderUTF8_Blended(font.get(), data.c_str(), color);
 		else
-			rendered = TTF_RenderText_Solid(font.get(), data.c_str(), color);
+			rendered = TTF_RenderUTF8_Solid(font.get(), data.c_str(), color);
 
 		assert(rendered);
 

+ 1 - 1
config/creatures/neutral.json

@@ -117,7 +117,7 @@
 			{
 				"type" : "DRAGON_NATURE"
 			},
-			"FLYING" : null
+			"FLYING_ARMY" : null
 		},
 		"graphics" :
 		{

+ 5 - 1
config/schemas/settings.json

@@ -17,7 +17,7 @@
 			"type" : "object",
 			"default": {},
 			"additionalProperties" : false,
-			"required" : [ "classicCreatureWindow", "playerName", "showfps", "music", "sound" ],
+			"required" : [ "classicCreatureWindow", "playerName", "showfps", "music", "sound", "encoding" ],
 			"properties" : {
 				"classicCreatureWindow" : {
 					"type" : "boolean",
@@ -38,6 +38,10 @@
 				"sound" : {
 					"type" : "number",
 					"default" : 88
+				},
+				"encoding" : {
+					"type" : "string",
+					"default" : "native"
 				}
 			}
 		},

+ 1 - 0
launcher/CMakeLists.txt

@@ -22,6 +22,7 @@ set(launcher_SRCS
 	main.cpp
 	mainwindow_moc.cpp
 	launcherdirs.cpp
+	jsonutils.cpp
 )
 
 set(launcher_FORMS

+ 96 - 0
launcher/jsonutils.cpp

@@ -0,0 +1,96 @@
+#include "StdInc.h"
+#include "jsonutils.h"
+
+static QVariantMap JsonToMap(const JsonMap & json)
+{
+	QVariantMap map;
+	for (auto & entry : json)
+	{
+		map.insert(QString::fromUtf8(entry.first.c_str()), JsonUtils::toVariant(entry.second));
+	}
+	return map;
+}
+
+static QVariantList JsonToList(const JsonVector & json)
+{
+	QVariantList list;
+	for (auto & entry : json)
+	{
+		list.push_back(JsonUtils::toVariant(entry));
+	}
+	return list;
+}
+
+static JsonVector VariantToList(QVariantList variant)
+{
+	JsonVector vector;
+	for (auto & entry : variant)
+	{
+		vector.push_back(JsonUtils::toJson(entry));
+	}
+	return vector;
+}
+
+static JsonMap VariantToMap(QVariantMap variant)
+{
+	JsonMap map;
+	for (auto & entry : variant.toStdMap())
+	{
+		map[entry.first.toUtf8().data()] = JsonUtils::toJson(entry.second);
+	}
+	return map;
+}
+
+namespace JsonUtils
+{
+
+QVariant toVariant(const JsonNode & node)
+{
+	switch (node.getType())
+	{
+		break; case JsonNode::DATA_NULL:   return QVariant();
+		break; case JsonNode::DATA_BOOL:   return QVariant(node.Bool());
+		break; case JsonNode::DATA_FLOAT:  return QVariant(node.Float());
+		break; case JsonNode::DATA_STRING: return QVariant(QString::fromUtf8(node.String().c_str()));
+		break; case JsonNode::DATA_VECTOR: return JsonToList(node.Vector());
+		break; case JsonNode::DATA_STRUCT: return JsonToMap(node.Struct());
+	}
+	return QVariant();
+}
+
+QVariant JsonFromFile(QString filename)
+{
+	QFile file(filename);
+	file.open(QFile::ReadOnly);
+	auto data = file.readAll();
+
+	JsonNode node(data.data(), data.size());
+	return toVariant(node);
+}
+
+JsonNode toJson(QVariant object)
+{
+	JsonNode ret;
+
+	if (object.canConvert<QVariantMap>())
+		ret.Struct() = VariantToMap(object.toMap());
+	if (object.canConvert<QVariantList>())
+		ret.Vector() = VariantToList(object.toList());
+	if (object.canConvert<QString>())
+		ret.String() = object.toString().toUtf8().data();
+	if (object.canConvert<double>())
+		ret.Bool() = object.toFloat();
+	if (object.canConvert<bool>())
+		ret.Bool() = object.toBool();
+
+	return ret;
+}
+
+void JsonToFile(QString filename, QVariant object)
+{
+	std::ofstream file(filename.toUtf8().data(), std::ofstream::binary);
+
+	file << toJson(object);
+}
+
+}

+ 13 - 0
launcher/jsonutils.h

@@ -0,0 +1,13 @@
+#pragma once
+
+#include <QVariant>
+#include "../lib/JsonNode.h"
+
+namespace JsonUtils
+{
+	QVariant toVariant(const JsonNode & node);
+	QVariant JsonFromFile(QString filename);
+
+	JsonNode toJson(QVariant object);
+	void JsonToFile(QString filename, QVariant object);
+}

+ 22 - 19
launcher/modManager/cmodlist.cpp

@@ -1,6 +1,9 @@
 #include "StdInc.h"
 #include "cmodlist.h"
 
+#include "../../lib/JsonNode.h"
+#include "../../lib/filesystem/CFileInputStream.h"
+
 bool CModEntry::compareVersions(QString lesser, QString greater)
 {
 	static const int maxSections = 3; // versions consist from up to 3 sections, major.minor.patch
@@ -25,7 +28,7 @@ bool CModEntry::compareVersions(QString lesser, QString greater)
 	return false;
 }
 
-CModEntry::CModEntry(QJsonObject repository, QJsonObject localData, QJsonValue modSettings, QString modname):
+CModEntry::CModEntry(QVariantMap repository, QVariantMap localData, QVariant modSettings, QString modname):
     repository(repository),
     localData(localData),
     modSettings(modSettings),
@@ -38,7 +41,7 @@ bool CModEntry::isEnabled() const
 	if (!isInstalled())
 		return false;
 
-	return modSettings.toBool(false);
+	return modSettings.toBool();
 }
 
 bool CModEntry::isDisabled() const
@@ -89,24 +92,24 @@ QString CModEntry::getName() const
 QVariant CModEntry::getValue(QString value) const
 {
 	if (repository.contains(value))
-		return repository[value].toVariant();
+		return repository[value];
 
 	if (localData.contains(value))
-		return localData[value].toVariant();
+		return localData[value];
 
 	return QVariant();
 }
 
-QJsonObject CModList::copyField(QJsonObject data, QString from, QString to)
+QVariantMap CModList::copyField(QVariantMap data, QString from, QString to)
 {
-	QJsonObject renamed;
+	QVariantMap renamed;
 
 	for (auto it = data.begin(); it != data.end(); it++)
 	{
-		QJsonObject object = it.value().toObject();
+		QVariant object = it.value();
 
-		object.insert(to, object.value(from));
-		renamed.insert(it.key(), QJsonValue(object));
+		object.toMap().insert(to, object.toMap().value(from));
+		renamed.insert(it.key(), object.toMap());
 	}
 	return renamed;
 }
@@ -116,40 +119,40 @@ void CModList::resetRepositories()
 	repositories.clear();
 }
 
-void CModList::addRepository(QJsonObject data)
+void CModList::addRepository(QVariantMap data)
 {
 	repositories.push_back(copyField(data, "version", "latestVersion"));
 }
 
-void CModList::setLocalModList(QJsonObject data)
+void CModList::setLocalModList(QVariantMap data)
 {
 	localModList = copyField(data, "version", "installedVersion");
 }
 
-void CModList::setModSettings(QJsonObject data)
+void CModList::setModSettings(QVariant data)
 {
-	modSettings = data;
+	modSettings = data.toMap();
 }
 
 CModEntry CModList::getMod(QString modname) const
 {
 	assert(hasMod(modname));
 
-	QJsonObject repo;
-	QJsonObject local = localModList[modname].toObject();
-	QJsonValue settings = modSettings[modname];
+	QVariantMap repo;
+	QVariantMap local = localModList[modname].toMap();
+	QVariant settings = modSettings[modname];
 
 	for (auto entry : repositories)
 	{
 		if (entry.contains(modname))
 		{
 			if (repo.empty())
-				repo = entry[modname].toObject();
+				repo = entry[modname].toMap();
 			else
 			{
 				if (CModEntry::compareVersions(repo["version"].toString(),
-				                               entry[modname].toObject()["version"].toString()))
-					repo = entry[modname].toObject();
+				                               entry[modname].toMap()["version"].toString()))
+					repo = entry[modname].toMap();
 			}
 		}
 	}

+ 14 - 13
launcher/modManager/cmodlist.h

@@ -1,9 +1,10 @@
 #pragma once
 
-#include <QJsonDocument>
-#include <QJsonObject>
+#include <QVariantMap>
 #include <QVariant>
 
+class JsonNode;
+
 namespace ModStatus
 {
 	enum EModStatus
@@ -19,13 +20,13 @@ namespace ModStatus
 class CModEntry
 {
 	// repository contains newest version only (if multiple are available)
-	QJsonObject repository;
-	QJsonObject localData;
-	QJsonValue modSettings;
+	QVariantMap repository;
+	QVariantMap localData;
+	QVariant modSettings;
 
 	QString modname;
 public:
-	CModEntry(QJsonObject repository, QJsonObject localData, QJsonValue modSettings, QString modname);
+	CModEntry(QVariantMap repository, QVariantMap localData, QVariant modSettings, QString modname);
 
 	// installed and enabled
 	bool isEnabled() const;
@@ -52,16 +53,16 @@ public:
 
 class CModList
 {
-	QVector<QJsonObject> repositories;
-	QJsonObject localModList;
-	QJsonObject modSettings;
+	QVector<QVariantMap> repositories;
+	QVariantMap localModList;
+	QVariantMap modSettings;
 
-	QJsonObject copyField(QJsonObject data, QString from, QString to);
+	QVariantMap copyField(QVariantMap data, QString from, QString to);
 public:
 	virtual void resetRepositories();
-	virtual void addRepository(QJsonObject data);
-	virtual void setLocalModList(QJsonObject data);
-	virtual void setModSettings(QJsonObject data);
+	virtual void addRepository(QVariantMap data);
+	virtual void setLocalModList(QVariantMap data);
+	virtual void setModSettings(QVariant data);
 
 	// returns mod by name. Note: mod MUST exist
 	CModEntry getMod(QString modname) const;

+ 3 - 3
launcher/modManager/cmodlistmodel_moc.cpp

@@ -117,21 +117,21 @@ void CModListModel::resetRepositories()
 	endResetModel();
 }
 
-void CModListModel::addRepository(QJsonObject data)
+void CModListModel::addRepository(QVariantMap data)
 {
 	beginResetModel();
 	CModList::addRepository(data);
 	endResetModel();
 }
 
-void CModListModel::setLocalModList(QJsonObject data)
+void CModListModel::setLocalModList(QVariantMap data)
 {
 	beginResetModel();
 	CModList::setLocalModList(data);
 	endResetModel();
 }
 
-void CModListModel::setModSettings(QJsonObject data)
+void CModListModel::setModSettings(QVariant data)
 {
 	beginResetModel();
 	CModList::setModSettings(data);

+ 3 - 3
launcher/modManager/cmodlistmodel_moc.h

@@ -30,9 +30,9 @@ class CModListModel : public QAbstractTableModel, public CModList
 public:
 	/// CModListContainer overrides
 	void resetRepositories();
-	void addRepository(QJsonObject data);
-	void setLocalModList(QJsonObject data);
-	void setModSettings(QJsonObject data);
+	void addRepository(QVariantMap data);
+	void setLocalModList(QVariantMap data);
+	void setModSettings(QVariant data);
 
 	QString modIndexToName(int index) const;
 

+ 12 - 34
launcher/modManager/cmodmanager.cpp

@@ -5,23 +5,9 @@
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/filesystem/CZipLoader.h"
 
+#include "../jsonutils.h"
 #include "../launcherdirs.h"
 
-static QJsonObject JsonFromFile(QString filename)
-{
-	QFile file(filename);
-	file.open(QFile::ReadOnly);
-
-	return QJsonDocument::fromJson(file.readAll()).object();
-}
-
-static void JsonToFile(QString filename, QJsonObject object)
-{
-	QFile file(filename);
-	file.open(QFile::WriteOnly);
-	file.write(QJsonDocument(object).toJson());
-}
-
 static QString detectModArchive(QString path, QString modName)
 {
 	auto files = ZipArchive::listFiles(path.toUtf8().data());
@@ -57,8 +43,8 @@ QString CModManager::settingsPath()
 
 void CModManager::loadModSettings()
 {
-	modSettings = JsonFromFile(settingsPath());
-	modList->setModSettings(modSettings["activeMods"].toObject());
+	modSettings = JsonUtils::JsonFromFile(settingsPath()).toMap();
+	modList->setModSettings(modSettings["activeMods"]);
 }
 
 void CModManager::resetRepositories()
@@ -68,7 +54,7 @@ void CModManager::resetRepositories()
 
 void CModManager::loadRepository(QString file)
 {
-	modList->addRepository(JsonFromFile(file));
+	modList->addRepository(JsonUtils::JsonFromFile(file).toMap());
 }
 
 void CModManager::loadMods()
@@ -78,17 +64,9 @@ void CModManager::loadMods()
 	for (auto modname : installedMods)
 	{
 		ResourceID resID("Mods/" + modname + "/mod.json");
-
-		if (CResourceHandler::get()->existsResource(resID))
-		{
-			auto data = CResourceHandler::get()->load(resID)->readAll();
-			auto array = QByteArray(reinterpret_cast<char *>(data.first.get()), data.second);
-
-			auto mod = QJsonDocument::fromJson(array);
-			assert (mod.isObject()); // TODO: use JsonNode from vcmi code here - QJsonNode parser is just too pedantic
-
-			localMods.insert(QString::fromUtf8(modname.c_str()).toLower(), QJsonValue(mod.object()));
-		}
+		std::string name = *CResourceHandler::get()->getResourceName(resID);
+		auto mod = JsonUtils::JsonFromFile(QString::fromUtf8(name.c_str()));
+		localMods.insert(QString::fromUtf8(modname.c_str()).toLower(), mod);
 	}
 	modList->setLocalModList(localMods);
 }
@@ -209,15 +187,15 @@ bool CModManager::canDisableMod(QString modname)
 
 bool CModManager::doEnableMod(QString mod, bool on)
 {
-	QJsonValue value(on);
-	QJsonObject list = modSettings["activeMods"].toObject();
+	QVariant value(on);
+	QVariantMap list = modSettings["activeMods"].toMap();
 
 	list.insert(mod, value);
 	modSettings.insert("activeMods", list);
 
-	modList->setModSettings(modSettings["activeMods"].toObject());
+	modList->setModSettings(modSettings["activeMods"]);
 
-	JsonToFile(settingsPath(), modSettings);
+	JsonUtils::JsonToFile(settingsPath(), modSettings);
 
 	return true;
 }
@@ -245,7 +223,7 @@ bool CModManager::doInstallMod(QString modname, QString archivePath)
 		return addError(modname, "Failed to extract mod data");
 	}
 
-	QJsonObject json = JsonFromFile(destDir + modDirName + "/mod.json");
+	QVariantMap json = JsonUtils::JsonFromFile(destDir + modDirName + "/mod.json").toMap();
 
 	localMods.insert(modname, json);
 	modList->setLocalModList(localMods);

+ 2 - 2
launcher/modManager/cmodmanager.h

@@ -13,8 +13,8 @@ class CModManager
 	bool doInstallMod(QString mod, QString archivePath);
 	bool doUninstallMod(QString mod);
 
-	QJsonObject modSettings;
-	QJsonObject localMods;
+	QVariantMap modSettings;
+	QVariantMap localMods;
 
 	QStringList recentErrors;
 	bool addError(QString modname, QString message);

+ 12 - 0
launcher/settingsView/csettingsview_moc.cpp

@@ -108,5 +108,17 @@ void CSettingsView::on_plainTextEditRepos_textChanged()
 			node->Vector().push_back(entry);
 		}
 	}
+}
+
+void CSettingsView::on_comboBoxEncoding_currentIndexChanged(int index)
+{
+	std::string encodings[] =
+	{
+	    "native", // right now indicates disabled unicode, may be removed in future
+	    "CP1250", "CP1251", "CP1252", // european Windows-125X encoding
+	    "GBK", "gb2312"  // chinese, aka CP936. Same encoding rules but different font files.
+	};
 
+	Settings node = settings.write["general"]["encoding"];
+	node->String() = encodings[index];
 }

+ 2 - 0
launcher/settingsView/csettingsview_moc.h

@@ -29,6 +29,8 @@ private slots:
 
 	void on_plainTextEditRepos_textChanged();
 
+	void on_comboBoxEncoding_currentIndexChanged(int index);
+
 private:
 	Ui::CSettingsView *ui;
 };

+ 200 - 159
launcher/settingsView/csettingsview_moc.ui

@@ -7,15 +7,42 @@
     <x>0</x>
     <y>0</y>
     <width>700</width>
-    <height>303</height>
+    <height>308</height>
    </rect>
   </property>
   <property name="windowTitle">
    <string>Form</string>
   </property>
   <layout class="QGridLayout" name="gridLayout">
-   <item row="1" column="4">
-    <widget class="QLineEdit" name="lineEditGameDir">
+   <item row="2" column="1">
+    <widget class="QComboBox" name="comboBoxFullScreen">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <item>
+      <property name="text">
+       <string>Off</string>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>On</string>
+      </property>
+     </item>
+    </widget>
+   </item>
+   <item row="11" column="0" colspan="5">
+    <widget class="QPlainTextEdit" name="plainTextEditRepos">
+     <property name="lineWrapMode">
+      <enum>QPlainTextEdit::NoWrap</enum>
+     </property>
+     <property name="plainText">
+      <string>http://downloads.vcmi.eu/Mods/repository.json</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="4">
+    <widget class="QLineEdit" name="lineEditUserDataDir">
      <property name="enabled">
       <bool>false</bool>
      </property>
@@ -26,38 +53,104 @@
       </size>
      </property>
      <property name="text">
-      <string>/usr/share/vcmi</string>
+      <string>/home/user/.vcmi</string>
      </property>
      <property name="readOnly">
       <bool>true</bool>
      </property>
     </widget>
    </item>
-   <item row="7" column="0">
-    <spacer name="spacerRepos">
+   <item row="5" column="0">
+    <widget class="QLabel" name="labelPlayerAI">
+     <property name="text">
+      <string>Player AI</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="2">
+    <spacer name="spacerColumns">
      <property name="orientation">
-      <enum>Qt::Vertical</enum>
+      <enum>Qt::Horizontal</enum>
      </property>
      <property name="sizeType">
       <enum>QSizePolicy::Fixed</enum>
      </property>
      <property name="sizeHint" stdset="0">
       <size>
-       <width>20</width>
-       <height>8</height>
+       <width>8</width>
+       <height>20</height>
       </size>
      </property>
     </spacer>
    </item>
-   <item row="1" column="0">
-    <widget class="QLabel" name="labelResolution">
+   <item row="1" column="4">
+    <widget class="QLineEdit" name="lineEditGameDir">
+     <property name="enabled">
+      <bool>false</bool>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>150</width>
+       <height>0</height>
+      </size>
+     </property>
      <property name="text">
-      <string>Resolution</string>
+      <string>/usr/share/vcmi</string>
+     </property>
+     <property name="readOnly">
+      <bool>true</bool>
      </property>
     </widget>
    </item>
-   <item row="3" column="0">
-    <spacer name="spacerSections">
+   <item row="7" column="3">
+    <widget class="QLabel" name="labelEnableMods">
+     <property name="text">
+      <string>Enable mods on install</string>
+     </property>
+    </widget>
+   </item>
+   <item row="5" column="4">
+    <widget class="QSpinBox" name="spinBoxNetworkPort">
+     <property name="minimum">
+      <number>1024</number>
+     </property>
+     <property name="maximum">
+      <number>65535</number>
+     </property>
+     <property name="value">
+      <number>3030</number>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="3">
+    <widget class="QLabel" name="labelUserDataDir">
+     <property name="text">
+      <string>User data directory</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="3">
+    <widget class="QLabel" name="labelGameDir">
+     <property name="text">
+      <string>Game directory</string>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="0" colspan="2">
+    <widget class="QLabel" name="labelAISettings">
+     <property name="font">
+      <font>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>AI Settings</string>
+     </property>
+    </widget>
+   </item>
+   <item row="8" column="0">
+    <spacer name="spacerRepos">
      <property name="orientation">
       <enum>Qt::Vertical</enum>
      </property>
@@ -66,29 +159,68 @@
      </property>
      <property name="sizeHint" stdset="0">
       <size>
-       <width>56</width>
+       <width>20</width>
        <height>8</height>
       </size>
      </property>
     </spacer>
    </item>
-   <item row="2" column="0">
-    <widget class="QLabel" name="labelFullScreen">
+   <item row="1" column="0">
+    <widget class="QLabel" name="labelResolution">
      <property name="text">
-      <string>Fullscreen</string>
+      <string>Resolution</string>
      </property>
     </widget>
    </item>
-   <item row="5" column="4">
-    <widget class="QSpinBox" name="spinBoxNetworkPort">
-     <property name="minimum">
-      <number>1024</number>
+   <item row="0" column="0" colspan="2">
+    <widget class="QLabel" name="labelVideo">
+     <property name="font">
+      <font>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
      </property>
-     <property name="maximum">
-      <number>65535</number>
+     <property name="text">
+      <string>Video</string>
      </property>
-     <property name="value">
-      <number>3030</number>
+    </widget>
+   </item>
+   <item row="0" column="3" colspan="2">
+    <widget class="QLabel" name="labelDataDirs">
+     <property name="font">
+      <font>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>Data Directories (unchangeable)</string>
+     </property>
+    </widget>
+   </item>
+   <item row="9" column="0" colspan="2">
+    <widget class="QLabel" name="labelRepositories">
+     <property name="font">
+      <font>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>Repositories</string>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="3" colspan="2">
+    <widget class="QLabel" name="labelGeneral">
+     <property name="font">
+      <font>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>General</string>
      </property>
     </widget>
    </item>
@@ -167,14 +299,7 @@
      </item>
     </widget>
    </item>
-   <item row="2" column="3">
-    <widget class="QLabel" name="labelUserDataDir">
-     <property name="text">
-      <string>User data directory</string>
-     </property>
-    </widget>
-   </item>
-   <item row="6" column="4">
+   <item row="7" column="4">
     <widget class="QComboBox" name="comboBoxEnableMods">
      <property name="currentIndex">
       <number>1</number>
@@ -191,100 +316,61 @@
      </item>
     </widget>
    </item>
-   <item row="8" column="0" colspan="2">
-    <widget class="QLabel" name="labelRepositories">
-     <property name="font">
-      <font>
-       <weight>75</weight>
-       <bold>true</bold>
-      </font>
-     </property>
-     <property name="text">
-      <string>Repositories</string>
-     </property>
-    </widget>
-   </item>
-   <item row="10" column="0" colspan="5">
-    <widget class="QPlainTextEdit" name="plainTextEditRepos">
-     <property name="lineWrapMode">
-      <enum>QPlainTextEdit::NoWrap</enum>
-     </property>
-     <property name="plainText">
-      <string>http://downloads.vcmi.eu/Mods/repository.json</string>
-     </property>
-    </widget>
-   </item>
-   <item row="5" column="0">
-    <widget class="QLabel" name="labelPlayerAI">
-     <property name="text">
-      <string>Player AI</string>
-     </property>
-    </widget>
-   </item>
-   <item row="2" column="2">
-    <spacer name="spacerColumns">
+   <item row="3" column="0">
+    <spacer name="spacerSections">
      <property name="orientation">
-      <enum>Qt::Horizontal</enum>
+      <enum>Qt::Vertical</enum>
      </property>
      <property name="sizeType">
       <enum>QSizePolicy::Fixed</enum>
      </property>
      <property name="sizeHint" stdset="0">
       <size>
-       <width>8</width>
-       <height>20</height>
+       <width>56</width>
+       <height>8</height>
       </size>
      </property>
     </spacer>
    </item>
-   <item row="2" column="1">
-    <widget class="QComboBox" name="comboBoxFullScreen">
-     <property name="currentIndex">
-      <number>0</number>
+   <item row="2" column="0">
+    <widget class="QLabel" name="labelFullScreen">
+     <property name="text">
+      <string>Fullscreen</string>
      </property>
+    </widget>
+   </item>
+   <item row="6" column="4">
+    <widget class="QComboBox" name="comboBoxEncoding">
      <item>
       <property name="text">
-       <string>Off</string>
+       <string>Native (unicode disabled)</string>
       </property>
      </item>
      <item>
       <property name="text">
-       <string>On</string>
+       <string>Central European (Windows 1250)</string>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>Cyrillic script (Windows 1251)</string>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>Western European (Windows 1252)</string>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>Simplified Chinese (GBK)</string>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>Simplified Chinese (GB2312)</string>
       </property>
      </item>
-    </widget>
-   </item>
-   <item row="6" column="3">
-    <widget class="QLabel" name="labelEnableMods">
-     <property name="text">
-      <string>Enable mods on install</string>
-     </property>
-    </widget>
-   </item>
-   <item row="2" column="4">
-    <widget class="QLineEdit" name="lineEditUserDataDir">
-     <property name="enabled">
-      <bool>false</bool>
-     </property>
-     <property name="minimumSize">
-      <size>
-       <width>150</width>
-       <height>0</height>
-      </size>
-     </property>
-     <property name="text">
-      <string>/home/user/.vcmi</string>
-     </property>
-     <property name="readOnly">
-      <bool>true</bool>
-     </property>
-    </widget>
-   </item>
-   <item row="6" column="0">
-    <widget class="QLabel" name="labelNeutralAI">
-     <property name="text">
-      <string>Neutral AI</string>
-     </property>
     </widget>
    </item>
    <item row="6" column="1">
@@ -301,62 +387,17 @@
      </item>
     </widget>
    </item>
-   <item row="1" column="3">
-    <widget class="QLabel" name="labelGameDir">
-     <property name="text">
-      <string>Game directory</string>
-     </property>
-    </widget>
-   </item>
-   <item row="4" column="0" colspan="2">
-    <widget class="QLabel" name="labelAISettings">
-     <property name="font">
-      <font>
-       <weight>75</weight>
-       <bold>true</bold>
-      </font>
-     </property>
-     <property name="text">
-      <string>AI Settings</string>
-     </property>
-    </widget>
-   </item>
-   <item row="0" column="0" colspan="2">
-    <widget class="QLabel" name="labelVideo">
-     <property name="font">
-      <font>
-       <weight>75</weight>
-       <bold>true</bold>
-      </font>
-     </property>
-     <property name="text">
-      <string>Video</string>
-     </property>
-    </widget>
-   </item>
-   <item row="0" column="3" colspan="2">
-    <widget class="QLabel" name="labelDataDirs">
-     <property name="font">
-      <font>
-       <weight>75</weight>
-       <bold>true</bold>
-      </font>
-     </property>
+   <item row="6" column="0">
+    <widget class="QLabel" name="labelNeutralAI">
      <property name="text">
-      <string>Data Directories (unchangeable)</string>
+      <string>Neutral AI</string>
      </property>
     </widget>
    </item>
-   <item row="4" column="3" colspan="2">
-    <widget class="QLabel" name="labelGeneral">
-     <property name="font">
-      <font>
-       <weight>75</weight>
-       <bold>true</bold>
-      </font>
-     </property>
+   <item row="6" column="3">
+    <widget class="QLabel" name="labelEncoding">
      <property name="text">
-      <string>General</string>
+      <string>Heroes III character set</string>
      </property>
     </widget>
    </item>

+ 1 - 1
lib/CHeroHandler.cpp

@@ -472,7 +472,7 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod
 {
 	auto object = loadFromJson(data);
 	object->ID = HeroTypeID(heroes.size());
-	object->imageIndex = heroes.size() + 10; // 2 special frames + some extra portraits
+	object->imageIndex = heroes.size() + 30; // 2 special frames + some extra portraits
 
 	heroes.push_back(object);
 

+ 1 - 1
lib/CMakeLists.txt

@@ -95,7 +95,7 @@ set(lib_HEADERS
 
 add_library(vcmi SHARED ${lib_SRCS} ${lib_HEADERS})
 set_target_properties(vcmi PROPERTIES XCODE_ATTRIBUTE_LD_DYLIB_INSTALL_NAME "@executable_path/libvcmi.dylib")
-target_link_libraries(vcmi minizip ${Boost_LIBRARIES} ${SDL_LIBRARY} ${ZLIB_LIBRARIES})
+target_link_libraries(vcmi minizip ${Boost_LIBRARIES} ${SDL_LIBRARY} ${ZLIB_LIBRARIES} ${RT_LIB} ${DL_LIB})
 
 if (NOT APPLE) # Already inside vcmiclient bundle
     install(TARGETS vcmi DESTINATION ${LIB_DIR})

+ 15 - 3
lib/CModHandler.cpp

@@ -141,17 +141,29 @@ bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
 	auto entries = registeredObjects.equal_range(fullID);
 	if (entries.first != entries.second)
 	{
+		size_t matchesFound = 0;
+
 		for (auto it = entries.first; it != entries.second; it++)
 		{
 			if (vstd::contains(allowedScopes, it->second.scope))
 			{
-				request.callback(it->second.id);
-				return true;
+				if (matchesFound == 0) // trigger only once
+					request.callback(it->second.id);
+				matchesFound++;
 			}
 		}
 
+		if (matchesFound == 1)
+			return true; // success, only one matching ID
+
 		// error found. Try to generate some debug info
-		logGlobal->errorStream() << "Unknown identifier " << request.type << "." << request.name << " from mod " << request.localScope;
+		if (matchesFound == 0)
+			logGlobal->errorStream() << "Unknown identifier!";
+		else
+			logGlobal->errorStream() << "Ambiguous identifier request!";
+
+		 logGlobal->errorStream() << "Request for " << request.type << "." << request.name << " from mod " << request.localScope;
+
 		for (auto it = entries.first; it != entries.second; it++)
 		{
 			logGlobal->errorStream() << "\tID is available in mod " << it->second.scope;

+ 3 - 2
lib/CObjectHandler.cpp

@@ -4494,12 +4494,13 @@ void CQuest::getVisitText (MetaString &iwText, std::vector<Component> &component
 		}
 			break;
 		case MISSION_KILL_HERO:
-			components.push_back(Component(Component::HERO, heroPortrait, 0, 0));
+			components.push_back(Component(Component::HERO_PORTRAIT, heroPortrait, 0, 0));
 			if (!isCustom)
 				addReplacements(iwText, text);
 			break;
 		case MISSION_HERO:
-			components.push_back(Component (Component::HERO, m13489val, 0, 0));
+            //FIXME: portrait may not match hero, if custom portrait was set in map editor
+			components.push_back(Component (Component::HERO_PORTRAIT, VLC->heroh->heroes[m13489val]->imageIndex, 0, 0));
 			if (!isCustom)
 				iwText.addReplacement(VLC->heroh->heroes[m13489val]->name);
 			break;

+ 1 - 1
lib/NetPacks.h

@@ -1138,7 +1138,7 @@ struct NewTurn : public CPackForClient //101
 
 struct Component : public CPack //2002 helper for object scrips informations
 {
-	enum EComponentType {PRIM_SKILL, SEC_SKILL, RESOURCE, CREATURE, ARTIFACT, EXPERIENCE, SPELL, MORALE, LUCK, BUILDING, HERO, FLAG};
+	enum EComponentType {PRIM_SKILL, SEC_SKILL, RESOURCE, CREATURE, ARTIFACT, EXPERIENCE, SPELL, MORALE, LUCK, BUILDING, HERO_PORTRAIT, FLAG};
 	ui16 id, subtype; //id uses ^^^ enums, when id==EXPPERIENCE subtype==0 means exp points and subtype==1 levels)
 	si32 val; // + give; - take
 	si16 when; // 0 - now; +x - within x days; -x - per x days

+ 3 - 3
lib/NetPacksLib.cpp

@@ -1011,15 +1011,15 @@ DLL_LINKAGE void HeroLevelUp::applyGs( CGameState *gs )
 	CGHeroInstance* h = gs->getHero(hero->id);
 	h->level = level;
 	//deterministic secondary skills
-	h->skillsInfo.magicSchoolCounter = (++h->skillsInfo.magicSchoolCounter) % h->maxlevelsToMagicSchool();
-	h->skillsInfo.wisdomCounter = (++h->skillsInfo.wisdomCounter) % h->maxlevelsToWisdom();
+	h->skillsInfo.magicSchoolCounter = (h->skillsInfo.magicSchoolCounter + 1) % h->maxlevelsToMagicSchool();
+	h->skillsInfo.wisdomCounter = (h->skillsInfo.wisdomCounter + 1) % h->maxlevelsToWisdom();
 	if (vstd::contains(skills, SecondarySkill::WISDOM))
 		h->skillsInfo.resetWisdomCounter();
 	SecondarySkill spellSchools[] = {
 		SecondarySkill::FIRE_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC, SecondarySkill::EARTH_MAGIC};
 	for (auto skill : spellSchools)
 	{
-		if (vstd::contains(skills, SecondarySkill::WISDOM))
+		if (vstd::contains(skills, skill))
 		{
 			h->skillsInfo.resetMagicSchoolCounter();
 			break;

+ 3 - 1
lib/filesystem/Filesystem.cpp

@@ -300,7 +300,9 @@ std::vector<std::string> CResourceHandler::getAvailableMods()
 				continue;
 			}
 		}
-		foundMods.push_back(name);
+
+		if (!name.empty())
+			foundMods.push_back(name);
 	}
 	return foundMods;
 }