diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..c3d859191 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +charset = utf-8 +indent_style = tab +insert_final_newline = true +spelling_language = en-US +trim_trailing_whitespace = true + +[{*.py,CMakePresets.json}] +indent_style = space +indent_size = 4 + +[*.{md,yml}] +indent_style = space +indent_size = 2 + +[*.ui] +indent_style = space +indent_size = 1 diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index e639c9e78..0e8a62395 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "../../lib/ArtifactUtils.h" +#include "../../lib/AsyncRunner.h" #include "../../lib/UnlockGuard.h" #include "../../lib/StartInfo.h" #include "../../lib/entities/building/CBuilding.h" @@ -32,8 +33,6 @@ #include "AIGateway.h" #include "Goals/Goals.h" -static tbb::task_arena executeActionAsyncArena; - namespace NKAI { @@ -73,7 +72,7 @@ AIGateway::AIGateway() destinationTeleport = ObjectInstanceID(); destinationTeleportPos = int3(-1); nullkiller.reset(new Nullkiller()); - asyncTasks = std::make_unique(); + asyncTasks = std::make_unique(); } AIGateway::~AIGateway() @@ -593,11 +592,11 @@ void AIGateway::yourTurn(QueryID queryID) nullkiller->makingTurnInterrupption.reset(); - executeActionAsyncArena.enqueue(asyncTasks->defer([this]() + asyncTasks->run([this]() { ScopedThreadName guard("NKAI::makingTurn"); makeTurn(); - })); + }); } void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector & skills, QueryID queryID) @@ -1608,13 +1607,13 @@ void AIGateway::executeActionAsync(const std::string & description, const std::f if (!asyncTasks) throw std::runtime_error("Attempt to execute task on shut down AI state!"); - executeActionAsyncArena.enqueue(asyncTasks->defer([this, description, whatToDo]() + asyncTasks->run([this, description, whatToDo]() { ScopedThreadName guard("NKAI::" + description); SET_GLOBAL_STATE(this); std::shared_lock gsLock(CGameState::mutex); whatToDo(); - })); + }); } void AIGateway::lostHero(HeroPtr h) diff --git a/AI/Nullkiller/AIGateway.h b/AI/Nullkiller/AIGateway.h index 55caf59ca..73d3707d0 100644 --- a/AI/Nullkiller/AIGateway.h +++ b/AI/Nullkiller/AIGateway.h @@ -22,8 +22,9 @@ #include "Pathfinding/AIPathfinder.h" #include "Engine/Nullkiller.h" -#include -#include +VCMI_LIB_NAMESPACE_BEGIN +class AsyncRunner; +VCMI_LIB_NAMESPACE_END namespace NKAI { @@ -74,7 +75,7 @@ public: AIStatus status; std::string battlename; std::shared_ptr myCb; - std::unique_ptr asyncTasks; + std::unique_ptr asyncTasks; public: ObjectInstanceID selectedObject; diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 6355cf303..4d37fa0bb 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -15,6 +15,7 @@ #include "Goals/Goals.h" #include "../../lib/ArtifactUtils.h" +#include "../../lib/AsyncRunner.h" #include "../../lib/CThreadHelper.h" #include "../../lib/UnlockGuard.h" #include "../../lib/StartInfo.h" @@ -37,8 +38,6 @@ #include "AIhelper.h" -static tbb::task_arena executeActionAsyncArena; - extern FuzzyHelper * fh; const double SAFE_ATTACK_CONSTANT = 1.5; @@ -78,7 +77,7 @@ struct SetGlobalState VCAI::VCAI() { LOG_TRACE(logAi); - asyncTasks = std::make_unique(); + asyncTasks = std::make_unique(); destinationTeleport = ObjectInstanceID(); destinationTeleportPos = int3(-1); @@ -653,11 +652,11 @@ void VCAI::yourTurn(QueryID queryID) status.startedTurn(); makingTurnInterrupption.reset(); - executeActionAsyncArena.enqueue(asyncTasks->defer([this]() + asyncTasks->run([this]() { ScopedThreadName guard("VCAI::makingTurn"); makeTurn(); - })); + }); } void VCAI::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector & skills, QueryID queryID) @@ -2510,13 +2509,13 @@ void VCAI::executeActionAsync(const std::string & description, const std::functi if (!asyncTasks) throw std::runtime_error("Attempt to execute task on shut down AI state!"); - executeActionAsyncArena.enqueue(asyncTasks->defer([this, description, whatToDo]() + asyncTasks->run([this, description, whatToDo]() { ScopedThreadName guard("VCAI::" + description); SET_GLOBAL_STATE(this); std::shared_lock gsLock(CGameState::mutex); whatToDo(); - })); + }); } void VCAI::lostHero(HeroPtr h) diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 6961c137e..34f6c29d4 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -23,13 +23,11 @@ #include "../../lib/spells/CSpellHandler.h" #include "Pathfinding/AIPathfinder.h" -#include -#include - VCMI_LIB_NAMESPACE_BEGIN struct QuestInfo; class PathfinderCache; +class AsyncRunner; VCMI_LIB_NAMESPACE_END @@ -108,7 +106,7 @@ public: std::shared_ptr myCb; - std::unique_ptr asyncTasks; + std::unique_ptr asyncTasks; ThreadInterruption makingTurnInterrupption; public: diff --git a/client/CMT.h b/client/CMT.h index e73c065b2..894a60676 100644 --- a/client/CMT.h +++ b/client/CMT.h @@ -16,4 +16,3 @@ extern SDL_Renderer * mainRenderer; /// Defined in clientapp EntryPoint /// TODO: decide on better location for this method [[noreturn]] void handleFatalError(const std::string & message, bool terminate); -void handleQuit(bool ask = true); diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 42d55aa42..e06be5566 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -703,8 +703,6 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared entry->Bool() = true; } - GAME->mainmenu()->makeActiveInterface(); - if(!ourCampaign->isCampaignFinished()) GAME->mainmenu()->openCampaignLobby(ourCampaign); else diff --git a/client/GameEngine.cpp b/client/GameEngine.cpp index 19982eefd..30866a60a 100644 --- a/client/GameEngine.cpp +++ b/client/GameEngine.cpp @@ -23,20 +23,17 @@ #include "media/CVideoHandler.h" #include "media/CEmptyVideoPlayer.h" -#include "CPlayerInterface.h" #include "adventureMap/AdventureMapInterface.h" #include "render/Canvas.h" #include "render/Colors.h" -#include "render/Graphics.h" #include "render/IFont.h" #include "render/EFont.h" #include "renderSDL/ScreenHandler.h" #include "renderSDL/RenderHandler.h" -#include "CMT.h" #include "GameEngineUser.h" #include "battle/BattleInterface.h" -#include "../lib/CThreadHelper.h" +#include "../lib/AsyncRunner.h" #include "../lib/CConfigHandler.h" #include @@ -58,7 +55,9 @@ ObjectConstruction::~ObjectConstruction() ENGINE->captureChildren = !ENGINE->createdObj.empty(); } -void GameEngine::init() +GameEngine::GameEngine() + : captureChildren(false) + , fakeStatusBar(std::make_shared()) { inGuiThread = true; @@ -81,9 +80,11 @@ void GameEngine::init() soundPlayerInstance = std::make_unique(); musicPlayerInstance = std::make_unique(); - sound().setVolume((ui32)settings["general"]["sound"].Float()); - music().setVolume((ui32)settings["general"]["music"].Float()); + sound().setVolume(settings["general"]["sound"].Integer()); + music().setVolume(settings["general"]["music"].Integer()); cursorHandlerInstance = std::make_unique(); + + asyncTasks = std::make_unique(); } void GameEngine::handleEvents() @@ -104,33 +105,33 @@ void GameEngine::fakeMouseMove() }); } -void GameEngine::renderFrame() +[[noreturn]] void GameEngine::mainLoop() { + for (;;) { - std::scoped_lock interfaceLock(ENGINE->interfaceMutex); - - engineUser->onUpdate(); - - handleEvents(); - windows().simpleRedraw(); - - if (settings["video"]["showfps"].Bool()) - drawFPSCounter(); - - screenHandlerInstance->updateScreenTexture(); - - windows().onFrameRendered(); - ENGINE->cursor().update(); + input().fetchEvents(); + updateFrame(); + screenHandlerInstance->presentScreenTexture(); + framerate().framerateDelay(); // holds a constant FPS } - - screenHandlerInstance->presentScreenTexture(); - framerate().framerateDelay(); // holds a constant FPS } -GameEngine::GameEngine() - : captureChildren(false) - , fakeStatusBar(std::make_shared()) +void GameEngine::updateFrame() { + std::scoped_lock interfaceLock(ENGINE->interfaceMutex); + + engineUser->onUpdate(); + + handleEvents(); + windows().simpleRedraw(); + + if (settings["video"]["showfps"].Bool()) + drawFPSCounter(); + + screenHandlerInstance->updateScreenTexture(); + + windows().onFrameRendered(); + ENGINE->cursor().update(); } GameEngine::~GameEngine() @@ -239,7 +240,7 @@ std::shared_ptr GameEngine::statusbar() return locked; } -void GameEngine::setStatusbar(std::shared_ptr newStatusBar) +void GameEngine::setStatusbar(const std::shared_ptr & newStatusBar) { currentStatusBar = newStatusBar; } diff --git a/client/GameEngine.h b/client/GameEngine.h index 43c8653f1..80dee43e2 100644 --- a/client/GameEngine.h +++ b/client/GameEngine.h @@ -11,6 +11,7 @@ VCMI_LIB_NAMESPACE_BEGIN class Point; +class AsyncRunner; class Rect; VCMI_LIB_NAMESPACE_END @@ -52,9 +53,14 @@ private: std::unique_ptr musicPlayerInstance; std::unique_ptr cursorHandlerInstance; std::unique_ptr videoPlayerInstance; + std::unique_ptr asyncTasks; IGameEngineUser *engineUser = nullptr; + void updateFrame(); + void handleEvents(); //takes events from queue and calls interested objects + void drawFPSCounter(); // draws the FPS to the upper left corner of the screen + public: std::mutex interfaceMutex; @@ -66,6 +72,7 @@ public: EventDispatcher & events(); InputHandler & input(); + AsyncRunner & async() { return *asyncTasks; } IGameEngineUser & user() { return *engineUser; } ISoundPlayer & sound() { return *soundPlayerInstance; } IMusicPlayer & music() { return *musicPlayerInstance; } @@ -97,7 +104,9 @@ public: std::shared_ptr statusbar(); /// Set currently active status bar - void setStatusbar(std::shared_ptr); + void setStatusbar(const std::shared_ptr &); + + /// Sets engine user that is used as target of callback for events received by engine void setEngineUser(IGameEngineUser * user); bool captureChildren; //all newly created objects will get their parents from stack and will be added to parents children list @@ -106,16 +115,17 @@ public: GameEngine(); ~GameEngine(); - void init(); - void renderFrame(); + /// Performs main game loop till game shutdown + /// This method never returns, to abort main loop throw GameShutdownException + [[noreturn]] void mainLoop(); /// called whenever SDL_WINDOWEVENT_RESTORED is reported or the user selects a different resolution, requiring to center/resize all windows void onScreenResize(bool resolutionChanged); - void handleEvents(); //takes events from queue and calls interested objects + /// Simulate mouse movement to force refresh UI state that updates on mouse move void fakeMouseMove(); - void drawFPSCounter(); // draws the FPS to the upper left corner of the screen + /// Returns true for calls made from main (GUI) thread, false othervice bool amIGuiThread(); /// Calls provided functor in main thread on next execution frame diff --git a/client/GameEngineUser.h b/client/GameEngineUser.h index 88916ef17..12337db41 100644 --- a/client/GameEngineUser.h +++ b/client/GameEngineUser.h @@ -20,6 +20,9 @@ public: /// Called on every game tick for game to update its state virtual void onUpdate() = 0; + /// Called when app shutdown has been requested in any way - exit button, Alt-F4, etc + virtual void onShutdownRequested(bool askForConfirmation) = 0; + /// Returns true if all input events should be captured and ignored virtual bool capturedAllEvents() = 0; }; diff --git a/client/GameInstance.cpp b/client/GameInstance.cpp index 57e305346..e57d9935a 100644 --- a/client/GameInstance.cpp +++ b/client/GameInstance.cpp @@ -11,12 +11,16 @@ #include "GameInstance.h" #include "CPlayerInterface.h" +#include "CMT.h" #include "CServerHandler.h" #include "mapView/mapHandler.h" #include "globalLobby/GlobalLobbyClient.h" #include "mainmenu/CMainMenu.h" +#include "windows/InfoWindows.h" #include "../lib/CConfigHandler.h" +#include "../lib/GameLibrary.h" +#include "../lib/texts/CGeneralTextHandler.h" std::unique_ptr GAME = nullptr; @@ -93,3 +97,18 @@ bool GameInstance::capturedAllEvents() else return false; } + +void GameInstance::onShutdownRequested(bool ask) +{ + auto doQuit = [](){ throw GameShutdownException(); }; + + if(!ask) + doQuit(); + else + { + if (interface()) + interface()->showYesNoDialog(LIBRARY->generaltexth->allTexts[69], doQuit, nullptr); + else + CInfoWindow::showYesNoDialog(LIBRARY->generaltexth->allTexts[69], {}, doQuit, {}, PlayerColor(1)); + } +} diff --git a/client/GameInstance.h b/client/GameInstance.h index cf2a5b1fe..05b81bb33 100644 --- a/client/GameInstance.h +++ b/client/GameInstance.h @@ -21,6 +21,15 @@ VCMI_LIB_NAMESPACE_BEGIN class INetworkHandler; VCMI_LIB_NAMESPACE_END +class GameShutdownException final : public std::exception +{ +public: + const char* what() const noexcept final + { + return "Game shutdown has been requested"; + } +}; + class GameInstance final : boost::noncopyable, public IGameEngineUser { std::unique_ptr serverInstance; @@ -45,6 +54,7 @@ public: void onGlobalLobbyInterfaceActivated() final; void onUpdate() final; bool capturedAllEvents() final; + void onShutdownRequested(bool askForConfirmation) final; }; extern std::unique_ptr GAME; diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 696e8c0c3..ed1261624 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -418,7 +418,7 @@ void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack) { logAi->info("Red player %s. Ending game.", pack.victoryLossCheckResult.victory() ? "won" : "lost"); - handleQuit(settings["session"]["spectate"].Bool()); // if spectator is active ask to close client or not + GAME->onShutdownRequested(settings["session"]["spectate"].Bool()); // if spectator is active ask to close client or not } } diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index 0fbf9c0ff..113bfa06e 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -354,14 +354,8 @@ void AdventureMapShortcuts::quitGame() { GAME->interface()->showYesNoDialog( LIBRARY->generaltexth->allTexts[578], - []() - { - ENGINE->dispatchMainThread( []() - { - handleQuit(false); - }); - }, - 0 + [](){ GAME->onShutdownRequested(false);}, + nullptr ); } diff --git a/client/eventsSDL/InputHandler.cpp b/client/eventsSDL/InputHandler.cpp index 5cc2caab3..7d2bf4eb7 100644 --- a/client/eventsSDL/InputHandler.cpp +++ b/client/eventsSDL/InputHandler.cpp @@ -19,6 +19,7 @@ #include "InputSourceGameController.h" #include "../GameEngine.h" +#include "../GameEngineUser.h" #include "../gui/CursorHandler.h" #include "../gui/EventDispatcher.h" #include "../gui/MouseButton.h" @@ -194,9 +195,9 @@ void InputHandler::preprocessEvent(const SDL_Event & ev) { std::scoped_lock interfaceLock(ENGINE->interfaceMutex); #ifdef VCMI_ANDROID - handleQuit(false); + ENGINE->user().onShutdownRequested(false); #else - handleQuit(true); + ENGINE->user().onShutdownRequested(true); #endif return; } @@ -206,14 +207,14 @@ void InputHandler::preprocessEvent(const SDL_Event & ev) { // FIXME: dead code? Looks like intercepted by OS/SDL and delivered as SDL_Quit instead? std::scoped_lock interfaceLock(ENGINE->interfaceMutex); - handleQuit(true); + ENGINE->user().onShutdownRequested(true); return; } if(ev.key.keysym.scancode == SDL_SCANCODE_AC_BACK && !settings["input"]["handleBackRightMouseButton"].Bool()) { std::scoped_lock interfaceLock(ENGINE->interfaceMutex); - handleQuit(true); + ENGINE->user().onShutdownRequested(true); return; } } diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 5a0b7a392..f82e76a40 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -60,7 +60,7 @@ bool mapSorter::operator()(const std::shared_ptr aaa, const std::sh { if(boost::algorithm::starts_with(aaa->folderName, "..") || boost::algorithm::starts_with(bbb->folderName, "..")) return boost::algorithm::starts_with(aaa->folderName, ".."); - return boost::ilexicographical_compare(aaa->folderName, bbb->folderName); + return TextOperations::compareLocalizedStrings(aaa->folderName, bbb->folderName); } } @@ -115,13 +115,13 @@ bool mapSorter::operator()(const std::shared_ptr aaa, const std::sh return (a->victoryIconIndex < b->victoryIconIndex); break; case _name: //by name - return boost::ilexicographical_compare(a->name.toString(), b->name.toString()); + return TextOperations::compareLocalizedStrings(aaa->name, bbb->name); case _fileName: //by filename - return boost::ilexicographical_compare(aaa->fileURI, bbb->fileURI); + return TextOperations::compareLocalizedStrings(aaa->fileURI, bbb->fileURI); case _changeDate: //by changedate return aaa->lastWrite < bbb->lastWrite; default: - return boost::ilexicographical_compare(a->name.toString(), b->name.toString()); + return TextOperations::compareLocalizedStrings(aaa->name, bbb->name); } } else //if we are sorting campaigns @@ -131,9 +131,9 @@ bool mapSorter::operator()(const std::shared_ptr aaa, const std::sh case _numOfMaps: //by number of maps in campaign return aaa->campaign->scenariosCount() < bbb->campaign->scenariosCount(); case _name: //by name - return boost::ilexicographical_compare(aaa->campaign->getNameTranslated(), bbb->campaign->getNameTranslated()); + return TextOperations::compareLocalizedStrings(aaa->campaign->getNameTranslated(), bbb->campaign->getNameTranslated()); default: - return boost::ilexicographical_compare(aaa->campaign->getNameTranslated(), bbb->campaign->getNameTranslated()); + return TextOperations::compareLocalizedStrings(aaa->campaign->getNameTranslated(), bbb->campaign->getNameTranslated()); } } } diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 30dacb609..0b6c270eb 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -64,14 +64,6 @@ ISelectionScreenInfo * SEL = nullptr; -static void do_quit() -{ - ENGINE->dispatchMainThread([]() - { - handleQuit(false); - }); -} - CMenuScreen::CMenuScreen(const JsonNode & configNode) : CWindowObject(BORDERED), config(configNode) { @@ -210,7 +202,7 @@ static std::function genCommand(CMenuScreen * menu, std::vectorgeneraltexth->allTexts[69], std::vector>(), do_quit, 0, PlayerColor(1)); }; + return []() { CInfoWindow::showYesNoDialog(LIBRARY->generaltexth->allTexts[69], std::vector>(), [](){GAME->onShutdownRequested(false);}, 0, PlayerColor(1)); }; } break; case 5: //highscores diff --git a/client/mainmenu/CPrologEpilogVideo.cpp b/client/mainmenu/CPrologEpilogVideo.cpp index 3e026c762..7f7ab20cd 100644 --- a/client/mainmenu/CPrologEpilogVideo.cpp +++ b/client/mainmenu/CPrologEpilogVideo.cpp @@ -14,6 +14,7 @@ #include "../media/IMusicPlayer.h" #include "../media/ISoundPlayer.h" #include "../GameEngine.h" +#include "../gui/Shortcut.h" #include "../widgets/TextControls.h" #include "../widgets/VideoWidget.h" #include "../widgets/Images.h" @@ -92,7 +93,7 @@ void CPrologEpilogVideo::show(Canvas & to) text->showAll(to); // blit text over video, if needed } -void CPrologEpilogVideo::clickPressed(const Point & cursorPosition) +void CPrologEpilogVideo::exit() { ENGINE->music().setVolume(ENGINE->music().getVolume() * 2); // restore background volume close(); @@ -102,3 +103,14 @@ void CPrologEpilogVideo::clickPressed(const Point & cursorPosition) if(exitCb) exitCb(); } + +void CPrologEpilogVideo::clickPressed(const Point & cursorPosition) +{ + exit(); +} + +void CPrologEpilogVideo::keyPressed(EShortcut key) +{ + if(key == EShortcut::GLOBAL_RETURN) + exit(); +} diff --git a/client/mainmenu/CPrologEpilogVideo.h b/client/mainmenu/CPrologEpilogVideo.h index 40c5bcfb8..171bdf08b 100644 --- a/client/mainmenu/CPrologEpilogVideo.h +++ b/client/mainmenu/CPrologEpilogVideo.h @@ -30,6 +30,8 @@ class CPrologEpilogVideo : public CWindowObject std::shared_ptr videoPlayer; std::shared_ptr backgroundAroundMenu; + void exit(); + bool voiceStopped = false; public: @@ -37,5 +39,6 @@ public: void tick(uint32_t msPassed) override; void clickPressed(const Point & cursorPosition) override; + void keyPressed(EShortcut key) override; void show(Canvas & to) override; }; diff --git a/client/media/CAudioBase.cpp b/client/media/CAudioBase.cpp index 24bbc1eff..e4f08e8a0 100644 --- a/client/media/CAudioBase.cpp +++ b/client/media/CAudioBase.cpp @@ -37,7 +37,8 @@ CAudioBase::~CAudioBase() --initializationCounter; if(initializationCounter == 0 && initializeSuccess) + { Mix_CloseAudio(); - - initializeSuccess = false; + initializeSuccess = false; + } } diff --git a/client/media/CMusicHandler.cpp b/client/media/CMusicHandler.cpp index e3ad5bf55..bd9ee81be 100644 --- a/client/media/CMusicHandler.cpp +++ b/client/media/CMusicHandler.cpp @@ -91,7 +91,6 @@ CMusicHandler::~CMusicHandler() std::scoped_lock guard(mutex); Mix_HookMusicFinished(nullptr); - current->stop(); current.reset(); next.reset(); @@ -233,8 +232,7 @@ MusicEntry::~MusicEntry() if(loop == 0 && Mix_FadingMusic() != MIX_NO_FADING) { - assert(0); - logGlobal->error("Attempt to delete music while fading out!"); + logGlobal->trace("Halting playback of music file %s", currentName.getOriginalName()); Mix_HaltMusic(); } diff --git a/client/media/CSoundHandler.cpp b/client/media/CSoundHandler.cpp index e86ab15a9..31014c57d 100644 --- a/client/media/CSoundHandler.cpp +++ b/client/media/CSoundHandler.cpp @@ -56,6 +56,7 @@ CSoundHandler::~CSoundHandler() { if(isInitialized()) { + Mix_ChannelFinished(nullptr); Mix_HaltChannel(-1); for(auto & chunk : soundChunks) @@ -63,6 +64,12 @@ CSoundHandler::~CSoundHandler() if(chunk.second.first) Mix_FreeChunk(chunk.second.first); } + + for(auto & chunk : soundChunksRaw) + { + if(chunk.second.first) + Mix_FreeChunk(chunk.second.first); + } } } diff --git a/client/render/IScreenHandler.h b/client/render/IScreenHandler.h index 84a75a09b..a9464ccec 100644 --- a/client/render/IScreenHandler.h +++ b/client/render/IScreenHandler.h @@ -25,9 +25,6 @@ public: /// Updates window state after fullscreen state has been changed in settings virtual void onScreenResize() = 0; - /// De-initializes window state - virtual void close() = 0; - /// Fills screen with black color, erasing any existing content virtual void clearScreen() = 0; diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 7d0c020fd..e912275a0 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -20,10 +20,10 @@ #include "../GameEngine.h" #include "../render/IScreenHandler.h" +#include "../../lib/AsyncRunner.h" #include "../../lib/CConfigHandler.h" #include -#include #include #include @@ -256,8 +256,6 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL SDLImageShared::SDLImageShared(const SDLImageShared * from, int integerScaleFactor, EScalingAlgorithm algorithm) { - static tbb::task_arena upscalingArena; - upscalingInProgress = true; auto scaler = std::make_shared(from->surf, Rect(from->margins, from->fullSize), true); @@ -273,7 +271,7 @@ SDLImageShared::SDLImageShared(const SDLImageShared * from, int integerScaleFact }; if(settings["video"]["asyncUpscaling"].Bool()) - upscalingArena.enqueue(scalingTask); + ENGINE->async().run(scalingTask); else scalingTask(); } @@ -401,6 +399,9 @@ std::shared_ptr SDLImageShared::horizontalFlip() const ret->margins.y = fullSize.y - surf->h - margins.y; ret->fullSize = fullSize; + // erase our own reference + SDL_FreeSurface(flipped); + return ret; } @@ -419,6 +420,9 @@ std::shared_ptr SDLImageShared::verticalFlip() const ret->margins.y = margins.y; ret->fullSize = fullSize; + // erase our own reference + SDL_FreeSurface(flipped); + return ret; } diff --git a/client/renderSDL/ScreenHandler.cpp b/client/renderSDL/ScreenHandler.cpp index 7c700bad8..eca366f9c 100644 --- a/client/renderSDL/ScreenHandler.cpp +++ b/client/renderSDL/ScreenHandler.cpp @@ -212,6 +212,11 @@ ScreenHandler::ScreenHandler() else SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight"); +#ifdef VCMI_IOS + if(!settings["general"]["ignoreMuteSwitch"].Bool()) + SDL_SetHint(SDL_HINT_AUDIO_CATEGORY, "AVAudioSessionCategoryAmbient"); +#endif + if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER)) { logGlobal->error("Something was wrong: %s", SDL_GetError()); @@ -595,7 +600,7 @@ void ScreenHandler::destroyWindow() } } -void ScreenHandler::close() +ScreenHandler::~ScreenHandler() { if(settings["general"]["notifications"].Bool()) NotificationHandler::destroy(); diff --git a/client/renderSDL/ScreenHandler.h b/client/renderSDL/ScreenHandler.h index 2e6282406..8f33c6a1b 100644 --- a/client/renderSDL/ScreenHandler.h +++ b/client/renderSDL/ScreenHandler.h @@ -96,13 +96,11 @@ public: /// Creates and initializes screen, window and SDL state ScreenHandler(); + ~ScreenHandler(); /// Updates and potentially recreates target screen to match selected fullscreen status void onScreenResize() final; - /// De-initializes and destroys screen, window and SDL state - void close() final; - /// Fills screen with black color, erasing any existing content void clearScreen() final; diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 89286fd41..7ccb296ff 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -92,7 +92,7 @@ public: return false; } - return A->getNameTranslated() < B->getNameTranslated(); + return TextOperations::compareLocalizedStrings(A->getNameTranslated(), B->getNameTranslated()); } }; @@ -241,12 +241,18 @@ void CSpellWindow::processSpells() mySpells.reserve(LIBRARY->spellh->objects.size()); for(auto const & spell : LIBRARY->spellh->objects) { - bool searchTextFound = !searchBox || TextOperations::textSearchSimilar(searchBox->getText(), spell->getNameTranslated()); + bool searchTextFound = !searchBox || TextOperations::textSearchSimilarityScore(searchBox->getText(), spell->getNameTranslated()); if(onSpellSelect) { - if(spell->isCombat() == openOnBattleSpells && !spell->isSpecial() && !spell->isCreatureAbility() && searchTextFound && (showAllSpells->isSelected() || myHero->canCastThisSpell(spell.get()))) + if(spell->isCombat() == openOnBattleSpells + && !spell->isSpecial() + && !spell->isCreatureAbility() + && searchTextFound + && (showAllSpells->isSelected() || myHero->canCastThisSpell(spell.get()))) + { mySpells.push_back(spell.get()); + } continue; } diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 4bc932116..ea6f9b8b5 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -39,6 +39,7 @@ #include "../render/Canvas.h" #include "../render/IRenderHandler.h" #include "../render/IImage.h" +#include "../render/IFont.h" #include "../../CCallback.h" @@ -1531,7 +1532,11 @@ CObjectListWindow::CObjectListWindow(const std::vector & _items, std::share items.reserve(_items.size()); for(int id : _items) - items.push_back(std::make_pair(id, GAME->interface()->cb->getObjInstance(ObjectInstanceID(id))->getObjectName())); + { + std::string objectName = GAME->interface()->cb->getObjInstance(ObjectInstanceID(id))->getObjectName(); + trimTextIfTooWide(objectName, id); + items.emplace_back(id, objectName); + } itemsVisible = items; init(titleWidget_, _title, _descr, searchBoxEnabled); @@ -1550,8 +1555,12 @@ CObjectListWindow::CObjectListWindow(const std::vector & _items, st items.reserve(_items.size()); - for(size_t i=0; i<_items.size(); i++) - items.push_back(std::make_pair(int(i), _items[i])); + for(size_t i = 0; i < _items.size(); i++) + { + std::string objectName = _items[i]; + trimTextIfTooWide(objectName, static_cast(i)); + items.emplace_back(static_cast(i), objectName); + } itemsVisible = items; init(titleWidget_, _title, _descr, searchBoxEnabled); @@ -1570,7 +1579,7 @@ void CObjectListWindow::init(std::shared_ptr titleWidget_, std::stri { addChild(titleWidget.get()); titleWidget->pos.x = pos.w/2 + pos.x - titleWidget->pos.w/2; - titleWidget->pos.y =75 + pos.y - titleWidget->pos.h/2; + titleWidget->pos.y = 75 + pos.y - titleWidget->pos.h/2; } list = std::make_shared(std::bind(&CObjectListWindow::genItem, this, _1), Point(14, 151), Point(0, 25), 9, itemsVisible.size(), 0, 1, Rect(262, -32, 256, 256) ); @@ -1590,21 +1599,64 @@ void CObjectListWindow::init(std::shared_ptr titleWidget_, std::stri searchBoxDescription = std::make_shared(r.center().x, r.center().y, FONT_SMALL, ETextAlignment::CENTER, grayedColor, LIBRARY->generaltexth->translate("vcmi.spellBook.search")); searchBox = std::make_shared(r, FONT_SMALL, ETextAlignment::CENTER, true); - searchBox->setCallback([this](const std::string & text){ - searchBoxDescription->setEnabled(text.empty()); + searchBox->setCallback(std::bind(&CObjectListWindow::itemsSearchCallback, this, std::placeholders::_1)); +} - itemsVisible.clear(); - for(auto & item : items) - if(TextOperations::textSearchSimilar(text, item.second)) - itemsVisible.push_back(item); +void CObjectListWindow::trimTextIfTooWide(std::string & text, int id) const +{ + int maxWidth = pos.w - 60; // 60 px for scrollbar and borders + std::string idStr = '(' + std::to_string(id) + ')'; + const auto & font = ENGINE->renderHandler().loadFont(FONT_SMALL); + std::string suffix = " ... " + idStr; - selected = 0; - list->resize(itemsVisible.size()); - list->scrollTo(0); - ok->block(!itemsVisible.size()); + if(font->getStringWidth(text) >= maxWidth) + { + logGlobal->warn("Mapobject name '%s' is too long and probably needs to be fixed! Trimming...", + text.substr(0, text.size() - idStr.size() + 1)); - redraw(); + // Trim text until it fits + while(!text.empty()) + { + std::string trimmedText = text + suffix; + + if(font->getStringWidth(trimmedText) < maxWidth) + break; + + TextOperations::trimRightUnicode(text); + } + + text += suffix; + } +} + +void CObjectListWindow::itemsSearchCallback(const std::string & text) +{ + searchBoxDescription->setEnabled(text.empty()); + + itemsVisible.clear(); + std::vector> rankedItems; // Store (score, item) + + for(const auto & item : items) + { + if(auto score = TextOperations::textSearchSimilarityScore(text, item.second)) // Keep only relevant items + rankedItems.emplace_back(score.value(), item); + } + + // Sort: Lower score is better match + std::sort(rankedItems.begin(), rankedItems.end(), [](const auto & a, const auto & b) + { + return a.first < b.first; }); + + for(const auto & rankedItem : rankedItems) + itemsVisible.push_back(rankedItem.second); + + selected = 0; + list->resize(itemsVisible.size()); + list->scrollTo(0); + ok->block(!itemsVisible.size()); + + redraw(); } std::shared_ptr CObjectListWindow::genItem(size_t index) @@ -1734,7 +1786,8 @@ void VideoWindow::clickPressed(const Point & cursorPosition) void VideoWindow::keyPressed(EShortcut key) { - exit(true); + if(key == EShortcut::GLOBAL_RETURN) + exit(true); } void VideoWindow::notFocusedClick() diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index b65a5b888..4b0ec539c 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -197,6 +197,8 @@ class CObjectListWindow : public CWindowObject std::vector< std::pair > itemsVisible; //visible items present in list void init(std::shared_ptr titleWidget_, std::string _title, std::string _descr, bool searchBoxEnabled); + void trimTextIfTooWide(std::string & text, int id) const; // trim item's text to fit within window's width + void itemsSearchCallback(const std::string & text); void exitPressed(); public: size_t selected;//index of currently selected item diff --git a/client/windows/settings/SettingsMainWindow.cpp b/client/windows/settings/SettingsMainWindow.cpp index 654670aae..1600ef25a 100644 --- a/client/windows/settings/SettingsMainWindow.cpp +++ b/client/windows/settings/SettingsMainWindow.cpp @@ -125,12 +125,9 @@ void SettingsMainWindow::quitGameButtonCallback() [this]() { close(); - ENGINE->dispatchMainThread( []() - { - handleQuit(false); - }); + ENGINE->user().onShutdownRequested(false); }, - 0 + nullptr ); } diff --git a/clientapp/EntryPoint.cpp b/clientapp/EntryPoint.cpp index 40d934f9e..6ab52e40f 100644 --- a/clientapp/EntryPoint.cpp +++ b/clientapp/EntryPoint.cpp @@ -17,24 +17,19 @@ #include "../client/CMT.h" #include "../client/CPlayerInterface.h" #include "../client/CServerHandler.h" -#include "../client/eventsSDL/InputHandler.h" #include "../client/GameEngine.h" #include "../client/GameInstance.h" #include "../client/gui/CursorHandler.h" #include "../client/gui/WindowHandler.h" #include "../client/mainmenu/CMainMenu.h" -#include "../client/media/CEmptyVideoPlayer.h" -#include "../client/media/CMusicHandler.h" -#include "../client/media/CSoundHandler.h" -#include "../client/media/CVideoHandler.h" #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" +#include "../lib/AsyncRunner.h" #include "../lib/CConsoleHandler.h" +#include "../lib/CConfigHandler.h" #include "../lib/CThreadHelper.h" #include "../lib/ExceptionsCommon.h" #include "../lib/filesystem/Filesystem.h" @@ -42,7 +37,6 @@ #include "../lib/modding/IdentifierStorage.h" #include "../lib/modding/CModHandler.h" #include "../lib/modding/ModDescription.h" -#include "../lib/texts/CGeneralTextHandler.h" #include "../lib/texts/MetaString.h" #include "../lib/GameLibrary.h" #include "../lib/VCMIDirs.h" @@ -68,20 +62,13 @@ namespace po_style = boost::program_options::command_line_style; static std::atomic headlessQuit = false; static std::optional criticalInitializationError; -#ifndef VCMI_IOS -void processCommand(const std::string &message); -#endif -[[noreturn]] static void quitApplication(); -static void mainLoop(); - -static CBasicLogConfigurator *logConfig; - static void init() { - CStopWatch tmh; try { - loadDLLClasses(); + CStopWatch tmh; + LIBRARY->initializeLibrary(); + logGlobal->info("Initializing VCMI_Lib: %d ms", tmh.getDiff()); } catch (const DataLoadingException & e) { @@ -89,8 +76,6 @@ static void init() return; } - logGlobal->info("Initializing VCMI_Lib: %d ms", tmh.getDiff()); - // Debug code to load all maps on start //ClientCommandManager commandController; //commandController.processCommand("translate maps", false); @@ -241,7 +226,8 @@ int main(int argc, char * argv[]) // Init filesystem and settings try { - preinitDLL(false); + LIBRARY = new GameLibrary; + LIBRARY->initializeFilesystem(false); } catch (const DataLoadingException & e) { @@ -249,14 +235,14 @@ int main(int argc, char * argv[]) } Settings session = settings.write["session"]; - auto setSettingBool = [&](std::string key, std::string arg) { + auto setSettingBool = [&](const std::string & key, const std::string & arg) { Settings s = settings.write(vstd::split(key, "/")); if(vm.count(arg)) s->Bool() = true; else if(s->isNull()) s->Bool() = false; }; - auto setSettingInteger = [&](std::string key, std::string arg, si64 defaultValue) { + auto setSettingInteger = [&](const std::string & key, const std::string & arg, si64 defaultValue) { Settings s = settings.write(vstd::split(key, "/")); if(vm.count(arg)) s->Integer() = vm[arg].as(); @@ -294,7 +280,7 @@ int main(int argc, char * argv[]) logGlobal->debug("settings = %s", settings.toJsonNode().toString()); // Some basic data validation to produce better error messages in cases of incorrect install - auto testFile = [](std::string filename, std::string message) + auto testFile = [](const std::string & filename, const std::string & message) { if (!CResourceHandler::get()->existsResource(ResourcePath(filename))) handleFatalError(message, false); @@ -308,13 +294,13 @@ int main(int argc, char * argv[]) srand ( (unsigned int)time(nullptr) ); - ENGINE = std::make_unique(); - if(!settings["session"]["headless"].Bool()) - ENGINE->init(); + ENGINE = std::make_unique(); GAME = std::make_unique(); - ENGINE->setEngineUser(GAME.get()); + + if (ENGINE) + ENGINE->setEngineUser(GAME.get()); #ifndef VCMI_NO_THREADED_LOAD //we can properly play intro only in the main thread, so we have to move loading to the separate thread @@ -345,7 +331,7 @@ int main(int argc, char * argv[]) handleFatalError(criticalInitializationError.value(), false); } - if(!settings["session"]["headless"].Bool()) + if (ENGINE) { pomtime.getDiff(); graphics = new Graphics(); // should be before curh @@ -387,56 +373,32 @@ int main(int argc, char * argv[]) GAME->mainmenu()->playMusic(); } - std::vector names; - - if(!settings["session"]["headless"].Bool()) - { - checkForModLoadingFailure(); - mainLoop(); - } - else - { - while(!headlessQuit) - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - - quitApplication(); - } - - return 0; -} - -static void mainLoop() -{ #ifndef VCMI_UNIX // on Linux, name of main thread is also name of our process. Which we don't want to change setThreadName("MainGUI"); #endif - while(1) //main SDL events loop + try { - ENGINE->input().fetchEvents(); - ENGINE->renderFrame(); + if (ENGINE) + { + checkForModLoadingFailure(); + ENGINE->mainLoop(); + } + else + { + while(!headlessQuit) + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + } + catch (const GameShutdownException & ) + { + // no-op - just break out of main loop + logGlobal->info("Main loop termination requested"); } -} -[[noreturn]] static void quitApplicationImmediately(int error_code) -{ - // Perform quick exit without executing static destructors and let OS cleanup anything that we did not - // We generally don't care about them and this leads to numerous issues, e.g. - // destruction of locked mutexes (fails an assertion), even in third-party libraries (as well as native libs on Android) - // Android - std::quick_exit is available only starting from API level 21 - // Mingw, macOS and iOS - std::quick_exit is unavailable (at least in current version of CI) -#if (defined(__ANDROID_API__) && __ANDROID_API__ < 21) || (defined(__MINGW32__)) || defined(VCMI_APPLE) - ::exit(error_code); -#else - std::quick_exit(error_code); -#endif -} - -[[noreturn]] static void quitApplication() -{ GAME->server().endNetwork(); if(!settings["session"]["headless"].Bool()) @@ -444,7 +406,8 @@ static void mainLoop() if(GAME->server().client) GAME->server().endGameplay(); - ENGINE->windows().clear(); + if (ENGINE) + ENGINE->windows().clear(); } GAME.reset(); @@ -452,51 +415,19 @@ static void mainLoop() if(!settings["session"]["headless"].Bool()) { CMessage::dispose(); - vstd::clear_pointer(graphics); } + // must be executed before reset - since unique_ptr resets pointer to null before calling destructor + ENGINE->async().wait(); + + ENGINE.reset(); + vstd::clear_pointer(LIBRARY); - - // sometimes leads to a hang. TODO: investigate - //vstd::clear_pointer(console);// should be removed after everything else since used by logging - - if(!settings["session"]["headless"].Bool()) - ENGINE->screenHandler().close(); - - if(logConfig != nullptr) - { - logConfig->deconfigure(); - delete logConfig; - logConfig = nullptr; - } - - //ENGINE.reset(); + logConfigurator.deconfigure(); std::cout << "Ending...\n"; - quitApplicationImmediately(0); -} - -void handleQuit(bool ask) -{ - if(!ask) - { - if(settings["session"]["headless"].Bool()) - { - headlessQuit = true; - } - else - { - quitApplication(); - } - - return; - } - - if (GAME->interface()) - GAME->interface()->showYesNoDialog(LIBRARY->generaltexth->allTexts[69], quitApplication, nullptr); - else - CInfoWindow::showYesNoDialog(LIBRARY->generaltexth->allTexts[69], {}, quitApplication, {}, PlayerColor(1)); + return 0; } /// Notify user about encountered fatal error and terminate the game @@ -513,5 +444,5 @@ void handleFatalError(const std::string & message, bool terminate) if (terminate) throw std::runtime_error(message); else - quitApplicationImmediately(1); + ::exit(1); } diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 48f54b315..f89d64dc4 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -45,7 +45,8 @@ "audioMuteFocus", "enableOverlay", "lastKindomInterface", - "enableSubtitle" + "enableSubtitle", + "ignoreMuteSwitch" ], "properties" : { "playerName" : { @@ -161,6 +162,10 @@ "enableSubtitle" : { "type": "boolean", "default": true + }, + "ignoreMuteSwitch" : { + "type": "boolean", + "default": true } } }, diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index c1cb1cbb5..f921f73fb 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -130,6 +130,10 @@ void CSettingsView::loadSettings() ui->labelHandleBackRightMouseButton->hide(); ui->buttonAllowPortrait->hide(); ui->labelAllowPortrait->hide(); +#endif +#ifndef VCMI_IOS + ui->labelIgnoreMuteSwitch->hide(); + ui->buttonIgnoreMuteSwitch->hide(); #endif fillValidScalingRange(); @@ -224,6 +228,8 @@ void CSettingsView::loadToggleButtonSettings() setCheckbuttonState(ui->buttonHandleBackRightMouseButton, settings["input"]["handleBackRightMouseButton"].Bool()); + setCheckbuttonState(ui->buttonIgnoreMuteSwitch, settings["general"]["ignoreMuteSwitch"].Bool()); + std::string cursorType = settings["video"]["cursor"].String(); int cursorTypeIndex = vstd::find_pos(cursorTypesList, cursorType); setCheckbuttonState(ui->buttonCursorType, cursorTypeIndex); @@ -866,3 +872,9 @@ void CSettingsView::on_buttonHandleBackRightMouseButton_toggled(bool checked) updateCheckbuttonText(ui->buttonHandleBackRightMouseButton); } +void CSettingsView::on_buttonIgnoreMuteSwitch_toggled(bool checked) +{ + Settings node = settings.write["general"]["ignoreMuteSwitch"]; + node->Bool() = checked; + updateCheckbuttonText(ui->buttonIgnoreMuteSwitch); +} diff --git a/launcher/settingsView/csettingsview_moc.h b/launcher/settingsView/csettingsview_moc.h index a8cebead3..866a67886 100644 --- a/launcher/settingsView/csettingsview_moc.h +++ b/launcher/settingsView/csettingsview_moc.h @@ -91,18 +91,13 @@ private slots: void on_buttonFontScalable_clicked(bool checked); void on_buttonFontOriginal_clicked(bool checked); - void on_buttonValidationOff_clicked(bool checked); - void on_buttonValidationBasic_clicked(bool checked); - void on_buttonValidationFull_clicked(bool checked); - void on_sliderScalingCursor_valueChanged(int value); - void on_buttonScalingAuto_toggled(bool checked); - void on_buttonHandleBackRightMouseButton_toggled(bool checked); + void on_buttonIgnoreMuteSwitch_toggled(bool checked); private: Ui::CSettingsView * ui; diff --git a/launcher/settingsView/csettingsview_moc.ui b/launcher/settingsView/csettingsview_moc.ui index 590ed4515..a7a918973 100644 --- a/launcher/settingsView/csettingsview_moc.ui +++ b/launcher/settingsView/csettingsview_moc.ui @@ -69,7 +69,7 @@ - + Mods Validation @@ -105,7 +105,7 @@ - + 500 @@ -133,7 +133,7 @@ - + Reset @@ -147,7 +147,7 @@ - + 500 @@ -172,7 +172,7 @@ - + true @@ -239,7 +239,7 @@ - + true @@ -268,14 +268,14 @@ - + Haptic Feedback - + Touch Tap Tolerance @@ -288,7 +288,7 @@ - + 1024 @@ -301,7 +301,7 @@ - + true @@ -326,7 +326,7 @@ - + @@ -379,7 +379,7 @@ - + VCAI @@ -396,7 +396,7 @@ - + @@ -406,7 +406,7 @@ - + VCAI @@ -430,7 +430,7 @@ - + @@ -445,7 +445,7 @@ - + Default repository @@ -471,7 +471,7 @@ - + Relative Pointer Speed @@ -485,7 +485,7 @@ - + true @@ -588,7 +588,7 @@ - + @@ -607,21 +607,21 @@ - + Refresh now - + Adventure Map Allies - + Neutral AI in battles @@ -659,7 +659,7 @@ - + @@ -673,7 +673,7 @@ - + Use Relative Pointer Mode @@ -683,14 +683,14 @@ - + Autocombat AI in battles - + true @@ -715,7 +715,7 @@ - + @@ -737,7 +737,7 @@ - + @@ -768,7 +768,7 @@ - + Additional repository @@ -791,7 +791,7 @@ - + Adventure Map Enemies @@ -819,7 +819,7 @@ - + @@ -853,7 +853,7 @@ - + @@ -876,28 +876,28 @@ - + Online Lobby address - + Online Lobby port - + Controller Click Tolerance - + BattleAI @@ -933,7 +933,7 @@ - + Ignore SSL errors @@ -943,14 +943,14 @@ - + Show Tutorial again - + Sticks Acceleration @@ -972,7 +972,7 @@ - + false @@ -1006,21 +1006,21 @@ - + Mouse Click Tolerance - + Handle back as right mouse button - + @@ -1036,7 +1036,7 @@ - + Enemy AI in battles @@ -1050,7 +1050,7 @@ - + 0 @@ -1092,7 +1092,7 @@ - + false @@ -1112,7 +1112,7 @@ - + @@ -1164,7 +1164,7 @@ - + @@ -1179,7 +1179,7 @@ - + @@ -1194,7 +1194,7 @@ - + 100 @@ -1266,21 +1266,21 @@ Fullscreen Exclusive Mode - the game will cover the entirety of your screen and - + Long Touch Duration - + Check on startup - + 0 @@ -1324,7 +1324,7 @@ Fullscreen Exclusive Mode - the game will cover the entirety of your screen and - + 100 @@ -1366,7 +1366,7 @@ Fullscreen Exclusive Mode - the game will cover the entirety of your screen and - + 0 @@ -1439,7 +1439,7 @@ Fullscreen Exclusive Mode - the game will cover the entirety of your screen and - + Sticks Sensitivity @@ -1470,7 +1470,7 @@ Fullscreen Exclusive Mode - the game will cover the entirety of your screen and - + true @@ -1495,6 +1495,26 @@ Fullscreen Exclusive Mode - the game will cover the entirety of your screen and + + + + Ignore mute switch + + + + + + + + 0 + 0 + + + + true + + + diff --git a/lib/AsyncRunner.h b/lib/AsyncRunner.h new file mode 100644 index 000000000..5d9496bd4 --- /dev/null +++ b/lib/AsyncRunner.h @@ -0,0 +1,44 @@ +/* + * AsyncRunner.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include +#include + +VCMI_LIB_NAMESPACE_BEGIN + +/// Helper class for running asynchronous tasks using TBB thread pool +class AsyncRunner : boost::noncopyable +{ + tbb::task_arena arena; + tbb::task_group taskGroup; + +public: + /// Runs the provided functor asynchronously on a thread from the TBB worker pool. + template + void run(Functor && f) + { + arena.enqueue(taskGroup.defer(std::forward(f))); + } + + /// Waits for all previously enqueued task. + /// Re-entrable - waiting for tasks does not prevent submitting new tasks + void wait() + { + taskGroup.wait(); + } + + ~AsyncRunner() + { + wait(); + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/CArtifactInstance.h b/lib/CArtifactInstance.h index f994ee03b..8d2d04959 100644 --- a/lib/CArtifactInstance.h +++ b/lib/CArtifactInstance.h @@ -94,17 +94,7 @@ public: { h & static_cast(*this); h & static_cast(*this); - if (h.version >= Handler::Version::REMOVE_VLC_POINTERS) - { - h & artTypeID; - } - else - { - bool isNull = false; - h & isNull; - if (!isNull) - h & artTypeID; - } + h & artTypeID; h & id; BONUS_TREE_DESERIALIZATION_FIX } diff --git a/lib/CConsoleHandler.cpp b/lib/CConsoleHandler.cpp index 66ce9feb2..e7089b7ed 100644 --- a/lib/CConsoleHandler.cpp +++ b/lib/CConsoleHandler.cpp @@ -251,8 +251,9 @@ int CConsoleHandler::run() if ( cb ) cb(buffer, false); } - else - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + std::unique_lock guard(shutdownMutex); + shutdownVariable.wait_for(guard, std::chrono::seconds(1)); if (shutdownPending) return -1; @@ -308,6 +309,7 @@ void CConsoleHandler::end() { #ifndef _MSC_VER shutdownPending = true; + shutdownVariable.notify_all(); #else TerminateThread(thread.native_handle(),0); #endif diff --git a/lib/CConsoleHandler.h b/lib/CConsoleHandler.h index 17753122c..d3ba9e946 100644 --- a/lib/CConsoleHandler.h +++ b/lib/CConsoleHandler.h @@ -94,6 +94,8 @@ private: //function to be called when message is received - string: message, bool: whether call was made from in-game console std::function cb; + std::condition_variable shutdownVariable; + std::mutex shutdownMutex; std::atomic shutdownPending = false; std::mutex smx; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 85aa980f5..186c497ab 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -693,6 +693,7 @@ set(lib_MAIN_HEADERS AI_Base.h ArtifactUtils.h + AsyncRunner.h BattleFieldHandler.h CAndroidVMHelper.h CArtHandler.h diff --git a/lib/CPlayerState.h b/lib/CPlayerState.h index 6456681ed..e13fca027 100644 --- a/lib/CPlayerState.h +++ b/lib/CPlayerState.h @@ -121,24 +121,8 @@ public: h & resources; h & status; h & turnTimer; - - if (h.version >= Handler::Version::LOCAL_PLAYER_STATE_DATA) - h & *playerLocalSettings; - - if (h.version >= Handler::Version::PLAYER_STATE_OWNED_OBJECTS) - { - h & ownedObjects; - } - else - { - std::vector heroes; - std::vector towns; - std::vector dwellings; - - h & heroes; - h & towns; - h & dwellings; - } + h & *playerLocalSettings; + h & ownedObjects; h & quests; h & visitedObjects; h & visitedObjectsGlobal; @@ -173,23 +157,9 @@ public: { h & id; h & players; - if (h.version < Handler::Version::REMOVE_FOG_OF_WAR_POINTER) - { - struct Helper : public Serializeable - { - void serialize(Handler &h) const - {} - }; - Helper helper; - auto ptrHelper = &helper; - h & ptrHelper; - } - h & fogOfWarMap; h & static_cast(*this); - - if (h.version >= Handler::Version::REWARDABLE_BANKS) - h & scoutedObjects; + h & scoutedObjects; } }; diff --git a/lib/GameLibrary.cpp b/lib/GameLibrary.cpp index 76ef07fb7..e02b30f18 100644 --- a/lib/GameLibrary.cpp +++ b/lib/GameLibrary.cpp @@ -46,20 +46,7 @@ VCMI_LIB_NAMESPACE_BEGIN GameLibrary * LIBRARY = nullptr; -DLL_LINKAGE void preinitDLL(bool extractArchives) -{ - LIBRARY = new GameLibrary(); - LIBRARY->loadFilesystem(extractArchives); - settings.init("config/settings.json", "vcmi:settings"); - persistentStorage.init("config/persistentStorage.json", ""); - LIBRARY->loadModFilesystem(); -} - -DLL_LINKAGE void loadDLLClasses(bool onlyEssential) -{ - LIBRARY->init(onlyEssential); -} const ArtifactService * GameLibrary::artifacts() const { @@ -160,12 +147,21 @@ void GameLibrary::loadModFilesystem() logGlobal->info("\tMod filesystems: %d ms", loadTime.getDiff()); } -template void createHandler(std::shared_ptr & handler) +template +void createHandler(std::unique_ptr & handler) { - handler = std::make_shared(); + handler = std::make_unique(); } -void GameLibrary::init(bool onlyEssential) +void GameLibrary::initializeFilesystem(bool extractArchives) +{ + loadFilesystem(extractArchives); + settings.init("config/settings.json", "vcmi:settings"); + persistentStorage.init("config/persistentStorage.json", ""); + loadModFilesystem(); +} + +void GameLibrary::initializeLibrary() { createHandler(settingsHandler); modh->initializeConfig(); @@ -194,7 +190,7 @@ void GameLibrary::init(bool onlyEssential) createHandler(obstacleHandler); modh->load(); - modh->afterLoad(onlyEssential); + modh->afterLoad(); } #if SCRIPTING_ENABLED @@ -207,14 +203,4 @@ void GameLibrary::scriptsLoaded() GameLibrary::GameLibrary() = default; GameLibrary::~GameLibrary() = default; -std::shared_ptr GameLibrary::getContent() const -{ - return modh->content; -} - -void GameLibrary::setContent(std::shared_ptr content) -{ - modh->content = std::move(content); -} - VCMI_LIB_NAMESPACE_END diff --git a/lib/GameLibrary.h b/lib/GameLibrary.h index 4726f7cc4..3b954171d 100644 --- a/lib/GameLibrary.h +++ b/lib/GameLibrary.h @@ -51,10 +51,6 @@ namespace scripting /// Loads and constructs several handlers class DLL_LINKAGE GameLibrary final : public Services { - - std::shared_ptr getContent() const; - void setContent(std::shared_ptr content); - public: const ArtifactService * artifacts() const override; const CreatureService * creatures() const override; @@ -76,38 +72,44 @@ public: const IBonusTypeHandler * getBth() const; //deprecated const CIdentifierStorage * identifiers() const; - std::shared_ptr arth; - std::shared_ptr bth; - std::shared_ptr heroh; - std::shared_ptr heroclassesh; - std::shared_ptr creh; - std::shared_ptr spellh; - std::shared_ptr skillh; + std::unique_ptr arth; + std::unique_ptr bth; + std::unique_ptr heroh; + std::unique_ptr heroclassesh; + std::unique_ptr creh; + std::unique_ptr spellh; + std::unique_ptr skillh; // TODO: Remove ObjectHandler altogether? - std::shared_ptr objh; - std::shared_ptr objtypeh; - std::shared_ptr townh; - std::shared_ptr generaltexth; - std::shared_ptr modh; - std::shared_ptr terrainTypeHandler; - std::shared_ptr roadTypeHandler; - std::shared_ptr riverTypeHandler; - std::shared_ptr identifiersHandler; - std::shared_ptr terviewh; - std::shared_ptr tplh; - std::shared_ptr battlefieldsHandler; - std::shared_ptr obstacleHandler; - std::shared_ptr settingsHandler; - std::shared_ptr biomeHandler; + std::unique_ptr objh; + std::unique_ptr objtypeh; + std::unique_ptr townh; + std::unique_ptr generaltexth; + std::unique_ptr modh; + std::unique_ptr terrainTypeHandler; + std::unique_ptr roadTypeHandler; + std::unique_ptr riverTypeHandler; + std::unique_ptr identifiersHandler; + std::unique_ptr terviewh; + std::unique_ptr tplh; + std::unique_ptr battlefieldsHandler; + std::unique_ptr obstacleHandler; + std::unique_ptr settingsHandler; + std::unique_ptr biomeHandler; #if SCRIPTING_ENABLED - std::shared_ptr scriptHandler; + std::unique_ptr scriptHandler; #endif - GameLibrary(); //c-tor, loads .lods and NULLs handlers + GameLibrary(); ~GameLibrary(); - void init(bool onlyEssential); //uses standard config file + /// initializes settings and filesystem + void initializeFilesystem(bool extractArchives); + + /// Loads all game entities + void initializeLibrary(); + +private: // basic initialization. should be called before init(). Can also extract original H3 archives void loadFilesystem(bool extractArchives); void loadModFilesystem(); @@ -119,8 +121,4 @@ public: extern DLL_LINKAGE GameLibrary * LIBRARY; -DLL_LINKAGE void preinitDLL(bool extractArchives); -DLL_LINKAGE void loadDLLClasses(bool onlyEssential = false); - - VCMI_LIB_NAMESPACE_END diff --git a/lib/StartInfo.h b/lib/StartInfo.h index 8a79dc344..36ba625ec 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -51,11 +51,7 @@ struct DLL_LINKAGE SimturnsInfo h & requiredTurns; h & optionalTurns; h & allowHumanWithAI; - - if (h.version >= Handler::Version::SAVE_COMPATIBILITY_FIXES) - h & ignoreAlliedContacts; - else - ignoreAlliedContacts = true; + h & ignoreAlliedContacts; } }; @@ -108,14 +104,7 @@ struct DLL_LINKAGE PlayerSettings h & heroNameTextId; h & bonus; h & color; - if (h.version >= Handler::Version::PLAYER_HANDICAP) - h & handicap; - else - { - enum EHandicap {NO_HANDICAP, MILD, SEVERE}; - EHandicap handicapLegacy = NO_HANDICAP; - h & handicapLegacy; - } + h & handicap; h & name; h & connectedPlayerIDs; h & compOnly; @@ -173,24 +162,7 @@ struct DLL_LINKAGE StartInfo : public Serializeable h & mode; h & difficulty; h & playerInfos; - if (h.version < Handler::Version::REMOVE_LIB_RNG) - { - uint32_t oldSeeds = 0; - h & oldSeeds; - h & oldSeeds; - h & oldSeeds; - } - if (h.version < Handler::Version::FOLDER_NAME_REWORK) - { - std::string startTimeLegacy; - h & startTimeLegacy; - struct std::tm tm; - std::istringstream ss(startTimeLegacy); - ss >> std::get_time(&tm, "%Y%m%dT%H%M%S"); - startTime = mktime(&tm); - } - else - h & startTime; + h & startTime; h & fileURI; h & simturnsInfo; h & turnTimerInfo; diff --git a/lib/VCMIDirs.cpp b/lib/VCMIDirs.cpp index 86cb2104d..3161d04cd 100644 --- a/lib/VCMIDirs.cpp +++ b/lib/VCMIDirs.cpp @@ -171,9 +171,6 @@ class VCMIDirsWIN32 final : public IVCMIDirs void VCMIDirsWIN32::init() { - std::locale::global(boost::locale::generator().generate("en_US.UTF-8")); - boost::filesystem::path::imbue(std::locale()); - // Call base (init dirs) IVCMIDirs::init(); diff --git a/lib/battle/BattleHexArray.h b/lib/battle/BattleHexArray.h index 99510f87e..7fc188124 100644 --- a/lib/battle/BattleHexArray.h +++ b/lib/battle/BattleHexArray.h @@ -77,7 +77,7 @@ public: void insert(const BattleHex & hex) noexcept { - if(contains(hex)) + if(!isValidToInsert(hex)) return; presenceFlags.set(hex.toInt()); @@ -86,6 +86,9 @@ public: void set(size_type index, const BattleHex & hex) { + if(!isValidToInsert(hex)) + return; + if(index >= internalStorage.size()) { logGlobal->error("Invalid BattleHexArray::set index parameter. It is " + std::to_string(index) @@ -94,9 +97,6 @@ public: + " and current size is " + std::to_string(internalStorage.size())); } - if(contains(hex)) - return; - presenceFlags.set(hex.toInt()); internalStorage[index] = hex; } @@ -198,7 +198,7 @@ public: { static const BattleHexArray invalid; - if (hex.isValid()) + if(hex.isValid()) return allNeighbouringTiles[hex.toInt()]; else return invalid; @@ -209,7 +209,7 @@ public: { static const BattleHexArray invalid; - if (hex.isValid()) + if(hex.isValid()) return neighbouringTiles[hex.toInt()]; else return invalid; @@ -223,13 +223,23 @@ public: return neighbouringTilesDoubleWide.at(side)[hex.toInt()]; } - /// note: returns true when param is ivalid BattleHex + [[nodiscard]] inline bool isValidToInsert(const BattleHex & hex) const noexcept + { + if(!hex.isValid()) + return false; + + if(contains(hex)) + return false; + + return true; + } + [[nodiscard]] inline bool contains(const BattleHex & hex) const noexcept { if(hex.isValid()) return presenceFlags.test(hex.toInt()); - return true; + return false; } template diff --git a/lib/bonuses/Limiters.h b/lib/bonuses/Limiters.h index bafa4f7b0..3023e8779 100644 --- a/lib/bonuses/Limiters.h +++ b/lib/bonuses/Limiters.h @@ -108,16 +108,7 @@ public: template void serialize(Handler &h) { h & static_cast(*this); - - if (h.version < Handler::Version::REMOVE_TOWN_PTR) - { - bool isNull = false; - h & isNull; - if(!isNull) - h & creatureID; - } - else - h & creatureID; + h & creatureID; h & includeUpgrades; } }; diff --git a/lib/campaign/CampaignState.h b/lib/campaign/CampaignState.h index 78017482d..bcff8a883 100644 --- a/lib/campaign/CampaignState.h +++ b/lib/campaign/CampaignState.h @@ -46,16 +46,8 @@ class DLL_LINKAGE CampaignRegions template void serialize(Handler &h) { h & infix; - if (h.version >= Handler::Version::REGION_LABEL) - { - h & pos; - h & labelPos; - } - else - { - h & pos.x; - h & pos.y; - } + h & pos; + h & labelPos; } static CampaignRegions::RegionDescription fromJson(const JsonNode & node); @@ -79,11 +71,8 @@ public: h & campPrefix; h & colorSuffixLength; h & regions; - if (h.version >= Handler::Version::CAMPAIGN_REGIONS) - { - h & campSuffix; - h & campBackground; - } + h & campSuffix; + h & campBackground; } static CampaignRegions fromJson(const JsonNode & node); @@ -150,27 +139,20 @@ public: h & numberOfScenarios; h & name; h & description; - if (h.version >= Handler::Version::MAP_FORMAT_ADDITIONAL_INFOS) - { - h & author; - h & authorContact; - h & campaignVersion; - h & creationDateTime; - } + h & author; + h & authorContact; + h & campaignVersion; + h & creationDateTime; h & difficultyChosenByPlayer; h & filename; h & modName; h & music; h & encoding; h & textContainer; - if (h.version >= Handler::Version::CHRONICLES_SUPPORT) - { - h & loadingBackground; - h & videoRim; - h & introVideo; - } - if (h.version >= Handler::Version::CAMPAIGN_OUTRO_SUPPORT) - h & outroVideo; + h & loadingBackground; + h & videoRim; + h & introVideo; + h & outroVideo; } }; @@ -374,8 +356,7 @@ public: h & chosenCampaignBonuses; h & campaignSet; h & mapTranslations; - if (h.version >= Handler::Version::HIGHSCORE_PARAMETERS) - h & highscoreParameters; + h & highscoreParameters; } }; diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index e21533459..30590019f 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -351,15 +351,6 @@ void CGameState::initCampaign() map = campaign->getCurrentMap(); } -void CGameState::generateOwnedObjectsAfterDeserialize() -{ - for (auto & object : map->objects) - { - if (object && object->asOwnable() && object->getOwner().isValidPlayer()) - players.at(object->getOwner()).addOwnedObject(object.get()); - } -} - void CGameState::initGlobalBonuses() { const JsonNode & baseBonuses = getSettings().getValue(EGameSettings::BONUSES_GLOBAL); diff --git a/lib/gameState/CGameState.h b/lib/gameState/CGameState.h index 5e77ce7e6..1f18dd11a 100644 --- a/lib/gameState/CGameState.h +++ b/lib/gameState/CGameState.h @@ -171,21 +171,13 @@ public: h & day; h & map; h & players; - if (h.version < Handler::Version::PLAYER_STATE_OWNED_OBJECTS) - generateOwnedObjectsAfterDeserialize(); h & teams; h & heroesPool; h & globalEffects; - if (h.version < Handler::Version::REMOVE_LIB_RNG) - { - std::string oldStateOfRNG; - h & oldStateOfRNG; - } h & currentRumor; h & campaign; h & allocatedArtifacts; - if (h.version >= Handler::Version::STATISTICS) - h & statistic; + h & statistic; BONUS_TREE_DESERIALIZATION_FIX } @@ -213,8 +205,6 @@ private: void initVisitingAndGarrisonedHeroes(); void initCampaign(); - void generateOwnedObjectsAfterDeserialize(); - // ----- bonus system handling ----- void buildBonusSystemTree(); diff --git a/lib/gameState/GameStatistics.h b/lib/gameState/GameStatistics.h index 851f6417c..d867bc015 100644 --- a/lib/gameState/GameStatistics.h +++ b/lib/gameState/GameStatistics.h @@ -63,8 +63,7 @@ struct DLL_LINKAGE StatisticDataSetEntry h & timestamp; h & day; h & player; - if(h.version >= Handler::Version::STATISTICS_SCREEN) - h & playerName; + h & playerName; h & team; h & isHuman; h & status; @@ -92,11 +91,8 @@ struct DLL_LINKAGE StatisticDataSetEntry h & spentResourcesForArmy; h & spentResourcesForBuildings; h & tradeVolume; - if(h.version >= Handler::Version::STATISTICS_SCREEN) - { - h & eventCapturedTown; - h & eventDefeatedStrongestHero; - } + h & eventCapturedTown; + h & eventDefeatedStrongestHero; h & movementPointsUsed; } }; @@ -136,11 +132,8 @@ public: h & spentResourcesForBuildings; h & tradeVolume; h & movementPointsUsed; - if(h.version >= Handler::Version::STATISTICS_SCREEN) - { - h & lastCapturedTownDay; - h & lastDefeatedStrongestHeroDay; - } + h & lastCapturedTownDay; + h & lastDefeatedStrongestHeroDay; } }; std::vector data; diff --git a/lib/mapObjects/CBank.h b/lib/mapObjects/CBank.h index b61563421..13116ab89 100644 --- a/lib/mapObjects/CBank.h +++ b/lib/mapObjects/CBank.h @@ -51,10 +51,7 @@ public: h & bankConfig; h & resetDuration; h & coastVisitable; - if (h.version >= Handler::Version::BANK_UNIT_PLACEMENT) - h & regularUnitPlacement; - else if (!h.saving) - regularUnitPlacement = false; + h & regularUnitPlacement; } friend class CBankInstanceConstructor; diff --git a/lib/mapObjects/CGCreature.h b/lib/mapObjects/CGCreature.h index b3fd43384..d06a87743 100644 --- a/lib/mapObjects/CGCreature.h +++ b/lib/mapObjects/CGCreature.h @@ -27,7 +27,7 @@ public: COMPLIANT = 0, FRIENDLY = 1, AGGRESSIVE = 2, HOSTILE = 3, SAVAGE = 4 }; - ui32 identifier; //unique code for this monster (used in missions) + ui32 identifier = -1; //unique code for this monster (used in missions) si8 character = 0; //character of this set of creatures (0 - the most friendly, 4 - the most hostile) => on init changed to -4 (compliant) ... 10 value (savage) MetaString message; //message printed for attacking hero TResources resources; // resources given to hero that has won with monsters diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index d980d5795..59c431671 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -371,14 +371,6 @@ public: h & skillsInfo; h & visitedTown; h & boat; - if (h.version < Handler::Version::REMOVE_TOWN_PTR) - { - HeroTypeID type; - bool isNull = false; - h & isNull; - if(!isNull) - h & type; - } h & commander; h & visitedObjects; BONUS_TREE_DESERIALIZATION_FIX diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h index 6f4f92a42..76ff390e5 100644 --- a/lib/mapObjects/CGMarket.h +++ b/lib/mapObjects/CGMarket.h @@ -36,37 +36,6 @@ public: int getMarketEfficiency() const override; int availableUnits(EMarketMode mode, int marketItemSerial) const override; //-1 if unlimited std::set availableModes() const override; - - template - void serialize(Handler &h) - { - h & static_cast(*this); - if (h.version < Handler::Version::NEW_MARKETS) - { - std::set marketModes; - h & marketModes; - } - - if (h.version < Handler::Version::MARKET_TRANSLATION_FIX) - { - int unused = 0; - h & unused; - } - - if (h.version < Handler::Version::NEW_MARKETS) - { - std::string speech; - std::string title; - h & speech; - h & title; - } - } - - template void serializeArtifactsAltar(Handler &h) - { - serialize(h); - IMarket::serializeArtifactsAltar(h); - } }; class DLL_LINKAGE CGBlackMarket : public CGMarket @@ -82,24 +51,7 @@ public: template void serialize(Handler &h) { h & static_cast(*this); - if (h.version < Handler::Version::REMOVE_VLC_POINTERS) - { - int32_t size = 0; - h & size; - for (int32_t i = 0; i < size; ++i) - { - bool isNull = false; - ArtifactID artifact; - h & isNull; - if (!isNull) - h & artifact; - artifacts.push_back(artifact); - } - } - else - { - h & artifacts; - } + h & artifacts; } }; @@ -119,12 +71,6 @@ public: { h & static_cast(*this); h & skills; - if (h.version >= Handler::Version::NEW_MARKETS && h.version < Handler::Version::MARKET_TRANSLATION_FIX) - { - std::string temp; - h & temp; - h & temp; - } } }; diff --git a/lib/mapObjects/CGObjectInstance.h b/lib/mapObjects/CGObjectInstance.h index c201dd4db..4038d22c4 100644 --- a/lib/mapObjects/CGObjectInstance.h +++ b/lib/mapObjects/CGObjectInstance.h @@ -143,12 +143,6 @@ public: template void serialize(Handler &h) { h & instanceName; - if (h.version < Handler::Version::REMOVE_OBJECT_TYPENAME) - { - std::string unused; - h & unused; - h & unused; - } h & pos; h & ID; subID.serializeIdentifier(h, ID); diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 5918b0621..e2e77bbc0 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -94,43 +94,13 @@ public: h & obligatorySpells; h & spells; h & events; - - if (h.version >= Handler::Version::SPELL_RESEARCH) - { - h & spellResearchCounterDay; - h & spellResearchAcceptedCounter; - h & spellResearchAllowed; - } - - if (h.version >= Handler::Version::NEW_TOWN_BUILDINGS) - { - h & rewardableBuildings; - } - else - { - std::vector oldVector; - h & oldVector; - rewardableBuildings = convertOldBuildings(oldVector); - } - - if (h.version < Handler::Version::REMOVE_TOWN_PTR) - { - FactionID faction; - bool isNull = false; - h & isNull; - if (!isNull) - h & faction; - } - + h & spellResearchCounterDay; + h & spellResearchAcceptedCounter; + h & spellResearchAllowed; + h & rewardableBuildings; h & townAndVis; BONUS_TREE_DESERIALIZATION_FIX - if (h.version < Handler::Version::NEW_TOWN_BUILDINGS) - { - std::set overriddenBuildings; - h & overriddenBuildings; - } - if(!h.saving) postDeserialize(); } diff --git a/lib/mapObjects/IMarket.h b/lib/mapObjects/IMarket.h index b4822e732..b163990cb 100644 --- a/lib/mapObjects/IMarket.h +++ b/lib/mapObjects/IMarket.h @@ -36,11 +36,6 @@ public: CArtifactSet * getArtifactsStorage() const; bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units - template void serializeArtifactsAltar(Handler &h) - { - h & *altarArtifactsStorage; - } - private: std::unique_ptr altarArtifactsStorage; }; diff --git a/lib/mapObjects/TownBuildingInstance.h b/lib/mapObjects/TownBuildingInstance.h index b7b06a8a2..eace6f9b1 100644 --- a/lib/mapObjects/TownBuildingInstance.h +++ b/lib/mapObjects/TownBuildingInstance.h @@ -43,14 +43,6 @@ public: template void serialize(Handler &h) { h & bID; - if (h.version < Handler::Version::NEW_TOWN_BUILDINGS) - { - // compatibility code - si32 indexOnTV = 0; //identifies its index on towns vector - BuildingSubID::EBuildingSubID bType = BuildingSubID::NONE; - h & indexOnTV; - h & bType; - } } private: @@ -90,8 +82,7 @@ public: template void serialize(Handler &h) { h & static_cast(*this); - if (h.version >= Handler::Version::NEW_TOWN_BUILDINGS) - h & static_cast(*this); + h & static_cast(*this); h & visitors; } }; diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 9c69be1cd..80b7e6690 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -192,31 +192,11 @@ public: // static members h & obeliskCount; h & obelisksVisited; - - if (h.version < Handler::Version::REMOVE_VLC_POINTERS) - { - int32_t size = 0; - h & size; - for (int32_t i = 0; i < size; ++i) - { - bool isNull = false; - ArtifactID artifact; - h & isNull; - if (!isNull) - h & artifact; - townMerchantArtifacts.push_back(artifact); - } - } - else - { - h & townMerchantArtifacts; - } + h & townMerchantArtifacts; h & townUniversitySkills; h & instanceNames; - - if (h.version >= Handler::Version::PER_MAP_GAME_SETTINGS) - h & *gameSettings; + h & *gameSettings; } }; diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index 2655786f6..cb3a1156f 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -52,26 +52,12 @@ public: h & name; h & message; h & resources; - if (h.version >= Handler::Version::EVENTS_PLAYER_SET) - { - h & players; - } - else - { - ui8 playersMask = 0; - h & playersMask; - for (int i = 0; i < 8; ++i) - if ((playersMask & (1 << i)) != 0) - players.insert(PlayerColor(i)); - } + h & players; h & humanAffected; h & computerAffected; h & firstOccurrence; h & nextOccurrence; - if(h.version >= Handler::Version::EVENT_OBJECTS_DELETION) - { - h & deletedObjectsInstances; - } + h & deletedObjectsInstances; } virtual void serializeJson(JsonSerializeFormat & handler); @@ -147,49 +133,13 @@ struct DLL_LINKAGE TerrainTile template void serialize(Handler & h) { - if (h.version >= Handler::Version::REMOVE_VLC_POINTERS) - { - h & terrainType; - } - else - { - bool isNull = false; - h & isNull; - if (!isNull) - h & terrainType; - } + h & terrainType; h & terView; - if (h.version >= Handler::Version::REMOVE_VLC_POINTERS) - { - h & riverType; - } - else - { - bool isNull = false; - h & isNull; - if (!isNull) - h & riverType; - } + h & riverType; h & riverDir; - if (h.version >= Handler::Version::REMOVE_VLC_POINTERS) - { - h & roadType; - } - else - { - bool isNull = false; - h & isNull; - if (!isNull) - h & roadType; - } + h & roadType; h & roadDir; h & extTileFlags; - if (h.version < Handler::Version::REMOVE_VLC_POINTERS) - { - bool unused = false; - h & unused; - h & unused; - } h & visitableObjects; h & blockingObjects; } diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index d5caef3d2..8bd828eb9 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -287,25 +287,14 @@ public: h & mods; h & name; h & description; - if (h.version >= Handler::Version::MAP_FORMAT_ADDITIONAL_INFOS) - { - h & author; - h & authorContact; - h & mapVersion; - h & creationDateTime; - } + h & author; + h & authorContact; + h & mapVersion; + h & creationDateTime; h & width; h & height; h & twoLevel; - - if (h.version >= Handler::Version::SAVE_COMPATIBILITY_FIXES) - h & difficulty; - else - { - uint8_t difficultyInteger = static_cast(difficulty); - h & difficultyInteger; - difficulty = static_cast(difficultyInteger); - } + h & difficulty; h & levelLimit; h & areAnyPlayers; diff --git a/lib/mapping/CMapInfo.cpp b/lib/mapping/CMapInfo.cpp index 8a901c8cf..79741fc55 100644 --- a/lib/mapping/CMapInfo.cpp +++ b/lib/mapping/CMapInfo.cpp @@ -48,6 +48,8 @@ void CMapInfo::mapInit(const std::string & fname) originalFileURI = resource.getOriginalName(); fullFileURI = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(resource)).string(); mapHeader = mapService.loadMapHeader(resource); + lastWrite = boost::filesystem::last_write_time(*CResourceHandler::get()->getResourceName(resource)); + date = TextOperations::getFormattedDateTimeLocal(lastWrite); countPlayers(); } @@ -76,6 +78,8 @@ void CMapInfo::campaignInit() originalFileURI = resource.getOriginalName(); fullFileURI = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(resource)).string(); campaign = CampaignHandler::getHeader(fileURI); + lastWrite = boost::filesystem::last_write_time(*CResourceHandler::get()->getResourceName(resource)); + date = TextOperations::getFormattedDateTimeLocal(lastWrite); } void CMapInfo::countPlayers() diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index fe5529d05..e7fb31dae 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -309,7 +309,7 @@ void CModHandler::load() logMod->info("\tAll game content loaded"); } -void CModHandler::afterLoad(bool onlyEssential) +void CModHandler::afterLoad() { JsonNode modSettings; for (const auto & modEntry : getActiveMods()) diff --git a/lib/modding/CModHandler.h b/lib/modding/CModHandler.h index e991459c8..ae7bfc531 100644 --- a/lib/modding/CModHandler.h +++ b/lib/modding/CModHandler.h @@ -62,7 +62,7 @@ public: /// load content from all available mods void load(); - void afterLoad(bool onlyEssential); + void afterLoad(); CModHandler(); ~CModHandler(); diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index e4f96fa48..4e752845d 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -481,8 +481,7 @@ struct DLL_LINKAGE PlayerEndsGame : public CPackForClient { h & player; h & victoryLossCheckResult; - if (h.version >= Handler::Version::STATISTICS_SCREEN) - h & statistic; + h & statistic; } }; diff --git a/lib/rewardable/Configuration.h b/lib/rewardable/Configuration.h index e9e9f832f..7c1b7548a 100644 --- a/lib/rewardable/Configuration.h +++ b/lib/rewardable/Configuration.h @@ -194,13 +194,8 @@ struct DLL_LINKAGE Configuration h & canRefuse; h & showScoutedPreview; h & infoWindowType; - if (h.version >= Handler::Version::REWARDABLE_BANKS) - { - h & coastVisitable; - h & guardsLayout; - } - else - coastVisitable = false; + h & coastVisitable; + h & guardsLayout; } }; diff --git a/lib/rewardable/Reward.h b/lib/rewardable/Reward.h index a9ad214c9..0dd46d189 100644 --- a/lib/rewardable/Reward.h +++ b/lib/rewardable/Reward.h @@ -129,8 +129,7 @@ struct DLL_LINKAGE Reward final h & removeObject; h & manaPercentage; h & movePercentage; - if (h.version >= Handler::Version::REWARDABLE_GUARDS) - h & guards; + h & guards; h & heroExperience; h & heroLevel; h & manaDiff; diff --git a/lib/serializer/BinaryDeserializer.h b/lib/serializer/BinaryDeserializer.h index 25c232ca5..d4ed96c0a 100644 --- a/lib/serializer/BinaryDeserializer.h +++ b/lib/serializer/BinaryDeserializer.h @@ -162,10 +162,7 @@ public: else { static_assert(!std::is_same_v, "Serialization of unsigned 64-bit value may not work in some cases"); - if (hasFeature(Version::COMPACT_INTEGER_SERIALIZATION)) - data = loadEncodedInteger(); - else - this->read(static_cast(&data), sizeof(data), reverseEndianness); + data = loadEncodedInteger(); } } @@ -444,32 +441,23 @@ public: } void load(std::string &data) { - if (hasFeature(Version::COMPACT_STRING_SERIALIZATION)) - { - int32_t length; - load(length); + int32_t length; + load(length); - if (length < 0) - { - int32_t stringID = -length - 1; // -1, -2 ... -> 0, 1 ... - data = loadedStrings[stringID]; - } - if (length == 0) - { - data = {}; - } - if (length > 0) - { - data.resize(length); - this->read(static_cast(data.data()), length, false); - loadedStrings.push_back(data); - } - } - else + if (length < 0) + { + int32_t stringID = -length - 1; // -1, -2 ... -> 0, 1 ... + data = loadedStrings[stringID]; + } + if (length == 0) + { + data = {}; + } + if (length > 0) { - uint32_t length = readAndCheckLength(); data.resize(length); this->read(static_cast(data.data()), length, false); + loadedStrings.push_back(data); } } diff --git a/lib/serializer/BinarySerializer.h b/lib/serializer/BinarySerializer.h index 87a303706..6b6d170bc 100644 --- a/lib/serializer/BinarySerializer.h +++ b/lib/serializer/BinarySerializer.h @@ -148,10 +148,7 @@ public: } else { - if (hasFeature(Version::COMPACT_INTEGER_SERIALIZATION)) - saveEncodedInteger(data); - else - this->write(static_cast(&data), sizeof(data)); + saveEncodedInteger(data); } } @@ -325,36 +322,28 @@ public: void save(const std::string &data) { - if (hasFeature(Version::COMPACT_STRING_SERIALIZATION)) + if (data.empty()) { - if (data.empty()) - { - save(static_cast(0)); - return; - } - - auto it = savedStrings.find(data); - - if (it == savedStrings.end()) - { - save(static_cast(data.length())); - this->write(static_cast(data.data()), data.size()); - - // -1, -2... - int32_t newStringID = -1 - savedStrings.size(); - - savedStrings[data] = newStringID; - } - else - { - int32_t index = it->second; - save(index); - } + save(static_cast(0)); + return; } - else + + auto it = savedStrings.find(data); + + if (it == savedStrings.end()) { save(static_cast(data.length())); this->write(static_cast(data.data()), data.size()); + + // -1, -2... + int32_t newStringID = -1 - savedStrings.size(); + + savedStrings[data] = newStringID; + } + else + { + int32_t index = it->second; + save(index); } } diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index c7c47a828..6ba36f9d3 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -31,47 +31,8 @@ enum class ESerializationVersion : int32_t { NONE = 0, - RELEASE_150 = 840, - MINIMAL = RELEASE_150, - - VOTING_SIMTURNS, // 841 - allow modification of simturns duration via vote - REMOVE_TEXT_CONTAINER_SIZE_T, // 842 Fixed serialization of size_t from text containers - BANK_UNIT_PLACEMENT, // 843 Banks have unit placement flag - - RELEASE_156 = BANK_UNIT_PLACEMENT, - - COMPACT_STRING_SERIALIZATION, // 844 - optimized serialization of previously encountered strings - COMPACT_INTEGER_SERIALIZATION, // 845 - serialize integers in forms similar to protobuf - REMOVE_FOG_OF_WAR_POINTER, // 846 - fog of war is serialized as reference instead of pointer - SIMPLE_TEXT_CONTAINER_SERIALIZATION, // 847 - text container is serialized using common routine instead of custom approach - MAP_FORMAT_ADDITIONAL_INFOS, // 848 - serialize new infos in map format - REMOVE_LIB_RNG, // 849 - removed random number generators from library classes - HIGHSCORE_PARAMETERS, // 850 - saves parameter for campaign - PLAYER_HANDICAP, // 851 - player handicap selection at game start - STATISTICS, // 852 - removed random number generators from library classes - CAMPAIGN_REGIONS, // 853 - configurable campaign regions - EVENTS_PLAYER_SET, // 854 - map & town events use std::set instead of bitmask to store player list - NEW_TOWN_BUILDINGS, // 855 - old bonusing buildings have been removed - STATISTICS_SCREEN, // 856 - extent statistic functions - NEW_MARKETS, // 857 - reworked market classes - PLAYER_STATE_OWNED_OBJECTS, // 858 - player state stores all owned objects in a single list - SAVE_COMPATIBILITY_FIXES, // 859 - implementation of previoulsy postponed changes to serialization - CHRONICLES_SUPPORT, // 860 - support for heroes chronicles - PER_MAP_GAME_SETTINGS, // 861 - game settings are now stored per-map - CAMPAIGN_OUTRO_SUPPORT, // 862 - support for campaign outro video - REWARDABLE_BANKS, // 863 - team state contains list of scouted objects, coast visitable rewardable objects - REGION_LABEL, // 864 - labels for campaign regions - SPELL_RESEARCH, // 865 - spell research - LOCAL_PLAYER_STATE_DATA, // 866 - player state contains arbitrary client-side data - REMOVE_TOWN_PTR, // 867 - removed pointer to CTown from CGTownInstance - REMOVE_OBJECT_TYPENAME, // 868 - remove typename from CGObjectInstance - REMOVE_VLC_POINTERS, // 869 removed remaining pointers to LIBRARY entities - FOLDER_NAME_REWORK, // 870 - rework foldername - REWARDABLE_GUARDS, // 871 - fix missing serialization of guards in rewardable objects - MARKET_TRANSLATION_FIX, // 872 - remove serialization of markets translateable strings - EVENT_OBJECTS_DELETION, //873 - allow events to remove map objects - RELEASE_160 = 873, + MINIMAL = RELEASE_160, MAP_HEADER_DISPOSED_HEROES, // map header contains disposed heroes list diff --git a/lib/serializer/SerializerReflection.cpp b/lib/serializer/SerializerReflection.cpp index ecb3427b8..861174812 100644 --- a/lib/serializer/SerializerReflection.cpp +++ b/lib/serializer/SerializerReflection.cpp @@ -63,24 +63,6 @@ public: } }; -class SerializerCompatibilityBonusingBuilding final : public SerializerCompatibility -{ - void loadPtr(BinaryDeserializer &ar, IGameCallback * cb, Serializeable * data) const override - { - auto * realPtr = dynamic_cast(data); - realPtr->serialize(ar); - } -}; - -class SerializerCompatibilityArtifactsAltar final : public SerializerCompatibility -{ - void loadPtr(BinaryDeserializer &ar, IGameCallback * cb, Serializeable * data) const override - { - auto * realPtr = dynamic_cast(data); - realPtr->serializeArtifactsAltar(ar); - } -}; - template void CSerializationApplier::registerType(uint16_t ID) { @@ -91,10 +73,6 @@ void CSerializationApplier::registerType(uint16_t ID) CSerializationApplier::CSerializationApplier() { registerTypes(*this); - - apps[54].reset(new SerializerCompatibilityBonusingBuilding); - apps[55].reset(new SerializerCompatibilityBonusingBuilding); - apps[81].reset(new SerializerCompatibilityArtifactsAltar); } CSerializationApplier & CSerializationApplier::getInstance() diff --git a/lib/spells/effects/Catapult.cpp b/lib/spells/effects/Catapult.cpp index 763ad3045..509862a56 100644 --- a/lib/spells/effects/Catapult.cpp +++ b/lib/spells/effects/Catapult.cpp @@ -263,7 +263,7 @@ void Catapult::adjustHitChance() vstd::abetween(wall, 0, 100); vstd::abetween(crit, 0, 100); vstd::abetween(hit, 0, 100 - crit); - vstd::amin(noDmg, 100 - hit - crit); + noDmg = 100 - hit - crit; } void Catapult::serializeJsonEffect(JsonSerializeFormat & handler) diff --git a/lib/texts/Languages.h b/lib/texts/Languages.h index 4f6f0e2dc..bae0a99a4 100644 --- a/lib/texts/Languages.h +++ b/lib/texts/Languages.h @@ -66,6 +66,9 @@ struct Options /// encoding that is used by H3 for this language std::string encoding; + /// proper locale name, e.g. "en_US.UTF-8" + std::string localeName; + /// primary IETF language tag std::string tagIETF; @@ -86,28 +89,28 @@ inline const auto & getLanguageList() { static const std::array languages { { - { "bulgarian", "Bulgarian", "Български", "CP1251", "bg", "bul", "%d.%m.%Y %H:%M", EPluralForms::EN_2, true }, - { "czech", "Czech", "Čeština", "CP1250", "cs", "cze", "%d.%m.%Y %H:%M", EPluralForms::CZ_3, true }, - { "chinese", "Chinese", "简体中文", "GBK", "zh", "chi", "%Y-%m-%d %H:%M", EPluralForms::VI_1, true }, // Note: actually Simplified Chinese - { "english", "English", "English", "CP1252", "en", "eng", "%Y-%m-%d %H:%M", EPluralForms::EN_2, true }, // English uses international date/time format here - { "finnish", "Finnish", "Suomi", "CP1252", "fi", "fin", "%d.%m.%Y %H:%M", EPluralForms::EN_2, true }, - { "french", "French", "Français", "CP1252", "fr", "fre", "%d/%m/%Y %H:%M", EPluralForms::FR_2, true }, - { "german", "German", "Deutsch", "CP1252", "de", "ger", "%d.%m.%Y %H:%M", EPluralForms::EN_2, true }, - { "greek", "Greek", "ελληνικά", "CP1253", "el", "ell", "%d/%m/%Y %H:%M", EPluralForms::EN_2, false }, - { "hungarian", "Hungarian", "Magyar", "CP1250", "hu", "hun", "%Y. %m. %d. %H:%M", EPluralForms::EN_2, true }, - { "italian", "Italian", "Italiano", "CP1250", "it", "ita", "%d/%m/%Y %H:%M", EPluralForms::EN_2, true }, - { "japanese", "Japanese", "日本語", "JIS", "ja", "jpn", "%Y年%m月%d日 %H:%M", EPluralForms::NONE, false }, - { "korean", "Korean", "한국어", "CP949", "ko", "kor", "%Y-%m-%d %H:%M", EPluralForms::VI_1, true }, - { "polish", "Polish", "Polski", "CP1250", "pl", "pol", "%d.%m.%Y %H:%M", EPluralForms::PL_3, true }, - { "portuguese", "Portuguese", "Português", "CP1252", "pt", "por", "%d/%m/%Y %H:%M", EPluralForms::EN_2, true }, // Note: actually Brazilian Portuguese - { "romanian", "Romanian", "Română", "CP28606","ro", "rum", "%Y-%m-%d %H:%M", EPluralForms::RO_3, false }, - { "russian", "Russian", "Русский", "CP1251", "ru", "rus", "%d.%m.%Y %H:%M", EPluralForms::UK_3, true }, - { "spanish", "Spanish", "Español", "CP1252", "es", "spa", "%d/%m/%Y %H:%M", EPluralForms::EN_2, true }, - { "swedish", "Swedish", "Svenska", "CP1252", "sv", "swe", "%Y-%m-%d %H:%M", EPluralForms::EN_2, true }, - { "norwegian", "Norwegian", "Norsk", "CP1252", "no", "nor", "%d/%m/%Y %H:%M", EPluralForms::EN_2, false }, - { "turkish", "Turkish", "Türkçe", "CP1254", "tr", "tur", "%d.%m.%Y %H:%M", EPluralForms::EN_2, true }, - { "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", "ukr", "%d.%m.%Y %H:%M", EPluralForms::UK_3, true }, - { "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", "vie", "%d/%m/%Y %H:%M", EPluralForms::VI_1, true }, // Fan translation uses special encoding + { "bulgarian", "Bulgarian", "Български", "CP1251", "bg_BG.UTF-8", "bg", "bul", "%d.%m.%Y %H:%M", EPluralForms::EN_2, true }, + { "czech", "Czech", "Čeština", "CP1250", "cs_CZ.UTF-8", "cs", "cze", "%d.%m.%Y %H:%M", EPluralForms::CZ_3, true }, + { "chinese", "Chinese", "简体中文", "GBK", "zh_CN.UTF-8", "zh", "chi", "%Y-%m-%d %H:%M", EPluralForms::VI_1, true }, // Note: actually Simplified Chinese + { "english", "English", "English", "CP1252", "en_US.UTF-8", "en", "eng", "%Y-%m-%d %H:%M", EPluralForms::EN_2, true }, // English uses international date/time format here + { "finnish", "Finnish", "Suomi", "CP1252", "fi_FI.UTF-8", "fi", "fin", "%d.%m.%Y %H:%M", EPluralForms::EN_2, true }, + { "french", "French", "Français", "CP1252", "fr_FR.UTF-8", "fr", "fre", "%d/%m/%Y %H:%M", EPluralForms::FR_2, true }, + { "german", "German", "Deutsch", "CP1252", "de_DE.UTF-8", "de", "ger", "%d.%m.%Y %H:%M", EPluralForms::EN_2, true }, + { "greek", "Greek", "ελληνικά", "CP1253", "el_GR.UTF-8", "el", "ell", "%d/%m/%Y %H:%M", EPluralForms::EN_2, false }, + { "hungarian", "Hungarian", "Magyar", "CP1250", "hu_HU.UTF-8", "hu", "hun", "%Y. %m. %d. %H:%M", EPluralForms::EN_2, true }, + { "italian", "Italian", "Italiano", "CP1250", "it_IT.UTF-8", "it", "ita", "%d/%m/%Y %H:%M", EPluralForms::EN_2, true }, + { "japanese", "Japanese", "日本語", "JIS", "ja_JP.UTF-8", "ja", "jpn", "%Y年%m月%d日 %H:%M", EPluralForms::NONE, false }, + { "korean", "Korean", "한국어", "CP949", "ko_KR.UTF-8", "ko", "kor", "%Y-%m-%d %H:%M", EPluralForms::VI_1, true }, + { "polish", "Polish", "Polski", "CP1250", "pl_PL.UTF-8", "pl", "pol", "%d.%m.%Y %H:%M", EPluralForms::PL_3, true }, + { "portuguese", "Portuguese", "Português", "CP1252", "pt_BR.UTF-8", "pt", "por", "%d/%m/%Y %H:%M", EPluralForms::EN_2, true }, // Note: actually Brazilian Portuguese + { "romanian", "Romanian", "Română", "CP28606", "ro_RO.UTF-8", "ro", "rum", "%Y-%m-%d %H:%M", EPluralForms::RO_3, false }, + { "russian", "Russian", "Русский", "CP1251", "ru_RU.UTF-8", "ru", "rus", "%d.%m.%Y %H:%M", EPluralForms::UK_3, true }, + { "spanish", "Spanish", "Español", "CP1252", "es_ES.UTF-8", "es", "spa", "%d/%m/%Y %H:%M", EPluralForms::EN_2, true }, + { "swedish", "Swedish", "Svenska", "CP1252", "sv_SE.UTF-8", "sv", "swe", "%Y-%m-%d %H:%M", EPluralForms::EN_2, true }, + { "norwegian", "Norwegian", "Norsk Bokmål", "UTF-8", "nb_NO.UTF-8", "nb", "nor", "%d/%m/%Y %H:%M", EPluralForms::EN_2, false }, + { "turkish", "Turkish", "Türkçe", "CP1254", "tr_TR.UTF-8", "tr", "tur", "%d.%m.%Y %H:%M", EPluralForms::EN_2, true }, + { "ukrainian", "Ukrainian", "Українська", "CP1251", "uk_UA.UTF-8", "uk", "ukr", "%d.%m.%Y %H:%M", EPluralForms::UK_3, true }, + { "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi_VN.UTF-8", "vi", "vie", "%d/%m/%Y %H:%M", EPluralForms::VI_1, true }, // Fan translation uses special encoding } }; static_assert(languages.size() == static_cast(ELanguages::COUNT), "Languages array is missing a value!"); diff --git a/lib/texts/TextLocalizationContainer.h b/lib/texts/TextLocalizationContainer.h index 523335c57..56fcca467 100644 --- a/lib/texts/TextLocalizationContainer.h +++ b/lib/texts/TextLocalizationContainer.h @@ -93,45 +93,7 @@ public: void serialize(Handler & h) { std::lock_guard globalLock(globalTextMutex); - - if (h.version >= Handler::Version::SIMPLE_TEXT_CONTAINER_SERIALIZATION) - { - h & stringsLocalizations; - } - else - { - std::string key; - int64_t sz = stringsLocalizations.size(); - - if (h.version >= Handler::Version::REMOVE_TEXT_CONTAINER_SIZE_T) - { - int64_t size = sz; - h & size; - sz = size; - } - else - { - h & sz; - } - - if(h.saving) - { - for(auto & s : stringsLocalizations) - { - key = s.first; - h & key; - h & s.second; - } - } - else - { - for(size_t i = 0; i < sz; ++i) - { - h & key; - h & stringsLocalizations[key]; - } - } - } + h & stringsLocalizations; } }; diff --git a/lib/texts/TextOperations.cpp b/lib/texts/TextOperations.cpp index 0a1719ec6..4c4c5129e 100644 --- a/lib/texts/TextOperations.cpp +++ b/lib/texts/TextOperations.cpp @@ -10,7 +10,8 @@ #include "StdInc.h" #include "TextOperations.h" -#include "texts/CGeneralTextHandler.h" +#include "../GameLibrary.h" +#include "../texts/CGeneralTextHandler.h" #include "Languages.h" #include "CConfigHandler.h" @@ -252,7 +253,7 @@ std::string TextOperations::getCurrentFormattedDateTimeLocal(std::chrono::second return TextOperations::getFormattedDateTimeLocal(std::chrono::system_clock::to_time_t(timepoint)); } -int TextOperations::getLevenshteinDistance(const std::string & s, const std::string & t) +int TextOperations::getLevenshteinDistance(std::string_view s, std::string_view t) { int n = t.size(); int m = s.size(); @@ -300,30 +301,51 @@ int TextOperations::getLevenshteinDistance(const std::string & s, const std::str return v0[n]; } -bool TextOperations::textSearchSimilar(const std::string & s, const std::string & t) +DLL_LINKAGE std::string TextOperations::getLocaleName() { - boost::locale::generator gen; - std::locale loc = gen("en_US.UTF-8"); // support for UTF8 lowercase - + return Languages::getLanguageOptions(LIBRARY->generaltexth->getPreferredLanguage()).localeName; +} + +DLL_LINKAGE bool TextOperations::compareLocalizedStrings(std::string_view str1, std::string_view str2) +{ + static const std::locale loc(getLocaleName()); + static const std::collate & col = std::use_facet>(loc); + + return col.compare(str1.data(), str1.data() + str1.size(), + str2.data(), str2.data() + str2.size()) < 0; +} + +std::optional TextOperations::textSearchSimilarityScore(const std::string & s, const std::string & t) +{ + static const std::locale loc = boost::locale::generator().generate(getLocaleName()); + auto haystack = boost::locale::to_lower(t, loc); auto needle = boost::locale::to_lower(s, loc); - if(boost::algorithm::contains(haystack, needle)) - return true; + // 0 - Best possible match: text starts with the search string + if(haystack.rfind(needle, 0) == 0) + return 0; - if(needle.size() > haystack.size()) - return false; + // 1 - Strong match: text contains the search string + if(haystack.find(needle) != std::string::npos) + return 1; - for(int i = 0; i < haystack.size() - needle.size() + 1; i++) + // Dynamic threshold: Reject if too many typos based on word length + int maxAllowedDistance = std::max(2, static_cast(needle.size() / 2)); + + // Compute Levenshtein distance for fuzzy similarity + int minDist = std::numeric_limits::max(); + for(size_t i = 0; i <= haystack.size() - needle.size(); i++) { - auto dist = getLevenshteinDistance(haystack.substr(i, needle.size()), needle); - if(needle.size() > 2 && dist <= 1) - return true; - else if(needle.size() > 4 && dist <= 2) - return true; + int dist = getLevenshteinDistance(haystack.substr(i, needle.size()), needle); + minDist = std::min(minDist, dist); } - - return false; + + // Apply scaling: Short words tolerate smaller distances + if(needle.size() > 2 && minDist <= 2) + minDist += 1; + + return (minDist > maxAllowedDistance) ? std::nullopt : std::optional{ minDist }; } VCMI_LIB_NAMESPACE_END diff --git a/lib/texts/TextOperations.h b/lib/texts/TextOperations.h index 465f2fbad..3c526b362 100644 --- a/lib/texts/TextOperations.h +++ b/lib/texts/TextOperations.h @@ -74,10 +74,19 @@ namespace TextOperations /// Algorithm for detection of typos in words /// Determines how 'different' two strings are - how many changes must be done to turn one string into another one /// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows - DLL_LINKAGE int getLevenshteinDistance(const std::string & s, const std::string & t); + DLL_LINKAGE int getLevenshteinDistance(std::string_view s, std::string_view t); + + /// Retrieves the locale name based on the selected (in config) game language. + DLL_LINKAGE std::string getLocaleName(); + + /// Compares two strings using locale-aware collation based on the selected game language. + DLL_LINKAGE bool compareLocalizedStrings(std::string_view str1, std::string_view str2); /// Check if texts have similarity when typing into search boxes - DLL_LINKAGE bool textSearchSimilar(const std::string & s, const std::string & t); + /// 0 -> Exact match or starts with typed-in text, 1 -> Close match or substring match, + /// other values = Levenshtein distance, returns std::nullopt for unrelated word (bad match). + DLL_LINKAGE std::optional textSearchSimilarityScore(const std::string & s, const std::string & t); + }; template diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 56abbd45d..7d43e05b4 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -69,16 +69,6 @@ QPixmap pixmapFromJson(const QJsonValue &val) return p; } -void init() -{ - loadDLLClasses(); - - Settings config = settings.write["session"]["editor"]; - config->Bool() = true; - - logGlobal->info("Initializing VCMI_Lib"); -} - void MainWindow::loadUserSettings() { //load window settings @@ -190,7 +180,8 @@ MainWindow::MainWindow(QWidget* parent) : logGlobal->info("The log file will be saved to %s", logPath); //init - preinitDLL(extractionOptions.extractArchives); + LIBRARY = new GameLibrary(); + LIBRARY->initializeFilesystem(extractionOptions.extractArchives); // Initialize logging based on settings logConfig->configure(); @@ -250,7 +241,12 @@ MainWindow::MainWindow(QWidget* parent) : loadUserSettings(); //For example window size setTitle(); - init(); + LIBRARY->initializeLibrary(); + + Settings config = settings.write["session"]["editor"]; + config->Bool() = true; + + logGlobal->info("Initializing VCMI_Lib"); graphics = new Graphics(); // should be before curh->init() graphics->load();//must be after Content loading but should be in main thread diff --git a/server/processors/TurnOrderProcessor.h b/server/processors/TurnOrderProcessor.h index 5b038455f..e4d96d2b2 100644 --- a/server/processors/TurnOrderProcessor.h +++ b/server/processors/TurnOrderProcessor.h @@ -107,16 +107,7 @@ public: h & awaitingPlayers; h & actingPlayers; h & actedPlayers; - - if (h.version >= Handler::Version::VOTING_SIMTURNS) - { - h & simturnsMinDurationDays; - h & simturnsMaxDurationDays; - } - else if (!h.saving) - { - simturnsMinDurationDays.reset(); - simturnsMaxDurationDays.reset(); - } + h & simturnsMinDurationDays; + h & simturnsMaxDurationDays; } }; diff --git a/serverapp/EntryPoint.cpp b/serverapp/EntryPoint.cpp index b6c48ecbd..9bcce94b3 100644 --- a/serverapp/EntryPoint.cpp +++ b/serverapp/EntryPoint.cpp @@ -78,10 +78,11 @@ int main(int argc, const char * argv[]) boost::program_options::variables_map opts; handleCommandOptions(argc, argv, opts); - preinitDLL(false); + LIBRARY = new GameLibrary; + LIBRARY->initializeFilesystem(false); logConfigurator.configure(); - loadDLLClasses(); + LIBRARY->initializeLibrary(); std::srand(static_cast(time(nullptr))); { diff --git a/test/CVcmiTestConfig.cpp b/test/CVcmiTestConfig.cpp index 14ef7b6ce..6e7547e75 100644 --- a/test/CVcmiTestConfig.cpp +++ b/test/CVcmiTestConfig.cpp @@ -22,8 +22,9 @@ void CVcmiTestConfig::SetUp() { - preinitDLL(true); - loadDLLClasses(true); + LIBRARY = new GameLibrary; + LIBRARY->initializeFilesystem(false); + LIBRARY->initializeLibrary(); /* TEST_DATA_DIR may be wrong, if yes below test don't run, find your test data folder in your build and change TEST_DATA_DIR for it*/