diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index 5ccd18b6c..4d9eb5eb7 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -51,19 +51,23 @@ BattleWindow::BattleWindow(BattleInterface & owner): const JsonNode config(ResourceID("config/widgets/BattleWindow.json")); - addCallback("options", std::bind(&BattleWindow::bOptionsf, this)); - addCallback("surrender", std::bind(&BattleWindow::bSurrenderf, this)); - addCallback("flee", std::bind(&BattleWindow::bFleef, this)); - addCallback("autofight", std::bind(&BattleWindow::bAutofightf, this)); - addCallback("spellbook", std::bind(&BattleWindow::bSpellf, this)); - addCallback("wait", std::bind(&BattleWindow::bWaitf, this)); - addCallback("defence", std::bind(&BattleWindow::bDefencef, this)); - addCallback("consoleUp", std::bind(&BattleWindow::bConsoleUpf, this)); - addCallback("consoleDown", std::bind(&BattleWindow::bConsoleDownf, this)); - addCallback("tacticNext", std::bind(&BattleWindow::bTacticNextStack, this)); - addCallback("tacticEnd", std::bind(&BattleWindow::bTacticPhaseEnd, this)); - addCallback("alternativeAction", std::bind(&BattleWindow::bSwitchActionf, this)); - + addShortcut(EShortcut::GLOBAL_OPTIONS, std::bind(&BattleWindow::bOptionsf, this)); + addShortcut(EShortcut::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this)); + addShortcut(EShortcut::BATTLE_RETREAT, std::bind(&BattleWindow::bFleef, this)); + addShortcut(EShortcut::BATTLE_AUTOCOMBAT, std::bind(&BattleWindow::bAutofightf, this)); + addShortcut(EShortcut::BATTLE_CAST_SPELL, std::bind(&BattleWindow::bSpellf, this)); + addShortcut(EShortcut::BATTLE_WAIT, std::bind(&BattleWindow::bWaitf, this)); + addShortcut(EShortcut::BATTLE_DEFEND, std::bind(&BattleWindow::bDefencef, this)); + addShortcut(EShortcut::BATTLE_CONSOLE_UP, std::bind(&BattleWindow::bConsoleUpf, this)); + addShortcut(EShortcut::BATTLE_CONSOLE_DOWN, std::bind(&BattleWindow::bConsoleDownf, this)); + addShortcut(EShortcut::BATTLE_TACTICS_NEXT, std::bind(&BattleWindow::bTacticNextStack, this)); + addShortcut(EShortcut::BATTLE_TACTICS_END, std::bind(&BattleWindow::bTacticPhaseEnd, this)); + addShortcut(EShortcut::BATTLE_SELECT_ACTION, std::bind(&BattleWindow::bSwitchActionf, this)); + + addShortcut(EShortcut::BATTLE_TOGGLE_QUEUE, [this](){ this->toggleQueueVisibility();}); + addShortcut(EShortcut::BATTLE_USE_CREATURE_SPELL, [this](){ this->owner.actionsController->enterCreatureCastingMode(); }); + addShortcut(EShortcut::GLOBAL_CANCEL, [this](){ this->owner.actionsController->endCastingSpell(); }); + build(config); console = widget("console"); @@ -190,19 +194,7 @@ void BattleWindow::keyPressed(EShortcut key) owner.openingEnd(); return; } - - if(key == EShortcut::BATTLE_TOGGLE_QUEUE) - { - toggleQueueVisibility(); - } - else if(key == EShortcut::BATTLE_USE_CREATURE_SPELL) - { - owner.actionsController->enterCreatureCastingMode(); - } - else if(key == EShortcut::GLOBAL_CANCEL) - { - owner.actionsController->endCastingSpell(); - } + InterfaceObjectConfigurable::keyPressed(key); } void BattleWindow::clickRight(tribool down, bool previousState) @@ -538,40 +530,18 @@ void BattleWindow::blockUI(bool on) bool canWait = owner.stacksController->getActiveStack() ? !owner.stacksController->getActiveStack()->waitedThisTurn : false; - if(auto w = widget("options")) - w->block(on); - if(auto w = widget("flee")) - w->block(on || !owner.curInt->cb->battleCanFlee()); - if(auto w = widget("surrender")) - w->block(on || owner.curInt->cb->battleGetSurrenderCost() < 0); - if(auto w = widget("cast")) - w->block(on || owner.tacticsMode || !canCastSpells); - if(auto w = widget("wait")) - w->block(on || owner.tacticsMode || !canWait); - if(auto w = widget("defence")) - w->block(on || owner.tacticsMode); - if(auto w = widget("alternativeAction")) - w->block(on || owner.tacticsMode); - if(auto w = widget("autofight")) - w->block(owner.actionsController->spellcastingModeActive()); - - auto btactEnd = widget("tacticEnd"); - auto btactNext = widget("tacticNext"); - if(owner.tacticsMode && btactEnd && btactNext) - { - btactNext->block(on); - btactEnd->block(on); - } - else - { - auto bConsoleUp = widget("consoleUp"); - auto bConsoleDown = widget("consoleDown"); - if(bConsoleUp && bConsoleDown) - { - bConsoleUp->block(on); - bConsoleDown->block(on); - } - } + setShortcutBlocked(EShortcut::GLOBAL_OPTIONS, on); + setShortcutBlocked(EShortcut::BATTLE_RETREAT, on || !owner.curInt->cb->battleCanFlee()); + setShortcutBlocked(EShortcut::BATTLE_SURRENDER, on || owner.curInt->cb->battleGetSurrenderCost() < 0); + setShortcutBlocked(EShortcut::BATTLE_CAST_SPELL, on || owner.tacticsMode || !canCastSpells); + setShortcutBlocked(EShortcut::BATTLE_WAIT, on || owner.tacticsMode || !canWait); + setShortcutBlocked(EShortcut::BATTLE_DEFEND, on || owner.tacticsMode); + setShortcutBlocked(EShortcut::BATTLE_SELECT_ACTION, on || owner.tacticsMode); + setShortcutBlocked(EShortcut::BATTLE_AUTOCOMBAT, owner.actionsController->spellcastingModeActive()); + setShortcutBlocked(EShortcut::BATTLE_TACTICS_END, on && owner.tacticsMode); + setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on && owner.tacticsMode); + setShortcutBlocked(EShortcut::BATTLE_CONSOLE_DOWN, on && !owner.tacticsMode); + setShortcutBlocked(EShortcut::BATTLE_CONSOLE_UP, on && !owner.tacticsMode); } std::optional BattleWindow::getQueueHoveredUnitId() diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index 6423499d3..cc4d96422 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -201,19 +201,19 @@ std::pair InterfaceObjectConfigurable::readHintText(co return result; } -EShortcut InterfaceObjectConfigurable::readKeycode(const JsonNode & config) const +EShortcut InterfaceObjectConfigurable::readHotkey(const JsonNode & config) const { - logGlobal->debug("Reading keycode"); + logGlobal->debug("Reading hotkey"); if(config.getType() != JsonNode::JsonType::DATA_STRING) { - logGlobal->error("Invalid keycode format in interface configuration! Expected string!", config.String()); + logGlobal->error("Invalid hotket format in interface configuration! Expected string!", config.String()); return EShortcut::NONE; } EShortcut result = GH.shortcutsHandler().findShortcut(config.String()); if (result == EShortcut::NONE) - logGlobal->error("Invalid keycode '%s' in interface configuration!", config.String()); + logGlobal->error("Invalid hotkey '%s' in interface configuration!", config.String()); return result;; } @@ -320,11 +320,27 @@ std::shared_ptr InterfaceObjectConfigurable::buildButton(const JsonNode button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer()); } if(!config["callback"].isNull()) - button->addCallback(std::bind(callbacks.at(config["callback"].String()), 0)); + { + std::string callbackName = config["callback"].String(); + + if (callbacks.count(callbackName) > 0) + button->addCallback(std::bind(callbacks.at(callbackName), 0)); + else + logGlobal->error("Invalid callback '%s' in widget", callbackName ); + } if(!config["hotkey"].isNull()) { if(config["hotkey"].getType() == JsonNode::JsonType::DATA_STRING) - button->assignedKey = readKeycode(config["hotkey"]); + { + button->assignedKey = readHotkey(config["hotkey"]); + + auto target = shortcuts.find(button->assignedKey); + if (target != shortcuts.end()) + { + button->addCallback(target->second.callback); + target->second.assignedToButton = true; + } + } } return button; } @@ -430,3 +446,41 @@ std::shared_ptr InterfaceObjectConfigurable::buildWidget(JsonNode co logGlobal->error("Builder with type %s is not registered", type); return nullptr; } + +void InterfaceObjectConfigurable::setShortcutBlocked(EShortcut shortcut, bool isBlocked) +{ + auto target = shortcuts.find(key); + if (target == shortcuts.end()) + return; + + target->second.blocked = isBlocked; + + for (auto & entry : widgets) + { + auto button = std::dynamic_pointer_cast(entry.second); + + if (button && button->assignedKey == shortcut) + button->block(isBlocked); + } +} + +void InterfaceObjectConfigurable::addShortcut(EShortcut shortcut, std::function callback) +{ + assert(shortcuts.count(shortcut) == 0); + shortcuts[shortcut].callback = callback; +} + +void InterfaceObjectConfigurable::keyPressed(EShortcut key) +{ + auto target = shortcuts.find(key); + if (target == shortcuts.end()) + return; + + if (target->second.assignedToButton) + return; // will be handled by button instance + + if (target->second.blocked) + return; + + target->second.callback(); +} diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h index deba0481a..56ddcd19b 100644 --- a/client/gui/InterfaceObjectConfigurable.h +++ b/client/gui/InterfaceObjectConfigurable.h @@ -35,7 +35,14 @@ public: InterfaceObjectConfigurable(const JsonNode & config, int used=0, Point offset=Point()); protected: - + /// Set blocked status for all buttons assotiated with provided shortcut + void setShortcutBlocked(EShortcut shortcut, bool isBlocked); + + /// Registers provided callback to be called whenever specified shortcut is triggered + void addShortcut(EShortcut shortcut, std::function callback); + + void keyPressed(EShortcut key) override; + using BuilderFunction = std::function(const JsonNode &)>; void registerBuilder(const std::string &, BuilderFunction); @@ -64,7 +71,7 @@ protected: EFonts readFont(const JsonNode &) const; std::string readText(const JsonNode &) const; std::pair readHintText(const JsonNode &) const; - EShortcut readKeycode(const JsonNode &) const; + EShortcut readHotkey(const JsonNode &) const; //basic widgets std::shared_ptr buildPicture(const JsonNode &) const; @@ -82,9 +89,16 @@ protected: std::shared_ptr buildWidget(JsonNode config) const; private: + struct ShortcutState + { + std::function callback; + mutable bool assignedToButton = false; + bool blocked = false; + }; int unnamedObjectId = 0; std::map builders; std::map> widgets; std::map> callbacks; + std::map shortcuts; }; diff --git a/client/gui/Shortcut.h b/client/gui/Shortcut.h index 7579ef1dc..956c6ce88 100644 --- a/client/gui/Shortcut.h +++ b/client/gui/Shortcut.h @@ -124,6 +124,7 @@ enum class EShortcut BATTLE_CONSOLE_DOWN, BATTLE_TACTICS_NEXT, BATTLE_TACTICS_END, + BATTLE_SELECT_ACTION, // Alternative actions toggle // Town screen TOWN_OPEN_TAVERN, diff --git a/client/gui/ShortcutHandler.cpp b/client/gui/ShortcutHandler.cpp index 263f1e463..dfd30cb84 100644 --- a/client/gui/ShortcutHandler.cpp +++ b/client/gui/ShortcutHandler.cpp @@ -126,6 +126,7 @@ std::vector ShortcutHandler::translateKeycode(SDL_Keycode key) const {SDLK_SPACE, EShortcut::BATTLE_TACTICS_NEXT }, {SDLK_RETURN, EShortcut::BATTLE_TACTICS_END }, {SDLK_KP_ENTER, EShortcut::BATTLE_TACTICS_END }, + {SDLK_s, EShortcut::BATTLE_SELECT_ACTION }, {SDLK_t, EShortcut::TOWN_OPEN_TAVERN }, {SDLK_SPACE, EShortcut::TOWN_SWAP_ARMIES }, {SDLK_END, EShortcut::RECRUITMENT_MAX }, @@ -255,6 +256,7 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const {"battleConsoleDown", EShortcut::BATTLE_CONSOLE_DOWN }, {"battleTacticsNext", EShortcut::BATTLE_TACTICS_NEXT }, {"battleTacticsEnd", EShortcut::BATTLE_TACTICS_END }, + {"battleSelectAction", EShortcut::BATTLE_SELECT_ACTION }, {"townOpenTavern", EShortcut::TOWN_OPEN_TAVERN }, {"townSwapArmies", EShortcut::TOWN_SWAP_ARMIES }, {"recruitmentMax", EShortcut::RECRUITMENT_MAX }, diff --git a/config/widgets/battleWindow.json b/config/widgets/battleWindow.json index a23ecb7b1..7548caba1 100644 --- a/config/widgets/battleWindow.json +++ b/config/widgets/battleWindow.json @@ -21,7 +21,6 @@ "position": {"x": 4, "y": 560}, "image": "icm003", "help": "core.help.381", - "callback": "options", "hotkey": "globalOptions" }, @@ -31,7 +30,6 @@ "position": {"x": 55, "y": 560}, "image": "icm001", "help": "core.help.379", - "callback": "surrender", "hotkey": "battleSurrender" }, @@ -41,7 +39,6 @@ "position": {"x": 106, "y": 560}, "image": "icm002", "help": "core.help.380", - "callback": "flee", "hotkey": "battleRetreat" }, @@ -51,7 +48,6 @@ "position": {"x": 157, "y": 560}, "image": "icm004", "help": "core.help.382", - "callback": "autofight", "hotkey": "battleAutocombat" }, @@ -61,7 +57,6 @@ "position": {"x": 646, "y": 560}, "image": "icm005", "help": "core.help.385", - "callback": "spellbook", "hotkey": "battleCastSpell" }, @@ -71,7 +66,6 @@ "position": {"x": 697, "y": 560}, "image": "icm006", "help": "core.help.386", - "callback": "wait", "hotkey": "battleWait" }, @@ -81,7 +75,6 @@ "position": {"x": 748, "y": 560}, "image": "icm007", "help": "core.help.387", - "callback": "defence", "hotkey": "battleDefend" }, @@ -90,7 +83,6 @@ "name": "consoleUp", "position": {"x": 625, "y": 560}, "image": "ComSlide", - "callback": "consoleUp", "imageOrder": [0, 1, 0, 0], "hotkey": "battleConsoleUp" }, @@ -100,7 +92,6 @@ "name": "consoleDown", "position": {"x": 625, "y": 579}, "image": "ComSlide", - "callback": "consoleDown", "imageOrder": [2, 3, 2, 2], "hotkey": "battleConsoleDown" }, @@ -117,7 +108,6 @@ "name": "tacticNext", "position": {"x": 213, "y": 560}, "image": "icm011", - "callback": "tacticNext", "hotkey": "battleTacticsNext" }, @@ -126,7 +116,6 @@ "name": "tacticEnd", "position": {"x": 419, "y": 560}, "image": "icm012", - "callback": "tacticEnd", "hotkey": "battleTacticsEnd" } ]