1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-02 00:10:22 +02:00

Merge pull request #4020 from Laserlicht/quickspell

[1.6] Quickspell support
This commit is contained in:
Ivan Savenko 2024-07-18 23:04:05 +03:00 committed by GitHub
commit 31caf73383
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 287 additions and 9 deletions

View File

@ -31,6 +31,7 @@
#include "../render/IFont.h" #include "../render/IFont.h"
#include "../render/Graphics.h" #include "../render/Graphics.h"
#include "../widgets/Buttons.h" #include "../widgets/Buttons.h"
#include "../widgets/CComponent.h"
#include "../widgets/Images.h" #include "../widgets/Images.h"
#include "../widgets/Slider.h" #include "../widgets/Slider.h"
#include "../widgets/TextControls.h" #include "../widgets/TextControls.h"
@ -39,9 +40,11 @@
#include "../windows/CMessage.h" #include "../windows/CMessage.h"
#include "../windows/CCreatureWindow.h" #include "../windows/CCreatureWindow.h"
#include "../windows/CSpellWindow.h" #include "../windows/CSpellWindow.h"
#include "../windows/InfoWindows.h"
#include "../render/CAnimation.h" #include "../render/CAnimation.h"
#include "../render/IRenderHandler.h" #include "../render/IRenderHandler.h"
#include "../adventureMap/CInGameConsole.h" #include "../adventureMap/CInGameConsole.h"
#include "../eventsSDL/InputHandler.h"
#include "../../CCallback.h" #include "../../CCallback.h"
#include "../../lib/CStack.h" #include "../../lib/CStack.h"
@ -55,6 +58,8 @@
#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/networkPacks/PacksForClientBattle.h" #include "../../lib/networkPacks/PacksForClientBattle.h"
#include "../../lib/TextOperations.h" #include "../../lib/TextOperations.h"
#include "../../lib/json/JsonUtils.h"
void BattleConsole::showAll(Canvas & to) void BattleConsole::showAll(Canvas & to)
{ {
@ -417,6 +422,79 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
addUsedEvents(TIME); addUsedEvents(TIME);
} }
QuickSpellPanel::QuickSpellPanel(BattleInterface & owner)
: CIntObject(0), owner(owner)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
addUsedEvents(LCLICK | SHOW_POPUP | MOVE);
pos = Rect(0, 0, 52, 600);
background = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), pos);
rect = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w + 1, pos.h + 1), ColorRGBA(0, 0, 0, 0), ColorRGBA(241, 216, 120, 255));
create();
}
void QuickSpellPanel::create()
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
const JsonNode config = JsonUtils::assembleFromFiles("config/shortcutsConfig");
labels.clear();
buttons.clear();
buttonsDisabled.clear();
auto hero = owner.getBattle()->battleGetMyHero();
if(!hero)
return;
for(int i = 0; i < 12; i++) {
std::string spellIdentifier = persistentStorage["quickSpell"][std::to_string(i)].String();
SpellID id;
try
{
id = SpellID::decode(spellIdentifier);
}
catch(const IdentifierResolutionException& e)
{
id = SpellID::NONE;
}
auto button = std::make_shared<CButton>(Point(2, 7 + 50 * i), AnimationPath::builtin("spellint"), CButton::tooltip(), [this, id, hero](){
if(id.hasValue() && id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, hero))
{
owner.castThisSpell(id);
}
});
button->setOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("spellint"), !spellIdentifier.empty() ? id.num + 1 : 0));
button->addPopupCallback([this, i, hero](){
GH.input().hapticFeedback();
GH.windows().createAndPushWindow<CSpellWindow>(hero, owner.curInt.get(), true, [this, i](SpellID spell){
Settings configID = persistentStorage.write["quickSpell"][std::to_string(i)];
configID->String() = spell.toSpell()->identifier;
create();
});
});
if(!id.hasValue() || !id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, hero))
{
buttonsDisabled.push_back(std::make_shared<TransparentFilledRectangle>(Rect(2, 7 + 50 * i, 48, 36), ColorRGBA(0, 0, 0, 172)));
}
labels.push_back(std::make_shared<CLabel>(7, 10 + 50 * i, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, config["keyboard"]["battleSpellShortcut" + std::to_string(i)].String()));
buttons.push_back(button);
}
}
void QuickSpellPanel::show(Canvas & to)
{
showAll(to);
CIntObject::show(to);
}
HeroInfoBasicPanel::HeroInfoBasicPanel(const InfoAboutHero & hero, Point * position, bool initializeBackground) HeroInfoBasicPanel::HeroInfoBasicPanel(const InfoAboutHero & hero, Point * position, bool initializeBackground)
: CIntObject(0) : CIntObject(0)
{ {

View File

@ -22,6 +22,7 @@ class CGHeroInstance;
struct BattleResult; struct BattleResult;
struct InfoAboutHero; struct InfoAboutHero;
class CStack; class CStack;
class CPlayerBattleCallback;
namespace battle namespace battle
{ {
@ -44,6 +45,7 @@ class TransparentFilledRectangle;
class CPlayerInterface; class CPlayerInterface;
class BattleRenderer; class BattleRenderer;
class VideoWidget; class VideoWidget;
class QuickSpellPanel;
/// Class which shows the console at the bottom of the battle screen and manages the text of the console /// Class which shows the console at the bottom of the battle screen and manages the text of the console
class BattleConsole : public CIntObject, public IStatusBar class BattleConsole : public CIntObject, public IStatusBar
@ -147,6 +149,26 @@ public:
BattleHero(const BattleInterface & owner, const CGHeroInstance * hero, bool defender); BattleHero(const BattleInterface & owner, const CGHeroInstance * hero, bool defender);
}; };
class QuickSpellPanel : public CIntObject
{
private:
std::shared_ptr<CFilledTexture> background;
std::shared_ptr<TransparentFilledRectangle> rect;
std::vector<std::shared_ptr<CButton>> buttons;
std::vector<std::shared_ptr<TransparentFilledRectangle>> buttonsDisabled;
std::vector<std::shared_ptr<CLabel>> labels;
BattleInterface & owner;
public:
bool isEnabled; // isActive() is not working on multiple conditions, because of this we need a seperate flag
QuickSpellPanel(BattleInterface & owner);
void create();
void show(Canvas & to) override;
};
class HeroInfoBasicPanel : public CIntObject //extracted from InfoWindow to fit better as non-popup embed element class HeroInfoBasicPanel : public CIntObject //extracted from InfoWindow to fit better as non-popup embed element
{ {
private: private:

View File

@ -45,8 +45,8 @@
#include "../../lib/CPlayerState.h" #include "../../lib/CPlayerState.h"
#include "../windows/settings/SettingsMainWindow.h" #include "../windows/settings/SettingsMainWindow.h"
BattleWindow::BattleWindow(BattleInterface & owner): BattleWindow::BattleWindow(BattleInterface & Owner):
owner(owner), owner(Owner),
lastAlternativeAction(PossiblePlayerBattleAction::INVALID) lastAlternativeAction(PossiblePlayerBattleAction::INVALID)
{ {
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
@ -64,6 +64,20 @@ BattleWindow::BattleWindow(BattleInterface & owner):
const JsonNode config(JsonPath::builtin("config/widgets/BattleWindow2.json")); const JsonNode config(JsonPath::builtin("config/widgets/BattleWindow2.json"));
addShortcut(EShortcut::BATTLE_TOGGLE_QUICKSPELL, [this](){ this->toggleStickyQuickSpellVisibility();});
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_0, [this](){ useSpellIfPossible(0); });
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_1, [this](){ useSpellIfPossible(1); });
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_2, [this](){ useSpellIfPossible(2); });
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_3, [this](){ useSpellIfPossible(3); });
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_4, [this](){ useSpellIfPossible(4); });
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_5, [this](){ useSpellIfPossible(5); });
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_6, [this](){ useSpellIfPossible(6); });
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_7, [this](){ useSpellIfPossible(7); });
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_8, [this](){ useSpellIfPossible(8); });
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_9, [this](){ useSpellIfPossible(9); });
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_10, [this](){ useSpellIfPossible(10); });
addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_11, [this](){ useSpellIfPossible(11); });
addShortcut(EShortcut::GLOBAL_OPTIONS, std::bind(&BattleWindow::bOptionsf, this)); addShortcut(EShortcut::GLOBAL_OPTIONS, std::bind(&BattleWindow::bOptionsf, this));
addShortcut(EShortcut::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this)); addShortcut(EShortcut::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this));
addShortcut(EShortcut::BATTLE_RETREAT, std::bind(&BattleWindow::bFleef, this)); addShortcut(EShortcut::BATTLE_RETREAT, std::bind(&BattleWindow::bFleef, this));
@ -95,6 +109,7 @@ BattleWindow::BattleWindow(BattleInterface & owner):
owner.fieldController->createHeroes(); owner.fieldController->createHeroes();
createQueue(); createQueue();
createQuickSpellWindow();
createStickyHeroInfoWindows(); createStickyHeroInfoWindows();
createTimerInfoWindows(); createTimerInfoWindows();
@ -164,10 +179,67 @@ void BattleWindow::createStickyHeroInfoWindows()
setPositionInfoWindow(); setPositionInfoWindow();
} }
void BattleWindow::createQuickSpellWindow()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
quickSpellWindow = std::make_shared<QuickSpellPanel>(owner);
quickSpellWindow->moveTo(Point(pos.x - 67, pos.y));
if(settings["battle"]["enableQuickSpellPanel"].Bool())
showStickyQuickSpellWindow();
else
hideStickyQuickSpellWindow();
}
void BattleWindow::toggleStickyQuickSpellVisibility()
{
if(settings["battle"]["enableQuickSpellPanel"].Bool())
hideStickyQuickSpellWindow();
else
showStickyQuickSpellWindow();
}
void BattleWindow::hideStickyQuickSpellWindow()
{
Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"];
showStickyQuickSpellWindow->Bool() = false;
quickSpellWindow->disable();
quickSpellWindow->isEnabled = false;
setPositionInfoWindow();
createTimerInfoWindows();
GH.windows().totalRedraw();
}
void BattleWindow::showStickyQuickSpellWindow()
{
Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"];
showStickyQuickSpellWindow->Bool() = true;
if(GH.screenDimensions().x >= 1050)
{
quickSpellWindow->enable();
quickSpellWindow->isEnabled = true;
}
else
{
quickSpellWindow->disable();
quickSpellWindow->isEnabled = false;
}
setPositionInfoWindow();
createTimerInfoWindows();
GH.windows().totalRedraw();
}
void BattleWindow::createTimerInfoWindows() void BattleWindow::createTimerInfoWindows()
{ {
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
int xOffsetAttacker = quickSpellWindow->isEnabled ? -53 : 0;
if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.battleTimer != 0 || LOCPLINT->cb->getStartInfo()->turnTimerInfo.unitTimer != 0) if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.battleTimer != 0 || LOCPLINT->cb->getStartInfo()->turnTimerInfo.unitTimer != 0)
{ {
PlayerColor attacker = owner.getBattle()->sideToPlayer(BattleSide::ATTACKER); PlayerColor attacker = owner.getBattle()->sideToPlayer(BattleSide::ATTACKER);
@ -176,7 +248,7 @@ void BattleWindow::createTimerInfoWindows()
if (attacker.isValidPlayer()) if (attacker.isValidPlayer())
{ {
if (GH.screenDimensions().x >= 1000) if (GH.screenDimensions().x >= 1000)
attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(-92, 1), attacker); attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(-92 + xOffsetAttacker, 1), attacker);
else else
attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(1, 135), attacker); attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(1, 135), attacker);
} }
@ -199,6 +271,24 @@ std::shared_ptr<BattleConsole> BattleWindow::buildBattleConsole(const JsonNode &
return std::make_shared<BattleConsole>(owner, background, rect.topLeft(), offset, rect.dimensions() ); return std::make_shared<BattleConsole>(owner, background, rect.topLeft(), offset, rect.dimensions() );
} }
void BattleWindow::useSpellIfPossible(int slot)
{
std::string spellIdentifier = persistentStorage["quickSpell"][std::to_string(slot)].String();
SpellID id;
try
{
id = SpellID::decode(spellIdentifier);
}
catch(const IdentifierResolutionException& e)
{
return;
}
if(id.hasValue() && owner.getBattle()->battleGetMyHero() && id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, owner.getBattle()->battleGetMyHero()))
{
owner.castThisSpell(id);
}
};
void BattleWindow::toggleQueueVisibility() void BattleWindow::toggleQueueVisibility()
{ {
if(settings["battle"]["showQueue"].Bool()) if(settings["battle"]["showQueue"].Bool())
@ -283,10 +373,12 @@ void BattleWindow::showStickyHeroWindows()
void BattleWindow::updateQueue() void BattleWindow::updateQueue()
{ {
queue->update(); queue->update();
createQuickSpellWindow();
} }
void BattleWindow::setPositionInfoWindow() void BattleWindow::setPositionInfoWindow()
{ {
int xOffsetAttacker = quickSpellWindow->isEnabled ? -53 : 0;
if(defenderHeroWindow) if(defenderHeroWindow)
{ {
Point position = (GH.screenDimensions().x >= 1000) Point position = (GH.screenDimensions().x >= 1000)
@ -297,7 +389,7 @@ void BattleWindow::setPositionInfoWindow()
if(attackerHeroWindow) if(attackerHeroWindow)
{ {
Point position = (GH.screenDimensions().x >= 1000) Point position = (GH.screenDimensions().x >= 1000)
? Point(pos.x - 93, pos.y + 60) ? Point(pos.x - 93 + xOffsetAttacker, pos.y + 60)
: Point(pos.x + 1, pos.y + 195); : Point(pos.x + 1, pos.y + 195);
attackerHeroWindow->moveTo(position); attackerHeroWindow->moveTo(position);
} }
@ -311,7 +403,7 @@ void BattleWindow::setPositionInfoWindow()
if(attackerStackWindow) if(attackerStackWindow)
{ {
Point position = (GH.screenDimensions().x >= 1000) Point position = (GH.screenDimensions().x >= 1000)
? Point(pos.x - 93, attackerHeroWindow ? attackerHeroWindow->pos.y + 210 : pos.y + 60) ? Point(pos.x - 93 + xOffsetAttacker, attackerHeroWindow ? attackerHeroWindow->pos.y + 210 : pos.y + 60)
: Point(pos.x + 1, attackerHeroWindow ? attackerHeroWindow->pos.y : pos.y + 195); : Point(pos.x + 1, attackerHeroWindow ? attackerHeroWindow->pos.y : pos.y + 195);
attackerStackWindow->moveTo(position); attackerStackWindow->moveTo(position);
} }
@ -346,6 +438,7 @@ void BattleWindow::updateStackInfoWindow(const CStack * stack)
attackerStackWindow = nullptr; attackerStackWindow = nullptr;
setPositionInfoWindow(); setPositionInfoWindow();
createTimerInfoWindows();
} }
void BattleWindow::heroManaPointsChanged(const CGHeroInstance * hero) void BattleWindow::heroManaPointsChanged(const CGHeroInstance * hero)
@ -765,6 +858,8 @@ void BattleWindow::blockUI(bool on)
setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on || !owner.tacticsMode); setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on || !owner.tacticsMode);
setShortcutBlocked(EShortcut::BATTLE_CONSOLE_DOWN, on && !owner.tacticsMode); setShortcutBlocked(EShortcut::BATTLE_CONSOLE_DOWN, on && !owner.tacticsMode);
setShortcutBlocked(EShortcut::BATTLE_CONSOLE_UP, on && !owner.tacticsMode); setShortcutBlocked(EShortcut::BATTLE_CONSOLE_UP, on && !owner.tacticsMode);
quickSpellWindow->setInputEnabled(!on);
} }
void BattleWindow::bOpenActiveUnit() void BattleWindow::bOpenActiveUnit()

View File

@ -27,6 +27,7 @@ class StackQueue;
class TurnTimerWidget; class TurnTimerWidget;
class HeroInfoBasicPanel; class HeroInfoBasicPanel;
class StackInfoBasicPanel; class StackInfoBasicPanel;
class QuickSpellPanel;
/// GUI object that handles functionality of panel at the bottom of combat screen /// GUI object that handles functionality of panel at the bottom of combat screen
class BattleWindow : public InterfaceObjectConfigurable class BattleWindow : public InterfaceObjectConfigurable
@ -40,6 +41,8 @@ class BattleWindow : public InterfaceObjectConfigurable
std::shared_ptr<StackInfoBasicPanel> attackerStackWindow; std::shared_ptr<StackInfoBasicPanel> attackerStackWindow;
std::shared_ptr<StackInfoBasicPanel> defenderStackWindow; std::shared_ptr<StackInfoBasicPanel> defenderStackWindow;
std::shared_ptr<QuickSpellPanel> quickSpellWindow;
std::shared_ptr<TurnTimerWidget> attackerTimerWidget; std::shared_ptr<TurnTimerWidget> attackerTimerWidget;
std::shared_ptr<TurnTimerWidget> defenderTimerWidget; std::shared_ptr<TurnTimerWidget> defenderTimerWidget;
@ -68,12 +71,16 @@ class BattleWindow : public InterfaceObjectConfigurable
PossiblePlayerBattleAction lastAlternativeAction; PossiblePlayerBattleAction lastAlternativeAction;
void showAlternativeActionIcon(PossiblePlayerBattleAction); void showAlternativeActionIcon(PossiblePlayerBattleAction);
void useSpellIfPossible(int slot);
/// flip battle queue visibility to opposite /// flip battle queue visibility to opposite
void toggleQueueVisibility(); void toggleQueueVisibility();
void createQueue(); void createQueue();
void toggleStickyHeroWindowsVisibility(); void toggleStickyHeroWindowsVisibility();
void toggleStickyQuickSpellVisibility();
void createStickyHeroInfoWindows(); void createStickyHeroInfoWindows();
void createQuickSpellWindow();
void createTimerInfoWindows(); void createTimerInfoWindows();
std::shared_ptr<BattleConsole> buildBattleConsole(const JsonNode &) const; std::shared_ptr<BattleConsole> buildBattleConsole(const JsonNode &) const;
@ -94,6 +101,10 @@ public:
void hideStickyHeroWindows(); void hideStickyHeroWindows();
void showStickyHeroWindows(); void showStickyHeroWindows();
/// Toggle permanent quickspell windows visibility
void hideStickyQuickSpellWindow();
void showStickyQuickSpellWindow();
/// Event handler for netpack changing hero mana points /// Event handler for netpack changing hero mana points
void heroManaPointsChanged(const CGHeroInstance * hero); void heroManaPointsChanged(const CGHeroInstance * hero);

View File

@ -186,6 +186,19 @@ enum class EShortcut
BATTLE_TOGGLE_HEROES_STATS, BATTLE_TOGGLE_HEROES_STATS,
BATTLE_OPEN_ACTIVE_UNIT, BATTLE_OPEN_ACTIVE_UNIT,
BATTLE_OPEN_HOVERED_UNIT, BATTLE_OPEN_HOVERED_UNIT,
BATTLE_TOGGLE_QUICKSPELL,
BATTLE_SPELL_SHORTCUT_0,
BATTLE_SPELL_SHORTCUT_1,
BATTLE_SPELL_SHORTCUT_2,
BATTLE_SPELL_SHORTCUT_3,
BATTLE_SPELL_SHORTCUT_4,
BATTLE_SPELL_SHORTCUT_5,
BATTLE_SPELL_SHORTCUT_6,
BATTLE_SPELL_SHORTCUT_7,
BATTLE_SPELL_SHORTCUT_8,
BATTLE_SPELL_SHORTCUT_9,
BATTLE_SPELL_SHORTCUT_10,
BATTLE_SPELL_SHORTCUT_11,
MARKET_DEAL, MARKET_DEAL,
MARKET_MAX_AMOUNT, MARKET_MAX_AMOUNT,

View File

@ -222,6 +222,19 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
{"battleTacticsNext", EShortcut::BATTLE_TACTICS_NEXT }, {"battleTacticsNext", EShortcut::BATTLE_TACTICS_NEXT },
{"battleTacticsEnd", EShortcut::BATTLE_TACTICS_END }, {"battleTacticsEnd", EShortcut::BATTLE_TACTICS_END },
{"battleSelectAction", EShortcut::BATTLE_SELECT_ACTION }, {"battleSelectAction", EShortcut::BATTLE_SELECT_ACTION },
{"battleToggleQuickSpell", EShortcut::BATTLE_TOGGLE_QUICKSPELL },
{"battleSpellShortcut0", EShortcut::BATTLE_SPELL_SHORTCUT_0 },
{"battleSpellShortcut1", EShortcut::BATTLE_SPELL_SHORTCUT_1 },
{"battleSpellShortcut2", EShortcut::BATTLE_SPELL_SHORTCUT_2 },
{"battleSpellShortcut3", EShortcut::BATTLE_SPELL_SHORTCUT_3 },
{"battleSpellShortcut4", EShortcut::BATTLE_SPELL_SHORTCUT_4 },
{"battleSpellShortcut5", EShortcut::BATTLE_SPELL_SHORTCUT_5 },
{"battleSpellShortcut6", EShortcut::BATTLE_SPELL_SHORTCUT_6 },
{"battleSpellShortcut7", EShortcut::BATTLE_SPELL_SHORTCUT_7 },
{"battleSpellShortcut8", EShortcut::BATTLE_SPELL_SHORTCUT_8 },
{"battleSpellShortcut9", EShortcut::BATTLE_SPELL_SHORTCUT_9 },
{"battleSpellShortcut10", EShortcut::BATTLE_SPELL_SHORTCUT_10 },
{"battleSpellShortcut11", EShortcut::BATTLE_SPELL_SHORTCUT_11 },
{"spectateTrackHero", EShortcut::SPECTATE_TRACK_HERO }, {"spectateTrackHero", EShortcut::SPECTATE_TRACK_HERO },
{"spectateSkipBattle", EShortcut::SPECTATE_SKIP_BATTLE }, {"spectateSkipBattle", EShortcut::SPECTATE_SKIP_BATTLE },
{"spectateSkipBattleResult", EShortcut::SPECTATE_SKIP_BATTLE_RESULT }, {"spectateSkipBattleResult", EShortcut::SPECTATE_SKIP_BATTLE_RESULT },

View File

@ -67,6 +67,11 @@ void CButton::addCallback(const std::function<void()> & callback)
this->callback += callback; this->callback += callback;
} }
void CButton::addPopupCallback(const std::function<void()> & callback)
{
this->callbackPopup += callback;
}
void ButtonBase::setTextOverlay(const std::string & Text, EFonts font, ColorRGBA color) void ButtonBase::setTextOverlay(const std::string & Text, EFonts font, ColorRGBA color)
{ {
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE); OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
@ -289,6 +294,8 @@ void CButton::clickCancel(const Point & cursorPosition)
void CButton::showPopupWindow(const Point & cursorPosition) void CButton::showPopupWindow(const Point & cursorPosition)
{ {
callbackPopup();
if(!helpBox.empty()) //there is no point to show window with nothing inside... if(!helpBox.empty()) //there is no point to show window with nothing inside...
CRClickPopup::createAndPush(helpBox); CRClickPopup::createAndPush(helpBox);
} }

View File

@ -69,6 +69,7 @@ public:
class CButton : public ButtonBase class CButton : public ButtonBase
{ {
CFunctionList<void()> callback; CFunctionList<void()> callback;
CFunctionList<void()> callbackPopup;
std::array<std::string, 4> hoverTexts; //texts for statusbar, if empty - first entry will be used std::array<std::string, 4> hoverTexts; //texts for statusbar, if empty - first entry will be used
std::optional<ColorRGBA> borderColor; // mapping of button state to border color std::optional<ColorRGBA> borderColor; // mapping of button state to border color
@ -90,6 +91,7 @@ public:
/// adds one more callback to on-click actions /// adds one more callback to on-click actions
void addCallback(const std::function<void()> & callback); void addCallback(const std::function<void()> & callback);
void addPopupCallback(const std::function<void()> & callback);
void addHoverText(EButtonState state, const std::string & text); void addHoverText(EButtonState state, const std::string & text);

View File

@ -98,13 +98,15 @@ public:
} }
}; };
CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _myInt, bool openOnBattleSpells): CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _myInt, bool openOnBattleSpells, std::function<void(SpellID)> onSpellSelect):
CWindowObject(PLAYER_COLORED | (settings["gameTweaks"]["enableLargeSpellbook"].Bool() ? BORDERED : 0)), CWindowObject(PLAYER_COLORED | (settings["gameTweaks"]["enableLargeSpellbook"].Bool() ? BORDERED : 0)),
battleSpellsOnly(openOnBattleSpells), battleSpellsOnly(openOnBattleSpells),
selectedTab(4), selectedTab(4),
currentPage(0), currentPage(0),
myHero(_myHero), myHero(_myHero),
myInt(_myInt), myInt(_myInt),
openOnBattleSpells(openOnBattleSpells),
onSpellSelect(onSpellSelect),
isBigSpellbook(settings["gameTweaks"]["enableLargeSpellbook"].Bool()), isBigSpellbook(settings["gameTweaks"]["enableLargeSpellbook"].Bool()),
spellsPerPage(24), spellsPerPage(24),
offL(-11), offL(-11),
@ -293,6 +295,14 @@ void CSpellWindow::processSpells()
for(auto const & spell : CGI->spellh->objects) for(auto const & spell : CGI->spellh->objects)
{ {
bool searchTextFound = !searchBox || boost::algorithm::contains(boost::algorithm::to_lower_copy(spell->getNameTranslated()), boost::algorithm::to_lower_copy(searchBox->getText())); bool searchTextFound = !searchBox || boost::algorithm::contains(boost::algorithm::to_lower_copy(spell->getNameTranslated()), boost::algorithm::to_lower_copy(searchBox->getText()));
if(onSpellSelect)
{
if(spell->isCombat() == openOnBattleSpells && !spell->isSpecial() && !spell->isCreatureAbility())
mySpells.push_back(spell.get());
continue;
}
if(!spell->isCreatureAbility() && myHero->canCastThisSpell(spell.get()) && searchTextFound) if(!spell->isCreatureAbility() && myHero->canCastThisSpell(spell.get()) && searchTextFound)
mySpells.push_back(spell.get()); mySpells.push_back(spell.get());
} }
@ -602,6 +612,13 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
{ {
if(mySpell) if(mySpell)
{ {
if(owner->onSpellSelect)
{
owner->onSpellSelect(mySpell->id);
owner->fexitb();
return;
}
auto spellCost = owner->myInt->cb->getSpellCost(mySpell, owner->myHero); auto spellCost = owner->myInt->cb->getSpellCost(mySpell, owner->myHero);
if(spellCost > owner->myHero->mana) //insufficient mana if(spellCost > owner->myHero->mana) //insufficient mana
{ {
@ -738,7 +755,7 @@ void CSpellWindow::SpellArea::setSpell(const CSpell * spell)
} }
ColorRGBA firstLineColor, secondLineColor; ColorRGBA firstLineColor, secondLineColor;
if(spellCost > owner->myHero->mana) //hero cannot cast this spell if(spellCost > owner->myHero->mana && !owner->onSpellSelect) //hero cannot cast this spell
{ {
firstLineColor = Colors::WHITE; firstLineColor = Colors::WHITE;
secondLineColor = Colors::ORANGE; secondLineColor = Colors::ORANGE;

View File

@ -115,8 +115,11 @@ class CSpellWindow : public CWindowObject
std::shared_ptr<IImage> createBigSpellBook(); std::shared_ptr<IImage> createBigSpellBook();
bool openOnBattleSpells;
std::function<void(SpellID)> onSpellSelect; //external processing of selected spell
public: public:
CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _myInt, bool openOnBattleSpells = true); CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _myInt, bool openOnBattleSpells = true, std::function<void(SpellID)> onSpellSelect = nullptr);
~CSpellWindow(); ~CSpellWindow();
void fexitb(); void fexitb();

View File

@ -372,7 +372,7 @@
"type" : "object", "type" : "object",
"additionalProperties" : false, "additionalProperties" : false,
"default" : {}, "default" : {},
"required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize", "stickyHeroInfoWindows", "enableAutocombatSpells", "endWithAutocombat", "queueSmallSlots", "queueSmallOutside" ], "required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize", "stickyHeroInfoWindows", "enableAutocombatSpells", "endWithAutocombat", "queueSmallSlots", "queueSmallOutside", "enableQuickSpellPanel" ],
"properties" : { "properties" : {
"speedFactor" : { "speedFactor" : {
"type" : "number", "type" : "number",
@ -430,6 +430,10 @@
"queueSmallOutside" : { "queueSmallOutside" : {
"type": "boolean", "type": "boolean",
"default": false "default": false
},
"enableQuickSpellPanel" : {
"type": "boolean",
"default": true
} }
} }
}, },

View File

@ -64,6 +64,19 @@
"battleOpenHoveredUnit": "V", "battleOpenHoveredUnit": "V",
"battleRetreat": "R", "battleRetreat": "R",
"battleSelectAction": "S", "battleSelectAction": "S",
"battleToggleQuickSpell": "T",
"battleSpellShortcut0": "1",
"battleSpellShortcut1": "2",
"battleSpellShortcut2": "3",
"battleSpellShortcut3": "4",
"battleSpellShortcut4": "5",
"battleSpellShortcut5": "6",
"battleSpellShortcut6": "7",
"battleSpellShortcut7": "8",
"battleSpellShortcut8": "9",
"battleSpellShortcut9": "0",
"battleSpellShortcut10": "N",
"battleSpellShortcut11": "M",
"battleSurrender": "S", "battleSurrender": "S",
"battleTacticsEnd": [ "Return", "Keypad Enter"], "battleTacticsEnd": [ "Return", "Keypad Enter"],
"battleTacticsNext": "Space", "battleTacticsNext": "Space",