From 27adc78d6ce413e1db15d77bca1c9a339a5a26fe Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sat, 20 Jul 2024 09:28:50 -0300 Subject: [PATCH 1/7] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index 0e5391e17..de4b67adf 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -63,7 +63,7 @@ "vcmi.mainMenu.serverClosing" : "Fechando...", "vcmi.mainMenu.hostTCP" : "Hospedar jogo TCP/IP", "vcmi.mainMenu.joinTCP" : "Entrar em jogo TCP/IP", - + "vcmi.lobby.filepath" : "Caminho do arquivo", "vcmi.lobby.creationDate" : "Data de criação", "vcmi.lobby.scenarioName" : "Nome do cenário", @@ -237,6 +237,8 @@ "vcmi.battleOptions.skipBattleIntroMusic.help": "{Pula a Música de Introdução}\n\nPermite ações durante a música de introdução que toca no início de cada batalha.", "vcmi.battleOptions.endWithAutocombat.hover": "Terminar a batalha", "vcmi.battleOptions.endWithAutocombat.help": "{Termina a batalha}\n\nO Combate Automático reproduz a batalha até o final instantâneo.", + "vcmi.battleOptions.showQuickSpell.hover": "Mostrar Painel de Feitiço Rápido", + "vcmi.battleOptions.showQuickSpell.help": "{Mostrar Painel de Feitiço Rápido}\n\nMostra um painel para seleção rápida de feitiços", "vcmi.adventureMap.revisitObject.hover" : "Revisitar Objeto", "vcmi.adventureMap.revisitObject.help" : "{Revisitar Objeto}\n\nSe um herói estiver atualmente em um Objeto do Mapa, ele pode revisitar o local.", From 44f516d2fcbe788205585271972141c261310acc Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 19 Jul 2024 17:24:03 +0000 Subject: [PATCH 2/7] Add overlay that shows map objects and their name & status --- client/mapView/IMapRendererContext.h | 12 ++++- client/mapView/MapRendererContext.cpp | 77 ++++++++++++++++++++++++++- client/mapView/MapRendererContext.h | 11 +++- client/mapView/MapViewCache.cpp | 44 +++++++++++++-- client/mapView/MapViewCache.h | 1 + client/mapView/MapViewController.cpp | 1 + 6 files changed, 138 insertions(+), 8 deletions(-) diff --git a/client/mapView/IMapRendererContext.h b/client/mapView/IMapRendererContext.h index 1fab9394d..e1ae49f47 100644 --- a/client/mapView/IMapRendererContext.h +++ b/client/mapView/IMapRendererContext.h @@ -16,6 +16,7 @@ class Point; class CGObjectInstance; class ObjectInstanceID; struct TerrainTile; +class ColorRGBA; struct CGPath; VCMI_LIB_NAMESPACE_END @@ -67,6 +68,12 @@ public: /// returns index of image for overlay on specific tile, or numeric_limits::max if none virtual size_t overlayImageIndex(const int3 & coordinates) const = 0; + /// returns text that should be used as overlay for current tile + virtual std::string overlayText(const int3 & coordinates) const = 0; + + /// returns text that should be used as overlay for current tile + virtual ColorRGBA overlayTextColor(const int3 & coordinates) const = 0; + /// returns animation frame for terrain virtual size_t terrainImageIndex(size_t groupSize) const = 0; @@ -80,7 +87,10 @@ public: virtual bool showBorder() const = 0; /// if true, world view overlay will be shown - virtual bool showOverlay() const = 0; + virtual bool showImageOverlay() const = 0; + + // if true, new text overlay will be shown + virtual bool showTextOverlay() const = 0; /// if true, map grid should be visible on map virtual bool showGrid() const = 0; diff --git a/client/mapView/MapRendererContext.cpp b/client/mapView/MapRendererContext.cpp index 88348af09..fd872a210 100644 --- a/client/mapView/MapRendererContext.cpp +++ b/client/mapView/MapRendererContext.cpp @@ -156,6 +156,16 @@ size_t MapRendererBaseContext::overlayImageIndex(const int3 & coordinates) const return std::numeric_limits::max(); } +std::string MapRendererBaseContext::overlayText(const int3 & coordinates) const +{ + return {}; +} + +ColorRGBA MapRendererBaseContext::overlayTextColor(const int3 & coordinates) const +{ + return {}; +} + double MapRendererBaseContext::viewTransitionProgress() const { return 0; @@ -181,7 +191,12 @@ bool MapRendererBaseContext::showBorder() const return false; } -bool MapRendererBaseContext::showOverlay() const +bool MapRendererBaseContext::showImageOverlay() const +{ + return false; +} + +bool MapRendererBaseContext::showTextOverlay() const { return false; } @@ -253,6 +268,59 @@ size_t MapRendererAdventureContext::terrainImageIndex(size_t groupSize) const return frameIndex; } +std::string MapRendererAdventureContext::overlayText(const int3 & coordinates) const +{ + if(!isVisible(coordinates)) + return {}; + + const auto & tile = getMapTile(coordinates); + + if (!tile.visitable) + return {}; + + return tile.visitableObjects.back()->getObjectName(); +} + +ColorRGBA MapRendererAdventureContext::overlayTextColor(const int3 & coordinates) const +{ + if(!isVisible(coordinates)) + return {}; + + const auto & tile = getMapTile(coordinates); + + if (!tile.visitable) + return {}; + + auto * object = tile.visitableObjects.back(); + + if (object->getOwner() == LOCPLINT->playerID) + return { 0, 192, 0}; + + if (LOCPLINT->cb->getPlayerRelations(object->getOwner(), LOCPLINT->playerID) == PlayerRelations::ALLIES) + return { 0, 128, 255}; + + if (object->getOwner().isValidPlayer()) + return { 255, 0, 0}; + + if (object->ID == MapObjectID::MONSTER) + return { 255, 0, 0}; + + auto hero = LOCPLINT->localState->getCurrentHero(); + + if (hero) + { + if (object->wasVisited(hero)) + return { 160, 160, 160 }; + } + else + { + if (object->wasVisited(LOCPLINT->playerID)) + return { 160, 160, 160 }; + } + + return { 255, 192, 0 }; +} + bool MapRendererAdventureContext::showBorder() const { return true; @@ -273,6 +341,11 @@ bool MapRendererAdventureContext::showBlocked() const return settingShowBlocked; } +bool MapRendererAdventureContext::showTextOverlay() const +{ + return settingTextOverlay; +} + bool MapRendererAdventureContext::showSpellRange(const int3 & position) const { if (!settingSpellRange) @@ -411,7 +484,7 @@ MapRendererWorldViewContext::MapRendererWorldViewContext(const MapRendererContex { } -bool MapRendererWorldViewContext::showOverlay() const +bool MapRendererWorldViewContext::showImageOverlay() const { return true; } diff --git a/client/mapView/MapRendererContext.h b/client/mapView/MapRendererContext.h index 742053fd1..b956f1f09 100644 --- a/client/mapView/MapRendererContext.h +++ b/client/mapView/MapRendererContext.h @@ -48,13 +48,16 @@ public: size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override; size_t terrainImageIndex(size_t groupSize) const override; size_t overlayImageIndex(const int3 & coordinates) const override; + std::string overlayText(const int3 & coordinates) const override; + ColorRGBA overlayTextColor(const int3 & coordinates) const override; double viewTransitionProgress() const override; bool filterGrayscale() const override; bool showRoads() const override; bool showRivers() const override; bool showBorder() const override; - bool showOverlay() const override; + bool showImageOverlay() const override; + bool showTextOverlay() const override; bool showGrid() const override; bool showVisitable() const override; bool showBlocked() const override; @@ -69,6 +72,7 @@ public: bool settingShowVisitable = false; bool settingShowBlocked = false; bool settingSpellRange= false; + bool settingTextOverlay = false; bool settingsAdventureObjectAnimation = true; bool settingsAdventureTerrainAnimation = true; @@ -77,11 +81,14 @@ public: const CGPath * currentPath() const override; size_t objectImageIndex(ObjectInstanceID objectID, size_t groupSize) const override; size_t terrainImageIndex(size_t groupSize) const override; + std::string overlayText(const int3 & coordinates) const override; + ColorRGBA overlayTextColor(const int3 & coordinates) const override; bool showBorder() const override; bool showGrid() const override; bool showVisitable() const override; bool showBlocked() const override; + bool showTextOverlay() const override; bool showSpellRange(const int3 & position) const override; }; @@ -133,7 +140,7 @@ public: explicit MapRendererWorldViewContext(const MapRendererContextState & viewState); size_t overlayImageIndex(const int3 & coordinates) const override; - bool showOverlay() const override; + bool showImageOverlay() const override; }; class MapRendererSpellViewContext : public MapRendererWorldViewContext diff --git a/client/mapView/MapViewCache.cpp b/client/mapView/MapViewCache.cpp index cb9ff8d81..30085081b 100644 --- a/client/mapView/MapViewCache.cpp +++ b/client/mapView/MapViewCache.cpp @@ -18,9 +18,12 @@ #include "../render/CAnimation.h" #include "../render/Canvas.h" #include "../render/IImage.h" +#include "../render/IFont.h" #include "../render/IRenderHandler.h" +#include "../render/Graphics.h" #include "../gui/CGuiHandler.h" +#include "../widgets/TextControls.h" #include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/int3.h" @@ -30,6 +33,7 @@ MapViewCache::~MapViewCache() = default; MapViewCache::MapViewCache(const std::shared_ptr & model) : model(model) , cachedLevel(0) + , overlayWasVisible(false) , mapRenderer(new MapRenderer()) , iconsStorage(GH.renderHandler().loadAnimation(AnimationPath::builtin("VwSymbol"), EImageBlitMode::COLORKEY)) , intermediate(new Canvas(Point(32, 32))) @@ -137,7 +141,9 @@ void MapViewCache::update(const std::shared_ptr & context) void MapViewCache::render(const std::shared_ptr & context, Canvas & target, bool fullRedraw) { bool mapMoved = (cachedPosition != model->getMapViewCenter()); - bool lazyUpdate = !mapMoved && !fullRedraw && vstd::isAlmostZero(context->viewTransitionProgress()); + bool overlayVisible = context->showImageOverlay() || context->showTextOverlay(); + bool overlayVisibilityChanged = overlayVisible != overlayWasVisible; + bool lazyUpdate = !overlayVisibilityChanged && !mapMoved && !fullRedraw && vstd::isAlmostZero(context->viewTransitionProgress()); Rect dimensions = model->getTilesTotalRect(); @@ -161,18 +167,18 @@ void MapViewCache::render(const std::shared_ptr & context, } } - if(context->showOverlay()) + if(context->showImageOverlay()) { for(int y = dimensions.top(); y < dimensions.bottom(); ++y) { for(int x = dimensions.left(); x < dimensions.right(); ++x) { int3 tile(x, y, model->getLevel()); - Rect targetRect = model->getTargetTileArea(tile); auto overlay = getOverlayImageForTile(context, tile); if(overlay) { + Rect targetRect = model->getTargetTileArea(tile); Point position = targetRect.center() - overlay->dimensions() / 2; target.draw(overlay, position); } @@ -180,10 +186,42 @@ void MapViewCache::render(const std::shared_ptr & context, } } + if(context->showTextOverlay()) + { + for(int y = dimensions.top(); y < dimensions.bottom(); ++y) + { + for(int x = dimensions.left(); x < dimensions.right(); ++x) + { + int3 tile(x, y, model->getLevel()); + auto overlay = context->overlayText(tile); + + if(!overlay.empty()) + { + Rect targetRect = model->getTargetTileArea(tile); + Point position = targetRect.center(); + if (x % 2 == 0) + position.y += targetRect.h / 4; + else + position.y -= targetRect.h / 4; + + const auto font = graphics->fonts[EFonts::FONT_TINY]; + + Point dimensions(font->getStringWidth(overlay), font->getLineHeight()); + Rect textRect = Rect(position - dimensions / 2, dimensions).resize(2); + + target.drawColor(textRect, context->overlayTextColor(tile)); + target.drawBorder(textRect, Colors::BRIGHT_YELLOW); + target.drawText(position, EFonts::FONT_TINY, Colors::BLACK, ETextAlignment::CENTER, overlay); + } + } + } + } + if(!vstd::isAlmostZero(context->viewTransitionProgress())) target.drawTransparent(*terrainTransition, Point(0, 0), 1.0 - context->viewTransitionProgress()); cachedPosition = model->getMapViewCenter(); + overlayWasVisible = overlayVisible; } void MapViewCache::createTransitionSnapshot(const std::shared_ptr & context) diff --git a/client/mapView/MapViewCache.h b/client/mapView/MapViewCache.h index 0435d3584..ad2ba2a7a 100644 --- a/client/mapView/MapViewCache.h +++ b/client/mapView/MapViewCache.h @@ -44,6 +44,7 @@ class MapViewCache Point cachedSize; Point cachedPosition; int cachedLevel; + bool overlayWasVisible; std::shared_ptr model; diff --git a/client/mapView/MapViewController.cpp b/client/mapView/MapViewController.cpp index 7ba38a162..ec33d6f5b 100644 --- a/client/mapView/MapViewController.cpp +++ b/client/mapView/MapViewController.cpp @@ -224,6 +224,7 @@ void MapViewController::updateState() adventureContext->settingShowVisitable = settings["session"]["showVisitable"].Bool(); adventureContext->settingShowBlocked = settings["session"]["showBlocked"].Bool(); adventureContext->settingSpellRange = settings["session"]["showSpellRange"].Bool(); + adventureContext->settingTextOverlay = GH.isKeyboardAltDown(); } } From 5bd9a32d974010fa3219678cf08c29db675223af Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 20 Jul 2024 18:27:02 +0000 Subject: [PATCH 3/7] Implemented simple target selection logic for arrow towers --- lib/battle/CBattleInfoCallback.cpp | 11 +++++++ lib/battle/CBattleInfoCallback.h | 1 + server/battles/BattleFlowProcessor.cpp | 45 ++++++++++++++++++++------ 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index be1e6cc1d..f61b3fb1e 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -50,6 +50,12 @@ static bool sameSideOfWall(BattleHex pos1, BattleHex pos2) return stackLeft == destLeft; } +static bool isInsideWalls(BattleHex pos) +{ + const int wallInStackLine = lineToWallHex(pos.getY()); + return wallInStackLine < pos; +} + // parts of wall static const std::pair wallParts[] = { @@ -158,6 +164,11 @@ std::pair< std::vector, int > CBattleInfoCallback::getPath(BattleHex return std::make_pair(path, reachability.distances[dest]); } +bool CBattleInfoCallback::battleIsInsideWalls(BattleHex from) const +{ + return isInsideWalls(from); +} + bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const { auto isTileBlocked = [&](BattleHex tile) diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index 1ee127522..51a86d97b 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -104,6 +104,7 @@ public: DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageEstimation * retaliationDmg = nullptr) const; DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int getMovementRange, DamageEstimation * retaliationDmg = nullptr) const; + bool battleIsInsideWalls(BattleHex from) const; bool battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const; bool battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; bool battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; diff --git a/server/battles/BattleFlowProcessor.cpp b/server/battles/BattleFlowProcessor.cpp index 920ee1a4a..e16da4ba3 100644 --- a/server/battles/BattleFlowProcessor.cpp +++ b/server/battles/BattleFlowProcessor.cpp @@ -389,20 +389,47 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CBattleInfoCallback & bat attack.side = next->unitSide(); attack.stackNumber = next->unitId(); - //TODO: select target by priority + // TODO: unify logic with AI? + // Find best target using logic similar to H3 AI + + const auto & isBetterTarget = [&battle](const battle::Unit * candidate, const battle::Unit * current) + { + bool candidateInsideWalls = battle.battleIsInsideWalls(candidate->getPosition()); + bool currentInsideWalls = battle.battleIsInsideWalls(current->getPosition()); + + if (candidateInsideWalls != currentInsideWalls) + return candidateInsideWalls > currentInsideWalls; + + // also check for war machines - shooters are more dangerous than war machines, ballista or catapult + bool candidateCanShoot = candidate->canShoot() && candidate->unitType()->warMachine == ArtifactID::NONE; + bool currentCanShoot = current->canShoot() && current->unitType()->warMachine == ArtifactID::NONE; + + if (candidateCanShoot != currentCanShoot) + return candidateCanShoot > currentCanShoot; + + int64_t candidateTargetValue = static_cast(candidate->unitType()->getAIValue() * candidate->getCount()); + int64_t currentTargetValue = static_cast(current->unitType()->getAIValue() * current->getCount()); + + return candidateTargetValue > currentTargetValue; + }; const battle::Unit * target = nullptr; for(auto & elem : battle.battleGetAllStacks(true)) { - if(elem->unitType()->getId() != CreatureID::CATAPULT - && elem->unitOwner() != next->unitOwner() - && elem->isValidTarget() - && battle.battleCanShoot(next, elem->getPosition())) - { - target = elem; - break; - } + if (elem->unitOwner() == next->unitOwner()) + continue; + + if (!elem->isValidTarget()) + continue; + + if (!battle.battleCanShoot(next, elem->getPosition())) + continue; + + if (target && !isBetterTarget(elem, target)) + continue; + + target = elem; } if(target == nullptr) From f0568e21957049636c0e575a56093c7d84ca6123 Mon Sep 17 00:00:00 2001 From: void Date: Tue, 23 Jul 2024 07:28:45 +0900 Subject: [PATCH 4/7] docs: add building instruction for the NixOS --- docs/developers/Building_Linux.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/developers/Building_Linux.md b/docs/developers/Building_Linux.md index b64fc1541..4a9b42cf1 100644 --- a/docs/developers/Building_Linux.md +++ b/docs/developers/Building_Linux.md @@ -45,6 +45,27 @@ It can be found at https://aur.archlinux.org/packages/vcmi-git/ Information about building packages from the Arch User Repository (AUR) can be found at the Arch wiki. +### On NixOS or Nix + +On NixOS or any system with nix available, [it is recommended](https://nixos.wiki/wiki/C) to use nix-shell. Create a shell.nix file with the following content: + +```nix +with import {}; +stdenv.mkDerivation { + name = "build"; + nativeBuildInputs = [ cmake ]; + buildInputs = [ + cmake clang clang-tools llvm ccache ninja + boost zlib minizip xz + SDL2 SDL2_ttf SDL2_net SDL2_image SDL2_sound SDL2_mixer SDL2_gfx + ffmpeg tbb vulkan-headers libxkbcommon + qt6.full luajit + ]; +} +``` + +And put it into build directory. Then run `nix-shell` before running any build commands. + ## Getting the sources We recommend the following directory structure: From e2396d2a27af39927bb8aa8d1b265c84f16bbcc7 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Mon, 22 Jul 2024 05:04:07 +0200 Subject: [PATCH 5/7] CI: Use URL relative to repo's owner for test data This allows to easily run tests in forks --- .github/workflows/github.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index cd8498c2e..7d3b02406 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -146,7 +146,13 @@ jobs: HEROES_3_DATA_PASSWORD: ${{ secrets.HEROES_3_DATA_PASSWORD }} if: ${{ env.HEROES_3_DATA_PASSWORD != '' && matrix.test == 1 }} run: | - wget --progress=dot:giga https://github.com/vcmi-mods/vcmi-test-data/releases/download/v1.0/h3_assets.zip + if [[ ${{github.repository_owner}} == vcmi ]] + then + data_url="https://github.com/vcmi-mods/vcmi-test-data/releases/download/v1.0/h3_assets.zip" + else + data_url="https://github.com/${{github.repository_owner}}/vcmi-test-data/releases/download/v1.0/h3_assets.zip" + fi + wget --progress=dot:giga "$data_url" -O h3_assets.zip 7za x h3_assets.zip -p$HEROES_3_DATA_PASSWORD mkdir -p ~/.local/share/vcmi/ mv h3_assets/* ~/.local/share/vcmi/ From 2d100b00cd7b4311cf9a8fa638aada4263c0ee7d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 26 Jul 2024 01:06:54 +0200 Subject: [PATCH 6/7] check spellbook --- client/battle/BattleWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index 15216c811..d8bb527b5 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -218,7 +218,7 @@ void BattleWindow::showStickyQuickSpellWindow() Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"]; showStickyQuickSpellWindow->Bool() = true; - if(GH.screenDimensions().x >= 1050) + if(GH.screenDimensions().x >= 1050 && owner.getBattle()->battleGetMyHero()->hasSpellbook()) { quickSpellWindow->enable(); quickSpellWindow->isEnabled = true; @@ -838,7 +838,7 @@ void BattleWindow::blockUI(bool on) ESpellCastProblem spellcastingProblem = owner.getBattle()->battleCanCastSpell(hero, spells::Mode::HERO); //if magic is blocked, we leave button active, so the message can be displayed after button click - canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED; + canCastSpells = (spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED) && hero->hasSpellbook(); } bool canWait = owner.stacksController->getActiveStack() ? !owner.stacksController->getActiveStack()->waitedThisTurn : false; From e46f5f705bddf717b0bfd6fad40a63572c86d5fa Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 26 Jul 2024 20:34:47 +0200 Subject: [PATCH 7/7] better approach --- client/battle/BattleWindow.cpp | 2 +- lib/battle/CBattleInfoCallback.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index d8bb527b5..9bc8806cd 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -838,7 +838,7 @@ void BattleWindow::blockUI(bool on) ESpellCastProblem spellcastingProblem = owner.getBattle()->battleCanCastSpell(hero, spells::Mode::HERO); //if magic is blocked, we leave button active, so the message can be displayed after button click - canCastSpells = (spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED) && hero->hasSpellbook(); + canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED; } bool canWait = owner.stacksController->getActiveStack() ? !owner.stacksController->getActiveStack()->waitedThisTurn : false; diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index be1e6cc1d..0ad1a62e5 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -128,6 +128,8 @@ ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(const spells::Caster * return ESpellCastProblem::NO_HERO_TO_CAST_SPELL; if(hero->hasBonusOfType(BonusType::BLOCK_ALL_MAGIC)) return ESpellCastProblem::MAGIC_IS_BLOCKED; + if(!hero->hasSpellbook()) + return ESpellCastProblem::NO_SPELLBOOK; } break; default: