From 1fc424ce3077041ce21e1173f49835dc847f89c0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 3 Aug 2014 22:29:50 +0200 Subject: [PATCH 01/36] Make sure player doesn't get any AI packages (Bug #1749) --- apps/openmw/mwmechanics/aisequence.cpp | 3 +++ apps/openmw/mwworld/player.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 1d0065679..27f6f8a96 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -245,6 +245,9 @@ void AiSequence::clear() void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) { + if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("Can't add AI packages to player"); + if (package.getTypeId() == AiPackage::TypeIdCombat || package.getTypeId() == AiPackage::TypeIdPursue) { // Notify AiWander of our current position so we can return to it after combat finished diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 280761215..b3996f756 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -244,6 +244,8 @@ namespace MWWorld mPlayer.load (player.mObject); + getPlayer().getClass().getCreatureStats(getPlayer()).getAiSequence().clear(); + MWBase::World& world = *MWBase::Environment::get().getWorld(); try From 0b1e997bdd3258ecd89111ddaa00887369d17c81 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 4 Aug 2014 02:13:44 +0200 Subject: [PATCH 02/36] Make sure dialogue is ended properly when closing it by using a service (Fixes #1755) --- apps/openmw/mwgui/enchantingdialog.cpp | 2 +- apps/openmw/mwgui/tradewindow.cpp | 2 +- apps/openmw/mwgui/trainingwindow.cpp | 3 ++- apps/openmw/mwgui/travelwindow.cpp | 6 ++++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index e89777bee..8612617c1 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -338,7 +338,7 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager()->reportCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft, item.getClass().getValue(item)); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); return; } } diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index f94e39b2f..ccfe6b1eb 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -308,7 +308,7 @@ namespace MWGui it->mBase.getClass().getValue(it->mBase) * it->mCount); onCancelButtonClicked(mCancelButton); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); return; } } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index c93c73063..f67376c93 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -6,6 +6,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -162,7 +163,7 @@ namespace MWGui // go back to game mode MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); // advance time MWBase::Environment::get().getWorld ()->advanceTime (2); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index d874cecfe..9f3cc949d 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -9,6 +9,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -171,12 +172,13 @@ namespace MWGui MWBase::Environment::get().getWorld()->advanceTime(hours); } + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + // Teleports any followers, too. MWWorld::ActionTeleport action(interior ? cellname : "", pos); action.execute(player); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0); MWBase::Environment::get().getWindowManager()->fadeScreenIn(1); } From c6bf9dfbfb844b1e706018435b5834f5194beba7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 4 Aug 2014 16:32:53 +0200 Subject: [PATCH 03/36] Add ManualResourceLoader for character previews (Fixes #1752) --- apps/openmw/mwgui/inventorywindow.cpp | 7 +++ apps/openmw/mwrender/characterpreview.cpp | 57 +++++++++++++++++------ apps/openmw/mwrender/characterpreview.hpp | 11 ++++- libs/openengine/ogre/selectionbuffer.cpp | 30 ++++++++++-- libs/openengine/ogre/selectionbuffer.hpp | 9 +++- 5 files changed, 93 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index abea68185..baeeba585 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -91,6 +91,13 @@ namespace MWGui mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr()); mSortModel = new SortFilterItemModel(mTradeModel); mItemView->setModel(mSortModel); + + mPreview.reset(NULL); + mAvatarImage->setImageTexture(""); + MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().getTexture("CharacterPreview"); + if (tex) + MyGUI::RenderManager::getInstance().destroyTexture(tex); + mPreview.reset(new MWRender::InventoryPreview(mPtr)); mPreview->setup(); } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 5e88b2250..cc1a03a12 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -83,19 +83,10 @@ namespace MWRender mCamera->setNearClipDistance (0.01); mCamera->setFarClipDistance (1000); - mTexture = Ogre::TextureManager::getSingleton().getByName (mName); - if (mTexture.isNull ()) - mTexture = Ogre::TextureManager::getSingleton().createManual(mName, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mSizeX, mSizeY, 0, Ogre::PF_A8R8G8B8, Ogre::TU_RENDERTARGET); + mTexture = Ogre::TextureManager::getSingleton().createManual(mName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mSizeX, mSizeY, 0, Ogre::PF_A8R8G8B8, Ogre::TU_RENDERTARGET, this); - mRenderTarget = mTexture->getBuffer()->getRenderTarget(); - mRenderTarget->removeAllViewports (); - mViewport = mRenderTarget->addViewport(mCamera); - mViewport->setOverlaysEnabled(false); - mViewport->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0)); - mViewport->setShadowsEnabled(false); - mRenderTarget->setActive(true); - mRenderTarget->setAutoUpdated (false); + setupRenderTarget(); onSetup (); } @@ -107,6 +98,7 @@ namespace MWRender mSceneMgr->destroyAllCameras(); delete mAnimation; Ogre::Root::getSingleton().destroySceneManager(mSceneMgr); + Ogre::TextureManager::getSingleton().remove(mName); } } @@ -127,12 +119,39 @@ namespace MWRender onSetup(); } + void CharacterPreview::loadResource(Ogre::Resource *resource) + { + Ogre::Texture* tex = dynamic_cast(resource); + if (!tex) + return; + + tex->createInternalResources(); + + setupRenderTarget(); + + mRenderTarget->update(); + } + + void CharacterPreview::setupRenderTarget() + { + mRenderTarget = mTexture->getBuffer()->getRenderTarget(); + mRenderTarget->removeAllViewports (); + mViewport = mRenderTarget->addViewport(mCamera); + mViewport->setOverlaysEnabled(false); + mViewport->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0)); + mViewport->setShadowsEnabled(false); + mRenderTarget->setActive(true); + mRenderTarget->setAutoUpdated (false); + } + // -------------------------------------------------------------------------------------------------- InventoryPreview::InventoryPreview(MWWorld::Ptr character) : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 65, -180), Ogre::Vector3(0,65,0)) , mSelectionBuffer(NULL) + , mSizeX(0) + , mSizeY(0) { } @@ -143,6 +162,9 @@ namespace MWRender void InventoryPreview::update(int sizeX, int sizeY) { + mSizeX = sizeX; + mSizeY = sizeY; + mAnimation->updateParts(); MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter); @@ -197,15 +219,21 @@ namespace MWRender mAnimation->runAnimation(0.0f); - mViewport->setDimensions (0, 0, std::min(1.f, float(sizeX) / float(512)), std::min(1.f, float(sizeY) / float(1024))); - mNode->setOrientation (Ogre::Quaternion::IDENTITY); + mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); + mTexture->load(); mRenderTarget->update(); mSelectionBuffer->update(); } + void InventoryPreview::setupRenderTarget() + { + CharacterPreview::setupRenderTarget(); + mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); + } + int InventoryPreview::getSlotSelected (int posX, int posY) { return mSelectionBuffer->getSelected (posX, posY); @@ -243,6 +271,7 @@ namespace MWRender void RaceSelectionPreview::render() { + mTexture->load(); mRenderTarget->update(); } diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 60312455f..8990018dd 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -22,7 +22,7 @@ namespace MWRender class NpcAnimation; - class CharacterPreview + class CharacterPreview : public Ogre::ManualResourceLoader { public: CharacterPreview(MWWorld::Ptr character, int sizeX, int sizeY, const std::string& name, @@ -34,6 +34,8 @@ namespace MWRender virtual void rebuild(); + void loadResource(Ogre::Resource *resource); + private: CharacterPreview(const CharacterPreview&); CharacterPreview& operator=(const CharacterPreview&); @@ -41,6 +43,8 @@ namespace MWRender protected: virtual bool renderHeadOnly() { return false; } + virtual void setupRenderTarget(); + Ogre::TexturePtr mTexture; Ogre::RenderTarget* mRenderTarget; Ogre::Viewport* mViewport; @@ -76,7 +80,12 @@ namespace MWRender int getSlotSelected(int posX, int posY); + protected: + virtual void setupRenderTarget(); + private: + int mSizeX; + int mSizeY; OEngine::Render::SelectionBuffer* mSelectionBuffer; }; diff --git a/libs/openengine/ogre/selectionbuffer.cpp b/libs/openengine/ogre/selectionbuffer.cpp index 5aeb35c28..620b7ea8a 100644 --- a/libs/openengine/ogre/selectionbuffer.cpp +++ b/libs/openengine/ogre/selectionbuffer.cpp @@ -18,22 +18,42 @@ namespace Render { SelectionBuffer::SelectionBuffer(Ogre::Camera *camera, int sizeX, int sizeY, int visibilityFlags) + : mCamera(camera) + , mVisibilityFlags(visibilityFlags) { mTexture = Ogre::TextureManager::getSingleton().createManual("SelectionBuffer", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, sizeX, sizeY, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET); + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, sizeX, sizeY, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET, this); + setupRenderTarget(); + + mCurrentColour = Ogre::ColourValue(0.3, 0.3, 0.3); + } + + void SelectionBuffer::setupRenderTarget() + { mRenderTarget = mTexture->getBuffer()->getRenderTarget(); - Ogre::Viewport* vp = mRenderTarget->addViewport(camera); + Ogre::Viewport* vp = mRenderTarget->addViewport(mCamera); vp->setOverlaysEnabled(false); vp->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0)); vp->setShadowsEnabled(false); vp->setMaterialScheme("selectionbuffer"); - if (visibilityFlags != 0) - vp->setVisibilityMask (visibilityFlags); + if (mVisibilityFlags != 0) + vp->setVisibilityMask (mVisibilityFlags); mRenderTarget->setActive(true); mRenderTarget->setAutoUpdated (false); + } - mCurrentColour = Ogre::ColourValue(0.3, 0.3, 0.3); + void SelectionBuffer::loadResource(Ogre::Resource *resource) + { + Ogre::Texture* tex = dynamic_cast(resource); + if (!tex) + return; + + tex->createInternalResources(); + + setupRenderTarget(); + + mRenderTarget->update(); } SelectionBuffer::~SelectionBuffer() diff --git a/libs/openengine/ogre/selectionbuffer.hpp b/libs/openengine/ogre/selectionbuffer.hpp index b9b4cd9d9..9bdc4c137 100644 --- a/libs/openengine/ogre/selectionbuffer.hpp +++ b/libs/openengine/ogre/selectionbuffer.hpp @@ -20,7 +20,7 @@ namespace Render } }; - class SelectionBuffer : public Ogre::MaterialManager::Listener + class SelectionBuffer : public Ogre::MaterialManager::Listener, public Ogre::ManualResourceLoader { public: SelectionBuffer(Ogre::Camera* camera, int sizeX, int sizeY, int visibilityFlags); @@ -31,6 +31,8 @@ namespace Render void update(); + virtual void loadResource(Ogre::Resource* resource); + virtual Ogre::Technique* handleSchemeNotFound ( unsigned short schemeIndex, const Ogre::String &schemeName, Ogre::Material *originalMaterial, unsigned short lodIndex, const Ogre::Renderable *rend); @@ -46,7 +48,12 @@ namespace Render Ogre::ColourValue mCurrentColour; + Ogre::Camera* mCamera; + int mVisibilityFlags; + void getNextColour(); + + void setupRenderTarget(); }; } From 2e7b5fde7c2f01f178a2c44a0e14e452fdaca06b Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 4 Aug 2014 16:42:09 +0200 Subject: [PATCH 04/36] Fix uninitialized QuestStatus in DialInfo (Fixes #1694) --- components/esm/loadinfo.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index c9860dcef..7c98cc35b 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -10,6 +10,9 @@ namespace ESM void DialInfo::load(ESMReader &esm) { + mQuestStatus = QS_None; + mFactionLess = false; + mPrev = esm.getHNString("PNAM"); mNext = esm.getHNString("NNAM"); @@ -49,7 +52,6 @@ void DialInfo::load(ESMReader &esm) return; } - mFactionLess = false; if (subName.val == REC_FNAM) { mFaction = esm.getHString(); @@ -104,8 +106,6 @@ void DialInfo::load(ESMReader &esm) return; } - mQuestStatus = QS_None; - if (subName.val == REC_QSTN) mQuestStatus = QS_Name; else if (subName.val == REC_QSTF) From 44b517f66d7bd5ee2ae2d214476721269590fd72 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 4 Aug 2014 17:03:47 +0200 Subject: [PATCH 05/36] Implement hiding inventory windows by double click on caption (Fixes #1690) --- apps/openmw/mwgui/inventorywindow.cpp | 6 ++++++ apps/openmw/mwgui/inventorywindow.hpp | 1 + apps/openmw/mwgui/mapwindow.cpp | 6 ++++++ apps/openmw/mwgui/mapwindow.hpp | 1 + apps/openmw/mwgui/spellwindow.cpp | 6 ++++++ apps/openmw/mwgui/spellwindow.hpp | 1 + apps/openmw/mwgui/statswindow.cpp | 6 ++++++ apps/openmw/mwgui/statswindow.hpp | 1 + apps/openmw/mwgui/windowpinnablebase.cpp | 16 ++++++++++++++++ apps/openmw/mwgui/windowpinnablebase.hpp | 2 ++ files/mygui/openmw_windows.skin.xml | 1 + 11 files changed, 47 insertions(+) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index baeeba585..6772d6e04 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -373,6 +373,12 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setWeaponVisibility(!mPinned); } + void InventoryWindow::onTitleDoubleClicked() + { + if (!mPinned) + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory); + } + void InventoryWindow::useItem(const MWWorld::Ptr &ptr) { const std::string& script = ptr.getClass().getScript(ptr); diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index ae7af5719..760ca87b0 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -98,6 +98,7 @@ namespace MWGui void onFilterChanged(MyGUI::Widget* _sender); void onAvatarClicked(MyGUI::Widget* _sender); void onPinToggled(); + void onTitleDoubleClicked(); void updateEncumbranceBar(); void notifyContentChanged(); diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index af26456f2..39df4d03e 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -536,6 +536,12 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setMinimapVisibility(!mPinned); } + void MapWindow::onTitleDoubleClicked() + { + if (!mPinned) + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Map); + } + void MapWindow::open() { // force markers to foreground diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index c73e5d7a1..7021a5d62 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -150,6 +150,7 @@ namespace MWGui protected: virtual void onPinToggled(); + virtual void onTitleDoubleClicked(); virtual void notifyPlayerUpdate(); virtual void notifyMapChanged(); diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 15b5bf280..aa5fcd88a 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -66,6 +66,12 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setSpellVisibility(!mPinned); } + void SpellWindow::onTitleDoubleClicked() + { + if (!mPinned) + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Magic); + } + void SpellWindow::open() { updateSpells(); diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index 53eed1ba1..2aa4d83d2 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -42,6 +42,7 @@ namespace MWGui void onDeleteSpellAccept(); virtual void onPinToggled(); + virtual void onTitleDoubleClicked(); virtual void open(); SpellIcons* mSpellIcons; diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 7f3b66d83..2a07a1624 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -591,4 +591,10 @@ namespace MWGui { MWBase::Environment::get().getWindowManager()->setHMSVisibility(!mPinned); } + + void StatsWindow::onTitleDoubleClicked() + { + if (!mPinned) + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Stats); + } } diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index d90c16be9..1a62b6d6b 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -74,6 +74,7 @@ namespace MWGui protected: virtual void onPinToggled(); + virtual void onTitleDoubleClicked(); }; } #endif diff --git a/apps/openmw/mwgui/windowpinnablebase.cpp b/apps/openmw/mwgui/windowpinnablebase.cpp index 919d315f2..92ba5876d 100644 --- a/apps/openmw/mwgui/windowpinnablebase.cpp +++ b/apps/openmw/mwgui/windowpinnablebase.cpp @@ -11,6 +11,17 @@ namespace MWGui mPinButton = window->getSkinWidget ("Button"); mPinButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WindowPinnableBase::onPinButtonClicked); + + MyGUI::Button* button = NULL; + MyGUI::VectorWidgetPtr widgets = window->getSkinWidgetsByName("Action"); + for (MyGUI::VectorWidgetPtr::iterator it = widgets.begin(); it != widgets.end(); ++it) + { + if ((*it)->isUserString("HideWindowOnDoubleClick")) + button = (*it)->castType(); + } + + if (button) + button->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WindowPinnableBase::onDoubleClick); } void WindowPinnableBase::onPinButtonClicked(MyGUI::Widget* _sender) @@ -25,6 +36,11 @@ namespace MWGui onPinToggled(); } + void WindowPinnableBase::onDoubleClick(MyGUI::Widget *_sender) + { + onTitleDoubleClicked(); + } + void WindowPinnableBase::setPinned(bool pinned) { if (pinned != mPinned) diff --git a/apps/openmw/mwgui/windowpinnablebase.hpp b/apps/openmw/mwgui/windowpinnablebase.hpp index 3aad60988..8b7bbefaf 100644 --- a/apps/openmw/mwgui/windowpinnablebase.hpp +++ b/apps/openmw/mwgui/windowpinnablebase.hpp @@ -17,9 +17,11 @@ namespace MWGui private: void onPinButtonClicked(MyGUI::Widget* _sender); + void onDoubleClick(MyGUI::Widget* _sender); protected: virtual void onPinToggled() = 0; + virtual void onTitleDoubleClicked() = 0; MyGUI::Widget* mPinButton; bool mPinned; diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index 22586716c..a7092416f 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -829,6 +829,7 @@ window by dragging the caption. --> + From c5a9cd0aa11e7469eee7e694d1594cb49d96a2ba Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 4 Aug 2014 19:02:00 +0200 Subject: [PATCH 06/36] Sync upstream changes to shiny --- extern/shiny/Extra/core.h | 8 +++- extern/shiny/Main/Factory.cpp | 40 +++++++++++--------- extern/shiny/Main/Factory.hpp | 2 +- extern/shiny/Main/MaterialInstance.cpp | 3 +- extern/shiny/Main/Preprocessor.cpp | 3 +- extern/shiny/Main/Preprocessor.hpp | 2 - extern/shiny/Platforms/Ogre/OgrePlatform.cpp | 2 +- 7 files changed, 34 insertions(+), 26 deletions(-) diff --git a/extern/shiny/Extra/core.h b/extern/shiny/Extra/core.h index cba716777..1687d5ed1 100644 --- a/extern/shiny/Extra/core.h +++ b/extern/shiny/Extra/core.h @@ -1,13 +1,16 @@ #if SH_HLSL == 1 || SH_CG == 1 #define shTexture2D sampler2D + #define shTexture3D sampler3D #define shSample(tex, coord) tex2D(tex, coord) #define shCubicSample(tex, coord) texCUBE(tex, coord) #define shLerp(a, b, t) lerp(a, b, t) #define shSaturate(a) saturate(a) #define shSampler2D(name) , uniform sampler2D name : register(s@shCounter(0)) @shUseSampler(name) - + + #define shSampler3D(name) , uniform sampler3D name : register(s@shCounter(0)) @shUseSampler(name) + #define shSamplerCube(name) , uniform samplerCUBE name : register(s@shCounter(0)) @shUseSampler(name) #define shMatrixMult(m, v) mul(m, v) @@ -67,6 +70,7 @@ #define int3 ivec3 #define int4 ivec4 #define shTexture2D sampler2D + #define shTexture3D sampler3D #define shSample(tex, coord) texture2D(tex, coord) #define shCubicSample(tex, coord) textureCube(tex, coord) #define shLerp(a, b, t) mix(a, b, t) @@ -76,6 +80,8 @@ #define shSampler2D(name) uniform sampler2D name; @shUseSampler(name) + #define shSampler3D(name) uniform sampler3D name; @shUseSampler(name) + #define shSamplerCube(name) uniform samplerCube name; @shUseSampler(name) #define shMatrixMult(m, v) (m * v) diff --git a/extern/shiny/Main/Factory.cpp b/extern/shiny/Main/Factory.cpp index eab5c8dfc..48caa225a 100644 --- a/extern/shiny/Main/Factory.cpp +++ b/extern/shiny/Main/Factory.cpp @@ -278,14 +278,15 @@ namespace sh MaterialMap::iterator it = mMaterials.find(name); if (it != mMaterials.end()) return &(it->second); - else + else return NULL; } MaterialInstance* Factory::findInstance (const std::string& name) { - assert (mMaterials.find(name) != mMaterials.end()); - return &mMaterials.find(name)->second; + MaterialInstance* m = searchInstance(name); + assert (m); + return m; } MaterialInstance* Factory::requestMaterial (const std::string& name, const std::string& configuration, unsigned short lodIndex) @@ -297,27 +298,24 @@ namespace sh if (m) { - // make sure all lod techniques below (higher lod) exist - int i = lodIndex; - while (i>0) + if (m->createForConfiguration (configuration, 0)) { - --i; - if (m->createForConfiguration (configuration, i)) + if (mListener) + mListener->materialCreated (m, configuration, 0); + } + else + return NULL; + + for (LodConfigurationMap::iterator it = mLodConfigurations.begin(); it != mLodConfigurations.end(); ++it) + { + if (m->createForConfiguration (configuration, it->first)) { if (mListener) - mListener->materialCreated (m, configuration, i); + mListener->materialCreated (m, configuration, it->first); } else return NULL; } - - if (m->createForConfiguration (configuration, lodIndex)) - { - if (mListener) - mListener->materialCreated (m, configuration, lodIndex); - } - else - return NULL; } return m; } @@ -625,8 +623,14 @@ namespace sh { MaterialInstance* m = searchInstance (name); assert(m); + m->createForConfiguration (configuration, 0); - } + + for (LodConfigurationMap::iterator it = mLodConfigurations.begin(); it != mLodConfigurations.end(); ++it) + { + m->createForConfiguration (configuration, it->first); + } + } bool Factory::removeCache(const std::string& pattern) { diff --git a/extern/shiny/Main/Factory.hpp b/extern/shiny/Main/Factory.hpp index e8562011c..721b4af7d 100644 --- a/extern/shiny/Main/Factory.hpp +++ b/extern/shiny/Main/Factory.hpp @@ -259,8 +259,8 @@ namespace sh Platform* mPlatform; MaterialInstance* findInstance (const std::string& name); - private: MaterialInstance* searchInstance (const std::string& name); + /// @return was anything removed? bool removeCache (const std::string& pattern); diff --git a/extern/shiny/Main/MaterialInstance.cpp b/extern/shiny/Main/MaterialInstance.cpp index 128cc593b..c69d13401 100644 --- a/extern/shiny/Main/MaterialInstance.cpp +++ b/extern/shiny/Main/MaterialInstance.cpp @@ -163,7 +163,8 @@ namespace sh mTexUnits.push_back(texUnit); // set texture unit indices (required by GLSL) - if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && (mFactory->getCurrentLanguage () == Language_GLSL || mFactory->getCurrentLanguage() == Language_GLSLES)) + if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && (mFactory->getCurrentLanguage () == Language_GLSL + || mFactory->getCurrentLanguage() == Language_GLSLES)) { pass->setTextureUnitIndex (foundVertex ? GPT_Vertex : GPT_Fragment, texIt->getName(), i); diff --git a/extern/shiny/Main/Preprocessor.cpp b/extern/shiny/Main/Preprocessor.cpp index c03879d46..26481aa03 100644 --- a/extern/shiny/Main/Preprocessor.cpp +++ b/extern/shiny/Main/Preprocessor.cpp @@ -9,8 +9,7 @@ /* Almost exact copy of load_file_to_string policy found in boost::wave headers with the only change that it uses - boost::filesystem facility to handle UTF-8 paths used - throughout OpenMW (bfs::fstream, bfs::path). + boost::filesystem facility to handle UTF-8 paths properly on windows. Original namespace is used due to required bost::wave internal symbols. diff --git a/extern/shiny/Main/Preprocessor.hpp b/extern/shiny/Main/Preprocessor.hpp index 4eb533499..7ee30ae7f 100644 --- a/extern/shiny/Main/Preprocessor.hpp +++ b/extern/shiny/Main/Preprocessor.hpp @@ -1,8 +1,6 @@ #ifndef SH_PREPROCESSOR_H #define SH_PREPROCESSOR_H -#include - #include #include diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp index 9f309fbcd..eab8f93e2 100644 --- a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp +++ b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp @@ -151,7 +151,7 @@ namespace sh else if (typeid(*value) == typeid(IntValue)) type = Ogre::GCT_INT1; else - throw std::runtime_error("unexpected type"); + throw std::runtime_error("unexpected type"); params->addConstantDefinition(name, type); mSharedParameters[name] = params; } From a9a7a7ec738164ff1e158caa3a903fa522cbcd6a Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Aug 2014 02:19:32 +0200 Subject: [PATCH 07/36] Use Modified attribute values to derive magicka and fatigue (Fixes #1760) --- apps/openmw/mwmechanics/actors.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 37981acf8..eb00af7be 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -290,11 +290,11 @@ namespace MWMechanics { CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); - int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); - int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getBase(); - int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getBase(); - int agility = creatureStats.getAttribute(ESM::Attribute::Agility).getBase(); - int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase(); + int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getModified(); + int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified(); + int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getModified(); + int agility = creatureStats.getAttribute(ESM::Attribute::Agility).getModified(); + int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getModified(); double magickaFactor = creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude * 0.1 + 0.5; From 2bfcec53bf2fe38dc182841d308dbef4e82a4b8b Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Aug 2014 02:46:56 +0200 Subject: [PATCH 08/36] Don't show sMagicInvalidTarget for soultrap on NPCs (Fixes #1762) Note the message was accurate, since NPCs can't be soultrapped. Maybe vanilla doesn't show it to not spam the screen with message boxes when attacking with a "soultrap on strike" weapon... --- apps/openmw/mwmechanics/spellcasting.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 025777d09..f53aceaef 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -252,12 +252,12 @@ namespace MWMechanics } break; case ESM::MagicEffect::Soultrap: - if ((target.getClass().isActor() && target.getClass().isNpc()) - || (target.getTypeName() == typeid(ESM::Creature).name() && target.get()->mBase->mData.mSoul == 0)) + if (!target.getClass().isNpc() // no messagebox for NPCs + && (target.getTypeName() == typeid(ESM::Creature).name() && target.get()->mBase->mData.mSoul == 0)) { if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); - return false; + return true; // must still apply to get visual effect and have target regard it as attack } break; case ESM::MagicEffect::AlmsiviIntervention: From 0f8a10f4682725c1e7eff5d20e9822e82ebb2b59 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Aug 2014 02:57:11 +0200 Subject: [PATCH 09/36] Savegame: Set equipment slot for lights (Fixes #1761) --- apps/openmw/mwworld/containerstore.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 3188c1cd5..085b079d9 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -701,7 +701,8 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& state) for (std::vector >::const_iterator iter (state.mLights.begin()); iter!=state.mLights.end(); ++iter) { - getState (lights, iter->first); + int slot = iter->second; + setSlot (getState (lights, iter->first), slot); } mLevelledItemMap = state.mLevelledItemMap; From 22d7d8a46660eb17052fd0bfb1e77ee9599e0de0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 6 Aug 2014 21:16:14 +0200 Subject: [PATCH 10/36] Implement Command creature/humanoid magic effects (Fixes #1120) --- apps/openmw/mwmechanics/actors.cpp | 57 +++++++++++++++++++++++++- apps/openmw/mwmechanics/aifollow.cpp | 15 +++++-- apps/openmw/mwmechanics/aifollow.hpp | 5 ++- apps/openmw/mwmechanics/aisequence.cpp | 14 +++++++ apps/openmw/mwmechanics/aisequence.hpp | 2 + components/esm/aisequence.cpp | 3 ++ components/esm/aisequence.hpp | 1 + 7 files changed, 91 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index eb00af7be..bd1fd8794 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -89,6 +89,58 @@ bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate) return false; } +class CheckActorCommanded : public MWMechanics::EffectSourceVisitor +{ + MWWorld::Ptr mActor; +public: + bool mCommanded; + CheckActorCommanded(MWWorld::Ptr actor) + : mActor(actor) + , mCommanded(false){} + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, int casterActorId, + float magnitude, float remainingTime = -1) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if ( ((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc()) + || (key.mId == ESM::MagicEffect::CommandCreature && mActor.getTypeName() == typeid(ESM::Creature).name())) + && casterActorId == player.getClass().getCreatureStats(player).getActorId() + && magnitude >= mActor.getClass().getCreatureStats(mActor).getLevel()) + mCommanded = true; + } +}; + +void adjustCommandedActor (const MWWorld::Ptr& actor) +{ + CheckActorCommanded check(actor); + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + stats.getActiveSpells().visitEffectSources(check); + + bool hasCommandPackage = false; + + std::list::const_iterator it; + for (it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) + { + if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow && + dynamic_cast(*it)->isCommanded()) + { + hasCommandPackage = true; + break; + } + } + + if (check.mCommanded && !hasCommandPackage) + { + MWMechanics::AiFollow package("player", true); + stats.getAiSequence().stack(package, actor); + } + else if (!check.mCommanded && hasCommandPackage) + { + stats.getAiSequence().erase(it); + } +} + void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka) { MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); @@ -1005,7 +1057,10 @@ namespace MWMechanics if (MWBase::Environment::get().getMechanicsManager()->isAIActive()) { if (timerUpdateAITargets == 0) - { + { + if (iter->first != player) + adjustCommandedActor(iter->first); + for(PtrControllerMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { if (it->first == iter->first || iter->first == player) // player is not AI-controlled diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 27d944657..623306411 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -16,16 +16,16 @@ #include "steering.hpp" MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId("") +: mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId("") { } MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId) +: mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId) { } -MWMechanics::AiFollow::AiFollow(const std::string &actorId) -: mAlwaysFollow(true), mRemainingDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId("") +MWMechanics::AiFollow::AiFollow(const std::string &actorId, bool commanded) +: mAlwaysFollow(true), mCommanded(commanded), mRemainingDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId("") { } @@ -99,6 +99,11 @@ int MWMechanics::AiFollow::getTypeId() const return TypeIdFollow; } +bool MWMechanics::AiFollow::isCommanded() const +{ + return mCommanded; +} + void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const { std::auto_ptr follow(new ESM::AiSequence::AiFollow()); @@ -109,6 +114,7 @@ void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) co follow->mRemainingDuration = mRemainingDuration; follow->mCellId = mCellId; follow->mAlwaysFollow = mAlwaysFollow; + follow->mCommanded = mCommanded; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Follow; @@ -120,6 +126,7 @@ MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) : mAlwaysFollow(follow->mAlwaysFollow), mRemainingDuration(follow->mRemainingDuration) , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) , mActorId(follow->mTargetId), mCellId(follow->mCellId) + , mCommanded(follow->mCommanded) { } diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 744a9c505..c0eef8008 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -27,7 +27,7 @@ namespace MWMechanics /// Follow Actor for duration or until you arrive at a position in a cell AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); /// Follow Actor indefinitively - AiFollow(const std::string &ActorId); + AiFollow(const std::string &ActorId, bool commanded=false); AiFollow(const ESM::AiSequence::AiFollow* follow); @@ -44,10 +44,13 @@ namespace MWMechanics virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + bool isCommanded() const; + private: /// This will make the actor always follow. /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ bool mAlwaysFollow; + bool mCommanded; float mRemainingDuration; // Seconds float mX; float mY; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 27f6f8a96..f15b357e5 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -82,6 +82,20 @@ std::list::const_iterator AiSequence::end() const return mPackages.end(); } +void AiSequence::erase(std::list::const_iterator package) +{ + // Not sure if manually terminated packages should trigger mDone, probably not? + for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + if (package == it) + { + mPackages.erase(it); + return; + } + } + throw std::runtime_error("can't find package to erase"); +} + bool AiSequence::isInCombat() const { for(std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index af14cce59..c7d176661 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -54,6 +54,8 @@ namespace MWMechanics std::list::const_iterator begin() const; std::list::const_iterator end() const; + void erase (std::list::const_iterator package); + /// Returns currently executing AiPackage type /** \see enum AiPackage::TypeId **/ int getTypeId() const; diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index 80440bdd3..0e3e54102 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -58,6 +58,8 @@ namespace AiSequence esm.getHNT (mRemainingDuration, "DURA"); mCellId = esm.getHNOString ("CELL"); esm.getHNT (mAlwaysFollow, "ALWY"); + mCommanded = false; + esm.getHNOT (mCommanded, "CMND"); } void AiFollow::save(ESMWriter &esm) const @@ -68,6 +70,7 @@ namespace AiSequence if (!mCellId.empty()) esm.writeHNString ("CELL", mCellId); esm.writeHNT ("ALWY", mAlwaysFollow); + esm.writeHNT ("CMND", mCommanded); } void AiActivate::load(ESMReader &esm) diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index bf0e17fa0..da16bf867 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -96,6 +96,7 @@ namespace ESM float mRemainingDuration; bool mAlwaysFollow; + bool mCommanded; void load(ESMReader &esm); void save(ESMWriter &esm) const; From 143609be597ac7c54a7fba6be934cc9e901fb449 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 7 Aug 2014 16:27:39 +0200 Subject: [PATCH 11/36] Set glyph width/height separately from texture coordinates (Bug #1096) Requires MyGUI SVN --- apps/openmw/mwgui/fontloader.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/fontloader.cpp b/apps/openmw/mwgui/fontloader.cpp index 13f4e41f2..7a10a1e3a 100644 --- a/apps/openmw/mwgui/fontloader.cpp +++ b/apps/openmw/mwgui/fontloader.cpp @@ -257,6 +257,7 @@ namespace MWGui code->addAttribute("advance", data[i].width); code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + code->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height)); // More hacks! The french game uses several win1252 characters that are not included // in the cp437 encoding of the font. Fall back to similar available characters. @@ -302,6 +303,7 @@ namespace MWGui code->addAttribute("advance", data[i].width); code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + code->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height)); } } @@ -317,6 +319,7 @@ namespace MWGui cursorCode->addAttribute("advance", data[i].width); cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + cursorCode->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height)); } // Question mark, use for NotDefined marker (used for glyphs not existing in the font) @@ -331,6 +334,7 @@ namespace MWGui cursorCode->addAttribute("advance", data[i].width); cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + cursorCode->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height)); } } @@ -348,7 +352,7 @@ namespace MWGui cursorCode->addAttribute("coord", "0 0 0 0"); cursorCode->addAttribute("advance", "0"); cursorCode->addAttribute("bearing", "0 0"); - + cursorCode->addAttribute("size", "0 0"); } font->deserialization(root, MyGUI::Version(3,2,0)); From 8a4e0a2ce8746d1faa1c81e4c6fa460e881897ac Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 7 Aug 2014 16:37:03 +0200 Subject: [PATCH 12/36] Don't fade out screen if teleport target cell is not found --- apps/openmw/mwworld/scene.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 312fb9e36..ee107e1f2 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -403,22 +403,21 @@ namespace MWWorld void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { - Nif::NIFFile::CacheLock lock; - MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); - - Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - Loading::ScopedLoad load(loadingListener); - - mRendering.enableTerrain(false); - - std::string loadingInteriorText = "#{sLoadingMessage2}"; - loadingListener->setLabel(loadingInteriorText); - CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); bool loadcell = (mCurrentCell == NULL); if(!loadcell) loadcell = *mCurrentCell != *cell; + Nif::NIFFile::CacheLock lock; + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); + + Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + std::string loadingInteriorText = "#{sLoadingMessage2}"; + loadingListener->setLabel(loadingInteriorText); + Loading::ScopedLoad load(loadingListener); + + mRendering.enableTerrain(false); + if(!loadcell) { MWBase::World *world = MWBase::Environment::get().getWorld(); From a731ec3587bf377b2b0803465f1d7441f6c6f900 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 7 Aug 2014 17:19:06 +0200 Subject: [PATCH 13/36] Remove summoned creature and its effect when it is killed (Fixes #1341) --- apps/openmw/mwmechanics/actors.cpp | 71 +++++++++++++++++++----------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index bd1fd8794..0dd1e3140 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -159,6 +159,30 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float } } +void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId) +{ + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId); + if (!ptr.isEmpty()) + { + // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation + // plays though, which is a rather lame exploit in vanilla. + MWBase::Environment::get().getWorld()->deleteObject(ptr); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_End"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); + } + else + { + // We didn't find the creature. It's probably in an inactive cell. + // Add to graveyard so we can delete it when the cell becomes active. + std::vector& graveyard = casterStats.getSummonedCreatureGraveyard(); + graveyard.push_back(creatureActorId); + } +} + } namespace MWMechanics @@ -664,9 +688,9 @@ namespace MWMechanics summonMap[ESM::MagicEffect::SummonCreature05] = "sMagicCreature05ID"; } + std::map& creatureMap = creatureStats.getSummonedCreatureMap(); for (std::map::iterator it = summonMap.begin(); it != summonMap.end(); ++it) { - std::map& creatureMap = creatureStats.getSummonedCreatureMap(); bool found = creatureMap.find(it->first) != creatureMap.end(); int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude; if (found != (magnitude > 0)) @@ -717,34 +741,31 @@ namespace MWMechanics } else { - // Summon lifetime has expired. Try to delete the creature. - int actorId = creatureMap[it->first]; - creatureMap.erase(it->first); - - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(actorId); - if (!ptr.isEmpty()) - { - // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation - // plays though, which is a rather lame exploit in vanilla. - MWBase::Environment::get().getWorld()->deleteObject(ptr); - - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_End"); - if (fx) - MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); - } - else - { - // We didn't find the creature. It's probably in an inactive cell. - // Add to graveyard so we can delete it when the cell becomes active. - std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); - graveyard.push_back(actorId); - } + // Effect has ended + std::map::iterator foundCreature = creatureMap.find(it->first); + cleanupSummonedCreature(creatureStats, foundCreature->second); + creatureMap.erase(foundCreature); } } } + for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); + if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead()) + { + // Purge the magic effect so a new creature can be summoned if desired + creatureStats.getActiveSpells().purgeEffect(it->first); + if (ptr.getClass().hasInventoryStore(ptr)) + ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first); + + cleanupSummonedCreature(creatureStats, it->second); + creatureMap.erase(it++); + } + else + ++it; + } + std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); for (std::vector::iterator it = graveyard.begin(); it != graveyard.end(); ) { From accab4772412939006c8d9ac5602c4c8ed1fb37a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 7 Aug 2014 19:30:04 +0200 Subject: [PATCH 14/36] Don't knock down from fall damage in onHit (CharacterController is doing that already) --- apps/openmw/mwclass/creature.cpp | 21 ++++++++++++--------- apps/openmw/mwclass/npc.cpp | 4 ++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index b6cb0e62d..36937ac7e 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -368,18 +368,21 @@ namespace MWClass if (damage > 0.f) { - // Check for knockdown - float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat(); - float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() - * getGmst().iKnockDownOddsMult->getInt() * 0.01 + getGmst().iKnockDownOddsBase->getInt(); - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (ishealth && agilityTerm <= damage && knockdownTerm <= roll) + if (!attacker.isEmpty()) { - getCreatureStats(ptr).setKnockedDown(true); + // Check for knockdown + float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat(); + float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() + * getGmst().iKnockDownOddsMult->getInt() * 0.01 + getGmst().iKnockDownOddsBase->getInt(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (ishealth && agilityTerm <= damage && knockdownTerm <= roll) + { + getCreatureStats(ptr).setKnockedDown(true); + } + else + getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? } - else - getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? damage = std::max(1.f, damage); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index a4a36602d..a90b41f1e 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -672,7 +672,7 @@ namespace MWClass if (damage < 0.001f) damage = 0; - if(damage > 0.0f) + if(damage > 0.0f && !attacker.isEmpty()) { // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying // something, alert the character controller, scripts, etc. @@ -700,7 +700,7 @@ namespace MWClass else getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? - if(damage > 0 && ishealth && !attacker.isEmpty()) // Don't use armor mitigation for fall damage + if(damage > 0 && ishealth) { // Hit percentages: // cuirass = 30% From 982453d4f66e278253c5fb6665a2489e9680e387 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 7 Aug 2014 20:31:07 +0200 Subject: [PATCH 15/36] Move ESM terrain data handler to esmterrain component so it can be used by the editor (Feature #1597) --- apps/openmw/mwrender/terrainstorage.cpp | 521 ----------------------- apps/openmw/mwrender/terrainstorage.hpp | 94 +---- components/CMakeLists.txt | 4 + components/esmterrain/storage.cpp | 529 ++++++++++++++++++++++++ components/esmterrain/storage.hpp | 117 ++++++ 5 files changed, 653 insertions(+), 612 deletions(-) create mode 100644 components/esmterrain/storage.cpp create mode 100644 components/esmterrain/storage.hpp diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 2558c95c5..cbd9e2444 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -1,21 +1,11 @@ #include "terrainstorage.hpp" -#include -#include -#include -#include -#include -#include -#include - #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" -#include - namespace MWRender { @@ -59,515 +49,4 @@ namespace MWRender return esmStore.get().find(index, plugin); } - bool TerrainStorage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) - { - assert (size <= 1 && "TerrainStorage::getMinMaxHeights, chunk size should be <= 1 cell"); - - /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead - - Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); - - int cellX = origin.x; - int cellY = origin.y; - - const ESM::Land* land = getLand(cellX, cellY); - if (!land) - return false; - - min = std::numeric_limits().max(); - max = -std::numeric_limits().max(); - for (int row=0; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; - if (h > max) - max = h; - if (h < min) - min = h; - } - } - return true; - } - - void TerrainStorage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) - { - while (col >= ESM::Land::LAND_SIZE-1) - { - ++cellY; - col -= ESM::Land::LAND_SIZE-1; - } - while (row >= ESM::Land::LAND_SIZE-1) - { - ++cellX; - row -= ESM::Land::LAND_SIZE-1; - } - while (col < 0) - { - --cellY; - col += ESM::Land::LAND_SIZE-1; - } - while (row < 0) - { - --cellX; - row += ESM::Land::LAND_SIZE-1; - } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mHasData) - { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalise(); - } - else - normal = Ogre::Vector3(0,0,1); - } - - void TerrainStorage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row) - { - Ogre::Vector3 n1,n2,n3,n4; - fixNormal(n1, cellX, cellY, col+1, row); - fixNormal(n2, cellX, cellY, col-1, row); - fixNormal(n3, cellX, cellY, col, row+1); - fixNormal(n4, cellX, cellY, col, row-1); - normal = (n1+n2+n3+n4); - normal.normalise(); - } - - void TerrainStorage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row) - { - if (col == ESM::Land::LAND_SIZE-1) - { - ++cellY; - col = 0; - } - if (row == ESM::Land::LAND_SIZE-1) - { - ++cellX; - row = 0; - } - ESM::Land* land = getLand(cellX, cellY); - if (land && land->mLandData->mUsingColours) - { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; - } - else - { - color.r = 1; - color.g = 1; - color.b = 1; - } - - } - - void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, - std::vector& positions, - std::vector& normals, - std::vector& colours) - { - // LOD level n means every 2^n-th vertex is kept - size_t increment = 1 << lodLevel; - - Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); - - int startX = origin.x; - int startY = origin.y; - - size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1; - - colours.resize(numVerts*numVerts*4); - positions.resize(numVerts*numVerts*3); - normals.resize(numVerts*numVerts*3); - - Ogre::Vector3 normal; - Ogre::ColourValue color; - - float vertY; - float vertX; - - float vertY_ = 0; // of current cell corner - for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) - { - float vertX_ = 0; // of current cell corner - for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) - { - ESM::Land* land = getLand(cellX, cellY); - if (land && !land->mHasData) - land = NULL; - bool hasColors = land && land->mLandData->mUsingColours; - - int rowStart = 0; - int colStart = 0; - // Skip the first row / column unless we're at a chunk edge, - // since this row / column is already contained in a previous cell - if (colStart == 0 && vertY_ != 0) - colStart += increment; - if (rowStart == 0 && vertX_ != 0) - rowStart += increment; - - vertY = vertY_; - for (int col=colStart; colmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; - else - positions[vertX*numVerts*3 + vertY*3 + 2] = -2048; - - if (land) - { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalise(); - } - else - normal = Ogre::Vector3(0,0,1); - - // Normals apparently don't connect seamlessly between cells - if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) - fixNormal(normal, cellX, cellY, col, row); - - // some corner normals appear to be complete garbage (z < 0) - if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) - averageNormal(normal, cellX, cellY, col, row); - - assert(normal.z > 0); - - normals[vertX*numVerts*3 + vertY*3] = normal.x; - normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y; - normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z; - - if (hasColors) - { - color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; - } - else - { - color.r = 1; - color.g = 1; - color.b = 1; - } - - // Unlike normals, colors mostly connect seamlessly between cells, but not always... - if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) - fixColour(color, cellX, cellY, col, row); - - color.a = 1; - Ogre::uint32 rsColor; - Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); - memcpy(&colours[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); - - ++vertX; - } - ++vertY; - } - vertX_ = vertX; - } - vertY_ = vertY; - - assert(vertX_ == numVerts); // Ensure we covered whole area - } - assert(vertY_ == numVerts); // Ensure we covered whole area - } - - TerrainStorage::UniqueTextureId TerrainStorage::getVtexIndexAt(int cellX, int cellY, - int x, int y) - { - // For the first/last row/column, we need to get the texture from the neighbour cell - // to get consistent blending at the borders - --x; - if (x < 0) - { - --cellX; - x += ESM::Land::LAND_TEXTURE_SIZE; - } - if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? - { - ++cellY; - y -= ESM::Land::LAND_TEXTURE_SIZE; - } - - assert(xmLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; - if (tex == 0) - return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin - return std::make_pair(tex, land->mPlugin); - } - else - return std::make_pair(0,0); - } - - std::string TerrainStorage::getTextureName(UniqueTextureId id) - { - if (id.first == 0) - return "_land_default.dds"; // Not sure if the default texture floatly is hardcoded? - - // NB: All vtex ids are +1 compared to the ltex ids - const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); - - std::string texture = ltex->mTexture; - //TODO this is needed due to MWs messed up texture handling - texture = texture.substr(0, texture.rfind(".")) + ".dds"; - - return texture; - } - - void TerrainStorage::getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) - { - for (std::vector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) - { - out.push_back(Terrain::LayerCollection()); - out.back().mTarget = *it; - getBlendmapsImpl((*it)->getSize(), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers); - } - } - - void TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) - { - getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList); - } - - void TerrainStorage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) - { - // TODO - blending isn't completely right yet; the blending radius appears to be - // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap - // and interpolate the rest of the cell by hand? :/ - - Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); - int cellX = origin.x; - int cellY = origin.y; - - // Save the used texture indices so we know the total number of textures - // and number of required blend maps - std::set textureIndices; - // Due to the way the blending works, the base layer will always shine through in between - // blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible). - // To get a consistent look, we need to make sure to use the same base layer in all cells. - // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. - textureIndices.insert(std::make_pair(0,0)); - - for (int y=0; y textureIndicesMap; - for (std::set::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it) - { - int size = textureIndicesMap.size(); - textureIndicesMap[*it] = size; - layerList.push_back(getLayerInfo(getTextureName(*it))); - } - - int numTextures = textureIndices.size(); - // numTextures-1 since the base layer doesn't need blending - int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1); - - int channels = pack ? 4 : 1; - - // Second iteration - create and fill in the blend maps - const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; - - for (int i=0; isecond; - int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1); - int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; - - if (blendIndex == i) - pData[y*blendmapSize*channels + x*channels + channel] = 255; - else - pData[y*blendmapSize*channels + x*channels + channel] = 0; - } - } - blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData)); - } - } - - float TerrainStorage::getHeightAt(const Ogre::Vector3 &worldPos) - { - int cellX = std::floor(worldPos.x / 8192.f); - int cellY = std::floor(worldPos.y / 8192.f); - - ESM::Land* land = getLand(cellX, cellY); - if (!land) - return -2048; - - // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition - - // Normalized position in the cell - float nX = (worldPos.x - (cellX * 8192))/8192.f; - float nY = (worldPos.y - (cellY * 8192))/8192.f; - - // get left / bottom points (rounded down) - float factor = ESM::Land::LAND_SIZE - 1.0f; - float invFactor = 1.0f / factor; - - int startX = static_cast(nX * factor); - int startY = static_cast(nY * factor); - int endX = startX + 1; - int endY = startY + 1; - - assert(endX < ESM::Land::LAND_SIZE); - assert(endY < ESM::Land::LAND_SIZE); - - // now get points in terrain space (effectively rounding them to boundaries) - float startXTS = startX * invFactor; - float startYTS = startY * invFactor; - float endXTS = endX * invFactor; - float endYTS = endY * invFactor; - - // get parametric from start coord to next point - float xParam = (nX - startXTS) * factor; - float yParam = (nY - startYTS) * factor; - - /* For even / odd tri strip rows, triangles are this shape: - even odd - 3---2 3---2 - | / | | \ | - 0---1 0---1 - */ - - // Build all 4 positions in normalized cell space, using point-sampled height - Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); - Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); - Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); - Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); - // define this plane in terrain space - Ogre::Plane plane; - // (At the moment, all rows have the same triangle alignment) - if (true) - { - // odd row - bool secondTri = ((1.0 - yParam) > xParam); - if (secondTri) - plane.redefine(v0, v1, v3); - else - plane.redefine(v1, v2, v3); - } - else - { - // even row - bool secondTri = (yParam > xParam); - if (secondTri) - plane.redefine(v0, v2, v3); - else - plane.redefine(v0, v1, v2); - } - - // Solve plane equation for z - return (-plane.normal.x * nX - -plane.normal.y * nY - - plane.d) / plane.normal.z * 8192; - - } - - float TerrainStorage::getVertexHeight(const ESM::Land *land, int x, int y) - { - assert(x < ESM::Land::LAND_SIZE); - assert(y < ESM::Land::LAND_SIZE); - return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; - } - - Terrain::LayerInfo TerrainStorage::getLayerInfo(const std::string& texture) - { - // Already have this cached? - if (mLayerInfoMap.find(texture) != mLayerInfoMap.end()) - return mLayerInfoMap[texture]; - - Terrain::LayerInfo info; - info.mParallax = false; - info.mSpecular = false; - info.mDiffuseMap = "textures\\" + texture; - std::string texture_ = texture; - boost::replace_last(texture_, ".", "_nh."); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) - { - info.mNormalMap = "textures\\" + texture_; - info.mParallax = true; - } - else - { - texture_ = texture; - boost::replace_last(texture_, ".", "_n."); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) - info.mNormalMap = "textures\\" + texture_; - } - - texture_ = texture; - boost::replace_last(texture_, ".", "_diffusespec."); - if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) - { - info.mDiffuseMap = "textures\\" + texture_; - info.mSpecular = true; - } - - // This wasn't cached, so the textures are probably not loaded either. - // Background load them so they are hopefully already loaded once we need them! - Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General"); - if (!info.mNormalMap.empty()) - Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General"); - - mLayerInfoMap[texture] = info; - - return info; - } - - Terrain::LayerInfo TerrainStorage::getDefaultLayer() - { - Terrain::LayerInfo info; - info.mDiffuseMap = "textures\\_land_default.dds"; - info.mParallax = false; - info.mSpecular = false; - return info; - } - - float TerrainStorage::getCellWorldSize() - { - return ESM::Land::REAL_SIZE; - } - - int TerrainStorage::getCellVertices() - { - return ESM::Land::LAND_SIZE; - } - } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index c1fb74445..30a2a61ac 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -1,15 +1,13 @@ #ifndef MWRENDER_TERRAINSTORAGE_H #define MWRENDER_TERRAINSTORAGE_H -#include -#include - -#include +#include namespace MWRender { - class TerrainStorage : public Terrain::Storage + /// @brief Connects the ESM Store used in OpenMW with the ESMTerrain storage. + class TerrainStorage : public ESMTerrain::Storage { private: virtual ESM::Land* getLand (int cellX, int cellY); @@ -18,92 +16,6 @@ namespace MWRender /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); - - /// Get the minimum and maximum heights of a terrain region. - /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. - /// Larger chunks can simply merge AABB of children. - /// @param size size of the chunk in cell units - /// @param center center of the chunk in cell units - /// @param min min height will be stored here - /// @param max max height will be stored here - /// @return true if there was data available for this terrain chunk - virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); - - /// Fill vertex buffers for a terrain chunk. - /// @note May be called from background threads. Make sure to only call thread-safe functions from here! - /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. - /// @param lodLevel LOD level, 0 = most detailed - /// @param size size of the terrain chunk in cell units - /// @param center center of the chunk in cell units - /// @param positions buffer to write vertices - /// @param normals buffer to write vertex normals - /// @param colours buffer to write vertex colours - virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, - std::vector& positions, - std::vector& normals, - std::vector& colours); - - /// Create textures holding layer blend values for a terrain chunk. - /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might - /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from background threads. - /// @param chunkSize size of the terrain chunk in cell units - /// @param chunkCenter center of the chunk in cell units - /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - - /// otherwise, each texture contains blend values for one layer only. Shader-based rendering - /// can utilize packing, FFP can't. - /// @param blendmaps created blendmaps will be written here - /// @param layerList names of the layer textures used will be written here - virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, - std::vector& layerList); - - /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. - /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. - /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might - /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from background threads. - /// @param nodes A collection of nodes for which to retrieve the aforementioned data - /// @param out Output vector - /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - - /// otherwise, each texture contains blend values for one layer only. Shader-based rendering - /// can utilize packing, FFP can't. - virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack); - - virtual float getHeightAt (const Ogre::Vector3& worldPos); - - virtual Terrain::LayerInfo getDefaultLayer(); - - /// Get the transformation factor for mapping cell units to world units. - virtual float getCellWorldSize(); - - /// Get the number of vertices on one side for each cell. Should be (power of two)+1 - virtual int getCellVertices(); - - private: - void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); - void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row); - void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); - - float getVertexHeight (const ESM::Land* land, int x, int y); - - // Since plugins can define new texture palettes, we need to know the plugin index too - // in order to retrieve the correct texture name. - // pair - typedef std::pair UniqueTextureId; - - UniqueTextureId getVtexIndexAt(int cellX, int cellY, - int x, int y); - std::string getTextureName (UniqueTextureId id); - - std::map mLayerInfoMap; - - Terrain::LayerInfo getLayerInfo(const std::string& texture); - - // Non-virtual - void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, - std::vector& layerList); }; } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f343ca8ed..5fb0f3d14 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -45,6 +45,10 @@ add_component_dir (esm aisequence ) +add_component_dir (esmterrain + storage + ) + add_component_dir (misc utf8stream stringops ) diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp new file mode 100644 index 000000000..af79b27e8 --- /dev/null +++ b/components/esmterrain/storage.cpp @@ -0,0 +1,529 @@ +#include "storage.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace ESMTerrain +{ + + bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) + { + assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); + + /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead + + Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + + assert(origin.x == (int) origin.x); + assert(origin.y == (int) origin.y); + + int cellX = origin.x; + int cellY = origin.y; + + const ESM::Land* land = getLand(cellX, cellY); + if (!land) + return false; + + min = std::numeric_limits().max(); + max = -std::numeric_limits().max(); + for (int row=0; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; + if (h > max) + max = h; + if (h < min) + min = h; + } + } + return true; + } + + void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) + { + while (col >= ESM::Land::LAND_SIZE-1) + { + ++cellY; + col -= ESM::Land::LAND_SIZE-1; + } + while (row >= ESM::Land::LAND_SIZE-1) + { + ++cellX; + row -= ESM::Land::LAND_SIZE-1; + } + while (col < 0) + { + --cellY; + col += ESM::Land::LAND_SIZE-1; + } + while (row < 0) + { + --cellX; + row += ESM::Land::LAND_SIZE-1; + } + ESM::Land* land = getLand(cellX, cellY); + if (land && land->mHasData) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalise(); + } + else + normal = Ogre::Vector3(0,0,1); + } + + void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row) + { + Ogre::Vector3 n1,n2,n3,n4; + fixNormal(n1, cellX, cellY, col+1, row); + fixNormal(n2, cellX, cellY, col-1, row); + fixNormal(n3, cellX, cellY, col, row+1); + fixNormal(n4, cellX, cellY, col, row-1); + normal = (n1+n2+n3+n4); + normal.normalise(); + } + + void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row) + { + if (col == ESM::Land::LAND_SIZE-1) + { + ++cellY; + col = 0; + } + if (row == ESM::Land::LAND_SIZE-1) + { + ++cellX; + row = 0; + } + ESM::Land* land = getLand(cellX, cellY); + if (land && land->mLandData->mUsingColours) + { + color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + } + else + { + color.r = 1; + color.g = 1; + color.b = 1; + } + + } + + void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, + std::vector& positions, + std::vector& normals, + std::vector& colours) + { + // LOD level n means every 2^n-th vertex is kept + size_t increment = 1 << lodLevel; + + Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + assert(origin.x == (int) origin.x); + assert(origin.y == (int) origin.y); + + int startX = origin.x; + int startY = origin.y; + + size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1; + + colours.resize(numVerts*numVerts*4); + positions.resize(numVerts*numVerts*3); + normals.resize(numVerts*numVerts*3); + + Ogre::Vector3 normal; + Ogre::ColourValue color; + + float vertY; + float vertX; + + float vertY_ = 0; // of current cell corner + for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) + { + float vertX_ = 0; // of current cell corner + for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) + { + ESM::Land* land = getLand(cellX, cellY); + if (land && !land->mHasData) + land = NULL; + bool hasColors = land && land->mLandData->mUsingColours; + + int rowStart = 0; + int colStart = 0; + // Skip the first row / column unless we're at a chunk edge, + // since this row / column is already contained in a previous cell + if (colStart == 0 && vertY_ != 0) + colStart += increment; + if (rowStart == 0 && vertX_ != 0) + rowStart += increment; + + vertY = vertY_; + for (int col=colStart; colmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; + else + positions[vertX*numVerts*3 + vertY*3 + 2] = -2048; + + if (land) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalise(); + } + else + normal = Ogre::Vector3(0,0,1); + + // Normals apparently don't connect seamlessly between cells + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixNormal(normal, cellX, cellY, col, row); + + // some corner normals appear to be complete garbage (z < 0) + if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) + averageNormal(normal, cellX, cellY, col, row); + + assert(normal.z > 0); + + normals[vertX*numVerts*3 + vertY*3] = normal.x; + normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y; + normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z; + + if (hasColors) + { + color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + } + else + { + color.r = 1; + color.g = 1; + color.b = 1; + } + + // Unlike normals, colors mostly connect seamlessly between cells, but not always... + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixColour(color, cellX, cellY, col, row); + + color.a = 1; + Ogre::uint32 rsColor; + Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); + memcpy(&colours[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); + + ++vertX; + } + ++vertY; + } + vertX_ = vertX; + } + vertY_ = vertY; + + assert(vertX_ == numVerts); // Ensure we covered whole area + } + assert(vertY_ == numVerts); // Ensure we covered whole area + } + + Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY, + int x, int y) + { + // For the first/last row/column, we need to get the texture from the neighbour cell + // to get consistent blending at the borders + --x; + if (x < 0) + { + --cellX; + x += ESM::Land::LAND_TEXTURE_SIZE; + } + if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? + { + ++cellY; + y -= ESM::Land::LAND_TEXTURE_SIZE; + } + + assert(xmLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + if (tex == 0) + return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin + return std::make_pair(tex, land->mPlugin); + } + else + return std::make_pair(0,0); + } + + std::string Storage::getTextureName(UniqueTextureId id) + { + if (id.first == 0) + return "_land_default.dds"; // Not sure if the default texture really is hardcoded? + + // NB: All vtex ids are +1 compared to the ltex ids + const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); + + std::string texture = ltex->mTexture; + //TODO this is needed due to MWs messed up texture handling + texture = texture.substr(0, texture.rfind(".")) + ".dds"; + + return texture; + } + + void Storage::getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) + { + for (std::vector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) + { + out.push_back(Terrain::LayerCollection()); + out.back().mTarget = *it; + getBlendmapsImpl((*it)->getSize(), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers); + } + } + + void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, + bool pack, std::vector &blendmaps, std::vector &layerList) + { + getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList); + } + + void Storage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter, + bool pack, std::vector &blendmaps, std::vector &layerList) + { + // TODO - blending isn't completely right yet; the blending radius appears to be + // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap + // and interpolate the rest of the cell by hand? :/ + + Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); + int cellX = origin.x; + int cellY = origin.y; + + // Save the used texture indices so we know the total number of textures + // and number of required blend maps + std::set textureIndices; + // Due to the way the blending works, the base layer will always shine through in between + // blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible). + // To get a consistent look, we need to make sure to use the same base layer in all cells. + // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. + textureIndices.insert(std::make_pair(0,0)); + + for (int y=0; y textureIndicesMap; + for (std::set::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it) + { + int size = textureIndicesMap.size(); + textureIndicesMap[*it] = size; + layerList.push_back(getLayerInfo(getTextureName(*it))); + } + + int numTextures = textureIndices.size(); + // numTextures-1 since the base layer doesn't need blending + int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1); + + int channels = pack ? 4 : 1; + + // Second iteration - create and fill in the blend maps + const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; + + for (int i=0; isecond; + int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1); + int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; + + if (blendIndex == i) + pData[y*blendmapSize*channels + x*channels + channel] = 255; + else + pData[y*blendmapSize*channels + x*channels + channel] = 0; + } + } + blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData)); + } + } + + float Storage::getHeightAt(const Ogre::Vector3 &worldPos) + { + int cellX = std::floor(worldPos.x / 8192.f); + int cellY = std::floor(worldPos.y / 8192.f); + + ESM::Land* land = getLand(cellX, cellY); + if (!land) + return -2048; + + // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition + + // Normalized position in the cell + float nX = (worldPos.x - (cellX * 8192))/8192.f; + float nY = (worldPos.y - (cellY * 8192))/8192.f; + + // get left / bottom points (rounded down) + float factor = ESM::Land::LAND_SIZE - 1.0f; + float invFactor = 1.0f / factor; + + int startX = static_cast(nX * factor); + int startY = static_cast(nY * factor); + int endX = startX + 1; + int endY = startY + 1; + + assert(endX < ESM::Land::LAND_SIZE); + assert(endY < ESM::Land::LAND_SIZE); + + // now get points in terrain space (effectively rounding them to boundaries) + float startXTS = startX * invFactor; + float startYTS = startY * invFactor; + float endXTS = endX * invFactor; + float endYTS = endY * invFactor; + + // get parametric from start coord to next point + float xParam = (nX - startXTS) * factor; + float yParam = (nY - startYTS) * factor; + + /* For even / odd tri strip rows, triangles are this shape: + even odd + 3---2 3---2 + | / | | \ | + 0---1 0---1 + */ + + // Build all 4 positions in normalized cell space, using point-sampled height + Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); + Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); + Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); + Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); + // define this plane in terrain space + Ogre::Plane plane; + // (At the moment, all rows have the same triangle alignment) + if (true) + { + // odd row + bool secondTri = ((1.0 - yParam) > xParam); + if (secondTri) + plane.redefine(v0, v1, v3); + else + plane.redefine(v1, v2, v3); + } + else + { + // even row + bool secondTri = (yParam > xParam); + if (secondTri) + plane.redefine(v0, v2, v3); + else + plane.redefine(v0, v1, v2); + } + + // Solve plane equation for z + return (-plane.normal.x * nX + -plane.normal.y * nY + - plane.d) / plane.normal.z * 8192; + + } + + float Storage::getVertexHeight(const ESM::Land *land, int x, int y) + { + assert(x < ESM::Land::LAND_SIZE); + assert(y < ESM::Land::LAND_SIZE); + return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; + } + + Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) + { + // Already have this cached? + if (mLayerInfoMap.find(texture) != mLayerInfoMap.end()) + return mLayerInfoMap[texture]; + + Terrain::LayerInfo info; + info.mParallax = false; + info.mSpecular = false; + info.mDiffuseMap = "textures\\" + texture; + std::string texture_ = texture; + boost::replace_last(texture_, ".", "_nh."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) + { + info.mNormalMap = "textures\\" + texture_; + info.mParallax = true; + } + else + { + texture_ = texture; + boost::replace_last(texture_, ".", "_n."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) + info.mNormalMap = "textures\\" + texture_; + } + + texture_ = texture; + boost::replace_last(texture_, ".", "_diffusespec."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) + { + info.mDiffuseMap = "textures\\" + texture_; + info.mSpecular = true; + } + + // This wasn't cached, so the textures are probably not loaded either. + // Background load them so they are hopefully already loaded once we need them! + Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General"); + if (!info.mNormalMap.empty()) + Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General"); + + mLayerInfoMap[texture] = info; + + return info; + } + + Terrain::LayerInfo Storage::getDefaultLayer() + { + Terrain::LayerInfo info; + info.mDiffuseMap = "textures\\_land_default.dds"; + info.mParallax = false; + info.mSpecular = false; + return info; + } + + float Storage::getCellWorldSize() + { + return ESM::Land::REAL_SIZE; + } + + int Storage::getCellVertices() + { + return ESM::Land::LAND_SIZE; + } + +} diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp new file mode 100644 index 000000000..d25f7552b --- /dev/null +++ b/components/esmterrain/storage.hpp @@ -0,0 +1,117 @@ +#ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H +#define COMPONENTS_ESM_TERRAIN_STORAGE_H + +#include + +#include +#include + +namespace ESMTerrain +{ + + /// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture) + /// into the terrain component, converting it on the fly as needed. + class Storage : public Terrain::Storage + { + private: + + // Not implemented in this class, because we need different Store implementations for game and editor + virtual ESM::Land* getLand (int cellX, int cellY) = 0; + virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; + + public: + + // Not implemented in this class, because we need different Store implementations for game and editor + /// Get bounds of the whole terrain in cell units + virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; + + /// Get the minimum and maximum heights of a terrain region. + /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. + /// Larger chunks can simply merge AABB of children. + /// @param size size of the chunk in cell units + /// @param center center of the chunk in cell units + /// @param min min height will be stored here + /// @param max max height will be stored here + /// @return true if there was data available for this terrain chunk + virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); + + /// Fill vertex buffers for a terrain chunk. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! + /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. + /// @param lodLevel LOD level, 0 = most detailed + /// @param size size of the terrain chunk in cell units + /// @param center center of the chunk in cell units + /// @param positions buffer to write vertices + /// @param normals buffer to write vertex normals + /// @param colours buffer to write vertex colours + virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, + std::vector& positions, + std::vector& normals, + std::vector& colours); + + /// Create textures holding layer blend values for a terrain chunk. + /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from background threads. + /// @param chunkSize size of the terrain chunk in cell units + /// @param chunkCenter center of the chunk in cell units + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + /// @param blendmaps created blendmaps will be written here + /// @param layerList names of the layer textures used will be written here + virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + std::vector& blendmaps, + std::vector& layerList); + + /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. + /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. + /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from background threads. + /// @param nodes A collection of nodes for which to retrieve the aforementioned data + /// @param out Output vector + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack); + + virtual float getHeightAt (const Ogre::Vector3& worldPos); + + virtual Terrain::LayerInfo getDefaultLayer(); + + /// Get the transformation factor for mapping cell units to world units. + virtual float getCellWorldSize(); + + /// Get the number of vertices on one side for each cell. Should be (power of two)+1 + virtual int getCellVertices(); + + private: + void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row); + void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + + float getVertexHeight (const ESM::Land* land, int x, int y); + + // Since plugins can define new texture palettes, we need to know the plugin index too + // in order to retrieve the correct texture name. + // pair + typedef std::pair UniqueTextureId; + + UniqueTextureId getVtexIndexAt(int cellX, int cellY, + int x, int y); + std::string getTextureName (UniqueTextureId id); + + std::map mLayerInfoMap; + + Terrain::LayerInfo getLayerInfo(const std::string& texture); + + // Non-virtual + void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + std::vector& blendmaps, + std::vector& layerList); + }; + +} + +#endif From 8c26f802e6efbc319808277331e6b1c4d338604c Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 7 Aug 2014 20:43:33 +0200 Subject: [PATCH 16/36] Move terrain grid implementation to a component so the editor can use it (Feature #1597) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- components/CMakeLists.txt | 2 +- .../mwrender => components/terrain}/terraingrid.cpp | 13 ++++++------- .../mwrender => components/terrain}/terraingrid.hpp | 12 ++++-------- 5 files changed, 14 insertions(+), 19 deletions(-) rename {apps/openmw/mwrender => components/terrain}/terraingrid.cpp (95%) rename {apps/openmw/mwrender => components/terrain}/terraingrid.hpp (93%) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 9e4a8a0ac..6df6210ee 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -23,7 +23,7 @@ add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows characterpreview globalmap videoplayer ripplesimulation refraction - terrainstorage renderconst effectmanager weaponanimation terraingrid + terrainstorage renderconst effectmanager weaponanimation ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a1d34f12f..ed6b56f37 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -23,6 +23,7 @@ #include #include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -45,7 +46,6 @@ #include "globalmap.hpp" #include "terrainstorage.hpp" #include "effectmanager.hpp" -#include "terraingrid.hpp" using namespace MWRender; using namespace Ogre; @@ -1045,7 +1045,7 @@ void RenderingManager::enableTerrain(bool enable) mTerrain = new Terrain::DefaultWorld(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); else - mTerrain = new MWRender::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + mTerrain = new Terrain::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY); mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), Settings::Manager::getBool("split", "Shadows")); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 5fb0f3d14..2ff2dc3db 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -76,7 +76,7 @@ add_component_dir (translation add_definitions(-DTERRAIN_USE_SHADER=1) add_component_dir (terrain - quadtreenode chunk world defaultworld storage material buffercache defs + quadtreenode chunk world defaultworld terraingrid storage material buffercache defs ) add_component_dir (loadinglistener diff --git a/apps/openmw/mwrender/terraingrid.cpp b/components/terrain/terraingrid.cpp similarity index 95% rename from apps/openmw/mwrender/terraingrid.cpp rename to components/terrain/terraingrid.cpp index f2bd92061..fe322a6bf 100644 --- a/apps/openmw/mwrender/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -4,12 +4,9 @@ #include #include -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" +#include "chunk.hpp" -#include - -namespace MWRender +namespace Terrain { TerrainGrid::TerrainGrid(Ogre::SceneManager *sceneMgr, Terrain::Storage *storage, int visibilityFlags, bool shaders, Terrain::Alignment align) @@ -145,8 +142,10 @@ void TerrainGrid::setVisible(bool visible) Ogre::AxisAlignedBox TerrainGrid::getWorldBoundingBox (const Ogre::Vector2& center) { - int cellX, cellY; - MWBase::Environment::get().getWorld()->positionToIndex(center.x, center.y, cellX, cellY); + float cellSize = getStorage()->getCellWorldSize(); + + int cellX = std::floor(center.x/cellSize); + int cellY = std::floor(center.y/cellSize); Grid::iterator it = mGrid.find(std::make_pair(cellX, cellY)); if (it == mGrid.end()) diff --git a/apps/openmw/mwrender/terraingrid.hpp b/components/terrain/terraingrid.hpp similarity index 93% rename from apps/openmw/mwrender/terraingrid.hpp rename to components/terrain/terraingrid.hpp index 1b5250dcf..7cbf45576 100644 --- a/apps/openmw/mwrender/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -1,16 +1,12 @@ -#ifndef OPENMW_MWRENDER_TERRAINGRID_H -#define OPENMW_MWRENDER_TERRAINGRID_H +#ifndef COMPONENTS_TERRAIN_TERRAINGRID_H +#define COMPONENTS_TERRAIN_TERRAINGRID_H -#include -#include +#include "world.hpp" +#include "material.hpp" namespace Terrain { class Chunk; -} - -namespace MWRender -{ struct GridElement { From 4a26dcb2ad6e92658230379aedba736fe5d2e0da Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 8 Aug 2014 15:44:22 +0200 Subject: [PATCH 17/36] Avoid calling RenderTarget::update from within loadResource --- apps/openmw/mwgui/inventorywindow.cpp | 1 + apps/openmw/mwrender/characterpreview.cpp | 21 ++++++++++++++++++--- apps/openmw/mwrender/characterpreview.hpp | 5 +++++ libs/openengine/ogre/selectionbuffer.cpp | 7 +++++-- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 6772d6e04..e56719da0 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -504,6 +504,7 @@ namespace MWGui void InventoryWindow::doRenderUpdate () { + mPreview->onFrame(); if (mPreviewDirty) { mPreviewDirty = false; diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index cc1a03a12..fde5e9432 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -37,6 +37,7 @@ namespace MWRender , mViewport(NULL) , mCamera(NULL) , mNode(NULL) + , mRecover(false) { mCharacter.mCell = NULL; } @@ -46,6 +47,16 @@ namespace MWRender } + void CharacterPreview::onFrame() + { + if (mRecover) + { + setupRenderTarget(); + mRenderTarget->update(); + mRecover = false; + } + } + void CharacterPreview::setup () { mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); @@ -127,9 +138,9 @@ namespace MWRender tex->createInternalResources(); - setupRenderTarget(); - - mRenderTarget->update(); + mRenderTarget = NULL; + mViewport = NULL; + mRecover = true; } void CharacterPreview::setupRenderTarget() @@ -223,6 +234,10 @@ namespace MWRender mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); mTexture->load(); + + if (!mRenderTarget) + setupRenderTarget(); + mRenderTarget->update(); mSelectionBuffer->update(); diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 8990018dd..d8f4609b8 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -34,8 +34,13 @@ namespace MWRender virtual void rebuild(); + void onFrame(); + void loadResource(Ogre::Resource *resource); + private: + bool mRecover; // Texture content was lost and needs to be re-rendered + private: CharacterPreview(const CharacterPreview&); CharacterPreview& operator=(const CharacterPreview&); diff --git a/libs/openengine/ogre/selectionbuffer.cpp b/libs/openengine/ogre/selectionbuffer.cpp index 620b7ea8a..af95750d5 100644 --- a/libs/openengine/ogre/selectionbuffer.cpp +++ b/libs/openengine/ogre/selectionbuffer.cpp @@ -51,9 +51,9 @@ namespace Render tex->createInternalResources(); - setupRenderTarget(); + mRenderTarget = NULL; - mRenderTarget->update(); + // Don't need to re-render texture, because we have a copy in system memory (mBuffer) } SelectionBuffer::~SelectionBuffer() @@ -65,6 +65,9 @@ namespace Render { Ogre::MaterialManager::getSingleton ().addListener (this); + if (mRenderTarget == NULL) + setupRenderTarget(); + mRenderTarget->update(); Ogre::MaterialManager::getSingleton ().removeListener (this); From fc0465eeb0fdd0b7778d56c17877a1f09f2b88d9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 9 Aug 2014 15:42:54 +0200 Subject: [PATCH 18/36] Add missing header to CMakeLists --- extern/sdl4ogre/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/extern/sdl4ogre/CMakeLists.txt b/extern/sdl4ogre/CMakeLists.txt index fb0832f71..86ce7b70e 100644 --- a/extern/sdl4ogre/CMakeLists.txt +++ b/extern/sdl4ogre/CMakeLists.txt @@ -15,6 +15,7 @@ endif () set(SDL4OGRE_HEADER_FILES OISCompat.h cursormanager.hpp + events.h ) add_library(${SDL4OGRE_LIBRARY} STATIC ${SDL4OGRE_SOURCE_FILES} ${SDL4OGRE_HEADER_FILES}) From 399fa90ef1c2c3c57f782b873a2eaf2b60b60818 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 10 Aug 2014 16:56:54 +0200 Subject: [PATCH 19/36] Add missing include guard --- apps/openmw/mwgui/screenfader.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwgui/screenfader.hpp b/apps/openmw/mwgui/screenfader.hpp index 0fb1f2794..975006a85 100644 --- a/apps/openmw/mwgui/screenfader.hpp +++ b/apps/openmw/mwgui/screenfader.hpp @@ -1,3 +1,6 @@ +#ifndef OPENMW_MWGUI_SCREENFADER_H +#define OPENMW_MWGUI_SCREENFADER_H + #include "windowbase.hpp" namespace MWGui @@ -37,3 +40,5 @@ namespace MWGui }; } + +#endif From 4138c3e966e0bce92c1a788fe03c30d728fa56bb Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 10 Aug 2014 17:09:14 +0200 Subject: [PATCH 20/36] Reuse health of previous level rather than recalculating initial health (Fixes #1787) --- apps/openmw/mwmechanics/npcstats.cpp | 8 ++------ apps/openmw/mwmechanics/npcstats.hpp | 4 +--- components/esm/npcstats.cpp | 8 +++----- components/esm/npcstats.hpp | 1 - 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 579969f9d..87fb835a7 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -34,7 +34,6 @@ MWMechanics::NpcStats::NpcStats() , mProfit(0) , mTimeToStartDrowning(20.0) , mLastDrowningHit(0) -, mLevelHealthBonus(0) { mSkillIncreases.resize (ESM::Attribute::Length, 0); } @@ -262,8 +261,7 @@ void MWMechanics::NpcStats::levelUp() // "When you gain a level, in addition to increasing three primary attributes, your Health // will automatically increase by 10% of your Endurance attribute. If you increased Endurance this level, // the Health increase is calculated from the increased Endurance" - mLevelHealthBonus += endurance * gmst.find("fLevelUpHealthEndMult")->getFloat(); - updateHealth(); + setHealth(getHealth().getBase() + endurance * gmst.find("fLevelUpHealthEndMult")->getFloat()); setLevel(getLevel()+1); } @@ -273,7 +271,7 @@ void MWMechanics::NpcStats::updateHealth() const int endurance = getAttribute(ESM::Attribute::Endurance).getBase(); const int strength = getAttribute(ESM::Attribute::Strength).getBase(); - setHealth(static_cast (0.5 * (strength + endurance)) + mLevelHealthBonus); + setHealth(static_cast (0.5 * (strength + endurance))); } int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const @@ -497,7 +495,6 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const state.mTimeToStartDrowning = mTimeToStartDrowning; state.mLastDrowningHit = mLastDrowningHit; - state.mLevelHealthBonus = mLevelHealthBonus; } void MWMechanics::NpcStats::readState (const ESM::NpcStats& state) @@ -549,5 +546,4 @@ void MWMechanics::NpcStats::readState (const ESM::NpcStats& state) mTimeToStartDrowning = state.mTimeToStartDrowning; mLastDrowningHit = state.mLastDrowningHit; - mLevelHealthBonus = state.mLevelHealthBonus; } diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index a066760d0..c78900287 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -52,8 +52,6 @@ namespace MWMechanics /// time since last hit from drowning float mLastDrowningHit; - float mLevelHealthBonus; - public: NpcStats(); @@ -104,7 +102,7 @@ namespace MWMechanics void updateHealth(); ///< Calculate health based on endurance and strength. - /// Called at character creation and at level up. + /// Called at character creation. void flagAsUsed (const std::string& id); diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index 26d8f9596..3e6aed99d 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -78,8 +78,9 @@ void ESM::NpcStats::load (ESMReader &esm) mLastDrowningHit = 0; esm.getHNOT (mLastDrowningHit, "DRLH"); - mLevelHealthBonus = 0; - esm.getHNOT (mLevelHealthBonus, "LVLH"); + // No longer used + float levelHealthBonus = 0; + esm.getHNOT (levelHealthBonus, "LVLH"); mCrimeId = -1; esm.getHNOT (mCrimeId, "CRID"); @@ -148,9 +149,6 @@ void ESM::NpcStats::save (ESMWriter &esm) const if (mLastDrowningHit) esm.writeHNT ("DRLH", mLastDrowningHit); - if (mLevelHealthBonus) - esm.writeHNT ("LVLH", mLevelHealthBonus); - if (mCrimeId != -1) esm.writeHNT ("CRID", mCrimeId); } diff --git a/components/esm/npcstats.hpp b/components/esm/npcstats.hpp index ce7c75d2a..0061fc05f 100644 --- a/components/esm/npcstats.hpp +++ b/components/esm/npcstats.hpp @@ -46,7 +46,6 @@ namespace ESM std::vector mUsedIds; float mTimeToStartDrowning; float mLastDrowningHit; - float mLevelHealthBonus; int mCrimeId; void load (ESMReader &esm); From fcd2a9e4d5cc6f2e61b3ef7d156b980df039effc Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 10 Aug 2014 23:52:32 +0200 Subject: [PATCH 21/36] Don't set magic effects for dead actors (Fixes #1783) --- apps/openmw/mwmechanics/actors.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 0dd1e3140..0e324cba5 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -344,7 +344,8 @@ namespace MWMechanics void Actors::adjustMagicEffects (const MWWorld::Ptr& creature) { CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature); - + if (creatureStats.isDead()) + return; MagicEffects now = creatureStats.getSpells().getMagicEffects(); if (creature.getTypeName()==typeid (ESM::NPC).name()) From f724732290cd7a512411ed5043b98dd43927a340 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Aug 2014 00:51:54 +0200 Subject: [PATCH 22/36] Implement mouse wheel journal navigation (Fixes #1790) --- apps/openmw/mwgui/journalwindow.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index b588ba3e1..3e2006e57 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -123,6 +123,9 @@ namespace getPage (LeftBookPage)->adviseLinkClicked (callback); getPage (RightBookPage)->adviseLinkClicked (callback); + + getPage (LeftBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); + getPage (RightBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); } { @@ -488,6 +491,14 @@ namespace MWBase::Environment::get().getWindowManager ()->popGuiMode (); } + void notifyMouseWheel(MyGUI::Widget* sender, int rel) + { + if (rel < 0) + notifyNextPage(sender); + else + notifyPrevPage(sender); + } + void notifyNextPage(MyGUI::Widget* _sender) { if (!mStates.empty ()) @@ -509,7 +520,7 @@ namespace { unsigned int & page = mStates.top ().mPage; - if(page > 0) + if(page >= 2) { page -= 2; updateShowingPages (); From 36ba56d037f20ac49b902f1e071c5185b99f114f Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Aug 2014 02:01:20 +0200 Subject: [PATCH 23/36] Make Position instruction move non-player actors within their cell only (Fixes #1791) --- apps/openmw/mwscript/transformationextensions.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 528a6b9b9..3355e705f 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -363,8 +363,19 @@ namespace MWScript runtime.pop(); int cx,cy; MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); - MWBase::Environment::get().getWorld()->moveObject(ptr, - MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z); + + // another morrowind oddity: player will be moved to the exterior cell at this location, + // non-player actors will move within the cell they are in. + if (ptr.getRefData().getHandle() == "player") + { + MWBase::Environment::get().getWorld()->moveObject(ptr, + MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z); + } + else + { + MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z); + } + float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity From 98bc4618cbcd0416ef9bfaf6e9525d7bb1c0ab2b Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Aug 2014 04:21:04 +0200 Subject: [PATCH 24/36] AiAvoidDoor: turn and walk forward instead of backwards or sideways (Fixes #1788) --- apps/openmw/mwmechanics/aiavoiddoor.cpp | 15 +++++++++------ apps/openmw/mwmechanics/aiescort.cpp | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index bab8bca28..eea1b4850 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -51,14 +51,17 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor,float duration ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door float x = pos.pos[0] - tPos.pos[0]; float y = pos.pos[1] - tPos.pos[1]; - float dirToDoor = std::atan2(x,y) + pos.rot[2] + mAdjAngle; //Calculates the direction to the door, relative to the direction of the NPC - // For example, if the NPC is directly facing the door this will be pi/2 - // Make actor move away from the door - actor.getClass().getMovementSettings(actor).mPosition[1] = -1 * std::sin(dirToDoor); //I knew I'd use trig someday - actor.getClass().getMovementSettings(actor).mPosition[0] = -1 * std::cos(dirToDoor); + actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); - //Make all nearby actors also avoid the door + // Turn away from the door and move when turn completed + if (zTurn(actor, Ogre::Radian(std::atan2(x,y) + mAdjAngle), Ogre::Degree(5))) + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + else + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + actor.getClass().getMovementSettings(actor).mPosition[0] = 0; + + // Make all nearby actors also avoid the door std::vector actors; MWBase::Environment::get().getMechanicsManager()->getActorsInRange(Ogre::Vector3(pos.pos[0],pos.pos[1],pos.pos[2]),100,actors); for(std::vector::iterator it = actors.begin(); it != actors.end(); it++) { diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index cc8dead8a..a0f8bffbd 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -75,6 +75,7 @@ namespace MWMechanics } actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); const float* const leaderPos = actor.getRefData().getPosition().pos; From f8040da6941221af6ec8ac26795571a0f0487a1e Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Aug 2014 04:43:06 +0200 Subject: [PATCH 25/36] Make doors move through dead actors (Fixes #1608) --- apps/openmw/mwworld/physicssystem.cpp | 4 ++-- apps/openmw/mwworld/physicssystem.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 ++-- libs/openengine/bullet/physic.cpp | 4 +++- libs/openengine/bullet/physic.hpp | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 02a198f3d..3c0e67729 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -612,9 +612,9 @@ namespace MWWorld } } - std::vector PhysicsSystem::getCollisions(const Ptr &ptr) + std::vector PhysicsSystem::getCollisions(const Ptr &ptr, int collisionGroup, int collisionMask) { - return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName()); + return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName(), collisionGroup, collisionMask); } Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight) diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index ac60a80b5..f0522426d 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -55,7 +55,7 @@ namespace MWWorld void stepSimulation(float dt); - std::vector getCollisions(const MWWorld::Ptr &ptr); ///< get handles this object collides with + std::vector getCollisions(const MWWorld::Ptr &ptr, int collisionGroup, int collisionMask); ///< get handles this object collides with Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, float maxHeight); std::pair getFacedHandle(float queryDistance); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d57528864..a84e4781d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1284,7 +1284,8 @@ namespace MWWorld bool reached = (targetRot == 90.f && it->second) || targetRot == 0.f; /// \todo should use convexSweepTest here - std::vector collisions = mPhysics->getCollisions(it->first); + std::vector collisions = mPhysics->getCollisions(it->first, OEngine::Physic::CollisionType_Actor + , OEngine::Physic::CollisionType_Actor); for (std::vector::iterator cit = collisions.begin(); cit != collisions.end(); ++cit) { MWWorld::Ptr ptr = getPtrViaHandle(*cit); @@ -1300,7 +1301,6 @@ namespace MWWorld // we need to undo the rotation localRotateObject(it->first, 0, 0, oldRot); reached = false; - //break; //Removed in case multiple actors are touching } } diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index c95552cb7..50d82cd37 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -661,12 +661,14 @@ namespace Physic }; - std::vector PhysicEngine::getCollisions(const std::string& name) + std::vector PhysicEngine::getCollisions(const std::string& name, int collisionGroup, int collisionMask) { RigidBody* body = getRigidBody(name); if (!body) // fall back to raycasting body if there is no collision body body = getRigidBody(name, true); ContactTestResultCallback callback; + callback.m_collisionFilterGroup = collisionGroup; + callback.m_collisionFilterMask = collisionMask; mDynamicsWorld->contactTest(body, callback); return callback.mResult; } diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 590b56c01..d846d60b1 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -311,7 +311,7 @@ namespace Physic std::pair sphereCast (float radius, btVector3& from, btVector3& to); ///< @return (hit, relative distance) - std::vector getCollisions(const std::string& name); + std::vector getCollisions(const std::string& name, int collisionGroup, int collisionMask); // Get the nearest object that's inside the given object, filtering out objects of the // provided name From 34847baa542e84ab930324ac767a6bf158ec66fe Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Aug 2014 05:00:13 +0200 Subject: [PATCH 26/36] Disable head animations for dead actors (Fixes #1781) --- apps/openmw/mwmechanics/character.cpp | 2 ++ apps/openmw/mwrender/animation.hpp | 1 + apps/openmw/mwrender/npcanimation.cpp | 15 ++++++++++++++- apps/openmw/mwrender/npcanimation.hpp | 6 ++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 9562b7cc9..9bfe771bc 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1511,6 +1511,8 @@ void CharacterController::update(float duration) else if (mAnimation) mAnimation->updateEffects(duration); mSkipAnim = false; + + mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead()); } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index e15bd6ecb..a282e34ed 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -306,6 +306,7 @@ public: virtual void attachArrow() {} virtual void releaseArrow() {} void enableLights(bool enable); + virtual void enableHeadAnimation(bool enable) {} Ogre::AxisAlignedBox getWorldBounds(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 0a9b56b33..23b761646 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -67,11 +67,16 @@ namespace MWRender { HeadAnimationTime::HeadAnimationTime(MWWorld::Ptr reference) - : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mValue(0) + : mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mValue(0), mEnabled(true) { resetBlinkTimer(); } +void HeadAnimationTime::setEnabled(bool enabled) +{ + mEnabled = enabled; +} + void HeadAnimationTime::resetBlinkTimer() { mBlinkTimer = -(2 + (std::rand() / double(RAND_MAX*1.0)) * 6); @@ -79,6 +84,9 @@ void HeadAnimationTime::resetBlinkTimer() void HeadAnimationTime::update(float dt) { + if (!mEnabled) + return; + if (MWBase::Environment::get().getSoundManager()->sayDone(mReference)) { mBlinkTimer += dt; @@ -864,6 +872,11 @@ void NpcAnimation::setAlpha(float alpha) } } +void NpcAnimation::enableHeadAnimation(bool enable) +{ + mHeadAnimationTime->setEnabled(enable); +} + void NpcAnimation::preRender(Ogre::Camera *camera) { Animation::preRender(camera); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 95cd35ddb..979210591 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -26,6 +26,8 @@ private: float mBlinkTimer; + bool mEnabled; + float mValue; private: void resetBlinkTimer(); @@ -34,6 +36,8 @@ public: void update(float dt); + void setEnabled(bool enabled); + void setTalkStart(float value); void setTalkStop(float value); void setBlinkStart(float value); @@ -125,6 +129,8 @@ public: ViewMode viewMode=VM_Normal); virtual ~NpcAnimation(); + virtual void enableHeadAnimation(bool enable); + virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } virtual Ogre::Vector3 runAnimation(float timepassed); From fc1687906bbed17c7709081ec2405ad025afea84 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Aug 2014 05:29:36 +0200 Subject: [PATCH 27/36] Use the last Loop Stop key for calculating animation velocity (Fixes #1776) --- apps/openmw/mwrender/animation.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 872740d74..e2054c6d1 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -467,7 +467,13 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node const std::string stop = groupname+": stop"; float starttime = std::numeric_limits::max(); float stoptime = 0.0f; - // Have to find keys in reverse (see reset method) + + // Pick the last Loop Stop key and the last Loop Start key. + // This is required because of broken text keys in AshVampire.nif. + // It has *two* WalkForward: Loop Stop keys at different times, the first one is used for stopping playback + // but the animation velocity calculation uses the second one. + // As result the animation velocity calculation is not correct, and this incorrect velocity must be replicated, + // because otherwise the Creature's Speed (dagoth uthol) would not be sufficient to move fast enough. NifOgre::TextKeyMap::const_reverse_iterator keyiter(keys.rbegin()); while(keyiter != keys.rend()) { @@ -476,8 +482,18 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node starttime = keyiter->first; break; } - else if(keyiter->second == loopstop || keyiter->second == stop) + ++keyiter; + } + keyiter = keys.rbegin(); + while(keyiter != keys.rend()) + { + if (keyiter->second == stop) stoptime = keyiter->first; + else if (keyiter->second == loopstop) + { + stoptime = keyiter->first; + break; + } ++keyiter; } From 8866b5f860f165e8b17397e652dc071df7516364 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Aug 2014 19:07:14 +0200 Subject: [PATCH 28/36] Fix maximum magicka calculation (Fixes #1795) --- apps/openmw/mwmechanics/actors.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 0e324cba5..92879fe37 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -374,10 +374,10 @@ namespace MWMechanics int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getModified(); double magickaFactor = - creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude * 0.1 + 0.5; + creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude * 0.1 + 1; DynamicStat magicka = creatureStats.getMagicka(); - float diff = (static_cast(intelligence + magickaFactor*intelligence)) - magicka.getBase(); + float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); magicka.modify(diff); creatureStats.setMagicka(magicka); From 28fe81df90611916c1f9b4c8e4dcb9cc23696515 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Aug 2014 20:04:56 +0200 Subject: [PATCH 29/36] Make Lights with OffDefault flag not emit light nor particles when placed in a cell (Fixes #1796) --- apps/openmw/mwclass/light.cpp | 5 ++++- apps/openmw/mwrender/animation.cpp | 9 +++++++++ apps/openmw/mwrender/animation.hpp | 1 + apps/openmw/mwrender/objects.cpp | 9 +++++++-- apps/openmw/mwrender/objects.hpp | 2 +- components/esm/loadligh.hpp | 2 +- 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 464c78e9f..8c2decb72 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -56,8 +56,11 @@ namespace MWClass { const std::string model = getModel(ptr); + MWWorld::LiveCellRef *ref = + ptr.get(); + // Insert even if model is empty, so that the light is added - renderingInterface.getObjects().insertModel(ptr, model); + renderingInterface.getObjects().insertModel(ptr, model, false, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } void Light::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e2054c6d1..1fa9c611d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1431,6 +1431,15 @@ void ObjectAnimation::addLight(const ESM::Light *light) addExtraLight(mInsert->getCreator(), mObjectRoot, light); } +void ObjectAnimation::removeParticles() +{ + for (unsigned int i=0; imParticles.size(); ++i) + { + mObjectRoot->mSceneMgr->destroyParticleSystem(mObjectRoot->mParticles[i]); + } + mObjectRoot->mParticles.clear(); +} + class FindEntityTransparency { public: diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index a282e34ed..dc4244c39 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -324,6 +324,7 @@ public: ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model); void addLight(const ESM::Light *light); + void removeParticles(); bool canBatch() const; void fillBatch(Ogre::StaticGeometry *sg); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index d9e20e1f8..96decbb36 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -73,14 +73,19 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) ptr.getRefData().setBaseNode(insert); } -void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch) +void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch, bool addLight) { insertBegin(ptr); std::auto_ptr anim(new ObjectAnimation(ptr, mesh)); if(ptr.getTypeName() == typeid(ESM::Light).name()) - anim->addLight(ptr.get()->mBase); + { + if (addLight) + anim->addLight(ptr.get()->mBase); + else + anim->removeParticles(); + } if (!mesh.empty()) { diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 02e974e2d..7f740dbab 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -41,7 +41,7 @@ public: , mRootNode(NULL) {} ~Objects(){} - void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false); + void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false, bool addLight=false); ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr); diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index 74eb37197..ffc291609 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -25,7 +25,7 @@ struct Light Negative = 0x004, // Negative light - i.e. darkness Flicker = 0x008, Fire = 0x010, - OffDefault = 0x020, // Off by default + OffDefault = 0x020, // Off by default - does not burn while placed in a cell, but can burn when equipped by an NPC FlickerSlow = 0x040, Pulse = 0x080, PulseSlow = 0x100 From 5335f00958ab62046af04daeb65aca56c3ba98c9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Aug 2014 20:07:49 +0200 Subject: [PATCH 30/36] Don't show Until Healed button when only fatigue is not full (Fixes #1798) --- apps/openmw/mwgui/waitdialog.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 1f97e13f1..434c6926e 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -177,8 +177,7 @@ namespace MWGui { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - bool full = (stats.getFatigue().getCurrent() >= stats.getFatigue().getModified()) - && (stats.getHealth().getCurrent() >= stats.getHealth().getModified()) + bool full = (stats.getHealth().getCurrent() >= stats.getHealth().getModified()) && (stats.getMagicka().getCurrent() >= stats.getMagicka().getModified()); MWMechanics::NpcStats& npcstats = player.getClass().getNpcStats(player); bool werewolf = npcstats.isWerewolf(); From cf22d2fa364e7196f86136163652168c20e59738 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Aug 2014 20:13:46 +0200 Subject: [PATCH 31/36] Make Potions reveal two effects for every fWortChanceValue of alchemy skill (Fixes #1792) --- apps/openmw/mwclass/potion.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index e5d0d279e..2da213c8d 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -145,11 +145,8 @@ namespace MWClass MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); for (MWGui::Widgets::SpellEffectList::iterator it = info.effects.begin(); it != info.effects.end(); ++it) { - it->mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue) - || (i == 1 && alchemySkill >= fWortChanceValue*2) - || (i == 2 && alchemySkill >= fWortChanceValue*3) - || (i == 3 && alchemySkill >= fWortChanceValue*4)); - + it->mKnown = (i <= 1 && alchemySkill >= fWortChanceValue) + || (i <= 3 && alchemySkill >= fWortChanceValue*2); ++i; } From 63cb91db2ead4103b6d9c76d2b3eb2ac30df2f77 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Aug 2014 20:37:29 +0200 Subject: [PATCH 32/36] Add --export-fonts command line option --- apps/openmw/engine.cpp | 11 ++++++++--- apps/openmw/engine.hpp | 4 ++++ apps/openmw/main.cpp | 4 ++++ apps/openmw/mwgui/fontloader.cpp | 23 ++++++++++++++++------- apps/openmw/mwgui/fontloader.hpp | 7 +++++-- apps/openmw/mwgui/windowmanagerimp.cpp | 4 ++-- apps/openmw/mwgui/windowmanagerimp.hpp | 2 +- 7 files changed, 40 insertions(+), 15 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 66eae5f5d..2e5689fa8 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -181,7 +181,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mActivationDistanceOverride(-1) , mGrab(true) , mScriptBlacklistUse (true) - + , mExportFonts(false) { std::srand ( std::time(NULL) ); MWClass::registerClasses(); @@ -372,7 +372,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) MWGui::WindowManager* window = new MWGui::WindowManager( mExtensions, mFpsLevel, mOgre, mCfgMgr.getLogPath().string() + std::string("/"), - mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding); + mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts); mEnvironment.setWindowManager (window); // Create sound system @@ -576,4 +576,9 @@ void OMW::Engine::setScriptBlacklist (const std::vector& list) void OMW::Engine::setScriptBlacklistUse (bool use) { mScriptBlacklistUse = use; -} \ No newline at end of file +} + +void OMW::Engine::enableFontExport(bool exportFonts) +{ + mExportFonts = exportFonts; +} diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 203379a93..10c1f6ff2 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -83,6 +83,8 @@ namespace OMW // Grab mouse? bool mGrab; + bool mExportFonts; + Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; @@ -187,6 +189,8 @@ namespace OMW void setScriptBlacklistUse (bool use); + void enableFontExport(bool exportFonts); + private: Files::ConfigurationManager& mCfgMgr; }; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 6c699bed1..57b3e045e 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -168,6 +168,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("no-grab", "Don't grab mouse cursor") + ("export-fonts", bpo::value()->implicit_value(true) + ->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory") + ("activate-dist", bpo::value ()->default_value (-1), "activation distance override"); bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) @@ -268,6 +271,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setSoundUsage(!variables["no-sound"].as()); engine.setFallbackValues(variables["fallback"].as().mMap); engine.setActivationDistanceOverride (variables["activate-dist"].as()); + engine.enableFontExport(variables["export-fonts"].as()); return true; } diff --git a/apps/openmw/mwgui/fontloader.cpp b/apps/openmw/mwgui/fontloader.cpp index 7a10a1e3a..b1cb7fccf 100644 --- a/apps/openmw/mwgui/fontloader.cpp +++ b/apps/openmw/mwgui/fontloader.cpp @@ -134,7 +134,7 @@ namespace MWGui mEncoding = encoding; } - void FontLoader::loadAllFonts() + void FontLoader::loadAllFonts(bool exportToFile) { Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) @@ -142,7 +142,7 @@ namespace MWGui Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "*.fnt"); for (Ogre::StringVector::iterator resource = resourcesInThisGroup->begin(); resource != resourcesInThisGroup->end(); ++resource) { - loadFont(*resource); + loadFont(*resource, exportToFile); } } } @@ -168,7 +168,7 @@ namespace MWGui float ascent; } GlyphInfo; - void FontLoader::loadFont(const std::string &fileName) + void FontLoader::loadFont(const std::string &fileName, bool exportToFile) { Ogre::DataStreamPtr file = Ogre::ResourceGroupManager::getSingleton().openResource(fileName); @@ -221,6 +221,9 @@ namespace MWGui width, height, 0, Ogre::PF_BYTE_RGBA); texture->loadImage(image); + if (exportToFile) + image.save(resourceName + ".png"); + // Register the font with MyGUI MyGUI::ResourceManualFont* font = static_cast( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont")); @@ -240,10 +243,10 @@ namespace MWGui for(int i = 0; i < 256; i++) { - int x1 = data[i].top_left.x*width; - int y1 = data[i].top_left.y*height; - int w = data[i].top_right.x*width - x1; - int h = data[i].bottom_left.y*height - y1; + float x1 = data[i].top_left.x*width; + float y1 = data[i].top_left.y*height; + float w = data[i].top_right.x*width - x1; + float h = data[i].bottom_left.y*height - y1; ToUTF8::Utf8Encoder encoder(mEncoding); unsigned long unicodeVal = utf8ToUnicode(getUtf8(i, encoder, mEncoding)); @@ -355,6 +358,12 @@ namespace MWGui cursorCode->addAttribute("size", "0 0"); } + if (exportToFile) + { + xmlDocument.createDeclaration(); + xmlDocument.save(resourceName + ".xml"); + } + font->deserialization(root, MyGUI::Version(3,2,0)); MyGUI::ResourceManager::getInstance().removeByName(font->getResourceName()); diff --git a/apps/openmw/mwgui/fontloader.hpp b/apps/openmw/mwgui/fontloader.hpp index 7954b0875..619717840 100644 --- a/apps/openmw/mwgui/fontloader.hpp +++ b/apps/openmw/mwgui/fontloader.hpp @@ -12,12 +12,15 @@ namespace MWGui { public: FontLoader (ToUTF8::FromType encoding); - void loadAllFonts (); + + /// @param exportToFile export the converted fonts (Images and XML with glyph metrics) to files? + void loadAllFonts (bool exportToFile); private: ToUTF8::FromType mEncoding; - void loadFont (const std::string& fileName); + /// @param exportToFile export the converted font (Image and XML with glyph metrics) to files? + void loadFont (const std::string& fileName, bool exportToFile); }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index da6065bac..2cd044cf4 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -73,7 +73,7 @@ namespace MWGui WindowManager::WindowManager( const Compiler::Extensions& extensions, int fpsLevel, OEngine::Render::OgreRenderer *ogre, const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, - Translation::Storage& translationDataStorage, ToUTF8::FromType encoding) + Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts) : mConsoleOnlyScripts(consoleOnlyScripts) , mGuiManager(NULL) , mRendering(ogre) @@ -148,7 +148,7 @@ namespace MWGui // Load fonts FontLoader fontLoader (encoding); - fontLoader.loadAllFonts(); + fontLoader.loadAllFonts(exportFonts); //Register own widgets with MyGUI MyGUI::FactoryManager::getInstance().registerFactory("Widget"); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 565e442f9..598ecd1ee 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -97,7 +97,7 @@ namespace MWGui WindowManager(const Compiler::Extensions& extensions, int fpsLevel, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, - Translation::Storage& translationDataStorage, ToUTF8::FromType encoding); + Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts); virtual ~WindowManager(); void initUI(); From 177de588846cd1f96cb2700e3bd80e0b56b58c32 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Aug 2014 01:10:18 +0200 Subject: [PATCH 33/36] Use fWereWolfRunMult only when no weapon is drawn --- apps/openmw/mwclass/npc.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index a90b41f1e..662672204 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -918,8 +918,6 @@ namespace MWClass float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() * gmst.fAthleticsRunBonus->getFloat() + gmst.fBaseRunMultiplier->getFloat()); - if(npcdata->mNpcStats.isWerewolf()) - runSpeed *= gmst.fWereWolfRunMult->getFloat(); float moveSpeed; if(normalizedEncumbrance >= 1.0f) @@ -951,6 +949,9 @@ namespace MWClass if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0) moveSpeed *= 0.75f; + if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing) + moveSpeed *= gmst.fWereWolfRunMult->getFloat(); + return moveSpeed; } From 025f50a2df8b5d183a575b430934a1087a7caf02 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Aug 2014 16:36:13 +0200 Subject: [PATCH 34/36] Improve performance of inventory window resize (don't update selection buffer unnecessarily) --- apps/openmw/mwgui/inventorywindow.cpp | 30 +++++++++++++++++------ apps/openmw/mwgui/inventorywindow.hpp | 1 + apps/openmw/mwrender/characterpreview.cpp | 13 +++++++++- apps/openmw/mwrender/characterpreview.hpp | 3 ++- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index e56719da0..e2048a34b 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -37,6 +37,7 @@ namespace MWGui , mLastYSize(0) , mPreview(new MWRender::InventoryPreview(MWBase::Environment::get().getWorld ()->getPlayerPtr())) , mPreviewDirty(true) + , mPreviewResize(true) , mDragAndDrop(dragAndDrop) , mSelectedItem(-1) , mGuiMode(GM_Inventory) @@ -100,6 +101,9 @@ namespace MWGui mPreview.reset(new MWRender::InventoryPreview(mPtr)); mPreview->setup(); + + mPreviewDirty = true; + mPreviewResize = true; } void InventoryWindow::setGuiMode(GuiMode mode) @@ -132,7 +136,7 @@ namespace MWGui Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height); if (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight()) - mPreviewDirty = true; + mPreviewResize = true; mMainWidget->setPosition(pos); mMainWidget->setSize(size); @@ -340,7 +344,7 @@ namespace MWGui { mLastXSize = mMainWidget->getSize().width; mLastYSize = mMainWidget->getSize().height; - mPreviewDirty = true; + mPreviewResize = true; } } @@ -416,9 +420,13 @@ namespace MWGui else mSkippedToEquip = ptr; - mItemView->update(); + if (isVisible()) + { + mItemView->update(); - notifyContentChanged(); + notifyContentChanged(); + } + // else: will be updated in open() } void InventoryWindow::onAvatarClicked(MyGUI::Widget* _sender) @@ -505,16 +513,22 @@ namespace MWGui void InventoryWindow::doRenderUpdate () { mPreview->onFrame(); - if (mPreviewDirty) + if (mPreviewResize) { - mPreviewDirty = false; + mPreviewResize = false; MyGUI::IntSize size = mAvatarImage->getSize(); - - mPreview->update (size.width, size.height); + mPreview->resize(size.width, size.height); mAvatarImage->setImageTexture("CharacterPreview"); mAvatarImage->setImageCoord(MyGUI::IntCoord(0, 0, std::min(512, size.width), std::min(1024, size.height))); mAvatarImage->setImageTile(MyGUI::IntSize(std::min(512, size.width), std::min(1024, size.height))); + } + if (mPreviewDirty) + { + mPreviewDirty = false; + mPreview->update (); + + mAvatarImage->setImageTexture("CharacterPreview"); mArmorRating->setCaptionWithReplacing ("#{sArmor}: " + boost::lexical_cast(static_cast(mPtr.getClass().getArmorRating(mPtr)))); diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 760ca87b0..6fd6ece4a 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -53,6 +53,7 @@ namespace MWGui DragAndDrop* mDragAndDrop; bool mPreviewDirty; + bool mPreviewResize; int mSelectedItem; MWWorld::Ptr mPtr; diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index fde5e9432..fd07f1a19 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -171,11 +171,22 @@ namespace MWRender delete mSelectionBuffer; } - void InventoryPreview::update(int sizeX, int sizeY) + void InventoryPreview::resize(int sizeX, int sizeY) { mSizeX = sizeX; mSizeY = sizeY; + mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024))); + mTexture->load(); + + if (!mRenderTarget) + setupRenderTarget(); + + mRenderTarget->update(); + } + + void InventoryPreview::update() + { mAnimation->updateParts(); MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter); diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index d8f4609b8..711de0d15 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -81,7 +81,8 @@ namespace MWRender virtual ~InventoryPreview(); virtual void onSetup(); - void update(int sizeX, int sizeY); + void update(); // Render preview again, e.g. after changed equipment + void resize(int sizeX, int sizeY); int getSlotSelected(int posX, int posY); From b374255447a077c79afaf9d1f51e84efcde10573 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Aug 2014 17:07:15 +0200 Subject: [PATCH 35/36] Improve performance of moving spell window (don't recreate widgets unnecessarily) --- apps/openmw/mwgui/spellwindow.cpp | 7 ++++++- apps/openmw/mwgui/spellwindow.hpp | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index aa5fcd88a..b558025d1 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -45,6 +45,7 @@ namespace MWGui , NoDrop(drag, mMainWidget) , mHeight(0) , mWidth(0) + , mWindowSize(mMainWidget->getSize()) { mSpellIcons = new SpellIcons(); @@ -308,7 +309,11 @@ namespace MWGui void SpellWindow::onWindowResize(MyGUI::Window* _sender) { - updateSpells(); + if (mMainWidget->getSize() != mWindowSize) + { + mWindowSize = mMainWidget->getSize(); + updateSpells(); + } } void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index 2aa4d83d2..a74847b90 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -29,6 +29,8 @@ namespace MWGui int mHeight; int mWidth; + MyGUI::IntSize mWindowSize; + std::string mSpellToDelete; void addGroup(const std::string& label, const std::string& label2); From 8ae6796b2f2c592a30a48397446353db143693cf Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 13 Aug 2014 01:53:56 +0200 Subject: [PATCH 36/36] Play creature movement animation without scaling if a fallback is used (Fixes #1797) --- apps/openmw/mwmechanics/character.cpp | 41 ++++++++++++++++++--------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 9bfe771bc..e0e8057f2 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -395,22 +395,35 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat CharacterState walkState = runStateToWalkState(mMovementState); const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState)); anim = stateinfo->groupname; + + if (mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) + speedmult = mMovementSpeed / vel; + else + // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), + // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist + // we will play without any scaling. + // Makes the speed attribute of most water creatures totally useless. + // And again, this can not be fixed without patching game data. + speedmult = 1.f; + } + else + { + if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) + { + speedmult = mMovementSpeed / vel; + } + else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) + speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed + else if (mMovementSpeed > 0.0f) + { + // The first person anims don't have any velocity to calculate a speed multiplier from. + // We use the third person velocities instead. + // FIXME: should be pulled from the actual animation, but it is not presently loaded. + speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f); + mMovementAnimationControlled = false; + } } - if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) - { - speedmult = mMovementSpeed / vel; - } - else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) - speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed - else if (mMovementSpeed > 0.0f) - { - // The first person anims don't have any velocity to calculate a speed multiplier from. - // We use the third person velocities instead. - // FIXME: should be pulled from the actual animation, but it is not presently loaded. - speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f); - mMovementAnimationControlled = false; - } mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); }