From d8f24ac4998b0fb297a167e7b756089eb07400da Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 17 Feb 2014 15:49:49 +0100 Subject: [PATCH 001/135] bug fix --- apps/openmw/mwmechanics/aiactivate.cpp | 4 +++- apps/openmw/mwmechanics/aifollow.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 1f3c585216..2d5d11134d 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -59,7 +59,9 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) } } - MWWorld::Ptr target = world->getPtr(mObjectId,false); + MWWorld::Ptr target = world->searchPtr(mObjectId,false); + if(target == MWWorld::Ptr()) return true; + ESM::Position targetPos = target.getRefData().getPosition(); bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index cf5291fd35..d3ee21abf9 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -20,7 +20,9 @@ MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &ce bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { - const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); + + if(target == MWWorld::Ptr()) return false; mTimer = mTimer + duration; mStuckTimer = mStuckTimer + duration; From f4879dacd54bc7599e676a297297a40c58e4376e Mon Sep 17 00:00:00 2001 From: gus Date: Wed, 5 Mar 2014 11:24:39 +0100 Subject: [PATCH 002/135] add AIfollow to summoned creatures --- apps/openmw/mwmechanics/actors.cpp | 2 ++ apps/openmw/mwmechanics/aifollow.cpp | 40 +++++++++++++++++----------- apps/openmw/mwmechanics/aifollow.hpp | 2 ++ 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 1fb22ce639..e28ce4b27b 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -539,6 +539,8 @@ namespace MWMechanics ref.getPtr().getCellRef().mPos = ipos; // TODO: Add AI to follow player and fight for him + AiFollow package(ptr.getRefData().getHandle()); + MWWorld::Class::get (ref.getPtr()).getCreatureStats (ref.getPtr()).getAiSequence().stack(package); // TODO: VFX_SummonStart, VFX_SummonEnd creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle())); diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index d3ee21abf9..5e4559c0e3 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -10,11 +10,16 @@ #include "steering.hpp" MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) +: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) { } MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0) +: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0) +{ +} + +MWMechanics::AiFollow::AiFollow(const std::string &actorId) +: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) { } @@ -30,22 +35,25 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) ESM::Position pos = actor.getRefData().getPosition(); - if(mTotalTime > mDuration && mDuration != 0) - return true; - - if((pos.pos[0]-mX)*(pos.pos[0]-mX) + - (pos.pos[1]-mY)*(pos.pos[1]-mY) + - (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) + if(!mAlwaysFollow) { - if(actor.getCell()->isExterior()) + if(mTotalTime > mDuration && mDuration != 0) + return true; + + if((pos.pos[0]-mX)*(pos.pos[0]-mX) + + (pos.pos[1]-mY)*(pos.pos[1]-mY) + + (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) { - if(mCellId == "") - return true; - } - else - { - if(mCellId == actor.getCell()->mCell->mName) - return true; + if(actor.getCell()->isExterior()) + { + if(mCellId == "") + return true; + } + else + { + if(mCellId == actor.getCell()->mCell->mName) + return true; + } } } diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 9d77b903de..48a8eb4c26 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -14,6 +14,7 @@ namespace MWMechanics public: AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z); AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); + AiFollow(const std::string &ActorId); virtual AiFollow *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? @@ -22,6 +23,7 @@ namespace MWMechanics std::string getFollowedActor(); private: + bool mAlwaysFollow; //this will make the actor always follow, thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). float mDuration; float mX; float mY; From d84319300a45caf5bd7c03e468ec557c6f3681c4 Mon Sep 17 00:00:00 2001 From: gus Date: Wed, 5 Mar 2014 11:27:16 +0100 Subject: [PATCH 003/135] fix --- apps/openmw/mwmechanics/aifollow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 5e4559c0e3..e2a96fc878 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -27,7 +27,7 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); - if(target == MWWorld::Ptr()) return false; + if(target == MWWorld::Ptr()) return true; mTimer = mTimer + duration; mStuckTimer = mStuckTimer + duration; From 452b522bc3105b3aba95aeab86124ddc0839347e Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 22 Mar 2014 19:01:41 +0100 Subject: [PATCH 004/135] Fix typos --- components/compiler/lineparser.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 5457d76255..98bd63ba1e 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -86,7 +86,7 @@ namespace Compiler { if (mState==PotentialEndState) { - getErrorHandler().warning ("stay string argument (ignoring it)", loc); + getErrorHandler().warning ("stray string argument (ignoring it)", loc); mState = EndState; return true; } @@ -377,19 +377,19 @@ namespace Compiler case Scanner::K_else: - getErrorHandler().warning ("stay else (ignoring it)", loc); + getErrorHandler().warning ("stray else (ignoring it)", loc); mState = EndState; return true; case Scanner::K_endif: - getErrorHandler().warning ("stay endif (ignoring it)", loc); + getErrorHandler().warning ("stray endif (ignoring it)", loc); mState = EndState; return true; case Scanner::K_begin: - getErrorHandler().warning ("stay begin (ignoring it)", loc); + getErrorHandler().warning ("stray begin (ignoring it)", loc); mState = EndState; return true; } From 0a17245633a10d3583d684fa92a5ac0b61c0fa83 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 22 Mar 2014 19:02:14 +0100 Subject: [PATCH 005/135] Refactored settings window --- CMakeLists.txt | 1 + apps/openmw/mwgui/settingswindow.cpp | 380 +++++++++------------- apps/openmw/mwgui/settingswindow.hpp | 37 +-- apps/openmw/mwrender/water.cpp | 6 +- files/mygui/openmw_settings_window.layout | 170 ++++++++-- files/settings-default.cfg | 2 - 6 files changed, 295 insertions(+), 301 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb3827588b..392fdfc66c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,7 @@ set(OENGINE_OGRE set(OENGINE_GUI ${LIBDIR}/openengine/gui/manager.cpp + ${LIBDIR}/openengine/gui/layout.hpp ) set(OENGINE_BULLET diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index c99e2d0de3..78adecd3ef 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -83,91 +83,116 @@ namespace } return false; } + + const char* checkButtonType = "CheckButton"; + const char* sliderType = "Slider"; + + std::string getSettingType(MyGUI::Widget* widget) + { + return widget->getUserString("SettingType"); + } + + std::string getSettingName(MyGUI::Widget* widget) + { + return widget->getUserString("SettingName"); + } + + std::string getSettingCategory(MyGUI::Widget* widget) + { + return widget->getUserString("SettingCategory"); + } + + std::string getSettingValueType(MyGUI::Widget* widget) + { + return widget->getUserString("SettingValueType"); + } + + void getSettingMinMax(MyGUI::Widget* widget, float& min, float& max) + { + const char* settingMin = "SettingMin"; + const char* settingMax = "SettingMax"; + min = 0.f; + max = 1.f; + if (!widget->getUserString(settingMin).empty()) + min = boost::lexical_cast(widget->getUserString(settingMin)); + if (!widget->getUserString(settingMax).empty()) + max = boost::lexical_cast(widget->getUserString(settingMax)); + } } namespace MWGui { + void SettingsWindow::configureWidgets(MyGUI::Widget* widget) + { + MyGUI::EnumeratorWidgetPtr widgets = widget->getEnumerator(); + while (widgets.next()) + { + MyGUI::Widget* current = widgets.current(); + + std::string type = getSettingType(current); + if (type == checkButtonType) + { + std::string initialValue = Settings::Manager::getBool(getSettingName(current), + getSettingCategory(current)) + ? "#{sOn}" : "#{sOff}"; + current->castType()->setCaptionWithReplacing(initialValue); + current->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + } + if (type == sliderType) + { + MyGUI::ScrollBar* scroll = current->castType(); + if (getSettingValueType(current) == "Float") + { + // TODO: ScrollBar isn't meant for this. should probably use a dedicated FloatSlider widget + float min,max; + getSettingMinMax(scroll, min, max); + float value = Settings::Manager::getFloat(getSettingName(current), getSettingCategory(current)); + value = (value-min)/(max-min); + + scroll->setScrollPosition( value * (scroll->getScrollRange()-1)); + } + else + { + int value = Settings::Manager::getFloat(getSettingName(current), getSettingCategory(current)); + scroll->setScrollPosition(value); + } + scroll->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + } + + configureWidgets(current); + } + } + SettingsWindow::SettingsWindow() : WindowBase("openmw_settings_window.layout") { + configureWidgets(mMainWidget); + getWidget(mOkButton, "OkButton"); - getWidget(mBestAttackButton, "BestAttackButton"); - getWidget(mGrabCursorButton, "GrabCursorButton"); - getWidget(mSubtitlesButton, "SubtitlesButton"); - getWidget(mCrosshairButton, "CrosshairButton"); getWidget(mResolutionList, "ResolutionList"); - getWidget(mMenuTransparencySlider, "MenuTransparencySlider"); - getWidget(mToolTipDelaySlider, "ToolTipDelaySlider"); - getWidget(mViewDistanceSlider, "ViewDistanceSlider"); getWidget(mFullscreenButton, "FullscreenButton"); getWidget(mVSyncButton, "VSyncButton"); getWidget(mFPSButton, "FPSButton"); getWidget(mFOVSlider, "FOVSlider"); - getWidget(mMasterVolumeSlider, "MasterVolume"); - getWidget(mVoiceVolumeSlider, "VoiceVolume"); - getWidget(mEffectsVolumeSlider, "EffectsVolume"); - getWidget(mFootstepsVolumeSlider, "FootstepsVolume"); - getWidget(mMusicVolumeSlider, "MusicVolume"); getWidget(mAnisotropySlider, "AnisotropySlider"); getWidget(mTextureFilteringButton, "TextureFilteringButton"); getWidget(mAnisotropyLabel, "AnisotropyLabel"); getWidget(mAnisotropyBox, "AnisotropyBox"); - getWidget(mWaterShaderButton, "WaterShaderButton"); - getWidget(mReflectObjectsButton, "ReflectObjectsButton"); - getWidget(mReflectActorsButton, "ReflectActorsButton"); - getWidget(mReflectTerrainButton, "ReflectTerrainButton"); getWidget(mShadersButton, "ShadersButton"); getWidget(mShaderModeButton, "ShaderModeButton"); getWidget(mShadowsEnabledButton, "ShadowsEnabledButton"); - getWidget(mShadowsLargeDistance, "ShadowsLargeDistance"); getWidget(mShadowsTextureSize, "ShadowsTextureSize"); - getWidget(mActorShadows, "ActorShadows"); - getWidget(mStaticsShadows, "StaticsShadows"); - getWidget(mMiscShadows, "MiscShadows"); - getWidget(mTerrainShadows, "TerrainShadows"); getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); - getWidget(mInvertYButton, "InvertYButton"); - getWidget(mCameraSensitivitySlider, "CameraSensitivitySlider"); getWidget(mRefractionButton, "RefractionButton"); - mSubtitlesButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mCrosshairButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mBestAttackButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mGrabCursorButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mInvertYButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); - mShadersButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadersToggled); mShaderModeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShaderModeToggled); - mFullscreenButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mWaterShaderButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mRefractionButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mReflectObjectsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mReflectTerrainButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mReflectActorsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mTextureFilteringButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged); - mVSyncButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mFPSButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onFpsToggled); - mMenuTransparencySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); - mFOVSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); - mToolTipDelaySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); - mViewDistanceSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected); - mAnisotropySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); - mShadowsEnabledButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mShadowsLargeDistance->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mShadowsTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSizeChanged); - mActorShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mStaticsShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mMiscShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mTerrainShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - - mMasterVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); - mVoiceVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); - mEffectsVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); - mFootstepsVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); - mMusicVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); center(); @@ -194,73 +219,25 @@ namespace MWGui mResolutionList->addItem(str); } - // read settings - int menu_transparency = (mMenuTransparencySlider->getScrollRange()-1) * Settings::Manager::getFloat("menu transparency", "GUI"); - mMenuTransparencySlider->setScrollPosition(menu_transparency); - int tooltip_delay = (mToolTipDelaySlider->getScrollRange()-1) * Settings::Manager::getFloat("tooltip delay", "GUI"); - mToolTipDelaySlider->setScrollPosition(tooltip_delay); - - mSubtitlesButton->setCaptionWithReplacing(Settings::Manager::getBool("subtitles", "GUI") ? "#{sOn}" : "#{sOff}"); - mCrosshairButton->setCaptionWithReplacing(Settings::Manager::getBool("crosshair", "HUD") ? "#{sOn}" : "#{sOff}"); - mBestAttackButton->setCaptionWithReplacing(Settings::Manager::getBool("best attack", "Game") ? "#{sOn}" : "#{sOff}"); - mGrabCursorButton->setCaptionWithReplacing(Settings::Manager::getBool("grab cursor", "Input") ? "#{sOn}" : "#{sOff}"); - - float fovVal = (Settings::Manager::getFloat("field of view", "General")-sFovMin)/(sFovMax-sFovMin); - mFOVSlider->setScrollPosition(fovVal * (mFOVSlider->getScrollRange()-1)); - MyGUI::TextBox* fovText; - getWidget(fovText, "FovText"); - fovText->setCaption("Field of View (" + boost::lexical_cast(int(Settings::Manager::getFloat("field of view", "General"))) + ")"); - - float anisotropyVal = Settings::Manager::getInt("anisotropy", "General") / 16.0; - mAnisotropySlider->setScrollPosition(anisotropyVal * (mAnisotropySlider->getScrollRange()-1)); std::string tf = Settings::Manager::getString("texture filtering", "General"); mTextureFilteringButton->setCaption(textureFilteringToStr(tf)); mAnisotropyLabel->setCaption("Anisotropy (" + boost::lexical_cast(Settings::Manager::getInt("anisotropy", "General")) + ")"); - float val = (Settings::Manager::getFloat("max viewing distance", "Viewing distance")-sViewDistMin)/(sViewDistMax-sViewDistMin); - int viewdist = (mViewDistanceSlider->getScrollRange()-1) * val; - mViewDistanceSlider->setScrollPosition(viewdist); - - mMasterVolumeSlider->setScrollPosition(Settings::Manager::getFloat("master volume", "Sound") * (mMasterVolumeSlider->getScrollRange()-1)); - mMusicVolumeSlider->setScrollPosition(Settings::Manager::getFloat("music volume", "Sound") * (mMusicVolumeSlider->getScrollRange()-1)); - mEffectsVolumeSlider->setScrollPosition(Settings::Manager::getFloat("sfx volume", "Sound") * (mEffectsVolumeSlider->getScrollRange()-1)); - mFootstepsVolumeSlider->setScrollPosition(Settings::Manager::getFloat("footsteps volume", "Sound") * (mFootstepsVolumeSlider->getScrollRange()-1)); - mVoiceVolumeSlider->setScrollPosition(Settings::Manager::getFloat("voice volume", "Sound") * (mVoiceVolumeSlider->getScrollRange()-1)); - - mWaterShaderButton->setCaptionWithReplacing(Settings::Manager::getBool("shader", "Water") ? "#{sOn}" : "#{sOff}"); - mReflectObjectsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect statics", "Water") ? "#{sOn}" : "#{sOff}"); - mReflectActorsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect actors", "Water") ? "#{sOn}" : "#{sOff}"); - mReflectTerrainButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect terrain", "Water") ? "#{sOn}" : "#{sOff}"); - mShadowsTextureSize->setCaption (Settings::Manager::getString ("texture size", "Shadows")); - mShadowsLargeDistance->setCaptionWithReplacing(Settings::Manager::getBool("split", "Shadows") ? "#{sOn}" : "#{sOff}"); - mShadowsEnabledButton->setCaptionWithReplacing(Settings::Manager::getBool("enabled", "Shadows") ? "#{sOn}" : "#{sOff}"); - mActorShadows->setCaptionWithReplacing(Settings::Manager::getBool("actor shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); - mStaticsShadows->setCaptionWithReplacing(Settings::Manager::getBool("statics shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); - mMiscShadows->setCaptionWithReplacing(Settings::Manager::getBool("misc shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); - mTerrainShadows->setCaptionWithReplacing(Settings::Manager::getBool("terrain shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); - - float cameraSens = (Settings::Manager::getFloat("camera sensitivity", "Input")-0.2)/(5.0-0.2); - mCameraSensitivitySlider->setScrollPosition (cameraSens * (mCameraSensitivitySlider->getScrollRange()-1)); - mCameraSensitivitySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); - - mInvertYButton->setCaptionWithReplacing(Settings::Manager::getBool("invert y axis", "Input") ? "#{sOn}" : "#{sOff}"); - - mShadersButton->setCaptionWithReplacing (Settings::Manager::getBool("shaders", "Objects") ? "#{sOn}" : "#{sOff}"); mShaderModeButton->setCaption (Settings::Manager::getString("shader mode", "General")); - mRefractionButton->setCaptionWithReplacing (Settings::Manager::getBool("refraction", "Water") ? "#{sOn}" : "#{sOff}"); - if (!Settings::Manager::getBool("shaders", "Objects")) { mRefractionButton->setEnabled(false); mShadowsEnabledButton->setEnabled(false); } - mFullscreenButton->setCaptionWithReplacing(Settings::Manager::getBool("fullscreen", "Video") ? "#{sOn}" : "#{sOff}"); - mVSyncButton->setCaptionWithReplacing(Settings::Manager::getBool("vsync", "Video") ? "#{sOn}": "#{sOff}"); mFPSButton->setCaptionWithReplacing(fpsLevelToStr(Settings::Manager::getInt("fps", "HUD"))); + + MyGUI::TextBox* fovText; + getWidget(fovText, "FovText"); + fovText->setCaption("Field of View (" + boost::lexical_cast(int(Settings::Manager::getInt("field of view", "General"))) + ")"); } void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender) @@ -320,6 +297,39 @@ namespace MWGui newState = true; } + if (_sender == mVSyncButton) + { + // Ogre::Window::setVSyncEnabled is bugged in 1.8 +#if OGRE_VERSION < (1 << 16 | 9 << 8 | 0) + MWBase::Environment::get().getWindowManager()-> + messageBox("VSync will be applied after a restart", std::vector()); +#endif + } + + if (_sender == mShadersButton) + { + if (newState == false) + { + // refraction needs shaders to display underwater fog + mRefractionButton->setCaptionWithReplacing("#{sOff}"); + mRefractionButton->setEnabled(false); + + Settings::Manager::setBool("refraction", "Water", false); + + // shadows not supported + mShadowsEnabledButton->setEnabled(false); + mShadowsEnabledButton->setCaptionWithReplacing("#{sOff}"); + Settings::Manager::setBool("enabled", "Shadows", false); + } + else + { + // re-enable + mRefractionButton->setEnabled(true); + + mShadowsEnabledButton->setEnabled(true); + } + } + if (_sender == mFullscreenButton) { // check if this resolution is supported in fullscreen @@ -341,64 +351,15 @@ namespace MWGui MWBase::Environment::get().getWindowManager()-> messageBox(msg); _sender->castType()->setCaption(off); - } - else - { - Settings::Manager::setBool("fullscreen", "Video", newState); - apply(); + return; } } - else if (_sender == mVSyncButton) + + if (getSettingType(_sender) == checkButtonType) { - Settings::Manager::setBool("vsync", "Video", newState); - // Ogre::Window::setVSyncEnabled is bugged in 1.8 -#if OGRE_VERSION < (1 << 16 | 9 << 8 | 0) - MWBase::Environment::get().getWindowManager()-> - messageBox("VSync will be applied after a restart", std::vector()); -#endif - apply(); - } - else - { - if (_sender == mVSyncButton) - Settings::Manager::setBool("vsync", "Video", newState); - if (_sender == mWaterShaderButton) - Settings::Manager::setBool("shader", "Water", newState); - else if (_sender == mRefractionButton) - Settings::Manager::setBool("refraction", "Water", newState); - else if (_sender == mReflectObjectsButton) - { - Settings::Manager::setBool("reflect misc", "Water", newState); - Settings::Manager::setBool("reflect statics", "Water", newState); - Settings::Manager::setBool("reflect statics small", "Water", newState); - } - else if (_sender == mReflectActorsButton) - Settings::Manager::setBool("reflect actors", "Water", newState); - else if (_sender == mReflectTerrainButton) - Settings::Manager::setBool("reflect terrain", "Water", newState); - else if (_sender == mShadowsEnabledButton) - Settings::Manager::setBool("enabled", "Shadows", newState); - else if (_sender == mShadowsLargeDistance) - Settings::Manager::setBool("split", "Shadows", newState); - else if (_sender == mActorShadows) - Settings::Manager::setBool("actor shadows", "Shadows", newState); - else if (_sender == mStaticsShadows) - Settings::Manager::setBool("statics shadows", "Shadows", newState); - else if (_sender == mMiscShadows) - Settings::Manager::setBool("misc shadows", "Shadows", newState); - else if (_sender == mTerrainShadows) - Settings::Manager::setBool("terrain shadows", "Shadows", newState); - else if (_sender == mInvertYButton) - Settings::Manager::setBool("invert y axis", "Input", newState); - else if (_sender == mCrosshairButton) - Settings::Manager::setBool("crosshair", "HUD", newState); - else if (_sender == mSubtitlesButton) - Settings::Manager::setBool("subtitles", "GUI", newState); - else if (_sender == mBestAttackButton) - Settings::Manager::setBool("best attack", "Game", newState); - else if (_sender == mGrabCursorButton) - Settings::Manager::setBool("grab cursor", "Input", newState); + Settings::Manager::setBool(getSettingName(_sender), getSettingCategory(_sender), newState); apply(); + return; } } @@ -419,50 +380,6 @@ namespace MWGui apply(); } - void SettingsWindow::onShadersToggled(MyGUI::Widget* _sender) - { - std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On"); - std::string off = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOff", "On"); - - std::string val = static_cast(_sender)->getCaption(); - if (val == off) - val = on; - else - val = off; - static_cast(_sender)->setCaptionWithReplacing (val); - - if (val == off) - { - Settings::Manager::setBool("shaders", "Objects", false); - - // refraction needs shaders to display underwater fog - mRefractionButton->setCaptionWithReplacing("#{sOff}"); - mRefractionButton->setEnabled(false); - - Settings::Manager::setBool("refraction", "Water", false); - Settings::Manager::setBool("underwater effect", "Water", false); - - // shadows not supported - mShadowsEnabledButton->setEnabled(false); - mShadowsEnabledButton->setCaptionWithReplacing("#{sOff}"); - Settings::Manager::setBool("enabled", "Shadows", false); - } - else - { - Settings::Manager::setBool("shaders", "Objects", true); - - // re-enable - mReflectObjectsButton->setEnabled(true); - mReflectActorsButton->setEnabled(true); - mReflectTerrainButton->setEnabled(true); - mRefractionButton->setEnabled(true); - - mShadowsEnabledButton->setEnabled(true); - } - - apply(); - } - void SettingsWindow::onFpsToggled(MyGUI::Widget* _sender) { int newLevel = (Settings::Manager::getInt("fps", "HUD") + 1) % 3; @@ -479,39 +396,34 @@ namespace MWGui void SettingsWindow::onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos) { - float val = pos / float(scroller->getScrollRange()-1); - if (scroller == mMenuTransparencySlider) - Settings::Manager::setFloat("menu transparency", "GUI", val); - else if (scroller == mToolTipDelaySlider) - Settings::Manager::setFloat("tooltip delay", "GUI", val); - else if (scroller == mViewDistanceSlider) - Settings::Manager::setFloat("max viewing distance", "Viewing distance", (1-val) * sViewDistMin + val * sViewDistMax); - else if (scroller == mFOVSlider) + if (getSettingType(scroller) == "Slider") { - MyGUI::TextBox* fovText; - getWidget(fovText, "FovText"); - fovText->setCaption("Field of View (" + boost::lexical_cast(int((1-val) * sFovMin + val * sFovMax)) + ")"); - Settings::Manager::setFloat("field of view", "General", (1-val) * sFovMin + val * sFovMax); - } - else if (scroller == mAnisotropySlider) - { - mAnisotropyLabel->setCaption("Anisotropy (" + boost::lexical_cast(int(val*16)) + ")"); - Settings::Manager::setInt("anisotropy", "General", val * 16); - } - else if (scroller == mMasterVolumeSlider) - Settings::Manager::setFloat("master volume", "Sound", val); - else if (scroller == mVoiceVolumeSlider) - Settings::Manager::setFloat("voice volume", "Sound", val); - else if (scroller == mEffectsVolumeSlider) - Settings::Manager::setFloat("sfx volume", "Sound", val); - else if (scroller == mFootstepsVolumeSlider) - Settings::Manager::setFloat("footsteps volume", "Sound", val); - else if (scroller == mMusicVolumeSlider) - Settings::Manager::setFloat("music volume", "Sound", val); - else if (scroller == mCameraSensitivitySlider) - Settings::Manager::setFloat("camera sensitivity", "Input", (1-val) * 0.2 + val * 5.f); + if (getSettingValueType(scroller) == "Float") + { + float value = pos / float(scroller->getScrollRange()-1); - apply(); + float min,max; + getSettingMinMax(scroller, min, max); + value = min + (max-min) * value; + Settings::Manager::setFloat(getSettingName(scroller), getSettingCategory(scroller), value); + + if (scroller == mFOVSlider) + { + MyGUI::TextBox* fovText; + getWidget(fovText, "FovText"); + fovText->setCaption("Field of View (" + boost::lexical_cast(int(value)) + ")"); + } + } + else + { + Settings::Manager::setInt(getSettingName(scroller), getSettingCategory(scroller), pos); + if (scroller == mAnisotropySlider) + { + mAnisotropyLabel->setCaption("Anisotropy (" + boost::lexical_cast(pos) + ")"); + } + } + apply(); + } } void SettingsWindow::apply() diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 6b9ce414b8..7a6c1a5ed5 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -19,61 +19,29 @@ namespace MWGui void updateControlsBox(); - private: - static int const sFovMin = 30; - static int const sFovMax = 140; - static int const sViewDistMin = 2000; - static int const sViewDistMax = 5600; - - protected: + protected: MyGUI::Button* mOkButton; - MyGUI::ScrollBar* mMenuTransparencySlider; - MyGUI::ScrollBar* mToolTipDelaySlider; - MyGUI::Button* mSubtitlesButton; - MyGUI::Button* mCrosshairButton; - MyGUI::Button* mBestAttackButton; - MyGUI::Button* mGrabCursorButton; - // graphics MyGUI::ListBox* mResolutionList; MyGUI::Button* mFullscreenButton; MyGUI::Button* mVSyncButton; MyGUI::Button* mFPSButton; - MyGUI::ScrollBar* mViewDistanceSlider; MyGUI::ScrollBar* mFOVSlider; MyGUI::ScrollBar* mAnisotropySlider; MyGUI::ComboBox* mTextureFilteringButton; MyGUI::TextBox* mAnisotropyLabel; MyGUI::Widget* mAnisotropyBox; - MyGUI::Button* mWaterShaderButton; - MyGUI::Button* mReflectObjectsButton; - MyGUI::Button* mReflectActorsButton; - MyGUI::Button* mReflectTerrainButton; MyGUI::Button* mShadersButton; MyGUI::Button* mShaderModeButton; MyGUI::Button* mRefractionButton; MyGUI::Button* mShadowsEnabledButton; - MyGUI::Button* mShadowsLargeDistance; MyGUI::ComboBox* mShadowsTextureSize; - MyGUI::Button* mActorShadows; - MyGUI::Button* mStaticsShadows; - MyGUI::Button* mMiscShadows; - MyGUI::Button* mTerrainShadows; - - // audio - MyGUI::ScrollBar* mMasterVolumeSlider; - MyGUI::ScrollBar* mVoiceVolumeSlider; - MyGUI::ScrollBar* mEffectsVolumeSlider; - MyGUI::ScrollBar* mFootstepsVolumeSlider; - MyGUI::ScrollBar* mMusicVolumeSlider; // controls MyGUI::ScrollView* mControlsBox; MyGUI::Button* mResetControlsButton; - MyGUI::Button* mInvertYButton; - MyGUI::ScrollBar* mCameraSensitivitySlider; void onOkButtonClicked(MyGUI::Widget* _sender); void onFpsToggled(MyGUI::Widget* _sender); @@ -84,7 +52,6 @@ namespace MWGui void onResolutionAccept(); void onResolutionCancel(); - void onShadersToggled(MyGUI::Widget* _sender); void onShaderModeToggled(MyGUI::Widget* _sender); void onShadowTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); @@ -94,6 +61,8 @@ namespace MWGui void onResetDefaultBindingsAccept (); void apply(); + + void configureWidgets(MyGUI::Widget* widget); }; } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 1fa5d88341..5368cbe687 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -418,10 +418,8 @@ void Water::applyRTT() void Water::applyVisibilityMask() { mVisibilityFlags = RV_Terrain * Settings::Manager::getBool("reflect terrain", "Water") - + RV_Statics * Settings::Manager::getBool("reflect statics", "Water") - + RV_StaticsSmall * Settings::Manager::getBool("reflect small statics", "Water") + + (RV_Statics + RV_StaticsSmall + RV_Misc) * Settings::Manager::getBool("reflect statics", "Water") + RV_Actors * Settings::Manager::getBool("reflect actors", "Water") - + RV_Misc * Settings::Manager::getBool("reflect misc", "Water") + RV_Sky; if (mReflection) @@ -444,8 +442,6 @@ void Water::processChangedSettings(const Settings::CategorySettingVector& settin if ( it->first == "Water" && ( it->second == "reflect actors" || it->second == "reflect terrain" - || it->second == "reflect misc" - || it->second == "reflect small statics" || it->second == "reflect statics")) applyVisMask = true; } diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 61103963db..adf9f1557f 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -18,6 +18,10 @@ + + + + @@ -34,6 +38,10 @@ + + + + @@ -45,28 +53,44 @@ - + + + + + - + + + + + - + + + + + - + + + + + @@ -81,6 +105,10 @@ + + + + @@ -89,6 +117,10 @@ + + + + @@ -97,6 +129,10 @@ + + + + @@ -105,6 +141,10 @@ + + + + @@ -113,6 +153,10 @@ + + + + @@ -127,7 +171,11 @@ - + + + + + @@ -139,6 +187,12 @@ + + + + + + @@ -149,8 +203,6 @@ - - @@ -163,16 +215,23 @@ - - + + + + + - + + + + + @@ -186,7 +245,11 @@ - + + + + + @@ -205,6 +268,12 @@ + + + + + + @@ -233,8 +302,10 @@ - - + + + + @@ -244,6 +315,12 @@ + + + + + + @@ -260,7 +337,11 @@ - + + + + + @@ -269,21 +350,33 @@ - + + + + + - + + + + + - + + + + + @@ -291,7 +384,11 @@ - + + + + + @@ -302,7 +399,11 @@ - + + + + + @@ -311,35 +412,52 @@ - + + + + + - - - + + + + - + + + + + - + + + + + - + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4fb7097f84..5dfc054553 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -137,9 +137,7 @@ refraction = true rtt size = 512 reflect terrain = true reflect statics = false -reflect small statics = false reflect actors = false -reflect misc = false [Sound] # Device name. Blank means default From 688415ce5476aeeb9bf91962f2294868e9d858e6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 26 Mar 2014 18:55:16 +0100 Subject: [PATCH 006/135] Play deathknockout/deathknockdown animations when appropriate --- apps/openmw/mwmechanics/character.cpp | 10 ++++++++++ apps/openmw/mwmechanics/character.hpp | 2 ++ 2 files changed, 12 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2db3bacf0e..93c789af1a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -414,6 +414,16 @@ void CharacterController::playRandomDeath(float startpoint) mDeathState = CharState_SwimDeath; mCurrentDeath = "swimdeath"; } + else if (mHitState == CharState_KnockDown) + { + mDeathState = CharState_DeathKnockDown; + mCurrentDeath = "deathknockdown"; + } + else if (mHitState == CharState_KnockOut) + { + mDeathState = CharState_DeathKnockOut; + mCurrentDeath = "deathknockout"; + } else { int selected=0; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 4009744efb..5aea0210f2 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -90,6 +90,8 @@ enum CharacterState { CharState_Death4, CharState_Death5, CharState_SwimDeath, + CharState_DeathKnockDown, + CharState_DeathKnockOut, CharState_Hit, CharState_KnockDown, From 793649c854d759a40fcb8b0421295a6f23c3ac2f Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 26 Mar 2014 19:55:52 +0100 Subject: [PATCH 007/135] ToggleAI: Report current status on toggle --- apps/openmw/mwbase/mechanicsmanager.hpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 3 ++- apps/openmw/mwmechanics/mechanicsmanagerimp.hpp | 2 +- apps/openmw/mwscript/aiextensions.cpp | 7 ++++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 22dda0ce09..e4c480a8c0 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -156,7 +156,7 @@ namespace MWBase /// paused we may want to do it manually (after equipping permanent enchantment) virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0; - virtual void toggleAI() = 0; + virtual bool toggleAI() = 0; virtual bool isAIActive() = 0; virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects) = 0; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 4c8f35edb0..3164ca1550 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -750,9 +750,10 @@ namespace MWMechanics mActors.updateMagicEffects(ptr); } - void MechanicsManager::toggleAI() + bool MechanicsManager::toggleAI() { mAI = !mAI; + return mAI; } bool MechanicsManager::isAIActive() diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 761caf586c..5dd7583779 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -137,7 +137,7 @@ namespace MWMechanics virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); - virtual void toggleAI(); + virtual bool toggleAI(); virtual bool isAIActive(); virtual void playerLoaded(); diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 8314d011a9..a34c5476c9 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -464,7 +464,12 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWBase::Environment::get().getMechanicsManager()->toggleAI(); + InterpreterContext& context + = static_cast (runtime.getContext()); + + bool enabled = MWBase::Environment::get().getMechanicsManager()->toggleAI(); + + context.report (enabled ? "AI -> On" : "AI -> Off"); } }; From 53ba23e3031e20ebd71d3af178cd9fcb95461702 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 26 Mar 2014 19:56:12 +0100 Subject: [PATCH 008/135] Fixes #1224: Store class name instead of id in savegame header --- apps/openmw/mwgui/savegamedialog.cpp | 2 +- apps/openmw/mwstate/statemanagerimp.cpp | 2 +- components/esm/savedgame.cpp | 4 ++-- components/esm/savedgame.hpp | 7 ++++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 77ad98121b..894cbe0a3e 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -86,7 +86,7 @@ namespace MWGui { std::stringstream title; title << it->getSignature().mPlayerName; - title << " (Level " << it->getSignature().mPlayerLevel << " " << it->getSignature().mPlayerClass << ")"; + title << " (Level " << it->getSignature().mPlayerLevel << " " << it->getSignature().mPlayerClassName << ")"; mCharacterSelection->addItem (title.str()); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index d6309c1c9f..a0d482f0a0 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -155,7 +155,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot profile.mPlayerName = player.getClass().getName (player); profile.mPlayerLevel = player.getClass().getNpcStats (player).getLevel(); - profile.mPlayerClass = player.get()->mBase->mClass; + profile.mPlayerClassName = world.getStore().get().find(player.get()->mBase->mClass)->mName; profile.mPlayerCell = world.getCellName(); diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index d6887f1704..813865e253 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -11,7 +11,7 @@ void ESM::SavedGame::load (ESMReader &esm) { mPlayerName = esm.getHNString("PLNA"); esm.getHNOT (mPlayerLevel, "PLLE"); - mPlayerClass = esm.getHNString("PLCL"); + mPlayerClassName = esm.getHNString("PLCL"); mPlayerCell = esm.getHNString("PLCE"); esm.getHNT (mInGameTime, "TSTM", 16); esm.getHNT (mTimePlayed, "TIME"); @@ -30,7 +30,7 @@ void ESM::SavedGame::save (ESMWriter &esm) const { esm.writeHNString ("PLNA", mPlayerName); esm.writeHNT ("PLLE", mPlayerLevel); - esm.writeHNString ("PLCL", mPlayerClass); + esm.writeHNString ("PLCL", mPlayerClassName); esm.writeHNString ("PLCE", mPlayerCell); esm.writeHNT ("TSTM", mInGameTime, 16); esm.writeHNT ("TIME", mTimePlayed); diff --git a/components/esm/savedgame.hpp b/components/esm/savedgame.hpp index 9c7bf551d7..b8615a6bc4 100644 --- a/components/esm/savedgame.hpp +++ b/components/esm/savedgame.hpp @@ -26,7 +26,12 @@ namespace ESM std::vector mContentFiles; std::string mPlayerName; int mPlayerLevel; - std::string mPlayerClass; // this is the ID and not the name of the class + // The (translated) name of the player class. So it will be displayed in the MW language + // the savegame was made in, not the currently running language of MW. + // However, savegames from a different MW language are not compatible anyway. + // And if only the ID was stored here, we would need to + // peek into the savegame to look for a class record in case it is a custom class. + std::string mPlayerClassName; std::string mPlayerCell; TimeStamp mInGameTime; double mTimePlayed; From ecfaa41e93699b0c20a717d76daf1f0e4ebf7661 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 26 Mar 2014 20:56:39 +0100 Subject: [PATCH 009/135] Savegame dialog: use locale-aware datetime format --- apps/openmw/mwgui/savegamedialog.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 894cbe0a3e..e897c5a6bc 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -241,7 +241,13 @@ namespace MWGui struct tm* timeinfo; timeinfo = localtime(&time); - text << asctime(timeinfo) << "\n"; + // Use system/environment locale settings for datetime formatting + std::setlocale(LC_TIME, ""); + + const int size=1024; + char buffer[size]; + if (std::strftime(buffer, size, "%x %X", timeinfo) > 0) + text << buffer << "\n"; text << "Level " << slot->mProfile.mPlayerLevel << "\n"; text << slot->mProfile.mPlayerCell << "\n"; // text << "Time played: " << slot->mProfile.mTimePlayed << "\n"; From 5562c78ac4933456b4e17e197b98b813bfa747e1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 26 Mar 2014 21:11:39 +0100 Subject: [PATCH 010/135] Ask for confirmation when selecting 'New Game' --- apps/openmw/mwgui/mainmenu.cpp | 17 ++++++++++++++++- apps/openmw/mwgui/mainmenu.hpp | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 4ad260fd93..655663eb68 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -13,6 +13,7 @@ #include "../mwstate/character.hpp" #include "savegamedialog.hpp" +#include "confirmationdialog.hpp" namespace MWGui { @@ -62,6 +63,11 @@ namespace MWGui OEngine::GUI::Layout::setVisible (visible); } + void MainMenu::onNewGameConfirmed() + { + MWBase::Environment::get().getStateManager()->newGame(); + } + void MainMenu::onButtonClicked(MyGUI::Widget *sender) { std::string name = *sender->getUserData(); @@ -77,7 +83,16 @@ namespace MWGui MWBase::Environment::get().getStateManager()->requestQuit(); else if (name == "newgame") { - MWBase::Environment::get().getStateManager()->newGame(); + if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) + onNewGameConfirmed(); + else + { + ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); + dialog->open("#{sNotifyMessage54}"); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &MainMenu::onNewGameConfirmed); + dialog->eventCancelClicked.clear(); + } } else diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index 722b329de1..f38b2b751b 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -32,6 +32,7 @@ namespace MWGui std::map mButtons; void onButtonClicked (MyGUI::Widget* sender); + void onNewGameConfirmed(); void updateMenu(); From 23f4bbc5b00c569ff542e86c13da827ce79b5b6c Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 26 Mar 2014 21:36:57 +0100 Subject: [PATCH 011/135] Videoplayer: only pause previous sounds if there is an audio stream --- apps/openmw/mwrender/videoplayer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index f3c0971e76..7abc8140a0 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -994,6 +994,9 @@ void VideoState::init(const std::string& resourceName) audio_index = i; } + if (audio_index != -1) + MWBase::Environment::get().getSoundManager()->pauseSounds(); + this->external_clock_base = av_gettime(); if(audio_index >= 0) this->stream_open(audio_index, this->format_ctx); @@ -1164,8 +1167,6 @@ void VideoPlayer::playVideo(const std::string &resourceName, bool allowSkipping) } mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); - MWBase::Environment::get().getSoundManager()->pauseSounds(); - try { mState = new VideoState; mState->init(resourceName); From a5598e9c2e5e6f9b74a938ae45a9da87569fc628 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 26 Mar 2014 21:38:57 +0100 Subject: [PATCH 012/135] Add Credits button to main menu (plays mw_credits.bik) --- apps/openmw/mwgui/mainmenu.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 655663eb68..4e4599e064 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -27,7 +27,7 @@ namespace MWGui std::stringstream sstream; sstream << "OpenMW version: " << OPENMW_VERSION; - // adding info about git hash if availible + // adding info about git hash if available std::string rev = OPENMW_VERSION_COMMITHASH; std::string tag = OPENMW_VERSION_TAGHASH; if (!rev.empty() && !tag.empty()) @@ -79,6 +79,8 @@ namespace MWGui } else if (name == "options") MWBase::Environment::get().getWindowManager ()->pushGuiMode (GM_Settings); + else if (name == "credits") + MWBase::Environment::get().getWorld()->playVideo("mw_credits.bik", true); else if (name == "exitgame") MWBase::Environment::get().getStateManager()->requestQuit(); else if (name == "newgame") @@ -135,7 +137,10 @@ namespace MWGui buttons.push_back("savegame"); buttons.push_back("options"); - //buttons.push_back("credits"); + + if (state==MWBase::StateManager::State_NoGame) + buttons.push_back("credits"); + buttons.push_back("exitgame"); // Create new buttons if needed From e5e1e84c5270f9120a39e7b7d676ba8c7298a516 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 26 Mar 2014 22:05:21 +0100 Subject: [PATCH 013/135] Ask for confirmation to exit OpenMW --- apps/openmw/mwgui/mainmenu.cpp | 18 +++++++++++++++++- apps/openmw/mwgui/mainmenu.hpp | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 4e4599e064..25821663df 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -68,6 +68,11 @@ namespace MWGui MWBase::Environment::get().getStateManager()->newGame(); } + void MainMenu::onExitConfirmed() + { + MWBase::Environment::get().getStateManager()->requestQuit(); + } + void MainMenu::onButtonClicked(MyGUI::Widget *sender) { std::string name = *sender->getUserData(); @@ -82,7 +87,18 @@ namespace MWGui else if (name == "credits") MWBase::Environment::get().getWorld()->playVideo("mw_credits.bik", true); else if (name == "exitgame") - MWBase::Environment::get().getStateManager()->requestQuit(); + { + if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) + onExitConfirmed(); + else + { + ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); + dialog->open("#{sMessage2}"); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &MainMenu::onExitConfirmed); + dialog->eventCancelClicked.clear(); + } + } else if (name == "newgame") { if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index f38b2b751b..1850c473c3 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -33,6 +33,7 @@ namespace MWGui void onButtonClicked (MyGUI::Widget* sender); void onNewGameConfirmed(); + void onExitConfirmed(); void updateMenu(); From 0e78ffe8c0b317e38e358d0b57504403ab5acf8b Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 27 Mar 2014 00:10:46 +0100 Subject: [PATCH 014/135] Add option to disable SDL's "minimize on focus loss" in fullscreen --- files/settings-default.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 5dfc054553..6361476e34 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -8,6 +8,9 @@ resolution y = 600 fullscreen = false screen = 0 +# Minimize the window if it loses key focus? +minimize on focus loss = true + # Render system # blank means default # Valid values: From 27956f362431127d92f2bafc7008c55d37241db6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 27 Mar 2014 02:36:21 +0100 Subject: [PATCH 015/135] Savegame dialog: Hide main menu after successful load --- apps/openmw/mwgui/savegamedialog.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index e897c5a6bc..6971358c17 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -169,7 +169,10 @@ namespace MWGui else { if (mCurrentCharacter && slot) + { MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot); + MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu); + } } setVisible(false); From 2e4ef93b28630f6f2ec8ccbe7eb2627e3912d6d1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 27 Mar 2014 04:15:47 +0100 Subject: [PATCH 016/135] Add proper main menu (background image, logo animation, title music) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/engine.cpp | 31 +++++++++++++++-- apps/openmw/mwgui/mainmenu.cpp | 61 +++++++++++++++++++++++++++++++--- apps/openmw/mwgui/mainmenu.hpp | 4 +++ files/mygui/openmw_layers.xml | 2 +- 5 files changed, 91 insertions(+), 9 deletions(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 20011b0d99..19f3473f79 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -33,7 +33,7 @@ add_openmw_dir (mwgui merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog - recharge + recharge mode ) add_openmw_dir (mwdialogue diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 47e0d016f5..a710ce59a7 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -339,6 +339,9 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) std::string aa = settings.getString("antialiasing", "Video"); windowSettings.fsaa = (aa.substr(0, 4) == "MSAA") ? aa.substr(5, aa.size()-5) : "0"; + SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, + settings.getBool("minimize on focus loss", "Video") ? "1" : "0"); + mOgre->createWindow("OpenMW", windowSettings); Bsa::registerResources (mFileCollections, mArchives, true, mFSStrict); @@ -356,6 +359,18 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding); mEnvironment.setWindowManager (window); + // Create sound system + mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound)); + + // TODO: play pre-load intro videos. Need to find a way to have them receive input. + // Make videoplayer a MyGUI widget? + /* + { + MWRender::VideoPlayer player(mOgre->getScene(), mOgre->getWindow()); + player.playVideo("mw_logo.bik", 1); + } + */ + // Create the world mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mContentFiles, mResDir, mCfgMgr.getCachePath(), mEncoder, mFallbackMap, @@ -373,9 +388,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) Compiler::registerExtensions (mExtensions); - // Create sound system - mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound)); - // Create script system mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full); mScriptContext->setExtensions (&mExtensions); @@ -434,7 +446,20 @@ void OMW::Engine::go() // start in main menu if (!mSkipMenu) + { MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + try + { + // Is there an ini setting for this filename or something? + MWBase::Environment::get().getSoundManager()->streamMusic("Special/morrowind title.mp3"); + + // TODO: there are other intro videos, too. They need to be imported from Morrowind.ini. + // Unfortunately those must play BEFORE any loading is done, which will currently not work. + // The videoplayer is created by World, so all content files must be loaded first... + MWBase::Environment::get().getWorld()->playVideo("mw_logo.bik", true); + } + catch (...) {} + } else MWBase::Environment::get().getStateManager()->newGame (true); diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 25821663df..0bc8b07d1d 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -22,6 +22,7 @@ namespace MWGui : OEngine::GUI::Layout("openmw_mainmenu.layout") , mButtonBox(0), mWidth (w), mHeight (h) , mSaveGameDialog(NULL) + , mBackground(NULL) { getWidget(mVersionText, "VersionText"); std::stringstream sstream; @@ -59,6 +60,10 @@ namespace MWGui { if (visible) updateMenu(); + else + showBackground( + MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && + MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); OEngine::GUI::Layout::setVisible (visible); } @@ -125,11 +130,42 @@ namespace MWGui } } + void MainMenu::showBackground(bool show) + { + if (mBackground) + { + MyGUI::Gui::getInstance().destroyWidget(mBackground); + mBackground = NULL; + } + if (show) + { + if (!mBackground) + { + mBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, + MyGUI::Align::Stretch, "Menu"); + mBackground->setImageTexture("black.png"); + + // Use black bars to correct aspect ratio. The video player also does it, so we need to do it + // for mw_logo.bik to align correctly with menu_morrowind.dds. + MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); + + // No way to un-hardcode this right now, menu_morrowind.dds is 1024x512 but was designed for 4:3 + double imageaspect = 4.0/3.0; + + int leftPadding = std::max(0.0, (screenSize.width - screenSize.height * imageaspect) / 2); + int topPadding = std::max(0.0, (screenSize.height - screenSize.width / imageaspect) / 2); + + MyGUI::ImageBox* image = mBackground->createWidget("ImageBox", + leftPadding, topPadding, screenSize.width - leftPadding*2, screenSize.height - topPadding*2, MyGUI::Align::Default); + image->setImageTexture("textures\\menu_morrowind.dds"); + } + } + } + void MainMenu::updateMenu() { setCoord(0,0, mWidth, mHeight); - if (!mButtonBox) mButtonBox = mMainWidget->createWidget("", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); @@ -137,6 +173,8 @@ namespace MWGui MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState(); + showBackground(state == MWBase::StateManager::State_NoGame); + std::vector buttons; if (state==MWBase::StateManager::State_Running) @@ -191,12 +229,27 @@ namespace MWGui assert(mButtons.find(*it) != mButtons.end()); MWGui::ImageButton* button = mButtons[*it]; button->setVisible(true); + MyGUI::IntSize requested = button->getRequestedSize(); - button->setCoord((maxwidth-requested.width) / 2, curH, requested.width, requested.height); - curH += requested.height; + + // Trim off some of the excessive padding + // TODO: perhaps do this within ImageButton? + int trim = 8; + button->setImageCoord(MyGUI::IntCoord(0, trim, requested.width, requested.height-trim)); + int height = requested.height-trim*2; + button->setImageTile(MyGUI::IntSize(requested.width, height)); + button->setCoord((maxwidth-requested.width) / 2, curH, requested.width, height); + curH += height; } - mButtonBox->setCoord (mWidth/2 - maxwidth/2, mHeight/2 - curH/2, maxwidth, curH); + if (state == MWBase::StateManager::State_NoGame) + { + // Align with the background image + int bottomPadding=48; + mButtonBox->setCoord (mWidth/2 - maxwidth/2, mHeight - curH - bottomPadding, maxwidth, curH); + } + else + mButtonBox->setCoord (mWidth/2 - maxwidth/2, mHeight/2 - curH/2, maxwidth, curH); } } diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index 1850c473c3..c571fda861 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -29,12 +29,16 @@ namespace MWGui MyGUI::Widget* mButtonBox; MyGUI::TextBox* mVersionText; + MyGUI::ImageBox* mBackground; + std::map mButtons; void onButtonClicked (MyGUI::Widget* sender); void onNewGameConfirmed(); void onExitConfirmed(); + void showBackground(bool show); + void updateMenu(); SaveGameDialog* mSaveGameDialog; diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index 84ec6f7c50..8bd95e2cd3 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -1,8 +1,8 @@ - + From 5906d795c0dc936a17e51a3fe37b12faada56160 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 27 Mar 2014 19:10:15 +0100 Subject: [PATCH 017/135] Refactored video player (now a MyGUI widget) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/engine.cpp | 2 +- apps/openmw/mwbase/windowmanager.hpp | 4 + apps/openmw/mwbase/world.hpp | 2 - apps/openmw/mwgui/mainmenu.cpp | 2 +- apps/openmw/mwgui/mode.hpp | 4 +- apps/openmw/mwgui/videowidget.cpp | 45 ++++++ apps/openmw/mwgui/videowidget.hpp | 39 +++++ apps/openmw/mwgui/windowmanagerimp.cpp | 71 +++++++- apps/openmw/mwgui/windowmanagerimp.hpp | 10 ++ apps/openmw/mwinput/inputmanagerimp.cpp | 4 +- apps/openmw/mwrender/renderingmanager.cpp | 19 --- apps/openmw/mwrender/renderingmanager.hpp | 5 - apps/openmw/mwrender/videoplayer.cpp | 188 +++++++--------------- apps/openmw/mwrender/videoplayer.hpp | 31 ++-- apps/openmw/mwscript/miscextensions.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 12 +- apps/openmw/mwworld/worldimp.hpp | 2 - files/mygui/openmw_layers.xml | 1 + 19 files changed, 242 insertions(+), 203 deletions(-) create mode 100644 apps/openmw/mwgui/videowidget.cpp create mode 100644 apps/openmw/mwgui/videowidget.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 19f3473f79..511435108e 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -33,7 +33,7 @@ add_openmw_dir (mwgui merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog - recharge mode + recharge mode videowidget ) add_openmw_dir (mwdialogue diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index a710ce59a7..a118152edd 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -456,7 +456,7 @@ void OMW::Engine::go() // TODO: there are other intro videos, too. They need to be imported from Morrowind.ini. // Unfortunately those must play BEFORE any loading is done, which will currently not work. // The videoplayer is created by World, so all content files must be loaded first... - MWBase::Environment::get().getWorld()->playVideo("mw_logo.bik", true); + MWBase::Environment::get().getWindowManager()->playVideo("mw_logo.bik", true); } catch (...) {} } diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 4fce19e337..e3bd428e22 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -96,6 +96,10 @@ namespace MWBase */ virtual void update() = 0; + /// @note This method will block until the video finishes playing + /// (and will continually update the window while doing so) + virtual void playVideo(const std::string& name, bool allowSkipping) = 0; + virtual void setNewGame(bool newgame) = 0; virtual void pushGuiMode (MWGui::GuiMode mode) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index bb6f5741d9..f03a9197d9 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -420,8 +420,6 @@ namespace MWBase virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) = 0; /// \todo this does not belong here - virtual void playVideo(const std::string& name, bool allowSkipping) = 0; - virtual void stopVideo() = 0; virtual void frameStarted (float dt, bool paused) = 0; virtual void screenshot (Ogre::Image& image, int w, int h) = 0; diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 0bc8b07d1d..00e124f6c2 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -90,7 +90,7 @@ namespace MWGui else if (name == "options") MWBase::Environment::get().getWindowManager ()->pushGuiMode (GM_Settings); else if (name == "credits") - MWBase::Environment::get().getWorld()->playVideo("mw_credits.bik", true); + MWBase::Environment::get().getWindowManager()->playVideo("mw_credits.bik", true); else if (name == "exitgame") { if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index 50d53abacd..a1688d2e53 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -47,9 +47,7 @@ namespace MWGui GM_Loading, GM_LoadingWallpaper, - GM_QuickKeysMenu, - - GM_Video + GM_QuickKeysMenu }; // Windows shown in inventory mode diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp new file mode 100644 index 0000000000..566c7cadbb --- /dev/null +++ b/apps/openmw/mwgui/videowidget.cpp @@ -0,0 +1,45 @@ +#include "videowidget.hpp" + +namespace MWGui +{ + +VideoWidget::VideoWidget() + : mAllowSkipping(true) +{ + eventKeyButtonPressed += MyGUI::newDelegate(this, &VideoWidget::onKeyPressed); + + setNeedKeyFocus(true); +} + +void VideoWidget::playVideo(const std::string &video, bool allowSkipping) +{ + mAllowSkipping = allowSkipping; + + mPlayer.playVideo(video); + + setImageTexture(mPlayer.getTextureName()); +} + +int VideoWidget::getVideoWidth() +{ + return mPlayer.getVideoWidth(); +} + +int VideoWidget::getVideoHeight() +{ + return mPlayer.getVideoHeight(); +} + +void VideoWidget::onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) +{ + if (_key == MyGUI::KeyCode::Escape && mAllowSkipping) + mPlayer.stopVideo(); +} + +bool VideoWidget::update() +{ + mPlayer.update(); + return mPlayer.isPlaying(); +} + +} diff --git a/apps/openmw/mwgui/videowidget.hpp b/apps/openmw/mwgui/videowidget.hpp new file mode 100644 index 0000000000..16a71d367d --- /dev/null +++ b/apps/openmw/mwgui/videowidget.hpp @@ -0,0 +1,39 @@ +#ifndef OPENMW_MWGUI_VIDEOWIDGET_H +#define OPENMW_MWGUI_VIDEOWIDGET_H + +#include + +#include "../mwrender/videoplayer.hpp" + +namespace MWGui +{ + + /** + * Widget that plays a video. Can be skipped by pressing Esc. + */ + class VideoWidget : public MyGUI::ImageBox + { + public: + MYGUI_RTTI_DERIVED(VideoWidget) + + VideoWidget(); + + void playVideo (const std::string& video, bool allowSkipping); + + int getVideoWidth(); + int getVideoHeight(); + + /// @return Is the video still playing? + bool update(); + + private: + bool mAllowSkipping; + + MWRender::VideoPlayer mPlayer; + + void onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char); + }; + +} + +#endif diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 1b71157a74..ca8459fa1e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "MyGUI_UString.h" #include "MyGUI_IPointer.h" @@ -59,6 +60,7 @@ #include "bookpage.hpp" #include "itemview.hpp" #include "fontloader.hpp" +#include "videowidget.hpp" namespace MWGui { @@ -104,6 +106,8 @@ namespace MWGui , mRecharge(NULL) , mRepair(NULL) , mCompanionWindow(NULL) + , mVideoBackground(NULL) + , mVideoWidget(NULL) , mTranslationDataStorage (translationDataStorage) , mCharGen(NULL) , mInputBlocker(NULL) @@ -155,6 +159,7 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); BookPage::registerMyGUIComponents (); ItemView::registerComponents(); @@ -186,6 +191,13 @@ namespace MWGui // hide mygui's pointer MyGUI::PointerManager::getInstance().setVisible(false); + + mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, + MyGUI::Align::Default, "Overlay"); + mVideoBackground->setImageTexture("black.png"); + mVideoBackground->setVisible(false); + + mVideoWidget = mVideoBackground->createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default); } void WindowManager::initUI() @@ -391,6 +403,7 @@ namespace MWGui mCompanionWindow->setVisible(false); mInventoryWindow->setTrading(false); mRecharge->setVisible(false); + mVideoBackground->setVisible(false); mHud->setVisible(mHudEnabled); @@ -539,10 +552,6 @@ namespace MWGui setCursorVisible(false); break; - case GM_Video: - setCursorVisible(false); - mHud->setVisible(false); - break; default: // Unsupported mode, switch back to game break; @@ -894,6 +903,7 @@ namespace MWGui void WindowManager::windowResized(int x, int y) { + sizeVideo(x, y); mGuiManager->windowResized(); mLoadingScreen->onResChange (x,y); if (!mHud) @@ -1401,4 +1411,57 @@ namespace MWGui mMap->readRecord(reader, type); } + void WindowManager::playVideo(const std::string &name, bool allowSkipping) + { + mVideoWidget->playVideo("video\\" + name, allowSkipping); + + // Turn off all rendering except for the GUI + mRendering->getScene()->clearSpecialCaseRenderQueues(); + // SCRQM_INCLUDE with RENDER_QUEUE_OVERLAY does not work? + for(int i = 0;i < Ogre::RENDER_QUEUE_MAX;++i) + { + if(i > 0 && i < 96) + mRendering->getScene()->addSpecialCaseRenderQueue(i); + } + mRendering->getScene()->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); + + MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); + sizeVideo(screenSize.width, screenSize.height); + + setKeyFocusWidget(mVideoWidget); + + mVideoBackground->setVisible(true); + + bool cursorWasVisible = mCursorVisible; + setCursorVisible(false); + + while (mVideoWidget->update()) + { + MWBase::Environment::get().getInputManager()->update(0, false); + + mRendering->getWindow()->update(); + } + + setCursorVisible(cursorWasVisible); + + // Restore normal rendering + mRendering->getScene()->clearSpecialCaseRenderQueues(); + mRendering->getScene()->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); + + mVideoBackground->setVisible(false); + } + + void WindowManager::sizeVideo(int screenWidth, int screenHeight) + { + // Use black bars to correct aspect ratio + mVideoBackground->setSize(screenWidth, screenHeight); + + double imageaspect = static_cast(mVideoWidget->getVideoWidth())/mVideoWidget->getVideoHeight(); + + int leftPadding = std::max(0.0, (screenWidth - screenHeight * imageaspect) / 2); + int topPadding = std::max(0.0, (screenHeight - screenWidth / imageaspect) / 2); + + mVideoWidget->setCoord(leftPadding, topPadding, + screenWidth - leftPadding*2, screenHeight - topPadding*2); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index dafb65e47b..ab9770a41b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -18,6 +18,7 @@ namespace MyGUI class Widget; class Window; class UString; + class ImageBox; } namespace Compiler @@ -80,6 +81,7 @@ namespace MWGui class SoulgemDialog; class Recharge; class CompanionWindow; + class VideoWidget; class WindowManager : public MWBase::WindowManager { @@ -98,6 +100,10 @@ namespace MWGui virtual Loading::Listener* getLoadingScreen(); + /// @note This method will block until the video finishes playing + /// (and will continually update the window while doing so) + virtual void playVideo(const std::string& name, bool allowSkipping); + /** * Should be called each frame to update windows/gui elements. * This could mean updating sizes of gui elements or opening @@ -332,6 +338,8 @@ namespace MWGui Repair* mRepair; Recharge* mRecharge; CompanionWindow* mCompanionWindow; + MyGUI::ImageBox* mVideoBackground; + VideoWidget* mVideoWidget; Translation::Storage& mTranslationDataStorage; Cursor* mSoftwareCursor; @@ -390,6 +398,8 @@ namespace MWGui void onCursorChange(const std::string& name); void onKeyFocusChanged(MyGUI::Widget* widget); + + void sizeVideo(int screenWidth, int screenHeight); }; } diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 4bfd3f4651..840a3f71a9 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -626,9 +626,7 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().isModalAny()) return; - if (MWBase::Environment::get().getWindowManager()->isGuiMode () && MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_Video) - MWBase::Environment::get().getWorld ()->stopVideo (); - else if (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)) + if (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)) { MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getSoundManager()->resumeSounds (MWBase::SoundManager::Play_TypeSfx); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 15d56b8a99..fa7b17a7c1 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -43,7 +43,6 @@ #include "water.hpp" #include "npcanimation.hpp" #include "globalmap.hpp" -#include "videoplayer.hpp" #include "terrainstorage.hpp" #include "effectmanager.hpp" @@ -171,9 +170,6 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); - mVideoPlayer = new VideoPlayer(mRendering.getScene (), mRendering.getWindow()); - mVideoPlayer->setResolution (Settings::Manager::getInt ("resolution x", "Video"), Settings::Manager::getInt ("resolution y", "Video")); - mSun = 0; mDebugging = new Debugging(mRootNode, engine); @@ -197,7 +193,6 @@ RenderingManager::~RenderingManager () delete mLocalMap; delete mOcclusionQuery; delete mWater; - delete mVideoPlayer; delete mActors; delete mObjects; delete mEffectManager; @@ -333,8 +328,6 @@ void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) void RenderingManager::update (float duration, bool paused) { - mVideoPlayer->update (); - if (MWBase::Environment::get().getStateManager()->getState()== MWBase::StateManager::State_NoGame) return; @@ -884,8 +877,6 @@ void RenderingManager::windowResized(int x, int y) Settings::Manager::setInt("resolution y", "Video", y); mRendering.adjustViewport(); - mVideoPlayer->setResolution (x, y); - MWBase::Environment::get().getWindowManager()->windowResized(x,y); } @@ -1001,16 +992,6 @@ void RenderingManager::screenshot(Image &image, int w, int h) mRendering.getCamera()->setAspectRatio(oldAspect); } -void RenderingManager::playVideo(const std::string& name, bool allowSkipping) -{ - mVideoPlayer->playVideo ("video/" + name, allowSkipping); -} - -void RenderingManager::stopVideo() -{ - mVideoPlayer->stopVideo (); -} - void RenderingManager::addWaterRippleEmitter (const MWWorld::Ptr& ptr, float scale, float force) { mWater->addEmitter (ptr, scale, force); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 115a94786e..423a7078ab 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -46,7 +46,6 @@ namespace MWRender class LocalMap; class Water; class GlobalMap; - class VideoPlayer; class Animation; class EffectManager; @@ -209,8 +208,6 @@ public: Animation* getAnimation(const MWWorld::Ptr &ptr); - void playVideo(const std::string& name, bool allowSkipping); - void stopVideo(); void frameStarted(float dt, bool paused); void screenshot(Ogre::Image& image, int w, int h); @@ -271,8 +268,6 @@ private: MWRender::LocalMap* mLocalMap; MWRender::Shadows* mShadows; - - VideoPlayer* mVideoPlayer; }; } diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 7abc8140a0..80704ca7c9 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -17,7 +17,6 @@ #include -#include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwsound/sound_decoder.hpp" @@ -126,7 +125,7 @@ struct VideoState { int stream_open(int stream_index, AVFormatContext *pFormatCtx); - bool update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height); + bool update(); static void video_thread_loop(VideoState *is); static void decode_thread_loop(VideoState *is); @@ -163,6 +162,7 @@ struct VideoState { static int OgreResource_Write(void *user_data, uint8_t *buf, int buf_size); static int64_t OgreResource_Seek(void *user_data, int64_t offset, int whence); + Ogre::TexturePtr mTexture; Ogre::DataStreamPtr stream; AVFormatContext* format_ctx; @@ -599,22 +599,17 @@ void VideoState::video_display() if((*this->video_st)->codec->width != 0 && (*this->video_st)->codec->height != 0) { - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().getByName("VideoTexture"); - if(texture.isNull() || static_cast(texture->getWidth()) != (*this->video_st)->codec->width - || static_cast(texture->getHeight()) != (*this->video_st)->codec->height) + + if(static_cast(mTexture->getWidth()) != (*this->video_st)->codec->width || + static_cast(mTexture->getHeight()) != (*this->video_st)->codec->height) { - Ogre::TextureManager::getSingleton ().remove ("VideoTexture"); - texture = Ogre::TextureManager::getSingleton().createManual( - "VideoTexture", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - (*this->video_st)->codec->width, (*this->video_st)->codec->height, - 0, - Ogre::PF_BYTE_RGBA, - Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); + mTexture->unload(); + mTexture->setWidth((*this->video_st)->codec->width); + mTexture->setHeight((*this->video_st)->codec->height); + mTexture->createInternalResources(); } Ogre::PixelBox pb((*this->video_st)->codec->width, (*this->video_st)->codec->height, 1, Ogre::PF_BYTE_RGBA, &vp->data[0]); - Ogre::HardwarePixelBufferSharedPtr buffer = texture->getBuffer(); + Ogre::HardwarePixelBufferSharedPtr buffer = mTexture->getBuffer(); buffer->blitFromMemory(pb); this->display_ready = true; } @@ -851,7 +846,7 @@ void VideoState::decode_thread_loop(VideoState *self) } -bool VideoState::update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int screen_width, int screen_height) +bool VideoState::update() { if(this->quit) return false; @@ -860,21 +855,6 @@ bool VideoState::update(Ogre::MaterialPtr &mat, Ogre::Rectangle2D *rect, int scr { this->refresh = false; this->video_refresh_timer(); - // Would be nice not to do this all the time... - if(this->display_ready) - mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("VideoTexture"); - - // Correct aspect ratio by adding black bars - double videoaspect = av_q2d((*this->video_st)->codec->sample_aspect_ratio); - if(videoaspect == 0.0) - videoaspect = 1.0; - videoaspect *= static_cast((*this->video_st)->codec->width) / (*this->video_st)->codec->height; - - double screenaspect = static_cast(screen_width) / screen_height; - double aspect_correction = videoaspect / screenaspect; - - rect->setCorners(std::max(-1.0, -1.0 * aspect_correction), std::min( 1.0, 1.0 / aspect_correction), - std::min( 1.0, 1.0 * aspect_correction), std::max(-1.0, -1.0 / aspect_correction)); } return true; } @@ -1001,8 +981,29 @@ void VideoState::init(const std::string& resourceName) if(audio_index >= 0) this->stream_open(audio_index, this->format_ctx); if(video_index >= 0) + { this->stream_open(video_index, this->format_ctx); + int width = (*this->video_st)->codec->width; + int height = (*this->video_st)->codec->height; + static int i = 0; + this->mTexture = Ogre::TextureManager::getSingleton().createManual( + "OpenMW/VideoTexture" + Ogre::StringConverter::toString(++i), + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + width, height, // TEST + 0, + Ogre::PF_BYTE_RGBA, + Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); + + // initialize to (0,0,0,0) + std::vector buffer; + buffer.resize(width * height, 0); + Ogre::PixelBox pb(width, height, 1, Ogre::PF_BYTE_RGBA, &buffer[0]); + this->mTexture->getBuffer()->blitFromMemory(pb); + } + + this->parse_thread = boost::thread(decode_thread_loop, this); } @@ -1073,111 +1074,26 @@ public: #endif // defined OPENMW_USE_FFMPEG -VideoPlayer::VideoPlayer(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* window) +VideoPlayer::VideoPlayer() : mState(NULL) - , mSceneMgr(sceneMgr) - , mRectangle(NULL) - , mNode(NULL) - , mAllowSkipping(false) - , mWindow(window) - , mWidth(0) - , mHeight(0) { - mVideoMaterial = Ogre::MaterialManager::getSingleton().getByName("VideoMaterial", "General"); - if (mVideoMaterial.isNull ()) - { - mVideoMaterial = Ogre::MaterialManager::getSingleton().create("VideoMaterial", "General"); - mVideoMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); - mVideoMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); - mVideoMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); - mVideoMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(); - mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); - } - mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("black.png"); - Ogre::MaterialPtr blackMaterial = Ogre::MaterialManager::getSingleton().getByName("BlackBarsMaterial", "General"); - if (blackMaterial.isNull ()) - { - blackMaterial = Ogre::MaterialManager::getSingleton().create("BlackBarsMaterial", "General"); - blackMaterial->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); - blackMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); - blackMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); - blackMaterial->getTechnique(0)->getPass(0)->createTextureUnitState()->setTextureName("black.png"); - } - - mRectangle = new Ogre::Rectangle2D(true); - mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0); - mRectangle->setMaterial("VideoMaterial"); - mRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY+2); - mBackgroundRectangle = new Ogre::Rectangle2D(true); - mBackgroundRectangle->setCorners(-1.0, 1.0, 1.0, -1.0); - mBackgroundRectangle->setMaterial("BlackBarsMaterial"); - mBackgroundRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY+1); - - // Use infinite AAB to always stay visible - Ogre::AxisAlignedBox aabInf; - aabInf.setInfinite(); - mRectangle->setBoundingBox(aabInf); - mBackgroundRectangle->setBoundingBox(aabInf); - - // Attach background to the scene - mNode = sceneMgr->getRootSceneNode()->createChildSceneNode(); - mNode->attachObject(mRectangle); - mBackgroundNode = sceneMgr->getRootSceneNode()->createChildSceneNode(); - mBackgroundNode->attachObject(mBackgroundRectangle); - - mRectangle->setVisible(false); - mRectangle->setVisibilityFlags(RV_Overlay); - mBackgroundRectangle->setVisible(false); - mBackgroundRectangle->setVisibilityFlags(RV_Overlay); } VideoPlayer::~VideoPlayer() { if(mState) close(); - - mSceneMgr->destroySceneNode(mNode); - mSceneMgr->destroySceneNode(mBackgroundNode); - - delete mRectangle; - delete mBackgroundRectangle; } -void VideoPlayer::playVideo(const std::string &resourceName, bool allowSkipping) +void VideoPlayer::playVideo(const std::string &resourceName) { - mAllowSkipping = allowSkipping; - if(mState) close(); - mRectangle->setVisible(true); - mBackgroundRectangle->setVisible(true); - mVideoMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("black.png"); - - MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Video); - - // Turn off rendering except the GUI - mSceneMgr->clearSpecialCaseRenderQueues(); - // SCRQM_INCLUDE with RENDER_QUEUE_OVERLAY does not work. - for(int i = 0;i < Ogre::RENDER_QUEUE_MAX;++i) - { - if(i > 0 && i < 96) - mSceneMgr->addSpecialCaseRenderQueue(i); - } - mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); - try { mState = new VideoState; mState->init(resourceName); - - while (isPlaying()) - { - MWBase::Environment::get().getInputManager()->update(0, false); - update(); - mWindow->update(); - } - } catch(std::exception& e) { std::cerr<< "Failed to play video: "<update(mVideoMaterial, mRectangle, mWidth, mHeight)) + if(!mState->update()) close(); } } +std::string VideoPlayer::getTextureName() +{ + std::string name; + if (mState) + name = mState->mTexture->getName(); + return name; +} + +int VideoPlayer::getVideoWidth() +{ + int width=0; + if (mState) + width = mState->mTexture->getWidth(); + return width; +} + +int VideoPlayer::getVideoHeight() +{ + int height=0; + if (mState) + height = mState->mTexture->getHeight(); + return height; +} + void VideoPlayer::stopVideo () { - if (mAllowSkipping) - close(); + close(); } void VideoPlayer::close() @@ -1211,13 +1150,6 @@ void VideoPlayer::close() } MWBase::Environment::get().getSoundManager()->resumeSounds(); - - mRectangle->setVisible(false); - mBackgroundRectangle->setVisible(false); - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Video); - - mSceneMgr->clearSpecialCaseRenderQueues(); - mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); } bool VideoPlayer::isPlaying () diff --git a/apps/openmw/mwrender/videoplayer.hpp b/apps/openmw/mwrender/videoplayer.hpp index 0e548e23e4..47e252cc15 100644 --- a/apps/openmw/mwrender/videoplayer.hpp +++ b/apps/openmw/mwrender/videoplayer.hpp @@ -1,27 +1,22 @@ #ifndef VIDEOPLAYER_H #define VIDEOPLAYER_H -#include - -namespace Ogre -{ - class SceneManager; - class SceneNode; - class Rectangle2D; - class RenderWindow; -} +#include namespace MWRender { struct VideoState; + /** + * @brief Plays a video on an Ogre texture. + */ class VideoPlayer { public: - VideoPlayer(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* window); + VideoPlayer(); ~VideoPlayer(); - void playVideo (const std::string& resourceName, bool allowSkipping); + void playVideo (const std::string& resourceName); void update(); @@ -30,22 +25,14 @@ namespace MWRender bool isPlaying(); - void setResolution (int w, int h) { mWidth = w; mHeight = h; } + std::string getTextureName(); + int getVideoWidth(); + int getVideoHeight(); private: VideoState* mState; - bool mAllowSkipping; - - Ogre::SceneManager* mSceneMgr; - Ogre::MaterialPtr mVideoMaterial; - Ogre::Rectangle2D* mRectangle; - Ogre::Rectangle2D* mBackgroundRectangle; - Ogre::SceneNode* mNode; - Ogre::SceneNode* mBackgroundNode; - Ogre::RenderWindow* mWindow; - int mWidth; int mHeight; }; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index fa8441aa57..89a5ceec1e 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -47,7 +47,7 @@ namespace MWScript bool allowSkipping = runtime[0].mInteger; runtime.pop(); - MWBase::Environment::get().getWorld ()->playVideo (name, allowSkipping); + MWBase::Environment::get().getWindowManager()->playVideo (name, allowSkipping); } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 62bdd38ea1..f884f69af2 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1305,7 +1305,7 @@ namespace MWWorld { --mPlayIntro; if (mPlayIntro == 0) - mRendering->playVideo(mFallback.getFallbackString("Movies_New_Game"), true); + MWBase::Environment::get().getWindowManager()->playVideo(mFallback.getFallbackString("Movies_New_Game"), true); } if (mGoToJail && !paused) @@ -1776,16 +1776,6 @@ namespace MWWorld return mRendering->getAnimation(ptr); } - void World::playVideo (const std::string &name, bool allowSkipping) - { - mRendering->playVideo(name, allowSkipping); - } - - void World::stopVideo () - { - mRendering->stopVideo(); - } - void World::frameStarted (float dt, bool paused) { mRendering->frameStarted(dt, paused); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 42f52cb619..f1e89bf6bc 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -523,8 +523,6 @@ namespace MWWorld virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr); /// \todo this does not belong here - virtual void playVideo(const std::string& name, bool allowSkipping); - virtual void stopVideo(); virtual void frameStarted (float dt, bool paused); virtual void screenshot (Ogre::Image& image, int w, int h); diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index 8bd95e2cd3..6f77369949 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -9,5 +9,6 @@ + From f2cd37edd0ee8903f0d4f2bd53214bf16a7290dd Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 27 Mar 2014 19:51:48 +0100 Subject: [PATCH 018/135] Play company logo movie --- apps/openmw/engine.cpp | 18 ++++++------------ apps/openmw/mwbase/inputmanager.hpp | 2 +- apps/openmw/mwgui/loadingscreen.cpp | 2 +- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- apps/openmw/mwinput/inputmanagerimp.cpp | 16 ++++++++-------- apps/openmw/mwinput/inputmanagerimp.hpp | 2 +- files/mygui/openmw_layers.xml | 2 +- 7 files changed, 19 insertions(+), 25 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index a118152edd..f02432ba86 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -362,14 +362,9 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create sound system mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound)); - // TODO: play pre-load intro videos. Need to find a way to have them receive input. - // Make videoplayer a MyGUI widget? - /* - { - MWRender::VideoPlayer player(mOgre->getScene(), mOgre->getWindow()); - player.playVideo("mw_logo.bik", 1); - } - */ + std::string logo = mFallbackMap["Movies_Company_Logo"]; + if (!logo.empty()) + window->playVideo(logo, 1); // Create the world mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mContentFiles, @@ -453,10 +448,9 @@ void OMW::Engine::go() // Is there an ini setting for this filename or something? MWBase::Environment::get().getSoundManager()->streamMusic("Special/morrowind title.mp3"); - // TODO: there are other intro videos, too. They need to be imported from Morrowind.ini. - // Unfortunately those must play BEFORE any loading is done, which will currently not work. - // The videoplayer is created by World, so all content files must be loaded first... - MWBase::Environment::get().getWindowManager()->playVideo("mw_logo.bik", true); + std::string logo = mFallbackMap["Movies_Morrowind_Logo"]; + if (!logo.empty()) + MWBase::Environment::get().getWindowManager()->playVideo(logo, true); } catch (...) {} } diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 42922a5b3d..d44da4974d 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -25,7 +25,7 @@ namespace MWBase virtual ~InputManager() {} - virtual void update(float dt, bool loading) = 0; + virtual void update(float dt, bool disableControls, bool disableEvents=false) = 0; virtual void changeInputMode(bool guiMode) = 0; diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 37e29591b8..b3f70a5ab0 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -226,7 +226,7 @@ namespace MWGui } mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); - MWBase::Environment::get().getInputManager()->update(0, true); + MWBase::Environment::get().getInputManager()->update(0, true, true); // First, swap buffers from last draw, then, queue an update of the // window contents, but don't swap buffers (which would have diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index ca8459fa1e..1e019aaa95 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1437,7 +1437,7 @@ namespace MWGui while (mVideoWidget->update()) { - MWBase::Environment::get().getInputManager()->update(0, false); + MWBase::Environment::get().getInputManager()->update(0, true, false); mRendering->getWindow()->update(); } diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 840a3f71a9..e2d4f8cb2d 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -96,12 +96,12 @@ namespace MWInput : mOgre(ogre) , mPlayer(NULL) , mEngine(engine) - , mMouseLookEnabled(true) + , mMouseLookEnabled(false) , mMouseX(ogre.getWindow()->getWidth ()/2.f) , mMouseY(ogre.getWindow()->getHeight ()/2.f) , mMouseWheel(0) , mDragDrop(false) - , mGuiCursorEnabled(false) + , mGuiCursorEnabled(true) , mUserFile(userFile) , mUserFileExists(userFileExists) , mInvertY (Settings::Manager::getBool("invert y axis", "Input")) @@ -256,18 +256,21 @@ namespace MWInput } } - void InputManager::update(float dt, bool loading) + void InputManager::update(float dt, bool disableControls, bool disableEvents) { mInputManager->setMouseVisible(MWBase::Environment::get().getWindowManager()->getCursorVisible()); - mInputManager->capture(loading); + mInputManager->capture(disableEvents); // inject some fake mouse movement to force updating MyGUI's widget states MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel); // update values of channels (as a result of pressed keys) - if (!loading) + if (!disableControls) mInputBinder->update(dt); + if (disableControls) + return; + bool grab = !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Console; @@ -288,9 +291,6 @@ namespace MWInput mInputManager->warpMouse(mMouseX, mMouseY); } - if (loading) - return; - // Disable movement in Gui mode if (MWBase::Environment::get().getWindowManager()->isGuiMode() || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 2eab03a34e..87fbda25cd 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -68,7 +68,7 @@ namespace MWInput /// Clear all savegame-specific data virtual void clear(); - virtual void update(float dt, bool loading); + virtual void update(float dt, bool disableControls, bool disableEvents=false); void setPlayer (MWWorld::Player* player) { mPlayer = player; } diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index 6f77369949..e66f3fc017 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -9,6 +9,6 @@ - + From 4f852daf45cfd9ee5e199edd25b924909e08f103 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 27 Mar 2014 19:59:33 +0100 Subject: [PATCH 019/135] Don't play company logo when using --skip-menu --- apps/openmw/engine.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f02432ba86..508b195e92 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -362,9 +362,12 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create sound system mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound)); - std::string logo = mFallbackMap["Movies_Company_Logo"]; - if (!logo.empty()) - window->playVideo(logo, 1); + if (!mSkipMenu) + { + std::string logo = mFallbackMap["Movies_Company_Logo"]; + if (!logo.empty()) + window->playVideo(logo, 1); + } // Create the world mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mContentFiles, From ea357cfed086154766808d54da9d62db9da9eb14 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 27 Mar 2014 20:39:56 +0100 Subject: [PATCH 020/135] Fix crash when resizing while company logo is playing --- libs/openengine/ogre/renderer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index c816f2060c..5f95789884 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -157,5 +157,6 @@ void OgreRenderer::setFov(float fov) void OgreRenderer::windowResized(int x, int y) { - mWindowListener->windowResized(x,y); + if (mWindowListener) + mWindowListener->windowResized(x,y); } From d7df9cae21dd4946901f01769a5f4f702cc01d09 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 27 Mar 2014 22:32:42 +0100 Subject: [PATCH 021/135] Bug #1224: Changed fix to potentially allow for language independent saves --- apps/openmw/mwgui/savegamedialog.cpp | 16 +++++++++++++++- apps/openmw/mwstate/statemanagerimp.cpp | 7 ++++++- apps/openmw/mwworld/store.hpp | 8 ++++++++ components/esm/savedgame.cpp | 12 ++++++++++-- components/esm/savedgame.hpp | 11 ++++++----- 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 6971358c17..caa082646e 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -86,7 +86,21 @@ namespace MWGui { std::stringstream title; title << it->getSignature().mPlayerName; - title << " (Level " << it->getSignature().mPlayerLevel << " " << it->getSignature().mPlayerClassName << ")"; + + // For a custom class, we will not find it in the store (unless we loaded the savegame first). + // Fall back to name stored in savegame header in that case. + std::string className; + if (it->getSignature().mPlayerClassId.empty()) + className = it->getSignature().mPlayerClassName; + else + { + // Find the localised name for this class from the store + const ESM::Class* class_ = MWBase::Environment::get().getWorld()->getStore().get().find( + it->getSignature().mPlayerClassId); + className = class_->mName; + } + + title << " (Level " << it->getSignature().mPlayerLevel << " " << className << ")"; mCharacterSelection->addItem (title.str()); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index a0d482f0a0..7793cc9608 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -155,7 +155,12 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot profile.mPlayerName = player.getClass().getName (player); profile.mPlayerLevel = player.getClass().getNpcStats (player).getLevel(); - profile.mPlayerClassName = world.getStore().get().find(player.get()->mBase->mClass)->mName; + + std::string classId = player.get()->mBase->mClass; + if (world.getStore().get().isDynamic(classId)) + profile.mPlayerClassName = world.getStore().get().find(classId)->mName; + else + profile.mPlayerClassId = classId; profile.mPlayerCell = world.getCellName(); diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 6b99c0a0c4..0fc2d547cb 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -157,6 +157,14 @@ namespace MWWorld return 0; } + /** + * Does the record with this ID come from the dynamic store? + */ + bool isDynamic(const std::string &id) const { + typename Dynamic::const_iterator dit = mDynamic.find(id); + return (dit != mDynamic.end()); + } + /** Returns a random record that starts with the named ID, or NULL if not found. */ const T *searchRandom(const std::string &id) const { diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 813865e253..b5e0810dbc 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -11,7 +11,10 @@ void ESM::SavedGame::load (ESMReader &esm) { mPlayerName = esm.getHNString("PLNA"); esm.getHNOT (mPlayerLevel, "PLLE"); - mPlayerClassName = esm.getHNString("PLCL"); + + mPlayerClassId = esm.getHNOString("PLCL"); + mPlayerClassName = esm.getHNOString("PLCN"); + mPlayerCell = esm.getHNString("PLCE"); esm.getHNT (mInGameTime, "TSTM", 16); esm.getHNT (mTimePlayed, "TIME"); @@ -30,7 +33,12 @@ void ESM::SavedGame::save (ESMWriter &esm) const { esm.writeHNString ("PLNA", mPlayerName); esm.writeHNT ("PLLE", mPlayerLevel); - esm.writeHNString ("PLCL", mPlayerClassName); + + if (!mPlayerClassId.empty()) + esm.writeHNString ("PLCL", mPlayerClassId); + else + esm.writeHNString ("PLCN", mPlayerClassName); + esm.writeHNString ("PLCE", mPlayerCell); esm.writeHNT ("TSTM", mInGameTime, 16); esm.writeHNT ("TIME", mTimePlayed); diff --git a/components/esm/savedgame.hpp b/components/esm/savedgame.hpp index b8615a6bc4..3e7cae775a 100644 --- a/components/esm/savedgame.hpp +++ b/components/esm/savedgame.hpp @@ -26,12 +26,13 @@ namespace ESM std::vector mContentFiles; std::string mPlayerName; int mPlayerLevel; - // The (translated) name of the player class. So it will be displayed in the MW language - // the savegame was made in, not the currently running language of MW. - // However, savegames from a different MW language are not compatible anyway. - // And if only the ID was stored here, we would need to - // peek into the savegame to look for a class record in case it is a custom class. + + // ID of class + std::string mPlayerClassId; + // Name of the class. When using a custom class, the ID is not really meaningful prior + // to loading the savegame, so the name is stored separately. std::string mPlayerClassName; + std::string mPlayerCell; TimeStamp mInGameTime; double mTimePlayed; From 90a813ad2cf1f55a54b018ebe1ad80d6007c49a5 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 24 Mar 2014 23:20:25 +1100 Subject: [PATCH 022/135] Allow interrupting a walking NPC to trigger a greeting. --- apps/openmw/mwmechanics/aiwander.cpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 2db875a01b..26975ff1e4 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -69,6 +69,7 @@ namespace MWMechanics return new AiWander(*this); } + // TODO: duration is passed in but never used, check if it is needed bool AiWander::execute (const MWWorld::Ptr& actor,float duration) { actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); @@ -102,20 +103,21 @@ namespace MWMechanics ESM::Position pos = actor.getRefData().getPosition(); + // Once off initialization to discover & store allowed node points for this actor. if(!mStoredAvailableNodes) { - mStoredAvailableNodes = true; mPathgrid = world->getStore().get().search(*actor.getCell()->getCell()); mCellX = actor.getCell()->getCell()->mData.mX; mCellY = actor.getCell()->getCell()->mData.mY; + // TODO: If there is no path does this actor get stuck forever? if(!mPathgrid) mDistance = 0; else if(mPathgrid->mPoints.empty()) mDistance = 0; - if(mDistance) + if(mDistance) // A distance value is initially passed into the constructor. { mXCell = 0; mYCell = 0; @@ -151,10 +153,13 @@ namespace MWMechanics } mCurrentNode = mAllowedNodes[index]; mAllowedNodes.erase(mAllowedNodes.begin() + index); + + mStoredAvailableNodes = true; // set only if successful in finding allowed nodes } } } + // TODO: Does this actor stay in one spot forever while in AiWander? if(mAllowedNodes.empty()) mDistance = 0; @@ -162,7 +167,7 @@ namespace MWMechanics if(mDistance && (mCellX != actor.getCell()->getCell()->mData.mX || mCellY != actor.getCell()->getCell()->mData.mY)) mDistance = 0; - if(mChooseAction) + if(mChooseAction) // Initially set true by the constructor. { mPlayedIdle = 0; unsigned short idleRoll = 0; @@ -208,7 +213,8 @@ namespace MWMechanics } } - if(mIdleNow) + // Allow interrupting a walking actor to trigger a greeting + if(mIdleNow || (mWalking && (mWalkState != State_Norm))) { // Play a random voice greeting if the player gets too close const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -222,6 +228,14 @@ namespace MWMechanics float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).distance( Ogre::Vector3(actor.getRefData().getPosition().pos)); + if(mWalking && playerDist <= helloDistance) + { + stopWalking(actor); + mMoveNow = false; + mWalking = false; + mWalkState = State_Norm; + } + if (!mSaidGreeting) { // TODO: check if actor is aware / has line of sight @@ -353,7 +367,7 @@ namespace MWMechanics if(mWalkState == State_Evade) { - //std::cout << "Stuck \""<= COUNT_BEFORE_RESET) // something has gone wrong, reset { - //std::cout << "Reset \""< Date: Thu, 27 Mar 2014 08:37:05 +1100 Subject: [PATCH 023/135] Prevent NPC suicides off silt the strider platform in Seyda Neen. Added some comments as well. There may be opportunities for some optimization but left that out for now. --- apps/openmw/mwmechanics/pathfinding.cpp | 208 +++++++++++------------- 1 file changed, 98 insertions(+), 110 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 3ecd407431..c0b78f3c77 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -57,97 +57,6 @@ namespace return closestIndex; } - /*std::list reconstructPath(const std::vector& graph,const ESM::Pathgrid* pathgrid, int lastNode,float xCell, float yCell) - { - std::list path; - while(graph[lastNode].parent != -1) - { - //std::cout << "not empty" << xCell; - ESM::Pathgrid::Point pt = pathgrid->mPoints[lastNode]; - pt.mX += xCell; - pt.mY += yCell; - path.push_front(pt); - lastNode = graph[lastNode].parent; - } - return path; - }*/ - - - - /*std::list buildPath2(const ESM::Pathgrid* pathgrid,int start,int goal,float xCell = 0, float yCell = 0) - { - std::vector graph; - for(unsigned int i = 0; i < pathgrid->mPoints.size(); i++) - { - Node node; - node.label = i; - node.parent = -1; - graph.push_back(node); - } - for(unsigned int i = 0; i < pathgrid->mEdges.size(); i++) - { - Edge edge; - edge.destination = pathgrid->mEdges[i].mV1; - edge.cost = distance(pathgrid->mPoints[pathgrid->mEdges[i].mV0],pathgrid->mPoints[pathgrid->mEdges[i].mV1]); - graph[pathgrid->mEdges[i].mV0].edges.push_back(edge); - edge.destination = pathgrid->mEdges[i].mV0; - graph[pathgrid->mEdges[i].mV1].edges.push_back(edge); - } - - std::vector g_score(pathgrid->mPoints.size(),-1.); - std::vector f_score(pathgrid->mPoints.size(),-1.); - - g_score[start] = 0; - f_score[start] = distance(pathgrid->mPoints[start],pathgrid->mPoints[goal]); - - std::list openset; - std::list closedset; - openset.push_back(start); - - int current = -1; - - while(!openset.empty()) - { - current = openset.front(); - openset.pop_front(); - - if(current == goal) break; - - closedset.push_back(current); - - for(int j = 0;jmPoints[dest],pathgrid->mPoints[goal]); - if(!isInOpenSet) - { - std::list::iterator it = openset.begin(); - for(it = openset.begin();it!= openset.end();it++) - { - if(g_score[*it]>g_score[dest]) - break; - } - openset.insert(it,dest); - } - } - } - } - - } - return reconstructPath(graph,pathgrid,current,xCell,yCell); - - }*/ - } namespace MWMechanics @@ -166,37 +75,83 @@ namespace MWMechanics mIsPathConstructed = false; } + /* + * NOTE: based on buildPath2(), please check git history if interested + * + * Populate mGraph with the cost of each allowed edge (measured in distance ^2) + * Any existing data in mGraph is wiped clean first. The node's parent is + * set with initial value of -1. The parent values are populated by aStarSearch(). + * mGSore and mFScore are also resized. + * + * + * mGraph[f].edges[n].destination = t + * + * f = point index of location "from" + * t = point index of location "to" + * n = index of edges from point f + * + * + * Example: (note from p(0) to p(2) not allowed) + * + * mGraph[0].edges[0].destination = 1 + * .edges[1].destination = 3 + * + * mGraph[1].edges[0].destination = 0 + * .edges[1].destination = 2 + * .edges[2].destination = 3 + * + * mGraph[2].edges[0].destination = 1 + * + * (etc, etc) + * + * + * low + * cost + * p(0) <---> p(1) <------------> p(2) + * ^ ^ + * | | + * | +-----> p(3) + * +----------------> + * high cost + */ void PathFinder::buildPathgridGraph(const ESM::Pathgrid* pathGrid) { mGraph.clear(); - mGScore.resize(pathGrid->mPoints.size(),-1); - mFScore.resize(pathGrid->mPoints.size(),-1); + // resize lists + mGScore.resize(pathGrid->mPoints.size(), -1); + mFScore.resize(pathGrid->mPoints.size(), -1); Node defaultNode; defaultNode.label = -1; defaultNode.parent = -1; mGraph.resize(pathGrid->mPoints.size(),defaultNode); + // initialise mGraph for(unsigned int i = 0; i < pathGrid->mPoints.size(); i++) { Node node; node.label = i; node.parent = -1; - mGraph[i] = node; + mGraph[i] = node; // TODO: old code used push_back(node), check if any difference } + // store the costs (measured in distance ^2) of each edge, in both directions for(unsigned int i = 0; i < pathGrid->mEdges.size(); i++) { Edge edge; + edge.cost = distance(pathGrid->mPoints[pathGrid->mEdges[i].mV0], + pathGrid->mPoints[pathGrid->mEdges[i].mV1]); + // forward path of the edge edge.destination = pathGrid->mEdges[i].mV1; - edge.cost = distance(pathGrid->mPoints[pathGrid->mEdges[i].mV0],pathGrid->mPoints[pathGrid->mEdges[i].mV1]); mGraph[pathGrid->mEdges[i].mV0].edges.push_back(edge); - edge.destination = pathGrid->mEdges[i].mV0; - mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); + // reverse path of the edge + // NOTE: These are redundant, ESM already contains the required reverse paths + //edge.destination = pathGrid->mEdges[i].mV0; + //mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); } mIsGraphConstructed = true; } void PathFinder::cleanUpAStar() { - for(int i=0;i (mGraph.size());i++) + for(int i = 0; i < static_cast (mGraph.size()); i++) { mGraph[i].parent = -1; mGScore[i] = -1; @@ -204,6 +159,25 @@ namespace MWMechanics } } + /* + * NOTE: based on buildPath2(), please check git history if interested + * + * Find the shortest path to the target goal using a well known algorithm. + * Uses mGraph which has pre-computed costs for allowed edges. It is assumed + * that mGraph is already constructed. The caller, i.e. buildPath(), needs + * to ensure this. + * + * Returns path (a list of pathgrid point indexes) which may be empty. + * + * openset - point indexes to be traversed, lowest cost at the front + * closedset - point indexes already traversed + * + * mGScore - past accumulated costs vector indexed by point index + * mFScore - future estimated costs vector indexed by point index + * these are resized by buildPathgridGraph() + * + * The heuristics used is distance^2 from current position to the final goal. + */ std::list PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell, float yCell) { cleanUpAStar(); @@ -218,18 +192,19 @@ namespace MWMechanics while(!openset.empty()) { - current = openset.front(); + current = openset.front(); // front has the lowest cost openset.pop_front(); if(current == goal) break; closedset.push_back(current); - for(int j = 0;j (mGraph[current].edges.size());j++) + // check all edges for the "current" point index + for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) { - //int next = mGraph[current].edges[j].destination if(std::find(closedset.begin(),closedset.end(),mGraph[current].edges[j].destination) == closedset.end()) { + // not in closedset - i.e. have not traversed this edge destination int dest = mGraph[current].edges[j].destination; float tentative_g = mGScore[current] + mGraph[current].edges[j].cost; bool isInOpenSet = std::find(openset.begin(),openset.end(),dest) != openset.end(); @@ -241,20 +216,22 @@ namespace MWMechanics mFScore[dest] = tentative_g + distance(pathGrid->mPoints[dest],pathGrid->mPoints[goal]); if(!isInOpenSet) { + // add this edge to openset, lowest cost goes to the front + // TODO: if this causes performance problems a hash table may help (apparently) std::list::iterator it = openset.begin(); for(it = openset.begin();it!= openset.end();it++) { - if(mGScore[*it]>mGScore[dest]) + if(mGScore[*it] > mGScore[dest]) break; } - openset.insert(it,dest); + openset.insert(it, dest); } } - } + } // if in closedset, i.e. traversed this edge already, try the next edge } - } + // reconstruct path to return std::list path; while(mGraph[current].parent != -1) { @@ -266,6 +243,9 @@ namespace MWMechanics current = mGraph[current].parent; } + // TODO: Is this a bug? If path is empty the destination is inserted. + // Commented out pending further testing. +#if 0 if(path.empty()) { ESM::Pathgrid::Point pt = pathGrid->mPoints[goal]; @@ -273,10 +253,19 @@ namespace MWMechanics pt.mY += yCell; path.push_front(pt); } - +#endif return path; } + /* + * NOTE: This method may fail to find a path. The caller must check the result before using it. + * If there is no path the AI routies need to implement some other heuristics to reach the target. + * + * Updates mPath using aStarSearch(). + * mPathConstructed is set true if successful, false if not + * + * May update mGraph by calling buildPathgridGraph() if it isn't constructed yet. + */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, const MWWorld::CellStore* cell, bool allowShortcuts) { @@ -310,11 +299,10 @@ namespace MWMechanics { if(!mIsGraphConstructed) buildPathgridGraph(pathGrid); - mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell);//findPath(startNode, endNode, mGraph); + mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell); if(!mPath.empty()) { - mPath.push_back(endPoint); mIsPathConstructed = true; } } @@ -331,8 +319,8 @@ namespace MWMechanics float PathFinder::getZAngleToNext(float x, float y) const { - // This should never happen (programmers should have an if statement checking mIsPathConstructed that prevents this call - // if otherwise). + // This should never happen (programmers should have an if statement checking + // mIsPathConstructed that prevents this call if otherwise). if(mPath.empty()) return 0.; From 07fd801d94c14d24d0615da7263063d2401bdb63 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 29 Mar 2014 19:28:30 +1100 Subject: [PATCH 024/135] My previous analysis of the pathfinding issue was incorrect. It was in fact caused due to some of the pathgrid points being unreachable. Instead of returning an empty path in such a scenario, incorrect path + requested destination were being returned. There was also a defect where past cost was being used for selecting open points. There is still an unresolved issue where mGraph and mSCComp are being rebuilt unnecessarily. The check mCell != cell in buildPath() is being triggered frequently. Not sure why. --- apps/openmw/mwmechanics/aiwander.cpp | 26 +- apps/openmw/mwmechanics/aiwander.hpp | 2 + apps/openmw/mwmechanics/pathfinding.cpp | 393 +++++++++++++++++++----- apps/openmw/mwmechanics/pathfinding.hpp | 34 +- 4 files changed, 378 insertions(+), 77 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 26975ff1e4..a3286b8c05 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -127,14 +127,18 @@ namespace MWMechanics mYCell = mCellY * ESM::Land::REAL_SIZE; } + // convert npcPos to local (i.e. cell) co-ordinates Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos); npcPos[0] = npcPos[0] - mXCell; npcPos[1] = npcPos[1] - mYCell; + // populate mAllowedNodes for this actor with pathgrid point indexes based on mDistance + // NOTE: mPoints and mAllowedNodes contain points in local co-ordinates for(unsigned int counter = 0; counter < mPathgrid->mPoints.size(); counter++) { - Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, mPathgrid->mPoints[counter].mY, - mPathgrid->mPoints[counter].mZ); + Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, + mPathgrid->mPoints[counter].mY, + mPathgrid->mPoints[counter].mZ); if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance) mAllowedNodes.push_back(mPathgrid->mPoints[counter]); } @@ -145,8 +149,9 @@ namespace MWMechanics unsigned int index = 0; for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) { - Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY, - mAllowedNodes[counterThree].mZ); + Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, + mAllowedNodes[counterThree].mY, + mAllowedNodes[counterThree].mZ); float tempDist = npcPos.squaredDistance(nodePos); if(tempDist < closestNode) index = counterThree; @@ -277,16 +282,24 @@ namespace MWMechanics dest.mY = destNodePos[1] + mYCell; dest.mZ = destNodePos[2]; + // actor position is already in world co-ordinates ESM::Pathgrid::Point start; start.mX = pos.pos[0]; start.mY = pos.pos[1]; start.mZ = pos.pos[2]; + // don't take shortcuts for wandering mPathFinder.buildPath(start, dest, actor.getCell(), false); if(mPathFinder.isPathConstructed()) { - // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): + // buildPath inserts dest in case it is not a pathgraph point index + // which is a duplicate for AiWander + //if(mPathFinder.getPathSize() > 1) + //mPathFinder.getPath().pop_back(); + + // Remove this node as an option and add back the previously used node + // (stops NPC from picking the same node): ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; mAllowedNodes.erase(mAllowedNodes.begin() + randNode); mAllowedNodes.push_back(mCurrentNode); @@ -377,7 +390,10 @@ namespace MWMechanics } else { + // normal walk forward actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + // turn towards the next point in mPath + // TODO: possibly no need to check every frame, maybe every 30 should be ok? zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 6de0b81813..48b67fb1cc 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -38,8 +38,10 @@ namespace MWMechanics float mY; float mZ; + // Cell location int mCellX; int mCellY; + // Cell location multiply by ESM::Land::REAL_SIZE to get the right scale float mXCell; float mYCell; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index c0b78f3c77..0eb7f07986 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -37,19 +37,75 @@ namespace return sqrt(x * x + y * y + z * z); } - int getClosestPoint(const ESM::Pathgrid* grid, float x, float y, float z) + // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html + // + // One of the smallest cost in Seyda Neen is between points 77 & 78: + // pt x y + // 77 = 8026, 4480 + // 78 = 7986, 4218 + // + // Euclidean distance is about 262 (ignoring z) and Manhattan distance is 300 + // (again ignoring z). Using a value of about 300 for D seems like a reasonable + // starting point for experiments. If in doubt, just use value 1. + // + // The distance between 3 & 4 are pretty small, too. + // 3 = 5435, 223 + // 4 = 5948, 193 + // + // Approx. 514 Euclidean distance and 533 Manhattan distance. + // + float manhattan(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) + { + return 300 * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ)); + } + + // Choose a heuristics - these may not be the best for directed graphs with + // non uniform edge costs. + // + // distance: + // - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2) + // - slower but more accurate + // + // Manhattan: + // - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z| + // - faster but not the shortest path + float costAStar(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) + { + //return distance(a, b); + return manhattan(a, b); + } + + // Slightly cheaper version for comparisons. + // Caller needs to be careful for very short distances (i.e. less than 1) + // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 + // + float distanceSquared(ESM::Pathgrid::Point point, Ogre::Vector3 pos) + { + return Ogre::Vector3(point.mX, point.mY, point.mZ).squaredDistance(pos); + } + + // Return the closest pathgrid point index from the specified position co + // -ordinates. NOTE: Does not check if there is a sensible way to get there + // (e.g. a cliff in front). + // + // NOTE: pos is expected to be in local co-ordinates, as is grid->mPoints + // + int getClosestPoint(const ESM::Pathgrid* grid, Ogre::Vector3 pos) { if(!grid || grid->mPoints.empty()) return -1; - float distanceBetween = distance(grid->mPoints[0], x, y, z); + float distanceBetween = distanceSquared(grid->mPoints[0], pos); int closestIndex = 0; + // TODO: if this full scan causes performance problems mapping pathgrid + // points to a quadtree may help for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++) { - if(distance(grid->mPoints[counter], x, y, z) < distanceBetween) + float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos); + if(potentialDistBetween < distanceBetween) { - distanceBetween = distance(grid->mPoints[counter], x, y, z); + distanceBetween = potentialDistBetween; closestIndex = counter; } } @@ -57,6 +113,39 @@ namespace return closestIndex; } + // Uses mSCComp to choose a reachable end pathgrid point. start is assumed reachable. + std::pair getClosestReachablePoint(const ESM::Pathgrid* grid, + Ogre::Vector3 pos, int start, std::vector &sCComp) + { + // assume grid is fine + int startGroup = sCComp[start]; + + float distanceBetween = distanceSquared(grid->mPoints[0], pos); + int closestIndex = 0; + int closestReachableIndex = 0; + // TODO: if this full scan causes performance problems mapping pathgrid + // points to a quadtree may help + for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++) + { + float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos); + if(potentialDistBetween < distanceBetween) + { + // found a closer one + distanceBetween = potentialDistBetween; + closestIndex = counter; + if (sCComp[counter] == startGroup) + { + closestReachableIndex = counter; + } + } + } + if(start == closestReachableIndex) + closestReachableIndex = -1; // couldn't find anyting other than start + + return std::pair + (closestReachableIndex, closestReachableIndex == closestIndex); + } + } namespace MWMechanics @@ -76,13 +165,13 @@ namespace MWMechanics } /* - * NOTE: based on buildPath2(), please check git history if interested + * NOTE: Based on buildPath2(), please check git history if interested * - * Populate mGraph with the cost of each allowed edge (measured in distance ^2) - * Any existing data in mGraph is wiped clean first. The node's parent is - * set with initial value of -1. The parent values are populated by aStarSearch(). - * mGSore and mFScore are also resized. + * Populate mGraph with the cost of each allowed edge. * + * Any existing data in mGraph is wiped clean first. The node's parent + * is set with initial value of -1. The parent values are populated by + * aStarSearch() in order to reconstruct a path. * * mGraph[f].edges[n].destination = t * @@ -91,7 +180,7 @@ namespace MWMechanics * n = index of edges from point f * * - * Example: (note from p(0) to p(2) not allowed) + * Example: (note from p(0) to p(2) not allowed in this example) * * mGraph[0].edges[0].destination = 1 * .edges[1].destination = 3 @@ -130,25 +219,110 @@ namespace MWMechanics Node node; node.label = i; node.parent = -1; - mGraph[i] = node; // TODO: old code used push_back(node), check if any difference + mGraph[i] = node; } - // store the costs (measured in distance ^2) of each edge, in both directions + // store the costs of each edge for(unsigned int i = 0; i < pathGrid->mEdges.size(); i++) { Edge edge; - edge.cost = distance(pathGrid->mPoints[pathGrid->mEdges[i].mV0], - pathGrid->mPoints[pathGrid->mEdges[i].mV1]); + edge.cost = costAStar(pathGrid->mPoints[pathGrid->mEdges[i].mV0], + pathGrid->mPoints[pathGrid->mEdges[i].mV1]); // forward path of the edge edge.destination = pathGrid->mEdges[i].mV1; mGraph[pathGrid->mEdges[i].mV0].edges.push_back(edge); // reverse path of the edge - // NOTE: These are redundant, ESM already contains the required reverse paths + // NOTE: These are redundant, the ESM already contains the reverse paths. //edge.destination = pathGrid->mEdges[i].mV0; //mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); } mIsGraphConstructed = true; } + // v is the pathgrid point index (some call them vertices) + void PathFinder::recursiveStrongConnect(int v) + { + mSCCPoint[v].first = mSCCIndex; // index + mSCCPoint[v].second = mSCCIndex; // lowlink + mSCCIndex++; + mSCCStack.push_back(v); + int w; + + for(int i = 0; i < mGraph[v].edges.size(); i++) + { + w = mGraph[v].edges[i].destination; + if(mSCCPoint[w].first == -1) // not visited + { + recursiveStrongConnect(w); // recurse + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].second); + } + else + { + if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].first); + } + } + + if(mSCCPoint[v].second == mSCCPoint[v].first) + { + // new component + do + { + w = mSCCStack.back(); + mSCCStack.pop_back(); + mSCComp[w] = mSCCId; + } + while(w != v); + + mSCCId++; + } + return; + } + + /* + * mSCComp contains the strongly connected component group id's. + * + * A cell can have disjointed pathgrid, e.g. Seyda Neen which has 3 + * + * mSCComp for Seyda Neen will have 3 different values. When selecting a + * random pathgrid point for AiWander, mSCComp can be checked for quickly + * finding whether the destination is reachable. + * + * Otherwise, buildPath will automatically select a closest reachable end + * pathgrid point (reachable from the closest start point). + * + * Using Tarjan's algorithm + * + * mGraph | graph G | + * mSCCPoint | V | derived from pathGrid->mPoints + * mGraph[v].edges | E (for v) | + * mSCCIndex | index | keep track of smallest unused index + * mSCCStack | S | + * pathGrid + * ->mEdges[v].mV1 | w | = mGraph[v].edges[i].destination + * + * FIXME: Some of these can be cleaned up by including them to struct + * Node used by mGraph + */ + void PathFinder::buildConnectedPoints(const ESM::Pathgrid* pathGrid) + { + mSCComp.clear(); + mSCComp.resize(pathGrid->mPoints.size(), 0); + mSCCId = 0; + + mSCCIndex = 0; + mSCCStack.clear(); + mSCCPoint.clear(); + mSCCPoint.resize(pathGrid->mPoints.size(), std::pair (-1, -1)); + + for(unsigned int v = 0; v < pathGrid->mPoints.size(); v++) + { + if(mSCCPoint[v].first == -1) // undefined (haven't visited) + recursiveStrongConnect(v); + } + } + void PathFinder::cleanUpAStar() { for(int i = 0; i < static_cast (mGraph.size()); i++) @@ -160,7 +334,8 @@ namespace MWMechanics } /* - * NOTE: based on buildPath2(), please check git history if interested + * NOTE: Based on buildPath2(), please check git history if interested + * Should consider a using 3rd party library version (e.g. boost) * * Find the shortest path to the target goal using a well known algorithm. * Uses mGraph which has pre-computed costs for allowed edges. It is assumed @@ -169,20 +344,27 @@ namespace MWMechanics * * Returns path (a list of pathgrid point indexes) which may be empty. * - * openset - point indexes to be traversed, lowest cost at the front - * closedset - point indexes already traversed + * Input params: + * start, goal - pathgrid point indexes (for this cell) + * xCell, yCell - values to add to convert path back to world scale * - * mGScore - past accumulated costs vector indexed by point index - * mFScore - future estimated costs vector indexed by point index - * these are resized by buildPathgridGraph() + * Variables: + * openset - point indexes to be traversed, lowest cost at the front + * closedset - point indexes already traversed * - * The heuristics used is distance^2 from current position to the final goal. + * Class variables: + * mGScore - past accumulated costs vector indexed by point index + * mFScore - future estimated costs vector indexed by point index + * these are resized by buildPathgridGraph() */ - std::list PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell, float yCell) + std::list PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid, + int start, int goal, + float xCell, float yCell) { cleanUpAStar(); + // mGScore & mFScore keep costs for each pathgrid point in pathGrid->mPoints mGScore[start] = 0; - mFScore[start] = distance(pathGrid->mPoints[start],pathGrid->mPoints[goal]); + mFScore[start] = costAStar(pathGrid->mPoints[start], pathGrid->mPoints[goal]); std::list openset; std::list closedset; @@ -195,33 +377,36 @@ namespace MWMechanics current = openset.front(); // front has the lowest cost openset.pop_front(); - if(current == goal) break; + if(current == goal) + break; - closedset.push_back(current); + closedset.push_back(current); // remember we've been here - // check all edges for the "current" point index + // check all edges for the current point index for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) { - if(std::find(closedset.begin(),closedset.end(),mGraph[current].edges[j].destination) == closedset.end()) + if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].destination) == + closedset.end()) { // not in closedset - i.e. have not traversed this edge destination int dest = mGraph[current].edges[j].destination; float tentative_g = mGScore[current] + mGraph[current].edges[j].cost; - bool isInOpenSet = std::find(openset.begin(),openset.end(),dest) != openset.end(); + bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); if(!isInOpenSet - || tentative_g < mGScore[dest] ) + || tentative_g < mGScore[dest]) { mGraph[dest].parent = current; mGScore[dest] = tentative_g; - mFScore[dest] = tentative_g + distance(pathGrid->mPoints[dest],pathGrid->mPoints[goal]); + mFScore[dest] = tentative_g + + costAStar(pathGrid->mPoints[dest], pathGrid->mPoints[goal]); if(!isInOpenSet) { // add this edge to openset, lowest cost goes to the front - // TODO: if this causes performance problems a hash table may help (apparently) + // TODO: if this causes performance problems a hash table may help std::list::iterator it = openset.begin(); - for(it = openset.begin();it!= openset.end();it++) + for(it = openset.begin(); it!= openset.end(); it++) { - if(mGScore[*it] > mGScore[dest]) + if(mFScore[*it] > mFScore[dest]) break; } openset.insert(it, dest); @@ -231,11 +416,14 @@ namespace MWMechanics } } - // reconstruct path to return std::list path; + if(current != goal) + return path; // for some reason couldn't build a path + // e.g. start was not reachable (we assume it is) + + // reconstruct path to return, using world co-ordinates while(mGraph[current].parent != -1) { - //std::cout << "not empty" << xCell; ESM::Pathgrid::Point pt = pathGrid->mPoints[current]; pt.mX += xCell; pt.mY += yCell; @@ -243,7 +431,8 @@ namespace MWMechanics current = mGraph[current].parent; } - // TODO: Is this a bug? If path is empty the destination is inserted. + // TODO: Is this a bug? If path is empty the algorithm couldn't find a path. + // Simply using the destination as the path in this scenario seems strange. // Commented out pending further testing. #if 0 if(path.empty()) @@ -258,63 +447,127 @@ namespace MWMechanics } /* - * NOTE: This method may fail to find a path. The caller must check the result before using it. - * If there is no path the AI routies need to implement some other heuristics to reach the target. + * NOTE: This method may fail to find a path. The caller must check the + * result before using it. If there is no path the AI routies need to + * implement some other heuristics to reach the target. * - * Updates mPath using aStarSearch(). - * mPathConstructed is set true if successful, false if not + * NOTE: startPoint & endPoint are in world co-ordinates * - * May update mGraph by calling buildPathgridGraph() if it isn't constructed yet. + * Updates mPath using aStarSearch() or ray test (if shortcut allowed). + * mPath consists of pathgrid points, except the last element which is + * endPoint. This may be useful where the endPoint is not on a pathgrid + * point (e.g. combat). However, if the caller has already chosen a + * pathgrid point (e.g. wander) then it may be worth while to call + * pop_back() to remove the redundant entry. + * + * mPathConstructed is set true if successful, false if not + * + * May update mGraph by calling buildPathgridGraph() if it isn't + * constructed yet. At the same time mConnectedPoints is also updated. + * + * NOTE: co-ordinates must be converted prior to calling getClosestPoint() + * + * | + * | cell + * | +-----------+ + * | | | + * | | | + * | | @ | + * | i | j | + * |<--->|<---->| | + * | +-----------+ + * | k + * |<---------->| world + * +----------------------------- + * + * i = x value of cell itself (multiply by ESM::Land::REAL_SIZE to convert) + * j = @.x in local co-ordinates (i.e. within the cell) + * k = @.x in world co-ordinates */ - void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, + void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, + const ESM::Pathgrid::Point &endPoint, const MWWorld::CellStore* cell, bool allowShortcuts) { mPath.clear(); - if(mCell != cell) mIsGraphConstructed = false; - mCell = cell; if(allowShortcuts) { - if(MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ, - endPoint.mX, endPoint.mY, endPoint.mZ)) - allowShortcuts = false; + // if there's a ray cast hit, can't take a direct path + if(!MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ, + endPoint.mX, endPoint.mY, endPoint.mZ)) + { + mPath.push_back(endPoint); + mIsPathConstructed = true; + return; + } } - if(!allowShortcuts) + if(mCell != cell) { - const ESM::Pathgrid *pathGrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); - float xCell = 0; - float yCell = 0; + mIsGraphConstructed = false; // must be in a new cell, need a new mGraph and mSCComp + mCell = cell; + } - if (mCell->isExterior()) + const ESM::Pathgrid *pathGrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); + float xCell = 0; + float yCell = 0; + + if (mCell->isExterior()) + { + xCell = mCell->getCell()->mData.mX * ESM::Land::REAL_SIZE; + yCell = mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE; + } + + // NOTE: It is possible that getClosestPoint returns a pathgrind point index + // that is unreachable in some situations. e.g. actor is standing + // outside an area enclosed by walls, but there is a pathgrid + // point right behind the wall that is closer than any pathgrid + // point outside the wall + // + // NOTE: getClosestPoint expects local co-ordinates + // + int startNode = getClosestPoint(pathGrid, + Ogre::Vector3(startPoint.mX - xCell, startPoint.mY - yCell, startPoint.mZ)); + + if(startNode != -1) // only check once, assume pathGrid won't change + { + if(!mIsGraphConstructed) { - xCell = mCell->getCell()->mData.mX * ESM::Land::REAL_SIZE; - yCell = mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE; + buildPathgridGraph(pathGrid); // pre-compute costs for use with aStarSearch + buildConnectedPoints(pathGrid); // must before calling getClosestReachablePoint } - int startNode = getClosestPoint(pathGrid, startPoint.mX - xCell, startPoint.mY - yCell,startPoint.mZ); - int endNode = getClosestPoint(pathGrid, endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ); + std::pair endNode = getClosestReachablePoint(pathGrid, + Ogre::Vector3(endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ), + startNode, mSCComp); - if(startNode != -1 && endNode != -1) + if(endNode.first != -1) { - if(!mIsGraphConstructed) buildPathgridGraph(pathGrid); - - mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell); + mPath = aStarSearch(pathGrid, startNode, endNode.first, xCell, yCell); if(!mPath.empty()) { mIsPathConstructed = true; + // Add the destination (which may be different to the closest + // pathgrid point). However only add if endNode was the closest + // point to endPoint. + // + // This logic can fail in the opposite situate, e.g. endPoint may + // have been reachable but happened to be very close to an + // unreachable pathgrid point. + // + // The AI routines will have to deal with such situations. + if(endNode.second) + mPath.push_back(endPoint); } + else + mIsPathConstructed = false; } + else + mIsPathConstructed = false; } else - { - mPath.push_back(endPoint); - mIsPathConstructed = true; - } - - if(mPath.empty()) - mIsPathConstructed = false; + mIsPathConstructed = false; // this shouldn't really happen, but just in case } float PathFinder::getZAngleToNext(float x, float y) const @@ -332,6 +585,7 @@ namespace MWMechanics return Ogre::Radian(Ogre::Math::ACos(directionY / directionResult) * sgn(Ogre::Math::ASin(directionX / directionResult))).valueDegrees(); } + // Used by AiCombat, use Euclidean distance float PathFinder::getDistToNext(float x, float y, float z) { ESM::Pathgrid::Point nextPoint = *mPath.begin(); @@ -372,6 +626,7 @@ namespace MWMechanics return false; } + // used by AiCombat, see header for the rationale void PathFinder::syncStart(const std::list &path) { if (mPath.size() < 2) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index ecaaef568f..ae849bff27 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -64,9 +64,10 @@ namespace MWMechanics return mPath; } - //When first point of newly created path is the nearest to actor point, then - //the cituation can occure when this point is undesirable (if the 2nd point of new path == the 1st point of old path) - //This functions deletes that point. + // When first point of newly created path is the nearest to actor point, + // then a situation can occure when this point is undesirable + // (if the 2nd point of new path == the 1st point of old path) + // This functions deletes that point. void syncStart(const std::list &path); void addPointToPath(ESM::Pathgrid::Point &point) @@ -74,6 +75,13 @@ namespace MWMechanics mPath.push_back(point); } + // While a public method is defined here, it is anticipated that + // mSCComp will only be used internally. + std::vector getSCComp() const + { + return mSCComp; + } + private: struct Edge @@ -101,6 +109,26 @@ namespace MWMechanics std::list mPath; bool mIsGraphConstructed; const MWWorld::CellStore* mCell; + + // contains an integer indicating the groups of connected pathgrid points + // (all connected points will have the same value) + // + // In Seyda Neen there are 3: + // + // 52, 53 and 54 are one set (enclosed yard) + // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 are another (ship & office) + // all other pathgrid points are the third set + // + std::vector mSCComp; + // variables used to calculate mSCComp + int mSCCId; + int mSCCIndex; + std::list mSCCStack; + typedef std::pair VPair; // first is index, second is lowlink + std::vector mSCCPoint; + // methods used to calculate mSCComp + void recursiveStrongConnect(int v); + void buildConnectedPoints(const ESM::Pathgrid* pathGrid); }; } From ebb1813b9bb01ee3ae5f8c43e282ad6b93be3d6c Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 29 Mar 2014 20:17:04 +1100 Subject: [PATCH 025/135] Minor comment clarification. --- apps/openmw/mwmechanics/aiwander.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 48b67fb1cc..ca6e546edf 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -41,7 +41,7 @@ namespace MWMechanics // Cell location int mCellX; int mCellY; - // Cell location multiply by ESM::Land::REAL_SIZE to get the right scale + // Cell location multiplied by ESM::Land::REAL_SIZE float mXCell; float mYCell; From c7b969821fd3e7ec2e8883dc289038e828a514d3 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 29 Mar 2014 11:11:43 +0100 Subject: [PATCH 026/135] silenced a warning --- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 0eb7f07986..86f9f9af23 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -247,7 +247,7 @@ namespace MWMechanics mSCCStack.push_back(v); int w; - for(int i = 0; i < mGraph[v].edges.size(); i++) + for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) { w = mGraph[v].edges[i].destination; if(mSCCPoint[w].first == -1) // not visited From 512ee1204ebf66795cfc64a7c070b80575d86157 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 29 Mar 2014 15:49:48 +0100 Subject: [PATCH 027/135] fixes a bug when resizing the window before a window listener is set --- libs/openengine/ogre/renderer.cpp | 17 ++++++++++++++++- libs/openengine/ogre/renderer.hpp | 9 ++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 5f95789884..caf62546e5 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -157,6 +157,21 @@ void OgreRenderer::setFov(float fov) void OgreRenderer::windowResized(int x, int y) { - if (mWindowListener) + if (mWindowListener) { mWindowListener->windowResized(x,y); + } + else { + mWindowWidth = x; + mWindowHeight = y; + mOutstandingResize = true; + } +} + +void OgreRenderer::setWindowListener(WindowSizeListener* listener) +{ + mWindowListener = listener; + if (mOutstandingResize) { + windowResized(mWindowWidth, mWindowHeight); + mOutstandingResize = false; + } } diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index 767e7cf99e..ad88b1606f 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -66,6 +66,10 @@ namespace OEngine WindowSizeListener* mWindowListener; + int mWindowWidth; + int mWindowHeight; + bool mOutstandingResize; + public: OgreRenderer() : mRoot(NULL) @@ -77,6 +81,9 @@ namespace OEngine , mOgreInit(NULL) , mFader(NULL) , mWindowListener(NULL) + , mWindowWidth(0) + , mWindowHeight(0) + , mOutstandingResize(false) { } @@ -133,7 +140,7 @@ namespace OEngine /// Viewport Ogre::Viewport *getViewport() { return mView; } - void setWindowListener(WindowSizeListener* listener) { mWindowListener = listener; } + void setWindowListener(WindowSizeListener* listener); void adjustViewport(); }; From d9ea7107b7b619fcd17ba5702c5f57377b8f25eb Mon Sep 17 00:00:00 2001 From: gus Date: Sat, 29 Mar 2014 18:36:32 +0100 Subject: [PATCH 028/135] compile fix. --- apps/openmw/mwmechanics/aifollow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f85d8889b5..c3b36516ce 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -3,6 +3,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/cellstore.hpp" #include "movement.hpp" #include From 72df9e77c6263b2341eaf9869a398005431c1868 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 28 Mar 2014 15:07:32 +0100 Subject: [PATCH 029/135] Don't show version text in the pause menu --- apps/openmw/mwgui/mainmenu.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 00e124f6c2..5257baf223 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -174,6 +174,7 @@ namespace MWGui MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState(); showBackground(state == MWBase::StateManager::State_NoGame); + mVersionText->setVisible(state == MWBase::StateManager::State_NoGame); std::vector buttons; From 5eeed03f5b61645b5965586857418dff29f01ee7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 30 Mar 2014 00:12:31 +0100 Subject: [PATCH 030/135] Only exchange bribe gold if the bribe was accepted --- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 19 +++++++++++++++++- apps/openmw/mwgui/dialogue.cpp | 20 ------------------- apps/openmw/mwgui/dialogue.hpp | 6 ------ 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index c9e8ad9551..88f1302bbf 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -500,7 +500,24 @@ namespace MWDialogue mTemporaryDispositionChange = 100 - curDisp; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::Class::get(player).skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); + + if (success) + { + int gold=0; + if (type == MWBase::MechanicsManager::PT_Bribe10) + gold = 10; + else if (type == MWBase::MechanicsManager::PT_Bribe100) + gold = 100; + else if (type == MWBase::MechanicsManager::PT_Bribe1000) + gold = 1000; + + if (gold) + { + player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, gold, player); + mActor.getClass().getContainerStore(mActor).add(MWWorld::ContainerStore::sGoldId, gold, mActor); + } + } std::string text; diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index e64c80c90a..6c43f47b4b 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -56,29 +56,16 @@ namespace MWGui void PersuasionDialog::onPersuade(MyGUI::Widget *sender) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWBase::MechanicsManager::PersuasionType type; if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire; else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate; else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt; else if (sender == mBribe10Button) - { - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 10, player); - mReceiver.getClass().getContainerStore(mReceiver).add(MWWorld::ContainerStore::sGoldId, 10, mReceiver); type = MWBase::MechanicsManager::PT_Bribe10; - } else if (sender == mBribe100Button) - { - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 100, player); - mReceiver.getClass().getContainerStore(mReceiver).add(MWWorld::ContainerStore::sGoldId, 100, mReceiver); type = MWBase::MechanicsManager::PT_Bribe100; - } else /*if (sender == mBribe1000Button)*/ - { - player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, 1000, player); - mReceiver.getClass().getContainerStore(mReceiver).add(MWWorld::ContainerStore::sGoldId, 1000, mReceiver); type = MWBase::MechanicsManager::PT_Bribe1000; - } MWBase::Environment::get().getDialogueManager()->persuade(type); @@ -100,12 +87,6 @@ namespace MWGui mGoldLabel->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); } - // The receiver of the bribe - void PersuasionDialog::setReceiver(MWWorld::Ptr receiver) - { - mReceiver = receiver; - } - // -------------------------------------------------------------------------------------------------- Response::Response(const std::string &text, const std::string &title) @@ -380,7 +361,6 @@ namespace MWGui mPtr = actor; mTopicsList->setEnabled(true); setTitle(npcName); - mPersuasionDialog.setReceiver(mPtr); mTopicsList->clear(); diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 3681405208..befbd6eeeb 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -35,9 +35,6 @@ namespace MWGui virtual void open(); - // The receiver of the bribe - void setReceiver(MWWorld::Ptr receiver); - private: MyGUI::Button* mCancelButton; MyGUI::Button* mAdmireButton; @@ -50,9 +47,6 @@ namespace MWGui void onCancel (MyGUI::Widget* sender); void onPersuade (MyGUI::Widget* sender); - - // The receiver of the bribe - MWWorld::Ptr mReceiver; }; From d04bb3befb996d9e04a5a9646ab3033657bc029d Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 30 Mar 2014 17:23:22 +1100 Subject: [PATCH 031/135] MSVC uses C version of locale. --- apps/openmw/mwgui/savegamedialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index caa082646e..d17cd8c215 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -259,7 +259,7 @@ namespace MWGui timeinfo = localtime(&time); // Use system/environment locale settings for datetime formatting - std::setlocale(LC_TIME, ""); + setlocale(LC_TIME, ""); const int size=1024; char buffer[size]; From 50af9bc0d356aea7941e6e26ed8a4e47c66eaac6 Mon Sep 17 00:00:00 2001 From: megaton <9megaton6@gmail.com> Date: Sun, 30 Mar 2014 19:01:59 +0400 Subject: [PATCH 032/135] General perfomance optimizations. --- apps/openmw/mwmechanics/actors.cpp | 18 +++++++++------ apps/openmw/mwrender/localmap.cpp | 13 +++++++---- components/nifogre/controller.hpp | 34 ++++++++++++++++++---------- components/nifogre/ogrenifloader.cpp | 17 +++++++++----- 4 files changed, 53 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 92be89f2f7..2707537189 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -196,9 +196,8 @@ namespace MWMechanics { disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); } - bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player) - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr); - if( ( (fight == 100 ) + + if( (fight == 100 ) || (fight >= 95 && d <= 3000) || (fight >= 90 && d <= 2000) || (fight >= 80 && d <= 1000) @@ -206,12 +205,17 @@ namespace MWMechanics || (fight >= 70 && disp <= 35 && d <= 1000) || (fight >= 60 && disp <= 30 && d <= 1000) || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) ) - && LOS + || (fight >= 40 && disp <= 10 && d <= 500) ) { - creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr())); - creatureStats.setHostile(true); + bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr); + + if (LOS) + { + creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr())); + creatureStats.setHostile(true); + } } } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 003f083009..0f6d782a65 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -390,8 +390,13 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog"); if (!tex.isNull()) { + std::map >::iterator anIter; + // get its buffer - if (mBuffers.find(texName) == mBuffers.end()) return; + anIter = mBuffers.find(texName); + if (anIter == mBuffers.end()) return; + + std::vector& aBuffer = (*anIter).second; int i=0; for (int texV = 0; texV> 24); alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); - mBuffers[texName][i] = (uint32) (alpha << 24); + aBuffer[i] = (uint32) (alpha << 24); ++i; } } // copy to the texture - memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &mBuffers[texName][0], sFogOfWarResolution*sFogOfWarResolution*4); + memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &aBuffer[0], sFogOfWarResolution*sFogOfWarResolution*4); tex->getBuffer()->unlock(); } } diff --git a/components/nifogre/controller.hpp b/components/nifogre/controller.hpp index 6d7f6ab3fb..317447d954 100644 --- a/components/nifogre/controller.hpp +++ b/components/nifogre/controller.hpp @@ -18,16 +18,21 @@ namespace NifOgre if(time <= keys.front().mTime) return keys.front().mValue; - Nif::FloatKeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) + const Nif::FloatKey* keyArray = keys.data(); + size_t size = keys.size(); + + for (size_t i = 1; i < size; ++i) { - if(iter->mTime < time) + const Nif::FloatKey* aKey = &keyArray[i]; + + if(aKey->mTime < time) continue; - Nif::FloatKeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); + const Nif::FloatKey* aLastKey = &keyArray[i-1]; + float a = (time - aLastKey->mTime) / (aKey->mTime - aLastKey->mTime); + return aLastKey->mValue + ((aKey->mValue - aLastKey->mValue) * a); } + return keys.back().mValue; } @@ -36,16 +41,21 @@ namespace NifOgre if(time <= keys.front().mTime) return keys.front().mValue; - Nif::Vector3KeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) + const Nif::Vector3Key* keyArray = keys.data(); + size_t size = keys.size(); + + for (size_t i = 1; i < size; ++i) { - if(iter->mTime < time) + const Nif::Vector3Key* aKey = &keyArray[i]; + + if(aKey->mTime < time) continue; - Nif::Vector3KeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); + const Nif::Vector3Key* aLastKey = &keyArray[i-1]; + float a = (time - aLastKey->mTime) / (aKey->mTime - aLastKey->mTime); + return aLastKey->mValue + ((aKey->mValue - aLastKey->mValue) * a); } + return keys.back().mValue; } }; diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index acab419b03..ce82446194 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -385,16 +385,21 @@ public: if(time <= keys.front().mTime) return keys.front().mValue; - Nif::QuaternionKeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) + const Nif::QuaternionKey* keyArray = keys.data(); + size_t size = keys.size(); + + for (size_t i = 1; i < size; ++i) { - if(iter->mTime < time) + const Nif::QuaternionKey* aKey = &keyArray[i]; + + if(aKey->mTime < time) continue; - Nif::QuaternionKeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return Ogre::Quaternion::nlerp(a, last->mValue, iter->mValue); + const Nif::QuaternionKey* aLastKey = &keyArray[i-1]; + float a = (time - aLastKey->mTime) / (aKey->mTime - aLastKey->mTime); + return Ogre::Quaternion::nlerp(a, aLastKey->mValue, aKey->mValue); } + return keys.back().mValue; } From 126513120395cbe172f9b6ac961ec6c64a0062b9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 30 Mar 2014 20:07:43 +0200 Subject: [PATCH 033/135] Set the selected index after all items are added (workaround for MyGUI bug) --- apps/openmw/mwgui/savegamedialog.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index caa082646e..52f6080d1a 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -80,6 +80,8 @@ namespace MWGui mCharacterSelection->removeAllItems(); + int selectedIndex = MyGUI::ITEM_NONE; + for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it) { if (it->begin()!=it->end()) @@ -109,11 +111,13 @@ namespace MWGui it->begin()->mPath.parent_path().filename().string()))) { mCurrentCharacter = &*it; - mCharacterSelection->setIndexSelected(mCharacterSelection->getItemCount()-1); + selectedIndex = mCharacterSelection->getItemCount()-1; } } } + mCharacterSelection->setIndexSelected(selectedIndex); + fillSaveList(); } From f5810b8e1c7416dc5d71cef2ec99cd8fdb70da65 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 30 Mar 2014 23:04:12 +0200 Subject: [PATCH 034/135] Consider aspect ratio for loading screen background More consistent with the main menu. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwgui/backgroundimage.cpp | 63 ++++++++++++++++++++++++ apps/openmw/mwgui/backgroundimage.hpp | 37 ++++++++++++++ apps/openmw/mwgui/loadingscreen.cpp | 45 +++++++---------- apps/openmw/mwgui/loadingscreen.hpp | 9 ++-- apps/openmw/mwgui/mainmenu.cpp | 36 ++++---------- apps/openmw/mwgui/mainmenu.hpp | 6 +-- apps/openmw/mwgui/windowmanagerimp.cpp | 2 + files/mygui/openmw_loading_screen.layout | 14 ++---- 9 files changed, 143 insertions(+), 71 deletions(-) create mode 100644 apps/openmw/mwgui/backgroundimage.cpp create mode 100644 apps/openmw/mwgui/backgroundimage.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 511435108e..d1f7c45f31 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -33,7 +33,7 @@ add_openmw_dir (mwgui merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog - recharge mode videowidget + recharge mode videowidget backgroundimage ) add_openmw_dir (mwdialogue diff --git a/apps/openmw/mwgui/backgroundimage.cpp b/apps/openmw/mwgui/backgroundimage.cpp new file mode 100644 index 0000000000..1e87c0ff19 --- /dev/null +++ b/apps/openmw/mwgui/backgroundimage.cpp @@ -0,0 +1,63 @@ +#include "backgroundimage.hpp" + +#include + +namespace MWGui +{ + +void BackgroundImage::setBackgroundImage (const std::string& image, bool fixedRatio, bool correct) +{ + if (mChild) + { + MyGUI::Gui::getInstance().destroyWidget(mChild); + mChild = NULL; + } + if (correct) + { + setImageTexture("black.png"); + + if (fixedRatio) + mAspect = 4.0/3.0; + else + mAspect = 0; // TODO + + mChild = createWidgetReal("ImageBox", + MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); + mChild->setImageTexture(image); + + adjustSize(); + } + else + { + mAspect = 0; + setImageTexture(image); + } +} + +void BackgroundImage::adjustSize() +{ + if (mAspect == 0) + return; + + MyGUI::IntSize screenSize = getSize(); + + int leftPadding = std::max(0.0, (screenSize.width - screenSize.height * mAspect) / 2); + int topPadding = std::max(0.0, (screenSize.height - screenSize.width / mAspect) / 2); + + mChild->setCoord(leftPadding, topPadding, screenSize.width - leftPadding*2, screenSize.height - topPadding*2); +} + +void BackgroundImage::setSize (const MyGUI::IntSize& _value) +{ + MyGUI::Widget::setSize (_value); + adjustSize(); +} + +void BackgroundImage::setCoord (const MyGUI::IntCoord& _value) +{ + MyGUI::Widget::setCoord (_value); + adjustSize(); +} + + +} diff --git a/apps/openmw/mwgui/backgroundimage.hpp b/apps/openmw/mwgui/backgroundimage.hpp new file mode 100644 index 0000000000..3d1a61eaf7 --- /dev/null +++ b/apps/openmw/mwgui/backgroundimage.hpp @@ -0,0 +1,37 @@ +#ifndef OPENMW_MWGUI_BACKGROUNDIMAGE_H +#define OPENMW_MWGUI_BACKGROUNDIMAGE_H + +#include + +namespace MWGui +{ + + /** + * @brief A variant of MyGUI::ImageBox with aspect ratio correction using black bars + */ + class BackgroundImage : public MyGUI::ImageBox + { + MYGUI_RTTI_DERIVED(BackgroundImage) + + public: + BackgroundImage() : mChild(NULL), mAspect(0) {} + + /** + * @param fixedRatio Use a fixed ratio of 4:3, regardless of the image dimensions + * @param correct Add black bars? + */ + void setBackgroundImage (const std::string& image, bool fixedRatio=true, bool correct=true); + + virtual void setSize (const MyGUI::IntSize &_value); + virtual void setCoord (const MyGUI::IntCoord &_value); + + private: + MyGUI::ImageBox* mChild; + double mAspect; + + void adjustSize(); + }; + +} + +#endif diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index b3f70a5ab0..7917c75f3b 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -9,14 +9,14 @@ #include #include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/inputmanager.hpp" +#include "backgroundimage.hpp" + namespace MWGui { @@ -32,28 +32,13 @@ namespace MWGui { getWidget(mLoadingText, "LoadingText"); getWidget(mProgressBar, "ProgressBar"); - getWidget(mBackgroundImage, "BackgroundImage"); mProgressBar->setScrollViewPage(1); - mBackgroundMaterial = Ogre::MaterialManager::getSingleton().create("BackgroundMaterial", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - mBackgroundMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); - mBackgroundMaterial->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); - mBackgroundMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(""); + mBackgroundImage = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, + MyGUI::Align::Stretch, "Menu"); - mRectangle = new Ogre::Rectangle2D(true); - mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0); - mRectangle->setMaterial("BackgroundMaterial"); - // Render the background before everything else - mRectangle->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY-1); - // Use infinite AAB to always stay visible - Ogre::AxisAlignedBox aabInf; - aabInf.setInfinite(); - mRectangle->setBoundingBox(aabInf); - // Attach background to the scene - Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - node->attachObject(mRectangle); - mRectangle->setVisible(false); + setVisible(false); } void LoadingScreen::setLabel(const std::string &label) @@ -63,18 +48,25 @@ namespace MWGui LoadingScreen::~LoadingScreen() { - delete mRectangle; + } + + void LoadingScreen::setVisible(bool visible) + { + WindowBase::setVisible(visible); + mBackgroundImage->setVisible(visible); } void LoadingScreen::onResChange(int w, int h) { setCoord(0,0,w,h); + + mBackgroundImage->setCoord(MyGUI::IntCoord(0,0,w,h)); } void LoadingScreen::loadingOn() { // Early-out if already on - if (mRectangle->getVisible()) + if (mMainWidget->getVisible()) return; // Temporarily turn off VSync, we want to do actual loading rather than waiting for the screen to sync. @@ -106,7 +98,7 @@ namespace MWGui texture->createInternalResources(); mWindow->copyContentsToMemory(texture->getBuffer()->lock(Ogre::Image::Box(0,0,width,height), Ogre::HardwareBuffer::HBL_DISCARD)); texture->getBuffer()->unlock(); - mBackgroundImage->setImageTexture(texture->getName()); + mBackgroundImage->setBackgroundImage(texture->getName(), false, false); } setVisible(true); @@ -149,9 +141,10 @@ namespace MWGui { std::string const & randomSplash = mResources.at (rand() % mResources.size()); - Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); + Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); - mBackgroundImage->setImageTexture (randomSplash); + // TODO: add option (filename pattern?) to use image aspect ratio instead of 4:3 + mBackgroundImage->setBackgroundImage(randomSplash, true, true); } else std::cerr << "No loading screens found!" << std::endl; @@ -237,8 +230,6 @@ namespace MWGui mWindow->update(false); - mRectangle->setVisible(false); - // resume 3d rendering mSceneMgr->clearSpecialCaseRenderQueues(); mSceneMgr->setSpecialCaseRenderQueueMode(Ogre::SceneManager::SCRQM_EXCLUDE); diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index e91e5951db..55235173f1 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -10,6 +10,8 @@ namespace MWGui { + class BackgroundImage; + class LoadingScreen : public WindowBase, public Loading::Listener { public: @@ -25,6 +27,8 @@ namespace MWGui virtual void setProgress (size_t value); virtual void increaseProgress (size_t increase); + virtual void setVisible(bool visible); + virtual void removeWallpaper(); LoadingScreen(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* rw); @@ -51,10 +55,7 @@ namespace MWGui MyGUI::TextBox* mLoadingText; MyGUI::ScrollBar* mProgressBar; - MyGUI::ImageBox* mBackgroundImage; - - Ogre::Rectangle2D* mRectangle; - Ogre::MaterialPtr mBackgroundMaterial; + BackgroundImage* mBackgroundImage; Ogre::StringVector mResources; diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 5257baf223..b6e3915bbe 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -14,6 +14,8 @@ #include "savegamedialog.hpp" #include "confirmationdialog.hpp" +#include "imagebutton.hpp" +#include "backgroundimage.hpp" namespace MWGui { @@ -132,34 +134,14 @@ namespace MWGui void MainMenu::showBackground(bool show) { + if (show && !mBackground) + { + mBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, + MyGUI::Align::Stretch, "Menu"); + mBackground->setBackgroundImage("textures\\menu_morrowind.dds"); + } if (mBackground) - { - MyGUI::Gui::getInstance().destroyWidget(mBackground); - mBackground = NULL; - } - if (show) - { - if (!mBackground) - { - mBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Stretch, "Menu"); - mBackground->setImageTexture("black.png"); - - // Use black bars to correct aspect ratio. The video player also does it, so we need to do it - // for mw_logo.bik to align correctly with menu_morrowind.dds. - MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); - - // No way to un-hardcode this right now, menu_morrowind.dds is 1024x512 but was designed for 4:3 - double imageaspect = 4.0/3.0; - - int leftPadding = std::max(0.0, (screenSize.width - screenSize.height * imageaspect) / 2); - int topPadding = std::max(0.0, (screenSize.height - screenSize.width / imageaspect) / 2); - - MyGUI::ImageBox* image = mBackground->createWidget("ImageBox", - leftPadding, topPadding, screenSize.width - leftPadding*2, screenSize.height - topPadding*2, MyGUI::Align::Default); - image->setImageTexture("textures\\menu_morrowind.dds"); - } - } + mBackground->setVisible(show); } void MainMenu::updateMenu() diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index c571fda861..c274425367 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -3,11 +3,11 @@ #include -#include "imagebutton.hpp" - namespace MWGui { + class ImageButton; + class BackgroundImage; class SaveGameDialog; class MainMenu : public OEngine::GUI::Layout @@ -29,7 +29,7 @@ namespace MWGui MyGUI::Widget* mButtonBox; MyGUI::TextBox* mVersionText; - MyGUI::ImageBox* mBackground; + BackgroundImage* mBackground; std::map mButtons; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 1e019aaa95..db19070a6e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -61,6 +61,7 @@ #include "itemview.hpp" #include "fontloader.hpp" #include "videowidget.hpp" +#include "backgroundimage.hpp" namespace MWGui { @@ -160,6 +161,7 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); BookPage::registerMyGUIComponents (); ItemView::registerComponents(); diff --git a/files/mygui/openmw_loading_screen.layout b/files/mygui/openmw_loading_screen.layout index 5fd3440f91..19649cfd21 100644 --- a/files/mygui/openmw_loading_screen.layout +++ b/files/mygui/openmw_loading_screen.layout @@ -4,17 +4,13 @@ - + - - - - - - - - + + + + From db4975dab8908d7cff82551060e4da10430871dd Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Mon, 31 Mar 2014 21:52:20 +0200 Subject: [PATCH 035/135] Changed IRC notifications for Travis CI from normal messages to IRC notices. Signed-off-by: Lukasz Gromanowski --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5d0326a070..e09fa46dc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,4 +41,4 @@ notifications: - "chat.freenode.net#openmw" on_success: change on_failure: always - + use_notice: true From 529d2436b50d0eecba4cd489e3767da02f11394c Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Mon, 31 Mar 2014 22:14:12 +0200 Subject: [PATCH 036/135] Temporary added broken CMakeLists.txt - Travis IRC notification test. Signed-off-by: Lukasz Gromanowski --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 392fdfc66c..0b5b0f8d54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,7 +116,7 @@ if (WIN32) endif() # We probably support older versions than this. -cmake_minimum_required(VERSION 2.6) +cmake_minimum_required(VERSION 999.6) # source directory: libs From 5b5069535eff4df7d97eafe9912235dd35823bad Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 1 Apr 2014 10:04:14 +0200 Subject: [PATCH 037/135] keep track of active cells in PagedWorldspaceWidget and update SubView title accordingly --- .../view/render/pagedworldspacewidget.cpp | 37 ++++++++++++++++++- .../view/render/pagedworldspacewidget.hpp | 14 +++++++ apps/opencs/view/render/worldspacewidget.cpp | 2 + apps/opencs/view/render/worldspacewidget.hpp | 3 ++ apps/opencs/view/world/scenesubview.cpp | 30 ++++++++++++++- apps/opencs/view/world/scenesubview.hpp | 4 ++ 6 files changed, 87 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index fa32e39596..96d44543ec 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -1,6 +1,39 @@ #include "pagedworldspacewidget.hpp" +#include + CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget *parent) -: WorldspaceWidget (parent) -{} \ No newline at end of file +: WorldspaceWidget (parent), mMin (std::make_pair (0, 0)), mMax (std::make_pair (-1, -1)) +{} + +void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) +{ + if (!hint.empty()) + { + if (hint[0]=='c') + { + char ignore1, ignore2, ignore3; + std::pair cellIndex; + + std::istringstream stream (hint.c_str()); + if (stream >> ignore1 >> ignore2 >> ignore3 >> cellIndex.first >> cellIndex.second) + { + setCellIndex (cellIndex, cellIndex); + + /// \todo adjust camera position + } + } + + /// \todo implement 'r' type hints + } +} + +void CSVRender::PagedWorldspaceWidget::setCellIndex (const std::pair& min, + const std::pair& max) +{ + mMin = min; + mMax = max; + + emit cellIndexChanged (mMin, mMax); +} \ No newline at end of file diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 172e2477a1..9a4b79c3ab 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -9,9 +9,23 @@ namespace CSVRender { Q_OBJECT + std::pair mMin; + std::pair mMax; + public: PagedWorldspaceWidget (QWidget *parent); + ///< \note Sets the cell area selection to an invalid value to indicate that currently + /// no cells are displayed. The cells to be displayed will be specified later through + /// hint system. + + virtual void useViewHint (const std::string& hint); + + void setCellIndex (const std::pair& min, const std::pair& max); + + signals: + + void cellIndexChanged (const std::pair& min, const std::pair& max); }; } diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 9959c5a673..4d2442c89f 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -26,6 +26,8 @@ void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) setNavigation (&mOrbit); } +void CSVRender::WorldspaceWidget::useViewHint (const std::string& hint) {} + void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() { setNavigation (&m1st); diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 7921c3560c..f7208d7a14 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -33,6 +33,9 @@ namespace CSVRender void selectDefaultNavigationMode(); + virtual void useViewHint (const std::string& hint); + ///< Default-implementation: ignored. + private slots: void selectNavigationMode (const std::string& mode); diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 10e8b40715..c075cb4d63 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -1,6 +1,8 @@ #include "scenesubview.hpp" +#include + #include #include #include @@ -35,7 +37,14 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SceneToolbar *toolbar = new SceneToolbar (48, this); if (id.getId()=="sys::default") - mScene = new CSVRender::PagedWorldspaceWidget (this); + { + CSVRender::PagedWorldspaceWidget *widget = new CSVRender::PagedWorldspaceWidget (this); + mScene = widget; + connect (widget, + SIGNAL (cellIndexChanged (const std::pair&, const std::pair&)), + this, + SLOT (cellIndexChanged (const std::pair&, const std::pair&))); + } else mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); @@ -83,7 +92,26 @@ void CSVWorld::SceneSubView::setStatusBar (bool show) mBottom->setStatusBar (show); } +void CSVWorld::SceneSubView::useHint (const std::string& hint) +{ + mScene->useViewHint (hint); +} + void CSVWorld::SceneSubView::closeRequest() { deleteLater(); +} + +void CSVWorld::SceneSubView::cellIndexChanged (const std::pair& min, + const std::pair& max) +{ + std::ostringstream stream; + stream << "Scene: " << getUniversalId().getId() << " (" << min.first << ", " << min.second; + + if (min!=max) + stream << " to " << max.first << ", " << max.second; + + stream << ")"; + + setWindowTitle (QString::fromUtf8 (stream.str().c_str())); } \ No newline at end of file diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index ecf3fe4e49..ee5b7b41fb 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -38,9 +38,13 @@ namespace CSVWorld virtual void setStatusBar (bool show); + virtual void useHint (const std::string& hint); + private slots: void closeRequest(); + + void cellIndexChanged (const std::pair& min, const std::pair& max); }; } From 78d322e6a5f3a5921776221c2f9ce2c6b878f4cc Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 1 Apr 2014 11:44:27 +0200 Subject: [PATCH 038/135] Revert "Temporary added broken CMakeLists.txt - Travis IRC notification test." This reverts commit 529d2436b50d0eecba4cd489e3767da02f11394c. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b5b0f8d54..392fdfc66c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,7 +116,7 @@ if (WIN32) endif() # We probably support older versions than this. -cmake_minimum_required(VERSION 999.6) +cmake_minimum_required(VERSION 2.6) # source directory: libs From 6ee998ac38d9c387f9507831a9dd11d418e86d85 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 1 Apr 2014 18:44:55 +0200 Subject: [PATCH 039/135] updated credits file --- credits.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/credits.txt b/credits.txt index cd533de3a2..eb427a22b7 100644 --- a/credits.txt +++ b/credits.txt @@ -52,6 +52,7 @@ Marc Bouvier (CramitDeFrog) Marcin Hulist (Gohan) Mark Siewert (mark76) Mateusz Kołaczek (PL_kolek) +megaton Michael Hogan (Xethik) Michael Mc Donnell Michael Papageorgiou (werdanith) From f597d3e88bc85208681b74d5c72579a6e81488d3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 3 Apr 2014 07:46:26 +1100 Subject: [PATCH 040/135] Use duration rather than frame counts. Stops false detection of being "stuck" with high frame rates (e.g. indoors). --- apps/openmw/mwmechanics/aiwander.cpp | 43 +++++++++++++++++----------- apps/openmw/mwmechanics/aiwander.hpp | 3 +- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index a3286b8c05..e89d43ca4c 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -18,9 +18,10 @@ namespace MWMechanics { // NOTE: determined empirically but probably need further tweaking - static const int COUNT_BEFORE_STUCK = 20; static const int COUNT_BEFORE_RESET = 200; - static const int COUNT_EVADE = 7; + static const float DIST_SAME_SPOT = 1.8f; + static const float DURATION_SAME_SPOT = 1.0f; + static const float DURATION_TO_EVADE = 0.4f; AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) @@ -35,7 +36,8 @@ namespace MWMechanics , mPrevY(0) , mWalkState(State_Norm) , mStuckCount(0) - , mEvadeCount(0) + , mEvadeDuration(0) + , mStuckDuration(0) , mSaidGreeting(false) { for(unsigned short counter = 0; counter < mIdle.size(); counter++) @@ -325,15 +327,21 @@ namespace MWMechanics } else { - /* 1 n + /* f t * State_Norm <---> State_CheckStuck --> State_Evade * ^ ^ | ^ | ^ | | * | | | | | | | | - * | +---+ +---+ +---+ | m - * | any < n < m | + * | +---+ +---+ +---+ | u + * | any < t < u | * +--------------------------------------------+ + * + * f = one frame + * t = how long before considered stuck + * u = how long to move sideways */ - bool samePosition = (abs(pos.pos[0] - mPrevX) < 1) && (abs(pos.pos[1] - mPrevY) < 1); + bool samePosition = (abs(pos.pos[0] - mPrevX) < DIST_SAME_SPOT) && + (abs(pos.pos[1] - mPrevY) < DIST_SAME_SPOT); + switch(mWalkState) { case State_Norm: @@ -349,30 +357,33 @@ namespace MWMechanics if(!samePosition) { mWalkState = State_Norm; - // to do this properly need yet another variable, simply don't clear for now - //mStuckCount = 0; + mStuckDuration = 0; break; } else { - // consider stuck only if position unchanges consecutively - if((mStuckCount++ % COUNT_BEFORE_STUCK) == 0) + mStuckDuration += duration; + // consider stuck only if position unchanges for a period + if(mStuckDuration > DURATION_SAME_SPOT) + { mWalkState = State_Evade; - // NOTE: mStuckCount is purposely not cleared here + mStuckDuration = 0; + mStuckCount++; + } else - break; // still in the same state, but counter got incremented + break; // still in the same state, but duration added to timer } } /* FALL THROUGH */ case State_Evade: { - if(mEvadeCount++ < COUNT_EVADE) + mEvadeDuration += duration; + if(mEvadeDuration < DURATION_TO_EVADE) break; else { mWalkState = State_Norm; // tried to evade, assume all is ok and start again - // NOTE: mStuckCount is purposely not cleared here - mEvadeCount = 0; + mEvadeDuration = 0; } } /* NO DEFAULT CASE */ diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index ca6e546edf..75621911e1 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -58,7 +58,8 @@ namespace MWMechanics WalkState mWalkState; int mStuckCount; - int mEvadeCount; + float mStuckDuration; + float mEvadeDuration; bool mStoredAvailableNodes; bool mChooseAction; From 98f77714ce928de922ec6b83ccf667500f1d8e1e Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 3 Apr 2014 21:41:34 +1100 Subject: [PATCH 041/135] Per-cell pathgrid data and calculation moved off PathFinder. Now the edge cost calculations and strongly connected component searches are done only once per cell. Per-actor data and methods still remain with PathFinder. This version still has debugging statements and needs cleaning up. --- apps/openmw/mwmechanics/pathfinding.cpp | 726 +++++++++++++----------- apps/openmw/mwmechanics/pathfinding.hpp | 90 +-- apps/openmw/mwworld/cellstore.cpp | 30 + apps/openmw/mwworld/cellstore.hpp | 9 + 4 files changed, 498 insertions(+), 357 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 86f9f9af23..f56cfad5fc 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -54,13 +54,13 @@ namespace // // Approx. 514 Euclidean distance and 533 Manhattan distance. // - float manhattan(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) + float manhattan(const ESM::Pathgrid::Point a, const ESM::Pathgrid::Point b) { return 300 * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ)); } - // Choose a heuristics - these may not be the best for directed graphs with - // non uniform edge costs. + // Choose a heuristics - Note that these may not be the best for directed + // graphs with non-uniform edge costs. // // distance: // - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2) @@ -69,7 +69,7 @@ namespace // Manhattan: // - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z| // - faster but not the shortest path - float costAStar(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) + float costAStar(const ESM::Pathgrid::Point a, const ESM::Pathgrid::Point b) { //return distance(a, b); return manhattan(a, b); @@ -113,12 +113,13 @@ namespace return closestIndex; } - // Uses mSCComp to choose a reachable end pathgrid point. start is assumed reachable. + // Chooses a reachable end pathgrid point. start is assumed reachable. std::pair getClosestReachablePoint(const ESM::Pathgrid* grid, - Ogre::Vector3 pos, int start, std::vector &sCComp) + const MWWorld::CellStore *cell, + Ogre::Vector3 pos, int start) { - // assume grid is fine - int startGroup = sCComp[start]; + if(!grid || grid->mPoints.empty()) + return std::pair (-1, false); float distanceBetween = distanceSquared(grid->mPoints[0], pos); int closestIndex = 0; @@ -133,7 +134,7 @@ namespace // found a closer one distanceBetween = potentialDistBetween; closestIndex = counter; - if (sCComp[counter] == startGroup) + if (cell->isPointConnected(start, counter)) { closestReachableIndex = counter; } @@ -152,7 +153,7 @@ namespace MWMechanics { PathFinder::PathFinder() : mIsPathConstructed(false), - mIsGraphConstructed(false), + mPathgrid(NULL), mCell(NULL) { } @@ -164,293 +165,14 @@ namespace MWMechanics mIsPathConstructed = false; } - /* - * NOTE: Based on buildPath2(), please check git history if interested - * - * Populate mGraph with the cost of each allowed edge. - * - * Any existing data in mGraph is wiped clean first. The node's parent - * is set with initial value of -1. The parent values are populated by - * aStarSearch() in order to reconstruct a path. - * - * mGraph[f].edges[n].destination = t - * - * f = point index of location "from" - * t = point index of location "to" - * n = index of edges from point f - * - * - * Example: (note from p(0) to p(2) not allowed in this example) - * - * mGraph[0].edges[0].destination = 1 - * .edges[1].destination = 3 - * - * mGraph[1].edges[0].destination = 0 - * .edges[1].destination = 2 - * .edges[2].destination = 3 - * - * mGraph[2].edges[0].destination = 1 - * - * (etc, etc) - * - * - * low - * cost - * p(0) <---> p(1) <------------> p(2) - * ^ ^ - * | | - * | +-----> p(3) - * +----------------> - * high cost - */ - void PathFinder::buildPathgridGraph(const ESM::Pathgrid* pathGrid) - { - mGraph.clear(); - // resize lists - mGScore.resize(pathGrid->mPoints.size(), -1); - mFScore.resize(pathGrid->mPoints.size(), -1); - Node defaultNode; - defaultNode.label = -1; - defaultNode.parent = -1; - mGraph.resize(pathGrid->mPoints.size(),defaultNode); - // initialise mGraph - for(unsigned int i = 0; i < pathGrid->mPoints.size(); i++) - { - Node node; - node.label = i; - node.parent = -1; - mGraph[i] = node; - } - // store the costs of each edge - for(unsigned int i = 0; i < pathGrid->mEdges.size(); i++) - { - Edge edge; - edge.cost = costAStar(pathGrid->mPoints[pathGrid->mEdges[i].mV0], - pathGrid->mPoints[pathGrid->mEdges[i].mV1]); - // forward path of the edge - edge.destination = pathGrid->mEdges[i].mV1; - mGraph[pathGrid->mEdges[i].mV0].edges.push_back(edge); - // reverse path of the edge - // NOTE: These are redundant, the ESM already contains the reverse paths. - //edge.destination = pathGrid->mEdges[i].mV0; - //mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); - } - mIsGraphConstructed = true; - } - - // v is the pathgrid point index (some call them vertices) - void PathFinder::recursiveStrongConnect(int v) - { - mSCCPoint[v].first = mSCCIndex; // index - mSCCPoint[v].second = mSCCIndex; // lowlink - mSCCIndex++; - mSCCStack.push_back(v); - int w; - - for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) - { - w = mGraph[v].edges[i].destination; - if(mSCCPoint[w].first == -1) // not visited - { - recursiveStrongConnect(w); // recurse - mSCCPoint[v].second = std::min(mSCCPoint[v].second, - mSCCPoint[w].second); - } - else - { - if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) - mSCCPoint[v].second = std::min(mSCCPoint[v].second, - mSCCPoint[w].first); - } - } - - if(mSCCPoint[v].second == mSCCPoint[v].first) - { - // new component - do - { - w = mSCCStack.back(); - mSCCStack.pop_back(); - mSCComp[w] = mSCCId; - } - while(w != v); - - mSCCId++; - } - return; - } - - /* - * mSCComp contains the strongly connected component group id's. - * - * A cell can have disjointed pathgrid, e.g. Seyda Neen which has 3 - * - * mSCComp for Seyda Neen will have 3 different values. When selecting a - * random pathgrid point for AiWander, mSCComp can be checked for quickly - * finding whether the destination is reachable. - * - * Otherwise, buildPath will automatically select a closest reachable end - * pathgrid point (reachable from the closest start point). - * - * Using Tarjan's algorithm - * - * mGraph | graph G | - * mSCCPoint | V | derived from pathGrid->mPoints - * mGraph[v].edges | E (for v) | - * mSCCIndex | index | keep track of smallest unused index - * mSCCStack | S | - * pathGrid - * ->mEdges[v].mV1 | w | = mGraph[v].edges[i].destination - * - * FIXME: Some of these can be cleaned up by including them to struct - * Node used by mGraph - */ - void PathFinder::buildConnectedPoints(const ESM::Pathgrid* pathGrid) - { - mSCComp.clear(); - mSCComp.resize(pathGrid->mPoints.size(), 0); - mSCCId = 0; - - mSCCIndex = 0; - mSCCStack.clear(); - mSCCPoint.clear(); - mSCCPoint.resize(pathGrid->mPoints.size(), std::pair (-1, -1)); - - for(unsigned int v = 0; v < pathGrid->mPoints.size(); v++) - { - if(mSCCPoint[v].first == -1) // undefined (haven't visited) - recursiveStrongConnect(v); - } - } - - void PathFinder::cleanUpAStar() - { - for(int i = 0; i < static_cast (mGraph.size()); i++) - { - mGraph[i].parent = -1; - mGScore[i] = -1; - mFScore[i] = -1; - } - } - - /* - * NOTE: Based on buildPath2(), please check git history if interested - * Should consider a using 3rd party library version (e.g. boost) - * - * Find the shortest path to the target goal using a well known algorithm. - * Uses mGraph which has pre-computed costs for allowed edges. It is assumed - * that mGraph is already constructed. The caller, i.e. buildPath(), needs - * to ensure this. - * - * Returns path (a list of pathgrid point indexes) which may be empty. - * - * Input params: - * start, goal - pathgrid point indexes (for this cell) - * xCell, yCell - values to add to convert path back to world scale - * - * Variables: - * openset - point indexes to be traversed, lowest cost at the front - * closedset - point indexes already traversed - * - * Class variables: - * mGScore - past accumulated costs vector indexed by point index - * mFScore - future estimated costs vector indexed by point index - * these are resized by buildPathgridGraph() - */ - std::list PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid, - int start, int goal, - float xCell, float yCell) - { - cleanUpAStar(); - // mGScore & mFScore keep costs for each pathgrid point in pathGrid->mPoints - mGScore[start] = 0; - mFScore[start] = costAStar(pathGrid->mPoints[start], pathGrid->mPoints[goal]); - - std::list openset; - std::list closedset; - openset.push_back(start); - - int current = -1; - - while(!openset.empty()) - { - current = openset.front(); // front has the lowest cost - openset.pop_front(); - - if(current == goal) - break; - - closedset.push_back(current); // remember we've been here - - // check all edges for the current point index - for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) - { - if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].destination) == - closedset.end()) - { - // not in closedset - i.e. have not traversed this edge destination - int dest = mGraph[current].edges[j].destination; - float tentative_g = mGScore[current] + mGraph[current].edges[j].cost; - bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); - if(!isInOpenSet - || tentative_g < mGScore[dest]) - { - mGraph[dest].parent = current; - mGScore[dest] = tentative_g; - mFScore[dest] = tentative_g + - costAStar(pathGrid->mPoints[dest], pathGrid->mPoints[goal]); - if(!isInOpenSet) - { - // add this edge to openset, lowest cost goes to the front - // TODO: if this causes performance problems a hash table may help - std::list::iterator it = openset.begin(); - for(it = openset.begin(); it!= openset.end(); it++) - { - if(mFScore[*it] > mFScore[dest]) - break; - } - openset.insert(it, dest); - } - } - } // if in closedset, i.e. traversed this edge already, try the next edge - } - } - - std::list path; - if(current != goal) - return path; // for some reason couldn't build a path - // e.g. start was not reachable (we assume it is) - - // reconstruct path to return, using world co-ordinates - while(mGraph[current].parent != -1) - { - ESM::Pathgrid::Point pt = pathGrid->mPoints[current]; - pt.mX += xCell; - pt.mY += yCell; - path.push_front(pt); - current = mGraph[current].parent; - } - - // TODO: Is this a bug? If path is empty the algorithm couldn't find a path. - // Simply using the destination as the path in this scenario seems strange. - // Commented out pending further testing. -#if 0 - if(path.empty()) - { - ESM::Pathgrid::Point pt = pathGrid->mPoints[goal]; - pt.mX += xCell; - pt.mY += yCell; - path.push_front(pt); - } -#endif - return path; - } - /* * NOTE: This method may fail to find a path. The caller must check the * result before using it. If there is no path the AI routies need to * implement some other heuristics to reach the target. * + * NOTE: It may be desirable to simply go directly to the endPoint if for + * example there are no pathgrids in this cell. + * * NOTE: startPoint & endPoint are in world co-ordinates * * Updates mPath using aStarSearch() or ray test (if shortcut allowed). @@ -462,9 +184,6 @@ namespace MWMechanics * * mPathConstructed is set true if successful, false if not * - * May update mGraph by calling buildPathgridGraph() if it isn't - * constructed yet. At the same time mConnectedPoints is also updated. - * * NOTE: co-ordinates must be converted prior to calling getClosestPoint() * * | @@ -486,7 +205,8 @@ namespace MWMechanics */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, bool allowShortcuts) + const MWWorld::CellStore* cell, + bool allowShortcuts) { mPath.clear(); @@ -502,48 +222,76 @@ namespace MWMechanics } } - if(mCell != cell) + if(mCell != cell || !mPathgrid) { - mIsGraphConstructed = false; // must be in a new cell, need a new mGraph and mSCComp mCell = cell; + + // Cache pathgrid as mPathgrid and update on cell changes. There + // might be a small gain in avoiding to search for it. + mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); } - const ESM::Pathgrid *pathGrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); + // Refer to AiWander reseach topic on openmw forums for some background. + // Maybe there is no pathgrid for this cell. Just go to destination and let + // physics take care of any blockages. + if(!mPathgrid || mPathgrid->mPoints.empty()) + { +//#if 0 + std::cout << "no pathgrid " << + +"\"" +mCell->getCell()->mName+ "\"" + +", " +std::to_string(mCell->getCell()->mData.mX) + +", " +std::to_string(mCell->getCell()->mData.mY) + << std::endl; +//#endif + mPath.push_back(endPoint); + mIsPathConstructed = true; + return; + } + + // NOTE: getClosestPoint expects local co-ordinates float xCell = 0; float yCell = 0; - if (mCell->isExterior()) { xCell = mCell->getCell()->mData.mX * ESM::Land::REAL_SIZE; yCell = mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE; } + + + + + + + + + + // NOTE: It is possible that getClosestPoint returns a pathgrind point index // that is unreachable in some situations. e.g. actor is standing // outside an area enclosed by walls, but there is a pathgrid // point right behind the wall that is closer than any pathgrid // point outside the wall - // - // NOTE: getClosestPoint expects local co-ordinates - // - int startNode = getClosestPoint(pathGrid, - Ogre::Vector3(startPoint.mX - xCell, startPoint.mY - yCell, startPoint.mZ)); - - if(startNode != -1) // only check once, assume pathGrid won't change + int startNode = getClosestPoint(mPathgrid, + Ogre::Vector3(startPoint.mX - xCell, startPoint.mY - yCell, startPoint.mZ)); + // Some cells don't have any pathgrids at all + if(startNode != -1) { - if(!mIsGraphConstructed) - { - buildPathgridGraph(pathGrid); // pre-compute costs for use with aStarSearch - buildConnectedPoints(pathGrid); // must before calling getClosestReachablePoint - } - std::pair endNode = getClosestReachablePoint(pathGrid, + std::pair endNode = getClosestReachablePoint(mPathgrid, cell, Ogre::Vector3(endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ), - startNode, mSCComp); - + startNode); +//#if 0 + if(!mPathgrid) + std::cout << "no pathgrid " << + +"\"" +mCell->getCell()->mName+ "\"" + +", " +std::to_string(mCell->getCell()->mData.mX) + +", " +std::to_string(mCell->getCell()->mData.mY) + << std::endl; +//#endif + // this shouldn't really happen, but just in case if(endNode.first != -1) { - mPath = aStarSearch(pathGrid, startNode, endNode.first, xCell, yCell); + mPath = mCell->aStarSearch(startNode, endNode.first, mCell->isExterior()); if(!mPath.empty()) { @@ -561,13 +309,34 @@ namespace MWMechanics mPath.push_back(endPoint); } else + { mIsPathConstructed = false; + std::cout << "empty path error " << std::endl; + } + //mIsPathConstructed = false; } else + { mIsPathConstructed = false; + std::cout << "second point error " << std::endl; + } + //mIsPathConstructed = false; } else - mIsPathConstructed = false; // this shouldn't really happen, but just in case + { + // FIXME: shouldn't return endpoint if first point error? + mIsPathConstructed = false; + std::cout << "first point error " << std::endl; + } + +#if 0 + if(!mIsPathConstructed) + { + mPath.push_back(endPoint); + mIsPathConstructed = true; + } +#endif + return; } float PathFinder::getZAngleToNext(float x, float y) const @@ -645,5 +414,326 @@ namespace MWMechanics } + // TODO: Any multi threading concerns? + PathgridGraph::PathgridGraph() + : mCell(NULL) + , mIsGraphConstructed(false) + , mPathgrid(NULL) + , mGraph(0) + , mSCCId(0) + , mSCCIndex(0) + { + } + + /* + * mGraph is populated with the cost of each allowed edge. + * + * The data structure is based on the code in buildPath2() but modified. + * Please check git history if interested. + * + * mGraph[v].edges[i].index = w + * + * v = point index of location "from" + * i = index of edges from point v + * w = point index of location "to" + * + * + * Example: (notice from p(0) to p(2) is not allowed in this example) + * + * mGraph[0].edges[0].index = 1 + * .edges[1].index = 3 + * + * mGraph[1].edges[0].index = 0 + * .edges[1].index = 2 + * .edges[2].index = 3 + * + * mGraph[2].edges[0].index = 1 + * + * (etc, etc) + * + * + * low + * cost + * p(0) <---> p(1) <------------> p(2) + * ^ ^ + * | | + * | +-----> p(3) + * +----------------> + * high cost + */ + bool PathgridGraph::initPathgridGraph(const ESM::Cell* cell) + { + if(!cell) + { + std::cout << "init error " << std::endl; + return false; + } + mCell = cell; + mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); + + if(!mPathgrid) + { + std::cout << "init error " << std::endl; + return false; + } + + mGraph.resize(mPathgrid->mPoints.size()); + for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) + { + ConnectedPoint neighbour; + neighbour.cost = costAStar(mPathgrid->mPoints[mPathgrid->mEdges[i].mV0], + mPathgrid->mPoints[mPathgrid->mEdges[i].mV1]); + // forward path of the edge + neighbour.index = mPathgrid->mEdges[i].mV1; + mGraph[mPathgrid->mEdges[i].mV0].edges.push_back(neighbour); + // reverse path of the edge + // NOTE: These are redundant, ESM already contains the required reverse paths + //neighbour.index = mPathgrid->mEdges[i].mV0; + //mGraph[mPathgrid->mEdges[i].mV1].edges.push_back(neighbour); + } + buildConnectedPoints(); + mIsGraphConstructed = true; + //#if 0 + std::cout << "loading pathgrid " << + +"\""+ mPathgrid->mCell +"\"" + +", "+ std::to_string(mPathgrid->mData.mX) + +", "+ std::to_string(mPathgrid->mData.mY) + << std::endl; + //#endif + return true; + } + + // v is the pathgrid point index (some call them vertices) + void PathgridGraph::recursiveStrongConnect(int v) + { + mSCCPoint[v].first = mSCCIndex; // index + mSCCPoint[v].second = mSCCIndex; // lowlink + mSCCIndex++; + mSCCStack.push_back(v); + int w; + + for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) + { + w = mGraph[v].edges[i].index; + if(mSCCPoint[w].first == -1) // not visited + { + recursiveStrongConnect(w); // recurse + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].second); + } + else + { + if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].first); + } + } + + if(mSCCPoint[v].second == mSCCPoint[v].first) + { // new component + do + { + w = mSCCStack.back(); + mSCCStack.pop_back(); + mGraph[w].componentId = mSCCId; + } + while(w != v); + mSCCId++; + } + return; + } + + /* + * mGraph contains the strongly connected component group id's along + * with pre-calculated edge costs. + * + * A cell can have disjointed pathgrids, e.g. Seyda Neen has 3 + * + * mGraph for Seyda Neen will therefore have 3 different values. When + * selecting a random pathgrid point for AiWander, mGraph can be checked + * for quickly finding whether the destination is reachable. + * + * Otherwise, buildPath can automatically select a closest reachable end + * pathgrid point (reachable from the closest start point). + * + * Using Tarjan's algorithm: + * + * mGraph | graph G | + * mSCCPoint | V | derived from mPoints + * mGraph[v].edges | E (for v) | + * mSCCIndex | index | tracking smallest unused index + * mSCCStack | S | + * mGraph[v].edges[i].index | w | + * + */ + void PathgridGraph::buildConnectedPoints() + { + // both of these are set to zero in the constructor + //mSCCId = 0; // how many strongly connected components in this cell + //mSCCIndex = 0; + int pointsSize = mPathgrid->mPoints.size(); + mSCCPoint.resize(pointsSize, std::pair (-1, -1)); + mSCCStack.reserve(pointsSize); + + for(int v = 0; v < static_cast (pointsSize); v++) + { + if(mSCCPoint[v].first == -1) // undefined (haven't visited) + recursiveStrongConnect(v); + } + //#if 0 + std::cout << "components: " << std::to_string(mSCCId) + +", "+ mPathgrid->mCell + << std::endl; + //#endif + } + + bool PathgridGraph::isPointConnected(const int start, const int end) const + { + return (mGraph[start].componentId == mGraph[end].componentId); + } + + /* + * NOTE: Based on buildPath2(), please check git history if interested + * Should consider using a 3rd party library version (e.g. boost) + * + * Find the shortest path to the target goal using a well known algorithm. + * Uses mGraph which has pre-computed costs for allowed edges. It is assumed + * that mGraph is already constructed. + * + * Should be possible to make this MT safe. + * + * Returns path (a list of pathgrid point indexes) which may be empty. + * + * Input params: + * start, goal - pathgrid point indexes (for this cell) + * isExterior - used to determine whether to convert to world co-ordinates + * + * Variables: + * openset - point indexes to be traversed, lowest cost at the front + * closedset - point indexes already traversed + * gScore - past accumulated costs vector indexed by point index + * fScore - future estimated costs vector indexed by point index + * + * TODO: An intersting exercise might be to cache the paths created for a + * start/goal pair. To cache the results the paths need to be in + * pathgrid points form (currently they are converted to world + * co-ordinates). Essentially trading speed w/ memory. + */ + std::list PathgridGraph::aStarSearch(const int start, + const int goal, + bool isExterior) const + { + std::list path; + if(!isPointConnected(start, goal)) + { + return path; // there is no path, return an empty path + } + + int graphSize = mGraph.size(); + std::vector gScore; + gScore.resize(graphSize, -1); + std::vector fScore; + fScore.resize(graphSize, -1); + std::vector graphParent; + graphParent.resize(graphSize, -1); + + // gScore & fScore keep costs for each pathgrid point in mPoints + gScore[start] = 0; + fScore[start] = costAStar(mPathgrid->mPoints[start], mPathgrid->mPoints[goal]); + + std::list openset; + std::list closedset; + openset.push_back(start); + + int current = -1; + + while(!openset.empty()) + { + current = openset.front(); // front has the lowest cost + openset.pop_front(); + + if(current == goal) + break; + + closedset.push_back(current); // remember we've been here + + // check all edges for the current point index + for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) + { + if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].index) == + closedset.end()) + { + // not in closedset - i.e. have not traversed this edge destination + int dest = mGraph[current].edges[j].index; + float tentative_g = gScore[current] + mGraph[current].edges[j].cost; + bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); + if(!isInOpenSet + || tentative_g < gScore[dest]) + { + graphParent[dest] = current; + gScore[dest] = tentative_g; + fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest], + mPathgrid->mPoints[goal]); + if(!isInOpenSet) + { + // add this edge to openset, lowest cost goes to the front + // TODO: if this causes performance problems a hash table may help + std::list::iterator it = openset.begin(); + for(it = openset.begin(); it!= openset.end(); it++) + { + if(fScore[*it] > fScore[dest]) + break; + } + openset.insert(it, dest); + } + } + } // if in closedset, i.e. traversed this edge already, try the next edge + } + } + + if(current != goal) + return path; // for some reason couldn't build a path + + // reconstruct path to return, using world co-ordinates + float xCell = 0; + float yCell = 0; + if (isExterior) + { + xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; + yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; + } +//#if 0 + // for debugging only + int tmp = current; + if(tmp != goal) + { + std::cout << "aStarSearch: goal and result differ" << std::endl; + std::cout << "goal: " << std::to_string(goal) + +", result: "+ std::to_string(tmp) + << std::endl; + } + std::cout << "start: " << std::to_string(start) + +", goal: "+ std::to_string(goal) + +", result: "+ std::to_string(tmp) + << std::endl; +//#endif + + while(graphParent[current] != -1) + { + ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; +//#if 0 + // for debugging only + std::cout << " point: "+ std::to_string(current) + +", X: "+ std::to_string(pt.mX) + +", Y: "+ std::to_string(pt.mY) + << std::endl; +//#endif + pt.mX += xCell; + pt.mY += yCell; + path.push_front(pt); + current = graphParent[current]; + } + return path; + } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index ae849bff27..fddb293d9f 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -2,6 +2,7 @@ #define GAME_MWMECHANICS_PATHFINDING_H #include +#include #include #include @@ -34,8 +35,6 @@ namespace MWMechanics void clearPath(); - void buildPathgridGraph(const ESM::Pathgrid* pathGrid); - void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, const MWWorld::CellStore* cell, bool allowShortcuts = true); @@ -75,60 +74,73 @@ namespace MWMechanics mPath.push_back(point); } - // While a public method is defined here, it is anticipated that - // mSCComp will only be used internally. - std::vector getSCComp() const - { - return mSCComp; - } - private: - struct Edge - { - int destination; - float cost; - }; - struct Node - { - int label; - std::vector edges; - int parent;//used in pathfinding - }; - - std::vector mGScore; - std::vector mFScore; - - std::list aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell = 0, float yCell = 0); - void cleanUpAStar(); - - std::vector mGraph; bool mIsPathConstructed; - std::list mPath; - bool mIsGraphConstructed; - const MWWorld::CellStore* mCell; - // contains an integer indicating the groups of connected pathgrid points - // (all connected points will have the same value) + const ESM::Pathgrid *mPathgrid; + const MWWorld::CellStore* mCell; + }; + + class PathgridGraph + { + public: + PathgridGraph(); + + bool isGraphConstructed() const + { + return mIsGraphConstructed; + }; + + bool initPathgridGraph(const ESM::Cell *cell); + + // returns true if end point is strongly connected (i.e. reachable + // from start point) both start and end are pathgrid point indexes + bool isPointConnected(const int start, const int end) const; + + // isOutside is used whether to convert path to world co-ordinates + std::list aStarSearch(const int start, const int end, + const bool isOutside) const; + private: + + const ESM::Cell *mCell; + const ESM::Pathgrid *mPathgrid; + + struct ConnectedPoint // edge + { + int index; // pathgrid point index of neighbour + float cost; + }; + + struct Node // point + { + int componentId; + std::vector edges; // neighbours + }; + + // componentId is an integer indicating the groups of connected + // pathgrid points (all connected points will have the same value) // // In Seyda Neen there are 3: // // 52, 53 and 54 are one set (enclosed yard) - // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 are another (ship & office) + // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office) // all other pathgrid points are the third set // - std::vector mSCComp; - // variables used to calculate mSCComp + std::vector mGraph; + bool mIsGraphConstructed; + + // variables used to calculate connected components int mSCCId; int mSCCIndex; - std::list mSCCStack; + std::vector mSCCStack; typedef std::pair VPair; // first is index, second is lowlink std::vector mSCCPoint; - // methods used to calculate mSCComp + // methods used to calculate connected components void recursiveStrongConnect(int v); - void buildConnectedPoints(const ESM::Pathgrid* pathGrid); + void buildConnectedPoints(); }; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 3f1ef8ab24..dd105a665d 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -680,4 +680,34 @@ namespace MWWorld { return !(left==right); } + + bool CellStore::isPointConnected(const int start, const int end) const + { + if(!mPathgridGraph.isGraphConstructed()) + { + // Ugh... there must be a better way... + MWMechanics::PathgridGraph *p = const_cast (&mPathgridGraph); + + if(!p->initPathgridGraph(mCell)) + return false; + } + return mPathgridGraph.isPointConnected(start, end); + + } + + std::list CellStore::aStarSearch(const int start, const int end, + const bool isOutside) const + { + if(!mPathgridGraph.isGraphConstructed()) + { + MWMechanics::PathgridGraph *p = const_cast (&mPathgridGraph); + + if(!p->initPathgridGraph(mCell)) + { + std::list path; // empty + return path; + } + } + return mPathgridGraph.aStarSearch(start, end, isOutside); + } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 4b7c0011b5..3cca993f27 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -8,6 +8,8 @@ #include "esmstore.hpp" #include "cellreflist.hpp" +#include "../mwmechanics/pathfinding.hpp" + namespace ESM { struct CellState; @@ -141,6 +143,11 @@ namespace MWWorld throw std::runtime_error ("Storage for this type not exist in cells"); } + bool isPointConnected(const int start, const int end) const; + + std::list aStarSearch(const int start, const int end, + const bool isOutside) const; + private: template @@ -166,6 +173,8 @@ namespace MWWorld ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. + + MWMechanics::PathgridGraph mPathgridGraph; }; template<> From 5d422fec8abd5f52adb1de423cd0effb3d4a6cb4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 3 Apr 2014 13:00:19 +0200 Subject: [PATCH 042/135] fixed scene toolbar layout problems --- apps/opencs/view/world/scenetool.cpp | 1 + apps/opencs/view/world/scenetoolbar.cpp | 7 ++++++- apps/opencs/view/world/scenetoolbar.hpp | 3 +++ apps/opencs/view/world/scenetoolmode.cpp | 3 ++- apps/opencs/view/world/scenetoolmode.hpp | 1 + 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/scenetool.cpp b/apps/opencs/view/world/scenetool.cpp index 320deb1ba9..612b4c6d39 100644 --- a/apps/opencs/view/world/scenetool.cpp +++ b/apps/opencs/view/world/scenetool.cpp @@ -6,6 +6,7 @@ CSVWorld::SceneTool::SceneTool (SceneToolbar *parent) : QPushButton (parent) { setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); + setIconSize (QSize (parent->getIconSize(), parent->getIconSize())); setFixedSize (parent->getButtonSize(), parent->getButtonSize()); connect (this, SIGNAL (clicked()), this, SLOT (openRequest())); diff --git a/apps/opencs/view/world/scenetoolbar.cpp b/apps/opencs/view/world/scenetoolbar.cpp index 2972c53913..9eb02ce2f4 100644 --- a/apps/opencs/view/world/scenetoolbar.cpp +++ b/apps/opencs/view/world/scenetoolbar.cpp @@ -6,7 +6,7 @@ #include "scenetool.hpp" CSVWorld::SceneToolbar::SceneToolbar (int buttonSize, QWidget *parent) -: QWidget (parent), mButtonSize (buttonSize) +: QWidget (parent), mButtonSize (buttonSize), mIconSize (buttonSize-8) { setFixedWidth (mButtonSize); @@ -26,4 +26,9 @@ void CSVWorld::SceneToolbar::addTool (SceneTool *tool) int CSVWorld::SceneToolbar::getButtonSize() const { return mButtonSize; +} + +int CSVWorld::SceneToolbar::getIconSize() const +{ + return mIconSize; } \ No newline at end of file diff --git a/apps/opencs/view/world/scenetoolbar.hpp b/apps/opencs/view/world/scenetoolbar.hpp index f713ca3dff..731806cc5f 100644 --- a/apps/opencs/view/world/scenetoolbar.hpp +++ b/apps/opencs/view/world/scenetoolbar.hpp @@ -15,6 +15,7 @@ namespace CSVWorld QVBoxLayout *mLayout; int mButtonSize; + int mIconSize; public: @@ -23,6 +24,8 @@ namespace CSVWorld void addTool (SceneTool *tool); int getButtonSize() const; + + int getIconSize() const; }; } diff --git a/apps/opencs/view/world/scenetoolmode.cpp b/apps/opencs/view/world/scenetoolmode.cpp index 281d703b65..73b01ae3a8 100644 --- a/apps/opencs/view/world/scenetoolmode.cpp +++ b/apps/opencs/view/world/scenetoolmode.cpp @@ -8,7 +8,7 @@ #include "scenetoolbar.hpp" CSVWorld::SceneToolMode::SceneToolMode (SceneToolbar *parent) -: SceneTool (parent), mButtonSize (parent->getButtonSize()) +: SceneTool (parent), mButtonSize (parent->getButtonSize()), mIconSize (parent->getIconSize()) { mPanel = new QFrame (this, Qt::Popup); @@ -29,6 +29,7 @@ void CSVWorld::SceneToolMode::addButton (const std::string& icon, const std::str { QPushButton *button = new QPushButton (QIcon (QPixmap (icon.c_str())), "", mPanel); button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); + button->setIconSize (QSize (mIconSize, mIconSize)); button->setFixedSize (mButtonSize, mButtonSize); mLayout->addWidget (button); diff --git a/apps/opencs/view/world/scenetoolmode.hpp b/apps/opencs/view/world/scenetoolmode.hpp index a8fe2b5a6c..a156c0c95a 100644 --- a/apps/opencs/view/world/scenetoolmode.hpp +++ b/apps/opencs/view/world/scenetoolmode.hpp @@ -20,6 +20,7 @@ namespace CSVWorld QHBoxLayout *mLayout; std::map mButtons; // widget, id int mButtonSize; + int mIconSize; public: From 325d0616bbe8e10d661fff03b9e64778865698b1 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 3 Apr 2014 22:17:45 +1100 Subject: [PATCH 043/135] Cleanup debug statements. --- apps/openmw/mwmechanics/pathfinding.cpp | 85 ++----------------------- 1 file changed, 6 insertions(+), 79 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index f56cfad5fc..1ef05eda89 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -225,9 +225,6 @@ namespace MWMechanics if(mCell != cell || !mPathgrid) { mCell = cell; - - // Cache pathgrid as mPathgrid and update on cell changes. There - // might be a small gain in avoiding to search for it. mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); } @@ -236,13 +233,6 @@ namespace MWMechanics // physics take care of any blockages. if(!mPathgrid || mPathgrid->mPoints.empty()) { -//#if 0 - std::cout << "no pathgrid " << - +"\"" +mCell->getCell()->mName+ "\"" - +", " +std::to_string(mCell->getCell()->mData.mX) - +", " +std::to_string(mCell->getCell()->mData.mY) - << std::endl; -//#endif mPath.push_back(endPoint); mIsPathConstructed = true; return; @@ -257,16 +247,6 @@ namespace MWMechanics yCell = mCell->getCell()->mData.mY * ESM::Land::REAL_SIZE; } - - - - - - - - - - // NOTE: It is possible that getClosestPoint returns a pathgrind point index // that is unreachable in some situations. e.g. actor is standing // outside an area enclosed by walls, but there is a pathgrid @@ -280,14 +260,7 @@ namespace MWMechanics std::pair endNode = getClosestReachablePoint(mPathgrid, cell, Ogre::Vector3(endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ), startNode); -//#if 0 - if(!mPathgrid) - std::cout << "no pathgrid " << - +"\"" +mCell->getCell()->mName+ "\"" - +", " +std::to_string(mCell->getCell()->mData.mX) - +", " +std::to_string(mCell->getCell()->mData.mY) - << std::endl; -//#endif + // this shouldn't really happen, but just in case if(endNode.first != -1) { @@ -309,33 +282,14 @@ namespace MWMechanics mPath.push_back(endPoint); } else - { mIsPathConstructed = false; - std::cout << "empty path error " << std::endl; - } - //mIsPathConstructed = false; } else - { mIsPathConstructed = false; - std::cout << "second point error " << std::endl; - } - //mIsPathConstructed = false; } else - { - // FIXME: shouldn't return endpoint if first point error? mIsPathConstructed = false; - std::cout << "first point error " << std::endl; - } -#if 0 - if(!mIsPathConstructed) - { - mPath.push_back(endPoint); - mIsPathConstructed = true; - } -#endif return; } @@ -464,18 +418,13 @@ namespace MWMechanics bool PathgridGraph::initPathgridGraph(const ESM::Cell* cell) { if(!cell) - { - std::cout << "init error " << std::endl; return false; - } + mCell = cell; mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); if(!mPathgrid) - { - std::cout << "init error " << std::endl; return false; - } mGraph.resize(mPathgrid->mPoints.size()); for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) @@ -493,13 +442,13 @@ namespace MWMechanics } buildConnectedPoints(); mIsGraphConstructed = true; - //#if 0 +//#if 0 std::cout << "loading pathgrid " << +"\""+ mPathgrid->mCell +"\"" +", "+ std::to_string(mPathgrid->mData.mX) +", "+ std::to_string(mPathgrid->mData.mY) << std::endl; - //#endif +//#endif return true; } @@ -580,11 +529,11 @@ namespace MWMechanics if(mSCCPoint[v].first == -1) // undefined (haven't visited) recursiveStrongConnect(v); } - //#if 0 +//#if 0 std::cout << "components: " << std::to_string(mSCCId) +", "+ mPathgrid->mCell << std::endl; - //#endif +//#endif } bool PathgridGraph::isPointConnected(const int start, const int end) const @@ -702,32 +651,10 @@ namespace MWMechanics xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; } -//#if 0 - // for debugging only - int tmp = current; - if(tmp != goal) - { - std::cout << "aStarSearch: goal and result differ" << std::endl; - std::cout << "goal: " << std::to_string(goal) - +", result: "+ std::to_string(tmp) - << std::endl; - } - std::cout << "start: " << std::to_string(start) - +", goal: "+ std::to_string(goal) - +", result: "+ std::to_string(tmp) - << std::endl; -//#endif while(graphParent[current] != -1) { ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; -//#if 0 - // for debugging only - std::cout << " point: "+ std::to_string(current) - +", X: "+ std::to_string(pt.mX) - +", Y: "+ std::to_string(pt.mY) - << std::endl; -//#endif pt.mX += xCell; pt.mY += yCell; path.push_front(pt); From 3a58da9ad78ade99a388923dafdfb29bc4110d8d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 3 Apr 2014 13:30:22 +0200 Subject: [PATCH 044/135] size adjustment --- apps/opencs/view/world/scenetoolbar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/world/scenetoolbar.cpp b/apps/opencs/view/world/scenetoolbar.cpp index 9eb02ce2f4..d60240da77 100644 --- a/apps/opencs/view/world/scenetoolbar.cpp +++ b/apps/opencs/view/world/scenetoolbar.cpp @@ -6,7 +6,7 @@ #include "scenetool.hpp" CSVWorld::SceneToolbar::SceneToolbar (int buttonSize, QWidget *parent) -: QWidget (parent), mButtonSize (buttonSize), mIconSize (buttonSize-8) +: QWidget (parent), mButtonSize (buttonSize), mIconSize (buttonSize-6) { setFixedWidth (mButtonSize); From a8b2eb1fe9f16e51602c6cee8717082211555fbc Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 3 Apr 2014 22:49:22 +1100 Subject: [PATCH 045/135] Make Travis happy. --- apps/openmw/mwmechanics/pathfinding.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 1ef05eda89..9fcd335e41 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -442,13 +442,13 @@ namespace MWMechanics } buildConnectedPoints(); mIsGraphConstructed = true; -//#if 0 +#if 0 std::cout << "loading pathgrid " << +"\""+ mPathgrid->mCell +"\"" +", "+ std::to_string(mPathgrid->mData.mX) +", "+ std::to_string(mPathgrid->mData.mY) << std::endl; -//#endif +#endif return true; } @@ -529,11 +529,11 @@ namespace MWMechanics if(mSCCPoint[v].first == -1) // undefined (haven't visited) recursiveStrongConnect(v); } -//#if 0 +#if 0 std::cout << "components: " << std::to_string(mSCCId) +", "+ mPathgrid->mCell << std::endl; -//#endif +#endif } bool PathgridGraph::isPointConnected(const int start, const int end) const From baf30ba29295a8535f3af18961060e72d8ec9a09 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 3 Apr 2014 14:44:48 +0200 Subject: [PATCH 046/135] added grid tool (does not work yet) --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/world/scenesubview.cpp | 13 ++++ apps/opencs/view/world/scenetoolgrid.cpp | 75 ++++++++++++++++++++++++ apps/opencs/view/world/scenetoolgrid.hpp | 29 +++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/view/world/scenetoolgrid.cpp create mode 100644 apps/opencs/view/world/scenetoolgrid.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index a7a694463e..05cc93f896 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -60,7 +60,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool - scenetoolmode infocreator scriptedit dialoguesubview previewsubview + scenetoolmode infocreator scriptedit dialoguesubview previewsubview scenetoolgrid ) opencs_units (view/render diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index c075cb4d63..dedaf014b6 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -18,6 +18,7 @@ #include "creator.hpp" #include "scenetoolbar.hpp" #include "scenetoolmode.hpp" +#include "scenetoolgrid.hpp" CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id) @@ -36,6 +37,8 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SceneToolbar *toolbar = new SceneToolbar (48, this); + SceneToolGrid *gridTool = 0; + if (id.getId()=="sys::default") { CSVRender::PagedWorldspaceWidget *widget = new CSVRender::PagedWorldspaceWidget (this); @@ -44,6 +47,13 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SIGNAL (cellIndexChanged (const std::pair&, const std::pair&)), this, SLOT (cellIndexChanged (const std::pair&, const std::pair&))); + + gridTool = new SceneToolGrid (toolbar); + + connect (widget, + SIGNAL (cellIndexChanged (const std::pair&, const std::pair&)), + gridTool, + SLOT (cellIndexChanged (const std::pair&, const std::pair&))); } else mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); @@ -54,6 +64,9 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); toolbar->addTool (lightingTool); + if (gridTool) + toolbar->addTool (gridTool); + layout2->addWidget (toolbar, 0); layout2->addWidget (mScene, 1); diff --git a/apps/opencs/view/world/scenetoolgrid.cpp b/apps/opencs/view/world/scenetoolgrid.cpp new file mode 100644 index 0000000000..0769be168c --- /dev/null +++ b/apps/opencs/view/world/scenetoolgrid.cpp @@ -0,0 +1,75 @@ + +#include "scenetoolgrid.hpp" + +#include + +#include +#include +#include + +#include "scenetoolbar.hpp" + +CSVWorld::SceneToolGrid::SceneToolGrid (SceneToolbar *parent) +: SceneTool (parent), mIconSize (parent->getIconSize()) +{ +} + +void CSVWorld::SceneToolGrid::showPanel (const QPoint& position) +{ + + +} + +void CSVWorld::SceneToolGrid::cellIndexChanged (const std::pair& min, + const std::pair& max) +{ + /// \todo make font size configurable + const int fontSize = 8; + + /// \todo replace with proper icon + QPixmap image (mIconSize, mIconSize); + image.fill (QColor (0, 0, 0, 0)); + + { + QPainter painter (&image); + painter.setPen (Qt::black); + QFont font (QApplication::font().family(), fontSize); + painter.setFont (font); + + QFontMetrics metrics (font); + + if (min==max) + { + // single cell + std::ostringstream stream; + stream << min.first << ", " << min.second; + + QString text = QString::fromUtf8 (stream.str().c_str()); + + painter.drawText (QPoint ((mIconSize-metrics.width (text))/2, mIconSize/2+fontSize/2), + text); + } + else + { + // range + { + std::ostringstream stream; + stream << min.first << ", " << min.second; + painter.drawText (QPoint (0, mIconSize), + QString::fromUtf8 (stream.str().c_str())); + } + + { + std::ostringstream stream; + stream << max.first << ", " << max.second; + + QString text = QString::fromUtf8 (stream.str().c_str()); + + painter.drawText (QPoint (mIconSize-metrics.width (text), fontSize), text); + } + } + } + + QIcon icon (image); + setIcon (icon); +} \ No newline at end of file diff --git a/apps/opencs/view/world/scenetoolgrid.hpp b/apps/opencs/view/world/scenetoolgrid.hpp new file mode 100644 index 0000000000..917df2a168 --- /dev/null +++ b/apps/opencs/view/world/scenetoolgrid.hpp @@ -0,0 +1,29 @@ +#ifndef CSV_WORLD_SCENETOOL_GRID_H +#define CSV_WORLD_SCENETOOL_GRID_H + +#include "scenetool.hpp" + +namespace CSVWorld +{ + class SceneToolbar; + + ///< \brief Cell grid selector tool + class SceneToolGrid : public SceneTool + { + Q_OBJECT + + int mIconSize; + + public: + + SceneToolGrid (SceneToolbar *parent); + + virtual void showPanel (const QPoint& position); + + public slots: + + void cellIndexChanged (const std::pair& min, const std::pair& max); + }; +} + +#endif From 040d4f8fc4495be3316c5ad47928c4eed6138f4a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 4 Apr 2014 06:13:47 +1100 Subject: [PATCH 047/135] Move PathgridGraph into separate files. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 335 ----------------------- apps/openmw/mwmechanics/pathfinding.hpp | 60 ---- apps/openmw/mwmechanics/pathgrid.cpp | 348 ++++++++++++++++++++++++ apps/openmw/mwmechanics/pathgrid.hpp | 75 +++++ apps/openmw/mwworld/cellstore.cpp | 19 +- apps/openmw/mwworld/cellstore.hpp | 2 +- 7 files changed, 426 insertions(+), 415 deletions(-) create mode 100644 apps/openmw/mwmechanics/pathgrid.cpp create mode 100644 apps/openmw/mwmechanics/pathgrid.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index d1f7c45f31..ea4756ac4d 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -68,7 +68,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow - aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting + aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting disease pickpocket levelledlist combat steering ) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 9fcd335e41..a4166d83b9 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -1,7 +1,5 @@ #include "pathfinding.hpp" -#include - #include "OgreMath.h" #include "OgreVector3.h" @@ -37,44 +35,6 @@ namespace return sqrt(x * x + y * y + z * z); } - // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html - // - // One of the smallest cost in Seyda Neen is between points 77 & 78: - // pt x y - // 77 = 8026, 4480 - // 78 = 7986, 4218 - // - // Euclidean distance is about 262 (ignoring z) and Manhattan distance is 300 - // (again ignoring z). Using a value of about 300 for D seems like a reasonable - // starting point for experiments. If in doubt, just use value 1. - // - // The distance between 3 & 4 are pretty small, too. - // 3 = 5435, 223 - // 4 = 5948, 193 - // - // Approx. 514 Euclidean distance and 533 Manhattan distance. - // - float manhattan(const ESM::Pathgrid::Point a, const ESM::Pathgrid::Point b) - { - return 300 * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ)); - } - - // Choose a heuristics - Note that these may not be the best for directed - // graphs with non-uniform edge costs. - // - // distance: - // - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2) - // - slower but more accurate - // - // Manhattan: - // - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z| - // - faster but not the shortest path - float costAStar(const ESM::Pathgrid::Point a, const ESM::Pathgrid::Point b) - { - //return distance(a, b); - return manhattan(a, b); - } - // Slightly cheaper version for comparisons. // Caller needs to be careful for very short distances (i.e. less than 1) // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 @@ -368,299 +328,4 @@ namespace MWMechanics } - // TODO: Any multi threading concerns? - PathgridGraph::PathgridGraph() - : mCell(NULL) - , mIsGraphConstructed(false) - , mPathgrid(NULL) - , mGraph(0) - , mSCCId(0) - , mSCCIndex(0) - { - } - - /* - * mGraph is populated with the cost of each allowed edge. - * - * The data structure is based on the code in buildPath2() but modified. - * Please check git history if interested. - * - * mGraph[v].edges[i].index = w - * - * v = point index of location "from" - * i = index of edges from point v - * w = point index of location "to" - * - * - * Example: (notice from p(0) to p(2) is not allowed in this example) - * - * mGraph[0].edges[0].index = 1 - * .edges[1].index = 3 - * - * mGraph[1].edges[0].index = 0 - * .edges[1].index = 2 - * .edges[2].index = 3 - * - * mGraph[2].edges[0].index = 1 - * - * (etc, etc) - * - * - * low - * cost - * p(0) <---> p(1) <------------> p(2) - * ^ ^ - * | | - * | +-----> p(3) - * +----------------> - * high cost - */ - bool PathgridGraph::initPathgridGraph(const ESM::Cell* cell) - { - if(!cell) - return false; - - mCell = cell; - mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); - - if(!mPathgrid) - return false; - - mGraph.resize(mPathgrid->mPoints.size()); - for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) - { - ConnectedPoint neighbour; - neighbour.cost = costAStar(mPathgrid->mPoints[mPathgrid->mEdges[i].mV0], - mPathgrid->mPoints[mPathgrid->mEdges[i].mV1]); - // forward path of the edge - neighbour.index = mPathgrid->mEdges[i].mV1; - mGraph[mPathgrid->mEdges[i].mV0].edges.push_back(neighbour); - // reverse path of the edge - // NOTE: These are redundant, ESM already contains the required reverse paths - //neighbour.index = mPathgrid->mEdges[i].mV0; - //mGraph[mPathgrid->mEdges[i].mV1].edges.push_back(neighbour); - } - buildConnectedPoints(); - mIsGraphConstructed = true; -#if 0 - std::cout << "loading pathgrid " << - +"\""+ mPathgrid->mCell +"\"" - +", "+ std::to_string(mPathgrid->mData.mX) - +", "+ std::to_string(mPathgrid->mData.mY) - << std::endl; -#endif - return true; - } - - // v is the pathgrid point index (some call them vertices) - void PathgridGraph::recursiveStrongConnect(int v) - { - mSCCPoint[v].first = mSCCIndex; // index - mSCCPoint[v].second = mSCCIndex; // lowlink - mSCCIndex++; - mSCCStack.push_back(v); - int w; - - for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) - { - w = mGraph[v].edges[i].index; - if(mSCCPoint[w].first == -1) // not visited - { - recursiveStrongConnect(w); // recurse - mSCCPoint[v].second = std::min(mSCCPoint[v].second, - mSCCPoint[w].second); - } - else - { - if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) - mSCCPoint[v].second = std::min(mSCCPoint[v].second, - mSCCPoint[w].first); - } - } - - if(mSCCPoint[v].second == mSCCPoint[v].first) - { // new component - do - { - w = mSCCStack.back(); - mSCCStack.pop_back(); - mGraph[w].componentId = mSCCId; - } - while(w != v); - mSCCId++; - } - return; - } - - /* - * mGraph contains the strongly connected component group id's along - * with pre-calculated edge costs. - * - * A cell can have disjointed pathgrids, e.g. Seyda Neen has 3 - * - * mGraph for Seyda Neen will therefore have 3 different values. When - * selecting a random pathgrid point for AiWander, mGraph can be checked - * for quickly finding whether the destination is reachable. - * - * Otherwise, buildPath can automatically select a closest reachable end - * pathgrid point (reachable from the closest start point). - * - * Using Tarjan's algorithm: - * - * mGraph | graph G | - * mSCCPoint | V | derived from mPoints - * mGraph[v].edges | E (for v) | - * mSCCIndex | index | tracking smallest unused index - * mSCCStack | S | - * mGraph[v].edges[i].index | w | - * - */ - void PathgridGraph::buildConnectedPoints() - { - // both of these are set to zero in the constructor - //mSCCId = 0; // how many strongly connected components in this cell - //mSCCIndex = 0; - int pointsSize = mPathgrid->mPoints.size(); - mSCCPoint.resize(pointsSize, std::pair (-1, -1)); - mSCCStack.reserve(pointsSize); - - for(int v = 0; v < static_cast (pointsSize); v++) - { - if(mSCCPoint[v].first == -1) // undefined (haven't visited) - recursiveStrongConnect(v); - } -#if 0 - std::cout << "components: " << std::to_string(mSCCId) - +", "+ mPathgrid->mCell - << std::endl; -#endif - } - - bool PathgridGraph::isPointConnected(const int start, const int end) const - { - return (mGraph[start].componentId == mGraph[end].componentId); - } - - /* - * NOTE: Based on buildPath2(), please check git history if interested - * Should consider using a 3rd party library version (e.g. boost) - * - * Find the shortest path to the target goal using a well known algorithm. - * Uses mGraph which has pre-computed costs for allowed edges. It is assumed - * that mGraph is already constructed. - * - * Should be possible to make this MT safe. - * - * Returns path (a list of pathgrid point indexes) which may be empty. - * - * Input params: - * start, goal - pathgrid point indexes (for this cell) - * isExterior - used to determine whether to convert to world co-ordinates - * - * Variables: - * openset - point indexes to be traversed, lowest cost at the front - * closedset - point indexes already traversed - * gScore - past accumulated costs vector indexed by point index - * fScore - future estimated costs vector indexed by point index - * - * TODO: An intersting exercise might be to cache the paths created for a - * start/goal pair. To cache the results the paths need to be in - * pathgrid points form (currently they are converted to world - * co-ordinates). Essentially trading speed w/ memory. - */ - std::list PathgridGraph::aStarSearch(const int start, - const int goal, - bool isExterior) const - { - std::list path; - if(!isPointConnected(start, goal)) - { - return path; // there is no path, return an empty path - } - - int graphSize = mGraph.size(); - std::vector gScore; - gScore.resize(graphSize, -1); - std::vector fScore; - fScore.resize(graphSize, -1); - std::vector graphParent; - graphParent.resize(graphSize, -1); - - // gScore & fScore keep costs for each pathgrid point in mPoints - gScore[start] = 0; - fScore[start] = costAStar(mPathgrid->mPoints[start], mPathgrid->mPoints[goal]); - - std::list openset; - std::list closedset; - openset.push_back(start); - - int current = -1; - - while(!openset.empty()) - { - current = openset.front(); // front has the lowest cost - openset.pop_front(); - - if(current == goal) - break; - - closedset.push_back(current); // remember we've been here - - // check all edges for the current point index - for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) - { - if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].index) == - closedset.end()) - { - // not in closedset - i.e. have not traversed this edge destination - int dest = mGraph[current].edges[j].index; - float tentative_g = gScore[current] + mGraph[current].edges[j].cost; - bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); - if(!isInOpenSet - || tentative_g < gScore[dest]) - { - graphParent[dest] = current; - gScore[dest] = tentative_g; - fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest], - mPathgrid->mPoints[goal]); - if(!isInOpenSet) - { - // add this edge to openset, lowest cost goes to the front - // TODO: if this causes performance problems a hash table may help - std::list::iterator it = openset.begin(); - for(it = openset.begin(); it!= openset.end(); it++) - { - if(fScore[*it] > fScore[dest]) - break; - } - openset.insert(it, dest); - } - } - } // if in closedset, i.e. traversed this edge already, try the next edge - } - } - - if(current != goal) - return path; // for some reason couldn't build a path - - // reconstruct path to return, using world co-ordinates - float xCell = 0; - float yCell = 0; - if (isExterior) - { - xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; - yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; - } - - while(graphParent[current] != -1) - { - ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; - pt.mX += xCell; - pt.mY += yCell; - path.push_front(pt); - current = graphParent[current]; - } - return path; - } } - diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index fddb293d9f..eb093ad699 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -2,7 +2,6 @@ #define GAME_MWMECHANICS_PATHFINDING_H #include -#include #include #include @@ -83,65 +82,6 @@ namespace MWMechanics const ESM::Pathgrid *mPathgrid; const MWWorld::CellStore* mCell; }; - - class PathgridGraph - { - public: - PathgridGraph(); - - bool isGraphConstructed() const - { - return mIsGraphConstructed; - }; - - bool initPathgridGraph(const ESM::Cell *cell); - - // returns true if end point is strongly connected (i.e. reachable - // from start point) both start and end are pathgrid point indexes - bool isPointConnected(const int start, const int end) const; - - // isOutside is used whether to convert path to world co-ordinates - std::list aStarSearch(const int start, const int end, - const bool isOutside) const; - private: - - const ESM::Cell *mCell; - const ESM::Pathgrid *mPathgrid; - - struct ConnectedPoint // edge - { - int index; // pathgrid point index of neighbour - float cost; - }; - - struct Node // point - { - int componentId; - std::vector edges; // neighbours - }; - - // componentId is an integer indicating the groups of connected - // pathgrid points (all connected points will have the same value) - // - // In Seyda Neen there are 3: - // - // 52, 53 and 54 are one set (enclosed yard) - // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office) - // all other pathgrid points are the third set - // - std::vector mGraph; - bool mIsGraphConstructed; - - // variables used to calculate connected components - int mSCCId; - int mSCCIndex; - std::vector mSCCStack; - typedef std::pair VPair; // first is index, second is lowlink - std::vector mSCCPoint; - // methods used to calculate connected components - void recursiveStrongConnect(int v); - void buildConnectedPoints(); - }; } #endif diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp new file mode 100644 index 0000000000..5580e5d3f2 --- /dev/null +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -0,0 +1,348 @@ +#include "pathgrid.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwworld/cellstore.hpp" + +namespace +{ + // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html + // + // One of the smallest cost in Seyda Neen is between points 77 & 78: + // pt x y + // 77 = 8026, 4480 + // 78 = 7986, 4218 + // + // Euclidean distance is about 262 (ignoring z) and Manhattan distance is 300 + // (again ignoring z). Using a value of about 300 for D seems like a reasonable + // starting point for experiments. If in doubt, just use value 1. + // + // The distance between 3 & 4 are pretty small, too. + // 3 = 5435, 223 + // 4 = 5948, 193 + // + // Approx. 514 Euclidean distance and 533 Manhattan distance. + // + float manhattan(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b) + { + return 300 * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ)); + } + + // Choose a heuristics - Note that these may not be the best for directed + // graphs with non-uniform edge costs. + // + // distance: + // - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2) + // - slower but more accurate + // + // Manhattan: + // - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z| + // - faster but not the shortest path + float costAStar(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b) + { + //return distance(a, b); + return manhattan(a, b); + } +} + +namespace MWMechanics +{ + PathgridGraph::PathgridGraph() + : mCell(NULL) + , mIsGraphConstructed(false) + , mPathgrid(NULL) + , mGraph(0) + , mSCCId(0) + , mSCCIndex(0) + { + } + + /* + * mGraph is populated with the cost of each allowed edge. + * + * The data structure is based on the code in buildPath2() but modified. + * Please check git history if interested. + * + * mGraph[v].edges[i].index = w + * + * v = point index of location "from" + * i = index of edges from point v + * w = point index of location "to" + * + * + * Example: (notice from p(0) to p(2) is not allowed in this example) + * + * mGraph[0].edges[0].index = 1 + * .edges[1].index = 3 + * + * mGraph[1].edges[0].index = 0 + * .edges[1].index = 2 + * .edges[2].index = 3 + * + * mGraph[2].edges[0].index = 1 + * + * (etc, etc) + * + * + * low + * cost + * p(0) <---> p(1) <------------> p(2) + * ^ ^ + * | | + * | +-----> p(3) + * +----------------> + * high cost + */ + bool PathgridGraph::load(const ESM::Cell* cell) + { + if(!cell) + return false; + + mCell = cell; + mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); + + if(!mPathgrid) + return false; + + if(mIsGraphConstructed) + return true; + + mGraph.resize(mPathgrid->mPoints.size()); + for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) + { + ConnectedPoint neighbour; + neighbour.cost = costAStar(mPathgrid->mPoints[mPathgrid->mEdges[i].mV0], + mPathgrid->mPoints[mPathgrid->mEdges[i].mV1]); + // forward path of the edge + neighbour.index = mPathgrid->mEdges[i].mV1; + mGraph[mPathgrid->mEdges[i].mV0].edges.push_back(neighbour); + // reverse path of the edge + // NOTE: These are redundant, ESM already contains the required reverse paths + //neighbour.index = mPathgrid->mEdges[i].mV0; + //mGraph[mPathgrid->mEdges[i].mV1].edges.push_back(neighbour); + } + buildConnectedPoints(); + mIsGraphConstructed = true; +#if 0 + std::cout << "loading pathgrid " << + +"\""+ mPathgrid->mCell +"\"" + +", "+ std::to_string(mPathgrid->mData.mX) + +", "+ std::to_string(mPathgrid->mData.mY) + << std::endl; +#endif + return true; + } + + // v is the pathgrid point index (some call them vertices) + void PathgridGraph::recursiveStrongConnect(int v) + { + mSCCPoint[v].first = mSCCIndex; // index + mSCCPoint[v].second = mSCCIndex; // lowlink + mSCCIndex++; + mSCCStack.push_back(v); + int w; + + for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) + { + w = mGraph[v].edges[i].index; + if(mSCCPoint[w].first == -1) // not visited + { + recursiveStrongConnect(w); // recurse + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].second); + } + else + { + if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].first); + } + } + + if(mSCCPoint[v].second == mSCCPoint[v].first) + { // new component + do + { + w = mSCCStack.back(); + mSCCStack.pop_back(); + mGraph[w].componentId = mSCCId; + } + while(w != v); + mSCCId++; + } + return; + } + + /* + * mGraph contains the strongly connected component group id's along + * with pre-calculated edge costs. + * + * A cell can have disjointed pathgrids, e.g. Seyda Neen has 3 + * + * mGraph for Seyda Neen will therefore have 3 different values. When + * selecting a random pathgrid point for AiWander, mGraph can be checked + * for quickly finding whether the destination is reachable. + * + * Otherwise, buildPath can automatically select a closest reachable end + * pathgrid point (reachable from the closest start point). + * + * Using Tarjan's algorithm: + * + * mGraph | graph G | + * mSCCPoint | V | derived from mPoints + * mGraph[v].edges | E (for v) | + * mSCCIndex | index | tracking smallest unused index + * mSCCStack | S | + * mGraph[v].edges[i].index | w | + * + */ + void PathgridGraph::buildConnectedPoints() + { + // both of these are set to zero in the constructor + //mSCCId = 0; // how many strongly connected components in this cell + //mSCCIndex = 0; + int pointsSize = mPathgrid->mPoints.size(); + mSCCPoint.resize(pointsSize, std::pair (-1, -1)); + mSCCStack.reserve(pointsSize); + + for(int v = 0; v < static_cast (pointsSize); v++) + { + if(mSCCPoint[v].first == -1) // undefined (haven't visited) + recursiveStrongConnect(v); + } +#if 0 + std::cout << "components: " << std::to_string(mSCCId) + +", "+ mPathgrid->mCell + << std::endl; +#endif + } + + bool PathgridGraph::isPointConnected(const int start, const int end) const + { + return (mGraph[start].componentId == mGraph[end].componentId); + } + + /* + * NOTE: Based on buildPath2(), please check git history if interested + * Should consider using a 3rd party library version (e.g. boost) + * + * Find the shortest path to the target goal using a well known algorithm. + * Uses mGraph which has pre-computed costs for allowed edges. It is assumed + * that mGraph is already constructed. + * + * Should be possible to make this MT safe. + * + * Returns path (a list of pathgrid point indexes) which may be empty. + * + * Input params: + * start, goal - pathgrid point indexes (for this cell) + * isExterior - used to determine whether to convert to world co-ordinates + * + * Variables: + * openset - point indexes to be traversed, lowest cost at the front + * closedset - point indexes already traversed + * gScore - past accumulated costs vector indexed by point index + * fScore - future estimated costs vector indexed by point index + * + * TODO: An intersting exercise might be to cache the paths created for a + * start/goal pair. To cache the results the paths need to be in + * pathgrid points form (currently they are converted to world + * co-ordinates). Essentially trading speed w/ memory. + */ + std::list PathgridGraph::aStarSearch(const int start, + const int goal, + bool isExterior) const + { + std::list path; + if(!isPointConnected(start, goal)) + { + return path; // there is no path, return an empty path + } + + int graphSize = mGraph.size(); + std::vector gScore; + gScore.resize(graphSize, -1); + std::vector fScore; + fScore.resize(graphSize, -1); + std::vector graphParent; + graphParent.resize(graphSize, -1); + + // gScore & fScore keep costs for each pathgrid point in mPoints + gScore[start] = 0; + fScore[start] = costAStar(mPathgrid->mPoints[start], mPathgrid->mPoints[goal]); + + std::list openset; + std::list closedset; + openset.push_back(start); + + int current = -1; + + while(!openset.empty()) + { + current = openset.front(); // front has the lowest cost + openset.pop_front(); + + if(current == goal) + break; + + closedset.push_back(current); // remember we've been here + + // check all edges for the current point index + for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) + { + if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].index) == + closedset.end()) + { + // not in closedset - i.e. have not traversed this edge destination + int dest = mGraph[current].edges[j].index; + float tentative_g = gScore[current] + mGraph[current].edges[j].cost; + bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); + if(!isInOpenSet + || tentative_g < gScore[dest]) + { + graphParent[dest] = current; + gScore[dest] = tentative_g; + fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest], + mPathgrid->mPoints[goal]); + if(!isInOpenSet) + { + // add this edge to openset, lowest cost goes to the front + // TODO: if this causes performance problems a hash table may help + std::list::iterator it = openset.begin(); + for(it = openset.begin(); it!= openset.end(); it++) + { + if(fScore[*it] > fScore[dest]) + break; + } + openset.insert(it, dest); + } + } + } // if in closedset, i.e. traversed this edge already, try the next edge + } + } + + if(current != goal) + return path; // for some reason couldn't build a path + + // reconstruct path to return, using world co-ordinates + float xCell = 0; + float yCell = 0; + if (isExterior) + { + xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; + yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; + } + + while(graphParent[current] != -1) + { + ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; + pt.mX += xCell; + pt.mY += yCell; + path.push_front(pt); + current = graphParent[current]; + } + return path; + } +} + diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp new file mode 100644 index 0000000000..9f3a997c6e --- /dev/null +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -0,0 +1,75 @@ +#ifndef GAME_MWMECHANICS_PATHGRID_H +#define GAME_MWMECHANICS_PATHGRID_H + +#include +#include + +namespace ESM +{ + class Cell; +} + +namespace MWWorld +{ + class CellStore; +} + +namespace MWMechanics +{ + class PathgridGraph + { + public: + PathgridGraph(); + + bool load(const ESM::Cell *cell); + + // returns true if end point is strongly connected (i.e. reachable + // from start point) both start and end are pathgrid point indexes + bool isPointConnected(const int start, const int end) const; + + // isOutside is used whether to convert path to world co-ordinates + std::list aStarSearch(const int start, + const int end, + const bool isOutside) const; + private: + + const ESM::Cell *mCell; + const ESM::Pathgrid *mPathgrid; + + struct ConnectedPoint // edge + { + int index; // pathgrid point index of neighbour + float cost; + }; + + struct Node // point + { + int componentId; + std::vector edges; // neighbours + }; + + // componentId is an integer indicating the groups of connected + // pathgrid points (all connected points will have the same value) + // + // In Seyda Neen there are 3: + // + // 52, 53 and 54 are one set (enclosed yard) + // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office) + // all other pathgrid points are the third set + // + std::vector mGraph; + bool mIsGraphConstructed; + + // variables used to calculate connected components + int mSCCId; + int mSCCIndex; + std::vector mSCCStack; + typedef std::pair VPair; // first is index, second is lowlink + std::vector mSCCPoint; + // methods used to calculate connected components + void recursiveStrongConnect(int v); + void buildConnectedPoints(); + }; +} + +#endif diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index dd105a665d..d39f22c3fb 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -363,6 +363,7 @@ namespace MWWorld loadRefs (store, esm); mState = State_Loaded; + mPathgridGraph.load(mCell); } } @@ -683,14 +684,6 @@ namespace MWWorld bool CellStore::isPointConnected(const int start, const int end) const { - if(!mPathgridGraph.isGraphConstructed()) - { - // Ugh... there must be a better way... - MWMechanics::PathgridGraph *p = const_cast (&mPathgridGraph); - - if(!p->initPathgridGraph(mCell)) - return false; - } return mPathgridGraph.isPointConnected(start, end); } @@ -698,16 +691,6 @@ namespace MWWorld std::list CellStore::aStarSearch(const int start, const int end, const bool isOutside) const { - if(!mPathgridGraph.isGraphConstructed()) - { - MWMechanics::PathgridGraph *p = const_cast (&mPathgridGraph); - - if(!p->initPathgridGraph(mCell)) - { - std::list path; // empty - return path; - } - } return mPathgridGraph.aStarSearch(start, end, isOutside); } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 3cca993f27..c352779b16 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -8,7 +8,7 @@ #include "esmstore.hpp" #include "cellreflist.hpp" -#include "../mwmechanics/pathfinding.hpp" +#include "../mwmechanics/pathgrid.hpp" namespace ESM { From 5cf8e7e9334f60a10fb0ddabdc25c4a953151388 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 4 Apr 2014 06:16:26 +1100 Subject: [PATCH 048/135] Remove logging. --- apps/openmw/mwmechanics/pathgrid.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index 5580e5d3f2..f8b24fa1a0 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -124,13 +124,6 @@ namespace MWMechanics } buildConnectedPoints(); mIsGraphConstructed = true; -#if 0 - std::cout << "loading pathgrid " << - +"\""+ mPathgrid->mCell +"\"" - +", "+ std::to_string(mPathgrid->mData.mX) - +", "+ std::to_string(mPathgrid->mData.mY) - << std::endl; -#endif return true; } @@ -211,11 +204,6 @@ namespace MWMechanics if(mSCCPoint[v].first == -1) // undefined (haven't visited) recursiveStrongConnect(v); } -#if 0 - std::cout << "components: " << std::to_string(mSCCId) - +", "+ mPathgrid->mCell - << std::endl; -#endif } bool PathgridGraph::isPointConnected(const int start, const int end) const From f59226265ad1b745d21747ce89b11de5a13b6d7d Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 4 Apr 2014 18:10:06 +1100 Subject: [PATCH 049/135] Remove redundant parameter from aStarSearch. Also update some comments. --- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- apps/openmw/mwmechanics/pathgrid.cpp | 11 ++++++----- apps/openmw/mwmechanics/pathgrid.hpp | 4 ++-- apps/openmw/mwworld/cellstore.cpp | 6 ++---- apps/openmw/mwworld/cellstore.hpp | 3 +-- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index a4166d83b9..730b8cb923 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -224,7 +224,7 @@ namespace MWMechanics // this shouldn't really happen, but just in case if(endNode.first != -1) { - mPath = mCell->aStarSearch(startNode, endNode.first, mCell->isExterior()); + mPath = mCell->aStarSearch(startNode, endNode.first); if(!mPath.empty()) { diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index f8b24fa1a0..83ee86db51 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -55,6 +55,7 @@ namespace MWMechanics , mGraph(0) , mSCCId(0) , mSCCIndex(0) + , mIsExterior(0) { } @@ -100,6 +101,7 @@ namespace MWMechanics return false; mCell = cell; + mIsExterior = cell->isExterior(); mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); if(!mPathgrid) @@ -221,11 +223,11 @@ namespace MWMechanics * * Should be possible to make this MT safe. * - * Returns path (a list of pathgrid point indexes) which may be empty. + * Returns path which may be empty. path contains pathgrid points in local + * cell co-ordinates (indoors) or world co-ordinates (external). * * Input params: * start, goal - pathgrid point indexes (for this cell) - * isExterior - used to determine whether to convert to world co-ordinates * * Variables: * openset - point indexes to be traversed, lowest cost at the front @@ -239,8 +241,7 @@ namespace MWMechanics * co-ordinates). Essentially trading speed w/ memory. */ std::list PathgridGraph::aStarSearch(const int start, - const int goal, - bool isExterior) const + const int goal) const { std::list path; if(!isPointConnected(start, goal)) @@ -316,7 +317,7 @@ namespace MWMechanics // reconstruct path to return, using world co-ordinates float xCell = 0; float yCell = 0; - if (isExterior) + if (mIsExterior) { xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index 9f3a997c6e..372f6bd2da 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -29,12 +29,12 @@ namespace MWMechanics // isOutside is used whether to convert path to world co-ordinates std::list aStarSearch(const int start, - const int end, - const bool isOutside) const; + const int end) const; private: const ESM::Cell *mCell; const ESM::Pathgrid *mPathgrid; + bool mIsExterior; struct ConnectedPoint // edge { diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index d39f22c3fb..6bc7657e4d 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -685,12 +685,10 @@ namespace MWWorld bool CellStore::isPointConnected(const int start, const int end) const { return mPathgridGraph.isPointConnected(start, end); - } - std::list CellStore::aStarSearch(const int start, const int end, - const bool isOutside) const + std::list CellStore::aStarSearch(const int start, const int end) const { - return mPathgridGraph.aStarSearch(start, end, isOutside); + return mPathgridGraph.aStarSearch(start, end); } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index c352779b16..b970afe1be 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -145,8 +145,7 @@ namespace MWWorld bool isPointConnected(const int start, const int end) const; - std::list aStarSearch(const int start, const int end, - const bool isOutside) const; + std::list aStarSearch(const int start, const int end) const; private: From 28f7c42fb77536f84e9875e8e4bc5284d75f6aa3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 4 Apr 2014 18:17:42 +1100 Subject: [PATCH 050/135] One more comment fix. --- apps/openmw/mwmechanics/pathgrid.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index 372f6bd2da..ac545efbcb 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -27,7 +27,9 @@ namespace MWMechanics // from start point) both start and end are pathgrid point indexes bool isPointConnected(const int start, const int end) const; - // isOutside is used whether to convert path to world co-ordinates + // the input parameters are pathgrid point indexes + // the output list is in local (internal cells) or world (external + // cells) co-ordinates std::list aStarSearch(const int start, const int end) const; private: From 0fe67b586a6c8156eb598887550b995dce8c7304 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 5 Apr 2014 13:16:13 +0200 Subject: [PATCH 051/135] increased scene toolbar button size --- apps/opencs/view/world/previewsubview.cpp | 2 +- apps/opencs/view/world/scenesubview.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/previewsubview.cpp b/apps/opencs/view/world/previewsubview.cpp index ac9776d222..e9a30e65d2 100644 --- a/apps/opencs/view/world/previewsubview.cpp +++ b/apps/opencs/view/world/previewsubview.cpp @@ -28,7 +28,7 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo else mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), this); - SceneToolbar *toolbar = new SceneToolbar (48, this); + SceneToolbar *toolbar = new SceneToolbar (48+6, this); SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); toolbar->addTool (lightingTool); diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index dedaf014b6..d62793cc97 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -35,7 +35,7 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D layout2->setContentsMargins (QMargins (0, 0, 0, 0)); - SceneToolbar *toolbar = new SceneToolbar (48, this); + SceneToolbar *toolbar = new SceneToolbar (48+6, this); SceneToolGrid *gridTool = 0; From ce7aa963717122a1de7d055d06be41e1365ab1d7 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Sun, 6 Apr 2014 22:21:28 +0200 Subject: [PATCH 052/135] Fix visual glitch happening when closing inventory while sneaking (bug #1255) --- apps/openmw/mwrender/renderingmanager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index fa7b17a7c1..97283d065e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -364,10 +364,9 @@ void RenderingManager::update (float duration, bool paused) static const int i1stPersonSneakDelta = MWBase::Environment::get().getWorld()->getStore().get() .find("i1stPersonSneakDelta")->getInt(); - if(isSneaking && !(isSwimming || isInAir)) + if(!paused && isSneaking && !(isSwimming || isInAir)) mCamera->setSneakOffset(i1stPersonSneakDelta); - mOcclusionQuery->update(duration); mRendering.update(duration); From 03b3487f1bbb3114e00999e18cc329bbaf961af8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 7 Apr 2014 09:56:36 +0200 Subject: [PATCH 053/135] minor cleanup --- apps/openmw/mwmechanics/pathgrid.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index 83ee86db51..ec647c1cb3 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -250,12 +250,9 @@ namespace MWMechanics } int graphSize = mGraph.size(); - std::vector gScore; - gScore.resize(graphSize, -1); - std::vector fScore; - fScore.resize(graphSize, -1); - std::vector graphParent; - graphParent.resize(graphSize, -1); + std::vector gScore (graphSize, -1); + std::vector fScore (graphSize, -1); + std::vector graphParent (graphSize, -1); // gScore & fScore keep costs for each pathgrid point in mPoints gScore[start] = 0; From 324b2743d447bb3d6f2a18dae780ea3783857070 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 7 Apr 2014 10:21:26 +0200 Subject: [PATCH 054/135] removed grid button (discarding the first attempt at a cell selector) --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/world/scenesubview.cpp | 13 ---- apps/opencs/view/world/scenetoolgrid.cpp | 75 ------------------------ apps/opencs/view/world/scenetoolgrid.hpp | 29 --------- 4 files changed, 1 insertion(+), 118 deletions(-) delete mode 100644 apps/opencs/view/world/scenetoolgrid.cpp delete mode 100644 apps/opencs/view/world/scenetoolgrid.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 05cc93f896..a7a694463e 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -60,7 +60,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool - scenetoolmode infocreator scriptedit dialoguesubview previewsubview scenetoolgrid + scenetoolmode infocreator scriptedit dialoguesubview previewsubview ) opencs_units (view/render diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index d62793cc97..4ebaa93521 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -18,7 +18,6 @@ #include "creator.hpp" #include "scenetoolbar.hpp" #include "scenetoolmode.hpp" -#include "scenetoolgrid.hpp" CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id) @@ -37,8 +36,6 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SceneToolbar *toolbar = new SceneToolbar (48+6, this); - SceneToolGrid *gridTool = 0; - if (id.getId()=="sys::default") { CSVRender::PagedWorldspaceWidget *widget = new CSVRender::PagedWorldspaceWidget (this); @@ -47,13 +44,6 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SIGNAL (cellIndexChanged (const std::pair&, const std::pair&)), this, SLOT (cellIndexChanged (const std::pair&, const std::pair&))); - - gridTool = new SceneToolGrid (toolbar); - - connect (widget, - SIGNAL (cellIndexChanged (const std::pair&, const std::pair&)), - gridTool, - SLOT (cellIndexChanged (const std::pair&, const std::pair&))); } else mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); @@ -64,9 +54,6 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); toolbar->addTool (lightingTool); - if (gridTool) - toolbar->addTool (gridTool); - layout2->addWidget (toolbar, 0); layout2->addWidget (mScene, 1); diff --git a/apps/opencs/view/world/scenetoolgrid.cpp b/apps/opencs/view/world/scenetoolgrid.cpp deleted file mode 100644 index 0769be168c..0000000000 --- a/apps/opencs/view/world/scenetoolgrid.cpp +++ /dev/null @@ -1,75 +0,0 @@ - -#include "scenetoolgrid.hpp" - -#include - -#include -#include -#include - -#include "scenetoolbar.hpp" - -CSVWorld::SceneToolGrid::SceneToolGrid (SceneToolbar *parent) -: SceneTool (parent), mIconSize (parent->getIconSize()) -{ -} - -void CSVWorld::SceneToolGrid::showPanel (const QPoint& position) -{ - - -} - -void CSVWorld::SceneToolGrid::cellIndexChanged (const std::pair& min, - const std::pair& max) -{ - /// \todo make font size configurable - const int fontSize = 8; - - /// \todo replace with proper icon - QPixmap image (mIconSize, mIconSize); - image.fill (QColor (0, 0, 0, 0)); - - { - QPainter painter (&image); - painter.setPen (Qt::black); - QFont font (QApplication::font().family(), fontSize); - painter.setFont (font); - - QFontMetrics metrics (font); - - if (min==max) - { - // single cell - std::ostringstream stream; - stream << min.first << ", " << min.second; - - QString text = QString::fromUtf8 (stream.str().c_str()); - - painter.drawText (QPoint ((mIconSize-metrics.width (text))/2, mIconSize/2+fontSize/2), - text); - } - else - { - // range - { - std::ostringstream stream; - stream << min.first << ", " << min.second; - painter.drawText (QPoint (0, mIconSize), - QString::fromUtf8 (stream.str().c_str())); - } - - { - std::ostringstream stream; - stream << max.first << ", " << max.second; - - QString text = QString::fromUtf8 (stream.str().c_str()); - - painter.drawText (QPoint (mIconSize-metrics.width (text), fontSize), text); - } - } - } - - QIcon icon (image); - setIcon (icon); -} \ No newline at end of file diff --git a/apps/opencs/view/world/scenetoolgrid.hpp b/apps/opencs/view/world/scenetoolgrid.hpp deleted file mode 100644 index 917df2a168..0000000000 --- a/apps/opencs/view/world/scenetoolgrid.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef CSV_WORLD_SCENETOOL_GRID_H -#define CSV_WORLD_SCENETOOL_GRID_H - -#include "scenetool.hpp" - -namespace CSVWorld -{ - class SceneToolbar; - - ///< \brief Cell grid selector tool - class SceneToolGrid : public SceneTool - { - Q_OBJECT - - int mIconSize; - - public: - - SceneToolGrid (SceneToolbar *parent); - - virtual void showPanel (const QPoint& position); - - public slots: - - void cellIndexChanged (const std::pair& min, const std::pair& max); - }; -} - -#endif From 67965ec10cc211469308c443363ea28e25fdf304 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 7 Apr 2014 13:42:00 +0200 Subject: [PATCH 055/135] added CellCoordinates and CellSelection classes --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/cellcoordinates.cpp | 60 +++++++++++++++ apps/opencs/model/world/cellcoordinates.hpp | 42 +++++++++++ apps/opencs/model/world/cellselection.cpp | 83 +++++++++++++++++++++ apps/opencs/model/world/cellselection.hpp | 57 ++++++++++++++ 5 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/model/world/cellcoordinates.cpp create mode 100644 apps/opencs/model/world/cellcoordinates.hpp create mode 100644 apps/opencs/model/world/cellselection.cpp create mode 100644 apps/opencs/model/world/cellselection.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index a7a694463e..d9e271498f 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -24,7 +24,7 @@ opencs_units (model/world opencs_units_noqt (model/world universalid record commands columnbase scriptcontext cell refidcollection - refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata + refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection ) opencs_hdrs_noqt (model/world diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp new file mode 100644 index 0000000000..b1c8441e63 --- /dev/null +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -0,0 +1,60 @@ + +#include "cellcoordinates.hpp" + +#include +#include + +CSMWorld::CellCoordinates::CellCoordinates() : mX (0), mY (0) {} + +CSMWorld::CellCoordinates::CellCoordinates (int x, int y) : mX (x), mY (y) {} + +int CSMWorld::CellCoordinates::getX() const +{ + return mX; +} + +int CSMWorld::CellCoordinates::getY() const +{ + return mY; +} + +CSMWorld::CellCoordinates CSMWorld::CellCoordinates::move (int x, int y) const +{ + return CellCoordinates (mX + x, mY + y); +} + +std::string CSMWorld::CellCoordinates::getId (const std::string& worldspace) const +{ + // we ignore the worldspace for now, since there is only one (will change in 1.1) + std::ostringstream stream; + + stream << "#" << mX << " " << mY; + + return stream.str(); +} + +bool CSMWorld::operator== (const CellCoordinates& left, const CellCoordinates& right) +{ + return left.getX()==right.getX() && left.getY()==right.getY(); +} + +bool CSMWorld::operator!= (const CellCoordinates& left, const CellCoordinates& right) +{ + return !(left==right); +} + +bool CSMWorld::operator< (const CellCoordinates& left, const CellCoordinates& right) +{ + if (left.getX()right.getX()) + return false; + + return left.getY() +#include + +#include + +namespace CSMWorld +{ + class CellCoordinates + { + int mX; + int mY; + + public: + + CellCoordinates(); + + CellCoordinates (int x, int y); + + int getX() const; + + int getY() const; + + CellCoordinates move (int x, int y) const; + ///< Return a copy of *this, moved by the given offset. + + std::string getId (const std::string& worldspace) const; + ///< Return the ID for the cell at these coordinates. + }; + + bool operator== (const CellCoordinates& left, const CellCoordinates& right); + bool operator!= (const CellCoordinates& left, const CellCoordinates& right); + bool operator< (const CellCoordinates& left, const CellCoordinates& right); + + std::ostream& operator<< (std::ostream& stream, const CellCoordinates& coordiantes); +} + +Q_DECLARE_METATYPE (CSMWorld::CellCoordinates) + +#endif diff --git a/apps/opencs/model/world/cellselection.cpp b/apps/opencs/model/world/cellselection.cpp new file mode 100644 index 0000000000..73b5196f13 --- /dev/null +++ b/apps/opencs/model/world/cellselection.cpp @@ -0,0 +1,83 @@ + +#include "cellselection.hpp" + +#include +#include +#include + +CSMWorld::CellSelection::Iterator CSMWorld::CellSelection::begin() const +{ + return mCells.begin(); +} + +CSMWorld::CellSelection::Iterator CSMWorld::CellSelection::end() const +{ + return mCells.end(); +} + +bool CSMWorld::CellSelection::add (const CellCoordinates& coordinates) +{ + return mCells.insert (coordinates).second; +} + +void CSMWorld::CellSelection::remove (const CellCoordinates& coordinates) +{ + mCells.erase (coordinates); +} + +bool CSMWorld::CellSelection::has (const CellCoordinates& coordinates) const +{ + return mCells.find (coordinates)!=end(); +} + +int CSMWorld::CellSelection::getSize() const +{ + return mCells.size(); +} + +CSMWorld::CellCoordinates CSMWorld::CellSelection::getCentre() const +{ + if (mCells.empty()) + throw std::logic_error ("call of getCentre on empty cell selection"); + + double x = 0; + double y = 0; + + for (Iterator iter = begin(); iter!=end(); ++iter) + { + x += iter->getX(); + y += iter->getY(); + } + + x /= mCells.size(); + y /= mCells.size(); + + Iterator closest = begin(); + double distance = std::numeric_limits::max(); + + for (Iterator iter (begin()); iter!=end(); ++iter) + { + double deltaX = x - iter->getX(); + double deltaY = y - iter->getY(); + + double delta = std::sqrt (deltaX * deltaX + deltaY * deltaY); + + if (deltamove (x, y)); + + mCells.swap (moved); +} diff --git a/apps/opencs/model/world/cellselection.hpp b/apps/opencs/model/world/cellselection.hpp new file mode 100644 index 0000000000..042416a33f --- /dev/null +++ b/apps/opencs/model/world/cellselection.hpp @@ -0,0 +1,57 @@ +#ifndef CSM_WOLRD_CELLSELECTION_H +#define CSM_WOLRD_CELLSELECTION_H + +#include + +#include + +#include "cellcoordinates.hpp" + +namespace CSMWorld +{ + /// \brief Selection of cells in a paged worldspace + /// + /// \note The CellSelection does not specify the worldspace it applies to. + class CellSelection + { + public: + + typedef std::set Container; + typedef Container::const_iterator Iterator; + + private: + + Container mCells; + + public: + + Iterator begin() const; + + Iterator end() const; + + bool add (const CellCoordinates& coordinates); + ///< Ignored if the cell specified by \a coordinates is already part of the selection. + /// + /// \return Was a cell added to the collection? + + void remove (const CellCoordinates& coordinates); + ///< ignored if the cell specified by \a coordinates is not part of the selection. + + bool has (const CellCoordinates& coordinates) const; + ///< \return Is the cell specified by \a coordinates part of the selection? + + int getSize() const; + ///< Return number of cells. + + CellCoordinates getCentre() const; + ///< Return the selected cell that is closest to the geometric centre of the selection. + /// + /// \attention This function must not be called on selections that are empty. + + void move (int x, int y); + }; +} + +Q_DECLARE_METATYPE (CSMWorld::CellSelection) + +#endif From 0d352cb8832863445b7ad464610f8351c2588323 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 7 Apr 2014 14:16:02 +0200 Subject: [PATCH 056/135] replaced rectangular cell selection with a CellSelection object --- .../view/render/pagedworldspacewidget.cpp | 21 ++++++------ .../view/render/pagedworldspacewidget.hpp | 9 +++--- apps/opencs/view/world/scenesubview.cpp | 32 +++++++++++++------ apps/opencs/view/world/scenesubview.hpp | 7 +++- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 96d44543ec..ab02d63eef 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -4,36 +4,37 @@ #include CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget *parent) -: WorldspaceWidget (parent), mMin (std::make_pair (0, 0)), mMax (std::make_pair (-1, -1)) +: WorldspaceWidget (parent) {} void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) { if (!hint.empty()) { + CSMWorld::CellSelection selection; + if (hint[0]=='c') { char ignore1, ignore2, ignore3; - std::pair cellIndex; + int x, y; std::istringstream stream (hint.c_str()); - if (stream >> ignore1 >> ignore2 >> ignore3 >> cellIndex.first >> cellIndex.second) + if (stream >> ignore1 >> ignore2 >> ignore3 >> x >> y) { - setCellIndex (cellIndex, cellIndex); + selection.add (CSMWorld::CellCoordinates (x, y)); /// \todo adjust camera position } } /// \todo implement 'r' type hints + + setCellSelection (selection); } } -void CSVRender::PagedWorldspaceWidget::setCellIndex (const std::pair& min, - const std::pair& max) +void CSVRender::PagedWorldspaceWidget::setCellSelection (const CSMWorld::CellSelection& selection) { - mMin = min; - mMax = max; - - emit cellIndexChanged (mMin, mMax); + mSelection = selection; + emit cellSelectionChanged (mSelection); } \ No newline at end of file diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 9a4b79c3ab..f6ff32dc1f 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -1,6 +1,8 @@ #ifndef OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H #define OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H +#include "../../model/world/cellselection.hpp" + #include "worldspacewidget.hpp" namespace CSVRender @@ -9,8 +11,7 @@ namespace CSVRender { Q_OBJECT - std::pair mMin; - std::pair mMax; + CSMWorld::CellSelection mSelection; public: @@ -21,11 +22,11 @@ namespace CSVRender virtual void useViewHint (const std::string& hint); - void setCellIndex (const std::pair& min, const std::pair& max); + void setCellSelection (const CSMWorld::CellSelection& selection); signals: - void cellIndexChanged (const std::pair& min, const std::pair& max); + void cellSelectionChanged (const CSMWorld::CellSelection& selection); }; } diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 4ebaa93521..0bb11ce8cc 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -9,6 +9,8 @@ #include "../../model/doc/document.hpp" +#include "../../model/world/cellselection.hpp" + #include "../filter/filterbox.hpp" #include "../render/pagedworldspacewidget.hpp" @@ -40,10 +42,8 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D { CSVRender::PagedWorldspaceWidget *widget = new CSVRender::PagedWorldspaceWidget (this); mScene = widget; - connect (widget, - SIGNAL (cellIndexChanged (const std::pair&, const std::pair&)), - this, - SLOT (cellIndexChanged (const std::pair&, const std::pair&))); + connect (widget, SIGNAL (cellSelectionChanged (const CSMWorld::CellSelection&)), + this, SLOT (cellSelectionChanged (const CSMWorld::CellSelection&))); } else mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); @@ -102,16 +102,28 @@ void CSVWorld::SceneSubView::closeRequest() deleteLater(); } -void CSVWorld::SceneSubView::cellIndexChanged (const std::pair& min, - const std::pair& max) +void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::CellSelection& selection) { + int size = selection.getSize(); + std::ostringstream stream; - stream << "Scene: " << getUniversalId().getId() << " (" << min.first << ", " << min.second; + stream << "Scene: " << getUniversalId().getId(); - if (min!=max) - stream << " to " << max.first << ", " << max.second; + if (size==0) + stream << " (empty)"; + else if (size==1) + { + stream << " (" << *selection.begin() << ")"; + } + else + { + stream << " (" << selection.getCentre() << " and " << size-1 << " more "; - stream << ")"; + if (size>1) + stream << "cells around it)"; + else + stream << "cell around it)"; + } setWindowTitle (QString::fromUtf8 (stream.str().c_str())); } \ No newline at end of file diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index ee5b7b41fb..0b15ea5413 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -5,6 +5,11 @@ class QModelIndex; +namespace CSMWorld +{ + class CellSelection; +} + namespace CSMDoc { class Document; @@ -44,7 +49,7 @@ namespace CSVWorld void closeRequest(); - void cellIndexChanged (const std::pair& min, const std::pair& max); + void cellSelectionChanged (const CSMWorld::CellSelection& selection); }; } From e0550ba336e7d0bd0b884ceb3facbc51b61d51c9 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 7 Apr 2014 15:23:14 +0200 Subject: [PATCH 057/135] allow multiple cell coordinates in c-type hint for scene subviews --- .../view/render/pagedworldspacewidget.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index ab02d63eef..0f23dfe7b7 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -15,19 +15,26 @@ void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) if (hint[0]=='c') { - char ignore1, ignore2, ignore3; - int x, y; + // syntax: c:#x1 y1; #x2 y2 (number of coordinate pairs can be 0 or larger) + char ignore; std::istringstream stream (hint.c_str()); - if (stream >> ignore1 >> ignore2 >> ignore3 >> x >> y) + if (stream >> ignore) { - selection.add (CSMWorld::CellCoordinates (x, y)); + char ignore1; // : or ; + char ignore2; // # + int x, y; + + while (stream >> ignore1 >> ignore2 >> x >> y) + selection.add (CSMWorld::CellCoordinates (x, y)); /// \todo adjust camera position } } - - /// \todo implement 'r' type hints + else if (hint[0]=='r') + { + /// \todo implement 'r' type hints + } setCellSelection (selection); } From 09bd0324c92b7c6125e291d1bbaf6cba601db2e3 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Tue, 8 Apr 2014 20:19:09 +0200 Subject: [PATCH 058/135] Fail properly when a content file is not found --- apps/openmw/mwworld/worldimp.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 594a9f7f4e..f808856c55 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2071,6 +2071,12 @@ namespace MWWorld { contentLoader.load(col.getPath(*it), idx); } + else + { + std::stringstream msg; + msg << "Failed loading " << *it << ": the content file does not exist"; + throw std::runtime_error(msg.str()); + } } } From 0516d95253a1baecff5136f78a93124c1f778b90 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 10 Apr 2014 22:12:09 +0200 Subject: [PATCH 059/135] added context menu with selection functions to region map --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/regionmap.cpp | 11 ++ apps/opencs/model/world/regionmap.hpp | 5 + apps/opencs/view/world/regionmap.cpp | 142 ++++++++++++++++++++ apps/opencs/view/world/regionmap.hpp | 45 +++++++ apps/opencs/view/world/regionmapsubview.cpp | 19 +-- apps/opencs/view/world/regionmapsubview.hpp | 8 +- 7 files changed, 214 insertions(+), 18 deletions(-) create mode 100644 apps/opencs/view/world/regionmap.cpp create mode 100644 apps/opencs/view/world/regionmap.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index d9e271498f..cbe90b1d3e 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -60,7 +60,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool - scenetoolmode infocreator scriptedit dialoguesubview previewsubview + scenetoolmode infocreator scriptedit dialoguesubview previewsubview regionmap ) opencs_units (view/render diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 697c5f4500..544d5cbc56 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -406,6 +406,17 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const return QString::fromUtf8 (stream.str().c_str()); } + if (role==Role_Region) + { + CellIndex cellIndex = getIndex (index); + + std::map::const_iterator cell = + mMap.find (cellIndex); + + if (cell!=mMap.end() && !cell->second.mRegion.empty()) + return QString::fromUtf8 (Misc::StringUtils::lowerCase (cell->second.mRegion).c_str()); + } + return QVariant(); } diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index 7fb89f20ac..699166a725 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -25,6 +25,11 @@ namespace CSMWorld typedef std::pair CellIndex; + enum Role + { + Role_Region = Qt::UserRole + }; + private: struct CellDescription diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp new file mode 100644 index 0000000000..9502c423f8 --- /dev/null +++ b/apps/opencs/view/world/regionmap.cpp @@ -0,0 +1,142 @@ + +#include "regionmap.hpp" + +#include +#include + +#include +#include +#include + +#include "../../model/world/regionmap.hpp" + +void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) +{ + QMenu menu (this); + + if (getUnselectedCells().size()>0) + menu.addAction (mSelectAllAction); + + if (selectionModel()->selectedIndexes().size()>0) + menu.addAction (mClearSelectionAction); + + if (getMissingRegionCells().size()>0) + menu.addAction (mSelectRegionsAction); + + menu.exec (event->globalPos()); +} + +QModelIndexList CSVWorld::RegionMap::getUnselectedCells() const +{ + const QAbstractItemModel *model = QTableView::model(); + + int rows = model->rowCount(); + int columns = model->columnCount(); + + QModelIndexList selected = selectionModel()->selectedIndexes(); + std::sort (selected.begin(), selected.end()); + + QModelIndexList all; + + for (int y=0; yindex (y, x); + if (model->data (index, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern)) + all.push_back (index); + } + + std::sort (all.begin(), all.end()); + + QModelIndexList list; + + std::set_difference (all.begin(), all.end(), selected.begin(), selected.end(), + std::back_inserter (list)); + + return list; +} + +QModelIndexList CSVWorld::RegionMap::getMissingRegionCells() const +{ + const QAbstractItemModel *model = QTableView::model(); + + QModelIndexList selected = selectionModel()->selectedIndexes(); + + std::set regions; + + for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + { + std::string region = + model->data (*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData(); + + if (!region.empty()) + regions.insert (region); + } + + QModelIndexList list; + + QModelIndexList unselected = getUnselectedCells(); + + for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) + { + std::string region = + model->data (*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData(); + + if (!region.empty() && regions.find (region)!=regions.end()) + list.push_back (*iter); + } + + return list; +} + +CSVWorld::RegionMap::RegionMap (QAbstractItemModel *model, QWidget *parent) +: QTableView (parent) +{ + verticalHeader()->hide(); + horizontalHeader()->hide(); + + setSelectionMode (QAbstractItemView::ExtendedSelection); + + setModel (model); + + resizeColumnsToContents(); + resizeRowsToContents(); + + mSelectAllAction = new QAction (tr ("Select All"), this); + connect (mSelectAllAction, SIGNAL (triggered()), this, SLOT (selectAll())); + addAction (mSelectAllAction); + + mClearSelectionAction = new QAction (tr ("Clear Selection"), this); + connect (mClearSelectionAction, SIGNAL (triggered()), this, SLOT (clearSelection())); + addAction (mClearSelectionAction); + + mSelectRegionsAction = new QAction (tr ("Select Regions"), this); + connect (mSelectRegionsAction, SIGNAL (triggered()), this, SLOT (selectRegions())); + addAction (mSelectRegionsAction); +} + +void CSVWorld::RegionMap::setEditLock (bool locked) +{ + +} + +void CSVWorld::RegionMap::selectAll() +{ + QModelIndexList unselected = getUnselectedCells(); + + for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) + selectionModel()->select (*iter, QItemSelectionModel::Select); +} + +void CSVWorld::RegionMap::clearSelection() +{ + selectionModel()->clearSelection(); +} + +void CSVWorld::RegionMap::selectRegions() +{ + QModelIndexList unselected = getMissingRegionCells(); + + for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) + selectionModel()->select (*iter, QItemSelectionModel::Select); +} \ No newline at end of file diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp new file mode 100644 index 0000000000..e30267b03b --- /dev/null +++ b/apps/opencs/view/world/regionmap.hpp @@ -0,0 +1,45 @@ +#ifndef CSV_WORLD_REGIONMAP_H +#define CSV_WORLD_REGIONMAP_H + +#include + +class QAction; +class QAbstractItemModel; + +namespace CSVWorld +{ + class RegionMap : public QTableView + { + Q_OBJECT + + QAction *mSelectAllAction; + QAction *mClearSelectionAction; + QAction *mSelectRegionsAction; + + private: + + void contextMenuEvent (QContextMenuEvent *event); + + QModelIndexList getUnselectedCells() const; + ///< Note non-existent cells are not listed. + + QModelIndexList getMissingRegionCells() const; + ///< Unselected cells within all regions that have at least one selected cell. + + public: + + RegionMap (QAbstractItemModel *model, QWidget *parent = 0); + + void setEditLock (bool locked); + + private slots: + + void selectAll(); + + void clearSelection(); + + void selectRegions(); + }; +} + +#endif diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp index b82c1afb54..e170ee309a 100644 --- a/apps/opencs/view/world/regionmapsubview.cpp +++ b/apps/opencs/view/world/regionmapsubview.cpp @@ -1,29 +1,18 @@ #include "regionmapsubview.hpp" -#include -#include +#include "regionmap.hpp" CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document) : CSVDoc::SubView (universalId) { - mTable = new QTableView (this); + mRegionMap = new RegionMap (document.getData().getTableModel (universalId), this); - mTable->verticalHeader()->hide(); - mTable->horizontalHeader()->hide(); - - mTable->setSelectionMode (QAbstractItemView::ExtendedSelection); - - mTable->setModel (document.getData().getTableModel (universalId)); - - mTable->resizeColumnsToContents(); - mTable->resizeRowsToContents(); - - setWidget (mTable); + setWidget (mRegionMap); } void CSVWorld::RegionMapSubView::setEditLock (bool locked) { - + mRegionMap->setEditLock (locked); } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmapsubview.hpp b/apps/opencs/view/world/regionmapsubview.hpp index 1655107af3..f329c7f3b9 100644 --- a/apps/opencs/view/world/regionmapsubview.hpp +++ b/apps/opencs/view/world/regionmapsubview.hpp @@ -3,7 +3,7 @@ #include "../doc/subview.hpp" -class QTableView; +class QAction; namespace CSMDoc { @@ -12,9 +12,13 @@ namespace CSMDoc namespace CSVWorld { + class RegionMap; + class RegionMapSubView : public CSVDoc::SubView { - QTableView *mTable; + Q_OBJECT + + RegionMap *mRegionMap; public: From d0ea23431c2417dd86df9aa650a0978ee8ca7749 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 11 Apr 2014 10:06:16 +0200 Subject: [PATCH 060/135] replaced CellIndex typedef with new CellCoordinates class --- apps/opencs/model/world/regionmap.cpp | 147 ++++++++++---------------- apps/opencs/model/world/regionmap.hpp | 19 ++-- 2 files changed, 66 insertions(+), 100 deletions(-) diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 544d5cbc56..7f233a4337 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -1,6 +1,7 @@ #include "regionmap.hpp" +#include #include #include @@ -25,17 +26,16 @@ CSMWorld::RegionMap::CellDescription::CellDescription (const Record& cell) mName = cell2.mName; } -CSMWorld::RegionMap::CellIndex CSMWorld::RegionMap::getIndex (const QModelIndex& index) const +CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex (const QModelIndex& index) const { - return CellIndex (index.column()+mMin.first, - (mMax.second-mMin.second - index.row()-1)+mMin.second); + return mMin.move (index.column(), mMax.getY()-mMin.getY() - index.row()-1); } -QModelIndex CSMWorld::RegionMap::getIndex (const CellIndex& index) const +QModelIndex CSMWorld::RegionMap::getIndex (const CellCoordinates& index) const { // I hate you, Qt API naming scheme! - return QAbstractTableModel::index (mMax.second-mMin.second - (index.second-mMin.second)-1, - index.first-mMin.first); + return QAbstractTableModel::index (mMax.getY()-mMin.getY() - (index.getY()-mMin.getY())-1, + index.getX()-mMin.getX()); } void CSMWorld::RegionMap::buildRegions() @@ -70,21 +70,21 @@ void CSMWorld::RegionMap::buildMap() { CellDescription description (cell); - CellIndex index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index (cell2.mData.mX, cell2.mData.mY); mMap.insert (std::make_pair (index, description)); } } - std::pair mapSize = getSize(); + std::pair mapSize = getSize(); mMin = mapSize.first; mMax = mapSize.second; } -void CSMWorld::RegionMap::addCell (const CellIndex& index, const CellDescription& description) +void CSMWorld::RegionMap::addCell (const CellCoordinates& index, const CellDescription& description) { - std::map::iterator cell = mMap.find (index); + std::map::iterator cell = mMap.find (index); if (cell!=mMap.end()) { @@ -114,7 +114,7 @@ void CSMWorld::RegionMap::addCells (int start, int end) if (cell2.isExterior()) { - CellIndex index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index (cell2.mData.mX, cell2.mData.mY); CellDescription description (cell); @@ -123,9 +123,9 @@ void CSMWorld::RegionMap::addCells (int start, int end) } } -void CSMWorld::RegionMap::removeCell (const CellIndex& index) +void CSMWorld::RegionMap::removeCell (const CellCoordinates& index) { - std::map::iterator cell = mMap.find (index); + std::map::iterator cell = mMap.find (index); if (cell!=mMap.end()) { @@ -160,7 +160,7 @@ void CSMWorld::RegionMap::updateRegions (const std::vector& regions std::for_each (regions2.begin(), regions2.end(), &Misc::StringUtils::lowerCase); std::sort (regions2.begin(), regions2.end()); - for (std::map::const_iterator iter (mMap.begin()); + for (std::map::const_iterator iter (mMap.begin()); iter!=mMap.end(); ++iter) { if (!iter->second.mRegion.empty() && @@ -176,90 +176,57 @@ void CSMWorld::RegionMap::updateRegions (const std::vector& regions void CSMWorld::RegionMap::updateSize() { - std::pair size = getSize(); + std::pair size = getSize(); + if (int diff = size.first.getX() - mMin.getX()) { - int diff = size.first.first - mMin.first; - - if (diff<0) - { - beginInsertColumns (QModelIndex(), 0, -diff-1); - mMin.first = size.first.first; - endInsertColumns(); - } - else if (diff>0) - { - beginRemoveColumns (QModelIndex(), 0, diff-1); - mMin.first = size.first.first; - endRemoveColumns(); - } + beginInsertColumns (QModelIndex(), 0, std::abs (diff)-1); + mMin = CellCoordinates (size.first.getX(), mMin.getY()); + endInsertColumns(); } + if (int diff = size.first.getY() - mMin.getY()) { - int diff = size.first.second - mMin.second; - - if (diff<0) - { - beginInsertRows (QModelIndex(), 0, -diff-1); - mMin.second = size.first.second; - endInsertRows(); - } - else if (diff>0) - { - beginRemoveRows (QModelIndex(), 0, diff-1); - mMin.second = size.first.second; - endRemoveRows(); - } + beginInsertRows (QModelIndex(), 0, std::abs (diff)-1); + mMin = CellCoordinates (mMin.getX(), size.first.getY()); + endInsertRows(); } + if (int diff = size.second.getX() - mMax.getX()) { - int diff = size.second.first - mMax.first; + int columns = columnCount(); if (diff>0) - { - int columns = columnCount(); beginInsertColumns (QModelIndex(), columns, columns+diff-1); - mMax.first = size.second.first; - endInsertColumns(); - } - else if (diff<0) - { - int columns = columnCount(); + else beginRemoveColumns (QModelIndex(), columns+diff, columns-1); - mMax.first = size.second.first; - endRemoveColumns(); - } + + mMax = CellCoordinates (size.second.getX(), mMax.getY()); + endInsertColumns(); } + if (int diff = size.second.getY() - mMax.getY()) { - int diff = size.second.second - mMax.second; + int rows = rowCount(); if (diff>0) - { - int rows = rowCount(); beginInsertRows (QModelIndex(), rows, rows+diff-1); - mMax.second = size.second.second; - endInsertRows(); - } - else if (diff<0) - { - int rows = rowCount(); + else beginRemoveRows (QModelIndex(), rows+diff, rows-1); - mMax.second = size.second.second; - endRemoveRows(); - } + + mMax = CellCoordinates (mMax.getX(), size.second.getY()); + endInsertRows(); } } -std::pair CSMWorld::RegionMap::getSize() - const +std::pair CSMWorld::RegionMap::getSize() const { const IdCollection& cells = mData.getCells(); int size = cells.getSize(); - CellIndex min (0, 0); - CellIndex max (0, 0); + CellCoordinates min (0, 0); + CellCoordinates max (0, 0); for (int i=0; i CSMWor if (cell2.isExterior()) { - CellIndex index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index (cell2.mData.mX, cell2.mData.mY); if (min==max) { min = index; - max = std::make_pair (min.first+1, min.second+1); + max = min.move (1, 1); } else { - if (index.first=max.first) - max.first = index.first + 1; + if (index.getX()=max.getX()) + max = CellCoordinates (index.getX()+1, max.getY()); - if (index.second=max.second) - max.second = index.second + 1; + if (index.getY()=max.getY()) + max = CellCoordinates (max.getX(), index.getY() + 1); } } } @@ -323,7 +290,7 @@ int CSMWorld::RegionMap::rowCount (const QModelIndex& parent) const if (parent.isValid()) return 0; - return mMax.second-mMin.second; + return mMax.getY()-mMin.getY(); } int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const @@ -331,7 +298,7 @@ int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const if (parent.isValid()) return 0; - return mMax.first-mMin.first; + return mMax.getX()-mMin.getX(); } QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const @@ -343,7 +310,7 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const { /// \todo GUI class in non-GUI code. Needs to be addressed eventually. - std::map::const_iterator cell = + std::map::const_iterator cell = mMap.find (getIndex (index)); if (cell!=mMap.end()) @@ -370,13 +337,13 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const if (role==Qt::ToolTipRole) { - CellIndex cellIndex = getIndex (index); + CellCoordinates cellIndex = getIndex (index); std::ostringstream stream; - stream << cellIndex.first << ", " << cellIndex.second; + stream << cellIndex; - std::map::const_iterator cell = + std::map::const_iterator cell = mMap.find (cellIndex); if (cell!=mMap.end()) @@ -408,9 +375,9 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const if (role==Role_Region) { - CellIndex cellIndex = getIndex (index); + CellCoordinates cellIndex = getIndex (index); - std::map::const_iterator cell = + std::map::const_iterator cell = mMap.find (cellIndex); if (cell!=mMap.end() && !cell->second.mRegion.empty()) @@ -503,7 +470,7 @@ void CSMWorld::RegionMap::cellsAboutToBeRemoved (const QModelIndex& parent, int if (cell2.isExterior()) { - CellIndex index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index (cell2.mData.mX, cell2.mData.mY); removeCell (index); } diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index 699166a725..29cc8c5122 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -9,6 +9,7 @@ #include "record.hpp" #include "cell.hpp" +#include "cellcoordinates.hpp" namespace CSMWorld { @@ -23,8 +24,6 @@ namespace CSMWorld public: - typedef std::pair CellIndex; - enum Role { Role_Region = Qt::UserRole @@ -44,27 +43,27 @@ namespace CSMWorld }; Data& mData; - std::map mMap; - CellIndex mMin; ///< inclusive - CellIndex mMax; ///< exclusive + std::map mMap; + CellCoordinates mMin; ///< inclusive + CellCoordinates mMax; ///< exclusive std::map mColours; ///< region ID, colour (RGBA) - CellIndex getIndex (const QModelIndex& index) const; + CellCoordinates getIndex (const QModelIndex& index) const; ///< Translates a Qt model index into a cell index (which can contain negative components) - QModelIndex getIndex (const CellIndex& index) const; + QModelIndex getIndex (const CellCoordinates& index) const; void buildRegions(); void buildMap(); - void addCell (const CellIndex& index, const CellDescription& description); + void addCell (const CellCoordinates& index, const CellDescription& description); ///< May be called on a cell that is already in the map (in which case an update is // performed) void addCells (int start, int end); - void removeCell (const CellIndex& index); + void removeCell (const CellCoordinates& index); ///< May be called on a cell that is not in the map (in which case the call is ignored) void addRegion (const std::string& region, unsigned int colour); @@ -83,7 +82,7 @@ namespace CSMWorld void updateSize(); - std::pair getSize() const; + std::pair getSize() const; public: From d2a41167d03a46808ab71542d0ab8c0d5c4388d9 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 11 Apr 2014 21:24:00 +1000 Subject: [PATCH 061/135] Allow flying and swimming creatures to step inclines. Should have listen to Chris in the first place, see https://forum.openmw.org/viewtopic.php?f=6&t=2075 --- apps/openmw/mwworld/physicssystem.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index c2d902d697..7d531d6d3f 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -308,11 +308,17 @@ namespace MWWorld break; } + Ogre::Vector3 oldPosition = newPosition; // We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over) - // NOTE: May need to stop slaughterfish step out of the water. - // NOTE: stepMove may modify newPosition - if((canWalk || isBipedal || isNpc) && stepMove(colobj, newPosition, velocity, remainingTime, engine)) - isOnGround = !(newPosition.z < waterlevel || isFlying); // Only on the ground if there's gravity + // NOTE: stepMove modifies newPosition if successful + if(stepMove(colobj, newPosition, velocity, remainingTime, engine)) + { + // don't let slaughterfish move out of water after stepMove + if(ptr.getClass().canSwim(ptr) && newPosition.z > (waterlevel - halfExtents.z * 0.5)) + newPosition = oldPosition; + else // Only on the ground if there's gravity + isOnGround = !(newPosition.z < waterlevel || isFlying); + } else { // Can't move this way, try to find another spot along the plane From 2f63eb7ca46f869fc4493488db1f9b465665970d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 12 Apr 2014 20:07:09 +0200 Subject: [PATCH 062/135] added missing edit lock for record reordering --- apps/opencs/view/world/table.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index a2927c2f09..712b8f5569 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -358,6 +358,9 @@ void CSVWorld::Table::cloneRecord() void CSVWorld::Table::moveUpRecord() { + if (mEditLock) + return; + QModelIndexList selectedRows = selectionModel()->selectedRows(); if (selectedRows.size()==1) @@ -387,6 +390,9 @@ void CSVWorld::Table::moveUpRecord() void CSVWorld::Table::moveDownRecord() { + if (mEditLock) + return; + QModelIndexList selectedRows = selectionModel()->selectedRows(); if (selectedRows.size()==1) From 0bef75487366b786c3db147181ea009f8649b86b Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 13 Apr 2014 08:46:02 +1000 Subject: [PATCH 063/135] Fix jumping animation glitches caused by minor vertical movements. Should resolve Bug #1271. --- apps/openmw/mwmechanics/character.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 93c789af1a..b7d99adeca 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1090,7 +1090,13 @@ void CharacterController::update(float duration) if (inwater || flying) cls.getCreatureStats(mPtr).land(); - if(!onground && !flying && !inwater) + // FIXME: The check for vec.z is a hack, but onground is not a reliable + // indicator of whether the actor is on the ground (defaults to false, which + // means this code block will always execute at least once for most, and + // collisions can move z position slightly off zero). A very small value of + // 0.1 is used here, but maybe something larger like 10 should be used. + // Should resolve Bug#1271. + if(!onground && !flying && !inwater && vec.z > 0.1f) { // In the air (either getting up —ascending part of jump— or falling). From e9be6d3f42fa89ef52ca3393e46db66d833e9b0d Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 13 Apr 2014 11:34:59 +1000 Subject: [PATCH 064/135] Fix falling animation where vec.z is set to zero. --- apps/openmw/mwmechanics/character.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b7d99adeca..d34812417d 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1090,13 +1090,14 @@ void CharacterController::update(float duration) if (inwater || flying) cls.getCreatureStats(mPtr).land(); - // FIXME: The check for vec.z is a hack, but onground is not a reliable - // indicator of whether the actor is on the ground (defaults to false, which - // means this code block will always execute at least once for most, and - // collisions can move z position slightly off zero). A very small value of - // 0.1 is used here, but maybe something larger like 10 should be used. - // Should resolve Bug#1271. - if(!onground && !flying && !inwater && vec.z > 0.1f) + if(!onground && !flying && !inwater + // FIXME: The check for vec.z is a hack, but onground is not a reliable + // indicator of whether the actor is on the ground (defaults to false, which + // means this code block will always execute at least once for most actors, + // and collisions can move z position slightly off zero). A very small value + // of 0.1 is used here, but something larger like 10 may be more suitable. + // Should resolve Bug#1271. + && (mJumpState == JumpState_Falling || vec.z > 0.1f)) { // In the air (either getting up —ascending part of jump— or falling). From 3e6e325e5b44e199b84ffa417765480d717962bc Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 13 Apr 2014 14:53:36 +1000 Subject: [PATCH 065/135] Instead of hacking character.cpp, provide a more reliable check for world->isOnGround(mPtr). --- apps/openmw/mwmechanics/character.cpp | 9 +-------- apps/openmw/mwworld/worldimp.cpp | 29 ++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d34812417d..93c789af1a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1090,14 +1090,7 @@ void CharacterController::update(float duration) if (inwater || flying) cls.getCreatureStats(mPtr).land(); - if(!onground && !flying && !inwater - // FIXME: The check for vec.z is a hack, but onground is not a reliable - // indicator of whether the actor is on the ground (defaults to false, which - // means this code block will always execute at least once for most actors, - // and collisions can move z position slightly off zero). A very small value - // of 0.1 is used here, but something larger like 10 may be more suitable. - // Should resolve Bug#1271. - && (mJumpState == JumpState_Falling || vec.z > 0.1f)) + if(!onground && !flying && !inwater) { // In the air (either getting up —ascending part of jump— or falling). diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f808856c55..83299e126b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -9,6 +9,7 @@ #include +#include #include #include @@ -1711,11 +1712,37 @@ namespace MWWorld return pos.z < cell->getWaterLevel(); } + // physactor->getOnGround() is not a reliable indicator of whether the actor + // is on the ground (defaults to false, which means code blocks such as + // CharacterController::update() may falsely detect "falling"). + // + // Also, collisions can move z position slightly off zero, giving a false + // indication. In order to reduce false detection of jumping, small distance + // below the actor is detected and ignored. A value of 1.5 is used here, but + // something larger may be more suitable. This change should resolve Bug#1271. + // + // FIXME: Collision detection each time may have a performance impact. + // There might be better places to update PhysicActor::mOnGround. bool World::isOnGround(const MWWorld::Ptr &ptr) const { RefData &refdata = ptr.getRefData(); const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); - return physactor && physactor->getOnGround(); + if(physactor) + { + Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); + OEngine::Physic::ActorTracer tracer; + // a small distance above collision object is considered "on ground" + tracer.findGround(physactor->getCollisionBody(), + pos, + pos - Ogre::Vector3(0, 0, 1.5f), // trace a small amount down + mPhysEngine); + if(tracer.mFraction < 1.0f) // collision, must be close to something below + return true; // TODO: should update physactor + else + return physactor->getOnGround(); + } + else + return false; } bool World::vanityRotateCamera(float * rot) From 966ed468701799429663edc2d7bb1abc3a4618ae Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 13 Apr 2014 18:34:08 +1000 Subject: [PATCH 066/135] Better performance but less tolerant of collision induced glitches. Also had to use const_cast to cache on ground status. --- apps/openmw/mwworld/worldimp.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 83299e126b..d56ca1f160 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1721,13 +1721,18 @@ namespace MWWorld // below the actor is detected and ignored. A value of 1.5 is used here, but // something larger may be more suitable. This change should resolve Bug#1271. // - // FIXME: Collision detection each time may have a performance impact. - // There might be better places to update PhysicActor::mOnGround. + // TODO: There might be better places to update PhysicActor::mOnGround. bool World::isOnGround(const MWWorld::Ptr &ptr) const { RefData &refdata = ptr.getRefData(); const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); - if(physactor) + + if(!physactor) + return false; + + if(physactor->getOnGround()) + return true; + else { Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); OEngine::Physic::ActorTracer tracer; @@ -1737,12 +1742,13 @@ namespace MWWorld pos - Ogre::Vector3(0, 0, 1.5f), // trace a small amount down mPhysEngine); if(tracer.mFraction < 1.0f) // collision, must be close to something below - return true; // TODO: should update physactor + { + const_cast (physactor)->setOnGround(true); + return true; + } else - return physactor->getOnGround(); + return false; } - else - return false; } bool World::vanityRotateCamera(float * rot) From 300eb6f444f75900a30937f029cf2a046909f2cf Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 13:23:50 +0200 Subject: [PATCH 067/135] make FNAM sub-record optional for all record types --- components/esm/loadacti.cpp | 4 ++-- components/esm/loadarmo.cpp | 4 ++-- components/esm/loadbody.cpp | 4 ++-- components/esm/loadbsgn.cpp | 4 ++-- components/esm/loadclas.cpp | 4 ++-- components/esm/loadfact.cpp | 4 ++-- components/esm/loadingr.cpp | 4 ++-- components/esm/loadlock.cpp | 4 ++-- components/esm/loadprob.cpp | 4 ++-- components/esm/loadrace.cpp | 4 ++-- components/esm/loadregn.cpp | 4 ++-- components/esm/loadrepa.cpp | 4 ++-- components/esm/loadsoun.cpp | 4 ++-- 13 files changed, 26 insertions(+), 26 deletions(-) diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index 6ba0df0b36..8efea3302a 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -11,13 +11,13 @@ namespace ESM void Activator::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); mScript = esm.getHNOString("SCRI"); } void Activator::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); } diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index ec8ff4f20c..5bf38c840d 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -34,7 +34,7 @@ unsigned int Armor::sRecordId = REC_ARMO; void Armor::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); mScript = esm.getHNOString("SCRI"); esm.getHNT(mData, "AODT", 24); mIcon = esm.getHNOString("ITEX"); @@ -45,7 +45,7 @@ void Armor::load(ESMReader &esm) void Armor::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); esm.writeHNT("AODT", mData, 24); esm.writeHNOCString("ITEX", mIcon); diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index 4015e6c91a..c45f8d252d 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -12,13 +12,13 @@ namespace ESM void BodyPart::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mRace = esm.getHNString("FNAM"); + mRace = esm.getHNOString("FNAM"); esm.getHNT(mData, "BYDT", 4); } void BodyPart::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mRace); + esm.writeHNOCString("FNAM", mRace); esm.writeHNT("BYDT", mData, 4); } diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 55e1e7f65a..db1a72a368 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -10,7 +10,7 @@ namespace ESM void BirthSign::load(ESMReader &esm) { - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); mTexture = esm.getHNOString("TNAM"); mDescription = esm.getHNOString("DESC"); @@ -19,7 +19,7 @@ void BirthSign::load(ESMReader &esm) void BirthSign::save(ESMWriter &esm) const { - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("TNAM", mTexture); esm.writeHNOCString("DESC", mDescription); diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index 33489eec46..ec339bd15e 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -41,7 +41,7 @@ const char *Class::sGmstSpecializationIds[3] = { void Class::load(ESMReader &esm) { - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); esm.getHNT(mData, "CLDT", 60); if (mData.mIsPlayable > 1) @@ -51,7 +51,7 @@ void Class::load(ESMReader &esm) } void Class::save(ESMWriter &esm) const { - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNT("CLDT", mData, 60); esm.writeHNOString("DESC", mDescription); } diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 61fa902639..84be21938b 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -28,7 +28,7 @@ namespace ESM void Faction::load(ESMReader &esm) { - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); // Read rank names. These are optional. int i = 0; @@ -52,7 +52,7 @@ void Faction::load(ESMReader &esm) } void Faction::save(ESMWriter &esm) const { - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); for (int i = 0; i < 10; i++) { diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 0e02433621..5c98cb8b97 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -11,7 +11,7 @@ namespace ESM void Ingredient::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); esm.getHNT(mData, "IRDT", 56); mScript = esm.getHNOString("SCRI"); mIcon = esm.getHNOString("ITEX"); @@ -42,7 +42,7 @@ void Ingredient::load(ESMReader &esm) void Ingredient::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNT("IRDT", mData, 56); esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 9ffce78a7d..42677a22bf 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -11,7 +11,7 @@ namespace ESM void Lockpick::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); esm.getHNT(mData, "LKDT", 16); @@ -22,7 +22,7 @@ void Lockpick::load(ESMReader &esm) void Lockpick::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNT("LKDT", mData, 16); esm.writeHNOString("SCRI", mScript); diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index caa3d7e0e7..b736bb64b5 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -11,7 +11,7 @@ namespace ESM void Probe::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); esm.getHNT(mData, "PBDT", 16); @@ -22,7 +22,7 @@ void Probe::load(ESMReader &esm) void Probe::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNT("PBDT", mData, 16); esm.writeHNOString("SCRI", mScript); diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index e50e43a744..17f2e02679 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -20,14 +20,14 @@ namespace ESM void Race::load(ESMReader &esm) { - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); esm.getHNT(mData, "RADT", 140); mPowers.load(esm); mDescription = esm.getHNOString("DESC"); } void Race::save(ESMWriter &esm) const { - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNT("RADT", mData, 140); mPowers.save(esm); esm.writeHNOString("DESC", mDescription); diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index fa4271e26a..da03e009f2 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -10,7 +10,7 @@ namespace ESM void Region::load(ESMReader &esm) { - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); if (esm.getVer() == VER_12) esm.getHNExact(&mData, sizeof(mData) - 2, "WEAT"); @@ -32,7 +32,7 @@ void Region::load(ESMReader &esm) } void Region::save(ESMWriter &esm) const { - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); if (esm.getVersion() == VER_12) esm.writeHNT("WEAT", mData, sizeof(mData) - 2); diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index a7132828d4..4e6cd7794f 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -11,7 +11,7 @@ namespace ESM void Repair::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); - mName = esm.getHNString("FNAM"); + mName = esm.getHNOString("FNAM"); esm.getHNT(mData, "RIDT", 16); @@ -22,7 +22,7 @@ void Repair::load(ESMReader &esm) void Repair::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); esm.writeHNT("RIDT", mData, 16); esm.writeHNOString("SCRI", mScript); diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 49c9eb54e2..28e4d7d9c5 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -10,7 +10,7 @@ namespace ESM void Sound::load(ESMReader &esm) { - mSound = esm.getHNString("FNAM"); + mSound = esm.getHNOString("FNAM"); esm.getHNT(mData, "DATA", 3); /* cout << "vol=" << (int)data.volume @@ -21,7 +21,7 @@ void Sound::load(ESMReader &esm) } void Sound::save(ESMWriter &esm) const { - esm.writeHNCString("FNAM", mSound); + esm.writeHNOCString("FNAM", mSound); esm.writeHNT("DATA", mData, 3); } From decd4270d9aa1b31729f4aa0c195d7b04c768f14 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 13:59:27 +0200 Subject: [PATCH 068/135] added CellId role to RegionMap model --- apps/opencs/model/world/regionmap.cpp | 15 +++++++++++---- apps/opencs/model/world/regionmap.hpp | 5 ++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 7f233a4337..fc4638432a 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -384,15 +384,22 @@ QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const return QString::fromUtf8 (Misc::StringUtils::lowerCase (cell->second.mRegion).c_str()); } + if (role==Role_CellId) + { + CellCoordinates cellIndex = getIndex (index); + + std::ostringstream stream; + stream << "#" << cellIndex.getX() << " " << cellIndex.getY(); + + return QString::fromUtf8 (stream.str().c_str()); + } + return QVariant(); } Qt::ItemFlags CSMWorld::RegionMap::flags (const QModelIndex& index) const { - if (mMap.find (getIndex (index))!=mMap.end()) - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; - - return 0; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } void CSMWorld::RegionMap::regionsAboutToBeRemoved (const QModelIndex& parent, int start, int end) diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index 29cc8c5122..5b82ac2171 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -26,7 +26,8 @@ namespace CSMWorld enum Role { - Role_Region = Qt::UserRole + Role_Region = Qt::UserRole, + Role_CellId = Qt::UserRole+1 }; private: @@ -93,6 +94,8 @@ namespace CSMWorld virtual int columnCount (const QModelIndex& parent = QModelIndex()) const; virtual QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const; + ///< \note Calling this function with role==Role_CellId may return the ID of a cell + /// that does not exist. virtual Qt::ItemFlags flags (const QModelIndex& index) const; From 19b31c4146efb4b5a0cbbb2d4d1714ab86d50ac2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 14:16:59 +0200 Subject: [PATCH 069/135] always use the cell ID instead of the exterior coordinate fields from the original cell struct --- apps/opencs/model/world/cell.hpp | 3 +++ apps/opencs/model/world/regionmap.cpp | 24 ++++++++++++++++-------- apps/opencs/model/world/regionmap.hpp | 2 ++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/cell.hpp b/apps/opencs/model/world/cell.hpp index 89854312ae..e6f3c8c355 100644 --- a/apps/opencs/model/world/cell.hpp +++ b/apps/opencs/model/world/cell.hpp @@ -9,6 +9,9 @@ namespace CSMWorld { /// \brief Wrapper for Cell record + /// + /// \attention The mData.mX and mData.mY fields of the ESM::Cell struct are not used. + /// Exterior cell coordinates are encoded in the cell ID. struct Cell : public ESM::Cell { std::string mId; diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index fc4638432a..5f030bb52e 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -38,6 +38,18 @@ QModelIndex CSMWorld::RegionMap::getIndex (const CellCoordinates& index) const index.getX()-mMin.getX()); } +CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex (const Cell& cell) const +{ + std::istringstream stream (cell.mId); + + char ignore; + int x = 0; + int y = 0; + stream >> ignore >> x >> y; + + return CellCoordinates (x, y); +} + void CSMWorld::RegionMap::buildRegions() { const IdCollection& regions = mData.getRegions(); @@ -70,7 +82,7 @@ void CSMWorld::RegionMap::buildMap() { CellDescription description (cell); - CellCoordinates index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index = getIndex (cell2); mMap.insert (std::make_pair (index, description)); } @@ -114,7 +126,7 @@ void CSMWorld::RegionMap::addCells (int start, int end) if (cell2.isExterior()) { - CellCoordinates index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index = getIndex (cell2); CellDescription description (cell); @@ -236,7 +248,7 @@ std::pair CSMWorld::Region if (cell2.isExterior()) { - CellCoordinates index (cell2.mData.mX, cell2.mData.mY); + CellCoordinates index = getIndex (cell2); if (min==max) { @@ -476,11 +488,7 @@ void CSMWorld::RegionMap::cellsAboutToBeRemoved (const QModelIndex& parent, int const Cell& cell2 = cell.get(); if (cell2.isExterior()) - { - CellCoordinates index (cell2.mData.mX, cell2.mData.mY); - - removeCell (index); - } + removeCell (getIndex (cell2)); } } diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index 5b82ac2171..7d7685e894 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -54,6 +54,8 @@ namespace CSMWorld QModelIndex getIndex (const CellCoordinates& index) const; + CellCoordinates getIndex (const Cell& cell) const; + void buildRegions(); void buildMap(); From fc4195a88fc682cb47b893523635aa4efc4b4347 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 14:17:18 +0200 Subject: [PATCH 070/135] added create cell menu item to regionmap --- apps/opencs/view/world/regionmap.cpp | 84 +++++++++++++++++++-- apps/opencs/view/world/regionmap.hpp | 25 +++++- apps/opencs/view/world/regionmapsubview.cpp | 2 +- 3 files changed, 102 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 9502c423f8..1e44ff0569 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -3,12 +3,19 @@ #include #include +#include #include #include #include +#include "../../model/doc/document.hpp" + #include "../../model/world/regionmap.hpp" +#include "../../model/world/universalid.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/commands.hpp" void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) { @@ -23,12 +30,28 @@ void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) if (getMissingRegionCells().size()>0) menu.addAction (mSelectRegionsAction); + int selectedNonExistentCells = getSelectedCells (false, true).size(); + + if (selectedNonExistentCells>0) + { + if (selectedNonExistentCells==1) + mCreateCellsAction->setText ("Create one cell"); + else + { + std::ostringstream stream; + stream << "Create " << selectedNonExistentCells << " cells"; + mCreateCellsAction->setText (QString::fromUtf8 (stream.str().c_str())); + } + + menu.addAction (mCreateCellsAction); + } + menu.exec (event->globalPos()); } QModelIndexList CSVWorld::RegionMap::getUnselectedCells() const { - const QAbstractItemModel *model = QTableView::model(); + const QAbstractItemModel *model = QTableView::model(); int rows = model->rowCount(); int columns = model->columnCount(); @@ -56,6 +79,25 @@ QModelIndexList CSVWorld::RegionMap::getUnselectedCells() const return list; } +QModelIndexList CSVWorld::RegionMap::getSelectedCells (bool existent, bool nonExistent) const +{ + const QAbstractItemModel *model = QTableView::model(); + + QModelIndexList selected = selectionModel()->selectedIndexes(); + + QModelIndexList list; + + for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + { + bool exists = model->data (*iter, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern); + + if ((exists && existent) || (!exists && nonExistent)) + list.push_back (*iter); + } + + return list; +} + QModelIndexList CSVWorld::RegionMap::getMissingRegionCells() const { const QAbstractItemModel *model = QTableView::model(); @@ -89,15 +131,16 @@ QModelIndexList CSVWorld::RegionMap::getMissingRegionCells() const return list; } -CSVWorld::RegionMap::RegionMap (QAbstractItemModel *model, QWidget *parent) -: QTableView (parent) +CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, + CSMDoc::Document& document, QWidget *parent) +: QTableView (parent), mEditLock (false), mDocument (document) { verticalHeader()->hide(); horizontalHeader()->hide(); setSelectionMode (QAbstractItemView::ExtendedSelection); - setModel (model); + setModel (document.getData().getTableModel (universalId)); resizeColumnsToContents(); resizeRowsToContents(); @@ -113,11 +156,15 @@ CSVWorld::RegionMap::RegionMap (QAbstractItemModel *model, QWidget *parent) mSelectRegionsAction = new QAction (tr ("Select Regions"), this); connect (mSelectRegionsAction, SIGNAL (triggered()), this, SLOT (selectRegions())); addAction (mSelectRegionsAction); + + mCreateCellsAction = new QAction (tr ("Create Cells Action"), this); + connect (mCreateCellsAction, SIGNAL (triggered()), this, SLOT (createCells())); + addAction (mCreateCellsAction); } void CSVWorld::RegionMap::setEditLock (bool locked) { - + mEditLock = locked; } void CSVWorld::RegionMap::selectAll() @@ -139,4 +186,31 @@ void CSVWorld::RegionMap::selectRegions() for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter) selectionModel()->select (*iter, QItemSelectionModel::Select); +} + +void CSVWorld::RegionMap::createCells() +{ + if (mEditLock) + return; + + QModelIndexList selected = getSelectedCells (false, true); + + QAbstractItemModel *regionModel = model(); + + CSMWorld::IdTable *cellsModel = &dynamic_cast (* + mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + if (selected.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Create cells")); + + for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + { + std::string cellId = regionModel->data (*iter, CSMWorld::RegionMap::Role_CellId). + toString().toUtf8().constData(); + + mDocument.getUndoStack().push (new CSMWorld::CreateCommand (*cellsModel, cellId)); + } + + if (selected.size()>1) + mDocument.getUndoStack().endMacro(); } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index e30267b03b..5570cc585a 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -4,7 +4,16 @@ #include class QAction; -class QAbstractItemModel; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class UniversalId; +} namespace CSVWorld { @@ -15,20 +24,28 @@ namespace CSVWorld QAction *mSelectAllAction; QAction *mClearSelectionAction; QAction *mSelectRegionsAction; + QAction *mCreateCellsAction; + bool mEditLock; + CSMDoc::Document& mDocument; private: void contextMenuEvent (QContextMenuEvent *event); QModelIndexList getUnselectedCells() const; - ///< Note non-existent cells are not listed. + ///< \note Non-existent cells are not listed. + + QModelIndexList getSelectedCells (bool existent = true, bool nonExistent = false) const; + ///< \param existant Include existant cells. + /// \param nonExistant Include non-existant cells. QModelIndexList getMissingRegionCells() const; ///< Unselected cells within all regions that have at least one selected cell. public: - RegionMap (QAbstractItemModel *model, QWidget *parent = 0); + RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, + QWidget *parent = 0); void setEditLock (bool locked); @@ -39,6 +56,8 @@ namespace CSVWorld void clearSelection(); void selectRegions(); + + void createCells(); }; } diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp index e170ee309a..a966c419f5 100644 --- a/apps/opencs/view/world/regionmapsubview.cpp +++ b/apps/opencs/view/world/regionmapsubview.cpp @@ -7,7 +7,7 @@ CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document) : CSVDoc::SubView (universalId) { - mRegionMap = new RegionMap (document.getData().getTableModel (universalId), this); + mRegionMap = new RegionMap (universalId, document, this); setWidget (mRegionMap); } From 1892550833e23bb064dfbeb679a74f5947be094c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 15:32:49 +0200 Subject: [PATCH 071/135] added set/unset region actions to region map --- apps/opencs/view/world/regionmap.cpp | 66 ++++++++++++++++++++++++++++ apps/opencs/view/world/regionmap.hpp | 10 +++++ 2 files changed, 76 insertions(+) diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 1e44ff0569..7e0a30242d 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -16,6 +16,7 @@ #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/commands.hpp" +#include "../../model/world/columns.hpp" void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) { @@ -46,6 +47,17 @@ void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) menu.addAction (mCreateCellsAction); } + if (getSelectedCells().size()>0) + { + if (!mRegionId.empty()) + { + mSetRegionAction->setText (QString::fromUtf8 (("Set region to " + mRegionId).c_str())); + menu.addAction (mSetRegionAction); + } + + menu.addAction (mUnsetRegionAction); + } + menu.exec (event->globalPos()); } @@ -131,6 +143,36 @@ QModelIndexList CSVWorld::RegionMap::getMissingRegionCells() const return list; } +void CSVWorld::RegionMap::setRegion (const std::string& regionId) +{ + QModelIndexList selected = getSelectedCells(); + + QAbstractItemModel *regionModel = model(); + + CSMWorld::IdTable *cellsModel = &dynamic_cast (* + mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + QString regionId2 = QString::fromUtf8 (regionId.c_str()); + + if (selected.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Set Region")); + + for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + { + std::string cellId = regionModel->data (*iter, CSMWorld::RegionMap::Role_CellId). + toString().toUtf8().constData(); + + QModelIndex index = cellsModel->getModelIndex (cellId, + cellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Region)); + + mDocument.getUndoStack().push ( + new CSMWorld::ModifyCommand (*cellsModel, index, regionId2)); + } + + if (selected.size()>1) + mDocument.getUndoStack().endMacro(); +} + CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, QWidget *parent) : QTableView (parent), mEditLock (false), mDocument (document) @@ -160,6 +202,14 @@ CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, mCreateCellsAction = new QAction (tr ("Create Cells Action"), this); connect (mCreateCellsAction, SIGNAL (triggered()), this, SLOT (createCells())); addAction (mCreateCellsAction); + + mSetRegionAction = new QAction (tr ("Set Region"), this); + connect (mSetRegionAction, SIGNAL (triggered()), this, SLOT (setRegion())); + addAction (mSetRegionAction); + + mUnsetRegionAction = new QAction (tr ("Unset Region"), this); + connect (mUnsetRegionAction, SIGNAL (triggered()), this, SLOT (unsetRegion())); + addAction (mUnsetRegionAction); } void CSVWorld::RegionMap::setEditLock (bool locked) @@ -213,4 +263,20 @@ void CSVWorld::RegionMap::createCells() if (selected.size()>1) mDocument.getUndoStack().endMacro(); +} + +void CSVWorld::RegionMap::setRegion() +{ + if (mEditLock) + return; + + setRegion (mRegionId); +} + +void CSVWorld::RegionMap::unsetRegion() +{ + if (mEditLock) + return; + + setRegion (""); } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index 5570cc585a..b93a13eb8a 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -25,8 +25,11 @@ namespace CSVWorld QAction *mClearSelectionAction; QAction *mSelectRegionsAction; QAction *mCreateCellsAction; + QAction *mSetRegionAction; + QAction *mUnsetRegionAction; bool mEditLock; CSMDoc::Document& mDocument; + std::string mRegionId; private: @@ -42,6 +45,9 @@ namespace CSVWorld QModelIndexList getMissingRegionCells() const; ///< Unselected cells within all regions that have at least one selected cell. + void setRegion (const std::string& regionId); + ///< Set region Id of selected cells. + public: RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, @@ -58,6 +64,10 @@ namespace CSVWorld void selectRegions(); void createCells(); + + void setRegion(); + + void unsetRegion(); }; } From 2eca9e72fd154774757e111968bc53df8ed19d9a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 15:46:31 +0200 Subject: [PATCH 072/135] added view action to region map --- apps/opencs/view/world/regionmap.cpp | 41 ++++++++++++++++++--- apps/opencs/view/world/regionmap.hpp | 7 ++++ apps/opencs/view/world/regionmapsubview.cpp | 9 +++++ apps/opencs/view/world/regionmapsubview.hpp | 4 ++ 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 7e0a30242d..5b15cc9a80 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -36,7 +36,7 @@ void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) if (selectedNonExistentCells>0) { if (selectedNonExistentCells==1) - mCreateCellsAction->setText ("Create one cell"); + mCreateCellsAction->setText ("Create one Cell"); else { std::ostringstream stream; @@ -51,13 +51,16 @@ void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) { if (!mRegionId.empty()) { - mSetRegionAction->setText (QString::fromUtf8 (("Set region to " + mRegionId).c_str())); + mSetRegionAction->setText (QString::fromUtf8 (("Set Region to " + mRegionId).c_str())); menu.addAction (mSetRegionAction); } menu.addAction (mUnsetRegionAction); } + if (selectionModel()->selectedIndexes().size()>0) + menu.addAction (mViewAction); + menu.exec (event->globalPos()); } @@ -210,6 +213,10 @@ CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, mUnsetRegionAction = new QAction (tr ("Unset Region"), this); connect (mUnsetRegionAction, SIGNAL (triggered()), this, SLOT (unsetRegion())); addAction (mUnsetRegionAction); + + mViewAction = new QAction (tr ("View Cells"), this); + connect (mViewAction, SIGNAL (triggered()), this, SLOT (view())); + addAction (mViewAction); } void CSVWorld::RegionMap::setEditLock (bool locked) @@ -245,8 +252,6 @@ void CSVWorld::RegionMap::createCells() QModelIndexList selected = getSelectedCells (false, true); - QAbstractItemModel *regionModel = model(); - CSMWorld::IdTable *cellsModel = &dynamic_cast (* mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); @@ -255,7 +260,7 @@ void CSVWorld::RegionMap::createCells() for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) { - std::string cellId = regionModel->data (*iter, CSMWorld::RegionMap::Role_CellId). + std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). toString().toUtf8().constData(); mDocument.getUndoStack().push (new CSMWorld::CreateCommand (*cellsModel, cellId)); @@ -279,4 +284,30 @@ void CSVWorld::RegionMap::unsetRegion() return; setRegion (""); +} + +void CSVWorld::RegionMap::view() +{ + std::ostringstream hint; + hint << "c:"; + + QModelIndexList selected = selectionModel()->selectedIndexes(); + + bool first = true; + + for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + { + std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). + toString().toUtf8().constData(); + + if (first) + first = false; + else + hint << "; "; + + hint << cellId; + } + + emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, "sys::default"), + hint.str()); } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index b93a13eb8a..12951b459c 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -27,6 +27,7 @@ namespace CSVWorld QAction *mCreateCellsAction; QAction *mSetRegionAction; QAction *mUnsetRegionAction; + QAction *mViewAction; bool mEditLock; CSMDoc::Document& mDocument; std::string mRegionId; @@ -55,6 +56,10 @@ namespace CSVWorld void setEditLock (bool locked); + signals: + + void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); + private slots: void selectAll(); @@ -68,6 +73,8 @@ namespace CSVWorld void setRegion(); void unsetRegion(); + + void view(); }; } diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp index a966c419f5..a7675a4a6d 100644 --- a/apps/opencs/view/world/regionmapsubview.cpp +++ b/apps/opencs/view/world/regionmapsubview.cpp @@ -10,9 +10,18 @@ CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId, mRegionMap = new RegionMap (universalId, document, this); setWidget (mRegionMap); + + connect (mRegionMap, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), + this, SLOT (editRequest (const CSMWorld::UniversalId&, const std::string&))); } void CSVWorld::RegionMapSubView::setEditLock (bool locked) { mRegionMap->setEditLock (locked); +} + +void CSVWorld::RegionMapSubView::editRequest (const CSMWorld::UniversalId& id, + const std::string& hint) +{ + focusId (id, hint); } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmapsubview.hpp b/apps/opencs/view/world/regionmapsubview.hpp index f329c7f3b9..5247279017 100644 --- a/apps/opencs/view/world/regionmapsubview.hpp +++ b/apps/opencs/view/world/regionmapsubview.hpp @@ -25,6 +25,10 @@ namespace CSVWorld RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document); virtual void setEditLock (bool locked); + + private slots: + + void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); }; } From 097c063b8a28cb6483c153507bff63fbf00e5e2c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 16:40:16 +0200 Subject: [PATCH 073/135] added 'view in table' action to region map --- apps/opencs/view/world/regionmap.cpp | 33 ++++++++++++++++++++++++++++ apps/opencs/view/world/regionmap.hpp | 3 +++ 2 files changed, 36 insertions(+) diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 5b15cc9a80..738de89ae6 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -56,6 +56,8 @@ void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) } menu.addAction (mUnsetRegionAction); + + menu.addAction (mViewInTableAction); } if (selectionModel()->selectedIndexes().size()>0) @@ -217,6 +219,10 @@ CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, mViewAction = new QAction (tr ("View Cells"), this); connect (mViewAction, SIGNAL (triggered()), this, SLOT (view())); addAction (mViewAction); + + mViewInTableAction = new QAction (tr ("View Cells in Table"), this); + connect (mViewInTableAction, SIGNAL (triggered()), this, SLOT (viewInTable())); + addAction (mViewInTableAction); } void CSVWorld::RegionMap::setEditLock (bool locked) @@ -310,4 +316,31 @@ void CSVWorld::RegionMap::view() emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, "sys::default"), hint.str()); +} + +void CSVWorld::RegionMap::viewInTable() +{ + std::ostringstream hint; + hint << "f:!or("; + + QModelIndexList selected = getSelectedCells(); + + bool first = true; + + for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter) + { + std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId). + toString().toUtf8().constData(); + + if (first) + first = false; + else + hint << ","; + + hint << "string(ID,\"" << cellId << "\")"; + } + + hint << ")"; + + emit editRequest (CSMWorld::UniversalId::Type_Cells, hint.str()); } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index 12951b459c..c3757fe45b 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -28,6 +28,7 @@ namespace CSVWorld QAction *mSetRegionAction; QAction *mUnsetRegionAction; QAction *mViewAction; + QAction *mViewInTableAction; bool mEditLock; CSMDoc::Document& mDocument; std::string mRegionId; @@ -75,6 +76,8 @@ namespace CSVWorld void unsetRegion(); void view(); + + void viewInTable(); }; } From d188e68227d2b6bd430bc49d6da6b00bb3634dd5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 13 Apr 2014 16:40:41 +0200 Subject: [PATCH 074/135] added f-type hint to TableSubView --- apps/opencs/view/filter/filterbox.cpp | 15 +++++++++----- apps/opencs/view/filter/filterbox.hpp | 8 ++++++- apps/opencs/view/filter/recordfilterbox.cpp | 15 +++++++++----- apps/opencs/view/filter/recordfilterbox.hpp | 6 ++++++ apps/opencs/view/world/tablesubview.cpp | 23 ++++++++++++++------- apps/opencs/view/world/tablesubview.hpp | 8 +++++++ 6 files changed, 57 insertions(+), 18 deletions(-) diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index a332880252..0089143e94 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -15,23 +15,28 @@ CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent) layout->setContentsMargins (0, 0, 0, 0); - RecordFilterBox *recordFilterBox = new RecordFilterBox (data, this); + mRecordFilterBox = new RecordFilterBox (data, this); - layout->addWidget (recordFilterBox); + layout->addWidget (mRecordFilterBox); setLayout (layout); - connect (recordFilterBox, + connect (mRecordFilterBox, SIGNAL (filterChanged (boost::shared_ptr)), this, SIGNAL (recordFilterChanged (boost::shared_ptr))); connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - recordFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); + mRecordFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); - connect(this, SIGNAL(useFilterRequest(const std::string&)), recordFilterBox, SIGNAL(useFilterRequest(const std::string&))); + connect(this, SIGNAL(useFilterRequest(const std::string&)), mRecordFilterBox, SIGNAL(useFilterRequest(const std::string&))); setAcceptDrops(true); } +void CSVFilter::FilterBox::setRecordFilter (const std::string& filter) +{ + mRecordFilterBox->setFilter (filter); +} + void CSVFilter::FilterBox::dropEvent (QDropEvent* event) { std::vector data = dynamic_cast (event->mimeData())->getData(); diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp index 5954035fcb..a8aa319531 100644 --- a/apps/opencs/view/filter/filterbox.hpp +++ b/apps/opencs/view/filter/filterbox.hpp @@ -16,10 +16,14 @@ namespace CSMWorld namespace CSVFilter { + class RecordFilterBox; + class FilterBox : public QWidget { Q_OBJECT + RecordFilterBox *mRecordFilterBox; + void dragEnterEvent (QDragEnterEvent* event); void dropEvent (QDropEvent* event); @@ -30,11 +34,13 @@ namespace CSVFilter FilterBox (CSMWorld::Data& data, QWidget *parent = 0); + void setRecordFilter (const std::string& filter); + signals: void recordFilterChanged (boost::shared_ptr filter); void recordDropped (std::vector& types, Qt::DropAction action); - void createFilterRequest(std::vector > >& filterSource, + void createFilterRequest(std::vector > >& filterSource, Qt::DropAction action); void useFilterRequest(const std::string& idOfFilter); }; diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index 2a1a1407fa..f15b1e17a0 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -15,18 +15,23 @@ CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *pare layout->addWidget (new QLabel ("Record Filter", this)); - EditWidget *editWidget = new EditWidget (data, this); + mEdit = new EditWidget (data, this); - layout->addWidget (editWidget); + layout->addWidget (mEdit); setLayout (layout); connect ( - editWidget, SIGNAL (filterChanged (boost::shared_ptr)), + mEdit, SIGNAL (filterChanged (boost::shared_ptr)), this, SIGNAL (filterChanged (boost::shared_ptr))); connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - editWidget, SLOT(createFilterRequest(std::vector > >&, Qt::DropAction))); + mEdit, SLOT(createFilterRequest(std::vector > >&, Qt::DropAction))); - connect(this, SIGNAL(useFilterRequest(const std::string&)), editWidget, SLOT(useFilterRequest(const std::string&))); + connect(this, SIGNAL(useFilterRequest(const std::string&)), mEdit, SLOT(useFilterRequest(const std::string&))); } + +void CSVFilter::RecordFilterBox::setFilter (const std::string& filter) +{ + mEdit->setText (QString::fromUtf8 (filter.c_str())); +} \ No newline at end of file diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp index fa5c9c3c28..8e01310a36 100644 --- a/apps/opencs/view/filter/recordfilterbox.hpp +++ b/apps/opencs/view/filter/recordfilterbox.hpp @@ -17,14 +17,20 @@ namespace CSMWorld namespace CSVFilter { + class EditWidget; + class RecordFilterBox : public QWidget { Q_OBJECT + EditWidget *mEdit; + public: RecordFilterBox (CSMWorld::Data& data, QWidget *parent = 0); + void setFilter (const std::string& filter); + signals: void filterChanged (boost::shared_ptr filter); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 2d08d186e5..7f7b1477e9 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -26,9 +26,9 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D layout->insertWidget (0, mTable = new Table (id, mBottom->canCreateAndDelete(), sorting, document), 2); - CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); + mFilterBox = new CSVFilter::FilterBox (document.getData(), this); - layout->insertWidget (0, filterBox); + layout->insertWidget (0, mFilterBox); QWidget *widget = new QWidget; @@ -48,7 +48,7 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D mTable->selectionSizeUpdate(); mTable->viewport()->installEventFilter(this); mBottom->installEventFilter(this); - filterBox->installEventFilter(this); + mFilterBox->installEventFilter(this); if (mBottom->canCreateAndDelete()) { @@ -63,17 +63,17 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D connect (mBottom, SIGNAL (requestFocus (const std::string&)), mTable, SLOT (requestFocus (const std::string&))); - connect (filterBox, + connect (mFilterBox, SIGNAL (recordFilterChanged (boost::shared_ptr)), mTable, SLOT (recordFilterChanged (boost::shared_ptr))); - connect(filterBox, SIGNAL(recordDropped(std::vector&, Qt::DropAction)), + connect(mFilterBox, SIGNAL(recordDropped(std::vector&, Qt::DropAction)), this, SLOT(createFilterRequest(std::vector&, Qt::DropAction))); - connect(this, SIGNAL(useFilterRequest(const std::string&)), filterBox, SIGNAL(useFilterRequest(const std::string&))); + connect(this, SIGNAL(useFilterRequest(const std::string&)), mFilterBox, SIGNAL(useFilterRequest(const std::string&))); connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - filterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); + mFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); } void CSVWorld::TableSubView::setEditLock (bool locked) @@ -97,6 +97,15 @@ void CSVWorld::TableSubView::setStatusBar (bool show) mBottom->setStatusBar (show); } +void CSVWorld::TableSubView::useHint (const std::string& hint) +{ + if (hint.empty()) + return; + + if (hint[0]=='f' && hint.size()>=2) + mFilterBox->setRecordFilter (hint.substr (2)); +} + void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone) { emit cloneRequest(toClone.getId(), toClone.getType()); diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index 910fec325b..3f82a7592a 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -17,6 +17,11 @@ namespace CSMDoc class Document; } +namespace CSVFilter +{ + class FilterBox; +} + namespace CSVWorld { class Table; @@ -29,6 +34,7 @@ namespace CSVWorld Table *mTable; TableBottomBox *mBottom; + CSVFilter::FilterBox *mFilterBox; public: @@ -41,6 +47,8 @@ namespace CSVWorld virtual void setStatusBar (bool show); + virtual void useHint (const std::string& hint); + protected: bool eventFilter(QObject* object, QEvent *event); From 89be1069a78e858f431112d6b0bc8423a6da6aa3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 14 Apr 2014 18:31:46 +1000 Subject: [PATCH 075/135] Bug #1260: show thief.dds image for a custom class level up menu --- apps/openmw/mwgui/levelupdialog.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 2f11a4d125..cd72f56770 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -131,7 +131,14 @@ namespace MWGui const ESM::Class *cls = world->getStore().get().find(playerData->mClass); - mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); + // Vanilla uses thief.dds for custom classes. A player with a custom class + // doesn't have mId set, see mwworld/esmstore.hpp where it is initialised as + // "$dynamic0". This check should resolve bug #1260. + if(world->getStore().get().isDynamic(cls->mId)) + mClassImage->setImageTexture ("textures\\levelup\\thief.dds"); + else + mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); + int level = creatureStats.getLevel ()+1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast(level)); From 1fc030653fb9c3b25dc96ee5ac202a5eb5778d4c Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 15 Apr 2014 22:30:41 +1000 Subject: [PATCH 076/135] Avoid hard coding "thief.dds" string. --- apps/openmw/mwgui/levelupdialog.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index cd72f56770..5a3fc2855d 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -134,12 +134,20 @@ namespace MWGui // Vanilla uses thief.dds for custom classes. A player with a custom class // doesn't have mId set, see mwworld/esmstore.hpp where it is initialised as // "$dynamic0". This check should resolve bug #1260. + // Choosing Stealth specialization and Speed/Agility as attributes. if(world->getStore().get().isDynamic(cls->mId)) - mClassImage->setImageTexture ("textures\\levelup\\thief.dds"); + { + MWWorld::SharedIterator it = world->getStore().get().begin(); + for(; it != world->getStore().get().end(); it++) + { + if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3) + break; + } + mClassImage->setImageTexture ("textures\\levelup\\" + it->mId + ".dds"); + } else mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); - int level = creatureStats.getLevel ()+1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast(level)); From fc1837e2edfe2c56057ea61c8d263a0dd0577bd5 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Tue, 15 Apr 2014 19:26:43 +0200 Subject: [PATCH 077/135] fixed bug: regions can be dragged ps fuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu --- apps/opencs/model/world/columnimp.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 89fb586aad..6976b454d9 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -785,7 +785,7 @@ namespace CSMWorld template struct RegionColumn : public Column { - RegionColumn() : Column (Columns::ColumnId_Region, ColumnBase::Display_String) {} + RegionColumn() : Column (Columns::ColumnId_Region, ColumnBase::Display_Region) {} virtual QVariant get (const Record& record) const { From 8fba71101c014a873f7ec412d8e590ca8413ef22 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Tue, 15 Apr 2014 20:39:19 +0200 Subject: [PATCH 078/135] removed signals --- apps/opencs/view/filter/editwidget.cpp | 9 +-------- apps/opencs/view/filter/editwidget.hpp | 6 +++--- apps/opencs/view/filter/filterbox.cpp | 10 ++++++---- apps/opencs/view/filter/filterbox.hpp | 20 ++++++++++---------- apps/opencs/view/filter/recordfilterbox.cpp | 14 ++++++++------ apps/opencs/view/filter/recordfilterbox.hpp | 8 +++++--- apps/opencs/view/world/tablesubview.cpp | 9 ++------- apps/opencs/view/world/tablesubview.hpp | 3 --- 8 files changed, 35 insertions(+), 44 deletions(-) diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index b163297f9a..68e99e0dee 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -193,11 +193,4 @@ std::string CSVFilter::EditWidget::generateFilter (std::pair< std::string, std:: } return ss.str(); -} - -void CSVFilter::EditWidget::useFilterRequest (const std::string& idOfFilter) -{ - clear(); - insert(QString::fromUtf8(idOfFilter.c_str())); -} - +} \ No newline at end of file diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp index e7e34b8e9a..a0f9f8919a 100644 --- a/apps/opencs/view/filter/editwidget.hpp +++ b/apps/opencs/view/filter/editwidget.hpp @@ -30,6 +30,9 @@ namespace CSVFilter EditWidget (CSMWorld::Data& data, QWidget *parent = 0); + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); + signals: void filterChanged (boost::shared_ptr filter); @@ -47,10 +50,7 @@ namespace CSVFilter void filterRowsInserted (const QModelIndex& parent, int start, int end); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); }; } diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index 0089143e94..e588770b11 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -25,10 +25,6 @@ CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent) SIGNAL (filterChanged (boost::shared_ptr)), this, SIGNAL (recordFilterChanged (boost::shared_ptr))); - connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - mRecordFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); - - connect(this, SIGNAL(useFilterRequest(const std::string&)), mRecordFilterBox, SIGNAL(useFilterRequest(const std::string&))); setAcceptDrops(true); } @@ -53,3 +49,9 @@ void CSVFilter::FilterBox::dragMoveEvent (QDragMoveEvent* event) { event->accept(); } + +void CSVFilter::FilterBox::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, + Qt::DropAction action) +{ + mRecordFilterBox->createFilterRequest(filterSource, action); +} \ No newline at end of file diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp index a8aa319531..c765164e76 100644 --- a/apps/opencs/view/filter/filterbox.hpp +++ b/apps/opencs/view/filter/filterbox.hpp @@ -24,25 +24,25 @@ namespace CSVFilter RecordFilterBox *mRecordFilterBox; + public: + FilterBox (CSMWorld::Data& data, QWidget *parent = 0); + + void setRecordFilter (const std::string& filter); + + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); + + + private: void dragEnterEvent (QDragEnterEvent* event); void dropEvent (QDropEvent* event); void dragMoveEvent(QDragMoveEvent *event); - public: - - FilterBox (CSMWorld::Data& data, QWidget *parent = 0); - - void setRecordFilter (const std::string& filter); - signals: - void recordFilterChanged (boost::shared_ptr filter); void recordDropped (std::vector& types, Qt::DropAction action); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); }; } diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index f15b1e17a0..ec56476185 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -24,14 +24,16 @@ CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *pare connect ( mEdit, SIGNAL (filterChanged (boost::shared_ptr)), this, SIGNAL (filterChanged (boost::shared_ptr))); - - connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - mEdit, SLOT(createFilterRequest(std::vector > >&, Qt::DropAction))); - - connect(this, SIGNAL(useFilterRequest(const std::string&)), mEdit, SLOT(useFilterRequest(const std::string&))); } void CSVFilter::RecordFilterBox::setFilter (const std::string& filter) { + mEdit->clear(); mEdit->setText (QString::fromUtf8 (filter.c_str())); -} \ No newline at end of file +} + +void CSVFilter::RecordFilterBox::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, + Qt::DropAction action) +{ + mEdit->createFilterRequest(filterSource, action); +} diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp index 8e01310a36..f4d17510b6 100644 --- a/apps/opencs/view/filter/recordfilterbox.hpp +++ b/apps/opencs/view/filter/recordfilterbox.hpp @@ -31,12 +31,14 @@ namespace CSVFilter void setFilter (const std::string& filter); + void useFilterRequest(const std::string& idOfFilter); + + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); + signals: void filterChanged (boost::shared_ptr filter); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); }; } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 7f7b1477e9..a5a7e8252e 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -69,11 +69,6 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D connect(mFilterBox, SIGNAL(recordDropped(std::vector&, Qt::DropAction)), this, SLOT(createFilterRequest(std::vector&, Qt::DropAction))); - - connect(this, SIGNAL(useFilterRequest(const std::string&)), mFilterBox, SIGNAL(useFilterRequest(const std::string&))); - - connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - mFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); } void CSVWorld::TableSubView::setEditLock (bool locked) @@ -122,7 +117,7 @@ void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::Univers filterSource.push_back(pair); } - emit createFilterRequest(filterSource, action); + mFilterBox->createFilterRequest(filterSource, action); } bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) @@ -134,7 +129,7 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); if (handled) { - emit useFilterRequest(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); + mFilterBox->setRecordFilter(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); } return handled; } diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index 3f82a7592a..b344ba1ad2 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -55,9 +55,6 @@ namespace CSVWorld signals: void cloneRequest(const std::string&, const CSMWorld::UniversalId::Type); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); private slots: From a7cece3d30d3ea43521119f72813313a580d0ef1 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 15 Apr 2014 22:34:15 +0200 Subject: [PATCH 079/135] do not generate modfiy commands on edits to change a cell to a value equal its original value before the edit --- apps/opencs/view/world/util.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 227c5c9c5a..b2a32b551c 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -108,7 +108,11 @@ void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemM { NastyTableModelHack hack (*model); QStyledItemDelegate::setModelData (editor, &hack, index); - mUndoStack.push (new CSMWorld::ModifyCommand (*model, index, hack.getData())); + + QVariant new_ = hack.getData(); + + if (model->data (index)!=new_) + mUndoStack.push (new CSMWorld::ModifyCommand (*model, index, new_)); } CSVWorld::CommandDelegate::CommandDelegate (QUndoStack& undoStack, QObject *parent) From 1ceeeb4a224753c6df6fae64c3bf2f6d9929fa74 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 13 Apr 2014 07:35:26 +1000 Subject: [PATCH 080/135] Fix movement glitches for actors with low speeds (e.g. a rat). Was caused by false detection of being stuck. --- apps/openmw/mwmechanics/aiwander.cpp | 30 ++++++++++++++++++---------- apps/openmw/mwmechanics/aiwander.hpp | 3 ++- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index e89d43ca4c..c50506c75d 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -35,6 +35,7 @@ namespace MWMechanics , mPrevX(0) , mPrevY(0) , mWalkState(State_Norm) + , mDistSameSpot(0) , mStuckCount(0) , mEvadeDuration(0) , mStuckDuration(0) @@ -71,7 +72,6 @@ namespace MWMechanics return new AiWander(*this); } - // TODO: duration is passed in but never used, check if it is needed bool AiWander::execute (const MWWorld::Ptr& actor,float duration) { actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); @@ -113,7 +113,9 @@ namespace MWMechanics mCellX = actor.getCell()->getCell()->mData.mX; mCellY = actor.getCell()->getCell()->mData.mY; - // TODO: If there is no path does this actor get stuck forever? + // If there is no path this actor doesn't go anywhere. See: + // https://forum.openmw.org/viewtopic.php?t=1556 + // http://www.fliggerty.com/phpBB3/viewtopic.php?f=30&t=5833 if(!mPathgrid) mDistance = 0; else if(mPathgrid->mPoints.empty()) @@ -166,7 +168,7 @@ namespace MWMechanics } } - // TODO: Does this actor stay in one spot forever while in AiWander? + // Actor becomes stationary - see above URL's for previous research if(mAllowedNodes.empty()) mDistance = 0; @@ -277,8 +279,10 @@ namespace MWMechanics { assert(mAllowedNodes.size()); unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size()); + // NOTE: destNodePos initially constructed with local (i.e. cell) co-ordinates Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ); + // convert dest to use world co-ordinates ESM::Pathgrid::Point dest; dest.mX = destNodePos[0] + mXCell; dest.mY = destNodePos[1] + mYCell; @@ -338,9 +342,15 @@ namespace MWMechanics * f = one frame * t = how long before considered stuck * u = how long to move sideways + * + * DIST_SAME_SPOT is calibrated for movement speed of around 150. + * A rat has walking speed of around 30, so we need to adjust for + * that. */ - bool samePosition = (abs(pos.pos[0] - mPrevX) < DIST_SAME_SPOT) && - (abs(pos.pos[1] - mPrevY) < DIST_SAME_SPOT); + if(!mDistSameSpot) + mDistSameSpot = DIST_SAME_SPOT * (actor.getClass().getSpeed(actor) / 150); + bool samePosition = (abs(pos.pos[0] - mPrevX) < mDistSameSpot) && + (abs(pos.pos[1] - mPrevY) < mDistSameSpot); switch(mWalkState) { @@ -364,14 +374,14 @@ namespace MWMechanics { mStuckDuration += duration; // consider stuck only if position unchanges for a period - if(mStuckDuration > DURATION_SAME_SPOT) + if(mStuckDuration < DURATION_SAME_SPOT) + break; // still checking, note duration added to timer + else { - mWalkState = State_Evade; mStuckDuration = 0; mStuckCount++; + mWalkState = State_Evade; } - else - break; // still in the same state, but duration added to timer } } /* FALL THROUGH */ @@ -395,7 +405,7 @@ namespace MWMechanics // diagonal should have same animation as walk forward actor.getClass().getMovementSettings(actor).mPosition[0] = 1; - actor.getClass().getMovementSettings(actor).mPosition[1] = 0.01f; + actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; // change the angle a bit, too zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 75621911e1..dd718a6770 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -58,8 +58,9 @@ namespace MWMechanics WalkState mWalkState; int mStuckCount; - float mStuckDuration; + float mStuckDuration; // accumulate time here while in same spot float mEvadeDuration; + float mDistSameSpot; // take account of actor's speed bool mStoredAvailableNodes; bool mChooseAction; From d3be725ee72f157afe14524e7955b885b46a4499 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 18 Apr 2014 09:03:36 +1000 Subject: [PATCH 081/135] Actors are moved on if idling near a closed interior door. Unreachable pathgrid points due to a closed door are removed from the allowed set of points. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 417 ++++++++++++++++----------- apps/openmw/mwmechanics/aiwander.hpp | 39 +-- apps/openmw/mwmechanics/obstacle.cpp | 176 +++++++++++ apps/openmw/mwmechanics/obstacle.hpp | 52 ++++ 5 files changed, 488 insertions(+), 198 deletions(-) create mode 100644 apps/openmw/mwmechanics/obstacle.cpp create mode 100644 apps/openmw/mwmechanics/obstacle.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 9fabb20806..e83ae2d8d7 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -69,7 +69,7 @@ add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence aipersue alchemy aiwander aitravel aifollow aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting - disease pickpocket levelledlist combat steering + disease pickpocket levelledlist combat steering obstacle ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index c50506c75d..029be2fb8a 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -17,11 +17,8 @@ namespace MWMechanics { - // NOTE: determined empirically but probably need further tweaking - static const int COUNT_BEFORE_RESET = 200; - static const float DIST_SAME_SPOT = 1.8f; - static const float DURATION_SAME_SPOT = 1.0f; - static const float DURATION_TO_EVADE = 0.4f; + static const int COUNT_BEFORE_RESET = 200; // TODO: maybe no longer needed + static const float DOOR_CHECK_INTERVAL = 1.5f; AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) @@ -29,16 +26,10 @@ namespace MWMechanics , mCellY(std::numeric_limits::max()) , mXCell(0) , mYCell(0) - , mX(0) - , mY(0) - , mZ(0) - , mPrevX(0) - , mPrevY(0) - , mWalkState(State_Norm) - , mDistSameSpot(0) - , mStuckCount(0) - , mEvadeDuration(0) - , mStuckDuration(0) + , mCell(NULL) + , mStuckCount(0) // TODO: maybe no longer needed + , mDoorCheckDuration(0) + , mTrimCurrentNode(false) , mSaidGreeting(false) { for(unsigned short counter = 0; counter < mIdle.size(); counter++) @@ -56,7 +47,7 @@ namespace MWMechanics mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); mPlayedIdle = 0; - mPathgrid = NULL; + //mPathgrid = NULL; mIdleChanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get().find("fIdleChanceMultiplier")->getFloat(); @@ -72,10 +63,71 @@ namespace MWMechanics return new AiWander(*this); } + /* + * AiWander high level states (0.29.0). Not entirely accurate in some cases + * e.g. non-NPC actors do not greet and some creatures may be moving even in + * the IdleNow state. + * + * [select node, + * build path] + * +---------->MoveNow----------->Walking + * | | + * [allowed | | + * nodes] | [hello if near] | + * start--->ChooseAction----->IdleNow | + * ^ ^ | | + * | | | | + * | +-----------+ | + * | | + * +----------------------------------+ + * + * + * New high level states. Not exactly as per vanilla (e.g. door stuff) + * but the differences are required because our physics does not work like + * vanilla and therefore have to compensate/work around. Note also many of + * the actions now have reaction times. + * + * [select node, [if stuck evade + * build path] or remove nodes if near door] + * +---------->MoveNow<---------->Walking + * | ^ | | + * | |(near door) | | + * [allowed | | | | + * nodes] | [hello if near] | | + * start--->ChooseAction----->IdleNow | | + * ^ ^ | ^ | | + * | | | | (stuck near | | + * | +-----------+ +---------------+ | + * | player) | + * +----------------------------------+ + * + * TODO: non-time critical operations should be run once every 250ms or so. + * + * TODO: It would be great if door opening/closing can be detected and pathgrid + * links dynamically updated. Currently (0.29.0) AiWander allows destination + * beyond closed doors which sometimes makes the actors stuck at the door and + * impossible for the player to open the door. + * + * For now detect being stuck at the door and simply delete the nodes from the + * allowed set. The issue is when the door opens the allowed set is not + * re-calculated. Normally this would not be an issue since hostile actors will + * enter combat (i.e. no longer wandering) + * + * FIXME: Sometimes allowed nodes that shouldn't be deleted are deleted. + */ bool AiWander::execute (const MWWorld::Ptr& actor,float duration) { - actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); - actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); + bool cellChange = mCell && (actor.getCell() != mCell); + if(!mCell || cellChange) + { + mCell = actor.getCell(); + mStoredAvailableNodes = false; // prob. not needed since mDistance = 0 + } + const ESM::Cell *cell = mCell->getCell(); + + MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor); + cStats.setDrawState(DrawState_Nothing); + cStats.setMovementFlag(CreatureStats::Flag_Run, false); MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) { @@ -105,65 +157,76 @@ namespace MWMechanics ESM::Position pos = actor.getRefData().getPosition(); - // Once off initialization to discover & store allowed node points for this actor. + // Initialization to discover & store allowed node points for this actor. if(!mStoredAvailableNodes) { - mPathgrid = world->getStore().get().search(*actor.getCell()->getCell()); + // infrequently used, therefore no benefit in caching it as a member + const ESM::Pathgrid * + pathgrid = world->getStore().get().search(*cell); - mCellX = actor.getCell()->getCell()->mData.mX; - mCellY = actor.getCell()->getCell()->mData.mY; + // cache the current cell location + mCellX = cell->mData.mX; + mCellY = cell->mData.mY; // If there is no path this actor doesn't go anywhere. See: // https://forum.openmw.org/viewtopic.php?t=1556 // http://www.fliggerty.com/phpBB3/viewtopic.php?f=30&t=5833 - if(!mPathgrid) - mDistance = 0; - else if(mPathgrid->mPoints.empty()) + if(!pathgrid || pathgrid->mPoints.empty()) mDistance = 0; - if(mDistance) // A distance value is initially passed into the constructor. + // A distance value passed into the constructor indicates how far the + // actor can wander from the spawn position. AiWander assumes that + // pathgrid points are available, and uses them to randomly select wander + // destinations within the allowed set of pathgrid points (nodes). + if(mDistance) { mXCell = 0; mYCell = 0; - if(actor.getCell()->getCell()->isExterior()) + if(cell->isExterior()) { mXCell = mCellX * ESM::Land::REAL_SIZE; mYCell = mCellY * ESM::Land::REAL_SIZE; } - // convert npcPos to local (i.e. cell) co-ordinates - Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos); - npcPos[0] = npcPos[0] - mXCell; - npcPos[1] = npcPos[1] - mYCell; + // convert actorPos to local (i.e. cell) co-ordinates + Ogre::Vector3 actorPos(pos.pos); + actorPos[0] = actorPos[0] - mXCell; + actorPos[1] = actorPos[1] - mYCell; - // populate mAllowedNodes for this actor with pathgrid point indexes based on mDistance - // NOTE: mPoints and mAllowedNodes contain points in local co-ordinates - for(unsigned int counter = 0; counter < mPathgrid->mPoints.size(); counter++) + // mAllowedNodes for this actor with pathgrid point indexes + // based on mDistance + // NOTE: mPoints and mAllowedNodes are in local co-ordinates + float closestNodeDist = -1; + unsigned int closestIndex = 0; + unsigned int indexAllowedNodes = 0; + for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) { - Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, - mPathgrid->mPoints[counter].mY, - mPathgrid->mPoints[counter].mZ); - if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance) - mAllowedNodes.push_back(mPathgrid->mPoints[counter]); + float sqrDist = actorPos.squaredDistance(Ogre::Vector3( + pathgrid->mPoints[counter].mX, + pathgrid->mPoints[counter].mY, + pathgrid->mPoints[counter].mZ)); + if(sqrDist <= (mDistance * mDistance)) + { + mAllowedNodes.push_back(pathgrid->mPoints[counter]); + // keep track of the closest node + if(closestNodeDist == -1 || sqrDist < closestNodeDist) + { + closestNodeDist = sqrDist; + closestIndex = indexAllowedNodes; + } + indexAllowedNodes++; + } } if(!mAllowedNodes.empty()) { - Ogre::Vector3 firstNodePos(mAllowedNodes[0].mX, mAllowedNodes[0].mY, mAllowedNodes[0].mZ); - float closestNode = npcPos.squaredDistance(firstNodePos); - unsigned int index = 0; - for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) - { - Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, - mAllowedNodes[counterThree].mY, - mAllowedNodes[counterThree].mZ); - float tempDist = npcPos.squaredDistance(nodePos); - if(tempDist < closestNode) - index = counterThree; - } - mCurrentNode = mAllowedNodes[index]; - mAllowedNodes.erase(mAllowedNodes.begin() + index); - - mStoredAvailableNodes = true; // set only if successful in finding allowed nodes + // Start with the closest node and remove it from the allowed set + // so that it does not get selected again. The removed node will + // later be put in the back of the queue, unless it gets removed + // due to inaccessibility (e.g. a closed door) + mCurrentNode = mAllowedNodes[closestIndex]; + mAllowedNodes.erase(mAllowedNodes.begin() + closestIndex); + // set only if successful in finding allowed nodes + mStoredAvailableNodes = true; } } } @@ -173,10 +236,10 @@ namespace MWMechanics mDistance = 0; // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. - if(mDistance && (mCellX != actor.getCell()->getCell()->mData.mX || mCellY != actor.getCell()->getCell()->mData.mY)) + if(mDistance && cellChange) mDistance = 0; - if(mChooseAction) // Initially set true by the constructor. + if(mChooseAction) { mPlayedIdle = 0; unsigned short idleRoll = 0; @@ -207,7 +270,7 @@ namespace MWMechanics mIdleNow = true; // Play idle voiced dialogue entries randomly - int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); + int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); if (hello > 0) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -216,19 +279,38 @@ namespace MWMechanics MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); // Don't bother if the player is out of hearing range - if (roll < chance && Ogre::Vector3(player.getRefData().getPosition().pos).distance(Ogre::Vector3(actor.getRefData().getPosition().pos)) < 1500) + if (roll < chance && Ogre::Vector3(player.getRefData().getPosition().pos).distance(Ogre::Vector3(pos.pos)) < 1500) MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); } } } + // Check if an idle actor is too close to a door - if so start walking + mDoorCheckDuration += duration; + if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL) + { + mDoorCheckDuration = 0; // restart timer + if(mDistance && // actor is not intended to be stationary + mIdleNow && // but is in idle + !mWalking && // FIXME: some actors are idle while walking + proximityToDoor(actor)) // NOTE: checks interior cells only + { + mIdleNow = false; + mMoveNow = true; + mTrimCurrentNode = false; // just in case +//#if 0 + std::cout << "idle door \""+actor.getClass().getName(actor)+"\" "<< std::endl; +//#endif + } + } + // Allow interrupting a walking actor to trigger a greeting - if(mIdleNow || (mWalking && (mWalkState != State_Norm))) + if(mIdleNow || (mWalking && !mObstacleCheck.isNormalState())) { // Play a random voice greeting if the player gets too close const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); + int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); float helloDistance = hello; int iGreetDistanceMultiplier = store.get().find("iGreetDistanceMultiplier")->getInt(); helloDistance *= iGreetDistanceMultiplier; @@ -242,7 +324,7 @@ namespace MWMechanics stopWalking(actor); mMoveNow = false; mWalking = false; - mWalkState = State_Norm; + mObstacleCheck.clear(); } if (!mSaidGreeting) @@ -275,12 +357,15 @@ namespace MWMechanics if(mMoveNow && mDistance) { + // Construct a new path if there isn't one if(!mPathFinder.isPathConstructed()) { assert(mAllowedNodes.size()); unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size()); - // NOTE: destNodePos initially constructed with local (i.e. cell) co-ordinates - Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ); + // NOTE: initially constructed with local (i.e. cell) co-ordinates + Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, + mAllowedNodes[randNode].mY, + mAllowedNodes[randNode].mZ); // convert dest to use world co-ordinates ESM::Pathgrid::Point dest; @@ -299,16 +384,27 @@ namespace MWMechanics if(mPathFinder.isPathConstructed()) { - // buildPath inserts dest in case it is not a pathgraph point index - // which is a duplicate for AiWander + // buildPath inserts dest in case it is not a pathgraph point + // index which is a duplicate for AiWander. However below code + // does not work since getPath() returns a copy of path not a + // reference //if(mPathFinder.getPathSize() > 1) //mPathFinder.getPath().pop_back(); - // Remove this node as an option and add back the previously used node - // (stops NPC from picking the same node): + // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; mAllowedNodes.erase(mAllowedNodes.begin() + randNode); - mAllowedNodes.push_back(mCurrentNode); + // check if mCurrentNode was taken out of mAllowedNodes + if(mTrimCurrentNode && mAllowedNodes.size() > 1) + { + mTrimCurrentNode = false; +#if 0 + std::cout << "deleted "<< std::to_string(mCurrentNode.mX) + +", "+std::to_string(mCurrentNode.mY) << std::endl; +#endif + } + else + mAllowedNodes.push_back(mCurrentNode); mCurrentNode = temp; mMoveNow = false; @@ -320,124 +416,97 @@ namespace MWMechanics } } - if(mWalking) + // Are we there yet? + if(mWalking && + mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { - if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) - { - stopWalking(actor); - mMoveNow = false; - mWalking = false; - mChooseAction = true; - } - else - { - /* f t - * State_Norm <---> State_CheckStuck --> State_Evade - * ^ ^ | ^ | ^ | | - * | | | | | | | | - * | +---+ +---+ +---+ | u - * | any < t < u | - * +--------------------------------------------+ - * - * f = one frame - * t = how long before considered stuck - * u = how long to move sideways - * - * DIST_SAME_SPOT is calibrated for movement speed of around 150. - * A rat has walking speed of around 30, so we need to adjust for - * that. - */ - if(!mDistSameSpot) - mDistSameSpot = DIST_SAME_SPOT * (actor.getClass().getSpeed(actor) / 150); - bool samePosition = (abs(pos.pos[0] - mPrevX) < mDistSameSpot) && - (abs(pos.pos[1] - mPrevY) < mDistSameSpot); + stopWalking(actor); + mMoveNow = false; + mWalking = false; + mChooseAction = true; + } + else if(mWalking) // have not yet reached the destination + { + // turn towards the next point in mPath + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; - switch(mWalkState) + // Returns true if evasive action needs to be taken + if(mObstacleCheck.check(actor, duration)) + { + // first check if we're walking into a door + if(proximityToDoor(actor)) // NOTE: checks interior cells only { - case State_Norm: - { - if(!samePosition) - break; - else - mWalkState = State_CheckStuck; - } - /* FALL THROUGH */ - case State_CheckStuck: - { - if(!samePosition) - { - mWalkState = State_Norm; - mStuckDuration = 0; - break; - } - else - { - mStuckDuration += duration; - // consider stuck only if position unchanges for a period - if(mStuckDuration < DURATION_SAME_SPOT) - break; // still checking, note duration added to timer - else - { - mStuckDuration = 0; - mStuckCount++; - mWalkState = State_Evade; - } - } - } - /* FALL THROUGH */ - case State_Evade: - { - mEvadeDuration += duration; - if(mEvadeDuration < DURATION_TO_EVADE) - break; - else - { - mWalkState = State_Norm; // tried to evade, assume all is ok and start again - mEvadeDuration = 0; - } - } - /* NO DEFAULT CASE */ + // remove allowed points then select another random destination + mTrimCurrentNode = true; + trimAllowedNodes(mAllowedNodes, mPathFinder); + mObstacleCheck.clear(); + mPathFinder.clearPath(); + mWalking = false; + mMoveNow = true; } - - if(mWalkState == State_Evade) + else // probably walking into another NPC { - //std::cout << "Stuck \""<= COUNT_BEFORE_RESET) // something has gone wrong, reset - { - //std::cout << "Reset \""<= COUNT_BEFORE_RESET) // something has gone wrong, reset + { + //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; + mObstacleCheck.clear(); + + stopWalking(actor); + mMoveNow = false; + mWalking = false; + mChooseAction = true; + } +//#endif } - return false; + return false; // AiWander package not yet completed + } + + void AiWander::trimAllowedNodes(std::vector& nodes, + const PathFinder& pathfinder) + { +//#if 0 + std::cout << "allowed size "<< std::to_string(nodes.size()) << std::endl; +//#endif + // TODO: how to add these back in once the door opens? + std::list paths = pathfinder.getPath(); + while(paths.size() >= 2) + { + ESM::Pathgrid::Point pt = paths.back(); +#if 0 + std::cout << "looking for "<< + "pt "+std::to_string(pt.mX)+", "+std::to_string(pt.mY) + < #include "pathfinding.hpp" +#include "obstacle.hpp" #include "../mwworld/timestamp.hpp" @@ -26,7 +27,7 @@ namespace MWMechanics void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); - int mDistance; + int mDistance; // how far the actor can wander from the spawn point int mDuration; int mTimeOfDay; std::vector mIdle; @@ -34,35 +35,18 @@ namespace MWMechanics bool mSaidGreeting; - float mX; - float mY; - float mZ; - - // Cell location + // Cached current cell location int mCellX; int mCellY; // Cell location multiplied by ESM::Land::REAL_SIZE float mXCell; float mYCell; - // for checking if we're stuck (but don't check Z axis) - float mPrevX; - float mPrevY; - - enum WalkState - { - State_Norm, - State_CheckStuck, - State_Evade - }; - WalkState mWalkState; - - int mStuckCount; - float mStuckDuration; // accumulate time here while in same spot - float mEvadeDuration; - float mDistSameSpot; // take account of actor's speed + const MWWorld::CellStore* mCell; // for detecting cell change + // if false triggers calculating allowed nodes based on mDistance bool mStoredAvailableNodes; + // AiWander states bool mChooseAction; bool mIdleNow; bool mMoveNow; @@ -73,12 +57,21 @@ namespace MWMechanics MWWorld::TimeStamp mStartTime; + // allowed pathgrid nodes based on mDistance from the spawn point std::vector mAllowedNodes; ESM::Pathgrid::Point mCurrentNode; + bool mTrimCurrentNode; + void trimAllowedNodes(std::vector& nodes, + const PathFinder& pathfinder); PathFinder mPathFinder; - const ESM::Pathgrid *mPathgrid; + //const ESM::Pathgrid *mPathgrid; + ObstacleCheck mObstacleCheck; + float mDoorCheckDuration; + int mStuckCount; + + //float mReaction; }; } diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp new file mode 100644 index 0000000000..00f97ae01f --- /dev/null +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -0,0 +1,176 @@ +#include "obstacle.hpp" + +#include + +#include "../mwbase/world.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/cellstore.hpp" + +namespace MWMechanics +{ + // NOTE: determined empirically but probably need further tweaking + static const float DIST_SAME_SPOT = 1.8f; + static const float DURATION_SAME_SPOT = 1.0f; + static const float DURATION_TO_EVADE = 0.4f; + + // Proximity check function for interior doors. Given that most interior cells + // do not have many doors performance shouldn't be too much of an issue. + // + // Limitation: there can be false detections, and does not test whether the + // actor is facing the door. + bool proximityToDoor(const MWWorld::Ptr& actor, float minSqr, bool closed) + { + MWWorld::CellStore *cell = actor.getCell(); + + if(cell->getCell()->isExterior()) + return false; // check interior cells only + + // Check all the doors in this cell + MWWorld::CellRefList& doors = cell->get(); + MWWorld::CellRefList::List& refList = doors.mList; + MWWorld::CellRefList::List::iterator it = refList.begin(); + Ogre::Vector3 pos(actor.getRefData().getPosition().pos); + + // TODO: How to check whether the actor is facing a door? Below code is for + // the player, perhaps it can be adapted. + //MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getFacedObject(); + //if(!ptr.isEmpty()) + //std::cout << "faced door " << ptr.getClass().getName(ptr) << std::endl; + + // TODO: The in-game observation of rot[2] value seems to be the + // opposite of the code in World::activateDoor() ::confused:: + for (; it != refList.end(); ++it) + { + MWWorld::LiveCellRef& ref = *it; + if(pos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr && + ref.mData.getLocalRotation().rot[2] == (closed ? 0 : 1)) + { +//#if 0 + std::cout << "\""+actor.getClass().getName(actor)+"\" " + <<"next to door "+ref.mRef.mRefID + //+", enabled? "+std::to_string(ref.mData.isEnabled()) + +", dist "+std::to_string(sqrt(pos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)))) + << std::endl; +//#endif + return true; // found, stop searching + } + } + return false; // none found + } + + ObstacleCheck::ObstacleCheck(): + mPrevX(0) // to see if the moved since last time + , mPrevY(0) + , mDistSameSpot(-1) // avoid calculating it each time + , mWalkState(State_Norm) + , mStuckDuration(0) + , mEvadeDuration(0) + { + } + + void ObstacleCheck::clear() + { + mWalkState = State_Norm; + mStuckDuration = 0; + mEvadeDuration = 0; + } + + bool ObstacleCheck::isNormalState() const + { + return mWalkState == State_Norm; + } + + /* + * input - actor, duration (time since last check) + * output - true if evasive action needs to be taken + * + * Walking state transitions (player greeting check not shown): + * + * MoveNow <------------------------------------+ + * | d| + * | | + * +-> State_Norm <---> State_CheckStuck --> State_Evade + * ^ ^ | f ^ | t ^ | | + * | | | | | | | | + * | +---+ +---+ +---+ | u + * | any < t < u | + * +--------------------------------------------+ + * + * f = one reaction time + * d = proximity to a closed door + * t = how long before considered stuck + * u = how long to move sideways + * + * DIST_SAME_SPOT is calibrated for movement speed of around 150. + * A rat has walking speed of around 30, so we need to adjust for + * that. + */ + bool ObstacleCheck::check(const MWWorld::Ptr& actor, float duration) + { + const MWWorld::Class& cls = actor.getClass(); + ESM::Position pos = actor.getRefData().getPosition(); + + if(mDistSameSpot == -1) + mDistSameSpot = DIST_SAME_SPOT * (cls.getSpeed(actor) / 150); + + bool samePosition = (abs(pos.pos[0] - mPrevX) < mDistSameSpot) && + (abs(pos.pos[1] - mPrevY) < mDistSameSpot); + // update position + mPrevX = pos.pos[0]; + mPrevY = pos.pos[1]; + + switch(mWalkState) + { + case State_Norm: + { + if(!samePosition) + break; + else + mWalkState = State_CheckStuck; + } + /* FALL THROUGH */ + case State_CheckStuck: + { + if(!samePosition) + { + mWalkState = State_Norm; + mStuckDuration = 0; + break; + } + else + { + mStuckDuration += duration; + // consider stuck only if position unchanges for a period + if(mStuckDuration < DURATION_SAME_SPOT) + break; // still checking, note duration added to timer + else + { + mStuckDuration = 0; + mWalkState = State_Evade; + } + } + } + /* FALL THROUGH */ + case State_Evade: + { + mEvadeDuration += duration; + if(mEvadeDuration < DURATION_TO_EVADE) + return true; + else + { +//#if 0 + std::cout << "evade \""+actor.getClass().getName(actor)+"\" " + //<<"dist spot "+std::to_string(mDistSameSpot) + //+", speed "+std::to_string(cls.getSpeed(actor)) + << std::endl; +//#endif + // tried to evade, assume all is ok and start again + mWalkState = State_Norm; + mEvadeDuration = 0; + } + } + /* NO DEFAULT CASE */ + } + return false; // no obstacles to evade (yet) + } +} diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp new file mode 100644 index 0000000000..920e2e794c --- /dev/null +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -0,0 +1,52 @@ +#ifndef OPENMW_MECHANICS_OBSTACLE_H + +namespace MWWorld +{ + class Ptr; +} + +namespace MWMechanics +{ + // NOTE: determined empirically based on in-game behaviour + static const float MIN_DIST_TO_DOOR_SQUARED = 128*128; + + // tests actor's proximity to a closed door by default + bool proximityToDoor(const MWWorld::Ptr& actor, + float minSqr = MIN_DIST_TO_DOOR_SQUARED, + bool closed = true); + + class ObstacleCheck + { + public: + ObstacleCheck(); + + // Clear the timers and set the state machine to default + void clear(); + + bool isNormalState() const; + + // Returns true if there is an obstacle and an evasive action + // should be taken + bool check(const MWWorld::Ptr& actor, float duration); + + private: + + // for checking if we're stuck (ignoring Z axis) + float mPrevX; + float mPrevY; + + enum WalkState + { + State_Norm, + State_CheckStuck, + State_Evade + }; + WalkState mWalkState; + + float mStuckDuration; // accumulate time here while in same spot + float mEvadeDuration; + float mDistSameSpot; // take account of actor's speed + }; +} + +#endif From aad13d315c4a19d8dc55be4ca4f7f1483373ee18 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 18 Apr 2014 14:25:08 +1000 Subject: [PATCH 082/135] Fixed issue where allowed nodes were being erased. PathFinder was returning an empty path if the closest pathgrid point to the start was also the closest pathgrid point to the goal. Still need to clean up and remove logging statements. --- apps/openmw/mwmechanics/aiwander.cpp | 99 +++++++++++++++---------- apps/openmw/mwmechanics/pathfinding.cpp | 42 ++++++++++- apps/openmw/mwmechanics/pathgrid.cpp | 14 ++++ 3 files changed, 114 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 029be2fb8a..c25821b4e2 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -112,11 +112,13 @@ namespace MWMechanics * allowed set. The issue is when the door opens the allowed set is not * re-calculated. Normally this would not be an issue since hostile actors will * enter combat (i.e. no longer wandering) - * - * FIXME: Sometimes allowed nodes that shouldn't be deleted are deleted. */ bool AiWander::execute (const MWWorld::Ptr& actor,float duration) { + MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor); + if(cStats.isDead() || cStats.getHealth().getCurrent() <= 0) + return true; // Don't bother with dead actors + bool cellChange = mCell && (actor.getCell() != mCell); if(!mCell || cellChange) { @@ -125,7 +127,6 @@ namespace MWMechanics } const ESM::Cell *cell = mCell->getCell(); - MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor); cStats.setDrawState(DrawState_Nothing); cStats.setMovementFlag(CreatureStats::Flag_Run, false); MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -188,45 +189,56 @@ namespace MWMechanics mYCell = mCellY * ESM::Land::REAL_SIZE; } - // convert actorPos to local (i.e. cell) co-ordinates - Ogre::Vector3 actorPos(pos.pos); - actorPos[0] = actorPos[0] - mXCell; - actorPos[1] = actorPos[1] - mYCell; + // FIXME: There might be a bug here. The allowed node points are + // based on the actor's current position rather than the actor's + // spawn point. As a result the allowed nodes for wander can change + // between saves, for example. + // + // convert npcPos to local (i.e. cell) co-ordinates + Ogre::Vector3 npcPos(pos.pos); + npcPos[0] = npcPos[0] - mXCell; + npcPos[1] = npcPos[1] - mYCell; - // mAllowedNodes for this actor with pathgrid point indexes - // based on mDistance + // mAllowedNodes for this actor with pathgrid point indexes based on mDistance // NOTE: mPoints and mAllowedNodes are in local co-ordinates - float closestNodeDist = -1; - unsigned int closestIndex = 0; - unsigned int indexAllowedNodes = 0; for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) { - float sqrDist = actorPos.squaredDistance(Ogre::Vector3( - pathgrid->mPoints[counter].mX, - pathgrid->mPoints[counter].mY, - pathgrid->mPoints[counter].mZ)); - if(sqrDist <= (mDistance * mDistance)) - { + Ogre::Vector3 nodePos(pathgrid->mPoints[counter].mX, pathgrid->mPoints[counter].mY, + pathgrid->mPoints[counter].mZ); + if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance) mAllowedNodes.push_back(pathgrid->mPoints[counter]); - // keep track of the closest node - if(closestNodeDist == -1 || sqrDist < closestNodeDist) - { - closestNodeDist = sqrDist; - closestIndex = indexAllowedNodes; - } - indexAllowedNodes++; - } } if(!mAllowedNodes.empty()) { - // Start with the closest node and remove it from the allowed set - // so that it does not get selected again. The removed node will - // later be put in the back of the queue, unless it gets removed - // due to inaccessibility (e.g. a closed door) - mCurrentNode = mAllowedNodes[closestIndex]; - mAllowedNodes.erase(mAllowedNodes.begin() + closestIndex); - // set only if successful in finding allowed nodes - mStoredAvailableNodes = true; + Ogre::Vector3 firstNodePos(mAllowedNodes[0].mX, mAllowedNodes[0].mY, mAllowedNodes[0].mZ); + float closestNode = npcPos.squaredDistance(firstNodePos); + unsigned int index = 0; + for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) + { + Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY, + mAllowedNodes[counterThree].mZ); + float tempDist = npcPos.squaredDistance(nodePos); + if(tempDist < closestNode) + index = counterThree; + } +#if 0 + if(actor.getClass().getName(actor) == "Rat") + { + std::cout << "rat allowed "<< std::to_string(mAllowedNodes.size()) + +" mDist "+std::to_string(mDistance) + +" pos "+std::to_string(static_cast(npcPos[0])) + +", "+std::to_string(static_cast(npcPos[1])) + < 1) { mTrimCurrentNode = false; -#if 0 +//#if 0 std::cout << "deleted "<< std::to_string(mCurrentNode.mX) +", "+std::to_string(mCurrentNode.mY) << std::endl; -#endif +//#endif +//#if 0 + std::cout << "allowed size "<< + std::to_string(mAllowedNodes.size()) << std::endl; +//#endif } else mAllowedNodes.push_back(mCurrentNode); @@ -412,7 +428,15 @@ namespace MWMechanics } // Choose a different node and delete this one from possible nodes because it is uncreachable: else + { mAllowedNodes.erase(mAllowedNodes.begin() + randNode); +//#if 0 + //std::cout << "actor \""<< actor.getClass().getName(actor) << "\"" << std::endl; + if(actor.getClass().getName(actor) == "Rat") + std::cout << "erase no path "<< std::to_string(mAllowedNodes[randNode].mX) + +", "+std::to_string(mAllowedNodes[randNode].mY) << std::endl; +//#endif + } } } @@ -477,9 +501,6 @@ namespace MWMechanics void AiWander::trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder) { -//#if 0 - std::cout << "allowed size "<< std::to_string(nodes.size()) << std::endl; -//#endif // TODO: how to add these back in once the door opens? std::list paths = pathfinder.getPath(); while(paths.size() >= 2) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 730b8cb923..3a67a9b720 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -100,8 +100,11 @@ namespace } } } - if(start == closestReachableIndex) - closestReachableIndex = -1; // couldn't find anyting other than start + // AiWander has logic that depends on whether a path was created, deleting + // allowed nodes if not. Hence a path needs to be created even if the start + // and the end points are the same. + //if(start == closestReachableIndex) + //closestReachableIndex = -1; // couldn't find anyting other than start return std::pair (closestReachableIndex, closestReachableIndex == closestIndex); @@ -224,6 +227,18 @@ namespace MWMechanics // this shouldn't really happen, but just in case if(endNode.first != -1) { + // AiWander has logic that depends on whether a path was created, + // deleting allowed nodes if not. Hence a path needs to be created + // even if the start and the end points are the same. + // NOTE: aStarSearch will return an empty path if the start and end + // nodes are the same + if(startNode == endNode.first) + { + mPath.push_back(endPoint); + mIsPathConstructed = true; + return; + } + mPath = mCell->aStarSearch(startNode, endNode.first); if(!mPath.empty()) @@ -243,9 +258,32 @@ namespace MWMechanics } else mIsPathConstructed = false; +#if 0 + { + mIsPathConstructed = false; + std::cout << "no path "<< + std::to_string(startPoint.mX-xCell) + +", "+std::to_string(startPoint.mY-yCell) + +", "+std::to_string(startNode) + +", "+std::to_string(endPoint.mX-xCell) + +", "+std::to_string(endPoint.mY-yCell) + +", "+std::to_string(endNode.first)<mName == "Gnisis, Arvs-Drelen") + for(unsigned int v = 0; v < mPathgrid->mPoints.size(); v++) + { + std::cout << "SCC \"X:" << + std::to_string(mPathgrid->mPoints[v].mX) + +", Y:"+std::to_string(mPathgrid->mPoints[v].mY) + +", Num:"+std::to_string(mSCCId) + +", Point:"+std::to_string(v) + +", Group:"+std::to_string(mGraph[v].componentId) + <<"\""< Date: Fri, 18 Apr 2014 14:35:27 +1000 Subject: [PATCH 083/135] Cleaned up logging statements. --- apps/openmw/mwmechanics/aiwander.cpp | 45 ------------------------- apps/openmw/mwmechanics/pathfinding.cpp | 23 ------------- apps/openmw/mwmechanics/pathgrid.cpp | 14 -------- 3 files changed, 82 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index c25821b4e2..276d1fac6b 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -221,21 +221,7 @@ namespace MWMechanics if(tempDist < closestNode) index = counterThree; } -#if 0 - if(actor.getClass().getName(actor) == "Rat") - { - std::cout << "rat allowed "<< std::to_string(mAllowedNodes.size()) - +" mDist "+std::to_string(mDistance) - +" pos "+std::to_string(static_cast(npcPos[0])) - +", "+std::to_string(static_cast(npcPos[1])) - < 1) - { mTrimCurrentNode = false; -//#if 0 - std::cout << "deleted "<< std::to_string(mCurrentNode.mX) - +", "+std::to_string(mCurrentNode.mY) << std::endl; -//#endif -//#if 0 - std::cout << "allowed size "<< - std::to_string(mAllowedNodes.size()) << std::endl; -//#endif - } else mAllowedNodes.push_back(mCurrentNode); mCurrentNode = temp; @@ -428,15 +401,7 @@ namespace MWMechanics } // Choose a different node and delete this one from possible nodes because it is uncreachable: else - { mAllowedNodes.erase(mAllowedNodes.begin() + randNode); -//#if 0 - //std::cout << "actor \""<< actor.getClass().getName(actor) << "\"" << std::endl; - if(actor.getClass().getName(actor) == "Rat") - std::cout << "erase no path "<< std::to_string(mAllowedNodes[randNode].mX) - +", "+std::to_string(mAllowedNodes[randNode].mY) << std::endl; -//#endif - } } } @@ -506,11 +471,6 @@ namespace MWMechanics while(paths.size() >= 2) { ESM::Pathgrid::Point pt = paths.back(); -#if 0 - std::cout << "looking for "<< - "pt "+std::to_string(pt.mX)+", "+std::to_string(pt.mY) - <mName == "Gnisis, Arvs-Drelen") - for(unsigned int v = 0; v < mPathgrid->mPoints.size(); v++) - { - std::cout << "SCC \"X:" << - std::to_string(mPathgrid->mPoints[v].mX) - +", Y:"+std::to_string(mPathgrid->mPoints[v].mY) - +", Num:"+std::to_string(mSCCId) - +", Point:"+std::to_string(v) - +", Group:"+std::to_string(mGraph[v].componentId) - <<"\""< Date: Fri, 18 Apr 2014 15:19:22 +1000 Subject: [PATCH 084/135] More cleaning up. --- apps/openmw/mwmechanics/aiwander.cpp | 16 ++++++++-------- apps/openmw/mwmechanics/aiwander.hpp | 3 --- apps/openmw/mwmechanics/obstacle.cpp | 15 --------------- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 4 files changed, 9 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 276d1fac6b..260aa72cf4 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -47,7 +47,6 @@ namespace MWMechanics mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); mPlayedIdle = 0; - //mPathgrid = NULL; mIdleChanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get().find("fIdleChanceMultiplier")->getFloat(); @@ -104,14 +103,15 @@ namespace MWMechanics * TODO: non-time critical operations should be run once every 250ms or so. * * TODO: It would be great if door opening/closing can be detected and pathgrid - * links dynamically updated. Currently (0.29.0) AiWander allows destination - * beyond closed doors which sometimes makes the actors stuck at the door and - * impossible for the player to open the door. + * links dynamically updated. Currently (0.29.0) AiWander allows choosing a + * destination beyond closed doors which sometimes makes the actors stuck at the + * door and impossible for the player to open the door. * * For now detect being stuck at the door and simply delete the nodes from the * allowed set. The issue is when the door opens the allowed set is not - * re-calculated. Normally this would not be an issue since hostile actors will - * enter combat (i.e. no longer wandering) + * re-calculated. However this would not be an issue in most cases since hostile + * actors will enter combat (i.e. no longer wandering) and different pathfinding + * will kick in. */ bool AiWander::execute (const MWWorld::Ptr& actor,float duration) { @@ -473,8 +473,8 @@ namespace MWMechanics ESM::Pathgrid::Point pt = paths.back(); for(int j = 0; j < nodes.size(); j++) { - // NOTE: doesn't hadle a door with the same X/Y - // coordinates but with a different Z + // FIXME: doesn't hadle a door with the same X/Y + // co-ordinates but with a different Z if(nodes[j].mX == pt.mX && nodes[j].mY == pt.mY) { nodes.erase(nodes.begin() + j); diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 8fe35e53d1..59c21de895 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -65,13 +65,10 @@ namespace MWMechanics const PathFinder& pathfinder); PathFinder mPathFinder; - //const ESM::Pathgrid *mPathgrid; ObstacleCheck mObstacleCheck; float mDoorCheckDuration; int mStuckCount; - - //float mReaction; }; } diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 00f97ae01f..9a748c0523 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -44,16 +44,7 @@ namespace MWMechanics MWWorld::LiveCellRef& ref = *it; if(pos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr && ref.mData.getLocalRotation().rot[2] == (closed ? 0 : 1)) - { -//#if 0 - std::cout << "\""+actor.getClass().getName(actor)+"\" " - <<"next to door "+ref.mRef.mRefID - //+", enabled? "+std::to_string(ref.mData.isEnabled()) - +", dist "+std::to_string(sqrt(pos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)))) - << std::endl; -//#endif return true; // found, stop searching - } } return false; // none found } @@ -158,12 +149,6 @@ namespace MWMechanics return true; else { -//#if 0 - std::cout << "evade \""+actor.getClass().getName(actor)+"\" " - //<<"dist spot "+std::to_string(mDistSameSpot) - //+", speed "+std::to_string(cls.getSpeed(actor)) - << std::endl; -//#endif // tried to evade, assume all is ok and start again mWalkState = State_Norm; mEvadeDuration = 0; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 1395535566..11d436700c 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -231,7 +231,7 @@ namespace MWMechanics // deleting allowed nodes if not. Hence a path needs to be created // even if the start and the end points are the same. // NOTE: aStarSearch will return an empty path if the start and end - // nodes are the same + // nodes are the same if(startNode == endNode.first) { mPath.push_back(endPoint); From a0fc514df78d605b9b4db6d89eb8fb0f9115ebb0 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 18 Apr 2014 15:45:39 +1000 Subject: [PATCH 085/135] Suppress compiler warning. --- apps/openmw/mwmechanics/aiwander.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 260aa72cf4..988da4eb4e 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -471,7 +471,7 @@ namespace MWMechanics while(paths.size() >= 2) { ESM::Pathgrid::Point pt = paths.back(); - for(int j = 0; j < nodes.size(); j++) + for(unsigned int j = 0; j < nodes.size(); j++) { // FIXME: doesn't hadle a door with the same X/Y // co-ordinates but with a different Z From 7437647f70fad054793b0bf649a4b1484cf0d0c9 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 18 Apr 2014 16:45:31 +1000 Subject: [PATCH 086/135] Forgot to add a guard :-( --- apps/openmw/mwmechanics/obstacle.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 920e2e794c..12030b2bee 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -1,4 +1,5 @@ #ifndef OPENMW_MECHANICS_OBSTACLE_H +#define OPENMW_MECHANICS_OBSTACLE_H namespace MWWorld { From 4625adfb7fdc85d5f83d9f1adf8c0ad00762daa6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 18 Apr 2014 11:43:37 +0200 Subject: [PATCH 087/135] Fix typo in a condition. --- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 7f5a7fac55..6ea3f54729 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -714,7 +714,7 @@ namespace MWMechanics effect.mDuration = 1; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { - if (!magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) magnitude = int((0.05 * y) / (0.1 * magicEffect->mData.mBaseCost)); else magnitude = int(y / (0.1 * magicEffect->mData.mBaseCost)); From d3d0b7362f1d4b0c1945116e88e041b2e8b92ad4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 18 Apr 2014 12:34:12 +0200 Subject: [PATCH 088/135] Fix older savegame compatibility regarding new crime feature. --- components/esm/player.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/esm/player.cpp b/components/esm/player.cpp index e41cc535e5..7a2c03b488 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -26,8 +26,10 @@ void ESM::Player::load (ESMReader &esm) mBirthsign = esm.getHNString ("SIGN"); - esm.getHNT (mCurrentCrimeId, "CURD"); - esm.getHNT (mPayedCrimeId, "PAYD"); + mCurrentCrimeId = 0; + esm.getHNOT (mCurrentCrimeId, "CURD"); + mPayedCrimeId = 0; + esm.getHNOT (mPayedCrimeId, "PAYD"); } void ESM::Player::save (ESMWriter &esm) const @@ -51,4 +53,4 @@ void ESM::Player::save (ESMWriter &esm) const esm.writeHNT ("CURD", mCurrentCrimeId); esm.writeHNT ("PAYD", mPayedCrimeId); -} \ No newline at end of file +} From b3916e77448eb65dd54434113cf5f55491edc83a Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 18 Apr 2014 13:44:09 +0200 Subject: [PATCH 089/135] Crime: mark witnesses as alarmed. Fixes guard dialogue to properly detect if the player turned himself in. --- apps/openmw/mwmechanics/actors.cpp | 1 + apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index f7478e22c3..7b83ff1f1d 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -763,6 +763,7 @@ namespace MWMechanics // TODO: Not a complete list, disposition changes? creatureStats.setHostile(false); creatureStats.setAttacked(false); + creatureStats.setAlarmed(false); // Update witness crime id npcStats.setCrimeId(-1); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 3a26ea7dc5..a44d1a2baa 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -866,6 +866,9 @@ namespace MWMechanics { it1->getClass().getNpcStats(*it1).setCrimeId(id); } + + // Mark as Alarmed for dialogue + it1->getClass().getCreatureStats(*it1).setAlarmed(true); } break; // Someone saw the crime and everyone has been told } From a3dffd5d57d272e95ad6875e8c3fccb2195db7d9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 18 Apr 2014 14:13:29 +0200 Subject: [PATCH 090/135] Add some missing SDL to MyGUI key translations. Most importantly to allow numpad enter as alternative to return key. --- extern/sdl4ogre/sdlinputwrapper.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index d48e43c010..c3ea2fd746 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -443,5 +443,10 @@ namespace SFO mKeyMap.insert( KeyMap::value_type(SDLK_PAGEDOWN, OIS::KC_PGDOWN) ); mKeyMap.insert( KeyMap::value_type(SDLK_INSERT, OIS::KC_INSERT) ); mKeyMap.insert( KeyMap::value_type(SDLK_DELETE, OIS::KC_DELETE) ); + mKeyMap.insert( KeyMap::value_type(SDLK_KP_ENTER, OIS::KC_NUMPADENTER) ); + mKeyMap.insert( KeyMap::value_type(SDLK_RCTRL, OIS::KC_RCONTROL) ); + mKeyMap.insert( KeyMap::value_type(SDLK_LGUI, OIS::KC_LWIN) ); + mKeyMap.insert( KeyMap::value_type(SDLK_RGUI, OIS::KC_RWIN) ); + mKeyMap.insert( KeyMap::value_type(SDLK_APPLICATION, OIS::KC_APPS) ); } } From 479a94b35df7f825a50ae2ede32a7c291d1840b2 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 19 Apr 2014 08:16:56 +1000 Subject: [PATCH 091/135] Backing off closed doors working, needs cleanup and tweaking. --- apps/openmw/mwmechanics/aicombat.cpp | 209 ++++++++++++++++++++++++++- apps/openmw/mwmechanics/aicombat.hpp | 10 ++ apps/openmw/mwmechanics/aiwander.cpp | 7 +- apps/openmw/mwmechanics/obstacle.cpp | 9 +- apps/openmw/mwmechanics/pathgrid.cpp | 6 +- 5 files changed, 231 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index fd2fa61ba0..e948993598 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -35,6 +35,9 @@ namespace namespace MWMechanics { + static const float DOOR_CHECK_INTERVAL = 1.5f; + // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp + AiCombat::AiCombat(const MWWorld::Ptr& actor) : mTarget(actor), mTimerAttack(0), @@ -44,12 +47,104 @@ namespace MWMechanics mReadyToAttack(false), mStrike(false), mCombatMove(false), + mBackOffDoor(false), mRotate(false), mMovement(), + mDoorIter(actor.getCell()->get().mList.end()), + mDoors(actor.getCell()->get().mList), + mDoorCheckDuration(0), mTargetAngle(0) { } + /* + * The 'pursuit' part of AiCombat now has some memory to allow backtracking. + * The intention is to allow actors to detect being 'stuck' somewhere, whether + * in a river or facing a door, to go back to a known 'good' position. + * + * Each actor goes through these states of movement once alerted and an AiCombat + * package is queued (FIXME: below is a DRAFT proposal only): + * + * - Maybe remember the starting position so that the actor can return if + * lose sight of the target? Last Known Target Location Tracking - keep track + * of target positions every sec, paired with actor's location at the time + * + * - Get to the target if far away (need path finding), how close depends on + * the current best (selected?) weapon. As the actor moves, some breadcrumb + * is left (e.g. every second store a position in a FIFO) so that the actor + * can back track if necessary. + * + * - If the actor gets stuck (need a way of detecting this) then decide what + * to do next. For example, if next to a door then maybe back track one or + * two positions and check LOS of the target every x frames (reaction time?). + * Or maybe back track then look for a new path. + * + * - Once in weapon range, may need a strategy to get to the target for a + * strike (maybe there are others nearby attacking the same target). + * + * + * Current AiCombat movement states (as of 0.29.0), ignoring the details of the + * attack states such as CombatMove, Strike and ReadyToAttack: + * + * +-----(within strike range)----->attack--(beyond strike range)-->follow + * | | ^ | | + * | | | | | + * pursue<----(beyond follow range)-----+ +----(within strike range)---+ | + * ^ | + * | | + * +--------------------------(beyond follow range)--------------------+ + * + * + * Below diagram is high level only, FIXME: code detail may be different: + * + * +-----------(same)-------------->attack---------(same)---------->follow + * | |^^ | | + * | ||| | | + * | +--(same)-----------------+|+----------(same)------------+ | + * | | | in range | + * | | too far | | + * | | +---------------got hit or LOS<---+ | + * | <----+ | | | + * pursue<---------+ door | | + * ^^ <--> maybe stuck, check --------------> back up and wait | + * || | ^ | | ^ | | + * || | | | stuck | | | waited | + * || +---+ | +---+ | too long | + * || backtrack | | | + * |+----------------------+ go back? <-----------+ | + * | | + * +----------------------------(same)---------------------------------+ + * + * FIXME: + * + * The new scheme is way too complicated, should really be implemented as a + * proper state machine. + * + * TODO: + * + * Use the Observer Pattern to co-ordinate attacks, provide intelligence on + * whether the target was hit, etc. + * + * TODO: + * + * Auto-generate large cubes or squares as a poor-man's navmesh? Many + * external cells do not have any pathgrids, and certainly none for flying + * or swimming. + * + * + * Called from: (check Doxygen as this comment could be out of date) + * + * OMW::Engine::go() | + * Ogre::Root::renderOneFrame() | + * Ogre::Root::... | ... detail omitted + * Ogre::Root::fireFrameRenderingQueued() | + * OMW::Engine::frameRenderingQueued() | virtual Ogre::FrameListener + * MWMechanics::MechanicsManager::update() | + * MWMechanics::Actors::update() | + * MWMechanics::Actors::updateActor() | + * MWMechanics::AiSequence::execute() | from priority queue mPackages + * MWMechanics::AiCombat::execute() | virtual MWMechanics::AiPackage + */ bool AiCombat::execute (const MWWorld::Ptr& actor,float duration) { //General description @@ -168,6 +263,43 @@ namespace MWMechanics ESM::Position pos = actor.getRefData().getPosition(); + /* + * Some notes on meanings of variables: + * + * rangeMelee: + * + * - Distance where attack using the actor's weapon is possible + * - longer for ranged weapons (obviously?) vs. melee weapons + * - Once within this distance mFollowTarget is triggered + * (TODO: check whether the follow logic still works for ranged + * weapons, since rangeCloseup is set to zero) + * - TODO: The variable name is confusing. It was ok when AiCombat only + * had melee weapons but now that ranged weapons are supported that is + * no longer the case. It should really be renamed to something + * like rangeStrike - alternatively, keep this name for melee + * weapons and use a different variable for tracking ranged weapon + * distance (rangeRanged maybe?) + * + * rangeCloseup: + * + * - Applies to melee weapons or hand to hand only (or creatures without + * weapons) + * - Distance a little further away from the actor's weapon strike + * i.e. rangeCloseup > rangeMelee for melee weapons + * (the variable names make this simple concept counter-intuitive, + * something like rangeMelee > rangeStrike may be better) + * - Once the target gets beyond this distance mFollowTarget is cleared + * and a path to the target needs to be found + * - TODO: Possibly rename this variable to rangeMelee or even rangeFollow + * + * mFollowTarget: + * + * - Once triggered, the actor follows the target with LOS shortcut + * (the shortcut really only applies to cells where pathgrids are + * available, since the default path without pathgrids is direct to + * target even if LOS is not achieved) + * + */ float rangeMelee; float rangeCloseUp; bool distantCombat = false; @@ -189,6 +321,7 @@ namespace MWMechanics Ogre::Vector3 vDir = vDest - vStart; float distBetween = vDir.length(); + // (within strike dist) || (not quite strike dist while following) if(distBetween < rangeMelee || (distBetween <= rangeCloseUp && mFollowTarget) ) { //Melee and Close-up combat @@ -198,12 +331,13 @@ namespace MWMechanics mRotate = true; //bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget); + // (not quite strike dist while following) if (mFollowTarget && distBetween > rangeMelee) { //Close-up combat: just run up on target mMovement.mPosition[1] = 1; } - else + else // (within strike dist) { //Melee: stop running and attack mMovement.mPosition[1] = 0; @@ -240,7 +374,7 @@ namespace MWMechanics } else { - //target is at far distance: build path to target OR follow target (if previously actor had reached it once) + //target is at far distance: build path to target mFollowTarget = false; buildNewPath(actor); //may fail to build a path, check before use @@ -261,6 +395,55 @@ namespace MWMechanics mMovement.mPosition[1] = 1; mReadyToAttack = false; + + // remember that this section gets updated every tReaction, which is + // currently hard coded at 250ms or 1/4 second + if(mObstacleCheck.check(actor, tReaction)) // true if evasive action needed + { + std::cout<<"found obstacle"<getCell()->isExterior()) + { + // Check all the doors in this cell + mDoors = cell->get().mList; // update list member + mDoorIter = mDoors.begin(); + Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); + for (; mDoorIter != mDoors.end(); ++mDoorIter) + { + MWWorld::LiveCellRef& ref = *mDoorIter; + if(actorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < 1.3*1.3*MIN_DIST_TO_DOOR_SQUARED && + ref.mData.getLocalRotation().rot[2] == 0) + { + std::cout<<"closed door id \""< rangeMelee) @@ -293,6 +476,28 @@ namespace MWMechanics } } + MWWorld::LiveCellRef& ref = *mDoorIter; + Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); + if(mBackOffDoor && + actorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < 1.5*1.5*MIN_DIST_TO_DOOR_SQUARED) + { + mMovement.mPosition[1] = -0.2; // back off, but slowly + if(mDoorIter != mDoors.end() && ref.mData.getLocalRotation().rot[2] >= 1) // open + { + mDoorIter = mDoors.end(); + mBackOffDoor = false; + std::cout<<"open door id \""<::List::iterator mDoorIter; + MWWorld::CellRefList::List& mDoors; + void buildNewPath(const MWWorld::Ptr& actor); }; } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 988da4eb4e..5d165488c6 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -83,8 +83,7 @@ namespace MWMechanics * * New high level states. Not exactly as per vanilla (e.g. door stuff) * but the differences are required because our physics does not work like - * vanilla and therefore have to compensate/work around. Note also many of - * the actions now have reaction times. + * vanilla and therefore have to compensate/work around. * * [select node, [if stuck evade * build path] or remove nodes if near door] @@ -467,6 +466,10 @@ namespace MWMechanics const PathFinder& pathfinder) { // TODO: how to add these back in once the door opens? + // Idea: keep a list of detected closed doors (see aicombat.cpp) + // Every now and then check whether one of the doors is opened. (maybe + // at the end of playing idle?) If the door is opened then re-calculate + // allowed nodes starting from the spawn point. std::list paths = pathfinder.getPath(); while(paths.size() >= 2) { diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 9a748c0523..6694de0964 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -42,9 +42,12 @@ namespace MWMechanics for (; it != refList.end(); ++it) { MWWorld::LiveCellRef& ref = *it; - if(pos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr && - ref.mData.getLocalRotation().rot[2] == (closed ? 0 : 1)) - return true; // found, stop searching + if(pos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr) + if(closed && ref.mData.getLocalRotation().rot[2] == 0 || + !closed && ref.mData.getLocalRotation().rot[2] >= 1) + { + return true; // found, stop searching + } } return false; // none found } diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index ec647c1cb3..cb9f051e32 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -197,11 +197,11 @@ namespace MWMechanics // both of these are set to zero in the constructor //mSCCId = 0; // how many strongly connected components in this cell //mSCCIndex = 0; - int pointsSize = mPathgrid->mPoints.size(); + int pointsSize = static_cast (mPathgrid->mPoints.size()); mSCCPoint.resize(pointsSize, std::pair (-1, -1)); mSCCStack.reserve(pointsSize); - for(int v = 0; v < static_cast (pointsSize); v++) + for(int v = 0; v < pointsSize; v++) { if(mSCCPoint[v].first == -1) // undefined (haven't visited) recursiveStrongConnect(v); @@ -249,7 +249,7 @@ namespace MWMechanics return path; // there is no path, return an empty path } - int graphSize = mGraph.size(); + int graphSize = static_cast (mGraph.size()); std::vector gScore (graphSize, -1); std::vector fScore (graphSize, -1); std::vector graphParent (graphSize, -1); From 3dfd08cf2de36dd081c25a75b1c637e9ede987ea Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 20 Apr 2014 08:31:02 +1000 Subject: [PATCH 092/135] Cleanup and little tweaking. --- apps/openmw/mwmechanics/aicombat.cpp | 235 ++++++++++++--------------- apps/openmw/mwmechanics/aicombat.hpp | 5 +- apps/openmw/mwmechanics/pathgrid.hpp | 2 + apps/openmw/mwworld/cellstore.hpp | 2 +- 4 files changed, 107 insertions(+), 137 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index e948993598..335bc87025 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -35,7 +35,7 @@ namespace namespace MWMechanics { - static const float DOOR_CHECK_INTERVAL = 1.5f; + static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp AiCombat::AiCombat(const MWWorld::Ptr& actor) : @@ -50,70 +50,49 @@ namespace MWMechanics mBackOffDoor(false), mRotate(false), mMovement(), + mCell(NULL), mDoorIter(actor.getCell()->get().mList.end()), - mDoors(actor.getCell()->get().mList), + mDoors(actor.getCell()->get()), mDoorCheckDuration(0), mTargetAngle(0) { } /* - * The 'pursuit' part of AiCombat now has some memory to allow backtracking. - * The intention is to allow actors to detect being 'stuck' somewhere, whether - * in a river or facing a door, to go back to a known 'good' position. - * - * Each actor goes through these states of movement once alerted and an AiCombat - * package is queued (FIXME: below is a DRAFT proposal only): - * - * - Maybe remember the starting position so that the actor can return if - * lose sight of the target? Last Known Target Location Tracking - keep track - * of target positions every sec, paired with actor's location at the time - * - * - Get to the target if far away (need path finding), how close depends on - * the current best (selected?) weapon. As the actor moves, some breadcrumb - * is left (e.g. every second store a position in a FIFO) so that the actor - * can back track if necessary. - * - * - If the actor gets stuck (need a way of detecting this) then decide what - * to do next. For example, if next to a door then maybe back track one or - * two positions and check LOS of the target every x frames (reaction time?). - * Or maybe back track then look for a new path. - * - * - Once in weapon range, may need a strategy to get to the target for a - * strike (maybe there are others nearby attacking the same target). - * - * * Current AiCombat movement states (as of 0.29.0), ignoring the details of the * attack states such as CombatMove, Strike and ReadyToAttack: * - * +-----(within strike range)----->attack--(beyond strike range)-->follow - * | | ^ | | - * | | | | | - * pursue<----(beyond follow range)-----+ +----(within strike range)---+ | - * ^ | - * | | - * +--------------------------(beyond follow range)--------------------+ + * +----(within strike range)----->attack--(beyond strike range)-->follow + * | | ^ | | + * | | | | | + * pursue<---(beyond follow range)-----+ +----(within strike range)---+ | + * ^ | + * | | + * +-------------------------(beyond follow range)--------------------+ * * - * Below diagram is high level only, FIXME: code detail may be different: + * Below diagram is high level only, the code detail is a little different + * (but including those detail will just complicate the diagram w/o adding much) * - * +-----------(same)-------------->attack---------(same)---------->follow - * | |^^ | | - * | ||| | | - * | +--(same)-----------------+|+----------(same)------------+ | - * | | | in range | - * | | too far | | - * | | +---------------got hit or LOS<---+ | - * | <----+ | | | - * pursue<---------+ door | | - * ^^ <--> maybe stuck, check --------------> back up and wait | - * || | ^ | | ^ | | - * || | | | stuck | | | waited | - * || +---+ | +---+ | too long | - * || backtrack | | | - * |+----------------------+ go back? <-----------+ | - * | | - * +----------------------------(same)---------------------------------+ + * +----------(same)-------------->attack---------(same)---------->follow + * | |^^ ||| + * | ||| ||| + * | +--(same)-----------------+|+----------(same)------------+|| + * | | | || + * | | | (in range) || + * | <---+ (too far) | || + * pursue<-------------------------[door open]<-----+ || + * ^^^ | || + * ||| | || + * ||+----------evade-----+ | || + * || | [closed door] | || + * |+----> maybe stuck, check --------------> back up, check door || + * | ^ | ^ | ^ || + * | | | | | | || + * | | +---+ +---+ || + * | +-------------------------------------------------------+| + * | | + * +---------------------------(same)---------------------------------+ * * FIXME: * @@ -124,26 +103,6 @@ namespace MWMechanics * * Use the Observer Pattern to co-ordinate attacks, provide intelligence on * whether the target was hit, etc. - * - * TODO: - * - * Auto-generate large cubes or squares as a poor-man's navmesh? Many - * external cells do not have any pathgrids, and certainly none for flying - * or swimming. - * - * - * Called from: (check Doxygen as this comment could be out of date) - * - * OMW::Engine::go() | - * Ogre::Root::renderOneFrame() | - * Ogre::Root::... | ... detail omitted - * Ogre::Root::fireFrameRenderingQueued() | - * OMW::Engine::frameRenderingQueued() | virtual Ogre::FrameListener - * MWMechanics::MechanicsManager::update() | - * MWMechanics::Actors::update() | - * MWMechanics::Actors::updateActor() | - * MWMechanics::AiSequence::execute() | from priority queue mPackages - * MWMechanics::AiCombat::execute() | virtual MWMechanics::AiPackage */ bool AiCombat::execute (const MWWorld::Ptr& actor,float duration) { @@ -175,7 +134,6 @@ namespace MWMechanics mRotate = false; } - mTimerAttack -= duration; actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike); @@ -190,6 +148,12 @@ namespace MWMechanics mTimerReact = 0; + bool cellChange = mCell && (actor.getCell() != mCell); + if(!mCell || cellChange) + { + mCell = actor.getCell(); + } + //actual attacking logic //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f float attackPeriod = 1.0f; @@ -298,7 +262,6 @@ namespace MWMechanics * (the shortcut really only applies to cells where pathgrids are * available, since the default path without pathgrids is direct to * target even if LOS is not achieved) - * */ float rangeMelee; float rangeCloseUp; @@ -395,55 +358,6 @@ namespace MWMechanics mMovement.mPosition[1] = 1; mReadyToAttack = false; - - // remember that this section gets updated every tReaction, which is - // currently hard coded at 250ms or 1/4 second - if(mObstacleCheck.check(actor, tReaction)) // true if evasive action needed - { - std::cout<<"found obstacle"<getCell()->isExterior()) - { - // Check all the doors in this cell - mDoors = cell->get().mList; // update list member - mDoorIter = mDoors.begin(); - Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); - for (; mDoorIter != mDoors.end(); ++mDoorIter) - { - MWWorld::LiveCellRef& ref = *mDoorIter; - if(actorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < 1.3*1.3*MIN_DIST_TO_DOOR_SQUARED && - ref.mData.getLocalRotation().rot[2] == 0) - { - std::cout<<"closed door id \""< rangeMelee) @@ -476,26 +390,77 @@ namespace MWMechanics } } + // NOTE: This section gets updated every tReaction, which is currently hard + // coded at 250ms or 1/4 second + // + // TODO: Add a parameter to vary DURATION_SAME_SPOT? + if((distBetween > rangeMelee || mFollowTarget) && + mObstacleCheck.check(actor, tReaction)) // check if evasive action needed + { + // first check if we're walking into a door + mDoorCheckDuration += 1.0f; // add time taken for obstacle check + MWWorld::CellStore *cell = actor.getCell(); + if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL && !cell->getCell()->isExterior()) + { + mDoorCheckDuration = 0; + // Check all the doors in this cell + mDoors = cell->get(); // update + mDoorIter = mDoors.mList.begin(); + Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); + for (; mDoorIter != mDoors.mList.end(); ++mDoorIter) + { + MWWorld::LiveCellRef& ref = *mDoorIter; + float minSqr = 1.3*1.3*MIN_DIST_TO_DOOR_SQUARED; // for legibility + if(actorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr && + ref.mData.getLocalRotation().rot[2] < 0.4f) // even small opening + { + //std::cout<<"closed door id \""<& ref = *mDoorIter; Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); + float minSqr = 1.6 * 1.6 * MIN_DIST_TO_DOOR_SQUARED; // for legibility + // TODO: add reaction to checking open doors if(mBackOffDoor && - actorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < 1.5*1.5*MIN_DIST_TO_DOOR_SQUARED) + actorPos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr) { mMovement.mPosition[1] = -0.2; // back off, but slowly - if(mDoorIter != mDoors.end() && ref.mData.getLocalRotation().rot[2] >= 1) // open - { - mDoorIter = mDoors.end(); - mBackOffDoor = false; - std::cout<<"open door id \""<= 1) + { + mDoorIter = mDoors.mList.end(); + mBackOffDoor = false; + //std::cout<<"open door id \""<::List::iterator mDoorIter; - MWWorld::CellRefList::List& mDoors; + MWWorld::CellRefList& mDoors; void buildNewPath(const MWWorld::Ptr& actor); }; diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index ac545efbcb..5d01dca009 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -30,6 +30,8 @@ namespace MWMechanics // the input parameters are pathgrid point indexes // the output list is in local (internal cells) or world (external // cells) co-ordinates + // + // NOTE: if start equals end an empty path is returned std::list aStarSearch(const int start, const int end) const; private: diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index b970afe1be..88b49ed1c0 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -8,7 +8,7 @@ #include "esmstore.hpp" #include "cellreflist.hpp" -#include "../mwmechanics/pathgrid.hpp" +#include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld namespace ESM { From 9bd31b62365a17341ddae54d2ca8af2d2e300125 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 20 Apr 2014 10:06:03 +1000 Subject: [PATCH 093/135] Added reaction time and moved game setting variables init to the constructor. --- apps/openmw/mwmechanics/aiwander.cpp | 173 +++++++++++++++------------ apps/openmw/mwmechanics/aiwander.hpp | 5 + 2 files changed, 101 insertions(+), 77 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 5d165488c6..12d86a5dc3 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -30,6 +30,10 @@ namespace MWMechanics , mStuckCount(0) // TODO: maybe no longer needed , mDoorCheckDuration(0) , mTrimCurrentNode(false) + , mReaction(0) + , mGreetDistanceMultiplier(0) + , mGreetDistanceReset(0) + , mChance(0) , mSaidGreeting(false) { for(unsigned short counter = 0; counter < mIdle.size(); counter++) @@ -47,8 +51,15 @@ namespace MWMechanics mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); mPlayedIdle = 0; + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); mIdleChanceMultiplier = - MWBase::Environment::get().getWorld()->getStore().get().find("fIdleChanceMultiplier")->getFloat(); + store.get().find("fIdleChanceMultiplier")->getFloat(); + + mGreetDistanceMultiplier = + store.get().find("iGreetDistanceMultiplier")->getInt(); + mGreetDistanceReset = + store.get().find("fGreetDistanceReset")->getFloat(); + mChance = store.get().find("fVoiceIdleOdds")->getFloat(); mStoredAvailableNodes = false; mChooseAction = true; @@ -99,7 +110,7 @@ namespace MWMechanics * | player) | * +----------------------------------+ * - * TODO: non-time critical operations should be run once every 250ms or so. + * NOTE: non-time critical operations are run once every 250ms or so. * * TODO: It would be great if door opening/closing can be detected and pathgrid * links dynamically updated. Currently (0.29.0) AiWander allows choosing a @@ -128,6 +139,83 @@ namespace MWMechanics cStats.setDrawState(DrawState_Nothing); cStats.setMovementFlag(CreatureStats::Flag_Run, false); + + ESM::Position pos = actor.getRefData().getPosition(); + + // Check if an idle actor is too close to a door - if so start walking + mDoorCheckDuration += duration; + if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL) + { + mDoorCheckDuration = 0; // restart timer + if(mDistance && // actor is not intended to be stationary + mIdleNow && // but is in idle + !mWalking && // FIXME: some actors are idle while walking + proximityToDoor(actor)) // NOTE: checks interior cells only + { + mIdleNow = false; + mMoveNow = true; + mTrimCurrentNode = false; // just in case + } + } + + if(mWalking) // have not yet reached the destination + { + // turn towards the next point in mPath + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + + // Returns true if evasive action needs to be taken + if(mObstacleCheck.check(actor, duration)) + { + // first check if we're walking into a door + if(proximityToDoor(actor)) // NOTE: checks interior cells only + { + // remove allowed points then select another random destination + mTrimCurrentNode = true; + trimAllowedNodes(mAllowedNodes, mPathFinder); + mObstacleCheck.clear(); + mPathFinder.clearPath(); + mWalking = false; + mMoveNow = true; + } + else // probably walking into another NPC + { + // TODO: diagonal should have same animation as walk forward + // but doesn't seem to do that? + actor.getClass().getMovementSettings(actor).mPosition[0] = 1; + actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; + // change the angle a bit, too + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); + } + mStuckCount++; // TODO: maybe no longer needed + } +//#if 0 + // TODO: maybe no longer needed + if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset + { + //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; + mObstacleCheck.clear(); + + stopWalking(actor); + mMoveNow = false; + mWalking = false; + mChooseAction = true; + } +//#endif + } + + + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + + mReaction += duration; + if(mReaction > 0.25f) // FIXME: hard coded constant + { + mReaction = 0; + return false; + } + + // NOTE: everything below get updated every mReaction + MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) { @@ -155,8 +243,6 @@ namespace MWMechanics } } - ESM::Position pos = actor.getRefData().getPosition(); - // Initialization to discover & store allowed node points for this actor. if(!mStoredAvailableNodes) { @@ -270,50 +356,29 @@ namespace MWMechanics int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); if (hello > 0) { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - float chance = store.get().find("fVoiceIdleOdds")->getFloat(); int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); // Don't bother if the player is out of hearing range - if (roll < chance && Ogre::Vector3(player.getRefData().getPosition().pos).distance(Ogre::Vector3(pos.pos)) < 1500) + if (roll < mChance && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500) MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); } } } - // Check if an idle actor is too close to a door - if so start walking - mDoorCheckDuration += duration; - if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL) - { - mDoorCheckDuration = 0; // restart timer - if(mDistance && // actor is not intended to be stationary - mIdleNow && // but is in idle - !mWalking && // FIXME: some actors are idle while walking - proximityToDoor(actor)) // NOTE: checks interior cells only - { - mIdleNow = false; - mMoveNow = true; - mTrimCurrentNode = false; // just in case - } - } - // Allow interrupting a walking actor to trigger a greeting if(mIdleNow || (mWalking && !mObstacleCheck.isNormalState())) { // Play a random voice greeting if the player gets too close - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); float helloDistance = hello; - int iGreetDistanceMultiplier = store.get().find("iGreetDistanceMultiplier")->getInt(); - helloDistance *= iGreetDistanceMultiplier; + helloDistance *= mGreetDistanceMultiplier; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).distance( + float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance( Ogre::Vector3(actor.getRefData().getPosition().pos)); - if(mWalking && playerDist <= helloDistance) + if(mWalking && playerDist <= helloDistance*helloDistance) { stopWalking(actor); mMoveNow = false; @@ -324,7 +389,7 @@ namespace MWMechanics if (!mSaidGreeting) { // TODO: check if actor is aware / has line of sight - if (playerDist <= helloDistance + if (playerDist <= helloDistance*helloDistance // Only play a greeting if the player is not moving && Ogre::Vector3(player.getClass().getMovementSettings(player).mPosition).squaredLength() == 0) { @@ -335,8 +400,7 @@ namespace MWMechanics } else { - float fGreetDistanceReset = store.get().find("fGreetDistanceReset")->getFloat(); - if (playerDist >= fGreetDistanceReset * iGreetDistanceMultiplier) + if (playerDist >= mGreetDistanceReset*mGreetDistanceReset * mGreetDistanceMultiplier*mGreetDistanceMultiplier) mSaidGreeting = false; } @@ -413,51 +477,6 @@ namespace MWMechanics mWalking = false; mChooseAction = true; } - else if(mWalking) // have not yet reached the destination - { - // turn towards the next point in mPath - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; - - // Returns true if evasive action needs to be taken - if(mObstacleCheck.check(actor, duration)) - { - // first check if we're walking into a door - if(proximityToDoor(actor)) // NOTE: checks interior cells only - { - // remove allowed points then select another random destination - mTrimCurrentNode = true; - trimAllowedNodes(mAllowedNodes, mPathFinder); - mObstacleCheck.clear(); - mPathFinder.clearPath(); - mWalking = false; - mMoveNow = true; - } - else // probably walking into another NPC - { - // TODO: diagonal should have same animation as walk forward - // but doesn't seem to do that? - actor.getClass().getMovementSettings(actor).mPosition[0] = 1; - actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; - // change the angle a bit, too - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); - } - mStuckCount++; // TODO: maybe no longer needed - } -//#if 0 - // TODO: maybe no longer needed - if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset - { - //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; - mObstacleCheck.clear(); - - stopWalking(actor); - mMoveNow = false; - mWalking = false; - mChooseAction = true; - } -//#endif - } return false; // AiWander package not yet completed } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 59c21de895..a398621357 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -34,6 +34,9 @@ namespace MWMechanics bool mRepeat; bool mSaidGreeting; + int mGreetDistanceMultiplier; + float mGreetDistanceReset; + float mChance; // Cached current cell location int mCellX; @@ -69,6 +72,8 @@ namespace MWMechanics ObstacleCheck mObstacleCheck; float mDoorCheckDuration; int mStuckCount; + + float mReaction; // update some actions infrequently }; } From 3d26702f5e9d8086cba0c4c775dc975b85b94649 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 20 Apr 2014 10:36:01 +1000 Subject: [PATCH 094/135] Make idle actors face the player. Turning animation is not smooth. --- apps/openmw/mwmechanics/aiwander.cpp | 31 ++++++++++++++++------------ 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 12d86a5dc3..167f619597 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -204,9 +204,6 @@ namespace MWMechanics //#endif } - - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); - mReaction += duration; if(mReaction > 0.25f) // FIXME: hard coded constant { @@ -375,32 +372,40 @@ namespace MWMechanics helloDistance *= mGreetDistanceMultiplier; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance( - Ogre::Vector3(actor.getRefData().getPosition().pos)); + Ogre::Vector3 playerPos(player.getRefData().getPosition().pos); + Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); + float playerDistSqr = playerPos.squaredDistance(actorPos); - if(mWalking && playerDist <= helloDistance*helloDistance) + if(playerDistSqr <= helloDistance*helloDistance) { - stopWalking(actor); - mMoveNow = false; - mWalking = false; - mObstacleCheck.clear(); + if(mWalking) + { + stopWalking(actor); + mMoveNow = false; + mWalking = false; + mObstacleCheck.clear(); + } + Ogre::Vector3 dir = playerPos - actorPos; + float length = dir.length(); + + // FIXME: horrible hack, and does not turn smoothly + zTurn(actor, Ogre::Degree(Ogre::Radian(Ogre::Math::ACos(dir.y / length) * ((Ogre::Math::ASin(dir.x / length).valueRadians()>0)?1.0:-1.0)).valueDegrees())); } if (!mSaidGreeting) { // TODO: check if actor is aware / has line of sight - if (playerDist <= helloDistance*helloDistance + if (playerDistSqr <= helloDistance*helloDistance // Only play a greeting if the player is not moving && Ogre::Vector3(player.getClass().getMovementSettings(player).mPosition).squaredLength() == 0) { mSaidGreeting = true; MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); - // TODO: turn to face player and interrupt the idle animation? } } else { - if (playerDist >= mGreetDistanceReset*mGreetDistanceReset * mGreetDistanceMultiplier*mGreetDistanceMultiplier) + if (playerDistSqr >= mGreetDistanceReset*mGreetDistanceReset * mGreetDistanceMultiplier*mGreetDistanceMultiplier) mSaidGreeting = false; } From 000afa48b73cad62b3e762998b3ef2042467af74 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 20 Apr 2014 11:59:47 +1000 Subject: [PATCH 095/135] An attempt at making turning animation smoother. Copied some code from AiCombat for rotation. --- apps/openmw/mwmechanics/aiwander.cpp | 34 ++++++++++++++++++++++++---- apps/openmw/mwmechanics/aiwander.hpp | 5 ++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 167f619597..548eb60f64 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -34,6 +34,9 @@ namespace MWMechanics , mGreetDistanceMultiplier(0) , mGreetDistanceReset(0) , mChance(0) + , mRotate(false) + , mTargetAngle(0) + , mOriginalAngle(0) , mSaidGreeting(false) { for(unsigned short counter = 0; counter < mIdle.size(); counter++) @@ -204,6 +207,12 @@ namespace MWMechanics //#endif } + if (mRotate) + { + if (zTurn(actor, Ogre::Degree(mTargetAngle))) + mRotate = false; + } + mReaction += duration; if(mReaction > 0.25f) // FIXME: hard coded constant { @@ -385,11 +394,28 @@ namespace MWMechanics mWalking = false; mObstacleCheck.clear(); } - Ogre::Vector3 dir = playerPos - actorPos; - float length = dir.length(); - // FIXME: horrible hack, and does not turn smoothly - zTurn(actor, Ogre::Degree(Ogre::Radian(Ogre::Math::ACos(dir.y / length) * ((Ogre::Math::ASin(dir.x / length).valueRadians()>0)?1.0:-1.0)).valueDegrees())); + if(!mRotate) + { + Ogre::Vector3 dir = playerPos - actorPos; + float length = dir.length(); + + // FIXME: horrible hack + float faceAngle = Ogre::Radian(Ogre::Math::ACos(dir.y / length) * + ((Ogre::Math::ASin(dir.x / length).valueRadians()>0)?1.0:-1.0)).valueDegrees(); + // an attempt at reducing the turning animation glitch + // TODO: doesn't seem to work very well + if(abs(faceAngle) > 10) + { + mTargetAngle = faceAngle; + mRotate = true; + } + } + } + else if(!mDistance) // FIXME: stationary actors go back to their normal position + { + //mTargetAngle = mOriginalAngle; + //mRotate = true; } if (!mSaidGreeting) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index a398621357..2ed1af3be3 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -73,6 +73,11 @@ namespace MWMechanics float mDoorCheckDuration; int mStuckCount; + // the z rotation angle (degrees) we want to reach + // used every frame when mRotate is true + float mTargetAngle; + float mOriginalAngle; + bool mRotate; float mReaction; // update some actions infrequently }; } From 2b544d550b0844e928b66a69baf5f4117636c4e4 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 20 Apr 2014 14:27:18 +1000 Subject: [PATCH 096/135] Fixed the rotation animation glitch. --- apps/openmw/mwmechanics/aiwander.cpp | 15 ++++++--------- apps/openmw/mwmechanics/steering.cpp | 3 +-- apps/openmw/mwmechanics/steering.hpp | 3 ++- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 548eb60f64..988879379d 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -209,7 +209,10 @@ namespace MWMechanics if (mRotate) { - if (zTurn(actor, Ogre::Degree(mTargetAngle))) + // Reduce the turning animation glitch by using a *HUGE* value of + // epsilon... TODO: a proper fix might be in either the physics or the + // animation subsystem + if (zTurn(actor, Ogre::Degree(mTargetAngle), Ogre::Degree(12))) mRotate = false; } @@ -220,7 +223,7 @@ namespace MWMechanics return false; } - // NOTE: everything below get updated every mReaction + // NOTE: everything below get updated every 0.25 seconds MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) @@ -404,7 +407,6 @@ namespace MWMechanics float faceAngle = Ogre::Radian(Ogre::Math::ACos(dir.y / length) * ((Ogre::Math::ASin(dir.x / length).valueRadians()>0)?1.0:-1.0)).valueDegrees(); // an attempt at reducing the turning animation glitch - // TODO: doesn't seem to work very well if(abs(faceAngle) > 10) { mTargetAngle = faceAngle; @@ -412,11 +414,6 @@ namespace MWMechanics } } } - else if(!mDistance) // FIXME: stationary actors go back to their normal position - { - //mTargetAngle = mOriginalAngle; - //mRotate = true; - } if (!mSaidGreeting) { @@ -436,7 +433,7 @@ namespace MWMechanics } // Check if idle animation finished - if(!checkIdle(actor, mPlayedIdle)) + if(!checkIdle(actor, mPlayedIdle) && !mRotate) { mPlayedIdle = 0; mIdleNow = false; diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp index d911fd81b8..054107f73d 100644 --- a/apps/openmw/mwmechanics/steering.cpp +++ b/apps/openmw/mwmechanics/steering.cpp @@ -10,7 +10,7 @@ namespace MWMechanics { -bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle) +bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree epsilon) { Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[2]); Ogre::Radian diff (targetAngle - currentAngle); @@ -27,7 +27,6 @@ bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle) // The turning animation actually moves you slightly, so the angle will be wrong again. // Use epsilon to prevent jerkiness. - const Ogre::Degree epsilon (0.5); if (absDiff < epsilon) return true; diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp index 504dc3ac33..4042b54121 100644 --- a/apps/openmw/mwmechanics/steering.hpp +++ b/apps/openmw/mwmechanics/steering.hpp @@ -12,7 +12,8 @@ namespace MWMechanics /// configure rotation settings for an actor to reach this target angle (eventually) /// @return have we reached the target angle? -bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle); +bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, + Ogre::Degree epsilon = Ogre::Degree(0.5)); } From 95ef874205ee741360563e9b2c6828a90fce0d4d Mon Sep 17 00:00:00 2001 From: Thomas Luppi Date: Sun, 20 Apr 2014 00:34:58 -0400 Subject: [PATCH 097/135] Gets default keybindings from scancode, not keyvalue. Gives keyboard set-up independant default keybindings; useful for international keyboards or dvorak users --- apps/openmw/mwinput/inputmanagerimp.cpp | 61 +++++++++++++------------ 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index e2d4f8cb2d..616d8ae911 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -811,36 +811,37 @@ namespace MWInput // across different versions of OpenMW (in the case where another input action is added) std::map defaultKeyBindings; - defaultKeyBindings[A_Activate] = SDLK_SPACE; - defaultKeyBindings[A_MoveBackward] = SDLK_s; - defaultKeyBindings[A_MoveForward] = SDLK_w; - defaultKeyBindings[A_MoveLeft] = SDLK_a; - defaultKeyBindings[A_MoveRight] = SDLK_d; - defaultKeyBindings[A_ToggleWeapon] = SDLK_f; - defaultKeyBindings[A_ToggleSpell] = SDLK_r; - defaultKeyBindings[A_QuickKeysMenu] = SDLK_F1; - defaultKeyBindings[A_Console] = SDLK_F2; - defaultKeyBindings[A_Run] = SDLK_LSHIFT; - defaultKeyBindings[A_Sneak] = SDLK_LCTRL; - defaultKeyBindings[A_AutoMove] = SDLK_q; - defaultKeyBindings[A_Jump] = SDLK_e; - defaultKeyBindings[A_Journal] = SDLK_j; - defaultKeyBindings[A_Rest] = SDLK_t; - defaultKeyBindings[A_GameMenu] = SDLK_ESCAPE; - defaultKeyBindings[A_TogglePOV] = SDLK_TAB; - defaultKeyBindings[A_QuickKey1] = SDLK_1; - defaultKeyBindings[A_QuickKey2] = SDLK_2; - defaultKeyBindings[A_QuickKey3] = SDLK_3; - defaultKeyBindings[A_QuickKey4] = SDLK_4; - defaultKeyBindings[A_QuickKey5] = SDLK_5; - defaultKeyBindings[A_QuickKey6] = SDLK_6; - defaultKeyBindings[A_QuickKey7] = SDLK_7; - defaultKeyBindings[A_QuickKey8] = SDLK_8; - defaultKeyBindings[A_QuickKey9] = SDLK_9; - defaultKeyBindings[A_QuickKey10] = SDLK_0; - defaultKeyBindings[A_Screenshot] = SDLK_F12; - defaultKeyBindings[A_ToggleHUD] = SDLK_F11; - defaultKeyBindings[A_AlwaysRun] = SDLK_y; + //Gets the Keyvalue from the Scancode; gives the button in the same place reguardless of keyboard format + defaultKeyBindings[A_Activate] = SDL_GetKeyFromScancode(SDL_SCANCODE_SPACE); + defaultKeyBindings[A_MoveBackward] = SDL_GetKeyFromScancode(SDL_SCANCODE_S); + defaultKeyBindings[A_MoveForward] = SDL_GetKeyFromScancode(SDL_SCANCODE_W); + defaultKeyBindings[A_MoveLeft] = SDL_GetKeyFromScancode(SDL_SCANCODE_A); + defaultKeyBindings[A_MoveRight] = SDL_GetKeyFromScancode(SDL_SCANCODE_D); + defaultKeyBindings[A_ToggleWeapon] = SDL_GetKeyFromScancode(SDL_SCANCODE_F); + defaultKeyBindings[A_ToggleSpell] = SDL_GetKeyFromScancode(SDL_SCANCODE_R); + defaultKeyBindings[A_QuickKeysMenu] = SDL_GetKeyFromScancode(SDL_SCANCODE_F1); + defaultKeyBindings[A_Console] = SDL_GetKeyFromScancode(SDL_SCANCODE_F2); + defaultKeyBindings[A_Run] = SDL_GetKeyFromScancode(SDL_SCANCODE_LSHIFT); + defaultKeyBindings[A_Sneak] = SDL_GetKeyFromScancode(SDL_SCANCODE_LCTRL); + defaultKeyBindings[A_AutoMove] = SDL_GetKeyFromScancode(SDL_SCANCODE_Q); + defaultKeyBindings[A_Jump] = SDL_GetKeyFromScancode(SDL_SCANCODE_E); + defaultKeyBindings[A_Journal] = SDL_GetKeyFromScancode(SDL_SCANCODE_J); + defaultKeyBindings[A_Rest] = SDL_GetKeyFromScancode(SDL_SCANCODE_T); + defaultKeyBindings[A_GameMenu] = SDL_GetKeyFromScancode(SDL_SCANCODE_ESCAPE); + defaultKeyBindings[A_TogglePOV] = SDL_GetKeyFromScancode(SDL_SCANCODE_TAB); + defaultKeyBindings[A_QuickKey1] = SDL_GetKeyFromScancode(SDL_SCANCODE_1); + defaultKeyBindings[A_QuickKey2] = SDL_GetKeyFromScancode(SDL_SCANCODE_2); + defaultKeyBindings[A_QuickKey3] = SDL_GetKeyFromScancode(SDL_SCANCODE_3); + defaultKeyBindings[A_QuickKey4] = SDL_GetKeyFromScancode(SDL_SCANCODE_4); + defaultKeyBindings[A_QuickKey5] = SDL_GetKeyFromScancode(SDL_SCANCODE_5); + defaultKeyBindings[A_QuickKey6] = SDL_GetKeyFromScancode(SDL_SCANCODE_6); + defaultKeyBindings[A_QuickKey7] = SDL_GetKeyFromScancode(SDL_SCANCODE_7); + defaultKeyBindings[A_QuickKey8] = SDL_GetKeyFromScancode(SDL_SCANCODE_8); + defaultKeyBindings[A_QuickKey9] = SDL_GetKeyFromScancode(SDL_SCANCODE_9); + defaultKeyBindings[A_QuickKey10] = SDL_GetKeyFromScancode(SDL_SCANCODE_0); + defaultKeyBindings[A_Screenshot] = SDL_GetKeyFromScancode(SDL_SCANCODE_F12); + defaultKeyBindings[A_ToggleHUD] = SDL_GetKeyFromScancode(SDL_SCANCODE_F11); + defaultKeyBindings[A_AlwaysRun] = SDL_GetKeyFromScancode(SDL_SCANCODE_y); std::map defaultMouseButtonBindings; defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT; From d6f1f129eb0df850ecb302dcc264b8f5b71724d9 Mon Sep 17 00:00:00 2001 From: Thomas Luppi Date: Sun, 20 Apr 2014 00:39:50 -0400 Subject: [PATCH 098/135] Forgot to capitalize something. whooooops dat y do --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 616d8ae911..1346b9e958 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -841,7 +841,7 @@ namespace MWInput defaultKeyBindings[A_QuickKey10] = SDL_GetKeyFromScancode(SDL_SCANCODE_0); defaultKeyBindings[A_Screenshot] = SDL_GetKeyFromScancode(SDL_SCANCODE_F12); defaultKeyBindings[A_ToggleHUD] = SDL_GetKeyFromScancode(SDL_SCANCODE_F11); - defaultKeyBindings[A_AlwaysRun] = SDL_GetKeyFromScancode(SDL_SCANCODE_y); + defaultKeyBindings[A_AlwaysRun] = SDL_GetKeyFromScancode(SDL_SCANCODE_Y); std::map defaultMouseButtonBindings; defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT; From e17fab891da936e82c0bf77108bf146b800749cd Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 20 Apr 2014 16:49:57 +1000 Subject: [PATCH 099/135] Suppress travis warning and one more tweak. --- apps/openmw/mwmechanics/aiwander.cpp | 2 +- apps/openmw/mwmechanics/obstacle.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 988879379d..d552e045dc 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -153,7 +153,7 @@ namespace MWMechanics if(mDistance && // actor is not intended to be stationary mIdleNow && // but is in idle !mWalking && // FIXME: some actors are idle while walking - proximityToDoor(actor)) // NOTE: checks interior cells only + proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6*1.6)) // NOTE: checks interior cells only { mIdleNow = false; mMoveNow = true; diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 6694de0964..1815609357 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -43,8 +43,8 @@ namespace MWMechanics { MWWorld::LiveCellRef& ref = *it; if(pos.squaredDistance(Ogre::Vector3(ref.mRef.mPos.pos)) < minSqr) - if(closed && ref.mData.getLocalRotation().rot[2] == 0 || - !closed && ref.mData.getLocalRotation().rot[2] >= 1) + if((closed && ref.mData.getLocalRotation().rot[2] == 0) || + (!closed && ref.mData.getLocalRotation().rot[2] >= 1)) { return true; // found, stop searching } From 6a1435c49e7ed6a83bb92801ed6c7303872eb78a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 20 Apr 2014 17:59:08 +1000 Subject: [PATCH 100/135] Remove rotation check that was freezing NPC's. --- apps/openmw/mwmechanics/aiwander.cpp | 3 +-- apps/openmw/mwmechanics/aiwander.hpp | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index d552e045dc..ad94be0eb4 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -36,7 +36,6 @@ namespace MWMechanics , mChance(0) , mRotate(false) , mTargetAngle(0) - , mOriginalAngle(0) , mSaidGreeting(false) { for(unsigned short counter = 0; counter < mIdle.size(); counter++) @@ -433,7 +432,7 @@ namespace MWMechanics } // Check if idle animation finished - if(!checkIdle(actor, mPlayedIdle) && !mRotate) + if(!checkIdle(actor, mPlayedIdle)) { mPlayedIdle = 0; mIdleNow = false; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 2ed1af3be3..fe14abeb61 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -76,7 +76,6 @@ namespace MWMechanics // the z rotation angle (degrees) we want to reach // used every frame when mRotate is true float mTargetAngle; - float mOriginalAngle; bool mRotate; float mReaction; // update some actions infrequently }; From 86bd2f48dc08da0878264a57b0204db72304e111 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 20 Apr 2014 17:28:10 +0200 Subject: [PATCH 101/135] removed an unused function --- apps/opencs/model/world/refcollection.cpp | 11 ----------- apps/opencs/model/world/refcollection.hpp | 5 ----- 2 files changed, 16 deletions(-) diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 9ee59bd1e0..4fdd979097 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -38,14 +38,3 @@ std::string CSMWorld::RefCollection::getNewId() stream << "ref#" << mNextId++; return stream.str(); } - -void CSMWorld::RefCollection::cloneRecord(const std::string& origin, - const std::string& destination, - const CSMWorld::UniversalId::Type type, - const CSMWorld::UniversalId::ArgumentType argumentType) -{ - Record clone(getRecord(origin)); - clone.mState = CSMWorld::RecordBase::State_ModifiedOnly; - clone.get().mId = destination; - insertRecord(clone, getAppendIndex(destination, type), type); -} \ No newline at end of file diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index dcfd2036cd..173efba05b 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -26,11 +26,6 @@ namespace CSMWorld ///< Load a sequence of references. std::string getNewId(); - - void cloneRecord(const std::string& origin, - const std::string& destination, - const CSMWorld::UniversalId::Type type, - const CSMWorld::UniversalId::ArgumentType argumentType); }; } From 79f32546e194d435e877f29b6216ea165be1218c Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 20 Apr 2014 19:28:25 +0200 Subject: [PATCH 102/135] Dead link fix --- cmake/FindLIBUNSHIELD.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/FindLIBUNSHIELD.cmake b/cmake/FindLIBUNSHIELD.cmake index 4f4e98a1cd..f0fa4cc824 100644 --- a/cmake/FindLIBUNSHIELD.cmake +++ b/cmake/FindLIBUNSHIELD.cmake @@ -4,7 +4,7 @@ # LIBUNSHIELD_FOUND, if false, do not try to link to LibUnshield # LIBUNSHIELD_INCLUDE_DIR, where to find the headers # -# Created by Tom Mason (wheybags) for OpenMW (http://openmw.com), based on FindMPG123.cmake +# Created by Tom Mason (wheybags) for OpenMW (http://openmw.org), based on FindMPG123.cmake # # Ripped off from other sources. In fact, this file is so generic (I # just did a search and replace on another file) that I wonder why the From 262e9596996bb29d4e000ded8235682988818613 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 20 Apr 2014 19:28:39 +0200 Subject: [PATCH 103/135] Remove unused slice_array --- components/CMakeLists.txt | 2 +- components/misc/slice_array.hpp | 82 --------------------------------- 2 files changed, 1 insertion(+), 83 deletions(-) delete mode 100644 components/misc/slice_array.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 2b06babe73..38be5b11a6 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -49,7 +49,7 @@ add_component_dir (esm ) add_component_dir (misc - slice_array stringops + utf8stream stringops ) add_component_dir (files diff --git a/components/misc/slice_array.hpp b/components/misc/slice_array.hpp deleted file mode 100644 index cd58e7bd69..0000000000 --- a/components/misc/slice_array.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (slice_array.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -#ifndef MISC_SLICE_ARRAY_H -#define MISC_SLICE_ARRAY_H - -// A simple array implementation containing a pointer and a -// length. Used for holding slices into a data buffer. -#include -#include - -namespace Misc -{ - -template -struct SliceArray -{ - const T* ptr; - size_t length; - - /// Initialize to zero length - SliceArray() : ptr(0), length(0) {} - - /// Initialize from pointer + length - SliceArray(const T* _ptr, size_t _length) - : ptr(_ptr), length(_length) {} - - /// Initialize from null-terminated string - SliceArray(const char* str) - { - ptr = str; - length = strlen(str); - } - - bool operator==(SliceArray &t) - { - return - length == t.length && - (memcmp(ptr,t.ptr, length*sizeof(T)) == 0); - } - - /// Only use this for stings - bool operator==(const char* str) - { - return - str[length] == 0 && - (strncmp(ptr, str, length) == 0); - } - - /** This allocates a copy of the data. Only use this for debugging - and error messages. */ - std::string toString() - { return std::string(ptr,length); } -}; - -typedef SliceArray SString; -typedef SliceArray IntArray; -typedef SliceArray FloatArray; - -} - -#endif From 2cb9f38a45596859f2f3dc7fe1db51cf2b78bf36 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 20 Apr 2014 19:34:58 +0200 Subject: [PATCH 104/135] Changed crime IDs for legacy savegames. Not an issue, but it was inconsistent. --- components/esm/player.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esm/player.cpp b/components/esm/player.cpp index 7a2c03b488..70f795afe6 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -26,9 +26,9 @@ void ESM::Player::load (ESMReader &esm) mBirthsign = esm.getHNString ("SIGN"); - mCurrentCrimeId = 0; + mCurrentCrimeId = -1; esm.getHNOT (mCurrentCrimeId, "CURD"); - mPayedCrimeId = 0; + mPayedCrimeId = -1; esm.getHNOT (mPayedCrimeId, "PAYD"); } From 6929e541ddd21b211faa2b3817d0a763f0f31777 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 21 Apr 2014 05:30:14 +0200 Subject: [PATCH 105/135] Fix texture name issue with Vality's Bitter Coast Mod --- components/nifogre/material.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 4dae1a93de..3a87e1d529 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -67,6 +67,10 @@ std::string NIFMaterialLoader::findTextureName(const std::string &filename) std::string texname = filename; Misc::StringUtils::toLower(texname); + // Apparently, leading separators are allowed + while (texname.size() && (texname[0] == '/' || texname[0] == '\\')) + texname.erase(0, 1); + if(texname.compare(0, sizeof(path)-1, path) != 0 && texname.compare(0, sizeof(path2)-1, path2) != 0) texname = path + texname; From 9998c2783eb4813137c61451085615f7ba8f70bc Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 21 Apr 2014 17:37:06 +0200 Subject: [PATCH 106/135] Fix travis Why again do we need a unit test for something that was never used? --- .../components/misc/test_slicearray.cpp | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 apps/openmw_test_suite/components/misc/test_slicearray.cpp diff --git a/apps/openmw_test_suite/components/misc/test_slicearray.cpp b/apps/openmw_test_suite/components/misc/test_slicearray.cpp deleted file mode 100644 index ab63e56c4f..0000000000 --- a/apps/openmw_test_suite/components/misc/test_slicearray.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include "components/misc/slice_array.hpp" - -struct SliceArrayTest : public ::testing::Test -{ - protected: - virtual void SetUp() - { - } - - virtual void TearDown() - { - } -}; - -TEST_F(SliceArrayTest, hello_string) -{ - Misc::SString s("hello"); - ASSERT_EQ(sizeof("hello") - 1, s.length); - ASSERT_FALSE(s=="hel"); - ASSERT_FALSE(s=="hell"); - ASSERT_TRUE(s=="hello"); -} - -TEST_F(SliceArrayTest, othello_string_with_offset_2_and_size_4) -{ - Misc::SString s("othello" + 2, 4); - ASSERT_EQ(sizeof("hell") - 1, s.length); - ASSERT_FALSE(s=="hel"); - ASSERT_TRUE(s=="hell"); - ASSERT_FALSE(s=="hello"); -} - From 1ab51306c3dfafdc5007dc8f15a96d9daf103118 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 21 Apr 2014 17:47:20 +0200 Subject: [PATCH 107/135] updated credits file --- credits.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/credits.txt b/credits.txt index eb427a22b7..499c55eebd 100644 --- a/credits.txt +++ b/credits.txt @@ -70,6 +70,7 @@ Sebastian Wick (swick) Sergey Shambir sir_herrbatka Sylvain Thesnieres (Garvek) +Thomas Luppi (Digmaster) Tom Mason (wheybags) Torben Leif Carrington (TorbenC) From d3148555fad4ed85bb9e592003804e4f185f9b14 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Tue, 22 Apr 2014 22:16:14 -0500 Subject: [PATCH 108/135] Delete old framework files --- .../model/settings/settingcontainer.cpp | 82 ------- .../model/settings/settingcontainer.hpp | 47 ---- apps/opencs/model/settings/settingsitem.cpp | 104 --------- apps/opencs/model/settings/settingsitem.hpp | 67 ------ apps/opencs/view/settings/abstractblock.cpp | 112 --------- apps/opencs/view/settings/abstractblock.hpp | 82 ------- apps/opencs/view/settings/abstractpage.cpp | 44 ---- apps/opencs/view/settings/abstractpage.hpp | 70 ------ apps/opencs/view/settings/abstractwidget.cpp | 78 ------- apps/opencs/view/settings/abstractwidget.hpp | 69 ------ apps/opencs/view/settings/blankpage.cpp | 50 ---- apps/opencs/view/settings/blankpage.hpp | 28 --- apps/opencs/view/settings/customblock.cpp | 121 ---------- apps/opencs/view/settings/customblock.hpp | 47 ---- .../view/settings/datadisplayformatpage.cpp | 57 ----- .../view/settings/datadisplayformatpage.hpp | 33 --- apps/opencs/view/settings/editorpage.cpp | 53 ----- apps/opencs/view/settings/editorpage.hpp | 33 --- apps/opencs/view/settings/groupblock.cpp | 108 --------- apps/opencs/view/settings/groupblock.hpp | 43 ---- apps/opencs/view/settings/groupbox.cpp | 56 ----- apps/opencs/view/settings/groupbox.hpp | 28 --- apps/opencs/view/settings/itemblock.cpp | 115 ---------- apps/opencs/view/settings/itemblock.hpp | 48 ---- apps/opencs/view/settings/proxyblock.cpp | 152 ------------- apps/opencs/view/settings/proxyblock.hpp | 52 ----- apps/opencs/view/settings/settingwidget.cpp | 1 - apps/opencs/view/settings/settingwidget.hpp | 214 ------------------ apps/opencs/view/settings/toggleblock.cpp | 80 ------- apps/opencs/view/settings/toggleblock.hpp | 29 --- .../view/settings/usersettingsdialog.cpp | 119 ---------- .../view/settings/usersettingsdialog.hpp | 71 ------ apps/opencs/view/settings/windowpage.cpp | 144 ------------ apps/opencs/view/settings/windowpage.hpp | 34 --- 34 files changed, 2471 deletions(-) delete mode 100644 apps/opencs/model/settings/settingcontainer.cpp delete mode 100644 apps/opencs/model/settings/settingcontainer.hpp delete mode 100644 apps/opencs/model/settings/settingsitem.cpp delete mode 100644 apps/opencs/model/settings/settingsitem.hpp delete mode 100644 apps/opencs/view/settings/abstractblock.cpp delete mode 100644 apps/opencs/view/settings/abstractblock.hpp delete mode 100644 apps/opencs/view/settings/abstractpage.cpp delete mode 100644 apps/opencs/view/settings/abstractpage.hpp delete mode 100644 apps/opencs/view/settings/abstractwidget.cpp delete mode 100644 apps/opencs/view/settings/abstractwidget.hpp delete mode 100644 apps/opencs/view/settings/blankpage.cpp delete mode 100644 apps/opencs/view/settings/blankpage.hpp delete mode 100644 apps/opencs/view/settings/customblock.cpp delete mode 100644 apps/opencs/view/settings/customblock.hpp delete mode 100755 apps/opencs/view/settings/datadisplayformatpage.cpp delete mode 100755 apps/opencs/view/settings/datadisplayformatpage.hpp delete mode 100644 apps/opencs/view/settings/editorpage.cpp delete mode 100644 apps/opencs/view/settings/editorpage.hpp delete mode 100644 apps/opencs/view/settings/groupblock.cpp delete mode 100644 apps/opencs/view/settings/groupblock.hpp delete mode 100644 apps/opencs/view/settings/groupbox.cpp delete mode 100644 apps/opencs/view/settings/groupbox.hpp delete mode 100644 apps/opencs/view/settings/itemblock.cpp delete mode 100644 apps/opencs/view/settings/itemblock.hpp delete mode 100644 apps/opencs/view/settings/proxyblock.cpp delete mode 100644 apps/opencs/view/settings/proxyblock.hpp delete mode 100644 apps/opencs/view/settings/settingwidget.cpp delete mode 100644 apps/opencs/view/settings/settingwidget.hpp delete mode 100644 apps/opencs/view/settings/toggleblock.cpp delete mode 100644 apps/opencs/view/settings/toggleblock.hpp delete mode 100644 apps/opencs/view/settings/usersettingsdialog.cpp delete mode 100644 apps/opencs/view/settings/usersettingsdialog.hpp delete mode 100644 apps/opencs/view/settings/windowpage.cpp delete mode 100644 apps/opencs/view/settings/windowpage.hpp diff --git a/apps/opencs/model/settings/settingcontainer.cpp b/apps/opencs/model/settings/settingcontainer.cpp deleted file mode 100644 index a75a84ec55..0000000000 --- a/apps/opencs/model/settings/settingcontainer.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "settingcontainer.hpp" - -#include - -CSMSettings::SettingContainer::SettingContainer(QObject *parent) : - QObject(parent), mValue (0), mValues (0) -{ -} - -CSMSettings::SettingContainer::SettingContainer(const QString &value, QObject *parent) : - QObject(parent), mValue (new QString (value)), mValues (0) -{ -} - -void CSMSettings::SettingContainer::insert (const QString &value) -{ - if (mValue) - { - mValues = new QStringList; - mValues->push_back (*mValue); - mValues->push_back (value); - - delete mValue; - mValue = 0; - } - else - { - delete mValue; - mValue = new QString (value); - } - -} - -void CSMSettings::SettingContainer::update (const QString &value, int index) -{ - if (isEmpty()) - mValue = new QString(value); - - else if (mValue) - *mValue = value; - - else if (mValues) - mValues->replace(index, value); -} - -QString CSMSettings::SettingContainer::getValue (int index) const -{ - QString retVal(""); - - //if mValue is valid, it's a single-value property. - //ignore the index and return the value - if (mValue) - retVal = *mValue; - - //otherwise, if it's a multivalued property - //return the appropriate value at the index - else if (mValues) - { - if (index == -1) - retVal = mValues->at(0); - - else if (index < mValues->size()) - retVal = mValues->at(index); - } - - return retVal; -} - -int CSMSettings::SettingContainer::count () const -{ - int retVal = 0; - - if (!isEmpty()) - { - if (mValues) - retVal = mValues->size(); - else - retVal = 1; - } - - return retVal; -} diff --git a/apps/opencs/model/settings/settingcontainer.hpp b/apps/opencs/model/settings/settingcontainer.hpp deleted file mode 100644 index 5af298a57a..0000000000 --- a/apps/opencs/model/settings/settingcontainer.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef SETTINGCONTAINER_HPP -#define SETTINGCONTAINER_HPP - -#include - -class QStringList; - -namespace CSMSettings -{ - class SettingContainer : public QObject - { - Q_OBJECT - - QString *mValue; - QStringList *mValues; - - public: - - explicit SettingContainer (QObject *parent = 0); - explicit SettingContainer (const QString &value, QObject *parent = 0); - - /// add a value to the container - /// multiple values supported - void insert (const QString &value); - - /// update an existing value - /// index specifies multiple values - void update (const QString &value, int index = 0); - - /// return value at specified index - QString getValue (int index = -1) const; - - /// retrieve list of all values - inline QStringList *getValues() const { return mValues; } - - /// return size of list - int count() const; - - /// test for empty container - /// useful for default-constructed containers returned by QMap when invalid key is passed - inline bool isEmpty() const { return (!mValue && !mValues); } - - inline bool isMultiValue() const { return (mValues); } - }; -} - -#endif // SETTINGCONTAINER_HPP diff --git a/apps/opencs/model/settings/settingsitem.cpp b/apps/opencs/model/settings/settingsitem.cpp deleted file mode 100644 index 5d897448ac..0000000000 --- a/apps/opencs/model/settings/settingsitem.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "settingsitem.hpp" - -#include - -bool CSMSettings::SettingsItem::updateItem (const QStringList *values) -{ - QStringList::ConstIterator it = values->begin(); - - //if the item is not multivalued, - //save the last value passed in the container - if (!mIsMultiValue) - { - it = values->end(); - it--; - } - - bool isValid = true; - QString value (""); - - for (; it != values->end(); ++it) - { - value = *it; - isValid = validate(value); - - //skip only the invalid values - if (!isValid) - continue; - - insert(value); - } - - return isValid; -} - -bool CSMSettings::SettingsItem::updateItem (const QString &value) -{ - //takes a value or a SettingsContainer and updates itself accordingly - //after validating the data against it's own definition - - QString newValue = value; - - if (!validate (newValue)) - newValue = mDefaultValue; - - bool success = (getValue() != newValue); - - if (success) - { - if (mIsMultiValue) - insert (newValue); - else - update (newValue); - } - return success; -} - -bool CSMSettings::SettingsItem::updateItem(int valueListIndex) -{ - bool success = false; - - if (mValueList) - { - if (mValueList->size() > valueListIndex) - success = updateItem (mValueList->at(valueListIndex)); - } - return success; -} - -bool CSMSettings::SettingsItem::validate (const QString &value) -{ - //if there is no value list or value pair, there is no validation to do - bool isValid = !(!mValueList->isEmpty() || mValuePair); - - if (!isValid && !mValueList->isEmpty()) - { - for (QStringList::Iterator it = mValueList->begin(); it != mValueList->end(); ++it) - // foreach (QString listItem, *mValueList) - { - isValid = (value == *it); - - if (isValid) - break; - } - } - else if (!isValid && mValuePair) - { - int numVal = value.toInt(); - - isValid = (numVal > mValuePair->left.toInt() && numVal < mValuePair->right.toInt()); - } - - return isValid; -} - -void CSMSettings::SettingsItem::setDefaultValue (const QString &value) -{ - mDefaultValue = value; - update (value); -} - -QString CSMSettings::SettingsItem::getDefaultValue() const -{ - return mDefaultValue; -} diff --git a/apps/opencs/model/settings/settingsitem.hpp b/apps/opencs/model/settings/settingsitem.hpp deleted file mode 100644 index 87a85e8e4e..0000000000 --- a/apps/opencs/model/settings/settingsitem.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef SETTINGSITEM_HPP -#define SETTINGSITEM_HPP - -#include -#include "support.hpp" -#include "settingcontainer.hpp" - -namespace CSMSettings -{ - /// Represents a setting including metadata - /// (valid values, ranges, defaults, and multivalue status - class SettingsItem : public SettingContainer - { - QStringPair *mValuePair; - QStringList *mValueList; - bool mIsMultiValue; - QString mDefaultValue; - - public: - explicit SettingsItem(QString name, bool isMultiValue, - const QString& defaultValue, QObject *parent = 0) - : SettingContainer(defaultValue, parent), - mIsMultiValue (isMultiValue), mValueList (0), - mValuePair (0), mDefaultValue (defaultValue) - { - QObject::setObjectName(name); - } - - /// updateItem overloads for updating setting value - /// provided a list of values (multi-valued), - /// a specific value - /// or an index value corresponding to the mValueList - bool updateItem (const QStringList *values); - bool updateItem (const QString &value); - bool updateItem (int valueListIndex); - - /// retrieve list of valid values for setting - inline QStringList *getValueList() { return mValueList; } - - /// write list of valid values for setting - inline void setValueList (QStringList *valueList) { mValueList = valueList; } - - /// valuePair used for spin boxes (max / min) - inline QStringPair *getValuePair() { return mValuePair; } - - /// set value range (spinbox / integer use) - inline void setValuePair (QStringPair valuePair) - { - delete mValuePair; - mValuePair = new QStringPair(valuePair); - } - - inline bool isMultivalue () { return mIsMultiValue; } - - void setDefaultValue (const QString &value); - QString getDefaultValue () const; - - private: - - /// Verifies that the supplied value is one of the following: - /// 1. Within the limits of the value pair (min / max) - /// 2. One of the values indicated in the value list - bool validate (const QString &value); - }; -} -#endif // SETTINGSITEM_HPP - diff --git a/apps/opencs/view/settings/abstractblock.cpp b/apps/opencs/view/settings/abstractblock.cpp deleted file mode 100644 index 65825ce8be..0000000000 --- a/apps/opencs/view/settings/abstractblock.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "abstractblock.hpp" - -CSVSettings::AbstractBlock::AbstractBlock(QWidget* parent) - : QObject (parent), mBox ( new GroupBox (parent) ), mWidgetParent (parent) -{} - -CSVSettings::AbstractBlock::AbstractBlock(bool isVisible, QWidget* parent) - : QObject (parent), mBox ( new GroupBox (isVisible, parent)), mWidgetParent (parent) -{} - -QLayout *CSVSettings::AbstractBlock::createLayout (Orientation direction, - bool isZeroMargin, QWidget* parent) -{ - QLayout *layout = 0; - - if (direction == Orient_Vertical) - layout = new QVBoxLayout (parent); - else - layout = new QHBoxLayout (parent); - - if (isZeroMargin) - layout->setContentsMargins(0, 0, 0, 0); - - return layout; -} - -QGroupBox *CSVSettings::AbstractBlock::getGroupBox() -{ - return mBox; -} - -CSVSettings::AbstractWidget *CSVSettings::AbstractBlock::buildWidget (const QString& widgetName, WidgetDef &def, - QLayout *layout, bool isConnected) const -{ - AbstractWidget *widg = 0; - - switch (def.type) - { - - case Widget_RadioButton: - widg = new SettingWidget (def, layout, mBox); - break; - - case Widget_SpinBox: - widg = new SettingWidget (def, layout, mBox); - break; - - case Widget_CheckBox: - widg = new SettingWidget (def, layout, mBox); - break; - - case Widget_LineEdit: - widg = new SettingWidget (def, layout, mBox); - break; - - case Widget_ListBox: - widg = new SettingWidget (def, layout, mBox); - break; - - case Widget_ComboBox: - widg = new SettingWidget (def, layout, mBox); - break; - - default: - break; - }; - - if (!mBox->layout()) - mBox->setLayout(widg->getLayout()); - - widg->widget()->setObjectName(widgetName); - - if (isConnected) - connect (widg, SIGNAL (signalUpdateItem (const QString &)), this, SLOT (slotUpdate (const QString &))); - connect (this, SIGNAL (signalUpdateWidget (const QString &)), widg, SLOT (slotUpdateWidget (const QString &) )); - - return widg; -} - -void CSVSettings::AbstractBlock::setVisible (bool isVisible) -{ - mBox->setBorderVisibility (isVisible); -} - -bool CSVSettings::AbstractBlock::isVisible () const -{ - return mBox->borderVisibile(); -} - -QWidget *CSVSettings::AbstractBlock::getParent() const -{ - return mWidgetParent; -} - -void CSVSettings::AbstractBlock::slotUpdate (const QString &value) -{ - slotUpdateSetting (objectName(), value); -} - -void CSVSettings::AbstractBlock::slotSetEnabled(bool value) -{ - mBox->setEnabled(value); -} - -void CSVSettings::AbstractBlock::slotUpdateSetting (const QString &settingName, const QString &settingValue) -{ - bool doEmit = true; - updateBySignal (settingName, settingValue, doEmit); - - if (doEmit) - emit signalUpdateSetting (settingName, settingValue); -} diff --git a/apps/opencs/view/settings/abstractblock.hpp b/apps/opencs/view/settings/abstractblock.hpp deleted file mode 100644 index 361339fe25..0000000000 --- a/apps/opencs/view/settings/abstractblock.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef ABSTRACTBLOCK_HPP -#define ABSTRACTBLOCK_HPP - -#include -#include - -#include "settingwidget.hpp" -#include "../../model/settings/settingsitem.hpp" -#include "groupbox.hpp" - -namespace CSVSettings -{ - - /// Abstract base class for all blocks - class AbstractBlock : public QObject - { - Q_OBJECT - - protected: - - typedef QMap SettingsItemMap; - GroupBox *mBox; - QWidget *mWidgetParent; - - public: - - explicit AbstractBlock (QWidget *parent = 0); - explicit AbstractBlock (bool isVisible, QWidget *parent = 0); - - QGroupBox *getGroupBox(); - void setVisible (bool isVisible); - bool isVisible() const; - - virtual CSMSettings::SettingList *getSettings() = 0; - - /// update settings found in the passed map and are encapsulated by the block - virtual bool updateSettings (const CSMSettings::SettingMap &settings) = 0; - - /// update callback function called from update slot - /// used for updating application-level settings in the editor - virtual bool updateBySignal (const QString &name, const QString &value, bool &doEmit) - { return false; } - - protected: - - /// Creates the layout for the block's QGroupBox - QLayout *createLayout (Orientation direction, bool isZeroMargin, QWidget* parent = 0); - - /// Creates widgets that exist as direct children of the block - AbstractWidget *buildWidget (const QString &widgetName, WidgetDef &wDef, - QLayout *layout = 0, bool isConnected = true) const; - - QWidget *getParent() const; - - public slots: - - /// enables / disables block-level widgets based on signals from other widgets - /// used in ToggleBlock - void slotSetEnabled (bool value); - - /// receives updates to applicaion-level settings in the Editor - void slotUpdateSetting (const QString &settingName, const QString &settingValue); - - private slots: - - /// receives updates to a setting in the block pushed from the application level - void slotUpdate (const QString &value); - - signals: - - /// signal to UserSettings instance - void signalUpdateSetting (const QString &propertyName, const QString &propertyValue); - - /// signal to widget for updating widget value - void signalUpdateWidget (const QString & value); - - /// ProxyBlock use only. - /// Name and value correspond to settings for which the block is a proxy. - void signalUpdateProxySetting (const QString &propertyName, const QString &propertyValue); - }; -} -#endif // ABSTRACTBLOCK_HPP diff --git a/apps/opencs/view/settings/abstractpage.cpp b/apps/opencs/view/settings/abstractpage.cpp deleted file mode 100644 index e6c605275d..0000000000 --- a/apps/opencs/view/settings/abstractpage.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "abstractpage.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -CSVSettings::AbstractPage::AbstractPage(QWidget *parent): - QWidget(parent) -{ - QGridLayout *pageLayout = new QGridLayout(this); - setLayout (pageLayout); -} - -CSVSettings::AbstractPage::AbstractPage(const QString &pageName, QWidget *parent): - QWidget(parent) -{ - QWidget::setObjectName (pageName); - - QGridLayout *pageLayout = new QGridLayout(this); - setLayout (pageLayout); -} - -CSVSettings::AbstractPage::~AbstractPage() -{ -} - -CSMSettings::SettingList *CSVSettings::AbstractPage::getSettings() -{ - CSMSettings::SettingList *settings = new CSMSettings::SettingList(); - - foreach (AbstractBlock *block, mAbstractBlocks) - { - CSMSettings::SettingList *groupSettings = block->getSettings(); - settings->append (*groupSettings); - } - - return settings; -} diff --git a/apps/opencs/view/settings/abstractpage.hpp b/apps/opencs/view/settings/abstractpage.hpp deleted file mode 100644 index 77ef4524f0..0000000000 --- a/apps/opencs/view/settings/abstractpage.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef ABSTRACTPAGE_HPP -#define ABSTRACTPAGE_HPP - -#include -#include -#include - -#include "abstractblock.hpp" - -class SettingMap; -class SettingList; - -namespace CSVSettings { - - typedef QList AbstractBlockList; - - /// Abstract base class for all setting pages in the dialog - - /// \todo Scripted implementation of settings should eliminate the need - /// \todo derive page classes. - /// \todo AbstractPage should be replaced with a general page construction class. - class AbstractPage: public QWidget - { - - protected: - - AbstractBlockList mAbstractBlocks; - - public: - - AbstractPage(QWidget *parent = 0); - AbstractPage (const QString &pageName, QWidget* parent = 0); - - ~AbstractPage(); - - virtual void setupUi() = 0; - - /// triggers widgiet initialization at the page level. All widgets updated to - /// current setting values - virtual void initializeWidgets (const CSMSettings::SettingMap &settings) = 0; - - /// retrieve the list of settings local to the page. - CSMSettings::SettingList *getSettings(); - - void setObjectName(); - - protected: - - /// Create a block for the page. - /// Block is constructed using passed definition struct - /// Page level-layout is created and assigned - template - AbstractBlock *buildBlock (T *def) - { - S *block = new S (this); - int ret = block->build (def); - - if (ret < 0) - return 0; - - QGroupBox *box = block->getGroupBox(); - QWidget::layout()->addWidget (box); - - return block; - } - - }; -} - -#endif // ABSTRACTPAGE_HPP diff --git a/apps/opencs/view/settings/abstractwidget.cpp b/apps/opencs/view/settings/abstractwidget.cpp deleted file mode 100644 index f268d3b279..0000000000 --- a/apps/opencs/view/settings/abstractwidget.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "abstractwidget.hpp" - -#include -#include - -void CSVSettings::AbstractWidget::build(QWidget *widget, WidgetDef &def, bool noLabel) -{ - if (!mLayout) - createLayout(def.orientation, true); - - buildLabelAndWidget (widget, def, noLabel); - -} - -void CSVSettings::AbstractWidget::buildLabelAndWidget (QWidget *widget, WidgetDef &def, bool noLabel) -{ - if (def.widgetWidth > -1) - widget->setFixedWidth (def.widgetWidth); - - if (!(def.caption.isEmpty() || noLabel) ) - { - QLabel *label = new QLabel (def.caption, &dynamic_cast( *parent())); - label->setBuddy (widget); - mLayout->addWidget (label); - - if (def.labelWidth > -1) - label->setFixedWidth(def.labelWidth); - } - - mLayout->addWidget (widget); - mLayout->setAlignment (widget, getAlignment (def.widgetAlignment)); -} - -void CSVSettings::AbstractWidget::createLayout - (Orientation direction, bool isZeroMargin) -{ - if (direction == Orient_Vertical) - mLayout = new QVBoxLayout (); - else - mLayout = new QHBoxLayout (); - - if (isZeroMargin) - mLayout->setContentsMargins(0, 0, 0, 0); -} - -QFlags CSVSettings::AbstractWidget::getAlignment (CSVSettings::Alignment flag) -{ - return QFlags(static_cast(flag)); -} - -QLayout *CSVSettings::AbstractWidget::getLayout() -{ - return mLayout; -} - -void CSVSettings::AbstractWidget::slotUpdateWidget (const QString &value) -{ - updateWidget (value); -} - -void CSVSettings::AbstractWidget::slotUpdateItem(const QString &value) -{ - emit signalUpdateItem (value); -} - -void CSVSettings::AbstractWidget::slotUpdateItem(bool value) -{ - if (value) - emit signalUpdateItem (widget()->objectName()); -} - -void CSVSettings::AbstractWidget::slotUpdateItem(int value) -{ - emit signalUpdateItem (QString::number(value)); -} - -void CSVSettings::AbstractWidget::slotUpdateItem (QListWidgetItem* current, QListWidgetItem* previous) -{} diff --git a/apps/opencs/view/settings/abstractwidget.hpp b/apps/opencs/view/settings/abstractwidget.hpp deleted file mode 100644 index 325de2bd23..0000000000 --- a/apps/opencs/view/settings/abstractwidget.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef ABSTRACTWIDGET_HPP -#define ABSTRACTWIDGET_HPP - -#include -#include "support.hpp" - -class QLayout; - -namespace CSVSettings -{ - /// Abstract base class for widgets which are used in user preferences dialog - class AbstractWidget : public QObject - { - Q_OBJECT - - QLayout *mLayout; - - public: - - /// Passed layout is assigned the constructed widget. - /// if no layout is passed, one is created. - explicit AbstractWidget (QLayout *layout = 0, QWidget* parent = 0) - : QObject (parent), mLayout (layout) - {} - - /// retrieve layout for insertion into itemblock - QLayout *getLayout(); - - /// create the derived widget instance - void build (QWidget* widget, WidgetDef &def, bool noLabel = false); - - /// reference to the derived widget instance - virtual QWidget *widget() = 0; - - protected: - - /// Callback called by receiving slot for widget udpates - virtual void updateWidget (const QString &value) = 0; - - /// Converts user-defined enum to Qt equivalents - QFlags getAlignment (Alignment flag); - - private: - - /// Creates layout and assigns label and widget as appropriate - void createLayout (Orientation direction, bool isZeroMargin); - - /// Creates label and widget according to passed definition - void buildLabelAndWidget (QWidget *widget, WidgetDef &def, bool noLabel); - - - signals: - - /// outbound update signal - void signalUpdateItem (const QString &value); - - public slots: - - /// receives inbound updates - void slotUpdateWidget (const QString &value); - - /// Overloads for outbound updates from derived widget signal - void slotUpdateItem (const QString &value); - void slotUpdateItem (bool value); - void slotUpdateItem (int value); - void slotUpdateItem (QListWidgetItem* current, QListWidgetItem* previous); - }; -} -#endif // ABSTRACTWIDGET_HPP diff --git a/apps/opencs/view/settings/blankpage.cpp b/apps/opencs/view/settings/blankpage.cpp deleted file mode 100644 index 837a31bee1..0000000000 --- a/apps/opencs/view/settings/blankpage.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "blankpage.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef Q_OS_MAC -#include -#endif - -#include "../../model/settings/usersettings.hpp" -#include "groupblock.hpp" -#include "toggleblock.hpp" - -CSVSettings::BlankPage::BlankPage(QWidget *parent): - AbstractPage("Blank", parent) -{ - -} - -CSVSettings::BlankPage::BlankPage(const QString &title, QWidget *parent): - AbstractPage(title, parent) -{ - // Hacks to get the stylesheet look properly -#ifdef Q_OS_MAC - QPlastiqueStyle *style = new QPlastiqueStyle; - //profilesComboBox->setStyle(style); -#endif - - setupUi(); -} - -void CSVSettings::BlankPage::setupUi() -{ - QGroupBox *pageBox = new QGroupBox(this); - layout()->addWidget(pageBox); -} - -void CSVSettings::BlankPage::initializeWidgets (const CSMSettings::SettingMap &settings) -{ - //iterate each item in each blocks in this section - //validate the corresponding setting against the defined valuelist if any. - foreach (AbstractBlock *block, mAbstractBlocks) - block->updateSettings (settings); -} diff --git a/apps/opencs/view/settings/blankpage.hpp b/apps/opencs/view/settings/blankpage.hpp deleted file mode 100644 index 07049fb71f..0000000000 --- a/apps/opencs/view/settings/blankpage.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef BLANKPAGE_HPP -#define BLANKPAGE_HPP - -#include "abstractpage.hpp" - -class QGroupBox; - -namespace CSVSettings { - - class UserSettings; - class AbstractBlock; - - /// Derived page with no widgets - /// Reference use only. - class BlankPage : public AbstractPage - { - - public: - - BlankPage (QWidget *parent = 0); - BlankPage (const QString &title, QWidget *parent); - - void setupUi(); - void initializeWidgets (const CSMSettings::SettingMap &settings); - }; -} - -#endif // BLANKPAGE_HPP diff --git a/apps/opencs/view/settings/customblock.cpp b/apps/opencs/view/settings/customblock.cpp deleted file mode 100644 index bbceafabe9..0000000000 --- a/apps/opencs/view/settings/customblock.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include "customblock.hpp" -#include "groupblock.hpp" -#include "itemblock.hpp" -#include "proxyblock.hpp" - -CSVSettings::CustomBlock::CustomBlock (QWidget *parent) : AbstractBlock (parent) -{ -} - -int CSVSettings::CustomBlock::build(GroupBlockDefList &defList, GroupBlockDefList::iterator *it) -{ - int retVal = 0; - - GroupBlockDefList::iterator defaultIt; - GroupBlockDefList::iterator listIt = defList.begin(); - GroupBlockDefList::iterator proxyIt = defaultIt; - - if (it) - listIt = *it; - - ProxyBlock *proxyBlock = new ProxyBlock(getParent()); - - for (; listIt != defList.end(); ++listIt) - { - if (!(*listIt)->isProxy) - retVal = buildGroupBlock (*listIt); - else - { - mGroupList << proxyBlock; - proxyIt = listIt; - } - } - - if (proxyIt != defaultIt) - retVal = buildProxyBlock (*proxyIt, proxyBlock); - - return retVal; -} - -CSVSettings::GroupBox *CSVSettings::CustomBlock::buildGroupBox (Orientation orientation) -{ - GroupBox *box = new GroupBox (false, mBox); - createLayout (orientation, true, box); - - return box; -} - -int CSVSettings::CustomBlock::buildGroupBlock(GroupBlockDef *def) -{ - GroupBlock *block = new GroupBlock (getParent()); - - mGroupList << block; - - connect (block, SIGNAL (signalUpdateSetting(const QString &, const QString &)), - this, SLOT (slotUpdateSetting (const QString &, const QString &))); - - return block->build(def); -} - -int CSVSettings::CustomBlock::buildProxyBlock(GroupBlockDef *def, ProxyBlock *block) -{ - if (def->settingItems.size() != 1) - return -1; - - int retVal = block->build(def); - - if (retVal != 0) - return retVal; - - // The first settingItem is the proxy setting, containing the list of settings bound to it. - foreach (QStringList *list, *(def->settingItems.at(0)->proxyList)) - { - QString proxiedBlockName = list->at(0); - - //iterate each group in the custom block, matching it to each proxied setting - //and connecting it appropriately - foreach (GroupBlock *groupBlock, mGroupList) - { - ItemBlock *proxiedBlock = groupBlock->getItemBlock (proxiedBlockName); - - if (proxiedBlock) - { - block->addSetting(proxiedBlock, list); - - //connect the proxy block's update signal to the custom block's slot - connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)), - this, SLOT (slotUpdateSetting (const QString &, const QString &))); - } - } - } - - return 0; -} - -CSMSettings::SettingList *CSVSettings::CustomBlock::getSettings() -{ - CSMSettings::SettingList *settings = new CSMSettings::SettingList(); - - foreach (GroupBlock *block, mGroupList) - { - CSMSettings::SettingList *groupSettings = block->getSettings(); - - if (groupSettings) - settings->append(*groupSettings); - } - - return settings; -} - -bool CSVSettings::CustomBlock::updateSettings (const CSMSettings::SettingMap &settings) -{ - bool success = true; - - foreach (GroupBlock *block, mGroupList) - { - bool success2 = block->updateSettings (settings); - success = success && success2; - } - - return success; -} diff --git a/apps/opencs/view/settings/customblock.hpp b/apps/opencs/view/settings/customblock.hpp deleted file mode 100644 index 54c50f395f..0000000000 --- a/apps/opencs/view/settings/customblock.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef CUSTOMBLOCK_HPP -#define CUSTOMBLOCK_HPP - -#include "abstractblock.hpp" - -namespace CSVSettings -{ - - class ProxyBlock; - - /// Base class for customized user preference setting blocks - /// Special block classes should be derived from CustomBlock - class CustomBlock : public AbstractBlock - { - - protected: - - GroupBlockList mGroupList; - - public: - - explicit CustomBlock (QWidget *parent = 0); - - /// Update settings local to the block - bool updateSettings (const CSMSettings::SettingMap &settings); - - /// Retrieve settings local to the block - CSMSettings::SettingList *getSettings(); - - /// construct the block using the passed definition - int build (GroupBlockDefList &defList, GroupBlockDefList::Iterator *it = 0); - - protected: - - /// construct the block groupbox - GroupBox *buildGroupBox (Orientation orientation); - - private: - - /// Construction function for creating a standard GroupBlock child - int buildGroupBlock(GroupBlockDef *def); - - /// Construction function for creating a standard ProxyBlock child - int buildProxyBlock(GroupBlockDef *def, ProxyBlock *block); - }; -} -#endif // CUSTOMBLOCK_HPP diff --git a/apps/opencs/view/settings/datadisplayformatpage.cpp b/apps/opencs/view/settings/datadisplayformatpage.cpp deleted file mode 100755 index 332b68f5c4..0000000000 --- a/apps/opencs/view/settings/datadisplayformatpage.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "datadisplayformatpage.hpp" -#include "groupblock.hpp" -#include "../../model/settings/usersettings.hpp" - -CSVSettings::DataDisplayFormatPage::DataDisplayFormatPage(QWidget* parent) : - AbstractPage("Display Format", parent) -{ - setupUi(); -} - -CSVSettings::GroupBlockDef *CSVSettings::DataDisplayFormatPage::setupDataDisplay( const QString &title) -{ - GroupBlockDef *statusBlock = new GroupBlockDef(QString(title)); - - SettingsItemDef *statusItem = new SettingsItemDef (statusBlock->title, "Icon Only"); - *(statusItem->valueList) << QString("Icon and Text") << QString("Icon Only") << QString("Text Only"); - - WidgetDef statusWidget (Widget_RadioButton); - statusWidget.valueList = statusItem->valueList; - - statusItem->widget = statusWidget; - - statusBlock->settingItems << statusItem; - - statusBlock->isZeroMargin = false; - - return statusBlock; -} - - -void CSVSettings::DataDisplayFormatPage::setupUi() -{ - - mAbstractBlocks << buildBlock (setupDataDisplay ("Record Status Display")); - mAbstractBlocks << buildBlock (setupDataDisplay ("Referenceable ID Type Display")); - - foreach (AbstractBlock *block, mAbstractBlocks) - { - connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)), - this, SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)) ); - } - - connect ( this, - SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &)), - &(CSMSettings::UserSettings::instance()), - SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &))); - -} - -void CSVSettings::DataDisplayFormatPage::initializeWidgets (const CSMSettings::SettingMap &settings) -{ - //iterate each item in each blocks in this section - //validate the corresponding setting against the defined valuelist if any. - for (AbstractBlockList::Iterator it_block = mAbstractBlocks.begin(); - it_block != mAbstractBlocks.end(); ++it_block) - (*it_block)->updateSettings (settings); -} diff --git a/apps/opencs/view/settings/datadisplayformatpage.hpp b/apps/opencs/view/settings/datadisplayformatpage.hpp deleted file mode 100755 index b785bbd238..0000000000 --- a/apps/opencs/view/settings/datadisplayformatpage.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef EDITORPAGE_HPP -#define EDITORPAGE_HPP - -#include "support.hpp" -#include "abstractpage.hpp" - -namespace CSVSettings -{ - class DataDisplayFormatPage : public AbstractPage - { - Q_OBJECT - - public: - explicit DataDisplayFormatPage(QWidget *parent = 0); - - void initializeWidgets (const CSMSettings::SettingMap &settings); - void setupUi(); - - private: - - /// User preference view of the record status delegate's icon / text setting - GroupBlockDef *setupDataDisplay(const QString &); - - signals: - - /// Signals up for changes to editor application-level settings - void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue); - - public slots: - }; -} - -#endif // EDITORPAGE_HPP diff --git a/apps/opencs/view/settings/editorpage.cpp b/apps/opencs/view/settings/editorpage.cpp deleted file mode 100644 index 153ac1551d..0000000000 --- a/apps/opencs/view/settings/editorpage.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "editorpage.hpp" -#include "groupblock.hpp" -#include "../../model/settings/usersettings.hpp" - -CSVSettings::EditorPage::EditorPage(QWidget* parent) : - AbstractPage("Display Format", parent) -{ - setupUi(); -} - -CSVSettings::GroupBlockDef *CSVSettings::EditorPage::setupRecordStatusDisplay() -{ - GroupBlockDef *statusBlock = new GroupBlockDef(QString("Record Status Display")); - - SettingsItemDef *statusItem = new SettingsItemDef (statusBlock->title, "Icon and Text"); - *(statusItem->valueList) << QString("Icon and Text") << QString("Icon Only") << QString("Text Only"); - - WidgetDef statusWidget (Widget_RadioButton); - statusWidget.valueList = statusItem->valueList; - - statusItem->widget = statusWidget; - - statusBlock->settingItems << statusItem; - - return statusBlock; -} - -void CSVSettings::EditorPage::setupUi() -{ - - mAbstractBlocks << buildBlock(setupRecordStatusDisplay()); - - foreach (AbstractBlock *block, mAbstractBlocks) - { - connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)), - this, SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)) ); - } - - connect ( this, - SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &)), - &(CSMSettings::UserSettings::instance()), - SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &))); - -} - -void CSVSettings::EditorPage::initializeWidgets (const CSMSettings::SettingMap &settings) -{ - //iterate each item in each blocks in this section - //validate the corresponding setting against the defined valuelist if any. - for (AbstractBlockList::Iterator it_block = mAbstractBlocks.begin(); - it_block != mAbstractBlocks.end(); ++it_block) - (*it_block)->updateSettings (settings); -} diff --git a/apps/opencs/view/settings/editorpage.hpp b/apps/opencs/view/settings/editorpage.hpp deleted file mode 100644 index 85215edabf..0000000000 --- a/apps/opencs/view/settings/editorpage.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef EDITORPAGE_HPP -#define EDITORPAGE_HPP - -#include "support.hpp" -#include "abstractpage.hpp" - -namespace CSVSettings -{ - class EditorPage : public AbstractPage - { - Q_OBJECT - - public: - explicit EditorPage(QWidget *parent = 0); - - void initializeWidgets (const CSMSettings::SettingMap &settings); - void setupUi(); - - private: - - /// User preference view of the record status delegate's icon / text setting - GroupBlockDef *setupRecordStatusDisplay(); - - signals: - - /// Signals up for changes to editor application-level settings - void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue); - - public slots: - }; -} - -#endif // EDITORPAGE_HPP diff --git a/apps/opencs/view/settings/groupblock.cpp b/apps/opencs/view/settings/groupblock.cpp deleted file mode 100644 index e31e526c03..0000000000 --- a/apps/opencs/view/settings/groupblock.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "groupblock.hpp" -#include "itemblock.hpp" - -CSVSettings::GroupBlock::GroupBlock (QWidget* parent) - : AbstractBlock (parent) -{} - -CSVSettings::GroupBlock::GroupBlock (bool isVisible, QWidget *parent) - : AbstractBlock (isVisible, parent) -{} - -int CSVSettings::GroupBlock::build (GroupBlockDef *def) -{ - - if (def->settingItems.size() == 0) - return -1; - - int retVal = 0; - - setVisible (def->isVisible); - - mBox->setLayout(createLayout (def->widgetOrientation, def->isZeroMargin)); - - setObjectName (def->title); - mBox->setTitle (def->title); - - foreach (SettingsItemDef *itemDef, def->settingItems) - { - ItemBlock *block = new ItemBlock (mBox); - - if (block->build (*itemDef) < 0) - { - retVal = -2; - break; - } - - mItemBlockList << block; - mBox->layout()->addWidget (block->getGroupBox()); - - connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)), - this, SLOT (slotUpdateSetting (const QString &, const QString &) )); - } - - return retVal; -} - -CSMSettings::SettingList *CSVSettings::GroupBlock::getSettings() -{ - CSMSettings::SettingList *settings = 0; - - foreach (ItemBlock *block, mItemBlockList) - { - if (!settings) - settings = new CSMSettings::SettingList(); - - settings->append(*(block->getSettings ())); - } - - return settings; -} - -CSVSettings::ItemBlock *CSVSettings::GroupBlock::getItemBlock (const QString &name, ItemBlockList *blockList) -{ - ItemBlock *retBlock = 0; - - if (!blockList) - blockList = &mItemBlockList; - - foreach (ItemBlock *block, *blockList) - { - if (block->objectName() == name) - { - retBlock = block; - break; - } - } - - return retBlock; -} - -CSVSettings::ItemBlock *CSVSettings::GroupBlock::getItemBlock (int index) -{ - ItemBlock *retBlock = 0; - - if (mItemBlockList.size() > index) - retBlock = mItemBlockList.at(index); - - return retBlock; -} - -bool CSVSettings::GroupBlock::updateSettings (const CSMSettings::SettingMap &settings) -{ - bool success = true; - - //update all non-proxy settings - foreach (ItemBlock *block, mItemBlockList) - { - CSMSettings::SettingContainer *setting = settings[block->objectName()]; - - if (setting) - { - bool success2 = block->update (setting->getValue()); - success = success && success2; - } - } - - return success; -} diff --git a/apps/opencs/view/settings/groupblock.hpp b/apps/opencs/view/settings/groupblock.hpp deleted file mode 100644 index 5c0754193c..0000000000 --- a/apps/opencs/view/settings/groupblock.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef GROUPBLOCK_HPP -#define GROUPBLOCK_HPP - -#include -#include "abstractblock.hpp" - -namespace CSVSettings -{ - class ItemBlock; - - /// Base class for group blocks. - /// Derived block classes should use CustomBlock - class GroupBlock : public AbstractBlock - { - ItemBlockList mItemBlockList; - - public: - GroupBlock (QWidget* parent = 0); - GroupBlock (bool isVisible, QWidget *parent = 0); - - /// build the gorup block based on passed definition - int build (GroupBlockDef *def); - - /// update settings local to the group block - bool updateSettings (const CSMSettings::SettingMap &settings); - - /// retrieve setting list local to the group block - CSMSettings::SettingList *getSettings(); - - /// retrieve item block by name from the passed list or local list - ItemBlock *getItemBlock (const QString &name, ItemBlockList *blockList = 0); - - /// retrieve the item block by index from the local list - ItemBlock *getItemBlock (int index); - - protected: - - /// create block layout based on passed definition - int buildLayout (GroupBlockDef &def); - - }; -} -#endif // GROUPBLOCK_HPP diff --git a/apps/opencs/view/settings/groupbox.cpp b/apps/opencs/view/settings/groupbox.cpp deleted file mode 100644 index da2cc25711..0000000000 --- a/apps/opencs/view/settings/groupbox.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "groupbox.hpp" - -const QString CSVSettings::GroupBox::INVISIBLE_BOX_STYLE = - QString::fromUtf8("QGroupBox { border: 0px; padding 0px; margin: 0px;}"); - -CSVSettings::GroupBox::GroupBox(QWidget *parent) : - QGroupBox (parent) -{ - initBox(); -} - -CSVSettings::GroupBox::GroupBox (bool isVisible, QWidget *parent) : - QGroupBox (parent) -{ - initBox(isVisible); -} - -void CSVSettings::GroupBox::initBox(bool isVisible) -{ - setFlat (true); - VISIBLE_BOX_STYLE = styleSheet(); - - if (!isVisible) - setStyleSheet (INVISIBLE_BOX_STYLE); -} - -bool CSVSettings::GroupBox::borderVisibile() const -{ - return (styleSheet() != INVISIBLE_BOX_STYLE); -} - -void CSVSettings::GroupBox::setTitle (const QString &title) -{ - if (borderVisibile() ) - { - QGroupBox::setTitle (title); - setMinimumWidth(); - } -} - -void CSVSettings::GroupBox::setBorderVisibility (bool value) -{ - if (value) - setStyleSheet(VISIBLE_BOX_STYLE); - else - setStyleSheet(INVISIBLE_BOX_STYLE); -} - -void CSVSettings::GroupBox::setMinimumWidth() -{ - //set minimum width to accommodate title, if needed - //1.5 multiplier to account for bold title. - QFontMetrics fm (font()); - int minWidth = fm.width(title()); - QGroupBox::setMinimumWidth (minWidth * 1.5); -} diff --git a/apps/opencs/view/settings/groupbox.hpp b/apps/opencs/view/settings/groupbox.hpp deleted file mode 100644 index 9d3a019363..0000000000 --- a/apps/opencs/view/settings/groupbox.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef GROUPBOX_HPP -#define GROUPBOX_HPP - -#include - -namespace CSVSettings -{ - /// Custom implementation of QGroupBox to be used with block classes - class GroupBox : public QGroupBox - { - static const QString INVISIBLE_BOX_STYLE; - QString VISIBLE_BOX_STYLE; //not a const... - - public: - explicit GroupBox (QWidget *parent = 0); - explicit GroupBox (bool isVisible, QWidget *parent = 0); - - void setTitle (const QString &title); - void setBorderVisibility (bool value); - bool borderVisibile() const; - - private: - void setMinimumWidth(); - void initBox(bool isVisible = true); - }; -} - -#endif // GROUPBOX_HPP diff --git a/apps/opencs/view/settings/itemblock.cpp b/apps/opencs/view/settings/itemblock.cpp deleted file mode 100644 index 9cb0ae1a1a..0000000000 --- a/apps/opencs/view/settings/itemblock.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "itemblock.hpp" - -#include - -CSVSettings::ItemBlock::ItemBlock (QWidget* parent) - : mSetting (0), AbstractBlock (false, parent) -{ -} - -int CSVSettings::ItemBlock::build(SettingsItemDef &iDef) -{ - buildItemBlock (iDef); - buildItemBlockWidgets (iDef); - - return 0; -} - -void CSVSettings::ItemBlock::buildItemBlockWidgets (SettingsItemDef &iDef) -{ - WidgetDef wDef = iDef.widget; - QLayout *blockLayout = 0; - QString defaultValue = iDef.defaultValue; - - switch (wDef.type) - { - - case Widget_CheckBox: - case Widget_RadioButton: - - foreach (QString item, *(iDef.valueList)) - { - wDef.caption = item; - wDef.isDefault = (item == defaultValue); - - blockLayout = buildWidget (item, wDef, blockLayout)->getLayout(); - } - - break; - - case Widget_ComboBox: - case Widget_ListBox: - - //assign the item's value list to the widget's value list. - //pass through to default to finish widget construction. - if (!wDef.valueList) - wDef.valueList = iDef.valueList; - - default: - //only one instance of this non-list widget type. - //Set it's value to the default value for the item and build the widget. - - if (wDef.value.isEmpty()) - wDef.value = iDef.defaultValue; - - buildWidget (iDef.name, wDef); - } -} - -void CSVSettings::ItemBlock::buildItemBlock (SettingsItemDef &iDef) -{ - QString defaultValue = iDef.defaultValue; - - setObjectName(iDef.name); - - mSetting = new CSMSettings::SettingsItem (objectName(), - iDef.hasMultipleValues, iDef.defaultValue, - parent()); - - if (iDef.valueList) - mSetting->setValueList(iDef.valueList); - - if (!iDef.minMax.isEmpty()) - mSetting->setValuePair(iDef.minMax); -} - - -bool CSVSettings::ItemBlock::update (const QString &value) -{ - bool success = updateItem (value); - - if (success) - signalUpdateWidget (value); - - return success; -} - - -bool CSVSettings::ItemBlock::updateItem (const QString &value) -{ - return mSetting->updateItem(value); -} - - -bool CSVSettings::ItemBlock::updateBySignal(const QString &name, const QString &value, bool &doEmit) -{ - bool success = (mSetting->getValue() != value); - - if (success) - success = updateItem(value); - - return success; -} - -CSMSettings::SettingList *CSVSettings::ItemBlock::getSettings () -{ - CSMSettings::SettingList *list = new CSMSettings::SettingList(); - list->push_back(mSetting); - - return list; -} - -QString CSVSettings::ItemBlock::getValue() const -{ - return mSetting->getValue(); -} diff --git a/apps/opencs/view/settings/itemblock.hpp b/apps/opencs/view/settings/itemblock.hpp deleted file mode 100644 index 2d1d45d418..0000000000 --- a/apps/opencs/view/settings/itemblock.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef ITEMBLOCK_HPP -#define ITEMBLOCK_HPP - -#include "abstractblock.hpp" - -namespace CSVSettings -{ - - class ItemBlock : public AbstractBlock - { - CSMSettings::SettingsItem *mSetting; - WidgetList mWidgetList; - - public: - - ItemBlock (QWidget* parent = 0); - - /// pure virtual function not implemented - bool updateSettings (const CSMSettings::SettingMap &settings) { return false; } - - CSMSettings::SettingList *getSettings (); - - QString getValue () const; - - /// item blocks encapsulate only one setting - int getSettingCount(); - - /// update setting value and corresponding widget - bool update (const QString &value); - - /// virtual construction function - int build(SettingsItemDef &iDef); - - private: - - /// custom construction function - void buildItemBlock (SettingsItemDef& iDef); - void buildItemBlockWidgets (SettingsItemDef& iDef); - - /// update the setting value - bool updateItem (const QString &); - - /// callback function triggered when update to application level is signalled - bool updateBySignal (const QString &name, const QString &value, bool &doEmit); - }; -} - -#endif // ITEMBLOCK_HPP diff --git a/apps/opencs/view/settings/proxyblock.cpp b/apps/opencs/view/settings/proxyblock.cpp deleted file mode 100644 index 81cc54fca5..0000000000 --- a/apps/opencs/view/settings/proxyblock.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include "proxyblock.hpp" -#include "itemblock.hpp" - -CSVSettings::ProxyBlock::ProxyBlock (QWidget *parent) - : GroupBlock (parent) -{ -} -int CSVSettings::ProxyBlock::build (GroupBlockDef *proxyDef) -{ - //get the list of pre-defined values for the proxy - mValueList = proxyDef->settingItems.at(0)->valueList; - - bool success = GroupBlock::build(proxyDef); - - //connect the item block of the proxy setting to the proxy-update slot - connect (getItemBlock(0), SIGNAL (signalUpdateSetting(const QString &, const QString &)), - this, SLOT (slotUpdateProxySetting (const QString &, const QString &))); - - return success; -} - -void CSVSettings::ProxyBlock::addSetting (ItemBlock *settingBlock, QStringList *proxyList) -{ - //connect the item block of the proxied seting to the generic update slot - connect (settingBlock, SIGNAL (signalUpdateSetting(const QString &, const QString &)), - this, SLOT (slotUpdateProxySetting(const QString &, const QString &))); - - mProxiedItemBlockList << settingBlock; - mProxyList << proxyList; -} - -bool CSVSettings::ProxyBlock::updateSettings (const CSMSettings::SettingMap &settings) -{ - return updateByProxiedSettings(&settings); -} - -bool CSVSettings::ProxyBlock::updateBySignal(const QString &name, const QString &value, bool &doEmit) -{ - doEmit = false; - return updateProxiedSettings(); -} - -void CSVSettings::ProxyBlock::slotUpdateProxySetting (const QString &name, const QString &value) -{ - updateByProxiedSettings(); -} - -bool CSVSettings::ProxyBlock::updateProxiedSettings() -{ - foreach (ItemBlock *block, mProxiedItemBlockList) - { - QString value = getItemBlock(0)->getValue(); - - bool success = false; - int i = 0; - - //find the value index of the selected value in the proxy setting - for (; i < mValueList->size(); ++i) - { - success = (value == mValueList->at(i)); - - if (success) - break; - } - - if (!success) - return false; - - // update the containing the proxied item's name - foreach (QStringList *list, mProxyList) - { - if ( list->at(0) == block->objectName()) - block->update (list->at(++i)); - } - } - - return true; -} - -bool CSVSettings::ProxyBlock::updateByProxiedSettings(const CSMSettings::SettingMap *settings) -{ - bool success = false; - int commonIndex = -1; - - //update all proxy settings based on values from non-proxies - foreach (QStringList *list, mProxyList) - { - //Iterate each proxy item's proxied setting list, getting the current values - //Compare those value indices. - //If indices match, they correlate to one of the proxy's values in it's value list - - //first value is always the name of the setting the proxy setting manages - QStringList::Iterator itProxyValue = list->begin(); - QString proxiedSettingName = (*itProxyValue); - QString proxiedSettingValue = ""; - itProxyValue++; - - if (!settings) - { - //get the actual setting value - ItemBlock *block = getProxiedItemBlock (proxiedSettingName); - - if (block) - proxiedSettingValue = block->getValue(); - } - else - proxiedSettingValue = (*settings)[proxiedSettingName]->getValue(); - - int j = 0; - - //iterate each value in the proxy string list - for (; itProxyValue != (list)->end(); ++itProxyValue) - { - success = ((*itProxyValue) == proxiedSettingValue); - - if (success) - break; - - j++; - } - - //break if no match was found - if ( !success ) - break; - - if (commonIndex != -1) - success = (commonIndex == j); - else - commonIndex = j; - - //break if indices were found, but mismatch - if (!success) - break; - } - - //if successful, the proxied setting values match a pre-defined value in the - //proxy's value list. Set the proxy to that value index - if (success) - { - ItemBlock *block = getItemBlock(0); - - if (block) - block->update (mValueList->at(commonIndex)); - } - - return success; -} - -CSVSettings::ItemBlock *CSVSettings::ProxyBlock::getProxiedItemBlock (const QString &name) -{ - return getItemBlock (name, &mProxiedItemBlockList); -} diff --git a/apps/opencs/view/settings/proxyblock.hpp b/apps/opencs/view/settings/proxyblock.hpp deleted file mode 100644 index 90fb9bc97e..0000000000 --- a/apps/opencs/view/settings/proxyblock.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef PROXYBLOCK_HPP -#define PROXYBLOCK_HPP - -#include "groupblock.hpp" - -namespace CSVSettings -{ - class ProxyBlock : public GroupBlock - { - Q_OBJECT - - /// TODO: Combine mProxyItemBlockList and mProxyList. - ItemBlockList mProxiedItemBlockList; - ProxyList mProxyList; - QStringList *mValueList; - - public: - - explicit ProxyBlock (QWidget *parent = 0); - explicit ProxyBlock (ItemBlock *proxyItemBlock, QWidget *parent = 0); - - /// Add a block that contains a proxied setting to the proxy block. - void addSetting (ItemBlock* settingBlock, QStringList *proxyList); - - int build (GroupBlockDef *def); - - CSMSettings::SettingList *getSettings() { return 0; } - - /// Update settings local to the proxy block pushed from application level - bool updateSettings (const CSMSettings::SettingMap &settings); - - /// callback function triggered when update to the application level is signaled. - bool updateBySignal (const QString &name, const QString &value, bool &doEmit); - - private: - - /// return the item block of a proxied setting - ItemBlock *getProxiedItemBlock (const QString &name); - - /// update the proxy setting with data from the proxied settings - bool updateByProxiedSettings(const CSMSettings::SettingMap *settings = 0); - - /// update proxied settings with data from the proxy setting - bool updateProxiedSettings(); - - private slots: - - void slotUpdateProxySetting (const QString &name, const QString &value); - - }; -} -#endif // PROXYBLOCK_HPP diff --git a/apps/opencs/view/settings/settingwidget.cpp b/apps/opencs/view/settings/settingwidget.cpp deleted file mode 100644 index 2c93986e75..0000000000 --- a/apps/opencs/view/settings/settingwidget.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "settingwidget.hpp" diff --git a/apps/opencs/view/settings/settingwidget.hpp b/apps/opencs/view/settings/settingwidget.hpp deleted file mode 100644 index 9f45136716..0000000000 --- a/apps/opencs/view/settings/settingwidget.hpp +++ /dev/null @@ -1,214 +0,0 @@ -#ifndef SETTINGWIDGET_HPP -#define SETTINGWIDGET_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "abstractwidget.hpp" - -namespace CSVSettings -{ - - /// Generic template for radiobuttons / checkboxes - template - class SettingWidget : public AbstractWidget - { - - T1 *mWidget; - - public: - - explicit SettingWidget (WidgetDef &def, QLayout *layout, QWidget* parent = 0) - : AbstractWidget (layout, parent), mWidget (new T1 (parent)) - { - mWidget->setText(def.caption); - build (mWidget, def, true); - mWidget->setChecked(def.isDefault); - - connect (mWidget, SIGNAL (toggled (bool)), - this, SLOT (slotUpdateItem (bool))); - } - - QWidget *widget() { return mWidget; } - - private: - - void updateWidget (const QString &value) - { - if ( value == mWidget->objectName() && !mWidget->isChecked() ) - mWidget->setChecked (true); - } - }; - - /// spin box template - template <> - class SettingWidget : public AbstractWidget - { - - QSpinBox *mWidget; - - public: - - SettingWidget (WidgetDef &def, QLayout *layout, QWidget *parent = 0) - : AbstractWidget (layout, parent), mWidget (new QSpinBox (parent)) - { - def.caption += tr(" (%1 to %2)").arg(def.minMax->left).arg(def.minMax->right); - - mWidget->setMaximum (def.minMax->right.toInt()); - mWidget->setMinimum (def.minMax->left.toInt()); - mWidget->setValue (def.value.toInt()); - - build (mWidget, def); - - connect (mWidget, SIGNAL (valueChanged (int)), - this, SLOT (slotUpdateItem (int))); - - mWidget->setAlignment (getAlignment(def.valueAlignment)); - - - } - - QWidget *widget() { return mWidget; } - - private: - - void updateWidget (const QString &value) - { - int intVal = value.toInt(); - - if (intVal >= mWidget->minimum() && intVal <= mWidget->maximum() && intVal != mWidget->value()) - mWidget->setValue (intVal); - } - - signals: - - }; - - /// combo box template - template <> - class SettingWidget : public CSVSettings::AbstractWidget - { - - QComboBox *mWidget; - - - public: - - explicit SettingWidget(WidgetDef &def, QLayout *layout, QWidget *parent = 0) - : AbstractWidget (layout, parent), mWidget (new QComboBox (parent)) - { - int i = 0; - - foreach (QString item, *(def.valueList)) - { - mWidget->addItem (item); - - if (item == def.value) - mWidget->setCurrentIndex(i); - - i++; - } - - build (mWidget, def); - - connect (mWidget, SIGNAL (currentIndexChanged (const QString &)), - this, SLOT (slotUpdateItem (const QString &))); - - //center the combo box items - mWidget->setEditable (true); - mWidget->lineEdit()->setReadOnly (true); - mWidget->lineEdit()->setAlignment (getAlignment(def.valueAlignment)); - - QFlags alignment = mWidget->lineEdit()->alignment(); - - for (int j = 0; j < mWidget->count(); j++) - mWidget->setItemData (j, QVariant(alignment), Qt::TextAlignmentRole); - } - - QWidget *widget() { return mWidget; } - - private: - - void updateWidget (const QString &value) - { - if (mWidget->currentText() != value) - mWidget->setCurrentIndex(mWidget->findText(value)); - } - - }; - - /// line edit template - template <> - class SettingWidget : public CSVSettings::AbstractWidget - { - - QLineEdit *mWidget; - - public: - - explicit SettingWidget(WidgetDef &def, QLayout *layout, QWidget *parent = 0) - : AbstractWidget (layout, parent), mWidget (new QLineEdit (parent)) - { - if (!def.inputMask.isEmpty()) - mWidget->setInputMask (def.inputMask); - - mWidget->setText (def.value); - - build (mWidget, def); - - connect (mWidget, SIGNAL (textChanged (const QString &)), - this, SLOT (slotUpdateItem (const QString &))); - - mWidget->setAlignment (getAlignment(def.valueAlignment)); - } - - QWidget *widget() { return mWidget; } - - void updateWidget (const QString &value) - { - if (mWidget->text() != value) - mWidget->setText(value); - } - }; - - /// list widget template - /// \todo Not fully implemented. Only widget supporting multi-valued settings - template <> - class SettingWidget : public CSVSettings::AbstractWidget - { - - QListWidget *mWidget; - - public: - - explicit SettingWidget(WidgetDef &def, QLayout *layout, QWidget *parent = 0 ) - : AbstractWidget (layout, parent), mWidget (new QListWidget (parent)) - { - int i = 0; - - foreach (QString item, *(def.valueList)) - { - mWidget->addItem (item); - - if (item == def.value) {} - i++; - } - build (mWidget, def); - } - - QWidget *widget() { return mWidget; } - - private: - void updateWidget (const QString &value) {} - }; - -} -#endif // SETTINGWIDGET_HPP diff --git a/apps/opencs/view/settings/toggleblock.cpp b/apps/opencs/view/settings/toggleblock.cpp deleted file mode 100644 index 3406a62c4d..0000000000 --- a/apps/opencs/view/settings/toggleblock.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "toggleblock.hpp" -#include "groupblock.hpp" -#include "groupbox.hpp" -#include "itemblock.hpp" - -CSVSettings::ToggleBlock::ToggleBlock(QWidget *parent) : - CustomBlock(parent) -{} - -int CSVSettings::ToggleBlock::build(CustomBlockDef *def) -{ - if (def->blockDefList.size()==0) - return -1; - - QList::Iterator it = def->blockDefList.begin(); - - //first def in the list is the def for the toggle block - GroupBlockDef *toggleDef = *it++; - - if (toggleDef->captions.size() != def->blockDefList.size()-1 ) - return -2; - - if (toggleDef->widgets.size() == 0) - return -3; - - //create the toogle block UI structure - QLayout *blockLayout = createLayout (def->blockOrientation, true); - GroupBox *propertyBox = buildGroupBox (toggleDef->widgetOrientation); - - mBox->setLayout(blockLayout); - mBox->setTitle (toggleDef->title); - - //build the blocks contained in the def list - //this manages proxy block construction. - //Any settings managed by the proxy setting - //must be included in the blocks defined in the list. - CustomBlock::build (def->blockDefList, &it); - - for (GroupBlockList::iterator it = mGroupList.begin(); it != mGroupList.end(); ++it) - propertyBox->layout()->addWidget ((*it)->getGroupBox()); - - //build togle widgets, linking them to the settings - GroupBox *toggleBox = buildToggleWidgets (toggleDef, def->defaultValue); - - blockLayout->addWidget(toggleBox); - blockLayout->addWidget(propertyBox); - blockLayout->setAlignment (propertyBox, Qt::AlignRight); - - return 0; -} - -CSVSettings::GroupBox *CSVSettings::ToggleBlock::buildToggleWidgets (GroupBlockDef *def, QString &defaultToggle) -{ - GroupBox *box = new GroupBox (false, getParent()); - - QLayout *layout = createLayout (def->widgetOrientation, true, static_cast(box)); - - for (int i = 0; i < def->widgets.size(); ++i) - { - QString caption = def->captions.at(i); - WidgetDef *wDef = def->widgets.at(i); - - wDef->caption = caption; - wDef->widgetAlignment = Align_Left; - - AbstractWidget *widg = buildWidget (caption, *wDef, layout, false); - - GroupBlock *block = mGroupList.at(i); - - //connect widget's update to the property block's enabled status - connect (widg->widget(), SIGNAL (toggled (bool)), block, SLOT (slotSetEnabled(bool))); - - //enable the default toggle option - block->getGroupBox()->setEnabled( caption == defaultToggle ); - - layout = widg->getLayout(); - } - - return box; -} diff --git a/apps/opencs/view/settings/toggleblock.hpp b/apps/opencs/view/settings/toggleblock.hpp deleted file mode 100644 index 4b6e8e344d..0000000000 --- a/apps/opencs/view/settings/toggleblock.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef TOGGLEBLOCK_HPP -#define TOGGLEBLOCK_HPP - -#include - -#include "customblock.hpp" - -namespace CSVSettings -{ - class GroupBlock; - class GroupBox; - class ToggleWidget; - class ItemBlock; - - class ToggleBlock : public CustomBlock - { - - public: - explicit ToggleBlock(QWidget *parent = 0); - - int build (CustomBlockDef *def); - - private: - /// Constructor for toggle widgets that are specific to toggle block - /// Widgets are not a part of the user preference settings - GroupBox *buildToggleWidgets (GroupBlockDef *def, QString &defaultToggle); - }; -} -#endif // TOGGLEBLOCK_HPP diff --git a/apps/opencs/view/settings/usersettingsdialog.cpp b/apps/opencs/view/settings/usersettingsdialog.cpp deleted file mode 100644 index e73e24dcb4..0000000000 --- a/apps/opencs/view/settings/usersettingsdialog.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "usersettingsdialog.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../model/settings/support.hpp" - -#include "datadisplayformatpage.hpp" -#include "windowpage.hpp" -#include "settingwidget.hpp" - -CSVSettings::UserSettingsDialog::UserSettingsDialog(QMainWindow *parent) : - QMainWindow (parent), mStackedWidget (0) -{ - setWindowTitle(QString::fromUtf8 ("User Settings")); - buildPages(); - setWidgetStates (); - - connect (mListWidget, - SIGNAL (currentItemChanged(QListWidgetItem*, QListWidgetItem*)), - this, - SLOT (slotChangePage (QListWidgetItem*, QListWidgetItem*))); - - QRect scr = QApplication::desktop()->screenGeometry(); - QRect rect = geometry(); - move (scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y()); -} - -CSVSettings::UserSettingsDialog::~UserSettingsDialog() -{ -} - -void CSVSettings::UserSettingsDialog::closeEvent (QCloseEvent *event) -{ - writeSettings(); -} - -void CSVSettings::UserSettingsDialog::setWidgetStates () -{ - CSMSettings::UserSettings::instance().loadSettings("opencs.cfg"); - - //iterate the tabWidget's pages (sections) - for (int i = 0; i < mStackedWidget->count(); i++) - { - //get the settings defined for the entire section - //and update widget - QString pageName = mStackedWidget->widget(i)->objectName(); - - const CSMSettings::SettingMap *settings = CSMSettings::UserSettings::instance().getSettings(pageName); - AbstractPage &page = getAbstractPage (i); - page.initializeWidgets(*settings); - } -} - -void CSVSettings::UserSettingsDialog::buildPages() -{ - //craete central widget with it's layout and immediate children - QWidget *centralWidget = new QWidget (this); - - mListWidget = new QListWidget (centralWidget); - mStackedWidget = new QStackedWidget (centralWidget); - - QGridLayout* dialogLayout = new QGridLayout(); - - mListWidget->setMinimumWidth(0); - mListWidget->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Expanding); - - mStackedWidget->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); - - dialogLayout->addWidget (mListWidget,0,0); - dialogLayout->addWidget (mStackedWidget,0,1, Qt::AlignTop); - - centralWidget->setLayout (dialogLayout); - - setCentralWidget (centralWidget); - setDockOptions (QMainWindow::AllowNestedDocks); - - createPage(); - createPage(); - -} - -void CSVSettings::UserSettingsDialog::writeSettings() -{ - QMap settings; - - for (int i = 0; i < mStackedWidget->count(); ++i) - { - AbstractPage &page = getAbstractPage (i); - settings [page.objectName()] = page.getSettings(); - } - CSMSettings::UserSettings::instance().writeSettings(settings); -} - -CSVSettings::AbstractPage &CSVSettings::UserSettingsDialog::getAbstractPage (int index) -{ - return dynamic_cast (*(mStackedWidget->widget (index))); -} - -void CSVSettings::UserSettingsDialog::slotChangePage(QListWidgetItem *current, QListWidgetItem *previous) -{ - if (!current) - current = previous; - - if (!(current == previous)) - mStackedWidget->setCurrentIndex (mListWidget->row(current)); -} diff --git a/apps/opencs/view/settings/usersettingsdialog.hpp b/apps/opencs/view/settings/usersettingsdialog.hpp deleted file mode 100644 index 3b3fa5b79e..0000000000 --- a/apps/opencs/view/settings/usersettingsdialog.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef USERSETTINGSDIALOG_H -#define USERSETTINGSDIALOG_H - -#include -#include -#include -#include - -#include "../../model/settings/usersettings.hpp" -#include "../../model/settings/support.hpp" - -class QHBoxLayout; -class AbstractWidget; -class QStackedWidget; -class QListWidget; - -namespace CSVSettings { - - class AbstractPage; - - class UserSettingsDialog : public QMainWindow - { - Q_OBJECT - - QListWidget *mListWidget; - QStackedWidget *mStackedWidget; - - public: - UserSettingsDialog(QMainWindow *parent = 0); - ~UserSettingsDialog(); - - private: - - /// Settings are written on close - void closeEvent (QCloseEvent *event); - - /// return the setting page by name - /// performs dynamic cast to AbstractPage * - AbstractPage &getAbstractPage (int index); - void setWidgetStates (); - void buildPages(); - void writeSettings(); - - /// Templated function to create a custom user preference page - template - void createPage () - { - T *page = new T(mStackedWidget); - - mStackedWidget->addWidget (&dynamic_cast(*page)); - - new QListWidgetItem (page->objectName(), mListWidget); - - //finishing touches - QFontMetrics fm (QApplication::font()); - int textWidth = fm.width(page->objectName()); - - if ((textWidth + 50) > mListWidget->minimumWidth()) - mListWidget->setMinimumWidth(textWidth + 50); - - resize (mStackedWidget->sizeHint()); - } - - public slots: - - /// Called when a different page is selected in the left-hand list widget - void slotChangePage (QListWidgetItem*, QListWidgetItem*); - }; - -} -#endif // USERSETTINGSDIALOG_H diff --git a/apps/opencs/view/settings/windowpage.cpp b/apps/opencs/view/settings/windowpage.cpp deleted file mode 100644 index ae42623b78..0000000000 --- a/apps/opencs/view/settings/windowpage.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "windowpage.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef Q_OS_MAC -#include -#endif - -#include "../../model/settings/usersettings.hpp" -#include "groupblock.hpp" -#include "toggleblock.hpp" -#include "../../view/settings/abstractblock.hpp" - -CSVSettings::WindowPage::WindowPage(QWidget *parent): - AbstractPage("Window Size", parent) -{ - // Hacks to get the stylesheet look properly -#ifdef Q_OS_MAC - QPlastiqueStyle *style = new QPlastiqueStyle; - //profilesComboBox->setStyle(style); -#endif - - setupUi(); -} - -CSVSettings::GroupBlockDef * CSVSettings::WindowPage::buildDefinedWindowSize() -{ - GroupBlockDef *block = new GroupBlockDef ( "Defined Size"); - - SettingsItemDef *widthByHeightItem = new SettingsItemDef ("Window Size", "640x480"); - WidgetDef widthByHeightWidget = WidgetDef (Widget_ComboBox); - widthByHeightWidget.widgetWidth = 90; - *(widthByHeightItem->valueList) << "640x480" << "800x600" << "1024x768" << "1440x900"; - - QStringList *widthProxy = new QStringList; - QStringList *heightProxy = new QStringList; - - (*widthProxy) << "Width" << "640" << "800" << "1024" << "1440"; - (*heightProxy) << "Height" << "480" << "600" << "768" << "900"; - - *(widthByHeightItem->proxyList) << widthProxy << heightProxy; - - widthByHeightItem->widget = widthByHeightWidget; - - block->settingItems << widthByHeightItem; - block->isProxy = true; - block->isVisible = false; - - return block; -} - -CSVSettings::GroupBlockDef *CSVSettings::WindowPage::buildCustomWindowSize() -{ - GroupBlockDef *block = new GroupBlockDef ("Custom Size"); - - //custom width - SettingsItemDef *widthItem = new SettingsItemDef ("Width", "640"); - widthItem->widget = WidgetDef (Widget_LineEdit); - widthItem->widget.widgetWidth = 45; - widthItem->widget.inputMask = "9999"; - - //custom height - SettingsItemDef *heightItem = new SettingsItemDef ("Height", "480"); - heightItem->widget = WidgetDef (Widget_LineEdit); - heightItem->widget.widgetWidth = 45; - heightItem->widget.caption = "x"; - heightItem->widget.inputMask = "9999"; - - block->settingItems << widthItem << heightItem; - block->widgetOrientation = Orient_Horizontal; - block->isVisible = false; - - return block; -} - -CSVSettings::GroupBlockDef *CSVSettings::WindowPage::buildWindowSizeToggle() -{ - GroupBlockDef *block = new GroupBlockDef (objectName()); - - // window size toggle - block->captions << "Pre-Defined" << "Custom"; - block->widgetOrientation = Orient_Vertical; - block->isVisible = false; - - //define a widget for each group in the toggle - for (int i = 0; i < 2; i++) - block->widgets << new WidgetDef (Widget_RadioButton); - - block->widgets.at(0)->isDefault = false; - - return block; -} - -CSVSettings::CustomBlockDef *CSVSettings::WindowPage::buildWindowSize(GroupBlockDef *toggle_def, - GroupBlockDef *defined_def, - GroupBlockDef *custom_def) -{ - CustomBlockDef *block = new CustomBlockDef(QString ("Window Size")); - - block->blockDefList << toggle_def << defined_def << custom_def; - block->defaultValue = "Custom"; - - return block; - -} - -void CSVSettings::WindowPage::setupUi() -{ - CustomBlockDef *windowSize = buildWindowSize(buildWindowSizeToggle(), - buildDefinedWindowSize(), - buildCustomWindowSize() - ); - - mAbstractBlocks << buildBlock (windowSize); - - foreach (AbstractBlock *block, mAbstractBlocks) - { - connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)), - this, SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)) ); - } - - connect ( this, - SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &)), - &(CSMSettings::UserSettings::instance()), - SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &))); - -} - - -void CSVSettings::WindowPage::initializeWidgets (const CSMSettings::SettingMap &settings) -{ - //iterate each item in each blocks in this section - //validate the corresponding setting against the defined valuelist if any. - for (AbstractBlockList::Iterator it_block = mAbstractBlocks.begin(); - it_block != mAbstractBlocks.end(); ++it_block) - (*it_block)->updateSettings (settings); -} diff --git a/apps/opencs/view/settings/windowpage.hpp b/apps/opencs/view/settings/windowpage.hpp deleted file mode 100644 index 2f28306256..0000000000 --- a/apps/opencs/view/settings/windowpage.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef WINDOWPAGE_H -#define WINDOWPAGE_H - -#include "abstractpage.hpp" - -class QGroupBox; - -namespace CSVSettings { - - class UserSettings; - class AbstractBlock; - - class WindowPage : public AbstractPage - { - Q_OBJECT - - public: - - WindowPage(QWidget *parent = 0); - - void setupUi(); - void initializeWidgets (const CSMSettings::SettingMap &settings); - - /// - GroupBlockDef *buildCustomWindowSize(); - GroupBlockDef *buildDefinedWindowSize(); - GroupBlockDef *buildWindowSizeToggle(); - CustomBlockDef *buildWindowSize (GroupBlockDef *, GroupBlockDef *, GroupBlockDef *); - - signals: - void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue); - }; -} -#endif //WINDOWPAGE_H From 331df17b48fb66f9305aea041a094f919df5707c Mon Sep 17 00:00:00 2001 From: graffy76 Date: Tue, 22 Apr 2014 22:17:19 -0500 Subject: [PATCH 109/135] Added new framework files --- apps/opencs/model/settings/connector.cpp | 127 +++++++ apps/opencs/model/settings/connector.hpp | 55 +++ apps/opencs/model/settings/setting.cpp | 281 +++++++++++++++ apps/opencs/model/settings/setting.hpp | 119 +++++++ apps/opencs/model/settings/settingmanager.cpp | 330 ++++++++++++++++++ apps/opencs/model/settings/settingmanager.hpp | 82 +++++ apps/opencs/view/settings/booleanview.cpp | 91 +++++ apps/opencs/view/settings/booleanview.hpp | 44 +++ apps/opencs/view/settings/dialog.cpp | 132 +++++++ apps/opencs/view/settings/dialog.hpp | 55 +++ apps/opencs/view/settings/frame.cpp | 103 ++++++ apps/opencs/view/settings/frame.hpp | 58 +++ apps/opencs/view/settings/listview.cpp | 106 ++++++ apps/opencs/view/settings/listview.hpp | 63 ++++ apps/opencs/view/settings/page.cpp | 88 +++++ apps/opencs/view/settings/page.hpp | 54 +++ .../view/settings/resizeablestackedwidget.cpp | 40 +++ .../view/settings/resizeablestackedwidget.hpp | 23 ++ apps/opencs/view/settings/settingwindow.cpp | 114 ++++++ apps/opencs/view/settings/settingwindow.hpp | 49 +++ apps/opencs/view/settings/textview.cpp | 73 ++++ apps/opencs/view/settings/textview.hpp | 56 +++ apps/opencs/view/settings/view.cpp | 218 ++++++++++++ apps/opencs/view/settings/view.hpp | 163 +++++++++ 24 files changed, 2524 insertions(+) create mode 100644 apps/opencs/model/settings/connector.cpp create mode 100644 apps/opencs/model/settings/connector.hpp create mode 100644 apps/opencs/model/settings/setting.cpp create mode 100644 apps/opencs/model/settings/setting.hpp create mode 100644 apps/opencs/model/settings/settingmanager.cpp create mode 100644 apps/opencs/model/settings/settingmanager.hpp create mode 100644 apps/opencs/view/settings/booleanview.cpp create mode 100644 apps/opencs/view/settings/booleanview.hpp create mode 100644 apps/opencs/view/settings/dialog.cpp create mode 100644 apps/opencs/view/settings/dialog.hpp create mode 100644 apps/opencs/view/settings/frame.cpp create mode 100644 apps/opencs/view/settings/frame.hpp create mode 100644 apps/opencs/view/settings/listview.cpp create mode 100644 apps/opencs/view/settings/listview.hpp create mode 100644 apps/opencs/view/settings/page.cpp create mode 100644 apps/opencs/view/settings/page.hpp create mode 100644 apps/opencs/view/settings/resizeablestackedwidget.cpp create mode 100644 apps/opencs/view/settings/resizeablestackedwidget.hpp create mode 100644 apps/opencs/view/settings/settingwindow.cpp create mode 100644 apps/opencs/view/settings/settingwindow.hpp create mode 100644 apps/opencs/view/settings/textview.cpp create mode 100644 apps/opencs/view/settings/textview.hpp create mode 100644 apps/opencs/view/settings/view.cpp create mode 100644 apps/opencs/view/settings/view.hpp diff --git a/apps/opencs/model/settings/connector.cpp b/apps/opencs/model/settings/connector.cpp new file mode 100644 index 0000000000..05a9ba8f96 --- /dev/null +++ b/apps/opencs/model/settings/connector.cpp @@ -0,0 +1,127 @@ +#include "connector.hpp" +#include "../../view/settings/view.hpp" +#include "../../view/settings/page.hpp" + +CSMSettings::Connector::Connector(CSVSettings::View *master, + QObject *parent) + : mMasterView (master), QObject(parent) +{} + +void CSMSettings::Connector::addSlaveView (CSVSettings::View *view, + QList &masterProxyValues) +{ + mSlaveViews.append (view); + + mProxyListMap[view->viewKey()].append (masterProxyValues); +} + +QList CSMSettings::Connector::getSlaveViewValues() const +{ + QList list; + + foreach (const CSVSettings::View *view, mSlaveViews) + list.append (view->selectedValues()); + + return list; +} + +bool CSMSettings::Connector::proxyListsMatch ( + const QList &list1, + const QList &list2) const +{ + bool success = true; + + for (int i = 0; i < list1.size(); i++) + { + success = stringListsMatch (list1.at(i), list2.at(i)); + + if (!success) + break; + } + return success; +} + +void CSMSettings::Connector::slotUpdateMaster() const +{ + //list of the current values for each slave. + QList slaveValueList = getSlaveViewValues(); + + int masterColumn = -1; + + /* + * A row in the master view is one of the values in the + * master view's data model. This corresponds directly to the number of + * values in a proxy list contained in the ProxyListMap member. + * Thus, we iterate each "column" in the master proxy list + * (one for each vlaue in the master. Each column represents + * one master value's corresponding list of slave values. We examine + * each master value's list, comparing it to the current slave value list, + * stopping when we find a match using proxyListsMatch(). + * + * If no match is found, clear the master view's value + */ + + for (int i = 0; i < mMasterView->rowCount(); i++) + { + QList proxyValueList; + + foreach (const QString &settingKey, mProxyListMap.keys()) + { + // append the proxy value list stored in the i'th column + // for each setting key. A setting key is the id of the setting + // in page.name format. + proxyValueList.append (mProxyListMap.value(settingKey).at(i)); + } + + if (proxyListsMatch (slaveValueList, proxyValueList)) + { + masterColumn = i; + break; + } + } + + QString masterValue = mMasterView->value (masterColumn); + mMasterView->setSelectedValue (masterValue); +} + +void CSMSettings::Connector::slotUpdateSlaves() const +{ + int row = mMasterView->currentIndex(); + + if (row == -1) + return; + + //iterate the proxy lists for the chosen master index + //and pass the list to each slave for updating + for (int i = 0; i < mSlaveViews.size(); i++) + { + QList proxyList = + mProxyListMap.value(mSlaveViews.at(i)->viewKey()); + + mSlaveViews.at(i)->setSelectedValues (proxyList.at(row)); + } +} + +bool CSMSettings::Connector::stringListsMatch ( + const QStringList &list1, + const QStringList &list2) const +{ + //returns a "sloppy" match, verifying that each list contains all the same + //items, though not necessarily in the same order. + + if (list1.size() != list2.size()) + return false; + + QStringList tempList(list2); + + //iterate each value in the list, removing one occurrence of the value in + //the other list. If no corresponding value is found, test fails + foreach (const QString &value, list1) + { + if (!tempList.contains(value)) + return false; + + tempList.removeOne(value); + } + return true; +} diff --git a/apps/opencs/model/settings/connector.hpp b/apps/opencs/model/settings/connector.hpp new file mode 100644 index 0000000000..da4c36d44c --- /dev/null +++ b/apps/opencs/model/settings/connector.hpp @@ -0,0 +1,55 @@ +#ifndef CSMSETTINGS_CONNECTOR_HPP +#define CSMSETTINGS_CONNECTOR_HPP + +#include +#include +#include +#include + +#include "support.hpp" + +namespace CSVSettings { + class View; +} + +namespace CSMSettings { + + class Connector : public QObject + { + Q_OBJECT + + CSVSettings::View *mMasterView; + + //map using the view pointer as a key to it's index value + QList mSlaveViews; + + //list of proxy values for each master value. + //value list order is indexed to the master value index. + QMap < QString, QList > mProxyListMap; + + public: + explicit Connector(CSVSettings::View *master, + QObject *parent = 0); + + void setMasterView (CSVSettings::View *view); + void addSlaveView (CSVSettings::View *view, + QList &masterProxyValues); + + private: + + bool proxyListsMatch (const QList &list1, + const QList &list2) const; + + bool stringListsMatch (const QStringList &list1, + const QStringList &list2) const; + + QList getSlaveViewValues() const; + + public slots: + + void slotUpdateSlaves() const; + void slotUpdateMaster() const; + }; +} + +#endif // CSMSETTINGS_CONNECTOR_HPP diff --git a/apps/opencs/model/settings/setting.cpp b/apps/opencs/model/settings/setting.cpp new file mode 100644 index 0000000000..6c7e780879 --- /dev/null +++ b/apps/opencs/model/settings/setting.cpp @@ -0,0 +1,281 @@ +#include "setting.hpp" +#include "support.hpp" + +CSMSettings::Setting::Setting() +{ + buildDefaultSetting(); +} + +CSMSettings::Setting::Setting(SettingType typ, const QString &settingName, + const QString &pageName, const QStringList &values) + : mIsEditorSetting (false) +{ + buildDefaultSetting(); + + int vType = static_cast (typ); + + if ((vType % 2) == 0) + setProperty (Property_IsMultiValue, + QVariant(true).toString()); + else + vType--; + + setProperty (Property_ViewType, QVariant (vType / 2).toString()); + setProperty (Property_Page, pageName); + setProperty (Property_Name, settingName); + setProperty (Property_DeclaredValues, values); +} + +void CSMSettings::Setting::buildDefaultSetting() +{ + int arrLen = sizeof(sPropertyDefaults) / sizeof (*sPropertyDefaults); + + for (int i = 0; i < arrLen; i++) + { + QStringList propertyList; + + if (i list; + + foreach (const QString &val, vals) + list << (QStringList() << val); + + mProxies [setting->page() + '.' + setting->name()] = list; +} + +void CSMSettings::Setting::addProxy (const Setting *setting, + const QList &list) +{ + if (serializable()) + setProperty (Property_Serializable, false); + + mProxies [setting->page() + '.' + setting->name()] = list; +} + +void CSMSettings::Setting::setColumnSpan (int value) +{ + setProperty (Property_ColumnSpan, value); +} + +int CSMSettings::Setting::columnSpan() const +{ + return property (Property_ColumnSpan).at(0).toInt(); +} + +QStringList CSMSettings::Setting::declaredValues() const +{ + return property (Property_DeclaredValues); +} + +void CSMSettings::Setting::setDefinedValues (QStringList list) +{ + setProperty (Property_DefinedValues, list); +} + +QStringList CSMSettings::Setting::definedValues() const +{ + return property (Property_DefinedValues); +} + +QStringList CSMSettings::Setting::property (SettingProperty prop) const +{ + if (prop >= mProperties.size()) + return QStringList(); + + return mProperties.at(prop); +} + +void CSMSettings::Setting::setDefaultValue (const QString &value) +{ + setDefaultValues (QStringList() << value); +} + +void CSMSettings::Setting::setDefaultValues (const QStringList &values) +{ + setProperty (Property_DefaultValues, values); +} + +QStringList CSMSettings::Setting::defaultValues() const +{ + return property (Property_DefaultValues); +} + +void CSMSettings::Setting::setDelimiter (const QString &value) +{ + setProperty (Property_Delimiter, value); +} + +QString CSMSettings::Setting::delimiter() const +{ + return property (Property_Delimiter).at(0); +} + +void CSMSettings::Setting::setEditorSetting(bool state) +{ + mIsEditorSetting = true; +} + +bool CSMSettings::Setting::isEditorSetting() const +{ + return mIsEditorSetting; +} +void CSMSettings::Setting::setIsMultiLine (bool state) +{ + setProperty (Property_IsMultiLine, state); +} + +bool CSMSettings::Setting::isMultiLine() const +{ + return (property (Property_IsMultiLine).at(0) == "true"); +} + +void CSMSettings::Setting::setIsMultiValue (bool state) +{ + setProperty (Property_IsMultiValue, state); +} + +bool CSMSettings::Setting::isMultiValue() const +{ + return (property (Property_IsMultiValue).at(0) == "true"); +} + +const CSMSettings::ProxyValueMap &CSMSettings::Setting::proxyLists() const +{ + return mProxies; +} + +void CSMSettings::Setting::setSerializable (bool state) +{ + setProperty (Property_Serializable, state); +} + +bool CSMSettings::Setting::serializable() const +{ + return (property (Property_Serializable).at(0) == "true"); +} + +void CSMSettings::Setting::setName (const QString &value) +{ + setProperty (Property_Name, value); +} + +QString CSMSettings::Setting::name() const +{ + return property (Property_Name).at(0); +} + +void CSMSettings::Setting::setPage (const QString &value) +{ + setProperty (Property_Page, value); +} + +QString CSMSettings::Setting::page() const +{ + return property (Property_Page).at(0); +} + +void CSMSettings::Setting::setRowSpan (const int value) +{ + setProperty (Property_RowSpan, value); +} + +int CSMSettings::Setting::rowSpan () const +{ + return property (Property_RowSpan).at(0).toInt(); +} + +void CSMSettings::Setting::setViewType (int vType) +{ + setProperty (Property_ViewType, vType); +} + +CSVSettings::ViewType CSMSettings::Setting::viewType() const +{ + return static_cast + (property(Property_ViewType).at(0).toInt()); +} + +void CSMSettings::Setting::setViewColumn (int value) +{ + setProperty (Property_ViewColumn, value); +} + +int CSMSettings::Setting::viewColumn() const +{ + return property (Property_ViewColumn).at(0).toInt(); +} + +void CSMSettings::Setting::setViewLocation (int row, int column) +{ + setViewRow (row); + setViewColumn (column); +} + +void CSMSettings::Setting::setViewRow (int value) +{ + setProperty (Property_ViewRow, value); +} + +int CSMSettings::Setting::viewRow() const +{ + return property (Property_ViewRow).at(0).toInt(); +} + +void CSMSettings::Setting::setWidgetWidth (int value) +{ + setProperty (Property_WidgetWidth, value); +} + +int CSMSettings::Setting::widgetWidth() const +{ + return property (Property_WidgetWidth).at(0).toInt(); +} +void CSMSettings::Setting::setProperty (SettingProperty prop, bool value) +{ + setProperty (prop, QStringList() << QVariant (value).toString()); +} + +void CSMSettings::Setting::setProperty (SettingProperty prop, int value) +{ + setProperty (prop, QStringList() << QVariant (value).toString()); +} + +void CSMSettings::Setting::setProperty (SettingProperty prop, + const QString &value) +{ + setProperty (prop, QStringList() << value); +} + +void CSMSettings::Setting::setProperty (SettingProperty prop, + const QStringList &value) +{ + if (prop < mProperties.size()) + mProperties.replace (prop, value); +} + +QDataStream &operator <<(QDataStream &stream, const CSMSettings::Setting& setting) +{ + stream << setting.properties(); + + stream << setting.proxies(); + return stream; +} + +QDataStream &operator >>(QDataStream& stream, CSMSettings::Setting& setting) +{ + // stream >> setting.properties(); + // stream >> setting.proxies(); + return stream; +} diff --git a/apps/opencs/model/settings/setting.hpp b/apps/opencs/model/settings/setting.hpp new file mode 100644 index 0000000000..1463e4d7d2 --- /dev/null +++ b/apps/opencs/model/settings/setting.hpp @@ -0,0 +1,119 @@ +#ifndef CSMSETTINGS_SETTING_HPP +#define CSMSETTINGS_SETTING_HPP + +#include +#include +#include "support.hpp" + +namespace CSMSettings +{ + //Maps setting id ("page.name") to a list of corresponding proxy values. + //Order of proxy value stringlists corresponds to order of master proxy's + //values in it's declared value list + typedef QMap > ProxyValueMap; + + class Setting + { + QList mProperties; + QStringList mDefaults; + + bool mIsEditorSetting; + + //QString is the setting id in the form of "page.name" + //QList is a list of stringlists of proxy values. + //Order is important! Proxy stringlists are matched against + //master values by their position in the QList. + ProxyValueMap mProxies; + + public: + + + explicit Setting(); + + explicit Setting(SettingType typ, const QString &settingName, + const QString &pageName, + const QStringList &values = QStringList()); + + void addProxy (const Setting *setting, const QStringList &vals); + void addProxy (const Setting *setting, const QList &list); + + const QList &properties() const { return mProperties; } + const ProxyValueMap &proxies() const { return mProxies; } + + void setColumnSpan (int value); + int columnSpan() const; + + void setDeclaredValues (QStringList list); + QStringList declaredValues() const; + + void setDefinedValues (QStringList list); + QStringList definedValues() const; + + void setDefaultValue (const QString &value); + + void setDefaultValues (const QStringList &values); + QStringList defaultValues() const; + + void setDelimiter (const QString &value); + QString delimiter() const; + + void setEditorSetting (bool state); + bool isEditorSetting() const; + + void setIsMultiLine (bool state); + bool isMultiLine() const; + + void setIsMultiValue (bool state); + bool isMultiValue() const; + + void setName (const QString &value); + QString name() const; + + void setPage (const QString &value); + QString page() const; + + void setRowSpan (const int value); + int rowSpan() const; + + const ProxyValueMap &proxyLists() const; + + void setSerializable (bool state); + bool serializable() const; + + void setViewColumn (int value); + int viewColumn() const; + + void setViewLocation (int row = -1, int column = -1); + + void setViewRow (int value); + int viewRow() const; + + void setViewType (int vType); + CSVSettings::ViewType viewType() const; + + void setWidgetWidth (int value); + int widgetWidth() const; + + ///returns the specified property value + QStringList property (SettingProperty prop) const; + + ///boilerplate code to convert setting values of common types + void setProperty (SettingProperty prop, bool value); + void setProperty (SettingProperty prop, int value); + void setProperty (SettingProperty prop, const QString &value); + void setProperty (SettingProperty prop, const QStringList &value); + + void addProxy (Setting* setting, + QMap &proxyMap); + + protected: + void buildDefaultSetting(); + }; +} + +Q_DECLARE_METATYPE(CSMSettings::Setting) + +QDataStream &operator <<(QDataStream &stream, const CSMSettings::Setting& setting); +QDataStream &operator >>(QDataStream &stream, CSMSettings::Setting& setting); + +#endif // CSMSETTINGS_SETTING_HPP diff --git a/apps/opencs/model/settings/settingmanager.cpp b/apps/opencs/model/settings/settingmanager.cpp new file mode 100644 index 0000000000..70b91ee40a --- /dev/null +++ b/apps/opencs/model/settings/settingmanager.cpp @@ -0,0 +1,330 @@ +#include +#include +#include +#include +#include + +#include "setting.hpp" +#include "settingmanager.hpp" + +CSMSettings::SettingManager::SettingManager(QObject *parent) : + QObject(parent) +{ + mReadWriteMessage = QObject::tr("
Could not open or create file for \ + writing

Please make sure you have the right\ + permissions and try again.
"); + + mReadOnlyMessage = QObject::tr("
Could not open file for \ + reading

Please make sure you have the \ + right permissions and try again.
"); + +} + +void CSMSettings::SettingManager::dumpModel() +{ + foreach (Setting *setting, mSettings) + { + if (setting->proxyLists().isEmpty()) + continue; + } +} + +CSMSettings::Setting *CSMSettings::SettingManager::createSetting + (CSMSettings::SettingType typ, const QString &page, const QString &name, + const QStringList &values) +{ + //get list of all settings for the current setting name + if (findSetting (page, name)) + { + qWarning() << "Duplicate declaration encountered: " + << (name + '.' + page); + return 0; + } + + Setting *setting = new Setting (typ, name, page, values); + + //add declaration to the model + mSettings.append (setting); + + return setting; +} + +CSMSettings::DefinitionPageMap + CSMSettings::SettingManager::readFilestream (QTextStream *stream) +{ + //regEx's for page names and keys / values + QRegExp pageRegEx ("^\\[([^]]+)\\]"); + QRegExp keyRegEx ("^([^=]+)\\s*=\\s*(.+)$"); + + QString currPage = "Unassigned"; + + DefinitionPageMap pageMap; + + if (!stream) + { + displayFileErrorMessage(mReadWriteMessage, false); + return pageMap; + } + + if (stream->atEnd()) + return pageMap; + + DefinitionMap *settingMap = new DefinitionMap(); + pageMap[currPage] = settingMap; + + while (!stream->atEnd()) + { + QString line = stream->readLine().simplified(); + + if (line.isEmpty() || line.startsWith("#")) + continue; + + //page name found + if (pageRegEx.exactMatch(line)) + { + currPage = pageRegEx.cap(1).simplified().trimmed(); + settingMap = new DefinitionMap(); + pageMap[currPage] = settingMap; + continue; + } + + //setting definition found + if ( (keyRegEx.indexIn(line) != -1)) + { + QString settingName = keyRegEx.cap(1).simplified(); + QString settingValue = keyRegEx.cap(2).simplified(); + + if (!settingMap->contains (settingName)) + settingMap->insert (settingName, new QStringList()); + + settingMap->value(settingName)->append(settingValue); + } + } + + //return empty map if no settings were ever added to + if (pageMap.size() == 1) + { + QString pageKey = pageMap.keys().at(0); + if (pageMap[pageKey]->size() == 0) + pageMap.clear(); + } + + return pageMap; +} + +bool CSMSettings::SettingManager::writeFilestream(QTextStream *stream, + const QMap &settingListMap) +{ + if (!stream) + { + displayFileErrorMessage(mReadWriteMessage, false); + return false; + } + //disabled after rolling selector class into view. Need to + //iterate views to get setting definitions before writing to file + + QStringList sectionKeys; + + foreach (const QString &key, settingListMap.keys()) + { + QStringList names = key.split('.'); + QString section = names.at(0); + + if (!sectionKeys.contains(section)) + if (!settingListMap.value(key).isEmpty()) + sectionKeys.append (section); + } + + foreach (const QString §ion, sectionKeys) + { + *stream << '[' << section << "]\n"; + foreach (const QString &key, settingListMap.keys()) + { + QStringList names = key.split('.'); + + if (names.at(0) != section) + continue; + + QStringList list = settingListMap.value(key); + + if (list.isEmpty()) + continue; + + QString name = names.at(1); + + foreach (const QString value, list) + { + if (value.isEmpty()) + continue; + + *stream << name << " = " << value << '\n'; + } + } + } + + destroyStream (stream); + return true; +} + +void CSMSettings::SettingManager::mergeSettings(DefinitionPageMap &destMap, DefinitionPageMap &srcMap) +{ + if (srcMap.isEmpty()) + return; + + foreach (const QString &pageKey, srcMap.keys()) + { + DefinitionMap *srcSetting = srcMap.value(pageKey); + //Unique Page: + //insertfrom the source map + if (!destMap.keys().contains (pageKey)) + { + destMap.insert (pageKey, srcSetting); + continue; + } + + DefinitionMap *destSetting = destMap.value(pageKey); + + //Duplicate Page: + //iterate the settings in the source and check for duplicates in the + //destination + foreach (const QString &srcKey, srcSetting->keys()) + { + //insert into destination if unique + if (!destSetting->keys().contains (srcKey)) + destSetting->insert(srcKey, srcSetting->value (srcKey)); + } + } +} + +QTextStream *CSMSettings::SettingManager::openFilestream (const QString &filePath, + bool isReadOnly) const +{ + QIODevice::OpenMode openFlags = QIODevice::Text; + + if (isReadOnly) + openFlags = QIODevice::ReadOnly | openFlags; + else + openFlags = QIODevice::ReadWrite | QIODevice::Truncate | openFlags; + + QFile *file = new QFile(filePath); + QTextStream *stream = 0; + + if (file->open(openFlags)) + stream = new QTextStream(file); + + if (stream) + stream->setCodec(QTextCodec::codecForName("UTF-8")); + + return stream; +} + +void CSMSettings::SettingManager::destroyStream(QTextStream *stream) const +{ + stream->device()->close(); + + delete stream; +} + +void CSMSettings::SettingManager::displayFileErrorMessage(const QString &message, + bool isReadOnly) const +{ + // File cannot be opened or created + QMessageBox msgBox; + msgBox.setWindowTitle(QObject::tr("OpenCS configuration file I/O error")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + + if (!isReadOnly) + msgBox.setText (mReadWriteMessage + message); + else + msgBox.setText (message); + + msgBox.exec(); +} + +void CSMSettings::SettingManager::addDefinitions (DefinitionPageMap &pageMap) +{ + foreach (QString pageName, pageMap.keys()) + { + DefinitionMap *settingMap = pageMap.value (pageName); + + foreach (QString settingName, (*settingMap).keys()) + { + QStringList *values = settingMap->value (settingName); + Setting *setting = findSetting (pageName, settingName); + + if (!setting) + { + qWarning() << "Found definitions for undeclared setting " + << pageName << "." << settingName; + continue; + } + + if (values->size() == 0) + values->append (setting->defaultValues()); + + setting->setDefinedValues (*values); + } + } +} + +QList CSMSettings::SettingManager::findSettings + (const QStringList &list) +{ + QList settings; + + foreach (const QString &value, list) + { + QStringList names = value.split(".", QString::SkipEmptyParts); + + if (names.size() != 2) + continue; + + Setting *setting = findSetting (names.at(0), names.at(1)); + + if (!setting) + continue; + + settings.append (setting); + } + + return settings; +} + + +CSMSettings::Setting *CSMSettings::SettingManager::findSetting + (const QString &pageName, const QString &settingName) +{ + foreach (Setting *setting, mSettings) + { + if (setting->name() == settingName) + { + if (setting->page() == pageName) + return setting; + } + } + return 0; +} + +QList CSMSettings::SettingManager::findSettings + (const QString &pageName) +{ + QList settings; + + foreach (Setting *setting, mSettings) + { + if (setting->page() == pageName) + settings.append (setting); + } + return settings; +} + +CSMSettings::SettingPageMap CSMSettings::SettingManager::settingPageMap() const +{ + SettingPageMap pageMap; + + foreach (Setting *setting, mSettings) + pageMap[setting->page()].append (setting); + + return pageMap; +} diff --git a/apps/opencs/model/settings/settingmanager.hpp b/apps/opencs/model/settings/settingmanager.hpp new file mode 100644 index 0000000000..8819096adf --- /dev/null +++ b/apps/opencs/model/settings/settingmanager.hpp @@ -0,0 +1,82 @@ +#ifndef CSMSETTINGS_SETTINGMANAGER_HPP +#define CSMSETTINGS_SETTINGMANAGER_HPP + +#include +#include +#include +#include + +#include "support.hpp" +#include "setting.hpp" + +namespace CSMSettings +{ + + typedef QMap DefinitionMap; + typedef QMap DefinitionPageMap; + + typedef QMap > SettingPageMap; + + class SettingManager : public QObject + { + Q_OBJECT + + QString mReadOnlyMessage; + QString mReadWriteMessage; + QList mSettings; + + public: + explicit SettingManager(QObject *parent = 0); + + ///retrieve a setting object from a given page and setting name + Setting *findSetting + (const QString &pageName, const QString &settingName); + + ///retrieve all settings for a specified page + QList findSettings (const QString &pageName); + + ///retrieve all settings named in the attached list. + ///Setting names are specified in "PageName.SettingName" format. + QList findSettings (const QStringList &list); + + ///Retreive a map of the settings, keyed by page name + SettingPageMap settingPageMap() const; + + protected: + + ///add a new setting to the model and return it + Setting *createSetting (CSMSettings::SettingType typ, + const QString &page, const QString &name, + const QStringList &values = QStringList()); + + ///add definitions to the settings specified in the page map + void addDefinitions (DefinitionPageMap &pageMap); + + ///read setting definitions from file + DefinitionPageMap readFilestream(QTextStream *stream); + + ///write setting definitions to file + bool writeFilestream (QTextStream *stream, + const QMap &settingMap); + + ///merge PageMaps of settings when loading from multiple files + void mergeSettings (DefinitionPageMap &destMap, DefinitionPageMap &srcMap); + + QTextStream *openFilestream (const QString &filePath, + bool isReadOnly) const; + + void destroyStream(QTextStream *stream) const; + + void displayFileErrorMessage(const QString &message, + bool isReadOnly) const; + + QList settings() const { return mSettings; } + void dumpModel(); + + signals: + + public slots: + + }; +} +#endif // CSMSETTINGS_SETTINGMANAGER_HPP diff --git a/apps/opencs/view/settings/booleanview.cpp b/apps/opencs/view/settings/booleanview.cpp new file mode 100644 index 0000000000..1c48199d1d --- /dev/null +++ b/apps/opencs/view/settings/booleanview.cpp @@ -0,0 +1,91 @@ +#include +#include + +#include +#include +#include + +#include + +#include "booleanview.hpp" +#include "../../model/settings/setting.hpp" + +CSVSettings::BooleanView::BooleanView (CSMSettings::Setting *setting, + Page *parent) + : View (setting, parent) +{ + foreach (const QString &value, setting->declaredValues()) + { + QAbstractButton *button = 0; + + if (isMultiValue()) + button = new QCheckBox (value, this); + else + button = new QRadioButton (value, this); + + connect (button, SIGNAL (clicked (bool)), + this, SLOT (slotToggled (bool))); + + button->setObjectName (value); + + addWidget (button); + + mButtons[value] = button; + } +} + +void CSVSettings::BooleanView::slotToggled (bool state) +{ + //test only for true to avoid multiple selection updates with radiobuttons + if (!isMultiValue() && !state) + return; + + QStringList values; + + foreach (QString key, mButtons.keys()) + { + if (mButtons.value(key)->isChecked()) + values.append (key); + } + setSelectedValues (values, false); + + View::updateView(); +} + +void CSVSettings::BooleanView::updateView (bool signalUpdate) const +{ + + QStringList values = selectedValues(); + + foreach (const QString &buttonName, mButtons.keys()) + { + QAbstractButton *button = mButtons[buttonName]; + + //if the value is not found in the list, the widget is checked false + bool buttonValue = values.contains(buttonName); + + //skip if the butotn value will not change + if (button->isChecked() == buttonValue) + continue; + + //disable autoexclusive if it's enabled and we're setting + //the button value to false + bool switchExclusive = (!buttonValue && button->autoExclusive()); + + if (switchExclusive) + button->setAutoExclusive (false); + + button->setChecked (buttonValue); + + if (switchExclusive) + button->setAutoExclusive(true); + } + View::updateView (signalUpdate); +} + +CSVSettings::BooleanView *CSVSettings::BooleanViewFactory::createView + (CSMSettings::Setting *setting, + Page *parent) +{ + return new BooleanView (setting, parent); +} diff --git a/apps/opencs/view/settings/booleanview.hpp b/apps/opencs/view/settings/booleanview.hpp new file mode 100644 index 0000000000..52f9e05f1f --- /dev/null +++ b/apps/opencs/view/settings/booleanview.hpp @@ -0,0 +1,44 @@ +#ifndef CSVSETTINGS_BOOLEANVIEW_HPP +#define CSVSETTINGS_BOOELANVIEW_HPP + +#include +#include + +#include "view.hpp" +#include "../../model/settings/support.hpp" + +class QStringListModel; + +namespace CSVSettings +{ + class BooleanView : public View + { + Q_OBJECT + + QMap mButtons; + + public: + explicit BooleanView (CSMSettings::Setting *setting, + Page *parent); + + protected: + void updateView (bool signalUpdate = true) const; + + private slots: + void slotToggled (bool state); + }; + + class BooleanViewFactory : public QObject, public IViewFactory + { + Q_OBJECT + + public: + explicit BooleanViewFactory (QWidget *parent = 0) + : QObject (parent) + {} + + BooleanView *createView (CSMSettings::Setting *setting, + Page *parent); + }; +} +#endif // CSVSETTINGS_BOOLEANVIEW_HPP diff --git a/apps/opencs/view/settings/dialog.cpp b/apps/opencs/view/settings/dialog.cpp new file mode 100644 index 0000000000..d9d5830d98 --- /dev/null +++ b/apps/opencs/view/settings/dialog.cpp @@ -0,0 +1,132 @@ +#include "dialog.hpp" + +#include +#include +#include +#include +#include + +#include "../../model/settings/usersettings.hpp" + +#include "page.hpp" + +#include + +#include + +#include +#include +#include + +#include +#include + +CSVSettings::Dialog::Dialog(QMainWindow *parent) + : mStackedWidget (0), mDebugMode (false), SettingWindow (parent) +{ + setWindowTitle(QString::fromUtf8 ("User Settings")); + + setupDialog(); + + connect (mPageListWidget, + SIGNAL (currentItemChanged(QListWidgetItem*, QListWidgetItem*)), + this, + SLOT (slotChangePage (QListWidgetItem*, QListWidgetItem*))); +} + +void CSVSettings::Dialog::slotChangePage + (QListWidgetItem *cur, QListWidgetItem *prev) +{ + mStackedWidget->changePage + (mPageListWidget->row (cur), mPageListWidget->row (prev)); + + layout()->activate(); + setFixedSize(minimumSizeHint()); +} + +void CSVSettings::Dialog::setupDialog() +{ + //create central widget with it's layout and immediate children + QWidget *centralWidget = new QGroupBox (this); + + centralWidget->setLayout (new QHBoxLayout()); + centralWidget->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Preferred); + setCentralWidget (centralWidget); + setDockOptions (QMainWindow::AllowNestedDocks); + + buildPageListWidget (centralWidget); + buildStackedWidget (centralWidget); +} + +void CSVSettings::Dialog::buildPages() +{ + SettingWindow::createPages (); + + QFontMetrics fm (QApplication::font()); + + foreach (Page *page, SettingWindow::pages()) + { + QString pageName = page->objectName(); + + int textWidth = fm.width(pageName); + + new QListWidgetItem (pageName, mPageListWidget); + mPageListWidget->setFixedWidth (textWidth + 50); + + mStackedWidget->addWidget (&dynamic_cast(*(page))); + } + + addDebugPage(); + + resize (mStackedWidget->sizeHint()); +} + +void CSVSettings::Dialog::addDebugPage() +{ + /* + QTreeView *tree = new QTreeView(); + + //tree->setModel( &CSMSettings::UserSettings::instance().model() ); + + mStackedWidget->addWidget(tree); + new QListWidgetItem ("Standard Item Model", mPageListWidget);*/ +} + +void CSVSettings::Dialog::buildPageListWidget (QWidget *centralWidget) +{ + mPageListWidget = new QListWidget (centralWidget); + mPageListWidget->setMinimumWidth(50); + mPageListWidget->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Expanding); + + mPageListWidget->setSelectionBehavior (QAbstractItemView::SelectItems); + + centralWidget->layout()->addWidget(mPageListWidget); +} + +void CSVSettings::Dialog::buildStackedWidget (QWidget *centralWidget) +{ + mStackedWidget = new ResizeableStackedWidget (centralWidget); + + centralWidget->layout()->addWidget (mStackedWidget); +} + +void CSVSettings::Dialog::closeEvent (QCloseEvent *event) +{ + //SettingWindow::closeEvent() must be called first to ensure + //model is updated + SettingWindow::closeEvent (event); + + saveSettings(); +} + +void CSVSettings::Dialog::show() +{ + if (pages().isEmpty()) + buildPages(); + + QPoint screenCenter = QApplication::desktop()->screenGeometry().center(); + + move (screenCenter - geometry().center()); + + QWidget::show(); +} diff --git a/apps/opencs/view/settings/dialog.hpp b/apps/opencs/view/settings/dialog.hpp new file mode 100644 index 0000000000..0cfd415acd --- /dev/null +++ b/apps/opencs/view/settings/dialog.hpp @@ -0,0 +1,55 @@ +#ifndef CSVSETTINGS_DIALOG_H +#define CSVSETTINGS_DIALOG_H + +#include "settingwindow.hpp" +#include "resizeablestackedwidget.hpp" +#include + +class QStackedWidget; +class QListWidget; +class QListWidgetItem; + +namespace CSVSettings { + + class Page; + + class Dialog : public SettingWindow + { + Q_OBJECT + + QListWidget *mPageListWidget; + ResizeableStackedWidget *mStackedWidget; + bool mDebugMode; + + public: + + explicit Dialog (QMainWindow *parent = 0); + + ///Enables setting debug mode. When the dialog opens, a page is created + ///which displays the SettingModel's contents in a Tree view. + void enableDebugMode (bool state, QStandardItemModel *model = 0); + + protected: + + /// Settings are written on close + void closeEvent (QCloseEvent *event); + + void setupDialog(); + + private: + + void buildPages(); + void buildPageListWidget (QWidget *centralWidget); + void buildStackedWidget (QWidget *centralWidget); + void addDebugPage(); + + public slots: + + void show(); + + private slots: + + void slotChangePage (QListWidgetItem *, QListWidgetItem *); + }; +} +#endif // CSVSETTINGS_DIALOG_H diff --git a/apps/opencs/view/settings/frame.cpp b/apps/opencs/view/settings/frame.cpp new file mode 100644 index 0000000000..db5091999a --- /dev/null +++ b/apps/opencs/view/settings/frame.cpp @@ -0,0 +1,103 @@ +#include "frame.hpp" + +#include + +const QString CSVSettings::Frame::sInvisibleBoxStyle = + QString::fromUtf8("Frame { border:2px; padding 2px; margin: 2px;}"); + +CSVSettings::Frame::Frame (bool isVisible, const QString &title, + QWidget *parent) + : mIsHorizontal (true), mLayout (new SettingLayout()), + QGroupBox (title, parent) +{ + setFlat (true); + mVisibleBoxStyle = styleSheet(); + + if (!isVisible) + setStyleSheet (sInvisibleBoxStyle); + + setLayout (mLayout); +} + +void CSVSettings::Frame::hideWidgets() +{ + for (int i = 0; i < children().size(); i++) + { + QObject *obj = children().at(i); + + Frame *widgFrame = dynamic_cast (obj); + + if (widgFrame) + { + widgFrame->hideWidgets(); + continue; + } + + QWidget *widg = static_cast (obj); + if (widg->property("sizePolicy").isValid()) + widg->setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Ignored); + } + + layout()->activate(); + setFixedSize(minimumSizeHint()); + +} + +void CSVSettings::Frame::showWidgets() +{ + for (int i = 0; i < children().size(); i++) + { + QObject *obj = children().at(i); + + Frame *widgFrame = dynamic_cast (obj); + + if (widgFrame) + { + widgFrame->showWidgets(); + continue; + } + + QWidget *widg = static_cast (obj); + + if (widg->property("sizePolicy").isValid()) + widg->setSizePolicy + (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + } + layout()->activate(); + setFixedSize(minimumSizeHint()); +} + +void CSVSettings::Frame::addWidget (QWidget *widget, int row, int column, + int rowSpan, int columnSpan) +{ + if (row == -1) + row = getNextRow(); + + if (column == -1) + column = getNextColumn(); + + mLayout->addWidget (widget, row, column, rowSpan, columnSpan); + //, Qt::AlignLeft | Qt::AlignTop); + + widget->setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Ignored); +} + +int CSVSettings::Frame::getNextRow () const +{ + int row = mLayout->rowCount(); + + if (mIsHorizontal && row > 0) + row--; + + return row; +} + +int CSVSettings::Frame::getNextColumn () const +{ + int column = 0; + + if (mIsHorizontal) + column = mLayout->columnCount(); + + return column; +} diff --git a/apps/opencs/view/settings/frame.hpp b/apps/opencs/view/settings/frame.hpp new file mode 100644 index 0000000000..2b52dfd1f4 --- /dev/null +++ b/apps/opencs/view/settings/frame.hpp @@ -0,0 +1,58 @@ +#ifndef CSVSETTINGS_FRAME_HPP +#define CSVSETTINGS_FRAME_HPP + +#include +#include +#include +#include "../../model/settings/support.hpp" + +namespace CSVSettings +{ + class SettingLayout : public QGridLayout + { + public: + explicit SettingLayout (QWidget *parent = 0) + : QGridLayout (parent) + { + setContentsMargins(0,0,0,0); + setAlignment(Qt::AlignLeft | Qt::AlignTop); + } + }; + + /// Custom implementation of QGroupBox to act as a base for view classes + class Frame : public QGroupBox + { + static const QString sInvisibleBoxStyle; + + QString mVisibleBoxStyle; + + bool mIsHorizontal; + + SettingLayout *mLayout; + + public: + explicit Frame (bool isVisible, const QString &title = "", + QWidget *parent = 0); + + ///Adds a widget to the grid layout, setting the position + ///relative to the last added widgets, or absolutely for positive + ///row / column values + void addWidget (QWidget *widget, int row = -1, int column = -1, + int rowSpan = 1, int columnSpan = 1); + + ///Force the grid to lay out in horizontal or vertical alignments + void setHLayout() { mIsHorizontal = true; } + void setVLayout() { mIsHorizontal = false; } + + void showWidgets(); + void hideWidgets(); + + private: + + int getNextColumn() const; + int getNextRow() const; + + }; +} + +#endif // CSVSETTINGS_FRAME_HPP diff --git a/apps/opencs/view/settings/listview.cpp b/apps/opencs/view/settings/listview.cpp new file mode 100644 index 0000000000..b2a47903ff --- /dev/null +++ b/apps/opencs/view/settings/listview.cpp @@ -0,0 +1,106 @@ +#include "listview.hpp" +#include "../../model/settings/setting.hpp" + +#include +#include +#include + +CSVSettings::ListView::ListView(CSMSettings::Setting *setting, + Page *parent) + : mComboBox (0), mAbstractItemView (0), View(setting, parent) +{ + QWidget *widget = + buildWidget(setting->isMultiLine(), setting->widgetWidth()); + + addWidget (widget, setting->viewRow(), setting->viewColumn()); + + if (mComboBox) + buildComboBoxModel(); + + else if (mAbstractItemView) + buildAbstractItemViewModel(); +} + +void CSVSettings::ListView::buildComboBoxModel() +{ + mComboBox->setModel (dataModel()); + mComboBox->setModelColumn (0); + mComboBox->view()->setSelectionModel (selectionModel()); + + int curIdx = -1; + + if (!selectionModel()->selection().isEmpty()) + curIdx = selectionModel()->selectedIndexes().at(0).row(); + + mComboBox->setCurrentIndex (curIdx); + + connect (mComboBox, SIGNAL(currentIndexChanged(int)), + this, SLOT(emitItemViewUpdate(int))); +} + +void CSVSettings::ListView::buildAbstractItemViewModel() +{ + mAbstractItemView->setModel (dataModel()); + mAbstractItemView->setSelectionModel (selectionModel()); + + //connection needs to go here for list view update to signal to + //the outside +} + +void CSVSettings::ListView::emitItemViewUpdate (int idx) +{ + emit viewUpdated (objectName(), selectedValues()); +} + +QWidget *CSVSettings::ListView::buildWidget(bool isMultiLine, int width) +{ + QWidget *widget = 0; + + if (isMultiLine) + { + mAbstractItemView = new QListView (this); + widget = mAbstractItemView; + + if (width > 0) + widget->setFixedWidth (widgetWidth (width)); + } + else + { + mComboBox = new QComboBox (this); + widget = mComboBox; + + if (width > 0) + mComboBox->setMinimumContentsLength (width); + } + + return widget; +} + +void CSVSettings::ListView::showEvent ( QShowEvent * event ) +{ + View::showEvent (event); +} + +void CSVSettings::ListView::updateView (bool signalUpdate) const +{ + QStringList values = selectedValues(); + + if (mComboBox) + { + int idx = -1; + + if (values.size() > 0) + idx = (mComboBox->findText(values.at(0))); + + mComboBox->setCurrentIndex (idx); + } + + View::updateView (signalUpdate); +} + +CSVSettings::ListView *CSVSettings::ListViewFactory::createView + (CSMSettings::Setting *setting, + Page *parent) +{ + return new ListView(setting, parent); +} diff --git a/apps/opencs/view/settings/listview.hpp b/apps/opencs/view/settings/listview.hpp new file mode 100644 index 0000000000..c2860d769a --- /dev/null +++ b/apps/opencs/view/settings/listview.hpp @@ -0,0 +1,63 @@ +#ifndef CSVSETTINGS_LISTVIEW_HPP +#define CSVSETTINGS_LISTVIEW_HPP + +#include "view.hpp" + + +class QStringListModel; +class QComboBox; +class QAbstractItemView; + +namespace CSVSettings +{ + class ListView : public View + { + Q_OBJECT + + QAbstractItemView *mAbstractItemView; + QComboBox *mComboBox; + + public: + explicit ListView (CSMSettings::Setting *setting, + Page *parent); + + protected: + + void updateView (bool signalUpdate = true) const; + void showEvent ( QShowEvent * event ); + + ///Receives signal from widget and signals viwUpdated() + void slotTextEdited (QString value); + + private: + + ///Helper function to construct a model for an AbstractItemView + void buildAbstractItemViewModel(); + + ///Helper function to construct a model for a combobox + void buildComboBoxModel(); + + ///Helper function to build the view widget + QWidget *buildWidget (bool isMultiLine, int width); + + private slots: + + ///Receives updates from single-select widgets (like combobox) and + ///signals viewUpdated with the selected values. + void emitItemViewUpdate (int idx); + }; + + class ListViewFactory : public QObject, public IViewFactory + { + Q_OBJECT + + public: + explicit ListViewFactory (QWidget *parent = 0) + : QObject (parent) + {} + + ListView *createView (CSMSettings::Setting *setting, + Page *parent); + }; +} +#endif // CSVSETTINGS_LISTVIEW_HPP diff --git a/apps/opencs/view/settings/page.cpp b/apps/opencs/view/settings/page.cpp new file mode 100644 index 0000000000..665326c2c0 --- /dev/null +++ b/apps/opencs/view/settings/page.cpp @@ -0,0 +1,88 @@ +#include "page.hpp" +#include "view.hpp" +#include "booleanview.hpp" +#include "textview.hpp" +#include "listview.hpp" + +#include "../../model/settings/usersettings.hpp" +#include "../../model/settings/connector.hpp" +#include "settingwindow.hpp" + +QMap + CSVSettings::Page::mViewFactories; + +CSVSettings::Page::Page(const QString &pageName, + QList settingList, + SettingWindow *parent) : + mParent(parent), mIsEditorPage (false), Frame(false, "", parent) +{ + setObjectName (pageName); + + if (mViewFactories.size() == 0) + buildFactories(); + + setVLayout(); + setupViews (settingList); +} + +void CSVSettings::Page::setupViews + (QList &settingList) +{ + foreach (CSMSettings::Setting *setting, settingList) + addView (setting); +} + +void CSVSettings::Page::addView (CSMSettings::Setting *setting) +{ + if (setting->viewType() == ViewType_Undefined) + return; + + View *view = mViewFactories[setting->viewType()]->createView(setting, this); + + if (!view) + return; + + mViews.append (view); + + addWidget (view, setting->viewRow(), setting->viewColumn(), + setting->rowSpan(), setting->columnSpan() ); + + //if this page is an editor page, connect each of it's views up to the + //UserSettings singleton for signaling back to OpenCS + if (setting->isEditorSetting()) { + connect (view, SIGNAL (viewUpdated(const QString&, const QStringList&)), + &CSMSettings::UserSettings::instance(), + SIGNAL (userSettingUpdated (const QString &, const QStringList &))); + } +} + +CSVSettings::View *CSVSettings::Page::findView (const QString &page, + const QString &setting) const +{ + + //if this is not the page we're looking for, + //appeal to the parent setting window to find the appropriate view + if (page != objectName()) + return mParent->findView (page, setting); + + //otherwise, return the matching view + for (int i = 0; i < mViews.size(); i++) + { + View *view = mViews.at(i); + + if (view->parentPage()->objectName() != page) + continue; + + if (view->objectName() == setting) + return view; + } + + return 0; +} + +void CSVSettings::Page::buildFactories() +{ + mViewFactories[ViewType_Boolean] = new BooleanViewFactory (this); + mViewFactories[ViewType_Text] = new TextViewFactory (this); + mViewFactories[ViewType_List] = new ListViewFactory (this); +} diff --git a/apps/opencs/view/settings/page.hpp b/apps/opencs/view/settings/page.hpp new file mode 100644 index 0000000000..7f24f6a625 --- /dev/null +++ b/apps/opencs/view/settings/page.hpp @@ -0,0 +1,54 @@ +#ifndef CSVSETTINGS_PAGE_HPP +#define CSVSETTINGS_PAGE_HPP + +#include +#include +#include +#include + +#include "frame.hpp" + +#include "../../model/settings/support.hpp" + +namespace CSMSettings { class Setting; } + +namespace CSVSettings +{ + class View; + class IViewFactory; + class SettingWindow; + + class Page : public Frame + { + Q_OBJECT + + QList mViews; + SettingWindow *mParent; + static QMap mViewFactories; + bool mIsEditorPage; + + public: + explicit Page(const QString &pageName, + QList settingList, + SettingWindow *parent); + + ///Creates a new view based on the passed setting and adds it to + ///the page. + void addView (CSMSettings::Setting *setting); + + ///Iterates the views created for this page based on the passed setting + ///and returns it. + View *findView (const QString &page, const QString &setting) const; + + const QList &views () const { return mViews; } + + private: + + ///Creates views based on the passed setting list + void setupViews (QList &settingList); + + ///Creates factory objects for view construction + void buildFactories(); + }; +} +#endif // CSVSETTINGS_PAGE_HPP diff --git a/apps/opencs/view/settings/resizeablestackedwidget.cpp b/apps/opencs/view/settings/resizeablestackedwidget.cpp new file mode 100644 index 0000000000..cb127cb761 --- /dev/null +++ b/apps/opencs/view/settings/resizeablestackedwidget.cpp @@ -0,0 +1,40 @@ +#include "resizeablestackedwidget.hpp" +#include "page.hpp" + +#include + +CSVSettings::ResizeableStackedWidget::ResizeableStackedWidget(QWidget *parent) : + QStackedWidget(parent) +{} + +void CSVSettings::ResizeableStackedWidget::addWidget(QWidget* pWidget) +{ + QStackedWidget::addWidget(pWidget); +} + +void CSVSettings::ResizeableStackedWidget::changePage + (int current, int previous) +{ + if (current == previous) + return; + + Page *prevPage = 0; + Page *curPage = 0; + + if (previous > -1) + prevPage = static_cast (widget (previous)); + + if (current > -1) + curPage = static_cast (widget (current)); + + if (prevPage) + prevPage->hideWidgets(); + + if (curPage) + curPage->showWidgets(); + + layout()->activate(); + setFixedSize(minimumSizeHint()); + + setCurrentIndex (current); +} diff --git a/apps/opencs/view/settings/resizeablestackedwidget.hpp b/apps/opencs/view/settings/resizeablestackedwidget.hpp new file mode 100644 index 0000000000..5e894d8dfd --- /dev/null +++ b/apps/opencs/view/settings/resizeablestackedwidget.hpp @@ -0,0 +1,23 @@ +#ifndef CSVSETTINGS_RESIZEABLESTACKEDWIDGET_HPP +#define CSVSETTINGS_RESIZEABLESTACKEDWIDGET_HPP + +#include + +class QListWidgetItem; + +namespace CSVSettings +{ + class ResizeableStackedWidget : public QStackedWidget + { + Q_OBJECT + + public: + explicit ResizeableStackedWidget(QWidget *parent = 0); + + void addWidget(QWidget* pWidget); + + void changePage (int, int); + }; +} + +#endif // CSVSETTINGS_RESIZEABLESTACKEDWIDGET_HPP diff --git a/apps/opencs/view/settings/settingwindow.cpp b/apps/opencs/view/settings/settingwindow.cpp new file mode 100644 index 0000000000..9283bbf51b --- /dev/null +++ b/apps/opencs/view/settings/settingwindow.cpp @@ -0,0 +1,114 @@ +#include +#include + +#include "../../model/settings/setting.hpp" +#include "../../model/settings/connector.hpp" +#include "../../model/settings/usersettings.hpp" +#include "settingwindow.hpp" +#include "page.hpp" +#include "view.hpp" + +CSVSettings::SettingWindow::SettingWindow(QWidget *parent) + : QMainWindow(parent) +{} + +void CSVSettings::SettingWindow::createPages() +{ + CSMSettings::SettingPageMap pageMap = mModel->settingPageMap(); + + QList connectedSettings; + + foreach (const QString &pageName, pageMap.keys()) + { + QList pageSettings = pageMap.value (pageName); + + mPages.append (new Page (pageName, pageSettings, this)); + + for (int i = 0; i < pageSettings.size(); i++) + { + CSMSettings::Setting *setting = pageSettings.at(i); + + if (!setting->proxyLists().isEmpty()) + connectedSettings.append (setting); + } + } + + if (!connectedSettings.isEmpty()) + createConnections(connectedSettings); +} + +void CSVSettings::SettingWindow::createConnections + (const QList &list) +{ + foreach (const CSMSettings::Setting *setting, list) + { + View *masterView = findView (setting->page(), setting->name()); + + CSMSettings::Connector *connector = + new CSMSettings::Connector (masterView, this); + + connect (masterView, + SIGNAL (viewUpdated(const QString &, const QStringList &)), + connector, + SLOT (slotUpdateSlaves()) + ); + + const CSMSettings::ProxyValueMap &proxyMap = setting->proxyLists(); + + foreach (const QString &key, proxyMap.keys()) + { + QStringList keyPair = key.split('.'); + + if (keyPair.size() != 2) + continue; + + View *slaveView = findView (keyPair.at(0), keyPair.at(1)); + + if (!slaveView) + { + qWarning () << "Unable to create connection for view " + << key; + continue; + } + + QList proxyList = proxyMap.value (key); + connector->addSlaveView (slaveView, proxyList); + + connect (slaveView, + SIGNAL (viewUpdated(const QString &, const QStringList &)), + connector, + SLOT (slotUpdateMaster())); + } + } +} + +CSVSettings::View *CSVSettings::SettingWindow::findView + (const QString &pageName, const QString &setting) +{ + foreach (const Page *page, mPages) + { + if (page->objectName() == pageName) + return page->findView (pageName, setting); + } + return 0; +} + +void CSVSettings::SettingWindow::saveSettings() +{ + QMap settingMap; + + foreach (const Page *page, mPages) + { + foreach (const View *view, page->views()) + { + if (view->serializable()) + settingMap[view->viewKey()] = view->selectedValues(); + } + } + CSMSettings::UserSettings::instance().saveSettings (settingMap); +} + +void CSVSettings::SettingWindow::closeEvent (QCloseEvent *event) +{ + QApplication::focusWidget()->clearFocus(); +} diff --git a/apps/opencs/view/settings/settingwindow.hpp b/apps/opencs/view/settings/settingwindow.hpp new file mode 100644 index 0000000000..35ae4c068c --- /dev/null +++ b/apps/opencs/view/settings/settingwindow.hpp @@ -0,0 +1,49 @@ +#ifndef CSVSETTINGS_SETTINGWINDOW_HPP +#define CSVSETTINGS_SETTINGWINDOW_HPP + +#include +#include + +#include "../../model/settings/support.hpp" + +namespace CSMSettings { + class Setting; + class SettingManager; +} + +namespace CSVSettings { + + class Page; + class View; + + typedef QList PageList; + + class SettingWindow : public QMainWindow + { + Q_OBJECT + + PageList mPages; + CSMSettings::SettingManager *mModel; + + public: + explicit SettingWindow(QWidget *parent = 0); + + View *findView (const QString &pageName, const QString &setting); + void setModel (CSMSettings::SettingManager &model) { mModel = &model; } + + protected: + + virtual void closeEvent (QCloseEvent *event); + + void createPages(); + + const PageList &pages() const { return mPages; } + + void saveSettings(); + + private: + void createConnections (const QList &list); + }; +} + +#endif // CSVSETTINGS_SETTINGWINDOW_HPP diff --git a/apps/opencs/view/settings/textview.cpp b/apps/opencs/view/settings/textview.cpp new file mode 100644 index 0000000000..5e10c346f7 --- /dev/null +++ b/apps/opencs/view/settings/textview.cpp @@ -0,0 +1,73 @@ +#include +#include + +#include "textview.hpp" +#include "../../model/settings/setting.hpp" + +CSVSettings::TextView::TextView(CSMSettings::Setting *setting, Page *parent) + : mDelimiter (setting->delimiter()), View (setting, parent) + +{ + if (setting->isMultiLine()) + mTextWidget = new QTextEdit ("", this); + else + mTextWidget = new QLineEdit ("", this); + + if (setting->widgetWidth() > 0) + mTextWidget->setFixedWidth (widgetWidth (setting->widgetWidth())); + + connect (mTextWidget, SIGNAL (textEdited (QString)), + this, SLOT (slotTextEdited (QString))); + + addWidget (mTextWidget, setting->viewRow(), setting->viewColumn()); +} + +bool CSVSettings::TextView::isEquivalent + (const QString &lhs, const QString &rhs) const +{ + return (lhs.trimmed() == rhs.trimmed()); +} + +void CSVSettings::TextView::setWidgetText (const QString &value) const +{ + mTextWidget->setProperty ("text", value); +} + +void CSVSettings::TextView::slotTextEdited (QString value) +{ + QStringList values = value.split (mDelimiter, QString::SkipEmptyParts); + + QStringList returnValues; + + foreach (const QString &splitValue, values) + returnValues.append (splitValue.trimmed()); + + setSelectedValues (returnValues, false); + + View::updateView(); +} + +void CSVSettings::TextView::updateView(bool signalUpdate) const +{ + QString values = selectedValues().join (mDelimiter); + + if (isEquivalent (widgetText(), values)) + return; + + setWidgetText (values); + + View::updateView (signalUpdate); +} + +QString CSVSettings::TextView::widgetText() const +{ + return mTextWidget->property("text").toString(); +} + +CSVSettings::TextView *CSVSettings::TextViewFactory::createView + (CSMSettings::Setting *setting, + Page *parent) +{ + return new TextView (setting, parent); +} + diff --git a/apps/opencs/view/settings/textview.hpp b/apps/opencs/view/settings/textview.hpp new file mode 100644 index 0000000000..6d718aad87 --- /dev/null +++ b/apps/opencs/view/settings/textview.hpp @@ -0,0 +1,56 @@ +#ifndef CSVSETTINGS_TEXTVIEW_HPP +#define CSVSETTINGS_TEXTVIEW_HPP + +#include "view.hpp" +#include "../../model/settings/setting.hpp" + +namespace CSVSettings +{ + class TextView : public View + { + Q_OBJECT + + QWidget *mTextWidget; + + QString mDelimiter; + + public: + explicit TextView (CSMSettings::Setting *setting, + Page *parent = 0); + + protected: + + void updateView (bool signalUpdate = true) const; + + protected slots: + + ///Receives updates to the widget for signalling + void slotTextEdited (QString value); + + private: + + ///Comparison function that returns true if the trimmed() strings + ///are equal + bool isEquivalent (const QString &lhs, const QString &rhs) const; + + ///Convenience function to return the text of the widget + QString widgetText() const; + + ///Convenience function to set the text of the widget + void setWidgetText (const QString &value) const; + }; + + class TextViewFactory : public QObject, public IViewFactory + { + Q_OBJECT + + public: + explicit TextViewFactory (QWidget *parent = 0) + : QObject (parent) + {} + + TextView *createView (CSMSettings::Setting *setting, + Page *parent); + }; +} +#endif // CSVSETTINGS_TEXTVIEW_HPP diff --git a/apps/opencs/view/settings/view.cpp b/apps/opencs/view/settings/view.cpp new file mode 100644 index 0000000000..259dd518b6 --- /dev/null +++ b/apps/opencs/view/settings/view.cpp @@ -0,0 +1,218 @@ +#include +#include +#include +#include + +#include "view.hpp" +#include "../../model/settings/support.hpp" +#include "../../model/settings/setting.hpp" +#include "page.hpp" + +CSVSettings::View::View(CSMSettings::Setting *setting, + Page *parent) + + : mDataModel(0), mParentPage (parent), + mHasFixedValues (!setting->declaredValues().isEmpty()), + mIsMultiValue (setting->isMultiValue()), + mViewKey (setting->page() + '.' + setting->name()), + mSerializable (setting->serializable()), + Frame(true, setting->name(), parent) +{ + setObjectName (setting->name()); + buildView(); + buildModel (setting); +} + +void CSVSettings::View::buildModel (const CSMSettings::Setting *setting) +{ + QStringList values = setting->definedValues(); + + if (values.isEmpty()) + values.append (setting->defaultValues()); + + if (mHasFixedValues) + buildFixedValueModel (setting->declaredValues()); + else + buildUpdatableValueModel (values); + + mSelectionModel = new QItemSelectionModel (mDataModel, this); + + setSelectedValues (values, false); +} + +void CSVSettings::View::buildFixedValueModel (const QStringList &values) +{ + mDataModel = new QStringListModel (values, this); +} + +void CSVSettings::View::buildUpdatableValueModel (const QStringList &values) +{ + QList itemList; + + foreach (const QString &value, values) + itemList.append (new QStandardItem(value)); + +// QSortFilterProxyModel *filter = new QSortFilterProxyModel (this); + QStandardItemModel *model = new QStandardItemModel (this); + model->appendColumn (itemList); + +// filter->setSourceModel (model); + /* filter->setFilterRegExp ("*"); + filter->setFilterKeyColumn (0); + filter->setFilterRole (Qt::DisplayRole);*/ + mDataModel = model; +} + +void CSVSettings::View::buildView() +{ + setFlat (true); + setHLayout(); +} + +int CSVSettings::View::currentIndex () const +{ + if (selectedValues().isEmpty()) + return -1; + + QString currentValue = selectedValues().at(0); + + for (int i = 0; i < mDataModel->rowCount(); i++) + if (value(i) == currentValue) + return i; + + return -1; +} + +void CSVSettings::View::refresh() const +{ + select (mSelectionModel->selection()); + updateView(); +} + +int CSVSettings::View::rowCount() const +{ + return mDataModel->rowCount(); +} + +void CSVSettings::View::select (const QItemSelection &selection) const +{ + mSelectionModel->clear(); + mSelectionModel->select(selection, QItemSelectionModel::Select); +} + +QStringList CSVSettings::View::selectedValues() const +{ + QStringList selValues; + + foreach (const QModelIndex &idx, mSelectionModel->selectedIndexes()) + selValues.append (value(idx.row())); + + return selValues; +} + +void CSVSettings::View::setSelectedValue (const QString &value, + bool doViewUpdate, bool signalUpdate) +{ + setSelectedValues (QStringList() << value, doViewUpdate, signalUpdate); +} + +void CSVSettings::View::setSelectedValues (const QStringList &list, + bool doViewUpdate, bool signalUpdate) +{ + QItemSelection selection; + + if (stringListsMatch (list, selectedValues())) + return; + + if (!mHasFixedValues) + { + QStandardItemModel *model = + static_cast (mDataModel); + + model->clear(); + model->appendColumn (toStandardItemList (list)); + + for (int i = 0; i < model->rowCount(); i++) + { + QModelIndex idx = model->index(i, 0); + selection.append (QItemSelectionRange (idx, idx)); + } + } + else + { + for (int i = 0; i < mDataModel->rowCount(); i++) + { + if (list.contains(value(i))) + { + QModelIndex idx = mDataModel->index(i, 0); + selection.append(QItemSelectionRange (idx, idx)); + } + } + } + select (selection); + + if (doViewUpdate) + updateView (signalUpdate); +} + +void CSVSettings::View::showEvent ( QShowEvent * event ) +{ + refresh(); +} + +bool CSVSettings::View::stringListsMatch ( + const QStringList &list1, + const QStringList &list2) const +{ + //returns a "sloppy" match, verifying that each list contains all the same + //items, though not necessarily in the same order. + + if (list1.size() != list2.size()) + return false; + + QStringList tempList(list2); + + //iterate each value in the list, removing one occurrence of the value in + //the other list. If no corresponding value is found, test fails + foreach (const QString &value, list1) + { + if (!tempList.contains(value)) + return false; + + tempList.removeOne(value); + } + return true; +} + +QList CSVSettings::View::toStandardItemList + (const QStringList &list) const +{ + QList itemList; + + foreach (const QString &value, list) + itemList.append (new QStandardItem (value)); + + return itemList; +} + +void CSVSettings::View::updateView (bool signalUpdate) const +{ + if (signalUpdate) + emit viewUpdated(objectName(), selectedValues()); +} + +QString CSVSettings::View::value (int row) const +{ + if (row > -1 && row < mDataModel->rowCount()) + return mDataModel->data (mDataModel->index(row, 0)).toString(); + + return ""; +} + +int CSVSettings::View::widgetWidth(int characterCount) const +{ + QString widthToken = QString().fill ('P', characterCount); + QFontMetrics fm (QApplication::font()); + + return (fm.width (widthToken)); +} diff --git a/apps/opencs/view/settings/view.hpp b/apps/opencs/view/settings/view.hpp new file mode 100644 index 0000000000..c998797622 --- /dev/null +++ b/apps/opencs/view/settings/view.hpp @@ -0,0 +1,163 @@ +#ifndef CSVSETTINGS_VIEW_HPP +#define CSVSETTINGS_VIEW_HPP + +#include +#include + +#include "frame.hpp" +#include "../../model/settings/support.hpp" + +class QGroupBox; +class QStringList; +class QStandardItem; +class QItemSelection; +class QStringListModel; +class QStandardItemModel; +class QAbstractItemModel; +class QItemSelectionModel; + +namespace CSMSettings { class Setting; } + +namespace CSVSettings +{ + class Page; + + class View : public Frame + { + Q_OBJECT + + ///Pointer to the owning Page instance + Page *mParentPage; + + ///Pointer to the selection model for the view + QItemSelectionModel *mSelectionModel; + + ///Pointer to the data model for the view's selection model + QAbstractItemModel *mDataModel; + + ///State indicating whether or not the setting has a pre-defined list + ///of values, limiting possible definitions + bool mHasFixedValues; + + ///State indicating whether the view will allow multiple values + bool mIsMultiValue; + + QString mViewKey; + + bool mSerializable; + + public: + + explicit View (CSMSettings::Setting *setting, Page *parent); + + ///Physical frame in which the view UI is contained + void addViewWidget (QWidget *widget, int row = -1, int col = -1) const; + + ///Returns the index / row of the passed value, -1 if not found. + int currentIndex () const; + + ///Returns the number of rows in the view's data model + int rowCount() const; + + ///Returns bool indicating the data in this view should / should not + ///be serialized to a config file + bool serializable() const { return mSerializable; } + + ///Returns a pointer to the view's owning parent page + const Page *parentPage() const { return mParentPage; } + + ///Returns the selected items in the selection model as a QStringList + QStringList selectedValues() const; + + ///Sets the selected items in the selection model based on passed list. + ///Bools allow opt-out of updating the view + ///or signaling the view was updatedto avoid viscious cylcing. + void setSelectedValues (const QStringList &values, + bool updateView = true, + bool signalUpdate = true); + + void setSelectedValue (const QString &value, + bool updateView = true, + bool signalUpdate = true); + + + ///Returns the value of the data model at the specified row + QString value (int row) const; + + QString viewKey() const { return mViewKey; } + + protected: + + /// Returns the model which provides data for the selection model + QAbstractItemModel *dataModel() { return mDataModel; } + + ///Accessor function for subclasses + bool isMultiValue() { return mIsMultiValue; } + + ///Returns the view selection model + QItemSelectionModel *selectionModel() { return mSelectionModel;} + + ///Global callback for basic view initialization + void showEvent ( QShowEvent * event ); + + ///Virtual for updating a specific View subclass + ///bool indicates whether a signal is emitted that the view was updated + virtual void updateView (bool signalUpdate = true) const; + + ///Returns the pixel width corresponding to the specified number of + ///characters. + int widgetWidth(int characterCount) const; + + private: + + ///Constructs the view layout + void buildView(); + + ///Constructs the data and selection models + void buildModel (const CSMSettings::Setting *setting); + + ///In cases where the view has a pre-defined list of possible values, + ///a QStringListModel is created using those values. + ///View changes operate on the selection model only. + void buildFixedValueModel (const QStringList &definitions); + + ///In cases where the view does not have a pre-defined list of possible + ///values, a QStandardItemModel is created, containing the actual + ///setting definitions. View changes first update the data in the + ///model to match the data in the view. The selection model always + ///selects all values. + void buildUpdatableValueModel (const QStringList &definitions); + + ///Refreshes the view + void refresh() const; + + ///Convenince function for selection model's select() method. Also + ///clears out the model beforehand to ensure complete selection. + void select (const QItemSelection &selection) const; + + ///Compares two string lists "loosely", ensuring that all values in + ///one list are contained entirely in the other, and that neither list + ///has more values than the other. List order is not considered. + bool stringListsMatch (const QStringList &list1, + const QStringList &list2) const; + + ///Converts a string list to a list of QStandardItem pointers. + QList toStandardItemList(const QStringList &) const; + + signals: + + ///Signals that the view has been changed. + void viewUpdated(const QString &, const QStringList &) const; + + }; + + class IViewFactory + { + public: + + ///Creation interface for view factories + virtual View *createView (CSMSettings::Setting *setting, + Page *parent) = 0; + }; +} +#endif // CSVSETTINGS_VIEW_HPP From e3384e399913e2dc4a56f599fea0f1e8b4ab65b9 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Tue, 22 Apr 2014 22:17:52 -0500 Subject: [PATCH 110/135] relocate support.hpp / remove support.cpp --- apps/opencs/CMakeLists.txt | 31 ++-- apps/opencs/model/settings/support.cpp | 1 - apps/opencs/model/settings/support.hpp | 139 +++++++++++++---- apps/opencs/view/settings/support.cpp | 1 - apps/opencs/view/settings/support.hpp | 206 ------------------------- 5 files changed, 126 insertions(+), 252 deletions(-) delete mode 100644 apps/opencs/model/settings/support.cpp delete mode 100644 apps/opencs/view/settings/support.cpp delete mode 100644 apps/opencs/view/settings/support.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index cbe90b1d3e..8ab44243dc 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -88,34 +88,29 @@ opencs_units_noqt (view/tools ) opencs_units (view/settings - abstractblock - proxyblock - abstractwidget - usersettingsdialog - datadisplayformatpage - windowpage + settingwindow + dialog + page + view + booleanview + textview + listview + resizeablestackedwidget ) opencs_units_noqt (view/settings - abstractpage - blankpage - groupblock - customblock - groupbox - itemblock - settingwidget - toggleblock - support + frame ) opencs_units (model/settings usersettings - settingcontainer + settingmanager + setting + connector ) -opencs_units_noqt (model/settings +opencs_hdrs_noqt (model/settings support - settingsitem ) opencs_units_noqt (model/filter diff --git a/apps/opencs/model/settings/support.cpp b/apps/opencs/model/settings/support.cpp deleted file mode 100644 index d79edfdb33..0000000000 --- a/apps/opencs/model/settings/support.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "support.hpp" diff --git a/apps/opencs/model/settings/support.hpp b/apps/opencs/model/settings/support.hpp index 4ffd01b73c..abc86a0cb6 100644 --- a/apps/opencs/model/settings/support.hpp +++ b/apps/opencs/model/settings/support.hpp @@ -1,39 +1,126 @@ -#ifndef MODEL_SUPPORT_HPP -#define MODEL_SUPPORT_HPP +#ifndef SETTING_SUPPORT_HPP +#define SETTING_SUPPORT_HPP -#include +#include +#include +#include +#include #include -class QLayout; -class QWidget; -class QListWidgetItem; - +//Typedefs namespace CSMSettings { - class SettingContainer; + // Definition / Declaration model typedefs + // "Pair" = Setting name and specific data + // "ListItem" = Page name and associated setting pair - typedef QList SettingList; - typedef QMap SettingMap; - typedef QMap SectionMap; + typedef QPair StringPair; + typedef QPair StringListPair; + typedef QList StringListPairs; - struct QStringPair +} + +//Enums +namespace CSMSettings +{ + enum SettingProperty { - QStringPair(): left (""), right ("") - {} + Property_Name = 0, + Property_Page = 1, + Property_ViewType = 2, + Property_IsMultiValue = 3, + Property_IsMultiLine = 4, + Property_WidgetWidth = 5, + Property_ViewRow = 6, + Property_ViewColumn = 7, + Property_Delimiter = 8, + Property_Serializable = 9, + Property_ColumnSpan = 10, + Property_RowSpan = 11, - QStringPair (const QString &leftValue, const QString &rightValue) - : left (leftValue), right(rightValue) - {} + //Stringlists should always be the last items + Property_DefaultValues = 12, + Property_DeclaredValues = 13, + Property_DefinedValues = 14, + Property_Proxies = 15 + }; - QStringPair (const QStringPair &pair) - : left (pair.left), right (pair.right) - {} + enum SettingType + { + Type_MultiBool = 0, + Type_SingleBool = 1, + Type_MultiList = 2, + Type_SingleList = 3, + Type_MultiRange = 4, + Type_SingleRange = 5, + Type_MultiText = 6, + Type_SingleText = 7 + }; - QString left; - QString right; - - bool isEmpty() const - { return (left.isEmpty() && right.isEmpty()); } + enum MergeMethod + { + Merge_Accept, + Merge_Ignore, + Merge_Overwrite }; } -#endif // MODEL_SUPPORT_HPP + +namespace CSVSettings +{ + enum ViewType + { + ViewType_Boolean = 0, + ViewType_List = 1, + ViewType_Range = 2, + ViewType_Text = 3, + ViewType_Undefined = 4 + }; + + enum Alignment + { + Align_Left = Qt::AlignLeft, + Align_Center = Qt::AlignHCenter, + Align_Right = Qt::AlignRight + }; +} + +// +namespace CSMSettings +{ + struct PropertyDefaultValues + { + int id; + QString name; + QVariant value; + }; + + const QString sPropertyNames[] = + { + "name", "page", "view_type", "is_multi_value", + "is_multi_line", "widget_width", "view_row", "view_column", "delimiter", + "is_serializable","column_span", "row_span", + "defaults", "declarations", "definitions", "proxies" + }; + + const QString sPropertyDefaults[] = + { + "", //name + "", //page + "0", //view type + "false", //multivalue + "false", //multiline + "0", //widget width + "-1", //view row + "-1", //view column + ",", //delimiter + "true", //serialized + "1", //column span + "1", //row span + "", //default values + "", //declared values + "", //defined values + "" //proxy values + }; +} + +#endif // VIEW_SUPPORT_HPP diff --git a/apps/opencs/view/settings/support.cpp b/apps/opencs/view/settings/support.cpp deleted file mode 100644 index d79edfdb33..0000000000 --- a/apps/opencs/view/settings/support.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "support.hpp" diff --git a/apps/opencs/view/settings/support.hpp b/apps/opencs/view/settings/support.hpp deleted file mode 100644 index 5d954505c4..0000000000 --- a/apps/opencs/view/settings/support.hpp +++ /dev/null @@ -1,206 +0,0 @@ -#ifndef VIEW_SUPPORT_HPP -#define VIEW_SUPPORT_HPP - -#include -#include - -#include "../../model/settings/support.hpp" - -namespace CSVSettings -{ - struct WidgetDef; - class ItemBlock; - class GroupBlock; - struct GroupBlockDef; - - typedef QList GroupBlockDefList; - typedef QList GroupBlockList; - typedef QList ItemBlockList; - typedef QList ProxyList; - typedef QList WidgetList; - typedef QMap ItemBlockMap; - - enum Orientation - { - Orient_Horizontal, - Orient_Vertical - }; - - enum WidgetType - { - Widget_CheckBox, - Widget_ComboBox, - Widget_LineEdit, - Widget_ListBox, - Widget_RadioButton, - Widget_SpinBox, - Widget_Undefined - }; - - enum Alignment - { - Align_Left = Qt::AlignLeft, - Align_Center = Qt::AlignHCenter, - Align_Right = Qt::AlignRight - }; - - /// definition struct for widgets - struct WidgetDef - { - /// type of widget providing input - WidgetType type; - - /// width of caption label - int labelWidth; - - /// width of input widget - int widgetWidth; - - /// label / widget orientation (horizontal / vertical) - Orientation orientation; - - /// input mask (line edit only) - QString inputMask; - - /// label caption. Leave empty for multiple items. See BlockDef::captionList - QString caption; - - /// widget value. Leave empty for multiple items. See BlockDef::valueList - QString value; - - /// Min/Max QString value pair. If empty, assigned to property item value pair. - CSMSettings::QStringPair *minMax; - - /// value list for list widgets. If left empty, is assigned to property item value list during block build(). - QStringList *valueList; - - /// determined at runtime - bool isDefault; - - /// left / center / right-justify text in widget - Alignment valueAlignment; - - /// left / center / right-justify widget in group box - Alignment widgetAlignment; - - - WidgetDef() : labelWidth (-1), widgetWidth (-1), - orientation (Orient_Horizontal), - isDefault (true), valueAlignment (Align_Center), - widgetAlignment (Align_Right), - inputMask (""), value (""), - caption (""), valueList (0) - {} - - WidgetDef (WidgetType widgType) - : type (widgType), orientation (Orient_Horizontal), - caption (""), value (""), valueAlignment (Align_Center), - widgetAlignment (Align_Right), - labelWidth (-1), widgetWidth (-1), - valueList (0), isDefault (true) - {} - - }; - - /// Defines the attributes of the setting as it is represented in the config file - /// as well as the UI elements (group box and widget) that serve it. - /// Only one widget may serve as the input widget for the setting. - struct SettingsItemDef - { - /// setting name - QString name; - - /// list of valid values for the setting - QStringList *valueList; - - /// Used to populate option widget captions or list widget item lists (see WidgetDef::caption / value) - QString defaultValue; - - /// flag indicating multi-valued setting - bool hasMultipleValues; - - /// minimum / maximum value pair - CSMSettings::QStringPair minMax; - - /// definition of the input widget for this setting - WidgetDef widget; - - /// general orientation of the widget / label for this setting - Orientation orientation; - - /// list of settings and corresponding default values for proxy widget - ProxyList *proxyList; - - SettingsItemDef() : name (""), defaultValue (""), orientation (Orient_Vertical), hasMultipleValues (false) - {} - - SettingsItemDef (QString propName, QString propDefault, Orientation propOrient = Orient_Vertical) - : name (propName), defaultValue (propDefault), orientation (propOrient), - hasMultipleValues(false), valueList (new QStringList), proxyList ( new ProxyList) - {} - }; - - - /// Generic container block - struct GroupBlockDef - { - /// block title - QString title; - - /// list of captions for widgets at the block level (not associated with any particular setting) - QStringList captions; - - /// list of widgets at the block level (not associated with any particular setting) - WidgetList widgets; - - /// list of the settings which are subordinate to the setting block. - QList settingItems; - - /// general orientation of widgets in group block - Orientation widgetOrientation; - - /// determines whether or not box border/title are visible - bool isVisible; - - /// indicates whether or not this block defines a proxy block - bool isProxy; - - /// generic default value attribute - QString defaultValue; - - /// shows / hides margins - bool isZeroMargin; - - GroupBlockDef (): title(""), widgetOrientation (Orient_Vertical), isVisible (true), isProxy (false), defaultValue (""), isZeroMargin (true) - {} - - GroupBlockDef (QString blockTitle) - : title (blockTitle), widgetOrientation (Orient_Vertical), isProxy (false), isVisible (true), defaultValue (""), isZeroMargin (true) - {} - }; - - /// used to create unique, complex blocks - struct CustomBlockDef - { - /// block title - QString title; - - /// default value for widgets unique to the custom block - QString defaultValue; - - /// list of settings groups that comprise the settings within the custom block - GroupBlockDefList blockDefList; - - /// orientation of the widgets within the block - Orientation blockOrientation; - - CustomBlockDef (): title (""), defaultValue (""), blockOrientation (Orient_Horizontal) - {} - - CustomBlockDef (const QString &blockTitle) - : title (blockTitle), defaultValue (""), blockOrientation (Orient_Horizontal) - {} - }; -} - -#endif // VIEW_SUPPORT_HPP From 4b607d658f5e8f2008d06395f270b2f902ef4d3f Mon Sep 17 00:00:00 2001 From: graffy76 Date: Tue, 22 Apr 2014 22:19:53 -0500 Subject: [PATCH 111/135] Re-link user settings to editor main application --- apps/opencs/editor.cpp | 13 + apps/opencs/editor.hpp | 4 +- apps/opencs/model/settings/usersettings.cpp | 477 +++++++++----------- apps/opencs/model/settings/usersettings.hpp | 45 +- apps/opencs/view/doc/subview.cpp | 9 +- apps/opencs/view/doc/subview.hpp | 4 +- apps/opencs/view/doc/view.cpp | 39 +- apps/opencs/view/doc/view.hpp | 2 + apps/opencs/view/doc/viewmanager.cpp | 17 +- apps/opencs/view/doc/viewmanager.hpp | 3 - apps/opencs/view/tools/reportsubview.cpp | 7 +- apps/opencs/view/tools/reportsubview.hpp | 5 +- apps/opencs/view/world/table.cpp | 14 +- apps/opencs/view/world/table.hpp | 2 +- apps/opencs/view/world/tablesubview.cpp | 7 +- apps/opencs/view/world/tablesubview.hpp | 3 +- apps/opencs/view/world/util.cpp | 6 +- apps/opencs/view/world/util.hpp | 4 +- 18 files changed, 290 insertions(+), 371 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 87660a60bc..2dfdb1de66 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -28,6 +28,7 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) setupDataFiles (config.first); CSMSettings::UserSettings::instance().loadSettings ("opencs.cfg"); + mSettings.setModel (CSMSettings::UserSettings::instance()); ogreInit.init ((mCfgMgr.getUserConfigPath() / "opencsOgre.log").string()); @@ -117,6 +118,18 @@ std::pair > CS::Editor::readConfi dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); + //iterate the data directories and add them to the file dialog for loading + for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) + { + QString path = QString::fromStdString(iter->string()); + mFileDialog.addFiles(path); + } +/* + //load the settings into the userSettings instance. + const QString settingFileName = "opencs.cfg"; + CSMSettings::UserSettings::instance().loadSettings(settingFileName); +*/ + return std::make_pair (dataDirs, variables["fallback-archive"].as >()); } diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 164398fb73..4c02b462e6 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -24,7 +24,7 @@ #include "view/doc/filedialog.hpp" #include "view/doc/newgame.hpp" -#include "view/settings/usersettingsdialog.hpp" +#include "view/settings/dialog.hpp" namespace OgreInit { @@ -43,7 +43,7 @@ namespace CS CSVDoc::ViewManager mViewManager; CSVDoc::StartupDialogue mStartup; CSVDoc::NewGameDialogue mNewGame; - CSVSettings::UserSettingsDialog mSettings; + CSVSettings::Dialog mSettings; CSVDoc::FileDialog mFileDialog; boost::filesystem::path mLocal; boost::filesystem::path mResources; diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 94cee8a43b..db31f42552 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -9,11 +9,16 @@ #include #include +#include + +#include #include -#include "settingcontainer.hpp" #include +#include "setting.hpp" +#include "support.hpp" + /** * Workaround for problems with whitespaces in paths in older versions of Boost library */ @@ -37,38 +42,151 @@ CSMSettings::UserSettings::UserSettings() assert(!mUserSettingsInstance); mUserSettingsInstance = this; - mReadWriteMessage = QObject::tr("
Could not open or create file for writing

\ - Please make sure you have the right permissions and try again.
"); - - mReadOnlyMessage = QObject::tr("
Could not open file for reading

\ - Please make sure you have the right permissions and try again.
"); - - buildEditorSettingDefaults(); + buildSettingModelDefaults(); } -void CSMSettings::UserSettings::buildEditorSettingDefaults() +void CSMSettings::UserSettings::buildSettingModelDefaults() { - SettingContainer *windowHeight = new SettingContainer("768", this); - SettingContainer *windowWidth = new SettingContainer("1024", this); - SettingContainer *rsDelegate = new SettingContainer("Icon and Text", this); - SettingContainer *refIdTypeDelegate = new SettingContainer("Icon and Text", this); + QString section = "Window Size"; + { + Setting *width = createSetting (Type_SingleText, section, "Width"); + Setting *height = createSetting (Type_SingleText, section, "Height"); - windowHeight->setObjectName ("Height"); - windowWidth->setObjectName ("Width"); - rsDelegate->setObjectName ("Record Status Display"); - refIdTypeDelegate->setObjectName ("Referenceable ID Type Display"); + width->setWidgetWidth (5); + height->setWidgetWidth (5); - SettingMap *displayFormatMap = new SettingMap; - SettingMap *windowSizeMap = new SettingMap; + width->setDefaultValues (QStringList() << "1024"); + height->setDefaultValues (QStringList() << "768"); - displayFormatMap->insert (rsDelegate->objectName(), rsDelegate ); - displayFormatMap->insert (refIdTypeDelegate->objectName(), refIdTypeDelegate); + width->setEditorSetting (true); + height->setEditorSetting (true); - windowSizeMap->insert (windowWidth->objectName(), windowWidth ); - windowSizeMap->insert (windowHeight->objectName(), windowHeight ); + height->setViewLocation (2,2); + width->setViewLocation (2,1); - mEditorSettingDefaults.insert ("Display Format", displayFormatMap); - mEditorSettingDefaults.insert ("Window Size", windowSizeMap); + /* + *Create the proxy setting for predefined values + */ + Setting *preDefined = createSetting (Type_SingleList, section, + "Pre-Defined", + QStringList() + << "640 x 480" + << "800 x 600" + << "1024 x 768" + << "1440 x 900" + ); + + preDefined->setViewLocation (1, 1); + preDefined->setWidgetWidth (10); + preDefined->setColumnSpan (2); + + preDefined->addProxy (width, + QStringList() << "640" << "800" << "1024" << "1440" + ); + + preDefined->addProxy (height, + QStringList() << "480" << "600" << "768" << "900" + ); + } + + section = "Display Format"; + { + QString defaultValue = "Icon and Text"; + + QStringList values = QStringList() + << defaultValue << "Icon Only" << "Text Only"; + + Setting *rsd = createSetting (Type_SingleBool, + section, "Record Status Display", + values); + + Setting *ritd = createSetting (Type_SingleBool, + section, "Referenceable ID Type Display", + values); + + rsd->setEditorSetting (true); + ritd->setEditorSetting (true); + } + + section = "Proxy Selection Test"; + { + //create three setting objects, specifying the basic widget type, + //the setting view name, the page name, and the default value + Setting *masterBoolean = createSetting (Type_SingleBool, section, + "Master Proxy", + QStringList() + << "Profile One" << "Profile Two" + << "Profile Three" << "Profile Four" + ); + + Setting *slaveBoolean = createSetting (Type_MultiBool, section, + "Proxy Checkboxes", + QStringList() << "One" << "Two" + << "Three" << "Four" << "Five" + ); + + Setting *slaveSingleText = createSetting (Type_SingleText, section, + "Proxy TextBox 1" + ); + + Setting *slaveMultiText = createSetting (Type_SingleText, section, + "ProxyTextBox 2" + ); + + // There are three types of values: + // + // Declared values - Pre-determined values, typically for + // combobox drop downs and boolean (radiobutton / checkbox) labels. + // These values represent the total possible list of values that may + // define a setting. No other values are allowed. + // + // Defined values - Values which represent the atual, current value of + // a setting. For settings with declared values, this must be one or + // several declared values, as appropriate. + // + // Proxy values - values the proxy master updates the proxy slave when + // it's own definition is set / changed. These are definitions for + // proxy slave settings, but must match any declared values the proxy + // slave has, if any. + + masterBoolean->addProxy (slaveBoolean, QList () + << (QStringList() << "One" << "Three") + << (QStringList() << "One" << "Three") + << (QStringList() << "One" << "Three" << "Five") + << (QStringList() << "Two" << "Four") + ); + + masterBoolean->addProxy (slaveSingleText, QList () + << (QStringList() << "Text A") + << (QStringList() << "Text B") + << (QStringList() << "Text A") + << (QStringList() << "Text C") + ); + + masterBoolean->addProxy (slaveMultiText, QList () + << (QStringList() << "One" << "Three") + << (QStringList() << "One" << "Three") + << (QStringList() << "One" << "Three" << "Five") + << (QStringList() << "Two" << "Four") + ); + + //settings with proxies are not serialized by default + //other settings non-serialized for demo purposes + slaveBoolean->setSerializable (false); + slaveSingleText->setSerializable (false); + slaveMultiText->setSerializable (false); + + slaveBoolean->setDefaultValues (QStringList() + << "One" << "Three" << "Five"); + + slaveSingleText->setDefaultValue ("Text A"); + + slaveMultiText->setDefaultValues (QStringList() + << "One" << "Three" << "Five"); + + slaveSingleText->setWidgetWidth (24); + slaveMultiText->setWidgetWidth (24); + } } CSMSettings::UserSettings::~UserSettings() @@ -76,230 +194,83 @@ CSMSettings::UserSettings::~UserSettings() mUserSettingsInstance = 0; } -QTextStream *CSMSettings::UserSettings::openFileStream (const QString &filePath, bool isReadOnly) const -{ - QIODevice::OpenMode openFlags = QIODevice::Text; - - if (isReadOnly) - openFlags = QIODevice::ReadOnly | openFlags; - else - openFlags = QIODevice::ReadWrite | QIODevice::Truncate | openFlags; - - QFile *file = new QFile(filePath); - QTextStream *stream = 0; - - if (file->open(openFlags)) - { - stream = new QTextStream(file); - stream->setCodec(QTextCodec::codecForName("UTF-8")); - } - - return stream; - -} - -bool CSMSettings::UserSettings::writeSettings(QMap &settings) -{ - QTextStream *stream = openFileStream(mUserFilePath); - - bool success = (stream); - - if (success) - { - QList keyList = settings.keys(); - - foreach (QString key, keyList) - { - SettingList *sectionSettings = settings[key]; - - *stream << "[" << key << "]" << '\n'; - - foreach (SettingContainer *item, *sectionSettings) - *stream << item->objectName() << " = " << item->getValue() << '\n'; - } - - stream->device()->close(); - delete stream; - stream = 0; - } - else - { - displayFileErrorMessage(mReadWriteMessage, false); - } - - return (success); -} - - -const CSMSettings::SectionMap &CSMSettings::UserSettings::getSectionMap() const -{ - return mSectionSettings; -} - -const CSMSettings::SettingMap *CSMSettings::UserSettings::getSettings(const QString §ionName) const -{ - return getValidSettings(sectionName); -} - -bool CSMSettings::UserSettings::loadFromFile(const QString &filePath) -{ - if (filePath.isEmpty()) - return false; - - SectionMap loadedSettings; - - QTextStream *stream = openFileStream (filePath, true); - - bool success = (stream); - - if (success) - { - //looks for a square bracket, "'\\[" - //that has one or more "not nothing" in it, "([^]]+)" - //and is closed with a square bracket, "\\]" - - QRegExp sectionRe("^\\[([^]]+)\\]"); - - //Find any character(s) that is/are not equal sign(s), "[^=]+" - //followed by an optional whitespace, an equal sign, and another optional whitespace, "\\s*=\\s*" - //and one or more periods, "(.+)" - - QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); - - CSMSettings::SettingMap *settings = 0; - QString section = "none"; - - while (!stream->atEnd()) - { - QString line = stream->readLine().simplified(); - - if (line.isEmpty() || line.startsWith("#")) - continue; - - //if a section is found, push it onto a new QStringList - //and push the QStringList onto - if (sectionRe.exactMatch(line)) - { - //add the previous section's settings to the member map - if (settings) - loadedSettings.insert(section, settings); - - //save new section and create a new list - section = sectionRe.cap(1); - settings = new SettingMap; - continue; - } - - if (keyRe.indexIn(line) != -1) - { - SettingContainer *sc = new SettingContainer (keyRe.cap(2).simplified()); - sc->setObjectName(keyRe.cap(1).simplified()); - (*settings)[keyRe.cap(1).simplified()] = sc; - } - - } - - loadedSettings.insert(section, settings); - - stream->device()->close(); - delete stream; - stream = 0; - } - - mergeMap (loadedSettings); - - return success; -} - -void CSMSettings::UserSettings::mergeMap (const CSMSettings::SectionMap §ionSettings) -{ - foreach (QString key, sectionSettings.uniqueKeys()) - { - // insert entire section if it does not already exist in the loaded files - if (mSectionSettings.find(key) == mSectionSettings.end()) - mSectionSettings.insert(key, sectionSettings.value(key)); - else - { - SettingMap *passedSettings = sectionSettings.value(key); - SettingMap *settings = mSectionSettings.value(key); - - foreach (QString key2, passedSettings->uniqueKeys()) - { - //insert section settings individially if they do not already exist - if (settings->find(key2) == settings->end()) - settings->insert(key2, passedSettings->value(key2)); - else - { - settings->value(key2)->update(passedSettings->value(key2)->getValue()); - } - } - } - } -} - void CSMSettings::UserSettings::loadSettings (const QString &fileName) { - mSectionSettings.clear(); + mUserFilePath = QString::fromUtf8 + (mCfgMgr.getUserConfigPath().c_str()) + fileName.toUtf8(); - //global - QString globalFilePath = QString::fromStdString(mCfgMgr.getGlobalPath().string()) + fileName; - bool globalOk = loadFromFile(globalFilePath); + QString global = QString::fromUtf8 + (mCfgMgr.getGlobalPath().c_str()) + fileName.toUtf8(); + QString local = QString::fromUtf8 + (mCfgMgr.getLocalPath().c_str()) + fileName.toUtf8(); - //local - QString localFilePath = QString::fromStdString(mCfgMgr.getLocalPath().string()) + fileName; - bool localOk = loadFromFile(localFilePath); + //open user and global streams + QTextStream *userStream = openFilestream (mUserFilePath, true); + QTextStream *otherStream = openFilestream (global, true); - //user - mUserFilePath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + fileName; - loadFromFile(mUserFilePath); + //failed stream, try for local + if (!otherStream) + otherStream = openFilestream (local, true); - if (!(localOk || globalOk)) + //error condition - notify and return + if (!otherStream || !userStream) { - QString message = QObject::tr("
Could not open user settings files for reading

\ - Global and local settings files could not be read.\ - You may have incorrect file permissions or the OpenCS installation may be corrupted.
"); + QString message = QObject::tr("
An error was encountered loading \ + user settings files.

One or several files could not \ + be read. This may be caused by a missing configuration file, \ + incorrect file permissions or a corrupted installation of \ + OpenCS.
"); - message += QObject::tr("
Global filepath: ") + globalFilePath; - message += QObject::tr("
Local filepath: ") + localFilePath; + message += QObject::tr("
Global filepath: ") + global; + message += QObject::tr("
Local filepath: ") + local; + message += QObject::tr("
User filepath: ") + mUserFilePath; displayFileErrorMessage ( message, true); - } -} - -void CSMSettings::UserSettings::updateSettings (const QString §ionName, const QString &settingName) -{ - - SettingMap *settings = getValidSettings(sectionName); - - if (!settings) return; + } - if (settingName.isEmpty()) - { - foreach (const SettingContainer *setting, *settings) - emit signalUpdateEditorSetting (setting->objectName(), setting->getValue()); - } - else - { - if (settings->find(settingName) != settings->end()) - { - const SettingContainer *setting = settings->value(settingName); - emit signalUpdateEditorSetting (setting->objectName(), setting->getValue()); - } - } + //success condition - merge the two streams into a single map and save + DefinitionPageMap totalMap = readFilestream (userStream); + DefinitionPageMap otherMap = readFilestream(otherStream); + + //merging other settings file in and ignore duplicate settings to + //avoid overwriting user-level settings + mergeSettings (totalMap, otherMap); + + if (!totalMap.isEmpty()) + addDefinitions (totalMap); } -QString CSMSettings::UserSettings::getSetting (const QString §ion, const QString &setting) const +void CSMSettings::UserSettings::saveSettings + (const QMap &settingMap) { - SettingMap *settings = getValidSettings(section); + for (int i = 0; i < settings().size(); i++) + { + Setting* setting = settings().at(i); - QString retVal = ""; + QString key = setting->page() + '.' + setting->name(); - if (settings->find(setting) != settings->end()) - retVal = settings->value(setting)->getValue(); + if (!settingMap.keys().contains(key)) + continue; - return retVal; + setting->setDefinedValues (settingMap.value(key)); + } + + writeFilestream (openFilestream (mUserFilePath, false), settingMap); +} + +QString CSMSettings::UserSettings::settingValue (const QString §ion, + const QString &name) +{ + Setting *setting = findSetting(section, name); + + if (setting) + { + if (!setting->definedValues().isEmpty()) + return setting->definedValues().at(0); + } + return ""; } CSMSettings::UserSettings& CSMSettings::UserSettings::instance() @@ -307,49 +278,3 @@ CSMSettings::UserSettings& CSMSettings::UserSettings::instance() assert(mUserSettingsInstance); return *mUserSettingsInstance; } - -void CSMSettings::UserSettings::displayFileErrorMessage(const QString &message, bool isReadOnly) -{ - // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle(QObject::tr("OpenCS configuration file I/O error")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - - if (!isReadOnly) - msgBox.setText (mReadWriteMessage + message); - else - msgBox.setText (message); - - msgBox.exec(); -} - -CSMSettings::SettingMap * -CSMSettings::UserSettings::getValidSettings (const QString §ionName) const -{ - SettingMap *settings = 0; - - //copy the default values for the entire section if it's not found - if (mSectionSettings.find(sectionName) == mSectionSettings.end()) - { - if (mEditorSettingDefaults.find(sectionName) != mEditorSettingDefaults.end()) - settings = mEditorSettingDefaults.value (sectionName); - } - //otherwise, iterate the section's settings, looking for missing values and replacing them with defaults. - else - { - SettingMap *loadedSettings = mSectionSettings[sectionName]; - SettingMap *defaultSettings = mEditorSettingDefaults[sectionName]; - - foreach (QString key, defaultSettings->uniqueKeys()) - { - //write the default value to the loaded settings - if (loadedSettings->find((key))==loadedSettings->end()) - loadedSettings->insert(key, defaultSettings->value(key)); - } - - settings = mSectionSettings.value (sectionName); - } - - return settings; -} diff --git a/apps/opencs/model/settings/usersettings.hpp b/apps/opencs/model/settings/usersettings.hpp index 63e78bd612..75a3a0797a 100644 --- a/apps/opencs/model/settings/usersettings.hpp +++ b/apps/opencs/model/settings/usersettings.hpp @@ -8,7 +8,7 @@ #include -#include "support.hpp" +#include "settingmanager.hpp" #ifndef Q_MOC_RUN #include @@ -21,17 +21,15 @@ class QFile; namespace CSMSettings { - struct UserSettings: public QObject + class UserSettings: public SettingManager { Q_OBJECT - SectionMap mSectionSettings; - SectionMap mEditorSettingDefaults; - static UserSettings *mUserSettingsInstance; QString mUserFilePath; Files::ConfigurationManager mCfgMgr; + QString mReadOnlyMessage; QString mReadWriteMessage; @@ -47,48 +45,23 @@ namespace CSMSettings { void operator= (UserSettings const &); //not implemented /// Writes settings to the last loaded settings file - bool writeSettings(QMap §ions); - - /// Called from editor to trigger signal to update the specified setting. - /// If no setting name is specified, all settings found in the specified section are updated. - void updateSettings (const QString §ionName, const QString &settingName = ""); + bool writeSettings(); /// Retrieves the settings file at all three levels (global, local and user). - - /// \todo Multi-valued settings are not fully implemented. Setting values - /// \todo loaded in later files will always overwrite previously loaded values. void loadSettings (const QString &fileName); - /// Returns the entire map of settings across all sections - const SectionMap &getSectionMap () const; + /// Writes settings to the user's config file path + void saveSettings (const QMap &settingMap); - const SettingMap *getSettings (const QString §ionName) const; - - /// Retrieves the value as a QString of the specified setting in the specified section - QString getSetting(const QString §ion, const QString &setting) const; + QString settingValue (const QString §ion, const QString &name); private: - - /// Opens a QTextStream from the provided path as read-only or read-write. - QTextStream *openFileStream (const QString &filePath, bool isReadOnly = false) const; - - /// Parses a setting file specified in filePath from the provided text stream. - bool loadFromFile (const QString &filePath = ""); - - /// merge the passed map into mSectionSettings - void mergeMap (const SectionMap &); - - void displayFileErrorMessage(const QString &message, bool isReadOnly); - - void buildEditorSettingDefaults(); - - SettingMap *getValidSettings (const QString §ionName) const; + void buildSettingModelDefaults(); signals: - void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue); - + void userSettingUpdated(const QString &, const QStringList &); }; } #endif // USERSETTINGS_HPP diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index 7fd0057173..a80d21cb2d 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -12,16 +12,15 @@ CSMWorld::UniversalId CSVDoc::SubView::getUniversalId() const return mUniversalId; } -void CSVDoc::SubView::updateEditorSetting (const QString &settingName, const QString &settingValue) -{ -} - void CSVDoc::SubView::setStatusBar (bool show) {} void CSVDoc::SubView::useHint (const std::string& hint) {} +void CSVDoc::SubView::updateUserSetting (const QString &, const QStringList &) +{} + void CSVDoc::SubView::setUniversalId (const CSMWorld::UniversalId& id) { mUniversalId = id; setWindowTitle (mUniversalId.toString().c_str()); -} \ No newline at end of file +} diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index 85274a18db..52e42cc0ef 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -37,7 +37,9 @@ namespace CSVDoc CSMWorld::UniversalId getUniversalId() const; virtual void setEditLock (bool locked) = 0; - virtual void updateEditorSetting (const QString &, const QString &); + + virtual void updateUserSetting + (const QString &, const QStringList &); virtual void setStatusBar (bool show); ///< Default implementation: ignored diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index bc34c6118e..47e1a80de3 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -12,7 +12,7 @@ #include "../../model/doc/document.hpp" #include "../world/subviews.hpp" #include "../tools/subviews.hpp" -#include "../settings/usersettingsdialog.hpp" +#include "../../model/settings/usersettings.hpp" #include "viewmanager.hpp" #include "operations.hpp" #include "subview.hpp" @@ -235,8 +235,11 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), mViewTotal (totalViews) { - QString width = CSMSettings::UserSettings::instance().getSetting(QString("Window Size"), QString("Width")); - QString height = CSMSettings::UserSettings::instance().getSetting(QString("Window Size"), QString("Height")); + QString width = CSMSettings::UserSettings::instance().settingValue + (QString("Window Size"), QString("Width")); + + QString height = CSMSettings::UserSettings::instance().settingValue + (QString("Window Size"), QString("Height")); resize (width.toInt(), height.toInt()); @@ -336,7 +339,10 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&)), this, SLOT (addSubView (const CSMWorld::UniversalId&, const std::string&))); - CSMSettings::UserSettings::instance().updateSettings("Display Format"); + connect (&CSMSettings::UserSettings::instance(), + SIGNAL (userSettingUpdated (const QString &, const QStringList &)), + view, + SLOT (updateUserSetting (const QString &, const QStringList &))); view->show(); } @@ -484,24 +490,19 @@ void CSVDoc::View::resizeViewHeight (int height) resize (geometry().width(), height); } -void CSVDoc::View::updateEditorSetting (const QString &settingName, const QString &settingValue) +void CSVDoc::View::updateUserSetting + (const QString &name, const QStringList &list) { - if ( (settingName == "Record Status Display") || (settingName == "Referenceable ID Type Display") ) - { - foreach (QObject *view, mSubViewWindow.children()) - { - // not all mSubviewWindow children are CSVDoc::Subview objects - CSVDoc::SubView *subview = dynamic_cast(view); + if (list.isEmpty()) + return; - if (subview) - subview->updateEditorSetting (settingName, settingValue); - } - } - else if (settingName == "Width") - resizeViewWidth (settingValue.toInt()); + int value = list.at(0).toInt(); - else if (settingName == "Height") - resizeViewHeight (settingValue.toInt()); + if (name == "Width") + resizeViewWidth (value); + + else if (name == "Height") + resizeViewHeight (value); } void CSVDoc::View::toggleShowStatusBar (bool show) diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index ee7380e2b1..5e6c9abc4d 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -126,6 +126,8 @@ namespace CSVDoc void abortOperation (int type); + void updateUserSetting (const QString &, const QStringList &); + private slots: void newView(); diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 4a4dbc1244..2297af0ba0 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -15,7 +15,8 @@ #include "../world/vartypedelegate.hpp" #include "../world/recordstatusdelegate.hpp" #include "../world/idtypedelegate.hpp" -#include "../settings/usersettingsdialog.hpp" + +#include "../../model/settings/usersettings.hpp" #include "view.hpp" @@ -83,9 +84,6 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) for (std::size_t i=0; iadd (sMapping[i].mDisplay, new CSVWorld::EnumDelegateFactory ( CSMWorld::Columns::getEnums (sMapping[i].mColumnId), sMapping[i].mAllowNone)); - - connect (&CSMSettings::UserSettings::instance(), SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)), - this, SLOT (slotUpdateEditorSetting (const QString &, const QString &))); } CSVDoc::ViewManager::~ViewManager() @@ -119,6 +117,11 @@ CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document) connect (view, SIGNAL (loadDocumentRequest ()), this, SIGNAL (loadDocumentRequest())); connect (view, SIGNAL (editSettingsRequest()), this, SIGNAL (editSettingsRequest())); + connect (&CSMSettings::UserSettings::instance(), + SIGNAL (userSettingUpdated(const QString &, const QStringList &)), + view, + SLOT (updateUserSetting (const QString &, const QStringList &))); + updateIndices(); return view; @@ -313,9 +316,3 @@ void CSVDoc::ViewManager::exitApplication (CSVDoc::View *view) if (notifySaveOnClose (view)) QApplication::instance()->exit(); } - -void CSVDoc::ViewManager::slotUpdateEditorSetting (const QString &settingName, const QString &settingValue) -{ - foreach (CSVDoc::View *view, mViews) - view->updateEditorSetting (settingName, settingValue); -} diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp index 01f4951864..00e33916d0 100644 --- a/apps/opencs/view/doc/viewmanager.hpp +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -76,9 +76,6 @@ namespace CSVDoc void progress (int current, int max, int type, int threads, CSMDoc::Document *document); void onExitWarningHandler(int state, CSMDoc::Document* document); - - /// connected to update signal in UserSettings - void slotUpdateEditorSetting (const QString &, const QString &); }; } diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index d59f0c2349..e84f5cf4ba 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -33,12 +33,13 @@ void CSVTools::ReportSubView::setEditLock (bool locked) // ignored. We don't change document state anyway. } -void CSVTools::ReportSubView::updateEditorSetting (const QString& key, const QString& value) +void CSVTools::ReportSubView::updateUserSetting + (const QString &name, const QStringList &list) { - mIdTypeDelegate->updateEditorSetting (key, value); + mIdTypeDelegate->updateUserSetting (name, list); } void CSVTools::ReportSubView::show (const QModelIndex& index) { focusId (mModel->getUniversalId (index.row()), ""); -} \ No newline at end of file +} diff --git a/apps/opencs/view/tools/reportsubview.hpp b/apps/opencs/view/tools/reportsubview.hpp index 6503ebd278..9f6a4c1da3 100644 --- a/apps/opencs/view/tools/reportsubview.hpp +++ b/apps/opencs/view/tools/reportsubview.hpp @@ -39,7 +39,8 @@ namespace CSVTools virtual void setEditLock (bool locked); - virtual void updateEditorSetting (const QString&, const QString&); + virtual void updateUserSetting + (const QString &, const QStringList &); private slots: @@ -47,4 +48,4 @@ namespace CSVTools }; } -#endif \ No newline at end of file +#endif diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 712b8f5569..995de21fc1 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -449,15 +449,19 @@ void CSVWorld::Table::previewRecord() } } -void CSVWorld::Table::updateEditorSetting (const QString &settingName, const QString &settingValue) +void CSVWorld::Table::updateUserSetting + (const QString &name, const QStringList &list) { int columns = mModel->columnCount(); for (int i=0; i (*delegate). - updateEditorSetting (settingName, settingValue)) - emit dataChanged (mModel->index (0, i), mModel->index (mModel->rowCount()-1, i)); + if (dynamic_cast + (*delegate).updateUserSetting (name, list)) + { + emit dataChanged (mModel->index (0, i), + mModel->index (mModel->rowCount()-1, i)); + } } void CSVWorld::Table::tableSizeUpdate() @@ -598,4 +602,4 @@ std::vector CSVWorld::Table::getColumnsWithDisplay(CSMWorld::Column } } return titles; -} \ No newline at end of file +} diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 4231a4a432..dfc74b3eb8 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -78,7 +78,7 @@ namespace CSVWorld CSMWorld::UniversalId getUniversalId (int row) const; - void updateEditorSetting (const QString &settingName, const QString &settingValue); + void updateUserSetting (const QString &name, const QStringList &list); std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index a5a7e8252e..c193ed32ba 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -82,9 +82,10 @@ void CSVWorld::TableSubView::editRequest (const CSMWorld::UniversalId& id, const focusId (id, hint); } -void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) +void CSVWorld::TableSubView::updateUserSetting + (const QString &name, const QStringList &list) { - mTable->updateEditorSetting(settingName, settingValue); + mTable->updateUserSetting(name, list); } void CSVWorld::TableSubView::setStatusBar (bool show) @@ -134,4 +135,4 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) return handled; } return false; -} \ No newline at end of file +} diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index b344ba1ad2..9d86c32e40 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -43,7 +43,8 @@ namespace CSVWorld virtual void setEditLock (bool locked); - virtual void updateEditorSetting (const QString& key, const QString& value); + virtual void updateUserSetting + (const QString& name, const QStringList &list); virtual void setStatusBar (bool show); diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index b2a32b551c..16310c8a97 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -196,8 +196,8 @@ bool CSVWorld::CommandDelegate::isEditLocked() const return mEditLock; } -bool CSVWorld::CommandDelegate::updateEditorSetting (const QString &settingName, - const QString &settingValue) +bool CSVWorld::CommandDelegate::updateUserSetting (const QString &name, + const QStringList &list) { return false; } @@ -263,4 +263,4 @@ void CSVWorld::DropLineEdit::dropEvent(QDropEvent *event) const CSMWorld::TableMimeData* data(dynamic_cast(event->mimeData())); emit tableMimeDataDropped(data->getData(), data->getDocumentPtr()); //WIP -} \ No newline at end of file +} diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp index 7664f3eae2..50e2cf858e 100644 --- a/apps/opencs/view/world/util.hpp +++ b/apps/opencs/view/world/util.hpp @@ -136,7 +136,9 @@ namespace CSVWorld bool isEditLocked() const; - virtual bool updateEditorSetting (const QString &settingName, const QString &settingValue); + virtual bool updateUserSetting + (const QString &name, const QStringList &list); + ///< \return Does column require update? virtual void setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay = false) const; From e71a119c23ac5992ed016f0d3a2e4298494b309c Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 02:57:48 -0400 Subject: [PATCH 112/135] Made aifollowers run when long distances (800 or 10000, depending) from what they're following. --- apps/openmw/mwmechanics/aifollow.cpp | 60 ++++++++++++++++++---------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index c3b36516ce..161c9700f1 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -4,6 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "creaturestats.hpp" #include "movement.hpp" #include @@ -26,69 +27,77 @@ MWMechanics::AiFollow::AiFollow(const std::string &actorId) bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { - const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); - - if(target == MWWorld::Ptr()) return true; + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); //The target to follow - mTimer = mTimer + duration; - mStuckTimer = mStuckTimer + duration; - mTotalTime = mTotalTime + duration; + if(target == MWWorld::Ptr()) return true; //Target doesn't exist - ESM::Position pos = actor.getRefData().getPosition(); + mTimer = mTimer + duration; //Update timer + mStuckTimer = mStuckTimer + duration; //Update stuck timer + mTotalTime = mTotalTime + duration; //Update total time following - if(!mAlwaysFollow) + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + + if(!mAlwaysFollow) //Update if you only follow for a bit { - if(mTotalTime > mDuration && mDuration != 0) + if(mTotalTime > mDuration && mDuration != 0) //Check if we've run out of time return true; if((pos.pos[0]-mX)*(pos.pos[0]-mX) + (pos.pos[1]-mY)*(pos.pos[1]-mY) + - (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) + (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) //Close-ish to final position { - if(actor.getCell()->isExterior()) + if(actor.getCell()->isExterior()) //Outside? { - if(mCellId == "") + if(mCellId == "") //No cell to travel to return true; } else { - if(mCellId == actor.getCell()->getCell()->mName) + if(mCellId == actor.getCell()->getCell()->mName) //Cell to travel to return true; } } } + //Set the target desition from the actor ESM::Pathgrid::Point dest; dest.mX = target.getRefData().getPosition().pos[0]; dest.mY = target.getRefData().getPosition().pos[1]; dest.mZ = target.getRefData().getPosition().pos[2]; + //Current position, for pathfilding stuff ESM::Pathgrid::Point start; start.mX = pos.pos[0]; start.mY = pos.pos[1]; start.mZ = pos.pos[2]; + //Build the path to get to the destination if(mPathFinder.getPath().empty()) mPathFinder.buildPath(start, dest, actor.getCell(), true); - + //*********************** + // Checks if you can't get to the end position at all + //*********************** if(mTimer > 0.25) { - if(!mPathFinder.getPath().empty()) + if(!mPathFinder.getPath().empty()) //Path has points in it { - ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); + ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path - if((dest.mX - lastPos.mX)*(dest.mX - lastPos.mX) + if((dest.mX - lastPos.mX)*(dest.mX - lastPos.mX) +(dest.mY - lastPos.mY)*(dest.mY - lastPos.mY) +(dest.mZ - lastPos.mZ)*(dest.mZ - lastPos.mZ) - > 100*100) - mPathFinder.addPointToPath(dest); + > 100*100) //End of the path is far from the destination + mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go } mTimer = 0; } - if(mStuckTimer>0.5) + //************************ + // Checks if you aren't moving; you're stuck + //************************ + if(mStuckTimer>0.5) //Checks every half of a second { if((mStuckPos.pos[0] - pos.pos[0])*(mStuckPos.pos[0] - pos.pos[0]) +(mStuckPos.pos[1] - pos.pos[1])*(mStuckPos.pos[1] - pos.pos[1]) @@ -99,6 +108,7 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) mStuckPos = pos; } + //Checks if the path isn't over, turn tomards the direction that you're going if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) { zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); @@ -110,6 +120,16 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) else actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + //Check if you're far away + if((dest.mX - start.mX)*(dest.mX - start.mX) + +(dest.mY - start.mY)*(dest.mY - start.mY) + +(dest.mZ - start.mZ)*(dest.mZ - start.mZ) > 1000*1000) + actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run + else if((dest.mX - start.mX)*(dest.mX - start.mX) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold + +(dest.mY - start.mY)*(dest.mY - start.mY) + +(dest.mZ - start.mZ)*(dest.mZ - start.mZ) < 800*800) + actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk + return false; } From cac8e52154a99b310368ceabe3b61dcf2f63d44c Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 05:12:07 -0400 Subject: [PATCH 113/135] Seperated locked and lock level, to allow for relocking doors to previous lock level. The data is stored in the esm as -lockLevel if unlocked; lockLevel if locked. While not tested, it should not present any problems. --- apps/openmw/mwclass/container.cpp | 23 +++++++++++++++-------- apps/openmw/mwclass/container.hpp | 5 ++++- apps/openmw/mwclass/door.cpp | 15 +++++++++++---- apps/openmw/mwclass/door.hpp | 6 +++++- apps/openmw/mwmechanics/security.cpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 5 +++-- apps/openmw/mwscript/miscextensions.cpp | 6 +++--- apps/openmw/mwworld/class.cpp | 7 ++++++- apps/openmw/mwworld/class.hpp | 2 ++ apps/openmw/mwworld/manualref.hpp | 1 + 10 files changed, 51 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 604b51990a..ee6dba9824 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -111,7 +111,7 @@ namespace MWClass MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWWorld::InventoryStore& invStore = MWWorld::Class::get(player).getInventoryStore(player); - bool needKey = ptr.getCellRef().mLockLevel>0; + bool needKey = ptr.getCellRef().mLocked; bool hasKey = false; std::string keyName; @@ -132,7 +132,7 @@ namespace MWClass if (needKey && hasKey) { MWBase::Environment::get().getWindowManager ()->messageBox (keyName + " #{sKeyUsed}"); - ptr.getCellRef().mLockLevel = 0; + unlock(ptr); // using a key disarms the trap ptr.getCellRef().mTrap = ""; } @@ -209,7 +209,7 @@ namespace MWClass info.caption = ref->mBase->mName; std::string text; - if (ref->mRef.mLockLevel > 0) + if (ref->mRef.mLocked) text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ref->mRef.mLockLevel); if (ref->mRef.mTrap != "") text += "\n#{sTrapped}"; @@ -240,15 +240,22 @@ namespace MWClass void Container::lock (const MWWorld::Ptr& ptr, int lockLevel) const { - if (lockLevel<0) - lockLevel = 0; - - ptr.getCellRef().mLockLevel = lockLevel; + ptr.getCellRef().mLocked = true; + if(lockLevel>=0) //Lock level setting left as most of the code relies on this + ptr.getCellRef().mLockLevel = lockLevel; } void Container::unlock (const MWWorld::Ptr& ptr) const { - ptr.getCellRef().mLockLevel = 0; + ptr.getCellRef().mLocked= false; + } + + void Container::changeLockLevel(const MWWorld::Ptr& ptr, int lockLevel, bool doLock) { + if (lockLevel<0) + lockLevel = 0; + + ptr.getCellRef().mLockLevel = lockLevel; + if(doLock) lock(ptr); } MWWorld::Ptr diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index c97867d35a..6945ae4414 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -48,12 +48,15 @@ namespace MWClass ///< Returns total weight of objects inside this object (including modifications from magic /// effects). Throws an exception, if the object can't hold other objects. - virtual void lock (const MWWorld::Ptr& ptr, int lockLevel) const; + virtual void lock (const MWWorld::Ptr& ptr, int lockLevel = -999) const; ///< Lock object virtual void unlock (const MWWorld::Ptr& ptr) const; ///< Unlock object + ///Changes the lock level of the object + virtual void changeLockLevel(const MWWorld::Ptr& ptr, int lockLevel, bool lock=true); + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const; ///< Read additional state from \a state into \a ptr. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 3cd8237e70..2662e1c537 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -76,7 +76,7 @@ namespace MWClass MWWorld::ContainerStore &invStore = get(actor).getContainerStore(actor); - bool needKey = ptr.getCellRef().mLockLevel>0; + bool needKey = ptr.getCellRef().mLocked; bool hasKey = false; std::string keyName; @@ -98,7 +98,7 @@ namespace MWClass { if(actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}"); - ptr.getCellRef().mLockLevel = 0; + unlock(ptr); //Call the function here. because that makes sense. // using a key disarms the trap ptr.getCellRef().mTrap = ""; } @@ -158,15 +158,22 @@ namespace MWClass void Door::lock (const MWWorld::Ptr& ptr, int lockLevel) const { + ptr.getCellRef().mLocked = true; + if(lockLevel>=0) //Lock level setting left as most of the code relies on this + ptr.getCellRef().mLockLevel = lockLevel; + } + + void Door::changeLockLevel(const MWWorld::Ptr& ptr, int lockLevel, bool doLock) const{ if (lockLevel<0) lockLevel = 0; ptr.getCellRef().mLockLevel = lockLevel; + if(doLock) lock(ptr); //A change in lock level almost always nesesitates a lock } void Door::unlock (const MWWorld::Ptr& ptr) const { - ptr.getCellRef().mLockLevel = 0; + ptr.getCellRef().mLocked = false; } std::string Door::getScript (const MWWorld::Ptr& ptr) const @@ -208,7 +215,7 @@ namespace MWClass text += "\n" + getDestination(*ref); } - if (ref->mRef.mLockLevel > 0) + if (ref->mRef.mLocked == true) text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ref->mRef.mLockLevel); if (ref->mRef.mTrap != "") text += "\n#{sTrapped}"; diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 2ac342a61e..e053136d5d 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -36,18 +36,22 @@ namespace MWClass static std::string getDestination (const MWWorld::LiveCellRef& door); ///< @return destination cell name or token - virtual void lock (const MWWorld::Ptr& ptr, int lockLevel) const; + virtual void lock (const MWWorld::Ptr& ptr, int lockLevel = -999) const; ///< Lock object virtual void unlock (const MWWorld::Ptr& ptr) const; ///< Unlock object + ///Change the lock level + virtual void changeLockLevel(const MWWorld::Ptr& ptr, int lockLevel, bool lock=true) const; + virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr static void registerSelf(); virtual std::string getModel(const MWWorld::Ptr &ptr) const; + private: }; } diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index edec45e15f..ba2a32727d 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -29,7 +29,7 @@ namespace MWMechanics void Security::pickLock(const MWWorld::Ptr &lock, const MWWorld::Ptr &lockpick, std::string& resultMessage, std::string& resultSound) { - if (lock.getCellRef().mLockLevel <= 0) + if (!lock.getCellRef().mLocked) return; int lockStrength = lock.getCellRef().mLockLevel; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 6ea3f54729..d861e2032b 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -420,6 +420,7 @@ namespace MWMechanics { if (effectId == ESM::MagicEffect::Lock) { + target.getCellRef().mLocked = true; if (target.getCellRef().mLockLevel < magnitude) target.getCellRef().mLockLevel = magnitude; } @@ -427,12 +428,12 @@ namespace MWMechanics { if (target.getCellRef().mLockLevel <= magnitude) { - if (target.getCellRef().mLockLevel > 0) + if (target.getCellRef().mLocked) { MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target); } - target.getCellRef().mLockLevel = 0; + target.getCellRef().mLocked=false; } else MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 968d3bbd74..f10d1df965 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -131,7 +131,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer lockLevel = 100; + Interpreter::Type_Integer lockLevel = ptr.getCellRef().mLockLevel; if (arg0==1) { @@ -283,7 +283,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { // We are ignoring the DontSaveObject statement for now. Probably not worth - /// bothering with. The incompatibility we are creating should be marginal at most. + // bothering with. The incompatibility we are creating should be marginal at most. } }; @@ -320,7 +320,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push (ptr.getCellRef ().mLockLevel > 0); + runtime.push (ptr.getCellRef().mLocked); } }; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 99dbcc66c5..4b3d40ab78 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -137,6 +137,11 @@ namespace MWWorld throw std::runtime_error ("class does not support locking"); } + void Class::setLockLevel (const Ptr& ptr, int lockLevel) const + { + throw std::runtime_error ("class does not support setting lock level"); + } + void Class::unlock (const Ptr& ptr) const { throw std::runtime_error ("class does not support unlocking"); @@ -397,7 +402,7 @@ namespace MWWorld void Class::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const {} - int Class::getBaseGold(const MWWorld::Ptr& ptr) const + int Class::getBaseGold(const MWWorld::Ptr& ptr) const { throw std::runtime_error("class does not support base gold"); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 27b8423484..dc06bf6c96 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -165,6 +165,8 @@ namespace MWWorld virtual void lock (const Ptr& ptr, int lockLevel) const; ///< Lock object (default implementation: throw an exception) + virtual void setLockLevel(const Ptr& ptr, int lockLevel) const; + virtual void unlock (const Ptr& ptr) const; ///< Unlock object (default implementation: throw an exception) diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index 0e21c55acd..174e344232 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -76,6 +76,7 @@ namespace MWWorld cellRef.mEnchantmentCharge = -1; cellRef.mTeleport = false; cellRef.mLockLevel = 0; + cellRef.mLocked = false; cellRef.mReferenceBlocked = 0; mPtr.getRefData().setCount(count); } From 420163d35f6aac7a233bc57fb5bee334b4fd6fbd Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 05:19:34 -0400 Subject: [PATCH 114/135] Small changes to lock command (If no valid lockLevel exists, defaults to 100) --- apps/openmw/mwscript/miscextensions.cpp | 4 ++++ components/esm/cellref.cpp | 18 +++++++++++++++--- components/esm/cellref.hpp | 1 + 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index f10d1df965..686fa94a74 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -132,6 +132,10 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer lockLevel = ptr.getCellRef().mLockLevel; + if(lockLevel<0) { //no lock level was ever set, set to 100 as default + ptr.getCellRef().mLockLevel = 100; + lockLevel = 100; + } if (arg0==1) { diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 00b15f4a30..c46d83c1bb 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -54,8 +54,15 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) else mTeleport = false; - mLockLevel = -1; + mLockLevel = -999; //Set to impossible value to indicate no lock + mLocked = false; esm.getHNOT (mLockLevel, "FLTV"); + if(mLockLevel < 0 && mLockLevel != -999) //Unlocked lock, save lock level properly + mLockLevel*=-1; + else if(mLockLevel != -999){ + mLocked = true; + } + mKey = esm.getHNOString ("KNAM"); mTrap = esm.getHNOString ("TNAM"); @@ -113,8 +120,12 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons esm.writeHNOCString("DNAM", mDestCell); } - if (mLockLevel != -1 && !inInventory) - esm.writeHNT("FLTV", mLockLevel); + if (mLockLevel != -999 && !inInventory) { + if(mLocked) + esm.writeHNT("FLTV", mLockLevel); + else //If it's not locked we simply flip the locklevel to indicate it's locked + esm.writeHNT("FLTV", -mLockLevel); + } if (!inInventory) esm.writeHNOCString ("KNAM", mKey); @@ -151,6 +162,7 @@ void ESM::CellRef::blank() mGoldValue = 0; mDestCell.clear(); mLockLevel = 0; + mLocked = false; mKey.clear(); mTrap.clear(); mReferenceBlocked = 0; diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index 16f6603a28..75122eacb0 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -71,6 +71,7 @@ namespace ESM // Lock level for doors and containers int mLockLevel; + bool mLocked; //Door locked/unlocked std::string mKey, mTrap; // Key and trap ID names, if any // This corresponds to the "Reference Blocked" checkbox in the construction set, From 6022ffbd1ff25d56934760fd330e1c6de797bee8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 05:54:18 -0400 Subject: [PATCH 115/135] Evidence chest now locks when new evidence is added to it --- apps/openmw/mwworld/worldimp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e3c6325126..5b4c2570ae 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2724,6 +2724,7 @@ namespace MWWorld } } } + closestChest.getCellRef().mLocked = true; } void World::goToJail() @@ -2791,7 +2792,7 @@ namespace MWWorld message += "\n" + skillMsg; } - // TODO: Sleep the player + // TODO: Sleep the player std::vector buttons; buttons.push_back("#{sOk}"); From 61341d420678e6e5e9cae019d342d0dde2f531ad Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 10:00:18 -0400 Subject: [PATCH 116/135] Removed mLocked, kept it as "Negative lock level means unlocked" --- apps/openmw/mwclass/container.cpp | 8 ++++---- apps/openmw/mwclass/door.cpp | 10 +++++----- apps/openmw/mwmechanics/security.cpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 6 +++--- apps/openmw/mwscript/miscextensions.cpp | 5 ++--- apps/openmw/mwworld/manualref.hpp | 1 - apps/openmw/mwworld/worldimp.cpp | 2 +- components/esm/cellref.cpp | 10 ---------- components/esm/cellref.hpp | 1 - 9 files changed, 16 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index ee6dba9824..1847fbd44d 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -111,7 +111,7 @@ namespace MWClass MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWWorld::InventoryStore& invStore = MWWorld::Class::get(player).getInventoryStore(player); - bool needKey = ptr.getCellRef().mLocked; + bool needKey = ptr.getCellRef().mLockLevel > 0; bool hasKey = false; std::string keyName; @@ -209,7 +209,7 @@ namespace MWClass info.caption = ref->mBase->mName; std::string text; - if (ref->mRef.mLocked) + if (ref->mRef.mLockLevel > 0) text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ref->mRef.mLockLevel); if (ref->mRef.mTrap != "") text += "\n#{sTrapped}"; @@ -240,14 +240,14 @@ namespace MWClass void Container::lock (const MWWorld::Ptr& ptr, int lockLevel) const { - ptr.getCellRef().mLocked = true; + ptr.getCellRef().mLockLevel = abs(ptr.getCellRef().mLockLevel); //Makes lockLevel positive if(lockLevel>=0) //Lock level setting left as most of the code relies on this ptr.getCellRef().mLockLevel = lockLevel; } void Container::unlock (const MWWorld::Ptr& ptr) const { - ptr.getCellRef().mLocked= false; + ptr.getCellRef().mLockLevel = -abs(ptr.getCellRef().mLockLevel); //Makes lockLevel negative } void Container::changeLockLevel(const MWWorld::Ptr& ptr, int lockLevel, bool doLock) { diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 2662e1c537..e9f3845720 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -76,7 +76,7 @@ namespace MWClass MWWorld::ContainerStore &invStore = get(actor).getContainerStore(actor); - bool needKey = ptr.getCellRef().mLocked; + bool needKey = ptr.getCellRef().mLockLevel > 0; bool hasKey = false; std::string keyName; @@ -158,9 +158,9 @@ namespace MWClass void Door::lock (const MWWorld::Ptr& ptr, int lockLevel) const { - ptr.getCellRef().mLocked = true; + ptr.getCellRef().mLockLevel = abs(ptr.getCellRef().mLockLevel); //Makes lockLevel positive; if(lockLevel>=0) //Lock level setting left as most of the code relies on this - ptr.getCellRef().mLockLevel = lockLevel; + ptr.getCellRef().mLockLevel = abs(lockLevel); } void Door::changeLockLevel(const MWWorld::Ptr& ptr, int lockLevel, bool doLock) const{ @@ -173,7 +173,7 @@ namespace MWClass void Door::unlock (const MWWorld::Ptr& ptr) const { - ptr.getCellRef().mLocked = false; + ptr.getCellRef().mLockLevel = -abs(ptr.getCellRef().mLockLevel); //Makes lockLevel positive } std::string Door::getScript (const MWWorld::Ptr& ptr) const @@ -215,7 +215,7 @@ namespace MWClass text += "\n" + getDestination(*ref); } - if (ref->mRef.mLocked == true) + if (ref->mRef.mLockLevel > 0) text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ref->mRef.mLockLevel); if (ref->mRef.mTrap != "") text += "\n#{sTrapped}"; diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index ba2a32727d..3751e58285 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -29,7 +29,7 @@ namespace MWMechanics void Security::pickLock(const MWWorld::Ptr &lock, const MWWorld::Ptr &lockpick, std::string& resultMessage, std::string& resultSound) { - if (!lock.getCellRef().mLocked) + if (!(lock.getCellRef().mLockLevel > 0)) //If it's unlocked back out immediately return; int lockStrength = lock.getCellRef().mLockLevel; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d861e2032b..db8daff15d 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -420,7 +420,7 @@ namespace MWMechanics { if (effectId == ESM::MagicEffect::Lock) { - target.getCellRef().mLocked = true; + target.getCellRef().mLockLevel = abs(target.getCellRef().mLockLevel); //Makes lockLevel positive if (target.getCellRef().mLockLevel < magnitude) target.getCellRef().mLockLevel = magnitude; } @@ -428,12 +428,12 @@ namespace MWMechanics { if (target.getCellRef().mLockLevel <= magnitude) { - if (target.getCellRef().mLocked) + if (target.getCellRef().mLockLevel > 0) { MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target); } - target.getCellRef().mLocked=false; + target.getCellRef().mLockLevel = -abs(target.getCellRef().mLockLevel); } else MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 686fa94a74..bc71cc4941 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -132,8 +132,7 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer lockLevel = ptr.getCellRef().mLockLevel; - if(lockLevel<0) { //no lock level was ever set, set to 100 as default - ptr.getCellRef().mLockLevel = 100; + if(lockLevel==-999) { //no lock level was ever set, set to 100 as default lockLevel = 100; } @@ -324,7 +323,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push (ptr.getCellRef().mLocked); + runtime.push (ptr.getCellRef().mLockLevel > 0); } }; diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index 174e344232..0e21c55acd 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -76,7 +76,6 @@ namespace MWWorld cellRef.mEnchantmentCharge = -1; cellRef.mTeleport = false; cellRef.mLockLevel = 0; - cellRef.mLocked = false; cellRef.mReferenceBlocked = 0; mPtr.getRefData().setCount(count); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5b4c2570ae..5c9b25d28e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2724,7 +2724,7 @@ namespace MWWorld } } } - closestChest.getCellRef().mLocked = true; + closestChest.getCellRef().mLockLevel = abs(closestChest.getCellRef().mLockLevel); } void World::goToJail() diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index c46d83c1bb..699d326ee6 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -55,13 +55,7 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) mTeleport = false; mLockLevel = -999; //Set to impossible value to indicate no lock - mLocked = false; esm.getHNOT (mLockLevel, "FLTV"); - if(mLockLevel < 0 && mLockLevel != -999) //Unlocked lock, save lock level properly - mLockLevel*=-1; - else if(mLockLevel != -999){ - mLocked = true; - } mKey = esm.getHNOString ("KNAM"); mTrap = esm.getHNOString ("TNAM"); @@ -121,10 +115,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons } if (mLockLevel != -999 && !inInventory) { - if(mLocked) esm.writeHNT("FLTV", mLockLevel); - else //If it's not locked we simply flip the locklevel to indicate it's locked - esm.writeHNT("FLTV", -mLockLevel); } if (!inInventory) @@ -162,7 +153,6 @@ void ESM::CellRef::blank() mGoldValue = 0; mDestCell.clear(); mLockLevel = 0; - mLocked = false; mKey.clear(); mTrap.clear(); mReferenceBlocked = 0; diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index 75122eacb0..16f6603a28 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -71,7 +71,6 @@ namespace ESM // Lock level for doors and containers int mLockLevel; - bool mLocked; //Door locked/unlocked std::string mKey, mTrap; // Key and trap ID names, if any // This corresponds to the "Reference Blocked" checkbox in the construction set, From f6deca7c80c489488412cd85959c09e8ee68f5b9 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 13:02:51 -0400 Subject: [PATCH 117/135] Fixed various issues caused by late-night coding. Also added "unlock" message to unlocked doors --- apps/openmw/mwclass/container.cpp | 16 ++++++---------- apps/openmw/mwclass/container.hpp | 5 +---- apps/openmw/mwclass/door.cpp | 19 +++++++------------ apps/openmw/mwclass/door.hpp | 5 +---- apps/openmw/mwmechanics/spellcasting.cpp | 6 +++--- apps/openmw/mwscript/miscextensions.cpp | 2 +- apps/openmw/mwworld/class.cpp | 5 ----- apps/openmw/mwworld/class.hpp | 2 -- components/esm/cellref.cpp | 4 ++-- 9 files changed, 21 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 1847fbd44d..be76bd0b41 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -211,6 +211,8 @@ namespace MWClass std::string text; if (ref->mRef.mLockLevel > 0) text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ref->mRef.mLockLevel); + else if (ref->mRef.mLockLevel < 0) + text += "\n#{sUnlocked}"; if (ref->mRef.mTrap != "") text += "\n#{sTrapped}"; @@ -240,9 +242,10 @@ namespace MWClass void Container::lock (const MWWorld::Ptr& ptr, int lockLevel) const { - ptr.getCellRef().mLockLevel = abs(ptr.getCellRef().mLockLevel); //Makes lockLevel positive - if(lockLevel>=0) //Lock level setting left as most of the code relies on this - ptr.getCellRef().mLockLevel = lockLevel; + if(lockLevel!=0) + ptr.getCellRef().mLockLevel = abs(lockLevel); //Changes lock to locklevel, in positive + else + ptr.getCellRef().mLockLevel = abs(ptr.getCellRef().mLockLevel); //No locklevel given, just flip the oriional one } void Container::unlock (const MWWorld::Ptr& ptr) const @@ -250,13 +253,6 @@ namespace MWClass ptr.getCellRef().mLockLevel = -abs(ptr.getCellRef().mLockLevel); //Makes lockLevel negative } - void Container::changeLockLevel(const MWWorld::Ptr& ptr, int lockLevel, bool doLock) { - if (lockLevel<0) - lockLevel = 0; - - ptr.getCellRef().mLockLevel = lockLevel; - if(doLock) lock(ptr); - } MWWorld::Ptr Container::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 6945ae4414..f012d675c8 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -48,15 +48,12 @@ namespace MWClass ///< Returns total weight of objects inside this object (including modifications from magic /// effects). Throws an exception, if the object can't hold other objects. - virtual void lock (const MWWorld::Ptr& ptr, int lockLevel = -999) const; + virtual void lock (const MWWorld::Ptr& ptr, int lockLevel = 0) const; ///< Lock object virtual void unlock (const MWWorld::Ptr& ptr) const; ///< Unlock object - ///Changes the lock level of the object - virtual void changeLockLevel(const MWWorld::Ptr& ptr, int lockLevel, bool lock=true); - virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const; ///< Read additional state from \a state into \a ptr. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index e9f3845720..984e21e72b 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -158,22 +158,15 @@ namespace MWClass void Door::lock (const MWWorld::Ptr& ptr, int lockLevel) const { - ptr.getCellRef().mLockLevel = abs(ptr.getCellRef().mLockLevel); //Makes lockLevel positive; - if(lockLevel>=0) //Lock level setting left as most of the code relies on this - ptr.getCellRef().mLockLevel = abs(lockLevel); - } - - void Door::changeLockLevel(const MWWorld::Ptr& ptr, int lockLevel, bool doLock) const{ - if (lockLevel<0) - lockLevel = 0; - - ptr.getCellRef().mLockLevel = lockLevel; - if(doLock) lock(ptr); //A change in lock level almost always nesesitates a lock + if(lockLevel!=0) + ptr.getCellRef().mLockLevel = abs(lockLevel); //Changes lock to locklevel, in positive + else + ptr.getCellRef().mLockLevel = abs(ptr.getCellRef().mLockLevel); //No locklevel given, just flip the origional one } void Door::unlock (const MWWorld::Ptr& ptr) const { - ptr.getCellRef().mLockLevel = -abs(ptr.getCellRef().mLockLevel); //Makes lockLevel positive + ptr.getCellRef().mLockLevel = -abs(ptr.getCellRef().mLockLevel); //Makes lockLevel negative } std::string Door::getScript (const MWWorld::Ptr& ptr) const @@ -217,6 +210,8 @@ namespace MWClass if (ref->mRef.mLockLevel > 0) text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ref->mRef.mLockLevel); + else if (ref->mRef.mLockLevel < 0) + text += "\n#{sUnlocked}"; if (ref->mRef.mTrap != "") text += "\n#{sTrapped}"; diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index e053136d5d..bddc46728d 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -36,15 +36,12 @@ namespace MWClass static std::string getDestination (const MWWorld::LiveCellRef& door); ///< @return destination cell name or token - virtual void lock (const MWWorld::Ptr& ptr, int lockLevel = -999) const; + virtual void lock (const MWWorld::Ptr& ptr, int lockLevel = 0) const; ///< Lock object virtual void unlock (const MWWorld::Ptr& ptr) const; ///< Unlock object - ///Change the lock level - virtual void changeLockLevel(const MWWorld::Ptr& ptr, int lockLevel, bool lock=true) const; - virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index db8daff15d..9a6cd6b896 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -420,20 +420,20 @@ namespace MWMechanics { if (effectId == ESM::MagicEffect::Lock) { - target.getCellRef().mLockLevel = abs(target.getCellRef().mLockLevel); //Makes lockLevel positive - if (target.getCellRef().mLockLevel < magnitude) + if (target.getCellRef().mLockLevel < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude target.getCellRef().mLockLevel = magnitude; } else if (effectId == ESM::MagicEffect::Open) { if (target.getCellRef().mLockLevel <= magnitude) { + //Door not already unlocked if (target.getCellRef().mLockLevel > 0) { MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target); } - target.getCellRef().mLockLevel = -abs(target.getCellRef().mLockLevel); + target.getCellRef().mLockLevel = -abs(target.getCellRef().mLockLevel); //unlocks the door } else MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index bc71cc4941..20013493f2 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -132,7 +132,7 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer lockLevel = ptr.getCellRef().mLockLevel; - if(lockLevel==-999) { //no lock level was ever set, set to 100 as default + if(lockLevel==0) { //no lock level was ever set, set to 100 as default lockLevel = 100; } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 4b3d40ab78..f20c5f6d27 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -137,11 +137,6 @@ namespace MWWorld throw std::runtime_error ("class does not support locking"); } - void Class::setLockLevel (const Ptr& ptr, int lockLevel) const - { - throw std::runtime_error ("class does not support setting lock level"); - } - void Class::unlock (const Ptr& ptr) const { throw std::runtime_error ("class does not support unlocking"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index dc06bf6c96..27b8423484 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -165,8 +165,6 @@ namespace MWWorld virtual void lock (const Ptr& ptr, int lockLevel) const; ///< Lock object (default implementation: throw an exception) - virtual void setLockLevel(const Ptr& ptr, int lockLevel) const; - virtual void unlock (const Ptr& ptr) const; ///< Unlock object (default implementation: throw an exception) diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 699d326ee6..f04e819c88 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -54,7 +54,7 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) else mTeleport = false; - mLockLevel = -999; //Set to impossible value to indicate no lock + mLockLevel = 0; //Set to 0 to indicate no lock esm.getHNOT (mLockLevel, "FLTV"); mKey = esm.getHNOString ("KNAM"); @@ -114,7 +114,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons esm.writeHNOCString("DNAM", mDestCell); } - if (mLockLevel != -999 && !inInventory) { + if (mLockLevel != 0 && !inInventory) { esm.writeHNT("FLTV", mLockLevel); } From ee581f593b29f2656160b04cd1f5ea1a32b7f776 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 13:20:43 -0400 Subject: [PATCH 118/135] Fixed issue which may occur if there's no evidence chest nearby --- apps/openmw/mwworld/worldimp.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5c9b25d28e..87bdd2f9d2 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2695,6 +2695,7 @@ namespace MWWorld MWWorld::Ptr closestChest; float closestDistance = FLT_MAX; + //Find closest stolen_goods chest std::vector chests; mCells.getInteriorPtrs("stolen_goods", chests); @@ -2712,19 +2713,19 @@ namespace MWWorld } } - if (!closestChest.isEmpty()) + if (!closestChest.isEmpty()) //Found a close chest { ContainerStore& store = ptr.getClass().getContainerStore(ptr); - for (ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + for (ContainerStoreIterator it = store.begin(); it != store.end(); ++it) //Move all stolen stuff into chest { - if (!it->getCellRef().mOwner.empty() && it->getCellRef().mOwner != "player") + if (!it->getCellRef().mOwner.empty() && it->getCellRef().mOwner != "player") //Not owned by no one/player? { closestChest.getClass().getContainerStore(closestChest).add(*it, it->getRefData().getCount(), closestChest); store.remove(*it, it->getRefData().getCount(), ptr); } } + closestChest.getCellRef().mLockLevel = abs(closestChest.getCellRef().mLockLevel); } - closestChest.getCellRef().mLockLevel = abs(closestChest.getCellRef().mLockLevel); } void World::goToJail() From ce566693397b6fd23f71221abf07181325678e2e Mon Sep 17 00:00:00 2001 From: graffy76 Date: Wed, 23 Apr 2014 17:02:37 -0500 Subject: [PATCH 119/135] Fixed failed signal/slot connection between UserSettings and CSVWorld::SubView --- apps/opencs/view/doc/subview.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index 52e42cc0ef..733a75bb0d 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -38,9 +38,6 @@ namespace CSVDoc virtual void setEditLock (bool locked) = 0; - virtual void updateUserSetting - (const QString &, const QStringList &); - virtual void setStatusBar (bool show); ///< Default implementation: ignored @@ -50,6 +47,10 @@ namespace CSVDoc signals: void focusId (const CSMWorld::UniversalId& universalId, const std::string& hint); + + public slots: + virtual void updateUserSetting + (const QString &, const QStringList &); }; } From f90810223abeac58b05c807476a77d9cf338a7ab Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 21:02:09 -0400 Subject: [PATCH 120/135] Added quicksave and quickload --- apps/openmw/mwinput/inputmanagerimp.cpp | 43 +++++++++++++++++++++++++ apps/openmw/mwinput/inputmanagerimp.hpp | 2 ++ 2 files changed, 45 insertions(+) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 1346b9e958..4bd2a1e653 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -26,6 +26,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwstate/character.hpp" using namespace ICS; @@ -252,6 +253,12 @@ namespace MWInput case A_ToggleHUD: MWBase::Environment::get().getWindowManager()->toggleHud(); break; + case A_QuickSave: + quickSave(); + break; + case A_QuickLoad: + quickLoad(); + break; } } } @@ -638,6 +645,36 @@ namespace MWInput } } + void InputManager::quickLoad() { + MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(false); //Get current character + if(mCurrentCharacter) { //Ensure a current character exists + const MWState::Slot* slot = &*mCurrentCharacter->begin(); //Get newest save + if(slot) //Don't even try loading it if there's no prior save. + MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot); //load newest save. That was easy! + } + } + + void InputManager::quickSave() { + const MWState::Slot* slot = NULL; + MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(false); //Get current character + if (mCurrentCharacter) //Ensure one exists, otherwise do nothing + { + //Find quicksave slot + for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) + { + if (it->mProfile.mDescription == "Quicksave") + slot = &*it; + } + //If no quicksave works create a new slot with Signature + if(slot == NULL) { + slot = mCurrentCharacter->createSlot(mCurrentCharacter->getSignature()); + if(slot==NULL) + std::cout << "Couldn't work out" << std::endl; + } + //MWBase::Environment::get().getWindowManager()->messageBox("#{sQuick_save}"); //No message on quicksave? + MWBase::Environment::get().getStateManager()->saveGame("Quicksave", slot); + } + } void InputManager::toggleSpell() { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; @@ -842,6 +879,8 @@ namespace MWInput defaultKeyBindings[A_Screenshot] = SDL_GetKeyFromScancode(SDL_SCANCODE_F12); defaultKeyBindings[A_ToggleHUD] = SDL_GetKeyFromScancode(SDL_SCANCODE_F11); defaultKeyBindings[A_AlwaysRun] = SDL_GetKeyFromScancode(SDL_SCANCODE_Y); + defaultKeyBindings[A_QuickSave] = SDL_GetKeyFromScancode(SDL_SCANCODE_F5); + defaultKeyBindings[A_QuickLoad] = SDL_GetKeyFromScancode(SDL_SCANCODE_F9); std::map defaultMouseButtonBindings; defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT; @@ -918,6 +957,8 @@ namespace MWInput descriptions[A_QuickKey9] = "sQuick9Cmd"; descriptions[A_QuickKey10] = "sQuick10Cmd"; descriptions[A_AlwaysRun] = "sAlways_Run"; + descriptions[A_QuickSave] = "sQuickSaveCmd"; + descriptions[A_QuickLoad] = "sQuickLoadCmd"; if (descriptions[action] == "") return ""; // not configurable @@ -961,6 +1002,8 @@ namespace MWInput ret.push_back(A_Journal); ret.push_back(A_Rest); ret.push_back(A_Console); + ret.push_back(A_QuickSave); + ret.push_back(A_QuickLoad); ret.push_back(A_Screenshot); ret.push_back(A_QuickKeysMenu); ret.push_back(A_QuickKey1); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 87fbda25cd..5eb355566b 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -183,6 +183,8 @@ namespace MWInput void toggleWalking(); void toggleAutoMove(); void rest(); + void quickLoad(); + void quickSave(); void quickKey (int index); void showQuickKeysMenu(); From e873135da7f1065c1168cff1ab6f18b8d5c04b8f Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 21:04:52 -0400 Subject: [PATCH 121/135] Missed a debug message --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 4bd2a1e653..7fc8671864 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -668,8 +668,6 @@ namespace MWInput //If no quicksave works create a new slot with Signature if(slot == NULL) { slot = mCurrentCharacter->createSlot(mCurrentCharacter->getSignature()); - if(slot==NULL) - std::cout << "Couldn't work out" << std::endl; } //MWBase::Environment::get().getWindowManager()->messageBox("#{sQuick_save}"); //No message on quicksave? MWBase::Environment::get().getStateManager()->saveGame("Quicksave", slot); From 49620968b93caf88d4b49f94d0da7649aaf90a24 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 21:21:11 -0400 Subject: [PATCH 122/135] Disallowed quicksave/load in character creation --- apps/openmw/mwinput/inputmanagerimp.cpp | 42 ++++++++++++++----------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 7fc8671864..3d5d2f4a85 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -646,31 +646,35 @@ namespace MWInput } void InputManager::quickLoad() { - MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(false); //Get current character - if(mCurrentCharacter) { //Ensure a current character exists - const MWState::Slot* slot = &*mCurrentCharacter->begin(); //Get newest save - if(slot) //Don't even try loading it if there's no prior save. - MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot); //load newest save. That was easy! + if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { + MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(false); //Get current character + if(mCurrentCharacter) { //Ensure a current character exists + const MWState::Slot* slot = &*mCurrentCharacter->begin(); //Get newest save + if(slot) //Don't even try loading it if there's no prior save. + MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot); //load newest save. That was easy! + } } } void InputManager::quickSave() { - const MWState::Slot* slot = NULL; - MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(false); //Get current character - if (mCurrentCharacter) //Ensure one exists, otherwise do nothing - { - //Find quicksave slot - for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) + if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { //ensure you're not in character creation + const MWState::Slot* slot = NULL; + MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(true); //Get current character + if (mCurrentCharacter) //Ensure one exists { - if (it->mProfile.mDescription == "Quicksave") - slot = &*it; + //Find quicksave slot + for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) + { + if (it->mProfile.mDescription == "Quicksave") + slot = &*it; + } + //If no quicksave works create a new slot with Signature + if(slot == NULL) { + slot = mCurrentCharacter->createSlot(mCurrentCharacter->getSignature()); + } + //MWBase::Environment::get().getWindowManager()->messageBox("#{sQuick_save}"); //No message on quicksave? + MWBase::Environment::get().getStateManager()->saveGame("Quicksave", slot); } - //If no quicksave works create a new slot with Signature - if(slot == NULL) { - slot = mCurrentCharacter->createSlot(mCurrentCharacter->getSignature()); - } - //MWBase::Environment::get().getWindowManager()->messageBox("#{sQuick_save}"); //No message on quicksave? - MWBase::Environment::get().getStateManager()->saveGame("Quicksave", slot); } } void InputManager::toggleSpell() From 682c39548869593b02873a11f819a019056145fc Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 21:39:25 -0400 Subject: [PATCH 123/135] Apparently a normal save must exist for some reason, calling for a character to be made doesn't cause the signature to be created, and I don't know how to force create the signature (It's kinda driving me nuts) --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 3d5d2f4a85..db030c940c 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -659,7 +659,7 @@ namespace MWInput void InputManager::quickSave() { if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { //ensure you're not in character creation const MWState::Slot* slot = NULL; - MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(true); //Get current character + MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(false); //Get current character if (mCurrentCharacter) //Ensure one exists { //Find quicksave slot From a598060071bd6c3224f5f8c959ed1ab38fd16beb Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 23:12:30 -0400 Subject: [PATCH 124/135] Added autosave on rest, as well as an option in the settings menu --- apps/openmw/mwgui/waitdialog.cpp | 21 +++++++++++++++++++++ apps/openmw/mwgui/waitdialog.hpp | 1 + apps/openmw/mwinput/inputmanagerimp.cpp | 6 +----- files/mygui/openmw_settings_window.layout | 18 +++++++++++++++--- files/settings-default.cfg | 2 ++ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 9417d2a4b6..7e4522de60 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -8,6 +8,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" @@ -15,6 +16,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwstate/charactermanager.hpp" namespace MWGui { @@ -236,6 +238,25 @@ namespace MWGui { MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup); } + if(Settings::Manager::getBool("autosave","Saves")) + autosave(); + } + + void WaitDialog::autosave() { + if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { //ensure you're not in character creation + const MWState::Slot* slot = NULL; + MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(true); //Get current character + if (mCurrentCharacter) //Ensure one exists + { + //Find quicksave slot + for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) + { + if (it->mProfile.mDescription == "Autosave") + slot = &*it; + } + MWBase::Environment::get().getStateManager()->saveGame("Autosave", slot); + } + } } void WaitDialog::wakeUp () diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp index d96649af6f..5a66c3370e 100644 --- a/apps/openmw/mwgui/waitdialog.hpp +++ b/apps/openmw/mwgui/waitdialog.hpp @@ -34,6 +34,7 @@ namespace MWGui bool getSleeping() { return mWaiting && mSleeping; } void wakeUp(); + void autosave(); protected: MyGUI::TextBox* mDateTimeText; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index db030c940c..c3f5ff7aa6 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -659,7 +659,7 @@ namespace MWInput void InputManager::quickSave() { if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { //ensure you're not in character creation const MWState::Slot* slot = NULL; - MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(false); //Get current character + MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(true); //Get current character if (mCurrentCharacter) //Ensure one exists { //Find quicksave slot @@ -668,10 +668,6 @@ namespace MWInput if (it->mProfile.mDescription == "Quicksave") slot = &*it; } - //If no quicksave works create a new slot with Signature - if(slot == NULL) { - slot = mCurrentCharacter->createSlot(mCurrentCharacter->getSignature()); - } //MWBase::Environment::get().getWindowManager()->messageBox("#{sQuick_save}"); //No message on quicksave? MWBase::Environment::get().getStateManager()->saveGame("Quicksave", slot); } diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index adf9f1557f..e348323be5 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -3,10 +3,10 @@ - - + + - + @@ -95,6 +95,18 @@ + + + + + + + + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 6361476e34..f24636d151 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -174,6 +174,8 @@ best attack = false [Saves] character = +# Save when resting +autosave = true [Windows] inventory x = 0 From 6a8bf71c4c173f4c05489eb028b4c679859de476 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Apr 2014 23:33:57 -0400 Subject: [PATCH 125/135] Moved autosave to before you rest, not after it. --- apps/openmw/mwgui/waitdialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 7e4522de60..b4dae090f2 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -121,6 +121,8 @@ namespace MWGui MWBase::World* world = MWBase::Environment::get().getWorld(); world->getFader ()->fadeOut(0.2); setVisible(false); + if(Settings::Manager::getBool("autosave","Saves")) //autosaves + autosave(); mProgressBar.setVisible (true); mWaiting = true; @@ -238,8 +240,6 @@ namespace MWGui { MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup); } - if(Settings::Manager::getBool("autosave","Saves")) - autosave(); } void WaitDialog::autosave() { From 05b21c92afde503ec2c524e66c1233f5772d1b21 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 24 Apr 2014 03:06:36 -0400 Subject: [PATCH 126/135] Moved quick save into statemanager class. Kept loader as is as it's rather specalized. --- apps/openmw/mwbase/statemanager.hpp | 4 ++++ apps/openmw/mwgui/waitdialog.cpp | 18 +----------------- apps/openmw/mwinput/inputmanagerimp.cpp | 16 +--------------- apps/openmw/mwstate/statemanagerimp.cpp | 21 +++++++++++++++++++-- apps/openmw/mwstate/statemanagerimp.hpp | 4 ++++ 5 files changed, 29 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp index cd907408a5..b9cf6aa7f0 100644 --- a/apps/openmw/mwbase/statemanager.hpp +++ b/apps/openmw/mwbase/statemanager.hpp @@ -65,6 +65,10 @@ namespace MWBase /// /// \note \a slot must belong to \a character. + ///Simple saver, writes over the file if already existing + /** Used for quick save and autosave **/ + virtual void quickSave(std::string = "Quicksave")=0; + virtual MWState::Character *getCurrentCharacter (bool create = true) = 0; ///< \param create Create a new character, if there is no current character. diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index b4dae090f2..023e01b064 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -122,7 +122,7 @@ namespace MWGui world->getFader ()->fadeOut(0.2); setVisible(false); if(Settings::Manager::getBool("autosave","Saves")) //autosaves - autosave(); + MWBase::Environment::get().getStateManager()->quickSave("Autosave"); mProgressBar.setVisible (true); mWaiting = true; @@ -242,22 +242,6 @@ namespace MWGui } } - void WaitDialog::autosave() { - if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { //ensure you're not in character creation - const MWState::Slot* slot = NULL; - MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(true); //Get current character - if (mCurrentCharacter) //Ensure one exists - { - //Find quicksave slot - for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) - { - if (it->mProfile.mDescription == "Autosave") - slot = &*it; - } - MWBase::Environment::get().getStateManager()->saveGame("Autosave", slot); - } - } - } void WaitDialog::wakeUp () { diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index c3f5ff7aa6..bc1aaf8f61 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -657,21 +657,7 @@ namespace MWInput } void InputManager::quickSave() { - if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { //ensure you're not in character creation - const MWState::Slot* slot = NULL; - MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(true); //Get current character - if (mCurrentCharacter) //Ensure one exists - { - //Find quicksave slot - for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) - { - if (it->mProfile.mDescription == "Quicksave") - slot = &*it; - } - //MWBase::Environment::get().getWindowManager()->messageBox("#{sQuick_save}"); //No message on quicksave? - MWBase::Environment::get().getStateManager()->saveGame("Quicksave", slot); - } - } + MWBase::Environment::get().getStateManager()->quickSave(); } void InputManager::toggleSpell() { diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 6b0871012c..19b6ca1f33 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -223,6 +223,23 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot slot->mPath.parent_path().filename().string()); } +void MWState::StateManager::quickSave(std::string name) { + if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { //ensure you're not in character creation + const MWState::Slot* slot = NULL; + MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(true); //Get current character + if (mCurrentCharacter) //Ensure one exists + { + //Find quicksave slot + for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) + { + if (it->mProfile.mDescription == name) + slot = &*it; + } + MWBase::Environment::get().getStateManager()->saveGame(name, slot); + } + } +} + void MWState::StateManager::loadGame (const Character *character, const Slot *slot) { try @@ -309,11 +326,11 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl MWBase::Environment::get().getMechanicsManager()->playerLoaded(); MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - + //Update the weapon icon in the hud with whatever the player is currently holding. MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); MWWorld::ContainerStoreIterator item = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - + if (item != invStore.end()) MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*item); diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index 46ade236b6..8082a2c785 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -49,6 +49,10 @@ namespace MWState /// /// \note Slot must belong to the current character. + ///Saves a file, using supplied filename, overwritting if needed + /** This is mostly used for quicksaving and autosaving, for they use the same name over and over again **/ + virtual void quickSave(std::string name = "Quicksave"); + virtual void loadGame (const Character *character, const Slot *slot); ///< Load a saved game file from \a slot. /// From 5b681e2199689120274e52e86585d803320552c4 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 24 Apr 2014 03:14:47 -0400 Subject: [PATCH 127/135] Moved quickload to statemanager for consistency's sake, as well as make autosave only occur on sleep, not wait. --- apps/openmw/mwbase/statemanager.hpp | 4 ++++ apps/openmw/mwgui/waitdialog.cpp | 2 +- apps/openmw/mwinput/inputmanagerimp.cpp | 9 +-------- apps/openmw/mwstate/statemanagerimp.cpp | 11 +++++++++++ apps/openmw/mwstate/statemanagerimp.hpp | 7 ++++++- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp index b9cf6aa7f0..fc4a2d806e 100644 --- a/apps/openmw/mwbase/statemanager.hpp +++ b/apps/openmw/mwbase/statemanager.hpp @@ -69,6 +69,10 @@ namespace MWBase /** Used for quick save and autosave **/ virtual void quickSave(std::string = "Quicksave")=0; + ///Simple loader, loads the last saved file + /** Used for quickload **/ + virtual void quickLoad()=0; + virtual MWState::Character *getCurrentCharacter (bool create = true) = 0; ///< \param create Create a new character, if there is no current character. diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 023e01b064..ed1b9e0a90 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -121,7 +121,7 @@ namespace MWGui MWBase::World* world = MWBase::Environment::get().getWorld(); world->getFader ()->fadeOut(0.2); setVisible(false); - if(Settings::Manager::getBool("autosave","Saves")) //autosaves + if(Settings::Manager::getBool("autosave","Saves") && mSleeping) //autosaves when enabled and sleeping (Not resting, apparently) MWBase::Environment::get().getStateManager()->quickSave("Autosave"); mProgressBar.setVisible (true); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index bc1aaf8f61..51c0a1621e 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -646,14 +646,7 @@ namespace MWInput } void InputManager::quickLoad() { - if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { - MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(false); //Get current character - if(mCurrentCharacter) { //Ensure a current character exists - const MWState::Slot* slot = &*mCurrentCharacter->begin(); //Get newest save - if(slot) //Don't even try loading it if there's no prior save. - MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot); //load newest save. That was easy! - } - } + MWBase::Environment::get().getStateManager()->quickLoad(); } void InputManager::quickSave() { diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 19b6ca1f33..a0971b9427 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -345,6 +345,17 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl } } +void MWState::StateManager::quickLoad() { + if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { + MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(false); //Get current character + if(mCurrentCharacter) { //Ensure a current character exists + const MWState::Slot* slot = &*mCurrentCharacter->begin(); //Get newest save + if(slot) //Don't even try loading it if there's no prior save. + MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot); //load newest save. That was easy! + } + } +} + MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) { return mCharacterManager.getCurrentCharacter (create); diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index 8082a2c785..2d3ca21fbc 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -50,9 +50,14 @@ namespace MWState /// \note Slot must belong to the current character. ///Saves a file, using supplied filename, overwritting if needed - /** This is mostly used for quicksaving and autosaving, for they use the same name over and over again **/ + /** This is mostly used for quicksaving and autosaving, for they use the same name over and over again + \param name Name of save, defaults to "Quicksave"**/ virtual void quickSave(std::string name = "Quicksave"); + ///Loads the last saved file + /** Used for quickload **/ + virtual void quickLoad(); + virtual void loadGame (const Character *character, const Slot *slot); ///< Load a saved game file from \a slot. /// From 42b3233bdad827479a75a070b9edfa18a888d643 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 24 Apr 2014 09:54:47 +0200 Subject: [PATCH 128/135] removed a redundant check --- apps/openmw/mwstate/statemanagerimp.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index a0971b9427..56f562e04e 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -227,16 +227,14 @@ void MWState::StateManager::quickSave(std::string name) { if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { //ensure you're not in character creation const MWState::Slot* slot = NULL; MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(true); //Get current character - if (mCurrentCharacter) //Ensure one exists + + //Find quicksave slot + for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) { - //Find quicksave slot - for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) - { - if (it->mProfile.mDescription == name) - slot = &*it; - } - MWBase::Environment::get().getStateManager()->saveGame(name, slot); + if (it->mProfile.mDescription == name) + slot = &*it; } + MWBase::Environment::get().getStateManager()->saveGame(name, slot); } } From 760c8c721426f4a02711faad9ef94211f6254581 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 24 Apr 2014 09:56:51 +0200 Subject: [PATCH 129/135] removed a redundant include --- apps/openmw/mwinput/inputmanagerimp.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 51c0a1621e..b378139153 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -26,7 +26,6 @@ #include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" -#include "../mwstate/character.hpp" using namespace ICS; From 9db9ad410d52387a5cd4c96660fda44206eef5e3 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 24 Apr 2014 10:14:17 +0200 Subject: [PATCH 130/135] fixed checks for allowing load/save and some general cleanup --- apps/openmw/mwstate/statemanagerimp.cpp | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 56f562e04e..e32aad5053 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -223,19 +223,23 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot slot->mPath.parent_path().filename().string()); } -void MWState::StateManager::quickSave(std::string name) { - if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { //ensure you're not in character creation - const MWState::Slot* slot = NULL; - MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(true); //Get current character +void MWState::StateManager::quickSave (std::string name) +{ + if (mState!=State_Running || + MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")!=-1) // char gen + return; - //Find quicksave slot - for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) - { - if (it->mProfile.mDescription == name) - slot = &*it; - } - MWBase::Environment::get().getStateManager()->saveGame(name, slot); + const Slot* slot = NULL; + Character* mCurrentCharacter = getCurrentCharacter(true); //Get current character + + //Find quicksave slot + for (Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) + { + if (it->mProfile.mDescription == name) + slot = &*it; } + + saveGame(name, slot); } void MWState::StateManager::loadGame (const Character *character, const Slot *slot) @@ -343,15 +347,11 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl } } -void MWState::StateManager::quickLoad() { - if(MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) { - MWState::Character* mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter(false); //Get current character - if(mCurrentCharacter) { //Ensure a current character exists - const MWState::Slot* slot = &*mCurrentCharacter->begin(); //Get newest save - if(slot) //Don't even try loading it if there's no prior save. - MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot); //load newest save. That was easy! - } - } +void MWState::StateManager::quickLoad() +{ + if (Character* mCurrentCharacter = getCurrentCharacter (false)) + if (const MWState::Slot* slot = &*mCurrentCharacter->begin()) //Get newest save + loadGame (mCurrentCharacter, slot); } MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) From 3f2ae950f5b51aa3c6a86d92b9756c3b7827e866 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Thu, 24 Apr 2014 12:50:10 -0500 Subject: [PATCH 131/135] Disabled view resize for open views when user setting changes. Fixed new view size issue to match existing user settings. --- apps/opencs/model/settings/settingmanager.cpp | 12 ++++++++++++ apps/opencs/model/settings/settingmanager.hpp | 3 +++ apps/opencs/model/settings/usersettings.hpp | 4 ---- apps/opencs/view/doc/view.cpp | 13 +------------ apps/opencs/view/settings/listview.cpp | 2 +- apps/opencs/view/settings/page.cpp | 2 +- apps/opencs/view/settings/view.cpp | 7 ++++++- apps/opencs/view/world/table.cpp | 6 ++++-- apps/opencs/view/world/table.hpp | 4 ++-- apps/opencs/view/world/util.cpp | 6 ------ apps/opencs/view/world/util.hpp | 8 +++----- 11 files changed, 33 insertions(+), 34 deletions(-) diff --git a/apps/opencs/model/settings/settingmanager.cpp b/apps/opencs/model/settings/settingmanager.cpp index 70b91ee40a..450252cd90 100644 --- a/apps/opencs/model/settings/settingmanager.cpp +++ b/apps/opencs/model/settings/settingmanager.cpp @@ -328,3 +328,15 @@ CSMSettings::SettingPageMap CSMSettings::SettingManager::settingPageMap() const return pageMap; } + +void CSMSettings::SettingManager::updateUserSetting(const QString &settingKey, + const QStringList &list) +{ + QStringList names = settingKey.split('.'); + + Setting *setting = findSetting (names.at(0), names.at(1)); + + setting->setDefinedValues (list); + + emit userSettingUpdated (names.at(1), list); +} diff --git a/apps/opencs/model/settings/settingmanager.hpp b/apps/opencs/model/settings/settingmanager.hpp index 8819096adf..ca8a2cc7b9 100644 --- a/apps/opencs/model/settings/settingmanager.hpp +++ b/apps/opencs/model/settings/settingmanager.hpp @@ -75,8 +75,11 @@ namespace CSMSettings signals: + void userSettingUpdated (const QString &, const QStringList &); + public slots: + void updateUserSetting (const QString &, const QStringList &); }; } #endif // CSMSETTINGS_SETTINGMANAGER_HPP diff --git a/apps/opencs/model/settings/usersettings.hpp b/apps/opencs/model/settings/usersettings.hpp index 75a3a0797a..63caed923e 100644 --- a/apps/opencs/model/settings/usersettings.hpp +++ b/apps/opencs/model/settings/usersettings.hpp @@ -58,10 +58,6 @@ namespace CSMSettings { private: void buildSettingModelDefaults(); - - signals: - - void userSettingUpdated(const QString &, const QStringList &); }; } #endif // USERSETTINGS_HPP diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 47e1a80de3..397567f99e 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -492,18 +492,7 @@ void CSVDoc::View::resizeViewHeight (int height) void CSVDoc::View::updateUserSetting (const QString &name, const QStringList &list) -{ - if (list.isEmpty()) - return; - - int value = list.at(0).toInt(); - - if (name == "Width") - resizeViewWidth (value); - - else if (name == "Height") - resizeViewHeight (value); -} +{} void CSVDoc::View::toggleShowStatusBar (bool show) { diff --git a/apps/opencs/view/settings/listview.cpp b/apps/opencs/view/settings/listview.cpp index b2a47903ff..36cdbb0ae6 100644 --- a/apps/opencs/view/settings/listview.cpp +++ b/apps/opencs/view/settings/listview.cpp @@ -49,7 +49,7 @@ void CSVSettings::ListView::buildAbstractItemViewModel() void CSVSettings::ListView::emitItemViewUpdate (int idx) { - emit viewUpdated (objectName(), selectedValues()); + updateView(); } QWidget *CSVSettings::ListView::buildWidget(bool isMultiLine, int width) diff --git a/apps/opencs/view/settings/page.cpp b/apps/opencs/view/settings/page.cpp index 665326c2c0..a5711c8f81 100644 --- a/apps/opencs/view/settings/page.cpp +++ b/apps/opencs/view/settings/page.cpp @@ -52,7 +52,7 @@ void CSVSettings::Page::addView (CSMSettings::Setting *setting) if (setting->isEditorSetting()) { connect (view, SIGNAL (viewUpdated(const QString&, const QStringList&)), &CSMSettings::UserSettings::instance(), - SIGNAL (userSettingUpdated (const QString &, const QStringList &))); + SLOT (updateUserSetting (const QString &, const QStringList &))); } } diff --git a/apps/opencs/view/settings/view.cpp b/apps/opencs/view/settings/view.cpp index 259dd518b6..4f93b1c0fe 100644 --- a/apps/opencs/view/settings/view.cpp +++ b/apps/opencs/view/settings/view.cpp @@ -151,6 +151,11 @@ void CSVSettings::View::setSelectedValues (const QStringList &list, } select (selection); + //push changes to model side + + + //update the view if the selection was set from the model side, not by the + //user if (doViewUpdate) updateView (signalUpdate); } @@ -198,7 +203,7 @@ QList CSVSettings::View::toStandardItemList void CSVSettings::View::updateView (bool signalUpdate) const { if (signalUpdate) - emit viewUpdated(objectName(), selectedValues()); + emit viewUpdated(viewKey(), selectedValues()); } QString CSVSettings::View::value (int row) const diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 995de21fc1..902ab268a3 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -456,12 +456,14 @@ void CSVWorld::Table::updateUserSetting for (int i=0; i - (*delegate).updateUserSetting (name, list)) + { + dynamic_cast + (*delegate).updateUserSetting (name, list); { emit dataChanged (mModel->index (0, i), mModel->index (mModel->rowCount()-1, i)); } + } } void CSVWorld::Table::tableSizeUpdate() diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index dfc74b3eb8..c2811b8932 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -78,8 +78,6 @@ namespace CSVWorld CSMWorld::UniversalId getUniversalId (int row) const; - void updateUserSetting (const QString &name, const QStringList &list); - std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; signals: @@ -123,6 +121,8 @@ namespace CSVWorld void requestFocus (const std::string& id); void recordFilterChanged (boost::shared_ptr filter); + + void updateUserSetting (const QString &name, const QStringList &list); }; } diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 16310c8a97..ea8a7c5415 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -196,12 +196,6 @@ bool CSVWorld::CommandDelegate::isEditLocked() const return mEditLock; } -bool CSVWorld::CommandDelegate::updateUserSetting (const QString &name, - const QStringList &list) -{ - return false; -} - void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const { QVariant v = index.data(Qt::EditRole); diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp index 50e2cf858e..1c7e37818b 100644 --- a/apps/opencs/view/world/util.hpp +++ b/apps/opencs/view/world/util.hpp @@ -136,17 +136,15 @@ namespace CSVWorld bool isEditLocked() const; - virtual bool updateUserSetting - (const QString &name, const QStringList &list); - ///< \return Does column require update? virtual void setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay = false) const; - private slots: + public slots: - virtual void slotUpdateEditorSetting (const QString &settingName, const QString &settingValue) {} + virtual void updateUserSetting + (const QString &name, const QStringList &list) {} }; } From f3272c941f0caca478418e0b32cd651db33dd526 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 24 Apr 2014 22:47:45 -0400 Subject: [PATCH 132/135] Fix for bug #1080, can't read/repair/make potions/use soul gems/rest/sleep while in combat. The radius for being in combat with the player is fAlarmDistance, which looked like the only pertinent thing. --- apps/openmw/mwbase/mechanicsmanager.hpp | 7 ++++- apps/openmw/mwinput/inputmanagerimp.cpp | 9 +++++-- apps/openmw/mwmechanics/actors.cpp | 26 +++++++++++++++++-- apps/openmw/mwmechanics/actors.hpp | 7 ++++- .../mwmechanics/mechanicsmanagerimp.cpp | 23 +++++++++++----- .../mwmechanics/mechanicsmanagerimp.hpp | 4 ++- apps/openmw/mwstate/statemanagerimp.cpp | 4 +++ apps/openmw/mwworld/actionalchemy.cpp | 8 ++++++ apps/openmw/mwworld/actionread.cpp | 12 +++++++-- apps/openmw/mwworld/actionrepair.cpp | 7 +++++ apps/openmw/mwworld/actionsoulgem.cpp | 22 ++++++++++------ apps/openmw/mwworld/player.cpp | 5 ++++ apps/openmw/mwworld/player.hpp | 5 +++- 13 files changed, 114 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index e4c480a8c0..44b3d0229e 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -161,9 +161,14 @@ namespace MWBase virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects) = 0; - ///return the list of actors which are following the given actor (ie AiFollow is active and the target is the actor) + ///return the list of actors which are following the given actor + /**ie AiFollow is active and the target is the actor**/ virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; + ///Returns a list of actors who are fighting the given actor within the fAlarmDistance + /** ie AiCombat is active and the target is the actor **/ + virtual std::list getActorsFighting(const MWWorld::Ptr& actor) = 0; + virtual void playerLoaded() = 0; }; } diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 51c0a1621e..75c9015a0a 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -19,6 +19,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" @@ -693,8 +694,12 @@ namespace MWInput if (!MWBase::Environment::get().getWindowManager()->getRestEnabled () || MWBase::Environment::get().getWindowManager()->isGuiMode ()) return; - /// \todo check if resting is currently allowed (enemies nearby?) - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_Rest); + if(mPlayer->isInCombat()) {//Check if in combat + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); //Nope, + return; + } + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_Rest); //Open rest GUI + } void InputManager::screenshot() diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 7b83ff1f1d..804ec7a41e 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -176,7 +176,7 @@ namespace MWMechanics adjustMagicEffects (ptr); if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats()) calculateDynamicStats (ptr); - + calculateCreatureStatModifiers (ptr, duration); // AI @@ -764,7 +764,7 @@ namespace MWMechanics creatureStats.setHostile(false); creatureStats.setAttacked(false); creatureStats.setAlarmed(false); - + // Update witness crime id npcStats.setCrimeId(-1); } @@ -1038,4 +1038,26 @@ namespace MWMechanics } return list; } + + std::list Actors::getActorsFighting(const MWWorld::Ptr& actor) { + std::list list; + std::vector neighbors; + Ogre::Vector3 position = Ogre::Vector3(actor.getRefData().getPosition().pos); + getObjectsInRange(position, + MWBase::Environment::get().getWorld()->getStore().get().find("fAlarmRadius")->getFloat(), + neighbors); //only care about those within the alarm disance + for(std::vector::iterator iter(neighbors.begin());iter != neighbors.end();iter++) + { + const MWWorld::Class &cls = MWWorld::Class::get(*iter); + CreatureStats &stats = cls.getCreatureStats(*iter); + + if(stats.getAiSequence().getTypeId() == AiPackage::TypeIdCombat) + { + MWMechanics::AiCombat* package = static_cast(stats.getAiSequence().getActivePackage()); + if(package->getTargetId() == actor.getCellRef().mRefID) + list.push_front(*iter); + } + } + return list; + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index d61d742587..f7dff1058b 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -98,8 +98,13 @@ namespace MWMechanics void getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector& out); + ///Returns the list of actors which are following the given actor + /**ie AiFollow is active and the target is the actor **/ std::list getActorsFollowing(const MWWorld::Ptr& actor); - /// getActorsFighting(const MWWorld::Ptr& actor); private: PtrControllerMap mActors; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index d24191d889..90c00a36ee 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -772,6 +772,11 @@ namespace MWMechanics bool MechanicsManager::sleepInBed(const MWWorld::Ptr &ptr, const MWWorld::Ptr &bed) { + if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); + return true; + } + MWWorld::Ptr victim; if (isAllowedToUse(ptr, bed, victim)) return false; @@ -832,17 +837,17 @@ namespace MWMechanics // Find all the NPCs within the alarm radius std::vector neighbors; - mActors.getObjectsInRange(Ogre::Vector3(ptr.getRefData().getPosition().pos), + mActors.getObjectsInRange(Ogre::Vector3(ptr.getRefData().getPosition().pos), esmStore.get().find("fAlarmRadius")->getInt(), neighbors); // Find an actor who witnessed the crime for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) - { + { if (*it == ptr) continue; // not the player // Was the crime seen? if ( ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) || - type == OT_Assault ) + type == OT_Assault ) { // Will the witness report the crime? @@ -853,7 +858,7 @@ namespace MWMechanics // Tell everyone, including yourself for (std::vector::iterator it1 = neighbors.begin(); it1 != neighbors.end(); ++it1) - { + { if (*it1 == ptr) continue; // not the player // TODO: Add more messages @@ -861,9 +866,9 @@ namespace MWMechanics MWBase::Environment::get().getDialogueManager()->say(*it1, "thief"); else if (type == OT_Assault) MWBase::Environment::get().getDialogueManager()->say(*it1, "attack"); - + // Will other witnesses paticipate in crime - if ( it1->getClass().getCreatureStats(*it1).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm + if ( it1->getClass().getCreatureStats(*it1).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm || type == OT_Assault ) { it1->getClass().getNpcStats(*it1).setCrimeId(id); @@ -884,7 +889,7 @@ namespace MWMechanics void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) { const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); - + // Bounty for each type of crime if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) arg = store.find("iCrimeTresspass")->getInt(); @@ -993,4 +998,8 @@ namespace MWMechanics { return mActors.getActorsFollowing(actor); } + + std::list MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { + return mActors.getActorsFighting(actor); + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 5dd7583779..4d59379d68 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -120,7 +120,7 @@ namespace MWMechanics /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item); /// Attempt sleeping in a bed. If this is illegal, call commitCrime. - /// @return was it illegal, and someone saw you doing it? + /// @return was it illegal, and someone saw you doing it? Also returns fail when enemies are nearby virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed); virtual void forceStateUpdate(const MWWorld::Ptr &ptr); @@ -137,6 +137,8 @@ namespace MWMechanics virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); + virtual std::list getActorsFighting(const MWWorld::Ptr& actor); + virtual bool toggleAI(); virtual bool isAIActive(); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index a0971b9427..6c834a2bee 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -235,9 +235,12 @@ void MWState::StateManager::quickSave(std::string name) { if (it->mProfile.mDescription == name) slot = &*it; } + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage4}"); //Saving... MWBase::Environment::get().getStateManager()->saveGame(name, slot); } } + else + MWBase::Environment::get().getWindowManager()->messageBox("#{sSaveGameDenied}"); //You can not save your game right now } void MWState::StateManager::loadGame (const Character *character, const Slot *slot) @@ -351,6 +354,7 @@ void MWState::StateManager::quickLoad() { if(mCurrentCharacter) { //Ensure a current character exists const MWState::Slot* slot = &*mCurrentCharacter->begin(); //Get newest save if(slot) //Don't even try loading it if there's no prior save. + //MWBase::Environment::get().getWindowManager()->messageBox("#{sLoadingMessage14}"); //it overlaps MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot); //load newest save. That was easy! } } diff --git a/apps/openmw/mwworld/actionalchemy.cpp b/apps/openmw/mwworld/actionalchemy.cpp index bba75bc499..bbba1081c3 100644 --- a/apps/openmw/mwworld/actionalchemy.cpp +++ b/apps/openmw/mwworld/actionalchemy.cpp @@ -2,11 +2,19 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/player.hpp" namespace MWWorld { void ActionAlchemy::executeImp (const Ptr& actor) { + if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { //Ensure we're not in combat + MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage3}"); + return; + } + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Alchemy); } } diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp index 67755259e0..433237e26a 100644 --- a/apps/openmw/mwworld/actionread.cpp +++ b/apps/openmw/mwworld/actionread.cpp @@ -3,6 +3,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/player.hpp" #include "../mwmechanics/npcstats.hpp" @@ -19,8 +22,13 @@ namespace MWWorld { } - void ActionRead::executeImp (const MWWorld::Ptr& actor) - { + void ActionRead::executeImp (const MWWorld::Ptr& actor) { + + if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { //Ensure we're not in combat + MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage4}"); + return; + } + LiveCellRef *ref = getTarget().get(); if (ref->mBase->mData.mIsScroll) diff --git a/apps/openmw/mwworld/actionrepair.cpp b/apps/openmw/mwworld/actionrepair.cpp index bd56421165..a86dc38b1c 100644 --- a/apps/openmw/mwworld/actionrepair.cpp +++ b/apps/openmw/mwworld/actionrepair.cpp @@ -2,6 +2,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/player.hpp" namespace MWWorld { @@ -12,6 +14,11 @@ namespace MWWorld void ActionRepair::executeImp (const Ptr& actor) { + if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { + MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage2}"); + return; + } + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Repair); MWBase::Environment::get().getWindowManager()->startRepairItem(getTarget()); } diff --git a/apps/openmw/mwworld/actionsoulgem.cpp b/apps/openmw/mwworld/actionsoulgem.cpp index 6746f692f1..7237fd3342 100644 --- a/apps/openmw/mwworld/actionsoulgem.cpp +++ b/apps/openmw/mwworld/actionsoulgem.cpp @@ -2,20 +2,26 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/player.hpp" namespace MWWorld { -ActionSoulgem::ActionSoulgem(const Ptr &object) - : Action(false, object) -{ + ActionSoulgem::ActionSoulgem(const Ptr &object) + : Action(false, object) + { -} + } -void ActionSoulgem::executeImp(const Ptr &actor) -{ - MWBase::Environment::get().getWindowManager()->showSoulgemDialog(getTarget()); -} + void ActionSoulgem::executeImp(const Ptr &actor) + { + if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { //Ensure we're not in combat + MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage5}"); + return; + } + MWBase::Environment::get().getWindowManager()->showSoulgemDialog(getTarget()); + } } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index a4a4e95684..9202118a4a 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -13,6 +13,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" @@ -166,6 +167,10 @@ namespace MWWorld mTeleported = teleported; } + bool Player::isInCombat() { + return MWBase::Environment::get().getMechanicsManager()->getActorsFighting(getPlayer()).size() != 0; + } + void Player::markPosition(CellStore *markedCell, ESM::Position markedPosition) { mMarkedCell = markedCell; diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 7dbaaddb43..7e3f7a3cf2 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -44,7 +44,7 @@ namespace MWWorld int mCurrentCrimeId; // the id assigned witnesses int mPayedCrimeId; // the last id payed off (0 bounty) - + public: Player(const ESM::NPC *player, const MWBase::World& world); @@ -90,6 +90,9 @@ namespace MWWorld bool wasTeleported() const; void setTeleported(bool teleported); + ///Checks all actors to see if anyone has an aipackage against you + bool isInCombat(); + void clear(); void write (ESM::ESMWriter& writer) const; From 2e06414b43663684df1ec2b8e2b59391b2f32f17 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Fri, 25 Apr 2014 07:16:40 -0500 Subject: [PATCH 133/135] Fixed broken delegate display modes (Display Format settings). Moved UserSetting update functions to DataDisplayDelegate. --- apps/opencs/model/settings/settingmanager.cpp | 2 +- apps/opencs/model/settings/usersettings.cpp | 9 ++-- apps/opencs/model/settings/usersettings.hpp | 2 +- apps/opencs/view/doc/view.cpp | 4 +- .../opencs/view/world/datadisplaydelegate.cpp | 44 +++++++++++++++++-- .../opencs/view/world/datadisplaydelegate.hpp | 12 ++++- apps/opencs/view/world/idtypedelegate.cpp | 25 ++--------- apps/opencs/view/world/idtypedelegate.hpp | 3 -- .../view/world/recordstatusdelegate.cpp | 23 ++-------- .../view/world/recordstatusdelegate.hpp | 3 -- 10 files changed, 67 insertions(+), 60 deletions(-) diff --git a/apps/opencs/model/settings/settingmanager.cpp b/apps/opencs/model/settings/settingmanager.cpp index 450252cd90..eec4c54cf3 100644 --- a/apps/opencs/model/settings/settingmanager.cpp +++ b/apps/opencs/model/settings/settingmanager.cpp @@ -338,5 +338,5 @@ void CSMSettings::SettingManager::updateUserSetting(const QString &settingKey, setting->setDefinedValues (list); - emit userSettingUpdated (names.at(1), list); + emit userSettingUpdated (settingKey, list); } diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index db31f42552..badb73ece2 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -260,13 +260,16 @@ void CSMSettings::UserSettings::saveSettings writeFilestream (openFilestream (mUserFilePath, false), settingMap); } -QString CSMSettings::UserSettings::settingValue (const QString §ion, - const QString &name) +QString CSMSettings::UserSettings::settingValue (const QString &settingKey) { - Setting *setting = findSetting(section, name); + QStringList names = settingKey.split('.'); + qDebug () << "looking for " << names.at(0) << ',' << names.at(1); + + Setting *setting = findSetting(names.at(0), names.at(1)); if (setting) { + qDebug() << "setting found"; if (!setting->definedValues().isEmpty()) return setting->definedValues().at(0); } diff --git a/apps/opencs/model/settings/usersettings.hpp b/apps/opencs/model/settings/usersettings.hpp index 63caed923e..f0ed7af41a 100644 --- a/apps/opencs/model/settings/usersettings.hpp +++ b/apps/opencs/model/settings/usersettings.hpp @@ -53,7 +53,7 @@ namespace CSMSettings { /// Writes settings to the user's config file path void saveSettings (const QMap &settingMap); - QString settingValue (const QString §ion, const QString &name); + QString settingValue (const QString &settingKey); private: diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 397567f99e..acb272553e 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -236,10 +236,10 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to mViewTotal (totalViews) { QString width = CSMSettings::UserSettings::instance().settingValue - (QString("Window Size"), QString("Width")); + ("Window Size.Width"); QString height = CSMSettings::UserSettings::instance().settingValue - (QString("Window Size"), QString("Height")); + ("Window Size.Height"); resize (width.toInt(), height.toInt()); diff --git a/apps/opencs/view/world/datadisplaydelegate.cpp b/apps/opencs/view/world/datadisplaydelegate.cpp index d838395f6e..ef0da56ac7 100755 --- a/apps/opencs/view/world/datadisplaydelegate.cpp +++ b/apps/opencs/view/world/datadisplaydelegate.cpp @@ -1,16 +1,26 @@ #include "datadisplaydelegate.hpp" +#include "../../model/settings/usersettings.hpp" + #include #include CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList &values, const IconList &icons, - QUndoStack &undoStack, QObject *parent) - : EnumDelegate (values, undoStack, parent), mDisplayMode (Mode_TextOnly), mIcons (icons) - , mIconSize (QSize(16, 16)), mIconLeftOffset(3), mTextLeftOffset(8) + QUndoStack &undoStack, + const QString &settingKey, + QObject *parent) + : EnumDelegate (values, undoStack, parent), mDisplayMode (Mode_TextOnly), + mIcons (icons), mIconSize (QSize(16, 16)), mIconLeftOffset(3), + mTextLeftOffset(8), mSettingKey (settingKey) { mTextAlignment.setAlignment (Qt::AlignLeft | Qt::AlignVCenter ); buildPixmaps(); + + QString value = + CSMSettings::UserSettings::instance().settingValue (settingKey); + + updateDisplayMode(value); } void CSVWorld::DataDisplayDelegate::buildPixmaps () @@ -89,6 +99,30 @@ void CSVWorld::DataDisplayDelegate::paintIcon (QPainter *painter, const QStyleOp painter->drawPixmap (iconRect, mPixmaps.at(index).second); } +void CSVWorld::DataDisplayDelegate::updateUserSetting (const QString &name, + const QStringList &list) +{ + if (list.isEmpty()) + return; + + QString value = list.at(0); + + if (name == mSettingKey) + updateDisplayMode (value); +} + +void CSVWorld::DataDisplayDelegate::updateDisplayMode (const QString &mode) +{ + if (mode == "Icon and Text") + mDisplayMode = Mode_IconAndText; + + else if (mode == "Icon Only") + mDisplayMode = Mode_IconOnly; + + else if (mode == "Text Only") + mDisplayMode = Mode_TextOnly; +} + CSVWorld::DataDisplayDelegate::~DataDisplayDelegate() { mIcons.clear(); @@ -106,5 +140,7 @@ CSVWorld::CommandDelegate *CSVWorld::DataDisplayDelegateFactory::makeDelegate (Q QObject *parent) const { - return new DataDisplayDelegate (mValues, mIcons, undoStack, parent); + return new DataDisplayDelegate (mValues, mIcons, undoStack, "", parent); } + + diff --git a/apps/opencs/view/world/datadisplaydelegate.hpp b/apps/opencs/view/world/datadisplaydelegate.hpp index d23b86631c..f11c4a2b9e 100755 --- a/apps/opencs/view/world/datadisplaydelegate.hpp +++ b/apps/opencs/view/world/datadisplaydelegate.hpp @@ -35,10 +35,14 @@ namespace CSVWorld int mIconLeftOffset; int mTextLeftOffset; + QString mSettingKey; + public: explicit DataDisplayDelegate (const ValueList & values, const IconList & icons, - QUndoStack& undoStack, QObject *parent); + QUndoStack& undoStack, + const QString &settingKey, + QObject *parent); ~DataDisplayDelegate(); @@ -53,8 +57,14 @@ namespace CSVWorld /// offset the horizontal position of the text from the right edge of the icon. Default is 8 pixels. void setTextLeftOffset (int offset); + ///update the display mode for the delegate + void updateUserSetting (const QString &name, const QStringList &list); + private: + /// update the display mode based on a passed string + void updateDisplayMode (const QString &); + /// custom paint function for painting the icon. Mode_IconAndText and Mode_Icon only. void paintIcon (QPainter *painter, const QStyleOptionViewItem &option, int i) const; diff --git a/apps/opencs/view/world/idtypedelegate.cpp b/apps/opencs/view/world/idtypedelegate.cpp index ce4e8f0148..485ca57ac9 100755 --- a/apps/opencs/view/world/idtypedelegate.cpp +++ b/apps/opencs/view/world/idtypedelegate.cpp @@ -4,30 +4,11 @@ CSVWorld::IdTypeDelegate::IdTypeDelegate (const ValueList &values, const IconList &icons, QUndoStack& undoStack, QObject *parent) - : DataDisplayDelegate (values, icons, undoStack, parent) + : DataDisplayDelegate (values, icons, undoStack, + "Display Format.Referenceable ID Type Display", + parent) {} -bool CSVWorld::IdTypeDelegate::updateEditorSetting (const QString &settingName, const QString &settingValue) -{ - /// \todo make the setting key a member variable, that is initialised from a constructor argument - if (settingName == "Referenceable ID Type Display") - { - if (settingValue == "Icon and Text") - mDisplayMode = Mode_IconAndText; - - else if (settingValue == "Icon Only") - mDisplayMode = Mode_IconOnly; - - else if (settingValue == "Text Only") - mDisplayMode = Mode_TextOnly; - - return true; - } - - return false; -} - - CSVWorld::IdTypeDelegateFactory::IdTypeDelegateFactory() { for (int i=0; i enums = diff --git a/apps/opencs/view/world/recordstatusdelegate.hpp b/apps/opencs/view/world/recordstatusdelegate.hpp index d9126fee04..1b42223afc 100644 --- a/apps/opencs/view/world/recordstatusdelegate.hpp +++ b/apps/opencs/view/world/recordstatusdelegate.hpp @@ -20,9 +20,6 @@ namespace CSVWorld explicit RecordStatusDelegate(const ValueList& values, const IconList& icons, QUndoStack& undoStack, QObject *parent = 0); - - virtual bool updateEditorSetting (const QString &settingName, const QString &settingValue); - }; class RecordStatusDelegateFactory : public DataDisplayDelegateFactory From 42b332775a4b6623f4cacd3c7de25f18ead56b40 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 25 Apr 2014 15:30:46 +0200 Subject: [PATCH 134/135] removed some debug statements --- apps/opencs/model/settings/usersettings.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index badb73ece2..808fbfc70f 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -11,8 +11,6 @@ #include #include -#include - #include #include @@ -263,13 +261,11 @@ void CSMSettings::UserSettings::saveSettings QString CSMSettings::UserSettings::settingValue (const QString &settingKey) { QStringList names = settingKey.split('.'); - qDebug () << "looking for " << names.at(0) << ',' << names.at(1); Setting *setting = findSetting(names.at(0), names.at(1)); if (setting) { - qDebug() << "setting found"; if (!setting->definedValues().isEmpty()) return setting->definedValues().at(0); } From 6eca5ac4bbab19dcf2a2a751431a655108718d45 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 26 Apr 2014 07:46:19 +0200 Subject: [PATCH 135/135] Fixes #1296: Streamlined cellChanged detection --- apps/openmw/engine.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 508b195e92..03361408cf 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -59,9 +59,6 @@ void OMW::Engine::executeLocalScripts() MWScript::InterpreterContext interpreterContext ( &script.second.getRefData().getLocals(), script.second); MWBase::Environment::get().getScriptManager()->run (script.first, interpreterContext); - - if (MWBase::Environment::get().getWorld()->hasCellChanged()) - break; } localScripts.setIgnore (MWWorld::Ptr()); @@ -101,15 +98,10 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) // global scripts MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); - bool changed = MWBase::Environment::get().getWorld()->hasCellChanged(); - // local scripts - executeLocalScripts(); // This does not handle the case where a global script causes a - // cell change, followed by a cell change in a local script during - // the same frame. + executeLocalScripts(); - if (changed) // keep change flag for another frame, if cell changed happened in local script - MWBase::Environment::get().getWorld()->markCellAsUnchanged(); + MWBase::Environment::get().getWorld()->markCellAsUnchanged(); if (!paused) MWBase::Environment::get().getWorld()->advanceTime(