diff --git a/client/adventureMap/AdventureMapInterface.cpp b/client/adventureMap/AdventureMapInterface.cpp index 020f4a323..3b918de77 100644 --- a/client/adventureMap/AdventureMapInterface.cpp +++ b/client/adventureMap/AdventureMapInterface.cpp @@ -34,6 +34,7 @@ #include "../render/IImage.h" #include "../render/IRenderHandler.h" #include "../render/IScreenHandler.h" +#include "../render/AssetGenerator.h" #include "../CMT.h" #include "../PlayerLocalState.h" #include "../CPlayerInterface.h" @@ -64,6 +65,8 @@ AdventureMapInterface::AdventureMapInterface(): pos.w = GH.screenDimensions().x; pos.h = GH.screenDimensions().y; + AssetGenerator::createPaletteShiftedSprites(); + shortcuts = std::make_shared(*this); widget = std::make_shared(shortcuts); diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index 9b56ccd9d..1a2ae9089 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -121,21 +121,31 @@ void MapTileStorage::load(size_t index, const AnimationPath & filename, EImageBl terrainAnimations[3]->horizontalFlip(); } -std::shared_ptr MapTileStorage::find(size_t fileIndex, size_t rotationIndex, size_t imageIndex) +std::shared_ptr MapTileStorage::find(size_t fileIndex, size_t rotationIndex, size_t imageIndex, size_t groupIndex) { const auto & animation = animations[fileIndex][rotationIndex]; if (animation) - return animation->getImage(imageIndex); + return animation->getImage(imageIndex, groupIndex); else return nullptr; } +int MapTileStorage::groupCount(size_t fileIndex, size_t rotationIndex, size_t imageIndex) +{ + const auto & animation = animations[fileIndex][rotationIndex]; + if (animation) + for(int i = 0;; i++) + if(!animation->getImage(imageIndex, i, false)) + return i; + return 1; +} + MapRendererTerrain::MapRendererTerrain() : storage(VLC->terrainTypeHandler->objects.size()) { logGlobal->debug("Loading map terrains"); for(const auto & terrain : VLC->terrainTypeHandler->objects) - storage.load(terrain->getIndex(), terrain->tilesFilename, EImageBlitMode::OPAQUE); + storage.load(terrain->getIndex(), AnimationPath::builtin(terrain->tilesFilename.getName() + (terrain->paletteAnimation.size() ? "_Shifted": "")), EImageBlitMode::OPAQUE); logGlobal->debug("Done loading map terrains"); } @@ -147,7 +157,8 @@ void MapRendererTerrain::renderTile(IMapRendererContext & context, Canvas & targ int32_t imageIndex = mapTile.terView; int32_t rotationIndex = mapTile.extTileFlags % 4; - const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex); + int groupCount = storage.groupCount(terrainIndex, rotationIndex, imageIndex); + const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex, groupCount > 1 ? context.terrainImageIndex(groupCount) : 0); assert(image); if (!image) @@ -156,9 +167,6 @@ void MapRendererTerrain::renderTile(IMapRendererContext & context, Canvas & targ return; } - for( auto const & element : mapTile.getTerrain()->paletteAnimation) - image->shiftPalette(element.start, element.length, context.terrainImageIndex(element.length)); - target.draw(image, Point(0, 0)); } @@ -176,7 +184,7 @@ MapRendererRiver::MapRendererRiver() { logGlobal->debug("Loading map rivers"); for(const auto & river : VLC->riverTypeHandler->objects) - storage.load(river->getIndex(), river->tilesFilename, EImageBlitMode::COLORKEY); + storage.load(river->getIndex(), AnimationPath::builtin(river->tilesFilename.getName() + (river->paletteAnimation.size() ? "_Shifted": "")), EImageBlitMode::COLORKEY); logGlobal->debug("Done loading map rivers"); } @@ -191,10 +199,8 @@ void MapRendererRiver::renderTile(IMapRendererContext & context, Canvas & target int32_t imageIndex = mapTile.riverDir; int32_t rotationIndex = (mapTile.extTileFlags >> 2) % 4; - const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex); - - for( auto const & element : mapTile.getRiver()->paletteAnimation) - image->shiftPalette(element.start, element.length, context.terrainImageIndex(element.length)); + int groupCount = storage.groupCount(terrainIndex, rotationIndex, imageIndex); + const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex, groupCount > 1 ? context.terrainImageIndex(groupCount) : 0); target.draw(image, Point(0, 0)); } diff --git a/client/mapView/MapRenderer.h b/client/mapView/MapRenderer.h index e248f8cec..085846c63 100644 --- a/client/mapView/MapRenderer.h +++ b/client/mapView/MapRenderer.h @@ -33,7 +33,8 @@ class MapTileStorage public: explicit MapTileStorage(size_t capacity); void load(size_t index, const AnimationPath & filename, EImageBlitMode blitMode); - std::shared_ptr find(size_t fileIndex, size_t rotationIndex, size_t imageIndex); + std::shared_ptr find(size_t fileIndex, size_t rotationIndex, size_t imageIndex, size_t groupIndex = 0); + int groupCount(size_t fileIndex, size_t rotationIndex, size_t imageIndex); }; class MapRendererTerrain diff --git a/client/render/AssetGenerator.cpp b/client/render/AssetGenerator.cpp index 5aeb5f734..a1e4befe7 100644 --- a/client/render/AssetGenerator.cpp +++ b/client/render/AssetGenerator.cpp @@ -16,12 +16,16 @@ #include "../render/Canvas.h" #include "../render/ColorFilter.h" #include "../render/IRenderHandler.h" +#include "../render/CAnimation.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/GameSettings.h" #include "../lib/IGameSettings.h" #include "../lib/json/JsonNode.h" #include "../lib/VCMI_Lib.h" +#include "../lib/RiverHandler.h" +#include "../lib/RoadHandler.h" +#include "../lib/TerrainHandler.h" void AssetGenerator::generateAll() { @@ -32,6 +36,7 @@ void AssetGenerator::generateAll() createCombatUnitNumberWindow(); createCampaignBackground(); createChroniclesCampaignImages(); + createPaletteShiftedSprites(); } void AssetGenerator::createAdventureOptionsCleanBackground() @@ -326,3 +331,106 @@ void AssetGenerator::createChroniclesCampaignImages() image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); } } + +void AssetGenerator::createPaletteShiftedSprites() +{ + std::vector tiles; + std::vector>> paletteAnimations; + for(auto entity : VLC->terrainTypeHandler->objects) + { + if(entity->paletteAnimation.size()) + { + tiles.push_back(entity->tilesFilename.getName()); + std::vector> tmpAnim; + for(auto & animEntity : entity->paletteAnimation) + tmpAnim.push_back(animEntity); + paletteAnimations.push_back(tmpAnim); + } + } + for(auto entity : VLC->riverTypeHandler->objects) + { + if(entity->paletteAnimation.size()) + { + tiles.push_back(entity->tilesFilename.getName()); + std::vector> tmpAnim; + for(auto & animEntity : entity->paletteAnimation) + tmpAnim.push_back(animEntity); + paletteAnimations.push_back(tmpAnim); + } + } + + for(int i = 0; i < tiles.size(); i++) + { + auto sprite = tiles[i]; + + JsonNode config; + config["basepath"].String() = sprite + "_Shifted/"; + config["images"].Vector(); + + auto filename = AnimationPath::builtin(sprite).addPrefix("SPRITES/"); + auto filenameNew = AnimationPath::builtin(sprite + "_Shifted").addPrefix("SPRITES/"); + + if(CResourceHandler::get()->existsResource(ResourcePath(filenameNew.getName(), EResType::JSON))) // overridden by mod, no generation + return; + + auto anim = GH.renderHandler().loadAnimation(filename, EImageBlitMode::COLORKEY); + for(int j = 0; j < anim->size(); j++) + { + int maxLen = 1; + for(int k = 0; k < paletteAnimations[i].size(); k++) + { + auto element = paletteAnimations[i][k]; + if(std::holds_alternative(element)) + maxLen = std::lcm(maxLen, std::get(element).length); + else + maxLen = std::lcm(maxLen, std::get(element).length); + } + for(int l = 0; l < maxLen; l++) + { + std::string spriteName = sprite + boost::str(boost::format("%02d") % j) + "_" + std::to_string(l) + ".png"; + std::string filenameNewImg = "sprites/" + sprite + "_Shifted" + "/" + spriteName; + ResourcePath savePath(filenameNewImg, EResType::IMAGE); + + if(!CResourceHandler::get("local")->createResource(filenameNewImg)) + return; + + auto imgLoc = anim->getImageLocator(j, 0); + imgLoc.scalingFactor = 1; + auto img = GH.renderHandler().loadImage(imgLoc, EImageBlitMode::COLORKEY); + for(int k = 0; k < paletteAnimations[i].size(); k++) + { + auto element = paletteAnimations[i][k]; + if(std::holds_alternative(element)) + { + auto tmp = std::get(element); + img->shiftPalette(tmp.start, tmp.length, l % tmp.length); + } + else + { + auto tmp = std::get(element); + img->shiftPalette(tmp.start, tmp.length, l % tmp.length); + } + } + + Canvas canvas = Canvas(Point(32, 32), CanvasScalingPolicy::IGNORE); + canvas.draw(img, Point((32 - img->dimensions().x) / 2, (32 - img->dimensions().y) / 2)); + std::shared_ptr image = GH.renderHandler().createImage(canvas.getInternalSurface()); + image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); + + JsonNode node(JsonMap{ + { "group", JsonNode(l) }, + { "frame", JsonNode(j) }, + { "file", JsonNode(spriteName) } + }); + config["images"].Vector().push_back(node); + } + } + + ResourcePath savePath(filenameNew.getOriginalName(), EResType::JSON); + if(!CResourceHandler::get("local")->createResource(filenameNew.getOriginalName() + ".json")) + return; + + std::fstream file(CResourceHandler::get("local")->getResourceName(savePath)->c_str(), std::ofstream::out | std::ofstream::trunc); + file << config.toString(); + } +} diff --git a/client/render/AssetGenerator.h b/client/render/AssetGenerator.h index 2eb73a886..8ca08aae6 100644 --- a/client/render/AssetGenerator.h +++ b/client/render/AssetGenerator.h @@ -23,4 +23,5 @@ public: static void createCombatUnitNumberWindow(); static void createCampaignBackground(); static void createChroniclesCampaignImages(); + static void createPaletteShiftedSprites(); }; diff --git a/client/render/CAnimation.cpp b/client/render/CAnimation.cpp index eeafff200..50276cf5e 100644 --- a/client/render/CAnimation.cpp +++ b/client/render/CAnimation.cpp @@ -18,11 +18,12 @@ #include "../../lib/filesystem/Filesystem.h" #include "../../lib/json/JsonUtils.h" -bool CAnimation::loadFrame(size_t frame, size_t group) +bool CAnimation::loadFrame(size_t frame, size_t group, bool verbose) { if(size(group) <= frame) { - printError(frame, group, "LoadFrame"); + if(verbose) + printError(frame, group, "LoadFrame"); return false; } @@ -119,7 +120,7 @@ void CAnimation::duplicateImage(const size_t sourceGroup, const size_t sourceFra std::shared_ptr CAnimation::getImage(size_t frame, size_t group, bool verbose) { - if (!loadFrame(frame, group)) + if (!loadFrame(frame, group, verbose)) return nullptr; return getImageImpl(frame, group, verbose); } diff --git a/client/render/CAnimation.h b/client/render/CAnimation.h index 75c8d1566..6b9e66272 100644 --- a/client/render/CAnimation.h +++ b/client/render/CAnimation.h @@ -41,7 +41,7 @@ private: PlayerColor player = PlayerColor::CANNOT_DETERMINE; //loader, will be called by load(), require opened def file for loading from it. Returns true if image is loaded - bool loadFrame(size_t frame, size_t group); + bool loadFrame(size_t frame, size_t group, bool verbose = true); //unloadFrame, returns true if image has been unloaded ( either deleted or decreased refCount) bool unloadFrame(size_t frame, size_t group); @@ -50,8 +50,6 @@ private: void printError(size_t frame, size_t group, std::string type) const; std::shared_ptr getImageImpl(size_t frame, size_t group=0, bool verbose=true); - - ImageLocator getImageLocator(size_t frame, size_t group) const; public: CAnimation(const AnimationPath & Name, std::map > layout, EImageBlitMode mode); ~CAnimation(); @@ -74,5 +72,7 @@ public: void playerColored(PlayerColor player); void createFlippedGroup(const size_t sourceGroup, const size_t targetGroup); + + ImageLocator getImageLocator(size_t frame, size_t group) const; }; diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index 3fd979e00..9df1fb38d 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -86,6 +86,11 @@ JsonNode::JsonNode(const std::string & string) { } +JsonNode::JsonNode(const JsonMap & map) + : data(map) +{ +} + JsonNode::JsonNode(const std::byte * data, size_t datasize, const std::string & fileName) : JsonNode(data, datasize, JsonParsingSettings(), fileName) { diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index 037148e9b..9ccb13cba 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -71,6 +71,9 @@ public: explicit JsonNode(const char * string); explicit JsonNode(const std::string & string); + /// Create tree from map + explicit JsonNode(const JsonMap & map); + /// Create tree from Json-formatted input explicit JsonNode(const std::byte * data, size_t datasize, const std::string & fileName); explicit JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings, const std::string & fileName);