diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 1c94e0d7d..cf1c896a1 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -430,9 +430,10 @@ void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack) { callAllInterfaces(cl, &IGameEventsReceiver::gameOver, pack.player, pack.victoryLossCheckResult); + bool localHumanWinsGame = vstd::contains(cl.playerint, pack.player) && cl.getPlayerState(pack.player)->human && pack.victoryLossCheckResult.victory(); bool lastHumanEndsGame = CSH->howManyPlayerInterfaces() == 1 && vstd::contains(cl.playerint, pack.player) && cl.getPlayerState(pack.player)->human && !settings["session"]["spectate"].Bool(); - if(lastHumanEndsGame) + if(lastHumanEndsGame || localHumanWinsGame) { assert(adventureInt); if(adventureInt) diff --git a/client/render/IImage.h b/client/render/IImage.h index a6e4e8b7b..8f10dc5fd 100644 --- a/client/render/IImage.h +++ b/client/render/IImage.h @@ -115,7 +115,7 @@ public: virtual std::shared_ptr horizontalFlip() const = 0; virtual std::shared_ptr verticalFlip() const = 0; - virtual std::shared_ptr scaleInteger(int factor, SDL_Palette * palette) const = 0; + virtual std::shared_ptr scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const = 0; virtual std::shared_ptr scaleTo(const Point & size, SDL_Palette * palette) const = 0; diff --git a/client/renderSDL/CBitmapFont.cpp b/client/renderSDL/CBitmapFont.cpp index a932e6368..4287221e7 100644 --- a/client/renderSDL/CBitmapFont.cpp +++ b/client/renderSDL/CBitmapFont.cpp @@ -201,7 +201,7 @@ CBitmapFont::CBitmapFont(const std::string & filename): static const std::map filterNameToEnum = { { "nearest", EScalingAlgorithm::NEAREST}, { "bilinear", EScalingAlgorithm::BILINEAR}, - { "xbrz", EScalingAlgorithm::XBRZ} + { "xbrz", EScalingAlgorithm::XBRZ_ALPHA} }; auto filterName = settings["video"]["fontUpscalingFilter"].String(); diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 84d37e7ee..df10c01d8 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -278,7 +278,7 @@ void SDLImageShared::optimizeSurface() } } -std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL_Palette * palette) const +std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode mode) const { if (factor <= 0) throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor)); @@ -293,7 +293,13 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL if(preScaleFactor == factor) return shared_from_this(); else if(preScaleFactor == 1) - scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ); + { + // dump heuristics to differentiate tileable UI elements from map object / combat assets + if (mode == EImageBlitMode::OPAQUE || mode == EImageBlitMode::COLORKEY || mode == EImageBlitMode::SIMPLE) + scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ_OPAQUE); + else + scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ_ALPHA); + } else scaled = CSDL_Ext::scaleSurface(surf, (surf->w / preScaleFactor) * factor, (surf->h / preScaleFactor) * factor); @@ -589,12 +595,12 @@ void SDLImageRGB::scaleTo(const Point & size) void SDLImageIndexed::scaleInteger(int factor) { - image = image->scaleInteger(factor, currentPalette); + image = image->scaleInteger(factor, currentPalette, blitMode); } void SDLImageRGB::scaleInteger(int factor) { - image = image->scaleInteger(factor, nullptr); + image = image->scaleInteger(factor, nullptr, blitMode); } void SDLImageRGB::exportBitmap(const boost::filesystem::path & path) const diff --git a/client/renderSDL/SDLImage.h b/client/renderSDL/SDLImage.h index 833ccec42..c1f6fcf77 100644 --- a/client/renderSDL/SDLImage.h +++ b/client/renderSDL/SDLImage.h @@ -60,7 +60,7 @@ public: std::shared_ptr createImageReference(EImageBlitMode mode) const override; std::shared_ptr horizontalFlip() const override; std::shared_ptr verticalFlip() const override; - std::shared_ptr scaleInteger(int factor, SDL_Palette * palette) const override; + std::shared_ptr scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const override; std::shared_ptr scaleTo(const Point & size, SDL_Palette * palette) const override; friend class SDLImageLoader; diff --git a/client/renderSDL/SDL_Extensions.cpp b/client/renderSDL/SDL_Extensions.cpp index 237f6800d..63a9089de 100644 --- a/client/renderSDL/SDL_Extensions.cpp +++ b/client/renderSDL/SDL_Extensions.cpp @@ -683,12 +683,17 @@ SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor case EScalingAlgorithm::BILINEAR: xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); break; - case EScalingAlgorithm::XBRZ: - tbb::parallel_for(tbb::blocked_range(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate](const tbb::blocked_range & r) + case EScalingAlgorithm::XBRZ_ALPHA: + case EScalingAlgorithm::XBRZ_OPAQUE: + { + auto format = algorithm == EScalingAlgorithm::XBRZ_OPAQUE ? xbrz::ColorFormat::ARGB_CLAMPED : xbrz::ColorFormat::ARGB; + tbb::parallel_for(tbb::blocked_range(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate, format](const tbb::blocked_range & r) { - xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, xbrz::ColorFormat::ARGB, {}, r.begin(), r.end()); + + xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}, r.begin(), r.end()); }); break; + } default: throw std::runtime_error("invalid scaling algorithm!"); } diff --git a/client/renderSDL/SDL_Extensions.h b/client/renderSDL/SDL_Extensions.h index 06fb17682..acdb0dc5a 100644 --- a/client/renderSDL/SDL_Extensions.h +++ b/client/renderSDL/SDL_Extensions.h @@ -31,7 +31,8 @@ enum class EScalingAlgorithm : int8_t { NEAREST, BILINEAR, - XBRZ + XBRZ_OPAQUE, // xbrz, image edges are considered to have same color as pixel inside image + XBRZ_ALPHA // xbrz, image edges are considered to be transparent }; namespace CSDL_Ext diff --git a/client/windows/settings/SettingsMainWindow.cpp b/client/windows/settings/SettingsMainWindow.cpp index 05562bae5..d5bd2831f 100644 --- a/client/windows/settings/SettingsMainWindow.cpp +++ b/client/windows/settings/SettingsMainWindow.cpp @@ -198,8 +198,3 @@ void SettingsMainWindow::onScreenResize() if (tab) tab->updateResolutionSelector(); } - -void SettingsMainWindow::inputModeChanged(InputMode mode) -{ - tabContentArea->reset(); -} diff --git a/client/windows/settings/SettingsMainWindow.h b/client/windows/settings/SettingsMainWindow.h index 5813ac46b..7b26921df 100644 --- a/client/windows/settings/SettingsMainWindow.h +++ b/client/windows/settings/SettingsMainWindow.h @@ -42,6 +42,5 @@ public: void showAll(Canvas & to) override; void onScreenResize() override; - void inputModeChanged(InputMode mode) override; }; diff --git a/client/xBRZ/xbrz.cpp b/client/xBRZ/xbrz.cpp index 2fab57f3c..1eda5a46b 100644 --- a/client/xBRZ/xbrz.cpp +++ b/client/xBRZ/xbrz.cpp @@ -1195,6 +1195,22 @@ void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth } break; + case ColorFormat::ARGB_CLAMPED: + switch (factor) + { + case 2: + return scaleImage, ColorDistanceARGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage, ColorDistanceARGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage, ColorDistanceARGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage, ColorDistanceARGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage, ColorDistanceARGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + break; + case ColorFormat::ARGB: switch (factor) { @@ -1238,6 +1254,7 @@ bool xbrz::equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, doub case ColorFormat::RGB: return ColorDistanceRGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; case ColorFormat::ARGB: + case ColorFormat::ARGB_CLAMPED: return ColorDistanceARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; case ColorFormat::ARGB_UNBUFFERED: return ColorDistanceUnbufferedARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; diff --git a/client/xBRZ/xbrz.h b/client/xBRZ/xbrz.h index 492fb43ad..4e646dba6 100644 --- a/client/xBRZ/xbrz.h +++ b/client/xBRZ/xbrz.h @@ -44,6 +44,7 @@ enum class ColorFormat //from high bits -> low bits, 8 bit per channel { RGB, //8 bit for each red, green, blue, upper 8 bits unused ARGB, //including alpha channel, BGRA byte order on little-endian machines + ARGB_CLAMPED, // like ARGB, but edges are treated as opaque, with same color as edge ARGB_UNBUFFERED, //like ARGB, but without the one-time buffer creation overhead (ca. 100 - 300 ms) at the expense of a slightly slower scaling time }; diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index 6e23fb4f6..d7b78b95e 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -441,9 +441,10 @@ void CModListView::selectMod(const QModelIndex & index) Helper::enableScrollBySwiping(ui->modInfoBrowser); Helper::enableScrollBySwiping(ui->changelogBrowser); - QStringList notInstalledDependencies = this->getModsToInstall(modName); - QStringList unavailableDependencies = this->findUnavailableMods(notInstalledDependencies); + QStringList notInstalledDependencies = getModsToInstall(modName); + QStringList unavailableDependencies = findUnavailableMods(notInstalledDependencies); bool translationMismatch = mod.isTranslation() && CGeneralTextHandler::getPreferredLanguage() != mod.getBaseLanguage().toStdString(); + bool modIsBeingDownloaded = enqueuedModDownloads.contains(mod.getID()); ui->disableButton->setVisible(modStateModel->isModInstalled(mod.getID()) && modStateModel->isModEnabled(mod.getID())); ui->enableButton->setVisible(modStateModel->isModInstalled(mod.getID()) && !modStateModel->isModEnabled(mod.getID())); @@ -454,9 +455,9 @@ void CModListView::selectMod(const QModelIndex & index) // Block buttons if action is not allowed at this time ui->disableButton->setEnabled(true); ui->enableButton->setEnabled(notInstalledDependencies.empty() && !translationMismatch); - ui->installButton->setEnabled(unavailableDependencies.empty()); + ui->installButton->setEnabled(unavailableDependencies.empty() && !modIsBeingDownloaded); ui->uninstallButton->setEnabled(true); - ui->updateButton->setEnabled(unavailableDependencies.empty()); + ui->updateButton->setEnabled(unavailableDependencies.empty() && !modIsBeingDownloaded); loadScreenshots(); } @@ -544,9 +545,6 @@ QStringList CModListView::getModsToInstall(QString mod) candidates.pop_back(); processed.push_back(potentialToInstall); - if (modStateModel->isModExists(potentialToInstall) && modStateModel->isModInstalled(potentialToInstall)) - continue; - if (modStateModel->isSubmod(potentialToInstall)) { QString topParent = modStateModel->getTopParent(potentialToInstall); @@ -560,7 +558,8 @@ QStringList CModListView::getModsToInstall(QString mod) potentialToInstall = modStateModel->getTopParent(potentialToInstall); } - result.push_back(potentialToInstall); + if (modStateModel->isModExists(potentialToInstall) && !modStateModel->isModInstalled(potentialToInstall)) + result.push_back(potentialToInstall); if (modStateModel->isModExists(potentialToInstall)) { @@ -580,6 +579,8 @@ void CModListView::on_updateButton_clicked() { QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString(); doUpdateMod(modName); + + ui->updateButton->setEnabled(false); } void CModListView::doUpdateMod(const QString & modName) @@ -587,14 +588,14 @@ void CModListView::doUpdateMod(const QString & modName) auto targetMod = modStateModel->getMod(modName); if(targetMod.isUpdateAvailable()) - downloadFile(modName + ".zip", targetMod.getDownloadUrl(), modName, targetMod.getDownloadSizeBytes()); + downloadMod(targetMod); for(const auto & name : getModsToInstall(modName)) { auto mod = modStateModel->getMod(name); // update required mod, install missing (can be new dependency) if(mod.isUpdateAvailable() || !mod.isInstalled()) - downloadFile(name + ".zip", mod.getDownloadUrl(), name, mod.getDownloadSizeBytes()); + downloadMod(mod); } } @@ -617,14 +618,18 @@ void CModListView::on_installButton_clicked() { QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString(); - for(const auto & name : getModsToInstall(modName)) - { - auto mod = modStateModel->getMod(name); - if(mod.isAvailable()) - downloadFile(name + ".zip", mod.getDownloadUrl(), name, mod.getDownloadSizeBytes()); - else if(!modStateModel->isModEnabled(name)) - enableModByName(name); - } + doInstallMod(modName); + + ui->installButton->setEnabled(false); +} + +void CModListView::downloadMod(const ModState & mod) +{ + if (enqueuedModDownloads.contains(mod.getID())) + return; + + enqueuedModDownloads.push_back(mod.getID()); + downloadFile(mod.getID() + ".zip", mod.getDownloadUrl(), mod.getName(), mod.getDownloadSizeBytes()); } void CModListView::downloadFile(QString file, QUrl url, QString description, qint64 sizeBytes) @@ -697,6 +702,7 @@ void CModListView::downloadFinished(QStringList savedFiles, QStringList failedFi doInstallFiles = true; } + enqueuedModDownloads.clear(); dlManager->deleteLater(); dlManager = nullptr; @@ -796,6 +802,7 @@ void CModListView::installFiles(QStringList files) { ChroniclesExtractor ce(this, [&prog](float progress) { prog = progress; }); ce.installChronicles(exe); + enableModByName("chronicles"); return true; }); @@ -959,11 +966,13 @@ void CModListView::on_screenshotsList_clicked(const QModelIndex & index) void CModListView::doInstallMod(const QString & modName) { - for(const auto & name : modStateModel->getMod(modName).getDependencies()) + for(const auto & name : getModsToInstall(modName)) { auto mod = modStateModel->getMod(name); - if(!mod.isInstalled()) - downloadFile(name + ".zip", mod.getDownloadUrl(), name, mod.getDownloadSizeBytes()); + if(mod.isAvailable()) + downloadMod(mod); + else if(!modStateModel->isModEnabled(name)) + enableModByName(name); } } diff --git a/launcher/modManager/cmodlistview_moc.h b/launcher/modManager/cmodlistview_moc.h index cf23a4319..8c2edd40a 100644 --- a/launcher/modManager/cmodlistview_moc.h +++ b/launcher/modManager/cmodlistview_moc.h @@ -37,6 +37,8 @@ class CModListView : public QWidget CModFilterModel * filterModel; CDownloadManager * dlManager; + QStringList enqueuedModDownloads; + void setupModModel(); void setupFilterModel(); void setupModsView(); @@ -111,6 +113,7 @@ public: /// returns true if mod is currently installed bool isModInstalled(const QString & modName); + void downloadMod(const ModState & mod); void downloadFile(QString file, QUrl url, QString description, qint64 sizeBytes = 0); void installFiles(QStringList mods); diff --git a/launcher/startGame/StartGameTab.cpp b/launcher/startGame/StartGameTab.cpp index 96e1395c8..8e947d124 100644 --- a/launcher/startGame/StartGameTab.cpp +++ b/launcher/startGame/StartGameTab.cpp @@ -102,7 +102,7 @@ void StartGameTab::refreshGameData() }; bool missingSoundtrack = !CResourceHandler::get()->existsResource(AudioPath::builtin("Music/MainMenu")); - bool missingVideoFiles = !CResourceHandler::get()->existsResource(VideoPath::builtin("Video/H3Intro")); + bool missingVideoFiles = !CResourceHandler::get()->existsResource(VideoPath::builtin("Video/H3Intro")) && !CResourceHandler::get()->existsResource(ResourcePath("Video/H3Intro", EResType::VIDEO_LOW_QUALITY)); bool missingGameFiles = false; bool missingCampaings = false; diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 7df3c63e9..4b8f99d58 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1400,8 +1400,10 @@ bool CGameState::checkForVictory(const PlayerColor & player, const EventConditio case EventCondition::TRANSPORT: { const auto * t = getTown(condition.objectID); - return (t->visitingHero && t->visitingHero->getOwner() == player && t->visitingHero->hasArt(condition.objectType.as())) || - (t->garrisonHero && t->garrisonHero->getOwner() == player && t->garrisonHero->hasArt(condition.objectType.as())); + bool garrisonedWon = t->garrisonHero && t->garrisonHero->getOwner() == player && t->garrisonHero->hasArt(condition.objectType.as()); + bool visitingWon = t->visitingHero && t->visitingHero->getOwner() == player && t->visitingHero->hasArt(condition.objectType.as()); + + return garrisonedWon || visitingWon; } case EventCondition::DAYS_PASSED: { @@ -1436,6 +1438,9 @@ PlayerColor CGameState::checkForStandardWin() const TeamID winnerTeam = TeamID::NO_TEAM; for(const auto & elem : players) { + if(elem.second.status == EPlayerStatus::WINNER) + return elem.second.color; + if(elem.second.status == EPlayerStatus::INGAME && elem.first.isValidPlayer()) { if(supposedWinner == PlayerColor::NEUTRAL) diff --git a/lib/modding/ModDescription.cpp b/lib/modding/ModDescription.cpp index d27253ea3..656036060 100644 --- a/lib/modding/ModDescription.cpp +++ b/lib/modding/ModDescription.cpp @@ -120,8 +120,7 @@ const JsonNode & ModDescription::getLocalizedValue(const std::string & keyName) const JsonNode & ModDescription::getValue(const std::string & keyName) const { - const JsonNode & localValue = getLocalValue(keyName); - if (localValue.isNull()) + if (!isInstalled() || isUpdateAvailable()) return getRepositoryValue(keyName); else return getLocalValue(keyName);