diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..9f5442ae4 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,42 @@ +# use the official gcc image, based on debian +# can use verions as well, like gcc:5.2 +# see https://hub.docker.com/_/gcc/ +image: gcc + +cache: + key: apt-cache + paths: + - apt-cache/ + +before_script: + - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR + - apt-get update -yq + - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev +# - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old + - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb + - dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb + +build: + stage: build + script: + - cores_to_use=$((`nproc`-2)); if (( $cores_to_use < 1 )); then cores_to_use=1; fi + - mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../ + - make -j$cores_to_use + - DESTDIR=artifacts make install + artifacts: + paths: + - build/artifacts/ + # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time + cache: + paths: + - "*.o" + +# TODO: run tests using the binary built before +#test: +# stage: test +# script: +# - ls diff --git a/CHANGELOG.md b/CHANGELOG.md index e69028e2b..ca462432c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,29 +2,49 @@ ------ Bug #1990: Sunrise/sunset not set correct + Bug #2222: Fatigue's effect on selling price is backwards Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped + Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash Bug #2835: Player able to slowly move when overencumbered + Bug #2852: No murder bounty when a player follower commits murder + Bug #2862: [macOS] Can't quit launcher using Command-Q or OpenMW->Quit + Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y Bug #3374: Touch spells not hitting kwama foragers + Bug #3486: [Mod] NPC Commands does not work Bug #3591: Angled hit distance too low Bug #3629: DB assassin attack never triggers creature spawning Bug #3876: Landscape texture painting is misaligned Bug #3897: Have Goodbye give all choices the effects of Goodbye + Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters Bug #3993: Terrain texture blending map is not upscaled Bug #3997: Almalexia doesn't pace Bug #4036: Weird behaviour of AI packages if package target has non-unique ID Bug #4047: OpenMW not reporting its version number in MacOS; OpenMW-CS not doing it fully + Bug #4125: OpenMW logo cropped on bugtracker Bug #4215: OpenMW shows book text after last
tag Bug #4221: Characters get stuck in V-shaped terrain Bug #4251: Stationary NPCs do not return to their position after combat + Bug #4286: Scripted animations can be interrupted + Bug #4291: Non-persistent actors that started the game as dead do not play death animations Bug #4293: Faction members are not aware of faction ownerships in barter + Bug #4307: World cleanup should remove dead bodies only if death animation is finished Bug #4327: Missing animations during spell/weapon stance switching Bug #4368: Settings window ok button doesn't have key focus by default + Bug #4393: NPCs walk back to where they were after using ResetActors Bug #4419: MRK NiStringExtraData is handled incorrectly Bug #4426: RotateWorld behavior is incorrect Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 Bug #4432: Guards behaviour is incorrect if they do not have AI packages Bug #4433: Guard behaviour is incorrect with Alarm = 0 + Bug #4451: Script fails to compile when using "Begin, [ScriptName]" syntax Bug #4452: Default terrain texture bleeds through texture transitions + Bug #4453: Quick keys behaviour is invalid for equipment + Bug #4454: AI opens doors too slow + Bug #4457: Item without CanCarry flag prevents shield autoequipping in dark areas + Bug #4458: AiWander console command handles idle chances incorrectly + Feature #4256: Implement ToggleBorders (TB) console command + Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results + Feature #4222: 360° screenshots Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts Feature #4345: Add equivalents for the command line commands to Launcher Feature #4444: Per-group KF-animation files support diff --git a/README.md b/README.md index 7d92879e2..9af9ef976 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ OpenMW ====== -[![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/github/openmw/openmw?svg=true)](https://ci.appveyor.com/project/psi29a/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) +[![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/github/openmw/openmw?svg=true)](https://ci.appveyor.com/project/psi29a/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![pipeline status](https://gitlab.com/OpenMW/openmw/badges/master/pipeline.svg)](https://gitlab.com/OpenMW/openmw/commits/master) -OpenMW is a open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. +OpenMW is an open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind. -OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. +OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set. * Version: 0.44.0 * License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index d3dbfa559..34442fe71 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -1,6 +1,7 @@ #include "graphicspage.hpp" #include +#include #include #include #include @@ -11,6 +12,7 @@ #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ #endif // MAC_OS_X_VERSION_MIN_REQUIRED +#include #include #include @@ -46,8 +48,28 @@ Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings: } +bool Launcher::GraphicsPage::connectToSdl() { + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); + SDL_SetMainReady(); + // Required for determining screen resolution and such on the Graphics tab + if (SDL_Init(SDL_INIT_VIDEO) != 0) + { + return false; + } + signal(SIGINT, SIG_DFL); // We don't want to use the SDL event loop in the launcher, + // so reset SIGINT which SDL wants to redirect to an SDL_Quit event. + + return true; +} + bool Launcher::GraphicsPage::setupSDL() { + bool sdlConnectSuccessful = connectToSdl(); + if (!sdlConnectSuccessful) + { + return false; + } + int displays = SDL_GetNumVideoDisplays(); if (displays < 0) @@ -67,6 +89,9 @@ bool Launcher::GraphicsPage::setupSDL() screenComboBox->addItem(QString(tr("Screen ")) + QString::number(i + 1)); } + // Disconnect from SDL processes + SDL_Quit(); + return true; } diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index e6eb53a3b..0354e5202 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -37,6 +37,11 @@ namespace Launcher QStringList getAvailableResolutions(int screen); QRect getMaximumResolution(); + /** + * Connect to the SDL so that we can use it to determine graphics + * @return whether or not connecting to SDL is successful + */ + bool connectToSdl(); bool setupSDL(); }; } diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 96cadc8a7..866ae2aa9 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -12,24 +11,12 @@ #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ #endif // MAC_OS_X_VERSION_MIN_REQUIRED -#include - #include "maindialog.hpp" int main(int argc, char *argv[]) { try { - SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); - SDL_SetMainReady(); - if (SDL_Init(SDL_INIT_VIDEO) != 0) - { - qDebug() << "SDL_Init failed: " << QString::fromUtf8(SDL_GetError()); - return 0; - } - signal(SIGINT, SIG_DFL); // We don't want to use the SDL event loop in the launcher, - // so reset SIGINT which SDL wants to redirect to an SDL_Quit event. - QApplication app(argc, argv); // Now we make sure the current dir is set to application path @@ -46,13 +33,11 @@ int main(int argc, char *argv[]) if (result == Launcher::FirstRunDialogResultContinue) mainWin.show(); - int returnValue = app.exec(); - SDL_Quit(); - return returnValue; + return app.exec(); } catch (std::exception& e) { std::cerr << "ERROR: " << e.what() << std::endl; return 0; } -} +} \ No newline at end of file diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 7a825ba39..e45d13aa9 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -320,12 +320,13 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool))); connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); - connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); + connect (&mTools, SIGNAL (done (int, bool)), this, SIGNAL (operationDone (int, bool))); + connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool))); connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)), this, SIGNAL (mergeDone (CSMDoc::Document*))); connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); - connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); + connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool))); connect ( &mSaving, SIGNAL (reportMessage (const CSMDoc::Message&, int)), @@ -437,7 +438,7 @@ void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type) std::cout << message.mMessage << std::endl; } -void CSMDoc::Document::operationDone (int type, bool failed) +void CSMDoc::Document::operationDone2 (int type, bool failed) { if (type==CSMDoc::State_Saving && !failed) mDirty = false; diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index d31fd5aca..4c442428e 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -168,13 +168,15 @@ namespace CSMDoc /// document. This signal must be handled to avoid a leak. void mergeDone (CSMDoc::Document *document); + void operationDone (int type, bool failed); + private slots: void modificationStateChanged (bool clean); void reportMessage (const CSMDoc::Message& message, int type); - void operationDone (int type, bool failed); + void operationDone2 (int type, bool failed); void runStateChanged(); diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index 493defa5a..9bada22af 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -3,11 +3,15 @@ #include #include "../../model/doc/document.hpp" +#include "../../model/doc/state.hpp" #include "../../model/tools/search.hpp" #include "../../model/tools/reportmodel.hpp" #include "../../model/world/idtablebase.hpp" #include "../../model/prefs/state.hpp" +#include "../world/tablebottombox.hpp" +#include "../world/creator.hpp" + #include "reporttable.hpp" #include "searchbox.hpp" @@ -73,6 +77,9 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc: layout->addWidget (mTable = new ReportTable (document, id, true), 2); + layout->addWidget (mBottom = + new CSVWorld::TableBottomBox (CSVWorld::NullCreatorFactory(), document, id, this), 0); + QWidget *widget = new QWidget; widget->setLayout (layout); @@ -93,6 +100,15 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc: this, SLOT (startSearch (const CSMTools::Search&))); connect (&mSearchBox, SIGNAL (replaceAll()), this, SLOT (replaceAllRequest())); + + connect (document.getReport (id), SIGNAL (rowsRemoved (const QModelIndex&, int, int)), + this, SLOT (tableSizeUpdate())); + + connect (document.getReport (id), SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (tableSizeUpdate())); + + connect (&document, SIGNAL (operationDone (int, bool)), + this, SLOT (operationDone (int, bool))); } void CSVTools::SearchSubView::setEditLock (bool locked) @@ -101,6 +117,11 @@ void CSVTools::SearchSubView::setEditLock (bool locked) mSearchBox.setEditLock (locked); } +void CSVTools::SearchSubView::setStatusBar (bool show) +{ + mBottom->setStatusBar(show); +} + void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *document) { mSearchBox.setSearchMode (!(state & CSMDoc::State_Searching)); @@ -126,3 +147,17 @@ void CSVTools::SearchSubView::replaceAllRequest() { replace (false); } + +void CSVTools::SearchSubView::tableSizeUpdate() +{ + mBottom->tableSizeChanged (mDocument.getReport (getUniversalId())->rowCount(), 0, 0); +} + +void CSVTools::SearchSubView::operationDone (int type, bool failed) +{ + if (type==CSMDoc::State_Searching && !failed && + !mDocument.getReport (getUniversalId())->rowCount()) + { + mBottom->setStatusMessage ("No Results"); + } +} diff --git a/apps/opencs/view/tools/searchsubview.hpp b/apps/opencs/view/tools/searchsubview.hpp index ac0a5a762..c0f3eac84 100644 --- a/apps/opencs/view/tools/searchsubview.hpp +++ b/apps/opencs/view/tools/searchsubview.hpp @@ -15,6 +15,11 @@ namespace CSMDoc class Document; } +namespace CSVWorld +{ + class TableBottomBox; +} + namespace CSVTools { class ReportTable; @@ -28,6 +33,7 @@ namespace CSVTools CSMDoc::Document& mDocument; CSMTools::Search mSearch; bool mLocked; + CSVWorld::TableBottomBox *mBottom; private: @@ -43,6 +49,8 @@ namespace CSVTools virtual void setEditLock (bool locked); + virtual void setStatusBar (bool show); + private slots: void stateChanged (int state, CSMDoc::Document *document); @@ -52,6 +60,10 @@ namespace CSVTools void replaceRequest(); void replaceAllRequest(); + + void tableSizeUpdate(); + + void operationDone (int type, bool failed); }; } diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index cfde5c694..f6b060a8f 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -28,6 +28,12 @@ void CSVWorld::TableBottomBox::updateStatus() { if (mShowStatusBar) { + if (!mStatusMessage.isEmpty()) + { + mStatus->setText (mStatusMessage); + return; + } + static const char *sLabels[4] = { "record", "deleted", "touched", "selected" }; static const char *sLabelsPlural[4] = { "records", "deleted", "touched", "selected" }; @@ -178,10 +184,17 @@ void CSVWorld::TableBottomBox::currentWidgetChanged(int /*index*/) updateSize(); } +void CSVWorld::TableBottomBox::setStatusMessage (const QString& message) +{ + mStatusMessage = message; + updateStatus(); +} + void CSVWorld::TableBottomBox::selectionSizeChanged (int size) { if (mStatusCount[3]!=size) { + mStatusMessage = ""; mStatusCount[3] = size; updateStatus(); } @@ -210,7 +223,10 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi } if (changed) + { + mStatusMessage = ""; updateStatus(); + } } void CSVWorld::TableBottomBox::positionChanged (int row, int column) diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index 5402c466e..baa68087b 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -39,6 +39,7 @@ namespace CSVWorld bool mHasPosition; int mRow; int mColumn; + QString mStatusMessage; private: @@ -73,6 +74,8 @@ namespace CSVWorld /// /// \note The BotomBox does not partake in the deletion of records. + void setStatusMessage (const QString& message); + signals: void requestFocus (const std::string& id); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 78e368cfc..5052a13cc 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -499,7 +499,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) else gameControllerdb = ""; //if it doesn't exist, pass in an empty string - MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, keybinderUser, keybinderUserExists, gameControllerdb, mGrab); + MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, gameControllerdb, mGrab); mEnvironment.setInputManager (input); std::string myguiResources = (mResDir / "mygui").string(); @@ -656,8 +656,11 @@ void OMW::Engine::go() settingspath = loadSettings (settings); - mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(), - Settings::Manager::getString("screenshot format", "General"))); + mScreenCaptureOperation = new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(), + Settings::Manager::getString("screenshot format", "General")); + + mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); + mViewer->addEventHandler(mScreenCaptureHandler); mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 2fff91c7e..e42a5c94f 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -7,7 +7,7 @@ #include #include - +#include #include "mwbase/environment.hpp" @@ -82,6 +82,7 @@ namespace OMW boost::filesystem::path mResDir; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; + osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; std::string mCellName; std::vector mContentFiles; bool mSkipMenu; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index fb1450aa6..e07c1ffd9 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -120,6 +120,7 @@ namespace MWBase virtual bool toggleWater() = 0; virtual bool toggleWorld() = 0; + virtual bool toggleBorders() = 0; virtual void adjustSky() = 0; @@ -450,6 +451,7 @@ namespace MWBase /// \todo this does not belong here virtual void screenshot (osg::Image* image, int w, int h) = 0; + virtual bool screenshot360 (osg::Image* image, std::string settingStr) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 17af4725e..73a4d37d7 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -31,7 +31,7 @@ namespace MWClass if (!model.empty()) { physics.addActor(ptr, model); - if (getCreatureStats(ptr).isDead()) + if (getCreatureStats(ptr).isDead() && getCreatureStats(ptr).isDeathAnimationFinished()) MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); } } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index b6a46cff8..1d51a7830 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -139,24 +139,21 @@ namespace MWClass const std::string trapActivationSound = "Disarm Trap Fail"; MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - const MWWorld::InventoryStore& invStore = player.getClass().getInventoryStore(player); + MWWorld::InventoryStore& invStore = player.getClass().getInventoryStore(player); bool isLocked = ptr.getCellRef().getLockLevel() > 0; bool isTrapped = !ptr.getCellRef().getTrap().empty(); bool hasKey = false; std::string keyName; - // make key id lowercase - std::string keyId = ptr.getCellRef().getKey(); - Misc::StringUtils::lowerCaseInPlace(keyId); - for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it) + const std::string keyId = ptr.getCellRef().getKey(); + if (!keyId.empty()) { - std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::lowerCaseInPlace(refId); - if (refId == keyId) + MWWorld::Ptr keyPtr = invStore.search(keyId); + if (!keyPtr.isEmpty()) { hasKey = true; - keyName = it->getClass().getName(*it); + keyName = keyPtr.getClass().getName(keyPtr); } } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2e16b13aa..a07a5c893 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -135,8 +135,9 @@ namespace MWClass data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); + // Persistent actors with 0 health do not play death animation if (data->mCreatureStats.isDead()) - data->mCreatureStats.setDeathAnimationFinished(true); + data->mCreatureStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr)); // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); @@ -814,6 +815,9 @@ namespace MWClass if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) return; + if (!creatureStats.isDeathAnimationFinished()) + return; + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 903ec4958..a6aacfa12 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -114,42 +114,44 @@ namespace MWClass const std::string lockedSound = "LockedDoor"; const std::string trapActivationSound = "Disarm Trap Fail"; - const MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); + MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); bool isLocked = ptr.getCellRef().getLockLevel() > 0; bool isTrapped = !ptr.getCellRef().getTrap().empty(); bool hasKey = false; std::string keyName; + // FIXME: If NPC activate teleporting door, it can lead to crash due to iterator invalidation in the Actors update. + // Make such activation a no-op for now, like how it is in the vanilla game. + if (actor != MWMechanics::getPlayer() && ptr.getCellRef().getTeleport()) + { + std::shared_ptr action(new MWWorld::FailedAction(std::string(), ptr)); + action->setSound(lockedSound); + return action; + } + // make door glow if player activates it with telekinesis - if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr() && - MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > + if (actor == MWMechanics::getPlayer() && + MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > MWBase::Environment::get().getWorld()->getMaxActivationDistance()) { MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis"); const ESM::MagicEffect *effect = store.get().find(index); animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing } - // make key id lowercase - std::string keyId = ptr.getCellRef().getKey(); + const std::string keyId = ptr.getCellRef().getKey(); if (!keyId.empty()) { - Misc::StringUtils::lowerCaseInPlace(keyId); - for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it) + MWWorld::Ptr keyPtr = invStore.search(keyId); + if (!keyPtr.isEmpty()) { - std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::lowerCaseInPlace(refId); - if (refId == keyId) - { - hasKey = true; - keyName = it->getClass().getName(*it); - break; - } + hasKey = true; + keyName = keyPtr.getClass().getName(keyPtr); } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 8e8b5c3ad..92e25baee 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -352,8 +352,10 @@ namespace MWClass data->mNpcStats.setNeedRecalcDynamicStats(true); } + + // Persistent actors with 0 health do not play death animation if (data->mNpcStats.isDead()) - data->mNpcStats.setDeathAnimationFinished(true); + data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr)); // race powers const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); @@ -1351,6 +1353,9 @@ namespace MWClass if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) return; + if (!creatureStats.isDeathAnimationFinished()) + return; + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 2c12547fd..99fe777aa 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -153,7 +153,7 @@ namespace MWGui virtual osg::BoundingSphere computeBound(const osg::Node&) const { return osg::BoundingSphere(); } }; - void LoadingScreen::loadingOn() + void LoadingScreen::loadingOn(bool visible) { mLoadingOnTime = mTimer.time_m(); // Early-out if already on @@ -169,7 +169,10 @@ namespace MWGui // We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound() mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); - mShowWallpaper = (MWBase::Environment::get().getStateManager()->getState() + mVisible = visible; + mLoadingBox->setVisible(mVisible); + + mShowWallpaper = mVisible && (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); setVisible(true); @@ -180,10 +183,15 @@ namespace MWGui } MWBase::Environment::get().getWindowManager()->pushGuiMode(mShowWallpaper ? GM_LoadingWallpaper : GM_Loading); + + if (!mVisible) + draw(); } void LoadingScreen::loadingOff() { + mLoadingBox->setVisible(true); // restore + if (mLastRenderTime < mLoadingOnTime) { // the loading was so fast that we didn't show loading screen at all @@ -306,7 +314,7 @@ namespace MWGui void LoadingScreen::draw() { - if (!needToDrawLoadingScreen()) + if (mVisible && !needToDrawLoadingScreen()) return; if (mShowWallpaper && mTimer.time_m() > mLastWallpaperChangeTime + 5000*1) diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 8536972a3..bdd210d00 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -35,7 +35,7 @@ namespace MWGui /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details virtual void setLabel (const std::string& label, bool important); - virtual void loadingOn(); + virtual void loadingOn(bool visible=true); virtual void loadingOff(); virtual void setProgressRange (size_t range); virtual void setProgress (size_t value); @@ -63,6 +63,8 @@ namespace MWGui bool mImportantLabel; + bool mVisible; + size_t mProgress; bool mShowWallpaper; diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 2ce9d04e5..08192625f 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -81,6 +81,47 @@ namespace MWGui delete mMagicSelectionDialog; } + void QuickKeysMenu::onOpen() + { + WindowBase::onOpen(); + + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + + // Check if quick keys are still valid + for (int i=0; i<10; ++i) + { + ItemWidget* button = mQuickKeyButtons[i]; + int type = mAssigned[i]; + + switch (type) + { + case Type_Unassigned: + case Type_HandToHand: + case Type_Magic: + break; + case Type_Item: + case Type_MagicItem: + { + MWWorld::Ptr item = *button->getUserData(); + // Make sure the item is available and is not broken + if (item.getRefData().getCount() < 1 || + (item.getClass().hasItemHealth(item) && + item.getClass().getItemHealth(item) <= 0)) + { + // Try searching for a compatible replacement + std::string id = item.getCellRef().getRefId(); + + item = store.findReplacement(id); + button->setUserData(MWWorld::Ptr(item)); + break; + } + } + } + } + + } + void QuickKeysMenu::unassign(ItemWidget* key, int index) { key->clearUserStrings(); @@ -122,12 +163,10 @@ namespace MWGui assert(index != -1); mSelectedIndex = index; - { - // open assign dialog - if (!mAssignDialog) - mAssignDialog = new QuickKeysMenuAssign(this); - mAssignDialog->setVisible (true); - } + // open assign dialog + if (!mAssignDialog) + mAssignDialog = new QuickKeysMenuAssign(this); + mAssignDialog->setVisible (true); } void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender) @@ -296,21 +335,16 @@ namespace MWGui if (type == Type_Item || type == Type_MagicItem) { MWWorld::Ptr item = *button->getUserData(); - // make sure the item is available - if (item.getRefData ().getCount() < 1) + // Make sure the item is available and is not broken + if (item.getRefData().getCount() < 1 || + (item.getClass().hasItemHealth(item) && + item.getClass().getItemHealth(item) <= 0)) { // Try searching for a compatible replacement std::string id = item.getCellRef().getRefId(); - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - if (Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), id)) - { - item = *it; - button->setUserData(MWWorld::Ptr(item)); - break; - } - } + item = store.findReplacement(id); + button->setUserData(MWWorld::Ptr(item)); if (item.getRefData().getCount() < 1) { @@ -498,6 +532,9 @@ namespace MWGui ESM::QuickKeys keys; keys.load(reader); + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + int i=0; for (std::vector::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it) { @@ -519,22 +556,7 @@ namespace MWGui case Type_MagicItem: { // Find the item by id - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - MWWorld::Ptr item; - for (MWWorld::ContainerStoreIterator iter = store.begin(); iter != store.end(); ++iter) - { - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) - { - if (item.isEmpty() || - // Prefer the stack with the lowest remaining uses - !item.getClass().hasItemHealth(*iter) || - iter->getClass().getItemHealth(*iter) < item.getClass().getItemHealth(item)) - { - item = *iter; - } - } - } + MWWorld::Ptr item = store.findReplacement(id); if (item.isEmpty()) unassign(button, i); diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index 0070aa55b..b5bc60b19 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -34,6 +34,7 @@ namespace MWGui void onAssignMagicItem (MWWorld::Ptr item); void onAssignMagic (const std::string& spellId); void onAssignMagicCancel (); + void onOpen(); void activateQuickKey(int index); void updateActivatedQuickKey(); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 507e97a6a..242058c5f 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -36,12 +36,14 @@ namespace MWInput SDL_Window* window, osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler, + osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, const std::string& controllerBindingsFile, bool grab) : mWindow(window) , mWindowVisible(true) , mViewer(viewer) , mScreenCaptureHandler(screenCaptureHandler) + , mScreenCaptureOperation(screenCaptureOperation) , mJoystickLastUsed(false) , mPlayer(NULL) , mInputManager(NULL) @@ -1033,8 +1035,28 @@ namespace MWInput void InputManager::screenshot() { - mScreenCaptureHandler->setFramesToCapture(1); - mScreenCaptureHandler->captureNextFrame(*mViewer); + bool regularScreenshot = true; + + std::string settingStr; + + settingStr = Settings::Manager::getString("screenshot type","Video"); + regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; + + if (regularScreenshot) + { + mScreenCaptureHandler->setFramesToCapture(1); + mScreenCaptureHandler->captureNextFrame(*mViewer); + } + else + { + osg::ref_ptr screenshot (new osg::Image); + + if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(),settingStr)) + { + (*mScreenCaptureOperation) (*(screenshot.get()),0); + // FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason + } + } } void InputManager::toggleInventory() diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index cba7fc743..bc62ef7dc 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -4,6 +4,7 @@ #include "../mwgui/mode.hpp" #include +#include #include #include @@ -14,7 +15,6 @@ #include "../mwbase/inputmanager.hpp" - namespace MWWorld { class Player; @@ -74,6 +74,7 @@ namespace MWInput SDL_Window* window, osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler, + osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, const std::string& controllerBindingsFile, bool grab); @@ -158,6 +159,7 @@ namespace MWInput bool mWindowVisible; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; + osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; bool mJoystickLastUsed; MWWorld::Player* mPlayer; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index de394c446..ec256a321 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -288,23 +288,9 @@ namespace MWMechanics if (prevItemId.empty()) return; - // Find the item by id - MWWorld::Ptr item; - for (MWWorld::ContainerStoreIterator iter = store.begin(); iter != store.end(); ++iter) - { - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), prevItemId)) - { - if (item.isEmpty() || - // Prefer the stack with the lowest remaining uses - !item.getClass().hasItemHealth(*iter) || - iter->getClass().getItemHealth(*iter) < item.getClass().getItemHealth(item)) - { - item = *iter; - } - } - } - + // Find previous item (or its replacement) by id. // we should equip previous item only if expired bound item was equipped. + MWWorld::Ptr item = store.findReplacement(prevItemId); if (item.isEmpty() || !wasEquipped) return; @@ -774,11 +760,14 @@ namespace MWMechanics { // The actor was killed by a magic effect. Figure out if the player was responsible for it. const ActiveSpells& spells = creatureStats.getActiveSpells(); - bool killedByPlayer = false; MWWorld::Ptr player = getPlayer(); + std::set playerFollowers; + getActorsSidingWith(player, playerFollowers); + for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) { const ActiveSpells::ActiveSpellParams& spell = it->second; + MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId); for (std::vector::const_iterator effectIt = spell.mEffects.begin(); effectIt != spell.mEffects.end(); ++effectIt) { @@ -796,17 +785,19 @@ namespace MWMechanics isDamageEffect = true; } - MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId); - if (isDamageEffect && caster == player) - killedByPlayer = true; + if (isDamageEffect) + { + if (caster == player || playerFollowers.find(caster) != playerFollowers.end()) + { + if (caster.getClass().getNpcStats(caster).isWerewolf()) + caster.getClass().getNpcStats(caster).addWerewolfKill(); + + MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player); + break; + } + } } } - if (killedByPlayer) - { - MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player); - if (player.getClass().getNpcStats(player).isWerewolf()) - player.getClass().getNpcStats(player).addWerewolfKill(); - } } // TODO: dirty flag for magic effects to avoid some unnecessary work below? @@ -998,7 +989,8 @@ namespace MWMechanics MWWorld::ContainerStoreIterator torch = inventoryStore.end(); for (MWWorld::ContainerStoreIterator it = inventoryStore.begin(); it != inventoryStore.end(); ++it) { - if (it->getTypeName() == typeid(ESM::Light).name()) + if (it->getTypeName() == typeid(ESM::Light).name() && + it->getClass().canBeEquipped(*it, ptr).first) { torch = it; break; @@ -1019,8 +1011,7 @@ namespace MWMechanics heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); // If we have a torch and can equip it, then equip it now. - if (heldIter == inventoryStore.end() - && torch->getClass().canBeEquipped(*torch, ptr).first == 1) + if (heldIter == inventoryStore.end()) { inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torch, ptr); } diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 6a0f5b013..e6cca0523 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -120,6 +120,9 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr if (!isDestReached && mTimer > AI_REACTION_TIME) { + if (actor.getClass().isBipedal(actor)) + openDoors(actor); + bool wasShortcutting = mIsShortcutting; bool destInLOS = false; @@ -209,41 +212,10 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur // first check if obstacle is a door static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); - MWWorld::Ptr door = getNearbyDoor(actor, distance); - if (door != MWWorld::Ptr() && actor.getClass().isBipedal(actor)) + const MWWorld::Ptr door = getNearbyDoor(actor, distance); + if (!door.isEmpty() && actor.getClass().isBipedal(actor)) { - // note: AiWander currently does not open doors - if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getClass().getDoorState(door) == 0) - { - if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 )) - { - MWBase::Environment::get().getWorld()->activate(door, actor); - return; - } - - std::string keyId = door.getCellRef().getKey(); - if (keyId.empty()) - return; - - bool hasKey = false; - const MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); - - // make key id lowercase - Misc::StringUtils::lowerCaseInPlace(keyId); - for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it) - { - std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::lowerCaseInPlace(refId); - if (refId == keyId) - { - hasKey = true; - break; - } - } - - if (hasKey) - MWBase::Environment::get().getWorld()->activate(door, actor); - } + openDoors(actor); } else { @@ -251,6 +223,35 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur } } +void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor) +{ + static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); + + const MWWorld::Ptr door = getNearbyDoor(actor, distance); + if (door == MWWorld::Ptr()) + return; + + // note: AiWander currently does not open doors + if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getClass().getDoorState(door) == 0) + { + if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 )) + { + MWBase::Environment::get().getWorld()->activate(door, actor); + return; + } + + const std::string keyId = door.getCellRef().getKey(); + if (keyId.empty()) + return; + + MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); + MWWorld::Ptr keyPtr = invStore.search(keyId); + + if (!keyPtr.isEmpty()) + MWBase::Environment::get().getWorld()->activate(door, actor); + } +} + const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const MWWorld::CellStore *cell) { const ESM::CellId& id = cell->getCell()->getCellId(); diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 7e8f905ad..2b685accc 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -123,6 +123,7 @@ namespace MWMechanics virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell); void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); + void openDoors(const MWWorld::Ptr& actor); const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 07e5fa7d6..fbdb19d5b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -561,6 +561,10 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) { + // If the current animation is persistent, do not touch it + if (isPersistentAnimPlaying()) + return; + if (mPtr.getClass().isActor()) refreshHitRecoilAnims(); @@ -744,6 +748,11 @@ void CharacterController::playRandomDeath(float startpoint) { mDeathState = chooseRandomDeathState(); } + + // Do not interrupt scripted animation by death + if (isPersistentAnimPlaying()) + return; + playDeath(startpoint, mDeathState); } @@ -829,8 +838,8 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim mIdleState = CharState_Idle; } - - if(mDeathState == CharState_None) + // Do not update animation status for dead actors + if(mDeathState == CharState_None && (!cls.isActor() || !cls.getCreatureStats(mPtr).isDead())) refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); mAnimation->runAnimation(0.f); @@ -1299,6 +1308,10 @@ bool CharacterController::updateWeaponState() } } + // Combat for actors with persistent animations obviously will be buggy + if (isPersistentAnimPlaying()) + return forcestateupdate; + float complete; bool animPlaying; if(mAttackingOrSpell) @@ -2013,15 +2026,17 @@ void CharacterController::update(float duration) { // initial start of death animation for actors that started the game as dead // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag - if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty()) + if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty() && cls.isPersistent(mPtr)) { + // Fast-forward death animation to end for persisting corpses playDeath(1.f, mDeathState); } // We must always queue movement, even if there is none, to apply gravity. world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); } - osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); + bool isPersist = isPersistentAnimPlaying(); + osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration); if(duration > 0.0f) moved /= duration; else @@ -2135,6 +2150,10 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int if(!mAnimation || !mAnimation->hasAnimation(groupname)) return false; + // We should not interrupt persistent animations by non-persistent ones + if (isPersistentAnimPlaying() && !persist) + return false; + // If this animation is a looped animation (has a "loop start" key) that is already playing // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count // and remove any other animations that were queued. @@ -2164,23 +2183,28 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) { - clearAnimQueue(); - mAnimQueue.push_back(entry); + clearAnimQueue(persist); mAnimation->disable(mCurrentIdle); mCurrentIdle.clear(); mIdleState = CharState_SpecialIdle; bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0); - mAnimation->play(groupname, Priority_Default, + mAnimation->play(groupname, persist && groupname != "idle" ? Priority_Persistent : Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback); } else { mAnimQueue.resize(1); - mAnimQueue.push_back(entry); } + + // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing + if (groupname == "idle") + entry.mPersist = false; + + mAnimQueue.push_back(entry); + return true; } @@ -2189,6 +2213,17 @@ void CharacterController::skipAnim() mSkipAnim = true; } +bool CharacterController::isPersistentAnimPlaying() +{ + if (!mAnimQueue.empty()) + { + AnimationQueueEntry& first = mAnimQueue.front(); + return first.mPersist && isAnimPlaying(first.mGroup); + } + + return false; +} + bool CharacterController::isAnimPlaying(const std::string &groupName) { if(mAnimation == NULL) @@ -2196,12 +2231,19 @@ bool CharacterController::isAnimPlaying(const std::string &groupName) return mAnimation->isPlaying(groupName); } - -void CharacterController::clearAnimQueue() +void CharacterController::clearAnimQueue(bool clearPersistAnims) { - if(!mAnimQueue.empty()) + // Do not interrupt scripted animations, if we want to keep them + if ((!isPersistentAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.clear(); + + for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) + { + if (clearPersistAnims || !it->mPersist) + it = mAnimQueue.erase(it); + else + ++it; + } } void CharacterController::forceStateUpdate() @@ -2211,6 +2253,7 @@ void CharacterController::forceStateUpdate() clearAnimQueue(); refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); + if(mDeathState != CharState_None) { playRandomDeath(); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index a172620b9..381cf71a5 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -39,8 +39,8 @@ enum Priority { Priority_Knockdown, Priority_Torch, Priority_Storm, - Priority_Death, + Priority_Persistent, Num_Priorities }; @@ -215,12 +215,14 @@ class CharacterController : public MWRender::Animation::TextKeyListener void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force=false); void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false); - void clearAnimQueue(); + void clearAnimQueue(bool clearPersistAnims = false); bool updateWeaponState(); bool updateCreatureState(); void updateIdleStormState(bool inwater); + bool isPersistentAnimPlaying(); + void updateAnimQueue(); void updateHeadTracking(float duration); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 89e1bef06..1fc98a327 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -630,22 +630,12 @@ namespace MWMechanics float d = static_cast(std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100)); float e = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); - float pcTerm = (clampedDisposition - 50 + a + b + c) * playerStats.getFatigueTerm(); float npcTerm = (d + e + f) * sellerStats.getFatigueTerm(); - float buyTerm = 0.01f * (100 - 0.5f * (pcTerm - npcTerm)); - float sellTerm = 0.01f * (50 - 0.5f * (npcTerm - pcTerm)); - - float x; - if(buying) x = buyTerm; - else x = std::min(buyTerm, sellTerm); - int offerPrice; - if (x < 1) - offerPrice = int(x * basePrice); - else - offerPrice = basePrice + int((x - 1) * basePrice); - offerPrice = std::max(1, offerPrice); - return offerPrice; + float buyTerm = 0.01f * std::max(75.f, (100 - 0.5f * (pcTerm - npcTerm))); + float sellTerm = 0.01f * std::min(75.f, (50 - 0.5f * (npcTerm - pcTerm))); + int offerPrice = int(basePrice * (buying ? buyTerm : sellTerm)); + return std::max(1, offerPrice); } int MechanicsManager::countDeaths (const std::string& id) const @@ -1505,7 +1495,7 @@ namespace MWMechanics void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) { - if (attacker.isEmpty() || attacker != getPlayer()) + if (attacker.isEmpty() || victim.isEmpty()) return; if (victim == attacker) @@ -1515,13 +1505,23 @@ namespace MWMechanics return; // TODO: implement animal rights const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim); + if (victimStats.getCrimeId() == -1) + return; + + // For now we report only about crimes of player and player's followers + const MWWorld::Ptr &player = getPlayer(); + if (attacker != player) + { + std::set playerFollowers; + getActorsSidingWith(player, playerFollowers); + if (playerFollowers.find(attacker) == playerFollowers.end()) + return; + } // Simple check for who attacked first: if the player attacked first, a crimeId should be set // Doesn't handle possible edge case where no one reported the assault, but in such a case, // for bystanders it is not possible to tell who attacked first, anyway. - if (victimStats.getCrimeId() != -1) - commitCrime(attacker, victim, MWBase::MechanicsManager::OT_Murder); - + commitCrime(player, victim, MWBase::MechanicsManager::OT_Murder); } bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 3c6f14bfd..0635a5520 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -26,13 +26,13 @@ namespace MWMechanics bool proximityToDoor(const MWWorld::Ptr& actor, float minDist) { - if(getNearbyDoor(actor, minDist)!=MWWorld::Ptr()) - return true; - else + if(getNearbyDoor(actor, minDist).isEmpty()) return false; + else + return true; } - MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist) + const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist) { MWWorld::CellStore *cell = actor.getCell(); @@ -50,6 +50,16 @@ namespace MWMechanics const MWWorld::LiveCellRef& ref = *it; osg::Vec3f doorPos(ref.mData.getPosition().asVec3()); + + // FIXME: cast + const MWWorld::Ptr doorPtr = MWWorld::Ptr(&const_cast &>(ref), actor.getCell()); + + int doorState = doorPtr.getClass().getDoorState(doorPtr); + float doorRot = ref.mData.getPosition().rot[2] - doorPtr.getCellRef().getPosition().rot[2]; + + if (doorState != 0 || doorRot != 0) + continue; // the door is already opened/opening + doorPos.z() = 0; float angle = std::acos(actorDir * (doorPos - pos) / (actorDir.length() * (doorPos - pos).length())); @@ -62,8 +72,7 @@ namespace MWMechanics if ((pos - doorPos).length2() > minDist*minDist) continue; - // FIXME cast - return MWWorld::Ptr(&const_cast &>(ref), actor.getCell()); // found, stop searching + return doorPtr; // found, stop searching } return MWWorld::Ptr(); // none found diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index f71207346..6a84e0ef9 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -17,7 +17,7 @@ namespace MWMechanics /// Returns door pointer within range. No guarantee is given as to which one /** \return Pointer to the door, or NULL if none exists **/ - MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); + const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); class ObstacleCheck { diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp index ede0d2de7..eee5e6449 100644 --- a/apps/openmw/mwmechanics/trading.cpp +++ b/apps/openmw/mwmechanics/trading.cpp @@ -32,7 +32,6 @@ namespace MWMechanics // Is the player buying? bool buying = (merchantOffer < 0); - int a = std::abs(merchantOffer); int b = std::abs(playerOffer); int d = (buying) @@ -56,7 +55,7 @@ namespace MWMechanics float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); float x = gmst.find("fBargainOfferMulti")->getFloat() * d + gmst.find("fBargainOfferBase")->getFloat() - + std::abs(int(pcTerm - npcTerm)); + + int(pcTerm - npcTerm); int roll = Misc::Rng::rollDice(100) + 1; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3ccc06665..d96b9f809 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1089,11 +1089,28 @@ namespace MWRender osg::Vec3f Animation::runAnimation(float duration) { + // If we have scripted animations, play only them + bool hasScriptedAnims = false; + for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) + { + if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) && stateiter->second.mPlaying) + { + hasScriptedAnims = true; + break; + } + } + osg::Vec3f movement(0.f, 0.f, 0.f); AnimStateMap::iterator stateiter = mStates.begin(); while(stateiter != mStates.end()) { AnimState &state = stateiter->second; + if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent))) + { + ++stateiter; + continue; + } + const NifOsg::TextKeyMap &textkeys = state.mSource->getTextKeys(); NifOsg::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.getTime())); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6f0ddba3a..3dbf6475c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -12,16 +12,21 @@ #include #include #include +#include +#include #include #include +#include + #include #include #include #include #include +#include #include @@ -39,7 +44,12 @@ #include #include +#include + #include "../mwworld/cellstore.hpp" +#include "../mwgui/loadingscreen.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include "sky.hpp" #include "effectmanager.hpp" @@ -193,8 +203,8 @@ namespace MWRender , mNightEyeFactor(0.f) , mDistantFog(false) , mDistantTerrain(false) - , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) + , mBorders(false) { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); @@ -212,7 +222,7 @@ namespace MWRender mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); - mRootNode->addChild(sceneRoot); + mRootNode->addChild(mSceneRoot); mPathgrid.reset(new Pathgrid(mRootNode)); @@ -244,9 +254,10 @@ namespace MWRender Settings::Manager::getBool("auto use terrain specular maps", "Shaders")); if (mDistantTerrain) - mTerrain.reset(new Terrain::QuadTreeWorld(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile)); + mTerrain.reset(new Terrain::QuadTreeWorld(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); else - mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile)); + mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); + mTerrain->setDefaultViewer(mViewer->getCamera()); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); @@ -467,6 +478,13 @@ namespace MWRender { mSky->setEnabled(enabled); } + + bool RenderingManager::toggleBorders() + { + mBorders = !mBorders; + mTerrain->setBordersVisible(mBorders); + return mBorders; + } bool RenderingManager::toggleRenderMode(RenderMode mode) { @@ -668,7 +686,203 @@ namespace MWRender mutable bool mDone; }; - void RenderingManager::screenshot(osg::Image *image, int w, int h) + bool RenderingManager::screenshot360(osg::Image* image, std::string settingStr) + { + int screenshotW = mViewer->getCamera()->getViewport()->width(); + int screenshotH = mViewer->getCamera()->getViewport()->height(); + int screenshotMapping = 0; + int cubeSize = screenshotMapping == 2 ? + screenshotW: // planet mapping needs higher resolution + screenshotW / 2; + + std::vector settingArgs; + boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" ")); + + if (settingArgs.size() > 0) + { + std::string typeStrings[4] = {"spherical","cylindrical","planet","cubemap"}; + bool found = false; + + for (int i = 0; i < 4; ++i) + if (settingArgs[0].compare(typeStrings[i]) == 0) + { + screenshotMapping = i; + found = true; + break; + } + + if (!found) + { + std::cerr << "Wrong screenshot type: " << settingArgs[0] << "." << std::endl; + return false; + } + } + + if (settingArgs.size() > 1) + screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str())); + + if (settingArgs.size() > 2) + screenshotH = std::min(10000,std::atoi(settingArgs[2].c_str())); + + if (settingArgs.size() > 3) + cubeSize = std::min(5000,std::atoi(settingArgs[3].c_str())); + + if (mCamera->isVanityOrPreviewModeEnabled()) + { + std::cerr << "Spherical screenshots are not allowed in preview mode." << std::endl; + return false; + } + + bool rawCubemap = screenshotMapping == 3; + + if (rawCubemap) + screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row + else if (screenshotMapping == 2) + screenshotH = screenshotW; // use square resolution for planet mapping + + std::vector> images; + + for (int i = 0; i < 6; ++i) + images.push_back(new osg::Image); + + osg::Vec3 directions[6] = { + rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), + osg::Vec3(0,0,-1), + osg::Vec3(-1,0,0), + rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0), + osg::Vec3(0,1,0), + osg::Vec3(0,-1,0)}; + + double rotations[] = { + -osg::PI / 2.0, + osg::PI / 2.0, + osg::PI, + 0, + osg::PI / 2.0, + osg::PI / 2.0}; + + double fovBackup = mFieldOfView; + mFieldOfView = 90.0; // each cubemap side sees 90 degrees + + int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask(); + + if (mCamera->isFirstPerson()) + mPlayerAnimation->getObjectRoot()->setNodeMask(0); + + for (int i = 0; i < 6; ++i) // for each cubemap side + { + osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]); + + if (!rawCubemap) + transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); + + osg::Image *sideImage = images[i].get(); + screenshot(sideImage,cubeSize,cubeSize,transform); + + if (!rawCubemap) + sideImage->flipHorizontal(); + } + + mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); + mFieldOfView = fovBackup; + + if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images + { + image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType()); + + for (int i = 0; i < 6; ++i) + osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0); + + return true; + } + + // run on GPU now: + + osg::ref_ptr cubeTexture (new osg::TextureCubeMap); + cubeTexture->setResizeNonPowerOfTwoHint(false); + + cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST); + cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST); + + cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + for (int i = 0; i < 6; ++i) + cubeTexture->setImage(i,images[i].get()); + + osg::ref_ptr screenshotCamera (new osg::Camera); + osg::ref_ptr quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0))); + + std::map defineMap; + + Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); + osg::ref_ptr fragmentShader (shaderMgr.getShader("s360_fragment.glsl",defineMap,osg::Shader::FRAGMENT)); + osg::ref_ptr vertexShader (shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); + osg::ref_ptr stateset = new osg::StateSet; + + osg::ref_ptr program (new osg::Program); + program->addShader(fragmentShader); + program->addShader(vertexShader); + stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("cubeMap",0)); + stateset->addUniform(new osg::Uniform("mapping",screenshotMapping)); + stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); + + quad->setStateSet(stateset); + quad->setUpdateCallback(NULL); + + screenshotCamera->addChild(quad); + + mRootNode->addChild(screenshotCamera); + + renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH); + + screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren()); + mRootNode->removeChild(screenshotCamera); + + return true; + } + + void RenderingManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) + { + camera->setNodeMask(Mask_RenderToTexture); + camera->attach(osg::Camera::COLOR_BUFFER, image); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); + + camera->setViewport(0, 0, w, h); + + osg::ref_ptr texture (new osg::Texture2D); + texture->setInternalFormat(GL_RGB); + texture->setTextureSize(w,h); + texture->setResizeNonPowerOfTwoHint(false); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + camera->attach(osg::Camera::COLOR_BUFFER,texture); + + image->setDataType(GL_UNSIGNED_BYTE); + image->setPixelFormat(texture->getInternalFormat()); + + // The draw needs to complete before we can copy back our image. + osg::ref_ptr callback (new NotifyDrawCompletedCallback); + camera->setFinalDrawCallback(callback); + + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false); + + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + callback->waitTillDone(); + + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff(); + + // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + } + + void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) { osg::ref_ptr rttCamera (new osg::Camera); rttCamera->setNodeMask(Mask_RenderToTexture); @@ -677,7 +891,8 @@ namespace MWRender rttCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); rttCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); - rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix()); + rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); + rttCamera->setViewport(0, 0, w, h); osg::ref_ptr texture (new osg::Texture2D); @@ -693,25 +908,17 @@ namespace MWRender rttCamera->setUpdateCallback(new NoTraverseCallback); rttCamera->addChild(mSceneRoot); - rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); - mRootNode->addChild(rttCamera); + rttCamera->addChild(mWater->getReflectionCamera()); + rttCamera->addChild(mWater->getRefractionCamera()); - // The draw needs to complete before we can copy back our image. - osg::ref_ptr callback (new NotifyDrawCompletedCallback); - rttCamera->setFinalDrawCallback(callback); + rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); - // at the time this function is called we are in the middle of a frame, - // so out of order calls are necessary to get a correct frameNumber for the next frame. - // refer to the advance() and frame() order in Engine::go() - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); + mRootNode->addChild(rttCamera); - callback->waitTillDone(); + rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed - mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + renderCameraToImage(rttCamera.get(),image,w,h); rttCamera->removeChildren(0, rttCamera->getNumChildren()); mRootNode->removeChild(rttCamera); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 1c689d29f..f8e7ba8bc 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -125,7 +126,8 @@ namespace MWRender void setWaterHeight(float level); /// Take a screenshot of w*h onto the given image, not including the GUI. - void screenshot(osg::Image* image, int w, int h); + void screenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); + bool screenshot360(osg::Image* image, std::string settingStr); struct RayResult { @@ -205,6 +207,8 @@ namespace MWRender LandManager* getLandManager() const; + bool toggleBorders(); + private: void updateProjectionMatrix(); void updateTextureFiltering(); @@ -213,6 +217,8 @@ namespace MWRender void reportStats() const; + void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); + osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); osg::ref_ptr mIntersectionVisitor; @@ -261,6 +267,7 @@ namespace MWRender float mFieldOfViewOverride; float mFieldOfView; float mFirstPersonFieldOfView; + bool mBorders; void operator = (const RenderingManager&); RenderingManager(const RenderingManager&); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 6e1fef716..3c617f794 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -470,6 +470,16 @@ void Water::updateWaterMaterial() updateVisible(); } +osg::Camera *Water::getReflectionCamera() +{ + return mReflection; +} + +osg::Camera *Water::getRefractionCamera() +{ + return mRefraction; +} + void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) { osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index a4fd1ed36..e2413cfa0 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -112,6 +113,9 @@ namespace MWRender void update(float dt); + osg::Camera *getReflectionCamera(); + osg::Camera *getRefractionCamera(); + void processChangedSettings(const Settings::CategorySettingVector& settings); osg::Uniform *getRainIntensityUniform(); diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index c98e0fc5a..c51a55b50 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -193,10 +193,18 @@ namespace MWScript Interpreter::Type_Integer time = static_cast(runtime[0].mFloat); runtime.pop(); + // Chance for Idle is unused + if (arg0) + { + --arg0; + runtime.pop(); + } + std::vector idleList; bool repeat = false; - for(int i=1; i < 10 && arg0; ++i) + // Chances for Idle2-Idle9 + for(int i=2; i<=9 && arg0; ++i) { if(!repeat) repeat = true; diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index e999097db..fed780be7 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -454,5 +454,6 @@ op 0x2000303: Fixme, explicit op 0x2000304: Show op 0x2000305: Show, explicit op 0x2000306: OnActivate, explicit +op 0x2000307: ToggleBorders, tb -opcodes 0x2000307-0x3ffffff unused +opcodes 0x2000308-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 59f2cc9c6..3fa66af10 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -254,6 +254,20 @@ namespace MWScript } }; + class OpToggleBorders : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + bool enabled = + MWBase::Environment::get().getWorld()->toggleBorders(); + + runtime.getContext().report (enabled ? + "Border Rendering -> On" : "Border Rendering -> Off"); + } + }; + class OpTogglePathgrid : public Interpreter::Opcode0 { public: @@ -1380,6 +1394,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevItem, new OpRemoveFromLevItem); interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraph, new OpShowSceneGraph); interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraphExplicit, new OpShowSceneGraph); + interpreter.installSegment5 (Compiler::Misc::opcodeToggleBorders, new OpToggleBorders); } } } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 1b6495c11..fc3c2e245 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -945,8 +945,13 @@ namespace MWWorld { const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get().find("fCorpseClearDelay")->getFloat(); - if (creatureStats.isDead() && !ptr.getClass().isPersistent(ptr) && creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + if (creatureStats.isDead() && + creatureStats.isDeathAnimationFinished() && + !ptr.getClass().isPersistent(ptr) && + creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + { MWBase::Environment::get().getWorld()->deleteObject(ptr); + } } void CellStore::respawn() diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index dd5d7a853..6c369a154 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -47,7 +47,7 @@ namespace for (typename MWWorld::CellRefList::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); ++iter) { - if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2)) + if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2) && iter->mData.getCount()) { MWWorld::Ptr ptr (&*iter, 0); ptr.setContainerStore (store); @@ -675,6 +675,30 @@ int MWWorld::ContainerStore::getType (const ConstPtr& ptr) "Object '" + ptr.getCellRef().getRefId() + "' of type " + ptr.getTypeName() + " can not be placed into a container"); } +MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) +{ + MWWorld::Ptr item; + int itemHealth = 1; + for (MWWorld::ContainerStoreIterator iter = begin(); iter != end(); ++iter) + { + int iterHealth = iter->getClass().hasItemHealth(*iter) ? iter->getClass().getItemHealth(*iter) : 1; + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) + { + // Prefer the stack with the lowest remaining uses + // Try to get item with zero durability only if there are no other items found + if (item.isEmpty() || + (iterHealth > 0 && iterHealth < itemHealth) || + (itemHealth <= 0 && iterHealth > 0)) + { + item = *iter; + itemHealth = iterHealth; + } + } + } + + return item; +} + MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) { { diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index dbb82cbda..b67eb6552 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -198,6 +198,9 @@ namespace MWWorld ///< This function throws an exception, if ptr does not point to an object, that can be /// put into a container. + Ptr findReplacement(const std::string& id); + ///< Returns replacement for object with given id. Prefer used items (with low durability left). + Ptr search (const std::string& id); virtual void writeState (ESM::InventoryState& state) const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index cbd5b9340..5c7ee9526 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1937,6 +1937,11 @@ namespace MWWorld return mRendering->toggleRenderMode(MWRender::Render_Scene); } + bool World::toggleBorders() + { + return mRendering->toggleBorders(); + } + void World::PCDropped (const Ptr& item) { std::string script = item.getClass().getScript(item); @@ -2300,6 +2305,11 @@ namespace MWWorld { mRendering->screenshot(image, w, h); } + + bool World::screenshot360(osg::Image* image, std::string settingStr) + { + return mRendering->screenshot360(image,settingStr); + } void World::activateDoor(const MWWorld::Ptr& door) { diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1d22b96bc..29bc4692c 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -218,6 +218,7 @@ namespace MWWorld bool toggleWater() override; bool toggleWorld() override; + bool toggleBorders() override; void adjustSky() override; @@ -561,6 +562,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; + bool screenshot360 (osg::Image* image, std::string settingStr) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index ecb844a9b..3efd236cb 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -114,7 +114,7 @@ add_component_dir (translation ) add_component_dir (terrain - storage world buffercache defs terraingrid material terraindrawable texturemanager chunkmanager compositemaprenderer quadtreeworld quadtreenode viewdata + storage world buffercache defs terraingrid material terraindrawable texturemanager chunkmanager compositemaprenderer quadtreeworld quadtreenode viewdata cellborder ) add_component_dir (loadinglistener diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index cd5bf7ef7..7638d0f78 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -318,6 +318,8 @@ namespace Compiler extensions.registerInstruction ("removefromlevcreature", "ccl", opcodeRemoveFromLevCreature); extensions.registerInstruction ("addtolevitem", "ccl", opcodeAddToLevItem); extensions.registerInstruction ("removefromlevitem", "ccl", opcodeRemoveFromLevItem); + extensions.registerInstruction ("tb", "", opcodeToggleBorders); + extensions.registerInstruction ("toggleborders", "", opcodeToggleBorders); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 6a6552467..aef92b311 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -295,6 +295,7 @@ namespace Compiler const int opcodeRemoveFromLevItem = 0x20002fe; const int opcodeShowSceneGraph = 0x2002f; const int opcodeShowSceneGraphExplicit = 0x20030; + const int opcodeToggleBorders = 0x2000307; } namespace Sky diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp index 1d48cce0b..6c7a3b090 100644 --- a/components/loadinglistener/loadinglistener.hpp +++ b/components/loadinglistener/loadinglistener.hpp @@ -20,7 +20,7 @@ namespace Loading /// @note To get the loading screen to actually update, you must call setProgress / increaseProgress periodically. /// @note It is best to use the ScopedLoad object instead of using loadingOn()/loadingOff() directly, /// so that the loading is exception safe. - virtual void loadingOn() {} + virtual void loadingOn(bool visible=true) {} virtual void loadingOff() {} /// Set the total range of progress (e.g. the number of objects to load). diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp new file mode 100644 index 000000000..d9e6d52fc --- /dev/null +++ b/components/terrain/cellborder.cpp @@ -0,0 +1,98 @@ +#include "cellborder.hpp" + +#include +#include +#include + +#include "world.hpp" +#include "../esm/loadland.hpp" + +namespace MWRender +{ + +CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask): + mWorld(world), + mRoot(root), + mBorderMask(borderMask) +{ +} + +void CellBorder::createCellBorderGeometry(int x, int y) +{ + const int cellSize = ESM::Land::REAL_SIZE; + const int borderSegments = 40; + const float offset = 10.0; + + osg::Vec3 cellCorner = osg::Vec3(x * cellSize,y * cellSize,0); + + osg::ref_ptr vertices = new osg::Vec3Array; + osg::ref_ptr colors = new osg::Vec4Array; + osg::ref_ptr normals = new osg::Vec3Array; + + normals->push_back(osg::Vec3(0.0f,-1.0f, 0.0f)); + + float borderStep = cellSize / ((float) borderSegments); + + for (int i = 0; i <= 2 * borderSegments; ++i) + { + osg::Vec3f pos = i < borderSegments ? + osg::Vec3(i * borderStep,0.0f,0.0f) : + osg::Vec3(cellSize,(i - borderSegments) * borderStep,0.0f); + + pos += cellCorner; + pos += osg::Vec3f(0,0,mWorld->getHeightAt(pos) + offset); + + vertices->push_back(pos); + + osg::Vec4f col = i % 2 == 0 ? + osg::Vec4f(0,0,0,1) : + osg::Vec4f(1,1,0,1); + + colors->push_back(col); + } + + osg::ref_ptr border = new osg::Geometry; + border->setVertexArray(vertices.get()); + border->setNormalArray(normals.get()); + border->setNormalBinding(osg::Geometry::BIND_OVERALL); + border->setColorArray(colors.get()); + border->setColorBinding(osg::Geometry::BIND_PER_VERTEX); + + border->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP,0,vertices->size())); + + osg::ref_ptr borderGeode = new osg::Geode; + borderGeode->addDrawable(border.get()); + + osg::StateSet *stateSet = borderGeode->getOrCreateStateSet(); + + osg::PolygonMode* polygonmode = new osg::PolygonMode; + polygonmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); + stateSet->setAttributeAndModes(polygonmode,osg::StateAttribute::ON); + + borderGeode->setNodeMask(mBorderMask); + + mRoot->addChild(borderGeode); + + mCellBorderNodes[std::make_pair(x,y)] = borderGeode; +} + +void CellBorder::destroyCellBorderGeometry(int x, int y) +{ + CellGrid::iterator it = mCellBorderNodes.find(std::make_pair(x,y)); + + if (it == mCellBorderNodes.end()) + return; + + osg::ref_ptr borderNode = it->second; + mRoot->removeChild(borderNode); + + mCellBorderNodes.erase(it); +} + +void CellBorder::destroyCellBorderGeometry() +{ + for (CellGrid::iterator it = mCellBorderNodes.begin(); it != mCellBorderNodes.end(); ++it) + destroyCellBorderGeometry(it->first.first,it->first.second); +} + +} diff --git a/components/terrain/cellborder.hpp b/components/terrain/cellborder.hpp new file mode 100644 index 000000000..530ea31ca --- /dev/null +++ b/components/terrain/cellborder.hpp @@ -0,0 +1,41 @@ +#ifndef GAME_RENDER_CELLBORDER +#define GAME_RENDER_CELLBORDER + +#include +#include + +namespace Terrain +{ + class World; +} + +namespace MWRender +{ + /** + * @Brief Handles the debug cell borders. + */ + class CellBorder + { + public: + typedef std::map, osg::ref_ptr > CellGrid; + + CellBorder(Terrain::World *world, osg::Group *root, int borderMask); + + void createCellBorderGeometry(int x, int y); + void destroyCellBorderGeometry(int x, int y); + + /** + Destroys the geometry for all borders. + */ + void destroyCellBorderGeometry(); + + protected: + Terrain::World *mWorld; + osg::Group *mRoot; + + CellGrid mCellBorderNodes; + int mBorderMask; + }; +} + +#endif diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 052090dc8..e75deb2f7 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -222,8 +222,8 @@ private: osg::ref_ptr mRootNode; }; -QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask) - : World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask) +QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask, int borderMask) + : World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) , mViewDataMap(new ViewDataMap) , mQuadTreeBuilt(false) { diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index ef33f158e..c166a9cb1 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -19,7 +19,7 @@ namespace Terrain class QuadTreeWorld : public Terrain::World { public: - QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0); + QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0, int borderMask=0); ~QuadTreeWorld(); void accept(osg::NodeVisitor& nv); diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 466cddddc..74f683774 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -17,8 +17,8 @@ public: virtual void reset(unsigned int frame) {} }; -TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask) - : Terrain::World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask) +TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask) + : Terrain::World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) , mNumSplits(4) { } @@ -75,6 +75,8 @@ void TerrainGrid::loadCell(int x, int y) if (!terrainNode) return; // no terrain defined + TerrainGrid::World::loadCell(x,y); + mTerrainRoot->addChild(terrainNode); mGrid[std::make_pair(x,y)] = terrainNode; @@ -82,10 +84,12 @@ void TerrainGrid::loadCell(int x, int y) void TerrainGrid::unloadCell(int x, int y) { - Grid::iterator it = mGrid.find(std::make_pair(x,y)); + MWRender::CellBorder::CellGrid::iterator it = mGrid.find(std::make_pair(x,y)); if (it == mGrid.end()) return; + Terrain::World::unloadCell(x,y); + osg::ref_ptr terrainNode = it->second; mTerrainRoot->removeChild(terrainNode); diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 189fe7f63..87e3b432c 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -14,7 +14,7 @@ namespace Terrain class TerrainGrid : public Terrain::World { public: - TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0); + TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0, int borderMask=0); ~TerrainGrid(); virtual void cacheCell(View* view, int x, int y); @@ -33,10 +33,8 @@ namespace Terrain // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks unsigned int mNumSplits; - typedef std::map, osg::ref_ptr > Grid; - Grid mGrid; + MWRender::CellBorder::CellGrid mGrid; }; - } #endif diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 213d5c8a7..cc81dbef8 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -14,10 +14,11 @@ namespace Terrain { -World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask) +World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask) : mStorage(storage) , mParent(parent) , mResourceSystem(resourceSystem) + , mBorderVisible(false) { mTerrainRoot = new osg::Group; mTerrainRoot->setNodeMask(nodeMask); @@ -39,7 +40,6 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst compileRoot->addChild(compositeCam); - mCompositeMapRenderer = new CompositeMapRenderer; compositeCam->addChild(mCompositeMapRenderer); @@ -47,6 +47,7 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst mTextureManager.reset(new TextureManager(mResourceSystem->getSceneManager())); mChunkManager.reset(new ChunkManager(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer)); + mCellBorder.reset(new MWRender::CellBorder(this,mTerrainRoot.get(),borderMask)); mResourceSystem->addResourceManager(mChunkManager.get()); mResourceSystem->addResourceManager(mTextureManager.get()); @@ -65,6 +66,35 @@ World::~World() delete mStorage; } +void World::setBordersVisible(bool visible) +{ + mBorderVisible = visible; + + if (visible) + { + for (std::set>::iterator it = mLoadedCells.begin(); it != mLoadedCells.end(); ++it) + mCellBorder->createCellBorderGeometry(it->first,it->second); + } + else + mCellBorder->destroyCellBorderGeometry(); +} + +void World::loadCell(int x, int y) +{ + if (mBorderVisible) + mCellBorder->createCellBorderGeometry(x,y); + + mLoadedCells.insert(std::pair(x,y)); +} + +void World::unloadCell(int x, int y) +{ + if (mBorderVisible) + mCellBorder->destroyCellBorderGeometry(x,y); + + mLoadedCells.erase(std::pair(x,y)); +} + void World::setTargetFrameRate(float rate) { mCompositeMapRenderer->setTargetFrameRate(rate); diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 688ed84d5..ae71693bd 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -6,8 +6,10 @@ #include #include +#include #include "defs.hpp" +#include "cellborder.hpp" namespace osg { @@ -54,7 +56,7 @@ namespace Terrain /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) /// @param nodeMask mask for the terrain root /// @param preCompileMask mask for pre compiling textures - World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask); + World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask); virtual ~World(); /// See CompositeMapRenderer::setTargetFrameRate @@ -76,16 +78,16 @@ namespace Terrain /// Load the cell into the scene graph. /// @note Not thread safe. - /// @note May be ignored by derived implementations that don't organize the terrain into cells. - virtual void loadCell(int x, int y) {} + virtual void loadCell(int x, int y); /// Remove the cell from the scene graph. /// @note Not thread safe. - /// @note May be ignored by derived implementations that don't organize the terrain into cells. - virtual void unloadCell(int x, int y) {} + virtual void unloadCell(int x, int y); virtual void enable(bool enabled) {} + virtual void setBordersVisible(bool visible); + /// Create a View to use with preload feature. The caller is responsible for deleting the view. /// @note Thread safe. virtual View* createView() { return NULL; } @@ -113,8 +115,13 @@ namespace Terrain std::unique_ptr mTextureManager; std::unique_ptr mChunkManager; - }; + std::unique_ptr mCellBorder; + + bool mBorderVisible; + + std::set> mLoadedCells; + }; } #endif diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 416f1bc1a..308a29546 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -77,6 +77,7 @@ This is how original Morrowind behaves. If this setting is false, player has to wait until end of death animation in all cases. This case is more safe, but makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. +Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation. This setting can only be configured by editing the settings configuration file. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 09283bfc9..1390bf8c6 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -402,6 +402,10 @@ contrast = 1.0 # Video gamma setting. (>0.0). No effect in Linux. gamma = 1.0 +# Type of screenshot to take (regular, cylindrical, spherical or planet), optionally followed by +# screenshot width, height and cubemap resolution in pixels. (e.g. spherical 1600 1000 1200) +screenshot type = regular + [Water] # Enable water shader with reflections and optionally refraction. diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 5833a592f..7baca78ef 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -16,6 +16,8 @@ set(SHADER_FILES terrain_fragment.glsl lighting.glsl parallax.glsl + s360_fragment.glsl + s360_vertex.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl new file mode 100644 index 000000000..f52e1478e --- /dev/null +++ b/files/shaders/s360_fragment.glsl @@ -0,0 +1,52 @@ +#version 120 + +varying vec2 uv; +uniform samplerCube cubeMap; +uniform int mapping; + +#define PI 3.1415926535 + +vec3 sphericalCoords(vec2 coords) +{ + coords.x = -1 * coords.x * 2 * PI; + coords.y = (coords.y - 0.5) * PI; + + vec3 result = vec3(0.0,cos(coords.y),sin(coords.y)); + result = vec3(cos(coords.x) * result.y,sin(coords.x) * result.y,result.z); + + return result; +} + +vec3 cylindricalCoords(vec2 coords) +{ + return normalize(vec3(cos(-1 * coords.x * 2 * PI),sin(-1 * coords.x * 2 * PI),coords.y * 2.0 - 1.0)); +} + +vec3 planetCoords(vec2 coords) +{ + vec2 fromCenter = coords - vec2(0.5,0.5); + + float magnitude = length(fromCenter); + + fromCenter = normalize(fromCenter); + + float dotProduct = dot(fromCenter,vec2(0.0,1.0)); + + coords.x = coords.x > 0.5 ? 0.5 - (dotProduct + 1.0) / 4.0 : 0.5 + (dotProduct + 1.0) / 4.0; + coords.y = max(0.0,1.0 - pow(magnitude / 0.5,0.5)); + return sphericalCoords(coords); +} + +void main(void) +{ + vec3 c; + + if (mapping == 0) + c = sphericalCoords(uv); + else if (mapping == 1) + c = cylindricalCoords(uv); + else + c = planetCoords(uv); + + gl_FragData[0] = textureCube(cubeMap,c); +} diff --git a/files/shaders/s360_vertex.glsl b/files/shaders/s360_vertex.glsl new file mode 100644 index 000000000..ad96620c3 --- /dev/null +++ b/files/shaders/s360_vertex.glsl @@ -0,0 +1,9 @@ +#version 120 + +varying vec2 uv; + +void main(void) +{ + gl_Position = gl_Vertex; + uv = (gl_Vertex.xy * vec2(1.0,-1.0) + vec2(1.0)) / 2; +}