diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 3ff581c61..efd2c6d88 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -60,6 +60,12 @@ void CBattleAI::initBattleInterface(std::shared_ptr ENV, std::share movesSkippedByDefense = 0; } +void CBattleAI::initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB, AutocombatPreferences autocombatPreferences) +{ + initBattleInterface(ENV, CB); + autobattlePreferences = autocombatPreferences; +} + BattleAction CBattleAI::useHealingTent(const CStack *stack) { auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE); diff --git a/AI/BattleAI/BattleAI.h b/AI/BattleAI/BattleAI.h index 95841684e..a059e501c 100644 --- a/AI/BattleAI/BattleAI.h +++ b/AI/BattleAI/BattleAI.h @@ -69,6 +69,7 @@ public: ~CBattleAI(); void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB) override; + void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB, AutocombatPreferences autocombatPreferences) override; void activeStack(const CStack * stack) override; //called when it's turn of that stack void yourTacticPhase(int distance) override; @@ -94,4 +95,5 @@ public: //void battleTriggerEffect(const BattleTriggerEffect & bte) override; //void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right //void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack + AutocombatPreferences autobattlePreferences = AutocombatPreferences(); }; diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index 50db79eb9..c8f495885 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -48,6 +48,11 @@ void CStupidAI::initBattleInterface(std::shared_ptr ENV, std::share CB->unlockGsWhenWaiting = false; } +void CStupidAI::initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB, AutocombatPreferences autocombatPreferences) +{ + initBattleInterface(ENV, CB); +} + void CStupidAI::actionFinished(const BattleAction &action) { print("actionFinished called"); diff --git a/AI/StupidAI/StupidAI.h b/AI/StupidAI/StupidAI.h index 3b074d643..6b0d230ad 100644 --- a/AI/StupidAI/StupidAI.h +++ b/AI/StupidAI/StupidAI.h @@ -29,6 +29,7 @@ public: ~CStupidAI(); void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB) override; + void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB, AutocombatPreferences autocombatPreferences) override; void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero void activeStack(const CStack * stack) override; //called when it's turn of that stack diff --git a/Mods/vcmi/Sprites/buttons/backpack.json b/Mods/vcmi/Sprites/buttons/backpack.json new file mode 100644 index 000000000..a74a8c3a1 --- /dev/null +++ b/Mods/vcmi/Sprites/buttons/backpack.json @@ -0,0 +1,8 @@ +{ + "basepath" : "buttons/", + "images" : + [ + { "frame" : 0, "file" : "backpackNormal.png"}, + { "frame" : 1, "file" : "backpackPressed.png"} + ] +} diff --git a/Mods/vcmi/Sprites/buttons/backpackNormal.png b/Mods/vcmi/Sprites/buttons/backpackNormal.png new file mode 100644 index 000000000..0b7691614 Binary files /dev/null and b/Mods/vcmi/Sprites/buttons/backpackNormal.png differ diff --git a/Mods/vcmi/Sprites/buttons/backpackPressed.png b/Mods/vcmi/Sprites/buttons/backpackPressed.png new file mode 100644 index 000000000..00471588e Binary files /dev/null and b/Mods/vcmi/Sprites/buttons/backpackPressed.png differ diff --git a/Mods/vcmi/Sprites/heroWindow/artifactSlotEmpty.png b/Mods/vcmi/Sprites/heroWindow/artifactSlotEmpty.png new file mode 100644 index 000000000..f0dec0972 Binary files /dev/null and b/Mods/vcmi/Sprites/heroWindow/artifactSlotEmpty.png differ diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 8ef48c372..f60af4153 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -172,6 +172,8 @@ "vcmi.heroWindow.openCommander.hover" : "Open commander info window", "vcmi.heroWindow.openCommander.help" : "Shows details about the commander of this hero", + "vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window", + "vcmi.heroWindow.openBackpack.help" : "Opens window that allows easier artifact backpack management", "vcmi.commanderWindow.artifactMessage" : "Do you want to return this artifact to the hero?", diff --git a/Mods/vcmi/config/vcmi/polish.json b/Mods/vcmi/config/vcmi/polish.json index 4ebc37479..fce3a093d 100644 --- a/Mods/vcmi/config/vcmi/polish.json +++ b/Mods/vcmi/config/vcmi/polish.json @@ -172,6 +172,8 @@ "vcmi.heroWindow.openCommander.hover" : "Otwórz okno dowódcy", "vcmi.heroWindow.openCommander.help" : "Wyświetla informacje o dowódcy przynależącym do tego bohatera", + "vcmi.heroWindow.openBackpack.hover" : "Otwórz okno sakwy", + "vcmi.heroWindow.openBackpack.help" : "Otwiera okno pozwalające łatwiej zarządzać artefaktami w sakwie", "vcmi.commanderWindow.artifactMessage" : "Czy chcesz zwrócić ten artefakt bohaterowi?", diff --git a/android/vcmi-app/build.gradle b/android/vcmi-app/build.gradle index 87ffcc3d4..3b7d295ea 100644 --- a/android/vcmi-app/build.gradle +++ b/android/vcmi-app/build.gradle @@ -3,13 +3,13 @@ plugins { } android { - compileSdk 31 + compileSdk 33 ndkVersion '25.2.9519653' defaultConfig { applicationId "is.xyz.vcmi" minSdk 19 - targetSdk 31 + targetSdk 33 versionCode 1400 versionName "1.4.0" setProperty("archivesBaseName", "vcmi") diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index ce4f4a419..8353e7a55 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -493,9 +493,6 @@ void CPlayerInterface::heroManaPointsChanged(const CGHeroInstance * hero) adventureInt->onHeroChanged(hero); if (makingTurn && hero->tempOwner == playerID) adventureInt->onHeroChanged(hero); - - for (auto window : GH.windows().findWindows()) - window->heroManaPointsChanged(hero); } void CPlayerInterface::heroMovePointsChanged(const CGHeroInstance * hero) { @@ -684,7 +681,11 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet if ((replayAllowed && useQuickCombat) || forceQuickCombat) { autofightingAI = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String()); - autofightingAI->initBattleInterface(env, cb); + + AutocombatPreferences autocombatPreferences = AutocombatPreferences(); + autocombatPreferences.enableSpellsUsage = settings["battle"]["enableAutocombatSpells"].Bool(); + + autofightingAI->initBattleInterface(env, cb, autocombatPreferences); autofightingAI->battleStart(army1, army2, tile, hero1, hero2, side, false); isAutoFightOn = true; cb->registerBattleInterface(autofightingAI); @@ -916,6 +917,12 @@ void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte) RETURN_IF_QUICK_COMBAT; battleInt->effectsController->battleTriggerEffect(bte); + + if(bte.effect == vstd::to_underlying(BonusType::MANA_DRAIN)) + { + const CGHeroInstance * manaDrainedHero = LOCPLINT->cb->getHero(ObjectInstanceID(bte.additionalInfo)); + battleInt->windowObject->heroManaPointsChanged(manaDrainedHero); + } } void CPlayerInterface::battleStacksAttacked(const std::vector & bsa, bool ranged) { diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 6408c2566..a87ddc69b 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -17,7 +17,9 @@ #include "mapView/mapHandler.h" #include "adventureMap/CInGameConsole.h" #include "battle/BattleInterface.h" +#include "battle/BattleWindow.h" #include "gui/CGuiHandler.h" +#include "gui/WindowHandler.h" #include "widgets/MiscWidgets.h" #include "CMT.h" #include "CServerHandler.h" @@ -153,6 +155,9 @@ void ApplyClientNetPackVisitor::visitSetMana(SetMana & pack) { const CGHeroInstance *h = cl.getHero(pack.hid); callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroManaPointsChanged, h); + + for (auto window : GH.windows().findWindows()) + window->heroManaPointsChanged(h); } void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack) diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index 7f45e126e..55b01d2af 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -215,7 +215,7 @@ std::vector BattleActionsController::getPossibleActi return std::vector(allActions); } -void BattleActionsController::reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context) +void BattleActionsController::reorderPossibleActionsPriority(const CStack * stack, const CStack * targetStack) { if(owner.tacticsMode || possibleActions.empty()) return; //this function is not supposed to be called in tactics mode or before getPossibleActionsForStack @@ -229,10 +229,17 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac case PossiblePlayerBattleAction::NO_LOCATION: case PossiblePlayerBattleAction::FREE_LOCATION: case PossiblePlayerBattleAction::OBSTACLE: - if(!stack->hasBonusOfType(BonusType::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX) - return 1; - else - return 100; //bottom priority + if(!stack->hasBonusOfType(BonusType::NO_SPELLCAST_BY_DEFAULT) && targetStack != nullptr) + { + PlayerColor stackOwner = owner.curInt->cb->battleGetOwner(targetStack); + bool enemyTargetingPositiveSpellcast = item.spell().toSpell()->isPositive() && stackOwner != LOCPLINT->playerID; + bool friendTargetingNegativeSpellcast = item.spell().toSpell()->isNegative() && stackOwner == LOCPLINT->playerID; + + if(!enemyTargetingPositiveSpellcast && !friendTargetingNegativeSpellcast) + return 1; + } + return 100; //bottom priority + break; case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: return 2; @@ -788,7 +795,7 @@ PossiblePlayerBattleAction BattleActionsController::selectAction(BattleHex targe const CStack * targetStack = getStackForHex(targetHex); - reorderPossibleActionsPriority(owner.stacksController->getActiveStack(), targetStack ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX); + reorderPossibleActionsPriority(owner.stacksController->getActiveStack(), targetStack); for (PossiblePlayerBattleAction action : possibleActions) { @@ -972,6 +979,11 @@ void BattleActionsController::activateStack() case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: actionsToSelect.push_back(possibleActions.front()); + actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK); + break; + case PossiblePlayerBattleAction::ANY_LOCATION: + actionsToSelect.push_back(possibleActions.front()); + actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK); break; } } @@ -981,8 +993,18 @@ void BattleActionsController::activateStack() void BattleActionsController::onHexRightClicked(BattleHex clickedHex) { - if (spellcastingModeActive()) + auto spellcastActionPredicate = [](PossiblePlayerBattleAction & action) + { + return action.spellcast(); + }; + + bool isCurrentStackInSpellcastMode = std::all_of(possibleActions.begin(), possibleActions.end(), spellcastActionPredicate); + + if (spellcastingModeActive() || isCurrentStackInSpellcastMode) + { endCastingSpell(); + return; + } auto selectedStack = owner.curInt->cb->battleGetStackByPos(clickedHex, true); @@ -998,7 +1020,7 @@ void BattleActionsController::onHexRightClicked(BattleHex clickedHex) bool BattleActionsController::spellcastingModeActive() const { - return heroSpellToCast != nullptr;; + return heroSpellToCast != nullptr; } bool BattleActionsController::currentActionSpellcasting(BattleHex hoveredHex) diff --git a/client/battle/BattleActionsController.h b/client/battle/BattleActionsController.h index 3010ac4e2..86acc8e44 100644 --- a/client/battle/BattleActionsController.h +++ b/client/battle/BattleActionsController.h @@ -23,12 +23,6 @@ VCMI_LIB_NAMESPACE_END class BattleInterface; -enum class MouseHoveredHexContext -{ - UNOCCUPIED_HEX, - OCCUPIED_HEX -}; - /// Class that controls actions that can be performed by player, e.g. moving stacks, attacking, etc /// As well as all relevant feedback for these actions in user interface class BattleActionsController @@ -53,7 +47,7 @@ class BattleActionsController bool isCastingPossibleHere (const CSpell * spell, const CStack *shere, BattleHex myNumber); bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber) const; //TODO: move to BattleState / callback std::vector getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn - void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context); + void reorderPossibleActionsPriority(const CStack * stack, const CStack * targetStack); bool actionIsLegal(PossiblePlayerBattleAction action, BattleHex hoveredHex); diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index 538b60b6c..79f558932 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -450,6 +450,10 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action) case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: iconName = variables["actionIconSpell"].String(); break; + + case PossiblePlayerBattleAction::ANY_LOCATION: + iconName = variables["actionIconSpell"].String(); + break; //TODO: figure out purpose of this icon //case PossiblePlayerBattleAction::???: @@ -500,7 +504,11 @@ void BattleWindow::bAutofightf() blockUI(true); auto ai = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String()); - ai->initBattleInterface(owner.curInt->env, owner.curInt->cb); + + AutocombatPreferences autocombatPreferences = AutocombatPreferences(); + autocombatPreferences.enableSpellsUsage = settings["battle"]["enableAutocombatSpells"].Bool(); + + ai->initBattleInterface(owner.curInt->env, owner.curInt->cb, autocombatPreferences); ai->battleStart(owner.army1, owner.army2, int3(0,0,0), owner.attackingHeroInstance, owner.defendingHeroInstance, owner.curInt->cb->battleGetMySide(), false); owner.curInt->autofightingAI = ai; owner.curInt->cb->registerBattleInterface(ai); diff --git a/client/gui/ShortcutHandler.cpp b/client/gui/ShortcutHandler.cpp index 9014d1fcf..13f4f7307 100644 --- a/client/gui/ShortcutHandler.cpp +++ b/client/gui/ShortcutHandler.cpp @@ -120,7 +120,7 @@ std::vector ShortcutHandler::translateKeycode(SDL_Keycode key) const {SDLK_KP_MINUS, EShortcut::ADVENTURE_ZOOM_OUT }, {SDLK_BACKSPACE, EShortcut::ADVENTURE_ZOOM_RESET }, {SDLK_q, EShortcut::BATTLE_TOGGLE_QUEUE }, - {SDLK_c, EShortcut::BATTLE_USE_CREATURE_SPELL }, + {SDLK_f, EShortcut::BATTLE_USE_CREATURE_SPELL }, {SDLK_s, EShortcut::BATTLE_SURRENDER }, {SDLK_r, EShortcut::BATTLE_RETREAT }, {SDLK_a, EShortcut::BATTLE_AUTOCOMBAT }, diff --git a/client/widgets/CArtifactsOfHeroBackpack.cpp b/client/widgets/CArtifactsOfHeroBackpack.cpp index feb3c974a..f8c509d05 100644 --- a/client/widgets/CArtifactsOfHeroBackpack.cpp +++ b/client/widgets/CArtifactsOfHeroBackpack.cpp @@ -14,6 +14,7 @@ #include "../gui/Shortcut.h" #include "Buttons.h" +#include "Images.h" #include "GameSettings.h" #include "IHandlerBase.h" #include "ObjectLists.h" @@ -27,25 +28,37 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(const Point & position) { OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE); pos += position; + setRedrawParent(true); const auto backpackCap = VLC->settings()->getInteger(EGameSettings::HEROES_BACKPACK_CAP); - auto visibleCapasityMax = HERO_BACKPACK_WINDOW_SLOT_LINES * HERO_BACKPACK_WINDOW_SLOT_COLUMNS; + auto visibleCapacityMax = HERO_BACKPACK_WINDOW_SLOT_ROWS * HERO_BACKPACK_WINDOW_SLOT_COLUMNS; if(backpackCap >= 0) - visibleCapasityMax = visibleCapasityMax > backpackCap ? backpackCap : visibleCapasityMax; + visibleCapacityMax = visibleCapacityMax > backpackCap ? backpackCap : visibleCapacityMax; - backpack.resize(visibleCapasityMax); + backpack.resize(visibleCapacityMax); size_t artPlaceIdx = 0; + + const int slotSizeWithMargin = 46; + + for(int i = 0; i < visibleCapacityMax; i++) + { + auto artifactSlotBackground = std::make_shared("heroWindow/artifactSlotEmpty", + Point(slotSizeWithMargin * (i % HERO_BACKPACK_WINDOW_SLOT_COLUMNS), slotSizeWithMargin * (i / HERO_BACKPACK_WINDOW_SLOT_COLUMNS))); + + backpackSlotsBackgrounds.emplace_back(artifactSlotBackground); + } + for(auto & artPlace : backpack) { artPlace = std::make_shared( - Point(46 * (artPlaceIdx % HERO_BACKPACK_WINDOW_SLOT_COLUMNS), 46 * (artPlaceIdx / HERO_BACKPACK_WINDOW_SLOT_COLUMNS))); + Point(slotSizeWithMargin * (artPlaceIdx % HERO_BACKPACK_WINDOW_SLOT_COLUMNS), slotSizeWithMargin * (artPlaceIdx / HERO_BACKPACK_WINDOW_SLOT_COLUMNS))); artPlace->setArtifact(nullptr); artPlace->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1); artPlace->rightClickCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1); artPlaceIdx++; } - if(backpackCap < 0 || visibleCapasityMax < backpackCap) + if(backpackCap < 0 || visibleCapacityMax < backpackCap) { auto onCreate = [](size_t index) -> std::shared_ptr { @@ -56,8 +69,8 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(const Point & position) scrollBackpack(static_cast(pos) * HERO_BACKPACK_WINDOW_SLOT_COLUMNS - backpackPos); }; backpackListBox = std::make_shared( - posMoved, onCreate, Point(0, 0), Point(0, 0), HERO_BACKPACK_WINDOW_SLOT_LINES, 0, 0, 1, - Rect(HERO_BACKPACK_WINDOW_SLOT_COLUMNS * 46 + 10, 0, HERO_BACKPACK_WINDOW_SLOT_LINES * 46 - 5, 0)); + posMoved, onCreate, Point(0, 0), Point(0, 0), HERO_BACKPACK_WINDOW_SLOT_ROWS, 0, 0, 1, + Rect(HERO_BACKPACK_WINDOW_SLOT_COLUMNS * slotSizeWithMargin + 10, 0, HERO_BACKPACK_WINDOW_SLOT_ROWS * slotSizeWithMargin - 5, 0)); } } diff --git a/client/widgets/CArtifactsOfHeroBackpack.h b/client/widgets/CArtifactsOfHeroBackpack.h index f504ba62c..e6767a601 100644 --- a/client/widgets/CArtifactsOfHeroBackpack.h +++ b/client/widgets/CArtifactsOfHeroBackpack.h @@ -31,6 +31,7 @@ public: private: std::shared_ptr backpackListBox; + std::vector> backpackSlotsBackgrounds; const size_t HERO_BACKPACK_WINDOW_SLOT_COLUMNS = 8; - const size_t HERO_BACKPACK_WINDOW_SLOT_LINES = 8; + const size_t HERO_BACKPACK_WINDOW_SLOT_ROWS = 8; }; diff --git a/client/widgets/CWindowWithArtifacts.cpp b/client/widgets/CWindowWithArtifacts.cpp index 5bd2a00a3..3a9c3156a 100644 --- a/client/widgets/CWindowWithArtifacts.cpp +++ b/client/widgets/CWindowWithArtifacts.cpp @@ -165,7 +165,7 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst if constexpr(std::is_same_v>) { - if(!isTransferAllowed) + if(!isTransferAllowed && artPlace.getArt()) { if(closeCallback) closeCallback(); diff --git a/client/windows/CHeroBackpackWindow.cpp b/client/windows/CHeroBackpackWindow.cpp index b491bf57d..46bcfda40 100644 --- a/client/windows/CHeroBackpackWindow.cpp +++ b/client/windows/CHeroBackpackWindow.cpp @@ -14,17 +14,33 @@ #include "../gui/Shortcut.h" #include "../widgets/Buttons.h" +#include "../widgets/Images.h" +#include "CMessage.h" +#include "render/Canvas.h" +#include "CPlayerInterface.h" CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero) - : CWindowObject(PLAYER_COLORED) + : CWindowObject((EOptions)0) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); - - arts = std::make_shared(Point(-100, -170)); + + stretchedBackground = std::make_shared("DIBOXBCK", Rect(0, 0, 410, 425)); + pos.w = stretchedBackground->pos.w; + pos.h = stretchedBackground->pos.h; + center(); + + + arts = std::make_shared(/*Point(-100, -170)*/Point(10, 10)); arts->setHero(hero); addSet(arts); addCloseCallback(std::bind(&CHeroBackpackWindow::close, this)); - quitButton = std::make_shared(Point(242, 200), "hsbtns.def", CButton::tooltip(""), [this]() { close(); }, EShortcut::GLOBAL_RETURN); + quitButton = std::make_shared(Point(173, 385), "IOKAY32.def", CButton::tooltip(""), [this]() { close(); }, EShortcut::GLOBAL_RETURN); +} + +void CHeroBackpackWindow::showAll(Canvas &to) +{ + CIntObject::showAll(to); + CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15); } diff --git a/client/windows/CHeroBackpackWindow.h b/client/windows/CHeroBackpackWindow.h index fefd1902d..df9aeb08d 100644 --- a/client/windows/CHeroBackpackWindow.h +++ b/client/windows/CHeroBackpackWindow.h @@ -12,6 +12,8 @@ #include "../widgets/CWindowWithArtifacts.h" #include "CWindowObject.h" +class CFilledTexture; + class CHeroBackpackWindow : public CWindowObject, public CWindowWithArtifacts { public: @@ -20,4 +22,7 @@ public: private: std::shared_ptr arts; std::shared_ptr quitButton; + std::shared_ptr stretchedBackground; + + void showAll(Canvas &to) override; }; diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 9e4f8f2fa..05ca48d85 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -84,11 +84,19 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero) quitButton = std::make_shared(Point(609, 516), "hsbtns.def", CButton::tooltip(heroscrn[17]), [=](){ close(); }, EShortcut::GLOBAL_RETURN); - dismissLabel = std::make_shared(CGI->generaltexth->jktexts[8], Rect(370, 430, 65, 35), 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); - dismissButton = std::make_shared(Point(454, 429), "hsbtns2.def", CButton::tooltip(heroscrn[28]), [=](){ dismissCurrent(); }, EShortcut::HERO_DISMISS); - - questlogLabel = std::make_shared(CGI->generaltexth->jktexts[9], Rect(510, 430, 65, 35), 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); - questlogButton = std::make_shared(Point(314, 429), "hsbtns4.def", CButton::tooltip(heroscrn[0]), [=](){ LOCPLINT->showQuestLog(); }, EShortcut::ADVENTURE_QUEST_LOG); + if(settings["general"]["enableUiEnhancements"].Bool()) + { + questlogButton = std::make_shared(Point(314, 429), "hsbtns4.def", CButton::tooltip(heroscrn[0]), [=](){ LOCPLINT->showQuestLog(); }, EShortcut::ADVENTURE_QUEST_LOG); + backpackButton = std::make_shared(Point(424, 429), "buttons/backpack", CButton::tooltipLocalized("vcmi.heroWindow.Backpack"), [=](){ createBackpackWindow(); }, EShortcut::HERO_BACKPACK); + dismissButton = std::make_shared(Point(534, 429), "hsbtns2.def", CButton::tooltip(heroscrn[28]), [=](){ dismissCurrent(); }, EShortcut::HERO_DISMISS); + } + else + { + dismissLabel = std::make_shared(CGI->generaltexth->jktexts[8], Rect(370, 430, 65, 35), 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); + questlogLabel = std::make_shared(CGI->generaltexth->jktexts[9], Rect(510, 430, 65, 35), 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); + dismissButton = std::make_shared(Point(454, 429), "hsbtns2.def", CButton::tooltip(heroscrn[28]), [=](){ dismissCurrent(); }, EShortcut::HERO_DISMISS); + questlogButton = std::make_shared(Point(314, 429), "hsbtns4.def", CButton::tooltip(heroscrn[0]), [=](){ LOCPLINT->showQuestLog(); }, EShortcut::ADVENTURE_QUEST_LOG); + } formations = std::make_shared(0); formations->addToggle(0, std::make_shared(Point(481, 483), "hsbtns6.def", std::make_pair(heroscrn[23], heroscrn[29]), 0, EShortcut::HERO_TIGHT_FORMATION)); diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index c145f3d4f..cd2c5f40f 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -554,7 +554,7 @@ std::shared_ptr CKingdomInterface::createMainTab(size_t index) void CKingdomInterface::generateMinesList(const std::vector & ownedObjects) { ui32 footerPos = OVERVIEW_SIZE * 116; - TResources minesCount(GameConstants::RESOURCE_QUANTITY, 0); + ResourceSet minesCount = ResourceSet(); int totalIncome=0; for(const CGObjectInstance * object : ownedObjects) diff --git a/client/windows/settings/BattleOptionsTab.cpp b/client/windows/settings/BattleOptionsTab.cpp index 99f874a4e..1e657d61c 100644 --- a/client/windows/settings/BattleOptionsTab.cpp +++ b/client/windows/settings/BattleOptionsTab.cpp @@ -64,6 +64,10 @@ BattleOptionsTab::BattleOptionsTab(BattleInterface * owner) { showStickyHeroWindowsChangedCallback(value, owner); }); + addCallback("enableAutocombatSpellsChanged", [this](bool value) + { + enableAutocombatSpellsChangedCallback(value); + }); build(config); std::shared_ptr animationSpeedToggle = widget("animationSpeedPicker"); @@ -92,6 +96,9 @@ BattleOptionsTab::BattleOptionsTab(BattleInterface * owner) std::shared_ptr skipBattleIntroMusicCheckbox = widget("skipBattleIntroMusicCheckbox"); skipBattleIntroMusicCheckbox->setSelected(settings["gameTweaks"]["skipBattleIntroMusic"].Bool()); + + std::shared_ptr enableAutocombatSpellsCheckbox = widget("enableAutocombatSpellsCheckbox"); + enableAutocombatSpellsCheckbox->setSelected(settings["battle"]["enableAutocombatSpells"].Bool()); } int BattleOptionsTab::getAnimSpeed() const @@ -235,3 +242,9 @@ void BattleOptionsTab::skipBattleIntroMusicChangedCallback(bool value) musicSkipSettingValue->Bool() = value; } +void BattleOptionsTab::enableAutocombatSpellsChangedCallback(bool value) +{ + Settings enableAutocombatSpells = settings.write["battle"]["enableAutocombatSpells"]; + enableAutocombatSpells->Bool() = value; +} + diff --git a/client/windows/settings/BattleOptionsTab.h b/client/windows/settings/BattleOptionsTab.h index 6a71d2e68..43d4d7e50 100644 --- a/client/windows/settings/BattleOptionsTab.h +++ b/client/windows/settings/BattleOptionsTab.h @@ -32,6 +32,7 @@ private: void queueSizeChangedCallback(int value, BattleInterface * parentBattleInterface); void skipBattleIntroMusicChangedCallback(bool value); void showStickyHeroWindowsChangedCallback(bool value, BattleInterface * parentBattleInterface); + void enableAutocombatSpellsChangedCallback(bool value); public: BattleOptionsTab(BattleInterface * owner = nullptr); }; diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index ddbd7815b..ab2fbea2c 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -330,6 +330,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/../include/vcmi/Team.h ${MAIN_LIB_DIR}/battle/AccessibilityInfo.h + ${MAIN_LIB_DIR}/battle/AutocombatPreferences.h ${MAIN_LIB_DIR}/battle/BattleAction.h ${MAIN_LIB_DIR}/battle/BattleAttackInfo.h ${MAIN_LIB_DIR}/battle/BattleHex.h diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 139b13cb2..57a3ef16b 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -38,7 +38,8 @@ "autosaveCountLimit", "useSavePrefix", "savePrefix", - "startTurnAutosave" + "startTurnAutosave", + "enableUiEnhancements" ], "properties" : { "playerName" : { @@ -126,6 +127,10 @@ "startTurnAutosave" : { "type": "boolean", "default": false + }, + "enableUiEnhancements" : { + "type": "boolean", + "default": true } } }, @@ -276,7 +281,7 @@ "type" : "object", "additionalProperties" : false, "default" : {}, - "required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize", "stickyHeroInfoWindows" ], + "required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize", "stickyHeroInfoWindows", "enableAutocombatSpells" ], "properties" : { "speedFactor" : { "type" : "number", @@ -318,6 +323,10 @@ "stickyHeroInfoWindows" : { "type" : "boolean", "default" : true + }, + "enableAutocombatSpells" : { + "type": "boolean", + "default": true } } }, diff --git a/config/widgets/settings/battleOptionsTab.json b/config/widgets/settings/battleOptionsTab.json index 0fde2a031..8f47a3fd5 100644 --- a/config/widgets/settings/battleOptionsTab.json +++ b/config/widgets/settings/battleOptionsTab.json @@ -51,14 +51,37 @@ ] }, { - "name": "autoCombatCheckboxes", + "name": "autoCombatFakeCheckboxes", "type" : "verticalLayout", "customType" : "checkboxFake", "position": {"x": 380, "y": 83}, "items": [ - {}, - {}, + {} + ] + }, + + { + "type" : "verticalLayout", + "customType" : "checkbox", + "position": {"x": 380, "y": 113}, + "items": + [ + { + "name": "enableAutocombatSpellsCheckbox", + "help": "vcmi.battleOptions.enableAutocombatSpells", + "callback": "enableAutocombatSpellsChanged" + } + ] + }, + + { + "name": "autoCombatFakeCheckboxes2", + "type" : "verticalLayout", + "customType" : "checkboxFake", + "position": {"x": 380, "y": 143}, + "items": + [ {}, {}, {} diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index 41fa7bbf3..24eb0a11f 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -9,6 +9,7 @@ */ #pragma once +#include "battle/AutocombatPreferences.h" #include "IGameEventsReceiver.h" #include "spells/ViewSpellInt.h" @@ -76,6 +77,7 @@ public: virtual ~CBattleGameInterface() {}; virtual void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB){}; + virtual void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB, AutocombatPreferences autocombatPreferences){}; //battle call-ins virtual void activeStack(const CStack * stack)=0; //called when it's turn of that stack diff --git a/lib/battle/AutocombatPreferences.h b/lib/battle/AutocombatPreferences.h new file mode 100644 index 000000000..0ca89c53e --- /dev/null +++ b/lib/battle/AutocombatPreferences.h @@ -0,0 +1,21 @@ +/* + * AutocombatPreferences.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +struct AutocombatPreferences +{ + bool enableSpellsUsage = true; + //TODO: below options exist in original H3, consider usefulness of mixed human-AI combat when enabling autocombat inside battle +// bool enableUnitsUsage = true; +// bool enableCatapultUsage = true; +// bool enableBallistaUsage = true; +// bool enableFirstAidTendUsage = true; +}; + diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index d7dd0a855..7700decc8 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1617,52 +1617,69 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler) //in json default skills means no field/null if(!defaultSkills) { - //enter structure here as handler initialize it - auto secondarySkills = handler.enterStruct("secondarySkills"); + //enter array here as handler initialize it + auto secondarySkills = handler.enterArray("secondarySkills"); + secondarySkills.syncSize(secSkills, JsonNode::JsonType::DATA_VECTOR); - for(auto & p : secSkills) + for(size_t skillIndex = 0; skillIndex < secondarySkills.size(); ++skillIndex) { - const si32 rawId = p.first.num; + JsonArraySerializer inner = secondarySkills.enterArray(skillIndex); + const si32 rawId = secSkills.at(skillIndex).first; if(rawId < 0 || rawId >= VLC->skillh->size()) logGlobal->error("Invalid secondary skill %d", rawId); - handler.serializeEnum((*VLC->skillh)[SecondarySkill(rawId)]->getJsonKey(), p.second, 0, NSecondarySkill::levels); + auto value = (*VLC->skillh)[SecondarySkill(rawId)]->getJsonKey(); + handler.serializeString("skill", value); + value = NSecondarySkill::levels.at(secSkills.at(skillIndex).second); + handler.serializeString("level", value); } } } else { - auto secondarySkills = handler.enterStruct("secondarySkills"); - const JsonNode & skillMap = handler.getCurrent(); + auto secondarySkills = handler.getCurrent()["secondarySkills"]; secSkills.clear(); - if(skillMap.getType() == JsonNode::JsonType::DATA_NULL) + if(secondarySkills.getType() == JsonNode::JsonType::DATA_NULL) { secSkills.emplace_back(SecondarySkill::DEFAULT, -1); } else { - for(const auto & p : skillMap.Struct()) + auto addSkill = [this](const std::string & skillId, const std::string & levelId) { - const std::string skillId = p.first; - const std::string levelId = p.second.String(); - const int rawId = CSkillHandler::decodeSkill(skillId); if(rawId < 0) { logGlobal->error("Invalid secondary skill %s", skillId); - continue; + return; } const int level = vstd::find_pos(NSecondarySkill::levels, levelId); if(level < 0) { logGlobal->error("Invalid secondary skill level%s", levelId); - continue; + return; } secSkills.emplace_back(SecondarySkill(rawId), level); + }; + + if(secondarySkills.getType() == JsonNode::JsonType::DATA_VECTOR) + { + for(const auto & p : secondarySkills.Vector()) + { + auto skillMap = p.Struct(); + addSkill(skillMap["skill"].String(), skillMap["level"].String()); + } + } + else if(secondarySkills.getType() == JsonNode::JsonType::DATA_STRUCT) + { + for(const auto & p : secondarySkills.Struct()) + { + addSkill(p.first, p.second.String()); + }; } } }