Przeglądaj źródła

Merge remote-tracking branch 'upstream/develop' into develop

Xilmi 10 miesięcy temu
rodzic
commit
371063c44e

+ 11 - 1
ChangeLog.md

@@ -1,10 +1,11 @@
 # VCMI Project Changelog
 
-## 1.5.7 -> 1.6.0 (in development)
+## 1.5.7 -> 1.6.0
 
 ### Major changes
 
 * Greatly improved decision-making of NullkillerAI
+* Implemented support for multiple mod presets allowing player to quickly switch between them in Launcher
 * Implemented handicap system, with options to reduce income and growth in addition to starting resources restriction
 * Game will now show statistics after scenario completion, such as resources or army strength over time
 * Implemented spell quick selection panel in combat
@@ -66,6 +67,7 @@
 * Mutare and Mutare Drake are now Overlord and not Warlock
 * Elixir of Life no longer affects siege machines
 * Banned skills known by hero now have minimal chance (1) instead of 0 to appear on levelup
+* The Transport Artifact victory condition fulfilled by the enemy AI will no longer trigger a victory for human players if "standard victory" is enabled on the map
 
 ### Video / Audio
 
@@ -178,6 +180,12 @@
 
 ### Launcher
 
+* Implemented support for multiple mod presets allowing player to quickly switch between them
+* Added new Start Game page to Launcher which is now used when starting the game
+* Added option to create empty mod preset to quickly disable all mods
+* Added button to update all installed mods to Start Game page
+* Added diagnostics to detect common issues with Heroes III data files
+* Added built-in help descriptions for functionalities such as data files import to better explain them to players
 * It is now always possible to disable or uninstall a mod. Any mods that depend on this mod will be automatically disabled
 * It is now always possible to update a mod, even if there are mods that depend on this mod.
 * It is now possible to enable mod that conflicts with already active mod. Conflicting mods will be automatically disabled
@@ -188,6 +196,8 @@
 * Launcher will now correctly show conflicts on both sides - if mod A is marked as conflicting with B, then information on this conflict will be shown in description of both mod A and mod B (instead of only in mod B)
 * Added Swedish translation
 * Added better diagnostics for gog installer extraction errors
+* It is no longer possible to start installation or update for a mod that is already being downloaded
+* Fixed detection of existing Heroes III Complete or Shadow of Death data files during import
 
 ### Map Editor
 

+ 1 - 1
android/vcmi-app/build.gradle

@@ -26,7 +26,7 @@ android {
 		minSdk = qtMinSdkVersion as Integer
 		targetSdk = qtTargetSdkVersion as Integer // ANDROID_TARGET_SDK_VERSION in the CMake project
 
-		versionCode 1600
+		versionCode 1610
 		versionName "1.6.0"
 
 		setProperty("archivesBaseName", "vcmi")

+ 35 - 27
client/render/CBitmapHandler.cpp

@@ -12,6 +12,7 @@
 
 #include "../renderSDL/SDL_Extensions.h"
 
+#include "../lib/ExceptionsCommon.h"
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/vcmi_endian.h"
 
@@ -112,40 +113,47 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(const ImagePath & path)
 
 	SDL_Surface * ret=nullptr;
 
-	auto readFile = CResourceHandler::get()->load(path)->readAll();
+	try {
+		auto readFile = CResourceHandler::get()->load(path)->readAll();
 
-	if (isPCX(readFile.first.get()))
-	{//H3-style PCX
-		ret = loadH3PCX(readFile.first.get(), readFile.second);
-		if (!ret)
-		{
-			logGlobal->error("Failed to open %s as H3 PCX!", path.getOriginalName());
-			return nullptr;
-		}
-	}
-	else
-	{ //loading via SDL_Image
-		ret = IMG_Load_RW(
-				  //create SDL_RW with our data (will be deleted by SDL)
-				  SDL_RWFromConstMem((void*)readFile.first.get(), (int)readFile.second),
-				  1); // mark it for auto-deleting
-		if (ret)
-		{
-			if (ret->format->palette)
+		if (isPCX(readFile.first.get()))
+		{//H3-style PCX
+			ret = loadH3PCX(readFile.first.get(), readFile.second);
+			if (!ret)
 			{
-				// set correct value for alpha\unused channel
-				// NOTE: might be unnecessary with SDL2
-				for (int i=0; i < ret->format->palette->ncolors; i++)
-					ret->format->palette->colors[i].a = SDL_ALPHA_OPAQUE;
+				logGlobal->error("Failed to open %s as H3 PCX!", path.getOriginalName());
+				return nullptr;
 			}
 		}
 		else
-		{
-			logGlobal->error("Failed to open %s via SDL_Image", path.getOriginalName());
-			logGlobal->error("Reason: %s", IMG_GetError());
-			return nullptr;
+		{ //loading via SDL_Image
+			ret = IMG_Load_RW(
+					  //create SDL_RW with our data (will be deleted by SDL)
+					  SDL_RWFromConstMem((void*)readFile.first.get(), (int)readFile.second),
+					  1); // mark it for auto-deleting
+			if (ret)
+			{
+				if (ret->format->palette)
+				{
+					// set correct value for alpha\unused channel
+					// NOTE: might be unnecessary with SDL2
+					for (int i=0; i < ret->format->palette->ncolors; i++)
+						ret->format->palette->colors[i].a = SDL_ALPHA_OPAQUE;
+				}
+			}
+			else
+			{
+				logGlobal->error("Failed to open %s via SDL_Image", path.getOriginalName());
+				logGlobal->error("Reason: %s", IMG_GetError());
+				return nullptr;
+			}
 		}
 	}
+	catch (const DataLoadingException & e)
+	{
+		logGlobal->error("%s", e.what());
+		return nullptr;
+	}
 
 	// When modifying anything here please check two use cases:
 	// 1) Vampire mansion in Necropolis (not 1st color is transparent)

+ 5 - 5
client/render/IImage.h

@@ -111,12 +111,12 @@ public:
 	virtual bool isTransparent(const Point & coords) const = 0;
 	virtual void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const = 0;
 
-	virtual std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) const = 0;
+	[[nodiscard]] virtual std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) const = 0;
 
-	virtual std::shared_ptr<const ISharedImage> horizontalFlip() const = 0;
-	virtual std::shared_ptr<const ISharedImage> verticalFlip() const = 0;
-	virtual std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const = 0;
-	virtual std::shared_ptr<const ISharedImage> scaleTo(const Point & size, SDL_Palette * palette) const = 0;
+	[[nodiscard]] virtual std::shared_ptr<const ISharedImage> horizontalFlip() const = 0;
+	[[nodiscard]] virtual std::shared_ptr<const ISharedImage> verticalFlip() const = 0;
+	[[nodiscard]] virtual std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const = 0;
+	[[nodiscard]] virtual std::shared_ptr<const ISharedImage> scaleTo(const Point & size, SDL_Palette * palette) const = 0;
 
 
 	virtual ~ISharedImage() = default;

+ 3 - 0
client/renderSDL/ImageScaled.cpp

@@ -43,6 +43,9 @@ void ImageScaled::scaleInteger(int factor)
 
 void ImageScaled::scaleTo(const Point & size)
 {
+	if (source)
+		source = source->scaleTo(size, nullptr);
+
 	if (body)
 		body = body->scaleTo(size * GH.screenHandler().getScalingFactor(), nullptr);
 }

+ 5 - 5
client/renderSDL/SDLImage.h

@@ -57,11 +57,11 @@ public:
 	void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const override;
 	Point dimensions() const override;
 	bool isTransparent(const Point & coords) const override;
-	std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) const override;
-	std::shared_ptr<const ISharedImage> horizontalFlip() const override;
-	std::shared_ptr<const ISharedImage> verticalFlip() const override;
-	std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const override;
-	std::shared_ptr<const ISharedImage> scaleTo(const Point & size, SDL_Palette * palette) const override;
+	[[nodiscard]] std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) const override;
+	[[nodiscard]] std::shared_ptr<const ISharedImage> horizontalFlip() const override;
+	[[nodiscard]] std::shared_ptr<const ISharedImage> verticalFlip() const override;
+	[[nodiscard]] std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const override;
+	[[nodiscard]] std::shared_ptr<const ISharedImage> scaleTo(const Point & size, SDL_Palette * palette) const override;
 
 	friend class SDLImageLoader;
 };

+ 1 - 1
debian/changelog

@@ -2,7 +2,7 @@ vcmi (1.6.0) jammy; urgency=medium
 
   * New upstream release
 
- -- Ivan Savenko <[email protected]>  Fri, 30 Aug 2024 12:00:00 +0200
+ -- Ivan Savenko <[email protected]>  Fri, 20 Dec 2024 12:00:00 +0200
 
 vcmi (1.5.7) jammy; urgency=medium
 

+ 1 - 3
docs/Readme.md

@@ -1,9 +1,7 @@
 # VCMI Project
 
 [![VCMI](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg?branch=develop&event=push)](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
-[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
-[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.6/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.6)
-[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.7/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.7)
+[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.6.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.6.0)
 [![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
 
 VCMI is an open-source recreation of Heroes of Might & Magic III engine, giving it new and extended possibilities.

+ 1 - 1
launcher/eu.vcmi.VCMI.metainfo.xml

@@ -90,7 +90,7 @@
 	</screenshots>
 	<launchable type="desktop-id">vcmilauncher.desktop</launchable>
 	<releases>
-		<release version="1.6.0" date="2024-08-30" type="development"/>
+		<release version="1.6.0" date="2024-12-20" type="stable"/>
 		<release version="1.5.7" date="2024-08-26" type="stable"/>
 		<release version="1.5.6" date="2024-08-04" type="stable"/>
 		<release version="1.5.5" date="2024-07-17" type="stable"/>

+ 0 - 2
launcher/firstLaunch/firstlaunch_moc.h

@@ -55,8 +55,6 @@ class FirstLaunchView : public QWidget
 	bool checkCanInstallExtras();
 	bool checkCanInstallMod(const QString & modID);
 
-	void installMod(const QString & modID);
-
 public:
 	explicit FirstLaunchView(QWidget * parent = nullptr);
 

+ 14 - 12
launcher/modManager/cmodlistview_moc.cpp

@@ -55,7 +55,7 @@ void CModListView::changeEvent(QEvent *event)
 	if(event->type() == QEvent::LanguageChange)
 	{
 		ui->retranslateUi(this);
-		modModel->reloadRepositories();
+		modModel->reloadViewModel();
 	}
 	QWidget::changeEvent(event);
 }
@@ -127,7 +127,7 @@ CModListView::CModListView(QWidget * parent)
 	ui->progressWidget->setVisible(false);
 	dlManager = nullptr;
 
-	modModel->reloadRepositories();
+	modModel->reloadViewModel();
 	if(settings["launcher"]["autoCheckRepositories"].Bool())
 		loadRepositories();
 
@@ -147,7 +147,7 @@ CModListView::CModListView(QWidget * parent)
 void CModListView::reload()
 {
 	modStateModel->reloadLocalState();
-	modModel->reloadRepositories();
+	modModel->reloadViewModel();
 }
 
 void CModListView::loadRepositories()
@@ -611,7 +611,7 @@ void CModListView::on_uninstallButton_clicked()
 		if(modStateModel->isModEnabled(modName))
 			manager->disableMod(modName);
 		manager->uninstallMod(modName);
-		modModel->reloadRepositories();
+		reload();
 	}
 	
 	checkManagerErrors();
@@ -781,7 +781,8 @@ void CModListView::installFiles(QStringList files)
 	{
 		logGlobal->info("Installing repository: started");
 		manager->setRepositoryData(accumulatedRepositoryData);
-		modModel->reloadRepositories();
+		modModel->reloadViewModel();
+		accumulatedRepositoryData.clear();
 
 		static const QString repositoryCachePath = CLauncherDirs::downloadsPath() + "/repositoryCache.json";
 		JsonUtils::jsonToFile(repositoryCachePath, modStateModel->getRepositoryData());
@@ -792,8 +793,7 @@ void CModListView::installFiles(QStringList files)
 	{
 		logGlobal->info("Installing mods: started");
 		installMods(mods);
-		modStateModel->reloadLocalState();
-		modModel->reloadRepositories();
+		reload();
 		logGlobal->info("Installing mods: ended");
 	}
 
@@ -817,8 +817,7 @@ void CModListView::installFiles(QStringList files)
 		{
 			ChroniclesExtractor ce(this, [&prog](float progress) { prog = progress; });
 			ce.installChronicles(exe);
-			modStateModel->reloadLocalState();
-			modModel->reloadRepositories();
+			reload();
 			enableModByName("chronicles");
 			return true;
 		});
@@ -835,8 +834,7 @@ void CModListView::installFiles(QStringList files)
 			ui->pushButton->setEnabled(true);
 			ui->progressWidget->setVisible(false);
 			//update
-			modStateModel->reloadLocalState();
-			modModel->reloadRepositories();
+			reload();
 		}
 		logGlobal->info("Installing chronicles: ended");
 	}
@@ -877,6 +875,8 @@ void CModListView::installMods(QStringList archives)
 		}
 	}
 
+	reload(); // FIXME: better way that won't reset selection
+
 	for(int i = 0; i < modNames.size(); i++)
 	{
 		logGlobal->info("Installing mod '%s'", modNames[i].toStdString());
@@ -884,6 +884,8 @@ void CModListView::installMods(QStringList archives)
 		manager->installMod(modNames[i], archives[i]);
 	}
 
+	reload();
+
 	if (!modsToEnable.empty())
 	{
 		manager->enableMods(modsToEnable);
@@ -1128,7 +1130,7 @@ void CModListView::deletePreset(const QString & presetName)
 void CModListView::activatePreset(const QString & presetName)
 {
 	modStateModel->activatePreset(presetName);
-	modStateModel->reloadLocalState();
+	reload();
 }
 
 void CModListView::renamePreset(const QString & oldPresetName, const QString & newPresetName)

+ 3 - 0
launcher/modManager/cmodlistview_moc.ui

@@ -322,6 +322,9 @@ li.checked::marker { content: &quot;\2612&quot;; }
         <property name="value">
          <number>0</number>
         </property>
+        <property name="alignment">
+         <set>Qt::AlignCenter</set>
+        </property>
         <property name="textVisible">
          <bool>true</bool>
         </property>

+ 0 - 7
launcher/modManager/modstatecontroller.cpp

@@ -192,9 +192,6 @@ bool ModStateController::doInstallMod(QString modname, QString archivePath)
 	if(!QFile(archivePath).exists())
 		return addError(modname, tr("Mod archive is missing"));
 
-	if(localMods.contains(modname))
-		return addError(modname, tr("Mod with such name is already installed"));
-
 	std::vector<std::string> filesToExtract;
 	QString modDirName = ::detectModArchive(archivePath, modname, filesToExtract);
 	if(!modDirName.size())
@@ -237,8 +234,6 @@ bool ModStateController::doInstallMod(QString modname, QString archivePath)
 	QString upperLevel = modDirName.section('/', 0, 0);
 	if(upperLevel != modDirName)
 		removeModDir(destDir + upperLevel);
-	
-	modList->reloadLocalState();
 
 	return true;
 }
@@ -256,8 +251,6 @@ bool ModStateController::doUninstallMod(QString modname)
 	if(!removeModDir(modDir))
 		return addError(modname, tr("Mod is located in a protected directory, please remove it manually:\n") + modFullDir.absolutePath());
 
-	modList->reloadLocalState();
-
 	return true;
 }
 

+ 0 - 2
launcher/modManager/modstatecontroller.h

@@ -27,8 +27,6 @@ class ModStateController : public QObject, public boost::noncopyable
 	bool doInstallMod(QString mod, QString archivePath);
 	bool doUninstallMod(QString mod);
 
-	QVariantMap localMods;
-
 	QStringList recentErrors;
 	bool addError(QString modname, QString message);
 	bool removeModDir(QString mod);

+ 1 - 1
launcher/modManager/modstateitemmodel_moc.cpp

@@ -195,7 +195,7 @@ QVariant ModStateItemModel::headerData(int section, Qt::Orientation orientation,
 	return QVariant();
 }
 
-void ModStateItemModel::reloadRepositories()
+void ModStateItemModel::reloadViewModel()
 {
 	beginResetModel();
 	endResetModel();

+ 1 - 1
launcher/modManager/modstateitemmodel_moc.h

@@ -72,7 +72,7 @@ public:
 	explicit ModStateItemModel(std::shared_ptr<ModStateModel> model, QObject * parent);
 
 	/// CModListContainer overrides
-	void reloadRepositories();
+	void reloadViewModel();
 	void modChanged(QString modID);
 
 	QVariant data(const QModelIndex & index, int role) const override;

+ 1 - 1
lib/modding/ModManager.cpp

@@ -654,7 +654,7 @@ void ModManager::updatePreset(const ModDependenciesResolver & testResolver)
 	for (const auto & modID : newBrokenMods)
 	{
 		const auto & mod = getModDescription(modID);
-		if (vstd::contains(newActiveMods, mod.getTopParentID()))
+		if (mod.getTopParentID().empty() || vstd::contains(newActiveMods, mod.getTopParentID()))
 			modsPreset->setModActive(modID, false);
 	}