diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 1cf794845..85e283e34 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/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.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.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.help" : "{Smooth Map Dragging}\n\nWhen enabled, map dragging has a modern run out effect.", "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Skip fading effects", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index 1ee92c1f6..7652ea23d 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/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.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.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.help" : "{Nahtloses Ziehen der Karte}\n\nWenn aktiviert hat das Ziehen der Karte einen sanften Auslaufeffekt.", "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Fading-Effekte überspringen", diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 4e05ce505..373bbec3e 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -23,6 +23,7 @@ #include "lobby/CLobbyScreen.h" #include "lobby/CBonusSelection.h" #include "windows/InfoWindows.h" +#include "windows/GUIClasses.h" #include "media/CMusicHandler.h" #include "media/IVideoPlayer.h" @@ -661,10 +662,13 @@ void CServerHandler::endGameplay() { GH.curInt = CMM.get(); CMM->enable(); + CMM->playMusic(); } 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 { 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(); - GH.windows().createAndPushWindow(ourCampaign->getOutroVideo(), ourCampaign->getVideoRim().empty() ? ImagePath::builtin("INTRORIM") : ourCampaign->getVideoRim(), [campaignScoreCalculator, statistic](){ + GH.windows().createAndPushWindow(ourCampaign->getOutroVideo(), ourCampaign->getVideoRim().empty() ? ImagePath::builtin("INTRORIM") : ourCampaign->getVideoRim(), false, 1, [campaignScoreCalculator, statistic](bool skipped){ GH.windows().createAndPushWindow(true, *campaignScoreCalculator, statistic); }); } diff --git a/client/Client.cpp b/client/Client.cpp index 43c708ea7..2d57375ca 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -13,6 +13,7 @@ #include "CGameInfo.h" #include "CPlayerInterface.h" +#include "PlayerLocalState.h" #include "CServerHandler.h" #include "ClientNetPackVisitors.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() { boost::unique_lock pathLock(pathCacheMutex); diff --git a/client/Client.h b/client/Client.h index c5b763a49..d63f70578 100644 --- a/client/Client.h +++ b/client/Client.h @@ -150,7 +150,9 @@ public: void battleFinished(const BattleID & battleID); 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 getPathsInfo(const CGHeroInstance * h); friend class CCallback; //handling players actions diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index aadcdcc54..cda16c4b3 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -101,7 +101,7 @@ void callBattleInterfaceIfPresentForBothSides(CClient & cl, const BattleID & bat { 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!"); return; @@ -161,14 +161,14 @@ void ApplyClientNetPackVisitor::visitSetMana(SetMana & pack) if(settings["session"]["headless"].Bool()) return; - for (auto window : GH.windows().findWindows()) + for(auto window : GH.windows().findWindows()) window->heroManaPointsChanged(h); } void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack) { const CGHeroInstance *h = cl.getHero(pack.hid); - cl.invalidatePaths(); + cl.updatePath(h); callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroMovePointsChanged, h); } @@ -229,7 +229,7 @@ void ApplyClientNetPackVisitor::visitSetStackType(SetStackType & pack) void ApplyClientNetPackVisitor::visitEraseStack(EraseStack & pack) { 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) @@ -237,15 +237,14 @@ void ApplyClientNetPackVisitor::visitSwapStacks(SwapStacks & pack) dispatchGarrisonChange(cl, 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) { 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) @@ -253,7 +252,10 @@ void ApplyClientNetPackVisitor::visitRebalanceStacks(RebalanceStacks & pack) dispatchGarrisonChange(cl, 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) @@ -266,7 +268,10 @@ void ApplyClientNetPackVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & p dispatchGarrisonChange(cl, 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) { + cl.updatePath(pack.artHolder); for(const auto & slotErase : pack.posPack) 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) 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); - 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) { 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) @@ -363,7 +370,7 @@ void ApplyClientNetPackVisitor::visitNewTurn(NewTurn & pack) { cl.invalidatePaths(); - if (pack.newWeekNotification) + if(pack.newWeekNotification) { const auto & newWeek = *pack.newWeekNotification; @@ -380,7 +387,7 @@ void ApplyClientNetPackVisitor::visitGiveBonus(GiveBonus & pack) case GiveBonus::ETarget::OBJECT: { const CGHeroInstance *h = gs.getHero(pack.id.as()); - if (h) + if(h) callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroBonusChanged, h, pack.bonus, true); } 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(); - if (lastHumanEndsGame) + if(lastHumanEndsGame) { assert(adventureInt); if(adventureInt) @@ -446,9 +453,9 @@ void ApplyClientNetPackVisitor::visitPlayerReinitInterface(PlayerReinitInterface { 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); callOnlyThatInterface(cl, player, &CGameInterface::yourTurn, QueryID::NONE); @@ -482,7 +489,7 @@ void ApplyClientNetPackVisitor::visitRemoveBonus(RemoveBonus & pack) case GiveBonus::ETarget::OBJECT: { const CGHeroInstance *h = gs.getHero(pack.whoID.as()); - if (h) + if(h) callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroBonusChanged, h, pack.bonus, false); } 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 - if (pack.what == ObjProperty::OWNER && CGI->mh) + if(pack.what == ObjProperty::OWNER && CGI->mh) { auto object = gs.getObjInstance(pack.id); 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 - if (pack.what == ObjProperty::OWNER && CGI->mh) + if(pack.what == ObjProperty::OWNER && CGI->mh) { auto object = gs.getObjInstance(pack.id); CGI->mh->onObjectInstantAdd(object, object->getOwner()); @@ -807,7 +814,7 @@ void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack & const CStack *activated = gs.getBattle(pack.battleID)->battleGetStackByID(pack.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() ? gs.getBattle(pack.battleID)->getSide(BattleSide::DEFENDER).color diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index f280b4d44..4689d8d57 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -33,6 +33,7 @@ #include "widgets/TextControls.h" #include "media/CMusicHandler.h" #include "media/IVideoPlayer.h" +#include "windows/GUIClasses.h" #include "../lib/CConfigHandler.h" #include "../lib/texts/CGeneralTextHandler.h" @@ -207,10 +208,10 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState & { auto bonusSel = std::make_shared(); 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(); - GH.windows().createAndPushWindow(handler.si->campState->getIntroVideo(), handler.si->campState->getVideoRim().empty() ? ImagePath::builtin("INTRORIM") : handler.si->campState->getVideoRim(), [bonusSel](){ + GH.windows().createAndPushWindow(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()) CCS->musich->playMusic(CSH->si->campState->getMusic(), true, false); GH.windows().pushWindow(bonusSel); diff --git a/client/eventsSDL/InputSourceMouse.cpp b/client/eventsSDL/InputSourceMouse.cpp index 4c463563a..6f4ab765e 100644 --- a/client/eventsSDL/InputSourceMouse.cpp +++ b/client/eventsSDL/InputSourceMouse.cpp @@ -34,7 +34,7 @@ InputSourceMouse::InputSourceMouse() void InputSourceMouse::handleEventMouseMotion(const SDL_MouseMotionEvent & motion) { 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; @@ -42,6 +42,8 @@ void InputSourceMouse::handleEventMouseMotion(const SDL_MouseMotionEvent & motio GH.events().dispatchGesturePanning(middleClickPosition, newPosition, distance); else if (mouseButtonsMask & SDL_BUTTON(SDL_BUTTON_LEFT)) GH.events().dispatchMouseDragged(newPosition, distance); + else if (mouseButtonsMask & SDL_BUTTON(SDL_BUTTON_RIGHT)) + GH.events().dispatchMouseDraggedPopup(newPosition, distance); else GH.input().setCursorPosition(newPosition); } diff --git a/client/gui/EventDispatcher.cpp b/client/gui/EventDispatcher.cpp index 0518d305f..b43f8d209 100644 --- a/client/gui/EventDispatcher.cpp +++ b/client/gui/EventDispatcher.cpp @@ -35,6 +35,7 @@ void EventDispatcher::processLists(ui16 activityFlag, const Functor & cb) processList(AEventsReceiver::HOVER, hoverable); processList(AEventsReceiver::MOVE, motioninterested); processList(AEventsReceiver::DRAG, draginterested); + processList(AEventsReceiver::DRAG_POPUP, dragPopupInterested); processList(AEventsReceiver::KEYBOARD, keyinterested); processList(AEventsReceiver::TIME, timeinterested); processList(AEventsReceiver::WHEEL, wheelInterested); @@ -433,3 +434,10 @@ void EventDispatcher::dispatchMouseDragged(const Point & currentPosition, const elem->mouseDragged(currentPosition, lastUpdateDistance); } } + +void EventDispatcher::dispatchMouseDraggedPopup(const Point & currentPosition, const Point & lastUpdateDistance) +{ + EventReceiversList diCopy = dragPopupInterested; + for(auto & elem : diCopy) + elem->mouseDraggedPopup(currentPosition, lastUpdateDistance); +} diff --git a/client/gui/EventDispatcher.h b/client/gui/EventDispatcher.h index cd61a6b0c..4c212779e 100644 --- a/client/gui/EventDispatcher.h +++ b/client/gui/EventDispatcher.h @@ -30,6 +30,7 @@ class EventDispatcher EventReceiversList keyinterested; EventReceiversList motioninterested; EventReceiversList draginterested; + EventReceiversList dragPopupInterested; EventReceiversList timeinterested; EventReceiversList wheelInterested; EventReceiversList doubleClickInterested; @@ -66,6 +67,7 @@ public: void dispatchMouseMoved(const Point & distance, const Point & position); void dispatchMouseDragged(const Point & currentPosition, const Point & lastUpdateDistance); + void dispatchMouseDraggedPopup(const Point & currentPosition, const Point & lastUpdateDistance); void dispatchShowPopup(const Point & position, int tolerance); void dispatchClosePopup(const Point & position); diff --git a/client/gui/EventsReceiver.h b/client/gui/EventsReceiver.h index 6727dec87..da194890f 100644 --- a/client/gui/EventsReceiver.h +++ b/client/gui/EventsReceiver.h @@ -61,6 +61,7 @@ public: virtual void wheelScrolled(int distance) {} virtual void mouseMoved(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 virtual void hover(bool on) {} @@ -97,7 +98,8 @@ public: TEXTINPUT = 512, GESTURE = 1024, DRAG = 2048, - INPUT_MODE_CHANGE = 4096 + INPUT_MODE_CHANGE = 4096, + DRAG_POPUP = 8192 }; /// Returns true if element is currently hovered by mouse diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index d0790335f..b4dcc7f8e 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -58,37 +58,6 @@ #include "../../lib/mapObjects/CGHeroInstance.h" - -CampaignRimVideo::CampaignRimVideo(VideoPath video, ImagePath rim, std::function closeCb) - : CWindowObject(BORDERED), closeCb(closeCb) -{ - OBJECT_CONSTRUCTION; - - addUsedEvents(LCLICK | KEYBOARD); - - pos = center(Rect(0, 0, 800, 600)); - - videoPlayer = std::make_shared(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 CBonusSelection::getCampaign() { return CSH->si->campState; diff --git a/client/lobby/CBonusSelection.h b/client/lobby/CBonusSelection.h index 176429f8d..b9b341c60 100644 --- a/client/lobby/CBonusSelection.h +++ b/client/lobby/CBonusSelection.h @@ -33,20 +33,6 @@ class VideoWidgetOnce; class CBonusSelection; -class CampaignRimVideo : public CWindowObject -{ - std::shared_ptr videoPlayer; - - std::function closeCb; - - void exit(); -public: - CampaignRimVideo(VideoPath video, ImagePath rim, std::function closeCb); - - void clickPressed(const Point & cursorPosition) override; - void keyPressed(EShortcut key) override; -}; - /// Campaign screen where you can choose one out of three starting bonuses class CBonusSelection : public CWindowObject { diff --git a/client/mainmenu/CHighScoreScreen.cpp b/client/mainmenu/CHighScoreScreen.cpp index 6676dcb1c..953f2ce2f 100644 --- a/client/mainmenu/CHighScoreScreen.cpp +++ b/client/mainmenu/CHighScoreScreen.cpp @@ -12,6 +12,7 @@ #include "CHighScoreScreen.h" #include "CStatisticScreen.h" +#include "CMainMenu.h" #include "../gui/CGuiHandler.h" #include "../gui/WindowHandler.h" #include "../gui/Shortcut.h" @@ -170,6 +171,7 @@ void CHighScoreScreen::buttonResetClick() void CHighScoreScreen::buttonExitClick() { close(); + CMM->playMusic(); } CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc, const StatisticDataSet & statistic) diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 54cd0d7e0..d19275185 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -18,6 +18,7 @@ #include "../lobby/CSelectionBase.h" #include "../lobby/CLobbyScreen.h" #include "../media/IMusicPlayer.h" +#include "../media/IVideoPlayer.h" #include "../gui/CursorHandler.h" #include "../windows/GUIClasses.h" #include "../gui/CGuiHandler.h" @@ -117,7 +118,6 @@ void CMenuScreen::show(Canvas & to) void CMenuScreen::activate() { - CCS->musich->playMusic(AudioPath::builtin("Music/MainMenu"), true, true); CIntObject::activate(); } @@ -300,6 +300,35 @@ CMainMenu::~CMainMenu() GH.curInt = nullptr; } +void CMainMenu::playIntroVideos() +{ + auto playVideo = [](std::string video, bool rim, float scaleFactor, std::function cb){ + if(CCS->videoh->open(VideoPath::builtin(video), scaleFactor)) + GH.windows().createAndPushWindow(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() { // check if screen was resized while main menu was inactive - e.g. in gameplay mode diff --git a/client/mainmenu/CMainMenu.h b/client/mainmenu/CMainMenu.h index d7e9c5a1e..82b684a43 100644 --- a/client/mainmenu/CMainMenu.h +++ b/client/mainmenu/CMainMenu.h @@ -142,6 +142,8 @@ class CMainMenu : public CIntObject, public IUpdateable, public std::enable_shar { std::shared_ptr backgroundAroundMenu; + std::vector videoPlayList; + CMainMenu(); //Use CMainMenu::create public: @@ -162,6 +164,8 @@ public: static std::shared_ptr createPicture(const JsonNode & config); + void playIntroVideos(); + void playMusic(); }; /// Simple window to enter the server's address. diff --git a/client/mapView/MapViewActions.cpp b/client/mapView/MapViewActions.cpp index 6c87edba4..6b2041a57 100644 --- a/client/mapView/MapViewActions.cpp +++ b/client/mapView/MapViewActions.cpp @@ -34,7 +34,7 @@ MapViewActions::MapViewActions(MapView & owner, const std::shared_ptrgetPixelsVisibleDimensions().x; 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 & context) @@ -101,6 +101,11 @@ void MapViewActions::mouseDragged(const Point & cursorPosition, const Point & la 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) { owner.onMapSwiped(lastUpdateDistance); diff --git a/client/mapView/MapViewActions.h b/client/mapView/MapViewActions.h index 02dd877aa..463f70780 100644 --- a/client/mapView/MapViewActions.h +++ b/client/mapView/MapViewActions.h @@ -42,6 +42,7 @@ public: void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override; void mouseMoved(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; bool dragActive; diff --git a/client/media/CEmptyVideoPlayer.h b/client/media/CEmptyVideoPlayer.h index 6dc91abaf..619591c9b 100644 --- a/client/media/CEmptyVideoPlayer.h +++ b/client/media/CEmptyVideoPlayer.h @@ -14,18 +14,12 @@ class CEmptyVideoPlayer final : public IVideoPlayer { 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 { } /// Load video from specified path - std::unique_ptr open(const VideoPath & name, bool scaleToScreen) override + std::unique_ptr open(const VideoPath & name, float scaleFactor) override { return nullptr; }; diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index 8a648646f..51e17ff10 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -173,18 +173,10 @@ void CVideoInstance::openVideo() openCodec(findVideoStream()); } -void CVideoInstance::prepareOutput(bool scaleToScreenSize, bool useTextureOutput) +void CVideoInstance::prepareOutput(float scaleFactor, bool useTextureOutput) { //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 if (useTextureOutput) @@ -352,10 +344,7 @@ FFMpegStream::~FFMpegStream() 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) @@ -575,7 +564,7 @@ std::pair, si64> CAudioInstance::extractAudio(const Vide 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; CAudioInstance audio; @@ -587,7 +576,7 @@ bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & po return true; instance.openVideo(); - instance.prepareOutput(scale, true); + instance.prepareOutput(1, true); auto lastTimePoint = boost::chrono::steady_clock::now(); @@ -633,17 +622,12 @@ bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & po 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) { - openAndPlayVideoImpl(name, position * GH.screenHandler().getScalingFactor(), false, false, false); + openAndPlayVideoImpl(name, position * GH.screenHandler().getScalingFactor(), false, false); } -std::unique_ptr CVideoPlayer::open(const VideoPath & name, bool scaleToScreen) +std::unique_ptr CVideoPlayer::open(const VideoPath & name, float scaleFactor) { auto result = std::make_unique(); @@ -651,7 +635,7 @@ std::unique_ptr CVideoPlayer::open(const VideoPath & name, bool return nullptr; result->openVideo(); - result->prepareOutput(scaleToScreen, false); + result->prepareOutput(scaleFactor, false); result->loadNextFrame(); // prepare 1st frame return result; diff --git a/client/media/CVideoHandler.h b/client/media/CVideoHandler.h index 3fc6256cd..d40582b62 100644 --- a/client/media/CVideoHandler.h +++ b/client/media/CVideoHandler.h @@ -80,7 +80,7 @@ class CVideoInstance final : public IVideoInstance, public FFMpegStream /// video playback current progress, in seconds double frameTime = 0.0; - void prepareOutput(bool scaleToScreenSize, bool useTextureOutput); + void prepareOutput(float scaleFactor, bool useTextureOutput); public: ~CVideoInstance(); @@ -97,13 +97,12 @@ public: 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); public: - bool playIntroVideo(const VideoPath & name) final; void playSpellbookAnimation(const VideoPath & name, const Point & position) final; - std::unique_ptr open(const VideoPath & name, bool scaleToScreen) final; + std::unique_ptr open(const VideoPath & name, float scaleFactor) final; std::pair, si64> getAudio(const VideoPath & videoToOpen) final; }; diff --git a/client/media/IVideoPlayer.h b/client/media/IVideoPlayer.h index a867e9b24..2c979a088 100644 --- a/client/media/IVideoPlayer.h +++ b/client/media/IVideoPlayer.h @@ -38,14 +38,11 @@ public: class IVideoPlayer : boost::noncopyable { 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 virtual void playSpellbookAnimation(const VideoPath & name, const Point & position) = 0; /// Load video from specified path. Returns nullptr on failure - virtual std::unique_ptr open(const VideoPath & name, bool scaleToScreen) = 0; + virtual std::unique_ptr open(const VideoPath & name, float scaleFactor) = 0; /// Extracts audio data from provided video in wav format virtual std::pair, si64> getAudio(const VideoPath & videoToOpen) = 0; diff --git a/client/widgets/VideoWidget.cpp b/client/widgets/VideoWidget.cpp index 2d68b82ca..0fa6570cf 100644 --- a/client/widgets/VideoWidget.cpp +++ b/client/widgets/VideoWidget.cpp @@ -17,7 +17,12 @@ #include "../render/Canvas.h" 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); pos += position; @@ -28,7 +33,7 @@ VideoWidgetBase::~VideoWidgetBase() = default; void VideoWidgetBase::playVideo(const VideoPath & fileToPlay) { - videoInstance = CCS->videoh->open(fileToPlay, false); + videoInstance = CCS->videoh->open(fileToPlay, scaleFactor); if (videoInstance) { 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 & callback) + : VideoWidgetBase(position, video, playAudio, scaleFactor) + , callback(callback) +{ +} + void VideoWidgetOnce::onPlaybackFinished() { callback(); diff --git a/client/widgets/VideoWidget.h b/client/widgets/VideoWidget.h index f7c963d51..b0264d58d 100644 --- a/client/widgets/VideoWidget.h +++ b/client/widgets/VideoWidget.h @@ -22,6 +22,7 @@ class VideoWidgetBase : public CIntObject std::pair, si64> audioData = {nullptr, 0}; int audioHandle = -1; bool playAudio = false; + float scaleFactor = 1.0; void loadAudio(const VideoPath & file); void startAudio(); @@ -29,6 +30,7 @@ class VideoWidgetBase : public CIntObject protected: VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio); + VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio, float scaleFactor); virtual void onPlaybackFinished() = 0; void playVideo(const VideoPath & video); @@ -62,4 +64,5 @@ class VideoWidgetOnce final: public VideoWidgetBase void onPlaybackFinished() final; public: VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, const std::function & callback); + VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, float scaleFactor, const std::function & callback); }; diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index d85f4a10a..ea9e7a77f 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1608,3 +1608,50 @@ void CObjectListWindow::keyPressed(EShortcut key) list->scrollTo(sel); changeSelection(sel); } + +VideoWindow::VideoWindow(VideoPath video, ImagePath rim, bool showBackground, float scaleFactor, std::function closeCb) + : CWindowObject(BORDERED | SHADOW_DISABLED | NEEDS_ANIMATED_BACKGROUND), closeCb(closeCb) +{ + OBJECT_CONSTRUCTION; + + addUsedEvents(LCLICK | KEYBOARD); + + if(!rim.empty()) + { + videoPlayer = std::make_shared(Point(80, 186), video, true, [this](){ exit(false); }); + pos = center(Rect(0, 0, 800, 600)); + } + else + { + videoPlayer = std::make_shared(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(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 +} diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index b3c1902fb..4c7b68a75 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -43,6 +43,7 @@ class CAnimImage; class CFilledTexture; class IImage; class VideoWidget; +class VideoWidgetOnce; enum class EUserEvent; @@ -501,3 +502,18 @@ public: CThievesGuildWindow(const CGObjectInstance * _owner); }; +class VideoWindow : public CWindowObject +{ + std::shared_ptr videoPlayer; + std::shared_ptr backgroundAroundWindow; + + std::function closeCb; + + void exit(bool skipped); +public: + VideoWindow(VideoPath video, ImagePath rim, bool showBackground, float scaleFactor, std::function closeCb); + + void clickPressed(const Point & cursorPosition) override; + void keyPressed(EShortcut key) override; + bool receiveEvent(const Point & position, int eventType) const override; +}; diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index dbd5060e1..cabeeef13 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -245,8 +245,11 @@ void CRClickPopup::createAndPush(const CGObjectInstance * obj, const Point & p, } } -CRClickPopupInt::CRClickPopupInt(const std::shared_ptr & our) +CRClickPopupInt::CRClickPopupInt(const std::shared_ptr & our) : + dragDistance(Point(0, 0)) { + addUsedEvents(DRAG_POPUP); + CCS->curh->hide(); inner = our; addChild(our.get(), false); @@ -257,6 +260,17 @@ CRClickPopupInt::~CRClickPopupInt() 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) { auto bounds = adventureInt->terrainAreaPixels(); @@ -267,6 +281,18 @@ Point CInfoBoxPopup::toScreen(Point 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) : CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("TOWNQVBK"), toScreen(position)) { @@ -275,6 +301,8 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town) OBJECT_CONSTRUCTION; tooltip = std::make_shared(Point(9, 10), iah); + + addUsedEvents(DRAG_POPUP); } CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero) @@ -285,6 +313,8 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero) OBJECT_CONSTRUCTION; tooltip = std::make_shared(Point(9, 10), iah); + + addUsedEvents(DRAG_POPUP); } CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr) @@ -295,6 +325,8 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr) OBJECT_CONSTRUCTION; tooltip = std::make_shared(Point(9, 10), iah); + + addUsedEvents(DRAG_POPUP); } CInfoBoxPopup::CInfoBoxPopup(Point position, const CGCreature * creature) @@ -302,6 +334,8 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGCreature * creature) { OBJECT_CONSTRUCTION; tooltip = std::make_shared(Point(9, 10), creature); + + addUsedEvents(DRAG_POPUP); } std::shared_ptr diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h index 5da10014b..a06416934 100644 --- a/client/windows/InfoWindows.h +++ b/client/windows/InfoWindows.h @@ -78,9 +78,13 @@ class CRClickPopupInt : public CRClickPopup { std::shared_ptr inner; + Point dragDistance; + public: CRClickPopupInt(const std::shared_ptr & our); ~CRClickPopupInt(); + + void mouseDraggedPopup(const Point & cursorPosition, const Point & lastUpdateDistance) override; }; /// 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 tooltip; Point toScreen(Point pos); + Point dragDistance; + public: CInfoBoxPopup(Point position, const CGTownInstance * town); CInfoBoxPopup(Point position, const CGHeroInstance * hero); CInfoBoxPopup(Point position, const CGGarrison * garr); CInfoBoxPopup(Point position, const CGCreature * creature); + + void mouseDraggedPopup(const Point & cursorPosition, const Point & lastUpdateDistance) override; }; /// component selection window diff --git a/client/windows/settings/AdventureOptionsTab.cpp b/client/windows/settings/AdventureOptionsTab.cpp index 21e289b60..0fa168a7b 100644 --- a/client/windows/settings/AdventureOptionsTab.cpp +++ b/client/windows/settings/AdventureOptionsTab.cpp @@ -11,6 +11,7 @@ #include "AdventureOptionsTab.h" +#include "../../eventsSDL/InputHandler.h" #include "../../../lib/filesystem/ResourcePath.h" #include "../../gui/CGuiHandler.h" #include "../../widgets/Buttons.h" @@ -36,6 +37,9 @@ AdventureOptionsTab::AdventureOptionsTab() OBJECT_CONSTRUCTION; 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 addConditional("mobile", true); addConditional("desktop", false); @@ -126,6 +130,10 @@ AdventureOptionsTab::AdventureOptionsTab() { return setBoolSetting("adventure", "leftButtonDrag", value); }); + addCallback("rightButtonDragChanged", [](bool value) + { + return setBoolSetting("adventure", "rightButtonDrag", value); + }); addCallback("smoothDraggingChanged", [](bool value) { return setBoolSetting("adventure", "smoothDragging", value); @@ -177,6 +185,10 @@ AdventureOptionsTab::AdventureOptionsTab() if (leftButtonDragCheckbox) leftButtonDragCheckbox->setSelected(settings["adventure"]["leftButtonDrag"].Bool()); + std::shared_ptr rightButtonDragCheckbox = widget("rightButtonDragCheckbox"); + if (rightButtonDragCheckbox) + rightButtonDragCheckbox->setSelected(settings["adventure"]["rightButtonDrag"].Bool()); + std::shared_ptr smoothDraggingCheckbox = widget("smoothDraggingCheckbox"); if (smoothDraggingCheckbox) smoothDraggingCheckbox->setSelected(settings["adventure"]["smoothDragging"].Bool()); diff --git a/client/windows/settings/GeneralOptionsTab.cpp b/client/windows/settings/GeneralOptionsTab.cpp index b0a965414..d8ead20f8 100644 --- a/client/windows/settings/GeneralOptionsTab.cpp +++ b/client/windows/settings/GeneralOptionsTab.cpp @@ -97,7 +97,9 @@ GeneralOptionsTab::GeneralOptionsTab() OBJECT_CONSTRUCTION; 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 addConditional("mobile", true); addConditional("desktop", false); diff --git a/client/windows/settings/SettingsMainWindow.cpp b/client/windows/settings/SettingsMainWindow.cpp index 6b7b53a1b..05562bae5 100644 --- a/client/windows/settings/SettingsMainWindow.cpp +++ b/client/windows/settings/SettingsMainWindow.cpp @@ -45,6 +45,8 @@ SettingsMainWindow::SettingsMainWindow(BattleInterface * parentBattleUi) : Inter addCallback("closeWindow", [this](int) { backButtonCallback(); }); build(config); + addUsedEvents(INPUT_MODE_CHANGE); + std::shared_ptr background = widget("background"); pos.w = background->pos.w; pos.h = background->pos.h; @@ -196,3 +198,8 @@ void SettingsMainWindow::onScreenResize() if (tab) tab->updateResolutionSelector(); } + +void SettingsMainWindow::inputModeChanged(InputMode mode) +{ + tabContentArea->reset(); +} diff --git a/client/windows/settings/SettingsMainWindow.h b/client/windows/settings/SettingsMainWindow.h index 7b26921df..5813ac46b 100644 --- a/client/windows/settings/SettingsMainWindow.h +++ b/client/windows/settings/SettingsMainWindow.h @@ -42,5 +42,6 @@ public: void showAll(Canvas & to) override; void onScreenResize() override; + void inputModeChanged(InputMode mode) override; }; diff --git a/clientapp/EntryPoint.cpp b/clientapp/EntryPoint.cpp index e32303f07..8d0ffa278 100644 --- a/clientapp/EntryPoint.cpp +++ b/clientapp/EntryPoint.cpp @@ -30,6 +30,7 @@ #include "../client/render/Graphics.h" #include "../client/render/IRenderHandler.h" #include "../client/render/IScreenHandler.h" +#include "../client/lobby/CBonusSelection.h" #include "../client/windows/CMessage.h" #include "../client/windows/InfoWindows.h" @@ -65,7 +66,6 @@ static std::optional criticalInitializationError; #ifndef VCMI_IOS void processCommand(const std::string &message); #endif -void playIntro(); [[noreturn]] static void quitApplication(); static void mainLoop(); @@ -319,13 +319,6 @@ int main(int argc, char * argv[]) init(); #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 #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(); 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 names; @@ -402,18 +401,6 @@ int main(int argc, char * argv[]) 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() { #ifndef VCMI_UNIX diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 7f08a6844..c8cab24d0 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -322,7 +322,7 @@ "type" : "object", "additionalProperties" : false, "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" : { "heroMoveTime" : { "type" : "number", diff --git a/config/schemas/spell.json b/config/schemas/spell.json index dff3bc2df..d4b00487b 100644 --- a/config/schemas/spell.json +++ b/config/schemas/spell.json @@ -171,6 +171,10 @@ "type" : "boolean", "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" : { "type" : "object", "description" : "Chance for this spell to appear in Mage Guild of a specific faction", diff --git a/config/widgets/settings/adventureOptionsTab.json b/config/widgets/settings/adventureOptionsTab.json index 57886e5e2..3cddfb4ac 100644 --- a/config/widgets/settings/adventureOptionsTab.json +++ b/config/widgets/settings/adventureOptionsTab.json @@ -364,13 +364,25 @@ }, { "text": "vcmi.adventureOptions.leftButtonDrag.hover", - "created" : "desktop" + "created" : "keyboardMouse" }, { "text": "vcmi.adventureOptions.smoothDragging.hover" } ] }, + { + "type": "verticalLayout", + "customType": "labelDescription", + "position": {"x": 225, "y": 415}, + "items": + [ + { + "text": "vcmi.adventureOptions.rightButtonDrag.hover", + "created" : "keyboardMouse" + } + ] + }, { "type": "verticalLayout", "customType": "checkbox", @@ -411,7 +423,7 @@ "name": "leftButtonDragCheckbox", "help": "vcmi.adventureOptions.leftButtonDrag", "callback": "leftButtonDragChanged", - "created" : "desktop" + "created" : "keyboardMouse" }, { "name": "smoothDraggingCheckbox", @@ -419,6 +431,20 @@ "callback": "smoothDraggingChanged" } ] + }, + { + "type": "verticalLayout", + "customType": "checkbox", + "position": {"x": 190, "y": 413}, + "items": + [ + { + "name": "rightButtonDragCheckbox", + "help": "vcmi.adventureOptions.rightButtonDrag", + "callback": "rightButtonDragChanged", + "created" : "keyboardMouse" + } + ] } ] } diff --git a/docs/modders/Entities_Format/Spell_Format.md b/docs/modders/Entities_Format/Spell_Format.md index 5e17e817e..41461b606 100644 --- a/docs/modders/Entities_Format/Spell_Format.md +++ b/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 false, then creature can only cast this spell on other units "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 "onlyOnWaterMap" : true, diff --git a/include/vcmi/spells/Spell.h b/include/vcmi/spells/Spell.h index 1822639e1..6b40bf258 100644 --- a/include/vcmi/spells/Spell.h +++ b/include/vcmi/spells/Spell.h @@ -45,6 +45,7 @@ public: virtual bool hasSchool(SpellSchool school) const = 0; virtual bool canCastOnSelf() const = 0; + virtual bool canCastWithoutSkip() const = 0; virtual void forEachSchool(const SchoolCallback & cb) const = 0; virtual int32_t getCost(const int32_t skillLevel) const = 0; diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index de2704058..131fc2a23 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -330,6 +330,7 @@ CUnitState::CUnitState(): drainedMana(false), fear(false), hadMorale(false), + castSpellThisTurn(false), ghost(false), ghostPending(false), movedThisRound(false), @@ -362,6 +363,7 @@ CUnitState & CUnitState::operator=(const CUnitState & other) drainedMana = other.drainedMana; fear = other.fear; hadMorale = other.hadMorale; + castSpellThisTurn = other.castSpellThisTurn; ghost = other.ghost; ghostPending = other.ghostPending; movedThisRound = other.movedThisRound; @@ -532,7 +534,7 @@ bool CUnitState::hasClone() 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 @@ -748,6 +750,7 @@ void CUnitState::serializeJson(JsonSerializeFormat & handler) handler.serializeBool("drainedMana", drainedMana); handler.serializeBool("fear", fear); handler.serializeBool("hadMorale", hadMorale); + handler.serializeBool("castSpellThisTurn", castSpellThisTurn); handler.serializeBool("ghost", ghost); handler.serializeBool("ghostPending", ghostPending); handler.serializeBool("moved", movedThisRound); @@ -782,6 +785,7 @@ void CUnitState::reset() drainedMana = false; fear = false; hadMorale = false; + castSpellThisTurn = false; ghost = false; ghostPending = false; movedThisRound = false; @@ -864,6 +868,7 @@ void CUnitState::afterNewRound() waitedThisTurn = false; movedThisRound = false; hadMorale = false; + castSpellThisTurn = false; fear = false; drainedMana = false; counterAttacks.reset(); diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index 02422e2e1..b5451ba89 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -141,6 +141,7 @@ public: bool drainedMana; bool fear; bool hadMorale; + bool castSpellThisTurn; bool ghost; bool ghostPending; bool movedThisRound; diff --git a/lib/bonuses/CBonusSystemNode.cpp b/lib/bonuses/CBonusSystemNode.cpp index 2fca57dff..7b944eefb 100644 --- a/lib/bonuses/CBonusSystemNode.cpp +++ b/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(); - limitBonuses(allBonuses, *ret); - return ret; -} - void CBonusSystemNode::treeHasChanged() { treeChanged++; diff --git a/lib/bonuses/CBonusSystemNode.h b/lib/bonuses/CBonusSystemNode.h index e5b320af3..460957f8b 100644 --- a/lib/bonuses/CBonusSystemNode.h +++ b/lib/bonuses/CBonusSystemNode.h @@ -55,6 +55,7 @@ private: void getAllBonusesRec(BonusList &out, const CSelector & selector) const; TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const; std::shared_ptr getUpdatedBonus(const std::shared_ptr & 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 getRedAncestors(TCNodes &out) const; @@ -84,8 +85,6 @@ public: explicit CBonusSystemNode(ENodeTypes NodeType); 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; void getParents(TCNodes &out) const; //retrieves list of parent nodes (nodes to inherit bonuses from), diff --git a/lib/bonuses/IBonusBearer.h b/lib/bonuses/IBonusBearer.h index b37dbfddb..b272edea4 100644 --- a/lib/bonuses/IBonusBearer.h +++ b/lib/bonuses/IBonusBearer.h @@ -17,9 +17,7 @@ class DLL_LINKAGE IBonusBearer { public: //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; virtual ~IBonusBearer() = default; virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const = 0; diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index bccbbef61..43309a8c5 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -2209,6 +2209,7 @@ void StartAction::applyGs(CGameState *gs) st->waiting = false; st->defendingAnim = false; st->movedThisRound = true; + st->castSpellThisTurn = ba.actionType == EActionType::MONSTER_SPELL; break; } } diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 05d639f62..4e3db1a5c 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -298,6 +298,11 @@ bool CSpell::canCastOnSelf() const return castOnSelf; } +bool CSpell::canCastWithoutSkip() const +{ + return castWithoutSkip; +} + const std::string & CSpell::getIconImmune() const { return iconImmune; @@ -779,6 +784,7 @@ std::shared_ptr CSpellHandler::loadFromJson(const std::string & scope, c } spell->castOnSelf = json["canCastOnSelf"].Bool(); + spell->castWithoutSkip = json["canCastWithoutSkip"].Bool(); spell->level = static_cast(json["level"].Integer()); spell->power = static_cast(json["power"].Integer()); diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index 32b91f3fb..c16e10ceb 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -167,6 +167,7 @@ public: bool hasSchool(SpellSchool school) const override; bool canCastOnSelf() const override; + bool canCastWithoutSkip() const override; /** * 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 creatureAbility; //if true, only creatures can use this spell 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 std::unique_ptr mechanics;//(!) do not serialize diff --git a/server/battles/BattleFlowProcessor.cpp b/server/battles/BattleFlowProcessor.cpp index c8beebb63..ce70d21fd 100644 --- a/server/battles/BattleFlowProcessor.cpp +++ b/server/battles/BattleFlowProcessor.cpp @@ -565,6 +565,19 @@ void BattleFlowProcessor::onActionMade(const CBattleInfoCallback & battle, const if(battle.battleGetTacticDist() != 0) 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()) { assert(activeStack != nullptr); diff --git a/test/mock/mock_spells_Spell.h b/test/mock/mock_spells_Spell.h index de03559eb..2d0ae31d1 100644 --- a/test/mock/mock_spells_Spell.h +++ b/test/mock/mock_spells_Spell.h @@ -47,6 +47,7 @@ public: MOCK_CONST_METHOD0(isSpecial, bool()); MOCK_CONST_METHOD0(isMagical, bool()); MOCK_CONST_METHOD0(canCastOnSelf, bool()); + MOCK_CONST_METHOD0(canCastWithoutSkip, bool()); MOCK_CONST_METHOD1(hasSchool, bool(SpellSchool)); MOCK_CONST_METHOD1(forEachSchool, void(const SchoolCallback &)); MOCK_CONST_METHOD0(getCastSound, const std::string &());