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/Graphics.h"
#include "../widgets/Buttons.h"
#include "../widgets/CComponent.h"
#include "../widgets/Images.h"
#include "../widgets/Slider.h"
#include "../widgets/TextControls.h"
@ -39,9 +40,11 @@
#include "../windows/CMessage.h"
#include "../windows/CCreatureWindow.h"
#include "../windows/CSpellWindow.h"
#include "../windows/InfoWindows.h"
#include "../render/CAnimation.h"
#include "../render/IRenderHandler.h"
#include "../adventureMap/CInGameConsole.h"
#include "../eventsSDL/InputHandler.h"
#include "../../CCallback.h"
#include "../../lib/CStack.h"
@ -55,6 +58,8 @@
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/networkPacks/PacksForClientBattle.h"
#include "../../lib/TextOperations.h"
#include "../../lib/json/JsonUtils.h"
void BattleConsole::showAll(Canvas & to)
{
@ -417,6 +422,79 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
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)
: CIntObject(0)
{

View File

@ -22,6 +22,7 @@ class CGHeroInstance;
struct BattleResult;
struct InfoAboutHero;
class CStack;
class CPlayerBattleCallback;
namespace battle
{
@ -44,6 +45,7 @@ class TransparentFilledRectangle;
class CPlayerInterface;
class BattleRenderer;
class VideoWidget;
class QuickSpellPanel;
/// 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
@ -147,6 +149,26 @@ public:
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
{
private:

View File

@ -45,8 +45,8 @@
#include "../../lib/CPlayerState.h"
#include "../windows/settings/SettingsMainWindow.h"
BattleWindow::BattleWindow(BattleInterface & owner):
owner(owner),
BattleWindow::BattleWindow(BattleInterface & Owner):
owner(Owner),
lastAlternativeAction(PossiblePlayerBattleAction::INVALID)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
@ -64,6 +64,20 @@ BattleWindow::BattleWindow(BattleInterface & owner):
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::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this));
addShortcut(EShortcut::BATTLE_RETREAT, std::bind(&BattleWindow::bFleef, this));
@ -95,6 +109,7 @@ BattleWindow::BattleWindow(BattleInterface & owner):
owner.fieldController->createHeroes();
createQueue();
createQuickSpellWindow();
createStickyHeroInfoWindows();
createTimerInfoWindows();
@ -164,10 +179,67 @@ void BattleWindow::createStickyHeroInfoWindows()
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()
{
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)
{
PlayerColor attacker = owner.getBattle()->sideToPlayer(BattleSide::ATTACKER);
@ -176,7 +248,7 @@ void BattleWindow::createTimerInfoWindows()
if (attacker.isValidPlayer())
{
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
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() );
}
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()
{
if(settings["battle"]["showQueue"].Bool())
@ -283,10 +373,12 @@ void BattleWindow::showStickyHeroWindows()
void BattleWindow::updateQueue()
{
queue->update();
createQuickSpellWindow();
}
void BattleWindow::setPositionInfoWindow()
{
int xOffsetAttacker = quickSpellWindow->isEnabled ? -53 : 0;
if(defenderHeroWindow)
{
Point position = (GH.screenDimensions().x >= 1000)
@ -297,7 +389,7 @@ void BattleWindow::setPositionInfoWindow()
if(attackerHeroWindow)
{
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);
attackerHeroWindow->moveTo(position);
}
@ -311,7 +403,7 @@ void BattleWindow::setPositionInfoWindow()
if(attackerStackWindow)
{
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);
attackerStackWindow->moveTo(position);
}
@ -346,6 +438,7 @@ void BattleWindow::updateStackInfoWindow(const CStack * stack)
attackerStackWindow = nullptr;
setPositionInfoWindow();
createTimerInfoWindows();
}
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_CONSOLE_DOWN, on && !owner.tacticsMode);
setShortcutBlocked(EShortcut::BATTLE_CONSOLE_UP, on && !owner.tacticsMode);
quickSpellWindow->setInputEnabled(!on);
}
void BattleWindow::bOpenActiveUnit()

View File

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

View File

@ -186,6 +186,19 @@ enum class EShortcut
BATTLE_TOGGLE_HEROES_STATS,
BATTLE_OPEN_ACTIVE_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_MAX_AMOUNT,

View File

@ -222,6 +222,19 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
{"battleTacticsNext", EShortcut::BATTLE_TACTICS_NEXT },
{"battleTacticsEnd", EShortcut::BATTLE_TACTICS_END },
{"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 },
{"spectateSkipBattle", EShortcut::SPECTATE_SKIP_BATTLE },
{"spectateSkipBattleResult", EShortcut::SPECTATE_SKIP_BATTLE_RESULT },

View File

@ -67,6 +67,11 @@ void CButton::addCallback(const std::function<void()> & 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)
{
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
@ -289,6 +294,8 @@ void CButton::clickCancel(const Point & cursorPosition)
void CButton::showPopupWindow(const Point & cursorPosition)
{
callbackPopup();
if(!helpBox.empty()) //there is no point to show window with nothing inside...
CRClickPopup::createAndPush(helpBox);
}

View File

@ -69,6 +69,7 @@ public:
class CButton : public ButtonBase
{
CFunctionList<void()> callback;
CFunctionList<void()> callbackPopup;
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
@ -90,6 +91,7 @@ public:
/// adds one more callback to on-click actions
void addCallback(const std::function<void()> & callback);
void addPopupCallback(const std::function<void()> & callback);
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)),
battleSpellsOnly(openOnBattleSpells),
selectedTab(4),
currentPage(0),
myHero(_myHero),
myInt(_myInt),
openOnBattleSpells(openOnBattleSpells),
onSpellSelect(onSpellSelect),
isBigSpellbook(settings["gameTweaks"]["enableLargeSpellbook"].Bool()),
spellsPerPage(24),
offL(-11),
@ -293,6 +295,14 @@ void CSpellWindow::processSpells()
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()));
if(onSpellSelect)
{
if(spell->isCombat() == openOnBattleSpells && !spell->isSpecial() && !spell->isCreatureAbility())
mySpells.push_back(spell.get());
continue;
}
if(!spell->isCreatureAbility() && myHero->canCastThisSpell(spell.get()) && searchTextFound)
mySpells.push_back(spell.get());
}
@ -602,6 +612,13 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
{
if(mySpell)
{
if(owner->onSpellSelect)
{
owner->onSpellSelect(mySpell->id);
owner->fexitb();
return;
}
auto spellCost = owner->myInt->cb->getSpellCost(mySpell, owner->myHero);
if(spellCost > owner->myHero->mana) //insufficient mana
{
@ -738,7 +755,7 @@ void CSpellWindow::SpellArea::setSpell(const CSpell * spell)
}
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;
secondLineColor = Colors::ORANGE;

View File

@ -115,8 +115,11 @@ class CSpellWindow : public CWindowObject
std::shared_ptr<IImage> createBigSpellBook();
bool openOnBattleSpells;
std::function<void(SpellID)> onSpellSelect; //external processing of selected spell
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();
void fexitb();

View File

@ -372,7 +372,7 @@
"type" : "object",
"additionalProperties" : false,
"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" : {
"speedFactor" : {
"type" : "number",
@ -430,6 +430,10 @@
"queueSmallOutside" : {
"type": "boolean",
"default": false
},
"enableQuickSpellPanel" : {
"type": "boolean",
"default": true
}
}
},

View File

@ -64,6 +64,19 @@
"battleOpenHoveredUnit": "V",
"battleRetreat": "R",
"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",
"battleTacticsEnd": [ "Return", "Keypad Enter"],
"battleTacticsNext": "Space",