#include "engine.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Start of tes3mp addition Include additional headers for multiplayer purposes */ #include #include "mwmp/Main.hpp" #include "mwmp/GUIController.hpp" /* End of tes3mp addition */ #include #include #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" #include "mwscript/scriptmanagerimp.hpp" #include "mwscript/interpretercontext.hpp" #include "mwsound/soundmanagerimp.hpp" #include "mwworld/class.hpp" #include "mwworld/player.hpp" #include "mwworld/worldimp.hpp" #include "mwrender/vismask.hpp" #include "mwrender/camera.hpp" #include "mwclass/classes.hpp" #include "mwdialogue/dialoguemanagerimp.hpp" #include "mwdialogue/journalimp.hpp" #include "mwdialogue/scripttest.hpp" #include "mwmechanics/mechanicsmanagerimp.hpp" #include "mwstate/statemanagerimp.hpp" #ifdef USE_OPENXR #include "mwvr/vrinputmanager.hpp" #include "mwvr/vrviewer.hpp" #include "mwvr/vrgui.hpp" #include "mwvr/vrcamera.hpp" #endif namespace { void checkSDLError(int ret) { if (ret != 0) Log(Debug::Error) << "SDL error: " << SDL_GetError(); } struct UserStats { const std::string mLabel; const std::string mBegin; const std::string mEnd; const std::string mTaken; UserStats(const std::string& label, const std::string& prefix) : mLabel(label), mBegin(prefix + "_time_begin"), mEnd(prefix + "_time_end"), mTaken(prefix + "_time_taken") {} }; enum class UserStatsType : std::size_t { Input, Sound, State, Script, Mechanics, Physics, PhysicsWorker, World, Gui, Number, }; template struct UserStatsValue { static const UserStats sValue; }; template <> const UserStats UserStatsValue::sValue {"Input", "input"}; template <> const UserStats UserStatsValue::sValue {"Sound", "sound"}; template <> const UserStats UserStatsValue::sValue {"State", "state"}; template <> const UserStats UserStatsValue::sValue {"Script", "script"}; template <> const UserStats UserStatsValue::sValue {"Mech", "mechanics"}; template <> const UserStats UserStatsValue::sValue {"Phys", "physics"}; template <> const UserStats UserStatsValue::sValue {" -Async", "physicsworker"}; template <> const UserStats UserStatsValue::sValue {"World", "world"}; template <> const UserStats UserStatsValue::sValue {"Gui", "gui"}; template struct ForEachUserStatsValue { template static void apply(F&& f) { f(UserStatsValue::sValue); using Next = ForEachUserStatsValue(static_cast(type) + 1)>; Next::apply(std::forward(f)); } }; template <> struct ForEachUserStatsValue { template static void apply(F&&) {} }; template void forEachUserStatsValue(F&& f) { ForEachUserStatsValue(0)>::apply(std::forward(f)); } template class ScopedProfile { public: ScopedProfile(osg::Timer_t frameStart, unsigned int frameNumber, const osg::Timer& timer, osg::Stats& stats) : mScopeStart(timer.tick()), mFrameStart(frameStart), mFrameNumber(frameNumber), mTimer(timer), mStats(stats) { } ScopedProfile(const ScopedProfile&) = delete; ScopedProfile& operator=(const ScopedProfile&) = delete; ~ScopedProfile() { if (!mStats.collectStats("engine")) return; const osg::Timer_t end = mTimer.tick(); const UserStats& stats = UserStatsValue::sValue; mStats.setAttribute(mFrameNumber, stats.mBegin, mTimer.delta_s(mFrameStart, mScopeStart)); mStats.setAttribute(mFrameNumber, stats.mTaken, mTimer.delta_s(mScopeStart, end)); mStats.setAttribute(mFrameNumber, stats.mEnd, mTimer.delta_s(mFrameStart, end)); } private: const osg::Timer_t mScopeStart; const osg::Timer_t mFrameStart; const unsigned int mFrameNumber; const osg::Timer& mTimer; osg::Stats& mStats; }; void initStatsHandler(Resource::Profiler& profiler) { const osg::Vec4f textColor(1.f, 1.f, 1.f, 1.f); const osg::Vec4f barColor(1.f, 1.f, 1.f, 1.f); const float multiplier = 1000; const bool average = true; const bool averageInInverseSpace = false; const float maxValue = 10000; forEachUserStatsValue([&] (const UserStats& v) { profiler.addUserStatsLine(v.mLabel, textColor, barColor, v.mTaken, multiplier, average, averageInInverseSpace, v.mBegin, v.mEnd, maxValue); }); // the forEachUserStatsValue loop is "run" at compile time, hence the settings manager is not available. // Unconditionnally add the async physics stats, and then remove it at runtime if necessary if (Settings::Manager::getInt("async num threads", "Physics") == 0) profiler.removeUserStatsLine(" -Async"); } } void OMW::Engine::executeLocalScripts() { MWWorld::LocalScripts& localScripts = mEnvironment.getWorld()->getLocalScripts(); localScripts.startIteration(); std::pair script; while (localScripts.getNext(script)) { MWScript::InterpreterContext interpreterContext ( &script.second.getRefData().getLocals(), script.second); /* Start of tes3mp addition By comparing its name with a list of script names, check if this script is allowed to send packets about its value changes If it is, set a tes3mp-only boolean to true in its interpreterContext */ if (mwmp::Main::isValidPacketScript(script.first)) { interpreterContext.sendPackets = true; } /* End of tes3mp addition */ /* Start of tes3mp addition Mark this InterpreterContext as having a SCRIPT_LOCAL context and as currently running the script with this name, so that packets sent by the Interpreter can have their origin determined by serverside scripts */ interpreterContext.trackContextType(Interpreter::Context::SCRIPT_LOCAL); interpreterContext.trackCurrentScriptName(script.first); /* End of tes3mp addition */ mEnvironment.getScriptManager()->run (script.first, interpreterContext); } } bool OMW::Engine::frame(float frametime) { try { const osg::Timer_t frameStart = mViewer->getStartTick(); const unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); const osg::Timer* const timer = osg::Timer::instance(); osg::Stats* const stats = mViewer->getViewerStats(); mEnvironment.setFrameDuration(frametime); // update input { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mEnvironment.getInputManager()->update(frametime, false); } // When the window is minimized, pause the game. Currently this *has* to be here to work around a MyGUI bug. // If we are not currently rendering, then RenderItems will not be reused resulting in a memory leak upon changing widget textures (fixed in MyGUI 3.3.2), // and destroyed widgets will not be deleted (not fixed yet, https://github.com/MyGUI/mygui/issues/21) { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (!mEnvironment.getWindowManager()->isWindowVisible()) { mEnvironment.getSoundManager()->pausePlayback(); /* Start of tes3mp change (major) The game cannot be paused in multiplayer, so prevent that from happening even here */ //return false; /* End of tes3mp change (major) */ } else mEnvironment.getSoundManager()->resumePlayback(); // sound if (mUseSound) mEnvironment.getSoundManager()->update(frametime); } /* Start of tes3mp addition Update multiplayer processing for the current frame */ mwmp::Main::frame(frametime); /* End of tes3mp addition */ // Main menu opened? Then scripts are also paused. bool paused = mEnvironment.getWindowManager()->containsMode(MWGui::GM_MainMenu); /* Start of tes3mp change (major) Time should not be frozen in multiplayer, so the paused boolean is always set to false instead */ paused = false; /* End of tes3mp change (major) */ // update game state { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mEnvironment.getStateManager()->update (frametime); } /* Start of tes3mp change (major) Whether the GUI is active should have no relevance in multiplayer, so the guiActive boolean is always set to false instead */ //bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); bool guiActive = false; /* End of tes3mp change (major) */ { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) { if (!paused) { if (mEnvironment.getWorld()->getScriptsEnabled()) { // local scripts executeLocalScripts(); // global scripts mEnvironment.getScriptManager()->getGlobalScripts().run(); } mEnvironment.getWorld()->markCellAsUnchanged(); } if (!guiActive) { double hours = (frametime * mEnvironment.getWorld()->getTimeScaleFactor()) / 3600.0; mEnvironment.getWorld()->advanceTime(hours, true); mEnvironment.getWorld()->rechargeItems(frametime, true); } } } // update mechanics { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) { mEnvironment.getMechanicsManager()->update(frametime, guiActive); } if (mEnvironment.getStateManager()->getState() == MWBase::StateManager::State_Running) { MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr(); /* Start of tes3mp change (major) In multiplayer, the game should not end when the player dies, so the code here has been commented out */ //if(!guiActive && player.getClass().getCreatureStats(player).isDead()) // mEnvironment.getStateManager()->endGame(); /* End of tes3mp change (major) */ } } // update physics { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) { mEnvironment.getWorld()->updatePhysics(frametime, guiActive, frameStart, frameNumber, *stats); } } // update world { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mEnvironment.getStateManager()->getState() != MWBase::StateManager::State_NoGame) { mEnvironment.getWorld()->update(frametime, guiActive); } } // update GUI { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mEnvironment.getWindowManager()->update(frametime); } if (stats->collectStats("resource")) { stats->setAttribute(frameNumber, "FrameNumber", frameNumber); mResourceSystem->reportStats(frameNumber, stats); stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems()); stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads()); mEnvironment.reportStats(frameNumber, *stats); } } catch (const std::exception& e) { Log(Debug::Error) << "Error in frame: " << e.what(); } return true; } OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) : mWindow(nullptr) , mEncoding(ToUTF8::WINDOWS_1252) , mEncoder(nullptr) , mScreenCaptureOperation(nullptr) , mStereoEnabled(false) , mStereoOverride(false) , mStereoView(nullptr) , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) , mCompileAllDialogue (false) , mWarningsMode (1) , mScriptConsoleMode (false) , mActivationDistanceOverride(-1) , mGrab(true) , mExportFonts(false) , mRandomSeed(0) , mScriptContext (nullptr) , mFSStrict (false) , mScriptBlacklistUse (true) , mNewGame (false) , mCfgMgr(configurationManager) { SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK|SDL_INIT_SENSOR; if(SDL_WasInit(flags) == 0) { SDL_SetMainReady(); if(SDL_Init(flags) != 0) { throw std::runtime_error("Could not initialize SDL! " + std::string(SDL_GetError())); } } } OMW::Engine::~Engine() { /* Start of tes3mp addition Free up memory allocated by multiplayer's GUIController, but make sure mwmp::Main has actually been initialized */ if (mwmp::Main::isInitialized()) mwmp::Main::get().getGUIController()->cleanUp(); /* End of tes3mp addition */ mStereoView = nullptr; mEnvironment.cleanup(); /* Start of tes3mp addition Free up memory allocated by multiplayer's Main class */ mwmp::Main::destroy(); /* End of tes3mp addition */ delete mScriptContext; mScriptContext = nullptr; mWorkQueue = nullptr; mViewer = nullptr; mResourceSystem.reset(); delete mEncoder; mEncoder = nullptr; if (mWindow) { SDL_DestroyWindow(mWindow); mWindow = nullptr; } SDL_Quit(); /* Start of tes3mp addition Free up memory allocated by multiplayer's logger */ LOG_QUIT(); /* End of tes3mp addition */ } void OMW::Engine::enableFSStrict(bool fsStrict) { mFSStrict = fsStrict; } // Set data dir void OMW::Engine::setDataDirs (const Files::PathContainer& dataDirs) { mDataDirs = dataDirs; mDataDirs.insert(mDataDirs.begin(), (mResDir / "vfs")); mFileCollections = Files::Collections (mDataDirs, !mFSStrict); } // Add BSA archive void OMW::Engine::addArchive (const std::string& archive) { mArchives.push_back(archive); } // Set resource dir void OMW::Engine::setResourceDir (const boost::filesystem::path& parResDir) { mResDir = parResDir; } // Set start cell name void OMW::Engine::setCell (const std::string& cellName) { mCellName = cellName; } void OMW::Engine::addContentFile(const std::string& file) { mContentFiles.push_back(file); } void OMW::Engine::addGroundcoverFile(const std::string& file) { mGroundcoverFiles.emplace_back(file); } void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) { mSkipMenu = skipMenu; mNewGame = newGame; } std::string OMW::Engine::loadSettings (Settings::Manager & settings) { // Create the settings manager and load default settings file const std::string localdefault = (mCfgMgr.getLocalPath() / "defaults.bin").string(); const std::string globaldefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string(); // prefer local if (boost::filesystem::exists(localdefault)) settings.loadDefault(localdefault); else if (boost::filesystem::exists(globaldefault)) settings.loadDefault(globaldefault); else throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); // load user settings if they exist const std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); #ifdef USE_OPENXR const std::string localoverrides = (mCfgMgr.getLocalPath() / "settings-overrides-vr.cfg").string(); const std::string globaloverrides = (mCfgMgr.getGlobalPath() / "settings-overrides-vr.cfg").string(); if (boost::filesystem::exists(localoverrides)) settings.loadOverrides(localoverrides); else if (boost::filesystem::exists(globaloverrides)) settings.loadOverrides(globaloverrides); else throw std::runtime_error("No settings overrides file found! Make sure the file \"settings-overrides-vr.cfg\" was properly installed."); #endif return settingspath; } void OMW::Engine::createWindow(Settings::Manager& settings) { int screen = settings.getInt("screen", "Video"); int width = settings.getInt("resolution x", "Video"); int height = settings.getInt("resolution y", "Video"); bool fullscreen = settings.getBool("fullscreen", "Video"); bool windowBorder = settings.getBool("window border", "Video"); bool vsync = settings.getBool("vsync", "Video"); unsigned int antialiasing = std::max(0, settings.getInt("antialiasing", "Video")); int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); if(fullscreen) { pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen); } Uint32 flags = SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE; if(fullscreen) flags |= SDL_WINDOW_FULLSCREEN; if (!windowBorder) flags |= SDL_WINDOW_BORDERLESS; SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, settings.getBool("minimize on focus loss", "Video") ? "1" : "0"); checkSDLError(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24)); if (Debug::shouldDebugOpenGL()) checkSDLError(SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG)); if (antialiasing > 0) { checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); } osg::ref_ptr graphicsWindow; while (!graphicsWindow || !graphicsWindow->valid()) { while (!mWindow) { /* Start of tes3mp change (major) Rename the window into TES3MP */ mWindow = SDL_CreateWindow("TES3MP", pos_x, pos_y, width, height, flags); /* End of tes3mp change (major) */ if (!mWindow) { // Try with a lower AA if (antialiasing > 0) { Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing/2; antialiasing /= 2; Settings::Manager::setInt("antialiasing", "Video", antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); continue; } else { std::stringstream error; error << "Failed to create SDL window: " << SDL_GetError(); throw std::runtime_error(error.str()); } } } setWindowIcon(); osg::ref_ptr traits = new osg::GraphicsContext::Traits; SDL_GetWindowPosition(mWindow, &traits->x, &traits->y); SDL_GetWindowSize(mWindow, &traits->width, &traits->height); traits->windowName = SDL_GetWindowTitle(mWindow); traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS); traits->screenNum = SDL_GetWindowDisplayIndex(mWindow); traits->vsync = vsync; traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow); graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits); if (!graphicsWindow->valid()) throw std::runtime_error("Failed to create GraphicsContext"); if (traits->samples < antialiasing) { Log(Debug::Warning) << "Warning: Framebuffer MSAA level is only " << traits->samples << "x instead of " << antialiasing << "x. Trying " << antialiasing / 2 << "x instead."; graphicsWindow->closeImplementation(); SDL_DestroyWindow(mWindow); mWindow = nullptr; antialiasing /= 2; Settings::Manager::setInt("antialiasing", "Video", antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); continue; } if (traits->red < 8) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->red << " bit red channel."; if (traits->green < 8) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->green << " bit green channel."; if (traits->blue < 8) Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->blue << " bit blue channel."; if (traits->depth < 24) Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->depth << " bits of depth precision."; traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel } osg::ref_ptr camera = mViewer->getCamera(); camera->setGraphicsContext(graphicsWindow); camera->setViewport(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); if (Debug::shouldDebugOpenGL()) mViewer->setRealizeOperation(new Debug::EnableGLDebugOperation()); #ifdef USE_OPENXR initVr(); #endif mViewer->realize(); mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); } void OMW::Engine::setWindowIcon() { boost::filesystem::ifstream windowIconStream; /* Start of tes3mp change (major) Use TES3MP's logo for the window icon */ std::string windowIcon = (mResDir / "mygui" / "tes3mp_logo.png").string(); /* End of tes3mp change (major) */ windowIconStream.open(windowIcon, std::ios_base::in | std::ios_base::binary); if (windowIconStream.fail()) Log(Debug::Error) << "Error: Failed to open " << windowIcon; osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!reader) { Log(Debug::Error) << "Error: Failed to read window icon, no png readerwriter found"; return; } osgDB::ReaderWriter::ReadResult result = reader->readImage(windowIconStream); if (!result.success()) Log(Debug::Error) << "Error: Failed to read " << windowIcon << ": " << result.message() << " code " << result.status(); else { osg::ref_ptr image = result.getImage(); auto surface = SDLUtil::imageToSurface(image, true); SDL_SetWindowIcon(mWindow, surface.get()); } } void OMW::Engine::prepareEngine (Settings::Manager & settings) { mEnvironment.setStateManager ( new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0))); createWindow(settings); osg::ref_ptr rootNode (new osg::Group); mViewer->setSceneData(rootNode); mCallbackManager.reset(new Misc::CallbackManager(mViewer)); mVFS.reset(new VFS::Manager(mFSStrict)); VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get())); mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(false); // keep to Off for now to allow better state sharing mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), Settings::Manager::getInt("anisotropy", "General") ); mEnvironment.setResourceSystem(mResourceSystem.get()); int numThreads = Settings::Manager::getInt("preload num threads", "Cells"); if (numThreads <= 0) throw std::runtime_error("Invalid setting: 'preload num threads' must be >0"); mWorkQueue = new SceneUtil::WorkQueue(numThreads); // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input_v3.xml").string(); bool keybinderUserExists = boost::filesystem::exists(keybinderUser); if(!keybinderUserExists) { std::string input2 = (mCfgMgr.getUserConfigPath() / "input_v2.xml").string(); if(boost::filesystem::exists(input2)) { boost::filesystem::copy_file(input2, keybinderUser); keybinderUserExists = boost::filesystem::exists(keybinderUser); Log(Debug::Info) << "Loading keybindings file: " << keybinderUser; } } else Log(Debug::Info) << "Loading keybindings file: " << keybinderUser; const std::string userdefault = mCfgMgr.getUserConfigPath().string() + "/gamecontrollerdb.txt"; const std::string localdefault = mCfgMgr.getLocalPath().string() + "/gamecontrollerdb.txt"; const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/gamecontrollerdb.txt"; std::string userGameControllerdb; if (boost::filesystem::exists(userdefault)){ userGameControllerdb = userdefault; } else userGameControllerdb = ""; std::string gameControllerdb; if (boost::filesystem::exists(localdefault)) gameControllerdb = localdefault; else if (boost::filesystem::exists(globaldefault)) gameControllerdb = globaldefault; else gameControllerdb = ""; //if it doesn't exist, pass in an empty string std::string myguiResources = (mResDir / "mygui").string(); osg::ref_ptr guiRoot = new osg::Group; guiRoot->setName("GUI Root"); guiRoot->setNodeMask(MWRender::Mask_GUI); rootNode->addChild(guiRoot); MWGui::WindowManager* window = new MWGui::WindowManager(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), mCfgMgr.getLogPath().string() + std::string("/"), myguiResources, mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, Version::getOpenmwVersionDescription(mResDir.string()), mCfgMgr.getUserConfigPath().string()); mEnvironment.setWindowManager (window); #ifdef USE_OPENXR const std::string xrinputuserdefault = mCfgMgr.getUserConfigPath().string() + "/xrcontrollersuggestions.xml"; const std::string xrinputlocaldefault = mCfgMgr.getLocalPath().string() + "/xrcontrollersuggestions.xml"; const std::string xrinputglobaldefault = mCfgMgr.getGlobalPath().string() + "/xrcontrollersuggestions.xml"; std::string xrControllerSuggestions; if (boost::filesystem::exists(xrinputuserdefault)) xrControllerSuggestions = xrinputuserdefault; else if (boost::filesystem::exists(xrinputlocaldefault)) xrControllerSuggestions = xrinputlocaldefault; else if (boost::filesystem::exists(xrinputglobaldefault)) xrControllerSuggestions = xrinputglobaldefault; else xrControllerSuggestions = ""; //if it doesn't exist, pass in an empty string Log(Debug::Verbose) << "xrinputuserdefault: " << xrinputuserdefault; Log(Debug::Verbose) << "xrinputlocaldefault: " << xrinputlocaldefault; Log(Debug::Verbose) << "xrinputglobaldefault: " << xrinputglobaldefault; MWInput::InputManager* input = new MWVR::VRInputManager(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab, xrControllerSuggestions); #else MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); #endif mEnvironment.setInputManager (input); // Create sound system mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound)); if (mStereoEnabled) { // Set up stereo // Stereo setup is split in two because the GeometryShader approach cannot be used before the RenderingManager has been created. // To be able to see the logo and initial loading screen the BruteForce technique must be set up here. mStereoView->initializeStereo(mViewer, Misc::StereoView::Technique::BruteForce); mResourceSystem->getSceneManager()->getShaderManager().setStereoGeometryShaderEnabled(Misc::getStereoTechnique() == Misc::StereoView::Technique::GeometryShader_IndexedViewports); } #ifdef USE_OPENXR mXrEnvironment.setGUIManager(new MWVR::VRGUIManager(mViewer, mResourceSystem.get(), rootNode)); mXrEnvironment.getViewer()->configureCallbacks(); mStereoView->setCullMask(mStereoView->getCullMask() & ~MWRender::VisMask::Mask_GUI); #endif #ifdef USE_OPENXR MWVR::VRCamera* camera = new MWVR::VRCamera(mViewer->getCamera()); #else MWRender::Camera* camera = new MWRender::Camera(mViewer->getCamera()); #endif if (!mSkipMenu) { const std::string& logo = Fallback::Map::getString("Movies_Company_Logo"); if (!logo.empty()) mEnvironment.getWindowManager()->playVideo(logo, true); } // Create the world mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, std::unique_ptr(camera), mResourceSystem.get(), mWorkQueue.get(), mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); #ifdef USE_OPENXR // TODO: Workaround. Needed to stop camera from querying the world object before it is created. // This will be prettier when i clean up the tracking logic. camera->setShouldTrackPlayerCharacter(true); #endif if (mStereoEnabled) { // Stereo shader technique can be set up now. mStereoView->setStereoTechnique(Misc::getStereoTechnique()); mStereoView->initializeScene(); if (mEnvironment.getVrMode()) mStereoView->setCullMask(mStereoView->getCullMask() & ~MWRender::VisMask::Mask_GUI); } window->setStore(mEnvironment.getWorld()->getStore()); window->initUI(); //Load translation data mTranslationDataStorage.setEncoder(mEncoder); for (size_t i = 0; i < mContentFiles.size(); i++) mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFiles[i]); Compiler::registerExtensions (mExtensions); // Create script system mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full); mScriptContext->setExtensions (&mExtensions); mEnvironment.setScriptManager (new MWScript::ScriptManager (mEnvironment.getWorld()->getStore(), *mScriptContext, mWarningsMode, mScriptBlacklistUse ? mScriptBlacklist : std::vector())); // Create game mechanics system MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager; mEnvironment.setMechanicsManager (mechanics); // Create dialog system mEnvironment.setJournal (new MWDialogue::Journal); mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage)); // scripts if (mCompileAll) { std::pair result = mEnvironment.getScriptManager()->compileAll(); if (result.first) Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " scripts (" << 100*static_cast (result.second)/result.first << "%)"; } if (mCompileAllDialogue) { std::pair result = MWDialogue::ScriptTest::compileAll(&mExtensions, mWarningsMode); if (result.first) Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " dialogue script/actor combinations a(" << 100*static_cast (result.second)/result.first << "%)"; } } class WriteScreenshotToFileOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation { public: WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat) : mScreenshotPath(screenshotPath) , mScreenshotFormat(screenshotFormat) { } void operator()(const osg::Image& image, const unsigned int context_id) override { // Count screenshots. int shotCount = 0; // Find the first unused filename with a do-while std::ostringstream stream; do { // Reset the stream stream.str(""); stream.clear(); stream << mScreenshotPath << "/screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << mScreenshotFormat; } while (boost::filesystem::exists(stream.str())); boost::filesystem::ofstream outStream; outStream.open(boost::filesystem::path(stream.str()), std::ios::binary); osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(mScreenshotFormat); if (!readerwriter) { Log(Debug::Error) << "Error: Can't write screenshot, no '" << mScreenshotFormat << "' readerwriter found"; return; } osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream); if (!result.success()) { Log(Debug::Error) << "Error: Can't write screenshot: " << result.message() << " code " << result.status(); } } private: std::string mScreenshotPath; std::string mScreenshotFormat; }; // Initialise and enter main loop. void OMW::Engine::go() { assert (!mContentFiles.empty()); /* Start of tes3mp change (major) Attempt multiplayer initialization and proceed no further if it fails */ if (!mwmp::Main::init(mContentFiles, mFileCollections)) return; /* End of tes3mp change (major) */ Log(Debug::Info) << "OSG version: " << osgGetVersion(); SDL_version sdlVersion; SDL_GetVersion(&sdlVersion); Log(Debug::Info) << "SDL version: " << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "." << (int)sdlVersion.patch; Misc::Rng::init(mRandomSeed); // Load settings Settings::Manager settings; std::string settingspath; settingspath = loadSettings (settings); MWClass::registerClasses(); // Create encoder mEncoder = new ToUTF8::Utf8Encoder(mEncoding); #ifdef USE_OPENXR mEnvironment.setVrMode(true); #endif // geometry shader must be enabled before the RenderingManager sets up any shaders // therefore this part is separate from the rest of stereo setup. mStereoEnabled = mEnvironment.getVrMode() || Settings::Manager::getBool("stereo enabled", "Stereo"); if (mStereoEnabled) { // Mask in everything that does not currently use shaders. // Remove that altogether when the sky finally uses them. auto noShaderMask = MWRender::VisMask::Mask_Sky | MWRender::VisMask::Mask_Sun | MWRender::VisMask::Mask_WeatherParticles; // Since shaders are not yet created, we need to use the brute force technique initially mStereoView.reset(new Misc::StereoView(noShaderMask, MWRender::VisMask::Mask_Scene)); } // Setup viewer mViewer = new osgViewer::Viewer; mViewer->setReleaseContextAtEndOfFrameHint(false); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,5) // Do not try to outsmart the OS thread scheduler (see bug #4785). mViewer->setUseConfigureAffinity(false); #endif mScreenCaptureOperation = new WriteScreenshotToFileOperation( mCfgMgr.getScreenshotPath().string(), Settings::Manager::getString("screenshot format", "General")); mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); mViewer->addEventHandler(mScreenCaptureHandler); mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); prepareEngine (settings); std::ofstream stats; if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE")) { stats.open(path, std::ios_base::out); if (stats.is_open()) Log(Debug::Info) << "Stats will be written to: " << path; else Log(Debug::Warning) << "Failed to open file for stats: " << path; } /* Start of tes3mp addition Handle post-initialization for multiplayer classes */ mwmp::Main::postInit(); /* End of tes3mp addition */ /* Start of tes3mp change (major) Always skip the main menu in multiplayer */ mSkipMenu = true; /* End of tes3mp change (major) */ // Setup profiler osg::ref_ptr statshandler = new Resource::Profiler(stats.is_open()); initStatsHandler(*statshandler); mViewer->addEventHandler(statshandler); osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open()); mViewer->addEventHandler(resourceshandler); if (stats.is_open()) Resource::CollectStatistics(mViewer); // Start the game if (!mSaveGameFile.empty()) { mEnvironment.getStateManager()->loadGame(mSaveGameFile); } else if (!mSkipMenu) { // start in main menu mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); mEnvironment.getSoundManager()->playTitleMusic(); const std::string& logo = Fallback::Map::getString("Movies_Morrowind_Logo"); if (!logo.empty()) mEnvironment.getWindowManager()->playVideo(logo, true); } else { mEnvironment.getStateManager()->newGame (!mNewGame); } if (!mStartupScript.empty() && mEnvironment.getStateManager()->getState() == MWState::StateManager::State_Running) { mEnvironment.getWindowManager()->executeInConsole(mStartupScript); } // Start the main rendering loop double simulationTime = 0.0; Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit()); const std::chrono::steady_clock::duration maxSimulationInterval(std::chrono::milliseconds(200)); while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest()) { const double dt = std::chrono::duration_cast>(std::min( frameRateLimiter.getLastFrameDuration(), maxSimulationInterval )).count(); mViewer->advance(simulationTime); if (!frame(dt)) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); continue; } else { mEnvironment.getWindowManager()->viewerTraversals(true); bool guiActive = mEnvironment.getWindowManager()->isGuiMode(); /* Start of tes3mp change (major) Whether the GUI is active should have no relevance in multiplayer, so the guiActive boolean is always set to false instead */ guiActive = false; /* End of tes3mp change (major) */ if (!guiActive) simulationTime += dt; } if (stats) { const auto frameNumber = mViewer->getFrameStamp()->getFrameNumber(); if (frameNumber >= 2) { mViewer->getViewerStats()->report(stats, frameNumber - 2); osgViewer::Viewer::Cameras cameras; mViewer->getCameras(cameras); for (auto camera : cameras) camera->getStats()->report(stats, frameNumber - 2); } } frameRateLimiter.limit(); } // Save user settings settings.saveUser(settingspath); mViewer->stopThreading(); Log(Debug::Info) << "Quitting peacefully."; } void OMW::Engine::setCompileAll (bool all) { mCompileAll = all; } void OMW::Engine::setCompileAllDialogue (bool all) { mCompileAllDialogue = all; } void OMW::Engine::setSoundUsage(bool soundUsage) { mUseSound = soundUsage; } void OMW::Engine::setEncoding(const ToUTF8::FromType& encoding) { mEncoding = encoding; } void OMW::Engine::setScriptConsoleMode (bool enabled) { mScriptConsoleMode = enabled; } void OMW::Engine::setStartupScript (const std::string& path) { mStartupScript = path; } void OMW::Engine::setActivationDistanceOverride (int distance) { mActivationDistanceOverride = distance; } void OMW::Engine::setWarningsMode (int mode) { mWarningsMode = mode; } void OMW::Engine::setScriptBlacklist (const std::vector& list) { mScriptBlacklist = list; } void OMW::Engine::setScriptBlacklistUse (bool use) { mScriptBlacklistUse = use; } void OMW::Engine::enableFontExport(bool exportFonts) { mExportFonts = exportFonts; } void OMW::Engine::setSaveGameFile(const std::string &savegame) { mSaveGameFile = savegame; } void OMW::Engine::setRandomSeed(unsigned int seed) { mRandomSeed = seed; }