Răsfoiți Sursa

Merge remote-tracking branch 'upstream/develop' into develop

Xilmi 1 an în urmă
părinte
comite
7c8f4c423c
48 a modificat fișierele cu 356 adăugiri și 159 ștergeri
  1. 4 2
      Mods/vcmi/config/vcmi/english.json
  2. 4 2
      Mods/vcmi/config/vcmi/german.json
  3. 7 3
      client/CServerHandler.cpp
  4. 14 0
      client/Client.cpp
  5. 3 1
      client/Client.h
  6. 28 21
      client/NetPacksClient.cpp
  7. 3 2
      client/NetPacksLobbyClient.cpp
  8. 3 1
      client/eventsSDL/InputSourceMouse.cpp
  9. 8 0
      client/gui/EventDispatcher.cpp
  10. 2 0
      client/gui/EventDispatcher.h
  11. 3 1
      client/gui/EventsReceiver.h
  12. 0 31
      client/lobby/CBonusSelection.cpp
  13. 0 14
      client/lobby/CBonusSelection.h
  14. 2 0
      client/mainmenu/CHighScoreScreen.cpp
  15. 30 1
      client/mainmenu/CMainMenu.cpp
  16. 4 0
      client/mainmenu/CMainMenu.h
  17. 6 1
      client/mapView/MapViewActions.cpp
  18. 1 0
      client/mapView/MapViewActions.h
  19. 1 7
      client/media/CEmptyVideoPlayer.h
  20. 8 24
      client/media/CVideoHandler.cpp
  21. 3 4
      client/media/CVideoHandler.h
  22. 1 4
      client/media/IVideoPlayer.h
  23. 13 2
      client/widgets/VideoWidget.cpp
  24. 3 0
      client/widgets/VideoWidget.h
  25. 47 0
      client/windows/GUIClasses.cpp
  26. 16 0
      client/windows/GUIClasses.h
  27. 35 1
      client/windows/InfoWindows.cpp
  28. 8 0
      client/windows/InfoWindows.h
  29. 12 0
      client/windows/settings/AdventureOptionsTab.cpp
  30. 3 1
      client/windows/settings/GeneralOptionsTab.cpp
  31. 7 0
      client/windows/settings/SettingsMainWindow.cpp
  32. 1 0
      client/windows/settings/SettingsMainWindow.h
  33. 7 20
      clientapp/EntryPoint.cpp
  34. 1 1
      config/schemas/settings.json
  35. 4 0
      config/schemas/spell.json
  36. 28 2
      config/widgets/settings/adventureOptionsTab.json
  37. 3 0
      docs/modders/Entities_Format/Spell_Format.md
  38. 1 0
      include/vcmi/spells/Spell.h
  39. 6 1
      lib/battle/CUnitState.cpp
  40. 1 0
      lib/battle/CUnitState.h
  41. 0 7
      lib/bonuses/CBonusSystemNode.cpp
  42. 1 2
      lib/bonuses/CBonusSystemNode.h
  43. 1 3
      lib/bonuses/IBonusBearer.h
  44. 1 0
      lib/networkPacks/NetPacksLib.cpp
  45. 6 0
      lib/spells/CSpellHandler.cpp
  46. 2 0
      lib/spells/CSpellHandler.h
  47. 13 0
      server/battles/BattleFlowProcessor.cpp
  48. 1 0
      test/mock/mock_spells_Spell.h

+ 4 - 2
Mods/vcmi/config/vcmi/english.json

@@ -234,8 +234,10 @@
 	"vcmi.adventureOptions.borderScroll.help" : "{Border Scrolling}\n\nScroll adventure map when cursor is adjacent to window edge. Can be disabled by holding down CTRL key.",
 	"vcmi.adventureOptions.borderScroll.help" : "{Border Scrolling}\n\nScroll adventure map when cursor is adjacent to window edge. Can be disabled by holding down CTRL key.",
 	"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Info Panel Creature Management",
 	"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Info Panel Creature Management",
 	"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Info Panel Creature Management}\n\nAllows rearranging creatures in info panel instead of cycling between default components.",
 	"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Info Panel Creature Management}\n\nAllows rearranging creatures in info panel instead of cycling between default components.",
-	"vcmi.adventureOptions.leftButtonDrag.hover" : "Left Click Drag Map",
-	"vcmi.adventureOptions.leftButtonDrag.help" : "{Left Click Drag Map}\n\nWhen enabled, moving mouse with left button pressed will drag adventure map view.",
+	"vcmi.adventureOptions.leftButtonDrag.hover" : "Left Click Drag",
+	"vcmi.adventureOptions.leftButtonDrag.help" : "{Left Click Drag}\n\nWhen enabled, moving mouse with left button pressed will drag adventure map view.",
+	"vcmi.adventureOptions.rightButtonDrag.hover" : "Right Click Drag",
+	"vcmi.adventureOptions.rightButtonDrag.help" : "{Right Click Drag}\n\nWhen enabled, moving mouse with right button pressed will drag adventure map view.",
 	"vcmi.adventureOptions.smoothDragging.hover" : "Smooth Map Dragging",
 	"vcmi.adventureOptions.smoothDragging.hover" : "Smooth Map Dragging",
 	"vcmi.adventureOptions.smoothDragging.help" : "{Smooth Map Dragging}\n\nWhen enabled, map dragging has a modern run out effect.",
 	"vcmi.adventureOptions.smoothDragging.help" : "{Smooth Map Dragging}\n\nWhen enabled, map dragging has a modern run out effect.",
 	"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Skip fading effects",
 	"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Skip fading effects",

+ 4 - 2
Mods/vcmi/config/vcmi/german.json

@@ -234,8 +234,10 @@
 	"vcmi.adventureOptions.borderScroll.help" : "{Scrollen am Rand}\n\nScrollt die Abenteuerkarte, wenn sich der Cursor neben dem Fensterrand befindet. Kann mit gedrückter STRG-Taste deaktiviert werden.",
 	"vcmi.adventureOptions.borderScroll.help" : "{Scrollen am Rand}\n\nScrollt die Abenteuerkarte, wenn sich der Cursor neben dem Fensterrand befindet. Kann mit gedrückter STRG-Taste deaktiviert werden.",
 	"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Info-Panel Kreaturenmanagement",
 	"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Info-Panel Kreaturenmanagement",
 	"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Info-Panel Kreaturenmanagement}\n\nErmöglicht die Neuanordnung von Kreaturen im Info-Panel, anstatt zwischen den Standardkomponenten zu wechseln",
 	"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Info-Panel Kreaturenmanagement}\n\nErmöglicht die Neuanordnung von Kreaturen im Info-Panel, anstatt zwischen den Standardkomponenten zu wechseln",
-	"vcmi.adventureOptions.leftButtonDrag.hover" : "Ziehen der Karte mit Links",
-	"vcmi.adventureOptions.leftButtonDrag.help" : "{Ziehen der Karte mit Links}\n\nWenn aktiviert, wird die Maus bei gedrückter linker Taste in die Kartenansicht gezogen",
+	"vcmi.adventureOptions.leftButtonDrag.hover" : "Ziehen mit Links",
+	"vcmi.adventureOptions.leftButtonDrag.help" : "{Ziehen mit Links}\n\nWenn aktiviert, kann mit gedrückter linker Taste die Kartenansicht gezogen werden",
+	"vcmi.adventureOptions.rightButtonDrag.hover" : "Ziehen mit Rechts",
+	"vcmi.adventureOptions.rightButtonDrag.help" : "{Ziehen mit Rechts}\n\nWenn aktiviert, kann mit gedrückter rechter Taste die Kartenansicht gezogen werden",
 	"vcmi.adventureOptions.smoothDragging.hover" : "Nahtloses Ziehen der Karte",
 	"vcmi.adventureOptions.smoothDragging.hover" : "Nahtloses Ziehen der Karte",
 	"vcmi.adventureOptions.smoothDragging.help" : "{Nahtloses Ziehen der Karte}\n\nWenn aktiviert hat das Ziehen der Karte einen sanften Auslaufeffekt.",
 	"vcmi.adventureOptions.smoothDragging.help" : "{Nahtloses Ziehen der Karte}\n\nWenn aktiviert hat das Ziehen der Karte einen sanften Auslaufeffekt.",
 	"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Fading-Effekte überspringen",
 	"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Fading-Effekte überspringen",

+ 7 - 3
client/CServerHandler.cpp

@@ -23,6 +23,7 @@
 #include "lobby/CLobbyScreen.h"
 #include "lobby/CLobbyScreen.h"
 #include "lobby/CBonusSelection.h"
 #include "lobby/CBonusSelection.h"
 #include "windows/InfoWindows.h"
 #include "windows/InfoWindows.h"
+#include "windows/GUIClasses.h"
 #include "media/CMusicHandler.h"
 #include "media/CMusicHandler.h"
 #include "media/IVideoPlayer.h"
 #include "media/IVideoPlayer.h"
 
 
@@ -661,10 +662,13 @@ void CServerHandler::endGameplay()
 	{
 	{
 		GH.curInt = CMM.get();
 		GH.curInt = CMM.get();
 		CMM->enable();
 		CMM->enable();
+		CMM->playMusic();
 	}
 	}
 	else
 	else
 	{
 	{
-		GH.curInt = CMainMenu::create().get();
+		auto mainMenu = CMainMenu::create();
+		GH.curInt = mainMenu.get();
+		mainMenu->playMusic();
 	}
 	}
 }
 }
 
 
@@ -708,10 +712,10 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared
 		else
 		else
 		{
 		{
 			CMM->openCampaignScreen(ourCampaign->campaignSet);
 			CMM->openCampaignScreen(ourCampaign->campaignSet);
-			if(!ourCampaign->getOutroVideo().empty() && CCS->videoh->open(ourCampaign->getOutroVideo(), false))
+			if(!ourCampaign->getOutroVideo().empty() && CCS->videoh->open(ourCampaign->getOutroVideo(), 1))
 			{
 			{
 				CCS->musich->stopMusic();
 				CCS->musich->stopMusic();
-				GH.windows().createAndPushWindow<CampaignRimVideo>(ourCampaign->getOutroVideo(), ourCampaign->getVideoRim().empty() ? ImagePath::builtin("INTRORIM") : ourCampaign->getVideoRim(), [campaignScoreCalculator, statistic](){
+				GH.windows().createAndPushWindow<VideoWindow>(ourCampaign->getOutroVideo(), ourCampaign->getVideoRim().empty() ? ImagePath::builtin("INTRORIM") : ourCampaign->getVideoRim(), false, 1, [campaignScoreCalculator, statistic](bool skipped){
 					GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *campaignScoreCalculator, statistic);
 					GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *campaignScoreCalculator, statistic);
 				});
 				});
 			}
 			}

+ 14 - 0
client/Client.cpp

@@ -13,6 +13,7 @@
 
 
 #include "CGameInfo.h"
 #include "CGameInfo.h"
 #include "CPlayerInterface.h"
 #include "CPlayerInterface.h"
+#include "PlayerLocalState.h"
 #include "CServerHandler.h"
 #include "CServerHandler.h"
 #include "ClientNetPackVisitors.h"
 #include "ClientNetPackVisitors.h"
 #include "adventureMap/AdventureMapInterface.h"
 #include "adventureMap/AdventureMapInterface.h"
@@ -495,6 +496,19 @@ void CClient::startPlayerBattleAction(const BattleID & battleID, PlayerColor col
 	}
 	}
 }
 }
 
 
+void CClient::updatePath(const ObjectInstanceID & id)
+{
+	invalidatePaths();
+	auto hero = getHero(id);
+	updatePath(hero);
+}
+
+void CClient::updatePath(const CGHeroInstance * hero)
+{
+	if(LOCPLINT && hero)
+		LOCPLINT->localState->verifyPath(hero);
+}
+
 void CClient::invalidatePaths()
 void CClient::invalidatePaths()
 {
 {
 	boost::unique_lock<boost::mutex> pathLock(pathCacheMutex);
 	boost::unique_lock<boost::mutex> pathLock(pathCacheMutex);

+ 3 - 1
client/Client.h

@@ -150,7 +150,9 @@ public:
 	void battleFinished(const BattleID & battleID);
 	void battleFinished(const BattleID & battleID);
 	void startPlayerBattleAction(const BattleID & battleID, PlayerColor color);
 	void startPlayerBattleAction(const BattleID & battleID, PlayerColor color);
 
 
-	void invalidatePaths();
+	void invalidatePaths(); // clears this->pathCache()
+	void updatePath(const ObjectInstanceID & heroID); // invalidatePaths and update displayed hero path 
+	void updatePath(const CGHeroInstance * hero);
 	std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h);
 	std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h);
 
 
 	friend class CCallback; //handling players actions
 	friend class CCallback; //handling players actions

+ 28 - 21
client/NetPacksClient.cpp

@@ -101,7 +101,7 @@ void callBattleInterfaceIfPresentForBothSides(CClient & cl, const BattleID & bat
 {
 {
 	assert(cl.gameState()->getBattle(battleID));
 	assert(cl.gameState()->getBattle(battleID));
 
 
-	if (!cl.gameState()->getBattle(battleID))
+	if(!cl.gameState()->getBattle(battleID))
 	{
 	{
 		logGlobal->error("Attempt to call battle interface without ongoing battle!");
 		logGlobal->error("Attempt to call battle interface without ongoing battle!");
 		return;
 		return;
@@ -161,14 +161,14 @@ void ApplyClientNetPackVisitor::visitSetMana(SetMana & pack)
 	if(settings["session"]["headless"].Bool())
 	if(settings["session"]["headless"].Bool())
 		return;
 		return;
 
 
-	for (auto window : GH.windows().findWindows<BattleWindow>())
+	for(auto window : GH.windows().findWindows<BattleWindow>())
 		window->heroManaPointsChanged(h);
 		window->heroManaPointsChanged(h);
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack)
 void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack)
 {
 {
 	const CGHeroInstance *h = cl.getHero(pack.hid);
 	const CGHeroInstance *h = cl.getHero(pack.hid);
-	cl.invalidatePaths();
+	cl.updatePath(h);
 	callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroMovePointsChanged, h);
 	callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroMovePointsChanged, h);
 }
 }
 
 
@@ -229,7 +229,7 @@ void ApplyClientNetPackVisitor::visitSetStackType(SetStackType & pack)
 void ApplyClientNetPackVisitor::visitEraseStack(EraseStack & pack)
 void ApplyClientNetPackVisitor::visitEraseStack(EraseStack & pack)
 {
 {
 	dispatchGarrisonChange(cl, pack.army, ObjectInstanceID());
 	dispatchGarrisonChange(cl, pack.army, ObjectInstanceID());
-	cl.invalidatePaths(); //it is possible to remove last non-native unit for current terrain and lose movement penalty
+	cl.updatePath(pack.army); //it is possible to remove last non-native unit for current terrain and lose movement penalty
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitSwapStacks(SwapStacks & pack)
 void ApplyClientNetPackVisitor::visitSwapStacks(SwapStacks & pack)
@@ -237,15 +237,14 @@ void ApplyClientNetPackVisitor::visitSwapStacks(SwapStacks & pack)
 	dispatchGarrisonChange(cl, pack.srcArmy, pack.dstArmy);
 	dispatchGarrisonChange(cl, pack.srcArmy, pack.dstArmy);
 
 
 	if(pack.srcArmy != pack.dstArmy)
 	if(pack.srcArmy != pack.dstArmy)
-		cl.invalidatePaths(); // adding/removing units may change terrain type penalty based on creature native terrains
+		cl.updatePath(pack.dstArmy); // adding/removing units may change terrain type penalty based on creature native terrains
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitInsertNewStack(InsertNewStack & pack)
 void ApplyClientNetPackVisitor::visitInsertNewStack(InsertNewStack & pack)
 {
 {
 	dispatchGarrisonChange(cl, pack.army, ObjectInstanceID());
 	dispatchGarrisonChange(cl, pack.army, ObjectInstanceID());
 
 
-	if(gs.getHero(pack.army))
-		cl.invalidatePaths(); // adding/removing units may change terrain type penalty based on creature native terrains
+	cl.updatePath(pack.army); // adding/removing units may change terrain type penalty based on creature native terrains
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitRebalanceStacks(RebalanceStacks & pack)
 void ApplyClientNetPackVisitor::visitRebalanceStacks(RebalanceStacks & pack)
@@ -253,7 +252,10 @@ void ApplyClientNetPackVisitor::visitRebalanceStacks(RebalanceStacks & pack)
 	dispatchGarrisonChange(cl, pack.srcArmy, pack.dstArmy);
 	dispatchGarrisonChange(cl, pack.srcArmy, pack.dstArmy);
 
 
 	if(pack.srcArmy != pack.dstArmy)
 	if(pack.srcArmy != pack.dstArmy)
-		cl.invalidatePaths(); // adding/removing units may change terrain type penalty based on creature native terrains
+	{
+		cl.updatePath(pack.srcArmy); // adding/removing units may change terrain type penalty based on creature native terrains
+		cl.updatePath(pack.dstArmy);
+	}
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & pack)
 void ApplyClientNetPackVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & pack)
@@ -266,7 +268,10 @@ void ApplyClientNetPackVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & p
 		dispatchGarrisonChange(cl, pack.moves[0].srcArmy, destArmy);
 		dispatchGarrisonChange(cl, pack.moves[0].srcArmy, destArmy);
 
 
 		if(pack.moves[0].srcArmy != destArmy)
 		if(pack.moves[0].srcArmy != destArmy)
-			cl.invalidatePaths(); // adding/removing units may change terrain type penalty based on creature native terrains
+		{
+			cl.updatePath(destArmy); // adding/removing units may change terrain type penalty based on creature native terrains
+			cl.updatePath(pack.moves[0].srcArmy);
+		}
 	}
 	}
 }
 }
 
 
@@ -292,6 +297,7 @@ void ApplyClientNetPackVisitor::visitPutArtifact(PutArtifact & pack)
 
 
 void ApplyClientNetPackVisitor::visitEraseArtifact(BulkEraseArtifacts & pack)
 void ApplyClientNetPackVisitor::visitEraseArtifact(BulkEraseArtifacts & pack)
 {
 {
+	cl.updatePath(pack.artHolder);
 	for(const auto & slotErase : pack.posPack)
 	for(const auto & slotErase : pack.posPack)
 		callInterfaceIfPresent(cl, cl.getOwner(pack.artHolder), &IGameEventsReceiver::artifactRemoved, ArtifactLocation(pack.artHolder, slotErase));
 		callInterfaceIfPresent(cl, cl.getOwner(pack.artHolder), &IGameEventsReceiver::artifactRemoved, ArtifactLocation(pack.artHolder, slotErase));
 }
 }
@@ -312,7 +318,8 @@ void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
 			if(pack.interfaceOwner != dstOwner)
 			if(pack.interfaceOwner != dstOwner)
 				callInterfaceIfPresent(cl, dstOwner, &IGameEventsReceiver::artifactMoved, srcLoc, dstLoc);
 				callInterfaceIfPresent(cl, dstOwner, &IGameEventsReceiver::artifactMoved, srcLoc, dstLoc);
 
 
-			cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
+			cl.updatePath(pack.srcArtHolder); // hero might have equipped/unequipped Angel Wings
+			cl.updatePath(pack.dstArtHolder);
 		}
 		}
 	};
 	};
 
 
@@ -342,14 +349,14 @@ void ApplyClientNetPackVisitor::visitAssembledArtifact(AssembledArtifact & pack)
 {
 {
 	callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactAssembled, pack.al);
 	callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactAssembled, pack.al);
 
 
-	cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
+	cl.updatePath(pack.al.artHolder); // hero might have equipped/unequipped Angel Wings
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitDisassembledArtifact(DisassembledArtifact & pack)
 void ApplyClientNetPackVisitor::visitDisassembledArtifact(DisassembledArtifact & pack)
 {
 {
 	callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactDisassembled, pack.al);
 	callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactDisassembled, pack.al);
 
 
-	cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
+	cl.updatePath(pack.al.artHolder); // hero might have equipped/unequipped Angel Wings
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitHeroVisit(HeroVisit & pack)
 void ApplyClientNetPackVisitor::visitHeroVisit(HeroVisit & pack)
@@ -363,7 +370,7 @@ void ApplyClientNetPackVisitor::visitNewTurn(NewTurn & pack)
 {
 {
 	cl.invalidatePaths();
 	cl.invalidatePaths();
 
 
-	if (pack.newWeekNotification)
+	if(pack.newWeekNotification)
 	{
 	{
 		const auto & newWeek = *pack.newWeekNotification;
 		const auto & newWeek = *pack.newWeekNotification;
 
 
@@ -380,7 +387,7 @@ void ApplyClientNetPackVisitor::visitGiveBonus(GiveBonus & pack)
 	case GiveBonus::ETarget::OBJECT:
 	case GiveBonus::ETarget::OBJECT:
 		{
 		{
 			const CGHeroInstance *h = gs.getHero(pack.id.as<ObjectInstanceID>());
 			const CGHeroInstance *h = gs.getHero(pack.id.as<ObjectInstanceID>());
-			if (h)
+			if(h)
 				callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroBonusChanged, h, pack.bonus, true);
 				callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroBonusChanged, h, pack.bonus, true);
 		}
 		}
 		break;
 		break;
@@ -419,7 +426,7 @@ void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
 
 
 	bool lastHumanEndsGame = CSH->howManyPlayerInterfaces() == 1 && vstd::contains(cl.playerint, pack.player) && cl.getPlayerState(pack.player)->human && !settings["session"]["spectate"].Bool();
 	bool lastHumanEndsGame = CSH->howManyPlayerInterfaces() == 1 && vstd::contains(cl.playerint, pack.player) && cl.getPlayerState(pack.player)->human && !settings["session"]["spectate"].Bool();
 
 
-	if (lastHumanEndsGame)
+	if(lastHumanEndsGame)
 	{
 	{
 		assert(adventureInt);
 		assert(adventureInt);
 		if(adventureInt)
 		if(adventureInt)
@@ -446,9 +453,9 @@ void ApplyClientNetPackVisitor::visitPlayerReinitInterface(PlayerReinitInterface
 	{
 	{
 		cl.initPlayerInterfaces();
 		cl.initPlayerInterfaces();
 
 
-		for (PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player)
+		for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player)
 		{
 		{
-			if (cl.gameState()->isPlayerMakingTurn(player))
+			if(cl.gameState()->isPlayerMakingTurn(player))
 			{
 			{
 				callAllInterfaces(cl, &IGameEventsReceiver::playerStartsTurn, player);
 				callAllInterfaces(cl, &IGameEventsReceiver::playerStartsTurn, player);
 				callOnlyThatInterface(cl, player, &CGameInterface::yourTurn, QueryID::NONE);
 				callOnlyThatInterface(cl, player, &CGameInterface::yourTurn, QueryID::NONE);
@@ -482,7 +489,7 @@ void ApplyClientNetPackVisitor::visitRemoveBonus(RemoveBonus & pack)
 	case GiveBonus::ETarget::OBJECT:
 	case GiveBonus::ETarget::OBJECT:
 		{
 		{
 			const CGHeroInstance *h = gs.getHero(pack.whoID.as<ObjectInstanceID>());
 			const CGHeroInstance *h = gs.getHero(pack.whoID.as<ObjectInstanceID>());
-			if (h)
+			if(h)
 				callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroBonusChanged, h, pack.bonus, false);
 				callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroBonusChanged, h, pack.bonus, false);
 		}
 		}
 		break;
 		break;
@@ -701,7 +708,7 @@ void ApplyFirstClientNetPackVisitor::visitSetObjectProperty(SetObjectProperty &
 	}
 	}
 
 
 	// invalidate section of map view with our object and force an update with new flag color
 	// invalidate section of map view with our object and force an update with new flag color
-	if (pack.what == ObjProperty::OWNER && CGI->mh)
+	if(pack.what == ObjProperty::OWNER && CGI->mh)
 	{
 	{
 		auto object = gs.getObjInstance(pack.id);
 		auto object = gs.getObjInstance(pack.id);
 		CGI->mh->onObjectInstantRemove(object, object->getOwner());
 		CGI->mh->onObjectInstantRemove(object, object->getOwner());
@@ -718,7 +725,7 @@ void ApplyClientNetPackVisitor::visitSetObjectProperty(SetObjectProperty & pack)
 	}
 	}
 
 
 	// invalidate section of map view with our object and force an update with new flag color
 	// invalidate section of map view with our object and force an update with new flag color
-	if (pack.what == ObjProperty::OWNER && CGI->mh)
+	if(pack.what == ObjProperty::OWNER && CGI->mh)
 	{
 	{
 		auto object = gs.getObjInstance(pack.id);
 		auto object = gs.getObjInstance(pack.id);
 		CGI->mh->onObjectInstantAdd(object, object->getOwner());
 		CGI->mh->onObjectInstantAdd(object, object->getOwner());
@@ -807,7 +814,7 @@ void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack &
 
 
 	const CStack *activated = gs.getBattle(pack.battleID)->battleGetStackByID(pack.stack);
 	const CStack *activated = gs.getBattle(pack.battleID)->battleGetStackByID(pack.stack);
 	PlayerColor playerToCall; //pack.player that will move activated stack
 	PlayerColor playerToCall; //pack.player that will move activated stack
-	if (activated->hasBonusOfType(BonusType::HYPNOTIZED))
+	if(activated->hasBonusOfType(BonusType::HYPNOTIZED))
 	{
 	{
 		playerToCall = gs.getBattle(pack.battleID)->getSide(BattleSide::ATTACKER).color == activated->unitOwner()
 		playerToCall = gs.getBattle(pack.battleID)->getSide(BattleSide::ATTACKER).color == activated->unitOwner()
 			? gs.getBattle(pack.battleID)->getSide(BattleSide::DEFENDER).color
 			? gs.getBattle(pack.battleID)->getSide(BattleSide::DEFENDER).color

+ 3 - 2
client/NetPacksLobbyClient.cpp

@@ -33,6 +33,7 @@
 #include "widgets/TextControls.h"
 #include "widgets/TextControls.h"
 #include "media/CMusicHandler.h"
 #include "media/CMusicHandler.h"
 #include "media/IVideoPlayer.h"
 #include "media/IVideoPlayer.h"
+#include "windows/GUIClasses.h"
 
 
 #include "../lib/CConfigHandler.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/texts/CGeneralTextHandler.h"
 #include "../lib/texts/CGeneralTextHandler.h"
@@ -207,10 +208,10 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState &
 	{
 	{
 		auto bonusSel = std::make_shared<CBonusSelection>();
 		auto bonusSel = std::make_shared<CBonusSelection>();
 		lobby->bonusSel = bonusSel;
 		lobby->bonusSel = bonusSel;
-		if(!handler.si->campState->conqueredScenarios().size() && !handler.si->campState->getIntroVideo().empty() && CCS->videoh->open(handler.si->campState->getIntroVideo(), false))
+		if(!handler.si->campState->conqueredScenarios().size() && !handler.si->campState->getIntroVideo().empty() && CCS->videoh->open(handler.si->campState->getIntroVideo(), 1))
 		{
 		{
 			CCS->musich->stopMusic();
 			CCS->musich->stopMusic();
-			GH.windows().createAndPushWindow<CampaignRimVideo>(handler.si->campState->getIntroVideo(), handler.si->campState->getVideoRim().empty() ? ImagePath::builtin("INTRORIM") : handler.si->campState->getVideoRim(), [bonusSel](){
+			GH.windows().createAndPushWindow<VideoWindow>(handler.si->campState->getIntroVideo(), handler.si->campState->getVideoRim().empty() ? ImagePath::builtin("INTRORIM") : handler.si->campState->getVideoRim(), false, 1, [bonusSel](bool skipped){
 				if(!CSH->si->campState->getMusic().empty())
 				if(!CSH->si->campState->getMusic().empty())
 					CCS->musich->playMusic(CSH->si->campState->getMusic(), true, false);
 					CCS->musich->playMusic(CSH->si->campState->getMusic(), true, false);
 				GH.windows().pushWindow(bonusSel);
 				GH.windows().pushWindow(bonusSel);

+ 3 - 1
client/eventsSDL/InputSourceMouse.cpp

@@ -34,7 +34,7 @@ InputSourceMouse::InputSourceMouse()
 void InputSourceMouse::handleEventMouseMotion(const SDL_MouseMotionEvent & motion)
 void InputSourceMouse::handleEventMouseMotion(const SDL_MouseMotionEvent & motion)
 {
 {
 	Point newPosition = Point(motion.x, motion.y) / GH.screenHandler().getScalingFactor();
 	Point newPosition = Point(motion.x, motion.y) / GH.screenHandler().getScalingFactor();
-	Point distance= Point(-motion.xrel, -motion.yrel) / GH.screenHandler().getScalingFactor();
+	Point distance = Point(-motion.xrel, -motion.yrel) / GH.screenHandler().getScalingFactor();
 
 
 	mouseButtonsMask = motion.state;
 	mouseButtonsMask = motion.state;
 
 
@@ -42,6 +42,8 @@ void InputSourceMouse::handleEventMouseMotion(const SDL_MouseMotionEvent & motio
 		GH.events().dispatchGesturePanning(middleClickPosition, newPosition, distance);
 		GH.events().dispatchGesturePanning(middleClickPosition, newPosition, distance);
 	else if (mouseButtonsMask & SDL_BUTTON(SDL_BUTTON_LEFT))
 	else if (mouseButtonsMask & SDL_BUTTON(SDL_BUTTON_LEFT))
 		GH.events().dispatchMouseDragged(newPosition, distance);
 		GH.events().dispatchMouseDragged(newPosition, distance);
+	else if (mouseButtonsMask & SDL_BUTTON(SDL_BUTTON_RIGHT))
+		GH.events().dispatchMouseDraggedPopup(newPosition, distance);
 	else
 	else
 		GH.input().setCursorPosition(newPosition);
 		GH.input().setCursorPosition(newPosition);
 }
 }

+ 8 - 0
client/gui/EventDispatcher.cpp

@@ -35,6 +35,7 @@ void EventDispatcher::processLists(ui16 activityFlag, const Functor & cb)
 	processList(AEventsReceiver::HOVER, hoverable);
 	processList(AEventsReceiver::HOVER, hoverable);
 	processList(AEventsReceiver::MOVE, motioninterested);
 	processList(AEventsReceiver::MOVE, motioninterested);
 	processList(AEventsReceiver::DRAG, draginterested);
 	processList(AEventsReceiver::DRAG, draginterested);
+	processList(AEventsReceiver::DRAG_POPUP, dragPopupInterested);
 	processList(AEventsReceiver::KEYBOARD, keyinterested);
 	processList(AEventsReceiver::KEYBOARD, keyinterested);
 	processList(AEventsReceiver::TIME, timeinterested);
 	processList(AEventsReceiver::TIME, timeinterested);
 	processList(AEventsReceiver::WHEEL, wheelInterested);
 	processList(AEventsReceiver::WHEEL, wheelInterested);
@@ -433,3 +434,10 @@ void EventDispatcher::dispatchMouseDragged(const Point & currentPosition, const
 			elem->mouseDragged(currentPosition, lastUpdateDistance);
 			elem->mouseDragged(currentPosition, lastUpdateDistance);
 	}
 	}
 }
 }
+
+void EventDispatcher::dispatchMouseDraggedPopup(const Point & currentPosition, const Point & lastUpdateDistance)
+{
+	EventReceiversList diCopy = dragPopupInterested;
+	for(auto & elem : diCopy)
+		elem->mouseDraggedPopup(currentPosition, lastUpdateDistance);
+}

+ 2 - 0
client/gui/EventDispatcher.h

@@ -30,6 +30,7 @@ class EventDispatcher
 	EventReceiversList keyinterested;
 	EventReceiversList keyinterested;
 	EventReceiversList motioninterested;
 	EventReceiversList motioninterested;
 	EventReceiversList draginterested;
 	EventReceiversList draginterested;
+	EventReceiversList dragPopupInterested;
 	EventReceiversList timeinterested;
 	EventReceiversList timeinterested;
 	EventReceiversList wheelInterested;
 	EventReceiversList wheelInterested;
 	EventReceiversList doubleClickInterested;
 	EventReceiversList doubleClickInterested;
@@ -66,6 +67,7 @@ public:
 	void dispatchMouseMoved(const Point & distance, const Point & position);
 	void dispatchMouseMoved(const Point & distance, const Point & position);
 
 
 	void dispatchMouseDragged(const Point & currentPosition, const Point & lastUpdateDistance);
 	void dispatchMouseDragged(const Point & currentPosition, const Point & lastUpdateDistance);
+	void dispatchMouseDraggedPopup(const Point & currentPosition, const Point & lastUpdateDistance);
 
 
 	void dispatchShowPopup(const Point & position, int tolerance);
 	void dispatchShowPopup(const Point & position, int tolerance);
 	void dispatchClosePopup(const Point & position);
 	void dispatchClosePopup(const Point & position);

+ 3 - 1
client/gui/EventsReceiver.h

@@ -61,6 +61,7 @@ public:
 	virtual void wheelScrolled(int distance) {}
 	virtual void wheelScrolled(int distance) {}
 	virtual void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) {}
 	virtual void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) {}
 	virtual void mouseDragged(const Point & cursorPosition, const Point & lastUpdateDistance) {}
 	virtual void mouseDragged(const Point & cursorPosition, const Point & lastUpdateDistance) {}
+	virtual void mouseDraggedPopup(const Point & cursorPosition, const Point & lastUpdateDistance) {}
 
 
 	/// Called when UI element hover status changes
 	/// Called when UI element hover status changes
 	virtual void hover(bool on) {}
 	virtual void hover(bool on) {}
@@ -97,7 +98,8 @@ public:
 		TEXTINPUT = 512,
 		TEXTINPUT = 512,
 		GESTURE = 1024,
 		GESTURE = 1024,
 		DRAG = 2048,
 		DRAG = 2048,
-		INPUT_MODE_CHANGE = 4096
+		INPUT_MODE_CHANGE = 4096,
+		DRAG_POPUP = 8192
 	};
 	};
 
 
 	/// Returns true if element is currently hovered by mouse
 	/// Returns true if element is currently hovered by mouse

+ 0 - 31
client/lobby/CBonusSelection.cpp

@@ -58,37 +58,6 @@
 
 
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 
 
-
-CampaignRimVideo::CampaignRimVideo(VideoPath video, ImagePath rim, std::function<void()> closeCb)
-	: CWindowObject(BORDERED), closeCb(closeCb)
-{
-	OBJECT_CONSTRUCTION;
-
-	addUsedEvents(LCLICK | KEYBOARD);
-
-	pos = center(Rect(0, 0, 800, 600));
-
-	videoPlayer = std::make_shared<VideoWidgetOnce>(Point(80, 186), video, true, [this](){ exit(); });
-	setBackground(rim);
-}
-
-void CampaignRimVideo::exit()
-{
-	close();
-	if(closeCb)
-		closeCb();
-}
-
-void CampaignRimVideo::clickPressed(const Point & cursorPosition)
-{
-	exit();
-}
-
-void CampaignRimVideo::keyPressed(EShortcut key)
-{
-	exit();
-}
-
 std::shared_ptr<CampaignState> CBonusSelection::getCampaign()
 std::shared_ptr<CampaignState> CBonusSelection::getCampaign()
 {
 {
 	return CSH->si->campState;
 	return CSH->si->campState;

+ 0 - 14
client/lobby/CBonusSelection.h

@@ -33,20 +33,6 @@ class VideoWidgetOnce;
 class CBonusSelection;
 class CBonusSelection;
 
 
 
 
-class CampaignRimVideo : public CWindowObject
-{
-	std::shared_ptr<VideoWidgetOnce> videoPlayer;
-
-	std::function<void()> closeCb;
-
-	void exit();
-public:
-	CampaignRimVideo(VideoPath video, ImagePath rim, std::function<void()> closeCb);
-
-	void clickPressed(const Point & cursorPosition) override;
-	void keyPressed(EShortcut key) override;
-};
-
 /// Campaign screen where you can choose one out of three starting bonuses
 /// Campaign screen where you can choose one out of three starting bonuses
 class CBonusSelection : public CWindowObject
 class CBonusSelection : public CWindowObject
 {
 {

+ 2 - 0
client/mainmenu/CHighScoreScreen.cpp

@@ -12,6 +12,7 @@
 
 
 #include "CHighScoreScreen.h"
 #include "CHighScoreScreen.h"
 #include "CStatisticScreen.h"
 #include "CStatisticScreen.h"
+#include "CMainMenu.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/WindowHandler.h"
 #include "../gui/WindowHandler.h"
 #include "../gui/Shortcut.h"
 #include "../gui/Shortcut.h"
@@ -170,6 +171,7 @@ void CHighScoreScreen::buttonResetClick()
 void CHighScoreScreen::buttonExitClick()
 void CHighScoreScreen::buttonExitClick()
 {
 {
 	close();
 	close();
+	CMM->playMusic();
 }
 }
 
 
 CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc, const StatisticDataSet & statistic)
 CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc, const StatisticDataSet & statistic)

+ 30 - 1
client/mainmenu/CMainMenu.cpp

@@ -18,6 +18,7 @@
 #include "../lobby/CSelectionBase.h"
 #include "../lobby/CSelectionBase.h"
 #include "../lobby/CLobbyScreen.h"
 #include "../lobby/CLobbyScreen.h"
 #include "../media/IMusicPlayer.h"
 #include "../media/IMusicPlayer.h"
+#include "../media/IVideoPlayer.h"
 #include "../gui/CursorHandler.h"
 #include "../gui/CursorHandler.h"
 #include "../windows/GUIClasses.h"
 #include "../windows/GUIClasses.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CGuiHandler.h"
@@ -117,7 +118,6 @@ void CMenuScreen::show(Canvas & to)
 
 
 void CMenuScreen::activate()
 void CMenuScreen::activate()
 {
 {
-	CCS->musich->playMusic(AudioPath::builtin("Music/MainMenu"), true, true);
 	CIntObject::activate();
 	CIntObject::activate();
 }
 }
 
 
@@ -300,6 +300,35 @@ CMainMenu::~CMainMenu()
 		GH.curInt = nullptr;
 		GH.curInt = nullptr;
 }
 }
 
 
+void CMainMenu::playIntroVideos()
+{
+	auto playVideo = [](std::string video, bool rim, float scaleFactor, std::function<void(bool)> cb){
+		if(CCS->videoh->open(VideoPath::builtin(video), scaleFactor))
+			GH.windows().createAndPushWindow<VideoWindow>(VideoPath::builtin(video), rim ? ImagePath::builtin("INTRORIM") : ImagePath::builtin(""), true, scaleFactor, [cb](bool skipped){ cb(skipped); });
+		else
+			cb(true);
+	};
+
+	playVideo("3DOLOGO.SMK", false, 1.25, [playVideo, this](bool skipped){
+		if(!skipped)
+			playVideo("NWCLOGO.SMK", false, 2, [playVideo, this](bool skipped){
+				if(!skipped)
+					playVideo("H3INTRO.SMK", true, 1, [this](bool skipped){
+						playMusic();
+					});
+				else
+					playMusic();
+			});
+		else
+			playMusic();
+	});
+}
+
+void CMainMenu::playMusic()
+{
+	CCS->musich->playMusic(AudioPath::builtin("Music/MainMenu"), true, true);
+}
+
 void CMainMenu::activate()
 void CMainMenu::activate()
 {
 {
 	// check if screen was resized while main menu was inactive - e.g. in gameplay mode
 	// check if screen was resized while main menu was inactive - e.g. in gameplay mode

+ 4 - 0
client/mainmenu/CMainMenu.h

@@ -142,6 +142,8 @@ class CMainMenu : public CIntObject, public IUpdateable, public std::enable_shar
 {
 {
 	std::shared_ptr<CFilledTexture> backgroundAroundMenu;
 	std::shared_ptr<CFilledTexture> backgroundAroundMenu;
 
 
+	std::vector<VideoPath> videoPlayList;
+
 	CMainMenu(); //Use CMainMenu::create
 	CMainMenu(); //Use CMainMenu::create
 
 
 public:
 public:
@@ -162,6 +164,8 @@ public:
 
 
 	static std::shared_ptr<CPicture> createPicture(const JsonNode & config);
 	static std::shared_ptr<CPicture> createPicture(const JsonNode & config);
 
 
+	void playIntroVideos();
+	void playMusic();
 };
 };
 
 
 /// Simple window to enter the server's address.
 /// Simple window to enter the server's address.

+ 6 - 1
client/mapView/MapViewActions.cpp

@@ -34,7 +34,7 @@ MapViewActions::MapViewActions(MapView & owner, const std::shared_ptr<MapViewMod
 	pos.w = model->getPixelsVisibleDimensions().x;
 	pos.w = model->getPixelsVisibleDimensions().x;
 	pos.h = model->getPixelsVisibleDimensions().y;
 	pos.h = model->getPixelsVisibleDimensions().y;
 
 
-	addUsedEvents(LCLICK | SHOW_POPUP | DRAG | GESTURE | HOVER | MOVE | WHEEL);
+	addUsedEvents(LCLICK | SHOW_POPUP | DRAG | DRAG_POPUP | GESTURE | HOVER | MOVE | WHEEL);
 }
 }
 
 
 void MapViewActions::setContext(const std::shared_ptr<IMapRendererContext> & context)
 void MapViewActions::setContext(const std::shared_ptr<IMapRendererContext> & context)
@@ -101,6 +101,11 @@ void MapViewActions::mouseDragged(const Point & cursorPosition, const Point & la
 		owner.onMapSwiped(lastUpdateDistance);
 		owner.onMapSwiped(lastUpdateDistance);
 }
 }
 
 
+void MapViewActions::mouseDraggedPopup(const Point & cursorPosition, const Point & lastUpdateDistance)
+{
+	owner.onMapSwiped(lastUpdateDistance);
+}
+
 void MapViewActions::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
 void MapViewActions::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
 {
 {
 	owner.onMapSwiped(lastUpdateDistance);
 	owner.onMapSwiped(lastUpdateDistance);

+ 1 - 0
client/mapView/MapViewActions.h

@@ -42,6 +42,7 @@ public:
 	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
 	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
 	void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override;
 	void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override;
 	void mouseDragged(const Point & cursorPosition, const Point & lastUpdateDistance) override;
 	void mouseDragged(const Point & cursorPosition, const Point & lastUpdateDistance) override;
+	void mouseDraggedPopup(const Point & cursorPosition, const Point & lastUpdateDistance) override;
 	void wheelScrolled(int distance) override;
 	void wheelScrolled(int distance) override;
 	
 	
 	bool dragActive;
 	bool dragActive;

+ 1 - 7
client/media/CEmptyVideoPlayer.h

@@ -14,18 +14,12 @@
 class CEmptyVideoPlayer final : public IVideoPlayer
 class CEmptyVideoPlayer final : public IVideoPlayer
 {
 {
 public:
 public:
-	/// Plays video on top of the screen, returns only after playback is over
-	bool playIntroVideo(const VideoPath & name) override
-	{
-		return false;
-	};
-
 	void playSpellbookAnimation(const VideoPath & name, const Point & position) override
 	void playSpellbookAnimation(const VideoPath & name, const Point & position) override
 	{
 	{
 	}
 	}
 
 
 	/// Load video from specified path
 	/// Load video from specified path
-	std::unique_ptr<IVideoInstance> open(const VideoPath & name, bool scaleToScreen) override
+	std::unique_ptr<IVideoInstance> open(const VideoPath & name, float scaleFactor) override
 	{
 	{
 		return nullptr;
 		return nullptr;
 	};
 	};

+ 8 - 24
client/media/CVideoHandler.cpp

@@ -173,18 +173,10 @@ void CVideoInstance::openVideo()
 	openCodec(findVideoStream());
 	openCodec(findVideoStream());
 }
 }
 
 
-void CVideoInstance::prepareOutput(bool scaleToScreenSize, bool useTextureOutput)
+void CVideoInstance::prepareOutput(float scaleFactor, bool useTextureOutput)
 {
 {
 	//setup scaling
 	//setup scaling
-	if(scaleToScreenSize)
-	{
-		dimensions.x = screen->w;
-		dimensions.y = screen->h;
-	}
-	else
-	{
-		dimensions = Point(getCodecContext()->width, getCodecContext()->height) * GH.screenHandler().getScalingFactor();
-	}
+	dimensions = Point(getCodecContext()->width * scaleFactor, getCodecContext()->height * scaleFactor) * GH.screenHandler().getScalingFactor();
 
 
 	// Allocate a place to put our YUV image on that screen
 	// Allocate a place to put our YUV image on that screen
 	if (useTextureOutput)
 	if (useTextureOutput)
@@ -352,10 +344,7 @@ FFMpegStream::~FFMpegStream()
 
 
 Point CVideoInstance::size()
 Point CVideoInstance::size()
 {
 {
-	if(!getCurrentFrame())
-		throw std::runtime_error("Invalid video frame!");
-
-	return Point(getCurrentFrame()->width, getCurrentFrame()->height);
+	return dimensions / GH.screenHandler().getScalingFactor();
 }
 }
 
 
 void CVideoInstance::show(const Point & position, Canvas & canvas)
 void CVideoInstance::show(const Point & position, Canvas & canvas)
@@ -575,7 +564,7 @@ std::pair<std::unique_ptr<ui8 []>, si64> CAudioInstance::extractAudio(const Vide
 	return dat;
 	return dat;
 }
 }
 
 
-bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & position, bool useOverlay, bool scale, bool stopOnKey)
+bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & position, bool useOverlay, bool stopOnKey)
 {
 {
 	CVideoInstance instance;
 	CVideoInstance instance;
 	CAudioInstance audio;
 	CAudioInstance audio;
@@ -587,7 +576,7 @@ bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & po
 		return true;
 		return true;
 
 
 	instance.openVideo();
 	instance.openVideo();
-	instance.prepareOutput(scale, true);
+	instance.prepareOutput(1, true);
 
 
 	auto lastTimePoint = boost::chrono::steady_clock::now();
 	auto lastTimePoint = boost::chrono::steady_clock::now();
 
 
@@ -633,17 +622,12 @@ bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & po
 	return true;
 	return true;
 }
 }
 
 
-bool CVideoPlayer::playIntroVideo(const VideoPath & name)
-{
-	return openAndPlayVideoImpl(name, Point(0, 0), true, true, true);
-}
-
 void CVideoPlayer::playSpellbookAnimation(const VideoPath & name, const Point & position)
 void CVideoPlayer::playSpellbookAnimation(const VideoPath & name, const Point & position)
 {
 {
-	openAndPlayVideoImpl(name, position * GH.screenHandler().getScalingFactor(), false, false, false);
+	openAndPlayVideoImpl(name, position * GH.screenHandler().getScalingFactor(), false, false);
 }
 }
 
 
-std::unique_ptr<IVideoInstance> CVideoPlayer::open(const VideoPath & name, bool scaleToScreen)
+std::unique_ptr<IVideoInstance> CVideoPlayer::open(const VideoPath & name, float scaleFactor)
 {
 {
 	auto result = std::make_unique<CVideoInstance>();
 	auto result = std::make_unique<CVideoInstance>();
 
 
@@ -651,7 +635,7 @@ std::unique_ptr<IVideoInstance> CVideoPlayer::open(const VideoPath & name, bool
 		return nullptr;
 		return nullptr;
 
 
 	result->openVideo();
 	result->openVideo();
-	result->prepareOutput(scaleToScreen, false);
+	result->prepareOutput(scaleFactor, false);
 	result->loadNextFrame(); // prepare 1st frame
 	result->loadNextFrame(); // prepare 1st frame
 
 
 	return result;
 	return result;

+ 3 - 4
client/media/CVideoHandler.h

@@ -80,7 +80,7 @@ class CVideoInstance final : public IVideoInstance, public FFMpegStream
 	/// video playback current progress, in seconds
 	/// video playback current progress, in seconds
 	double frameTime = 0.0;
 	double frameTime = 0.0;
 
 
-	void prepareOutput(bool scaleToScreenSize, bool useTextureOutput);
+	void prepareOutput(float scaleFactor, bool useTextureOutput);
 
 
 public:
 public:
 	~CVideoInstance();
 	~CVideoInstance();
@@ -97,13 +97,12 @@ public:
 
 
 class CVideoPlayer final : public IVideoPlayer
 class CVideoPlayer final : public IVideoPlayer
 {
 {
-	bool openAndPlayVideoImpl(const VideoPath & name, const Point & position, bool useOverlay, bool scale, bool stopOnKey);
+	bool openAndPlayVideoImpl(const VideoPath & name, const Point & position, bool useOverlay, bool stopOnKey);
 	void openVideoFile(CVideoInstance & state, const VideoPath & fname);
 	void openVideoFile(CVideoInstance & state, const VideoPath & fname);
 
 
 public:
 public:
-	bool playIntroVideo(const VideoPath & name) final;
 	void playSpellbookAnimation(const VideoPath & name, const Point & position) final;
 	void playSpellbookAnimation(const VideoPath & name, const Point & position) final;
-	std::unique_ptr<IVideoInstance> open(const VideoPath & name, bool scaleToScreen) final;
+	std::unique_ptr<IVideoInstance> open(const VideoPath & name, float scaleFactor) final;
 	std::pair<std::unique_ptr<ui8[]>, si64> getAudio(const VideoPath & videoToOpen) final;
 	std::pair<std::unique_ptr<ui8[]>, si64> getAudio(const VideoPath & videoToOpen) final;
 };
 };
 
 

+ 1 - 4
client/media/IVideoPlayer.h

@@ -38,14 +38,11 @@ public:
 class IVideoPlayer : boost::noncopyable
 class IVideoPlayer : boost::noncopyable
 {
 {
 public:
 public:
-	/// Plays video on top of the screen, returns only after playback is over, aborts on input event
-	virtual bool playIntroVideo(const VideoPath & name) = 0;
-
 	/// Plays video on top of the screen, returns only after playback is over
 	/// Plays video on top of the screen, returns only after playback is over
 	virtual void playSpellbookAnimation(const VideoPath & name, const Point & position) = 0;
 	virtual void playSpellbookAnimation(const VideoPath & name, const Point & position) = 0;
 
 
 	/// Load video from specified path. Returns nullptr on failure
 	/// Load video from specified path. Returns nullptr on failure
-	virtual std::unique_ptr<IVideoInstance> open(const VideoPath & name, bool scaleToScreen) = 0;
+	virtual std::unique_ptr<IVideoInstance> open(const VideoPath & name, float scaleFactor) = 0;
 
 
 	/// Extracts audio data from provided video in wav format
 	/// Extracts audio data from provided video in wav format
 	virtual std::pair<std::unique_ptr<ui8[]>, si64> getAudio(const VideoPath & videoToOpen) = 0;
 	virtual std::pair<std::unique_ptr<ui8[]>, si64> getAudio(const VideoPath & videoToOpen) = 0;

+ 13 - 2
client/widgets/VideoWidget.cpp

@@ -17,7 +17,12 @@
 #include "../render/Canvas.h"
 #include "../render/Canvas.h"
 
 
 VideoWidgetBase::VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio)
 VideoWidgetBase::VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio)
-	: playAudio(playAudio)
+	: VideoWidgetBase(position, video, playAudio, 1.0)
+{
+}
+
+VideoWidgetBase::VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio, float scaleFactor)
+	: playAudio(playAudio), scaleFactor(scaleFactor)
 {
 {
 	addUsedEvents(TIME);
 	addUsedEvents(TIME);
 	pos += position;
 	pos += position;
@@ -28,7 +33,7 @@ VideoWidgetBase::~VideoWidgetBase() = default;
 
 
 void VideoWidgetBase::playVideo(const VideoPath & fileToPlay)
 void VideoWidgetBase::playVideo(const VideoPath & fileToPlay)
 {
 {
-	videoInstance = CCS->videoh->open(fileToPlay, false);
+	videoInstance = CCS->videoh->open(fileToPlay, scaleFactor);
 	if (videoInstance)
 	if (videoInstance)
 	{
 	{
 		pos.w = videoInstance->size().x;
 		pos.w = videoInstance->size().x;
@@ -142,6 +147,12 @@ VideoWidgetOnce::VideoWidgetOnce(const Point & position, const VideoPath & video
 {
 {
 }
 }
 
 
+VideoWidgetOnce::VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, float scaleFactor, const std::function<void()> & callback)
+	: VideoWidgetBase(position, video, playAudio, scaleFactor)
+	, callback(callback)
+{
+}
+
 void VideoWidgetOnce::onPlaybackFinished()
 void VideoWidgetOnce::onPlaybackFinished()
 {
 {
 	callback();
 	callback();

+ 3 - 0
client/widgets/VideoWidget.h

@@ -22,6 +22,7 @@ class VideoWidgetBase : public CIntObject
 	std::pair<std::unique_ptr<ui8[]>, si64> audioData = {nullptr, 0};
 	std::pair<std::unique_ptr<ui8[]>, si64> audioData = {nullptr, 0};
 	int audioHandle = -1;
 	int audioHandle = -1;
 	bool playAudio = false;
 	bool playAudio = false;
+	float scaleFactor = 1.0;
 
 
 	void loadAudio(const VideoPath & file);
 	void loadAudio(const VideoPath & file);
 	void startAudio();
 	void startAudio();
@@ -29,6 +30,7 @@ class VideoWidgetBase : public CIntObject
 
 
 protected:
 protected:
 	VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio);
 	VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio);
+	VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio, float scaleFactor);
 
 
 	virtual void onPlaybackFinished() = 0;
 	virtual void onPlaybackFinished() = 0;
 	void playVideo(const VideoPath & video);
 	void playVideo(const VideoPath & video);
@@ -62,4 +64,5 @@ class VideoWidgetOnce final: public VideoWidgetBase
 	void onPlaybackFinished() final;
 	void onPlaybackFinished() final;
 public:
 public:
 	VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, const std::function<void()> & callback);
 	VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, const std::function<void()> & callback);
+	VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, float scaleFactor, const std::function<void()> & callback);
 };
 };

+ 47 - 0
client/windows/GUIClasses.cpp

@@ -1608,3 +1608,50 @@ void CObjectListWindow::keyPressed(EShortcut key)
 	list->scrollTo(sel);
 	list->scrollTo(sel);
 	changeSelection(sel);
 	changeSelection(sel);
 }
 }
+
+VideoWindow::VideoWindow(VideoPath video, ImagePath rim, bool showBackground, float scaleFactor, std::function<void(bool skipped)> closeCb)
+	: CWindowObject(BORDERED | SHADOW_DISABLED | NEEDS_ANIMATED_BACKGROUND), closeCb(closeCb)
+{
+	OBJECT_CONSTRUCTION;
+
+	addUsedEvents(LCLICK | KEYBOARD);
+
+	if(!rim.empty())
+	{
+		videoPlayer = std::make_shared<VideoWidgetOnce>(Point(80, 186), video, true, [this](){ exit(false); });
+		pos = center(Rect(0, 0, 800, 600));
+	}
+	else
+	{
+		videoPlayer = std::make_shared<VideoWidgetOnce>(Point(0, 0), video, true, scaleFactor, [this](){ exit(false); });
+		pos = center(Rect(0, 0, videoPlayer->pos.w, videoPlayer->pos.h));
+	}
+
+	if(showBackground)
+		backgroundAroundWindow = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(-pos.x, -pos.y, GH.screenDimensions().x, GH.screenDimensions().y));
+
+	if(!rim.empty())
+		setBackground(rim);
+}
+
+void VideoWindow::exit(bool skipped)
+{
+	close();
+	if(closeCb)
+		closeCb(skipped);
+}
+
+void VideoWindow::clickPressed(const Point & cursorPosition)
+{
+	exit(true);
+}
+
+void VideoWindow::keyPressed(EShortcut key)
+{
+	exit(true);
+}
+
+bool VideoWindow::receiveEvent(const Point & position, int eventType) const
+{
+	return true;  // capture click also outside of window
+}

+ 16 - 0
client/windows/GUIClasses.h

@@ -43,6 +43,7 @@ class CAnimImage;
 class CFilledTexture;
 class CFilledTexture;
 class IImage;
 class IImage;
 class VideoWidget;
 class VideoWidget;
+class VideoWidgetOnce;
 
 
 enum class EUserEvent;
 enum class EUserEvent;
 
 
@@ -501,3 +502,18 @@ public:
 	CThievesGuildWindow(const CGObjectInstance * _owner);
 	CThievesGuildWindow(const CGObjectInstance * _owner);
 };
 };
 
 
+class VideoWindow : public CWindowObject
+{
+	std::shared_ptr<VideoWidgetOnce> videoPlayer;
+	std::shared_ptr<CFilledTexture> backgroundAroundWindow;
+
+	std::function<void(bool)> closeCb;
+
+	void exit(bool skipped);
+public:
+	VideoWindow(VideoPath video, ImagePath rim, bool showBackground, float scaleFactor, std::function<void(bool)> closeCb);
+
+	void clickPressed(const Point & cursorPosition) override;
+	void keyPressed(EShortcut key) override;
+	bool receiveEvent(const Point & position, int eventType) const override;
+};

+ 35 - 1
client/windows/InfoWindows.cpp

@@ -245,8 +245,11 @@ void CRClickPopup::createAndPush(const CGObjectInstance * obj, const Point & p,
 	}
 	}
 }
 }
 
 
-CRClickPopupInt::CRClickPopupInt(const std::shared_ptr<CIntObject> & our)
+CRClickPopupInt::CRClickPopupInt(const std::shared_ptr<CIntObject> & our) :
+	dragDistance(Point(0, 0))
 {
 {
+	addUsedEvents(DRAG_POPUP);
+
 	CCS->curh->hide();
 	CCS->curh->hide();
 	inner = our;
 	inner = our;
 	addChild(our.get(), false);
 	addChild(our.get(), false);
@@ -257,6 +260,17 @@ CRClickPopupInt::~CRClickPopupInt()
 	CCS->curh->show();
 	CCS->curh->show();
 }
 }
 
 
+void CRClickPopupInt::mouseDraggedPopup(const Point & cursorPosition, const Point & lastUpdateDistance)
+{
+	if(!settings["adventure"]["rightButtonDrag"].Bool())
+		return;
+	
+	dragDistance += lastUpdateDistance;
+	
+	if(dragDistance.length() > 16)
+		close();
+}
+
 Point CInfoBoxPopup::toScreen(Point p)
 Point CInfoBoxPopup::toScreen(Point p)
 {
 {
 	auto bounds = adventureInt->terrainAreaPixels();
 	auto bounds = adventureInt->terrainAreaPixels();
@@ -267,6 +281,18 @@ Point CInfoBoxPopup::toScreen(Point p)
 	return p;
 	return p;
 }
 }
 
 
+void CInfoBoxPopup::mouseDraggedPopup(const Point & cursorPosition, const Point & lastUpdateDistance)
+{
+	if(!settings["adventure"]["rightButtonDrag"].Bool())
+		return;
+	
+	dragDistance += lastUpdateDistance;
+	
+	if(dragDistance.length() > 16)
+		close();
+}
+
+
 CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town)
 CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town)
 	: CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("TOWNQVBK"), toScreen(position))
 	: CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("TOWNQVBK"), toScreen(position))
 {
 {
@@ -275,6 +301,8 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town)
 
 
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 	tooltip = std::make_shared<CTownTooltip>(Point(9, 10), iah);
 	tooltip = std::make_shared<CTownTooltip>(Point(9, 10), iah);
+
+	addUsedEvents(DRAG_POPUP);
 }
 }
 
 
 CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero)
 CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero)
@@ -285,6 +313,8 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero)
 
 
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 	tooltip = std::make_shared<CHeroTooltip>(Point(9, 10), iah);
 	tooltip = std::make_shared<CHeroTooltip>(Point(9, 10), iah);
+	
+	addUsedEvents(DRAG_POPUP);
 }
 }
 
 
 CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr)
 CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr)
@@ -295,6 +325,8 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr)
 
 
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 	tooltip = std::make_shared<CArmyTooltip>(Point(9, 10), iah);
 	tooltip = std::make_shared<CArmyTooltip>(Point(9, 10), iah);
+	
+	addUsedEvents(DRAG_POPUP);
 }
 }
 
 
 CInfoBoxPopup::CInfoBoxPopup(Point position, const CGCreature * creature)
 CInfoBoxPopup::CInfoBoxPopup(Point position, const CGCreature * creature)
@@ -302,6 +334,8 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGCreature * creature)
 {
 {
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 	tooltip = std::make_shared<CreatureTooltip>(Point(9, 10), creature);
 	tooltip = std::make_shared<CreatureTooltip>(Point(9, 10), creature);
+	
+	addUsedEvents(DRAG_POPUP);
 }
 }
 
 
 std::shared_ptr<WindowBase>
 std::shared_ptr<WindowBase>

+ 8 - 0
client/windows/InfoWindows.h

@@ -78,9 +78,13 @@ class CRClickPopupInt : public CRClickPopup
 {
 {
 	std::shared_ptr<CIntObject> inner;
 	std::shared_ptr<CIntObject> inner;
 
 
+	Point dragDistance;
+
 public:
 public:
 	CRClickPopupInt(const std::shared_ptr<CIntObject> & our);
 	CRClickPopupInt(const std::shared_ptr<CIntObject> & our);
 	~CRClickPopupInt();
 	~CRClickPopupInt();
+
+	void mouseDraggedPopup(const Point & cursorPosition, const Point & lastUpdateDistance) override;
 };
 };
 
 
 /// popup on adventure map for town\hero and other objects with customized popup content
 /// popup on adventure map for town\hero and other objects with customized popup content
@@ -89,11 +93,15 @@ class CInfoBoxPopup : public CWindowObject
 	std::shared_ptr<CIntObject> tooltip;
 	std::shared_ptr<CIntObject> tooltip;
 	Point toScreen(Point pos);
 	Point toScreen(Point pos);
 
 
+	Point dragDistance;
+
 public:
 public:
 	CInfoBoxPopup(Point position, const CGTownInstance * town);
 	CInfoBoxPopup(Point position, const CGTownInstance * town);
 	CInfoBoxPopup(Point position, const CGHeroInstance * hero);
 	CInfoBoxPopup(Point position, const CGHeroInstance * hero);
 	CInfoBoxPopup(Point position, const CGGarrison * garr);
 	CInfoBoxPopup(Point position, const CGGarrison * garr);
 	CInfoBoxPopup(Point position, const CGCreature * creature);
 	CInfoBoxPopup(Point position, const CGCreature * creature);
+
+	void mouseDraggedPopup(const Point & cursorPosition, const Point & lastUpdateDistance) override;
 };
 };
 
 
 /// component selection window
 /// component selection window

+ 12 - 0
client/windows/settings/AdventureOptionsTab.cpp

@@ -11,6 +11,7 @@
 
 
 #include "AdventureOptionsTab.h"
 #include "AdventureOptionsTab.h"
 
 
+#include "../../eventsSDL/InputHandler.h"
 #include "../../../lib/filesystem/ResourcePath.h"
 #include "../../../lib/filesystem/ResourcePath.h"
 #include "../../gui/CGuiHandler.h"
 #include "../../gui/CGuiHandler.h"
 #include "../../widgets/Buttons.h"
 #include "../../widgets/Buttons.h"
@@ -36,6 +37,9 @@ AdventureOptionsTab::AdventureOptionsTab()
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 	setRedrawParent(true);
 	setRedrawParent(true);
 
 
+	addConditional("touchscreen", GH.input().getCurrentInputMode() == InputMode::TOUCH);
+	addConditional("keyboardMouse", GH.input().getCurrentInputMode() == InputMode::KEYBOARD_AND_MOUSE);
+	addConditional("controller", GH.input().getCurrentInputMode() == InputMode::CONTROLLER);
 #ifdef VCMI_MOBILE
 #ifdef VCMI_MOBILE
 	addConditional("mobile", true);
 	addConditional("mobile", true);
 	addConditional("desktop", false);
 	addConditional("desktop", false);
@@ -126,6 +130,10 @@ AdventureOptionsTab::AdventureOptionsTab()
 	{
 	{
 		return setBoolSetting("adventure", "leftButtonDrag", value);
 		return setBoolSetting("adventure", "leftButtonDrag", value);
 	});
 	});
+	addCallback("rightButtonDragChanged", [](bool value)
+	{
+		return setBoolSetting("adventure", "rightButtonDrag", value);
+	});
 	addCallback("smoothDraggingChanged", [](bool value)
 	addCallback("smoothDraggingChanged", [](bool value)
 	{
 	{
 		return setBoolSetting("adventure", "smoothDragging", value);
 		return setBoolSetting("adventure", "smoothDragging", value);
@@ -177,6 +185,10 @@ AdventureOptionsTab::AdventureOptionsTab()
 	if (leftButtonDragCheckbox)
 	if (leftButtonDragCheckbox)
 		leftButtonDragCheckbox->setSelected(settings["adventure"]["leftButtonDrag"].Bool());
 		leftButtonDragCheckbox->setSelected(settings["adventure"]["leftButtonDrag"].Bool());
 
 
+	std::shared_ptr<CToggleButton> rightButtonDragCheckbox = widget<CToggleButton>("rightButtonDragCheckbox");
+	if (rightButtonDragCheckbox)
+		rightButtonDragCheckbox->setSelected(settings["adventure"]["rightButtonDrag"].Bool());
+
 	std::shared_ptr<CToggleButton> smoothDraggingCheckbox = widget<CToggleButton>("smoothDraggingCheckbox");
 	std::shared_ptr<CToggleButton> smoothDraggingCheckbox = widget<CToggleButton>("smoothDraggingCheckbox");
 	if (smoothDraggingCheckbox)
 	if (smoothDraggingCheckbox)
 		smoothDraggingCheckbox->setSelected(settings["adventure"]["smoothDragging"].Bool());
 		smoothDraggingCheckbox->setSelected(settings["adventure"]["smoothDragging"].Bool());

+ 3 - 1
client/windows/settings/GeneralOptionsTab.cpp

@@ -97,7 +97,9 @@ GeneralOptionsTab::GeneralOptionsTab()
 	OBJECT_CONSTRUCTION;
 	OBJECT_CONSTRUCTION;
 	setRedrawParent(true);
 	setRedrawParent(true);
 
 
-	addConditional("touchscreen", GH.input().hasTouchInputDevice());
+	addConditional("touchscreen", GH.input().getCurrentInputMode() == InputMode::TOUCH);
+	addConditional("keyboardMouse", GH.input().getCurrentInputMode() == InputMode::KEYBOARD_AND_MOUSE);
+	addConditional("controller", GH.input().getCurrentInputMode() == InputMode::CONTROLLER);
 #ifdef VCMI_MOBILE
 #ifdef VCMI_MOBILE
 	addConditional("mobile", true);
 	addConditional("mobile", true);
 	addConditional("desktop", false);
 	addConditional("desktop", false);

+ 7 - 0
client/windows/settings/SettingsMainWindow.cpp

@@ -45,6 +45,8 @@ SettingsMainWindow::SettingsMainWindow(BattleInterface * parentBattleUi) : Inter
 	addCallback("closeWindow", [this](int) { backButtonCallback(); });
 	addCallback("closeWindow", [this](int) { backButtonCallback(); });
 	build(config);
 	build(config);
 
 
+	addUsedEvents(INPUT_MODE_CHANGE);
+
 	std::shared_ptr<CIntObject> background = widget<CIntObject>("background");
 	std::shared_ptr<CIntObject> background = widget<CIntObject>("background");
 	pos.w = background->pos.w;
 	pos.w = background->pos.w;
 	pos.h = background->pos.h;
 	pos.h = background->pos.h;
@@ -196,3 +198,8 @@ void SettingsMainWindow::onScreenResize()
 	if (tab)
 	if (tab)
 		tab->updateResolutionSelector();
 		tab->updateResolutionSelector();
 }
 }
+
+void SettingsMainWindow::inputModeChanged(InputMode mode)
+{
+	tabContentArea->reset();
+}

+ 1 - 0
client/windows/settings/SettingsMainWindow.h

@@ -42,5 +42,6 @@ public:
 
 
 	void showAll(Canvas & to) override;
 	void showAll(Canvas & to) override;
 	void onScreenResize() override;
 	void onScreenResize() override;
+	void inputModeChanged(InputMode mode) override;
 };
 };
 
 

+ 7 - 20
clientapp/EntryPoint.cpp

@@ -30,6 +30,7 @@
 #include "../client/render/Graphics.h"
 #include "../client/render/Graphics.h"
 #include "../client/render/IRenderHandler.h"
 #include "../client/render/IRenderHandler.h"
 #include "../client/render/IScreenHandler.h"
 #include "../client/render/IScreenHandler.h"
+#include "../client/lobby/CBonusSelection.h"
 #include "../client/windows/CMessage.h"
 #include "../client/windows/CMessage.h"
 #include "../client/windows/InfoWindows.h"
 #include "../client/windows/InfoWindows.h"
 
 
@@ -65,7 +66,6 @@ static std::optional<std::string> criticalInitializationError;
 #ifndef VCMI_IOS
 #ifndef VCMI_IOS
 void processCommand(const std::string &message);
 void processCommand(const std::string &message);
 #endif
 #endif
-void playIntro();
 [[noreturn]] static void quitApplication();
 [[noreturn]] static void quitApplication();
 static void mainLoop();
 static void mainLoop();
 
 
@@ -319,13 +319,6 @@ int main(int argc, char * argv[])
 	init();
 	init();
 #endif
 #endif
 
 
-	if(!settings["session"]["headless"].Bool())
-	{
-		if(!vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool())
-			playIntro();
-		GH.screenHandler().clearScreen();
-	}
-
 #ifndef VCMI_NO_THREADED_LOAD
 #ifndef VCMI_NO_THREADED_LOAD
 	#ifdef VCMI_ANDROID // android loads the data quite slowly so we display native progressbar to prevent having only black screen for few seconds
 	#ifdef VCMI_ANDROID // android loads the data quite slowly so we display native progressbar to prevent having only black screen for few seconds
 	{
 	{
@@ -381,6 +374,12 @@ int main(int argc, char * argv[])
 	{
 	{
 		auto mmenu = CMainMenu::create();
 		auto mmenu = CMainMenu::create();
 		GH.curInt = mmenu.get();
 		GH.curInt = mmenu.get();
+
+		bool playIntroVideo = !settings["session"]["headless"].Bool() && !vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool();
+		if(playIntroVideo)
+			mmenu->playIntroVideos();
+		else
+			mmenu->playMusic();
 	}
 	}
 	
 	
 	std::vector<std::string> names;
 	std::vector<std::string> names;
@@ -402,18 +401,6 @@ int main(int argc, char * argv[])
 	return 0;
 	return 0;
 }
 }
 
 
-//plays intro, ends when intro is over or button has been pressed (handles events)
-void playIntro()
-{
-	if(!CCS->videoh->playIntroVideo(VideoPath::builtin("3DOLOGO.SMK")))
-		return;
-
-	if (!CCS->videoh->playIntroVideo(VideoPath::builtin("NWCLOGO.SMK")))
-		return;
-
-	CCS->videoh->playIntroVideo(VideoPath::builtin("H3INTRO.SMK"));
-}
-
 static void mainLoop()
 static void mainLoop()
 {
 {
 #ifndef VCMI_UNIX
 #ifndef VCMI_UNIX

+ 1 - 1
config/schemas/settings.json

@@ -322,7 +322,7 @@
 			"type" : "object",
 			"type" : "object",
 			"additionalProperties" : false,
 			"additionalProperties" : false,
 			"default" : {},
 			"default" : {},
-			"required" : [ "heroMoveTime", "enemyMoveTime", "scrollSpeedPixels", "heroReminder", "quickCombat", "objectAnimation", "terrainAnimation", "forceQuickCombat", "borderScroll", "leftButtonDrag", "smoothDragging", "backgroundDimLevel", "hideBackground", "backgroundDimSmallWindows" ],
+			"required" : [ "heroMoveTime", "enemyMoveTime", "scrollSpeedPixels", "heroReminder", "quickCombat", "objectAnimation", "terrainAnimation", "forceQuickCombat", "borderScroll", "leftButtonDrag", "rightButtonDrag", "smoothDragging", "backgroundDimLevel", "hideBackground", "backgroundDimSmallWindows" ],
 			"properties" : {
 			"properties" : {
 				"heroMoveTime" : {
 				"heroMoveTime" : {
 					"type" : "number",
 					"type" : "number",

+ 4 - 0
config/schemas/spell.json

@@ -171,6 +171,10 @@
 			"type" : "boolean",
 			"type" : "boolean",
 			"description" : "If used as creature spell, unit can cast this spell on itself"
 			"description" : "If used as creature spell, unit can cast this spell on itself"
 		},
 		},
+		"canCastWithoutSkip" : {
+			"type" : "boolean",
+			"description" : "If used the creature will not skip the turn after casting a spell."
+		},
 		"gainChance" : {
 		"gainChance" : {
 			"type" : "object",
 			"type" : "object",
 			"description" : "Chance for this spell to appear in Mage Guild of a specific faction",
 			"description" : "Chance for this spell to appear in Mage Guild of a specific faction",

+ 28 - 2
config/widgets/settings/adventureOptionsTab.json

@@ -364,13 +364,25 @@
 				},
 				},
 				{
 				{
 					"text": "vcmi.adventureOptions.leftButtonDrag.hover",
 					"text": "vcmi.adventureOptions.leftButtonDrag.hover",
-					"created" : "desktop"
+					"created" : "keyboardMouse"
 				},
 				},
 				{
 				{
 					"text": "vcmi.adventureOptions.smoothDragging.hover"
 					"text": "vcmi.adventureOptions.smoothDragging.hover"
 				}
 				}
 			]
 			]
 		},
 		},
+		{
+			"type": "verticalLayout",
+			"customType": "labelDescription",
+			"position": {"x": 225, "y": 415},
+			"items":
+			[
+				{
+					"text": "vcmi.adventureOptions.rightButtonDrag.hover",
+					"created" : "keyboardMouse"
+				}
+			]
+		},
 		{
 		{
 			"type": "verticalLayout",
 			"type": "verticalLayout",
 			"customType": "checkbox",
 			"customType": "checkbox",
@@ -411,7 +423,7 @@
 					"name": "leftButtonDragCheckbox",
 					"name": "leftButtonDragCheckbox",
 					"help": "vcmi.adventureOptions.leftButtonDrag",
 					"help": "vcmi.adventureOptions.leftButtonDrag",
 					"callback": "leftButtonDragChanged",
 					"callback": "leftButtonDragChanged",
-					"created" : "desktop"
+					"created" : "keyboardMouse"
 				},
 				},
 				{
 				{
 					"name": "smoothDraggingCheckbox",
 					"name": "smoothDraggingCheckbox",
@@ -419,6 +431,20 @@
 					"callback": "smoothDraggingChanged"
 					"callback": "smoothDraggingChanged"
 				}
 				}
 			]
 			]
+		},
+		{
+			"type": "verticalLayout",
+			"customType": "checkbox",
+			"position": {"x": 190, "y": 413},
+			"items":
+			[
+				{
+					"name": "rightButtonDragCheckbox",
+					"help": "vcmi.adventureOptions.rightButtonDrag",
+					"callback": "rightButtonDragChanged",
+					"created" : "keyboardMouse"
+				}
+			]
 		}
 		}
 	]
 	]
 }
 }

+ 3 - 0
docs/modders/Entities_Format/Spell_Format.md

@@ -64,6 +64,9 @@
 		// If true, then creature capable of casting this spell can cast this spell on itself
 		// If true, then creature capable of casting this spell can cast this spell on itself
 		// If false, then creature can only cast this spell on other units
 		// If false, then creature can only cast this spell on other units
 		"canCastOnSelf" : false,
 		"canCastOnSelf" : false,
+
+		// If true the creature will not skip the turn after casting a spell
+		"canCastWithoutSkip": false,
 		
 		
 		// If true, spell won't be available on a map without water
 		// If true, spell won't be available on a map without water
 		"onlyOnWaterMap" : true,
 		"onlyOnWaterMap" : true,

+ 1 - 0
include/vcmi/spells/Spell.h

@@ -45,6 +45,7 @@ public:
 
 
 	virtual bool hasSchool(SpellSchool school) const = 0;
 	virtual bool hasSchool(SpellSchool school) const = 0;
 	virtual bool canCastOnSelf() const = 0;
 	virtual bool canCastOnSelf() const = 0;
+	virtual bool canCastWithoutSkip() const = 0;
 	virtual void forEachSchool(const SchoolCallback & cb) const = 0;
 	virtual void forEachSchool(const SchoolCallback & cb) const = 0;
 	virtual int32_t getCost(const int32_t skillLevel) const = 0;
 	virtual int32_t getCost(const int32_t skillLevel) const = 0;
 
 

+ 6 - 1
lib/battle/CUnitState.cpp

@@ -330,6 +330,7 @@ CUnitState::CUnitState():
 	drainedMana(false),
 	drainedMana(false),
 	fear(false),
 	fear(false),
 	hadMorale(false),
 	hadMorale(false),
+	castSpellThisTurn(false),
 	ghost(false),
 	ghost(false),
 	ghostPending(false),
 	ghostPending(false),
 	movedThisRound(false),
 	movedThisRound(false),
@@ -362,6 +363,7 @@ CUnitState & CUnitState::operator=(const CUnitState & other)
 	drainedMana = other.drainedMana;
 	drainedMana = other.drainedMana;
 	fear = other.fear;
 	fear = other.fear;
 	hadMorale = other.hadMorale;
 	hadMorale = other.hadMorale;
+	castSpellThisTurn = other.castSpellThisTurn;
 	ghost = other.ghost;
 	ghost = other.ghost;
 	ghostPending = other.ghostPending;
 	ghostPending = other.ghostPending;
 	movedThisRound = other.movedThisRound;
 	movedThisRound = other.movedThisRound;
@@ -532,7 +534,7 @@ bool CUnitState::hasClone() const
 
 
 bool CUnitState::canCast() const
 bool CUnitState::canCast() const
 {
 {
-	return casts.canUse(1);//do not check specific cast abilities here
+	return casts.canUse(1) && !castSpellThisTurn;//do not check specific cast abilities here
 }
 }
 
 
 bool CUnitState::isCaster() const
 bool CUnitState::isCaster() const
@@ -748,6 +750,7 @@ void CUnitState::serializeJson(JsonSerializeFormat & handler)
 	handler.serializeBool("drainedMana", drainedMana);
 	handler.serializeBool("drainedMana", drainedMana);
 	handler.serializeBool("fear", fear);
 	handler.serializeBool("fear", fear);
 	handler.serializeBool("hadMorale", hadMorale);
 	handler.serializeBool("hadMorale", hadMorale);
+	handler.serializeBool("castSpellThisTurn", castSpellThisTurn);
 	handler.serializeBool("ghost", ghost);
 	handler.serializeBool("ghost", ghost);
 	handler.serializeBool("ghostPending", ghostPending);
 	handler.serializeBool("ghostPending", ghostPending);
 	handler.serializeBool("moved", movedThisRound);
 	handler.serializeBool("moved", movedThisRound);
@@ -782,6 +785,7 @@ void CUnitState::reset()
 	drainedMana = false;
 	drainedMana = false;
 	fear = false;
 	fear = false;
 	hadMorale = false;
 	hadMorale = false;
+	castSpellThisTurn = false;
 	ghost = false;
 	ghost = false;
 	ghostPending = false;
 	ghostPending = false;
 	movedThisRound = false;
 	movedThisRound = false;
@@ -864,6 +868,7 @@ void CUnitState::afterNewRound()
 	waitedThisTurn = false;
 	waitedThisTurn = false;
 	movedThisRound = false;
 	movedThisRound = false;
 	hadMorale = false;
 	hadMorale = false;
+	castSpellThisTurn = false;
 	fear = false;
 	fear = false;
 	drainedMana = false;
 	drainedMana = false;
 	counterAttacks.reset();
 	counterAttacks.reset();

+ 1 - 0
lib/battle/CUnitState.h

@@ -141,6 +141,7 @@ public:
 	bool drainedMana;
 	bool drainedMana;
 	bool fear;
 	bool fear;
 	bool hadMorale;
 	bool hadMorale;
+	bool castSpellThisTurn;
 	bool ghost;
 	bool ghost;
 	bool ghostPending;
 	bool ghostPending;
 	bool movedThisRound;
 	bool movedThisRound;

+ 0 - 7
lib/bonuses/CBonusSystemNode.cpp

@@ -625,13 +625,6 @@ void CBonusSystemNode::limitBonuses(const BonusList &allBonuses, BonusList &out)
 	}
 	}
 }
 }
 
 
-TBonusListPtr CBonusSystemNode::limitBonuses(const BonusList &allBonuses) const
-{
-	auto ret = std::make_shared<BonusList>();
-	limitBonuses(allBonuses, *ret);
-	return ret;
-}
-
 void CBonusSystemNode::treeHasChanged()
 void CBonusSystemNode::treeHasChanged()
 {
 {
 	treeChanged++;
 	treeChanged++;

+ 1 - 2
lib/bonuses/CBonusSystemNode.h

@@ -55,6 +55,7 @@ private:
 	void getAllBonusesRec(BonusList &out, const CSelector & selector) const;
 	void getAllBonusesRec(BonusList &out, const CSelector & selector) const;
 	TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const;
 	TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const;
 	std::shared_ptr<Bonus> getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const;
 	std::shared_ptr<Bonus> getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const;
+	void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here
 
 
 	void getRedParents(TCNodes &out) const;  //retrieves list of red parent nodes (nodes bonuses propagate from)
 	void getRedParents(TCNodes &out) const;  //retrieves list of red parent nodes (nodes bonuses propagate from)
 	void getRedAncestors(TCNodes &out) const;
 	void getRedAncestors(TCNodes &out) const;
@@ -84,8 +85,6 @@ public:
 	explicit CBonusSystemNode(ENodeTypes NodeType);
 	explicit CBonusSystemNode(ENodeTypes NodeType);
 	virtual ~CBonusSystemNode();
 	virtual ~CBonusSystemNode();
 
 
-	void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here
-	TBonusListPtr limitBonuses(const BonusList &allBonuses) const; //same as above, returns out by val for convenience
 	TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const override;
 	TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const override;
 	void getParents(TCNodes &out) const;  //retrieves list of parent nodes (nodes to inherit bonuses from),
 	void getParents(TCNodes &out) const;  //retrieves list of parent nodes (nodes to inherit bonuses from),
 
 

+ 1 - 3
lib/bonuses/IBonusBearer.h

@@ -17,9 +17,7 @@ class DLL_LINKAGE IBonusBearer
 {
 {
 public:
 public:
 	//new bonusing node interface
 	//new bonusing node interface
-	// * selector is predicate that tests if HeroBonus matches our criteria
-	// * root is node on which call was made (nullptr will be replaced with this)
-	//interface
+	// * selector is predicate that tests if Bonus matches our criteria
 	IBonusBearer() = default;
 	IBonusBearer() = default;
 	virtual ~IBonusBearer() = default;
 	virtual ~IBonusBearer() = default;
 	virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const = 0;
 	virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const = 0;

+ 1 - 0
lib/networkPacks/NetPacksLib.cpp

@@ -2209,6 +2209,7 @@ void StartAction::applyGs(CGameState *gs)
 				st->waiting = false;
 				st->waiting = false;
 				st->defendingAnim = false;
 				st->defendingAnim = false;
 				st->movedThisRound = true;
 				st->movedThisRound = true;
+				st->castSpellThisTurn = ba.actionType == EActionType::MONSTER_SPELL;
 				break;
 				break;
 		}
 		}
 	}
 	}

+ 6 - 0
lib/spells/CSpellHandler.cpp

@@ -298,6 +298,11 @@ bool CSpell::canCastOnSelf() const
 	return castOnSelf;
 	return castOnSelf;
 }
 }
 
 
+bool CSpell::canCastWithoutSkip() const
+{
+	return castWithoutSkip;
+}
+
 const std::string & CSpell::getIconImmune() const
 const std::string & CSpell::getIconImmune() const
 {
 {
 	return iconImmune;
 	return iconImmune;
@@ -779,6 +784,7 @@ std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, c
 	}
 	}
 
 
 	spell->castOnSelf = json["canCastOnSelf"].Bool();
 	spell->castOnSelf = json["canCastOnSelf"].Bool();
+	spell->castWithoutSkip = json["canCastWithoutSkip"].Bool();
 	spell->level = static_cast<si32>(json["level"].Integer());
 	spell->level = static_cast<si32>(json["level"].Integer());
 	spell->power = static_cast<si32>(json["power"].Integer());
 	spell->power = static_cast<si32>(json["power"].Integer());
 
 

+ 2 - 0
lib/spells/CSpellHandler.h

@@ -167,6 +167,7 @@ public:
 
 
 	bool hasSchool(SpellSchool school) const override;
 	bool hasSchool(SpellSchool school) const override;
 	bool canCastOnSelf() const override;
 	bool canCastOnSelf() const override;
+	bool canCastWithoutSkip() const override;
 
 
 	/**
 	/**
 	 * Calls cb for each school this spell belongs to
 	 * Calls cb for each school this spell belongs to
@@ -296,6 +297,7 @@ private:
 	bool combat; //is this spell combat (true) or adventure (false)
 	bool combat; //is this spell combat (true) or adventure (false)
 	bool creatureAbility; //if true, only creatures can use this spell
 	bool creatureAbility; //if true, only creatures can use this spell
 	bool castOnSelf; // if set, creature caster can cast this spell on itself
 	bool castOnSelf; // if set, creature caster can cast this spell on itself
+	bool castWithoutSkip; // if set the creature will not skip the turn after casting a spell
 	si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative
 	si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative
 
 
 	std::unique_ptr<spells::ISpellMechanicsFactory> mechanics;//(!) do not serialize
 	std::unique_ptr<spells::ISpellMechanicsFactory> mechanics;//(!) do not serialize

+ 13 - 0
server/battles/BattleFlowProcessor.cpp

@@ -565,6 +565,19 @@ void BattleFlowProcessor::onActionMade(const CBattleInfoCallback & battle, const
 	if(battle.battleGetTacticDist() != 0)
 	if(battle.battleGetTacticDist() != 0)
 		return;
 		return;
 
 
+	// creature will not skip the turn after casting a spell if spell uses canCastWithoutSkip
+	if(ba.actionType == EActionType::MONSTER_SPELL)
+	{
+		assert(activeStack != nullptr);
+		assert(actedStack != nullptr);
+
+		if(actedStack->castSpellThisTurn && SpellID(ba.spell).toSpell()->canCastWithoutSkip())
+		{
+			setActiveStack(battle, actedStack);
+			return;
+		}
+	}
+
 	if (ba.isUnitAction())
 	if (ba.isUnitAction())
 	{
 	{
 		assert(activeStack != nullptr);
 		assert(activeStack != nullptr);

+ 1 - 0
test/mock/mock_spells_Spell.h

@@ -47,6 +47,7 @@ public:
 	MOCK_CONST_METHOD0(isSpecial, bool());
 	MOCK_CONST_METHOD0(isSpecial, bool());
 	MOCK_CONST_METHOD0(isMagical, bool());
 	MOCK_CONST_METHOD0(isMagical, bool());
 	MOCK_CONST_METHOD0(canCastOnSelf, bool());
 	MOCK_CONST_METHOD0(canCastOnSelf, bool());
+	MOCK_CONST_METHOD0(canCastWithoutSkip, bool());
 	MOCK_CONST_METHOD1(hasSchool, bool(SpellSchool));
 	MOCK_CONST_METHOD1(hasSchool, bool(SpellSchool));
 	MOCK_CONST_METHOD1(forEachSchool, void(const SchoolCallback &));
 	MOCK_CONST_METHOD1(forEachSchool, void(const SchoolCallback &));
 	MOCK_CONST_METHOD0(getCastSound, const std::string &());
 	MOCK_CONST_METHOD0(getCastSound, const std::string &());