diff --git a/Mods/vcmi/Content/Sprites/adventureLayers.png b/Mods/vcmi/Content/Sprites/adventureLayers.png new file mode 100644 index 000000000..2f113e3f0 Binary files /dev/null and b/Mods/vcmi/Content/Sprites/adventureLayers.png differ diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index b6d3618cc..f1b8b11f5 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -572,9 +572,9 @@ bool AdventureMapShortcuts::optionCanToggleLevel() return optionSidePanelActive() && GAME->interface()->cb->getMapSize().z > 1; } -bool AdventureMapShortcuts::optionMapLevelSurface() +int AdventureMapShortcuts::optionMapLevel() { - return mapLevel == 0; + return mapLevel; } bool AdventureMapShortcuts::optionHeroSleeping() diff --git a/client/adventureMap/AdventureMapShortcuts.h b/client/adventureMap/AdventureMapShortcuts.h index b314e7bbd..4268fbfd2 100644 --- a/client/adventureMap/AdventureMapShortcuts.h +++ b/client/adventureMap/AdventureMapShortcuts.h @@ -84,7 +84,7 @@ public: bool optionCanViewQuests(); bool optionCanToggleLevel(); - bool optionMapLevelSurface(); + int optionMapLevel(); bool optionHeroSleeping(); bool optionHeroAwake(); bool optionHeroSelected(); diff --git a/client/adventureMap/AdventureMapWidget.cpp b/client/adventureMap/AdventureMapWidget.cpp index f5fc3f8e2..65c0828a0 100644 --- a/client/adventureMap/AdventureMapWidget.cpp +++ b/client/adventureMap/AdventureMapWidget.cpp @@ -30,7 +30,9 @@ #include "../CPlayerInterface.h" #include "../PlayerLocalState.h" +#include "../../lib/callback/CCallback.h" #include "../../lib/constants/StringConstants.h" +#include "../../lib/mapping/CMapHeader.h" #include "../../lib/filesystem/ResourcePath.h" AdventureMapWidget::AdventureMapWidget( std::shared_ptr shortcuts ) @@ -402,6 +404,8 @@ void AdventureMapWidget::updateActiveStateChildden(CIntObject * widget) { auto container = dynamic_cast(entry); + int mapLevels = GAME->interface()->cb->getMapHeader()->mapLevels; + if (container) { if (container->disableCondition == "heroAwake") @@ -410,11 +414,14 @@ void AdventureMapWidget::updateActiveStateChildden(CIntObject * widget) if (container->disableCondition == "heroSleeping") container->setEnabled(shortcuts->optionHeroSleeping()); - if (container->disableCondition == "mapLayerSurface") // TODO: multilevel support - container->setEnabled(shortcuts->optionMapLevelSurface()); + if (container->disableCondition == "mapLayerSurface") + container->setEnabled(shortcuts->optionMapLevel() == 0); if (container->disableCondition == "mapLayerUnderground") - container->setEnabled(!shortcuts->optionMapLevelSurface()); + container->setEnabled(shortcuts->optionMapLevel() == mapLevels - 1); + + if (container->disableCondition == "mapLayerOther") + container->setEnabled(shortcuts->optionMapLevel() > 0 && shortcuts->optionMapLevel() < mapLevels - 1); if (container->disableCondition == "mapViewMode") container->setEnabled(shortcuts->optionInWorldView()); diff --git a/client/render/AssetGenerator.cpp b/client/render/AssetGenerator.cpp index e7e36540e..868af0946 100644 --- a/client/render/AssetGenerator.cpp +++ b/client/render/AssetGenerator.cpp @@ -56,6 +56,8 @@ void AssetGenerator::initialize() for(int i = 1; i < 9; i++) imageFiles[ImagePath::builtin("CampaignHc" + std::to_string(i) + "Image.png")] = [this, i](){ return createChroniclesCampaignImages(i);}; + + animationFiles[AnimationPath::builtin("SPRITES/adventureLayersButton")] = createAdventureMapButton(ImagePath::builtin("adventureLayers.png")); createPaletteShiftedSprites(); } @@ -428,5 +430,108 @@ AssetGenerator::CanvasPtr AssetGenerator::createPaletteShiftedImage(const Animat canvas.draw(img, Point((32 - img->dimensions().x) / 2, (32 - img->dimensions().y) / 2)); return image; - +} + +void meanImage(AssetGenerator::CanvasPtr dst, std::vector & images) +{ + auto image = dst->getCanvas(); + + for(int x = 0; x < dst->width(); x++) + for(int y = 0; y < dst->height(); y++) + { + int sumR = 0; + int sumG = 0; + int sumB = 0; + int sumA = 0; + for(auto & img : images) + { + auto color = img.getPixel(Point(x, y)); + sumR += color.r; + sumG += color.g; + sumB += color.b; + sumA += color.a; + } + int ct = images.size(); + image.drawPoint(Point(x, y), ColorRGBA(sumR / ct, sumG / ct, sumB / ct, sumA / ct)); + } +} + +AssetGenerator::CanvasPtr AssetGenerator::createAdventureMapButtonClear(const PlayerColor & player) const +{ + auto imageNames = { "iam002", "iam003", "iam004", "iam005", "iam006", "iam007", "iam008", "iam009", "iam010", "iam011" }; + std::vector images; + + CanvasPtr dst = nullptr; + for(auto & imageName : imageNames) + { + auto animation = ENGINE->renderHandler().loadAnimation(AnimationPath::builtin(imageName), EImageBlitMode::COLORKEY); + animation->playerColored(player); + auto image = ENGINE->renderHandler().createImage(animation->getImage(2)->dimensions(), CanvasScalingPolicy::IGNORE); + if(!dst) + dst = ENGINE->renderHandler().createImage(animation->getImage(2)->dimensions(), CanvasScalingPolicy::IGNORE); + Canvas canvas = image->getCanvas(); + canvas.draw(animation->getImage(2), Point(0, 0)); + images.push_back(image->getCanvas()); + } + + meanImage(dst, images); + + return dst; +} + +AssetGenerator::AnimationLayoutMap AssetGenerator::createAdventureMapButton(const ImagePath & overlay) +{ + std::shared_ptr overlayImg = ENGINE->renderHandler().loadImage(ImageLocator(overlay, EImageBlitMode::OPAQUE)); + auto overlayCanvasImg = ENGINE->renderHandler().createImage(overlayImg->dimensions(), CanvasScalingPolicy::IGNORE); + Canvas overlayCanvas = overlayCanvasImg->getCanvas(); + overlayCanvas.draw(overlayImg, Point(0, 0)); + + AnimationLayoutMap layout; + for (PlayerColor color(0); color < PlayerColor::PLAYER_LIMIT; ++color) + { + auto clearButtonImg = createAdventureMapButtonClear(color); + for(int i = 0; i < 4; i++) + { + ImagePath spriteName = ImagePath::builtin(overlay.getOriginalName() + "Btn" + std::to_string(i) + ".png"); + ImagePath spriteNameColor = ImagePath::builtin(overlay.getOriginalName() + "Btn" + std::to_string(i) + "-" + color.toString() + ".png"); + + imageFiles[spriteNameColor] = [overlayCanvasImg, clearButtonImg, i](){ + auto newImg = ENGINE->renderHandler().createImage(overlayCanvasImg->dimensions(), CanvasScalingPolicy::IGNORE); + auto canvas = newImg->getCanvas(); + canvas.draw(clearButtonImg, Point(0, 0)); + switch (i) + { + case 0: + canvas.draw(overlayCanvasImg, Point(0, 0)); + return newImg; + case 1: + canvas.draw(clearButtonImg, Point(1, 1)); + canvas.draw(overlayCanvasImg, Point(1, 1)); + canvas.drawLine(Point(0, 0), Point(newImg->width() - 1, 0), ColorRGBA(0, 0, 0), ColorRGBA(0, 0, 0)); + canvas.drawLine(Point(0, 0), Point(0, newImg->height() - 1), ColorRGBA(0, 0, 0), ColorRGBA(0, 0, 0)); + canvas.drawColorBlended(Rect(0, 0, newImg->width(), 4), ColorRGBA(0, 0, 0, 160)); + canvas.drawColorBlended(Rect(0, 0, 4, newImg->height()), ColorRGBA(0, 0, 0, 160)); + return newImg; + case 2: + canvas.drawTransparent(overlayCanvasImg->getCanvas(), Point(0, 0), 0.25); + return newImg; + default: + canvas.draw(overlayCanvasImg, Point(0, 0)); + canvas.drawLine(Point(0, 0), Point(newImg->width() - 1, 0), ColorRGBA(255, 255, 255), ColorRGBA(255, 255, 255)); + canvas.drawLine(Point(newImg->width() - 1, 0), Point(newImg->width() - 1, newImg->height() - 1), ColorRGBA(255, 255, 255), ColorRGBA(255, 255, 255)); + canvas.drawLine(Point(newImg->width() - 1, newImg->height() - 1), Point(0, newImg->height() - 1), ColorRGBA(255, 255, 255), ColorRGBA(255, 255, 255)); + canvas.drawLine(Point(0, newImg->height() - 1), Point(0, 0), ColorRGBA(255, 255, 255), ColorRGBA(255, 255, 255)); + return newImg; + } + }; + + if(color == PlayerColor(0)) + { + layout[0].push_back(ImageLocator(spriteName, EImageBlitMode::SIMPLE)); + imageFiles[spriteName] = imageFiles[spriteNameColor]; + } + } + } + + return layout; } diff --git a/client/render/AssetGenerator.h b/client/render/AssetGenerator.h index 5baf38daa..bb2f500c7 100644 --- a/client/render/AssetGenerator.h +++ b/client/render/AssetGenerator.h @@ -53,6 +53,8 @@ private: CanvasPtr createSpellTabNone() const; CanvasPtr createChroniclesCampaignImages(int chronicle) const; CanvasPtr createPaletteShiftedImage(const AnimationPath & source, const std::vector & animation, int frameIndex, int paletteShiftCounter) const; + CanvasPtr createAdventureMapButtonClear(const PlayerColor & player) const; + AnimationLayoutMap createAdventureMapButton(const ImagePath & overlay); void createPaletteShiftedSprites(); void generatePaletteShiftedAnimation(const AnimationPath & source, const std::vector & animation); diff --git a/client/render/Canvas.cpp b/client/render/Canvas.cpp index 449645ec3..81017deb6 100644 --- a/client/render/Canvas.cpp +++ b/client/render/Canvas.cpp @@ -238,6 +238,13 @@ Rect Canvas::getRenderArea() const return renderArea; } +ColorRGBA Canvas::getPixel(const Point & position) const +{ + SDL_Color color; + SDL_GetRGBA(CSDL_Ext::getPixel(surface, position.x, position.y), surface->format, &color.r, &color.g, &color.b, &color.a); + return ColorRGBA(color.r, color.g, color.b, color.a); +} + CanvasClipRectGuard::CanvasClipRectGuard(Canvas & canvas, const Rect & rect): surf(canvas.surface) { CSDL_Ext::getClipRect(surf, oldRect); diff --git a/client/render/Canvas.h b/client/render/Canvas.h index 1e41e06c7..37d2f0fa4 100644 --- a/client/render/Canvas.h +++ b/client/render/Canvas.h @@ -122,6 +122,9 @@ public: /// get the render area Rect getRenderArea() const; + + /// get pixel color + ColorRGBA getPixel(const Point & position) const; }; class CanvasClipRectGuard : boost::noncopyable diff --git a/client/render/IImage.h b/client/render/IImage.h index cca4a8305..8b8735c97 100644 --- a/client/render/IImage.h +++ b/client/render/IImage.h @@ -128,6 +128,10 @@ public: /// Returns true if this image is still loading and can't be used virtual bool isLoading() const = 0; + /// When disabled upscaling needs to be done in sync (e.g. because there is no 1x base image) + virtual void setAsyncUpscale(bool on) = 0; + virtual bool getAsyncUpscale() const = 0; + virtual ~ISharedImage() = default; virtual const SDL_Palette * getPalette() const = 0; diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index b113851aa..27fbdfed6 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -322,6 +322,8 @@ std::shared_ptr RenderHandler::loadScaledImage(const ImageLocato img = std::make_shared(imagePathData, optimizeImage); else if(CResourceHandler::get()->existsResource(imagePath)) img = std::make_shared(imagePath, optimizeImage); + else if(locator.scalingFactor == 1) + img = std::dynamic_pointer_cast(assetGenerator->generateImage(imagePath)); if(img) { @@ -331,6 +333,9 @@ std::shared_ptr RenderHandler::loadScaledImage(const ImageLocato img = img->drawShadow((*locator.generateShadow) == SharedImageLocator::ShadowMode::SHADOW_SHEAR); if(isOverlay && generateOverlay && (*locator.generateOverlay) == SharedImageLocator::OverlayMode::OVERLAY_OUTLINE) img = img->drawOutline(Colors::WHITE, 1); + + if(locator.scalingFactor == 1) + img->setAsyncUpscale(false); // no base image, needs to be done in sync } return img; diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index a9096b410..b40b13693 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -271,7 +271,7 @@ std::shared_ptr SDLImageShared::createScaled(const SDLImageShare self->upscalingInProgress = false; }; - if(settings["video"]["asyncUpscaling"].Bool()) + if(settings["video"]["asyncUpscaling"].Bool() && from->getAsyncUpscale()) ENGINE->async().run(scalingTask); else scalingTask(); @@ -284,6 +284,16 @@ bool SDLImageShared::isLoading() const return upscalingInProgress; } +void SDLImageShared::setAsyncUpscale(bool on) +{ + asyncUpscale = on; +} + +bool SDLImageShared::getAsyncUpscale() const +{ + return asyncUpscale; +} + std::shared_ptr SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const { if(upscalingInProgress) diff --git a/client/renderSDL/SDLImage.h b/client/renderSDL/SDLImage.h index 56f99a8cf..45ac3f190 100644 --- a/client/renderSDL/SDLImage.h +++ b/client/renderSDL/SDLImage.h @@ -36,6 +36,7 @@ class SDLImageShared final : public ISharedImage, public std::enable_shared_from Point fullSize; std::atomic_bool upscalingInProgress = false; + bool asyncUpscale = true; // Keep the original palette, in order to do color switching operation void savePalette(); @@ -63,6 +64,8 @@ public: Rect contentRect() const override; bool isLoading() const override; + void setAsyncUpscale(bool on) override; + bool getAsyncUpscale() const override; const SDL_Palette * getPalette() const override; diff --git a/config/widgets/adventureMap.json b/config/widgets/adventureMap.json index 2c6a1ebcc..c6ef397e4 100644 --- a/config/widgets/adventureMap.json +++ b/config/widgets/adventureMap.json @@ -144,6 +144,22 @@ } ] }, + { + "type": "adventureMapContainer", + "hideWhen" : "mapLayerOther", + "area": { "top" : 0, "left": 32, "width" : 32, "height" : 32 }, + "items" : [ + { + "type": "adventureMapButton", + "name": "worldViewOther", + "image" : "adventureLayersButton", + "help" : "core.help.294", + "hotkey": "adventureToggleMapLevel", + "playerColored" : true, + "area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 } + } + ] + }, { "type": "adventureMapButton", "name": "buttonQuestLog", @@ -513,8 +529,8 @@ "items" : [ { "type": "adventureMapButton", - "name": "worldViewSurface", - "image" : "IAM003.DEF", + "name": "worldViewUnderground", + "image" : "IAM010.DEF", "hotkey": "adventureToggleMapLevel", "playerColored" : true, "area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 } @@ -536,8 +552,23 @@ "items" : [ { "type": "adventureMapButton", - "name": "worldViewUnderground", - "image" : "IAM010.DEF", + "name": "worldViewSurface", + "image" : "IAM003.DEF", + "playerColored" : true, + "hotkey": "adventureToggleMapLevel", + "area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 } + } + ] + }, + { + "type": "adventureMapContainer", + "hideWhen" : "mapLayerOther", + "area": { "top" : 343, "left": 79, "width" : 32, "height" : 32 }, + "items" : [ + { + "type": "adventureMapButton", + "name": "worldViewOther", + "image" : "adventureLayersButton", "playerColored" : true, "hotkey": "adventureToggleMapLevel", "area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }