diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index 1f79d012a..8c525c072 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -24,6 +24,7 @@ #include "../gui/CIntObject.h" #include "../gui/WindowHandler.h" #include "../windows/CCreatureWindow.h" +#include "../windows/InfoWindows.h" #include "../../CCallback.h" #include "../../lib/CConfigHandler.h" @@ -1003,6 +1004,7 @@ void BattleActionsController::onHexRightClicked(BattleHex clickedHex) if (spellcastingModeActive() || isCurrentStackInSpellcastMode) { endCastingSpell(); + CRClickPopup::createAndPush(CGI->generaltexth->translate("core.genrltxt.731")); // spell cancelled return; } diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 8eda33038..cb239a3eb 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -128,6 +128,9 @@ BattleFieldController::BattleFieldController(BattleInterface & owner): attackCursors = std::make_shared("CRCOMBAT"); attackCursors->preload(); + spellCursors = std::make_shared("CRSPELL"); + spellCursors->preload(); + initializeHexEdgeMaskToFrameIndex(); rangedFullDamageLimitImages = std::make_shared("battle/rangeHighlights/rangeHighlightsGreen.json"); @@ -888,10 +891,22 @@ void BattleFieldController::show(Canvas & to) if (isActive() && isGesturing() && getHoveredHex() != BattleHex::INVALID) { - auto cursorIndex = CCS->curh->get(); - auto imageIndex = static_cast(cursorIndex); + auto combatCursorIndex = CCS->curh->get(); + if (combatCursorIndex) + { + auto combatImageIndex = static_cast(*combatCursorIndex); + to.draw(attackCursors->getImage(combatImageIndex), hexPositionAbsolute(getHoveredHex()).center() - CCS->curh->getPivotOffsetCombat(combatImageIndex)); + return; + } + + auto spellCursorIndex = CCS->curh->get(); + if (spellCursorIndex) + { + auto spellImageIndex = static_cast(*spellCursorIndex); + to.draw(spellCursors->getImage(spellImageIndex), hexPositionAbsolute(getHoveredHex()).center() - CCS->curh->getPivotOffsetSpellcast()); + return; + } - to.draw(attackCursors->getImage(imageIndex), hexPositionAbsolute(getHoveredHex()).center() - CCS->curh->getPivotOffsetCombat(imageIndex)); } } diff --git a/client/battle/BattleFieldController.h b/client/battle/BattleFieldController.h index 00c0ca793..b97649cc8 100644 --- a/client/battle/BattleFieldController.h +++ b/client/battle/BattleFieldController.h @@ -37,6 +37,7 @@ class BattleFieldController : public CIntObject std::shared_ptr shootingRangeLimitImages; std::shared_ptr attackCursors; + std::shared_ptr spellCursors; /// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack std::unique_ptr backgroundWithHexes; diff --git a/client/gui/CursorHandler.h b/client/gui/CursorHandler.h index 531594b3a..20ad2b337 100644 --- a/client/gui/CursorHandler.h +++ b/client/gui/CursorHandler.h @@ -125,7 +125,6 @@ class CursorHandler final void changeGraphic(Cursor::Type type, size_t index); - Point getPivotOffsetSpellcast(); Point getPivotOffset(); void updateSpellcastCursor(); @@ -153,7 +152,7 @@ public: /// Returns current index of cursor template - Index get() + std::optional get() { bool typeValid = true; @@ -164,9 +163,10 @@ public: if (typeValid) return static_cast(frame); - return Index::POINTER; + return std::nullopt; } + Point getPivotOffsetSpellcast(); Point getPivotOffsetDefault(size_t index); Point getPivotOffsetMap(size_t index); Point getPivotOffsetCombat(size_t index); diff --git a/lib/rewardable/Interface.cpp b/lib/rewardable/Interface.cpp index f34aa8055..d8521dbea 100644 --- a/lib/rewardable/Interface.cpp +++ b/lib/rewardable/Interface.cpp @@ -140,13 +140,11 @@ void Rewardable::Interface::grantRewardAfterLevelup(IGameCallback * cb, const Re caster.setActualCaster(hero); caster.setSpellSchoolLevel(info.reward.spellCast.second); cb->castSpell(&caster, info.reward.spellCast.first, int3{-1, -1, -1}); - - if(info.reward.removeObject) - logMod->warn("Removal of object with spell casts is not supported!"); } - else if(info.reward.removeObject) //FIXME: object can't track spell cancel or finish, so removeObject leads to crash + + if(info.reward.removeObject) if(auto * instance = dynamic_cast(this)) - cb->removeObject(instance); + cb->removeAfterVisit(instance); } VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index db3162a27..186563bf0 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -6120,6 +6120,12 @@ void CGameHandler::runBattle() sendAndApply(&pack); } } + + // send empty event to client + // temporary(?) workaround to force animations to trigger + StacksInjured fakeEvent; + sendAndApply(&fakeEvent); + } stackEnchantedTrigger(stack); diff --git a/server/PlayerMessageProcessor.cpp b/server/PlayerMessageProcessor.cpp index 268802a9a..f044c17b6 100644 --- a/server/PlayerMessageProcessor.cpp +++ b/server/PlayerMessageProcessor.cpp @@ -211,15 +211,27 @@ void PlayerMessageProcessor::cheatGiveMachines(PlayerColor player, const CGHeroI gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3); } -void PlayerMessageProcessor::cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero) +void PlayerMessageProcessor::cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero, std::vector words) { if (!hero) return; - for(int g = 7; g < VLC->arth->objects.size(); ++g) //including artifacts from mods + if (!words.empty()) { - if(VLC->arth->objects[g]->canBePutAt(hero)) - gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[g], ArtifactPosition::FIRST_AVAILABLE); + for (auto const & word : words) + { + auto artID = VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "artifact", word, false); + if(artID && VLC->arth->objects[*artID]) + gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[*artID], ArtifactPosition::FIRST_AVAILABLE); + } + } + else + { + for(int g = 7; g < VLC->arth->objects.size(); ++g) //including artifacts from mods + { + if(VLC->arth->objects[g]->canBePutAt(hero)) + gameHandler->giveHeroNewArtifact(hero, VLC->arth->objects[g], ArtifactPosition::FIRST_AVAILABLE); + } } } @@ -432,7 +444,7 @@ void PlayerMessageProcessor::executeCheatCode(const std::string & cheatName, Pla const auto & doCheatGiveArmyCustom = [&]() { cheatGiveArmy(player, hero, words); }; const auto & doCheatGiveArmyFixed = [&](std::vector customWords) { cheatGiveArmy(player, hero, customWords); }; const auto & doCheatGiveMachines = [&]() { cheatGiveMachines(player, hero); }; - const auto & doCheatGiveArtifacts = [&]() { cheatGiveArtifacts(player, hero); }; + const auto & doCheatGiveArtifacts = [&]() { cheatGiveArtifacts(player, hero, words); }; const auto & doCheatLevelup = [&]() { cheatLevelup(player, hero, words); }; const auto & doCheatExperience = [&]() { cheatExperience(player, hero, words); }; const auto & doCheatMovement = [&]() { cheatMovement(player, hero, words); }; diff --git a/server/PlayerMessageProcessor.h b/server/PlayerMessageProcessor.h index 2351140ca..bb9335676 100644 --- a/server/PlayerMessageProcessor.h +++ b/server/PlayerMessageProcessor.h @@ -31,7 +31,7 @@ class PlayerMessageProcessor void cheatBuildTown(PlayerColor player, const CGTownInstance * town); void cheatGiveArmy(PlayerColor player, const CGHeroInstance * hero, std::vector words); void cheatGiveMachines(PlayerColor player, const CGHeroInstance * hero); - void cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero); + void cheatGiveArtifacts(PlayerColor player, const CGHeroInstance * hero, std::vector words); void cheatLevelup(PlayerColor player, const CGHeroInstance * hero, std::vector words); void cheatExperience(PlayerColor player, const CGHeroInstance * hero, std::vector words); void cheatMovement(PlayerColor player, const CGHeroInstance * hero, std::vector words);