Browse Source

Integrated shortcuts into adventure window configuration

Ivan Savenko 2 years ago
parent
commit
019d7b6811

+ 2 - 0
client/CMakeLists.txt

@@ -4,6 +4,7 @@ set(client_SRCS
 
 	adventureMap/CAdventureMapInterface.cpp
 	adventureMap/CAdventureMapWidget.cpp
+	adventureMap/AdventureMapShortcuts.cpp
 	adventureMap/CAdventureOptions.cpp
 	adventureMap/CInGameConsole.cpp
 	adventureMap/CInfoBar.cpp
@@ -132,6 +133,7 @@ set(client_HEADERS
 
 	adventureMap/CAdventureMapInterface.h
 	adventureMap/CAdventureMapWidget.h
+	adventureMap/AdventureMapShortcuts.h
 	adventureMap/CAdventureOptions.h
 	adventureMap/CInGameConsole.h
 	adventureMap/CInfoBar.h

+ 351 - 0
client/adventureMap/AdventureMapShortcuts.cpp

@@ -0,0 +1,351 @@
+/*
+ * AdventureMapShortcuts.cpp, 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
+ *
+ */
+
+#include "StdInc.h"
+#include "AdventureMapShortcuts.h"
+
+#include "../windows/CKingdomInterface.h"
+#include "../windows/CSpellWindow.h"
+#include "../windows/CTradeWindow.h"
+#include "../lobby/CSavingScreen.h"
+
+#include "../gui/CGuiHandler.h"
+#include "../gui/Shortcut.h"
+#include "../CPlayerInterface.h"
+#include "../PlayerLocalState.h"
+#include "../CGameInfo.h"
+#include "CAdventureMapInterface.h"
+#include "CAdventureOptions.h"
+#include "../windows/settings/SettingsMainWindow.h"
+
+#include "../../CCallback.h"
+#include "../../lib/CConfigHandler.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../../lib/mapObjects/CGTownInstance.h"
+#include "../../lib/CPathfinder.h"
+
+AdventureMapShortcuts::AdventureMapShortcuts(CAdventureMapInterface & owner)
+	:owner(owner)
+{}
+
+std::map<EShortcut, std::function<void()>> AdventureMapShortcuts::getFunctors()
+{
+	std::map<EShortcut, std::function<void()>> result = {
+		{ EShortcut::ADVENTURE_KINGDOM_OVERVIEW, [this]() { this->showOverview(); } },
+		{ EShortcut::NONE, [this]() { this->worldViewBack(); } },
+		{ EShortcut::NONE, [this]() { this->worldViewScale1x(); } },
+		{ EShortcut::NONE, [this]() { this->worldViewScale2x(); } },
+		{ EShortcut::NONE, [this]() { this->worldViewScale4x(); } },
+		{ EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL, [this]() { this->switchMapLevel(); } },
+		{ EShortcut::ADVENTURE_QUEST_LOG, [this]() { this->showQuestlog(); } },
+		{ EShortcut::ADVENTURE_TOGGLE_SLEEP, [this]() { this->toggleSleepWake(); } },
+		{ EShortcut::ADVENTURE_SET_HERO_ASLEEP, [this]() { this->setHeroSleeping(); } },
+		{ EShortcut::ADVENTURE_SET_HERO_AWAKE, [this]() { this->setHeroAwake(); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO, [this]() { this->moveHeroAlongPath(); } },
+		{ EShortcut::ADVENTURE_CAST_SPELL, [this]() { this->showSpellbook(); } },
+		{ EShortcut::ADVENTURE_GAME_OPTIONS, [this]() { this->adventureOptions(); } },
+		{ EShortcut::GLOBAL_OPTIONS, [this]() { this->systemOptions(); } },
+		{ EShortcut::ADVENTURE_NEXT_HERO, [this]() { this->nextHero(); } },
+		{ EShortcut::GAME_END_TURN, [this]() { this->endTurn(); } },
+		{ EShortcut::ADVENTURE_THIEVES_GUILD, [this]() { this->showThievesGuild(); } },
+		{ EShortcut::ADVENTURE_VIEW_SCENARIO, [this]() { this->showScenarioInfo(); } },
+		{ EShortcut::GAME_SAVE_GAME, [this]() { this->saveGame(); } },
+		{ EShortcut::GAME_LOAD_GAME, [this]() { this->loadGame(); } },
+		{ EShortcut::ADVENTURE_DIG_GRAIL, [this]() { this->digGrail(); } },
+		{ EShortcut::ADVENTURE_VIEW_PUZZLE, [this]() { this->viewPuzzleMap(); } },
+		{ EShortcut::ADVENTURE_VIEW_WORLD, [this]() { this->viewWorldMap(); } },
+		{ EShortcut::GAME_RESTART_GAME, [this]() { this->restartGame(); } },
+		{ EShortcut::ADVENTURE_VISIT_OBJECT, [this]() { this->visitObject(); } },
+		{ EShortcut::ADVENTURE_VIEW_SELECTED, [this]() { this->openObject(); } },
+		{ EShortcut::GLOBAL_CANCEL, [this]() { this->abortSpellcasting(); } },
+		{ EShortcut::GAME_OPEN_MARKETPLACE, [this]() { this->showMarketplace(); } },
+		{ EShortcut::ADVENTURE_NEXT_TOWN, [this]() { this->nextTown(); } },
+//		{ EShortcut::ADVENTURE_NEXT_OBJECT, [this]() { this->nextObject(); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_SW, [this]() { this->moveHeroDirectional({-1, +1}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_SS, [this]() { this->moveHeroDirectional({ 0, +1}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_SE, [this]() { this->moveHeroDirectional({+1, +1}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_WW, [this]() { this->moveHeroDirectional({-1,  0}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_EE, [this]() { this->moveHeroDirectional({+1,  0}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_NW, [this]() { this->moveHeroDirectional({-1, -1}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_NN, [this]() { this->moveHeroDirectional({ 0, -1}); } },
+		{ EShortcut::ADVENTURE_MOVE_HERO_NE, [this]() { this->moveHeroDirectional({+1, -1}); } },
+	};
+
+	return result;
+}
+
+void AdventureMapShortcuts::showOverview()
+{
+	GH.pushIntT<CKingdomInterface>();
+}
+
+void AdventureMapShortcuts::worldViewBack()
+{
+	owner.hotkeyExitWorldView();
+
+	auto hero = LOCPLINT->localState->getCurrentHero();
+	if (hero)
+		owner.centerOnObject(hero);
+}
+
+void AdventureMapShortcuts::worldViewScale1x()
+{
+	// TODO set corresponding scale button to "selected" mode
+	owner.openWorldView(7);
+}
+
+void AdventureMapShortcuts::worldViewScale2x()
+{
+	owner.openWorldView(11);
+}
+
+void AdventureMapShortcuts::worldViewScale4x()
+{
+	owner.openWorldView(16);
+}
+
+void AdventureMapShortcuts::switchMapLevel()
+{
+	// with support for future multi-level maps :)
+	int maxLevels = LOCPLINT->cb->getMapSize().z;
+	if (maxLevels < 2)
+		return;
+
+	owner.hotkeySwitchMapLevel();
+}
+
+void AdventureMapShortcuts::showQuestlog()
+{
+	LOCPLINT->showQuestLog();
+}
+
+void AdventureMapShortcuts::toggleSleepWake()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+	if (!h)
+		return;
+	bool newSleep = !LOCPLINT->localState->isHeroSleeping(h);
+
+	if (newSleep)
+		LOCPLINT->localState->setHeroAsleep(h);
+	else
+		LOCPLINT->localState->setHeroAwaken(h);
+
+	owner.onHeroChanged(h);
+
+	if (newSleep)
+		nextHero();
+}
+
+void AdventureMapShortcuts::setHeroSleeping()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+	if (h)
+	{
+		LOCPLINT->localState->setHeroAsleep(h);
+		owner.onHeroChanged(h);
+		nextHero();
+	}
+}
+
+void AdventureMapShortcuts::setHeroAwake()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+	if (h)
+	{
+		LOCPLINT->localState->setHeroAsleep(h);
+		owner.onHeroChanged(h);
+	}
+}
+
+void AdventureMapShortcuts::moveHeroAlongPath()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+	if (!h || !LOCPLINT->localState->hasPath(h))
+		return;
+
+	LOCPLINT->moveHero(h, LOCPLINT->localState->getPath(h));
+}
+
+void AdventureMapShortcuts::showSpellbook()
+{
+	if (!LOCPLINT->localState->getCurrentHero()) //checking necessary values
+		return;
+
+	owner.centerOnObject(LOCPLINT->localState->getCurrentHero());
+
+	GH.pushIntT<CSpellWindow>(LOCPLINT->localState->getCurrentHero(), LOCPLINT, false);
+}
+
+void AdventureMapShortcuts::adventureOptions()
+{
+	GH.pushIntT<CAdventureOptions>();
+}
+
+void AdventureMapShortcuts::systemOptions()
+{
+	GH.pushIntT<SettingsMainWindow>();
+}
+
+void AdventureMapShortcuts::nextHero()
+{
+	const auto * currHero = LOCPLINT->localState->getCurrentHero();
+	const auto * nextHero = LOCPLINT->localState->getNextWanderingHero(currHero);
+
+	if (nextHero)
+	{
+		LOCPLINT->localState->setSelection(nextHero);
+		owner.centerOnObject(nextHero);
+	}
+}
+
+void AdventureMapShortcuts::endTurn()
+{
+	if(!LOCPLINT->makingTurn)
+		return;
+
+	if(settings["adventure"]["heroReminder"].Bool())
+	{
+		for(auto hero : LOCPLINT->localState->getWanderingHeroes())
+		{
+			if(!LOCPLINT->localState->isHeroSleeping(hero) && hero->movement > 0)
+			{
+				// Only show hero reminder if conditions met:
+				// - There still movement points
+				// - Hero don't have a path or there not points for first step on path
+				LOCPLINT->localState->verifyPath(hero);
+
+				if(!LOCPLINT->localState->hasPath(hero))
+				{
+					LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], [this](){ owner.hotkeyEndingTurn(); }, nullptr);
+					return;
+				}
+
+				auto path = LOCPLINT->localState->getPath(hero);
+				if (path.nodes.size() < 2 || path.nodes[path.nodes.size() - 2].turns)
+				{
+					LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], [this](){ owner.hotkeyEndingTurn(); }, nullptr);
+					return;
+				}
+			}
+		}
+	}
+	owner.hotkeyEndingTurn();
+}
+
+void AdventureMapShortcuts::showThievesGuild()
+{
+	//find first town with tavern
+	auto itr = range::find_if(LOCPLINT->localState->getOwnedTowns(), [](const CGTownInstance * town)
+	{
+		return town->hasBuilt(BuildingID::TAVERN);
+	});
+
+	if(itr != LOCPLINT->localState->getOwnedTowns().end())
+		LOCPLINT->showThievesGuildWindow(*itr);
+	else
+		LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithTavern"));
+}
+
+void AdventureMapShortcuts::showScenarioInfo()
+{
+	CAdventureOptions::showScenarioInfo();
+}
+
+void AdventureMapShortcuts::saveGame()
+{
+	GH.pushIntT<CSavingScreen>();
+}
+
+void AdventureMapShortcuts::loadGame()
+{
+	LOCPLINT->proposeLoadingGame();
+}
+
+void AdventureMapShortcuts::digGrail()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+
+	if(h && LOCPLINT->makingTurn)
+		LOCPLINT->tryDiggging(h);
+	return;
+}
+
+void AdventureMapShortcuts::viewPuzzleMap()
+{
+	LOCPLINT->showPuzzleMap();
+}
+
+void AdventureMapShortcuts::viewWorldMap()
+{
+	LOCPLINT->viewWorldMap();
+}
+
+void AdventureMapShortcuts::restartGame()
+{
+	LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
+		[](){ GH.pushUserEvent(EUserEvent::RESTART_GAME); }, nullptr);
+}
+
+void AdventureMapShortcuts::visitObject()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+
+	if(h)
+		LOCPLINT->cb->moveHero(h,h->pos);
+}
+
+void AdventureMapShortcuts::openObject()
+{
+	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
+	const CGTownInstance *t = LOCPLINT->localState->getCurrentTown();
+	if(h)
+		LOCPLINT->openHeroWindow(h);
+
+	if(t)
+		LOCPLINT->openTownWindow(t);
+
+	return;
+}
+
+void AdventureMapShortcuts::abortSpellcasting()
+{
+	owner.hotkeyAbortCastingMode();
+}
+
+void AdventureMapShortcuts::showMarketplace()
+{
+	//check if we have any marketplace
+	const CGTownInstance *townWithMarket = nullptr;
+	for(const CGTownInstance *t : LOCPLINT->cb->getTownsInfo())
+	{
+		if(t->hasBuilt(BuildingID::MARKETPLACE))
+		{
+			townWithMarket = t;
+			break;
+		}
+	}
+
+	if(townWithMarket) //if any town has marketplace, open window
+		GH.pushIntT<CMarketplaceWindow>(townWithMarket);
+	else //if not - complain
+		LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithMarket"));
+}
+
+void AdventureMapShortcuts::nextTown()
+{
+	owner.hotkeyEndingTurn();
+}
+
+void AdventureMapShortcuts::moveHeroDirectional(const Point & direction)
+{
+	owner.hotkeyMoveHeroDirectional(direction);
+}

+ 61 - 0
client/adventureMap/AdventureMapShortcuts.h

@@ -0,0 +1,61 @@
+/*
+ * AdventureMapShortcuts.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
+
+VCMI_LIB_NAMESPACE_BEGIN
+class Point;
+VCMI_LIB_NAMESPACE_END
+
+enum class EShortcut;
+class CAdventureMapInterface;
+
+/// Class that contains list of functions for shortcuts available from adventure map
+class AdventureMapShortcuts
+{
+	CAdventureMapInterface & owner;
+
+	void showOverview();
+	void worldViewBack();
+	void worldViewScale1x();
+	void worldViewScale2x();
+	void worldViewScale4x();
+	void switchMapLevel();
+	void showQuestlog();
+	void toggleSleepWake();
+	void setHeroSleeping();
+	void setHeroAwake();
+	void moveHeroAlongPath();
+	void showSpellbook();
+	void adventureOptions();
+	void systemOptions();
+	void nextHero();
+	void endTurn();
+	void showThievesGuild();
+	void showScenarioInfo();
+	void saveGame();
+	void loadGame();
+	void digGrail();
+	void viewPuzzleMap();
+	void viewWorldMap();
+	void restartGame();
+	void visitObject();
+	void openObject();
+	void abortSpellcasting();
+	void showMarketplace();
+	void nextTown();
+	void nextObject();
+	void moveHeroDirectional(const Point & direction);
+
+public:
+	explicit AdventureMapShortcuts(CAdventureMapInterface & owner);
+
+	std::map<EShortcut, std::function<void()>> getFunctors();
+};

+ 22 - 292
client/adventureMap/CAdventureMapInterface.cpp

@@ -17,35 +17,23 @@
 #include "CInfoBar.h"
 #include "MapAudioPlayer.h"
 #include "CAdventureMapWidget.h"
+#include "AdventureMapShortcuts.h"
 
 #include "../mapView/mapHandler.h"
 #include "../mapView/MapView.h"
-#include "../windows/CKingdomInterface.h"
-#include "../windows/CSpellWindow.h"
-#include "../windows/CTradeWindow.h"
-#include "../windows/GUIClasses.h"
 #include "../windows/InfoWindows.h"
 #include "../CGameInfo.h"
-#include "../CPlayerInterface.h"
-#include "../lobby/CSavingScreen.h"
-#include "../render/CAnimation.h"
 #include "../gui/CursorHandler.h"
-#include "../render/IImage.h"
-#include "../renderSDL/SDL_Extensions.h"
 #include "../gui/CGuiHandler.h"
-#include "../gui/Shortcut.h"
-#include "../widgets/TextControls.h"
-#include "../widgets/Buttons.h"
-#include "../windows/settings/SettingsMainWindow.h"
 #include "../CMT.h"
 #include "../PlayerLocalState.h"
+#include "../CPlayerInterface.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
-#include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/CPathfinder.h"
 #include "../../lib/mapping/CMap.h"
 
@@ -62,53 +50,16 @@ CAdventureMapInterface::CAdventureMapInterface():
 	pos.h = GH.screenDimensions().y;
 	strongInterest = true; // handle all mouse move events to prevent dead mouse move space in fullscreen mode
 
-	widget = std::make_shared<CAdventureMapWidget>();
-	exitWorldView();
+	shortcuts = std::make_shared<AdventureMapShortcuts>(*this);
+
+	widget = std::make_shared<CAdventureMapWidget>(shortcuts);
+	widget->setState(EGameState::MAKING_TURN);
+	widget->getMapView()->onViewMapActivated();
 
 	widget->setOptionHasQuests(!CGI->mh->getMap()->quests.empty());
 	widget->setOptionHasUnderground(CGI->mh->getMap()->twoLevel);
 }
 
-void CAdventureMapInterface::fshowOverview()
-{
-	GH.pushIntT<CKingdomInterface>();
-}
-
-void CAdventureMapInterface::fworldViewBack()
-{
-	exitWorldView();
-
-	auto hero = LOCPLINT->localState->getCurrentHero();
-	if (hero)
-		centerOnObject(hero);
-}
-
-void CAdventureMapInterface::fworldViewScale1x()
-{
-	// TODO set corresponding scale button to "selected" mode
-	openWorldView(7);
-}
-
-void CAdventureMapInterface::fworldViewScale2x()
-{
-	openWorldView(11);
-}
-
-void CAdventureMapInterface::fworldViewScale4x()
-{
-	openWorldView(16);
-}
-
-void CAdventureMapInterface::fswitchLevel()
-{
-	// with support for future multi-level maps :)
-	int maxLevels = CGI->mh->getMap()->levels();
-	if (maxLevels < 2)
-		return;
-
-	widget->getMapView()->onMapLevelSwitched();
-}
-
 void CAdventureMapInterface::onMapViewMoved(const Rect & visibleArea, int mapLevel)
 {
 	widget->setOptionUndergroundLevel(mapLevel > 0);
@@ -125,104 +76,6 @@ void CAdventureMapInterface::onAudioPaused()
 	mapAudio->onAudioPaused();
 }
 
-void CAdventureMapInterface::fshowQuestlog()
-{
-	LOCPLINT->showQuestLog();
-}
-
-void CAdventureMapInterface::fsleepWake()
-{
-	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
-	if (!h)
-		return;
-	bool newSleep = !LOCPLINT->localState->isHeroSleeping(h);
-
-	if (newSleep)
-		LOCPLINT->localState->setHeroAsleep(h);
-	else
-		LOCPLINT->localState->setHeroAwaken(h);
-
-	onHeroChanged(h);
-
-	if (newSleep)
-		fnextHero();
-}
-
-void CAdventureMapInterface::fmoveHero()
-{
-	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
-	if (!h || !LOCPLINT->localState->hasPath(h) || CGI->mh->hasOngoingAnimations())
-		return;
-
-	LOCPLINT->moveHero(h, LOCPLINT->localState->getPath(h));
-}
-
-void CAdventureMapInterface::fshowSpellbok()
-{
-	if (!LOCPLINT->localState->getCurrentHero()) //checking necessary values
-		return;
-
-	centerOnObject(LOCPLINT->localState->getCurrentHero());
-
-	GH.pushIntT<CSpellWindow>(LOCPLINT->localState->getCurrentHero(), LOCPLINT, false);
-}
-
-void CAdventureMapInterface::fadventureOPtions()
-{
-	GH.pushIntT<CAdventureOptions>();
-}
-
-void CAdventureMapInterface::fsystemOptions()
-{
-	GH.pushIntT<SettingsMainWindow>();
-}
-
-void CAdventureMapInterface::fnextHero()
-{
-	const auto * currHero = LOCPLINT->localState->getCurrentHero();
-	const auto * nextHero = LOCPLINT->localState->getNextWanderingHero(currHero);
-
-	if (nextHero)
-	{
-		LOCPLINT->localState->setSelection(nextHero);
-		centerOnObject(nextHero);
-	}
-}
-
-void CAdventureMapInterface::fendTurn()
-{
-	if(!LOCPLINT->makingTurn)
-		return;
-
-	if(settings["adventure"]["heroReminder"].Bool())
-	{
-		for(auto hero : LOCPLINT->localState->getWanderingHeroes())
-		{
-			if(!LOCPLINT->localState->isHeroSleeping(hero) && hero->movement > 0)
-			{
-				// Only show hero reminder if conditions met:
-				// - There still movement points
-				// - Hero don't have a path or there not points for first step on path
-				LOCPLINT->localState->verifyPath(hero);
-
-				if(!LOCPLINT->localState->hasPath(hero))
-				{
-					LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], std::bind(&CAdventureMapInterface::endingTurn, this), nullptr );
-					return;
-				}
-
-				auto path = LOCPLINT->localState->getPath(hero);
-				if (path.nodes.size() < 2 || path.nodes[path.nodes.size() - 2].turns)
-				{
-					LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], std::bind(&CAdventureMapInterface::endingTurn, this), nullptr );
-					return;
-				}
-			}
-		}
-	}
-	endingTurn();
-}
-
 void CAdventureMapInterface::updateButtons()
 {
 	const auto * hero = LOCPLINT->localState->getCurrentHero();
@@ -376,136 +229,8 @@ void CAdventureMapInterface::centerOnObject(const CGObjectInstance * obj)
 
 void CAdventureMapInterface::keyPressed(EShortcut key)
 {
-	if (widget->getState() != EGameState::MAKING_TURN)
-		return;
-
 	//fake mouse use to trigger onTileHovered()
 	GH.fakeMouseMove();
-
-	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero(); //selected hero
-	const CGTownInstance *t = LOCPLINT->localState->getCurrentTown(); //selected town
-
-	switch(key)
-	{
-	case EShortcut::ADVENTURE_THIEVES_GUILD:
-		if(GH.topInt()->type & BLOCK_ADV_HOTKEYS)
-			return;
-
-		{
-			//find first town with tavern
-			auto itr = range::find_if(LOCPLINT->localState->getOwnedTowns(), [](const CGTownInstance * town)
-			{
-				return town->hasBuilt(BuildingID::TAVERN);
-			});
-
-			if(itr != LOCPLINT->localState->getOwnedTowns().end())
-				LOCPLINT->showThievesGuildWindow(*itr);
-			else
-				LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithTavern"));
-		}
-		return;
-	case EShortcut::ADVENTURE_VIEW_SCENARIO:
-		if(isActive())
-			CAdventureOptions::showScenarioInfo();
-		return;
-	case EShortcut::GAME_SAVE_GAME:
-		if(isActive())
-			GH.pushIntT<CSavingScreen>();
-		return;
-	case EShortcut::GAME_LOAD_GAME:
-		if(isActive())
-			LOCPLINT->proposeLoadingGame();
-		return;
-	case EShortcut::ADVENTURE_DIG_GRAIL:
-		{
-			if(h && isActive() && LOCPLINT->makingTurn)
-				LOCPLINT->tryDiggging(h);
-			return;
-		}
-	case EShortcut::ADVENTURE_VIEW_PUZZLE:
-		if(isActive())
-			LOCPLINT->showPuzzleMap();
-		return;
-	case EShortcut::ADVENTURE_VIEW_WORLD:
-		if(isActive())
-			LOCPLINT->viewWorldMap();
-		return;
-	case EShortcut::GAME_RESTART_GAME:
-		if(isActive() && GH.isKeyboardCtrlDown())
-		{
-			LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
-				[](){ GH.pushUserEvent(EUserEvent::RESTART_GAME); }, nullptr);
-		}
-		return;
-	case EShortcut::ADVENTURE_VISIT_OBJECT: //space - try to revisit current object with selected hero
-		{
-			if(!isActive())
-				return;
-			if(h)
-			{
-				LOCPLINT->cb->moveHero(h,h->pos);
-			}
-		}
-		return;
-	case EShortcut::ADVENTURE_VIEW_SELECTED:
-		{
-			if(!isActive() || !LOCPLINT->localState->getCurrentArmy())
-				return;
-			if(h)
-				LOCPLINT->openHeroWindow(h);
-			else if(t)
-				LOCPLINT->openTownWindow(t);
-			return;
-		}
-	case EShortcut::GLOBAL_CANCEL:
-		{
-			//FIXME: this case is never executed since AdvMapInt is disabled while in spellcasting mode
-			if(!isActive() || GH.topInt().get() != this || !spellBeingCasted)
-				return;
-
-			abortCastingMode();
-			return;
-		}
-	case EShortcut::GAME_OPEN_MARKETPLACE:
-		{
-			//act on key down if marketplace windows is not already opened
-			if(GH.topInt()->type & BLOCK_ADV_HOTKEYS)
-				return;
-
-			if(GH.isKeyboardCtrlDown()) //CTRL + T => open marketplace
-			{
-				//check if we have any marketplace
-				const CGTownInstance *townWithMarket = nullptr;
-				for(const CGTownInstance *t : LOCPLINT->cb->getTownsInfo())
-				{
-					if(t->hasBuilt(BuildingID::MARKETPLACE))
-					{
-						townWithMarket = t;
-						break;
-					}
-				}
-
-				if(townWithMarket) //if any town has marketplace, open window
-					GH.pushIntT<CMarketplaceWindow>(townWithMarket);
-				else //if not - complain
-					LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithMarket"));
-			}
-	case EShortcut::ADVENTURE_NEXT_TOWN:
-			if(isActive() && !GH.isKeyboardCtrlDown()) //no ctrl, advmapint is on the top => switch to town
-			{
-				widget->getTownList()->selectNext();
-			}
-			return;
-		}
-	case EShortcut::ADVENTURE_MOVE_HERO_SW: return hotkeyMoveHeroDirectional({-1, +1});
-	case EShortcut::ADVENTURE_MOVE_HERO_SS: return hotkeyMoveHeroDirectional({ 0, +1});
-	case EShortcut::ADVENTURE_MOVE_HERO_SE: return hotkeyMoveHeroDirectional({+1, +1});
-	case EShortcut::ADVENTURE_MOVE_HERO_WW: return hotkeyMoveHeroDirectional({-1,  0});
-	case EShortcut::ADVENTURE_MOVE_HERO_EE: return hotkeyMoveHeroDirectional({+1,  0});
-	case EShortcut::ADVENTURE_MOVE_HERO_NW: return hotkeyMoveHeroDirectional({-1, -1});
-	case EShortcut::ADVENTURE_MOVE_HERO_NN: return hotkeyMoveHeroDirectional({ 0, -1});
-	case EShortcut::ADVENTURE_MOVE_HERO_NE: return hotkeyMoveHeroDirectional({+1, -1});
-	}
 }
 
 void CAdventureMapInterface::hotkeyMoveHeroDirectional(Point direction)
@@ -690,11 +415,11 @@ void CAdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID)
 		if(CInfoWindow *iw = dynamic_cast<CInfoWindow *>(GH.topInt().get()))
 			iw->close();
 
-		endingTurn();
+		hotkeyEndingTurn();
 	}
 }
 
-void CAdventureMapInterface::endingTurn()
+void CAdventureMapInterface::hotkeyEndingTurn()
 {
 	if(settings["session"]["spectate"].Bool())
 		return;
@@ -712,11 +437,6 @@ const CGObjectInstance* CAdventureMapInterface::getActiveObject(const int3 &mapP
 		return nullptr;
 
 	return *boost::range::max_element(bobjs, &CMapHandler::compareObjectBlitOrder);
-/*
-	if (bobjs.back()->ID == Obj::HERO)
-		return bobjs.back();
-	else
-		return bobjs.front();*/
 }
 
 void CAdventureMapInterface::onTileLeftClicked(const int3 &mapPos)
@@ -984,7 +704,7 @@ void CAdventureMapInterface::onTileRightClicked(const int3 &mapPos)
 
 	if(spellBeingCasted)
 	{
-		abortCastingMode();
+		hotkeyAbortCastingMode();
 		return;
 	}
 
@@ -1030,7 +750,7 @@ void CAdventureMapInterface::exitCastingMode()
 	config->Bool() = false;
 }
 
-void CAdventureMapInterface::abortCastingMode()
+void CAdventureMapInterface::hotkeyAbortCastingMode()
 {
 	exitCastingMode();
 	LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[731]); //Spell cancelled
@@ -1060,7 +780,7 @@ const IShipyard * CAdventureMapInterface::ourInaccessibleShipyard(const CGObject
 	return ret;
 }
 
-void CAdventureMapInterface::exitWorldView()
+void CAdventureMapInterface::hotkeyExitWorldView()
 {
 	widget->setState(EGameState::MAKING_TURN);
 	widget->getMapView()->onViewMapActivated();
@@ -1082,3 +802,13 @@ void CAdventureMapInterface::openWorldView(const std::vector<ObjectPosInfo>& obj
 	openWorldView(11);
 	widget->getMapView()->onViewSpellActivated(11, objectPositions, showTerrain);
 }
+
+void CAdventureMapInterface::hotkeyNextTown()
+{
+	widget->getTownList()->selectNext();
+}
+
+void CAdventureMapInterface::hotkeySwitchMapLevel()
+{
+	widget->getMapView()->onMapLevelSwitched();
+}

+ 9 - 26
client/adventureMap/CAdventureMapInterface.h

@@ -30,8 +30,7 @@ class IImage;
 class CAnimImage;
 class CGStatusBar;
 class CAdventureMapWidget;
-class CAdvMapPanel;
-class CAdvMapWorldViewPanel;
+class AdventureMapShortcuts;
 class CAnimation;
 class MapView;
 class CResDataBar;
@@ -59,26 +58,9 @@ private:
 
 	std::shared_ptr<MapAudioPlayer> mapAudio;
 	std::shared_ptr<CAdventureMapWidget> widget;
+	std::shared_ptr<AdventureMapShortcuts> shortcuts;
 
 private:
-	//functions bound to buttons
-	void fshowOverview();
-	void fworldViewBack();
-	void fworldViewScale1x();
-	void fworldViewScale2x();
-	void fworldViewScale4x();
-	void fswitchLevel();
-	void fshowQuestlog();
-	void fsleepWake();
-	void fmoveHero();
-	void fshowSpellbok();
-	void fadventureOPtions();
-	void fsystemOptions();
-	void fnextHero();
-	void fendTurn();
-
-	void hotkeyMoveHeroDirectional(Point direction);
-
 	bool isActive();
 	void adjustActiveness(bool aiTurnStart); //should be called every time at AI/human turn transition; blocks GUI during AI turn
 
@@ -93,15 +75,9 @@ private:
 
 	const CGObjectInstance *getActiveObject(const int3 &tile);
 
-	std::optional<Point> keyToMoveDirection(EShortcut key);
-
-	void endingTurn();
-
 	/// exits currently opened world view mode and returns to normal map
-	void exitWorldView();
 	void exitCastingMode();
 	void performSpellcasting(const int3 & castTarget);
-	void abortCastingMode();
 
 protected:
 	// CIntObject interface implementation
@@ -117,6 +93,13 @@ protected:
 public:
 	CAdventureMapInterface();
 
+	void hotkeyMoveHeroDirectional(Point direction);
+	void hotkeyAbortCastingMode();
+	void hotkeyExitWorldView();
+	void hotkeyEndingTurn();
+	void hotkeyNextTown();
+	void hotkeySwitchMapLevel();
+
 	/// Called by PlayerInterface when specified player is ready to start his turn
 	void onHotseatWaitStarted(PlayerColor playerID);
 

+ 14 - 2
client/adventureMap/CAdventureMapWidget.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 #include "CAdventureMapWidget.h"
 
+#include "AdventureMapShortcuts.h"
 #include "CInfoBar.h"
 #include "CList.h"
 #include "CMinimap.h"
@@ -29,8 +30,9 @@
 #include "../../lib/StringConstants.h"
 #include "../../lib/filesystem/ResourceID.h"
 
-CAdventureMapWidget::CAdventureMapWidget()
+CAdventureMapWidget::CAdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> shortcuts )
 	: state(EGameState::NOT_INITIALIZED)
+	, shortcuts(shortcuts)
 {
 	pos.x = pos.y = 0;
 	pos.w = GH.screenDimensions().x;
@@ -48,6 +50,9 @@ CAdventureMapWidget::CAdventureMapWidget()
 	REGISTER_BUILDER("adventureResourceDateBar", &CAdventureMapWidget::buildResourceDateBar );
 	REGISTER_BUILDER("adventureStatusBar",       &CAdventureMapWidget::buildStatusBar       );
 
+	for (const auto & entry : shortcuts->getFunctors())
+		addShortcut(entry.first, entry.second);
+
 	const JsonNode config(ResourceID("config/widgets/adventureMap.json"));
 
 	for(const auto & entry : config["options"]["imagesPlayerColored"].Vector())
@@ -57,6 +62,8 @@ CAdventureMapWidget::CAdventureMapWidget()
 	}
 
 	build(config);
+
+	addUsedEvents(KEYBOARD);
 }
 
 Rect CAdventureMapWidget::readSourceArea(const JsonNode & source, const JsonNode & sourceCommon)
@@ -151,7 +158,12 @@ std::shared_ptr<CIntObject> CAdventureMapWidget::buildMapButton(const JsonNode &
 	auto position = readTargetArea(input["area"]);
 	auto image = input["image"].String();
 	auto help = readHintText(input["help"]);
-	return std::make_shared<CButton>(position.topLeft(), image, help);
+
+	auto button = std::make_shared<CButton>(position.topLeft(), image, help);
+
+	loadButtonHotkey(button, input["hotkey"]);
+
+	return button;
 }
 
 std::shared_ptr<CIntObject> CAdventureMapWidget::buildMapContainer(const JsonNode & input)

+ 4 - 1
client/adventureMap/CAdventureMapWidget.h

@@ -17,6 +17,7 @@ class CMinimap;
 class MapView;
 class CInfoBar;
 class IImage;
+class AdventureMapShortcuts;
 
 enum class EGameState
 {
@@ -49,6 +50,8 @@ class CAdventureMapWidget : public InterfaceObjectConfigurable
 	std::shared_ptr<MapView> mapView;
 	std::shared_ptr<CInfoBar> infoBar;
 
+	std::shared_ptr<AdventureMapShortcuts> shortcuts;
+
 	Rect readTargetArea(const JsonNode & source);
 	Rect readSourceArea(const JsonNode & source, const JsonNode & sourceCommon);
 	Rect readArea(const JsonNode & source, const Rect & boundingBox);
@@ -70,7 +73,7 @@ class CAdventureMapWidget : public InterfaceObjectConfigurable
 
 	void setPlayerChildren(CIntObject * widget, const PlayerColor & player);
 public:
-	CAdventureMapWidget();
+	explicit CAdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> shortcuts );
 
 	std::shared_ptr<CHeroList> getHeroList();
 	std::shared_ptr<CTownList> getTownList();

+ 36 - 30
client/gui/InterfaceObjectConfigurable.cpp

@@ -289,15 +289,7 @@ std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(co
 		assert(imgOrder.size() >= 4);
 		button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
 	}
-	if(!config["callback"].isNull())
-	{
-		std::string callbackName = config["callback"].String();
-
-		if (callbacks.count(callbackName))
-			button->addCallback(callbacks.at(callbackName));
-		else
-			logGlobal->error("Invalid callback '%s' in widget", callbackName );
-	}
+	loadButtonCallback(button, config["callback"]);
 	return button;
 }
 
@@ -321,30 +313,44 @@ std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode
 		assert(imgOrder.size() >= 4);
 		button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
 	}
-	if(!config["callback"].isNull())
-	{
-		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())
+	loadButtonCallback(button, config["callback"]);
+	loadButtonHotkey(button, config["hotkey"]);
+	return button;
+}
+
+void InterfaceObjectConfigurable::loadButtonCallback(std::shared_ptr<CButton> button, const JsonNode & config) const
+{
+	if(config.isNull())
+		return;
+
+	std::string callbackName = config.String();
+
+	if (callbacks.count(callbackName) > 0)
+		button->addCallback(std::bind(callbacks.at(callbackName), 0));
+	else
+		logGlobal->error("Invalid callback '%s' in widget", callbackName );
+}
+
+void InterfaceObjectConfigurable::loadButtonHotkey(std::shared_ptr<CButton> button, const JsonNode & config) const
+{
+	if(config.isNull())
+		return;
+
+	if(config.getType() != JsonNode::JsonType::DATA_STRING)
 	{
-		if(config["hotkey"].getType() == JsonNode::JsonType::DATA_STRING)
-		{
-			button->assignedKey = readHotkey(config["hotkey"]);
-
-			auto target = shortcuts.find(button->assignedKey);
-			if (target != shortcuts.end())
-			{
-				button->addCallback(target->second.callback);
-				target->second.assignedToButton = true;
-			}
-		}
+		logGlobal->error("Invalid shortcut format - string expected!");
+		return;
 	}
-	return button;
+
+	button->assignedKey = readHotkey(config);
+
+	auto target = shortcuts.find(button->assignedKey);
+	if (target == shortcuts.end())
+		return;
+
+	button->addCallback(target->second.callback);
+	target->second.assignedToButton = true;
 }
 
 std::shared_ptr<CLabelGroup> InterfaceObjectConfigurable::buildLabelGroup(const JsonNode & config) const

+ 3 - 0
client/gui/InterfaceObjectConfigurable.h

@@ -73,6 +73,9 @@ protected:
 	std::pair<std::string, std::string> readHintText(const JsonNode &) const;
 	EShortcut readHotkey(const JsonNode &) const;
 	
+	void loadButtonCallback(std::shared_ptr<CButton> button, const JsonNode & config) const;
+	void loadButtonHotkey(std::shared_ptr<CButton> button, const JsonNode & config) const;
+
 	//basic widgets
 	std::shared_ptr<CPicture> buildPicture(const JsonNode &) const;
 	std::shared_ptr<CLabel> buildLabel(const JsonNode &) const;

+ 3 - 3
client/gui/Shortcut.h

@@ -81,7 +81,9 @@ enum class EShortcut
 	// Adventure map screen
 	ADVENTURE_GAME_OPTIONS, // 'o', Open CAdventureOptions window
 	ADVENTURE_TOGGLE_GRID,  // F6, Toggles map grid
-	ADVENTURE_TOGGLE_SLEEP, // z,w, Toggles hero sleep status
+	ADVENTURE_TOGGLE_SLEEP, // Toggles hero sleep status
+	ADVENTURE_SET_HERO_ASLEEP, // Moves hero to sleep state
+	ADVENTURE_SET_HERO_AWAKE, // Move hero to awake state
 	ADVENTURE_MOVE_HERO,    // Moves hero alongside set path
 	ADVENTURE_VISIT_OBJECT, // Revisits object hero is standing on
 	ADVENTURE_VIEW_SELECTED,// Open window with currently selected hero/town
@@ -98,7 +100,6 @@ enum class EShortcut
 	ADVENTURE_KINGDOM_OVERVIEW,
 	ADVENTURE_QUEST_LOG,
 	ADVENTURE_CAST_SPELL,
-	ADVENTURE_END_TURN,
 	ADVENTURE_THIEVES_GUILD,
 
 	// Move hero one tile in specified direction. Bound to cursors & numpad buttons
@@ -153,4 +154,3 @@ enum class EShortcut
 
 	AFTER_LAST
 };
-

+ 4 - 86
client/gui/ShortcutHandler.cpp

@@ -79,8 +79,8 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
 		{SDLK_TAB,       EShortcut::GAME_ACTIVATE_CONSOLE     },
 		{SDLK_o,         EShortcut::ADVENTURE_GAME_OPTIONS    },
 		{SDLK_F6,        EShortcut::ADVENTURE_TOGGLE_GRID     },
-		{SDLK_z,         EShortcut::ADVENTURE_TOGGLE_SLEEP    },
-		{SDLK_w,         EShortcut::ADVENTURE_TOGGLE_SLEEP    },
+		{SDLK_z,         EShortcut::ADVENTURE_SET_HERO_ASLEEP },
+		{SDLK_w,         EShortcut::ADVENTURE_SET_HERO_AWAKE  },
 		{SDLK_m,         EShortcut::ADVENTURE_MOVE_HERO       },
 		{SDLK_SPACE,     EShortcut::ADVENTURE_VISIT_OBJECT    },
 		{SDLK_KP_1,      EShortcut::ADVENTURE_MOVE_HERO_SW    },
@@ -110,7 +110,6 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
 		{SDLK_k,         EShortcut::ADVENTURE_KINGDOM_OVERVIEW},
 		{SDLK_q,         EShortcut::ADVENTURE_QUEST_LOG       },
 		{SDLK_c,         EShortcut::ADVENTURE_CAST_SPELL      },
-		{SDLK_e,         EShortcut::ADVENTURE_END_TURN        },
 		{SDLK_g,         EShortcut::ADVENTURE_THIEVES_GUILD   },
 		{SDLK_q,         EShortcut::BATTLE_TOGGLE_QUEUE       },
 		{SDLK_c,         EShortcut::BATTLE_USE_CREATURE_SPELL },
@@ -218,6 +217,8 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
 		{"adventureGameOptions",     EShortcut::ADVENTURE_GAME_OPTIONS    },
 		{"adventureToggleGrid",      EShortcut::ADVENTURE_TOGGLE_GRID     },
 		{"adventureToggleSleep",     EShortcut::ADVENTURE_TOGGLE_SLEEP    },
+		{"adventureSetHeroAsleep",   EShortcut::ADVENTURE_SET_HERO_ASLEEP },
+		{"adventureSetHeroAwake",    EShortcut::ADVENTURE_SET_HERO_AWAKE  },
 		{"adventureMoveHero",        EShortcut::ADVENTURE_MOVE_HERO       },
 		{"adventureVisitObject",     EShortcut::ADVENTURE_VISIT_OBJECT    },
 		{"adventureMoveHeroSW",      EShortcut::ADVENTURE_MOVE_HERO_SW    },
@@ -242,7 +243,6 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
 		{"adventureKingdomOverview", EShortcut::ADVENTURE_KINGDOM_OVERVIEW},
 		{"adventureQuestLog",        EShortcut::ADVENTURE_QUEST_LOG       },
 		{"adventureCastSpell",       EShortcut::ADVENTURE_CAST_SPELL      },
-		{"adventureEndTurn",         EShortcut::ADVENTURE_END_TURN        },
 		{"adventureThievesGuild",    EShortcut::ADVENTURE_THIEVES_GUILD   },
 		{"battleToggleQueue",        EShortcut::BATTLE_TOGGLE_QUEUE       },
 		{"battleUseCreatureSpell",   EShortcut::BATTLE_USE_CREATURE_SPELL },
@@ -278,85 +278,3 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
 		return shortcutNames.at(identifier);
 	return EShortcut::NONE;
 }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

+ 19 - 7
config/widgets/adventureMap.json

@@ -120,6 +120,7 @@
 					"name" : "buttonKingdomOverview",
 					"image" : "IAM002.DEF",
 					"help" : "core.help.293",
+					"hotkey": "adventureKingdomOverview",
 					"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
 				},
 				{
@@ -127,6 +128,7 @@
 					"name": "buttonUnderground",
 					"image" : "IAM010.DEF",
 					"help" : "core.help.294",
+					"hotkey": "adventureToggleMapLevel",
 					"area": { "top" : 0, "left": 32, "width" : 32, "height" : 32 }
 				},
 				{
@@ -134,6 +136,7 @@
 					"name": "buttonSurface",
 					"image" : "IAM003.DEF",
 					"help" : "core.help.294",
+					"hotkey": "adventureToggleMapLevel",
 					"area": { "top" : 0, "left": 32, "width" : 32, "height" : 32 }
 				},
 				{
@@ -141,6 +144,7 @@
 					"name": "buttonQuestLog",
 					"image" : "IAM004.DEF",
 					"help" : "core.help.295",
+					"hotkey": "adventureQuestLog",
 					"area": { "top" : 32, "left": 0, "width" : 32, "height" : 32 }
 				},
 				{
@@ -148,6 +152,7 @@
 					"name": "buttonSleep",
 					"image" : "IAM005.DEF",
 					"help" : "core.help.296",
+					"hotkey": "adventureSetHeroAsleep",
 					"area": { "top" : 32, "left": 32, "width" : 32, "height" : 32 }
 				},
 				{
@@ -155,6 +160,7 @@
 					"name": "buttonWake",
 					"image" : "IAM011.DEF",
 					"help" : "core.help.296",
+					"hotkey": "adventureSetHeroAwake",
 					"area": { "top" : 32, "left": 32, "width" : 32, "height" : 32 }
 				},
 				{
@@ -162,6 +168,7 @@
 					"name": "buttonMove",
 					"image" : "IAM006.DEF",
 					"help" : "core.help.297",
+					"hotkey": "adventureMoveHero",
 					"area": { "top" : 64, "left": 0, "width" : 32, "height" : 32 }
 				},
 				{
@@ -169,6 +176,7 @@
 					"name": "buttonCast",
 					"image" : "IAM007.DEF",
 					"help" : "core.help.298",
+					"hotkey": "adventureCastSpell",
 					"area": { "top" : 64, "left": 32, "width" : 32, "height" : 32 }
 				},
 				{
@@ -176,6 +184,7 @@
 					"name": "buttonAdventureOptions",
 					"image" : "IAM008.DEF",
 					"help" : "core.help.299",
+					"hotkey": "adventureGameOptions",
 					"area": { "top" : 96, "left": 0, "width" : 32, "height" : 32 }
 				},
 				{
@@ -183,6 +192,7 @@
 					"name": "buttonSystemOptions",
 					"image" : "IAM009.DEF",
 					"help" : "core.help.300",
+					"hotkey": "globalOptions",
 					"area": { "top" : 96, "left": 32, "width" : 32, "height" : 32 }
 				},
 				{
@@ -190,12 +200,14 @@
 					"name": "buttonNextHero",
 					"image" : "IAM000.DEF",
 					"help" : "core.help.301",
+					"hotkey": "adventureNextHero",
 					"area": { "top" : 128, "left": 0, "width" : 64, "height" : 32 }
 				},
 				{
 					"type": "adventureMapButton",
 					"name": "buttonEndTurn",
 					"image" : "IAM001.DEF",
+					"hotkey": "adventureEndTurn",
 					"help" : "core.help.302",
 					"area": { "top" : 160, "left": 0, "width" : 64, "height" : 32 }
 				}
@@ -287,7 +299,7 @@
 					"type": "adventureMapImage",
 					"name": "emptyAreaFillSmallImage",
 					"image" : "DiBoxBck.pcx",
-					"area": { "top": 0, "bottom" : 0, "left" : 0, "right" : 0 }
+					"area": { "top": 0, "bottom" : 0, "left" : 0, "right" : 0 },
 					"sourceArea": { "left" : 0, "top" : 0, "width" : 256, "height" : 256 }
 				},
 			]
@@ -303,21 +315,21 @@
 					"type": "adventureMapImage",
 					"name": "backgroundHeroListBorderRight",
 					"image" : "AdvMap.pcx",
-					"area": { "top": 0, "bottom" : 0, "right" : 121, "width" : 6 }
+					"area": { "top": 0, "bottom" : 0, "right" : 121, "width" : 6 },
 					"sourceArea": { "top": 196, "bottom" : 211, "right" : 121, "width" : 6 }
 				},
 				{
 					"type": "adventureMapImage",
 					"name": "backgroundTownListBorderLeft",
 					"image" : "AdvMap.pcx",
-					"area": { "top": 0, "bottom" : 0, "right" : 53, "width" : 4 }
+					"area": { "top": 0, "bottom" : 0, "right" : 53, "width" : 4 },
 					"sourceArea": { "top": 196, "bottom" : 211, "right" : 53, "width" : 4 }
 				},
 				{
 					"type": "adventureMapImage",
 					"name" : "backgroundBelowHeroTownList",
 					"image" : "AdvMap.pcx",
-					"area" : { "right": 0, "left" : 0, "bottom" : 0, "height" : 3 }
+					"area" : { "right": 0, "left" : 0, "bottom" : 0, "height" : 3 },
 					"sourceArea": { "bottom" : 208, "height" : 3, "right" : 0, "width" : 193 }
 				},
 				// Hero List
@@ -351,13 +363,13 @@
 					"item" : { "top" :  16, "left": 0, "width" : 48, "height" : 32 },
 					"itemsOffset" : { "x" : 0, "y" : 32 },
 					"itemsCount" : 7
-				}
+				},
 				// Fill empty area below buttons
 				{
 					"type": "adventureMapImage",
 					"name" : "backgroundBelowButtons",
 					"image" : "DiBoxBck.pcx",
-					"area": { "top": 192, "bottom" : 3, "right" : 57, "width" : 64 }
+					"area": { "top": 192, "bottom" : 3, "right" : 57, "width" : 64 },
 					"sourceArea": { "left" : 0, "top" : 0, "width" : 256, "height" : 256 }
 				},
 			]
@@ -372,7 +384,7 @@
 					"type": "adventureMapImage",
 					"name": "emptyAreaFillLargeImage",
 					"image" : "DiBoxBck.pcx",
-					"area": { "top": 0, "bottom" : 0, "left" : 0, "right" : 0 }
+					"area": { "top": 0, "bottom" : 0, "left" : 0, "right" : 0 },
 					"sourceArea": { "left" : 0, "top" : 0, "width" : 256, "height" : 256 }
 				},
 			]