diff --git a/Mods/vcmi/Content/config/czech.json b/Mods/vcmi/Content/config/czech.json index 9732e0bfe..05e1e02da 100644 --- a/Mods/vcmi/Content/config/czech.json +++ b/Mods/vcmi/Content/config/czech.json @@ -199,6 +199,7 @@ "vcmi.lobby.preview.error.invite" : "Nebyl jste pozván do této mísnosti.", "vcmi.lobby.preview.error.mods" : "Použváte jinou sadu modifikací.", "vcmi.lobby.preview.error.version" : "Používáte jinou verzi VCMI.", + "vcmi.lobby.channel.add" : "Přidat kanál", "vcmi.lobby.room.new" : "Nová hra", "vcmi.lobby.room.load" : "Načíst hru", "vcmi.lobby.room.type" : "Druh místnosti", @@ -326,9 +327,9 @@ "vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nPokud je tato možnost aktivována, posouvání mapy bude plynulé.", "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Přeskočit efekty mizení", "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Přeskočit efekty mizení}\n\nKdyž je povoleno, přeskočí se efekty mizení objektů a podobné efekty (sběr surovin, nalodění atd.). V některých případech zrychlí uživatelské rozhraní na úkor estetiky. Obzvláště užitečné v PvP hrách. Pro maximální rychlost pohybu je toto nastavení aktivní bez ohledu na další volby.", - "vcmi.adventureOptions.mapScrollSpeed1.hover": "", - "vcmi.adventureOptions.mapScrollSpeed5.hover": "", - "vcmi.adventureOptions.mapScrollSpeed6.hover": "", + "vcmi.adventureOptions.mapScrollSpeed1.hover" : "", + "vcmi.adventureOptions.mapScrollSpeed5.hover" : "", + "vcmi.adventureOptions.mapScrollSpeed6.hover" : "", "vcmi.adventureOptions.mapScrollSpeed1.help" : "Nastavit posouvání mapy na velmi pomalé", "vcmi.adventureOptions.mapScrollSpeed5.help" : "Nastavit posouvání mapy na velmi rychlé", "vcmi.adventureOptions.mapScrollSpeed6.help" : "Nastavit posouvání mapy na okamžité", @@ -337,16 +338,16 @@ "vcmi.battleOptions.queueSizeLabel.hover" : "Zobrazit frontu pořadí tahů", "vcmi.battleOptions.queueSizeNoneButton.hover" : "VYPNUTO", - "vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO", + "vcmi.battleOptions.queueSizeAutoButton.hover" : "AUTO", "vcmi.battleOptions.queueSizeSmallButton.hover" : "MALÁ", "vcmi.battleOptions.queueSizeBigButton.hover" : "VELKÁ", "vcmi.battleOptions.queueSizeNoneButton.help" : "Nezobrazovat frontu pořadí tahů.", "vcmi.battleOptions.queueSizeAutoButton.help" : "Nastavit automaticky velikost fronty pořadí tahů podle rozlišení obrazovky hry (Při výšce herního rozlišení menší než 700 pixelů je použita velikost MALÁ, jinak velikost VELKÁ)", "vcmi.battleOptions.queueSizeSmallButton.help" : "Zobrazit MALOU frontu pořadí tahů.", "vcmi.battleOptions.queueSizeBigButton.help" : "Zobrazit VELKOU frontu pořadí tahů (není podporováno, pokud výška rozlišení hry není alespoň 700 pixelů).", - "vcmi.battleOptions.animationsSpeed1.hover": "", - "vcmi.battleOptions.animationsSpeed5.hover": "", - "vcmi.battleOptions.animationsSpeed6.hover": "", + "vcmi.battleOptions.animationsSpeed1.hover" : "", + "vcmi.battleOptions.animationsSpeed5.hover" : "", + "vcmi.battleOptions.animationsSpeed6.hover" : "", "vcmi.battleOptions.animationsSpeed1.help" : "Nastavit rychlost animací na velmi pomalé.", "vcmi.battleOptions.animationsSpeed5.help" : "Nastavit rychlost animací na velmi rychlé.", "vcmi.battleOptions.animationsSpeed6.help" : "Nastavit rychlost animací na okamžité.", @@ -762,28 +763,28 @@ "core.bonus.MECHANICAL.name" : "Mechanický", "core.bonus.PRISM_HEX_ATTACK_BREATH.name" : "Trojitý dech", "core.bonus.PRISM_HEX_ATTACK_BREATH.description" : "Útok trojitým dechem (útok přes 3 směry)", - "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Odolnost vůči kouzlům", - "core.bonus.SPELL_DAMAGE_REDUCTION.name.air": "Odolnost vůči kouzlům vzduchu", - "core.bonus.SPELL_DAMAGE_REDUCTION.name.fire": "Odolnost vůči kouzlům ohně", - "core.bonus.SPELL_DAMAGE_REDUCTION.name.water": "Odolnost vůči kouzlům vody", - "core.bonus.SPELL_DAMAGE_REDUCTION.name.earth": "Odolnost vůči kouzlům země", - "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Poškození ze všech kouzel sníženo o ${val}%.", - "core.bonus.SPELL_DAMAGE_REDUCTION.description.air": "Poškození kouzel magie vzduchu sníženo o ${val}%.", - "core.bonus.SPELL_DAMAGE_REDUCTION.description.fire": "Poškození kouzel magie ohně sníženo o ${val}%.", - "core.bonus.SPELL_DAMAGE_REDUCTION.description.water": "Poškození kouzel magie vody sníženo o ${val}%.", - "core.bonus.SPELL_DAMAGE_REDUCTION.description.earth": "Poškození kouzel magie země sníženo o ${val}%.", - "core.bonus.SPELL_SCHOOL_IMMUNITY.name": "Imunita vůči kouzlům", - "core.bonus.SPELL_SCHOOL_IMMUNITY.name.air": "Vzdušná imunita", - "core.bonus.SPELL_SCHOOL_IMMUNITY.name.fire": "Ohnivá imunita", - "core.bonus.SPELL_SCHOOL_IMMUNITY.name.water": "Vodní imunita", - "core.bonus.SPELL_SCHOOL_IMMUNITY.name.earth": "Zemská imunita", - "core.bonus.SPELL_SCHOOL_IMMUNITY.description": "Jednotka je imunní vůči všem kouzlům.", - "core.bonus.SPELL_SCHOOL_IMMUNITY.description.air": "Jednotka je imunní vůči všem kouzlům magie vzduchu.", - "core.bonus.SPELL_SCHOOL_IMMUNITY.description.fire": "Jednotka je imunní vůči všem kouzlům magie ohně.", - "core.bonus.SPELL_SCHOOL_IMMUNITY.description.water": "Jednotka je imunní vůči všem kouzlům magie vody.", - "core.bonus.SPELL_SCHOOL_IMMUNITY.description.earth": "Jednotka je imunní vůči všem kouzlům magie země.", - "core.bonus.OPENING_BATTLE_SPELL.name": "Začíná kouzlem", - "core.bonus.OPENING_BATTLE_SPELL.description": "Sesílá ${subtype.spell} na začátku bitvy.", + "core.bonus.SPELL_DAMAGE_REDUCTION.name" : "Odolnost vůči kouzlům", + "core.bonus.SPELL_DAMAGE_REDUCTION.name.air" : "Odolnost vůči kouzlům vzduchu", + "core.bonus.SPELL_DAMAGE_REDUCTION.name.fire" : "Odolnost vůči kouzlům ohně", + "core.bonus.SPELL_DAMAGE_REDUCTION.name.water" : "Odolnost vůči kouzlům vody", + "core.bonus.SPELL_DAMAGE_REDUCTION.name.earth" : "Odolnost vůči kouzlům země", + "core.bonus.SPELL_DAMAGE_REDUCTION.description" : "Poškození ze všech kouzel sníženo o ${val}%.", + "core.bonus.SPELL_DAMAGE_REDUCTION.description.air" : "Poškození kouzel magie vzduchu sníženo o ${val}%.", + "core.bonus.SPELL_DAMAGE_REDUCTION.description.fire" : "Poškození kouzel magie ohně sníženo o ${val}%.", + "core.bonus.SPELL_DAMAGE_REDUCTION.description.water" : "Poškození kouzel magie vody sníženo o ${val}%.", + "core.bonus.SPELL_DAMAGE_REDUCTION.description.earth" : "Poškození kouzel magie země sníženo o ${val}%.", + "core.bonus.SPELL_SCHOOL_IMMUNITY.name" : "Imunita vůči kouzlům", + "core.bonus.SPELL_SCHOOL_IMMUNITY.name.air" : "Vzdušná imunita", + "core.bonus.SPELL_SCHOOL_IMMUNITY.name.fire" : "Ohnivá imunita", + "core.bonus.SPELL_SCHOOL_IMMUNITY.name.water" : "Vodní imunita", + "core.bonus.SPELL_SCHOOL_IMMUNITY.name.earth" : "Zemská imunita", + "core.bonus.SPELL_SCHOOL_IMMUNITY.description" : "Jednotka je imunní vůči všem kouzlům.", + "core.bonus.SPELL_SCHOOL_IMMUNITY.description.air" : "Jednotka je imunní vůči všem kouzlům magie vzduchu.", + "core.bonus.SPELL_SCHOOL_IMMUNITY.description.fire" : "Jednotka je imunní vůči všem kouzlům magie ohně.", + "core.bonus.SPELL_SCHOOL_IMMUNITY.description.water" : "Jednotka je imunní vůči všem kouzlům magie vody.", + "core.bonus.SPELL_SCHOOL_IMMUNITY.description.earth" : "Jednotka je imunní vůči všem kouzlům magie země.", + "core.bonus.OPENING_BATTLE_SPELL.name" : "Začíná kouzlem", + "core.bonus.OPENING_BATTLE_SPELL.description" : "Sesílá ${subtype.spell} na začátku bitvy.", "spell.core.castleMoat.name" : "Hradní příkop", "spell.core.castleMoatTrigger.name" : "Hradní příkop", @@ -806,4 +807,4 @@ "spell.core.strongholdMoatTrigger.name" : "Dřevěné bodce", "spell.core.summonDemons.name" : "Přivolání démonů", "spell.core.towerMoat.name" : "Pozemní mina" -} \ No newline at end of file +} diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 6b6db20b9..61fda3b47 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -62,6 +62,7 @@ #include "windows/CTutorialWindow.h" #include "windows/GUIClasses.h" #include "windows/InfoWindows.h" +#include "windows/settings/SettingsMainWindow.h" #include "../CCallback.h" @@ -187,6 +188,7 @@ void CPlayerInterface::closeAllDialogs() while(true) { auto adventureWindow = GH.windows().topWindow(); + auto settingsWindow = GH.windows().topWindow(); auto infoWindow = GH.windows().topWindow(); auto topWindow = GH.windows().topWindow(); @@ -196,10 +198,16 @@ void CPlayerInterface::closeAllDialogs() if(infoWindow && infoWindow->ID != QueryID::NONE) break; - if (topWindow == nullptr) - throw std::runtime_error("Invalid or non-existing top window! Total windows: " + std::to_string(GH.windows().count())); + if (settingsWindow) + { + settingsWindow->close(); + continue; + } - topWindow->close(); + if (topWindow) + topWindow->close(); + else + GH.windows().popWindows(1); // does not inherits from WindowBase, e.g. settings dialog } } diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index fc41ffab1..c6f31e881 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -18,7 +18,6 @@ #include "gui/CGuiHandler.h" #include "gui/WindowHandler.h" #include "render/IRenderHandler.h" -#include "render/AssetGenerator.h" #include "ClientNetPackVisitors.h" #include "../lib/CConfigHandler.h" #include "../lib/gameState/CGameState.h" @@ -510,7 +509,7 @@ void ClientCommandManager::handleVsLog(std::istringstream & singleWordBuffer) void ClientCommandManager::handleGenerateAssets() { - AssetGenerator::generateAll(); + GH.renderHandler().exportGeneratedAssets(); printCommandMessage("All assets generated"); } diff --git a/client/adventureMap/AdventureMapInterface.cpp b/client/adventureMap/AdventureMapInterface.cpp index 2742ae213..b77809af7 100644 --- a/client/adventureMap/AdventureMapInterface.cpp +++ b/client/adventureMap/AdventureMapInterface.cpp @@ -34,7 +34,6 @@ #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" @@ -65,8 +64,6 @@ 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/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index ed99d5298..412215deb 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -683,18 +683,24 @@ BattleHex::EDir BattleFieldController::selectAttackDirection(const BattleHex & m // | - - | - - | - - | - o o | o o - | - - | - - | o o for (size_t i : { 1, 2, 3}) - attackAvailability[i] = occupiableHexes.contains(neighbours[i]) && occupiableHexes.contains(neighbours[i].cloneInDirection(BattleHex::RIGHT, false)); + { + BattleHex target = neighbours[i].cloneInDirection(BattleHex::RIGHT, false); + attackAvailability[i] = neighbours[i].isValid() && occupiableHexes.contains(neighbours[i]) && target.isValid() && occupiableHexes.contains(target); + } for (size_t i : { 4, 5, 0}) - attackAvailability[i] = occupiableHexes.contains(neighbours[i]) && occupiableHexes.contains(neighbours[i].cloneInDirection(BattleHex::LEFT, false)); + { + BattleHex target = neighbours[i].cloneInDirection(BattleHex::LEFT, false); + attackAvailability[i] = neighbours[i].isValid() && occupiableHexes.contains(neighbours[i]) && target.isValid() && occupiableHexes.contains(target); + } - attackAvailability[6] = occupiableHexes.contains(neighbours[0]) && occupiableHexes.contains(neighbours[1]); - attackAvailability[7] = occupiableHexes.contains(neighbours[3]) && occupiableHexes.contains(neighbours[4]); + attackAvailability[6] = neighbours[0].isValid() && neighbours[1].isValid() && occupiableHexes.contains(neighbours[0]) && occupiableHexes.contains(neighbours[1]); + attackAvailability[7] = neighbours[3].isValid() && neighbours[4].isValid() && occupiableHexes.contains(neighbours[3]) && occupiableHexes.contains(neighbours[4]); } else { for (size_t i = 0; i < 6; ++i) - attackAvailability[i] = occupiableHexes.contains(neighbours[i]); + attackAvailability[i] = neighbours[i].isValid() && occupiableHexes.contains(neighbours[i]); attackAvailability[6] = false; attackAvailability[7] = false; @@ -739,7 +745,7 @@ BattleHex::EDir BattleFieldController::selectAttackDirection(const BattleHex & m BattleHex BattleFieldController::fromWhichHexAttack(const BattleHex & attackTarget) { - BattleHex::EDir direction = selectAttackDirection(getHoveredHex()); + BattleHex::EDir direction = selectAttackDirection(attackTarget); const CStack * attacker = owner.stacksController->getActiveStack(); diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 596bdbd1e..1f57228da 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -686,7 +686,12 @@ void StackInfoBasicPanel::initializeData(const CStack * stack) if (hasGraphics) { //FIXME: support permanent duration - int duration = stack->getFirstBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect)))->turnsRemain; + auto spellBonuses = stack->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect))); + + if (spellBonuses->empty()) + throw std::runtime_error("Failed to find effects for spell " + effect.toSpell()->getJsonKey()); + + int duration = spellBonuses->front()->duration; icons.push_back(std::make_shared(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed)); if(settings["general"]["enableUiEnhancements"].Bool()) diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 6c981d621..d0be307cc 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -27,7 +27,6 @@ #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" #include "../media/ISoundPlayer.h" -#include "../render/AssetGenerator.h" #include "../render/Colors.h" #include "../render/Canvas.h" #include "../render/IRenderHandler.h" @@ -80,7 +79,6 @@ BattleStacksController::BattleStacksController(BattleInterface & owner): stackToActivate(nullptr), animIDhelper(0) { - AssetGenerator::createCombatUnitNumberWindow(); //preparing graphics for displaying amounts of creatures amountNormal = GH.renderHandler().loadImage(ImagePath::builtin("combatUnitNumberWindowDefault"), EImageBlitMode::COLORKEY); amountPositive = GH.renderHandler().loadImage(ImagePath::builtin("combatUnitNumberWindowPositive"), EImageBlitMode::COLORKEY); diff --git a/client/globalLobby/GlobalLobbyLoginWindow.cpp b/client/globalLobby/GlobalLobbyLoginWindow.cpp index d76f80d17..88bdb7f3c 100644 --- a/client/globalLobby/GlobalLobbyLoginWindow.cpp +++ b/client/globalLobby/GlobalLobbyLoginWindow.cpp @@ -116,6 +116,7 @@ void GlobalLobbyLoginWindow::onLogin() onConnectionSuccess(); buttonClose->block(true); + buttonLogin->block(true); } void GlobalLobbyLoginWindow::onConnectionSuccess() @@ -142,4 +143,5 @@ void GlobalLobbyLoginWindow::onConnectionFailed(const std::string & reason) labelStatus->setText(formatter.toString()); buttonClose->block(false); + buttonLogin->block(false); } diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index 6608ca44d..9dfe71703 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -134,6 +134,7 @@ CGuiHandler::~CGuiHandler() // enforce deletion order on shutdown // all UI elements including adventure map must be destroyed before Gui Handler // proper solution would be removal of adventureInt global + windowHandlerInstance->clear(); adventureInt.reset(); } diff --git a/client/gui/CIntObject.cpp b/client/gui/CIntObject.cpp index 14e3d09a1..d38238ce6 100644 --- a/client/gui/CIntObject.cpp +++ b/client/gui/CIntObject.cpp @@ -345,7 +345,7 @@ void WindowBase::close() if(!GH.windows().isTopWindow(this)) { auto topWindow = GH.windows().topWindow().get(); - throw std::runtime_error(std::string("Only top interface can be closed! Top window is ") + typeid(*this).name() + " but attempted to close " + typeid(*topWindow).name()); + throw std::runtime_error(std::string("Only top interface can be closed! Top window is ") + typeid(*topWindow).name() + " but attempted to close " + typeid(*this).name()); } GH.windows().popWindows(1); } diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 97bb2383b..dd00f5442 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -68,6 +68,9 @@ void OptionsTab::recreate() entries.clear(); humanPlayers = 0; + for (auto heroOverview : GH.windows().findWindows()) + heroOverview->close(); + for (auto selectionWindow : GH.windows().findWindows()) { selectionWindow->reopen(); diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index 8335d9f9d..ad148b7a2 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -18,7 +18,6 @@ #include "../widgets/TextControls.h" #include "../CServerHandler.h" #include "../CGameInfo.h" -#include "../render/AssetGenerator.h" #include "../../lib/StartInfo.h" #include "../../lib/texts/CGeneralTextHandler.h" @@ -69,8 +68,6 @@ std::vector OptionsTabBase::getSimturnsPresets() const OptionsTabBase::OptionsTabBase(const JsonPath & configPath) { - AssetGenerator::createAdventureOptionsCleanBackground(); - recActions = 0; auto setTimerPresetCallback = [this](int index){ diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 05b2f71cb..95ba7c80a 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -38,7 +38,6 @@ #include "../widgets/VideoWidget.h" #include "../windows/InfoWindows.h" #include "../CServerHandler.h" -#include "../render/AssetGenerator.h" #include "../CGameInfo.h" #include "../CPlayerInterface.h" @@ -278,7 +277,7 @@ CMenuEntry::CMenuEntry(CMenuScreen * parent, const JsonNode & config) for (const auto& item : campaign["items"].Vector()) { std::string filename = item["file"].String(); - if (CResourceHandler::get()->existsResource(ResourcePath(filename + ".h3c"))) { + if (CResourceHandler::get()->existsResource(ResourcePath(filename, EResType::CAMPAIGN))) { fileExists = true; break; } @@ -428,9 +427,6 @@ void CMainMenu::openCampaignScreen(std::string name) { auto const & config = CMainMenuConfig::get().getCampaigns(); - AssetGenerator::createCampaignBackground(); - AssetGenerator::createChroniclesCampaignImages(); - if(!vstd::contains(config.Struct(), name)) { logGlobal->error("Unknown campaign set: %s", name); diff --git a/client/render/AssetGenerator.cpp b/client/render/AssetGenerator.cpp index 20882069d..6f264b0ac 100644 --- a/client/render/AssetGenerator.cpp +++ b/client/render/AssetGenerator.cpp @@ -29,36 +29,60 @@ #include "../lib/RoadHandler.h" #include "../lib/TerrainHandler.h" -void AssetGenerator::clear() +AssetGenerator::AssetGenerator() +{ +} + +void AssetGenerator::initialize() { // clear to avoid non updated sprites after mod change (if base imnages are used) if(boost::filesystem::is_directory(VCMIDirs::get().userDataPath() / "Generated")) boost::filesystem::remove_all(VCMIDirs::get().userDataPath() / "Generated"); -} -void AssetGenerator::generateAll() -{ - createBigSpellBook(); - createAdventureOptionsCleanBackground(); - for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) - createPlayerColoredBackground(PlayerColor(i)); - createCombatUnitNumberWindow(); - createCampaignBackground(); - createChroniclesCampaignImages(); + imageFiles[ImagePath::builtin("AdventureOptionsBackgroundClear.png")] = [this](){ return createAdventureOptionsCleanBackground();}; + imageFiles[ImagePath::builtin("SpellBookLarge.png")] = [this](){ return createBigSpellBook();}; + + imageFiles[ImagePath::builtin("combatUnitNumberWindowDefault.png")] = [this](){ return createCombatUnitNumberWindow(0.6f, 0.2f, 1.0f);}; + imageFiles[ImagePath::builtin("combatUnitNumberWindowNeutral.png")] = [this](){ return createCombatUnitNumberWindow(1.0f, 1.0f, 2.0f);}; + imageFiles[ImagePath::builtin("combatUnitNumberWindowPositive.png")] = [this](){ return createCombatUnitNumberWindow(0.2f, 1.0f, 0.2f);}; + imageFiles[ImagePath::builtin("combatUnitNumberWindowNegative.png")] = [this](){ return createCombatUnitNumberWindow(1.0f, 0.2f, 0.2f);}; + + imageFiles[ImagePath::builtin("CampaignBackground8.png")] = [this](){ return createCampaignBackground();}; + + for (PlayerColor color(0); color < PlayerColor::PLAYER_LIMIT; ++color) + imageFiles[ImagePath::builtin("DialogBoxBackground_" + color.toString())] = [this, color](){ return createPlayerColoredBackground(color);}; + + for(int i = 1; i < 9; i++) + imageFiles[ImagePath::builtin("CampaignHc" + std::to_string(i) + "Image.png")] = [this, i](){ return createChroniclesCampaignImages(i);}; + createPaletteShiftedSprites(); } -void AssetGenerator::createAdventureOptionsCleanBackground() +std::shared_ptr AssetGenerator::generateImage(const ImagePath & image) { - std::string filename = "data/AdventureOptionsBackgroundClear.png"; + if (imageFiles.count(image)) + return imageFiles.at(image)()->toSharedImage(); // TODO: cache? + else + return nullptr; +} - if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation - return; +std::map> AssetGenerator::generateAllImages() +{ + std::map> result; - if(!CResourceHandler::get("local")->createResource(filename)) - return; - ResourcePath savePath(filename, EResType::IMAGE); + for (const auto & entry : imageFiles) + result[entry.first] = entry.second()->toSharedImage(); + return result; +} + +std::map AssetGenerator::generateAllAnimations() +{ + return animationFiles; +} + +AssetGenerator::CanvasPtr AssetGenerator::createAdventureOptionsCleanBackground() +{ auto locator = ImageLocator(ImagePath::builtin("ADVOPTBK"), EImageBlitMode::OPAQUE); std::shared_ptr img = GH.renderHandler().loadImage(locator); @@ -74,20 +98,11 @@ void AssetGenerator::createAdventureOptionsCleanBackground() canvas.draw(img, Point(53, 567), Rect(53, 520, 339, 3)); canvas.draw(img, Point(53, 520), Rect(53, 264, 339, 47)); - image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); + return image; } -void AssetGenerator::createBigSpellBook() +AssetGenerator::CanvasPtr AssetGenerator::createBigSpellBook() { - std::string filename = "data/SpellBookLarge.png"; - - if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation - return; - - if(!CResourceHandler::get("local")->createResource(filename)) - return; - ResourcePath savePath(filename, EResType::IMAGE); - auto locator = ImageLocator(ImagePath::builtin("SpelBack"), EImageBlitMode::OPAQUE); std::shared_ptr img = GH.renderHandler().loadImage(locator); @@ -135,21 +150,11 @@ void AssetGenerator::createBigSpellBook() canvas.draw(img, Point(575, 465), Rect(417, 406, 37, 45)); canvas.draw(img, Point(667, 465), Rect(478, 406, 37, 47)); - image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); + return image; } -void AssetGenerator::createPlayerColoredBackground(const PlayerColor & player) +AssetGenerator::CanvasPtr AssetGenerator::createPlayerColoredBackground(const PlayerColor & player) { - std::string filename = "data/DialogBoxBackground_" + player.toString() + ".png"; - - if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation - return; - - if(!CResourceHandler::get("local")->createResource(filename)) - return; - - ResourcePath savePath(filename, EResType::IMAGE); - auto locator = ImageLocator(ImagePath::builtin("DiBoxBck"), EImageBlitMode::OPAQUE); std::shared_ptr texture = GH.renderHandler().loadImage(locator); @@ -169,71 +174,44 @@ void AssetGenerator::createPlayerColoredBackground(const PlayerColor & player) assert(player.isValidPlayer()); if (!player.isValidPlayer()) - { - logGlobal->error("Unable to colorize to invalid player color %d!", player.getNum()); - return; - } + throw std::runtime_error("Unable to colorize to invalid player color" + std::to_string(player.getNum())); texture->adjustPalette(filters[player.getNum()], 0); - texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); + + auto image = GH.renderHandler().createImage(texture->dimensions(), CanvasScalingPolicy::IGNORE); + Canvas canvas = image->getCanvas(); + canvas.draw(texture, Point(0,0)); + + return image; } -void AssetGenerator::createCombatUnitNumberWindow() +AssetGenerator::CanvasPtr AssetGenerator::createCombatUnitNumberWindow(float multR, float multG, float multB) { - std::string filenameToSave = "data/combatUnitNumberWindow"; - - ResourcePath savePathDefault(filenameToSave + "Default.png", EResType::IMAGE); - ResourcePath savePathNeutral(filenameToSave + "Neutral.png", EResType::IMAGE); - ResourcePath savePathPositive(filenameToSave + "Positive.png", EResType::IMAGE); - ResourcePath savePathNegative(filenameToSave + "Negative.png", EResType::IMAGE); - - if(CResourceHandler::get()->existsResource(savePathDefault)) // overridden by mod, no generation - return; - - if(!CResourceHandler::get("local")->createResource(savePathDefault.getOriginalName() + ".png") || - !CResourceHandler::get("local")->createResource(savePathNeutral.getOriginalName() + ".png") || - !CResourceHandler::get("local")->createResource(savePathPositive.getOriginalName() + ".png") || - !CResourceHandler::get("local")->createResource(savePathNegative.getOriginalName() + ".png")) - return; - auto locator = ImageLocator(ImagePath::builtin("CMNUMWIN"), EImageBlitMode::OPAQUE); locator.layer = EImageBlitMode::OPAQUE; std::shared_ptr texture = GH.renderHandler().loadImage(locator); - static const auto shifterNormal = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.6f, 0.2f, 1.0f ); - static const auto shifterPositive = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.2f, 1.0f, 0.2f ); - static const auto shifterNegative = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 0.2f, 0.2f ); - static const auto shifterNeutral = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 1.0f, 0.2f ); + const auto shifter= ColorFilter::genRangeShifter(0.f, 0.f, 0.f, multR, multG, multB); // do not change border color static const int32_t ignoredMask = 1 << 26; - texture->adjustPalette(shifterNormal, ignoredMask); - texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathDefault)); - texture->adjustPalette(shifterPositive, ignoredMask); - texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathPositive)); - texture->adjustPalette(shifterNegative, ignoredMask); - texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathNegative)); - texture->adjustPalette(shifterNeutral, ignoredMask); - texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathNeutral)); + texture->adjustPalette(shifter, ignoredMask); + + auto image = GH.renderHandler().createImage(texture->dimensions(), CanvasScalingPolicy::IGNORE); + Canvas canvas = image->getCanvas(); + canvas.draw(texture, Point(0,0)); + + return image; } -void AssetGenerator::createCampaignBackground() +AssetGenerator::CanvasPtr AssetGenerator::createCampaignBackground() { - std::string filename = "data/CampaignBackground8.png"; - - if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation - return; - - if(!CResourceHandler::get("local")->createResource(filename)) - return; - ResourcePath savePath(filename, EResType::IMAGE); - auto locator = ImageLocator(ImagePath::builtin("CAMPBACK"), EImageBlitMode::OPAQUE); std::shared_ptr img = GH.renderHandler().loadImage(locator); - auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE); + auto image = GH.renderHandler().createImage(Point(200, 116), CanvasScalingPolicy::IGNORE); Canvas canvas = image->getCanvas(); canvas.draw(img, Point(0, 0), Rect(0, 0, 800, 600)); @@ -264,171 +242,112 @@ void AssetGenerator::createCampaignBackground() std::shared_ptr imgSkull = GH.renderHandler().loadImage(locatorSkull); canvas.draw(imgSkull, Point(562, 509), Rect(178, 108, 43, 19)); - image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); + return image; } -void AssetGenerator::createChroniclesCampaignImages() +AssetGenerator::CanvasPtr AssetGenerator::createChroniclesCampaignImages(int chronicle) { - for(int i = 1; i < 9; i++) + auto imgPathBg = ImagePath::builtin("data/chronicles_" + std::to_string(chronicle) + "/GamSelBk"); + auto locator = ImageLocator(imgPathBg, EImageBlitMode::OPAQUE); + + std::shared_ptr img = GH.renderHandler().loadImage(locator); + auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE); + Canvas canvas = image->getCanvas(); + + std::array sourceRect = { + Rect(149, 144, 200, 116), + Rect(156, 150, 200, 116), + Rect(171, 153, 200, 116), + Rect(35, 358, 200, 116), + Rect(216, 248, 200, 116), + Rect(58, 234, 200, 116), + Rect(184, 219, 200, 116), + Rect(268, 210, 200, 116), + }; + + canvas.draw(img, Point(0, 0), sourceRect.at(chronicle-1)); + + if (chronicle == 8) { - std::string filename = "data/CampaignHc" + std::to_string(i) + "Image.png"; - - if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation - continue; - - auto imgPathBg = ImagePath::builtin("data/chronicles_" + std::to_string(i) + "/GamSelBk"); - if(!CResourceHandler::get()->existsResource(imgPathBg)) // Chronicle episode not installed - continue; - - if(!CResourceHandler::get("local")->createResource(filename)) - continue; - ResourcePath savePath(filename, EResType::IMAGE); - - auto locator = ImageLocator(imgPathBg, EImageBlitMode::OPAQUE); - - std::shared_ptr img = GH.renderHandler().loadImage(locator); - auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE); - Canvas canvas = image->getCanvas(); - - switch (i) - { - case 1: - canvas.draw(img, Point(0, 0), Rect(149, 144, 200, 116)); - break; - case 2: - canvas.draw(img, Point(0, 0), Rect(156, 150, 200, 116)); - break; - case 3: - canvas.draw(img, Point(0, 0), Rect(171, 153, 200, 116)); - break; - case 4: - canvas.draw(img, Point(0, 0), Rect(35, 358, 200, 116)); - break; - case 5: - canvas.draw(img, Point(0, 0), Rect(216, 248, 200, 116)); - break; - case 6: - canvas.draw(img, Point(0, 0), Rect(58, 234, 200, 116)); - break; - case 7: - canvas.draw(img, Point(0, 0), Rect(184, 219, 200, 116)); - break; - case 8: - canvas.draw(img, Point(0, 0), Rect(268, 210, 200, 116)); - - //skull - auto locatorSkull = ImageLocator(ImagePath::builtin("CampSP1"), EImageBlitMode::OPAQUE); - std::shared_ptr imgSkull = GH.renderHandler().loadImage(locatorSkull); - canvas.draw(imgSkull, Point(162, 94), Rect(162, 94, 41, 22)); - canvas.draw(img, Point(162, 94), Rect(424, 304, 14, 4)); - canvas.draw(img, Point(162, 98), Rect(424, 308, 10, 4)); - canvas.draw(img, Point(158, 102), Rect(424, 312, 10, 4)); - canvas.draw(img, Point(154, 106), Rect(424, 316, 10, 4)); - break; - } - - image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); + //skull + auto locatorSkull = ImageLocator(ImagePath::builtin("CampSP1"), EImageBlitMode::OPAQUE); + std::shared_ptr imgSkull = GH.renderHandler().loadImage(locatorSkull); + canvas.draw(imgSkull, Point(162, 94), Rect(162, 94, 41, 22)); + canvas.draw(img, Point(162, 94), Rect(424, 304, 14, 4)); + canvas.draw(img, Point(162, 98), Rect(424, 308, 10, 4)); + canvas.draw(img, Point(158, 102), Rect(424, 312, 10, 4)); + canvas.draw(img, Point(154, 106), Rect(424, 316, 10, 4)); } + + return image; } 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); - } + if(entity->paletteAnimation.empty()) + continue; + + std::vector paletteShifts; + for(auto & animEntity : entity->paletteAnimation) + paletteShifts.push_back({animEntity.start, animEntity.length}); + + generatePaletteShiftedAnimation(entity->tilesFilename, paletteShifts); + } 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); - } - } + if(entity->paletteAnimation.empty()) + continue; - for(int i = 0; i < tiles.size(); i++) - { - auto sprite = tiles[i]; + std::vector paletteShifts; + for(auto & animEntity : entity->paletteAnimation) + paletteShifts.push_back({animEntity.start, animEntity.length}); - 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); - auto img = GH.renderHandler().loadImage(imgLoc); - 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); - } - } - - auto image = GH.renderHandler().createImage(Point(32, 32), CanvasScalingPolicy::IGNORE); - Canvas canvas = image->getCanvas(); - canvas.draw(img, Point((32 - img->dimensions().x) / 2, (32 - img->dimensions().y) / 2)); - 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(); + generatePaletteShiftedAnimation(entity->tilesFilename, paletteShifts); } } + +void AssetGenerator::generatePaletteShiftedAnimation(const AnimationPath & sprite, const std::vector & paletteAnimations) +{ + AnimationLayoutMap layout; + + auto animation = GH.renderHandler().loadAnimation(sprite, EImageBlitMode::COLORKEY); + + int paletteTransformLength = 1; + for (const auto & transform : paletteAnimations) + paletteTransformLength = std::lcm(paletteTransformLength, transform.length); + + for(int tileIndex = 0; tileIndex < animation->size(); tileIndex++) + { + for(int paletteIndex = 0; paletteIndex < paletteTransformLength; paletteIndex++) + { + ImagePath spriteName = ImagePath::builtin(sprite.getName() + boost::str(boost::format("%02d") % tileIndex) + "_" + std::to_string(paletteIndex) + ".png"); + layout[paletteIndex].push_back(ImageLocator(spriteName, EImageBlitMode::SIMPLE)); + + imageFiles[spriteName] = [=](){ return createPaletteShiftedImage(sprite, paletteAnimations, tileIndex, paletteIndex);}; + } + } + + AnimationPath shiftedPath = AnimationPath::builtin("SPRITES/" + sprite.getName() + "_SHIFTED"); + animationFiles[shiftedPath] = layout; +} + +AssetGenerator::CanvasPtr AssetGenerator::createPaletteShiftedImage(const AnimationPath & source, const std::vector & palette, int frameIndex, int paletteShiftCounter) +{ + auto animation = GH.renderHandler().loadAnimation(source, EImageBlitMode::COLORKEY); + + auto imgLoc = animation->getImageLocator(frameIndex, 0); + auto img = GH.renderHandler().loadImage(imgLoc); + + for(const auto & element : palette) + img->shiftPalette(element.start, element.length, paletteShiftCounter % element.length); + + auto image = GH.renderHandler().createImage(Point(32, 32), CanvasScalingPolicy::IGNORE); + Canvas canvas = image->getCanvas(); + canvas.draw(img, Point((32 - img->dimensions().x) / 2, (32 - img->dimensions().y) / 2)); + + return image; + +} diff --git a/client/render/AssetGenerator.h b/client/render/AssetGenerator.h index 51584754a..c0646b727 100644 --- a/client/render/AssetGenerator.h +++ b/client/render/AssetGenerator.h @@ -9,20 +9,53 @@ */ #pragma once +#include "ImageLocator.h" + VCMI_LIB_NAMESPACE_BEGIN class PlayerColor; VCMI_LIB_NAMESPACE_END +class ISharedImage; +class CanvasImage; + class AssetGenerator { public: - static void clear(); - static void generateAll(); - static void createAdventureOptionsCleanBackground(); - static void createBigSpellBook(); - static void createPlayerColoredBackground(const PlayerColor & player); - static void createCombatUnitNumberWindow(); - static void createCampaignBackground(); - static void createChroniclesCampaignImages(); - static void createPaletteShiftedSprites(); + using AnimationLayoutMap = std::map>; + using CanvasPtr = std::shared_ptr; + + AssetGenerator(); + + void initialize(); + + std::shared_ptr generateImage(const ImagePath & image); + + std::map> generateAllImages(); + std::map generateAllAnimations(); + +private: + using ImageGenerationFunctor = std::function; + + struct PaletteAnimation + { + /// index of first color to cycle + int32_t start; + /// total numbers of colors to cycle + int32_t length; + }; + + std::map imageFiles; + std::map animationFiles; + + CanvasPtr createAdventureOptionsCleanBackground(); + CanvasPtr createBigSpellBook(); + CanvasPtr createPlayerColoredBackground(const PlayerColor & player); + CanvasPtr createCombatUnitNumberWindow(float multR, float multG, float multB); + CanvasPtr createCampaignBackground(); + CanvasPtr createChroniclesCampaignImages(int chronicle); + CanvasPtr createPaletteShiftedImage(const AnimationPath & source, const std::vector & animation, int frameIndex, int paletteShiftCounter); + + void createPaletteShiftedSprites(); + void generatePaletteShiftedAnimation(const AnimationPath & source, const std::vector & animation); + }; diff --git a/client/render/CanvasImage.cpp b/client/render/CanvasImage.cpp index 07e9b3474..dc6d619bb 100644 --- a/client/render/CanvasImage.cpp +++ b/client/render/CanvasImage.cpp @@ -14,6 +14,7 @@ #include "../render/IScreenHandler.h" #include "../renderSDL/SDL_Extensions.h" #include "../renderSDL/SDLImageScaler.h" +#include "../renderSDL/SDLImage.h" #include #include @@ -61,3 +62,8 @@ Point CanvasImage::dimensions() const { return {surface->w, surface->h}; } + +std::shared_ptr CanvasImage::toSharedImage() +{ + return std::make_shared(surface); +} diff --git a/client/render/CanvasImage.h b/client/render/CanvasImage.h index 18c342832..095a4276b 100644 --- a/client/render/CanvasImage.h +++ b/client/render/CanvasImage.h @@ -34,6 +34,8 @@ public: void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override{}; void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override{}; + std::shared_ptr toSharedImage(); + private: SDL_Surface * surface; CanvasScalingPolicy scalingPolicy; diff --git a/client/render/IRenderHandler.h b/client/render/IRenderHandler.h index cefd02890..2210bd797 100644 --- a/client/render/IRenderHandler.h +++ b/client/render/IRenderHandler.h @@ -50,4 +50,6 @@ public: /// Returns font with specified identifer virtual std::shared_ptr loadFont(EFonts font) = 0; + + virtual void exportGeneratedAssets() = 0; }; diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index 285c539cd..92469f4d3 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -16,6 +16,7 @@ #include "../gui/CGuiHandler.h" +#include "../render/AssetGenerator.h" #include "../render/CAnimation.h" #include "../render/CanvasImage.h" #include "../render/CDefFile.h" @@ -43,6 +44,13 @@ #include #include +RenderHandler::RenderHandler() + :assetGenerator(std::make_unique()) +{ +} + +RenderHandler::~RenderHandler() = default; + std::shared_ptr RenderHandler::getAnimationFile(const AnimationPath & path) { AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/"); @@ -201,12 +209,28 @@ std::shared_ptr RenderHandler::loadImageImpl(const ImageLoc return scaledImage; } -std::shared_ptr RenderHandler::loadImageFromFileUncached(const ImageLocator & locator) +std::shared_ptr RenderHandler::loadImageFromFileUncached(const ImageLocator & locator) { if(locator.image) { - // TODO: create EmptySharedImage class that will be instantiated if image does not exists or fails to load - return std::make_shared(*locator.image); + auto imagePath = *locator.image; + auto imagePathSprites = imagePath.addPrefix("SPRITES/"); + auto imagePathData = imagePath.addPrefix("DATA/"); + + if(CResourceHandler::get()->existsResource(imagePathSprites)) + return std::make_shared(imagePathSprites); + + if(CResourceHandler::get()->existsResource(imagePathData)) + return std::make_shared(imagePathData); + + if(CResourceHandler::get()->existsResource(imagePath)) + return std::make_shared(imagePath); + + auto generated = assetGenerator->generateImage(imagePath); + if (generated) + return generated; + + return std::make_shared(ImagePath::builtin("DEFAULT")); } if(locator.defFile) @@ -423,6 +447,10 @@ static void detectOverlappingBuildings(RenderHandler * renderHandler, const Fact void RenderHandler::onLibraryLoadingFinished(const Services * services) { + assert(animationLayouts.empty()); + assetGenerator->initialize(); + animationLayouts = assetGenerator->generateAllAnimations(); + addImageListEntries(services->creatures()); addImageListEntries(services->heroTypes()); addImageListEntries(services->artifacts()); @@ -469,3 +497,9 @@ std::shared_ptr RenderHandler::loadFont(EFonts font) fonts[font] = loadedFont; return loadedFont; } + +void RenderHandler::exportGeneratedAssets() +{ + for (const auto & entry : assetGenerator->generateAllImages()) + entry.second->exportBitmap(VCMIDirs::get().userDataPath() / "Generated" / (entry.first.getOriginalName() + ".png"), nullptr); +} diff --git a/client/renderSDL/RenderHandler.h b/client/renderSDL/RenderHandler.h index 4a7e73c6a..b795e5a4e 100644 --- a/client/renderSDL/RenderHandler.h +++ b/client/renderSDL/RenderHandler.h @@ -18,8 +18,9 @@ VCMI_LIB_NAMESPACE_END class CDefFile; class SDLImageShared; class ScalableImageShared; +class AssetGenerator; -class RenderHandler : public IRenderHandler +class RenderHandler final : public IRenderHandler { using AnimationLayoutMap = std::map>; @@ -27,6 +28,7 @@ class RenderHandler : public IRenderHandler std::map animationLayouts; std::map> imageFiles; std::map> fonts; + std::unique_ptr assetGenerator; std::shared_ptr getAnimationFile(const AnimationPath & path); AnimationLayoutMap & getAnimationLayout(const AnimationPath & path, int scalingFactor, EImageBlitMode mode); @@ -38,13 +40,15 @@ class RenderHandler : public IRenderHandler std::shared_ptr loadImageImpl(const ImageLocator & config); - std::shared_ptr loadImageFromFileUncached(const ImageLocator & locator); + std::shared_ptr loadImageFromFileUncached(const ImageLocator & locator); ImageLocator getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group, int scaling, EImageBlitMode mode); int getScalingFactor() const; public: + RenderHandler(); + ~RenderHandler(); // IRenderHandler implementation void onLibraryLoadingFinished(const Services * services) override; @@ -61,4 +65,6 @@ public: /// Returns font with specified identifer std::shared_ptr loadFont(EFonts font) override; + + void exportGeneratedAssets() override; }; diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 16adecf69..d584f4683 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -306,6 +306,10 @@ std::shared_ptr SDLImageShared::scaleTo(const Point & size, void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palette * palette) const { + auto directory = path; + directory.remove_filename(); + boost::filesystem::create_directories(directory); + assert(upscalingInProgress == false); if (!surf) return; diff --git a/client/renderSDL/SDLImageScaler.cpp b/client/renderSDL/SDLImageScaler.cpp index dfa906e0d..d2a958f5f 100644 --- a/client/renderSDL/SDLImageScaler.cpp +++ b/client/renderSDL/SDLImageScaler.cpp @@ -120,6 +120,9 @@ const Rect & SDLImageOptimizer::getResultDimensions() const void SDLImageScaler::scaleSurface(Point targetDimensions, EScalingAlgorithm algorithm) { + if (!intermediate) + return; // may happen on scaling of empty images + if(!targetDimensions.x || !targetDimensions.y) throw std::runtime_error("invalid scaling dimensions!"); @@ -144,6 +147,9 @@ void SDLImageScaler::scaleSurface(Point targetDimensions, EScalingAlgorithm algo void SDLImageScaler::scaleSurfaceIntegerFactor(int factor, EScalingAlgorithm algorithm) { + if (!intermediate) + return; // may happen on scaling of empty images + if(factor == 0) throw std::runtime_error("invalid scaling factor!"); diff --git a/client/widgets/Images.cpp b/client/widgets/Images.cpp index 6ebabdc0f..6693300e3 100644 --- a/client/widgets/Images.cpp +++ b/client/widgets/Images.cpp @@ -13,7 +13,6 @@ #include "MiscWidgets.h" #include "../gui/CGuiHandler.h" -#include "../render/AssetGenerator.h" #include "../render/IImage.h" #include "../render/IRenderHandler.h" #include "../render/CAnimation.h" @@ -184,8 +183,6 @@ FilledTexturePlayerColored::FilledTexturePlayerColored(Rect position) void FilledTexturePlayerColored::setPlayerColor(PlayerColor player) { - AssetGenerator::createPlayerColoredBackground(player); - ImagePath imagePath = ImagePath::builtin("DialogBoxBackground_" + player.toString() + ".bmp"); texture = GH.renderHandler().loadImage(imagePath, EImageBlitMode::COLORKEY); diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index 286ed0f8a..9279a080c 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -234,7 +234,11 @@ CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int spellText = CGI->generaltexth->allTexts[610]; //"%s, duration: %d rounds." boost::replace_first(spellText, "%s", spell->getNameTranslated()); //FIXME: support permanent duration - int duration = battleStack->getFirstBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect)))->turnsRemain; + auto spellBonuses = battleStack->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect))); + if (spellBonuses->empty()) + throw std::runtime_error("Failed to find effects for spell " + effect.toSpell()->getJsonKey()); + + int duration = spellBonuses->front()->duration; boost::replace_first(spellText, "%d", std::to_string(duration)); spellIcons.push_back(std::make_shared(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed)); diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 56168d707..f3033e864 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -32,7 +32,6 @@ #include "../widgets/Buttons.h" #include "../widgets/VideoWidget.h" #include "../adventureMap/AdventureMapInterface.h" -#include "../render/AssetGenerator.h" #include "../../CCallback.h" @@ -118,7 +117,6 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m if(isBigSpellbook) { - AssetGenerator::createBigSpellBook(); background = std::make_shared(ImagePath::builtin("SpellBookLarge"), 0, 0); updateShadow(); } diff --git a/client/windows/settings/SettingsMainWindow.h b/client/windows/settings/SettingsMainWindow.h index 7b26921df..5e59695d0 100644 --- a/client/windows/settings/SettingsMainWindow.h +++ b/client/windows/settings/SettingsMainWindow.h @@ -29,7 +29,6 @@ private: std::shared_ptr createTab(size_t index); void openTab(size_t index); - void close(); //TODO: copypaste of WindowBase::close(), consider changing Windowbase to IWindowbase with default close() implementation and changing WindowBase inheritance to CIntObject + IWindowBase void loadGameButtonCallback(); void saveGameButtonCallback(); @@ -40,6 +39,7 @@ private: public: SettingsMainWindow(BattleInterface * parentBattleInterface = nullptr); + void close(); //TODO: copypaste of WindowBase::close(), consider changing Windowbase to IWindowbase with default close() implementation and changing WindowBase inheritance to CIntObject + IWindowBase void showAll(Canvas & to) override; void onScreenResize() override; }; diff --git a/clientapp/EntryPoint.cpp b/clientapp/EntryPoint.cpp index b4081ce66..a0cbd7b16 100644 --- a/clientapp/EntryPoint.cpp +++ b/clientapp/EntryPoint.cpp @@ -27,7 +27,6 @@ #include "../client/media/CMusicHandler.h" #include "../client/media/CSoundHandler.h" #include "../client/media/CVideoHandler.h" -#include "../client/render/AssetGenerator.h" #include "../client/render/Graphics.h" #include "../client/render/IRenderHandler.h" #include "../client/render/IScreenHandler.h" @@ -235,8 +234,6 @@ int main(int argc, char * argv[]) logGlobal->info("Creating console and configuring logger: %d ms", pomtime.getDiff()); logGlobal->info("The log file will be saved to %s", logPath); - AssetGenerator::clear(); - // Init filesystem and settings try { diff --git a/lib/texts/TextOperations.cpp b/lib/texts/TextOperations.cpp index 8410867d0..d29dbefdf 100644 --- a/lib/texts/TextOperations.cpp +++ b/lib/texts/TextOperations.cpp @@ -161,12 +161,24 @@ uint32_t TextOperations::getUnicodeCodepoint(char data, const std::string & enco std::string TextOperations::toUnicode(const std::string &text, const std::string &encoding) { - return boost::locale::conv::to_utf(text, encoding); + try { + return boost::locale::conv::to_utf(text, encoding); + } + catch (const boost::locale::conv::conversion_error &) + { + throw std::runtime_error("Failed to convert text '" + text + "' from encoding " + encoding ); + } } std::string TextOperations::fromUnicode(const std::string &text, const std::string &encoding) { - return boost::locale::conv::from_utf(text, encoding); + try { + return boost::locale::conv::from_utf(text, encoding); + } + catch (const boost::locale::conv::conversion_error &) + { + throw std::runtime_error("Failed to convert text '" + text + "' to encoding " + encoding ); + } } void TextOperations::trimRightUnicode(std::string & text, const size_t amount) diff --git a/mapeditor/inspector/towneventdialog.cpp b/mapeditor/inspector/towneventdialog.cpp index 7d5ff496d..e2fa1805d 100644 --- a/mapeditor/inspector/towneventdialog.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -230,10 +230,10 @@ QVariantMap TownEventDialog::resourcesToVariant() auto res = params.value("resources").toMap(); for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i) { - auto * itemType = ui->resourcesTable->item(i, 0); + auto itemType = QString::fromStdString(GameConstants::RESOURCE_NAMES[i]); auto * itemQty = static_cast (ui->resourcesTable->cellWidget(i, 1)); - res[itemType->text()] = QVariant::fromValue(itemQty->value()); + res[itemType] = QVariant::fromValue(itemQty->value()); } return res; } diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 8210b77ca..b4daf03a8 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -979,10 +979,12 @@ void MainWindow::on_actionLevel_triggered() ui->minimapView->setScene(controller.miniScene(mapLevel)); if (mapLevel == 0) { + ui->actionLevel->setText(tr("View underground")); ui->actionLevel->setToolTip(tr("View underground")); } else { + ui->actionLevel->setText(tr("View surface")); ui->actionLevel->setToolTip(tr("View surface")); } } diff --git a/mapeditor/mainwindow.ui b/mapeditor/mainwindow.ui index 31431bd8d..c0cc0382a 100644 --- a/mapeditor/mainwindow.ui +++ b/mapeditor/mainwindow.ui @@ -1067,7 +1067,7 @@ - U/G + View underground View underground diff --git a/mapeditor/mapsettings/timedevent.cpp b/mapeditor/mapsettings/timedevent.cpp index 5cc14afe6..6df7957f1 100644 --- a/mapeditor/mapsettings/timedevent.cpp +++ b/mapeditor/mapsettings/timedevent.cpp @@ -96,9 +96,9 @@ void TimedEvent::on_TimedEvent_finished(int result) auto res = target->data(Qt::UserRole).toMap().value("resources").toMap(); for(int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i) { - auto * itemType = ui->resources->item(i, 0); + auto itemType = QString::fromStdString(GameConstants::RESOURCE_NAMES[i]); auto * itemQty = ui->resources->item(i, 1); - res[itemType->text()] = QVariant::fromValue(itemQty->text().toInt()); + res[itemType] = QVariant::fromValue(itemQty->text().toInt()); } descriptor["resources"] = res; diff --git a/mapeditor/translation/czech.ts b/mapeditor/translation/czech.ts index f238b42f9..74e0adbd2 100644 --- a/mapeditor/translation/czech.ts +++ b/mapeditor/translation/czech.ts @@ -464,7 +464,7 @@ General - Všeobecné + Nastavení @@ -474,7 +474,7 @@ Players settings - Hráčské nastavení + Hráči @@ -500,7 +500,7 @@ Validate - Ověřit + Validátor @@ -2457,7 +2457,7 @@ Map validation results - Výsledky ověření mapy + Výsledky validátoru @@ -2660,10 +2660,6 @@ Map size Velikost mapy - - Two level map - Dvouvrstvá mapa - Height @@ -2689,14 +2685,6 @@ Players Hráči - - 0 - 0 - - - Human/Computer - Hráč/počítač - S (36x36) @@ -2735,10 +2723,6 @@ Random Náhodně - - Computer only - Pouze počítač - Human teams @@ -2851,10 +2835,6 @@ OK OK - - Ok - Dobře - Cancel