From 76bf774485d7da4285a9af3b9605b558465af50e Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 6 Jun 2014 03:52:41 +1000 Subject: [PATCH 001/226] Small changes for compiling with MSVC 2013. --- extern/oics/ICSPrerequisites.h | 4 ++++ extern/sdl4ogre/sdlinputwrapper.hpp | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/extern/oics/ICSPrerequisites.h b/extern/oics/ICSPrerequisites.h index 52daea3f47..5fe58a63df 100644 --- a/extern/oics/ICSPrerequisites.h +++ b/extern/oics/ICSPrerequisites.h @@ -37,6 +37,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#if defined(_WIN32) && _MSC_VER >= 1800 +#include /* std::min and std::max */ +#endif + #include "tinyxml.h" #include "SDL_keyboard.h" diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index f08e3eff6b..e4c97066dc 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -1,6 +1,11 @@ #ifndef SDL4OGRE_SDLINPUTWRAPPER_H #define SDL4OGRE_SDLINPUTWRAPPER_H +#if defined(_WIN32) && _MSC_VER >= 1800 +#include +#define NOMINMAX +#endif + #include #include From fd758bacd3d2773906416b3dd12f09563d1054d2 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 6 Jun 2014 19:58:05 +1000 Subject: [PATCH 002/226] Remove #ifdef guards. --- extern/oics/ICSPrerequisites.h | 7 ++----- extern/sdl4ogre/sdlinputwrapper.hpp | 3 --- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/extern/oics/ICSPrerequisites.h b/extern/oics/ICSPrerequisites.h index 5fe58a63df..6e6cd814ba 100644 --- a/extern/oics/ICSPrerequisites.h +++ b/extern/oics/ICSPrerequisites.h @@ -36,10 +36,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include - -#if defined(_WIN32) && _MSC_VER >= 1800 -#include /* std::min and std::max */ -#endif +#include /* std::min and std::max for MSVC 2013 */ #include "tinyxml.h" @@ -94,7 +91,7 @@ namespace ICS // from http://www.cplusplus.com/forum/articles/9645/ template - T FromString ( const std::string &Text )//Text not by const reference so that the function can be used with a + T FromString ( const std::string &Text )//Text not by const reference so that the function can be used with a { //character array as argument std::stringstream ss(Text); T result; diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index e4c97066dc..2757018146 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -1,10 +1,7 @@ #ifndef SDL4OGRE_SDLINPUTWRAPPER_H #define SDL4OGRE_SDLINPUTWRAPPER_H -#if defined(_WIN32) && _MSC_VER >= 1800 -#include #define NOMINMAX -#endif #include From 927ae00454d9e91db30040764df639938b486709 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 6 Jun 2014 21:15:23 +0200 Subject: [PATCH 003/226] Fix code that stopped animation immediately after starting it, due to thinking it has completed (Fixes #1370) --- apps/openmw/mwmechanics/character.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index db4e599291..67d506e436 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -832,6 +832,8 @@ bool CharacterController::updateWeaponState() MWRender::Animation::Group_UpperBody, false, weapSpeed, mAttackType+" max attack", mAttackType+" min hit", 1.0f-complete, 0); + + complete = 0.f; mUpperBodyState = UpperCharState_MaxAttackToMinHit; } else if (mHitState == CharState_KnockDown) From 2ec324c80bb834ea543786725fffe60e2110034a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 7 Jun 2014 17:10:02 +0200 Subject: [PATCH 004/226] Consider all splash screens in the Splash folder (Fixes #1416) --- apps/openmw/mwgui/loadingscreen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index f1bbc68cdf..f6d6b81355 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -136,7 +136,7 @@ namespace MWGui Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) { - Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "Splash_*.tga"); + Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "Splash/*.tga"); mResources.insert(mResources.end(), resourcesInThisGroup->begin(), resourcesInThisGroup->end()); } } From b470596206c21ece3fd77e0159d15fc4d68197e5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 7 Jun 2014 17:48:40 +0200 Subject: [PATCH 005/226] Handle failed savegame file operations (Fixes #1413) --- apps/openmw/mwstate/statemanagerimp.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 0e56365d60..7a70944dcf 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -233,6 +233,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.close(); + if (stream.fail()) + throw std::runtime_error("Write operation failed"); + Settings::Manager::setString ("character", "Saves", slot->mPath.parent_path().filename().string()); } @@ -246,6 +249,10 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot std::vector buttons; buttons.push_back("#{sOk}"); MWBase::Environment::get().getWindowManager()->messageBox(error.str(), buttons); + + // If no file was written, clean up the slot + if (slot && !boost::filesystem::exists(slot->mPath)) + mCharacterManager.getCurrentCharacter()->deleteSlot(slot); } } From a0bff03560b4413500161c5d45ba38bf41d25341 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 7 Jun 2014 17:58:03 +0200 Subject: [PATCH 006/226] Fix not handling failbit/badbit in ifstream (Bug #1355) --- components/translation/translation.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index 423c3971a0..976ae926d0 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -32,6 +32,9 @@ namespace Translation boost::filesystem::ifstream stream ( dataFileCollections.getCollection (extension).getPath (fileName)); + // Configure the stream to throw exception upon error + stream.exceptions ( boost::filesystem::ifstream::failbit | boost::filesystem::ifstream::badbit ); + if (!stream.is_open()) throw std::runtime_error ("failed to open translation file: " + fileName); @@ -41,6 +44,7 @@ namespace Translation void Storage::loadDataFromStream(ContainerType& container, std::istream& stream) { + // NOTE: does not handle failbit/badbit. stream must be set up beforehand to throw in these cases. std::string line; while (!stream.eof()) { From 69855097cac627c679587f983d97f11634c37502 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 7 Jun 2014 18:00:39 +0200 Subject: [PATCH 007/226] Fix an always true condition (Bug #1355) --- apps/openmw/mwmechanics/character.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 67d506e436..2d4bd87442 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -977,7 +977,8 @@ bool CharacterController::updateWeaponState() } //if playing combat animation and lowerbody is not busy switch to whole body animation - if((weaptype != WeapType_None || UpperCharState_UnEquipingWeap) && animPlaying) + if((weaptype != WeapType_None || mUpperBodyState == UpperCharState_UnEquipingWeap + || mUpperBodyState == UpperCharState_EquipingWeap) && animPlaying) { if( mMovementState != CharState_None || mJumpState != JumpState_None || From 823ccb1b3d7bcdbc3b864e937c667cfe456a6e7c Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 7 Jun 2014 18:48:12 +0200 Subject: [PATCH 008/226] Don't batch statics that have "references persist" set (temporary fix for Arkngthand door - Fixes #1386) --- apps/openmw/mwclass/static.cpp | 5 ++++- apps/openmw/mwrender/objects.cpp | 4 ++-- apps/openmw/mwrender/objects.hpp | 2 +- components/esm/loadstat.cpp | 1 + components/esm/loadstat.hpp | 2 ++ 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 4ac41350fd..8768bde063 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -14,9 +14,12 @@ namespace MWClass { void Static::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { + MWWorld::LiveCellRef *ref = + ptr.get(); + const std::string model = getModel(ptr); if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model); + renderingInterface.getObjects().insertModel(ptr, model, !ref->mBase->mPersistent); } } diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 7953a31178..d9e20e1f8e 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -73,7 +73,7 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) ptr.getRefData().setBaseNode(insert); } -void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh) +void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch) { insertBegin(ptr); @@ -99,7 +99,7 @@ void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh) mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; mBounds[ptr.getCell()].merge(bounds); - if(ptr.getTypeName() == typeid(ESM::Static).name() && + if(batch && Settings::Manager::getBool("use static geometry", "Objects") && anim->canBatch()) { diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 665a1e6570..02e974e2d8 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -41,7 +41,7 @@ public: , mRootNode(NULL) {} ~Objects(){} - void insertModel(const MWWorld::Ptr& ptr, const std::string &model); + void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false); ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr); diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index a71f22dc23..53d1b4bb59 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -10,6 +10,7 @@ namespace ESM void Static::load(ESMReader &esm) { + mPersistent = esm.getRecordFlags() & 0x0400; mModel = esm.getHNString("MODL"); } void Static::save(ESMWriter &esm) const diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index d912d10583..45b05136ad 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -26,6 +26,8 @@ struct Static std::string mId, mModel; + bool mPersistent; + void load(ESMReader &esm); void save(ESMWriter &esm) const; From b9dadff5a378ae818f80c1a6250d48c9368bfe29 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 7 Jun 2014 19:21:37 +0200 Subject: [PATCH 009/226] Recognize DELE subrecords at the end of the record (Fixes #1414) --- apps/openmw/mwworld/esmstore.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index cd6cc4a165..12831e7dc1 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -99,6 +99,13 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) } it->second->load(esm, id); + // DELE can also occur after the usual subrecords + if (esm.isNextSub("DELE")) { + esm.skipRecord(); + it->second->eraseStatic(id); + continue; + } + if (n.val==ESM::REC_DIAL) { dialogue = const_cast(mDialogs.find(id)); } else { From 98d7b6672a894f345ab2a13de4a6138ae769d649 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 8 Jun 2014 01:42:43 +0200 Subject: [PATCH 010/226] Make MODL subrecord optional for potions (Fixes #1419) --- components/esm/loadalch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index f6bfc6a11d..aac88482ff 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -10,7 +10,7 @@ namespace ESM void Potion::load(ESMReader &esm) { - mModel = esm.getHNString("MODL"); + mModel = esm.getHNOString("MODL"); mIcon = esm.getHNOString("TEXT"); // not ITEX here for some reason mScript = esm.getHNOString("SCRI"); mName = esm.getHNOString("FNAM"); From d2dca270678502169036fd44ce6d4c436c500509 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 8 Jun 2014 11:25:10 +0200 Subject: [PATCH 011/226] Correct wrong assertions (Fixes #1425) --- apps/openmw/mwgui/quickkeysmenu.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index e142171770..eda9daeffe 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -162,7 +162,7 @@ namespace MWGui void QuickKeysMenu::onAssignItem(MWWorld::Ptr item) { - assert (mSelectedIndex > 0); + assert (mSelectedIndex >= 0); ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; while (button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); @@ -184,7 +184,7 @@ namespace MWGui void QuickKeysMenu::onAssignMagicItem (MWWorld::Ptr item) { - assert (mSelectedIndex > 0); + assert (mSelectedIndex >= 0); ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; while (button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); @@ -203,7 +203,7 @@ namespace MWGui void QuickKeysMenu::onAssignMagic (const std::string& spellId) { - assert (mSelectedIndex > 0); + assert (mSelectedIndex >= 0); ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; while (button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); From 67abc60264a6aec4d5b9080150bb22e76cde02f5 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sun, 8 Jun 2014 20:59:26 +0400 Subject: [PATCH 012/226] aiming to moving target in ranged combat ai 1) Taking into account target move vector and speed. However aiming is not ideal, since attack strength can't be controlled directly. I did achieve almost 100% accuracy updating it everyframe but then thought it would be unfair, cause AI should mimic human targetting. 2) Also added in this commit func to measure real attack durations for weapon. --- apps/openmw/mwmechanics/aicombat.cpp | 346 +++++++++++++++++------ apps/openmw/mwmechanics/aicombat.hpp | 9 +- apps/openmw/mwrender/animation.cpp | 19 +- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwrender/weaponanimation.cpp | 2 +- 5 files changed, 278 insertions(+), 100 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 8f81885597..7a846e00d9 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -1,8 +1,6 @@ #include "aicombat.hpp" #include -#include - #include "../mwworld/class.hpp" #include "../mwworld/timestamp.hpp" @@ -14,6 +12,8 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwrender/animation.hpp" + #include "creaturestats.hpp" #include "steering.hpp" @@ -30,7 +30,12 @@ namespace } //chooses an attack depending on probability to avoid uniformity - void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); + ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); + + void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2]); + + Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, + float duration, int weapType, float strength); float getZAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f) { @@ -76,18 +81,20 @@ namespace namespace MWMechanics { - static const float MAX_ATTACK_DURATION = 0.35f; 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) : mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()), + mLastTargetPos(actor.getRefData().getPosition().pos), mTimerAttack(0), mTimerReact(0), mTimerCombatMove(0), mFollowTarget(false), mReadyToAttack(false), mAttack(false), + mStrength(0), + mMinMaxAttackDuration(), mCombatMove(false), mMovement(), mForceNoShortcut(false), @@ -158,9 +165,9 @@ namespace MWMechanics return true; if (!actor.getClass().isNpc() && target == MWBase::Environment::get().getWorld()->getPlayerPtr() && - (actor.getClass().canSwim(actor) && !actor.getClass().canWalk(actor) // pure water creature - && !MWBase::Environment::get().getWorld()->isSwimming(target)) // Player moved out of water - || (!actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(target))) // creature can't swim to Player + (actor.getClass().canSwim(actor) && !actor.getClass().canWalk(actor) // 1. pure water creature and Player moved out of water + && !MWBase::Environment::get().getWorld()->isSwimming(target)) + || (!actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(target))) // 2. creature can't swim to Player { actor.getClass().getCreatureStats(actor).setHostile(false); actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false); @@ -194,6 +201,27 @@ namespace MWMechanics } mTimerAttack -= duration; + + //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f + float attacksPeriod = 1.0f; + + ESM::Weapon::AttackType attackType; + + if(mReadyToAttack) + { + if (mMinMaxAttackDuration[0][0] == 0) + { + getMinMaxAttackDuration(actor, mMinMaxAttackDuration); + } + + if (mTimerAttack <= 0) mAttack = false; + } + else + { + mTimerAttack = -attacksPeriod; + mAttack = false; + } + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack); float tReaction = 0.25f; @@ -213,40 +241,6 @@ namespace MWMechanics mCell = actor.getCell(); } - //actual attacking logic - //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f - float attacksPeriod = 1.0f; - if(mReadyToAttack) - { - if(mTimerAttack <= -attacksPeriod) - { - //TODO: should depend on time between 'start' to 'min attack' - //for better controlling of NPCs' attack strength. - //Also it seems that this time is different for slash/thrust/chop - mTimerAttack = MAX_ATTACK_DURATION * static_cast(rand())/RAND_MAX; - mAttack = true; - - //say a provoking combat phrase - if (actor.getClass().isNpc()) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - int chance = store.get().find("iVoiceAttackOdds")->getInt(); - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (roll < chance) - { - MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); - } - } - } - else if (mTimerAttack <= 0) - mAttack = false; - } - else - { - mTimerAttack = -attacksPeriod; - mAttack = false; - } - const MWWorld::Class &actorCls = actor.getClass(); const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype; @@ -270,9 +264,9 @@ namespace MWMechanics if (weaptype == WeapType_HandToHand) { - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - weapRange = gmst.find("fHandToHandReach")->getFloat(); + static float fHandToHandReach = + MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->getFloat(); + weapRange = fHandToHandReach; } else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell) { @@ -289,6 +283,49 @@ namespace MWMechanics weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) } + float rangeAttack; + float rangeFollow; + bool distantCombat = false; + if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) + { + rangeAttack = 1000; // TODO: should depend on archer skill + rangeFollow = 0; // not needed in ranged combat + distantCombat = true; + } + else + { + rangeAttack = weapRange; + rangeFollow = 300; + } + + // start new attack + if(mReadyToAttack) + { + if(mTimerAttack <= -attacksPeriod) + { + mAttack = true; // attack starts just now + + if (!distantCombat) attackType = chooseBestAttack(weapon, mMovement); + else attackType = ESM::Weapon::AT_Chop; // cause it's =0 + + mStrength = static_cast(rand()) / RAND_MAX; + mTimerAttack = mMinMaxAttackDuration[attackType][0] + + (mMinMaxAttackDuration[attackType][1] - mMinMaxAttackDuration[attackType][0]) * mStrength; + + //say a provoking combat phrase + if (actor.getClass().isNpc()) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + int chance = store.get().find("iVoiceAttackOdds")->getInt(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < chance) + { + MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); + } + } + } + } + /* * Some notes on meanings of variables: @@ -319,21 +356,6 @@ namespace MWMechanics * target even if LOS is not achieved) */ - float rangeAttack; - float rangeFollow; - bool distantCombat = false; - if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) - { - rangeAttack = 1000; // TODO: should depend on archer skill - rangeFollow = 0; // not needed in ranged combat - distantCombat = true; - } - else - { - rangeAttack = weapRange; - rangeFollow = 300; - } - ESM::Position pos = actor.getRefData().getPosition(); Ogre::Vector3 vActorPos(pos.pos); Ogre::Vector3 vTargetPos(target.getRefData().getPosition().pos); @@ -342,40 +364,46 @@ namespace MWMechanics bool isStuck = false; float speed = 0.0f; - if(mMovement.mPosition[1] && (Ogre::Vector3(mLastPos.pos) - vActorPos).length() < (speed = actorCls.getSpeed(actor)) * tReaction / 2) + if(mMovement.mPosition[1] && (mLastActorPos - vActorPos).length() < (speed = actorCls.getSpeed(actor)) * tReaction / 2) isStuck = true; - mLastPos = pos; + mLastActorPos = vActorPos; // check if actor can move along z-axis bool canMoveByZ = (actorCls.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) || MWBase::Environment::get().getWorld()->isFlying(actor); - // determine vertical angle to target - // if actor can move along z-axis it will control movement dir - // if can't - it will control correct aiming - mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); - - // (within strike dist) || (not quite strike dist while following) + // (within attack dist) || (not quite attack dist while following) if(distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck) ) { //Melee and Close-up combat - // if we preserve dir.z then horizontal angle can be inaccurate - mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0)); + // getXAngleToDir determines vertical angle to target: + // if actor can move along z-axis it will control movement dir + // if can't - it will control correct aiming. + // note: in getZAngleToDir if we preserve dir.z then horizontal angle can be inaccurate + if (distantCombat) + { + Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, mLastTargetPos, tReaction, weaptype, mStrength); + mLastTargetPos = vTargetPos; + mMovement.mRotation[0] = getXAngleToDir(vAimDir); + mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vAimDir.x, vAimDir.y, 0)); + } + else + { + mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); + mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0)); + } - // (not quite strike dist while following) + // (not quite attack dist while following) if (mFollowTarget && distToTarget > rangeAttack) { //Close-up combat: just run up on target mMovement.mPosition[1] = 1; } - else // (within strike dist) + else // (within attack dist) { - mMovement.mPosition[1] = 0; - - // set slash/thrust/chop attack - if (mAttack && !distantCombat) chooseBestAttack(weapon, mMovement); + if (!mAttack) mMovement.mPosition[1] = 0; if(mMovement.mPosition[0] || mMovement.mPosition[1]) { @@ -479,9 +507,9 @@ namespace MWMechanics //special run attack; it shouldn't affect melee combat tactics if(actorCls.getMovementSettings(actor).mPosition[1] == 1) { - //check if actor can overcome the distance = distToTarget - attackerWeapRange - //less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing) - //then start attacking + /* check if actor can overcome the distance = distToTarget - attackerWeapRange + less than in time of swinging with weapon (t_swing), then start attacking + */ float speed1 = actorCls.getSpeed(actor); float speed2 = target.getClass().getSpeed(target); if(target.getClass().getMovementSettings(target).mPosition[0] == 0 @@ -491,13 +519,16 @@ namespace MWMechanics float s1 = distToTarget - weapRange; float t = s1/speed1; float s2 = speed2 * t; - float t_swing = (MAX_ATTACK_DURATION/2) / weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags + float t_swing = + mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0] + + (mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * static_cast(rand()) / RAND_MAX; + if (t + s2/speed1 <= t_swing) { mReadyToAttack = true; if(mTimerAttack <= -attacksPeriod) { - mTimerAttack = MAX_ATTACK_DURATION * static_cast(rand())/RAND_MAX; + mTimerAttack = t_swing; mAttack = true; } } @@ -650,8 +681,10 @@ namespace MWMechanics namespace { -void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement) +ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement) { + ESM::Weapon::AttackType attackType; + if (weapon == NULL) { //hand-to-hand deal equal damage for each type @@ -660,34 +693,161 @@ void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement { movement.mPosition[0] = (static_cast(rand())/RAND_MAX < 0.5f)? 1: -1; movement.mPosition[1] = 0; + attackType = ESM::Weapon::AT_Slash; } else if(roll <= 0.666f) //forward punch + { movement.mPosition[1] = 1; + attackType = ESM::Weapon::AT_Thrust; + } else { movement.mPosition[1] = movement.mPosition[0] = 0; + attackType = ESM::Weapon::AT_Chop; } + } + else + { + //the more damage attackType deals the more probability it has + int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; + int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; + int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + + float total = slash + chop + thrust; + + float roll = static_cast(rand())/RAND_MAX; + if(roll <= static_cast(slash)/total) + { + movement.mPosition[0] = (static_cast(rand())/RAND_MAX < 0.5f)? 1: -1; + movement.mPosition[1] = 0; + attackType = ESM::Weapon::AT_Slash; + } + else if(roll <= (static_cast(slash) + static_cast(thrust))/total) + { + movement.mPosition[1] = 1; + attackType = ESM::Weapon::AT_Thrust; + } + else + { + movement.mPosition[1] = movement.mPosition[0] = 0; + attackType = ESM::Weapon::AT_Chop; + } + } + + return attackType; +} + +void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2]) +{ + if (!actor.getClass().hasInventoryStore(actor)) // creatures + { + fMinMaxDurations[0][0] = fMinMaxDurations[0][1] = 0.1f; + fMinMaxDurations[1][0] = fMinMaxDurations[1][1] = 0.1f; + fMinMaxDurations[2][0] = fMinMaxDurations[2][1] = 0.1f; return; } - //the more damage attackType deals the more probability it has - int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; - int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; - int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + // get weapon information: type and speed + const ESM::Weapon *weapon = NULL; + MWMechanics::WeaponType weaptype; - float total = slash + chop + thrust; + MWWorld::ContainerStoreIterator weaponSlot = + MWMechanics::getActiveWeapon(actor.getClass().getCreatureStats(actor), actor.getClass().getInventoryStore(actor), &weaptype); - float roll = static_cast(rand())/RAND_MAX; - if(roll <= static_cast(slash)/total) + float weapSpeed; + if (weaptype != MWMechanics::WeapType_HandToHand) { - movement.mPosition[0] = (static_cast(rand())/RAND_MAX < 0.5f)? 1: -1; - movement.mPosition[1] = 0; + weapon = weaponSlot->get()->mBase; + weapSpeed = weapon->mData.mSpeed; + } + else weapSpeed = 1.0f; + + MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(actor); + + std::string weapGroup; + MWMechanics::getWeaponGroup(weaptype, weapGroup); + weapGroup = weapGroup + ": "; + + bool bRangedWeap = (weaptype >= MWMechanics::WeapType_BowAndArrow && weaptype <= MWMechanics::WeapType_Thrown); + + const char *attackType[] = {"chop ", "slash ", "thrust ", "shoot "}; + + std::string textKey = "start"; + std::string textKey2; + + // get durations for each attack type + for (int i = 0; i < (bRangedWeap ? 1 : 3); i++) + { + float start1 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey, false); + + textKey2 = "min attack"; + float start2 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2, false); + + fMinMaxDurations[i][0] = (start2 - start1) / weapSpeed; + + textKey2 = "max attack"; + start1 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2, false); + + fMinMaxDurations[i][1] = fMinMaxDurations[i][0] + (start1 - start2) / weapSpeed; + } + +} + +Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, + float duration, int weapType, float strength) +{ + float projSpeed; + + // get projectile speed (depending on weapon type) + if (weapType == ESM::Weapon::MarksmanThrown) + { + static float fThrownWeaponMinSpeed = + MWBase::Environment::get().getWorld()->getStore().get().find("fThrownWeaponMinSpeed")->getFloat(); + static float fThrownWeaponMaxSpeed = + MWBase::Environment::get().getWorld()->getStore().get().find("fThrownWeaponMaxSpeed")->getFloat(); + + projSpeed = + fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * strength; } - else if(roll <= (static_cast(slash) + static_cast(thrust))/total) - movement.mPosition[1] = 1; else - movement.mPosition[1] = movement.mPosition[0] = 0; + { + static float fProjectileMinSpeed = + MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMinSpeed")->getFloat(); + static float fProjectileMaxSpeed = + MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMaxSpeed")->getFloat(); + + projSpeed = + fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * strength; + } + + // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same + + Ogre::Vector3 vActorPos = Ogre::Vector3(actor.getRefData().getPosition().pos); + Ogre::Vector3 vTargetPos = Ogre::Vector3(target.getRefData().getPosition().pos); + Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; + float distToTarget = vDirToTarget.length(); + + Ogre::Vector3 vTargetMoveDir = vTargetPos - vLastTargetPos; + vTargetMoveDir /= duration; // |vTargetMoveDir| is target real speed in units/sec now + + Ogre::Vector3 vPerpToDir = vDirToTarget.crossProduct(Ogre::Vector3::UNIT_Z); + + float velPerp = vTargetMoveDir.dotProduct(vPerpToDir.normalisedCopy()); + float velDir = vTargetMoveDir.dotProduct(vDirToTarget.normalisedCopy()); + + // time to collision between target and projectile + float t_collision; + + float projVelDirSquared = projSpeed * projSpeed - velPerp * velPerp; + float projDistDiff = vDirToTarget.dotProduct(vTargetMoveDir.normalisedCopy()); + projDistDiff = sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff); + + if (projVelDirSquared > 0) + t_collision = projDistDiff / (sqrt(projVelDirSquared) - velDir); + else t_collision = 0; // speed of projectile is not enough to reach moving target + + return vTargetPos + vTargetMoveDir * t_collision - vActorPos; } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 4b728ff221..e168dfc957 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -8,6 +8,8 @@ #include "movement.hpp" #include "obstacle.hpp" +#include + #include "../mwworld/cellstore.hpp" // for Doors #include "../mwbase/world.hpp" @@ -48,12 +50,17 @@ namespace MWMechanics bool mCombatMove; bool mBackOffDoor; + float mStrength; // this is actually make sense only in ranged combat + float mMinMaxAttackDuration[3][2]; // slash, thrust, chop has different durations + bool mForceNoShortcut; ESM::Position mShortcutFailPos; - ESM::Position mLastPos; + Ogre::Vector3 mLastActorPos; MWMechanics::Movement mMovement; + int mTargetActorId; + Ogre::Vector3 mLastTargetPos; const MWWorld::CellStore* mCell; ObstacleCheck mObstacleCheck; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3b8b91b0e4..641ddb43e5 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -896,15 +896,26 @@ bool Animation::getInfo(const std::string &groupname, float *complete, float *sp return true; } -float Animation::getStartTime(const std::string &groupname) const +float Animation::getStartTime(const std::string &groupname, bool onlyGroup) const { AnimSourceList::const_iterator iter(mAnimSources.begin()); for(;iter != mAnimSources.end();iter++) { const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; - NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); - if(found != keys.end()) - return found->first; + if (onlyGroup) + { + NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); + if(found != keys.end()) + return found->first; + } + else + { + for(NifOgre::TextKeyMap::const_iterator iter(keys.begin()); iter != keys.end(); ++iter) + { + if(iter->second.compare(0, groupname.size(), groupname) == 0) + return iter->first; + } + } } return -1.f; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 564bb73ef1..2fc57f0e91 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -275,7 +275,7 @@ public: bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const; /// Get the absolute position in the animation track of the first text key with the given group. - float getStartTime(const std::string &groupname) const; + float getStartTime(const std::string &groupname, bool onlyGroup) const; /// Get the current absolute position in the animation track for the animation that is currently playing from the given group. float getCurrentTime(const std::string& groupname) const; diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 5f953bd838..3d4886ceac 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -32,7 +32,7 @@ float WeaponAnimationTime::getValue() const void WeaponAnimationTime::setGroup(const std::string &group) { mWeaponGroup = group; - mStartTime = mAnimation->getStartTime(mWeaponGroup); + mStartTime = mAnimation->getStartTime(mWeaponGroup, true); } void WeaponAnimationTime::updateStartTime() From a3752da79f3785ed814d0ca75fe201269449c2cc Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 8 Jun 2014 19:50:39 +0200 Subject: [PATCH 013/226] Store Always Run control state in settings --- apps/openmw/mwinput/inputmanagerimp.cpp | 4 +++- files/settings-default.cfg | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 943453a393..f75dc7ff03 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -117,7 +117,7 @@ namespace MWInput , mPreviewPOVDelay(0.f) , mTimeIdle(0.f) , mOverencumberedMessageDelay(0.f) - , mAlwaysRunActive(false) + , mAlwaysRunActive(Settings::Manager::getBool("always run", "Input")) , mControlsDisabled(false) { @@ -819,6 +819,8 @@ namespace MWInput { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; mAlwaysRunActive = !mAlwaysRunActive; + + Settings::Manager::setBool("always run", "Input", mAlwaysRunActive); } void InputManager::resetIdleTime() diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 9eed2c7d92..2ed3d6cb96 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -168,6 +168,8 @@ camera y multiplier = 1.0 ui y multiplier = 1.0 +always run = false + [Game] # Always use the most powerful attack when striking with a weapon (chop, slash or thrust) best attack = false From cdd53862cbda2677bfed35477ccf6b4e518047e3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 8 Jun 2014 21:37:50 +0200 Subject: [PATCH 014/226] Increase width of dialogue topic list (Fixes #1440) --- files/mygui/openmw_dialogue_window.layout | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/files/mygui/openmw_dialogue_window.layout b/files/mygui/openmw_dialogue_window.layout index 78daa0705f..5a7cd772da 100644 --- a/files/mygui/openmw_dialogue_window.layout +++ b/files/mygui/openmw_dialogue_window.layout @@ -4,28 +4,28 @@ - + - - + + - + - - + - + From 5488fe1ab3bacdffed8aa934882ef4b6b525ebe3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 9 Jun 2014 03:40:14 +0200 Subject: [PATCH 015/226] Change npc training skills to prefer skills with lowest ID if skill values are the same (Fixes #1445) --- apps/openmw/mwgui/trainingwindow.cpp | 46 ++++++++++++++++------------ 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 9a2c3b8055..6463db3d79 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -16,6 +16,24 @@ #include "tooltips.hpp" +namespace +{ +// Sorts a container descending by skill value. If skill value is equal, sorts ascending by skill ID. +// pair +bool sortSkills (const std::pair& left, const std::pair& right) +{ + if (left == right) + return false; + + if (left.second > right.second) + return true; + else if (left.second < right.second) + return false; + + return left.first < right.first; +} +} + namespace MWGui { @@ -52,29 +70,17 @@ namespace MWGui MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats (actor); // NPC can train you in his best 3 skills - std::vector< std::pair > bestSkills; - bestSkills.push_back (std::make_pair(-1, -1)); - bestSkills.push_back (std::make_pair(-1, -1)); - bestSkills.push_back (std::make_pair(-1, -1)); + std::vector< std::pair > skills; for (int i=0; i bestSkills[j].second) - { - if (j<2) - { - bestSkills[j+1] = bestSkills[j]; - } - bestSkills[j] = std::make_pair(i, value); - break; - } - } + skills.push_back(std::make_pair(i, value)); } + std::sort(skills.begin(), skills.end(), sortSkills); + MyGUI::EnumeratorWidgetPtr widgets = mTrainingOptions->getEnumerator (); MyGUI::Gui::getInstance ().destroyWidgets (widgets); @@ -86,20 +92,20 @@ namespace MWGui for (int i=0; i<3; ++i) { int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer - (mPtr,pcStats.getSkill (bestSkills[i].first).getBase() * gmst.find("iTrainingMod")->getInt (),true); + (mPtr,pcStats.getSkill (skills[i].first).getBase() * gmst.find("iTrainingMod")->getInt (),true); MyGUI::Button* button = mTrainingOptions->createWidget("SandTextButton", MyGUI::IntCoord(5, 5+i*18, mTrainingOptions->getWidth()-10, 18), MyGUI::Align::Default); button->setEnabled(price <= playerGold); - button->setUserData(bestSkills[i].first); + button->setUserData(skills[i].first); button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected); - button->setCaptionWithReplacing("#{" + ESM::Skill::sSkillNameIds[bestSkills[i].first] + "} - " + boost::lexical_cast(price)); + button->setCaptionWithReplacing("#{" + ESM::Skill::sSkillNameIds[skills[i].first] + "} - " + boost::lexical_cast(price)); button->setSize(button->getTextSize ().width+12, button->getSize().height); - ToolTips::createSkillToolTip (button, bestSkills[i].first); + ToolTips::createSkillToolTip (button, skills[i].first); } center(); From 1bab74a98dd7448ab70d7d2ab1cdcd8a12a22b9c Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 9 Jun 2014 03:42:29 +0200 Subject: [PATCH 016/226] Fix punishment for stealing 0 value items (Fixes #1435) --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 900ea72cad..f39a4b961c 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -929,7 +929,10 @@ namespace MWMechanics else if (type == OT_Murder) arg = store.find("iCrimeKilling")->getInt(); else if (type == OT_Theft) + { arg *= store.find("fCrimeStealing")->getFloat(); + arg = std::max(1, arg); // Minimum bounty of 1, in case items with zero value are stolen + } MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}"); ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty() From 698cbba6ef6bb3e139da218b001686f0583d0a54 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Mon, 9 Jun 2014 23:02:06 +0400 Subject: [PATCH 017/226] old bug + comment fix --- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 5727996d95..76a84cc9e1 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -928,7 +928,7 @@ namespace MWMechanics if (timerUpdateAITargets == 0 && iter->first.getTypeName() == typeid(ESM::Creature).name() && !listGuards.empty()) { sBasePoint = Ogre::Vector3(iter->first.getRefData().getPosition().pos); - listGuards.sort(comparePtrDist); // try to engage combat starting from the nearest creature + listGuards.sort(comparePtrDist); // try to engage combat starting from the nearest guard for (std::list::iterator it = listGuards.begin(); it != listGuards.end(); ++it) { diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index db4e599291..b487ffb21f 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -975,7 +975,7 @@ bool CharacterController::updateWeaponState() } //if playing combat animation and lowerbody is not busy switch to whole body animation - if((weaptype != WeapType_None || UpperCharState_UnEquipingWeap) && animPlaying) + if((weaptype != WeapType_None || mUpperBodyState == UpperCharState_UnEquipingWeap) && animPlaying) { if( mMovementState != CharState_None || mJumpState != JumpState_None || From e796fa23130a4476839ee862454f1561766dae01 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 9 Jun 2014 22:18:53 +0200 Subject: [PATCH 018/226] Add another french morrowind font workaround (Fixes #1447) --- apps/openmw/mwgui/fontloader.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/fontloader.cpp b/apps/openmw/mwgui/fontloader.cpp index 9d47bc38d1..b7c2007b46 100644 --- a/apps/openmw/mwgui/fontloader.cpp +++ b/apps/openmw/mwgui/fontloader.cpp @@ -258,12 +258,16 @@ namespace MWGui code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); - // More hacks! The french game uses U+2019, which is nowhere to be found in - // the CP437 encoding of the font. Fall back to 39 (regular apostrophe) - if (i == 39 && mEncoding == ToUTF8::CP437) + // More hacks! The french game uses several win1252 characters that are not included + // in the cp437 encoding of the font. Fall back to similar available characters. + // Same for U+2013 + std::map additional; + additional[39] = 0x2019; // apostrophe + additional[45] = 0x2013; // dash + if (additional.find(i) != additional.end() && mEncoding == ToUTF8::CP437) { MyGUI::xml::ElementPtr code = codes->createChild("Code"); - code->addAttribute("index", 0x2019); + code->addAttribute("index", additional[i]); code->addAttribute("coord", MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + MyGUI::utility::toString(w) + " " From 7721e541910063ea2b74462168bbdc05e1eaaa60 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 00:22:00 +0200 Subject: [PATCH 019/226] Use descriptive names for save files and character folders (Fixes #1449) --- apps/openmw/mwstate/character.cpp | 30 ++++++++++++++------ apps/openmw/mwstate/character.hpp | 1 - apps/openmw/mwstate/charactermanager.cpp | 35 ++++++++++++++++-------- apps/openmw/mwstate/charactermanager.hpp | 7 +++-- apps/openmw/mwstate/statemanagerimp.cpp | 16 +++++++---- 5 files changed, 58 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 5fe80ce0ca..6f569f078c 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -54,9 +54,28 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile) Slot slot; std::ostringstream stream; - stream << mNext++; + + // The profile description is user-supplied, so we need to escape the path + for (std::string::const_iterator it = profile.mDescription.begin(); it != profile.mDescription.end(); ++it) + { + if (std::isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters + stream << *it; + else + stream << "_"; + } slot.mPath = mPath / stream.str(); + + // Append an index if necessary to ensure a unique file + int i=0; + while (boost::filesystem::exists(slot.mPath)) + { + std::ostringstream test; + test << stream.str(); + test << " - " << ++i; + slot.mPath = mPath / test.str(); + } + slot.mProfile = profile; slot.mTimeStamp = std::time (0); @@ -64,7 +83,7 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile) } MWState::Character::Character (const boost::filesystem::path& saves, const std::string& game) -: mPath (saves), mNext (0) +: mPath (saves) { if (!boost::filesystem::is_directory (mPath)) { @@ -82,13 +101,6 @@ MWState::Character::Character (const boost::filesystem::path& saves, const std:: addSlot (slotPath, game); } catch (...) {} // ignoring bad saved game files for now - - std::istringstream stream (slotPath.filename().string()); - - int index = 0; - - if ((stream >> index) && index>=mNext) - mNext = index+1; } std::sort (mSlots.begin(), mSlots.end()); diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp index 8745332892..4703f0cca3 100644 --- a/apps/openmw/mwstate/character.hpp +++ b/apps/openmw/mwstate/character.hpp @@ -26,7 +26,6 @@ namespace MWState boost::filesystem::path mPath; std::vector mSlots; - int mNext; void addSlot (const boost::filesystem::path& path, const std::string& game); diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index d773904db2..91d728ae07 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -8,7 +8,7 @@ MWState::CharacterManager::CharacterManager (const boost::filesystem::path& saves, const std::string& game) -: mPath (saves), mNext (0), mCurrent (0), mGame (game) +: mPath (saves), mCurrent (0), mGame (game) { if (!boost::filesystem::is_directory (mPath)) { @@ -28,21 +28,14 @@ MWState::CharacterManager::CharacterManager (const boost::filesystem::path& save if (character.begin()!=character.end()) mCharacters.push_back (character); } - - std::istringstream stream (characterDir.filename().string()); - - int index = 0; - - if ((stream >> index) && index>=mNext) - mNext = index+1; } } } -MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create) +MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create, const std::string& name) { if (!mCurrent && create) - createCharacter(); + createCharacter(name); return mCurrent; } @@ -63,13 +56,31 @@ void MWState::CharacterManager::deleteSlot(const MWState::Character *character, } } -void MWState::CharacterManager::createCharacter() +void MWState::CharacterManager::createCharacter(const std::string& name) { std::ostringstream stream; - stream << mNext++; + + // The character name is user-supplied, so we need to escape the path + for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) + { + if (std::isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters + stream << *it; + else + stream << "_"; + } boost::filesystem::path path = mPath / stream.str(); + // Append an index if necessary to ensure a unique directory + int i=0; + while (boost::filesystem::exists(path)) + { + std::ostringstream test; + test << stream.str(); + test << " - " << ++i; + path = mPath / test.str(); + } + mCharacters.push_back (Character (path, mGame)); mCurrent = &mCharacters.back(); diff --git a/apps/openmw/mwstate/charactermanager.hpp b/apps/openmw/mwstate/charactermanager.hpp index adf9d2ef44..c44c10b5a2 100644 --- a/apps/openmw/mwstate/charactermanager.hpp +++ b/apps/openmw/mwstate/charactermanager.hpp @@ -10,7 +10,6 @@ namespace MWState class CharacterManager { boost::filesystem::path mPath; - int mNext; // Uses std::list, so that mCurrent stays valid when characters are deleted std::list mCharacters; @@ -32,13 +31,15 @@ namespace MWState CharacterManager (const boost::filesystem::path& saves, const std::string& game); - Character *getCurrentCharacter (bool create = true); + Character *getCurrentCharacter (bool create, const std::string& name); ///< \param create Create a new character, if there is no current character. + /// \param name The character name to use in case a new character is created. void deleteSlot(const MWState::Character *character, const MWState::Slot *slot); - void createCharacter(); + void createCharacter(const std::string& name); ///< Create new character within saved game management + /// \param name Name for the character (does not need to be unique) void setCurrentCharacter (const Character *character); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 7a70944dcf..68cb91eb9d 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -184,9 +184,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot encoded->read(&profile.mScreenshot[0], encoded->size()); if (!slot) - slot = mCharacterManager.getCurrentCharacter()->createSlot (profile); + slot = getCurrentCharacter()->createSlot (profile); else - slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile); + slot = getCurrentCharacter()->updateSlot (slot, profile); boost::filesystem::ofstream stream (slot->mPath, std::ios::binary); @@ -252,7 +252,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot // If no file was written, clean up the slot if (slot && !boost::filesystem::exists(slot->mPath)) - mCharacterManager.getCurrentCharacter()->deleteSlot(slot); + getCurrentCharacter()->deleteSlot(slot); } } @@ -419,7 +419,10 @@ void MWState::StateManager::deleteGame(const MWState::Character *character, cons MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) { - return mCharacterManager.getCurrentCharacter (create); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + std::string name = player.getClass().getName(player); + + return mCharacterManager.getCurrentCharacter (create, name); } MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin() @@ -440,11 +443,12 @@ void MWState::StateManager::update (float duration) if (mAskLoadRecent) { int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); - if(iButton==0) + MWState::Character *curCharacter = getCurrentCharacter(false); + if(iButton==0 && curCharacter) { mAskLoadRecent = false; //Load last saved game for current character - MWState::Character *curCharacter = getCurrentCharacter(); + MWState::Slot lastSave = *curCharacter->begin(); loadGame(curCharacter, &lastSave); } From e0d6670ac452162b096b883edf806a91e3e60d5e Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 01:57:54 +0200 Subject: [PATCH 020/226] Move video skip detection to WindowManager Fixes a bug where skipping using Esc would not work if a mouse button had been pressed previously --- apps/openmw/mwgui/confirmationdialog.cpp | 8 ++++---- apps/openmw/mwgui/mainmenu.cpp | 4 ++-- apps/openmw/mwgui/videowidget.cpp | 15 ++------------- apps/openmw/mwgui/videowidget.hpp | 12 ++++-------- apps/openmw/mwgui/windowmanagerimp.cpp | 24 +++++++++++++++++++++--- apps/openmw/mwgui/windowmanagerimp.hpp | 6 ++++++ 6 files changed, 39 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp index 89f477598f..57c88bfa2e 100644 --- a/apps/openmw/mwgui/confirmationdialog.cpp +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -30,9 +30,9 @@ namespace MWGui void ConfirmationDialog::exit() { - eventCancelClicked(); - setVisible(false); + + eventCancelClicked(); } void ConfirmationDialog::onCancelButtonClicked(MyGUI::Widget* _sender) @@ -42,8 +42,8 @@ namespace MWGui void ConfirmationDialog::onOkButtonClicked(MyGUI::Widget* _sender) { - eventOkClicked(); - setVisible(false); + + eventOkClicked(); } } diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index b5cd61f59c..1d47d3b765 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -167,7 +167,7 @@ namespace MWGui mVideo = mVideoBackground->createWidget("ImageBox", 0,0,1,1, MyGUI::Align::Stretch, "Menu"); - mVideo->playVideo("video\\menu_background.bik", false); + mVideo->playVideo("video\\menu_background.bik"); } MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); @@ -204,7 +204,7 @@ namespace MWGui if (!mVideo->update()) { // If finished playing, start again - mVideo->playVideo("video\\menu_background.bik", 0); + mVideo->playVideo("video\\menu_background.bik"); } } } diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp index 8430c1c7bc..cfd837a953 100644 --- a/apps/openmw/mwgui/videowidget.cpp +++ b/apps/openmw/mwgui/videowidget.cpp @@ -4,17 +4,12 @@ namespace MWGui { VideoWidget::VideoWidget() - : mAllowSkipping(true) { - eventKeyButtonPressed += MyGUI::newDelegate(this, &VideoWidget::onKeyPressed); - setNeedKeyFocus(true); } -void VideoWidget::playVideo(const std::string &video, bool allowSkipping) +void VideoWidget::playVideo(const std::string &video) { - mAllowSkipping = allowSkipping; - mPlayer.playVideo(video); setImageTexture(mPlayer.getTextureName()); @@ -30,19 +25,13 @@ 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(); } -void VideoWidget::cleanup() +void VideoWidget::stop() { mPlayer.close(); } diff --git a/apps/openmw/mwgui/videowidget.hpp b/apps/openmw/mwgui/videowidget.hpp index 9360c8359f..ad3d4d5e3c 100644 --- a/apps/openmw/mwgui/videowidget.hpp +++ b/apps/openmw/mwgui/videowidget.hpp @@ -9,7 +9,7 @@ namespace MWGui { /** - * Widget that plays a video. Can be skipped by pressing Esc. + * Widget that plays a video. */ class VideoWidget : public MyGUI::ImageBox { @@ -18,7 +18,7 @@ namespace MWGui VideoWidget(); - void playVideo (const std::string& video, bool allowSkipping); + void playVideo (const std::string& video); int getVideoWidth(); int getVideoHeight(); @@ -26,15 +26,11 @@ namespace MWGui /// @return Is the video still playing? bool update(); - /// Free video player resources (done automatically on destruction) - void cleanup(); + /// Stop video and free resources (done automatically on destruction) + void stop(); private: - bool mAllowSkipping; - MWRender::VideoPlayer mPlayer; - - void onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char); }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 35ee2adc90..7ebdb6a83e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -202,8 +202,12 @@ namespace MWGui MyGUI::Align::Default, "Overlay"); mVideoBackground->setImageTexture("black.png"); mVideoBackground->setVisible(false); + mVideoBackground->setNeedMouseFocus(true); + mVideoBackground->setNeedKeyFocus(true); mVideoWidget = mVideoBackground->createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default); + mVideoWidget->setNeedMouseFocus(true); + mVideoWidget->setNeedKeyFocus(true); } void WindowManager::initUI() @@ -263,7 +267,7 @@ namespace MWGui mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); trackWindow(mCompanionWindow, "companion"); - mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows",""); + mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows"); mHud->setVisible(mHudEnabled); @@ -1559,7 +1563,15 @@ namespace MWGui void WindowManager::playVideo(const std::string &name, bool allowSkipping) { - mVideoWidget->playVideo("video\\" + name, allowSkipping); + mVideoWidget->playVideo("video\\" + name); + + mVideoWidget->eventKeyButtonPressed.clear(); + mVideoBackground->eventKeyButtonPressed.clear(); + if (allowSkipping) + { + mVideoWidget->eventKeyButtonPressed += MyGUI::newDelegate(this, &WindowManager::onVideoKeyPressed); + mVideoBackground->eventKeyButtonPressed += MyGUI::newDelegate(this, &WindowManager::onVideoKeyPressed); + } // Turn off all rendering except for the GUI mRendering->getScene()->clearSpecialCaseRenderQueues(); @@ -1587,7 +1599,7 @@ namespace MWGui mRendering->getWindow()->update(); } - mVideoWidget->cleanup(); + mVideoWidget->stop(); setCursorVisible(cursorWasVisible); @@ -1628,4 +1640,10 @@ namespace MWGui if(input == mCurrentModals.top()) mCurrentModals.pop(); } + + void WindowManager::onVideoKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) + { + if (_key == MyGUI::KeyCode::Escape) + mVideoWidget->stop(); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index b1dbf3a24a..aee6cef474 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -12,6 +12,9 @@ #include "../mwbase/windowmanager.hpp" +#include +#include + namespace MyGUI { class Gui; @@ -424,6 +427,9 @@ namespace MWGui void onCursorChange(const std::string& name); void onKeyFocusChanged(MyGUI::Widget* widget); + // Key pressed while playing a video + void onVideoKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char); + void sizeVideo(int screenWidth, int screenHeight); }; } From 881ae33b74d374a444c37e9a2114213c771d98ac Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 02:15:09 +0200 Subject: [PATCH 021/226] Don't allow Creatures with no movement abilities to move (Fixes #1457) --- apps/openmw/mwclass/creature.cpp | 4 +++- apps/openmw/mwworld/physicssystem.cpp | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 1a6e4e321d..5744ebb9b6 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -345,7 +345,9 @@ namespace MWClass getCreatureStats(ptr).setAttacked(true); // Self defense - if (!attacker.isEmpty() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() < 80) + if (!attacker.isEmpty() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() < 80 + && (canWalk(ptr) || canFly(ptr) || canSwim(ptr))) // No retaliation for totally static creatures + // (they have no movement or attacks anyway) MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); if(!successful) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index b0b00c6dbd..85d41a478b 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -193,6 +193,11 @@ namespace MWWorld const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); + // Early-out for totally static creatures + // (Not sure if gravity should still apply?) + if (!ptr.getClass().canWalk(ptr) && !isFlying && !ptr.getClass().canSwim(ptr)) + return position; + /* Anything to collide with? */ OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle()); if(!physicActor || !physicActor->getCollisionMode()) From 311acfa8ff7a875373adf9a731b8239b3b596ae3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 02:27:38 +0200 Subject: [PATCH 022/226] Add delete button to save load menu (Fixes #1453) --- apps/openmw/mwgui/savegamedialog.cpp | 28 +++++++++++++++++------ apps/openmw/mwgui/savegamedialog.hpp | 4 ++++ files/mygui/openmw_savegame_dialog.layout | 10 +++++--- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index ef5d5858b6..e5390d5a6c 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -30,11 +30,13 @@ namespace MWGui getWidget(mInfoText, "InfoText"); getWidget(mOkButton, "OkButton"); getWidget(mCancelButton, "CancelButton"); + getWidget(mDeleteButton, "DeleteButton"); getWidget(mSaveList, "SaveList"); getWidget(mSaveNameEdit, "SaveNameEdit"); getWidget(mSpacer, "Spacer"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked); + mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteButtonClicked); mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected); mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected); mSaveList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SaveGameDialog::onSlotMouseClick); @@ -54,13 +56,16 @@ namespace MWGui onSlotSelected(sender, pos); if (pos != MyGUI::ITEM_NONE && MyGUI::InputManager::getInstance().isShiftPressed()) - { - ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->open("#{sMessage3}"); - dialog->eventOkClicked.clear(); - dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed); - dialog->eventCancelClicked.clear(); - } + confirmDeleteSave(); + } + + void SaveGameDialog::confirmDeleteSave() + { + ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); + dialog->open("#{sMessage3}"); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed); + dialog->eventCancelClicked.clear(); } void SaveGameDialog::onDeleteSlotConfirmed() @@ -175,6 +180,9 @@ namespace MWGui mCharacterSelection->setVisible(load); mSpacer->setUserString("Hidden", load ? "false" : "true"); + mDeleteButton->setUserString("Hidden", load ? "false" : "true"); + mDeleteButton->setVisible(load); + if (!load) { mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter (false); @@ -188,6 +196,12 @@ namespace MWGui exit(); } + void SaveGameDialog::onDeleteButtonClicked(MyGUI::Widget *sender) + { + if (mCurrentSlot) + confirmDeleteSave(); + } + void SaveGameDialog::onConfirmationGiven() { accept(true); diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp index 9f44d53708..80cfad2794 100644 --- a/apps/openmw/mwgui/savegamedialog.hpp +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -24,8 +24,11 @@ namespace MWGui void setLoadOrSave(bool load); private: + void confirmDeleteSave(); + void onCancelButtonClicked (MyGUI::Widget* sender); void onOkButtonClicked (MyGUI::Widget* sender); + void onDeleteButtonClicked (MyGUI::Widget* sender); void onCharacterSelected (MyGUI::ComboBox* sender, size_t pos); // Slot selected (mouse click or arrow keys) void onSlotSelected (MyGUI::ListBox* sender, size_t pos); @@ -51,6 +54,7 @@ namespace MWGui MyGUI::EditBox* mInfoText; MyGUI::Button* mOkButton; MyGUI::Button* mCancelButton; + MyGUI::Button* mDeleteButton; MyGUI::ListBox* mSaveList; MyGUI::EditBox* mSaveNameEdit; MyGUI::Widget* mSpacer; diff --git a/files/mygui/openmw_savegame_dialog.layout b/files/mygui/openmw_savegame_dialog.layout index ceb1a84288..7015336368 100644 --- a/files/mygui/openmw_savegame_dialog.layout +++ b/files/mygui/openmw_savegame_dialog.layout @@ -2,8 +2,8 @@ - - + + @@ -49,10 +49,14 @@ - + + + + + From 271aac3fcc2b317996c829076e492a41f7ebde72 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 02:47:02 +0200 Subject: [PATCH 023/226] Savegame dialog: Grey out buttons if no save is selected --- apps/openmw/mwgui/savegamedialog.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index e5390d5a6c..caa0cbb360 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -112,6 +112,7 @@ namespace MWGui mCurrentCharacter = NULL; mCurrentSlot = NULL; mSaveList->removeAllItems(); + onSlotSelected(mSaveList, MyGUI::ITEM_NONE); MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); if (mgr->characterBegin() == mgr->characterEnd()) @@ -239,10 +240,8 @@ namespace MWGui } else { - if (mCurrentCharacter && mCurrentSlot) - { - MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot); - } + assert (mCurrentCharacter && mCurrentSlot); + MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot); } } @@ -292,6 +291,9 @@ namespace MWGui void SaveGameDialog::onSlotSelected(MyGUI::ListBox *sender, size_t pos) { + mOkButton->setEnabled(pos != MyGUI::ITEM_NONE || mSaving); + mDeleteButton->setEnabled(pos != MyGUI::ITEM_NONE); + if (pos == MyGUI::ITEM_NONE) { mCurrentSlot = NULL; From c0c1db44907a4719773c7f734fefeb599a32eeda Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 03:28:21 +0200 Subject: [PATCH 024/226] Don't list non-usable items in QuickKeysMenu selection (Fixes #1427) --- apps/openmw/mwgui/quickkeysmenu.cpp | 2 ++ apps/openmw/mwgui/sortfilteritemmodel.cpp | 5 +++++ apps/openmw/mwgui/sortfilteritemmodel.hpp | 1 + 3 files changed, 8 insertions(+) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index eda9daeffe..f52a4b9413 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -24,6 +24,7 @@ #include "spellwindow.hpp" #include "itemwidget.hpp" +#include "sortfilteritemmodel.hpp" namespace MWGui @@ -134,6 +135,7 @@ namespace MWGui } mItemSelectionDialog->setVisible(true); mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); + mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyUsableItems); mAssignDialog->setVisible (false); } diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index b8dcbcbbb1..93e5432ca9 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -14,6 +14,7 @@ #include #include "../mwworld/class.hpp" +#include "../mwworld/nullaction.hpp" namespace { @@ -126,6 +127,10 @@ namespace MWGui && !base.get()->mBase->mData.mIsScroll) return false; + if ((mFilter & Filter_OnlyUsableItems) && typeid(*base.getClass().use(base)) == typeid(MWWorld::NullAction) + && base.getClass().getScript(base).empty()) + return false; + return true; } diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index c7feaa3b92..4af35e7a8b 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -36,6 +36,7 @@ namespace MWGui static const int Filter_OnlyEnchanted = (1<<1); static const int Filter_OnlyEnchantable = (1<<2); static const int Filter_OnlyChargedSoulstones = (1<<3); + static const int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action private: From 65d5311037f821098580a92c9577c025f59fdb84 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 04:10:34 +0200 Subject: [PATCH 025/226] Revert "Don't try to show exceptions in a message box if SDL was not initialized" According to SDL docs, "This function may be called at any time, even before SDL_Init()". Also fixes an issue where message boxes weren't working due to SDL_Quit already having been called by ~Engine. This reverts commit 39eea24dc3866760cc40b79b6d57ebbc6799fc73. Conflicts: apps/openmw/main.cpp --- apps/openmw/main.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 3098d953ec..302a9cf648 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -4,7 +4,8 @@ #include #include -#include +#include +#include #include "engine.hpp" #if defined(_WIN32) && !defined(_CONSOLE) @@ -286,7 +287,7 @@ int main(int argc, char**argv) catch (std::exception &e) { #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE - if (isatty(fileno(stdin)) || !SDL_WasInit(SDL_INIT_VIDEO)) + if (isatty(fileno(stdin))) std::cerr << "\nERROR: " << e.what() << std::endl; else #endif From 8419002393d89bd20222773a1872efb858f7ea32 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 10 Jun 2014 11:25:39 +0200 Subject: [PATCH 026/226] Task #940: Move licenses to appropriate place in docs. Additional cleanup. --- .gitignore | 2 +- CMakeLists.txt | 2 +- credits.txt | 4 ++-- {Docs => docs}/Doxyfile | 2 +- {Docs => docs}/DoxyfilePages | 2 +- .../license/DejaVu Font License.txt | 0 GPL3.txt => docs/license/GPL3.txt | 0 {Docs => docs}/mainpage.hpp.cmake | 0 8 files changed, 6 insertions(+), 6 deletions(-) rename {Docs => docs}/Doxyfile (99%) rename {Docs => docs}/DoxyfilePages (99%) rename DejaVu Font License.txt => docs/license/DejaVu Font License.txt (100%) rename GPL3.txt => docs/license/GPL3.txt (100%) rename {Docs => docs}/mainpage.hpp.cmake (100%) diff --git a/.gitignore b/.gitignore index 3975c4521b..08bf0bad62 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,7 @@ resources ## generated objects apps/openmw/config.hpp components/version/version.hpp -Docs/mainpage.hpp +docs/mainpage.hpp moc_*.cxx *.cxx_parameters *qrc_launcher.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 3632f1e080..7038e05bae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,7 @@ include(OpenMWMacros) # doxygen main page -configure_file ("${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp") +configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/docs/mainpage.hpp") option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE) option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binaries" FALSE) diff --git a/credits.txt b/credits.txt index 092f5c15e8..791db04339 100644 --- a/credits.txt +++ b/credits.txt @@ -19,8 +19,8 @@ Alexander Olofsson (Ace) Artem Kotsynyak (greye) Arthur Moore (EmperorArthur) athile +Bret Curtis (psi29a) Britt Mathis (galdor557) -BrotherBrick cc9cii Chris Boyce (slothlife) Chris Robinson (KittyCat) @@ -79,7 +79,7 @@ Torben Leif Carrington (TorbenC) Packagers: Alexander Olofsson (Ace) - Windows -BrotherBrick - Ubuntu Linux +Bret Curtis (psi29a) - Ubuntu Linux Edmondo Tommasina (edmondo) - Gentoo Linux Julian Ospald (hasufell) - Gentoo Linux Karl-Felix Glatzer (k1ll) - Linux Binaries diff --git a/Docs/Doxyfile b/docs/Doxyfile similarity index 99% rename from Docs/Doxyfile rename to docs/Doxyfile index 43c3312ad3..156e23abd2 100644 --- a/Docs/Doxyfile +++ b/docs/Doxyfile @@ -576,7 +576,7 @@ WARN_LOGFILE = INPUT = apps \ components \ libs \ - Docs + docs # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/Docs/DoxyfilePages b/docs/DoxyfilePages similarity index 99% rename from Docs/DoxyfilePages rename to docs/DoxyfilePages index 5ce82a7c29..dca9d7a12a 100644 --- a/Docs/DoxyfilePages +++ b/docs/DoxyfilePages @@ -576,7 +576,7 @@ WARN_LOGFILE = INPUT = apps \ components \ libs \ - Docs + docs # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/DejaVu Font License.txt b/docs/license/DejaVu Font License.txt similarity index 100% rename from DejaVu Font License.txt rename to docs/license/DejaVu Font License.txt diff --git a/GPL3.txt b/docs/license/GPL3.txt similarity index 100% rename from GPL3.txt rename to docs/license/GPL3.txt diff --git a/Docs/mainpage.hpp.cmake b/docs/mainpage.hpp.cmake similarity index 100% rename from Docs/mainpage.hpp.cmake rename to docs/mainpage.hpp.cmake From 979128b2c55f3c38a5160cf82222aa5325824916 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Tue, 10 Jun 2014 14:20:29 +0400 Subject: [PATCH 027/226] Combat music; some minor combat fixes --- apps/openmw/mwmechanics/actors.cpp | 19 +++++++++++++++++++ apps/openmw/mwmechanics/aicombat.cpp | 8 ++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 76a84cc9e1..7102fc0eeb 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -970,6 +970,9 @@ namespace MWMechanics } // Kill dead actors, update some variables + + int hostilesCount = 0; // need to know this to play Battle music + for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const MWWorld::Class &cls = iter->first.getClass(); @@ -988,6 +991,8 @@ namespace MWMechanics if(!stats.isDead()) { + if (stats.isHostile()) hostilesCount++; + if(iter->second->isDead()) { // Actor has been resurrected. Notify the CharacterController and re-enable collision. @@ -1045,6 +1050,20 @@ namespace MWMechanics } } + // check if we still have any player enemies to switch music + static bool isBattleMusic = false; + + if (isBattleMusic && hostilesCount == 0) + { + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); + isBattleMusic = false; + } + else if (!isBattleMusic && hostilesCount > 0) + { + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); + isBattleMusic = true; + } + // if player is in sneak state see if anyone detects him if (player.getClass().getCreatureStats(player).getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak)) { diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 5e2847ed2f..2218623da7 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -405,8 +405,6 @@ namespace MWMechanics } else // (within attack dist) { - if (!mAttack) mMovement.mPosition[1] = 0; - if(mMovement.mPosition[0] || mMovement.mPosition[1]) { mTimerCombatMove = 0.1f + 0.1f * static_cast(rand())/RAND_MAX; @@ -501,6 +499,12 @@ namespace MWMechanics } mMovement.mPosition[1] = 1; + if (mReadyToAttack) + { + // to stop possible sideway moving after target moved out of attack range + mCombatMove = true; + mTimerCombatMove = 0; + } mReadyToAttack = false; } From 4119038f1d772bdd9afde02ad1e7b965005ead1d Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 14:46:44 +0200 Subject: [PATCH 028/226] Remove an old workaround (Fixes #1458) --- apps/openmw/mwinput/inputmanagerimp.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index f75dc7ff03..279f7c2afb 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -919,11 +919,6 @@ namespace MWInput mInputBinder->addMouseButtonBinding (control, defaultMouseButtonBindings[i], ICS::Control::INCREASE); } } - - // Printscreen key should not be allowed because it's captured by system screenshot function - // We check this explicitely here to fix up pre-0.26 config files. Can be removed after a few versions - if (mInputBinder->getKeyBinding(mInputBinder->getControl(A_Screenshot), ICS::Control::INCREASE) == SDLK_PRINTSCREEN) - mInputBinder->addKeyBinding(mInputBinder->getControl(A_Screenshot), SDLK_F12, ICS::Control::INCREASE); } std::string InputManager::getActionDescription (int action) From e28888df394739094e93e9b98576392cb56c3234 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 10 Jun 2014 14:51:51 +0200 Subject: [PATCH 029/226] fix for dejavu location --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7038e05bae..d67b724aba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -432,7 +432,7 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF(BUILD_OPENCS) # Install licenses - INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) + INSTALL(FILES "docs/license/DejaVu Font License.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) ENDIF (DPKG_PROGRAM) From 3bf599248ec8103e4226d7d88efe73aa62f47734 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 14:58:09 +0200 Subject: [PATCH 030/226] CMake cleanup: Remove distribution-specific install stuff (unused) --- CMakeLists.txt | 71 ++++++++++++------------------- apps/launcher/CMakeLists.txt | 4 -- apps/mwiniimporter/CMakeLists.txt | 5 --- apps/opencs/CMakeLists.txt | 4 -- apps/openmw/CMakeLists.txt | 4 -- 5 files changed, 27 insertions(+), 61 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3632f1e080..8e5ba6cd86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,13 +80,6 @@ option(USE_FFMPEG "use ffmpeg for sound" ON) # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) -if(UNIX AND NOT APPLE) - option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF) - if(BUILD_WITH_DPKG) - find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") - endif(BUILD_WITH_DPKG) -endif(UNIX AND NOT APPLE) - # Location of morrowind data files if (APPLE) set(MORROWIND_DATA_FILES "./data" CACHE PATH "location of Morrowind data files") @@ -395,46 +388,36 @@ if (CMAKE_COMPILER_IS_GNUCC) endif (CMAKE_COMPILER_IS_GNUCC) IF(NOT WIN32 AND NOT APPLE) - ## Debian and non debian Linux building + # Linux building # Paths - IF (DPKG_PROGRAM) - ## Debian specific - SET(CMAKE_INSTALL_PREFIX "/usr") - SET(DATAROOTDIR "share" CACHE PATH "Sets the root of data directories to a non-default location") - SET(DATADIR "share/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") - SET(ICONDIR "share/pixmaps" CACHE PATH "Set icon dir") - SET(SYSCONFDIR "../etc/openmw" CACHE PATH "Set config dir") - ELSE () - ## Non debian specific - SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") - SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") - SET(DATADIR "${DATAROOTDIR}/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") - SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") - SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") - SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir") + SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") + SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") + SET(DATADIR "${DATAROOTDIR}/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") + SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") + SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") + SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir") - # Install binaries - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) - IF(BUILD_LAUNCHER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/omwlauncher" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_LAUNCHER) - IF(BUILD_BSATOOL) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_BSATOOL) - IF(BUILD_ESMTOOL) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_ESMTOOL) - IF(BUILD_MWINIIMPORTER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/mwiniimport" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_MWINIIMPORTER) - IF(BUILD_OPENCS) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_OPENCS) + # Install binaries + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) + IF(BUILD_LAUNCHER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/omwlauncher" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_LAUNCHER) + IF(BUILD_BSATOOL) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_BSATOOL) + IF(BUILD_ESMTOOL) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_ESMTOOL) + IF(BUILD_MWINIIMPORTER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/mwiniimport" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_MWINIIMPORTER) + IF(BUILD_OPENCS) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_OPENCS) - # Install licenses - INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) - INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) - ENDIF (DPKG_PROGRAM) + # Install licenses + INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) + INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) # Install icon and desktop file INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index ec721a5e59..d7733ba0e1 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -119,10 +119,6 @@ endif(NOT WIN32) -if(DPKG_PROGRAM) - INSTALL(TARGETS omwlauncher RUNTIME DESTINATION games COMPONENT omwlauncher) -endif() - if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(omwlauncher gcov) diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 702f665138..deab88ce28 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -22,8 +22,3 @@ if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(mwiniimport gcov) endif() - -if(DPKG_PROGRAM) - INSTALL(TARGETS mwiniimport RUNTIME DESTINATION games COMPONENT mwiniimport) -endif() - diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f18ac0bcab..dd31aa81b9 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -199,10 +199,6 @@ target_link_libraries(opencs components ) -if(DPKG_PROGRAM) - INSTALL(TARGETS opencs RUNTIME DESTINATION games COMPONENT opencs) -endif() - if(APPLE) INSTALL(TARGETS opencs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) endif() diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 8310e8abfa..7c047adefb 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -142,10 +142,6 @@ if(APPLE) endif() endif(APPLE) -if(DPKG_PROGRAM) - INSTALL(TARGETS openmw RUNTIME DESTINATION games COMPONENT openmw) -endif(DPKG_PROGRAM) - if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw gcov) From 1ed3f092c142488072d4915d9edd47f6660fa4ba Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 15:42:50 +0200 Subject: [PATCH 031/226] Implement text replacement for journal topic responses (Fixes #1429) --- apps/openmw/mwbase/journal.hpp | 2 +- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 4 +-- apps/openmw/mwdialogue/journalentry.cpp | 28 ++++++++++++++----- apps/openmw/mwdialogue/journalentry.hpp | 10 +++++-- apps/openmw/mwdialogue/journalimp.cpp | 7 +++-- apps/openmw/mwdialogue/journalimp.hpp | 2 +- apps/openmw/mwdialogue/topic.cpp | 5 ---- apps/openmw/mwdialogue/topic.hpp | 2 -- 8 files changed, 37 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index a49ebb9bc4..c43ebeb8f3 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -59,7 +59,7 @@ namespace MWBase virtual int getJournalIndex (const std::string& id) const = 0; ///< Get the journal index. - virtual void addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName) = 0; + virtual void addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) = 0; virtual TEntryIter begin() const = 0; ///< Iterator pointing to the begin of the main journal. diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 57681b7fb0..c1a6234185 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -294,7 +294,7 @@ namespace MWDialogue { if (iter->mId == info->mId) { - MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor.getClass().getName(mActor)); + MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor); break; } } @@ -472,7 +472,7 @@ namespace MWDialogue { if (iter->mId == info->mId) { - MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId, mActor.getClass().getName(mActor)); + MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId, mActor); break; } } diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 55574bf3eb..b92d7cacea 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -5,16 +5,21 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwscript/interpretercontext.hpp" + + namespace MWDialogue { Entry::Entry() {} - Entry::Entry (const std::string& topic, const std::string& infoId) + Entry::Entry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor) : mInfoId (infoId) { const ESM::Dialogue *dialogue = @@ -24,8 +29,17 @@ namespace MWDialogue iter!=dialogue->mInfo.end(); ++iter) if (iter->mId == mInfoId) { - /// \todo text replacement - mText = iter->mResponse; + if (actor.isEmpty()) + { + MWScript::InterpreterContext interpreterContext(NULL,MWWorld::Ptr()); + mText = Interpreter::fixDefinesDialog(iter->mResponse, interpreterContext); + } + else + { + MWScript::InterpreterContext interpreterContext(&actor.getRefData().getLocals(),actor); + mText = Interpreter::fixDefinesDialog(iter->mResponse, interpreterContext); + } + return; } @@ -49,8 +63,8 @@ namespace MWDialogue JournalEntry::JournalEntry() {} - JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId) - : Entry (topic, infoId), mTopic (topic) + JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor) + : Entry (topic, infoId, actor), mTopic (topic) {} JournalEntry::JournalEntry (const ESM::JournalEntry& record) @@ -65,7 +79,7 @@ namespace MWDialogue JournalEntry JournalEntry::makeFromQuest (const std::string& topic, int index) { - return JournalEntry (topic, idFromIndex (topic, index)); + return JournalEntry (topic, idFromIndex (topic, index), MWWorld::Ptr()); } std::string JournalEntry::idFromIndex (const std::string& topic, int index) @@ -90,7 +104,7 @@ namespace MWDialogue StampedJournalEntry::StampedJournalEntry (const std::string& topic, const std::string& infoId, int day, int month, int dayOfMonth) - : JournalEntry (topic, infoId), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) + : JournalEntry (topic, infoId, MWWorld::Ptr()), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) {} StampedJournalEntry::StampedJournalEntry (const ESM::JournalEntry& record) diff --git a/apps/openmw/mwdialogue/journalentry.hpp b/apps/openmw/mwdialogue/journalentry.hpp index a77ba4f7cf..3ae3efcc8d 100644 --- a/apps/openmw/mwdialogue/journalentry.hpp +++ b/apps/openmw/mwdialogue/journalentry.hpp @@ -8,6 +8,11 @@ namespace ESM struct JournalEntry; } +namespace MWWorld +{ + class Ptr; +} + namespace MWDialogue { /// \brief Basic quest/dialogue/topic entry @@ -19,7 +24,8 @@ namespace MWDialogue Entry(); - Entry (const std::string& topic, const std::string& infoId); + /// actor is optional + Entry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor); Entry (const ESM::JournalEntry& record); @@ -37,7 +43,7 @@ namespace MWDialogue JournalEntry(); - JournalEntry (const std::string& topic, const std::string& infoId); + JournalEntry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor); JournalEntry (const ESM::JournalEntry& record); diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 04aa0534dd..1429415e51 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -9,6 +9,7 @@ #include #include "../mwworld/esmstore.hpp" +#include "../mwworld/class.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -103,12 +104,12 @@ namespace MWDialogue quest.setIndex (index); } - void Journal::addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName) + void Journal::addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) { Topic& topic = getTopic (topicId); - JournalEntry entry(topicId, infoId); - entry.mActorName = actorName; + JournalEntry entry(topicId, infoId, actor); + entry.mActorName = actor.getClass().getName(actor); topic.addEntry (entry); } diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp index 00511f47c1..17b764436c 100644 --- a/apps/openmw/mwdialogue/journalimp.hpp +++ b/apps/openmw/mwdialogue/journalimp.hpp @@ -38,7 +38,7 @@ namespace MWDialogue virtual int getJournalIndex (const std::string& id) const; ///< Get the journal index. - virtual void addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName); + virtual void addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor); virtual TEntryIter begin() const; ///< Iterator pointing to the begin of the main journal. diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index f7df305c70..ea39174b8f 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -58,9 +58,4 @@ namespace MWDialogue { return mEntries.end(); } - - JournalEntry Topic::getEntry (const std::string& infoId) const - { - return JournalEntry (mTopic, infoId); - } } diff --git a/apps/openmw/mwdialogue/topic.hpp b/apps/openmw/mwdialogue/topic.hpp index 02fa6d5246..cbff2296cb 100644 --- a/apps/openmw/mwdialogue/topic.hpp +++ b/apps/openmw/mwdialogue/topic.hpp @@ -53,8 +53,6 @@ namespace MWDialogue TEntryIter end() const; ///< Iterator pointing past the end of the journal for this topic. - - JournalEntry getEntry (const std::string& infoId) const; }; } From a90245147bfc40994cc5bf7a0785077762f21755 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 16:00:02 +0200 Subject: [PATCH 032/226] Don't reset history when ForceGreeting is used and a dialogue window was already open for the same actor (Fixes #1423) --- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 6 +++++- apps/openmw/mwgui/dialogue.cpp | 12 ++++++++---- apps/openmw/mwgui/dialogue.hpp | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index c1a6234185..aca287854b 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -140,7 +140,11 @@ namespace MWDialogue mActorKnownTopics.clear(); MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - win->startDialogue(actor, actor.getClass().getName (actor)); + + // If the dialogue window was already open, keep the existing history + bool resetHistory = (!MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Dialogue)); + + win->startDialogue(actor, actor.getClass().getName (actor), resetHistory); //setup the list of topics known by the actor. Topics who are also on the knownTopics list will be added to the GUI updateTopics(); diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 441e31e327..e7dd74eee3 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -366,10 +366,11 @@ namespace MWGui } } - void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName) + void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName, bool resetHistory) { mGoodbye = false; mEnabled = true; + bool sameActor = (mPtr == actor); mPtr = actor; mTopicsList->setEnabled(true); setTitle(npcName); @@ -378,9 +379,12 @@ namespace MWGui mTopicsList->clear(); - for (std::vector::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it) - delete (*it); - mHistoryContents.clear(); + if (resetHistory || !sameActor) + { + for (std::vector::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it) + delete (*it); + mHistoryContents.clear(); + } for (std::vector::iterator it = mLinks.begin(); it != mLinks.end(); ++it) delete (*it); diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index fcb1338b5f..4e0ca5dde9 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -111,7 +111,7 @@ namespace MWGui void notifyLinkClicked (TypesetBook::InteractiveId link); - void startDialogue(MWWorld::Ptr actor, std::string npcName); + void startDialogue(MWWorld::Ptr actor, std::string npcName, bool resetHistory); void setKeywords(std::list keyWord); void addResponse (const std::string& text, const std::string& title=""); From 2dd54dbcfcef832ce2c3fe49f31cecf741e316f7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 16:36:22 +0200 Subject: [PATCH 033/226] Implement ClearInfoActor script instruction (Fixes #1422) --- apps/openmw/mwbase/dialoguemanager.hpp | 3 +++ apps/openmw/mwbase/journal.hpp | 5 +++++ apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 9 +++++++++ apps/openmw/mwdialogue/dialoguemanagerimp.hpp | 5 ++++- apps/openmw/mwdialogue/journalimp.cpp | 10 ++++++++++ apps/openmw/mwdialogue/journalimp.hpp | 5 +++++ apps/openmw/mwdialogue/topic.cpp | 13 +++++++++++++ apps/openmw/mwdialogue/topic.hpp | 2 ++ apps/openmw/mwscript/dialogueextensions.cpp | 14 ++++++++++++++ apps/openmw/mwscript/docs/vmformat.txt | 4 +++- components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 2 ++ 12 files changed, 71 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index dfb002cfc0..d0e64b23c8 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -76,6 +76,9 @@ namespace MWBase /// @return faction1's opinion of faction2 virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const = 0; + + /// Removes the last added topic response for the given actor from the journal + virtual void clearInfoActor (const MWWorld::Ptr& actor) const = 0; }; } diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index c43ebeb8f3..938cec74b2 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -60,6 +60,11 @@ namespace MWBase ///< Get the journal index. virtual void addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) = 0; + /// \note topicId must be lowercase + + virtual void removeLastAddedTopicResponse (const std::string& topicId, const std::string& actorName) = 0; + ///< Removes the last topic response added for the given topicId and actor name. + /// \note topicId must be lowercase virtual TEntryIter begin() const = 0; ///< Iterator pointing to the begin of the main journal. diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index aca287854b..aa7df1fd4e 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -698,6 +698,15 @@ namespace MWDialogue return diff; } + void DialogueManager::clearInfoActor(const MWWorld::Ptr &actor) const + { + if (actor == mActor && !mLastTopic.empty()) + { + MWBase::Environment::get().getJournal()->removeLastAddedTopicResponse( + mLastTopic, actor.getClass().getName(actor)); + } + } + std::vector ParseHyperText(const std::string& text) { std::vector result; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 94f8f3ec1a..6553ddc014 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -40,7 +40,7 @@ namespace MWDialogue bool mTalkedTo; int mChoice; - std::string mLastTopic; + std::string mLastTopic; // last topic ID, lowercase bool mIsInChoice; float mTemporaryDispositionChange; @@ -99,6 +99,9 @@ namespace MWDialogue /// @return faction1's opinion of faction2 virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const; + + /// Removes the last added topic response for the given actor from the journal + virtual void clearInfoActor (const MWWorld::Ptr& actor) const; }; diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 1429415e51..9497347e3a 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -113,6 +113,16 @@ namespace MWDialogue topic.addEntry (entry); } + void Journal::removeLastAddedTopicResponse(const std::string &topicId, const std::string &actorName) + { + Topic& topic = getTopic (topicId); + + topic.removeLastAddedResponse(actorName); + + if (topic.begin() == topic.end()) + mTopics.erase(mTopics.find(topicId)); // All responses removed -> remove topic + } + int Journal::getJournalIndex (const std::string& id) const { TQuestContainer::const_iterator iter = mQuests.find (id); diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp index 17b764436c..d15b909fa0 100644 --- a/apps/openmw/mwdialogue/journalimp.hpp +++ b/apps/openmw/mwdialogue/journalimp.hpp @@ -39,6 +39,11 @@ namespace MWDialogue ///< Get the journal index. virtual void addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor); + /// \note topicId must be lowercase + + virtual void removeLastAddedTopicResponse (const std::string& topicId, const std::string& actorName); + ///< Removes the last topic response added for the given topicId and actor name. + /// \note topicId must be lowercase virtual TEntryIter begin() const; ///< Iterator pointing to the begin of the main journal. diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index ea39174b8f..c1a45f841c 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -58,4 +58,17 @@ namespace MWDialogue { return mEntries.end(); } + + void Topic::removeLastAddedResponse (const std::string& actorName) + { + for (std::vector::reverse_iterator it = mEntries.rbegin(); + it != mEntries.rend(); ++it) + { + if (it->mActorName == actorName) + { + mEntries.erase( (++it).base() ); // erase doesn't take a reverse_iterator + return; + } + } + } } diff --git a/apps/openmw/mwdialogue/topic.hpp b/apps/openmw/mwdialogue/topic.hpp index cbff2296cb..72486ef8af 100644 --- a/apps/openmw/mwdialogue/topic.hpp +++ b/apps/openmw/mwdialogue/topic.hpp @@ -48,6 +48,8 @@ namespace MWDialogue virtual std::string getName() const; + void removeLastAddedResponse (const std::string& actorName); + TEntryIter begin() const; ///< Iterator pointing to the begin of the journal for this topic. diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 9dde65ab2c..45eeccf182 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -235,6 +235,18 @@ namespace MWScript } }; + template + class OpClearInfoActor : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + MWBase::Environment::get().getDialogueManager()->clearInfoActor(ptr); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -256,6 +268,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFactionExplicit, new OpSameFaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeModFactionReaction, new OpModFactionReaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeGetFactionReaction, new OpGetFactionReaction); + interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActor, new OpClearInfoActor); + interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActorExplicit, new OpClearInfoActor); } } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 24b0b6f7aa..91d0f031f3 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -393,5 +393,7 @@ op 0x2000241: onKnockoutExplicit op 0x2000242: ModFactionReaction op 0x2000243: GetFactionReaction op 0x2000244: Activate, explicit +op 0x2000245: ClearInfoActor +op 0x2000246: ClearInfoActor, explicit -opcodes 0x2000245-0x3ffffff unused +opcodes 0x2000247-0x3ffffff unused diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 0f726a52d2..61464e5c2a 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -181,6 +181,7 @@ namespace Compiler opcodeSameFactionExplicit); extensions.registerInstruction("modfactionreaction", "ccl", opcodeModFactionReaction); extensions.registerFunction("getfactionreaction", 'l', "ccl", opcodeGetFactionReaction); + extensions.registerInstruction("clearinfoactor", "", opcodeClearInfoActor, opcodeClearInfoActorExplicit); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 8796c53c54..4b22e4b9cd 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -154,6 +154,8 @@ namespace Compiler const int opcodeSameFactionExplicit = 0x20001b6; const int opcodeModFactionReaction = 0x2000242; const int opcodeGetFactionReaction = 0x2000243; + const int opcodeClearInfoActor = 0x2000245; + const int opcodeClearInfoActorExplicit = 0x2000246; } namespace Gui From 6ba112619ae3362c91ddfc180ae034f49f044eaa Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 16:46:13 +0200 Subject: [PATCH 034/226] Fix dropped items ending up inaccessible when standing in objects with no collision (Fixes #1441) --- apps/openmw/mwworld/worldimp.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ebdac7ba14..bf752734fb 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1703,14 +1703,15 @@ namespace MWWorld Ogre::Vector3 orig = Ogre::Vector3(pos.pos); + orig.z += 20; Ogre::Vector3 dir = Ogre::Vector3(0, 0, -1); - float len = (pos.pos[2] >= 0) ? pos.pos[2] : -pos.pos[2]; - len += 100.0; + float len = 100.0; std::pair hit = mPhysics->castRay(orig, dir, len); - pos.pos[2] = hit.second.z; + if (hit.first) + pos.pos[2] = hit.second.z; // copy the object and set its count int origCount = object.getRefData().getCount(); From 3788fb042e4b739892c347183a7d148fb0bf47be Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 17:47:59 +0200 Subject: [PATCH 035/226] Implement MenuTest script instruction (Fixes #1454) --- apps/openmw/mwbase/windowmanager.hpp | 2 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 23 +++++++++++++ apps/openmw/mwgui/windowmanagerimp.hpp | 2 ++ apps/openmw/mwgui/windowpinnablebase.cpp | 6 ++++ apps/openmw/mwgui/windowpinnablebase.hpp | 1 + apps/openmw/mwscript/docs/vmformat.txt | 3 +- apps/openmw/mwscript/guiextensions.cpp | 42 ++++++++++++++++++++++++ components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 1 + 9 files changed, 80 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 2dfa50eb51..0f45542ba0 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -328,6 +328,8 @@ namespace MWBase /** Used when one Modal adds another Modal \param input Pointer to the current modal, to ensure proper modal is removed **/ virtual void removeCurrentModal(MWGui::WindowModal* input) = 0; + + virtual void pinWindow (MWGui::GuiWindow window) = 0; }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 7ebdb6a83e..b49bfbfa6f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1646,4 +1646,27 @@ namespace MWGui if (_key == MyGUI::KeyCode::Escape) mVideoWidget->stop(); } + + void WindowManager::pinWindow(GuiWindow window) + { + switch (window) + { + case GW_Inventory: + mInventoryWindow->setPinned(true); + break; + case GW_Map: + mMap->setPinned(true); + break; + case GW_Magic: + mSpellWindow->setPinned(true); + break; + case GW_Stats: + mStatsWindow->setPinned(true); + break; + default: + break; + } + + updateVisible(); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index aee6cef474..dd41635ad3 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -319,6 +319,8 @@ namespace MWGui \param input Pointer to the current modal, to ensure proper modal is removed **/ virtual void removeCurrentModal(WindowModal* input); + virtual void pinWindow (MWGui::GuiWindow window); + private: bool mConsoleOnlyScripts; diff --git a/apps/openmw/mwgui/windowpinnablebase.cpp b/apps/openmw/mwgui/windowpinnablebase.cpp index 47364337c7..919d315f27 100644 --- a/apps/openmw/mwgui/windowpinnablebase.cpp +++ b/apps/openmw/mwgui/windowpinnablebase.cpp @@ -25,6 +25,12 @@ namespace MWGui onPinToggled(); } + void WindowPinnableBase::setPinned(bool pinned) + { + if (pinned != mPinned) + onPinButtonClicked(mPinButton); + } + void WindowPinnableBase::setPinButtonVisible(bool visible) { mPinButton->setVisible(visible); diff --git a/apps/openmw/mwgui/windowpinnablebase.hpp b/apps/openmw/mwgui/windowpinnablebase.hpp index cd393f9187..3aad60988c 100644 --- a/apps/openmw/mwgui/windowpinnablebase.hpp +++ b/apps/openmw/mwgui/windowpinnablebase.hpp @@ -12,6 +12,7 @@ namespace MWGui public: WindowPinnableBase(const std::string& parLayout); bool pinned() { return mPinned; } + void setPinned (bool pinned); void setPinButtonVisible(bool visible); private: diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 91d0f031f3..1576bf0af9 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -57,7 +57,8 @@ op 0x20028: RemoveSoulGem, explicit reference op 0x20029: PCRaiseRank, explicit reference op 0x2002a: PCLowerRank, explicit reference op 0x2002b: PCJoinFaction, explicit reference -opcodes 0x2002c-0x3ffff unused +op 0x2002c: MenuTest +opcodes 0x2002d-0x3ffff unused Segment 4: (not implemented yet) diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index be241a5649..333be5be61 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -162,6 +162,47 @@ namespace MWScript } }; + class OpMenuTest : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + int arg=0; + if(arg0>0) + { + arg = runtime[0].mInteger; + runtime.pop(); + } + + + if (arg == 0) + { + MWGui::GuiMode modes[] = { MWGui::GM_Inventory, MWGui::GM_Container }; + + for (int i=0; i<2; ++i) + { + if (MWBase::Environment::get().getWindowManager()->containsMode(modes[i])) + MWBase::Environment::get().getWindowManager()->removeGuiMode(modes[i]); + } + } + else + { + MWGui::GuiWindow gw = MWGui::GW_None; + if (arg == 3) + gw = MWGui::GW_Stats; + if (arg == 4) + gw = MWGui::GW_Inventory; + if (arg == 5) + gw = MWGui::GW_Magic; + if (arg == 6) + gw = MWGui::GW_Map; + + MWBase::Environment::get().getWindowManager()->pinWindow(gw); + } + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -200,6 +241,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Gui::opcodeShowMap, new OpShowMap); interpreter.installSegment5 (Compiler::Gui::opcodeFillMap, new OpFillMap); + interpreter.installSegment3 (Compiler::Gui::opcodeMenuTest, new OpMenuTest); } } } diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 61464e5c2a..b9e36db801 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -216,6 +216,7 @@ namespace Compiler extensions.registerInstruction ("showmap", "S", opcodeShowMap); extensions.registerInstruction ("fillmap", "", opcodeFillMap); + extensions.registerInstruction ("menutest", "/l", opcodeMenuTest); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 4b22e4b9cd..14f4db060c 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -177,6 +177,7 @@ namespace Compiler const int opcodeToggleFullHelp = 0x2000151; const int opcodeShowMap = 0x20001a0; const int opcodeFillMap = 0x20001a1; + const int opcodeMenuTest = 0x2002c; } namespace Misc From 5a955279bb3b3f2f9e3bbadf67454cb91a040589 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 18:29:20 +0200 Subject: [PATCH 036/226] Fix main menu background showing when resizing window during load --- apps/openmw/mwgui/mainmenu.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 1d47d3b765..ca7e877cce 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -68,10 +68,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); + + showBackground( + MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && + MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); OEngine::GUI::Layout::setVisible (visible); } @@ -220,7 +220,6 @@ 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 7b5482f25bad75275e14be4b67422303e7cf9da9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 18:31:39 +0200 Subject: [PATCH 037/226] Fix character selection caption when there is no character in settings.cfg --- apps/openmw/mwgui/savegamedialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index caa0cbb360..a35415e754 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -163,6 +163,8 @@ namespace MWGui } mCharacterSelection->setIndexSelected(selectedIndex); + if (selectedIndex == MyGUI::ITEM_NONE) + mCharacterSelection->setCaption("Select Character ..."); fillSaveList(); From 47172fb8a2945b8703175c5a8b2bdd74d0b525ca Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 19:23:42 +0200 Subject: [PATCH 038/226] ContentModel: Don't confuse file path with file name (Fixes #1352) --- .../contentselector/model/contentmodel.cpp | 31 ++++++++++--------- .../contentselector/model/contentmodel.hpp | 4 +-- components/contentselector/model/esmfile.hpp | 2 ++ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 0d274474c6..f5bc2369a4 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -239,7 +239,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const return false; EsmFile *file = item(index.row()); - QString fileName = file->filePath(); + QString fileName = file->fileName(); bool success = false; switch(role) @@ -266,7 +266,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const if (success) { - success = setCheckState(fileName, value.toBool()); + success = setCheckState(file->filePath(), value.toBool()); emit dataChanged(index, index); } } @@ -277,19 +277,19 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const int checkValue = value.toInt(); bool success = false; bool setState = false; - if ((checkValue==Qt::Checked) && !isChecked(fileName)) + if ((checkValue==Qt::Checked) && !isChecked(file->filePath())) { setState = true; success = true; } - else if ((checkValue == Qt::Checked) && isChecked (fileName)) + else if ((checkValue == Qt::Checked) && isChecked (file->filePath())) setState = true; else if (checkValue == Qt::Unchecked) setState = true; if (setState) { - setCheckState(fileName, success); + setCheckState(file->filePath(), success); emit dataChanged(index, index); } @@ -517,10 +517,10 @@ void ContentSelectorModel::ContentModel::sortFiles() } } -bool ContentSelectorModel::ContentModel::isChecked(const QString& name) const +bool ContentSelectorModel::ContentModel::isChecked(const QString& filepath) const { - if (mCheckStates.contains(name)) - return (mCheckStates[name] == Qt::Checked); + if (mCheckStates.contains(filepath)) + return (mCheckStates[filepath] == Qt::Checked); return false; } @@ -543,12 +543,12 @@ void ContentSelectorModel::ContentModel::refreshModel() emit dataChanged (index(0,0), index(rowCount()-1,0)); } -bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool checkState) +bool ContentSelectorModel::ContentModel::setCheckState(const QString &filepath, bool checkState) { - if (name.isEmpty()) + if (filepath.isEmpty()) return false; - const EsmFile *file = item(name); + const EsmFile *file = item(filepath); if (!file) return false; @@ -558,8 +558,8 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool if (checkState) state = Qt::Checked; - mCheckStates[name] = state; - emit dataChanged(indexFromItem(item(name)), indexFromItem(item(name))); + mCheckStates[filepath] = state; + emit dataChanged(indexFromItem(item(filepath)), indexFromItem(item(filepath))); if (file->isGameFile()) refreshModel(); @@ -586,7 +586,10 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool { foreach (const EsmFile *downstreamFile, mFiles) { - if (downstreamFile->gameFiles().contains(name)) + QFileInfo fileInfo(filepath); + QString filename = fileInfo.fileName(); + + if (downstreamFile->gameFiles().contains(filename)) { if (mCheckStates.contains(downstreamFile->filePath())) mCheckStates[downstreamFile->filePath()] = Qt::Unchecked; diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 8c8c2124bc..6d147bce98 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -45,8 +45,8 @@ namespace ContentSelectorModel const EsmFile *item(const QString &name) const; bool isEnabled (QModelIndex index) const; - bool isChecked(const QString &name) const; - bool setCheckState(const QString &name, bool isChecked); + bool isChecked(const QString &filepath) const; + bool setCheckState(const QString &filepath, bool isChecked); void setCheckStates (const QStringList &fileList, bool isChecked); ContentFileList checkedItems() const; void uncheckAll(); diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index ca24b52d11..7e1edcaba6 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -53,6 +53,8 @@ namespace ContentSelectorModel inline QDateTime modified() const { return mModified; } inline float format() const { return mFormat; } inline QString filePath() const { return mPath; } + + /// @note Contains file names, not paths. inline const QStringList &gameFiles() const { return mGameFiles; } inline QString description() const { return mDescription; } inline QString toolTip() const { return sToolTip.arg(mAuthor) From 07d20c212bcd7dbe162e63de662f2928e95bfbeb Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 10 Jun 2014 21:34:47 +0200 Subject: [PATCH 039/226] Fix crash activating quick key 1 --- apps/openmw/mwgui/quickkeysmenu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index f52a4b9413..328ef21fb7 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -247,7 +247,7 @@ namespace MWGui void QuickKeysMenu::activateQuickKey(int index) { - assert (index-1 > 0); + assert (index-1 >= 0); ItemWidget* button = mQuickKeyButtons[index-1]; QuickKeyType type = mAssigned[index-1]; From d6d9df6cec4c3c5e1dc02bc756f4174243caff56 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Wed, 11 Jun 2014 00:20:46 +0400 Subject: [PATCH 040/226] split getStartTime --- apps/openmw/mwmechanics/aicombat.cpp | 12 ++++++-- apps/openmw/mwrender/animation.cpp | 35 ++++++++++++++---------- apps/openmw/mwrender/animation.hpp | 5 +++- apps/openmw/mwrender/weaponanimation.cpp | 2 +- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 2218623da7..4a3c724d59 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -785,15 +785,21 @@ void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations // get durations for each attack type for (int i = 0; i < (bRangedWeap ? 1 : 3); i++) { - float start1 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey, false); + float start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey); + + if (start1 < 0) + { + fMinMaxDurations[i][0] = fMinMaxDurations[i][1] = 0.1f; + continue; + } textKey2 = "min attack"; - float start2 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2, false); + float start2 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2); fMinMaxDurations[i][0] = (start2 - start1) / weapSpeed; textKey2 = "max attack"; - start1 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2, false); + start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2); fMinMaxDurations[i][1] = fMinMaxDurations[i][0] + (start1 - start2) / weapSpeed; } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6c27465577..1c86fab891 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -903,27 +903,32 @@ bool Animation::getInfo(const std::string &groupname, float *complete, float *sp return true; } -float Animation::getStartTime(const std::string &groupname, bool onlyGroup) const +float Animation::getStartTime(const std::string &groupname) const { - AnimSourceList::const_iterator iter(mAnimSources.begin()); - for(;iter != mAnimSources.end();iter++) + for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) { const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; - if (onlyGroup) + + NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); + if(found != keys.end()) + return found->first; + } + return -1.f; +} + +float Animation::getTextKeyTime(const std::string &textKey) const +{ + for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) + { + const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; + + for(NifOgre::TextKeyMap::const_iterator iterKey(keys.begin()); iterKey != keys.end(); ++iterKey) { - NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); - if(found != keys.end()) - return found->first; - } - else - { - for(NifOgre::TextKeyMap::const_iterator iter(keys.begin()); iter != keys.end(); ++iter) - { - if(iter->second.compare(0, groupname.size(), groupname) == 0) - return iter->first; - } + if(iterKey->second.compare(0, textKey.size(), textKey) == 0) + return iterKey->first; } } + return -1.f; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 2fc57f0e91..b2d69b79a4 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -275,7 +275,10 @@ public: bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const; /// Get the absolute position in the animation track of the first text key with the given group. - float getStartTime(const std::string &groupname, bool onlyGroup) const; + float getStartTime(const std::string &groupname) const; + + /// Get the absolute position in the animation track of the text key + float getTextKeyTime(const std::string &textKey) const; /// Get the current absolute position in the animation track for the animation that is currently playing from the given group. float getCurrentTime(const std::string& groupname) const; diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 3d4886ceac..5f953bd838 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -32,7 +32,7 @@ float WeaponAnimationTime::getValue() const void WeaponAnimationTime::setGroup(const std::string &group) { mWeaponGroup = group; - mStartTime = mAnimation->getStartTime(mWeaponGroup, true); + mStartTime = mAnimation->getStartTime(mWeaponGroup); } void WeaponAnimationTime::updateStartTime() From a41339da1e9b4ff0f18090d6376b2563e1a7a0cc Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Tue, 10 Jun 2014 02:22:58 +0200 Subject: [PATCH 041/226] Write logs in log directory The crash.log file was created in the working directory, requiring users that had installed the game to run it with augmented privileges to be able to create the file. --- apps/openmw/main.cpp | 168 ++++++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 80 deletions(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 3098d953ec..06db76fcf2 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -7,16 +7,15 @@ #include #include "engine.hpp" -#if defined(_WIN32) && !defined(_CONSOLE) #include #include +#if defined(_WIN32) // For OutputDebugString #define WIN32_LEAN_AND_MEAN #include // makes __argc and __argv available on windows #include - #endif @@ -253,29 +252,92 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return true; } +#if defined(_WIN32) && defined(_DEBUG) +class DebugOutput : public boost::iostreams::sink +{ +public: + std::streamsize write(const char *str, std::streamsize size) + { + // Make a copy for null termination + std::string tmp (str, size); + // Write string to Visual Studio Debug output + OutputDebugString (tmp.c_str ()); + return size; + } +}; +#else +class Tee : public boost::iostreams::sink +{ +public: + Tee(std::ostream &stream, std::ostream &stream2) + : out(stream), out2(stream2) + { + } + + std::streamsize write(const char *str, std::streamsize size) + { + out.write (str, size); + out.flush(); + out2.write (str, size); + out2.flush(); + return size; + } + +private: + std::ostream &out; + std::ostream &out2; +}; +#endif + int main(int argc, char**argv) { -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE - // Unix crash catcher - if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached()) - { - int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; - cc_install_handlers(argc, argv, 5, s, "crash.log", NULL); - std::cout << "Installing crash catcher" << std::endl; - } - else - std::cout << "Running in a debugger, not installing crash catcher" << std::endl; -#endif - -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - // set current dir to bundle path - boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); - boost::filesystem::current_path(bundlePath); -#endif + std::streambuf* cout_rdbuf = std::cout.rdbuf (); + std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); + int ret = 0; try { Files::ConfigurationManager cfgMgr; + +#if defined(_WIN32) && defined(_DEBUG) + // Redirect cout and cerr to VS debug output when running in debug mode + boost::iostreams::stream_buffer sb; + sb.open(DebugOutput()); + std::cout.rdbuf (&sb); + std::cerr.rdbuf (&sb); +#else + // Redirect cout and cerr to openmw.log + std::ofstream logfile (std::string(cfgMgr.getLogPath().string() + "/openmw.log").c_str()); + + boost::iostreams::stream_buffer coutsb; + std::ostream oldcout(cout_rdbuf); + coutsb.open (Tee(logfile, oldcout)); + std::cout.rdbuf (&coutsb); + + boost::iostreams::stream_buffer cerrsb; + std::ostream oldcerr(cerr_rdbuf); + cerrsb.open (Tee(logfile, oldcerr)); + std::cerr.rdbuf (&cerrsb); +#endif + +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE + // Unix crash catcher + if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached()) + { + int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; + cc_install_handlers(argc, argv, 5, s, std::string(cfgMgr.getLogPath().string() + "/crash.log").c_str(), NULL); + std::cout << "Installing crash catcher" << std::endl; + } + else + std::cout << "Running in a debugger, not installing crash catcher" << std::endl; +#endif + +#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE + // set current dir to bundle path + boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); + boost::filesystem::current_path(bundlePath); +#endif + OMW::Engine engine(cfgMgr); if (parseOptions(argc, argv, engine, cfgMgr)) @@ -292,76 +354,22 @@ int main(int argc, char**argv) #endif SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); - return 1; + ret = 1; } - return 0; + // Restore cout and cerr + std::cout.rdbuf(cout_rdbuf); + std::cerr.rdbuf(cerr_rdbuf); + + return ret; } // Platform specific for Windows when there is no console built into the executable. // Windows will call the WinMain function instead of main in this case, the normal // main function is then called with the __argc and __argv parameters. -// In addition if it is a debug build it will redirect cout to the debug console in Visual Studio #if defined(_WIN32) && !defined(_CONSOLE) - -#if defined(_DEBUG) -class DebugOutput : public boost::iostreams::sink -{ -public: - std::streamsize write(const char *str, std::streamsize size) - { - // Make a copy for null termination - std::string tmp (str, size); - // Write string to Visual Studio Debug output - OutputDebugString (tmp.c_str ()); - return size; - } -}; -#else -class Logger : public boost::iostreams::sink -{ -public: - Logger(std::ofstream &stream) - : out(stream) - { - } - - std::streamsize write(const char *str, std::streamsize size) - { - out.write (str, size); - out.flush(); - return size; - } - -private: - std::ofstream &out; -}; -#endif - int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { - std::streambuf* old_rdbuf = std::cout.rdbuf (); - - int ret = 0; -#if defined(_DEBUG) - // Redirect cout to VS debug output when running in debug mode - { - boost::iostreams::stream_buffer sb; - sb.open(DebugOutput()); -#else - // Redirect cout to openmw.log - std::ofstream logfile ("openmw.log"); - { - boost::iostreams::stream_buffer sb; - sb.open (Logger (logfile)); -#endif - std::cout.rdbuf (&sb); - - ret = main (__argc, __argv); - - std::cout.rdbuf(old_rdbuf); - } - return ret; + return main(__argc, __argv); } - #endif From 14a9f0ebf8d54608057f8a40e632a5859b721eb3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jun 2014 02:24:17 +0200 Subject: [PATCH 042/226] Handle Quadratic and Linear attenuation independently (Fixes #1456) --- apps/openmw/mwrender/animation.cpp | 36 ++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 9ed4cb6d3c..dd900f79c4 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -356,28 +356,40 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScene objlist->mControllers.push_back(Ogre::Controller(src, dest, func)); bool interior = !(mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior()); - bool quadratic = fallback->getFallbackBool("LightAttenuation_OutQuadInLin") ? - !interior : fallback->getFallbackBool("LightAttenuation_UseQuadratic"); + + static bool outQuadInLin = fallback->getFallbackBool("LightAttenuation_OutQuadInLin"); + static bool useQuadratic = fallback->getFallbackBool("LightAttenuation_UseQuadratic"); + static float quadraticValue = fallback->getFallbackFloat("LightAttenuation_QuadraticValue"); + static float quadraticRadiusMult = fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult"); + static bool useLinear = fallback->getFallbackBool("LightAttenuation_UseLinear"); + static float linearRadiusMult = fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); + static float linearValue = fallback->getFallbackFloat("LightAttenuation_LinearValue"); + + bool quadratic = useQuadratic && (!outQuadInLin || !interior); + // with the standard 1 / (c + d*l + d*d*q) equation the attenuation factor never becomes zero, // so we ignore lights if their attenuation falls below this factor. const float threshold = 0.03; - if (!quadratic) + float quadraticAttenuation = 0; + float linearAttenuation = 0; + float activationRange = 0; + if (quadratic) { - float r = radius * fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); - float attenuation = fallback->getFallbackFloat("LightAttenuation_LinearValue") / r; - float activationRange = 1.0f / (threshold * attenuation); - olight->setAttenuation(activationRange, 0, attenuation, 0); + float r = radius * quadraticRadiusMult; + quadraticAttenuation = quadraticValue / std::pow(r, 2); + activationRange = std::sqrt(1.0f / (threshold * quadraticAttenuation)); } - else + if (useLinear) { - float r = radius * fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult"); - float attenuation = fallback->getFallbackFloat("LightAttenuation_QuadraticValue") / std::pow(r, 2); - float activationRange = std::sqrt(1.0f / (threshold * attenuation)); - olight->setAttenuation(activationRange, 0, 0, attenuation); + float r = radius * linearRadiusMult; + linearAttenuation = linearValue / r; + activationRange = std::max(activationRange, 1.0f / (threshold * linearAttenuation)); } + olight->setAttenuation(activationRange, 0, linearAttenuation, quadraticAttenuation); + // If there's an AttachLight bone, attach the light to that, otherwise put it in the center, if(objlist->mSkelBase && objlist->mSkelBase->getSkeleton()->hasBone("AttachLight")) objlist->mSkelBase->attachObjectToBone("AttachLight", olight); From 0f31e310885cbb1777b96899daca15d0ef430890 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jun 2014 03:08:22 +0200 Subject: [PATCH 043/226] Allow opening journal during dialogue (Fixes #1460) --- apps/openmw/mwinput/inputmanagerimp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 3d2f57bf60..bb18b5aa78 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -807,8 +807,9 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().isModalAny()) return; - // Toggle between game mode and journal mode - if(!MWBase::Environment::get().getWindowManager()->isGuiMode() && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) + if((!MWBase::Environment::get().getWindowManager()->isGuiMode() + || MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Dialogue) + && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) { MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0); MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Journal); @@ -817,7 +818,6 @@ namespace MWInput { MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } - // .. but don't touch any other mode. } void InputManager::quickKey (int index) From 7d1ecea20c9e3c2cac2168f961c8a78b00bf5d2b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 11 Jun 2014 11:43:38 +0200 Subject: [PATCH 044/226] added extended versions of revert and delete --- apps/opencs/model/world/commanddispatcher.cpp | 102 ++++++++++++++++++ apps/opencs/model/world/commanddispatcher.hpp | 16 +++ apps/opencs/view/world/table.cpp | 26 +++++ apps/opencs/view/world/table.hpp | 2 + 4 files changed, 146 insertions(+) diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index ce6d1e3982..4e146d87c0 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -1,6 +1,10 @@ #include "commanddispatcher.hpp" +#include + +#include + #include "../doc/document.hpp" #include "idtable.hpp" @@ -88,6 +92,13 @@ void CSMWorld::CommandDispatcher::setEditLock (bool locked) void CSMWorld::CommandDispatcher::setSelection (const std::vector& selection) { mSelection = selection; + std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::toLower); + std::sort (mSelection.begin(), mSelection.end()); +} + +void CSMWorld::CommandDispatcher::setExtendedTypes (const std::vector& types) +{ + mExtendedTypes = types; } bool CSMWorld::CommandDispatcher::canDelete() const @@ -106,6 +117,20 @@ bool CSMWorld::CommandDispatcher::canRevert() const return getRevertableRecords().size()!=0; } +std::vector CSMWorld::CommandDispatcher::getExtendedTypes() const +{ + std::vector tables; + + if (mId==UniversalId::Type_Cells) + { + tables.push_back (mId); + tables.push_back (UniversalId::Type_References); + /// \todo add other cell-specific types + } + + return tables; +} + void CSMWorld::CommandDispatcher::executeDelete() { if (mLocked) @@ -163,3 +188,80 @@ void CSMWorld::CommandDispatcher::executeRevert() if (rows.size()>1) mDocument.getUndoStack().endMacro(); } + +void CSMWorld::CommandDispatcher::executeExtendedDelete() +{ + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Extended delete of multiple records")); + + for (std::vector::const_iterator iter (mExtendedTypes.begin()); + iter!=mExtendedTypes.end(); ++iter) + { + if (*iter==mId) + executeDelete(); + else if (*iter==UniversalId::Type_References) + { + IdTable& model = dynamic_cast ( + *mDocument.getData().getTableModel (*iter)); + + const RefCollection& collection = mDocument.getData().getReferences(); + + int size = collection.getSize(); + + for (int i=size-1; i>=0; --i) + { + const Record& record = collection.getRecord (i); + + if (record.mState==RecordBase::State_Deleted) + continue; + + if (!std::binary_search (mSelection.begin(), mSelection.end(), + Misc::StringUtils::lowerCase (record.get().mCell))) + continue; + + mDocument.getUndoStack().push ( + new CSMWorld::DeleteCommand (model, record.get().mId)); + } + } + } + + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().endMacro(); +} + +void CSMWorld::CommandDispatcher::executeExtendedRevert() +{ + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Extended revert of multiple records")); + + for (std::vector::const_iterator iter (mExtendedTypes.begin()); + iter!=mExtendedTypes.end(); ++iter) + { + if (*iter==mId) + executeRevert(); + else if (*iter==UniversalId::Type_References) + { + IdTable& model = dynamic_cast ( + *mDocument.getData().getTableModel (*iter)); + + const RefCollection& collection = mDocument.getData().getReferences(); + + int size = collection.getSize(); + + for (int i=size-1; i>=0; --i) + { + const Record& record = collection.getRecord (i); + + if (!std::binary_search (mSelection.begin(), mSelection.end(), + Misc::StringUtils::lowerCase (record.get().mCell))) + continue; + + mDocument.getUndoStack().push ( + new CSMWorld::RevertCommand (model, record.get().mId)); + } + } + } + + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().endMacro(); +} \ No newline at end of file diff --git a/apps/opencs/model/world/commanddispatcher.hpp b/apps/opencs/model/world/commanddispatcher.hpp index 3bf81f0683..50085b1a1e 100644 --- a/apps/opencs/model/world/commanddispatcher.hpp +++ b/apps/opencs/model/world/commanddispatcher.hpp @@ -22,6 +22,7 @@ namespace CSMWorld CSMDoc::Document& mDocument; UniversalId mId; std::vector mSelection; + std::vector mExtendedTypes; std::vector getDeletableRecords() const; @@ -37,16 +38,31 @@ namespace CSMWorld void setSelection (const std::vector& selection); + void setExtendedTypes (const std::vector& types); + ///< Set record lists selected by the user for extended operations. + bool canDelete() const; bool canRevert() const; + /// Return IDs of the record collection that can also be affected when + /// operating on the record collection this dispatcher is used for. + /// + /// \note The returned collection contains the ID of the record collection this + /// dispatcher is used for. However if that record collection does not support + /// the extended mode, the returned vector will be empty instead. + std::vector getExtendedTypes() const; + public slots: void executeDelete(); void executeRevert(); + void executeExtendedDelete(); + + void executeExtendedRevert(); + }; } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 0fc7dd4b2f..877fd51c07 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -44,6 +44,10 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) mDispatcher->setSelection (records); + std::vector extendedTypes = mDispatcher->getExtendedTypes(); + + mDispatcher->setExtendedTypes (extendedTypes); + // create context menu QMenu menu (this); @@ -63,11 +67,21 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) menu.addAction (mCreateAction); if (mDispatcher->canRevert()) + { menu.addAction (mRevertAction); + if (!extendedTypes.empty()) + menu.addAction (mExtendedRevertAction); + } + if (mDispatcher->canDelete()) + { menu.addAction (mDeleteAction); + if (!extendedTypes.empty()) + menu.addAction (mExtendedDeleteAction); + } + if (mModel->getFeatures() & CSMWorld::IdTable::Feature_ReorderWithinTopic) { /// \todo allow reordering of multiple rows @@ -203,6 +217,18 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord())); addAction (mPreviewAction); + /// \todo add a user option, that redirects the extended action to an input panel (in + /// the bottom bar) that lets the user select which record collections should be + /// modified. + + mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this); + connect (mExtendedDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedDelete())); + addAction (mExtendedDeleteAction); + + mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this); + connect (mExtendedRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedRevert())); + addAction (mExtendedRevertAction); + connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 54971fb668..255c430ea5 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -46,6 +46,8 @@ namespace CSVWorld QAction *mMoveDownAction; QAction *mViewAction; QAction *mPreviewAction; + QAction *mExtendedDeleteAction; + QAction *mExtendedRevertAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTable *mModel; int mRecordStatusDisplay; From 73be45780585e1c3427a35b47c70a0b5e22fe26c Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jun 2014 15:37:05 +0200 Subject: [PATCH 045/226] Merge ESM::Cell fields by subrecord Fixes an issue with the Morrowind Patched mod where cell ambient values would become black due to the new cell records not including an AMBI subrecord. Also fixes a bug where mLeasedRefs was incorrectly cleared when overwriting a cell (*oldcell = *cell;) --- apps/openmw/mwworld/store.cpp | 87 ++++++++++++++++++++--------------- apps/openmw/mwworld/store.hpp | 2 + components/esm/loadcell.cpp | 55 +++++++++------------- components/esm/loadcell.hpp | 12 ++--- 4 files changed, 82 insertions(+), 74 deletions(-) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 1156cbc152..19b204073e 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -3,22 +3,8 @@ namespace MWWorld { - -void Store::load(ESM::ESMReader &esm, const std::string &id) +void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) { - // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, - // and we merge all this data into one Cell object. However, we can't simply search for the cell id, - // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they - // are not available until both cells have been loaded! So first, proceed as usual. - - // All cells have a name record, even nameless exterior cells. - std::string idLower = Misc::StringUtils::lowerCase(id); - ESM::Cell *cell = new ESM::Cell; - cell->mName = id; - - //First part of cell loading - cell->preLoad(esm); - //Handling MovedCellRefs, there is no way to do it inside loadcell while (esm.isNextSub("MVRF")) { ESM::CellRef ref; @@ -43,35 +29,56 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) else *iter = ref; } +} - //Second part of cell loading - cell->postLoad(esm); +void Store::load(ESM::ESMReader &esm, const std::string &id) +{ + // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, + // and we merge all this data into one Cell object. However, we can't simply search for the cell id, + // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they + // are not available until both cells have been loaded at least partially! - if(cell->mData.mFlags & ESM::Cell::Interior) + // All cells have a name record, even nameless exterior cells. + std::string idLower = Misc::StringUtils::lowerCase(id); + ESM::Cell cell; + cell.mName = id; + + // Load the (x,y) coordinates of the cell, if it is an exterior cell, + // so we can find the cell we need to merge with + cell.loadData(esm); + + if(cell.mData.mFlags & ESM::Cell::Interior) { // Store interior cell by name, try to merge with existing parent data. ESM::Cell *oldcell = const_cast(search(idLower)); if (oldcell) { - // push the new references on the list of references to manage - oldcell->mContextList.push_back(cell->mContextList.at(0)); - // copy list into new cell - cell->mContextList = oldcell->mContextList; - // have new cell replace old cell - ESM::Cell::merge(oldcell, cell); + // merge new cell into old cell + // push the new references on the list of references to manage (saveContext = true) + oldcell->mData = cell.mData; + oldcell->loadCell(esm, true); } else - mInt[idLower] = *cell; + { + // spawn a new cell + cell.loadCell(esm, true); + + mInt[idLower] = cell; + } } else { // Store exterior cells by grid position, try to merge with existing parent data. - ESM::Cell *oldcell = const_cast(search(cell->getGridX(), cell->getGridY())); + ESM::Cell *oldcell = const_cast(search(cell.getGridX(), cell.getGridY())); if (oldcell) { - // push the new references on the list of references to manage - oldcell->mContextList.push_back(cell->mContextList.at(0)); - // copy list into new cell - cell->mContextList = oldcell->mContextList; + // merge new cell into old cell + // push the new references on the list of references to manage (saveContext = true) + oldcell->mData = cell.mData; + oldcell->loadCell(esm, true); + + // handle moved ref (MVRF) subrecords + handleMovedCellRefs (esm, &cell); + // merge lists of leased references, use newer data in case of conflict - for (ESM::MovedCellRefTracker::const_iterator it = cell->mMovedRefs.begin(); it != cell->mMovedRefs.end(); ++it) { + for (ESM::MovedCellRefTracker::const_iterator it = cell.mMovedRefs.begin(); it != cell.mMovedRefs.end(); ++it) { // remove reference from current leased ref tracker and add it to new cell ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefNum); if (itold != oldcell->mMovedRefs.end()) { @@ -82,13 +89,21 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) *itold = *it; } } - cell->mMovedRefs = oldcell->mMovedRefs; - // have new cell replace old cell - ESM::Cell::merge(oldcell, cell); + + // We don't need to merge mLeasedRefs of cell / oldcell. This list is filled when another cell moves a + // reference to this cell, so the list for the new cell should be empty. The list for oldcell, + // however, could have leased refs in it and so should be kept. } else - mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell; + { + // spawn a new cell + cell.loadCell(esm, true); + + // handle moved ref (MVRF) subrecords + handleMovedCellRefs (esm, &cell); + + mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; + } } - delete cell; } } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 1dfb2f9766..a04267f499 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -591,6 +591,8 @@ namespace MWWorld return search(cell.mName); } + void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell); + public: ESMStore *mEsmStore; diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 0830c5de69..83864569fb 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -50,18 +50,14 @@ namespace ESM return ref.mRefNum == refNum; } - void Cell::load(ESMReader &esm, bool saveContext) { - // Ignore this for now, it might mean we should delete the entire - // cell? - // TODO: treat the special case "another plugin moved this ref, but we want to delete it"! - if (esm.isNextSub("DELE")) { - esm.skipHSub(); - } - - esm.getHNT(mData, "DATA", 12); + loadData(esm); + loadCell(esm, saveContext); +} +void Cell::loadCell(ESMReader &esm, bool saveContext) +{ mNAM0 = 0; if (mData.mFlags & Interior) @@ -73,12 +69,10 @@ void Cell::load(ESMReader &esm, bool saveContext) esm.getHT(waterl); mWater = (float) waterl; mWaterInt = true; - mHasWaterLevelRecord = true; } else if (esm.isNextSub("WHGT")) { esm.getHT(mWater); - mHasWaterLevelRecord = true; } // Quasi-exterior cells have a region (which determines the @@ -107,6 +101,18 @@ void Cell::load(ESMReader &esm, bool saveContext) } } +void Cell::loadData(ESMReader &esm) +{ + // Ignore this for now, it might mean we should delete the entire + // cell? + // TODO: treat the special case "another plugin moved this ref, but we want to delete it"! + if (esm.isNextSub("DELE")) { + esm.skipHSub(); + } + + esm.getHNT(mData, "DATA", 12); +} + void Cell::preLoad(ESMReader &esm) //Can't be "load" because it conflicts with function in esmtool { this->load(esm, false); @@ -124,14 +130,12 @@ void Cell::save(ESMWriter &esm) const esm.writeHNT("DATA", mData, 12); if (mData.mFlags & Interior) { - if (mHasWaterLevelRecord) { - if (mWaterInt) { - int water = - (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); - esm.writeHNT("INTV", water); - } else { - esm.writeHNT("WHGT", mWater); - } + if (mWaterInt) { + int water = + (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); + esm.writeHNT("INTV", water); + } else { + esm.writeHNT("WHGT", mWater); } if (mData.mFlags & QuasiEx) @@ -228,19 +232,6 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) mAmbi.mFogDensity = 0; } - void Cell::merge(Cell *original, Cell *modified) - { - float waterLevel = original->mWater; - if (modified->mHasWaterLevelRecord) - { - waterLevel = modified->mWater; - } - // else: keep original water level, instead of resetting to 0 - - *original = *modified; - original->mWater = waterLevel; - } - CellId Cell::getCellId() const { CellId id; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 28204c9ee1..fb4b6b28ac 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -78,10 +78,7 @@ struct Cell float mFogDensity; }; - Cell() : mWater(0), mHasWaterLevelRecord(false) {} - - /// Merge \a modified into \a original - static void merge (Cell* original, Cell* modified); + Cell() : mWater(0) {} // Interior cells are indexed by this (it's the 'id'), for exterior // cells it is optional. @@ -93,8 +90,8 @@ struct Cell std::vector mContextList; // File position; multiple positions for multiple plugin support DATAstruct mData; AMBIstruct mAmbi; + float mWater; // Water level - bool mHasWaterLevelRecord; bool mWaterInt; int mMapColor; int mNAM0; @@ -109,7 +106,10 @@ struct Cell // This method is left in for compatibility with esmtool. Parsing moved references currently requires // passing ESMStore, bit it does not know about this parameter, so we do it this way. - void load(ESMReader &esm, bool saveContext = true); + void load(ESMReader &esm, bool saveContext = true); // Load everything (except references) + void loadData(ESMReader &esm); // Load DATAstruct only + void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except DATAstruct and references + void save(ESMWriter &esm) const; bool isExterior() const From d60df66811a53e130fc03128c64954c1bee533aa Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jun 2014 16:17:48 +0200 Subject: [PATCH 046/226] Change openmw.log to boost ofstream to fix unicode path on windows (see https://github.com/OpenMW/openmw/pull/108) --- apps/openmw/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 338eb2a7c9..503ccaf250 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -10,6 +10,7 @@ #include #include +#include #if defined(_WIN32) // For OutputDebugString @@ -309,7 +310,8 @@ int main(int argc, char**argv) std::cerr.rdbuf (&sb); #else // Redirect cout and cerr to openmw.log - std::ofstream logfile (std::string(cfgMgr.getLogPath().string() + "/openmw.log").c_str()); + boost::filesystem::ofstream logfile (boost::filesystem::path( + cfgMgr.getLogPath() / "/openmw.log")); boost::iostreams::stream_buffer coutsb; std::ostream oldcout(cout_rdbuf); From d970cc06d72bde012ffbcf315424c4e94d7cea1a Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jun 2014 18:10:58 +0200 Subject: [PATCH 047/226] Don't play the same music track twice in a row (Fixes #746) --- apps/openmw/mwsound/soundmanagerimp.cpp | 28 +++++++++++++++++++------ apps/openmw/mwsound/soundmanagerimp.hpp | 4 ++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 4a3093b10d..3f85059ce1 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -179,6 +179,7 @@ namespace MWSound if(!mOutput->isInitialized()) return; std::cout <<"Playing "<begin(), resourcesInThisGroup->end()); + Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); + for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) + { + Ogre::StringVectorPtr resourcesInThisGroup = mResourceMgr.findResourceNames(*it, + "Music/"+mCurrentPlaylist+"/*"); + filelist.insert(filelist.end(), resourcesInThisGroup->begin(), resourcesInThisGroup->end()); + } + mMusicFiles[mCurrentPlaylist] = filelist; } + else + filelist = mMusicFiles[mCurrentPlaylist]; if(!filelist.size()) return; int i = rand()%filelist.size(); + + // Don't play the same music track twice in a row + if (filelist[i] == mLastPlayedMusic) + { + if (i-1 == int(filelist.size())) + i = 0; + else + ++i; + } + streamMusicFull(filelist[i]); } diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index ab9dcf7345..558b6966af 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -31,6 +31,10 @@ namespace MWSound std::auto_ptr mOutput; + // Caches available music tracks by + std::map mMusicFiles; + std::string mLastPlayedMusic; // The music file that was last played + float mMasterVolume; float mSFXVolume; float mMusicVolume; From 013916fca3c92841e8aad016e5c77b639ebed9d4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jun 2014 18:59:51 +0200 Subject: [PATCH 048/226] Fix for broken physics in exteriors (Fixes #1446) The wrong function was being used to check the distance between old and new positions. It took the length of the vectors into account, which makes no sense for positions. The issue was only observed in exteriors, since most interiors are relatively close to the origin. --- apps/openmw/mwworld/physicssystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 0f6fdec197..73a704c88e 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -304,7 +304,7 @@ namespace MWWorld continue; // velocity updated, calculate nextpos again } - if(!newPosition.positionCloses(nextpos, 0.00000001)) + if(newPosition.squaredDistance(nextpos) > 0.00000001*0.00000001) { // trace to where character would go if there were no obstructions tracer.doTrace(colobj, newPosition, nextpos, engine); From 41ab7329a8fac6b579addb11d69bc3483d104ec0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jun 2014 19:47:42 +0200 Subject: [PATCH 049/226] Store keybindings as keycode, not keyname. Also use SDL_GetKeyName instead of a manually created map. Fixes #1202 Note: breaks compatibility with input.xml, so the filename was changed. --- apps/openmw/engine.cpp | 2 +- extern/oics/ICSInputControlSystem.cpp | 127 +----------------- extern/oics/ICSInputControlSystem.h | 4 - .../oics/ICSInputControlSystem_keyboard.cpp | 2 +- 4 files changed, 6 insertions(+), 129 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 3647f8ccb0..bda903d561 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -357,7 +357,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so - std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input.xml").string(); + std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input_v1.xml").string(); bool keybinderUserExists = boost::filesystem::exists(keybinderUser); MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists, mGrab); mEnvironment.setInputManager (input); diff --git a/extern/oics/ICSInputControlSystem.cpp b/extern/oics/ICSInputControlSystem.cpp index a053bbab4e..34d16ed2bd 100644 --- a/extern/oics/ICSInputControlSystem.cpp +++ b/extern/oics/ICSInputControlSystem.cpp @@ -41,8 +41,6 @@ namespace ICS this->mActive = active; - this->fillSDLKeysMap(); - ICS_LOG("Channel count = " + ToString(channelCount) ); for(size_t i=0;i( getKeyBinding(*o, Control/*::ControlChangingDirection*/::INCREASE)).c_str() ); keyBinder.SetAttribute( "direction", "INCREASE" ); control.InsertEndChild(keyBinder); @@ -443,7 +438,7 @@ namespace ICS { TiXmlElement keyBinder( "KeyBinder" ); - keyBinder.SetAttribute( "key", keyCodeToString( + keyBinder.SetAttribute( "key", ToString( getKeyBinding(*o, Control/*::ControlChangingDirection*/::DECREASE)).c_str() ); keyBinder.SetAttribute( "direction", "DECREASE" ); control.InsertEndChild(keyBinder); @@ -806,128 +801,14 @@ namespace ICS mDetectingBindingControl = NULL; } - void InputControlSystem::fillSDLKeysMap() - { - mKeys["UNASSIGNED"]= SDLK_UNKNOWN; - mKeys["ESCAPE"]= SDLK_ESCAPE; - mKeys["1"]= SDLK_1; - mKeys["2"]= SDLK_2; - mKeys["3"]= SDLK_3; - mKeys["4"]= SDLK_4; - mKeys["5"]= SDLK_5; - mKeys["6"]= SDLK_6; - mKeys["7"]= SDLK_7; - mKeys["8"]= SDLK_8; - mKeys["9"]= SDLK_9; - mKeys["0"]= SDLK_0; - mKeys["MINUS"]= SDLK_MINUS; - mKeys["EQUALS"]= SDLK_EQUALS; - mKeys["BACK"]= SDLK_BACKSPACE; - mKeys["TAB"]= SDLK_TAB; - mKeys["Q"]= SDLK_q; - mKeys["W"]= SDLK_w; - mKeys["E"]= SDLK_e; - mKeys["R"]= SDLK_r; - mKeys["T"]= SDLK_t; - mKeys["Y"]= SDLK_y; - mKeys["U"]= SDLK_u; - mKeys["I"]= SDLK_i; - mKeys["O"]= SDLK_o; - mKeys["P"]= SDLK_p; - mKeys["LBRACKET"]= SDLK_LEFTBRACKET; - mKeys["RBRACKET"]= SDLK_RIGHTBRACKET; - mKeys["RETURN"]= SDLK_RETURN; - mKeys["LCONTROL"]= SDLK_LCTRL; - mKeys["A"]= SDLK_a; - mKeys["S"]= SDLK_s; - mKeys["D"]= SDLK_d; - mKeys["F"]= SDLK_f; - mKeys["G"]= SDLK_g; - mKeys["H"]= SDLK_h; - mKeys["J"]= SDLK_j; - mKeys["K"]= SDLK_k; - mKeys["L"]= SDLK_l; - mKeys["SEMICOLON"]= SDLK_SEMICOLON; - mKeys["APOSTROPHE"]= SDLK_QUOTE; - mKeys["GRAVE"]= SDLK_BACKQUOTE; - mKeys["LSHIFT"]= SDLK_LSHIFT; - mKeys["BACKSLASH"]= SDLK_BACKSLASH; - mKeys["Z"]= SDLK_z; - mKeys["X"]= SDLK_x; - mKeys["C"]= SDLK_c; - mKeys["V"]= SDLK_v; - mKeys["B"]= SDLK_b; - mKeys["N"]= SDLK_n; - mKeys["M"]= SDLK_m; - mKeys["COMMA"]= SDLK_COMMA; - mKeys["PERIOD"]= SDLK_PERIOD; - mKeys["SLASH"]= SDLK_SLASH; - mKeys["RSHIFT"]= SDLK_RSHIFT; - mKeys["MULTIPLY"]= SDLK_ASTERISK; - mKeys["LMENU"]= SDLK_LALT; - mKeys["SPACE"]= SDLK_SPACE; - mKeys["CAPITAL"]= SDLK_CAPSLOCK; - mKeys["F1"]= SDLK_F1; - mKeys["F2"]= SDLK_F2; - mKeys["F3"]= SDLK_F3; - mKeys["F4"]= SDLK_F4; - mKeys["F5"]= SDLK_F5; - mKeys["F6"]= SDLK_F6; - mKeys["F7"]= SDLK_F7; - mKeys["F8"]= SDLK_F8; - mKeys["F9"]= SDLK_F9; - mKeys["F10"]= SDLK_F10; - mKeys["F11"]= SDLK_F11; - mKeys["F12"]= SDLK_F12; - mKeys["NUMLOCK"]= SDLK_NUMLOCKCLEAR; - mKeys["SCROLL"]= SDLK_SCROLLLOCK; - mKeys["NUMPAD7"]= SDLK_KP_7; - mKeys["NUMPAD8"]= SDLK_KP_8; - mKeys["NUMPAD9"]= SDLK_KP_9; - mKeys["SUBTRACT"]= SDLK_KP_MINUS; - mKeys["NUMPAD4"]= SDLK_KP_4; - mKeys["NUMPAD5"]= SDLK_KP_5; - mKeys["NUMPAD6"]= SDLK_KP_6; - mKeys["ADD"]= SDLK_KP_PLUS; - mKeys["NUMPAD1"]= SDLK_KP_1; - mKeys["NUMPAD2"]= SDLK_KP_2; - mKeys["NUMPAD3"]= SDLK_KP_3; - mKeys["NUMPAD0"]= SDLK_KP_0; - mKeys["DECIMAL"]= SDLK_KP_DECIMAL; - mKeys["RCONTROL"]= SDLK_RCTRL; - mKeys["DIVIDE"]= SDLK_SLASH; - mKeys["SYSRQ"]= SDLK_SYSREQ; - mKeys["PRNTSCRN"] = SDLK_PRINTSCREEN; - mKeys["RMENU"]= SDLK_RALT; - mKeys["PAUSE"]= SDLK_PAUSE; - mKeys["HOME"]= SDLK_HOME; - mKeys["UP"]= SDLK_UP; - mKeys["PGUP"]= SDLK_PAGEUP; - mKeys["LEFT"]= SDLK_LEFT; - mKeys["RIGHT"]= SDLK_RIGHT; - mKeys["END"]= SDLK_END; - mKeys["DOWN"]= SDLK_DOWN; - mKeys["PGDOWN"]= SDLK_PAGEDOWN; - mKeys["INSERT"]= SDLK_INSERT; - mKeys["DELETE"]= SDLK_DELETE; - - mKeys["NUMPADENTER"]= SDLK_KP_ENTER; - - for(std::map::iterator it = mKeys.begin() - ; it != mKeys.end() ; it++) - { - mKeyCodes[ it->second ] = it->first; - } - } - std::string InputControlSystem::keyCodeToString(SDL_Keycode key) { - return mKeyCodes[key]; + return std::string(SDL_GetKeyName(key)); } SDL_Keycode InputControlSystem::stringToKeyCode(std::string key) { - return mKeys[key]; + return SDL_GetKeyFromName(key.c_str()); } void InputControlSystem::adjustMouseRegion(Uint16 width, Uint16 height) diff --git a/extern/oics/ICSInputControlSystem.h b/extern/oics/ICSInputControlSystem.h index 4aaecdea9f..1e9c78e602 100644 --- a/extern/oics/ICSInputControlSystem.h +++ b/extern/oics/ICSInputControlSystem.h @@ -208,8 +208,6 @@ namespace ICS std::vector mChannels; ControlsKeyBinderMapType mControlsKeyBinderMap; - std::map mKeys; - std::map mKeyCodes; bool mActive; InputControlSystemLog* mLog; @@ -227,8 +225,6 @@ namespace ICS private: - void fillSDLKeysMap(); - Uint16 mClientWidth; Uint16 mClientHeight; }; diff --git a/extern/oics/ICSInputControlSystem_keyboard.cpp b/extern/oics/ICSInputControlSystem_keyboard.cpp index 0a9a34d63c..8e7415b5b1 100644 --- a/extern/oics/ICSInputControlSystem_keyboard.cpp +++ b/extern/oics/ICSInputControlSystem_keyboard.cpp @@ -43,7 +43,7 @@ namespace ICS dir = Control::DECREASE; } - addKeyBinding(mControls.back(), mKeys[xmlKeyBinder->Attribute("key")], dir); + addKeyBinding(mControls.back(), FromString(xmlKeyBinder->Attribute("key")), dir); xmlKeyBinder = xmlKeyBinder->NextSiblingElement("KeyBinder"); } From 68d6b6b2f3eeea910e6490a45d826db7fd43e8c3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jun 2014 21:56:10 +0200 Subject: [PATCH 050/226] Cell merge fix (reference context position was not saved correctly) --- apps/openmw/mwworld/store.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 19b204073e..fdeb290e5f 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -70,13 +70,15 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) ESM::Cell *oldcell = const_cast(search(cell.getGridX(), cell.getGridY())); if (oldcell) { // merge new cell into old cell - // push the new references on the list of references to manage (saveContext = true) oldcell->mData = cell.mData; - oldcell->loadCell(esm, true); + oldcell->loadCell(esm, false); // handle moved ref (MVRF) subrecords handleMovedCellRefs (esm, &cell); + // push the new references on the list of references to manage + oldcell->postLoad(esm); + // merge lists of leased references, use newer data in case of conflict for (ESM::MovedCellRefTracker::const_iterator it = cell.mMovedRefs.begin(); it != cell.mMovedRefs.end(); ++it) { // remove reference from current leased ref tracker and add it to new cell @@ -96,11 +98,14 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) } else { // spawn a new cell - cell.loadCell(esm, true); + cell.loadCell(esm, false); // handle moved ref (MVRF) subrecords handleMovedCellRefs (esm, &cell); + // push the new references on the list of references to manage + cell.postLoad(esm); + mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; } } From 37f0c253f6a922fdb202b04173b062d0aaa9a105 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 12 Jun 2014 07:04:57 +1000 Subject: [PATCH 051/226] For compiling with MSVC 2012 --- apps/openmw/mwstate/charactermanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index 91d728ae07..70e9f09258 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -3,6 +3,7 @@ #include #include +#include // std::isalnum #include From 029e438c110808f8ee3105af9a664db8c6289397 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Jun 2014 23:57:39 +0200 Subject: [PATCH 052/226] Don't check mInterpolationType each iteration --- components/nif/niffile.hpp | 51 ++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index daec80ea1c..fbb64e4f74 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -177,42 +177,49 @@ struct KeyListT { KeyT key; NIFStream &nifReference = *nif; - for(size_t i = 0;i < count;i++) + + if(mInterpolationType == sLinearInterpolation) { - if(mInterpolationType == sLinearInterpolation) + for(size_t i = 0;i < count;i++) { readTimeAndValue(nifReference, key); mKeys.push_back(key); } - else if(mInterpolationType == sQuadraticInterpolation) + } + else if(mInterpolationType == sQuadraticInterpolation) + { + for(size_t i = 0;i < count;i++) { readQuadratic(nifReference, key); mKeys.push_back(key); } - else if(mInterpolationType == sTBCInterpolation) + } + else if(mInterpolationType == sTBCInterpolation) + { + for(size_t i = 0;i < count;i++) { readTBC(nifReference, key); mKeys.push_back(key); } - //XYZ keys aren't actually read here. - //data.hpp sees that the last type read was sXYZInterpolation and: - // Eats a floating point number, then - // Re-runs the read function 3 more times, with force enabled so that the previous values aren't cleared. - // When it does that it's reading in a bunch of sLinearInterpolation keys, not sXYZInterpolation. - else if(mInterpolationType == sXYZInterpolation) - { - //Don't try to read XYZ keys into the wrong part - if ( count != 1 ) - nif->file->fail("XYZ_ROTATION_KEY count should always be '1' . Retrieved Value: "+Ogre::StringConverter::toString(count)); - } - else if (0 == mInterpolationType) - { - if (count != 0) - nif->file->fail("Interpolation type 0 doesn't work with keys"); - } - else - nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); } + //XYZ keys aren't actually read here. + //data.hpp sees that the last type read was sXYZInterpolation and: + // Eats a floating point number, then + // Re-runs the read function 3 more times, with force enabled so that the previous values aren't cleared. + // When it does that it's reading in a bunch of sLinearInterpolation keys, not sXYZInterpolation. + else if(mInterpolationType == sXYZInterpolation) + { + //Don't try to read XYZ keys into the wrong part + if ( count != 1 ) + nif->file->fail("XYZ_ROTATION_KEY count should always be '1' . Retrieved Value: "+Ogre::StringConverter::toString(count)); + } + else if (0 == mInterpolationType) + { + if (count != 0) + nif->file->fail("Interpolation type 0 doesn't work with keys"); + } + else + nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); } private: From df7213185fd90ce93e69a33a0ce63e34e80af343 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Thu, 12 Jun 2014 23:42:33 +0400 Subject: [PATCH 053/226] warning fixes --- apps/openmw/mwmechanics/aicombat.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 4a3c724d59..94bb2cd90b 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -167,15 +167,18 @@ namespace MWMechanics const MWWorld::Class& actorClass = actor.getClass(); MWBase::World* world = MWBase::Environment::get().getWorld(); - if (!actorClass.isNpc() && target == world->getPlayerPtr() && - (actorClass.canSwim(actor) && !actor.getClass().canWalk(actor) // 1. pure water creature and Player moved out of water - && !world->isSwimming(target)) - || (!actorClass.canSwim(actor) && world->isSwimming(target))) // 2. creature can't swim to Player + if (!actorClass.isNpc() && + // 1. pure water creature and Player moved out of water + ((target == world->getPlayerPtr() && + actorClass.canSwim(actor) && !actor.getClass().canWalk(actor) && !world->isSwimming(target)) + // 2. creature can't swim to target + || (!actorClass.canSwim(actor) && world->isSwimming(target)))) { - actorClass.getCreatureStats(actor).setHostile(false); + if (target == world->getPlayerPtr()) + actorClass.getCreatureStats(actor).setHostile(false); actorClass.getCreatureStats(actor).setAttackingOrSpell(false); return true; - } + } //Update every frame if(mCombatMove) @@ -246,7 +249,7 @@ namespace MWMechanics const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype; - float weapRange, weapSpeed = 1.0f; + float weapRange; actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); @@ -275,7 +278,6 @@ namespace MWMechanics // All other WeapTypes are actually weapons, so get is safe. weapon = weaponSlot->get()->mBase; weapRange = weapon->mData.mReach; - weapSpeed = weapon->mData.mSpeed; } weapRange *= 100.0f; } From b158919c4bfecf53e2a6f6adab04913e9d828c3e Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 12 Jun 2014 15:11:52 +0200 Subject: [PATCH 054/226] Fix uninitialized variable use --- components/nif/niffile.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index fbb64e4f74..a9e2c6fe71 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -165,6 +165,9 @@ struct KeyListT { void read(NIFStream *nif, bool force=false) { assert(nif); + + mInterpolationType = 0; + size_t count = nif->getUInt(); if(count == 0 && !force) return; From 604d5ac00089a79e00541ab5a87cb4caec26619f Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 12 Jun 2014 21:46:23 +0200 Subject: [PATCH 055/226] Fix console reference label not resetting when loading save --- apps/openmw/mwgui/console.cpp | 6 ++++++ apps/openmw/mwgui/console.hpp | 2 ++ apps/openmw/mwgui/referenceinterface.hpp | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 2ae4d6ed1c..9f67524ae2 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -435,4 +435,10 @@ namespace MWGui { setSelectedObject(MWWorld::Ptr()); } + + void Console::resetReference() + { + ReferenceInterface::resetReference(); + setSelectedObject(MWWorld::Ptr()); + } } diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index ec699b317c..90da740c20 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -66,6 +66,8 @@ namespace MWGui void executeFile (const std::string& path); + virtual void resetReference (); + protected: virtual void onReferenceUnavailable(); diff --git a/apps/openmw/mwgui/referenceinterface.hpp b/apps/openmw/mwgui/referenceinterface.hpp index df53a42b70..0ba180de86 100644 --- a/apps/openmw/mwgui/referenceinterface.hpp +++ b/apps/openmw/mwgui/referenceinterface.hpp @@ -17,7 +17,7 @@ namespace MWGui void checkReferenceAvailable(); ///< closes the window, if the MW-reference has become unavailable - void resetReference() { mPtr = MWWorld::Ptr(); mCurrentPlayerCell = NULL; } + virtual void resetReference() { mPtr = MWWorld::Ptr(); mCurrentPlayerCell = NULL; } protected: virtual void onReferenceUnavailable() = 0; ///< called when reference has become unavailable From 91ed5183c8efbe52982da3938c00f771c0986b41 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 12 Jun 2014 21:49:18 +0200 Subject: [PATCH 056/226] Show base item value in tooltip, not real value (Fixes #1469) --- apps/openmw/mwclass/apparatus.cpp | 2 +- apps/openmw/mwclass/armor.cpp | 2 +- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwclass/light.cpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 2 +- apps/openmw/mwclass/potion.cpp | 2 +- apps/openmw/mwclass/probe.cpp | 2 +- apps/openmw/mwclass/repair.cpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 947a9cb948..d61ba038a3 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -124,7 +124,7 @@ namespace MWClass std::string text; text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 825b14978f..a7a4593826 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -244,7 +244,7 @@ namespace MWClass + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight) + " (" + typeText + ")"; - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 0cc2e60207..0adee57e31 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -136,7 +136,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index c0362188b8..dc98e323e1 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -190,7 +190,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 60c0efeb86..6830929239 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -144,7 +144,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index ef4549268f..8a2c20f699 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -184,7 +184,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 19381a3fd5..5d91db9e23 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -138,7 +138,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 7440617c25..d7cb0cd9bb 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -127,7 +127,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 5d076a3c5e..5db0d7a798 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -137,7 +137,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 9b528a4fce..f8b72be373 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -141,7 +141,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 26618c021c..98b02b9142 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -343,7 +343,7 @@ namespace MWClass } text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); info.enchant = ref->mBase->mEnchant; From be6f1fe4fe1e9a536b490b1751ae98c4690328a3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 12 Jun 2014 23:12:48 +0200 Subject: [PATCH 057/226] Fix a sign error --- apps/openmw/mwsound/soundmanagerimp.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 3f85059ce1..35b8579d79 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -226,10 +226,7 @@ namespace MWSound // Don't play the same music track twice in a row if (filelist[i] == mLastPlayedMusic) { - if (i-1 == int(filelist.size())) - i = 0; - else - ++i; + i = (i+1) % filelist.size(); } streamMusicFull(filelist[i]); From a54ac579a554d95823d3f8153b74c9600b800034 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 12 Jun 2014 23:27:04 +0200 Subject: [PATCH 058/226] Savegame: Store AiSequence --- apps/openmw/mwmechanics/aiactivate.cpp | 59 ++++-- apps/openmw/mwmechanics/aiactivate.hpp | 21 ++- apps/openmw/mwmechanics/aiavoiddoor.cpp | 8 +- apps/openmw/mwmechanics/aiavoiddoor.hpp | 2 + apps/openmw/mwmechanics/aicombat.cpp | 121 +++++-------- apps/openmw/mwmechanics/aicombat.hpp | 20 +- apps/openmw/mwmechanics/aiescort.cpp | 57 +++--- apps/openmw/mwmechanics/aiescort.hpp | 15 +- apps/openmw/mwmechanics/aifollow.cpp | 48 ++++- apps/openmw/mwmechanics/aifollow.hpp | 28 ++- apps/openmw/mwmechanics/aipackage.cpp | 2 - apps/openmw/mwmechanics/aipackage.hpp | 19 +- apps/openmw/mwmechanics/aipursue.cpp | 19 ++ apps/openmw/mwmechanics/aipursue.hpp | 12 ++ apps/openmw/mwmechanics/aisequence.cpp | 77 +++++++- apps/openmw/mwmechanics/aisequence.hpp | 11 ++ apps/openmw/mwmechanics/aitravel.cpp | 24 +++ apps/openmw/mwmechanics/aitravel.hpp | 16 +- apps/openmw/mwmechanics/aiwander.cpp | 113 ++++++++---- apps/openmw/mwmechanics/aiwander.hpp | 22 ++- apps/openmw/mwmechanics/creaturestats.cpp | 2 + apps/openmw/mwscript/aiextensions.cpp | 3 +- components/CMakeLists.txt | 1 + components/esm/aisequence.cpp | 211 ++++++++++++++++++++++ components/esm/aisequence.hpp | 154 ++++++++++++++++ components/esm/creaturestats.cpp | 2 + components/esm/creaturestats.hpp | 3 + 27 files changed, 868 insertions(+), 202 deletions(-) create mode 100644 components/esm/aisequence.cpp create mode 100644 components/esm/aisequence.hpp diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 3dfacb853d..1cdda24c00 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -1,5 +1,7 @@ #include "aiactivate.hpp" +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -19,26 +21,26 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const return new AiActivate(*this); } bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) -{ - ESM::Position pos = actor.getRefData().getPosition(); //position of the actor - const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow - - if(target == MWWorld::Ptr()) - return true; //Target doesn't exist - - //Set the target desition from the actor - ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 200) { //Stop when you get close - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); - target.getClass().activate(target,actor).get()->execute(actor); //Arrest player - return true; - } - else { - pathTo(actor, dest, duration); //Go to the destination - } - +{ + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow + + if(target == MWWorld::Ptr()) + return true; //Target doesn't exist + + //Set the target desition from the actor + ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; + + if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 200) { //Stop when you get close + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); + target.getClass().activate(target,actor).get()->execute(actor); //Arrest player + return true; + } + else { + pathTo(actor, dest, duration); //Go to the destination + } + return false; } @@ -46,3 +48,20 @@ int MWMechanics::AiActivate::getTypeId() const { return TypeIdActivate; } + +void MWMechanics::AiActivate::writeState(ESM::AiSequence::AiSequence &sequence) const +{ + std::auto_ptr activate(new ESM::AiSequence::AiActivate()); + activate->mTargetId = mObjectId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Activate; + package.mPackage = activate.release(); + sequence.mPackages.push_back(package); +} + +MWMechanics::AiActivate::AiActivate(const ESM::AiSequence::AiActivate *activate) + : mObjectId(activate->mTargetId) +{ + +} diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 0e660e967d..8003c2a361 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -6,20 +6,33 @@ #include "pathfinding.hpp" +namespace ESM +{ +namespace AiSequence +{ + struct AiActivate; +} +} + namespace MWMechanics -{ - /// \brief Causes actor to walk to activatable object and activate it +{ + /// \brief Causes actor to walk to activatable object and activate it /** Will activate when close to object **/ class AiActivate : public AiPackage { - public: - /// Constructor + public: + /// Constructor /** \param objectId Reference to object to activate **/ AiActivate(const std::string &objectId); + + AiActivate(const ESM::AiSequence::AiActivate* activate); + virtual AiActivate *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); virtual int getTypeId() const; + virtual void writeState(ESM::AiSequence::AiSequence& sequence) const; + private: std::string mObjectId; int mCellX; diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index ea6f296cc8..bab8bca280 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -78,8 +78,14 @@ MWMechanics::AiAvoidDoor *MWMechanics::AiAvoidDoor::clone() const return new AiAvoidDoor(*this); } - int MWMechanics::AiAvoidDoor::getTypeId() const +int MWMechanics::AiAvoidDoor::getTypeId() const { return TypeIdAvoidDoor; } +unsigned int MWMechanics::AiAvoidDoor::getPriority() const +{ + return 2; +} + + diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index d2a2e33a1f..2374fbc1be 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -24,6 +24,8 @@ namespace MWMechanics virtual int getTypeId() const; + virtual unsigned int getPriority() const; + private: float mDuration; MWWorld::Ptr mDoorPtr; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 9ec770c9d1..f4aae14d5e 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "../mwworld/class.hpp" #include "../mwworld/timestamp.hpp" @@ -81,23 +82,22 @@ namespace MWMechanics // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp AiCombat::AiCombat(const MWWorld::Ptr& actor) : - mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()), - mTimerAttack(0), - mTimerReact(0), - mTimerCombatMove(0), - mFollowTarget(false), - mReadyToAttack(false), - mAttack(false), - mCombatMove(false), - mMovement(), - mForceNoShortcut(false), - mShortcutFailPos(), - mBackOffDoor(false), - mCell(NULL), - mDoorIter(actor.getCell()->get().mList.end()), - mDoors(actor.getCell()->get()), - mDoorCheckDuration(0) + mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) { + init(); + } + + void AiCombat::init() + { + mTimerAttack = 0; + mTimerReact = 0; + mTimerCombatMove = 0; + mFollowTarget = false; + mReadyToAttack = false; + mAttack = false; + mCombatMove = false; + mForceNoShortcut = false; + mCell = NULL; } /* @@ -511,70 +511,22 @@ namespace MWMechanics // coded at 250ms or 1/4 second // // TODO: Add a parameter to vary DURATION_SAME_SPOT? - MWWorld::CellStore *cell = actor.getCell(); if((distToTarget > rangeAttack || 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 - 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(); - for (; mDoorIter != mDoors.mList.end(); ++mDoorIter) - { - MWWorld::LiveCellRef& ref = *mDoorIter; - float minSqr = 1.3*1.3*MIN_DIST_TO_DOOR_SQUARED; // for legibility - if(vActorPos.squaredDistance(Ogre::Vector3(ref.mRef.getPosition().pos)) < minSqr && - ref.mData.getLocalRotation().rot[2] < 0.4f) // even small opening - { - //std::cout<<"closed door id \""<getCell()->isExterior() && !mDoors.mList.empty()) - { - MWWorld::LiveCellRef& ref = *mDoorIter; - float minSqr = 1.6 * 1.6 * MIN_DIST_TO_DOOR_SQUARED; // for legibility - // TODO: add reaction to checking open doors - if(mBackOffDoor && - vActorPos.squaredDistance(Ogre::Vector3(ref.mRef.getPosition().pos)) < minSqr) - { - mMovement.mPosition[1] = -0.2; // back off, but slowly - } - else if(mBackOffDoor && - mDoorIter != mDoors.mList.end() && - ref.mData.getLocalRotation().rot[2] >= 1) - { - mDoorIter = mDoors.mList.end(); - mBackOffDoor = false; - //std::cout<<"open door id \""<mTargetActorId; + init(); + } + + void AiCombat::writeState(ESM::AiSequence::AiSequence &sequence) const + { + std::auto_ptr combat(new ESM::AiSequence::AiCombat()); + combat->mTargetActorId = mTargetActorId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Combat; + package.mPackage = combat.release(); + sequence.mPackages.push_back(package); + } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 4b728ff221..061a6a3d3c 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -12,6 +12,14 @@ #include "../mwbase/world.hpp" +namespace ESM +{ + namespace AiSequence + { + struct AiCombat; + } +} + namespace MWMechanics { /// \brief Causes the actor to fight another actor @@ -22,6 +30,10 @@ namespace MWMechanics /** \param actor Actor to fight **/ AiCombat(const MWWorld::Ptr& actor); + AiCombat (const ESM::AiSequence::AiCombat* combat); + + void init(); + virtual AiCombat *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); @@ -33,6 +45,8 @@ namespace MWMechanics ///Returns target ID MWWorld::Ptr getTarget() const; + virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + private: PathFinder mPathFinder; // controls duration of the actual strike @@ -46,7 +60,6 @@ namespace MWMechanics bool mReadyToAttack, mAttack; bool mFollowTarget; bool mCombatMove; - bool mBackOffDoor; bool mForceNoShortcut; ESM::Position mShortcutFailPos; @@ -57,11 +70,6 @@ namespace MWMechanics const MWWorld::CellStore* mCell; ObstacleCheck mObstacleCheck; - float mDoorCheckDuration; - // TODO: for some reason mDoors.searchViaHandle() returns - // null pointers, workaround by keeping an iterator - MWWorld::CellRefList::List::iterator mDoorIter; - MWWorld::CellRefList& mDoors; void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); }; diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 07bb9726ef..3f5724077f 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -1,12 +1,13 @@ #include "aiescort.hpp" +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/timestamp.hpp" #include "steering.hpp" #include "movement.hpp" @@ -20,7 +21,7 @@ namespace MWMechanics { AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) - : mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration) + : mActorId(actorId), mX(x), mY(y), mZ(z), mRemainingDuration(duration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { @@ -29,32 +30,29 @@ namespace MWMechanics // The CS Help File states that if a duration is given, the AI package will run for that long // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. if(mX != 0 || mY != 0 || mZ != 0) - mDuration = 0; - - else - { - MWWorld::TimeStamp startTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - mStartingSecond = ((startTime.getHour() - int(startTime.getHour())) * 100); - } + mRemainingDuration = 0; } AiEscort::AiEscort(const std::string &actorId, const std::string &cellId,int duration, float x, float y, float z) - : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration) + : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mRemainingDuration(duration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { mMaxDist = 470; // The CS Help File states that if a duration is given, the AI package will run for that long - // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. + // BUT if a location is given, it "trumps" the duration so it will simply escort to that location. if(mX != 0 || mY != 0 || mZ != 0) - mDuration = 0; + mRemainingDuration = 0; + } - else - { - MWWorld::TimeStamp startTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - mStartingSecond = ((startTime.getHour() - int(startTime.getHour())) * 100); - } + AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) + : mActorId(escort->mTargetId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) + , mCellId(escort->mCellId) + , mRemainingDuration(escort->mRemainingDuration) + { } @@ -67,11 +65,10 @@ namespace MWMechanics { // If AiEscort has ran for as long or longer then the duration specified // and the duration is not infinite, the package is complete. - if(mDuration != 0) + if(mRemainingDuration != 0) { - MWWorld::TimeStamp current = MWBase::Environment::get().getWorld()->getTimeStamp(); - unsigned int currentSecond = ((current.getHour() - int(current.getHour())) * 100); - if(currentSecond - mStartingSecond >= mDuration) + mRemainingDuration -= duration; + if (duration <= 0) return true; } @@ -89,7 +86,7 @@ namespace MWMechanics if(distanceBetweenResult <= mMaxDist * mMaxDist) { - if(pathTo(actor,ESM::Pathgrid::Point(mX,mY,mZ),duration)) //Returns true on path complete + if(pathTo(actor,ESM::Pathgrid::Point(mX,mY,mZ),duration)) //Returns true on path complete return true; mMaxDist = 470; } @@ -108,5 +105,21 @@ namespace MWMechanics { return TypeIdEscort; } + + void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const + { + std::auto_ptr escort(new ESM::AiSequence::AiEscort()); + escort->mData.mX = mX; + escort->mData.mY = mY; + escort->mData.mZ = mZ; + escort->mTargetId = mActorId; + escort->mRemainingDuration = mRemainingDuration; + escort->mCellId = mCellId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Escort; + package.mPackage = escort.release(); + sequence.mPackages.push_back(package); + } } diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 3771417fa2..820df969f1 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -6,6 +6,14 @@ #include "pathfinding.hpp" +namespace ESM +{ +namespace AiSequence +{ + struct AiEscort; +} +} + namespace MWMechanics { /// \brief AI Package to have an NPC lead the player to a specific point @@ -21,12 +29,16 @@ namespace MWMechanics \implement AiEscortCell **/ AiEscort(const std::string &actorId,const std::string &cellId,int duration, float x, float y, float z); + AiEscort(const ESM::AiSequence::AiEscort* escort); + virtual AiEscort *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); virtual int getTypeId() const; + void writeState(ESM::AiSequence::AiSequence &sequence) const; + private: std::string mActorId; std::string mCellId; @@ -34,8 +46,7 @@ namespace MWMechanics float mY; float mZ; float mMaxDist; - unsigned int mStartingSecond; - unsigned int mDuration; + float mRemainingDuration; // In seconds PathFinder mPathFinder; int mCellX; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f1296a9493..5ab7e17305 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -1,5 +1,9 @@ #include "aifollow.hpp" + #include + +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" @@ -12,16 +16,16 @@ #include "steering.hpp" MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), AiPackage() +: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId("") { } MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), AiPackage() +: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId) { } MWMechanics::AiFollow::AiFollow(const std::string &actorId) -: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId(""), AiPackage() +: mAlwaysFollow(true), mRemainingDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId("") { } @@ -35,8 +39,13 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) if(!mAlwaysFollow) //Update if you only follow for a bit { - if(mTotalTime > mDuration && mDuration != 0) //Check if we've run out of time - return true; + //Check if we've run out of time + if (mRemainingDuration != 0) + { + mRemainingDuration -= duration; + if (duration <= 0) + return true; + } if((pos.pos[0]-mX)*(pos.pos[0]-mX) + (pos.pos[1]-mY)*(pos.pos[1]-mY) + @@ -55,7 +64,7 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) } } - //Set the target desition from the actor + //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) //Stop when you get close @@ -83,7 +92,32 @@ MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const return new AiFollow(*this); } - int MWMechanics::AiFollow::getTypeId() const +int MWMechanics::AiFollow::getTypeId() const { return TypeIdFollow; } + +void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const +{ + std::auto_ptr follow(new ESM::AiSequence::AiFollow()); + follow->mData.mX = mX; + follow->mData.mY = mY; + follow->mData.mZ = mZ; + follow->mTargetId = mActorId; + follow->mRemainingDuration = mRemainingDuration; + follow->mCellId = mCellId; + follow->mAlwaysFollow = mAlwaysFollow; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Follow; + package.mPackage = follow.release(); + sequence.mPackages.push_back(package); +} + +MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) + : mAlwaysFollow(follow->mAlwaysFollow), mRemainingDuration(follow->mRemainingDuration) + , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) + , mActorId(follow->mTargetId), mCellId(follow->mCellId) +{ + +} diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 10a381410a..e9587b36eb 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -6,13 +6,21 @@ #include "pathfinding.hpp" #include +namespace ESM +{ +namespace AiSequence +{ + struct AiFollow; +} +} + namespace MWMechanics { /// \brief AiPackage for an actor to follow another actor/the PC /** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the actor to follow the other indefinitely - **/ - class AiFollow : public AiPackage - { + **/ + class AiFollow : public AiPackage + { public: /// Follow Actor for duration or until you arrive at a world position AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z); @@ -21,6 +29,8 @@ namespace MWMechanics /// Follow Actor indefinitively AiFollow(const std::string &ActorId); + AiFollow(const ESM::AiSequence::AiFollow* follow); + virtual AiFollow *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); @@ -30,16 +40,18 @@ namespace MWMechanics /// Returns the actor being followed std::string getFollowedActor(); + virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + private: /// This will make the actor always follow. /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ bool mAlwaysFollow; - float mDuration; + float mRemainingDuration; // Seconds float mX; float mY; float mZ; std::string mActorId; - std::string mCellId; - }; -} -#endif + std::string mCellId; + }; +} +#endif diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 2144aa11d9..7790942b2f 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -26,8 +26,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po //Update various Timers mTimer += duration; //Update timer mStuckTimer += duration; //Update stuck timer - mTotalTime += duration; //Update total time following - ESM::Position pos = actor.getRefData().getPosition(); //position of the actor diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 055958384a..983777c0ad 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -12,6 +12,14 @@ namespace MWWorld class Ptr; } +namespace ESM +{ + namespace AiSequence + { + class AiSequence; + } +} + namespace MWMechanics { /// \brief Base class for AI packages @@ -51,6 +59,8 @@ namespace MWMechanics /// Higher number is higher priority (0 being the lowest) virtual unsigned int getPriority() const {return 0;} + virtual void writeState (ESM::AiSequence::AiSequence& sequence) const {} + protected: /// Causes the actor to attempt to walk to the specified location /** \return If the actor has arrived at his destination **/ @@ -60,12 +70,11 @@ namespace MWMechanics ObstacleCheck mObstacleCheck; float mDoorCheckDuration; - float mTimer; - float mStuckTimer; - float mTotalTime; + float mTimer; + float mStuckTimer; + + MWWorld::Ptr mLastDoorChecked; //Used to ensure we don't try to CONSTANTLY open a door - MWWorld::Ptr mLastDoorChecked; //Used to ensure we don't try to CONSTANTLY open a door - ESM::Position mStuckPos; ESM::Pathgrid::Point mPrevDest; }; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index cd9d34e085..60f671c12a 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -1,5 +1,7 @@ #include "aipursue.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" @@ -18,6 +20,12 @@ AiPursue::AiPursue(const MWWorld::Ptr& actor) : mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) { } + +AiPursue::AiPursue(const ESM::AiSequence::AiPursue *pursue) + : mTargetActorId(pursue->mTargetActorId) +{ +} + AiPursue *MWMechanics::AiPursue::clone() const { return new AiPursue(*this); @@ -57,4 +65,15 @@ MWWorld::Ptr AiPursue::getTarget() const return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); } +void AiPursue::writeState(ESM::AiSequence::AiSequence &sequence) const +{ + std::auto_ptr pursue(new ESM::AiSequence::AiPursue()); + pursue->mTargetActorId = mTargetActorId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Pursue; + package.mPackage = pursue.release(); + sequence.mPackages.push_back(package); +} + } // namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 27affc9acd..18a22b6763 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -7,6 +7,14 @@ #include "pathfinding.hpp" +namespace ESM +{ +namespace AiSequence +{ + struct AiPursue; +} +} + namespace MWMechanics { /// \brief Makes the actor very closely follow the actor @@ -20,12 +28,16 @@ namespace MWMechanics /** \param actor Actor to pursue **/ AiPursue(const MWWorld::Ptr& actor); + AiPursue(const ESM::AiSequence::AiPursue* pursue); + virtual AiPursue *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); virtual int getTypeId() const; MWWorld::Ptr getTarget() const; + virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + private: int mTargetActorId; // The actor to pursue diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 8a6a69b278..2d4dc2732f 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -11,6 +11,8 @@ #include "aicombat.hpp" #include "aipursue.hpp" +#include + #include "../mwworld/class.hpp" #include "creaturestats.hpp" #include "npcstats.hpp" @@ -258,7 +260,8 @@ void AiSequence::fill(const ESM::AIPackageList &list) if (it->mType == ESM::AI_Wander) { ESM::AIWander data = it->mWander; - std::vector idles; + std::vector idles; + idles.reserve(8); for (int i=0; i<8; ++i) idles.push_back(data.mIdle[i]); package = new MWMechanics::AiWander(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat); @@ -287,4 +290,76 @@ void AiSequence::fill(const ESM::AIPackageList &list) } } +void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const +{ + for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) + { + (*iter)->writeState(sequence); + } +} + +void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) +{ + clear(); + + for (std::vector::const_iterator it = sequence.mPackages.begin(); + it != sequence.mPackages.end(); ++it) + { + switch (it->mType) + { + case ESM::AiSequence::Ai_Wander: + { + MWMechanics::AiWander* wander = new AiWander( + dynamic_cast(it->mPackage)); + mPackages.push_back(wander); + break; + } + case ESM::AiSequence::Ai_Travel: + { + MWMechanics::AiTravel* travel = new AiTravel( + dynamic_cast(it->mPackage)); + mPackages.push_back(travel); + break; + } + case ESM::AiSequence::Ai_Escort: + { + MWMechanics::AiEscort* escort = new AiEscort( + dynamic_cast(it->mPackage)); + mPackages.push_back(escort); + break; + } + case ESM::AiSequence::Ai_Follow: + { + MWMechanics::AiFollow* follow = new AiFollow( + dynamic_cast(it->mPackage)); + mPackages.push_back(follow); + break; + } + case ESM::AiSequence::Ai_Activate: + { + MWMechanics::AiActivate* activate = new AiActivate( + dynamic_cast(it->mPackage)); + mPackages.push_back(activate); + break; + } + case ESM::AiSequence::Ai_Combat: + { + MWMechanics::AiCombat* combat = new AiCombat( + dynamic_cast(it->mPackage)); + mPackages.push_back(combat); + break; + } + case ESM::AiSequence::Ai_Pursue: + { + MWMechanics::AiPursue* pursue = new AiPursue( + dynamic_cast(it->mPackage)); + mPackages.push_back(pursue); + break; + } + default: + break; + } + } +} + } // namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 41a280da80..b789d33cd0 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -10,6 +10,14 @@ namespace MWWorld class Ptr; } +namespace ESM +{ + namespace AiSequence + { + class AiSequence; + } +} + namespace MWMechanics { class AiPackage; @@ -90,6 +98,9 @@ namespace MWMechanics /** Typically used for loading from the ESM \see ESM::AIPackageList **/ void fill (const ESM::AIPackageList& list); + + void writeState (ESM::AiSequence::AiSequence& sequence) const; + void readState (const ESM::AiSequence::AiSequence& sequence); }; } diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 024656b38a..db137037d5 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,5 +1,7 @@ #include "aitravel.hpp" +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -19,6 +21,15 @@ namespace MWMechanics { } + AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) + : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ) + , mPathFinder() + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) + { + + } + AiTravel *MWMechanics::AiTravel::clone() const { return new AiTravel(*this); @@ -92,5 +103,18 @@ namespace MWMechanics { return TypeIdTravel; } + + void AiTravel::writeState(ESM::AiSequence::AiSequence &sequence) const + { + std::auto_ptr travel(new ESM::AiSequence::AiTravel()); + travel->mData.mX = mX; + travel->mData.mY = mY; + travel->mData.mZ = mZ; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Travel; + package.mPackage = travel.release(); + sequence.mPackages.push_back(package); + } } diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index ea7f1dc329..91ee302534 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -5,14 +5,26 @@ #include "pathfinding.hpp" +namespace ESM +{ +namespace AiSequence +{ + struct AiTravel; +} +} + namespace MWMechanics -{ +{ /// \brief Causes the AI to travel to the specified point class AiTravel : public AiPackage { - public: + public: /// Default constructor AiTravel(float x, float y, float z); + AiTravel(const ESM::AiSequence::AiTravel* travel); + + void writeState(ESM::AiSequence::AiSequence &sequence) const; + virtual AiTravel *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 1c870bda47..6a68397fde 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -21,31 +23,28 @@ namespace MWMechanics 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): + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) - , mCellX(std::numeric_limits::max()) - , mCellY(std::numeric_limits::max()) - , mXCell(0) - , mYCell(0) - , mCell(NULL) - , mStuckCount(0) // TODO: maybe no longer needed - , mDoorCheckDuration(0) - , mTrimCurrentNode(false) - , mReaction(0) - , mGreetDistanceMultiplier(0) - , mGreetDistanceReset(0) - , mChance(0) - , mRotate(false) - , mTargetAngle(0) - , mSaidGreeting(false) - , mHasReturnPosition(false) - , mReturnPosition(0,0,0) { - for(unsigned short counter = 0; counter < mIdle.size(); counter++) - { - if(mIdle[counter] >= 127 || mIdle[counter] < 0) - mIdle[counter] = 0; - } + init(); + } + + void AiWander::init() + { + mCellX = std::numeric_limits::max(); + mCellY = std::numeric_limits::max(); + mXCell = 0; + mYCell = 0; + mCell = NULL; + mStuckCount = 0;// TODO: maybe no longer needed + mDoorCheckDuration = 0; + mTrimCurrentNode = false; + mReaction = 0; + mRotate = false; + mTargetAngle = 0; + mSaidGreeting = false; + mHasReturnPosition = false; + mReturnPosition = Ogre::Vector3(0,0,0); if(mDistance < 0) mDistance = 0; @@ -56,15 +55,6 @@ namespace MWMechanics mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); mPlayedIdle = 0; - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - mIdleChanceMultiplier = - 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; @@ -219,11 +209,12 @@ namespace MWMechanics } mReaction += duration; - if(mReaction > 0.25f) // FIXME: hard coded constant + if(mReaction < 0.25f) // FIXME: hard coded constant { - mReaction = 0; return false; } + else + mReaction = 0; // NOTE: everything below get updated every 0.25 seconds @@ -394,7 +385,10 @@ namespace MWMechanics MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); // Don't bother if the player is out of hearing range - if (roll < mChance && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500) + static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() + .get().find("fVoiceIdleOdds")->getFloat(); + + if (roll < fVoiceIdleOdds && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500) MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); } } @@ -406,7 +400,10 @@ namespace MWMechanics // Play a random voice greeting if the player gets too close int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); float helloDistance = hello; - helloDistance *= mGreetDistanceMultiplier; + static int iGreetDistanceMultiplier =MWBase::Environment::get().getWorld()->getStore() + .get().find("iGreetDistanceMultiplier")->getInt(); + + helloDistance *= iGreetDistanceMultiplier; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); Ogre::Vector3 playerPos(player.getRefData().getPosition().pos); @@ -455,7 +452,10 @@ namespace MWMechanics } else { - if (playerDistSqr >= mGreetDistanceReset*mGreetDistanceReset * mGreetDistanceMultiplier*mGreetDistanceMultiplier) + static float fGreetDistanceReset = MWBase::Environment::get().getWorld()->getStore() + .get().find("fGreetDistanceReset")->getFloat(); + + if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset * iGreetDistanceMultiplier*iGreetDistanceMultiplier) mSaidGreeting = false; } @@ -632,8 +632,11 @@ namespace MWMechanics for(unsigned int counter = 0; counter < mIdle.size(); counter++) { - unsigned short idleChance = mIdleChanceMultiplier * mIdle[counter]; - unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / mIdleChanceMultiplier)); + static float fIdleChanceMultiplier = MWBase::Environment::get().getWorld()->getStore() + .get().find("fIdleChanceMultiplier")->getFloat(); + + unsigned short idleChance = fIdleChanceMultiplier * mIdle[counter]; + unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / fIdleChanceMultiplier)); if(randSelect < idleChance && randSelect > idleRoll) { mPlayedIdle = counter+2; @@ -641,5 +644,37 @@ namespace MWMechanics } } } + + void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const + { + std::auto_ptr wander(new ESM::AiSequence::AiWander()); + wander->mData.mDistance = mDistance; + wander->mData.mDuration = mDuration; + wander->mData.mTimeOfDay = mTimeOfDay; + wander->mStartTime = mStartTime.toEsm(); + assert (mIdle.size() >= 8); + for (int i=0; i<8; ++i) + wander->mData.mIdle[i] = mIdle[i]; + wander->mData.mShouldRepeat = mRepeat; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Wander; + package.mPackage = wander.release(); + sequence.mPackages.push_back(package); + } + + AiWander::AiWander (const ESM::AiSequence::AiWander* wander) + { + init(); + + mDistance = wander->mData.mDistance; + mDuration = wander->mData.mDuration; + mStartTime = MWWorld::TimeStamp(wander->mStartTime); + mTimeOfDay = wander->mData.mTimeOfDay; + for (int i=0; i<8; ++i) + mIdle.push_back(wander->mData.mIdle[i]); + + mRepeat = wander->mData.mShouldRepeat; + } } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 6481b2a01c..7abd19e27a 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -12,6 +12,14 @@ #include "../mwworld/timestamp.hpp" +namespace ESM +{ + namespace AiSequence + { + struct AiWander; + } +} + namespace MWMechanics { /// \brief Causes the Actor to wander within a specified range @@ -24,7 +32,11 @@ namespace MWMechanics \param timeOfDay Start time of the package, if it has a duration. Currently unimplemented \param idle Chances of each idle to play (9 in total) \param repeat Repeat wander or not **/ - AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); + AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); + + AiWander (const ESM::AiSequence::AiWander* wander); + + void init(); virtual AiPackage *clone() const; @@ -36,6 +48,8 @@ namespace MWMechanics /** In case another AI package moved the actor elsewhere **/ void setReturnPosition (const Ogre::Vector3& position); + virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + private: void stopWalking(const MWWorld::Ptr& actor); void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); @@ -45,13 +59,10 @@ namespace MWMechanics int mDistance; // how far the actor can wander from the spawn point int mDuration; int mTimeOfDay; - std::vector mIdle; + std::vector mIdle; bool mRepeat; bool mSaidGreeting; - int mGreetDistanceMultiplier; - float mGreetDistanceReset; - float mChance; bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, // if we had the actor in the AiWander constructor... @@ -74,7 +85,6 @@ namespace MWMechanics bool mMoveNow; bool mWalking; - float mIdleChanceMultiplier; unsigned short mPlayedIdle; MWWorld::TimeStamp mStartTime; diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 7fd26c25c9..7396b27355 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -503,6 +503,7 @@ namespace MWMechanics mSpells.writeState(state.mSpells); mActiveSpells.writeState(state.mActiveSpells); + mAiSequence.writeState(state.mAiSequence); } void CreatureStats::readState (const ESM::CreatureStats& state) @@ -543,6 +544,7 @@ namespace MWMechanics mSpells.readState(state.mSpells); mActiveSpells.readState(state.mActiveSpells); + mAiSequence.readState(state.mAiSequence); } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index cc17905df6..823d22a6ae 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -186,7 +186,7 @@ namespace MWScript Interpreter::Type_Integer time = runtime[0].mFloat; runtime.pop(); - std::vector idleList; + std::vector idleList; bool repeat = false; for(int i=1; i < 10 && arg0; ++i) @@ -194,6 +194,7 @@ namespace MWScript if(!repeat) repeat = true; Interpreter::Type_Integer idleValue = runtime[0].mInteger; + idleValue = std::min(255, std::max(0, idleValue)); idleList.push_back(idleValue); runtime.pop(); --arg0; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e0166138e4..060e3472d6 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -42,6 +42,7 @@ add_component_dir (esm loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate + aisequence ) add_component_dir (misc diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp new file mode 100644 index 0000000000..80440bdd3e --- /dev/null +++ b/components/esm/aisequence.cpp @@ -0,0 +1,211 @@ +#include "aisequence.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +#include "defs.hpp" + +#include + +namespace ESM +{ +namespace AiSequence +{ + + void AiWander::load(ESMReader &esm) + { + esm.getHNT (mData, "DATA"); + esm.getHNT(mStartTime, "STAR"); + } + + void AiWander::save(ESMWriter &esm) const + { + esm.writeHNT ("DATA", mData); + esm.writeHNT ("STAR", mStartTime); + } + + void AiTravel::load(ESMReader &esm) + { + esm.getHNT (mData, "DATA"); + } + + void AiTravel::save(ESMWriter &esm) const + { + esm.writeHNT ("DATA", mData); + } + + void AiEscort::load(ESMReader &esm) + { + esm.getHNT (mData, "DATA"); + mTargetId = esm.getHNString("TARG"); + esm.getHNT (mRemainingDuration, "DURA"); + mCellId = esm.getHNOString ("CELL"); + } + + void AiEscort::save(ESMWriter &esm) const + { + esm.writeHNT ("DATA", mData); + esm.writeHNString ("TARG", mTargetId); + esm.writeHNT ("DURA", mRemainingDuration); + if (!mCellId.empty()) + esm.writeHNString ("CELL", mCellId); + } + + void AiFollow::load(ESMReader &esm) + { + esm.getHNT (mData, "DATA"); + mTargetId = esm.getHNString("TARG"); + esm.getHNT (mRemainingDuration, "DURA"); + mCellId = esm.getHNOString ("CELL"); + esm.getHNT (mAlwaysFollow, "ALWY"); + } + + void AiFollow::save(ESMWriter &esm) const + { + esm.writeHNT ("DATA", mData); + esm.writeHNString("TARG", mTargetId); + esm.writeHNT ("DURA", mRemainingDuration); + if (!mCellId.empty()) + esm.writeHNString ("CELL", mCellId); + esm.writeHNT ("ALWY", mAlwaysFollow); + } + + void AiActivate::load(ESMReader &esm) + { + mTargetId = esm.getHNString("TARG"); + } + + void AiActivate::save(ESMWriter &esm) const + { + esm.writeHNString("TARG", mTargetId); + } + + void AiCombat::load(ESMReader &esm) + { + esm.getHNT (mTargetActorId, "TARG"); + } + + void AiCombat::save(ESMWriter &esm) const + { + esm.writeHNT ("TARG", mTargetActorId); + } + + void AiPursue::load(ESMReader &esm) + { + esm.getHNT (mTargetActorId, "TARG"); + } + + void AiPursue::save(ESMWriter &esm) const + { + esm.writeHNT ("TARG", mTargetActorId); + } + + AiSequence::~AiSequence() + { + for (std::vector::iterator it = mPackages.begin(); it != mPackages.end(); ++it) + delete it->mPackage; + } + + void AiSequence::save(ESMWriter &esm) const + { + for (std::vector::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + esm.writeHNT ("AIPK", it->mType); + switch (it->mType) + { + case Ai_Wander: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Travel: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Escort: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Follow: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Activate: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Combat: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Pursue: + static_cast(it->mPackage)->save(esm); + break; + + default: + break; + } + } + } + + void AiSequence::load(ESMReader &esm) + { + while (esm.isNextSub("AIPK")) + { + int type; + esm.getHT(type); + + mPackages.push_back(AiPackageContainer()); + mPackages.back().mType = type; + + switch (type) + { + case Ai_Wander: + { + std::auto_ptr ptr (new AiWander()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Travel: + { + std::auto_ptr ptr (new AiTravel()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Escort: + { + std::auto_ptr ptr (new AiEscort()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Follow: + { + std::auto_ptr ptr (new AiFollow()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Activate: + { + std::auto_ptr ptr (new AiActivate()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Combat: + { + std::auto_ptr ptr (new AiCombat()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Pursue: + { + std::auto_ptr ptr (new AiPursue()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + default: + return; + } + } + } +} +} diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp new file mode 100644 index 0000000000..bf0e17fa06 --- /dev/null +++ b/components/esm/aisequence.hpp @@ -0,0 +1,154 @@ +#ifndef OPENMW_COMPONENTS_ESM_AISEQUENCE_H +#define OPENMW_COMPONENTS_ESM_AISEQUENCE_H + +#include +#include + +#include "defs.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + namespace AiSequence + { + + // format 0, saved games only + // As opposed to AiPackageList, this stores the "live" version of AI packages. + + enum AiPackages + { + Ai_Wander = ESM::FourCC<'W','A','N','D'>::value, + Ai_Travel = ESM::FourCC<'T','R','A','V'>::value, + Ai_Escort = ESM::FourCC<'E','S','C','O'>::value, + Ai_Follow = ESM::FourCC<'F','O','L','L'>::value, + Ai_Activate = ESM::FourCC<'A','C','T','I'>::value, + Ai_Combat = ESM::FourCC<'C','O','M','B'>::value, + Ai_Pursue = ESM::FourCC<'P','U','R','S'>::value + }; + + + struct AiPackage + { + virtual ~AiPackage() {} + }; + + +#pragma pack(push,1) + struct AiWanderData + { + short mDistance; + short mDuration; + unsigned char mTimeOfDay; + unsigned char mIdle[8]; + unsigned char mShouldRepeat; + }; + struct AiTravelData + { + float mX, mY, mZ; + }; + struct AiEscortData + { + float mX, mY, mZ; + short mDuration; + }; + +#pragma pack(pop) + + struct AiWander : AiPackage + { + AiWanderData mData; + ESM::TimeStamp mStartTime; + + /// \todo add more AiWander state + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiTravel : AiPackage + { + AiTravelData mData; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiEscort : AiPackage + { + AiEscortData mData; + + std::string mTargetId; + std::string mCellId; + float mRemainingDuration; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiFollow : AiPackage + { + AiEscortData mData; + + std::string mTargetId; + std::string mCellId; + float mRemainingDuration; + + bool mAlwaysFollow; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiActivate : AiPackage + { + std::string mTargetId; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiCombat : AiPackage + { + int mTargetActorId; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiPursue : AiPackage + { + int mTargetActorId; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiPackageContainer + { + int mType; + + AiPackage* mPackage; + }; + + struct AiSequence + { + AiSequence() {} + ~AiSequence(); + + std::vector mPackages; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + + private: + AiSequence(const AiSequence&); + AiSequence& operator=(const AiSequence&); + }; + + } + +} + +#endif diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 3860e9351f..8151091b2d 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -79,6 +79,7 @@ void ESM::CreatureStats::load (ESMReader &esm) mSpells.load(esm); mActiveSpells.load(esm); + mAiSequence.load(esm); } void ESM::CreatureStats::save (ESMWriter &esm) const @@ -160,4 +161,5 @@ void ESM::CreatureStats::save (ESMWriter &esm) const mSpells.save(esm); mActiveSpells.save(esm); + mAiSequence.save(esm); } diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 5ca3d071fe..c8dc97403c 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -11,6 +11,7 @@ #include "spellstate.hpp" #include "activespells.hpp" +#include "aisequence.hpp" namespace ESM { @@ -23,6 +24,8 @@ namespace ESM StatState mAttributes[8]; StatState mDynamic[3]; + AiSequence::AiSequence mAiSequence; + ESM::TimeStamp mTradeTime; int mGoldPool; int mActorId; From 797134aa512db26665235475ed61cf11afdd8a2b Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jun 2014 00:01:29 +0200 Subject: [PATCH 059/226] Handle activation scripts in AiActivate (Fixes #1478) --- apps/openmw/engine.cpp | 19 +------------------ apps/openmw/mwbase/world.hpp | 2 ++ apps/openmw/mwmechanics/aiactivate.cpp | 5 +++-- apps/openmw/mwscript/interpretercontext.cpp | 7 +++---- apps/openmw/mwscript/interpretercontext.hpp | 2 +- apps/openmw/mwscript/miscextensions.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 20 ++++++++++++++++++++ apps/openmw/mwworld/worldimp.hpp | 2 ++ 8 files changed, 33 insertions(+), 26 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index bda903d561..46f384f594 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -489,24 +489,7 @@ void OMW::Engine::activate() if (ptr.getClass().getName(ptr) == "") // objects without name presented to user can never be activated return; - MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr); - - interpreterContext.activate (ptr); - - std::string script = ptr.getClass().getScript (ptr); - - MWBase::Environment::get().getWorld()->breakInvisibility(MWBase::Environment::get().getWorld()->getPlayerPtr()); - - if (!script.empty()) - { - MWBase::Environment::get().getWorld()->getLocalScripts().setIgnore (ptr); - MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); - } - - if (!interpreterContext.hasActivationBeenHandled()) - { - interpreterContext.executeActivation(ptr); - } + MWBase::Environment::get().getWorld()->activate(ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); } void OMW::Engine::screenshot() diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 81bec6fe86..9fb91398da 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -517,6 +517,8 @@ namespace MWBase virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0; + + virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; }; } diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 1cdda24c00..9e01c3fe72 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -6,7 +6,6 @@ #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/action.hpp" #include "../mwworld/cellstore.hpp" #include "steering.hpp" @@ -34,7 +33,9 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 200) { //Stop when you get close actor.getClass().getMovementSettings(actor).mPosition[1] = 0; MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); - target.getClass().activate(target,actor).get()->execute(actor); //Arrest player + + MWBase::Environment::get().getWorld()->activate(target, actor); + return true; } else { diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 6bf50371b8..ebe88c3a42 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -419,11 +419,10 @@ namespace MWScript mActivationHandled = false; } - void InterpreterContext::executeActivation(MWWorld::Ptr ptr) + void InterpreterContext::executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - boost::shared_ptr action = (ptr.getClass().activate(ptr, player)); - action->execute (player); + boost::shared_ptr action = (ptr.getClass().activate(ptr, actor)); + action->execute (actor); if (mActivated == ptr) mActivationHandled = true; } diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 1137efed31..fdf9d6424d 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -129,7 +129,7 @@ namespace MWScript ///< Store reference acted upon. The actual execution of the action does not /// take place here. - void executeActivation(MWWorld::Ptr ptr); + void executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor); ///< Execute the activation action for this ptr. If ptr is mActivated, mark activation as handled. void clearActivation(); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 9d6d5e50dc..cd76f3b9c2 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -121,7 +121,7 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); - context.executeActivation(ptr); + context.executeActivation(ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bf752734fb..371f9edfea 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -36,6 +36,8 @@ #include "../mwrender/sky.hpp" #include "../mwrender/animation.hpp" +#include "../mwscript/interpretercontext.hpp" + #include "../mwclass/door.hpp" #include "player.hpp" @@ -2759,4 +2761,22 @@ namespace MWWorld cast.inflict(apply->first, caster, effects, ESM::RT_Target, false, true); } } + + void World::activate(const Ptr &object, const Ptr &actor) + { + MWScript::InterpreterContext interpreterContext (&object.getRefData().getLocals(), object); + interpreterContext.activate (object); + + std::string script = object.getClass().getScript (object); + + breakInvisibility(actor); + + if (!script.empty()) + { + getLocalScripts().setIgnore (object); + MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); + } + if (!interpreterContext.hasActivationBeenHandled()) + interpreterContext.executeActivation(object, actor); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 0a396ef5c6..5b493efcc8 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -586,6 +586,8 @@ namespace MWWorld virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName); + + virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor); }; } From e458cf1df2a2e6adbbfeff565d05b31fc869facf Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jun 2014 01:24:58 +0200 Subject: [PATCH 060/226] Savegame: Store death counter (Fixes #1477) --- apps/openmw/mwbase/mechanicsmanager.hpp | 17 ++++++++++ apps/openmw/mwmechanics/actors.cpp | 32 +++++++++++++++++++ apps/openmw/mwmechanics/actors.hpp | 6 ++++ .../mwmechanics/mechanicsmanagerimp.cpp | 20 ++++++++++++ .../mwmechanics/mechanicsmanagerimp.hpp | 8 +++++ apps/openmw/mwstate/statemanagerimp.cpp | 10 +++++- components/esm/defs.hpp | 1 + 7 files changed, 93 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index f31241bdb4..30a576a15f 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace Ogre { @@ -13,6 +14,9 @@ namespace Ogre namespace ESM { struct Class; + + class ESMReader; + class ESMWriter; } namespace MWWorld @@ -21,6 +25,11 @@ namespace MWWorld class CellStore; } +namespace Loading +{ + class Listener; +} + namespace MWBase { /// \brief Interface for game mechanics manager (implemented in MWMechanics) @@ -174,6 +183,14 @@ namespace MWBase virtual std::list getActorsFighting(const MWWorld::Ptr& actor) = 0; virtual void playerLoaded() = 0; + + virtual int countSavedGameRecords() const = 0; + + virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const = 0; + + virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; + + virtual void clear() = 0; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 5727996d95..f3c42682b2 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1181,4 +1181,36 @@ namespace MWMechanics } return list; } + + void Actors::write (ESM::ESMWriter& writer, Loading::Listener& listener) const + { + writer.startRecord(ESM::REC_DCOU); + for (std::map::const_iterator it = mDeathCount.begin(); it != mDeathCount.end(); ++it) + { + writer.writeHNString("ID__", it->first); + writer.writeHNT ("COUN", it->second); + } + writer.endRecord(ESM::REC_DCOU); + + listener.increaseProgress(1); + } + + void Actors::readRecord (ESM::ESMReader& reader, int32_t type) + { + if (type == ESM::REC_DCOU) + { + while (reader.isNextSub("ID__")) + { + std::string id = reader.getHString(); + int count; + reader.getHNT (count, "COUN"); + mDeathCount[id] = count; + } + } + } + + void Actors::clear() + { + mDeathCount.clear(); + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index cc95660dc2..4784162f48 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -112,6 +112,12 @@ namespace MWMechanics /**ie AiCombat is active and the target is the actor **/ std::list getActorsFighting(const MWWorld::Ptr& actor); + void write (ESM::ESMWriter& writer, Loading::Listener& listener) const; + + void readRecord (ESM::ESMReader& reader, int32_t type); + + void clear(); // Clear death counter + private: PtrControllerMap mActors; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index f39a4b961c..5737c380a8 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1050,4 +1050,24 @@ namespace MWMechanics std::list MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { return mActors.getActorsFighting(actor); } + + int MechanicsManager::countSavedGameRecords() const + { + return 1; // Death counter + } + + void MechanicsManager::write(ESM::ESMWriter &writer, Loading::Listener &listener) const + { + mActors.write(writer, listener); + } + + void MechanicsManager::readRecord(ESM::ESMReader &reader, int32_t type) + { + mActors.readRecord(reader, type); + } + + void MechanicsManager::clear() + { + mActors.clear(); + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index dcd12ee140..2511d5785e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -147,6 +147,14 @@ namespace MWMechanics virtual bool isAIActive(); virtual void playerLoaded(); + + virtual int countSavedGameRecords() const; + + virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const; + + virtual void readRecord (ESM::ESMReader& reader, int32_t type); + + virtual void clear(); }; } diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 68cb91eb9d..a459c87bbb 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -45,6 +45,7 @@ void MWState::StateManager::cleanup (bool force) MWBase::Environment::get().getWorld()->clear(); MWBase::Environment::get().getWindowManager()->clear(); MWBase::Environment::get().getInputManager()->clear(); + MWBase::Environment::get().getMechanicsManager()->clear(); mState = State_NoGame; mCharacterManager.clearCurrentCharacter(); @@ -205,7 +206,8 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot +MWBase::Environment::get().getWorld()->countSavedGameRecords() +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() - +MWBase::Environment::get().getWindowManager()->countSavedGameRecords(); + +MWBase::Environment::get().getWindowManager()->countSavedGameRecords() + +MWBase::Environment::get().getMechanicsManager()->countSavedGameRecords(); writer.setRecordCount (recordCount); writer.save (stream); @@ -226,6 +228,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot MWBase::Environment::get().getWorld()->write (writer, listener); MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer, listener); MWBase::Environment::get().getWindowManager()->write(writer, listener); + MWBase::Environment::get().getMechanicsManager()->write(writer, listener); // Ensure we have written the number of records that was estimated if (writer.getRecordCount() != recordCount+1) // 1 extra for TES3 record @@ -357,6 +360,11 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val); break; + case ESM::REC_DCOU: + + MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.val); + break; + default: // ignore invalid records diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index bdeb95291f..f967af274a 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -111,6 +111,7 @@ enum RecNameInts REC_ACTC = FourCC<'A','C','T','C'>::value, REC_MPRJ = FourCC<'M','P','R','J'>::value, REC_PROJ = FourCC<'P','R','O','J'>::value, + REC_DCOU = FourCC<'D','C','O','U'>::value, // format 1 REC_FILT = 0x544C4946 From 8114f4846963aa802bbd4624714b1afad2753275 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jun 2014 19:06:09 +0200 Subject: [PATCH 061/226] Workaround per-frame resources leak in MyGUI. New vertex buffers were being created every frame. --- apps/openmw/mwgui/itemwidget.cpp | 31 +++++++++++++++++++++++++++++-- apps/openmw/mwgui/itemwidget.hpp | 3 +++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index a1ca5cb6c9..7c79f8027c 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -33,12 +33,26 @@ namespace MWGui void ItemWidget::setIcon(const std::string &icon) { + // HACK HACK HACK: Don't setImageTexture if it hasn't changed. + // There is a leak in MyGUI for each setImageTexture on the same widget. + // http://www.ogre3d.org/addonforums/viewtopic.php?f=17&t=30251 + if (mCurrentItemTexture == icon) + return; + + mCurrentItemTexture = icon; if (mItem) mItem->setImageTexture(icon); } void ItemWidget::setFrame(const std::string &frame, const MyGUI::IntCoord &coord) { + // HACK HACK HACK: Don't setImageTexture if it hasn't changed. + // There is a leak in MyGUI for each setImageTexture on the same widget. + // http://www.ogre3d.org/addonforums/viewtopic.php?f=17&t=30251 + if (mCurrentFrameTexture == frame) + return; + + mCurrentFrameTexture = frame; if (mFrame) { mFrame->setImageTexture(frame); @@ -69,8 +83,21 @@ namespace MWGui if (ptr.isEmpty()) { if (mFrame) - mFrame->setImageTexture(""); - mItem->setImageTexture(""); + { + // HACK HACK HACK: Don't setImageTexture if it hasn't changed. + // There is a leak in MyGUI for each setImageTexture on the same widget. + // http://www.ogre3d.org/addonforums/viewtopic.php?f=17&t=30251 + if (!mCurrentFrameTexture.empty()) + { + mFrame->setImageTexture(""); + mCurrentFrameTexture = ""; + } + } + if (!mCurrentItemTexture.empty()) + { + mCurrentItemTexture = ""; + mItem->setImageTexture(""); + } return; } diff --git a/apps/openmw/mwgui/itemwidget.hpp b/apps/openmw/mwgui/itemwidget.hpp index 3de98489d0..5cdf712126 100644 --- a/apps/openmw/mwgui/itemwidget.hpp +++ b/apps/openmw/mwgui/itemwidget.hpp @@ -42,6 +42,9 @@ namespace MWGui MyGUI::ImageBox* mItem; MyGUI::ImageBox* mFrame; + + std::string mCurrentItemTexture; + std::string mCurrentFrameTexture; }; } From 3334078d4dc6ddab109c2178c7a99041e7428214 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jun 2014 21:14:17 +0200 Subject: [PATCH 062/226] Add first person meshes to refraction render (Fixes #1481) --- apps/openmw/mwrender/refraction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/refraction.cpp b/apps/openmw/mwrender/refraction.cpp index f22968e9d0..c2809a1aaf 100644 --- a/apps/openmw/mwrender/refraction.cpp +++ b/apps/openmw/mwrender/refraction.cpp @@ -33,7 +33,7 @@ namespace MWRender Ogre::Viewport* vp = mRenderTarget->addViewport(mCamera); vp->setOverlaysEnabled(false); vp->setShadowsEnabled(false); - vp->setVisibilityMask(RV_Actors + RV_Misc + RV_Statics + RV_StaticsSmall + RV_Terrain + RV_Sky); + vp->setVisibilityMask(RV_Actors + RV_Misc + RV_Statics + RV_StaticsSmall + RV_Terrain + RV_Sky + RV_FirstPerson); vp->setMaterialScheme("water_refraction"); vp->setBackgroundColour (Ogre::ColourValue(0.18039, 0.23137, 0.25490)); mRenderTarget->setAutoUpdated(true); From c4e5872620ed7f6d1a6210b9dc0eaccddf6f42a2 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Fri, 13 Jun 2014 23:22:00 +0400 Subject: [PATCH 063/226] fix bug http://bugs.openmw.org/issues/1470 --- apps/openmw/mwmechanics/aicombat.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 94bb2cd90b..78e304b319 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -377,8 +377,11 @@ namespace MWMechanics bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor); + // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack + bool inLOS = distantCombat ? world->getLOS(actor, target) : true; + // (within attack dist) || (not quite attack dist while following) - if(distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck) ) + if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck))) { //Melee and Close-up combat @@ -437,7 +440,7 @@ namespace MWMechanics else // remote pathfinding { bool preferShortcut = false; - bool inLOS = world->getLOS(actor, target); + if (!distantCombat) inLOS = world->getLOS(actor, target); // check if shortcut is available if(inLOS && (!isStuck || mReadyToAttack) From 4f742fd4686dc05055258c64fa496f705e6a83ca Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jun 2014 21:53:47 +0200 Subject: [PATCH 064/226] Allow INFO records with no subrecords following DATA (Fixes #1486) --- components/esm/loadinfo.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index f86ad3b51b..c9860dcef1 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -18,6 +18,9 @@ void DialInfo::load(ESMReader &esm) esm.getHT(mData, 12); } + if (!esm.hasMoreSubs()) + return; + // What follows is somewhat spaghetti-ish, but it's worth if for // an extra speedup. INFO is by far the most common record type. From 54d8606b78ae0db14c4db865905ef493fd0300b7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jun 2014 22:03:52 +0200 Subject: [PATCH 065/226] Fix logging fatal exceptions to cerr, and log them to openmw.log --- apps/openmw/main.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 503ccaf250..b082ff9085 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -294,9 +294,19 @@ private: int main(int argc, char**argv) { + // Some objects used to redirect cout and cerr + // Scope must be here, so this still works inside the catch block for logging exceptions std::streambuf* cout_rdbuf = std::cout.rdbuf (); std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); + boost::iostreams::stream_buffer coutsb; + boost::iostreams::stream_buffer cerrsb; + + std::ostream oldcout(cout_rdbuf); + std::ostream oldcerr(cerr_rdbuf); + + boost::filesystem::ofstream logfile; + int ret = 0; try { @@ -310,20 +320,16 @@ int main(int argc, char**argv) std::cerr.rdbuf (&sb); #else // Redirect cout and cerr to openmw.log - boost::filesystem::ofstream logfile (boost::filesystem::path( - cfgMgr.getLogPath() / "/openmw.log")); + logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / "/openmw.log")); - boost::iostreams::stream_buffer coutsb; - std::ostream oldcout(cout_rdbuf); coutsb.open (Tee(logfile, oldcout)); - std::cout.rdbuf (&coutsb); - - boost::iostreams::stream_buffer cerrsb; - std::ostream oldcerr(cerr_rdbuf); cerrsb.open (Tee(logfile, oldcerr)); + + std::cout.rdbuf (&coutsb); std::cerr.rdbuf (&cerrsb); #endif + #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE // Unix crash catcher if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached()) From fcab53b3f7ab7cf730ece238ba9fea795a86ef63 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jun 2014 22:21:44 +0200 Subject: [PATCH 066/226] Fix locked mouse cursor due to SDL_SetRelativeMouseMode even when --no-grab was specified --- extern/sdl4ogre/sdlinputwrapper.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index 82db5ea99f..b9314781e7 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -239,9 +239,11 @@ namespace SFO mWrapPointer = false; - //eep, wrap the pointer manually if the input driver doesn't support - //relative positioning natively - bool success = SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE) == 0; + // eep, wrap the pointer manually if the input driver doesn't support + // relative positioning natively + // also use wrapping if no-grab was specified in options (SDL_SetRelativeMouseMode + // appears to eat the mouse cursor when pausing in a debugger) + bool success = mAllowGrab && SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE) == 0; if(relative && !success) mWrapPointer = true; From 162549b7af1e610706c45ba75878bf11a41dc0aa Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jun 2014 22:23:24 +0200 Subject: [PATCH 067/226] Remove a wrong comment --- extern/sdl4ogre/sdlinputwrapper.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index b9314781e7..f65dfb376f 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -340,9 +340,6 @@ namespace SFO { //lifted from OIS's SDLKeyboard.cpp - //TODO: Consider switching to scancodes so we - //can properly support international keyboards - //look at SDL_GetKeyFromScancode and SDL_GetKeyName mKeyMap.insert( KeyMap::value_type(SDLK_UNKNOWN, OIS::KC_UNASSIGNED)); mKeyMap.insert( KeyMap::value_type(SDLK_ESCAPE, OIS::KC_ESCAPE) ); mKeyMap.insert( KeyMap::value_type(SDLK_1, OIS::KC_1) ); From ad3a78706ea1260c030001f3e361e6c166776ba8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jun 2014 23:17:54 +0200 Subject: [PATCH 068/226] Bug #1417: Use fmod to wrap local rotations (more efficient and robust) --- apps/openmw/mwworld/worldimp.cpp | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 371f9edfea..20a3e495e9 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -54,6 +54,20 @@ using namespace Ogre; +namespace +{ + +// Wraps a value to (-PI, PI] +void wrap(float& rad) +{ + if (rad>0) + rad = std::fmod(rad+M_PI, 2.0*M_PI)-M_PI; + else + rad = std::fmod(rad-M_PI, 2.0*M_PI)+M_PI; +} + +} + namespace MWWorld { struct GameContentLoader : public ContentLoader @@ -1105,21 +1119,9 @@ namespace MWWorld ptr.getRefData().getLocalRotation().rot[1]=Ogre::Degree(y).valueRadians(); ptr.getRefData().getLocalRotation().rot[2]=Ogre::Degree(z).valueRadians(); - float fullRotateRad=Ogre::Degree(360).valueRadians(); - - while(ptr.getRefData().getLocalRotation().rot[0]>=fullRotateRad) - ptr.getRefData().getLocalRotation().rot[0]-=fullRotateRad; - while(ptr.getRefData().getLocalRotation().rot[1]>=fullRotateRad) - ptr.getRefData().getLocalRotation().rot[1]-=fullRotateRad; - while(ptr.getRefData().getLocalRotation().rot[2]>=fullRotateRad) - ptr.getRefData().getLocalRotation().rot[2]-=fullRotateRad; - - while(ptr.getRefData().getLocalRotation().rot[0]<=-fullRotateRad) - ptr.getRefData().getLocalRotation().rot[0]+=fullRotateRad; - while(ptr.getRefData().getLocalRotation().rot[1]<=-fullRotateRad) - ptr.getRefData().getLocalRotation().rot[1]+=fullRotateRad; - while(ptr.getRefData().getLocalRotation().rot[2]<=-fullRotateRad) - ptr.getRefData().getLocalRotation().rot[2]+=fullRotateRad; + wrap(ptr.getRefData().getLocalRotation().rot[0]); + wrap(ptr.getRefData().getLocalRotation().rot[1]); + wrap(ptr.getRefData().getLocalRotation().rot[2]); Ogre::Quaternion worldRotQuat(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z); if (!ptr.getClass().isActor()) From 0637cde26793f984aa7b99bf23fde82950d7006e Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Jun 2014 23:40:49 +0200 Subject: [PATCH 069/226] Bug #1417: Make sure to reset all position/rotation fields when placing items --- apps/openmw/mwworld/worldimp.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 20a3e495e9..066cae4948 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1681,6 +1681,13 @@ namespace MWWorld MWWorld::Ptr dropped = object.getClass().copyToCell(object, *cell, pos); + // Reset some position values that could be uninitialized if this item came from a container + LocalRotation& localRotation = dropped.getRefData().getLocalRotation(); + localRotation.rot[0] = 0; + localRotation.rot[1] = 0; + localRotation.rot[2] = 0; + dropped.getCellRef().setPosition(pos); + if (mWorldScene->isCellActive(*cell)) { if (dropped.getRefData().isEnabled()) { mWorldScene->addObjectToScene(dropped); From 832f1a4857d6095f3d3b8e3f0bf2b3f0907345ed Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jun 2014 02:31:01 +0200 Subject: [PATCH 070/226] Fix merge mistake --- apps/openmw/mwmechanics/aicombat.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 31de1a8886..a134e3dec4 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -88,12 +88,23 @@ namespace MWMechanics AiCombat::AiCombat(const MWWorld::Ptr& actor) : mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) + , mMinMaxAttackDuration() + , mMovement() { init(); mLastTargetPos = Ogre::Vector3(actor.getRefData().getPosition().pos); } + AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat) + : mMinMaxAttackDuration() + , mMovement() + { + mTargetActorId = combat->mTargetActorId; + + init(); + } + void AiCombat::init() { mTimerAttack = 0; @@ -638,13 +649,6 @@ namespace MWMechanics return new AiCombat(*this); } - AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat) - { - mTargetActorId = combat->mTargetActorId; - - init(); - } - void AiCombat::writeState(ESM::AiSequence::AiSequence &sequence) const { std::auto_ptr combat(new ESM::AiSequence::AiCombat()); From dc2fefffc7d4980fe809bf28275b8179a49c15a1 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 14 Jun 2014 13:29:55 +0200 Subject: [PATCH 071/226] Fix missing M_PI define on Windows --- apps/openmw/mwworld/worldimp.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 066cae4948..4a29ae1d17 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -52,6 +52,10 @@ #include "esmloader.hpp" #include "omwloader.hpp" +#ifdef _WIN32 +#define M_PI 3.14159 // Windows doesn't have this +#endif + using namespace Ogre; namespace From 6ea29812ac401d41787a7c1e376f38fa22994bbd Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 14 Jun 2014 13:54:01 +0200 Subject: [PATCH 072/226] Another way of getting M_PI --- apps/openmw/mwworld/worldimp.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4a29ae1d17..f8c537cce3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,4 +1,10 @@ #include "worldimp.hpp" + +#ifdef _WIN32 // For M_PI +#define _USE_MATH_DEFINES +#include +#endif + #ifdef _WIN32 #include #elif defined HAVE_UNORDERED_MAP @@ -52,10 +58,6 @@ #include "esmloader.hpp" #include "omwloader.hpp" -#ifdef _WIN32 -#define M_PI 3.14159 // Windows doesn't have this -#endif - using namespace Ogre; namespace From 5960d3f1d94105f1e49b4faf369ebc47cdcda909 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 14 Jun 2014 13:54:12 +0200 Subject: [PATCH 073/226] These files moved, leading to package failure --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d92930c99..1769f884d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -446,8 +446,8 @@ if(WIN32) INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg") INSTALL(FILES "${OpenMW_SOURCE_DIR}/readme.txt" - "${OpenMW_SOURCE_DIR}/GPL3.txt" - "${OpenMW_SOURCE_DIR}/DejaVu Font License.txt" + "${OpenMW_SOURCE_DIR}/Docs/license/GPL3.txt" + "${OpenMW_SOURCE_DIR}/Docs/license/DejaVu Font License.txt" "${OpenMW_BINARY_DIR}/settings-default.cfg" "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" "${OpenMW_BINARY_DIR}/Release/openmw.exe" From c24e08dff6db50ce674e11ec26cad22186ccecc8 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 14 Jun 2014 14:43:24 +0200 Subject: [PATCH 074/226] Use Ogre::Math::PI --- apps/openmw/mwworld/worldimp.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f8c537cce3..6a18298f99 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,10 +1,5 @@ #include "worldimp.hpp" -#ifdef _WIN32 // For M_PI -#define _USE_MATH_DEFINES -#include -#endif - #ifdef _WIN32 #include #elif defined HAVE_UNORDERED_MAP @@ -67,9 +62,9 @@ namespace void wrap(float& rad) { if (rad>0) - rad = std::fmod(rad+M_PI, 2.0*M_PI)-M_PI; + rad = std::fmod(rad+Ogre::Math::PI, 2.0f*Ogre::Math::PI)-Ogre::Math::PI; else - rad = std::fmod(rad-M_PI, 2.0*M_PI)+M_PI; + rad = std::fmod(rad-Ogre::Math::PI, 2.0f*Ogre::Math::PI)+Ogre::Math::PI; } } From 6da061fd96cb22f525d62c9a81967988f191f342 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sat, 14 Jun 2014 23:36:57 +0400 Subject: [PATCH 075/226] fix http://bugs.openmw.org/issues/1340 Did I understand right that guards trigger combat after refusing pay gold/go to jail via script? --- .../mwmechanics/mechanicsmanagerimp.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index f39a4b961c..7178a2556b 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1024,8 +1024,27 @@ namespace MWMechanics { ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr); if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { ptr.getClass().getCreatureStats(ptr).setHostile(true); + // if guard starts combat with player, guards pursuing player should do the same + if (ptr.getClass().isClass(ptr, "Guard")) + { + for (Actors::PtrControllerMap::const_iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + { + if (iter->first.getClass().isClass(iter->first, "Guard")) + { + MWMechanics::AiSequence& aiSeq = iter->first.getClass().getCreatureStats(iter->first).getAiSequence(); + if (aiSeq.getActivePackage()->getTypeId() == MWMechanics::AiPackage::TypeIdPursue) + { + aiSeq.stopPursuit(); + aiSeq.stack(MWMechanics::AiCombat(target), ptr); + } + } + } + } + } + // Must be done after the target is set up, so that CreatureTargetted dialogue filter works properly if (ptr.getClass().isNpc()) MWBase::Environment::get().getDialogueManager()->say(ptr, "attack"); From a6ee4272aa890d6b721088a077c44e19370456ce Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sun, 15 Jun 2014 00:14:18 +0400 Subject: [PATCH 076/226] fix bug when you were able to kill somebody from large distance without being spotted --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 7178a2556b..2fa12bc452 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -853,8 +853,15 @@ namespace MWMechanics // Find all the actors within the alarm radius std::vector neighbors; - mActors.getObjectsInRange(Ogre::Vector3(player.getRefData().getPosition().pos), - esmStore.get().find("fAlarmRadius")->getInt(), neighbors); + + Ogre::Vector3 from = Ogre::Vector3(player.getRefData().getPosition().pos); + int radius = esmStore.get().find("fAlarmRadius")->getInt(); + + mActors.getObjectsInRange(from, radius, neighbors); + + // victim should be considered even beyond alarm radius + if (from.squaredDistance(Ogre::Vector3(victim.getRefData().getPosition().pos)) > radius*radius) + neighbors.push_back(victim); int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); From abeb1d4ab31b287915d3baad91f46a2eafd418db Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 15 Jun 2014 09:37:24 +0200 Subject: [PATCH 077/226] Valgrind: Added constructor for KeyListT class, and added initialziation of ver member field in NIFFile class. Signed-off-by: Lukasz Gromanowski --- components/nif/niffile.cpp | 3 ++- components/nif/niffile.hpp | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 10847e8f4d..84f4aaceeb 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -172,7 +172,8 @@ NIFFile::ptr NIFFile::create (const std::string &name) { return LoadedCache::cre /// Open a NIF stream. The name is used for error messages. NIFFile::NIFFile(const std::string &name, psudo_private_modifier) - : filename(name) + : ver(0) + , filename(name) { parse(); } diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index fbb64e4f74..bc9e99faf9 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -161,6 +161,8 @@ struct KeyListT { unsigned int mInterpolationType; VecType mKeys; + KeyListT() : mInterpolationType(sLinearInterpolation) {} + //Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html) void read(NIFStream *nif, bool force=false) { From b20f8cc04f40b49d5c8c2f5ecf3a04d864e24ae4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jun 2014 15:35:35 +0200 Subject: [PATCH 078/226] Don't clear AiSequence if there are no saved packages (eg for old savegames) --- apps/openmw/mwmechanics/aisequence.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 2d4dc2732f..85e56af8bb 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -300,7 +300,8 @@ void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) { - clear(); + if (!sequence.mPackages.empty()) + clear(); for (std::vector::const_iterator it = sequence.mPackages.begin(); it != sequence.mPackages.end(); ++it) From 395f98e47683998d98c32f61241a2b9b1647930a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jun 2014 17:56:41 +0200 Subject: [PATCH 079/226] Fix triggering changed flag for all references when cell is visited The InsertFunctor for cells was calling localRotateObject() for all references which set the mChanged flag in RefData to true. Also clean up RefData interface slightly. --- apps/openmw/mwclass/creature.cpp | 3 +- apps/openmw/mwclass/npc.cpp | 3 +- apps/openmw/mwmechanics/aisequence.cpp | 4 +- apps/openmw/mwmechanics/pathgrid.cpp | 5 +- apps/openmw/mwrender/ripplesimulation.cpp | 2 +- .../mwscript/transformationextensions.cpp | 13 ++-- apps/openmw/mwworld/cellref.hpp | 3 +- apps/openmw/mwworld/cellstore.cpp | 3 + apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/player.cpp | 5 +- apps/openmw/mwworld/refdata.cpp | 14 +++- apps/openmw/mwworld/refdata.hpp | 6 +- apps/openmw/mwworld/scene.cpp | 44 +++++++++-- apps/openmw/mwworld/scene.hpp | 4 + apps/openmw/mwworld/worldimp.cpp | 76 +++++++++---------- apps/openmw/mwworld/worldimp.hpp | 3 +- 16 files changed, 122 insertions(+), 68 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 5744ebb9b6..99b83a5ac2 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -837,8 +837,7 @@ namespace MWClass ptr.getRefData().setCount(1); // Reset to original position - ESM::Position& pos = ptr.getRefData().getPosition(); - pos = ptr.getCellRef().getPosition(); + ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); ptr.getRefData().setCustomData(NULL); } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 5ec192ab20..6d5ff9f7f6 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1325,8 +1325,7 @@ namespace MWClass ptr.getRefData().setCount(1); // Reset to original position - ESM::Position& pos = ptr.getRefData().getPosition(); - pos = ptr.getCellRef().getPosition(); + ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); ptr.getRefData().setCustomData(NULL); } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 85e56af8bb..05723c5756 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -88,7 +88,7 @@ bool AiSequence::canAddTarget(const ESM::Position& actorPos, float distToTarget) else { // add new target only if current target (player) is farther - ESM::Position &targetPos = combat->getTarget().getRefData().getPosition(); + const ESM::Position &targetPos = combat->getTarget().getRefData().getPosition(); float distToCurrTarget = (Ogre::Vector3(targetPos.pos) - Ogre::Vector3(actorPos.pos)).length(); return (distToCurrTarget > distToTarget); @@ -153,7 +153,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor,float duration) } else { - ESM::Position &targetPos = target.getRefData().getPosition(); + const ESM::Position &targetPos = target.getRefData().getPosition(); float distTo = (Ogre::Vector3(targetPos.pos) - vActorPos).length(); if (distTo < nearestDist) diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index c3fa0a662e..4983a4a4f2 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -100,6 +100,9 @@ namespace MWMechanics if(!cell) return false; + if(mIsGraphConstructed) + return true; + mCell = cell; mIsExterior = cell->isExterior(); mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); @@ -107,8 +110,6 @@ namespace MWMechanics if(!mPathgrid) return false; - if(mIsGraphConstructed) - return true; mGraph.resize(mPathgrid->mPoints.size()); for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 74216c1de4..64b5e48c38 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -165,7 +165,7 @@ void RippleSimulation::addImpulses() // for non-player actors this is done in updateObjectCell it->mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); } - float* _currentPos = it->mPtr.getRefData().getPosition().pos; + const float* _currentPos = it->mPtr.getRefData().getPosition().pos; Ogre::Vector3 currentPos (_currentPos[0], _currentPos[1], _currentPos[2]); if ( (currentPos - it->mLastEmitPosition).length() > 2 diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index a944a31b8d..138326ff04 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -578,7 +578,7 @@ namespace MWScript Interpreter::Type_Float rotation = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); - float *objRot = ptr.getRefData().getPosition().rot; + const float *objRot = ptr.getRefData().getPosition().rot; float ax = Ogre::Radian(objRot[0]).valueDegrees(); float ay = Ogre::Radian(objRot[1]).valueDegrees(); @@ -613,9 +613,12 @@ namespace MWScript if (!ptr.isInCell()) return; - ptr.getRefData().getLocalRotation().rot[0] = 0; - ptr.getRefData().getLocalRotation().rot[1] = 0; - ptr.getRefData().getLocalRotation().rot[2] = 0; + MWWorld::LocalRotation rot; + rot.rot[0] = 0; + rot.rot[1] = 0; + rot.rot[2] = 0; + ptr.getRefData().setLocalRotation(rot); + MWBase::Environment::get().getWorld()->rotateObject(ptr, 0,0,0,true); MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2]); @@ -678,7 +681,7 @@ namespace MWScript Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); - float *objPos = ptr.getRefData().getPosition().pos; + const float *objPos = ptr.getRefData().getPosition().pos; if (axis == "x") { diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 4db362b1e0..689671c025 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -42,7 +42,8 @@ namespace MWWorld float getScale() const; void setScale(float scale); - // Position and rotation of this object within the cell + // The *original* position and rotation as it was given in the Construction Set. + // Current position and rotation of the object is stored in RefData. ESM::Position getPosition() const; void setPosition (const ESM::Position& position); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 63cdbfb1a3..3e29a9cae0 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -410,6 +410,9 @@ namespace MWWorld loadRefs (store, esm); mState = State_Loaded; + + // TODO: the pathgrid graph only needs to be loaded for active cells, so move this somewhere else. + // In a simple test, loading the graph for all cells in MW + expansions took 200 ms mPathgridGraph.load(mCell); } } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index c13ecfab5a..446519b877 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -366,7 +366,7 @@ namespace MWWorld Class::copyToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const { Ptr newPtr = copyToCell(ptr, cell); - newPtr.getRefData().getPosition() = pos; + newPtr.getRefData().setPosition(pos); return newPtr; } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 9913b888b2..b88483bfe8 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -44,8 +44,9 @@ namespace MWWorld cellRef.mRefID = "player"; mPlayer = LiveCellRef(cellRef, player); - float* playerPos = mPlayer.mData.getPosition().pos; - playerPos[0] = playerPos[1] = playerPos[2] = 0; + ESM::Position playerPos = mPlayer.mData.getPosition(); + playerPos.pos[0] = playerPos.pos[1] = playerPos.pos[2] = 0; + mPlayer.mData.setPosition(playerPos); } void Player::set(const ESM::NPC *player) diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 2e267b37ce..3b7521e8dd 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -188,15 +188,25 @@ namespace MWWorld mEnabled = false; } - ESM::Position& RefData::getPosition() + void RefData::setPosition(const ESM::Position& pos) { mChanged = true; + mPosition = pos; + } + + const ESM::Position& RefData::getPosition() + { return mPosition; } - LocalRotation& RefData::getLocalRotation() + void RefData::setLocalRotation(const LocalRotation& rot) { mChanged = true; + mLocalRotation = rot; + } + + const LocalRotation& RefData::getLocalRotation() + { return mLocalRotation; } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index a8ffad6843..fcd1437c97 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -100,9 +100,11 @@ namespace MWWorld void disable(); - ESM::Position& getPosition(); + void setPosition (const ESM::Position& pos); + const ESM::Position& getPosition(); - LocalRotation& getLocalRotation(); + void setLocalRotation (const LocalRotation& rotation); + const LocalRotation& getLocalRotation(); void setCustomData (CustomData *data); ///< Set custom data (potentially replacing old custom data). The ownership of \æ data is diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index f753ae1f45..b2faa1a010 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -22,6 +22,30 @@ namespace { + void updateObjectLocalRotation (const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics, + MWRender::RenderingManager& rendering) + { + if (ptr.getRefData().getBaseNode() != NULL) + { + Ogre::Quaternion worldRotQuat(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z); + if (!ptr.getClass().isActor()) + worldRotQuat = Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)* + Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y)* worldRotQuat; + + float x = ptr.getRefData().getLocalRotation().rot[0]; + float y = ptr.getRefData().getLocalRotation().rot[1]; + float z = ptr.getRefData().getLocalRotation().rot[2]; + + Ogre::Quaternion rot(Ogre::Radian(z), Ogre::Vector3::NEGATIVE_UNIT_Z); + if (!ptr.getClass().isActor()) + rot = Ogre::Quaternion(Ogre::Radian(x), Ogre::Vector3::NEGATIVE_UNIT_X)* + Ogre::Quaternion(Ogre::Radian(y), Ogre::Vector3::NEGATIVE_UNIT_Y)*rot; + + ptr.getRefData().getBaseNode()->setOrientation(worldRotQuat*rot); + physics.rotateObject(ptr); + } + } + struct InsertFunctor { MWWorld::CellStore& mCell; @@ -60,11 +84,7 @@ namespace mRendering.addObject (ptr); ptr.getClass().insertObject (ptr, mPhysics); - float ax = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees(); - float ay = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees(); - float az = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees(); - MWBase::Environment::get().getWorld()->localRotateObject (ptr, ax, ay, az); - + updateObjectLocalRotation(ptr, mPhysics, mRendering); MWBase::Environment::get().getWorld()->scaleObject (ptr, ptr.getCellRef().getScale()); ptr.getClass().adjustPosition (ptr); } @@ -85,6 +105,20 @@ namespace namespace MWWorld { + void Scene::updateObjectLocalRotation (const Ptr& ptr) + { + ::updateObjectLocalRotation(ptr, *mPhysics, mRendering); + } + + void Scene::updateObjectRotation (const Ptr& ptr) + { + if(ptr.getRefData().getBaseNode() != 0) + { + mRendering.rotateObject(ptr); + mPhysics->rotateObject(ptr); + } + } + void Scene::update (float duration, bool paused) { if (mNeedMapUpdate) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 4496447544..e0eeee1871 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -103,6 +103,10 @@ namespace MWWorld void removeObjectFromScene (const Ptr& ptr); ///< Remove an object from the scene, but not from the world model. + void updateObjectLocalRotation (const Ptr& ptr); + + void updateObjectRotation (const Ptr& ptr); + bool isCellActive(const CellStore &cell); Ptr searchPtrViaHandle (const std::string& handle); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 6a18298f99..e6049c1853 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -957,12 +957,14 @@ namespace MWWorld void World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z) { - ESM::Position &pos = ptr.getRefData().getPosition(); + ESM::Position pos = ptr.getRefData().getPosition(); pos.pos[0] = x; pos.pos[1] = y; pos.pos[2] = z; + ptr.getRefData().setPosition(pos); + Ogre::Vector3 vec(x, y, z); CellStore *currCell = ptr.isInCell() ? ptr.getCell() : NULL; @@ -1068,7 +1070,8 @@ namespace MWWorld const float two_pi = Ogre::Math::TWO_PI; const float pi = Ogre::Math::PI; - float *objRot = ptr.getRefData().getPosition().rot; + ESM::Position pos = ptr.getRefData().getPosition(); + float *objRot = pos.rot; if(adjust) { objRot[0] += rot.x; @@ -1105,43 +1108,33 @@ namespace MWWorld while(objRot[2] < -pi) objRot[2] += two_pi; while(objRot[2] > pi) objRot[2] -= two_pi; - if(ptr.getRefData().getBaseNode() != 0) - { - mRendering->rotateObject(ptr); - mPhysics->rotateObject(ptr); - } + ptr.getRefData().setPosition(pos); + + mWorldScene->updateObjectRotation(ptr); } void World::localRotateObject (const Ptr& ptr, float x, float y, float z) { - if (ptr.getRefData().getBaseNode() != 0) { + if (ptr.getRefData().getBaseNode() != 0) + { + LocalRotation rot = ptr.getRefData().getLocalRotation(); + rot.rot[0]=Ogre::Degree(x).valueRadians(); + rot.rot[1]=Ogre::Degree(y).valueRadians(); + rot.rot[2]=Ogre::Degree(z).valueRadians(); - ptr.getRefData().getLocalRotation().rot[0]=Ogre::Degree(x).valueRadians(); - ptr.getRefData().getLocalRotation().rot[1]=Ogre::Degree(y).valueRadians(); - ptr.getRefData().getLocalRotation().rot[2]=Ogre::Degree(z).valueRadians(); + wrap(rot.rot[0]); + wrap(rot.rot[1]); + wrap(rot.rot[2]); - wrap(ptr.getRefData().getLocalRotation().rot[0]); - wrap(ptr.getRefData().getLocalRotation().rot[1]); - wrap(ptr.getRefData().getLocalRotation().rot[2]); + ptr.getRefData().setLocalRotation(rot); - Ogre::Quaternion worldRotQuat(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z); - if (!ptr.getClass().isActor()) - worldRotQuat = Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)* - Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y)* worldRotQuat; - - Ogre::Quaternion rot(Ogre::Degree(z), Ogre::Vector3::NEGATIVE_UNIT_Z); - if (!ptr.getClass().isActor()) - rot = Ogre::Quaternion(Ogre::Degree(x), Ogre::Vector3::NEGATIVE_UNIT_X)* - Ogre::Quaternion(Ogre::Degree(y), Ogre::Vector3::NEGATIVE_UNIT_Y)*rot; - - ptr.getRefData().getBaseNode()->setOrientation(worldRotQuat*rot); - mPhysics->rotateObject(ptr); + mWorldScene->updateObjectLocalRotation(ptr); } } void World::adjustPosition(const Ptr &ptr) { - Ogre::Vector3 pos (ptr.getRefData().getPosition().pos); + ESM::Position pos (ptr.getRefData().getPosition()); if(!ptr.getRefData().getBaseNode()) { @@ -1149,21 +1142,23 @@ namespace MWWorld return; } - float terrainHeight = mRendering->getTerrainHeightAt(pos); + float terrainHeight = mRendering->getTerrainHeightAt(Ogre::Vector3(pos.pos)); - if (pos.z < terrainHeight) - pos.z = terrainHeight; + if (pos.pos[2] < terrainHeight) + pos.pos[2] = terrainHeight; - ptr.getRefData().getPosition().pos[2] = pos.z + 20; // place slightly above. will snap down to ground with code below + pos.pos[2] += 20; // place slightly above. will snap down to ground with code below + + ptr.getRefData().setPosition(pos); if (!isFlying(ptr)) { Ogre::Vector3 traced = mPhysics->traceDown(ptr); - if (traced.z < pos.z) - pos.z = traced.z; + if (traced.z < pos.pos[2]) + pos.pos[2] = traced.z; } - moveObject(ptr, ptr.getCell(), pos.x, pos.y, pos.z); + moveObject(ptr, ptr.getCell(), pos.pos[0], pos.pos[1], pos.pos[2]); } void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust) @@ -1429,7 +1424,7 @@ namespace MWWorld // cast a ray from player to sun to detect if the sun is visible // this is temporary until we find a better place to put this code // currently its here because we need to access the physics system - float* p = mPlayer->getPlayer().getRefData().getPosition().pos; + const float* p = mPlayer->getPlayer().getRefData().getPosition().pos; Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); } @@ -1683,10 +1678,11 @@ namespace MWWorld object.getClass().copyToCell(object, *cell, pos); // Reset some position values that could be uninitialized if this item came from a container - LocalRotation& localRotation = dropped.getRefData().getLocalRotation(); + LocalRotation localRotation; localRotation.rot[0] = 0; localRotation.rot[1] = 0; localRotation.rot[2] = 0; + dropped.getRefData().setLocalRotation(localRotation); dropped.getCellRef().setPosition(pos); if (mWorldScene->isCellActive(*cell)) { @@ -1785,7 +1781,7 @@ namespace MWWorld bool World::isSubmerged(const MWWorld::Ptr &object) const { - float *fpos = object.getRefData().getPosition().pos; + const float *fpos = object.getRefData().getPosition().pos; Ogre::Vector3 pos(fpos[0], fpos[1], fpos[2]); const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(object.getRefData().getHandle()); @@ -1798,7 +1794,7 @@ namespace MWWorld World::isSwimming(const MWWorld::Ptr &object) const { /// \todo add check ifActor() - only actors can swim - float *fpos = object.getRefData().getPosition().pos; + const float *fpos = object.getRefData().getPosition().pos; Ogre::Vector3 pos(fpos[0], fpos[1], fpos[2]); /// \fixme 3/4ths submerged? @@ -2031,9 +2027,9 @@ namespace MWWorld if (!targetNpc.getRefData().isEnabled() || !npc.getRefData().isEnabled()) return false; // cannot get LOS unless both NPC's are enabled Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents(); - float* pos1 = npc.getRefData().getPosition().pos; + const float* pos1 = npc.getRefData().getPosition().pos; Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents(); - float* pos2 = targetNpc.getRefData().getPosition().pos; + const float* pos2 = targetNpc.getRefData().getPosition().pos; btVector3 from(pos1[0],pos1[1],pos1[2]+halfExt1.z); btVector3 to(pos2[0],pos2[1],pos2[2]+halfExt2.z); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 5b493efcc8..8307d53a4f 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -336,10 +336,11 @@ namespace MWWorld virtual void scaleObject (const Ptr& ptr, float scale); - /// Rotates object, uses degrees + /// World rotates object, uses degrees /// \param adjust indicates rotation should be set or adjusted virtual void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false); + /// Local rotates object, uses degrees virtual void localRotateObject (const Ptr& ptr, float x, float y, float z); virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos); From 31a4e10c6f67bcf3041648d321994369434c5eb9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jun 2014 19:12:49 +0200 Subject: [PATCH 080/226] Fix incorrect trigger of change flag for static doors --- apps/openmw/mwclass/door.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 12645c9f39..0a5b774f5f 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -57,11 +57,13 @@ namespace MWClass physics.addObject(ptr); // Resume the door's opening/closing animation if it wasn't finished - ensureCustomData(ptr); - const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); - if (customData.mDoorState > 0) + if (ptr.getRefData().getCustomData()) { - MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState == 1 ? true : false); + const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + if (customData.mDoorState > 0) + { + MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState == 1 ? true : false); + } } } From 7afd2ca6141246a55225d83a8ee13979dc826d28 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jun 2014 19:13:40 +0200 Subject: [PATCH 081/226] Consider moved references in CellStore::listRefs --- apps/openmw/mwworld/cellstore.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 3e29a9cae0..ce8d77758c 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -450,10 +450,25 @@ namespace MWWorld if (deleted) continue; + // Don't list reference if it was moved to a different cell. + ESM::MovedCellRefTracker::const_iterator iter = + std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); + if (iter != mCell->mMovedRefs.end()) { + continue; + } + mIds.push_back (Misc::StringUtils::lowerCase (ref.mRefID)); } } + // List moved references, from separately tracked list. + for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); ++it) + { + ESM::CellRef &ref = const_cast(*it); + + mIds.push_back(Misc::StringUtils::lowerCase(ref.mRefID)); + } + std::sort (mIds.begin(), mIds.end()); } From afbd5162ee947c893284c0cec1b1f19ed23a65bd Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jun 2014 20:02:49 +0200 Subject: [PATCH 082/226] Move AiWander arrival check to time-critical section --- apps/openmw/mwmechanics/aiwander.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 6a68397fde..79151e8961 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -153,6 +153,17 @@ namespace MWMechanics } } + // Are we there yet? + if(mWalking && + mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) + { + stopWalking(actor); + mMoveNow = false; + mWalking = false; + mChooseAction = true; + mHasReturnPosition = false; + } + if(mWalking) // have not yet reached the destination { // turn towards the next point in mPath @@ -524,17 +535,6 @@ namespace MWMechanics } } - // Are we there yet? - if(mWalking && - mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) - { - stopWalking(actor); - mMoveNow = false; - mWalking = false; - mChooseAction = true; - mHasReturnPosition = false; - } - return false; // AiWander package not yet completed } From 8d8015ce184dfacb214758c139b6fb3e0ed3b002 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jun 2014 20:05:23 +0200 Subject: [PATCH 083/226] Remove a squareroot in pathfinding --- 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 d77a35ea48..62c1756c27 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -90,12 +90,12 @@ namespace namespace MWMechanics { - float distanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z) + float sqrDistanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z) { x -= point.mX; y -= point.mY; z -= point.mZ; - return sqrt(x * x + y * y + 0.1 * z * z); + return (x * x + y * y + 0.1 * z * z); } float distance(ESM::Pathgrid::Point point, float x, float y, float z) @@ -296,7 +296,7 @@ namespace MWMechanics return true; ESM::Pathgrid::Point nextPoint = *mPath.begin(); - if(distanceZCorrected(nextPoint, x, y, z) < 64) + if(sqrDistanceZCorrected(nextPoint, x, y, z) < 64*64) { mPath.pop_front(); if(mPath.empty()) mIsPathConstructed = false; @@ -311,7 +311,7 @@ namespace MWMechanics return true; ESM::Pathgrid::Point nextPoint = *mPath.begin(); - if(distanceZCorrected(nextPoint, x, y, z) < 64) + if(sqrDistanceZCorrected(nextPoint, x, y, z) < 64*64) { mPath.pop_front(); if(mPath.empty()) From 88f8f25ffa6f745a6c49d9f874c03937100e8f33 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jun 2014 20:12:52 +0200 Subject: [PATCH 084/226] Don't allow scripted dialogue with dead actors (Fixes #1493) --- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index aa7df1fd4e..678e040c11 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -125,6 +125,10 @@ namespace MWDialogue void DialogueManager::startDialogue (const MWWorld::Ptr& actor) { + // Dialogue with dead actor (e.g. through script) should not be allowed. + if (actor.getClass().getCreatureStats(actor).isDead()) + return; + mLastTopic = ""; mPermanentDispositionChange = 0; mTemporaryDispositionChange = 0; From 1adb81abd0f4933c4e584b0c4c7e15e0acac17ea Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jun 2014 20:25:44 +0200 Subject: [PATCH 085/226] Sort using ciLess, not ciEqual --- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 678e040c11..be0a1b1638 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -406,7 +406,7 @@ namespace MWDialogue win->setServices (windowServices); // sort again, because the previous sort was case-sensitive - keywordList.sort(Misc::StringUtils::ciEqual); + keywordList.sort(Misc::StringUtils::ciLess); win->setKeywords(keywordList); mChoice = choice; From e2884378857f7c6a1a8af8dcd2c81be451e5da3c Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jun 2014 21:27:16 +0200 Subject: [PATCH 086/226] Disable all other animations on death (Fixes #1483) --- apps/openmw/mwmechanics/character.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c54d00fb0c..a5964bdf41 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -435,9 +435,23 @@ void CharacterController::playDeath(float startpoint, CharacterState death) mPtr.getClass().getCreatureStats(mPtr).setDeathAnimation(mDeathState - CharState_Death1); // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually. + // Note that these animations wouldn't actually be visible (due to the Death animation's priority being higher). + // However, they could still trigger text keys, such as Hit events, or sounds. mMovementState = CharState_None; mAnimation->disable(mCurrentMovement); mCurrentMovement = ""; + mUpperBodyState = UpperCharState_Nothing; + mAnimation->disable(mCurrentWeapon); + mCurrentWeapon = ""; + mHitState = CharState_None; + mAnimation->disable(mCurrentHit); + mCurrentHit = ""; + mIdleState = CharState_None; + mAnimation->disable(mCurrentIdle); + mCurrentIdle = ""; + mJumpState = JumpState_None; + mAnimation->disable(mCurrentJump); + mCurrentJump = ""; mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, false, 1.0f, "start", "stop", startpoint, 0); From 9e6d21d95bac5a2fa528c2c8fb92df6d914e2e03 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Jun 2014 21:52:54 +0200 Subject: [PATCH 087/226] Clear message boxes in windowmanager cleanup (Fixes #1496) --- apps/openmw/mwgui/messagebox.cpp | 17 +++++++++++++++++ apps/openmw/mwgui/messagebox.hpp | 3 +++ apps/openmw/mwgui/windowmanagerimp.cpp | 1 + 3 files changed, 21 insertions(+) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index d73a0e1223..c4b204de72 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -25,6 +25,23 @@ namespace MWGui } } + void MessageBoxManager::clear() + { + delete mInterMessageBoxe; + mInterMessageBoxe = NULL; + + std::vector::iterator it(mMessageBoxes.begin()); + for (; it != mMessageBoxes.end(); ++it) + { + if (*it == mStaticMessageBox) + mStaticMessageBox = NULL; + delete *it; + } + mMessageBoxes.clear(); + + mLastButtonPressed = -1; + } + void MessageBoxManager::onFrame (float frameDuration) { std::vector::iterator it; diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index caa37008ca..406d98c484 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -30,6 +30,9 @@ namespace MWGui bool createInteractiveMessageBox (const std::string& message, const std::vector& buttons); bool isInteractiveMessageBox (); + /// Remove all message boxes + void clear(); + bool removeMessageBox (MessageBox *msgbox); void setMessageBoxSpeed (int speed); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index b49bfbfa6f..3f239e0e31 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1502,6 +1502,7 @@ namespace MWGui { mMap->clear(); mQuickKeysMenu->clear(); + mMessageBoxManager->clear(); mTrainingWindow->resetReference(); mDialogueWindow->resetReference(); From 326c74750855e8bfa43ff28d5a6a1d420525c74b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 15 Jun 2014 00:09:12 +0200 Subject: [PATCH 088/226] Don't run scripts when in pause menu (Fixes #1495) --- apps/openmw/engine.cpp | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 46f384f594..f8b4c98568 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -91,7 +91,11 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) if (mUseSound) MWBase::Environment::get().getSoundManager()->update(frametime); - bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode(); + // GUI active? Most game processing will be paused, but scripts still run. + bool guiActive = MWBase::Environment::get().getWindowManager()->isGuiMode(); + + // Main menu opened? Then scripts are also paused. + bool paused = MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu); // update game state MWBase::Environment::get().getStateManager()->update (frametime); @@ -99,15 +103,18 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) if (MWBase::Environment::get().getStateManager()->getState()== MWBase::StateManager::State_Running) { - // global scripts - MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); - - // local scripts - executeLocalScripts(); - - MWBase::Environment::get().getWorld()->markCellAsUnchanged(); - if (!paused) + { + // global scripts + MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + + // local scripts + executeLocalScripts(); + + MWBase::Environment::get().getWorld()->markCellAsUnchanged(); + } + + if (!guiActive) MWBase::Environment::get().getWorld()->advanceTime( frametime*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/3600); } @@ -118,14 +125,14 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) MWBase::StateManager::State_NoGame) { MWBase::Environment::get().getMechanicsManager()->update(frametime, - paused); + guiActive); } if (MWBase::Environment::get().getStateManager()->getState()== MWBase::StateManager::State_Running) { MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr(); - if(!paused && player.getClass().getCreatureStats(player).isDead()) + if(!guiActive && player.getClass().getCreatureStats(player).isDead()) MWBase::Environment::get().getStateManager()->endGame(); } @@ -133,7 +140,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) if (MWBase::Environment::get().getStateManager()->getState()!= MWBase::StateManager::State_NoGame) { - MWBase::Environment::get().getWorld()->update(frametime, paused); + MWBase::Environment::get().getWorld()->update(frametime, guiActive); } // update GUI From 77388fe2cef8005fa074b3292bcc2b455b77cae0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 15 Jun 2014 15:15:59 +0200 Subject: [PATCH 089/226] Implement BetaComment instruction (dumps reference info) --- apps/openmw/mwscript/docs/vmformat.txt | 4 ++- apps/openmw/mwscript/miscextensions.cpp | 43 +++++++++++++++++++++++++ components/compiler/extensions0.cpp | 2 ++ components/compiler/opcodes.hpp | 2 ++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 1576bf0af9..ab69bc4b41 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -396,5 +396,7 @@ op 0x2000243: GetFactionReaction op 0x2000244: Activate, explicit op 0x2000245: ClearInfoActor op 0x2000246: ClearInfoActor, explicit +op 0x2000247: BetaComment +op 0x2000248: BetaComment, explicit -opcodes 0x2000247-0x3ffffff unused +opcodes 0x2000249-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index cd76f3b9c2..17824fe491 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -859,6 +859,47 @@ namespace MWScript } }; + template + class OpBetaComment : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::stringstream msg; + + msg << "Content file: "; + + if (ptr.getCellRef().getRefNum().mContentFile == -1) + msg << "[None]" << std::endl; + else + { + std::vector contentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); + assert (contentFiles.size() > ptr.getCellRef().getRefNum().mContentFile); + msg << contentFiles[ptr.getCellRef().getRefNum().mContentFile] << std::endl; + } + + msg << "RefID: " << ptr.getCellRef().getRefId() << std::endl; + + if (ptr.isInCell()) + { + MWWorld::CellStore* cell = ptr.getCell(); + msg << "Cell: " << MWBase::Environment::get().getWorld()->getCellName(cell) << std::endl; + + Ogre::Vector3 pos (ptr.getRefData().getPosition().pos); + msg << "Coordinates: " << pos << std::endl; + } + + std::string notes = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + if (!notes.empty()) + msg << "Notes: " << notes << std::endl; + + runtime.getContext().report(msg.str()); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); @@ -932,6 +973,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpellExplicit, new OpExplodeSpell); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcInJail, new OpGetPcInJail); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcTraveling, new OpGetPcTraveling); + interpreter.installSegment5 (Compiler::Misc::opcodeBetaComment, new OpBetaComment); + interpreter.installSegment5 (Compiler::Misc::opcodeBetaCommentExplicit, new OpBetaComment); } } } diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index b9e36db801..c4b078ae81 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -282,6 +282,8 @@ namespace Compiler extensions.registerInstruction ("enablelevitation", "", opcodeEnableLevitation); extensions.registerFunction ("getpcinjail", 'l', "", opcodeGetPcInJail); extensions.registerFunction ("getpctraveling", 'l', "", opcodeGetPcTraveling); + extensions.registerInstruction ("betacomment", "S", opcodeBetaComment, opcodeBetaCommentExplicit); + extensions.registerInstruction ("bc", "S", opcodeBetaComment, opcodeBetaCommentExplicit); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 14f4db060c..8716cdd182 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -207,6 +207,8 @@ namespace Compiler const int opcodeGetLockedExplicit = 0x20001c8; const int opcodeGetEffect = 0x20001cf; const int opcodeGetEffectExplicit = 0x20001d0; + const int opcodeBetaComment = 0x2000247; + const int opcodeBetaCommentExplicit = 0x2000248; const int opcodeAddSoulGem = 0x20001f3; const int opcodeAddSoulGemExplicit = 0x20001f4; const int opcodeRemoveSoulGem = 0x20027; From fd59a4a79e7142c8167f0c47386ee29a6cdf0563 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 15 Jun 2014 15:58:01 +0200 Subject: [PATCH 090/226] Fix some doxygen typos --- apps/opencs/model/world/columns.hpp | 2 +- apps/opencs/view/world/util.hpp | 4 ++-- apps/openmw/mwworld/refdata.hpp | 2 +- components/compiler/controlparser.hpp | 2 +- components/compiler/extensions.hpp | 2 +- components/compiler/fileparser.hpp | 2 +- components/compiler/output.hpp | 2 +- components/compiler/scanner.hpp | 2 +- components/compiler/scriptparser.hpp | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 855e89cade..1dba99b895 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -212,7 +212,7 @@ namespace CSMWorld bool hasEnums (ColumnId column); std::vector getEnums (ColumnId column); - ///< Returns an empty vector, if \æ column isn't an enum type column. + ///< Returns an empty vector, if \a column isn't an enum type column. } } diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp index 1c7e37818b..c95a249820 100644 --- a/apps/opencs/view/world/util.hpp +++ b/apps/opencs/view/world/util.hpp @@ -73,9 +73,9 @@ namespace CSVWorld ~CommandDelegateFactoryCollection(); void add (CSMWorld::ColumnBase::Display display, CommandDelegateFactory *factory); - ///< The ownership of \æ factory is transferred to *this. + ///< The ownership of \a factory is transferred to *this. /// - /// This function must not be called more than once per value of \æ display. + /// This function must not be called more than once per value of \a display. CommandDelegate *makeDelegate (CSMWorld::ColumnBase::Display display, QUndoStack& undoStack, QObject *parent) const; diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index fcd1437c97..753230e7f1 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -107,7 +107,7 @@ namespace MWWorld const LocalRotation& getLocalRotation(); void setCustomData (CustomData *data); - ///< Set custom data (potentially replacing old custom data). The ownership of \æ data is + ///< Set custom data (potentially replacing old custom data). The ownership of \a data is /// transferred to this. CustomData *getCustomData(); diff --git a/components/compiler/controlparser.hpp b/components/compiler/controlparser.hpp index 1175a0ed58..59958fd902 100644 --- a/components/compiler/controlparser.hpp +++ b/components/compiler/controlparser.hpp @@ -52,7 +52,7 @@ namespace Compiler Literals& literals); void appendCode (std::vector& code) const; - ///< store generated code in \æ code. + ///< store generated code in \a code. virtual bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner); diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp index 3f91ca357d..418cbb402b 100644 --- a/components/compiler/extensions.hpp +++ b/components/compiler/extensions.hpp @@ -105,7 +105,7 @@ namespace Compiler ///< Append code for function to \a code. void listKeywords (std::vector& keywords) const; - ///< Append all known keywords to \æ kaywords. + ///< Append all known keywords to \a kaywords. }; } diff --git a/components/compiler/fileparser.hpp b/components/compiler/fileparser.hpp index 13c7fb5376..00f96cff0a 100644 --- a/components/compiler/fileparser.hpp +++ b/components/compiler/fileparser.hpp @@ -31,7 +31,7 @@ namespace Compiler ///< Return script name. void getCode (std::vector& code) const; - ///< store generated code in \æ code. + ///< store generated code in \a code. const Locals& getLocals() const; ///< get local variable declarations. diff --git a/components/compiler/output.hpp b/components/compiler/output.hpp index 37b88cee92..c544fb715e 100644 --- a/components/compiler/output.hpp +++ b/components/compiler/output.hpp @@ -22,7 +22,7 @@ namespace Compiler Output (Locals& locals); void getCode (std::vector& code) const; - ///< store generated code in \æ code. + ///< store generated code in \a code. const Literals& getLiterals() const; diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 19f4ca96ad..344ae05825 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -119,7 +119,7 @@ namespace Compiler ///< put back a keyword token void listKeywords (std::vector& keywords); - ///< Append all known keywords to \æ kaywords. + ///< Append all known keywords to \a kaywords. }; } diff --git a/components/compiler/scriptparser.hpp b/components/compiler/scriptparser.hpp index 000244c793..edabb9c5c8 100644 --- a/components/compiler/scriptparser.hpp +++ b/components/compiler/scriptparser.hpp @@ -27,7 +27,7 @@ namespace Compiler bool end = false); void getCode (std::vector& code) const; - ///< store generated code in \æ code. + ///< store generated code in \a code. virtual bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner); From a0f9a6718ffbfe4cc5cefd87a30492468a403662 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 15 Jun 2014 16:00:29 +0200 Subject: [PATCH 091/226] Disable fProjectileThrownStoreChance when shooting at player (Fixes #1490) --- apps/openmw/mwmechanics/combat.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index be62e8315f..6feb5879b0 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -231,9 +231,13 @@ namespace MWMechanics if (damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); - float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); - if ((::rand()/(RAND_MAX+1.0)) < fProjectileThrownStoreChance/100.f) - victim.getClass().getContainerStore(victim).add(projectile, 1, victim); + // Arrows shot at enemies have a chance to turn up in their inventory + if (victim != MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); + if ((::rand()/(RAND_MAX+1.0)) < fProjectileThrownStoreChance/100.f) + victim.getClass().getContainerStore(victim).add(projectile, 1, victim); + } victim.getClass().onHit(victim, damage, true, projectile, attacker, true); } From ee2b81763ea336684c1fae4e2f8a3e22e22d696d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 15 Jun 2014 21:19:37 +0200 Subject: [PATCH 092/226] Savegame: Store AiSettings and summoned creatures CreatureStats state is now completely stored (Closes #1174) Also play VFX_Summon_Start and VFX_Summon_End visual effects. --- apps/openmw/mwbase/world.hpp | 2 + apps/openmw/mwmechanics/actors.cpp | 45 ++++++++++++++++++----- apps/openmw/mwmechanics/creaturestats.cpp | 24 ++++++++++++ apps/openmw/mwmechanics/creaturestats.hpp | 16 ++++---- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 5 +++ apps/openmw/mwworld/worldimp.hpp | 2 + components/esm/creaturestats.cpp | 40 ++++++++++++++++++++ components/esm/creaturestats.hpp | 6 +++ 9 files changed, 124 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 9fb91398da..df75ce64d1 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -515,6 +515,8 @@ namespace MWBase /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0; + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const Ogre::Vector3& worldPos) = 0; + virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 43857e5194..e9e9fbe28f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -20,6 +20,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwrender/animation.hpp" + #include "npcstats.hpp" #include "creaturestats.hpp" #include "movement.hpp" @@ -529,7 +531,8 @@ namespace MWMechanics for (std::map::iterator it = summonMap.begin(); it != summonMap.end(); ++it) { - bool found = creatureStats.mSummonedCreatures.find(it->first) != creatureStats.mSummonedCreatures.end(); + std::map& creatureMap = creatureStats.getSummonedCreatureMap(); + bool found = creatureMap.find(it->first) != creatureMap.end(); int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude; if (found != (magnitude > 0)) { @@ -563,17 +566,25 @@ namespace MWMechanics summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); int creatureActorId = summonedCreatureStats.getActorId(); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); - // TODO: VFX_SummonStart, VFX_SummonEnd - creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, creatureActorId)); + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); + if (anim) + { + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_Start"); + if (fx) + anim->addEffect("meshes\\" + fx->mModel, -1, false); + } + + creatureMap.insert(std::make_pair(it->first, creatureActorId)); } } else { // Summon lifetime has expired. Try to delete the creature. - int actorId = creatureStats.mSummonedCreatures[it->first]; - creatureStats.mSummonedCreatures.erase(it->first); + int actorId = creatureMap[it->first]; + creatureMap.erase(it->first); MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(actorId); if (!ptr.isEmpty()) @@ -581,24 +592,38 @@ namespace MWMechanics // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation // plays though, which is a rather lame exploit in vanilla. MWBase::Environment::get().getWorld()->deleteObject(ptr); - creatureStats.mSummonedCreatures.erase(it->first); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_End"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); } else { // We didn't find the creature. It's probably in an inactive cell. // Add to graveyard so we can delete it when the cell becomes active. - creatureStats.mSummonGraveyard.push_back(actorId); + std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); + graveyard.push_back(actorId); } } } } - for (std::vector::iterator it = creatureStats.mSummonGraveyard.begin(); it != creatureStats.mSummonGraveyard.end(); ) + std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); + for (std::vector::iterator it = graveyard.begin(); it != graveyard.end(); ) { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(*it); if (!ptr.isEmpty()) { - it = creatureStats.mSummonGraveyard.erase(it); + it = graveyard.erase(it); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_End"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); + MWBase::Environment::get().getWorld()->deleteObject(ptr); } else diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 7396b27355..e26e7a8ba2 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -504,6 +504,13 @@ namespace MWMechanics mSpells.writeState(state.mSpells); mActiveSpells.writeState(state.mActiveSpells); mAiSequence.writeState(state.mAiSequence); + + state.mSummonedCreatureMap = mSummonedCreatures; + state.mSummonGraveyard = mSummonGraveyard; + + state.mHasAiSettings = true; + for (int i=0; i<4; ++i) + mAiSettings[i].writeState (state.mAiSettings[i]); } void CreatureStats::readState (const ESM::CreatureStats& state) @@ -545,6 +552,13 @@ namespace MWMechanics mSpells.readState(state.mSpells); mActiveSpells.readState(state.mActiveSpells); mAiSequence.readState(state.mAiSequence); + + mSummonedCreatures = state.mSummonedCreatureMap; + mSummonGraveyard = state.mSummonGraveyard; + + if (state.mHasAiSettings) + for (int i=0; i<4; ++i) + mAiSettings[i].readState(state.mAiSettings[i]); } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) @@ -605,4 +619,14 @@ namespace MWMechanics { mDeathAnimation = index; } + + std::map& CreatureStats::getSummonedCreatureMap() + { + return mSummonedCreatures; + } + + std::vector& CreatureStats::getSummonedCreatureGraveyard() + { + return mSummonGraveyard; + } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 8b2398dfbd..d02af8fb6f 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -67,6 +67,12 @@ namespace MWMechanics // The index of the death animation that was played unsigned char mDeathAnimation; + // + std::map mSummonedCreatures; + // Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet. + // This may be necessary when the creature is in an inactive cell. + std::vector mSummonGraveyard; + protected: // These two are only set by NpcStats, but they are declared in CreatureStats to prevent using virtual methods. bool mIsWerewolf; @@ -208,6 +214,9 @@ namespace MWMechanics void setBlock(bool value); bool getBlock() const; + std::map& getSummonedCreatureMap(); + std::vector& getSummonedCreatureGraveyard(); + enum Flag { Flag_ForceRun = 1, @@ -233,13 +242,6 @@ namespace MWMechanics // TODO: Put it somewhere else? std::set mBoundItems; - // TODO: store in savegame - // TODO: encapsulate? - // - std::map mSummonedCreatures; - // Contains summoned creatures with an expired lifetime that have not been deleted yet. - std::vector mSummonGraveyard; - void writeState (ESM::CreatureStats& state) const; void readState (const ESM::CreatureStats& state); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index b2d69b79a4..297af558f7 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -210,7 +210,7 @@ public: /** * @brief Add an effect mesh attached to a bone or the insert scene node * @param model - * @param effectId An ID for this effect. Note that adding the same ID again won't add another effect. + * @param effectId An ID for this effect by which you can identify it later. If this is not wanted, set to -1. * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, * you need to remove it manually using removeEffect when the effect should end. * @param bonename Bone to attach to, or empty string to use the scene node instead diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e6049c1853..625942da77 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2704,6 +2704,11 @@ namespace MWWorld mRendering->spawnEffect(model, texture, worldPosition); } + void World::spawnEffect(const std::string &model, const std::string &textureOverride, const Vector3 &worldPos) + { + mRendering->spawnEffect(model, textureOverride, worldPos); + } + void World::explodeSpell(const Vector3 &origin, const ESM::EffectList &effects, const Ptr &caster, const std::string& id, const std::string& sourceName) { diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 8307d53a4f..ac1244652a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -585,6 +585,8 @@ namespace MWWorld /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition); + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const Ogre::Vector3& worldPos); + virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName); diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 8151091b2d..84902d8c29 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -80,6 +80,31 @@ void ESM::CreatureStats::load (ESMReader &esm) mSpells.load(esm); mActiveSpells.load(esm); mAiSequence.load(esm); + + while (esm.isNextSub("SUMM")) + { + int magicEffect; + esm.getHT(magicEffect); + int actorId; + esm.getHNT (actorId, "ACID"); + mSummonedCreatureMap[magicEffect] = actorId; + } + + while (esm.isNextSub("GRAV")) + { + int actorId; + esm.getHT(actorId); + mSummonGraveyard.push_back(actorId); + } + + mHasAiSettings = false; + esm.getHNOT(mHasAiSettings, "AISE"); + + if (mHasAiSettings) + { + for (int i=0; i<4; ++i) + mAiSettings[i].load(esm); + } } void ESM::CreatureStats::save (ESMWriter &esm) const @@ -162,4 +187,19 @@ void ESM::CreatureStats::save (ESMWriter &esm) const mSpells.save(esm); mActiveSpells.save(esm); mAiSequence.save(esm); + + for (std::map::const_iterator it = mSummonedCreatureMap.begin(); it != mSummonedCreatureMap.end(); ++it) + { + esm.writeHNT ("SUMM", it->first); + esm.writeHNT ("ACID", it->second); + } + + for (std::vector::const_iterator it = mSummonGraveyard.begin(); it != mSummonGraveyard.end(); ++it) + { + esm.writeHNT ("GRAV", *it); + } + + esm.writeHNT("AISE", mHasAiSettings); + for (int i=0; i<4; ++i) + mAiSettings[i].save(esm); } diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index c8dc97403c..628bfee0a9 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -26,6 +26,12 @@ namespace ESM AiSequence::AiSequence mAiSequence; + bool mHasAiSettings; + StatState mAiSettings[4]; + + std::map mSummonedCreatureMap; + std::vector mSummonGraveyard; + ESM::TimeStamp mTradeTime; int mGoldPool; int mActorId; From d5b97005aba2406b26aece189ea03ca44f61dae9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 15 Jun 2014 23:05:38 +0200 Subject: [PATCH 093/226] Make ESM::Faction skills optional (Fixes #1508) Also increased size of mSkills array to 7. Some factions with 7 skills can be found in the vanilla CS. The previously "mUnknown" int appears to be the 7th element of the skills array. --- apps/esmtool/record.cpp | 4 +--- apps/opencs/model/tools/factioncheck.cpp | 4 ++-- apps/opencs/model/world/data.cpp | 4 ++-- apps/openmw/mwgui/statswindow.cpp | 15 +++++++++++---- apps/openmw/mwmechanics/npcstats.cpp | 13 +++++++++++-- components/esm/loadfact.cpp | 7 +++---- components/esm/loadfact.hpp | 5 +++-- 7 files changed, 33 insertions(+), 19 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index bcf16091f4..e4ada7d189 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -682,13 +682,11 @@ void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Hidden: " << mData.mData.mIsHidden << std::endl; - if (mData.mData.mUnknown != -1) - std::cout << " Unknown: " << mData.mData.mUnknown << std::endl; std::cout << " Attribute1: " << attributeLabel(mData.mData.mAttribute[0]) << " (" << mData.mData.mAttribute[0] << ")" << std::endl; std::cout << " Attribute2: " << attributeLabel(mData.mData.mAttribute[1]) << " (" << mData.mData.mAttribute[1] << ")" << std::endl; - for (int i = 0; i != 6; i++) + for (int i = 0; i < 7; i++) if (mData.mData.mSkills[i] != -1) std::cout << " Skill: " << skillLabel(mData.mData.mSkills[i]) << " (" << mData.mData.mSkills[i] << ")" << std::endl; diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp index 42d577163f..ba8cfe1f9b 100644 --- a/apps/opencs/model/tools/factioncheck.cpp +++ b/apps/opencs/model/tools/factioncheck.cpp @@ -42,7 +42,7 @@ void CSMTools::FactionCheckStage::perform (int stage, Messages& messages) // test for non-unique skill std::map skills; // ID, number of occurrences - for (int i=0; i<6; ++i) + for (int i=0; i<7; ++i) if (faction.mData.mSkills[i]!=-1) ++skills[faction.mData.mSkills[i]]; @@ -54,4 +54,4 @@ void CSMTools::FactionCheckStage::perform (int stage, Messages& messages) } /// \todo check data members that can't be edited in the table view -} \ No newline at end of file +} diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 7f2eac20dc..0319d71acc 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -102,7 +102,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding) mFactions.addColumn (new AttributesColumn (0)); mFactions.addColumn (new AttributesColumn (1)); mFactions.addColumn (new HiddenColumn); - for (int i=0; i<6; ++i) + for (int i=0; i<7; ++i) mFactions.addColumn (new SkillsColumn (i)); mRaces.addColumn (new StringIdColumn); @@ -742,4 +742,4 @@ void CSMWorld::Data::dataChanged (const QModelIndex& topLeft, const QModelIndex& void CSMWorld::Data::rowsChanged (const QModelIndex& parent, int start, int end) { emit idListChanged(); -} \ No newline at end of file +} diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 246ade7bf2..b4bd0d7387 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -504,11 +504,18 @@ namespace MWGui text += "\n\n#DDC79E#{sFavoriteSkills}"; text += "\n#BF9959"; - for (int i=0; i<6; ++i) + bool firstSkill = true; + for (int i=0; i<7; ++i) { - text += "#{"+ESM::Skill::sSkillNameIds[faction->mData.mSkills[i]]+"}"; - if (i<5) - text += ", "; + if (faction->mData.mSkills[i] != -1) + { + if (!firstSkill) + text += ", "; + + firstSkill = false; + + text += "#{"+ESM::Skill::sSkillNameIds[faction->mData.mSkills[i]]+"}"; + } } text += "\n"; diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index d50f2c5ae4..642e1cfe18 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -361,8 +361,14 @@ bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int std::vector skills; - for (int i=0; i<6; ++i) - skills.push_back (static_cast (getSkill (faction.mData.mSkills[i]).getModified())); + for (int i=0; i<7; ++i) + { + if (faction.mData.mSkills[i] != -1) + skills.push_back (static_cast (getSkill (faction.mData.mSkills[i]).getModified())); + } + + if (skills.empty()) + return true; std::sort (skills.begin(), skills.end()); @@ -373,6 +379,9 @@ bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int if (*iter=rankData.mSkill2; } diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 0924efb174..db7e5b7b44 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -12,7 +12,7 @@ namespace ESM int& Faction::FADTstruct::getSkill (int index, bool ignored) { - if (index<0 || index>=6) + if (index<0 || index>=7) throw std::logic_error ("skill index out of range"); return mSkills[index]; @@ -20,7 +20,7 @@ namespace ESM int Faction::FADTstruct::getSkill (int index, bool ignored) const { - if (index<0 || index>=6) + if (index<0 || index>=7) throw std::logic_error ("skill index out of range"); return mSkills[index]; @@ -75,7 +75,6 @@ void Faction::save(ESMWriter &esm) const { mName.clear(); mData.mAttribute[0] = mData.mAttribute[1] = 0; - mData.mUnknown = -1; mData.mIsHidden = 0; for (int i=0; i<10; ++i) @@ -87,7 +86,7 @@ void Faction::save(ESMWriter &esm) const mRanks[i].clear(); } - for (int i=0; i<6; ++i) + for (int i=0; i<7; ++i) mData.mSkills[i] = 0; mReactions.clear(); diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 75e30a5bf1..d31670fe28 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -40,8 +40,9 @@ struct Faction RankData mRankData[10]; - int mSkills[6]; // IDs of skills this faction require - int mUnknown; // Always -1? + int mSkills[7]; // IDs of skills this faction require + // Each element will either contain an ESM::Skill index, or -1. + int mIsHidden; // 1 - hidden from player int& getSkill (int index, bool ignored = false); From 3b7119ba0d2326066e270e46efcb3efadb9256b8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 15 Jun 2014 23:09:51 +0200 Subject: [PATCH 094/226] Make Bipedal creatures always able to walk and swim (Fixes #1509) This is necessary since the vanilla CS greys out the walk/swim checkboxes when Bipedal is checked. --- apps/openmw/mwclass/creature.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 99b83a5ac2..10c318367b 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -713,7 +713,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mFlags & ESM::Creature::Swims; + return ref->mBase->mFlags & ESM::Creature::Swims || ref->mBase->mFlags & ESM::Creature::Bipedal; } bool Creature::canWalk(const MWWorld::Ptr &ptr) const @@ -721,7 +721,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mFlags & ESM::Creature::Walks; + return ref->mBase->mFlags & ESM::Creature::Walks || ref->mBase->mFlags & ESM::Creature::Bipedal; } int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name) From 0aba1088a1ff249baa85d57d84f8a5c46f84c8cc Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 00:51:58 +0200 Subject: [PATCH 095/226] Attempt to open companion window if no greetings are found (Bug #1507) --- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index be0a1b1638..8d9dc670f2 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -182,10 +182,20 @@ namespace MWDialogue win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript); mLastTopic = Misc::StringUtils::lowerCase(it->mId); - break; + return; } } } + + // No greetings found. The dialogue window should not be shown. + // If this is a companion, we must show the companion window directly (used by BM_bear_be_unique). + bool isCompanion = !mActor.getClass().getScript(mActor).empty() + && mActor.getRefData().getLocals().getIntVar(mActor.getClass().getScript(mActor), "companion"); + if (isCompanion) + { + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Companion); + MWBase::Environment::get().getWindowManager()->showCompanionWindow(mActor); + } } bool DialogueManager::compile (const std::string& cmd,std::vector& code) From 4f73e8bb7184ebdc72cdc125e1513b5a562b5017 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 00:52:40 +0200 Subject: [PATCH 096/226] Add items to player inventory upon drag start (Fixes #1507) --- apps/openmw/mwgui/container.cpp | 26 ++++++++++++++++++++++++++ apps/openmw/mwgui/inventorywindow.cpp | 5 +++++ apps/openmw/mwgui/inventorywindow.hpp | 1 + 3 files changed, 32 insertions(+) diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 4ba454a1ca..011feb4d35 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -38,6 +38,32 @@ namespace MWGui mIsOnDragAndDrop = true; mDragAndDropWidget->setVisible(true); + // If picking up an item that isn't from the player's inventory, the item gets added to player inventory backend + // immediately, even though it's still floating beneath the mouse cursor. A bit counterintuitive, + // but this is how it works in vanilla, and not doing so would break quests (BM_beasts for instance). + ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); + if (mSourceModel != playerModel) + { + MWWorld::Ptr item = mSourceModel->moveItem(mItem, mDraggedCount, playerModel); + + playerModel->update(); + + ItemModel::ModelIndex newIndex = -1; + for (unsigned int i=0; igetItemCount(); ++i) + { + if (playerModel->getItem(i).mBase == item) + { + newIndex = i; + break; + } + } + mItem = playerModel->getItem(newIndex); + mSourceModel = playerModel; + + SortFilterItemModel* playerFilterModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getSortFilterModel(); + mSourceSortModel = playerFilterModel; + } + std::string sound = mItem.mBase.getClass().getUpSoundId(mItem.mBase); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index b1e8052d8c..abea68185d 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -132,6 +132,11 @@ namespace MWGui adjustPanes(); } + SortFilterItemModel* InventoryWindow::getSortFilterModel() + { + return mSortModel; + } + TradeItemModel* InventoryWindow::getTradeModel() { return mTradeModel; diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index df563b3d46..ae7af5719b 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -37,6 +37,7 @@ namespace MWGui mPreview->rebuild(); } + SortFilterItemModel* getSortFilterModel(); TradeItemModel* getTradeModel(); ItemModel* getModel(); From edccb62c1f8f3a4d9936e6419dc68a1f0130b0f9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 01:03:43 +0200 Subject: [PATCH 097/226] Clear MyGUI's clipboard, since we don't want to use it (Fixes #1277) This would cause pasted text to appear twice, but only when using an SVN version of MyGUI, since 3.2 had a bug where it wouldn't recognize the Ctrl C/V/X keys when using separated key/textinput events. --- apps/openmw/mwinput/inputmanagerimp.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index bb18b5aa78..15b5abde64 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -547,6 +547,10 @@ namespace MWInput } if (!mControlsDisabled) mInputBinder->keyPressed (arg); + + // Clear MyGUI's clipboard, so it doesn't interfere with our own clipboard implementation. + // We do not use MyGUI's clipboard manager because it doesn't support system clipboard integration with SDL. + MyGUI::ClipboardManager::getInstance().clearClipboardData("Text"); } void InputManager::textInput(const SDL_TextInputEvent &arg) From 9ea071c1e8c8e1ba0c8f2ee111728e7b4b0f31e2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 01:46:10 +0200 Subject: [PATCH 098/226] Fix weapon incorrectly showing when saving and loading with spell equipped --- apps/openmw/mwmechanics/character.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index a5964bdf41..6847cd7254 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -509,7 +509,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim if (cls.hasInventoryStore(mPtr)) { getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); - if(mWeaponType != WeapType_None) + if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) { getWeaponGroup(mWeaponType, mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; @@ -522,8 +522,8 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim mIdleState = CharState_Idle; else { - /* FIXME: Get the actual death state used. */ - mDeathState = CharState_Death1; + int deathindex = mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation(); + playDeath(1.0f, CharacterState(CharState_Death1 + deathindex)); } } else @@ -535,12 +535,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim } - if(mDeathState != CharState_None) - { - int deathindex = mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation(); - playDeath(1.0f, CharacterState(CharState_Death1 + deathindex)); - } - else + if(mDeathState == CharState_None) refreshCurrentAnims(mIdleState, mMovementState, true); } From 345ba99c1751ad7cc95282b94d4be5fff26d8d6a Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 01:58:13 +0200 Subject: [PATCH 099/226] Fix WeaponAnimationTime not working after restoring draw state (when loading save or re-entering a cell - Fixes #1497) --- apps/openmw/mwmechanics/character.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 6847cd7254..6f0a255a9e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -514,6 +514,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim getWeaponGroup(mWeaponType, mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; mAnimation->showWeapons(true); + mAnimation->setWeaponGroup(mCurrentWeapon); } mAnimation->showCarriedLeft(mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand); } From 78f30e297057e5445e63267be4e2101e67ed99e2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 02:42:07 +0200 Subject: [PATCH 100/226] Fix a journal layout bug --- apps/openmw/mwgui/bookpage.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 2cebc8e194..659ba714c6 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -360,10 +360,16 @@ struct TypesetBookImpl::Typesetter : BookTypesetter int spaceLeft = mPageHeight - (curPageStop - curPageStart); int sectionHeight = i->mRect.height (); - if (sectionHeight <= mPageHeight) + // This is NOT equal to i->mRect.height(), which doesn't account for section breaks. + int spaceRequired = (i->mRect.bottom - curPageStop); + if (curPageStart == curPageStop) // If this is a new page, the section break is not needed + spaceRequired = i->mRect.height(); + + if (spaceRequired <= mPageHeight) { - if (sectionHeight > spaceLeft) + if (spaceRequired > spaceLeft) { + // The section won't completely fit on the current page. Finish the current page and start a new one. assert (curPageStart != curPageStop); mBook->mPages.push_back (Page (curPageStart, curPageStop)); From 468db7a9c4237a8c9b91f003ccf4576a7b3e49a5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 03:16:50 +0200 Subject: [PATCH 101/226] Remove terrain limit warning text that no longer applies --- files/settings-default.cfg | 2 -- 1 file changed, 2 deletions(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 2ed3d6cb96..65883b97fc 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -65,8 +65,6 @@ shader mode = enabled = false # Split the shadow maps, allows for a larger shadow distance -# Warning: enabling this will cause some terrain textures to disappear due to -# hitting the texture unit limit of the terrain material split = false # Increasing shadow distance will lower the shadow quality. From 1b610cdbd0f96b4d7302549ca58f0ccd80ae67d4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 03:17:31 +0200 Subject: [PATCH 102/226] Allow following NPCs to fast-travel (Fixes #1501) --- apps/openmw/mwgui/travelwindow.cpp | 25 +++++++++++-------------- apps/openmw/mwgui/travelwindow.hpp | 4 +--- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 79d50cdc59..c16603554e 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -13,6 +13,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/actionteleport.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" @@ -50,7 +51,7 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); } - void TravelWindow::addDestination(const std::string& travelId,ESM::Position pos,bool interior) + void TravelWindow::addDestination(const std::string& name,ESM::Position pos,bool interior) { int price = 0; @@ -85,13 +86,12 @@ namespace MWGui oss << price; toAdd->setUserString("price",oss.str()); - toAdd->setCaptionWithReplacing("#{sCell=" + travelId + "} - " + boost::lexical_cast(price)+"#{sgp}"); + toAdd->setCaptionWithReplacing("#{sCell=" + name + "} - " + boost::lexical_cast(price)+"#{sgp}"); toAdd->setSize(toAdd->getTextSize().width,sLineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel); - toAdd->setUserString("Destination", travelId); + toAdd->setUserString("Destination", name); toAdd->setUserData(pos); toAdd->eventMouseButtonClick += MyGUI::newDelegate(this, &TravelWindow::onTravelButtonClick); - mDestinationsWidgetMap.insert(std::make_pair (toAdd, travelId)); } void TravelWindow::clearDestinations() @@ -100,7 +100,6 @@ namespace MWGui mCurrentY = 0; while (mDestinationsView->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mDestinationsView->getChildAt(0)); - mDestinationsWidgetMap.clear(); } void TravelWindow::startTravel(const MWWorld::Ptr& actor) @@ -118,7 +117,8 @@ namespace MWGui mPtr.get()->mBase->mTransport[i].mPos.pos[1],x,y); if (cellname == "") { - cellname = MWBase::Environment::get().getWorld()->getExterior(x,y)->getCell()->mName; + MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(x,y); + cellname = MWBase::Environment::get().getWorld()->getCellName(cell); interior = false; } addDestination(cellname,mPtr.get()->mBase->mTransport[i].mPos,interior); @@ -146,12 +146,8 @@ namespace MWGui MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1); ESM::Position pos = *_sender->getUserData(); std::string cellname = _sender->getUserString("Destination"); - int x,y; bool interior = _sender->getUserString("interior") == "y"; - MWBase::Environment::get().getWorld()->positionToIndex(pos.pos[0],pos.pos[1],x,y); - if(interior) - MWBase::Environment::get().getWorld()->changeToInteriorCell(cellname, pos); - else + if (!interior) { ESM::Position playerPos = player.getRefData().getPosition(); float d = Ogre::Vector3(pos.pos[0], pos.pos[1], 0).distance( @@ -162,11 +158,12 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager ()->rest (true); } MWBase::Environment::get().getWorld()->advanceTime(hours); - - MWBase::Environment::get().getWorld()->changeToExteriorCell(pos); } - player.getClass().adjustPosition(player); + // Teleports any followers, too. + MWWorld::ActionTeleport action(interior ? cellname : "", pos); + action.execute(player); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0); diff --git a/apps/openmw/mwgui/travelwindow.hpp b/apps/openmw/mwgui/travelwindow.hpp index 5387bd690d..4328f7ac28 100644 --- a/apps/openmw/mwgui/travelwindow.hpp +++ b/apps/openmw/mwgui/travelwindow.hpp @@ -34,12 +34,10 @@ namespace MWGui MyGUI::ScrollView* mDestinationsView; - std::map mDestinationsWidgetMap; - void onCancelButtonClicked(MyGUI::Widget* _sender); void onTravelButtonClick(MyGUI::Widget* _sender); void onMouseWheel(MyGUI::Widget* _sender, int _rel); - void addDestination(const std::string& destinationID,ESM::Position pos,bool interior); + void addDestination(const std::string& name, ESM::Position pos, bool interior); void clearDestinations(); int mLastPos,mCurrentY; From 5f5c0c5bff9040898859babe81e2210e80290b94 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 03:29:02 +0200 Subject: [PATCH 103/226] Remove an unused setting --- files/settings-default.cfg | 2 -- 1 file changed, 2 deletions(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 65883b97fc..fac2a0f977 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -164,8 +164,6 @@ ui sensitivity = 1.0 camera y multiplier = 1.0 -ui y multiplier = 1.0 - always run = false [Game] From f935cfc6c973e42c57d78edb11a21e251ece7767 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 04:03:53 +0200 Subject: [PATCH 104/226] Fix crash when stealing items with the owner not around (Fixes #1512) --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 4 +++- apps/openmw/mwmechanics/mechanicsmanagerimp.hpp | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 2dea28346b..79b121b311 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -827,6 +827,8 @@ namespace MWMechanics // NOTE: int arg can be from itemTaken() so DON'T modify it, since it is // passed to reportCrime later on in this function. + // NOTE: victim may be empty + // Only player can commit crime if (player.getRefData().getHandle() != "player") return false; @@ -860,7 +862,7 @@ namespace MWMechanics mActors.getObjectsInRange(from, radius, neighbors); // victim should be considered even beyond alarm radius - if (from.squaredDistance(Ogre::Vector3(victim.getRefData().getPosition().pos)) > radius*radius) + if (!victim.isEmpty() && from.squaredDistance(Ogre::Vector3(victim.getRefData().getPosition().pos)) > radius*radius) neighbors.push_back(victim); int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 2511d5785e..714537a383 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -111,6 +111,7 @@ namespace MWMechanics /** * @brief Commit a crime. If any actors witness the crime and report it, * reportCrime will be called automatically. + * @note victim may be empty * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. * @return was the crime reported? */ From 47bd170d7eab68fea72b219e00b5636492f940d6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 04:13:36 +0200 Subject: [PATCH 105/226] Crashcatcher: create temp file in /tmp, not working directory (which may not have write access) --- apps/openmw/crashcatcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 65a0369193..7f805f2332 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -128,7 +128,7 @@ static void gdb_info(pid_t pid) int fd; /* Create a temp file to put gdb commands into */ - strcpy(respfile, "gdb-respfile-XXXXXX"); + strcpy(respfile, "/tmp/gdb-respfile-XXXXXX"); if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL) { fprintf(f, "attach %d\n" From 2ce8323a422776f957067fde24ebe40317f17d11 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 16:34:00 +0200 Subject: [PATCH 106/226] Fix getDistance not detecting references in inactive cells properly --- apps/openmw/mwscript/interpretercontext.cpp | 12 ++++++++++-- apps/openmw/mwscript/interpretercontext.hpp | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index ebe88c3a42..028f83a61a 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -378,12 +378,20 @@ namespace MWScript float InterpreterContext::getDistance (const std::string& name, const std::string& id) const { - const MWWorld::Ptr ref2 = getReference (id, false, false); + // NOTE: id may be empty, indicating an implicit reference + + MWWorld::Ptr ref2; + + if (id.empty()) + ref2 = getReference("", true, true); + else + ref2 = MWBase::Environment::get().getWorld()->searchPtr(id, true); + // If either actor is in a non-active cell, return a large value (just like vanilla) if (ref2.isEmpty()) return std::numeric_limits().max(); - const MWWorld::Ptr ref = getReference (name, false, false); + const MWWorld::Ptr ref = MWBase::Environment::get().getWorld()->searchPtr(name, true); if (ref.isEmpty()) return std::numeric_limits().max(); diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index fdf9d6424d..99026392ed 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -118,6 +118,7 @@ namespace MWScript virtual void stopScript (const std::string& name); virtual float getDistance (const std::string& name, const std::string& id = "") const; + ///< @note if \a id is empty, assumes an implicit reference bool hasBeenActivated (const MWWorld::Ptr& ptr); ///< \attention Calling this function for the right reference will mark the action as From d11a5e19f79b9ed3cf0e49cde5b1b063a9155fb9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 17:14:35 +0200 Subject: [PATCH 107/226] Fix positionCell not properly teleporting actors from inactive to active cells (Fixes #1516) --- apps/openmw/mwworld/worldimp.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 625942da77..4386f5b0de 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -989,9 +989,18 @@ namespace MWWorld } else { - if (!mWorldScene->isCellActive(*currCell)) - ptr.getClass().copyToCell(ptr, *newCell, pos); - else if (!mWorldScene->isCellActive(*newCell)) + if (!mWorldScene->isCellActive(*currCell) && mWorldScene->isCellActive(*newCell)) + { + MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, *newCell, pos); + mWorldScene->addObjectToScene(newPtr); + + std::string script = newPtr.getClass().getScript(newPtr); + if (!script.empty()) { + mLocalScripts.add(script, newPtr); + } + addContainerScripts(newPtr, newCell); + } + else if (!mWorldScene->isCellActive(*newCell) && mWorldScene->isCellActive(*currCell)) { mWorldScene->removeObjectFromScene(ptr); mLocalScripts.remove(ptr); From 28feb260eb8fd675dca04f8da2dc0dc88e3e043a Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 19:34:53 +0200 Subject: [PATCH 108/226] Implement disposition/distance based aggression (Fixes #1520) --- apps/openmw/mwbase/mechanicsmanager.hpp | 2 ++ apps/openmw/mwclass/creature.cpp | 2 +- apps/openmw/mwclass/npc.cpp | 3 +-- apps/openmw/mwdialogue/filter.cpp | 3 ++- apps/openmw/mwmechanics/actors.cpp | 22 +++++++--------- .../mwmechanics/mechanicsmanagerimp.cpp | 25 +++++++++++++++++++ .../mwmechanics/mechanicsmanagerimp.hpp | 2 ++ 7 files changed, 42 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 30a576a15f..07d4eb2bc0 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -191,6 +191,8 @@ namespace MWBase virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; virtual void clear() = 0; + + virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 10c318367b..59c1087f98 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -345,7 +345,7 @@ namespace MWClass getCreatureStats(ptr).setAttacked(true); // Self defense - if (!attacker.isEmpty() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() < 80 + if (!attacker.isEmpty() && !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker) && (canWalk(ptr) || canFly(ptr) || canSwim(ptr))) // No retaliation for totally static creatures // (they have no movement or attacks anyway) MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 6d5ff9f7f6..2b4c54e245 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -624,9 +624,8 @@ namespace MWClass // NOTE: 'object' and/or 'attacker' may be empty. // Attacking peaceful NPCs is a crime - // anything below 80 is considered peaceful (see Actors::updateActor) if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).isHostile() && - ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() < 80) + !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker)) MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); getCreatureStats(ptr).setAttacked(true); diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index d301e88aac..08cdb1d003 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -530,7 +530,8 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_ShouldAttack: - return mActor.getClass().getCreatureStats(mActor).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() >= 80; + return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, + MWBase::Environment::get().getWorld()->getPlayerPtr()); case SelectWrapper::Function_CreatureTargetted: diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e9e9fbe28f..ddb3087dd5 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -201,32 +201,28 @@ namespace MWMechanics || (!actor1.getClass().canSwim(actor1) && MWBase::Environment::get().getWorld()->isSwimming(actor2)))) // creature can't swim to target return; - float fight; + bool aggressive; if (againstPlayer) - fight = actor1.getClass().getCreatureStats(actor1).getAiSetting(CreatureStats::AI_Fight).getModified(); + aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); else { - fight = 0; + aggressive = false; // if one of actors is creature then we should make a decision to start combat or not // NOTE: function doesn't take into account combat between 2 creatures if (!actor1.getClass().isNpc()) { // if creature is hostile then it is necessarily to start combat - if (creatureStats.isHostile()) fight = 100; - else fight = creatureStats.getAiSetting(CreatureStats::AI_Fight).getModified(); + if (creatureStats.isHostile()) aggressive = true; + else aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); } } - ESM::Position actor1Pos = actor1.getRefData().getPosition(); - ESM::Position actor2Pos = actor2.getRefData().getPosition(); - float d = Ogre::Vector3(actor1Pos.pos).distance(Ogre::Vector3(actor2Pos.pos)); - - if( (fight == 100 && d <= 5000) - || (fight >= 95 && d <= 3000) - || (fight >= 90 && d <= 2000) - || (fight >= 80 && d <= 1000)) + if(aggressive) { + const ESM::Position& actor1Pos = actor2.getRefData().getPosition(); + const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); + float d = Ogre::Vector3(actor1Pos.pos).distance(Ogre::Vector3(actor2Pos.pos)); if (againstPlayer || actor2.getClass().getCreatureStats(actor2).getAiSequence().canAddTarget(actor2Pos, d)) { bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 79b121b311..4d69301352 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1098,4 +1098,29 @@ namespace MWMechanics { mActors.clear(); } + + bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) + { + Ogre::Vector3 pos1 (ptr.getRefData().getPosition().pos); + Ogre::Vector3 pos2 (target.getRefData().getPosition().pos); + + float d = pos1.distance(pos2); + + static int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get().find( + "iFightDistanceBase")->getInt(); + static float fFightDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get().find( + "fFightDistanceMultiplier")->getFloat(); + static float fFightDispMult = MWBase::Environment::get().getWorld()->getStore().get().find( + "fFightDispMult")->getFloat(); + + int disposition = 50; + if (ptr.getClass().isNpc()) + disposition = getDerivedDisposition(ptr); + + int fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified() + + (iFightDistanceBase - fFightDistanceMultiplier * d) + + ((50 - disposition) * fFightDispMult); + + return (fight >= 100); + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 714537a383..677d60ae58 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -156,6 +156,8 @@ namespace MWMechanics virtual void readRecord (ESM::ESMReader& reader, int32_t type); virtual void clear(); + + virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target); }; } From cfea7736d94fa0dacb16c3754218b7e3dd89cc81 Mon Sep 17 00:00:00 2001 From: Fil Krynicki Date: Mon, 16 Jun 2014 17:08:02 -0400 Subject: [PATCH 109/226] WIP fix Bug is fixed, but appears to have broken vision underwater. Notes: + basically fixed by darkening the colour of water such that it is darker than refracted terrain + also disabled sunlight scattering at night. This may actually be desirable, but given there is no visible moon it seems unlikely to make much sense, and blends a lot of green into the water colour appearing luminescent at night --- apps/openmw/mwrender/refraction.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 11 ++++++----- apps/openmw/mwrender/renderingmanager.hpp | 2 +- apps/openmw/mwrender/sky.cpp | 4 ++-- apps/openmw/mwrender/sky.hpp | 2 +- apps/openmw/mwworld/weather.cpp | 4 +++- files/materials/underwater.h | 2 +- files/materials/water.shader | 4 ++-- 8 files changed, 17 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwrender/refraction.cpp b/apps/openmw/mwrender/refraction.cpp index f22968e9d0..7c64ea1d8c 100644 --- a/apps/openmw/mwrender/refraction.cpp +++ b/apps/openmw/mwrender/refraction.cpp @@ -35,7 +35,7 @@ namespace MWRender vp->setShadowsEnabled(false); vp->setVisibilityMask(RV_Actors + RV_Misc + RV_Statics + RV_StaticsSmall + RV_Terrain + RV_Sky); vp->setMaterialScheme("water_refraction"); - vp->setBackgroundColour (Ogre::ColourValue(0.18039, 0.23137, 0.25490)); + vp->setBackgroundColour (Ogre::ColourValue(0.090195, 0.115685, 0.12745)); mRenderTarget->setAutoUpdated(true); mRenderTarget->addListener(this); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 8a22c63c64..3ceda6d216 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -522,9 +522,10 @@ void RenderingManager::applyFog (bool underwater) } else { - mRendering.getScene()->setFog (FOG_LINEAR, Ogre::ColourValue(0.18039, 0.23137, 0.25490), 0, 0, 1000); - mRendering.getViewport()->setBackgroundColour (Ogre::ColourValue(0.18039, 0.23137, 0.25490)); - mWater->setViewportBackground (Ogre::ColourValue(0.18039, 0.23137, 0.25490)); + //Ogre::ColourValue clv(0.090195, 0.115685, 0.12745); + mRendering.getScene()->setFog (FOG_LINEAR, Ogre::ColourValue(clv)); + mRendering.getViewport()->setBackgroundColour (Ogre::ColourValue(clv)); + mWater->setViewportBackground (Ogre::ColourValue(clv)); } } @@ -631,12 +632,12 @@ void RenderingManager::sunDisable(bool real) } } -void RenderingManager::setSunDirection(const Ogre::Vector3& direction) +void RenderingManager::setSunDirection(const Ogre::Vector3& direction, bool is_moon) { // direction * -1 (because 'direction' is camera to sun vector and not sun to camera), if (mSun) mSun->setDirection(Vector3(-direction.x, -direction.y, -direction.z)); - mSkyManager->setSunDirection(direction); + mSkyManager->setSunDirection(direction, is_moon); } void RenderingManager::setGlare(bool glare) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index f539f9270f..ea7905cf5a 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -145,7 +145,7 @@ public: void setAmbientColour(const Ogre::ColourValue& colour); void setSunColour(const Ogre::ColourValue& colour); - void setSunDirection(const Ogre::Vector3& direction); + void setSunDirection(const Ogre::Vector3& direction, bool is_moon); void sunEnable(bool real); ///< @param real whether or not to really disable the sunlight (otherwise just set diffuse to 0) void sunDisable(bool real); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 90c08c2995..b10b7fd5fc 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -546,14 +546,14 @@ void SkyManager::sunDisable() mSunEnabled = false; } -void SkyManager::setSunDirection(const Vector3& direction) +void SkyManager::setSunDirection(const Vector3& direction, bool is_moon) { if (!mCreated) return; mSun->setPosition(direction); mSunGlare->setPosition(direction); float height = direction.z; - float fade = ( height > 0.5) ? 1.0 : height * 2; + float fade = is_moon ? 0.0 : (( height > 0.5) ? 1.0 : height * 2); sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty(new sh::Vector2(fade, height))); } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 965907a979..35f2a267e5 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -148,7 +148,7 @@ namespace MWRender void sunDisable(); - void setSunDirection(const Ogre::Vector3& direction); + void setSunDirection(const Ogre::Vector3& direction, bool is_moon); void setMasserDirection(const Ogre::Vector3& direction); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 25f523bee9..64d1385da3 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -378,11 +378,13 @@ void WeatherManager::update(float duration) int facing = (mHour > 13.f) ? 1 : -1; + bool sun_is_moon = mHour >= mNightStart || mHour <= mSunriseTime; + Vector3 final( (height - 1) * facing, (height - 1) * facing, height); - mRendering->setSunDirection(final); + mRendering->setSunDirection(final, sun_is_moon); /* * TODO: import separated fadeInStart/Finish, fadeOutStart/Finish diff --git a/files/materials/underwater.h b/files/materials/underwater.h index 8474f299d0..332a0fd7d7 100644 --- a/files/materials/underwater.h +++ b/files/materials/underwater.h @@ -1,4 +1,4 @@ -#define UNDERWATER_COLOUR float3(0.18039, 0.23137, 0.25490) +#define UNDERWATER_COLOUR float3(0.090195, 0.115685, 0.12745) #define VISIBILITY 1000.0 // how far you can look through water diff --git a/files/materials/water.shader b/files/materials/water.shader index 87e90a2916..701154ffa3 100644 --- a/files/materials/water.shader +++ b/files/materials/water.shader @@ -340,7 +340,7 @@ #if REFRACTION float3 refraction = shSample(refractionMap, (screenCoords-(normal.xy*REFR_BUMP))*1.0).rgb; - + // brighten up the refraction underwater refraction = (cameraPos.z < 0) ? shSaturate(refraction * 1.5) : refraction; #endif @@ -351,7 +351,7 @@ #if REFRACTION shOutputColour(0).xyz = shLerp( shLerp(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpecular.xyz; #else - shOutputColour(0).xyz = shLerp(reflection, float3(0.18039, 0.23137, 0.25490), (1.0-fresnel)*0.5) + specular * sunSpecular.xyz; + shOutputColour(0).xyz = shLerp(reflection, float3(0.090195, 0.115685, 0.12745), (1.0-fresnel)*0.5) + specular * sunSpecular.xyz; #endif // fog float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); From c36fc48c473d03b4d2f2416425ad93052093f09a Mon Sep 17 00:00:00 2001 From: Fil Krynicki Date: Mon, 16 Jun 2014 17:15:49 -0400 Subject: [PATCH 110/226] Fixed underwater issue I had accidentally deleted some interpolation parameters. --- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3ceda6d216..2c1b663dc0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -522,8 +522,8 @@ void RenderingManager::applyFog (bool underwater) } else { - //Ogre::ColourValue clv(0.090195, 0.115685, 0.12745); - mRendering.getScene()->setFog (FOG_LINEAR, Ogre::ColourValue(clv)); + Ogre::ColourValue clv(0.090195, 0.115685, 0.12745); + mRendering.getScene()->setFog (FOG_LINEAR, Ogre::ColourValue(clv), 0, 0, 1000); mRendering.getViewport()->setBackgroundColour (Ogre::ColourValue(clv)); mWater->setViewportBackground (Ogre::ColourValue(clv)); } From 8ac019611dc3ae875e5a86a4e6e567bffb0d1b99 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Jun 2014 20:23:57 +0200 Subject: [PATCH 111/226] Fix Potion use action removing the potion even when the action is not executed (Fixes #1521) --- apps/openmw/mwclass/potion.cpp | 7 +------ apps/openmw/mwworld/actionapply.cpp | 20 +++++++++++++------- apps/openmw/mwworld/actionapply.hpp | 4 ++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index d7cb0cd9bb..440121d35b 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -166,13 +166,8 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPlayerPtr(); - - // remove used potion (assume it is present in inventory) - ptr.getContainerStore()->remove(ptr, 1, actor); - boost::shared_ptr action ( - new MWWorld::ActionApply (actor, ref->mBase->mId)); + new MWWorld::ActionApply (ptr, ref->mBase->mId)); action->setSound ("Drink"); diff --git a/apps/openmw/mwworld/actionapply.cpp b/apps/openmw/mwworld/actionapply.cpp index 6b12cc3e65..bfd64c85d8 100644 --- a/apps/openmw/mwworld/actionapply.cpp +++ b/apps/openmw/mwworld/actionapply.cpp @@ -6,30 +6,36 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/containerstore.hpp" + namespace MWWorld { - ActionApply::ActionApply (const Ptr& target, const std::string& id) - : Action (false, target), mId (id) + ActionApply::ActionApply (const Ptr& object, const std::string& id) + : Action (false, object), mId (id) {} void ActionApply::executeImp (const Ptr& actor) { MWBase::Environment::get().getWorld()->breakInvisibility(actor); - getTarget().getClass().apply (getTarget(), mId, actor); + actor.getClass().apply (actor, mId, actor); + + actor.getClass().getContainerStore(actor).remove(getTarget(), 1, actor); } - ActionApplyWithSkill::ActionApplyWithSkill (const Ptr& target, const std::string& id, + ActionApplyWithSkill::ActionApplyWithSkill (const Ptr& object, const std::string& id, int skillIndex, int usageType) - : Action (false, target), mId (id), mSkillIndex (skillIndex), mUsageType (usageType) + : Action (false, object), mId (id), mSkillIndex (skillIndex), mUsageType (usageType) {} void ActionApplyWithSkill::executeImp (const Ptr& actor) { MWBase::Environment::get().getWorld()->breakInvisibility(actor); - if (getTarget().getClass().apply (getTarget(), mId, actor) && mUsageType!=-1) - getTarget().getClass().skillUsageSucceeded (actor, mSkillIndex, mUsageType); + if (actor.getClass().apply (actor, mId, actor) && mUsageType!=-1) + actor.getClass().skillUsageSucceeded (actor, mSkillIndex, mUsageType); + + actor.getClass().getContainerStore(actor).remove(getTarget(), 1, actor); } } diff --git a/apps/openmw/mwworld/actionapply.hpp b/apps/openmw/mwworld/actionapply.hpp index 669a190675..5294745a61 100644 --- a/apps/openmw/mwworld/actionapply.hpp +++ b/apps/openmw/mwworld/actionapply.hpp @@ -16,7 +16,7 @@ namespace MWWorld public: - ActionApply (const Ptr& target, const std::string& id); + ActionApply (const Ptr& object, const std::string& id); }; class ActionApplyWithSkill : public Action @@ -29,7 +29,7 @@ namespace MWWorld public: - ActionApplyWithSkill (const Ptr& target, const std::string& id, + ActionApplyWithSkill (const Ptr& object, const std::string& id, int skillIndex, int usageType); }; } From ffb6f5d555fd850ecdd2a9ff0930f58c309f0161 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 01:10:37 +0200 Subject: [PATCH 112/226] Use fFight GMSTs to control attacks in response to crimes --- apps/openmw/mwbase/mechanicsmanager.hpp | 5 +- apps/openmw/mwmechanics/actors.cpp | 11 -- apps/openmw/mwmechanics/aisequence.cpp | 5 + .../mwmechanics/mechanicsmanagerimp.cpp | 137 ++++++++++++------ .../mwmechanics/mechanicsmanagerimp.hpp | 4 +- 5 files changed, 105 insertions(+), 57 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 07d4eb2bc0..91d0ed9bf5 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -120,6 +120,7 @@ namespace MWBase /** * @brief Commit a crime. If any actors witness the crime and report it, * reportCrime will be called automatically. + * @note victim may be empty * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. * @return was the crime reported? */ @@ -192,7 +193,9 @@ namespace MWBase virtual void clear() = 0; - virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; + /// @param bias Can be used to add an additional aggression bias towards the target, + /// making it more likely for the function to return true. + virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0) = 0; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index ddb3087dd5..5e46a11a3b 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -789,7 +789,6 @@ namespace MWMechanics if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.isHostile()) { - /// \todo Move me! I shouldn't be here... const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); float cutoff = float(esmStore.get().find("iCrimeThreshold")->getInt()); // Force dialogue on sight if bounty is greater than the cutoff @@ -817,7 +816,6 @@ namespace MWMechanics creatureStats.getAiSequence().stopCombat(); // Reset factors to attack - // TODO: Not a complete list, disposition changes? creatureStats.setHostile(false); creatureStats.setAttacked(false); creatureStats.setAlarmed(false); @@ -825,15 +823,6 @@ namespace MWMechanics // Update witness crime id npcStats.setCrimeId(-1); } - else if (!creatureStats.isHostile() && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue) - { - if (ptr.getClass().isClass(ptr, "Guard")) - creatureStats.getAiSequence().stack(AiPursue(player), ptr); - else - { - MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); - } - } } } } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 05723c5756..3aeeee65a4 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -221,6 +221,11 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) { return; // target is already pursued } + if((*iter)->getTypeId() == AiPackage::TypeIdCombat && package.getTypeId() == AiPackage::TypeIdCombat + && static_cast(*iter)->getTarget() == static_cast(&package)->getTarget()) + { + return; // already in combat with this actor + } else if ((*iter)->getTypeId() == AiPackage::TypeIdWander) static_cast(*iter)->setReturnPosition(Ogre::Vector3(actor.getRefData().getPosition().pos)); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 4d69301352..cb59d188f7 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -14,6 +14,7 @@ #include "../mwworld/player.hpp" #include "../mwmechanics/aicombat.hpp" +#include "../mwmechanics/aipursue.hpp" #include @@ -836,7 +837,7 @@ namespace MWMechanics const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); // What amount of alarm did this crime generate? - int alarm; + int alarm = 0; if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) alarm = esmStore.get().find("iAlarmTresspass")->getInt(); else if (type == OT_Pickpocket) @@ -847,17 +848,14 @@ namespace MWMechanics alarm = esmStore.get().find("iAlarmKilling")->getInt(); else if (type == OT_Theft) alarm = esmStore.get().find("iAlarmStealing")->getInt(); - else - return false; - // Innocent until proven guilty bool reported = false; // Find all the actors within the alarm radius std::vector neighbors; Ogre::Vector3 from = Ogre::Vector3(player.getRefData().getPosition().pos); - int radius = esmStore.get().find("fAlarmRadius")->getInt(); + float radius = esmStore.get().find("fAlarmRadius")->getFloat(); mActors.getObjectsInRange(from, radius, neighbors); @@ -865,9 +863,9 @@ namespace MWMechanics if (!victim.isEmpty() && from.squaredDistance(Ogre::Vector3(victim.getRefData().getPosition().pos)) > radius*radius) neighbors.push_back(victim); - int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); + bool victimAware = false; - // Find actors who witnessed the crime + // Find actors who directly witnessed the crime for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) { if (*it == player) continue; // not the player @@ -875,22 +873,13 @@ namespace MWMechanics // Was the crime seen? if (MWBase::Environment::get().getWorld()->getLOS(player, *it) && awarenessCheck(player, *it) ) { - // TODO: Add more messages + if (*it == victim) + victimAware = true; + + // TODO: are there other messages? if (type == OT_Theft) MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); - if (*it == victim) - { - // Self-defense - // The victim is aware of the criminal/assailant. If being assaulted, fight back now - // (regardless of whether the assault is reported or not) - // This applies to both NPCs and creatures - - // ... except if this is a guard: then the player is given a chance to pay a fine / go to jail instead - if (type == OT_Assault && !it->getClass().isClass(*it, "guard")) - MWBase::Environment::get().getMechanicsManager()->startCombat(victim, player); - } - // Crime reporting only applies to NPCs if (!it->getClass().isNpc()) continue; @@ -899,32 +888,19 @@ namespace MWMechanics if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) { reported = true; - - // Tell everyone, including yourself - for (std::vector::iterator it1 = neighbors.begin(); it1 != neighbors.end(); ++it1) - { - if ( *it1 == player - || !it1->getClass().isNpc()) continue; // not the player and is an NPC - - // Will other witnesses paticipate in crime - if ( it1->getClass().getCreatureStats(*it1).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm - || type == OT_Assault ) - { - it1->getClass().getNpcStats(*it1).setCrimeId(id); - } - - // Mark as Alarmed for dialogue - it1->getClass().getCreatureStats(*it1).setAlarmed(true); - } } } } + if (reported) reportCrime(player, victim, type, arg); + else if (victimAware && !victim.isEmpty() && type == OT_Assault) + startCombat(victim, player); + return reported; } - void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) + void MechanicsManager::reportCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg) { const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); @@ -944,7 +920,7 @@ namespace MWMechanics } MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}"); - ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty() + player.getClass().getNpcStats(player).setBounty(player.getClass().getNpcStats(player).getBounty() + arg); // If committing a crime against a faction member, expell from the faction @@ -953,9 +929,81 @@ namespace MWMechanics std::string factionID; if(!victim.getClass().getNpcStats(victim).getFactionRanks().empty()) factionID = victim.getClass().getNpcStats(victim).getFactionRanks().begin()->first; - if (ptr.getClass().getNpcStats(ptr).isSameFaction(victim.getClass().getNpcStats(victim))) + if (player.getClass().getNpcStats(player).isSameFaction(victim.getClass().getNpcStats(victim))) { - ptr.getClass().getNpcStats(ptr).expell(factionID); + player.getClass().getNpcStats(player).expell(factionID); + } + } + + // Make surrounding actors within alarm distance respond to the crime + std::vector neighbors; + + const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + + Ogre::Vector3 from = Ogre::Vector3(player.getRefData().getPosition().pos); + float radius = esmStore.get().find("fAlarmRadius")->getFloat(); + + mActors.getObjectsInRange(from, radius, neighbors); + + // victim should be considered even beyond alarm radius + if (!victim.isEmpty() && from.squaredDistance(Ogre::Vector3(victim.getRefData().getPosition().pos)) > radius*radius) + neighbors.push_back(victim); + + int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); + + // What amount of provocation did this crime generate? + // Controls whether witnesses will engage combat with the criminal. + int fight = 0; + if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) + fight = esmStore.get().find("iFightTrespass")->getInt(); + else if (type == OT_Pickpocket) + fight = esmStore.get().find("iFightPickpocket")->getInt(); + else if (type == OT_Assault) // Note: iFightAttack is for the victim, iFightAttacking for witnesses? + fight = esmStore.get().find("iFightAttack")->getInt(); + else if (type == OT_Murder) + fight = esmStore.get().find("iFightKilling")->getInt(); + else if (type == OT_Theft) + fight = esmStore.get().find("fFightStealing")->getFloat(); + + const int iFightAttacking = esmStore.get().find("iFightAttacking")->getInt(); + + // Tell everyone (including the original reporter) in alarm range + for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) + { + if ( *it == player + || !it->getClass().isNpc()) continue; + + int aggression = fight; + + // Note: iFightAttack is used for the victim, iFightAttacking for witnesses? + if (*it != victim && type == OT_Assault) + aggression = iFightAttacking; + + if (it->getClass().isClass(*it, "guard")) + { + // Mark as Alarmed for dialogue + it->getClass().getCreatureStats(*it).setAlarmed(true); + + // Set the crime ID, which we will use to calm down participants + // once the bounty has been paid. + it->getClass().getNpcStats(*it).setCrimeId(id); + + it->getClass().getCreatureStats(*it).getAiSequence().stack(AiPursue(player), *it); + } + else + { + bool aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(*it, player, aggression); + if (aggressive) + { + startCombat(*it, player); + + // Set the crime ID, which we will use to calm down participants + // once the bounty has been paid. + it->getClass().getNpcStats(*it).setCrimeId(id); + + // Mark as Alarmed for dialogue + it->getClass().getCreatureStats(*it).setAlarmed(true); + } } } } @@ -1099,7 +1147,7 @@ namespace MWMechanics mActors.clear(); } - bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) + bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target, int bias) { Ogre::Vector3 pos1 (ptr.getRefData().getPosition().pos); Ogre::Vector3 pos2 (target.getRefData().getPosition().pos); @@ -1117,9 +1165,10 @@ namespace MWMechanics if (ptr.getClass().isNpc()) disposition = getDerivedDisposition(ptr); - int fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified() + int fight = std::max(0.f, ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified() + (iFightDistanceBase - fFightDistanceMultiplier * d) - + ((50 - disposition) * fFightDispMult); + + ((50 - disposition) * fFightDispMult)) + + bias; return (fight >= 100); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 677d60ae58..0fee72b77e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -157,7 +157,9 @@ namespace MWMechanics virtual void clear(); - virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target); + /// @param bias Can be used to add an additional aggression bias towards the target, + /// making it more likely for the function to return true. + virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0); }; } From ec66484472b235ed1cfcd310b312683daa3a0544 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 01:19:14 +0200 Subject: [PATCH 113/226] Fix forceGreeting with explicit references (Fixes #1518) --- components/compiler/extensions0.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index c4b078ae81..5733a41702 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -167,7 +167,6 @@ namespace Compiler extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex); extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic); extensions.registerInstruction ("choice", "/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); - extensions.registerInstruction("forcegreeting","",opcodeForceGreeting); extensions.registerInstruction("forcegreeting","",opcodeForceGreeting, opcodeForceGreetingExplicit); extensions.registerInstruction("goodbye", "", opcodeGoodbye); From 3801dfb4bab2ae5fdde3c24e65edeed006fdc2d1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 01:53:09 +0200 Subject: [PATCH 114/226] Add delay to sneak icon update and skill progress (Fixes #1321) --- apps/openmw/mwmechanics/actors.cpp | 58 ++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 5e46a11a3b..dcbb6b8bde 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1074,6 +1074,9 @@ namespace MWMechanics isBattleMusic = true; } + static float sneakTimer = 0.f; // times update of sneak icon + static float sneakSkillTimer = 0.f; // times sneak skill progress from "avoid notice" + // if player is in sneak state see if anyone detects him if (player.getClass().getCreatureStats(player).getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak)) { @@ -1081,27 +1084,54 @@ namespace MWMechanics const int radius = esmStore.get().find("fSneakUseDist")->getInt(); bool detected = false; - for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + static float fSneakUseDelay = esmStore.get().find("fSneakUseDelay")->getFloat(); + + if (sneakTimer >= fSneakUseDelay) + sneakTimer = 0.f; + + if (sneakTimer == 0.f) { - if (iter->first == player) // not the player - continue; + // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress. + bool avoidedNotice = false; - // is the player in range and can they be detected - if ( (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(player.getRefData().getPosition().pos)) <= radius*radius) - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, iter->first) - && MWBase::Environment::get().getWorld()->getLOS(player, iter->first)) + for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { - detected = true; - MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); - break; - } - } + if (iter->first == player) // not the player + continue; - if (!detected) - MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); + // is the player in range and can they be detected + if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(player.getRefData().getPosition().pos)) <= radius*radius + && MWBase::Environment::get().getWorld()->getLOS(player, iter->first)) + { + if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, iter->first)) + { + detected = true; + avoidedNotice = false; + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + break; + } + else if (!detected) + avoidedNotice = true; + } + } + + if (sneakSkillTimer >= fSneakUseDelay) + sneakSkillTimer = 0.f; + + if (avoidedNotice && sneakSkillTimer == 0.f) + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); + + if (!detected) + MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); + } + sneakTimer += duration; + sneakSkillTimer += duration; } else + { + sneakTimer = 0.f; MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + } } } void Actors::restoreDynamicStats(bool sleep) From 2477456f993acdc1f26215da51603d36b7acdfcd Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 03:54:41 +0200 Subject: [PATCH 115/226] Implement Murder crimes and OnMurder instruction (Fixes #1315) --- apps/openmw/mwclass/npc.cpp | 13 ++++++ apps/openmw/mwmechanics/activespells.hpp | 8 ++-- apps/openmw/mwmechanics/actors.cpp | 40 +++++++++++++++++++ apps/openmw/mwmechanics/creaturestats.cpp | 19 ++++++++- apps/openmw/mwmechanics/creaturestats.hpp | 7 ++++ .../mwmechanics/mechanicsmanagerimp.cpp | 15 +++++-- apps/openmw/mwscript/docs/vmformat.txt | 4 +- apps/openmw/mwscript/statsextensions.cpp | 21 ++++++++++ components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 2 + components/esm/creaturestats.cpp | 6 +++ components/esm/creaturestats.hpp | 1 + 12 files changed, 128 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 2b4c54e245..1a33747f21 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -628,6 +628,8 @@ namespace MWClass !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker)) MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); + bool wasDead = getCreatureStats(ptr).isDead(); + getCreatureStats(ptr).setAttacked(true); if(!successful) @@ -751,6 +753,17 @@ namespace MWClass fatigue.setCurrent(fatigue.getCurrent() - damage, true); getCreatureStats(ptr).setFatigue(fatigue); } + + if (!wasDead && getCreatureStats(ptr).isDead()) + { + // NPC was killed + // Simple check for who attacked first: if the player attacked first, a crimeId should be set + // Doesn't handle possible edge case where no one reported the assault, but in such a case, + // for bystanders it is not possible to tell who attacked first, anyway. + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 && ptr != player) + MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder); + } } void Npc::block(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 6c4c93128f..9c1a5e613b 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -40,6 +40,10 @@ namespace MWMechanics void readState (const ESM::ActiveSpells& state); void writeState (ESM::ActiveSpells& state) const; + TIterator begin() const; + + TIterator end() const; + private: mutable TContainer mSpells; @@ -57,10 +61,6 @@ namespace MWMechanics const TContainer& getActiveSpells() const; - TIterator begin() const; - - TIterator end() const; - public: ActiveSpells(); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index dcbb6b8bde..0c5a2abec0 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -342,6 +342,8 @@ namespace MWMechanics CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr); const MagicEffects &effects = creatureStats.getMagicEffects(); + bool wasDead = creatureStats.isDead(); + // attributes for(int i = 0;i < ESM::Attribute::Length;++i) { @@ -454,6 +456,44 @@ namespace MWMechanics } creatureStats.setHealth(health); + if (!wasDead && creatureStats.isDead()) + { + // The actor was killed by a magic effect. Figure out if the player was responsible for it. + const ActiveSpells& spells = creatureStats.getActiveSpells(); + for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + const ActiveSpells::ActiveSpellParams& spell = it->second; + for (std::vector::const_iterator effectIt = spell.mEffects.begin(); + effectIt != spell.mEffects.end(); ++effectIt) + { + int effectId = effectIt->mEffectId; + bool isDamageEffect = false; + for (unsigned int i=0; igetPlayerPtr(); + MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId); + if (isDamageEffect && caster == player) + { + // Simple check for who attacked first: if the player attacked first, a crimeId should be set + // Doesn't handle possible edge case where no one reported the assault, but in such a case, + // for bystanders it is not possible to tell who attacked first, anyway. + if (ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 + && ptr != player) + MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder); + break; + } + } + } + } + // TODO: dirty flag for magic effects to avoid some unnecessary work below? // Update bound effects diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index e26e7a8ba2..af35a109a6 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -14,7 +14,7 @@ namespace MWMechanics int CreatureStats::sActorId = 0; CreatureStats::CreatureStats() - : mLevel (0), mDead (false), mDied (false), mFriendlyHits (0), + : mLevel (0), mDead (false), mDied (false), mMurdered(false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), mAttacked (false), mHostile (false), mAttackingOrSpell(false), @@ -245,6 +245,21 @@ namespace MWMechanics mDied = false; } + bool CreatureStats::hasBeenMurdered() const + { + return mMurdered; + } + + void CreatureStats::notifyMurder() + { + mMurdered = true; + } + + void CreatureStats::clearHasBeenMurdered() + { + mMurdered = false; + } + void CreatureStats::resurrect() { if (mDead) @@ -479,6 +494,7 @@ namespace MWMechanics state.mDead = mDead; state.mDied = mDied; + state.mMurdered = mMurdered; state.mFriendlyHits = mFriendlyHits; state.mTalkedTo = mTalkedTo; state.mAlarmed = mAlarmed; @@ -527,6 +543,7 @@ namespace MWMechanics mDead = state.mDead; mDied = state.mDied; + mMurdered = state.mMurdered; mFriendlyHits = state.mFriendlyHits; mTalkedTo = state.mTalkedTo; mAlarmed = state.mAlarmed; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index d02af8fb6f..b365a0b89c 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -35,6 +35,7 @@ namespace MWMechanics AiSequence mAiSequence; bool mDead; bool mDied; + bool mMurdered; int mFriendlyHits; bool mTalkedTo; bool mAlarmed; @@ -170,6 +171,12 @@ namespace MWMechanics void clearHasDied(); + bool hasBeenMurdered() const; + + void clearHasBeenMurdered(); + + void notifyMurder(); + void resurrect(); bool hasCommonDisease() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index cb59d188f7..612a4ea84a 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -868,10 +868,16 @@ namespace MWMechanics // Find actors who directly witnessed the crime for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) { - if (*it == player) continue; // not the player + if (*it == player) + continue; // skip player + if (it->getClass().getCreatureStats(*it).isDead()) + continue; // Was the crime seen? - if (MWBase::Environment::get().getWorld()->getLOS(player, *it) && awarenessCheck(player, *it) ) + if ((MWBase::Environment::get().getWorld()->getLOS(player, *it) && awarenessCheck(player, *it) ) + // Murder crime can be reported even if no one saw it (hearing is enough, I guess). + // TODO: Add mod support for stealth executions! + || (type == OT_Murder && *it != victim)) { if (*it == victim) victimAware = true; @@ -904,6 +910,9 @@ namespace MWMechanics { const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); + if (type == OT_Murder && !victim.isEmpty()) + victim.getClass().getCreatureStats(victim).notifyMurder(); + // Bounty for each type of crime if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) arg = store.find("iCrimeTresspass")->getInt(); @@ -971,7 +980,7 @@ namespace MWMechanics for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) { if ( *it == player - || !it->getClass().isNpc()) continue; + || !it->getClass().isNpc() || it->getClass().getCreatureStats(*it).isDead()) continue; int aggression = fight; diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index ab69bc4b41..c125985c05 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -398,5 +398,7 @@ op 0x2000245: ClearInfoActor op 0x2000246: ClearInfoActor, explicit op 0x2000247: BetaComment op 0x2000248: BetaComment, explicit +op 0x2000249: OnMurder +op 0x200024a: OnMurder, explicit -opcodes 0x2000249-0x3ffffff unused +opcodes 0x200024b-0x3ffffff unused diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 8b61237a1d..a3bc223aa6 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1072,6 +1072,25 @@ namespace MWScript } }; + template + class OpOnMurder : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + Interpreter::Type_Integer value = + ptr.getClass().getCreatureStats (ptr).hasBeenMurdered(); + + if (value) + ptr.getClass().getCreatureStats (ptr).clearHasBeenMurdered(); + + runtime.push (value); + } + }; + template class OpOnKnockout : public Interpreter::Opcode0 { @@ -1264,6 +1283,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Stats::opcodeOnDeath, new OpOnDeath); interpreter.installSegment5 (Compiler::Stats::opcodeOnDeathExplicit, new OpOnDeath); + interpreter.installSegment5 (Compiler::Stats::opcodeOnMurder, new OpOnMurder); + interpreter.installSegment5 (Compiler::Stats::opcodeOnMurderExplicit, new OpOnMurder); interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockout, new OpOnKnockout); interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockoutExplicit, new OpOnKnockout); diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 5733a41702..2d140044c5 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -449,6 +449,7 @@ namespace Compiler extensions.registerInstruction ("lowerrank", "", opcodeLowerRank, opcodeLowerRankExplicit); extensions.registerFunction ("ondeath", 'l', "", opcodeOnDeath, opcodeOnDeathExplicit); + extensions.registerFunction ("onmurder", 'l', "", opcodeOnMurder, opcodeOnMurderExplicit); extensions.registerFunction ("onknockout", 'l', "", opcodeOnKnockout, opcodeOnKnockoutExplicit); extensions.registerFunction ("iswerewolf", 'l', "", opcodeIsWerewolf, opcodeIsWerewolfExplicit); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 8716cdd182..ed6f1df923 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -385,6 +385,8 @@ namespace Compiler const int opcodeLowerRankExplicit = 0x20001eb; const int opcodeOnDeath = 0x20001fc; const int opcodeOnDeathExplicit = 0x2000205; + const int opcodeOnMurder = 0x2000249; + const int opcodeOnMurderExplicit = 0x200024a; const int opcodeOnKnockout = 0x2000240; const int opcodeOnKnockoutExplicit = 0x2000241; diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 84902d8c29..0a9a361093 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -21,6 +21,9 @@ void ESM::CreatureStats::load (ESMReader &esm) mDied = false; esm.getHNOT (mDied, "DIED"); + mMurdered = false; + esm.getHNOT (mMurdered, "MURD"); + mFriendlyHits = 0; esm.getHNOT (mFriendlyHits, "FRHT"); @@ -127,6 +130,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mDied) esm.writeHNT ("DIED", mDied); + if (mMurdered) + esm.writeHNT ("MURD", mMurdered); + if (mFriendlyHits) esm.writeHNT ("FRHT", mFriendlyHits); diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 628bfee0a9..7ae57da16c 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -38,6 +38,7 @@ namespace ESM bool mDead; bool mDied; + bool mMurdered; int mFriendlyHits; bool mTalkedTo; bool mAlarmed; From 073cc3f02ca8799e22364be50cdbc8150de2a891 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 17 Jun 2014 11:01:17 +0200 Subject: [PATCH 116/226] fixed base flag in content file loader code --- apps/opencs/model/doc/loader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index c106c06e82..6fb10426c5 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -39,6 +39,7 @@ void CSMDoc::Loader::load() Document *document = iter->first; int size = static_cast (document->getContentFiles().size()); + int editedIndex = size-1; // index of the file to be edited/created if (document->isNew()) --size; @@ -77,7 +78,7 @@ void CSMDoc::Loader::load() { boost::filesystem::path path = document->getContentFiles()[iter->second.mFile]; - int steps = document->getData().startLoading (path, iter->second.mFilegetData().startLoading (path, iter->second.mFile!=editedIndex, false); iter->second.mRecordsLeft = true; emit nextStage (document, path.filename().string(), steps/batchingSize); From cb6f1d3a52b50a858487bcfa571b5bb9aa1721af Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 14:46:18 +0200 Subject: [PATCH 117/226] Fix wait dialog arrow button step (Fixes #1524) --- files/mygui/openmw_wait_dialog.layout | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/mygui/openmw_wait_dialog.layout b/files/mygui/openmw_wait_dialog.layout index eeb7012eb0..7c065038de 100644 --- a/files/mygui/openmw_wait_dialog.layout +++ b/files/mygui/openmw_wait_dialog.layout @@ -19,6 +19,8 @@ + + From 4f9ebd148c06770256d32e37aac02a633f7db9bd Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 15:25:54 +0200 Subject: [PATCH 118/226] Fix broken AI movement on Z axis --- apps/openmw/mwmechanics/aicombat.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 846f6d8dc2..f5881d605f 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -470,6 +470,8 @@ namespace MWMechanics if(preferShortcut) { + if (canMoveByZ) + mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); mForceNoShortcut = false; mShortcutFailPos.pos[0] = mShortcutFailPos.pos[1] = mShortcutFailPos.pos[2] = 0; From 1244da85df5ab746e4eafa8d34ddc867c1a7f173 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 16:27:33 +0200 Subject: [PATCH 119/226] Make Detect Life spell detect NPCs when in werewolf form (Fixes #1527) --- apps/openmw/mwgui/hud.cpp | 2 ++ apps/openmw/mwgui/mapwindow.cpp | 15 +++++++++++++++ apps/openmw/mwgui/mapwindow.hpp | 6 +++++- apps/openmw/mwworld/worldimp.cpp | 24 +++++++++++++++++------- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index d87ac7ec59..b39fd0db73 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -384,6 +384,8 @@ namespace MWGui void HUD::onFrame(float dt) { + LocalMapBase::onFrame(dt); + mCellNameTimer -= dt; mWeaponSpellTimer -= dt; if (mCellNameTimer < 0) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 29c065f3d5..db3cfadde6 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -36,6 +36,7 @@ namespace MWGui , mLastDirectionX(0.0f) , mLastDirectionY(0.0f) , mCompass(NULL) + , mMarkerUpdateTimer(0.0f) { } @@ -345,6 +346,18 @@ namespace MWGui markerWidget->setUserString("IsMarker", "true"); markerWidget->setUserData(markerPos); markerWidget->setColour(markerColour); + mMarkerWidgets.push_back(markerWidget); + } + } + + void LocalMapBase::onFrame(float dt) + { + mMarkerUpdateTimer += dt; + + if (mMarkerUpdateTimer >= 0.25) + { + mMarkerUpdateTimer = 0; + updateMarkers(); } } @@ -471,6 +484,8 @@ namespace MWGui void MapWindow::onFrame(float dt) { + LocalMapBase::onFrame(dt); + for (std::vector::iterator it = mQueuedToExplore.begin(); it != mQueuedToExplore.end(); ++it) { mGlobalMapRender->exploreCell(it->first, it->second); diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index d23b0c2285..c73e5d7a14 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -35,6 +35,8 @@ namespace MWGui void setPlayerDir(const float x, const float y); void setPlayerPos(const float x, const float y); + void onFrame(float dt); + bool toggleFogOfWar(); struct MarkerPosition @@ -73,12 +75,14 @@ namespace MWGui virtual void notifyMapChanged() {} // Update markers (Detect X effects, Mark/Recall effects) - // Note, door markers handled in setActiveCell + // Note, door markers are handled in setActiveCell void updateMarkers(); void addDetectionMarkers(int type); OEngine::GUI::Layout* mLayout; + float mMarkerUpdateTimer; + bool mMapDragAndDrop; float mLastPositionX; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4386f5b0de..f6573096db 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2445,14 +2445,15 @@ namespace MWWorld if (!ptr.getRefData().isEnabled()) return true; - // Consider references inside containers as well - if (ptr.getClass().isActor() || ptr.getClass().getTypeName() == typeid(ESM::Container).name()) + // Consider references inside containers as well (except if we are looking for a Creature, they cannot be in containers) + if (mType != World::Detect_Creature && + (ptr.getClass().isActor() || ptr.getClass().getTypeName() == typeid(ESM::Container).name())) { MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); { for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - if (needToAdd(*it)) + if (needToAdd(*it, mDetector)) { mOut.push_back(ptr); return true; @@ -2461,16 +2462,25 @@ namespace MWWorld } } - if (needToAdd(ptr)) + if (needToAdd(ptr, mDetector)) mOut.push_back(ptr); return true; } - bool needToAdd (MWWorld::Ptr ptr) + bool needToAdd (MWWorld::Ptr ptr, MWWorld::Ptr detector) { - if (mType == World::Detect_Creature && ptr.getClass().getTypeName() != typeid(ESM::Creature).name()) - return false; + if (mType == World::Detect_Creature) + { + // If in werewolf form, this detects only NPCs, otherwise only creatures + if (detector.getClass().isNpc() && detector.getClass().getNpcStats(detector).isWerewolf()) + { + if (ptr.getClass().getTypeName() != typeid(ESM::NPC).name()) + return false; + } + else if (ptr.getClass().getTypeName() != typeid(ESM::Creature).name()) + return false; + } if (mType == World::Detect_Key && !ptr.getClass().isKey(ptr)) return false; if (mType == World::Detect_Enchantment && ptr.getClass().getEnchantment(ptr).empty()) From 1dc9e151cb63355844a9347e66b02729db64f787 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 16:51:59 +0200 Subject: [PATCH 120/226] Count werewolf kills (Fixes #1525) --- apps/openmw/mwclass/npc.cpp | 7 ++++++- apps/openmw/mwmechanics/actors.cpp | 12 +++++++++--- apps/openmw/mwmechanics/npcstats.cpp | 7 +++++++ apps/openmw/mwmechanics/npcstats.hpp | 3 +++ components/compiler/extensions0.cpp | 2 +- 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 1a33747f21..a93cae9baa 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -757,11 +757,16 @@ namespace MWClass if (!wasDead && getCreatureStats(ptr).isDead()) { // NPC was killed + if (!attacker.isEmpty() && attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf()) + { + attacker.getClass().getNpcStats(attacker).addWerewolfKill(); + } + // Simple check for who attacked first: if the player attacked first, a crimeId should be set // Doesn't handle possible edge case where no one reported the assault, but in such a case, // for bystanders it is not possible to tell who attacked first, anyway. MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 && ptr != player) + if (attacker == player && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 && ptr != player) MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder); } } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 0c5a2abec0..d72ab7db89 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -460,6 +460,9 @@ namespace MWMechanics { // The actor was killed by a magic effect. Figure out if the player was responsible for it. const ActiveSpells& spells = creatureStats.getActiveSpells(); + bool killedByPlayer = false; + bool murderedByPlayer = false; + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) { const ActiveSpells::ActiveSpellParams& spell = it->second; @@ -478,20 +481,23 @@ namespace MWMechanics if (effectId == ESM::MagicEffect::DamageHealth || effectId == ESM::MagicEffect::AbsorbHealth) isDamageEffect = true; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId); if (isDamageEffect && caster == player) { + killedByPlayer = true; // Simple check for who attacked first: if the player attacked first, a crimeId should be set // Doesn't handle possible edge case where no one reported the assault, but in such a case, // for bystanders it is not possible to tell who attacked first, anyway. if (ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 && ptr != player) - MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder); - break; + murderedByPlayer = true; } } } + if (murderedByPlayer) + MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder); + if (killedByPlayer && player.getClass().getNpcStats(player).isWerewolf()) + player.getClass().getNpcStats(player).addWerewolfKill(); } // TODO: dirty flag for magic effects to avoid some unnecessary work below? diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 642e1cfe18..579969f9d2 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -396,6 +396,8 @@ void MWMechanics::NpcStats::setWerewolf (bool set) { const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + mWerewolfKills = 0; + for(size_t i = 0;i < ESM::Attribute::Length;i++) { mWerewolfAttributes[i] = getAttribute(i); @@ -427,6 +429,11 @@ int MWMechanics::NpcStats::getWerewolfKills() const return mWerewolfKills; } +void MWMechanics::NpcStats::addWerewolfKill() +{ + ++mWerewolfKills; +} + int MWMechanics::NpcStats::getProfit() const { return mProfit; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 185a58b947..a066760d00 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -126,6 +126,9 @@ namespace MWMechanics int getWerewolfKills() const; + /// Increments mWerewolfKills by 1. + void addWerewolfKill(); + float getTimeToStartDrowning() const; /// Sets time left for the creature to drown if it stays underwater. /// @param time value from [0,20] diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 2d140044c5..efd45f9127 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -441,7 +441,7 @@ namespace Compiler extensions.registerFunction ("getrace", 'l', "c", opcodeGetRace, opcodeGetRaceExplicit); - extensions.registerFunction ("getwerewolfkills", 'f', "", opcodeGetWerewolfKills); + extensions.registerFunction ("getwerewolfkills", 'l', "", opcodeGetWerewolfKills); extensions.registerFunction ("pcexpelled", 'l', "/S", opcodePcExpelled, opcodePcExpelledExplicit); extensions.registerInstruction ("pcexpell", "/S", opcodePcExpell, opcodePcExpellExplicit); extensions.registerInstruction ("pcclearexpelled", "/S", opcodePcClearExpelled, opcodePcClearExpelledExplicit); From a3ea7cb9563ccaf93372893e7c4b05299202a917 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 17:04:41 +0200 Subject: [PATCH 121/226] Ignore distance when considering aggression due to crime (seems to work better, all balmora mages guild members now come to help when one is attacked) --- apps/openmw/mwbase/mechanicsmanager.hpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 8 +++++--- apps/openmw/mwmechanics/mechanicsmanagerimp.hpp | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 91d0ed9bf5..f2b71bd4cc 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -195,7 +195,7 @@ namespace MWBase /// @param bias Can be used to add an additional aggression bias towards the target, /// making it more likely for the function to return true. - virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0) = 0; + virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0, bool ignoreDistance=false) = 0; }; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 612a4ea84a..f4ffa499c2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1001,7 +1001,7 @@ namespace MWMechanics } else { - bool aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(*it, player, aggression); + bool aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(*it, player, aggression, true); if (aggressive) { startCombat(*it, player); @@ -1156,12 +1156,14 @@ namespace MWMechanics mActors.clear(); } - bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target, int bias) + bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target, int bias, bool ignoreDistance) { Ogre::Vector3 pos1 (ptr.getRefData().getPosition().pos); Ogre::Vector3 pos2 (target.getRefData().getPosition().pos); - float d = pos1.distance(pos2); + float d = 0; + if (!ignoreDistance) + d = pos1.distance(pos2); static int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get().find( "iFightDistanceBase")->getInt(); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 0fee72b77e..596e6887ec 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -159,7 +159,7 @@ namespace MWMechanics /// @param bias Can be used to add an additional aggression bias towards the target, /// making it more likely for the function to return true. - virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0); + virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0, bool ignoreDistance=false); }; } From 666dbc6ddca39329240cef6141bccb943e1b773e Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 17:18:30 +0200 Subject: [PATCH 122/226] Disable QuickKeysMenu in werewolf form --- apps/openmw/mwinput/inputmanagerimp.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 15b5abde64..cd3be4b423 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -26,7 +26,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" #include "../mwdialogue/dialoguemanagerimp.hpp" @@ -826,6 +826,14 @@ namespace MWInput void InputManager::quickKey (int index) { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (player.getClass().getNpcStats(player).isWerewolf()) + { + // Cannot use items or spells while in werewolf form + MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); + return; + } + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) MWBase::Environment::get().getWindowManager()->activateQuickKey (index); } @@ -834,7 +842,18 @@ namespace MWInput { if (!MWBase::Environment::get().getWindowManager()->isGuiMode () && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (player.getClass().getNpcStats(player).isWerewolf()) + { + // Cannot use items or spells while in werewolf form + MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); + return; + } + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu); + + } else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu) { while(MyGUI::InputManager::getInstance().isModalAny()) { //Handle any open Modal windows MWBase::Environment::get().getWindowManager()->getCurrentModal()->exit(); From d4678a8d5517a8d897c64151eba63f2a02c2bd45 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 18:33:53 +0200 Subject: [PATCH 123/226] Fix level up dialogue layout (Fixes #1393) --- apps/openmw/mwgui/levelupdialog.cpp | 5 +++-- apps/openmw/mwgui/tooltips.cpp | 8 ++------ apps/openmw/mwgui/widgets.cpp | 7 +++++-- files/mygui/openmw_levelup_dialog.layout | 24 ++++++++++++++++-------- files/mygui/openmw_text.skin.xml | 7 +++++++ 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 38995ac326..eaa8227225 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -104,9 +104,10 @@ namespace MWGui int attribute = mSpentAttributes[i]; - int xdiff = mAttributeMultipliers[attribute]->getCaption() == "" ? 0 : 30; + int xdiff = mAttributeMultipliers[attribute]->getCaption() == "" ? 0 : 20; - MyGUI::IntPoint pos = mAttributes[attribute]->getAbsolutePosition() - mMainWidget->getAbsolutePosition() - MyGUI::IntPoint(24+xdiff,-4); + MyGUI::IntPoint pos = mAttributes[attribute]->getAbsolutePosition() - mMainWidget->getAbsolutePosition() - MyGUI::IntPoint(22+xdiff,0); + pos.top += (mAttributes[attribute]->getHeight() - image->getHeight())/2; image->setPosition(pos); } diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 16b010908a..676a9ee63d 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -233,13 +233,9 @@ namespace MWGui for (std::map::iterator it = userStrings.begin(); it != userStrings.end(); ++it) { - if (it->first == "ToolTipType" - || it->first == "ToolTipLayout" - || it->first == "IsMarker") - continue; - - size_t underscorePos = it->first.find("_"); + if (underscorePos == std::string::npos) + continue; std::string propertyKey = it->first.substr(0, underscorePos); std::string widgetName = it->first.substr(underscorePos+1, it->first.size()-(underscorePos+1)); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index b30cf2bae8..b83ca0b09c 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -630,8 +630,11 @@ namespace MWGui MyGUI::IntSize AutoSizedButton::getRequestedSize() { - MyGUI::IntSize size = getTextSize() + MyGUI::IntSize(24,0); - size.height = std::max(24, size.height); + MyGUI::IntSize padding(24, 8); + if (isUserString("TextPadding")) + padding = MyGUI::IntSize::parse(getUserString("TextPadding")); + + MyGUI::IntSize size = getTextSize() + MyGUI::IntSize(padding.width,padding.height); return size; } diff --git a/files/mygui/openmw_levelup_dialog.layout b/files/mygui/openmw_levelup_dialog.layout index 765bf88a83..0b12d7a052 100644 --- a/files/mygui/openmw_levelup_dialog.layout +++ b/files/mygui/openmw_levelup_dialog.layout @@ -31,17 +31,18 @@ - - - - - - - - + + + + + + + + + @@ -55,6 +56,7 @@ + @@ -68,6 +70,7 @@ + @@ -81,6 +84,7 @@ + @@ -95,6 +99,7 @@ + @@ -108,6 +113,7 @@ + @@ -121,6 +127,7 @@ + @@ -134,6 +141,7 @@ + diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index d4c72c75b4..4c1117cf5f 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -32,6 +32,13 @@ + + + + + + + From 80f66e21576d96cb1f4b486c816451e826cf1827 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 21:16:32 +0200 Subject: [PATCH 124/226] Fix crash when avformat_open_input fails (Fixes #1522) --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 75f7ccae4c..10a782b964 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -159,17 +159,21 @@ void FFmpeg_Decoder::open(const std::string &fname) mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) { - if (mFormatCtx->pb != NULL) + // "Note that a user-supplied AVFormatContext will be freed on failure". + if (mFormatCtx) { - if (mFormatCtx->pb->buffer != NULL) - { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = NULL; - } - av_free(mFormatCtx->pb); - mFormatCtx->pb = NULL; + if (mFormatCtx->pb != NULL) + { + if (mFormatCtx->pb->buffer != NULL) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = NULL; + } + avformat_free_context(mFormatCtx); } - avformat_free_context(mFormatCtx); mFormatCtx = NULL; fail("Failed to allocate input stream"); } From 56bc5a9d39421c71b15a16883f0253e74ba99dde Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 21:27:41 +0200 Subject: [PATCH 125/226] Fix being able to steal undetected just after invisibility breaks --- apps/openmw/mwworld/worldimp.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f6573096db..645591cb86 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2344,6 +2344,9 @@ namespace MWWorld actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility); if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility); + + // Normally updated once per frame, but here it is kinda important to do it right away. + MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); } bool World::isDark() const From fe1e6a27199794da5c093ce4ff7e0f792e1987d1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 22:24:56 +0200 Subject: [PATCH 126/226] Make Weakness effects apply to all subsequent effects within the same spell (Fixes #1150) --- apps/openmw/mwmechanics/spellcasting.cpp | 27 ++++++++++++++++++------ apps/openmw/mwmechanics/spellcasting.hpp | 9 ++++++-- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index c996e90d6f..26480ed031 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -148,13 +148,17 @@ namespace MWMechanics return school; } - float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell) + float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell, const MagicEffects* effects) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( effectId); const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects(); + if (effects) + magicEffects = effects; float resisted = 0; if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) @@ -165,9 +169,9 @@ namespace MWMechanics float resistance = 0; if (resistanceEffect != -1) - resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude; + resistance += magicEffects->get(resistanceEffect).mMagnitude; if (weaknessEffect != -1) - resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude; + resistance -= magicEffects->get(weaknessEffect).mMagnitude; float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); @@ -204,9 +208,10 @@ namespace MWMechanics return resisted; } - float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell) + float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell, const MagicEffects* effects) { - float resistance = getEffectResistance(effectId, actor, caster, spell); + float resistance = getEffectResistance(effectId, actor, caster, spell, effects); if (resistance >= 0) return 1 - resistance / 100.f; else @@ -261,6 +266,13 @@ namespace MWMechanics bool firstAppliedEffect = true; bool anyHarmfulEffect = false; + // HACK: cache target's magic effects here, and add any applied effects to it. Use the cached effects for determining resistance. + // This is required for Weakness effects in a spell to apply to any subsequent effects in the spell. + // Otherwise, they'd only apply after the whole spell was added. + MagicEffects targetEffects; + if (target.getClass().isActor()) + targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); + bool castByPlayer = (!caster.isEmpty() && caster.getRefData().getHandle() == "player"); for (std::vector::const_iterator effectIt (effects.mList.begin()); @@ -346,8 +358,7 @@ namespace MWMechanics // Try resisting if (magnitudeMult > 0 && target.getClass().isActor()) { - - magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); + magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects); if (magnitudeMult == 0) { // Fully resisted, show message @@ -375,6 +386,8 @@ namespace MWMechanics effect.mDuration = effectIt->mDuration; effect.mMagnitude = magnitude; + targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); + appliedLastingEffects.push_back(effect); // For absorb effects, also apply the effect to the caster - but with a negative diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index dce4b792e2..821d7ae0e8 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -18,6 +18,7 @@ namespace ESM namespace MWMechanics { class EffectKey; + class MagicEffects; ESM::Skill::SkillEnum spellSchoolToSkill(int school); @@ -35,9 +36,13 @@ namespace MWMechanics int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); /// @return >=100 for fully resisted. can also return negative value for damage amplification. - float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL); + /// @param effects Override the actor's current magicEffects. Useful if there are effects currently + /// being applied (but not applied yet) that should also be considered. + float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell = NULL, const MagicEffects* effects = NULL); - float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL); + float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell = NULL, const MagicEffects* effects = NULL); class CastSpell { From e95483c40f7c04168a09e64d45040146310ecc81 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 18 Jun 2014 01:41:07 +0200 Subject: [PATCH 127/226] Fix crash for on target spells cast by non-actors (Fixes #1529) --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 22 ++++++++++++++---- apps/openmw/mwmechanics/spellcasting.hpp | 9 ++++++-- apps/openmw/mwworld/projectilemanager.cpp | 28 ++++++++++++++++------- apps/openmw/mwworld/projectilemanager.hpp | 11 +++++++-- apps/openmw/mwworld/worldimp.cpp | 4 ++-- apps/openmw/mwworld/worldimp.hpp | 2 +- 7 files changed, 57 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index df75ce64d1..29f821e85b 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -470,7 +470,7 @@ namespace MWBase virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId, float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& actor, const std::string& sourceName) = 0; + const MWWorld::Ptr& caster, const std::string& sourceName, const Ogre::Vector3& fallbackDirection) = 0; virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) = 0; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 26480ed031..6467730dda 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -340,7 +340,7 @@ namespace MWMechanics } // Try reflecting - if (!reflected && magnitudeMult > 0 && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) + if (!reflected && magnitudeMult > 0 && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) { int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] @@ -465,14 +465,14 @@ namespace MWMechanics if (!appliedLastingEffects.empty()) { int casterActorId = -1; - if (caster.getClass().isActor()) + if (!caster.isEmpty() && caster.getClass().isActor()) casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, mSourceName, casterActorId); } // Notify the target actor they've been hit - if (anyHarmfulEffect && target.getClass().isActor() && target != caster && caster.getClass().isActor()) + if (anyHarmfulEffect && target.getClass().isActor() && target != caster && !caster.isEmpty() && caster.getClass().isActor()) target.getClass().onHit(target, 0.f, true, MWWorld::Ptr(), caster, true); } @@ -657,7 +657,9 @@ namespace MWMechanics getProjectileInfo(enchantment->mEffects, projectileModel, sound, speed); if (!projectileModel.empty()) MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, - false, enchantment->mEffects, mCaster, mSourceName); + false, enchantment->mEffects, mCaster, mSourceName, + // Not needed, enchantments can only be cast by actors + Ogre::Vector3(1,0,0)); return true; } @@ -742,8 +744,18 @@ namespace MWMechanics float speed = 0; getProjectileInfo(spell->mEffects, projectileModel, sound, speed); if (!projectileModel.empty()) + { + Ogre::Vector3 fallbackDirection (0,1,0); + // Fall back to a "caster to target" direction if we have no other means of determining it + // (e.g. when cast by a non-actor) + if (!mTarget.isEmpty()) + fallbackDirection = + Ogre::Vector3(mTarget.getRefData().getPosition().pos)- + Ogre::Vector3(mCaster.getRefData().getPosition().pos); + MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, - false, spell->mEffects, mCaster, mSourceName); + false, spell->mEffects, mCaster, mSourceName, fallbackDirection); + } return true; } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 821d7ae0e8..b526a43530 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -47,8 +47,8 @@ namespace MWMechanics class CastSpell { private: - MWWorld::Ptr mCaster; - MWWorld::Ptr mTarget; + MWWorld::Ptr mCaster; // May be empty + MWWorld::Ptr mTarget; // May be empty public: bool mStack; std::string mId; // ID of spell, potion, item etc @@ -59,8 +59,13 @@ namespace MWMechanics CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); bool cast (const ESM::Spell* spell); + + /// @note mCaster must be an actor bool cast (const MWWorld::Ptr& item); + + /// @note mCaster must be an NPC bool cast (const ESM::Ingredient* ingredient); + bool cast (const ESM::Potion* potion); /// @note Auto detects if spell, ingredient or potion diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 4e4f0b271a..cfb407b4a0 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -57,22 +57,32 @@ namespace MWWorld void ProjectileManager::launchMagicBolt(const std::string &model, const std::string &sound, const std::string &spellId, float speed, bool stack, - const ESM::EffectList &effects, const Ptr &actor, const std::string &sourceName) + const ESM::EffectList &effects, const Ptr &caster, const std::string &sourceName, + const Ogre::Vector3& fallbackDirection) { - // Spawn at 0.75 * ActorHeight - float height = mPhysEngine.getCharacter(actor.getRefData().getHandle())->getHalfExtents().z * 2 * 0.75; + float height = 0; + if (OEngine::Physic::PhysicActor* actor = mPhysEngine.getCharacter(caster.getRefData().getHandle())) + height = actor->getHalfExtents().z * 2 * 0.75; // Spawn at 0.75 * ActorHeight - Ogre::Vector3 pos(actor.getRefData().getPosition().pos); + Ogre::Vector3 pos(caster.getRefData().getPosition().pos); pos.z += height; - Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + Ogre::Quaternion orient; + if (caster.getClass().isActor()) + orient = Ogre::Quaternion(Ogre::Radian(caster.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(caster.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + else + orient = Ogre::Vector3::UNIT_Y.getRotationTo(fallbackDirection); MagicBoltState state; state.mSourceName = sourceName; state.mId = model; state.mSpellId = spellId; - state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); + state.mCaster = caster; + if (caster.getClass().isActor()) + state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); + else + state.mActorId = -1; state.mSpeed = speed; state.mStack = stack; state.mSoundId = sound; @@ -152,7 +162,9 @@ namespace MWWorld { MWWorld::Ptr obstacle = MWBase::Environment::get().getWorld()->searchPtrViaHandle(cIt->second); - MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); + MWWorld::Ptr caster = it->mCaster; + if (caster.isEmpty()) + caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); if (!obstacle.isEmpty() && obstacle == caster) continue; diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index da965a4cff..4627d3b8a5 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -39,9 +39,10 @@ namespace MWWorld ProjectileManager (Ogre::SceneManager* sceneMgr, OEngine::Physic::PhysicEngine& engine); + /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. void launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId, float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& actor, const std::string& sourceName); + const MWWorld::Ptr& caster, const std::string& sourceName, const Ogre::Vector3& fallbackDirection); void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, const Ogre::Vector3& pos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed); @@ -64,9 +65,15 @@ namespace MWWorld NifOgre::ObjectScenePtr mObject; Ogre::SceneNode* mNode; - // Actor who shot this projectile int mActorId; + // actorId doesn't work for non-actors, so we also keep track of the Ptr. + // For non-actors, the caster ptr is mainly needed to prevent the projectile + // from colliding with its caster. + // TODO: this will break when the game is saved and reloaded, since there is currently + // no way to write identifiers for non-actors to a savegame. + MWWorld::Ptr mCaster; + // MW-id of this projectile std::string mId; }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 645591cb86..0b623f8544 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2329,9 +2329,9 @@ namespace MWWorld void World::launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId, float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& actor, const std::string& sourceName) + const MWWorld::Ptr& caster, const std::string& sourceName, const Ogre::Vector3& fallbackDirection) { - mProjectileManager->launchMagicBolt(model, sound, spellId, speed, stack, effects, actor, sourceName); + mProjectileManager->launchMagicBolt(model, sound, spellId, speed, stack, effects, caster, sourceName, fallbackDirection); } const std::vector& World::getContentFiles() const diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index ac1244652a..db599b0de7 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -546,7 +546,7 @@ namespace MWWorld virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId, float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& actor, const std::string& sourceName); + const MWWorld::Ptr& caster, const std::string& sourceName, const Ogre::Vector3& fallbackDirection); virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed); From 224163e5a2f4f1a8f073d5331df1d124cb4afb71 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 18 Jun 2014 05:04:49 +0200 Subject: [PATCH 128/226] Fix console text becoming unreadable when selected (Fixes #1530) --- files/mygui/openmw_console.layout | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/files/mygui/openmw_console.layout b/files/mygui/openmw_console.layout index 0c9a97d043..c88c1955d8 100644 --- a/files/mygui/openmw_console.layout +++ b/files/mygui/openmw_console.layout @@ -13,10 +13,13 @@ + - + + + From 9a6737073fe400c0215daa7a93aaac9b45ac37af Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 18 Jun 2014 15:33:09 +0200 Subject: [PATCH 129/226] Fix broken swimdeath in first person --- apps/openmw/mwmechanics/character.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 6f0a255a9e..ddfba51ce1 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -409,13 +409,6 @@ MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::I void CharacterController::playDeath(float startpoint, CharacterState death) { - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) - { - // The first-person animations do not include death, so we need to - // force-switch to third person before playing the death animation. - MWBase::Environment::get().getWorld()->useDeathCamera(); - } - switch (death) { case CharState_SwimDeath: @@ -459,6 +452,13 @@ void CharacterController::playDeath(float startpoint, CharacterState death) void CharacterController::playRandomDeath(float startpoint) { + if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + // The first-person animations do not include death, so we need to + // force-switch to third person before playing the death animation. + MWBase::Environment::get().getWorld()->useDeathCamera(); + } + if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) { mDeathState = CharState_SwimDeath; From 8a4227ec380f7e895ba449537167b9a5328c786f Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 18 Jun 2014 16:01:20 +0200 Subject: [PATCH 130/226] Heal player while in jail --- apps/openmw/mwworld/worldimp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0b623f8544..6e975d25a8 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2615,6 +2615,8 @@ namespace MWWorld int days = std::max(1, bounty / iDaysinPrisonMod); advanceTime(days * 24); + for (int i=0; irest (true); std::set skills; for (int day=0; day buttons; buttons.push_back("#{sOk}"); MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); From 5645c9185beafb5dc81b0948b62b4f56d908e009 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 18 Jun 2014 17:18:48 +0200 Subject: [PATCH 131/226] Fix location of local data path --- components/files/configurationmanager.cpp | 4 ++-- files/openmw.cfg | 2 +- files/openmw.cfg.local | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index ffa911b44f..58d75d1fd7 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -18,7 +18,7 @@ static const char* const openmwCfgFile = "openmw.cfg"; const char* const mwToken = "?mw?"; const char* const localToken = "?local?"; -const char* const userToken = "?user?"; +const char* const userDataToken = "?userdata?"; const char* const globalToken = "?global?"; ConfigurationManager::ConfigurationManager() @@ -40,7 +40,7 @@ void ConfigurationManager::setupTokensMapping() { mTokensMapping.insert(std::make_pair(mwToken, &FixedPath<>::getInstallPath)); mTokensMapping.insert(std::make_pair(localToken, &FixedPath<>::getLocalPath)); - mTokensMapping.insert(std::make_pair(userToken, &FixedPath<>::getUserConfigPath)); + mTokensMapping.insert(std::make_pair(userDataToken, &FixedPath<>::getUserDataPath)); mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); } diff --git a/files/openmw.cfg b/files/openmw.cfg index 4633141a69..b67b79a964 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -1,4 +1,4 @@ data="?global?data" data="?mw?Data Files" -data-local="?user?data" +data-local="?userdata?data" resources=${OPENMW_RESOURCE_FILES} diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index d6ca2d5549..6a578542df 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -1,5 +1,5 @@ data="?global?data" data="?mw?Data Files" data=./data -data-local="?user?data" +data-local="?userdata?data" resources=./resources From 4e71db70819a49ffcb8144dc16d27e1ed252b4bf Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 18 Jun 2014 21:39:17 +0200 Subject: [PATCH 132/226] Savegame: Don't load/save deleted container items. This is currently pointless, and also causes new garbage being added on each load/save cycle: Container stores are first filled from ESM records, then cleared and filled from the savegame. The items from ESM records remain as deleted refs. --- apps/openmw/mwworld/containerstore.cpp | 2 ++ components/esm/inventorystate.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 8a076c3fc0..e330ddaeed 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -92,6 +92,8 @@ void MWWorld::ContainerStore::storeStates (const CellRefList& collection, for (typename CellRefList::List::const_iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) { + if (iter->mData.getCount() == 0) + continue; ESM::ObjectState state; storeState (*iter, state); int slot = equipable ? getSlot (*iter) : -1; diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp index 1b0bc772e9..2154faa830 100644 --- a/components/esm/inventorystate.cpp +++ b/components/esm/inventorystate.cpp @@ -37,6 +37,8 @@ void ESM::InventoryState::load (ESMReader &esm) LightState state; int slot; read (esm, state, slot); + if (state.mCount == 0) + continue; mLights.push_back (std::make_pair (state, slot)); } else @@ -44,6 +46,8 @@ void ESM::InventoryState::load (ESMReader &esm) ObjectState state; int slot; read (esm, state, slot); + if (state.mCount == 0) + continue; mItems.push_back (std::make_pair (state, std::make_pair (id, slot))); } } From 2193977eec52e51981ee7799de03123caabfeadc Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 18 Jun 2014 22:59:18 +0200 Subject: [PATCH 133/226] Savegame: Don't fill CustomData from ESM records if the savegame overwrites it anyway This gets rid of some junk in ContainerStores (since clear() only sets count to 0 and doesn't really delete references), and significantly speeds up loading savegames (by about 80% in my test) --- apps/openmw/mwclass/container.cpp | 7 ++++++- apps/openmw/mwclass/creature.cpp | 15 ++++++++++++++- apps/openmw/mwclass/npc.cpp | 7 ++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 9498ea52df..53add42746 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -286,7 +286,12 @@ namespace MWClass { const ESM::ContainerState& state2 = dynamic_cast (state); - ensureCustomData (ptr); + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new ContainerCustomData); + ptr.getRefData().setCustomData (data.release()); + } dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. readState (state2.mInventory); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 59c1087f98..97cf30fe12 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -798,7 +798,20 @@ namespace MWClass { const ESM::CreatureState& state2 = dynamic_cast (state); - ensureCustomData (ptr); + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new CreatureCustomData); + + MWWorld::LiveCellRef *ref = ptr.get(); + + if (ref->mBase->mFlags & ESM::Creature::Weapon) + data->mContainerStore = new MWWorld::InventoryStore(); + else + data->mContainerStore = new MWWorld::ContainerStore(); + + ptr.getRefData().setCustomData (data.release()); + } CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index a93cae9baa..58a5bc622d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1293,7 +1293,12 @@ namespace MWClass { const ESM::NpcState& state2 = dynamic_cast (state); - ensureCustomData (ptr); + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new NpcCustomData); + ptr.getRefData().setCustomData (data.release()); + } NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); From c3e4160a0ae94dda942f483850b3f267e8f7665a Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 18 Jun 2014 23:50:55 +0200 Subject: [PATCH 134/226] Don't crash in StatsWindow for invalid faction rank values PcRaiseRank: don't allow to raise rank beyond max rank --- apps/openmw/mwgui/statswindow.cpp | 10 ++++++---- apps/openmw/mwscript/statsextensions.cpp | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index b4bd0d7387..6c4e00ae5c 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -488,14 +488,16 @@ namespace MWGui text += "\n#BF9959#{sExpelled}"; else { - text += std::string("\n#BF9959") + faction->mRanks[it->second]; + int rank = it->second; + rank = std::max(0, std::min(9, rank)); + text += std::string("\n#BF9959") + faction->mRanks[rank]; - if (it->second < 9) + if (rank < 9) { // player doesn't have max rank yet - text += std::string("\n\n#DDC79E#{sNextRank} ") + faction->mRanks[it->second+1]; + text += std::string("\n\n#DDC79E#{sNextRank} ") + faction->mRanks[rank+1]; - ESM::RankData rankData = faction->mData.mRankData[it->second+1]; + ESM::RankData rankData = faction->mData.mRankData[rank+1]; const ESM::Attribute* attr1 = store.get().find(faction->mData.mAttribute[0]); const ESM::Attribute* attr2 = store.get().find(faction->mData.mAttribute[1]); diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index a3bc223aa6..5a0cd8ae68 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -587,7 +587,9 @@ namespace MWScript } else { - player.getClass().getNpcStats(player).getFactionRanks()[factionID] = player.getClass().getNpcStats(player).getFactionRanks()[factionID] +1; + player.getClass().getNpcStats(player).getFactionRanks()[factionID] = + std::min(player.getClass().getNpcStats(player).getFactionRanks()[factionID] +1, + 9); } } } From d878456d0f1c77a6fe3c6831e3bc10e00cf32e8d Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jun 2014 01:10:33 +0200 Subject: [PATCH 135/226] Don't add an extra path separator --- apps/openmw/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index b082ff9085..a061cf63c8 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -335,7 +335,7 @@ int main(int argc, char**argv) if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached()) { int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; - cc_install_handlers(argc, argv, 5, s, std::string(cfgMgr.getLogPath().string() + "/crash.log").c_str(), NULL); + cc_install_handlers(argc, argv, 5, s, (cfgMgr.getLogPath() / "crash.log").string().c_str(), NULL); std::cout << "Installing crash catcher" << std::endl; } else From 4234c70232f80e46bcb72000c976989fd7cfaae0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jun 2014 02:00:40 +0200 Subject: [PATCH 136/226] Savegame: Disable CustomData load optimization for npcs and creatures for now to preserve compatibility (still enabled for containers) --- apps/openmw/mwclass/creature.cpp | 7 +++++++ apps/openmw/mwclass/npc.cpp | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 97cf30fe12..5dea2ed1a8 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -798,6 +798,12 @@ namespace MWClass { const ESM::CreatureState& state2 = dynamic_cast (state); + ensureCustomData(ptr); + + // If we do the following instead we get a sizable speedup, but this causes compatibility issues + // with 0.30 savegames, where some state in CreatureStats was not saved yet, + // and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release. + /* if (!ptr.getRefData().getCustomData()) { // Create a CustomData, but don't fill it from ESM records (not needed) @@ -812,6 +818,7 @@ namespace MWClass ptr.getRefData().setCustomData (data.release()); } + */ CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 58a5bc622d..6b77f30e7c 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1293,12 +1293,18 @@ namespace MWClass { const ESM::NpcState& state2 = dynamic_cast (state); + ensureCustomData(ptr); + // If we do the following instead we get a sizable speedup, but this causes compatibility issues + // with 0.30 savegames, where some state in CreatureStats was not saved yet, + // and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release. + /* if (!ptr.getRefData().getCustomData()) { // Create a CustomData, but don't fill it from ESM records (not needed) std::auto_ptr data (new NpcCustomData); ptr.getRefData().setCustomData (data.release()); } + */ NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); From 6760f4c897b083b71d412000ee534541c3e1be5a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jun 2014 02:09:46 +0200 Subject: [PATCH 137/226] Make cached GMSTs in MWClass::Npc/Creature safer --- apps/openmw/mwclass/creature.cpp | 83 +++++++-------- apps/openmw/mwclass/creature.hpp | 31 +++--- apps/openmw/mwclass/npc.cpp | 175 ++++++++++++++----------------- apps/openmw/mwclass/npc.hpp | 47 +++++---- 4 files changed, 160 insertions(+), 176 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 5dea2ed1a8..4a159a7731 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -59,35 +59,38 @@ namespace namespace MWClass { + const Creature::GMST& Creature::getGmst() + { + static GMST gmst; + static bool inited = false; + if (!inited) + { + const MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Store &store = world->getStore().get(); + gmst.fMinWalkSpeedCreature = store.find("fMinWalkSpeedCreature"); + gmst.fMaxWalkSpeedCreature = store.find("fMaxWalkSpeedCreature"); + gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect"); + gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier"); + gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus"); + gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier"); + gmst.fMinFlySpeed = store.find("fMinFlySpeed"); + gmst.fMaxFlySpeed = store.find("fMaxFlySpeed"); + gmst.fSwimRunBase = store.find("fSwimRunBase"); + gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult"); + gmst.fKnockDownMult = store.find("fKnockDownMult"); + gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult"); + gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); + inited = true; + } + return gmst; + } + void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { std::auto_ptr data (new CreatureCustomData); - static bool inited = false; - if(!inited) - { - const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); - - fMinWalkSpeedCreature = gmst.find("fMinWalkSpeedCreature"); - fMaxWalkSpeedCreature = gmst.find("fMaxWalkSpeedCreature"); - fEncumberedMoveEffect = gmst.find("fEncumberedMoveEffect"); - fSneakSpeedMultiplier = gmst.find("fSneakSpeedMultiplier"); - fAthleticsRunBonus = gmst.find("fAthleticsRunBonus"); - fBaseRunMultiplier = gmst.find("fBaseRunMultiplier"); - fMinFlySpeed = gmst.find("fMinFlySpeed"); - fMaxFlySpeed = gmst.find("fMaxFlySpeed"); - fSwimRunBase = gmst.find("fSwimRunBase"); - fSwimRunAthleticsMult = gmst.find("fSwimRunAthleticsMult"); - fKnockDownMult = gmst.find("fKnockDownMult"); - iKnockDownOddsMult = gmst.find("iKnockDownOddsMult"); - iKnockDownOddsBase = gmst.find("iKnockDownOddsBase"); - - inited = true; - } - MWWorld::LiveCellRef *ref = ptr.get(); // creature stats @@ -376,9 +379,9 @@ namespace MWClass if (damage > 0.f) { // Check for knockdown - float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat(); + float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat(); float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() - * iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt(); + * getGmst().iKnockDownOddsMult->getInt() * 0.01 + getGmst().iKnockDownOddsBase->getInt(); int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] if (ishealth && agilityTerm <= damage && knockdownTerm <= roll) { @@ -522,9 +525,10 @@ namespace MWClass float Creature::getSpeed(const MWWorld::Ptr &ptr) const { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const GMST& gmst = getGmst(); - float walkSpeed = fMinWalkSpeedCreature->getFloat() + 0.01 * stats.getAttribute(ESM::Attribute::Speed).getModified() - * (fMaxWalkSpeedCreature->getFloat() - fMinWalkSpeedCreature->getFloat()); + float walkSpeed = gmst.fMinWalkSpeedCreature->getFloat() + 0.01 * stats.getAttribute(ESM::Attribute::Speed).getModified() + * (gmst.fMaxWalkSpeedCreature->getFloat() - gmst.fMinWalkSpeedCreature->getFloat()); const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); @@ -534,7 +538,7 @@ namespace MWClass bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run); float runSpeed = walkSpeed*(0.01f * getSkill(ptr, ESM::Skill::Athletics) * - fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat()); + gmst.fAthleticsRunBonus->getFloat() + gmst.fBaseRunMultiplier->getFloat()); float moveSpeed; if(normalizedEncumbrance >= 1.0f) @@ -544,8 +548,8 @@ namespace MWClass { float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + mageffects.get(ESM::MagicEffect::Levitate).mMagnitude); - flySpeed = fMinFlySpeed->getFloat() + flySpeed*(fMaxFlySpeed->getFloat() - fMinFlySpeed->getFloat()); - flySpeed *= 1.0f - fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; + flySpeed = gmst.fMinFlySpeed->getFloat() + flySpeed*(gmst.fMaxFlySpeed->getFloat() - gmst.fMinFlySpeed->getFloat()); + flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; } @@ -555,8 +559,8 @@ namespace MWClass if(running) swimSpeed = runSpeed; swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).mMagnitude; - swimSpeed *= fSwimRunBase->getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics) * - fSwimRunAthleticsMult->getFloat(); + swimSpeed *= gmst.fSwimRunBase->getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics) * + gmst.fSwimRunAthleticsMult->getFloat(); moveSpeed = swimSpeed; } else if(running) @@ -871,19 +875,4 @@ namespace MWClass MWWorld::ContainerStore& store = getContainerStore(ptr); store.restock(list, ptr, ptr.getCellRef().getRefId(), ptr.getCellRef().getFaction()); } - - const ESM::GameSetting* Creature::fMinWalkSpeedCreature; - const ESM::GameSetting* Creature::fMaxWalkSpeedCreature; - const ESM::GameSetting *Creature::fEncumberedMoveEffect; - const ESM::GameSetting *Creature::fSneakSpeedMultiplier; - const ESM::GameSetting *Creature::fAthleticsRunBonus; - const ESM::GameSetting *Creature::fBaseRunMultiplier; - const ESM::GameSetting *Creature::fMinFlySpeed; - const ESM::GameSetting *Creature::fMaxFlySpeed; - const ESM::GameSetting *Creature::fSwimRunBase; - const ESM::GameSetting *Creature::fSwimRunAthleticsMult; - const ESM::GameSetting *Creature::fKnockDownMult; - const ESM::GameSetting *Creature::iKnockDownOddsMult; - const ESM::GameSetting *Creature::iKnockDownOddsBase; - } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 30573cd154..6920a4b1d3 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -19,20 +19,25 @@ namespace MWClass static int getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name); - static const ESM::GameSetting *fMinWalkSpeedCreature; - static const ESM::GameSetting *fMaxWalkSpeedCreature; - static const ESM::GameSetting *fEncumberedMoveEffect; - static const ESM::GameSetting *fSneakSpeedMultiplier; - static const ESM::GameSetting *fAthleticsRunBonus; - static const ESM::GameSetting *fBaseRunMultiplier; - static const ESM::GameSetting *fMinFlySpeed; - static const ESM::GameSetting *fMaxFlySpeed; - static const ESM::GameSetting *fSwimRunBase; - static const ESM::GameSetting *fSwimRunAthleticsMult; - static const ESM::GameSetting *fKnockDownMult; - static const ESM::GameSetting *iKnockDownOddsMult; - static const ESM::GameSetting *iKnockDownOddsBase; + // cached GMSTs + struct GMST + { + const ESM::GameSetting *fMinWalkSpeedCreature; + const ESM::GameSetting *fMaxWalkSpeedCreature; + const ESM::GameSetting *fEncumberedMoveEffect; + const ESM::GameSetting *fSneakSpeedMultiplier; + const ESM::GameSetting *fAthleticsRunBonus; + const ESM::GameSetting *fBaseRunMultiplier; + const ESM::GameSetting *fMinFlySpeed; + const ESM::GameSetting *fMaxFlySpeed; + const ESM::GameSetting *fSwimRunBase; + const ESM::GameSetting *fSwimRunAthleticsMult; + const ESM::GameSetting *fKnockDownMult; + const ESM::GameSetting *iKnockDownOddsMult; + const ESM::GameSetting *iKnockDownOddsBase; + }; + static const GMST& getGmst(); public: diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 6b77f30e7c..c003e0b227 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -228,38 +228,44 @@ namespace namespace MWClass { - void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const + const Npc::GMST& Npc::getGmst() { + static GMST gmst; static bool inited = false; if(!inited) { const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); + const MWWorld::Store &store = world->getStore().get(); - fMinWalkSpeed = gmst.find("fMinWalkSpeed"); - fMaxWalkSpeed = gmst.find("fMaxWalkSpeed"); - fEncumberedMoveEffect = gmst.find("fEncumberedMoveEffect"); - fSneakSpeedMultiplier = gmst.find("fSneakSpeedMultiplier"); - fAthleticsRunBonus = gmst.find("fAthleticsRunBonus"); - fBaseRunMultiplier = gmst.find("fBaseRunMultiplier"); - fMinFlySpeed = gmst.find("fMinFlySpeed"); - fMaxFlySpeed = gmst.find("fMaxFlySpeed"); - fSwimRunBase = gmst.find("fSwimRunBase"); - fSwimRunAthleticsMult = gmst.find("fSwimRunAthleticsMult"); - fJumpEncumbranceBase = gmst.find("fJumpEncumbranceBase"); - fJumpEncumbranceMultiplier = gmst.find("fJumpEncumbranceMultiplier"); - fJumpAcrobaticsBase = gmst.find("fJumpAcrobaticsBase"); - fJumpAcroMultiplier = gmst.find("fJumpAcroMultiplier"); - fJumpRunMultiplier = gmst.find("fJumpRunMultiplier"); - fWereWolfRunMult = gmst.find("fWereWolfRunMult"); - fKnockDownMult = gmst.find("fKnockDownMult"); - iKnockDownOddsMult = gmst.find("iKnockDownOddsMult"); - iKnockDownOddsBase = gmst.find("iKnockDownOddsBase"); - fDamageStrengthBase = gmst.find("fDamageStrengthBase"); - fDamageStrengthMult = gmst.find("fDamageStrengthMult"); + gmst.fMinWalkSpeed = store.find("fMinWalkSpeed"); + gmst.fMaxWalkSpeed = store.find("fMaxWalkSpeed"); + gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect"); + gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier"); + gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus"); + gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier"); + gmst.fMinFlySpeed = store.find("fMinFlySpeed"); + gmst.fMaxFlySpeed = store.find("fMaxFlySpeed"); + gmst.fSwimRunBase = store.find("fSwimRunBase"); + gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult"); + gmst.fJumpEncumbranceBase = store.find("fJumpEncumbranceBase"); + gmst.fJumpEncumbranceMultiplier = store.find("fJumpEncumbranceMultiplier"); + gmst.fJumpAcrobaticsBase = store.find("fJumpAcrobaticsBase"); + gmst.fJumpAcroMultiplier = store.find("fJumpAcroMultiplier"); + gmst.fJumpRunMultiplier = store.find("fJumpRunMultiplier"); + gmst.fWereWolfRunMult = store.find("fWereWolfRunMult"); + gmst.fKnockDownMult = store.find("fKnockDownMult"); + gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult"); + gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); + gmst.fDamageStrengthBase = store.find("fDamageStrengthBase"); + gmst.fDamageStrengthMult = store.find("fDamageStrengthMult"); inited = true; } + return gmst; + } + + void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const + { if (!ptr.getRefData().getCustomData()) { std::auto_ptr data(new NpcCustomData); @@ -404,11 +410,6 @@ namespace MWClass ptr.get(); assert(ref->mBase != NULL); - //std::string headID = ref->mBase->mHead; - - //int end = headID.find_last_of("head_") - 4; - //std::string bodyRaceID = headID.substr(0, end); - std::string model = "meshes\\base_anim.nif"; const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); if(race->mData.mFlags & ESM::Race::Beast) @@ -423,9 +424,9 @@ namespace MWClass if(getNpcStats(ptr).isWerewolf()) { const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); + const MWWorld::Store &store = world->getStore().get(); - return gmst.find("sWerewolfPopup")->getString(); + return store.find("sWerewolfPopup")->getString(); } MWWorld::LiveCellRef *ref = ptr.get(); @@ -450,7 +451,9 @@ namespace MWClass void Npc::hit(const MWWorld::Ptr& ptr, int type) const { MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); + const GMST& gmst = getGmst(); + + const MWWorld::Store &store = world->getStore().get(); // Get the weapon used (if hand-to-hand, weapon = inv.end()) MWWorld::InventoryStore &inv = getInventoryStore(ptr); @@ -461,9 +464,9 @@ namespace MWClass // Reduce fatigue // somewhat of a guess, but using the weapon weight makes sense - const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat(); - const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat(); - const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat(); + const float fFatigueAttackBase = store.find("fFatigueAttackBase")->getFloat(); + const float fFatigueAttackMult = store.find("fFatigueAttackMult")->getFloat(); + const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->getFloat(); MWMechanics::DynamicStat fatigue = getCreatureStats(ptr).getFatigue(); const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr); float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; @@ -472,10 +475,10 @@ namespace MWClass fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); getCreatureStats(ptr).setFatigue(fatigue); - const float fCombatDistance = gmst.find("fCombatDistance")->getFloat(); + const float fCombatDistance = store.find("fCombatDistance")->getFloat(); float dist = fCombatDistance * (!weapon.isEmpty() ? weapon.get()->mBase->mData.mReach : - gmst.find("fHandToHandReach")->getFloat()); + store.find("fHandToHandReach")->getFloat()); // TODO: Use second to work out the hit angle std::pair result = world->getHitContact(ptr, dist); @@ -522,8 +525,8 @@ namespace MWClass if(attack) { damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); - damage *= fDamageStrengthBase->getFloat() + - (stats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult->getFloat() * 0.1); + damage *= gmst.fDamageStrengthBase->getFloat() + + (stats.getAttribute(ESM::Attribute::Strength).getModified() * gmst.fDamageStrengthMult->getFloat() * 0.1); if(weaphashealth) { int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); @@ -535,7 +538,7 @@ namespace MWClass { // Reduce weapon charge by at least one, but cap at 0 weaphealth -= std::min(std::max(1, - (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weaphealth); + (int)(damage * store.find("fWeaponDamageMult")->getFloat())), weaphealth); weapon.getCellRef().setCharge(weaphealth); } @@ -552,8 +555,8 @@ namespace MWClass // Note: MCP contains an option to include Strength in hand-to-hand damage // calculations. Some mods recommend using it, so we may want to include am // option for it. - float minstrike = gmst.find("fMinHandToHandMult")->getFloat(); - float maxstrike = gmst.find("fMaxHandToHandMult")->getFloat(); + float minstrike = store.find("fMinHandToHandMult")->getFloat(); + float maxstrike = store.find("fMaxHandToHandMult")->getFloat(); damage = stats.getSkill(weapskill).getModified(); damage *= minstrike + ((maxstrike-minstrike)*stats.getAttackStrength()); @@ -567,7 +570,7 @@ namespace MWClass damage *= glob.find("WerewolfClawMult")->mValue.getFloat(); } if(healthdmg) - damage *= gmst.find("fHandtoHandHealthPer")->getFloat(); + damage *= store.find("fHandtoHandHealthPer")->getFloat(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(stats.isWerewolf()) @@ -585,12 +588,12 @@ namespace MWClass bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim); if(!detected) { - damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); + damage *= store.find("fCombatCriticalStrikeMult")->getFloat(); MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); } if (othercls.getCreatureStats(victim).getKnockedDown()) - damage *= gmst.find("fCombatKODamageMult")->getFloat(); + damage *= store.find("fCombatKODamageMult")->getFloat(); // Apply "On hit" enchanted weapons std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : ""; @@ -661,6 +664,8 @@ namespace MWClass // something, alert the character controller, scripts, etc. const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const GMST& gmst = getGmst(); + int chance = store.get().find("iVoiceHitOdds")->getInt(); int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] if (roll < chance) @@ -669,9 +674,9 @@ namespace MWClass } // Check for knockdown - float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat(); + float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat(); float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() - * iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt(); + * gmst.iKnockDownOddsMult->getInt() * 0.01 + gmst.iKnockDownOddsBase->getInt(); roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] if (ishealth && agilityTerm <= damage && knockdownTerm <= roll) { @@ -870,6 +875,8 @@ namespace MWClass float Npc::getSpeed(const MWWorld::Ptr& ptr) const { const MWBase::World *world = MWBase::Environment::get().getWorld(); + const GMST& gmst = getGmst(); + const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); @@ -878,17 +885,17 @@ namespace MWClass bool sneaking = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak); bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run); - float walkSpeed = fMinWalkSpeed->getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()* - (fMaxWalkSpeed->getFloat() - fMinWalkSpeed->getFloat()); - walkSpeed *= 1.0f - fEncumberedMoveEffect->getFloat()*normalizedEncumbrance; + float walkSpeed = gmst.fMinWalkSpeed->getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()* + (gmst.fMaxWalkSpeed->getFloat() - gmst.fMinWalkSpeed->getFloat()); + walkSpeed *= 1.0f - gmst.fEncumberedMoveEffect->getFloat()*normalizedEncumbrance; walkSpeed = std::max(0.0f, walkSpeed); if(sneaking) - walkSpeed *= fSneakSpeedMultiplier->getFloat(); + walkSpeed *= gmst.fSneakSpeedMultiplier->getFloat(); float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() * - fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat()); + gmst.fAthleticsRunBonus->getFloat() + gmst.fBaseRunMultiplier->getFloat()); if(npcdata->mNpcStats.isWerewolf()) - runSpeed *= fWereWolfRunMult->getFloat(); + runSpeed *= gmst.fWereWolfRunMult->getFloat(); float moveSpeed; if(normalizedEncumbrance >= 1.0f) @@ -898,8 +905,8 @@ namespace MWClass { float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() + mageffects.get(ESM::MagicEffect::Levitate).mMagnitude); - flySpeed = fMinFlySpeed->getFloat() + flySpeed*(fMaxFlySpeed->getFloat() - fMinFlySpeed->getFloat()); - flySpeed *= 1.0f - fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; + flySpeed = gmst.fMinFlySpeed->getFloat() + flySpeed*(gmst.fMaxFlySpeed->getFloat() - gmst.fMinFlySpeed->getFloat()); + flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; } @@ -909,8 +916,8 @@ namespace MWClass if(running) swimSpeed = runSpeed; swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).mMagnitude; - swimSpeed *= fSwimRunBase->getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()* - fSwimRunAthleticsMult->getFloat(); + swimSpeed *= gmst.fSwimRunBase->getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()* + gmst.fSwimRunAthleticsMult->getFloat(); moveSpeed = swimSpeed; } else if(running && !sneaking) @@ -926,9 +933,10 @@ namespace MWClass float Npc::getJump(const MWWorld::Ptr &ptr) const { const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); + const GMST& gmst = getGmst(); const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); - const float encumbranceTerm = fJumpEncumbranceBase->getFloat() + - fJumpEncumbranceMultiplier->getFloat() * + const float encumbranceTerm = gmst.fJumpEncumbranceBase->getFloat() + + gmst.fJumpEncumbranceMultiplier->getFloat() * (1.0f - Npc::getEncumbrance(ptr)/Npc::getCapacity(ptr)); float a = npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified(); @@ -939,14 +947,14 @@ namespace MWClass a = 50.0f; } - float x = fJumpAcrobaticsBase->getFloat() + - std::pow(a / 15.0f, fJumpAcroMultiplier->getFloat()); - x += 3.0f * b * fJumpAcroMultiplier->getFloat(); + float x = gmst.fJumpAcrobaticsBase->getFloat() + + std::pow(a / 15.0f, gmst.fJumpAcroMultiplier->getFloat()); + x += 3.0f * b * gmst.fJumpAcroMultiplier->getFloat(); x += mageffects.get(ESM::MagicEffect::Jump).mMagnitude * 64; x *= encumbranceTerm; if(ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) - x *= fJumpRunMultiplier->getFloat(); + x *= gmst.fJumpRunMultiplier->getFloat(); x *= npcdata->mNpcStats.getFatigueTerm(); x -= -627.2f;/*gravity constant*/ x /= 3.0f; @@ -957,19 +965,19 @@ namespace MWClass float Npc::getFallDamage(const MWWorld::Ptr &ptr, float fallHeight) const { MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); + const MWWorld::Store &store = world->getStore().get(); - const float fallDistanceMin = gmst.find("fFallDamageDistanceMin")->getFloat(); + const float fallDistanceMin = store.find("fFallDamageDistanceMin")->getFloat(); if (fallHeight >= fallDistanceMin) { const float acrobaticsSkill = ptr.getClass().getNpcStats (ptr).getSkill(ESM::Skill::Acrobatics).getModified(); const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(ESM::MagicEffect::Jump).mMagnitude; - const float fallAcroBase = gmst.find("fFallAcroBase")->getFloat(); - const float fallAcroMult = gmst.find("fFallAcroMult")->getFloat(); - const float fallDistanceBase = gmst.find("fFallDistanceBase")->getFloat(); - const float fallDistanceMult = gmst.find("fFallDistanceMult")->getFloat(); + const float fallAcroBase = store.find("fFallAcroBase")->getFloat(); + const float fallAcroMult = store.find("fFallAcroMult")->getFloat(); + const float fallDistanceBase = store.find("fFallDistanceBase")->getFloat(); + const float fallDistanceMult = store.find("fFallDistanceMult")->getFloat(); float x = fallHeight - fallDistanceMin; x -= (1.5 * acrobaticsSkill) + jumpSpellBonus; @@ -1104,14 +1112,14 @@ namespace MWClass float Npc::getArmorRating (const MWWorld::Ptr& ptr) const { const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); + const MWWorld::Store &store = world->getStore().get(); MWMechanics::NpcStats &stats = getNpcStats(ptr); MWWorld::InventoryStore &invStore = getInventoryStore(ptr); - int iBaseArmorSkill = gmst.find("iBaseArmorSkill")->getInt(); - float fUnarmoredBase1 = gmst.find("fUnarmoredBase1")->getFloat(); - float fUnarmoredBase2 = gmst.find("fUnarmoredBase2")->getFloat(); + int iBaseArmorSkill = store.find("iBaseArmorSkill")->getInt(); + float fUnarmoredBase1 = store.find("fUnarmoredBase1")->getFloat(); + float fUnarmoredBase2 = store.find("fUnarmoredBase2")->getFloat(); int unarmoredSkill = stats.getSkill(ESM::Skill::Unarmored).getModified(); int ratings[MWWorld::InventoryStore::Slots]; @@ -1367,27 +1375,4 @@ namespace MWClass MWWorld::ContainerStore& store = getContainerStore(ptr); store.restock(list, ptr, ptr.getCellRef().getRefId(), ptr.getCellRef().getFaction()); } - - const ESM::GameSetting *Npc::fMinWalkSpeed; - const ESM::GameSetting *Npc::fMaxWalkSpeed; - const ESM::GameSetting *Npc::fEncumberedMoveEffect; - const ESM::GameSetting *Npc::fSneakSpeedMultiplier; - const ESM::GameSetting *Npc::fAthleticsRunBonus; - const ESM::GameSetting *Npc::fBaseRunMultiplier; - const ESM::GameSetting *Npc::fMinFlySpeed; - const ESM::GameSetting *Npc::fMaxFlySpeed; - const ESM::GameSetting *Npc::fSwimRunBase; - const ESM::GameSetting *Npc::fSwimRunAthleticsMult; - const ESM::GameSetting *Npc::fJumpEncumbranceBase; - const ESM::GameSetting *Npc::fJumpEncumbranceMultiplier; - const ESM::GameSetting *Npc::fJumpAcrobaticsBase; - const ESM::GameSetting *Npc::fJumpAcroMultiplier; - const ESM::GameSetting *Npc::fJumpRunMultiplier; - const ESM::GameSetting *Npc::fWereWolfRunMult; - const ESM::GameSetting *Npc::fKnockDownMult; - const ESM::GameSetting *Npc::iKnockDownOddsMult; - const ESM::GameSetting *Npc::iKnockDownOddsBase; - const ESM::GameSetting *Npc::fDamageStrengthBase; - const ESM::GameSetting *Npc::fDamageStrengthMult; - } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 356e358b9d..d6b1f8c268 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -17,27 +17,32 @@ namespace MWClass virtual MWWorld::Ptr copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; - static const ESM::GameSetting *fMinWalkSpeed; - static const ESM::GameSetting *fMaxWalkSpeed; - static const ESM::GameSetting *fEncumberedMoveEffect; - static const ESM::GameSetting *fSneakSpeedMultiplier; - static const ESM::GameSetting *fAthleticsRunBonus; - static const ESM::GameSetting *fBaseRunMultiplier; - static const ESM::GameSetting *fMinFlySpeed; - static const ESM::GameSetting *fMaxFlySpeed; - static const ESM::GameSetting *fSwimRunBase; - static const ESM::GameSetting *fSwimRunAthleticsMult; - static const ESM::GameSetting *fJumpEncumbranceBase; - static const ESM::GameSetting *fJumpEncumbranceMultiplier; - static const ESM::GameSetting *fJumpAcrobaticsBase; - static const ESM::GameSetting *fJumpAcroMultiplier; - static const ESM::GameSetting *fJumpRunMultiplier; - static const ESM::GameSetting *fWereWolfRunMult; - static const ESM::GameSetting *fKnockDownMult; - static const ESM::GameSetting *iKnockDownOddsMult; - static const ESM::GameSetting *iKnockDownOddsBase; - static const ESM::GameSetting *fDamageStrengthBase; - static const ESM::GameSetting *fDamageStrengthMult; + struct GMST + { + const ESM::GameSetting *fMinWalkSpeed; + const ESM::GameSetting *fMaxWalkSpeed; + const ESM::GameSetting *fEncumberedMoveEffect; + const ESM::GameSetting *fSneakSpeedMultiplier; + const ESM::GameSetting *fAthleticsRunBonus; + const ESM::GameSetting *fBaseRunMultiplier; + const ESM::GameSetting *fMinFlySpeed; + const ESM::GameSetting *fMaxFlySpeed; + const ESM::GameSetting *fSwimRunBase; + const ESM::GameSetting *fSwimRunAthleticsMult; + const ESM::GameSetting *fJumpEncumbranceBase; + const ESM::GameSetting *fJumpEncumbranceMultiplier; + const ESM::GameSetting *fJumpAcrobaticsBase; + const ESM::GameSetting *fJumpAcroMultiplier; + const ESM::GameSetting *fJumpRunMultiplier; + const ESM::GameSetting *fWereWolfRunMult; + const ESM::GameSetting *fKnockDownMult; + const ESM::GameSetting *iKnockDownOddsMult; + const ESM::GameSetting *iKnockDownOddsBase; + const ESM::GameSetting *fDamageStrengthBase; + const ESM::GameSetting *fDamageStrengthMult; + }; + + static const GMST& getGmst(); public: From cc3c6ae7b854601d74cbebf9c26e1178e230f62e Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jun 2014 02:33:44 +0200 Subject: [PATCH 138/226] Fix very slow movement on some creatures, e.g. rats (Bug #1136) Neither fAthleticsRunBonus, fBaseRunMultiplier or the creature's athletics skill (i.e. Combat stat) have any effect on the run speed (tested by setting those to absurd values). The new formula is just a guess and doesn't seem to be completely accurate. --- apps/openmw/mwclass/creature.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4a159a7731..1f286f7e6d 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -537,8 +537,8 @@ namespace MWClass bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run); - float runSpeed = walkSpeed*(0.01f * getSkill(ptr, ESM::Skill::Athletics) * - gmst.fAthleticsRunBonus->getFloat() + gmst.fBaseRunMultiplier->getFloat()); + float runSpeed = walkSpeed*6; + runSpeed = std::min(gmst.fMaxWalkSpeedCreature->getFloat(), runSpeed); // flame atronach runs way too fast without this float moveSpeed; if(normalizedEncumbrance >= 1.0f) From 185ff279a3a19aa0b3a66fcc1cc5389d8adc62dc Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jun 2014 03:29:36 +0200 Subject: [PATCH 139/226] Add missing sound effect for mages guild transport --- apps/openmw/mwgui/travelwindow.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index c16603554e..9aa75173a6 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -10,6 +10,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -140,6 +141,9 @@ namespace MWGui if (playerGoldisExterior()) + // Interior cell -> mages guild transport + MWBase::Environment::get().getSoundManager()->playSound("mysticism cast", 1, 1); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); From 4648524df43d4c8bed8195791b2323a05384412c Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jun 2014 04:53:25 +0200 Subject: [PATCH 140/226] Improve getLOS (use eye level). Also, don't crash when used with non-actors. --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwscript/aiextensions.cpp | 9 ++++++++- apps/openmw/mwworld/worldimp.cpp | 20 ++++++++++++-------- apps/openmw/mwworld/worldimp.hpp | 2 +- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 29f821e85b..f42edafd49 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -407,7 +407,7 @@ namespace MWBase virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; ///< get all items in active cells owned by this Npc - virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0; + virtual bool getLOS(const MWWorld::Ptr& actor,const MWWorld::Ptr& targetActor) = 0; ///< get Line of Sight (morrowind stupid implementation) virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist) = 0; diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 823d22a6ae..9a3387a00d 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -371,6 +371,12 @@ namespace MWScript MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPtr(actorID, true); + if(!actor.getClass().isActor() || !observer.getClass().isActor()) + { + runtime.push(0); + return; + } + Interpreter::Type_Integer value = MWBase::Environment::get().getWorld()->getLOS(observer, actor) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer); @@ -392,9 +398,10 @@ namespace MWScript std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); + MWWorld::Ptr dest = MWBase::Environment::get().getWorld()->getPtr(actorID,true); bool value = false; - if(dest != MWWorld::Ptr() ) + if(dest != MWWorld::Ptr() && source.getClass().isActor() && dest.getClass().isActor()) { value = MWBase::Environment::get().getWorld()->getLOS(source,dest); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 6e975d25a8..bcae8c070c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2031,20 +2031,24 @@ namespace MWWorld } } - bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) + bool World::getLOS(const MWWorld::Ptr& actor,const MWWorld::Ptr& targetActor) { - if (!targetNpc.getRefData().isEnabled() || !npc.getRefData().isEnabled()) + if (!targetActor.getRefData().isEnabled() || !actor.getRefData().isEnabled()) return false; // cannot get LOS unless both NPC's are enabled - Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents(); - const float* pos1 = npc.getRefData().getPosition().pos; - Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents(); - const float* pos2 = targetNpc.getRefData().getPosition().pos; + if (!targetActor.getRefData().getBaseNode() || !targetActor.getRefData().getBaseNode()) + return false; // not in active cell - btVector3 from(pos1[0],pos1[1],pos1[2]+halfExt1.z); - btVector3 to(pos2[0],pos2[1],pos2[2]+halfExt2.z); + Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(actor.getRefData().getHandle())->getHalfExtents(); + const float* pos1 = actor.getRefData().getPosition().pos; + Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetActor.getRefData().getHandle())->getHalfExtents(); + const float* pos2 = targetActor.getRefData().getPosition().pos; + + btVector3 from(pos1[0],pos1[1],pos1[2]+halfExt1.z*2*0.9); // eye level + btVector3 to(pos2[0],pos2[1],pos2[2]+halfExt2.z*2*0.9); std::pair result = mPhysEngine->rayTest(from, to,false); if(result.first == "") return true; + return false; } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index db599b0de7..8cb2ac18a3 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -482,7 +482,7 @@ namespace MWWorld virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out); ///< get all items in active cells owned by this Npc - virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc); + virtual bool getLOS(const MWWorld::Ptr& actor,const MWWorld::Ptr& targetActor); ///< get Line of Sight (morrowind stupid implementation) virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist); From c69a311ad83a9c4046a6131118738911d6e620f3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jun 2014 04:57:46 +0200 Subject: [PATCH 141/226] Disable lighting for particles Fixes magic cast visuals being too dark depending on the environment. --- components/nifogre/material.cpp | 10 +++++++++- components/nifogre/material.hpp | 2 +- components/nifogre/ogrenifloader.cpp | 5 ++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 3a87e1d529..b1ae83107b 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -106,7 +106,7 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, const Nif::NiZBufferProperty *zprop, const Nif::NiSpecularProperty *specprop, const Nif::NiWireframeProperty *wireprop, - bool &needTangents) + bool &needTangents, bool disableLighting) { Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton(); Ogre::MaterialPtr material = matMgr.getByName(name); @@ -245,6 +245,14 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, } } + if (disableLighting) + { + ambient = Ogre::Vector3(0.f); + diffuse = Ogre::Vector3(0.f); + specular = Ogre::Vector3(0.f); + emissive = Ogre::Vector3(1.f); + } + { // Generate a hash out of all properties that can affect the material. size_t h = 0; diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp index b02c7c2366..890255dcc6 100644 --- a/components/nifogre/material.hpp +++ b/components/nifogre/material.hpp @@ -49,7 +49,7 @@ public: const Nif::NiZBufferProperty *zprop, const Nif::NiSpecularProperty *specprop, const Nif::NiWireframeProperty *wireprop, - bool &needTangents); + bool &needTangents, bool disableLighting=false); }; } diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 36d7508214..813c1660d9 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -850,7 +850,10 @@ class NIFObjectLoader partsys->setMaterialName(NIFMaterialLoader::getMaterial(particledata, fullname, group, texprop, matprop, alphaprop, vertprop, zprop, specprop, - wireprop, needTangents)); + wireprop, needTangents, + // MW doesn't light particles, but the MaterialProperty + // used still has lighting, so that must be ignored. + true)); partsys->setCullIndividually(false); partsys->setParticleQuota(particledata->numParticles); From 11b05c352fc038dabd41eb8e51d0ebbb411dcff3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jun 2014 16:00:27 +0200 Subject: [PATCH 142/226] Fix exception when casting Bound Gloves spell --- apps/openmw/mwmechanics/actors.cpp | 8 ++++++-- apps/openmw/mwworld/projectilemanager.cpp | 4 ++-- apps/openmw/mwworld/projectilemanager.hpp | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d72ab7db89..6ea65275ba 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -530,8 +530,12 @@ namespace MWMechanics itemGmst)->getString(); if (it->first == ESM::MagicEffect::BoundGloves) { - adjustBoundItem("sMagicBoundLeftGauntletID", magnitude > 0, ptr); - adjustBoundItem("sMagicBoundRightGauntletID", magnitude > 0, ptr); + item = MWBase::Environment::get().getWorld()->getStore().get().find( + "sMagicBoundLeftGauntletID")->getString(); + adjustBoundItem(item, magnitude > 0, ptr); + item = MWBase::Environment::get().getWorld()->getStore().get().find( + "sMagicBoundRightGauntletID")->getString(); + adjustBoundItem(item, magnitude > 0, ptr); } else adjustBoundItem(item, magnitude > 0, ptr); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index cfb407b4a0..3122cb3252 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -78,7 +78,7 @@ namespace MWWorld state.mSourceName = sourceName; state.mId = model; state.mSpellId = spellId; - state.mCaster = caster; + state.mCasterHandle = caster.getRefData().getHandle(); if (caster.getClass().isActor()) state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); else @@ -162,7 +162,7 @@ namespace MWWorld { MWWorld::Ptr obstacle = MWBase::Environment::get().getWorld()->searchPtrViaHandle(cIt->second); - MWWorld::Ptr caster = it->mCaster; + MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaHandle(it->mCasterHandle); if (caster.isEmpty()) caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 4627d3b8a5..6e84b9efb2 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -67,12 +67,12 @@ namespace MWWorld int mActorId; - // actorId doesn't work for non-actors, so we also keep track of the Ptr. + // actorId doesn't work for non-actors, so we also keep track of the Ogre-handle. // For non-actors, the caster ptr is mainly needed to prevent the projectile // from colliding with its caster. // TODO: this will break when the game is saved and reloaded, since there is currently // no way to write identifiers for non-actors to a savegame. - MWWorld::Ptr mCaster; + std::string mCasterHandle; // MW-id of this projectile std::string mId; From 218f916d6da5c63203fa7001cfbac3cc37d34b2a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jun 2014 17:17:26 +0200 Subject: [PATCH 143/226] Savegame: Don't write stat modifiers that are zero --- components/esm/statstate.hpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/components/esm/statstate.hpp b/components/esm/statstate.hpp index 4b4023bc22..f1a3b4d794 100644 --- a/components/esm/statstate.hpp +++ b/components/esm/statstate.hpp @@ -12,7 +12,8 @@ namespace ESM struct StatState { T mBase; - T mMod; + T mMod; // Note: can either be the modifier, or the modified value. + // A bit inconsistent, but we can't fix this without breaking compatibility. T mCurrent; T mDamage; float mProgress; @@ -30,7 +31,9 @@ namespace ESM void StatState::load (ESMReader &esm) { esm.getHNT (mBase, "STBA"); - esm.getHNT (mMod, "STMO"); + + mMod = 0; + esm.getHNOT (mMod, "STMO"); mCurrent = 0; esm.getHNOT (mCurrent, "STCU"); mDamage = 0; @@ -43,7 +46,9 @@ namespace ESM void StatState::save (ESMWriter &esm) const { esm.writeHNT ("STBA", mBase); - esm.writeHNT ("STMO", mMod); + + if (mMod != 0) + esm.writeHNT ("STMO", mMod); if (mCurrent) esm.writeHNT ("STCU", mCurrent); @@ -56,4 +61,4 @@ namespace ESM } } -#endif \ No newline at end of file +#endif From 8eab3abb15b30770a8f08c25165db53a1f8a5d47 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jun 2014 19:02:42 +0200 Subject: [PATCH 144/226] Fix initial view offset for large world maps (Fixes #1523) --- apps/openmw/mwgui/mapwindow.cpp | 7 +++---- files/mygui/openmw_map_window.layout | 2 -- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index db3cfadde6..af26456f27 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -431,6 +431,9 @@ namespace MWGui { mGlobalMapRender = new MWRender::GlobalMap(""); mGlobalMapRender->render(loadingListener); + mGlobalMap->setCanvasSize (mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); + mGlobalMapImage->setSize(mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); + mGlobalMapImage->setImageTexture("GlobalMap.png"); mGlobalMapOverlay->setImageTexture("GlobalMapOverlay"); } @@ -512,7 +515,6 @@ namespace MWGui else mGlobalMap->setViewOffset( mGlobalMap->getViewOffset() + diff ); - mLastDragPos = MyGUI::IntPoint(_left, _top); } @@ -536,9 +538,6 @@ namespace MWGui void MapWindow::open() { - mGlobalMap->setCanvasSize (mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); - mGlobalMapImage->setSize(mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); - // force markers to foreground for (unsigned int i=0; igetChildCount (); ++i) { diff --git a/files/mygui/openmw_map_window.layout b/files/mygui/openmw_map_window.layout index 6e0efce7e1..b842888a19 100644 --- a/files/mygui/openmw_map_window.layout +++ b/files/mygui/openmw_map_window.layout @@ -17,8 +17,6 @@ - - From 091f9a8fdceb59be6cd37aa716aef04d0460ab0c Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jun 2014 19:14:59 +0200 Subject: [PATCH 145/226] Optimize global map render slightly --- apps/openmw/mwrender/globalmap.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 0537ed5160..1ccfd9527c 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -62,6 +62,12 @@ namespace MWRender loadingListener->setProgressRange((mMaxX-mMinX+1) * (mMaxY-mMinY+1)); loadingListener->setProgress(0); + const Ogre::ColourValue waterShallowColour(0.15, 0.2, 0.19); + const Ogre::ColourValue waterDeepColour(0.1, 0.14, 0.13); + const Ogre::ColourValue groundColour(0.254, 0.19, 0.13); + const Ogre::ColourValue mountainColour(0.05, 0.05, 0.05); + const Ogre::ColourValue hillColour(0.16, 0.12, 0.08); + //if (!boost::filesystem::exists(mCacheDir + "/GlobalMap.png")) if (1) { @@ -91,12 +97,6 @@ namespace MWRender int texelX = (x-mMinX) * cellSize + cellX; int texelY = (mHeight-1) - ((y-mMinY) * cellSize + cellY); - Ogre::ColourValue waterShallowColour(0.15, 0.2, 0.19); - Ogre::ColourValue waterDeepColour(0.1, 0.14, 0.13); - Ogre::ColourValue groundColour(0.254, 0.19, 0.13); - Ogre::ColourValue mountainColour(0.05, 0.05, 0.05); - Ogre::ColourValue hillColour(0.16, 0.12, 0.08); - unsigned char r,g,b; if (land) From ad0a182b7e57b90a953d70a4541d2e63b3f249e2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Jun 2014 20:08:53 +0200 Subject: [PATCH 146/226] Improve error message for unknown cells --- apps/openmw/mwscript/transformationextensions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 138326ff04..3860257ad6 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -323,7 +323,7 @@ namespace MWScript } else { - throw std::runtime_error ("unknown cell"); + throw std::runtime_error (std::string("unknown cell (") + cellID + ")"); } } }; @@ -420,7 +420,7 @@ namespace MWScript } else { - throw std::runtime_error ("unknown cell"); + throw std::runtime_error ( std::string("unknown cell (") + cellID + ")"); } } }; From 9a26cf22e6a904ef1875d3cc4d23f78ac263b821 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 20 Jun 2014 18:49:19 +0200 Subject: [PATCH 147/226] Implement toggleMenus --- apps/openmw/mwbase/windowmanager.hpp | 1 + apps/openmw/mwgui/windowmanagerimp.cpp | 17 ++++++++++++++++- apps/openmw/mwgui/windowmanagerimp.hpp | 4 ++++ apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- apps/openmw/mwscript/docs/vmformat.txt | 3 ++- apps/openmw/mwscript/guiextensions.cpp | 10 ++++++++++ components/compiler/extensions0.cpp | 2 ++ components/compiler/opcodes.hpp | 1 + 8 files changed, 37 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 0f45542ba0..8b407c9bab 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -228,6 +228,7 @@ namespace MWBase virtual void showCrosshair(bool show) = 0; virtual bool getSubtitlesEnabled() = 0; virtual void toggleHud() = 0; + virtual bool toggleGui() = 0; virtual void disallowMouse() = 0; virtual void allowMouse() = 0; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 3f239e0e31..865ad1dca9 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -118,6 +118,7 @@ namespace MWGui , mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD")) , mSubtitlesEnabled(Settings::Manager::getBool ("subtitles", "GUI")) , mHudEnabled(true) + , mGuiEnabled(true) , mCursorVisible(true) , mPlayerName() , mPlayerRaceId() @@ -420,7 +421,7 @@ namespace MWGui mRecharge->setVisible(false); mVideoBackground->setVisible(false); - mHud->setVisible(mHudEnabled); + mHud->setVisible(mHudEnabled && mGuiEnabled); bool gameMode = !isGuiMode(); @@ -430,6 +431,13 @@ namespace MWGui if (gameMode) setKeyFocusWidget (NULL); + if (!mGuiEnabled) + { + if (containsMode(GM_Console)) + mConsole->setVisible(true); + return; + } + // Icons of forced hidden windows are displayed setMinimapVisibility((mAllowed & GW_Map) && (!mMap->pinned() || (mForceHidden & GW_Map))); setWeaponVisibility((mAllowed & GW_Inventory) && (!mInventoryWindow->pinned() || (mForceHidden & GW_Inventory))); @@ -1345,6 +1353,13 @@ namespace MWGui mHud->setVisible (mHudEnabled); } + bool WindowManager::toggleGui() + { + mGuiEnabled = !mGuiEnabled; + updateVisible(); + return mGuiEnabled; + } + bool WindowManager::getRestEnabled() { //Enable rest dialogue if character creation finished diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index dd41635ad3..8093d637e2 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -223,6 +223,9 @@ namespace MWGui virtual bool getSubtitlesEnabled(); virtual void toggleHud(); + /// Turn visibility of *all* GUI elements on or off (HUD and all windows, except the console) + virtual bool toggleGui(); + virtual void disallowMouse(); virtual void allowMouse(); virtual void notifyInputActionBound(); @@ -381,6 +384,7 @@ namespace MWGui bool mCrosshairEnabled; bool mSubtitlesEnabled; bool mHudEnabled; + bool mGuiEnabled; bool mCursorVisible; void setCursorVisible(bool visible); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index cd3be4b423..afdde6fb01 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -277,7 +277,7 @@ namespace MWInput showQuickKeysMenu(); break; case A_ToggleHUD: - MWBase::Environment::get().getWindowManager()->toggleHud(); + MWBase::Environment::get().getWindowManager()->toggleGui(); break; case A_QuickSave: quickSave(); diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index c125985c05..4b2a91a958 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -400,5 +400,6 @@ op 0x2000247: BetaComment op 0x2000248: BetaComment, explicit op 0x2000249: OnMurder op 0x200024a: OnMurder, explicit +op 0x200024b: ToggleMenus -opcodes 0x200024b-0x3ffffff unused +opcodes 0x200024c-0x3ffffff unused diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 333be5be61..afc745beb9 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -203,6 +203,15 @@ namespace MWScript } }; + class OpToggleMenus : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + bool state = MWBase::Environment::get().getWindowManager()->toggleGui(); + runtime.getContext().report(state ? "GUI -> On" : "GUI -> Off"); + } + }; void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -242,6 +251,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Gui::opcodeShowMap, new OpShowMap); interpreter.installSegment5 (Compiler::Gui::opcodeFillMap, new OpFillMap); interpreter.installSegment3 (Compiler::Gui::opcodeMenuTest, new OpMenuTest); + interpreter.installSegment5 (Compiler::Gui::opcodeToggleMenus, new OpToggleMenus); } } } diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index efd45f9127..9fc66900f0 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -216,6 +216,8 @@ namespace Compiler extensions.registerInstruction ("showmap", "S", opcodeShowMap); extensions.registerInstruction ("fillmap", "", opcodeFillMap); extensions.registerInstruction ("menutest", "/l", opcodeMenuTest); + extensions.registerInstruction ("togglemenus", "", opcodeToggleMenus); + extensions.registerInstruction ("tm", "", opcodeToggleMenus); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index ed6f1df923..b097a017bd 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -178,6 +178,7 @@ namespace Compiler const int opcodeShowMap = 0x20001a0; const int opcodeFillMap = 0x20001a1; const int opcodeMenuTest = 0x2002c; + const int opcodeToggleMenus = 0x200024b; } namespace Misc From 7bf7daa846606774e57fd5320e5af764a9ba35af Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 20 Jun 2014 21:33:03 +0200 Subject: [PATCH 148/226] Fix exception (getline will trigger failbit on the last line) --- components/translation/translation.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index 976ae926d0..5341240afa 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -32,9 +32,6 @@ namespace Translation boost::filesystem::ifstream stream ( dataFileCollections.getCollection (extension).getPath (fileName)); - // Configure the stream to throw exception upon error - stream.exceptions ( boost::filesystem::ifstream::failbit | boost::filesystem::ifstream::badbit ); - if (!stream.is_open()) throw std::runtime_error ("failed to open translation file: " + fileName); @@ -44,9 +41,8 @@ namespace Translation void Storage::loadDataFromStream(ContainerType& container, std::istream& stream) { - // NOTE: does not handle failbit/badbit. stream must be set up beforehand to throw in these cases. std::string line; - while (!stream.eof()) + while (!stream.eof() && !stream.fail()) { std::getline( stream, line ); if (!line.empty() && *line.rbegin() == '\r') From a4ce9d6a7fb472549b0a136d431bf312ec34743a Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 20 Jun 2014 23:56:21 +0200 Subject: [PATCH 149/226] Always show the script name when a script fails to compile --- apps/openmw/mwscript/scriptmanagerimp.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 74c85dbbf7..7b858dacf5 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -64,12 +64,12 @@ namespace MWScript Success = false; } - if (!Success && mVerbose) + if (!Success) { std::cerr - << "compiling failed: " << name << std::endl - << script->mScriptText - << std::endl << std::endl; + << "compiling failed: " << name << std::endl; + if (mVerbose) + std::cerr << script->mScriptText << std::endl << std::endl; } if (Success) From 98329a94b4b73b6ded6cd4ee2dd04cbf2a920762 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 21 Jun 2014 19:54:12 +0200 Subject: [PATCH 150/226] Add case sensitivity workaround for spine bones (Fixes #1547) --- apps/openmw/mwrender/weaponanimation.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 5f953bd838..cd9ae16b8b 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -150,9 +150,19 @@ void WeaponAnimation::pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel) return; float pitch = xrot * mPitchFactor; - Ogre::Node *node = skel->getBone("Bip01 Spine2"); + Ogre::Node *node; + + // In spherearcher.nif, we have spine, not Spine. Not sure if all bone names should be case insensitive? + if (skel->hasBone("Bip01 spine2")) + node = skel->getBone("Bip01 spine2"); + else + node = skel->getBone("Bip01 Spine2"); node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); - node = skel->getBone("Bip01 Spine1"); + + if (skel->hasBone("Bip01 spine1")) // in spherearcher.nif + node = skel->getBone("Bip01 spine1"); + else + node = skel->getBone("Bip01 Spine1"); node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); } From e002acdeaeb3c2559f36b7c9f0ee4ac6abeb7748 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 21 Jun 2014 23:32:58 +0200 Subject: [PATCH 151/226] Physics: Create actor shapes outside of BulletNifLoader This will allow to create a specialised shape instead, such as a capsule, which tends to work better for character controllers. --- apps/openmw/mwworld/physicssystem.cpp | 10 +- apps/openmw/mwworld/worldimp.cpp | 2 +- components/nifbullet/bulletnifloader.cpp | 49 ++++++ components/nifbullet/bulletnifloader.hpp | 5 + libs/openengine/bullet/physic.cpp | 194 +++++++++++------------ libs/openengine/bullet/physic.hpp | 52 +++--- libs/openengine/bullet/trace.cpp | 18 +-- libs/openengine/bullet/trace.h | 3 +- 8 files changed, 183 insertions(+), 150 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 73a704c88e..8aca591e8e 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -175,7 +175,7 @@ namespace MWWorld const int maxHeight = 200.f; OEngine::Physic::ActorTracer tracer; - tracer.findGround(physicActor->getCollisionBody(), position, position-Ogre::Vector3(0,0,maxHeight), engine); + tracer.findGround(physicActor, position, position-Ogre::Vector3(0,0,maxHeight), engine); if(tracer.mFraction >= 1.0f) { physicActor->setOnGround(false); @@ -607,9 +607,10 @@ namespace MWWorld Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); const std::string &handle = node->getName(); const Ogre::Quaternion &rotation = node->getOrientation(); + + // TODO: map to MWWorld::Ptr for faster access if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) { - //Needs to be changed act->setRotation(rotation); } if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle)) @@ -740,8 +741,9 @@ namespace MWWorld btCollisionObject object; object.setCollisionShape(&planeShape); + // TODO: this seems to have a slight performance impact if (waterCollision) - mEngine->dynamicsWorld->addCollisionObject(&object); + mEngine->mDynamicsWorld->addCollisionObject(&object); // 100 points of slowfall reduce gravity by 90% (this is just a guess) float slowFall = 1-std::min(std::max(0.f, (effects.get(ESM::MagicEffect::SlowFall).mMagnitude / 100.f) * 0.9f), 0.9f); @@ -751,7 +753,7 @@ namespace MWWorld waterlevel, slowFall, mEngine); if (waterCollision) - mEngine->dynamicsWorld->removeCollisionObject(&object); + mEngine->mDynamicsWorld->removeCollisionObject(&object); float heightDiff = newpos.z - oldHeight; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bcae8c070c..be334646be 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1847,7 +1847,7 @@ namespace MWWorld 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(), + tracer.findGround(physactor, pos, pos - Ogre::Vector3(0, 0, 1.5f), // trace a small amount down mPhysEngine); diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index cdddb94d0a..3e753dd37b 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -304,4 +304,53 @@ void ManualBulletShapeLoader::load(const std::string &name,const std::string &gr OEngine::Physic::BulletShapeManager::getSingleton().create(name,group,true,this); } +bool findBoundingBox (const Nif::Node* node, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation) +{ + if(node->hasBounds) + { + if (!(node->flags & Nif::NiNode::Flag_Hidden)) + { + translation = node->boundPos; + orientation = node->boundRot; + halfExtents = node->boundXYZ; + return true; + } + } + + const Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) + { + const Nif::NodeList &list = ninode->children; + for(size_t i = 0;i < list.length();i++) + { + if(!list[i].empty()) + if (findBoundingBox(list[i].getPtr(), halfExtents, translation, orientation)) + return true; + } + } + return false; +} + +bool getBoundingBox(const std::string& nifFile, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation) +{ + Nif::NIFFile::ptr pnif (Nif::NIFFile::create (nifFile)); + Nif::NIFFile & nif = *pnif.get (); + + if (nif.numRoots() < 1) + { + return false; + } + + Nif::Record *r = nif.getRoot(0); + assert(r != NULL); + + Nif::Node *node = dynamic_cast(r); + if (node == NULL) + { + return false; + } + + return findBoundingBox(node, halfExtents, translation, orientation); +} + } // namespace NifBullet diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index d1e8763053..fb7d3d70a3 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -108,6 +108,11 @@ private: bool mHasShape; }; + +bool getBoundingBox(const std::string& nifFile, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation); + +bool findBoundingBox(const Nif::Node* node, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation); + } #endif diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 8d9ac3c228..bc34f2f516 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -16,115 +16,93 @@ namespace Physic { PhysicActor::PhysicActor(const std::string &name, const std::string &mesh, PhysicEngine *engine, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, float scale) - : mName(name), mEngine(engine), mMesh(mesh), mBoxScaledTranslation(0,0,0), mBoxRotationInverse(0,0,0,0) - , mBody(0), mRaycastingBody(0), mOnGround(false), mCollisionMode(true), mBoxRotation(0,0,0,0) - , mCollisionBody(true) + : mName(name), mEngine(engine), mMesh(mesh) + , mBody(0), mOnGround(false), mInternalCollisionMode(true) + , mExternalCollisionMode(true) , mForce(0.0f) + , mScale(scale) { - mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation); - mRaycastingBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation, true); - Ogre::Quaternion inverse = mBoxRotation.Inverse(); - mBoxRotationInverse = Ogre::Quaternion(inverse.w, inverse.x, inverse.y,inverse.z); - mEngine->addRigidBody(mBody, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map + if (!NifBullet::getBoundingBox(mMesh, mHalfExtents, mMeshTranslation, mMeshOrientation)) + { + mHalfExtents = Ogre::Vector3(0.f); + mMeshTranslation = Ogre::Vector3(0.f); + mMeshOrientation = Ogre::Quaternion::IDENTITY; + } + + mShape.reset(new btBoxShape(BtOgre::Convert::toBullet(mHalfExtents))); + mShape->setLocalScaling(btVector3(scale,scale,scale)); + + btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo + (0,0, mShape.get()); + mBody = new RigidBody(CI, name); + mBody->mPlaceable = false; + + setPosition(position); + setRotation(rotation); + + mEngine->mDynamicsWorld->addRigidBody(mBody, CollisionType_Actor, + CollisionType_Actor|CollisionType_World|CollisionType_HeightMap); } PhysicActor::~PhysicActor() { if(mBody) { - mEngine->dynamicsWorld->removeRigidBody(mBody); + mEngine->mDynamicsWorld->removeRigidBody(mBody); delete mBody; - } - if(mRaycastingBody) - { - mEngine->dynamicsWorld->removeRigidBody(mRaycastingBody); - delete mRaycastingBody; - } + } } void PhysicActor::enableCollisionMode(bool collision) { - mCollisionMode = collision; + mInternalCollisionMode = collision; } void PhysicActor::enableCollisionBody(bool collision) { assert(mBody); - if(collision && !mCollisionBody) enableCollisionBody(); - if(!collision && mCollisionBody) disableCollisionBody(); - mCollisionBody = collision; + if(collision && !mExternalCollisionMode) enableCollisionBody(); + if(!collision && mExternalCollisionMode) disableCollisionBody(); + mExternalCollisionMode = collision; } - void PhysicActor::setPosition(const Ogre::Vector3 &pos) + const Ogre::Vector3& PhysicActor::getPosition() const + { + return mPosition; + } + + void PhysicActor::setPosition(const Ogre::Vector3 &position) { assert(mBody); - if(pos != getPosition()) - { - mEngine->adjustRigidBody(mBody, pos, getRotation(), mBoxScaledTranslation, mBoxRotation); - mEngine->adjustRigidBody(mRaycastingBody, pos, getRotation(), mBoxScaledTranslation, mBoxRotation); - } + + mPosition = position; + + btTransform tr = mBody->getWorldTransform(); + Ogre::Quaternion meshrot = mMeshOrientation; + Ogre::Vector3 transrot = meshrot * (mMeshTranslation * mScale); + Ogre::Vector3 newPosition = transrot + position; + + tr.setOrigin(BtOgre::Convert::toBullet(newPosition)); + mBody->setWorldTransform(tr); } - void PhysicActor::setRotation(const Ogre::Quaternion &quat) + void PhysicActor::setRotation (const Ogre::Quaternion& rotation) { - assert(mBody); - if(!quat.equals(getRotation(), Ogre::Radian(0))){ - mEngine->adjustRigidBody(mBody, getPosition(), quat, mBoxScaledTranslation, mBoxRotation); - mEngine->adjustRigidBody(mRaycastingBody, getPosition(), quat, mBoxScaledTranslation, mBoxRotation); - - } + btTransform tr = mBody->getWorldTransform(); + tr.setRotation(BtOgre::Convert::toBullet(mMeshOrientation * rotation)); + mBody->setWorldTransform(tr); } - - Ogre::Vector3 PhysicActor::getPosition() + void PhysicActor::setScale(float scale) { - assert(mBody); - btVector3 vec = mBody->getWorldTransform().getOrigin(); - Ogre::Quaternion rotation = Ogre::Quaternion(mBody->getWorldTransform().getRotation().getW(), mBody->getWorldTransform().getRotation().getX(), - mBody->getWorldTransform().getRotation().getY(), mBody->getWorldTransform().getRotation().getZ()); - Ogre::Vector3 transrot = rotation * mBoxScaledTranslation; - Ogre::Vector3 visualPosition = Ogre::Vector3(vec.getX(), vec.getY(), vec.getZ()) - transrot; - return visualPosition; - } - - Ogre::Quaternion PhysicActor::getRotation() - { - assert(mBody); - btQuaternion quat = mBody->getWorldTransform().getRotation(); - return Ogre::Quaternion(quat.getW(), quat.getX(), quat.getY(), quat.getZ()) * mBoxRotationInverse; - } - - void PhysicActor::setScale(float scale){ - //We only need to change the scaled box translation, box rotations remain the same. - assert(mBody); - mBoxScaledTranslation = mBoxScaledTranslation / mBody->getCollisionShape()->getLocalScaling().getX(); - mBoxScaledTranslation *= scale; - Ogre::Vector3 pos = getPosition(); - Ogre::Quaternion rot = getRotation(); - if(mBody){ - mEngine->dynamicsWorld->removeRigidBody(mBody); - mEngine->dynamicsWorld->removeRigidBody(mRaycastingBody); - delete mBody; - delete mRaycastingBody; - } - //Create the newly scaled rigid body - mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, pos, rot); - mRaycastingBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, pos, rot, 0, 0, true); - mEngine->addRigidBody(mCollisionBody ? mBody : 0, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map + mScale = scale; + mShape->setLocalScaling(btVector3(scale,scale,scale)); + setPosition(mPosition); } Ogre::Vector3 PhysicActor::getHalfExtents() const { - if(mBody) - { - btBoxShape *box = static_cast(mBody->getCollisionShape()); - if(box != NULL) - { - btVector3 size = box->getHalfExtentsWithMargin(); - return Ogre::Vector3(size.getX(), size.getY(), size.getZ()); - } - } - return Ogre::Vector3(0.0f); + return mHalfExtents; } void PhysicActor::setInertialForce(const Ogre::Vector3 &force) @@ -139,12 +117,16 @@ namespace Physic void PhysicActor::disableCollisionBody() { - mEngine->dynamicsWorld->removeRigidBody(mBody); + mEngine->mDynamicsWorld->removeRigidBody(mBody); + mEngine->mDynamicsWorld->addRigidBody(mBody, CollisionType_Actor, + CollisionType_Raycasting); } void PhysicActor::enableCollisionBody() { - mEngine->dynamicsWorld->addRigidBody(mBody,CollisionType_Actor,CollisionType_World|CollisionType_HeightMap); + mEngine->mDynamicsWorld->removeRigidBody(mBody); + mEngine->mDynamicsWorld->addRigidBody(mBody, CollisionType_Actor, + CollisionType_Actor|CollisionType_World|CollisionType_HeightMap); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -189,8 +171,8 @@ namespace Physic broadphase = new btDbvtBroadphase(); // The world. - dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration); - dynamicsWorld->setGravity(btVector3(0,0,-10)); + mDynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration); + mDynamicsWorld->setGravity(btVector3(0,0,-10)); if(BulletShapeManager::getSingletonPtr() == NULL) { @@ -208,10 +190,10 @@ namespace Physic if(!isDebugCreated) { Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - mDebugDrawer = new BtOgre::DebugDrawer(node, dynamicsWorld); - dynamicsWorld->setDebugDrawer(mDebugDrawer); + mDebugDrawer = new BtOgre::DebugDrawer(node, mDynamicsWorld); + mDynamicsWorld->setDebugDrawer(mDebugDrawer); isDebugCreated = true; - dynamicsWorld->debugDrawWorld(); + mDynamicsWorld->debugDrawWorld(); } } @@ -241,7 +223,7 @@ namespace Physic HeightFieldContainer::iterator hf_it = mHeightFieldMap.begin(); for (; hf_it != mHeightFieldMap.end(); ++hf_it) { - dynamicsWorld->removeRigidBody(hf_it->second.mBody); + mDynamicsWorld->removeRigidBody(hf_it->second.mBody); delete hf_it->second.mShape; delete hf_it->second.mBody; } @@ -251,7 +233,7 @@ namespace Physic { if (rb_it->second != NULL) { - dynamicsWorld->removeRigidBody(rb_it->second); + mDynamicsWorld->removeRigidBody(rb_it->second); delete rb_it->second; rb_it->second = NULL; @@ -262,7 +244,7 @@ namespace Physic { if (rb_it->second != NULL) { - dynamicsWorld->removeRigidBody(rb_it->second); + mDynamicsWorld->removeRigidBody(rb_it->second); delete rb_it->second; rb_it->second = NULL; @@ -281,7 +263,7 @@ namespace Physic delete mDebugDrawer; - delete dynamicsWorld; + delete mDynamicsWorld; delete solver; delete collisionConfiguration; delete dispatcher; @@ -331,7 +313,7 @@ namespace Physic mHeightFieldMap [name] = hf; - dynamicsWorld->addRigidBody(body,CollisionType_HeightMap|CollisionType_Raycasting, + mDynamicsWorld->addRigidBody(body,CollisionType_HeightMap, CollisionType_World|CollisionType_Actor|CollisionType_Raycasting); } @@ -343,7 +325,7 @@ namespace Physic HeightField hf = mHeightFieldMap [name]; - dynamicsWorld->removeRigidBody(hf.mBody); + mDynamicsWorld->removeRigidBody(hf.mBody); delete hf.mShape; delete hf.mBody; @@ -367,7 +349,6 @@ namespace Physic { std::string sid = (boost::format("%07.3f") % scale).str(); std::string outputstring = mesh + sid; - //std::cout << "The string" << outputstring << "\n"; //get the shape from the .nif mShapeLoader->load(outputstring,"General"); @@ -419,7 +400,7 @@ namespace Physic } - void PhysicEngine::addRigidBody(RigidBody* body, bool addToMap, RigidBody* raycastingBody,bool actor) + void PhysicEngine::addRigidBody(RigidBody* body, bool addToMap, RigidBody* raycastingBody) { if(!body && !raycastingBody) return; // nothing to do @@ -427,12 +408,11 @@ namespace Physic const std::string& name = (body ? body->mName : raycastingBody->mName); if (body){ - if(actor) dynamicsWorld->addRigidBody(body,CollisionType_Actor,CollisionType_World|CollisionType_HeightMap); - else dynamicsWorld->addRigidBody(body,CollisionType_World,CollisionType_World|CollisionType_Actor|CollisionType_HeightMap); + mDynamicsWorld->addRigidBody(body,CollisionType_World,CollisionType_World|CollisionType_Actor|CollisionType_HeightMap); } if (raycastingBody) - dynamicsWorld->addRigidBody(raycastingBody,CollisionType_Raycasting,CollisionType_Raycasting|CollisionType_World); + mDynamicsWorld->addRigidBody(raycastingBody,CollisionType_Raycasting,CollisionType_Raycasting); if(addToMap){ removeRigidBody(name); @@ -453,7 +433,7 @@ namespace Physic RigidBody* body = it->second; if(body != NULL) { - dynamicsWorld->removeRigidBody(body); + mDynamicsWorld->removeRigidBody(body); } } it = mRaycastingObjectMap.find(name); @@ -462,7 +442,7 @@ namespace Physic RigidBody* body = it->second; if(body != NULL) { - dynamicsWorld->removeRigidBody(body); + mDynamicsWorld->removeRigidBody(body); } } } @@ -605,7 +585,7 @@ namespace Physic if (!body) // fall back to raycasting body if there is no collision body body = getRigidBody(name, true); ContactTestResultCallback callback; - dynamicsWorld->contactTest(body, callback); + mDynamicsWorld->contactTest(body, callback); return callback.mResult; } @@ -615,8 +595,9 @@ namespace Physic btCollisionObject *object) { DeepestNotMeContactTestResultCallback callback(filter, origin); + callback.m_collisionFilterGroup = 0xff; callback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor; - dynamicsWorld->contactTest(object, callback); + mDynamicsWorld->contactTest(object, callback); return std::make_pair(callback.mObject, callback.mContactPoint); } @@ -624,7 +605,7 @@ namespace Physic void PhysicEngine::stepSimulation(double deltaT) { // This seems to be needed for character controller objects - dynamicsWorld->stepSimulation(deltaT,10, 1/60.0); + mDynamicsWorld->stepSimulation(deltaT,10, 1/60.0); if(isDebugCreated) { mDebugDrawer->step(); @@ -684,14 +665,15 @@ namespace Physic float d = -1; btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); + resultCallback1.m_collisionFilterGroup = 0xff; if(raycastingObjectOnly) - resultCallback1.m_collisionFilterMask = CollisionType_Raycasting; + resultCallback1.m_collisionFilterMask = CollisionType_Raycasting|CollisionType_Actor; else resultCallback1.m_collisionFilterMask = CollisionType_World; if(!ignoreHeightMap) resultCallback1.m_collisionFilterMask = resultCallback1.m_collisionFilterMask | CollisionType_HeightMap; - dynamicsWorld->rayTest(from, to, resultCallback1); + mDynamicsWorld->rayTest(from, to, resultCallback1); if (resultCallback1.hasHit()) { name = static_cast(*resultCallback1.m_collisionObject).mName; @@ -724,6 +706,7 @@ namespace Physic std::pair PhysicEngine::sphereCast (float radius, btVector3& from, btVector3& to) { OurClosestConvexResultCallback callback(from, to); + callback.m_collisionFilterGroup = 0xff; callback.m_collisionFilterMask = OEngine::Physic::CollisionType_World|OEngine::Physic::CollisionType_HeightMap; btSphereShape shape(radius); @@ -732,7 +715,7 @@ namespace Physic btTransform from_ (btrot, from); btTransform to_ (btrot, to); - dynamicsWorld->convexSweepTest(&shape, from_, to_, callback); + mDynamicsWorld->convexSweepTest(&shape, from_, to_, callback); if (callback.hasHit()) return std::make_pair(true, callback.m_closestHitFraction); @@ -743,8 +726,9 @@ namespace Physic std::vector< std::pair > PhysicEngine::rayTest2(btVector3& from, btVector3& to) { MyRayResultCallback resultCallback1; - resultCallback1.m_collisionFilterMask = CollisionType_Raycasting; - dynamicsWorld->rayTest(from, to, resultCallback1); + resultCallback1.m_collisionFilterGroup = 0xff; + resultCallback1.m_collisionFilterMask = CollisionType_Raycasting|CollisionType_Actor; + mDynamicsWorld->rayTest(from, to, resultCallback1); std::vector< std::pair > results = resultCallback1.results; std::vector< std::pair > results2; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 803986d5b7..f24ef93d0e 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -76,6 +76,9 @@ namespace Physic RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name); virtual ~RigidBody(); std::string mName; + + // Hack: placeable objects (that can be picked up by the player) have different collision behaviour. + // This variable needs to be passed to BulletNifLoader. bool mPlaceable; }; @@ -92,13 +95,6 @@ namespace Physic void setPosition(const Ogre::Vector3 &pos); - /** - * This adjusts the rotation of a PhysicActor - * If we have any problems with this (getting stuck in pmove) we should change it - * from setting the visual orientation to setting the orientation of the rigid body directly. - */ - void setRotation(const Ogre::Quaternion &quat); - /** * Sets the collisionMode for this actor. If disabled, the actor can fly and clip geometry. */ @@ -111,26 +107,18 @@ namespace Physic bool getCollisionMode() const { - return mCollisionMode; + return mInternalCollisionMode; } - - /** - * This returns the visual position of the PhysicActor (used to position a scenenode). - * Note - this is different from the position of the contained mBody. - */ - Ogre::Vector3 getPosition(); - - /** - * Returns the visual orientation of the PhysicActor - */ - Ogre::Quaternion getRotation(); - /** * Sets the scale of the PhysicActor */ void setScale(float scale); + void setRotation (const Ogre::Quaternion& rotation); + + const Ogre::Vector3& getPosition() const; + /** * Returns the half extents for this PhysiActor */ @@ -153,7 +141,7 @@ namespace Physic bool getOnGround() const { - return mCollisionMode && mOnGround; + return mInternalCollisionMode && mOnGround; } btCollisionObject *getCollisionBody() const @@ -165,17 +153,21 @@ namespace Physic void disableCollisionBody(); void enableCollisionBody(); - OEngine::Physic::RigidBody* mBody; - OEngine::Physic::RigidBody* mRaycastingBody; + boost::shared_ptr mShape; - Ogre::Vector3 mBoxScaledTranslation; - Ogre::Quaternion mBoxRotation; - Ogre::Quaternion mBoxRotationInverse; + OEngine::Physic::RigidBody* mBody; + + Ogre::Quaternion mMeshOrientation; + Ogre::Vector3 mMeshTranslation; + Ogre::Vector3 mHalfExtents; + + float mScale; + Ogre::Vector3 mPosition; Ogre::Vector3 mForce; bool mOnGround; - bool mCollisionMode; - bool mCollisionBody; + bool mInternalCollisionMode; + bool mExternalCollisionMode; std::string mMesh; std::string mName; @@ -242,7 +234,7 @@ namespace Physic /** * Add a RigidBody to the simulation */ - void addRigidBody(RigidBody* body, bool addToMap = true, RigidBody* raycastingBody = NULL,bool actor = false); + void addRigidBody(RigidBody* body, bool addToMap = true, RigidBody* raycastingBody = NULL); /** * Remove a RigidBody from the simulation. It does not delete it, and does not remove it from the RigidBodyMap. @@ -335,7 +327,7 @@ namespace Physic btDefaultCollisionConfiguration* collisionConfiguration; btSequentialImpulseConstraintSolver* solver; btCollisionDispatcher* dispatcher; - btDiscreteDynamicsWorld* dynamicsWorld; + btDiscreteDynamicsWorld* mDynamicsWorld; //the NIF file loader. BulletShapeLoader* mShapeLoader; diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index 6eab43a606..78fb5a3a79 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -65,12 +65,13 @@ void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, to.setOrigin(btend); ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0)); + newTraceCallback.m_collisionFilterGroup = CollisionType_Actor; newTraceCallback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor; btCollisionShape *shape = actor->getCollisionShape(); assert(shape->isConvex()); - enginePass->dynamicsWorld->convexSweepTest(static_cast(shape), + enginePass->mDynamicsWorld->convexSweepTest(static_cast(shape), from, to, newTraceCallback); // Copy the hit data over to our trace results struct: @@ -89,27 +90,26 @@ void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, } } -void ActorTracer::findGround(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass) +void ActorTracer::findGround(const OEngine::Physic::PhysicActor* actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass) { const btVector3 btstart(start.x, start.y, start.z+1.0f); const btVector3 btend(end.x, end.y, end.z+1.0f); - const btTransform &trans = actor->getWorldTransform(); + const btTransform &trans = actor->getCollisionBody()->getWorldTransform(); btTransform from(trans.getBasis(), btstart); btTransform to(trans.getBasis(), btend); - ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0)); + ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionBody(), btstart-btend, btScalar(0.0)); + newTraceCallback.m_collisionFilterGroup = CollisionType_Actor; newTraceCallback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor; - const btBoxShape *shape = dynamic_cast(actor->getCollisionShape()); - assert(shape); + btVector3 halfExtents(actor->getHalfExtents().x, actor->getHalfExtents().y, actor->getHalfExtents().z); - btVector3 halfExtents = shape->getHalfExtentsWithMargin(); halfExtents[2] = 1.0f; - btBoxShape box(halfExtents); + btBoxShape base(halfExtents); - enginePass->dynamicsWorld->convexSweepTest(&box, from, to, newTraceCallback); + enginePass->mDynamicsWorld->convexSweepTest(&base, from, to, newTraceCallback); if(newTraceCallback.hasHit()) { const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h index 92795c87fa..b9fbce64d9 100644 --- a/libs/openengine/bullet/trace.h +++ b/libs/openengine/bullet/trace.h @@ -12,6 +12,7 @@ namespace OEngine namespace Physic { class PhysicEngine; + class PhysicActor; struct ActorTracer { @@ -22,7 +23,7 @@ namespace Physic void doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass); - void findGround(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, + void findGround(const OEngine::Physic::PhysicActor* actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass); }; } From 0bed6d9d56fd89510f8f869044c76757153c47d4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 21 Jun 2014 23:56:59 +0200 Subject: [PATCH 152/226] Physics: Recognize BBoxCollision flag, but don't use it for raycasting (Fixes #1349) --- components/nifbullet/bulletnifloader.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 3e753dd37b..bed3e8869b 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -137,12 +137,7 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) handleNode(mesh2, node,0,true,true,false); - if(mBoundingBox != NULL) - { - mShape->mRaycastingShape = mBoundingBox; - delete mesh2; - } - else if (mHasShape) + if (mHasShape) { mShape->mRaycastingShape = new TriangleMeshShape(mesh2,true); } @@ -227,10 +222,12 @@ void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node * if ( (isCollisionNode || (!mShape->mHasCollisionNode && !raycasting)) && (!isMarker || (mShape->mHasCollisionNode && !raycasting))) { + // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! + // It must be ignored completely. + // (occurs in tr_ex_imp_wall_arch_04.nif) if(node->hasBounds) { - // Checking for BBoxCollision flag causes issues with some actors :/ - if (!(node->flags & Nif::NiNode::Flag_Hidden)) + if (flags & Nif::NiNode::Flag_BBoxCollision && !raycasting) { mShape->mBoxTranslation = node->boundPos; mShape->mBoxRotation = node->boundRot; From e23a7694f30afcb8ae683fe67bfa6631fab96244 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 22 Jun 2014 00:34:32 +0200 Subject: [PATCH 153/226] Don't throw exception when using ModDisposition on creatures (Fixes #1548) --- apps/openmw/mwscript/statsextensions.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 5a0cd8ae68..1d3a8bc4b2 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -690,8 +690,11 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - ptr.getClass().getNpcStats (ptr).setBaseDisposition - (ptr.getClass().getNpcStats (ptr).getBaseDisposition() + value); + if (ptr.getClass().isNpc()) + ptr.getClass().getNpcStats (ptr).setBaseDisposition + (ptr.getClass().getNpcStats (ptr).getBaseDisposition() + value); + + // else: must not throw exception (used by an Almalexia dialogue script) } }; @@ -707,7 +710,8 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - ptr.getClass().getNpcStats (ptr).setBaseDisposition (value); + if (ptr.getClass().isNpc()) + ptr.getClass().getNpcStats (ptr).setBaseDisposition (value); } }; @@ -720,7 +724,10 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push (MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr)); + if (!ptr.getClass().isNpc()) + runtime.push(0); + else + runtime.push (MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr)); } }; From ead801f2d031fb1947a17fa1e71213277b00234e Mon Sep 17 00:00:00 2001 From: slothlife Date: Sat, 21 Jun 2014 21:15:41 -0500 Subject: [PATCH 154/226] Fix for compiling Win32 debug. --- apps/openmw/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index a061cf63c8..adde408b9e 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -299,8 +299,10 @@ int main(int argc, char**argv) std::streambuf* cout_rdbuf = std::cout.rdbuf (); std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); +#if !(defined(_WIN32) && defined(_DEBUG)) boost::iostreams::stream_buffer coutsb; boost::iostreams::stream_buffer cerrsb; +#endif std::ostream oldcout(cout_rdbuf); std::ostream oldcerr(cerr_rdbuf); From 33ed11d8e6be6065b4683fbd4103555fc32c9431 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 22 Jun 2014 00:47:14 +0200 Subject: [PATCH 155/226] Physics: Use capsule shapes for actors if possible (Fixes #1437) --- libs/openengine/bullet/physic.cpp | 11 +++++++++-- libs/openengine/bullet/trace.cpp | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index bc34f2f516..d934639e0b 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -29,7 +29,14 @@ namespace Physic mMeshOrientation = Ogre::Quaternion::IDENTITY; } - mShape.reset(new btBoxShape(BtOgre::Convert::toBullet(mHalfExtents))); + // Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it) + if (std::abs(mHalfExtents.x-mHalfExtents.y)= mHalfExtents.x) + { + mShape.reset(new btCapsuleShapeZ(mHalfExtents.x, mHalfExtents.z*2.f - mHalfExtents.x*2.f)); + } + else + mShape.reset(new btBoxShape(BtOgre::Convert::toBullet(mHalfExtents))); + mShape->setLocalScaling(btVector3(scale,scale,scale)); btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo @@ -102,7 +109,7 @@ namespace Physic Ogre::Vector3 PhysicActor::getHalfExtents() const { - return mHalfExtents; + return mHalfExtents * mScale; } void PhysicActor::setInertialForce(const Ogre::Vector3 &force) diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index 78fb5a3a79..46db4c6b87 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -107,7 +107,7 @@ void ActorTracer::findGround(const OEngine::Physic::PhysicActor* actor, const Og btVector3 halfExtents(actor->getHalfExtents().x, actor->getHalfExtents().y, actor->getHalfExtents().z); halfExtents[2] = 1.0f; - btBoxShape base(halfExtents); + btCylinderShapeZ base(halfExtents); enginePass->mDynamicsWorld->convexSweepTest(&base, from, to, newTraceCallback); if(newTraceCallback.hasHit()) From baf490a2b593f89f4469028082dabe784ed46889 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 22 Jun 2014 03:05:10 +0200 Subject: [PATCH 156/226] Change to btCylinderShape --- libs/openengine/bullet/physic.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index d934639e0b..31cb67a584 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -32,7 +32,8 @@ namespace Physic // Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it) if (std::abs(mHalfExtents.x-mHalfExtents.y)= mHalfExtents.x) { - mShape.reset(new btCapsuleShapeZ(mHalfExtents.x, mHalfExtents.z*2.f - mHalfExtents.x*2.f)); + // Could also be btCapsuleShapeZ, but the movement solver seems to have issues with it (jumping on slopes doesn't work) + mShape.reset(new btCylinderShapeZ(BtOgre::Convert::toBullet(mHalfExtents))); } else mShape.reset(new btBoxShape(BtOgre::Convert::toBullet(mHalfExtents))); From cbec0ffaee7bbb93d5c0346160f1b7c9449d15dd Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 22 Jun 2014 03:05:17 +0200 Subject: [PATCH 157/226] Exception fix --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index f4ffa499c2..0ad0b4d5a3 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1101,7 +1101,7 @@ namespace MWMechanics if (iter->first.getClass().isClass(iter->first, "Guard")) { MWMechanics::AiSequence& aiSeq = iter->first.getClass().getCreatureStats(iter->first).getAiSequence(); - if (aiSeq.getActivePackage()->getTypeId() == MWMechanics::AiPackage::TypeIdPursue) + if (aiSeq.getTypeId() == MWMechanics::AiPackage::TypeIdPursue) { aiSeq.stopPursuit(); aiSeq.stack(MWMechanics::AiCombat(target), ptr); From d296c6e9b7bb7dbceafcb4b0c0ea1ae2d00950fa Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 22 Jun 2014 16:10:52 +0200 Subject: [PATCH 158/226] Handle fog density values of 0 (Bug #1549) --- apps/openmw/mwrender/renderingmanager.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 8a22c63c64..89933414d0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -506,10 +506,19 @@ void RenderingManager::configureFog(const float density, const Ogre::ColourValue mFogColour = colour; float max = Settings::Manager::getFloat("max viewing distance", "Viewing distance"); - mFogStart = max / (density) * Settings::Manager::getFloat("fog start factor", "Viewing distance"); - mFogEnd = max / (density) * Settings::Manager::getFloat("fog end factor", "Viewing distance"); + if (density == 0) + { + mFogStart = 0; + mFogEnd = std::numeric_limits().max(); + mRendering.getCamera()->setFarClipDistance (max); + } + else + { + mFogStart = max / (density) * Settings::Manager::getFloat("fog start factor", "Viewing distance"); + mFogEnd = max / (density) * Settings::Manager::getFloat("fog end factor", "Viewing distance"); + mRendering.getCamera()->setFarClipDistance (max / density); + } - mRendering.getCamera()->setFarClipDistance ( Settings::Manager::getFloat("max viewing distance", "Viewing distance") / density ); } void RenderingManager::applyFog (bool underwater) From 8df0effcf5244121ca53fbd5560e0e5b3e915988 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 22 Jun 2014 16:25:29 +0200 Subject: [PATCH 159/226] Remove unused string --- libs/openengine/bullet/BulletShapeLoader.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/openengine/bullet/BulletShapeLoader.h b/libs/openengine/bullet/BulletShapeLoader.h index 0e5c652260..c6f7f727ce 100644 --- a/libs/openengine/bullet/BulletShapeLoader.h +++ b/libs/openengine/bullet/BulletShapeLoader.h @@ -15,8 +15,6 @@ namespace Physic */ class BulletShape : public Ogre::Resource { - Ogre::String mString; - protected: void loadImpl(); void unloadImpl(); From 64a4c2785e2883f530d10448bb523d94a8de66ce Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 22 Jun 2014 16:43:41 +0200 Subject: [PATCH 160/226] Fix water walking --- apps/openmw/mwworld/physicssystem.cpp | 3 ++- libs/openengine/bullet/physic.hpp | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 8aca591e8e..ad4300b1f5 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -743,7 +743,8 @@ namespace MWWorld // TODO: this seems to have a slight performance impact if (waterCollision) - mEngine->mDynamicsWorld->addCollisionObject(&object); + mEngine->mDynamicsWorld->addCollisionObject(&object, + 0xff, OEngine::Physic::CollisionType_Actor); // 100 points of slowfall reduce gravity by 90% (this is just a guess) float slowFall = 1-std::min(std::max(0.f, (effects.get(ESM::MagicEffect::SlowFall).mMagnitude / 100.f) * 0.9f), 0.9f); diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index f24ef93d0e..deef214431 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -82,10 +82,7 @@ namespace Physic bool mPlaceable; }; - /** - * A physic actor uses a rigid body based on box shapes. - * Pmove is used to move the physic actor around the dynamic world. - */ + class PhysicActor { public: From 9ea22324f76d1c5f71319050c1bbdcdc36a0e4f7 Mon Sep 17 00:00:00 2001 From: slothlife Date: Mon, 23 Jun 2014 01:13:30 -0500 Subject: [PATCH 161/226] Fix some MSVC warnings. Several fixes are warnings about truncations on 64-bit, while others are complaints about mixed signed / unsigned integer operations. --- CMakeLists.txt | 5 ++++- apps/opencs/view/render/scenewidget.cpp | 2 +- apps/openmw/mwgui/list.cpp | 2 +- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/videoplayer.cpp | 2 +- apps/openmw/mwsound/soundmanagerimp.cpp | 2 +- components/terrain/buffercache.cpp | 6 +++--- extern/sdl4ogre/sdlwindowhelper.cpp | 2 +- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1769f884d0..70ca1c7d79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -615,6 +615,7 @@ if (WIN32) 4244 # Storing value of one type in variable of another (size_t in int, for example) 4305 # Truncating value (double to float, for example) 4309 # Variable overflow, trying to store 128 in a signed char for example + 4351 # New behavior: elements of array 'array' will be default initialized (desired behavior) 4355 # Using 'this' in member initialization list 4505 # Unreferenced local function has been removed 4701 # Potentially uninitialized local variable used @@ -633,7 +634,9 @@ if (WIN32) set(SHINY_OGRE_WARNINGS "${WARNINGS} /wd4101") set_target_properties(shiny.OgrePlatform PROPERTIES COMPILE_FLAGS ${SHINY_OGRE_WARNINGS}) set_target_properties(sdl4ogre PROPERTIES COMPILE_FLAGS ${WARNINGS}) - set_target_properties(oics PROPERTIES COMPILE_FLAGS ${WARNINGS}) + # oics uses tinyxml, which has an initialized but unused variable + set(OICS_WARNINGS "${WARNINGS} /wd4189") + set_target_properties(oics PROPERTIES COMPILE_FLAGS ${OICS_WARNINGS}) set_target_properties(components PROPERTIES COMPILE_FLAGS ${WARNINGS}) if (BUILD_LAUNCHER) set_target_properties(omwlauncher PROPERTIES COMPILE_FLAGS ${WARNINGS}) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 52e7afefd3..eccaebd000 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -87,7 +87,7 @@ namespace CSVRender std::stringstream windowHandle; #ifdef WIN32 - windowHandle << Ogre::StringConverter::toString((unsigned long)(this->winId())); + windowHandle << Ogre::StringConverter::toString((uintptr_t)(this->winId())); #else windowHandle << this->winId(); #endif diff --git a/apps/openmw/mwgui/list.cpp b/apps/openmw/mwgui/list.cpp index ca2989646c..b0c514b9d3 100644 --- a/apps/openmw/mwgui/list.cpp +++ b/apps/openmw/mwgui/list.cpp @@ -101,7 +101,7 @@ namespace MWGui size_t viewRange = mScrollView->getCanvasSize().height; if(viewPosition > viewRange) viewPosition = viewRange; - mScrollView->setViewOffset(MyGUI::IntPoint(0, -viewPosition)); + mScrollView->setViewOffset(MyGUI::IntPoint(0, viewPosition * -1)); } void MWList::setPropertyOverride(const std::string &_key, const std::string &_value) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 2b0323675e..62907fcc35 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -319,7 +319,7 @@ void LocalMap::createFogOfWar(const std::string& texturePrefix) std::vector buffer; // initialize to (0, 0, 0, 1) - buffer.resize(sFogOfWarResolution*sFogOfWarResolution, (255 << 24)); + buffer.resize(sFogOfWarResolution*sFogOfWarResolution, 0xFF000000); // upload to the texture memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 79f2fa9483..03e74697c1 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -747,7 +747,7 @@ double VideoState::synchronize_video(AVFrame *src_frame, double pts) * buffer. We use this to store the global_pts in * a frame at the time it is allocated. */ -static uint64_t global_video_pkt_pts = AV_NOPTS_VALUE; +static uint64_t global_video_pkt_pts = static_cast(AV_NOPTS_VALUE); static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic) { int ret = avcodec_default_get_buffer(c, pic); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 35b8579d79..7683c2b901 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -385,7 +385,7 @@ namespace MWSound sound = mOutput->playSound3D(file, initialPos, volume, basevol, pitch, min, max, mode|type, offset); mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); } - catch(std::exception &e) + catch(std::exception &) { //std::cout <<"Sound Error: "< Date: Mon, 23 Jun 2014 20:43:24 +0200 Subject: [PATCH 162/226] Add support for animated collision shapes (Fixes #1549) --- apps/openmw/mwworld/physicssystem.cpp | 53 ++++++- apps/openmw/mwworld/physicssystem.hpp | 2 + apps/openmw/mwworld/worldimp.cpp | 2 +- components/nifbullet/bulletnifloader.cpp | 150 +++++++++++++------ components/nifbullet/bulletnifloader.hpp | 32 +++- libs/openengine/bullet/BulletShapeLoader.cpp | 5 +- libs/openengine/bullet/BulletShapeLoader.h | 8 + libs/openengine/bullet/physic.cpp | 136 +++++++++++++---- libs/openengine/bullet/physic.hpp | 21 ++- 9 files changed, 312 insertions(+), 97 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index ad4300b1f5..7f26fa75a9 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -26,10 +27,49 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../apps/openmw/mwrender/animation.hpp" +#include "../apps/openmw/mwbase/world.hpp" +#include "../apps/openmw/mwbase/environment.hpp" + #include "ptr.hpp" #include "class.hpp" using namespace Ogre; + +namespace +{ + +void animateCollisionShapes (std::map& map) +{ + for (std::map::iterator it = map.begin(); + it != map.end(); ++it) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle(it->first->mName); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); + + OEngine::Physic::AnimatedShapeInstance& instance = it->second; + + std::map& shapes = instance.mAnimatedShapes; + for (std::map::iterator shapeIt = shapes.begin(); + shapeIt != shapes.end(); ++shapeIt) + { + Ogre::Node* bone = animation->getNode(shapeIt->first); + + btCompoundShape* compound = dynamic_cast(instance.mCompound); + + btTransform trans; + trans.setOrigin(BtOgre::Convert::toBullet(bone->_getDerivedPosition())); + trans.setRotation(BtOgre::Convert::toBullet(bone->_getDerivedOrientation())); + + compound->getChildShape(shapeIt->second)->setLocalScaling(BtOgre::Convert::toBullet(bone->_getDerivedScale())); + compound->updateChildTransform(shapeIt->second, trans); + } + } +} + +} + + namespace MWWorld { @@ -564,11 +604,10 @@ namespace MWWorld std::string mesh = ptr.getClass().getModel(ptr); Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); handleToMesh[node->getName()] = mesh; - OEngine::Physic::RigidBody* body = mEngine->createAndAdjustRigidBody( + mEngine->createAndAdjustRigidBody( mesh, node->getName(), node->getScale().x, node->getPosition(), node->getOrientation(), 0, 0, false, placeable); - OEngine::Physic::RigidBody* raycastingBody = mEngine->createAndAdjustRigidBody( + mEngine->createAndAdjustRigidBody( mesh, node->getName(), node->getScale().x, node->getPosition(), node->getOrientation(), 0, 0, true, placeable); - mEngine->addRigidBody(body, true, raycastingBody); } void PhysicsSystem::addActor (const Ptr& ptr) @@ -770,4 +809,12 @@ namespace MWWorld return mMovementResults; } + + void PhysicsSystem::stepSimulation(float dt) + { + animateCollisionShapes(mEngine->mAnimatedShapes); + animateCollisionShapes(mEngine->mAnimatedRaycastingShapes); + + mEngine->stepSimulation(dt); + } } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index c590b40c87..df97186694 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -53,6 +53,8 @@ namespace MWWorld bool toggleCollisionMode(); + void stepSimulation(float dt); + std::vector getCollisions(const MWWorld::Ptr &ptr); ///< get handles this object collides with Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index be334646be..b4957abfde 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1231,7 +1231,7 @@ namespace MWWorld if(player != results.end()) moveObjectImp(player->first, player->second.x, player->second.y, player->second.z); - mPhysEngine->stepSimulation(duration); + mPhysics->stepSimulation(duration); } bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index bed3e8869b..64febc3c2d 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -49,20 +49,6 @@ typedef unsigned char ubyte; namespace NifBullet { -struct TriangleMeshShape : public btBvhTriangleMeshShape -{ - TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression) - : btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression) - { - } - - virtual ~TriangleMeshShape() - { - delete getTriangleInfoMap(); - delete m_meshInterface; - } -}; - ManualBulletShapeLoader::~ManualBulletShapeLoader() { } @@ -81,9 +67,8 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) mBoundingBox = NULL; mShape->mBoxTranslation = Ogre::Vector3(0,0,0); mShape->mBoxRotation = Ogre::Quaternion::IDENTITY; - mHasShape = false; - - btTriangleMesh* mesh1 = new btTriangleMesh(); + mCompoundShape = NULL; + mStaticMesh = NULL; // Load the NIF. TODO: Wrap this in a try-catch block once we're out // of the early stages of development. Right now we WANT to catch @@ -111,19 +96,35 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) mShape->mHasCollisionNode = hasRootCollisionNode(node); //do a first pass - handleNode(mesh1, node,0,false,false,false); + handleNode(node,0,false,false,false); if(mBoundingBox != NULL) { mShape->mCollisionShape = mBoundingBox; - delete mesh1; - } - else if (mHasShape && mShape->mCollide) - { - mShape->mCollisionShape = new TriangleMeshShape(mesh1,true); + delete mStaticMesh; + if (mCompoundShape) + { + int n = mCompoundShape->getNumChildShapes(); + for(int i=0; i getChildShape(i)); + delete mCompoundShape; + } } else - delete mesh1; + { + if (mCompoundShape) + { + mShape->mCollisionShape = mCompoundShape; + if (mStaticMesh) + { + btTransform trans; + trans.setIdentity(); + mCompoundShape->addChildShape(trans, new TriangleMeshShape(mStaticMesh,true)); + } + } + else if (mStaticMesh) + mShape->mCollisionShape = new TriangleMeshShape(mStaticMesh,true); + } //second pass which create a shape for raycasting. mResourceName = mShape->getName(); @@ -131,18 +132,23 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) mBoundingBox = NULL; mShape->mBoxTranslation = Ogre::Vector3(0,0,0); mShape->mBoxRotation = Ogre::Quaternion::IDENTITY; - mHasShape = false; + mStaticMesh = NULL; + mCompoundShape = NULL; - btTriangleMesh* mesh2 = new btTriangleMesh(); + handleNode(node,0,true,true,false); - handleNode(mesh2, node,0,true,true,false); - - if (mHasShape) + if (mCompoundShape) { - mShape->mRaycastingShape = new TriangleMeshShape(mesh2,true); + mShape->mRaycastingShape = mCompoundShape; + if (mStaticMesh) + { + btTransform trans; + trans.setIdentity(); + mCompoundShape->addChildShape(trans, new TriangleMeshShape(mStaticMesh,true)); + } } - else - delete mesh2; + else if (mStaticMesh) + mShape->mRaycastingShape = new TriangleMeshShape(mStaticMesh,true); } bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node const * node) @@ -167,14 +173,17 @@ bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node const * node) return false; } -void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node *node, int flags, +void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, bool isCollisionNode, - bool raycasting, bool isMarker) + bool raycasting, bool isMarker, bool isAnimated) { // Accumulate the flags from all the child nodes. This works for all // the flags we currently use, at least. flags |= node->flags; + if (!node->controller.empty() && node->controller->recType == Nif::RC_NiKeyframeController) + isAnimated = true; + if (!raycasting) isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode); else @@ -237,7 +246,7 @@ void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node * else if(node->recType == Nif::RC_NiTriShape) { mShape->mCollide = !(flags&0x800); - handleNiTriShape(mesh, static_cast(node), flags, node->getWorldTransform(), raycasting); + handleNiTriShape(static_cast(node), flags, node->getWorldTransform(), raycasting, isAnimated); } } @@ -249,13 +258,13 @@ void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node * for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) - handleNode(mesh, list[i].getPtr(), flags, isCollisionNode, raycasting, isMarker); + handleNode(list[i].getPtr(), flags, isCollisionNode, raycasting, isMarker, isAnimated); } } } -void ManualBulletShapeLoader::handleNiTriShape(btTriangleMesh* mesh, const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, - bool raycasting) +void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, + bool raycasting, bool isAnimated) { assert(shape != NULL); @@ -278,17 +287,64 @@ void ManualBulletShapeLoader::handleNiTriShape(btTriangleMesh* mesh, const Nif:: // bother setting it up. return; - mHasShape = true; + if (!shape->skin.empty()) + isAnimated = false; - const Nif::NiTriShapeData *data = shape->data.getPtr(); - const std::vector &vertices = data->vertices; - const short *triangles = &data->triangles[0]; - for(size_t i = 0;i < data->triangles.size();i+=3) + if (isAnimated) { - Ogre::Vector3 b1 = transform*vertices[triangles[i+0]]; - Ogre::Vector3 b2 = transform*vertices[triangles[i+1]]; - Ogre::Vector3 b3 = transform*vertices[triangles[i+2]]; - mesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z)); + if (!mCompoundShape) + mCompoundShape = new btCompoundShape(); + + btTriangleMesh* childMesh = new btTriangleMesh(); + + const Nif::NiTriShapeData *data = shape->data.getPtr(); + + childMesh->preallocateVertices(data->vertices.size()); + childMesh->preallocateIndices(data->triangles.size()); + + const std::vector &vertices = data->vertices; + const std::vector &triangles = data->triangles; + + for(size_t i = 0;i < data->triangles.size();i+=3) + { + Ogre::Vector3 b1 = vertices[triangles[i+0]]; + Ogre::Vector3 b2 = vertices[triangles[i+1]]; + Ogre::Vector3 b3 = vertices[triangles[i+2]]; + childMesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z)); + } + + TriangleMeshShape* childShape = new TriangleMeshShape(childMesh,true); + + childShape->setLocalScaling(btVector3(transform[0][0], transform[1][1], transform[2][2])); + + Ogre::Quaternion q = transform.extractQuaternion(); + Ogre::Vector3 v = transform.getTrans(); + btTransform trans(btQuaternion(q.x, q.y, q.z, q.w), btVector3(v.x, v.y, v.z)); + + if (raycasting) + mShape->mAnimatedRaycastingShapes.insert(std::make_pair(shape->name, mCompoundShape->getNumChildShapes())); + else + mShape->mAnimatedShapes.insert(std::make_pair(shape->name, mCompoundShape->getNumChildShapes())); + + mCompoundShape->addChildShape(trans, childShape); + } + else + { + if (!mStaticMesh) + mStaticMesh = new btTriangleMesh(); + + // Static shape, just transform all vertices into position + const Nif::NiTriShapeData *data = shape->data.getPtr(); + const std::vector &vertices = data->vertices; + const std::vector &triangles = data->triangles; + + for(size_t i = 0;i < data->triangles.size();i+=3) + { + Ogre::Vector3 b1 = transform*vertices[triangles[i+0]]; + Ogre::Vector3 b2 = transform*vertices[triangles[i+1]]; + Ogre::Vector3 b3 = transform*vertices[triangles[i+2]]; + mStaticMesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z)); + } } } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index fb7d3d70a3..a9ee968b96 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -44,6 +45,22 @@ namespace Nif namespace NifBullet { +// Subclass btBhvTriangleMeshShape to auto-delete the meshInterface +struct TriangleMeshShape : public btBvhTriangleMeshShape +{ + TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression) + : btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression) + { + } + + virtual ~TriangleMeshShape() + { + delete getTriangleInfoMap(); + delete m_meshInterface; + } +}; + + /** *Load bulletShape from NIF files. */ @@ -52,8 +69,9 @@ class ManualBulletShapeLoader : public OEngine::Physic::BulletShapeLoader public: ManualBulletShapeLoader() : mShape(NULL) + , mStaticMesh(NULL) + , mCompoundShape(NULL) , mBoundingBox(NULL) - , mHasShape(false) { } @@ -88,7 +106,8 @@ private: /** *Parse a node. */ - void handleNode(btTriangleMesh* mesh, Nif::Node const *node, int flags, bool isCollisionNode, bool raycasting, bool isMarker); + void handleNode(Nif::Node const *node, int flags, bool isCollisionNode, + bool raycasting, bool isMarker, bool isAnimated=false); /** *Helper function @@ -98,14 +117,17 @@ private: /** *convert a NiTriShape to a bullet trishape. */ - void handleNiTriShape(btTriangleMesh* mesh, const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, bool raycasting); + void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, bool raycasting, bool isAnimated); std::string mResourceName; OEngine::Physic::BulletShape* mShape;//current shape - btBoxShape *mBoundingBox; - bool mHasShape; + btCompoundShape* mCompoundShape; + + btTriangleMesh* mStaticMesh; + + btBoxShape *mBoundingBox; }; diff --git a/libs/openengine/bullet/BulletShapeLoader.cpp b/libs/openengine/bullet/BulletShapeLoader.cpp index 5528924a9a..bcb2ba6b27 100644 --- a/libs/openengine/bullet/BulletShapeLoader.cpp +++ b/libs/openengine/bullet/BulletShapeLoader.cpp @@ -42,7 +42,7 @@ void BulletShape::deleteShape(btCollisionShape* shape) { if(shape->isCompound()) { - btCompoundShape* ms = static_cast(mCollisionShape); + btCompoundShape* ms = static_cast(shape); int a = ms->getNumChildShapes(); for(int i=0; i mAnimatedShapes; + + std::map mAnimatedRaycastingShapes; + btCollisionShape* mCollisionShape; btCollisionShape* mRaycastingShape; diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 31cb67a584..469e33f255 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -11,6 +11,59 @@ #include #include +namespace +{ + +// Create a copy of the given collision shape (responsibility of user to delete the returned shape). +btCollisionShape *duplicateCollisionShape(btCollisionShape *shape) +{ + if(shape->isCompound()) + { + btCompoundShape *comp = static_cast(shape); + btCompoundShape *newShape = new btCompoundShape; + + int numShapes = comp->getNumChildShapes(); + for(int i = 0;i < numShapes;i++) + { + btCollisionShape *child = duplicateCollisionShape(comp->getChildShape(i)); + btTransform trans = comp->getChildTransform(i); + newShape->addChildShape(trans, child); + } + + return newShape; + } + + if(btBvhTriangleMeshShape *trishape = dynamic_cast(shape)) + { + btTriangleMesh* oldMesh = dynamic_cast(trishape->getMeshInterface()); + btTriangleMesh* newMesh = new btTriangleMesh(*oldMesh); + NifBullet::TriangleMeshShape *newShape = new NifBullet::TriangleMeshShape(newMesh, true); + + return newShape; + } + + throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName()); +} + +void deleteShape(btCollisionShape* shape) +{ + if(shape!=NULL) + { + if(shape->isCompound()) + { + btCompoundShape* ms = static_cast(shape); + int a = ms->getNumChildShapes(); + for(int i=0; i getChildShape(i)); + } + } + delete shape; + } +} + +} + namespace OEngine { namespace Physic { @@ -228,6 +281,11 @@ namespace Physic PhysicEngine::~PhysicEngine() { + for (std::map::iterator it = mAnimatedShapes.begin(); it != mAnimatedShapes.end(); ++it) + deleteShape(it->second.mCompound); + for (std::map::iterator it = mAnimatedRaycastingShapes.begin(); it != mAnimatedRaycastingShapes.end(); ++it) + deleteShape(it->second.mCompound); + HeightFieldContainer::iterator hf_it = mHeightFieldMap.begin(); for (; hf_it != mHeightFieldMap.end(); ++hf_it) { @@ -386,17 +444,38 @@ namespace Physic if (!shape->mRaycastingShape && raycasting) return NULL; - if (!raycasting) - shape->mCollisionShape->setLocalScaling( btVector3(scale,scale,scale)); - else - shape->mRaycastingShape->setLocalScaling( btVector3(scale,scale,scale)); + btCollisionShape* collisionShape = raycasting ? shape->mRaycastingShape : shape->mCollisionShape; + + // If this is an animated compound shape, we must duplicate it so we can animate + // multiple instances independently. + if (!raycasting && !shape->mAnimatedShapes.empty()) + collisionShape = duplicateCollisionShape(collisionShape); + if (raycasting && !shape->mAnimatedRaycastingShapes.empty()) + collisionShape = duplicateCollisionShape(collisionShape); + + collisionShape->setLocalScaling( btVector3(scale,scale,scale)); //create the real body btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo - (0,0, raycasting ? shape->mRaycastingShape : shape->mCollisionShape); + (0,0, collisionShape); RigidBody* body = new RigidBody(CI,name); body->mPlaceable = placeable; + if (!raycasting && !shape->mAnimatedShapes.empty()) + { + AnimatedShapeInstance instance; + instance.mAnimatedShapes = shape->mAnimatedShapes; + instance.mCompound = collisionShape; + mAnimatedShapes[body] = instance; + } + if (raycasting && !shape->mAnimatedRaycastingShapes.empty()) + { + AnimatedShapeInstance instance; + instance.mAnimatedShapes = shape->mAnimatedRaycastingShapes; + instance.mCompound = collisionShape; + mAnimatedRaycastingShapes[body] = instance; + } + if(scaledBoxTranslation != 0) *scaledBoxTranslation = shape->mBoxTranslation * scale; if(boxRotation != 0) @@ -404,33 +483,20 @@ namespace Physic adjustRigidBody(body, position, rotation, shape->mBoxTranslation * scale, shape->mBoxRotation); - return body; - - } - - void PhysicEngine::addRigidBody(RigidBody* body, bool addToMap, RigidBody* raycastingBody) - { - if(!body && !raycastingBody) - return; // nothing to do - - const std::string& name = (body ? body->mName : raycastingBody->mName); - - if (body){ + if (!raycasting) + { + assert (mCollisionObjectMap.find(name) == mCollisionObjectMap.end()); + mCollisionObjectMap[name] = body; mDynamicsWorld->addRigidBody(body,CollisionType_World,CollisionType_World|CollisionType_Actor|CollisionType_HeightMap); } - - if (raycastingBody) - mDynamicsWorld->addRigidBody(raycastingBody,CollisionType_Raycasting,CollisionType_Raycasting); - - if(addToMap){ - removeRigidBody(name); - deleteRigidBody(name); - - if (body) - mCollisionObjectMap[name] = body; - if (raycastingBody) - mRaycastingObjectMap[name] = raycastingBody; + else + { + assert (mRaycastingObjectMap.find(name) == mRaycastingObjectMap.end()); + mRaycastingObjectMap[name] = body; + mDynamicsWorld->addRigidBody(body,CollisionType_Raycasting,CollisionType_Raycasting); } + + return body; } void PhysicEngine::removeRigidBody(const std::string &name) @@ -464,6 +530,10 @@ namespace Physic if(body != NULL) { + if (mAnimatedShapes.find(body) != mAnimatedShapes.end()) + deleteShape(mAnimatedShapes[body].mCompound); + mAnimatedShapes.erase(body); + delete body; } mCollisionObjectMap.erase(it); @@ -475,6 +545,10 @@ namespace Physic if(body != NULL) { + if (mAnimatedRaycastingShapes.find(body) != mAnimatedRaycastingShapes.end()) + deleteShape(mAnimatedRaycastingShapes[body].mCompound); + mAnimatedRaycastingShapes.erase(body); + delete body; } mRaycastingObjectMap.erase(it); @@ -609,7 +683,6 @@ namespace Physic return std::make_pair(callback.mObject, callback.mContactPoint); } - void PhysicEngine::stepSimulation(double deltaT) { // This seems to be needed for character controller objects @@ -629,8 +702,6 @@ namespace Physic PhysicActor* newActor = new PhysicActor(name, mesh, this, position, rotation, scale); - - //dynamicsWorld->addAction( newActor->mCharacter ); mActorMap[name] = newActor; } @@ -642,7 +713,6 @@ namespace Physic PhysicActor* act = it->second; if(act != NULL) { - delete act; } mActorMap.erase(it); diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index deef214431..e37caee386 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -117,7 +117,7 @@ namespace Physic const Ogre::Vector3& getPosition() const; /** - * Returns the half extents for this PhysiActor + * Returns the (scaled) half extents */ Ogre::Vector3 getHalfExtents() const; @@ -178,6 +178,14 @@ namespace Physic RigidBody* mBody; }; + struct AnimatedShapeInstance + { + btCollisionShape* mCompound; + + // Maps bone name to child index in the compound shape + std::map mAnimatedShapes; + }; + /** * The PhysicEngine class contain everything which is needed for Physic. * It's needed that Ogre Resources are set up before the PhysicEngine is created. @@ -228,11 +236,6 @@ namespace Physic */ void removeHeightField(int x, int y); - /** - * Add a RigidBody to the simulation - */ - void addRigidBody(RigidBody* body, bool addToMap = true, RigidBody* raycastingBody = NULL); - /** * Remove a RigidBody from the simulation. It does not delete it, and does not remove it from the RigidBodyMap. */ @@ -335,8 +338,14 @@ namespace Physic typedef std::map RigidBodyContainer; RigidBodyContainer mCollisionObjectMap; + // Compound shapes that must be animated each frame based on bone positions + // the index refers to an element in mCollisionObjectMap + std::map mAnimatedShapes; + RigidBodyContainer mRaycastingObjectMap; + std::map mAnimatedRaycastingShapes; + typedef std::map PhysicActorContainer; PhysicActorContainer mActorMap; From 320ab1b2c1572b20fe193aff921a63524275e837 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jun 2014 21:59:13 +0200 Subject: [PATCH 163/226] Don't say voice when using startcombat for a dead actor (Fixes #1542) --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 0ad0b4d5a3..652f5363e8 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1112,7 +1112,7 @@ namespace MWMechanics } // Must be done after the target is set up, so that CreatureTargetted dialogue filter works properly - if (ptr.getClass().isNpc()) + if (ptr.getClass().isNpc() && !ptr.getClass().getCreatureStats(ptr).isDead()) MWBase::Environment::get().getDialogueManager()->say(ptr, "attack"); } From 750d8b55892c0efa495cb0d99d95b9442c1896b0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 23 Jun 2014 22:02:22 +0200 Subject: [PATCH 164/226] Allow garbage integer argument for addSpell (Fixes #1539) --- components/compiler/exprparser.cpp | 19 +++++++++++-------- components/compiler/extensions.hpp | 3 ++- components/compiler/extensions0.cpp | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index ed628278b5..e54b2e2a8f 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -775,7 +775,7 @@ namespace Compiler { parser.reset(); - if (optional) + if (optional || *iter == 'X') parser.setOptional (true); scanner.scan (parser); @@ -783,17 +783,20 @@ namespace Compiler if (optional && parser.isEmpty()) break; - std::vector tmp; + if (*iter != 'X') + { + std::vector tmp; - char type = parser.append (tmp); + char type = parser.append (tmp); - if (type!=*iter) - Generator::convert (tmp, type, *iter); + if (type!=*iter) + Generator::convert (tmp, type, *iter); - stack.push (tmp); + stack.push (tmp); - if (optional) - ++optionalCount; + if (optional) + ++optionalCount; + } } } diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp index 418cbb402b..d229751dee 100644 --- a/components/compiler/extensions.hpp +++ b/components/compiler/extensions.hpp @@ -20,7 +20,8 @@ namespace Compiler l - Integer
s - Short
S - String, case preserved
- x - Optional, ignored argument + x - Optional, ignored string argument + X - Optional, ignored integer argument **/ typedef std::string ScriptArgs; diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 9fc66900f0..f38c3320c1 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -405,7 +405,7 @@ namespace Compiler extensions.registerInstruction ("setpccrimelevel", "f", opcodeSetPCCrimeLevel); extensions.registerInstruction ("modpccrimelevel", "f", opcodeModPCCrimeLevel); - extensions.registerInstruction ("addspell", "cx", opcodeAddSpell, opcodeAddSpellExplicit); + extensions.registerInstruction ("addspell", "cxX", opcodeAddSpell, opcodeAddSpellExplicit); extensions.registerInstruction ("removespell", "c", opcodeRemoveSpell, opcodeRemoveSpellExplicit); extensions.registerInstruction ("removespelleffects", "c", opcodeRemoveSpellEffects, From 1d46ac19ff0e488c03be0a3cbd68e9d19c94a736 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 24 Jun 2014 02:24:32 +0200 Subject: [PATCH 165/226] Fix being able to activate through terrain --- apps/openmw/mwworld/worldimp.cpp | 12 +++++++++--- libs/openengine/bullet/physic.cpp | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b4957abfde..c4e4e3d3a8 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1471,8 +1471,13 @@ namespace MWWorld std::vector < std::pair < float, std::string > >::iterator it = results.begin(); while (it != results.end()) { - if ( (*it).second.find("HeightField") != std::string::npos // not interested in terrain - || getPtrViaHandle((*it).second) == mPlayer->getPlayer() ) // not interested in player (unless you want to talk to yourself) + if ((*it).second.find("HeightField") != std::string::npos) // Don't attempt to getPtrViaHandle on terrain + { + ++it; + continue; + } + + if (getPtrViaHandle((*it).second) == mPlayer->getPlayer() ) // not interested in player (unless you want to talk to yourself) { it = results.erase(it); } @@ -1480,7 +1485,8 @@ namespace MWWorld ++it; } - if (results.empty()) + if (results.empty() + || results.front().second.find("HeightField") != std::string::npos) // Blocked by terrain { mFacedHandle = ""; mFacedDistance = FLT_MAX; diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 469e33f255..d00441fdf6 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -805,7 +805,7 @@ namespace Physic { MyRayResultCallback resultCallback1; resultCallback1.m_collisionFilterGroup = 0xff; - resultCallback1.m_collisionFilterMask = CollisionType_Raycasting|CollisionType_Actor; + resultCallback1.m_collisionFilterMask = CollisionType_Raycasting|CollisionType_Actor|CollisionType_HeightMap; mDynamicsWorld->rayTest(from, to, resultCallback1); std::vector< std::pair > results = resultCallback1.results; From 30be59c029307937758c75a2a1ba98d1844950d9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 24 Jun 2014 03:07:10 +0200 Subject: [PATCH 166/226] Implement auto-adjusting of particle emit rate This makes ashcloud.nif and blightcloud.nif work properly. --- components/nif/controller.hpp | 9 ++++++++- components/nifogre/ogrenifloader.cpp | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index e44f4a6f30..920e634e65 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -89,7 +89,14 @@ public: float lifetime; float lifetimeRandom; - int emitFlags; // Bit 0: Emit Rate toggle bit (0 = auto adjust, 1 = use Emit Rate value) + enum EmitFlags + { + NoAutoAdjust = 0x1 // If this flag is set, we use the emitRate value. Otherwise, + // we calculate an emit rate so that the maximum number of particles + // in the system (numParticles) is never exceeded. + }; + int emitFlags; + Ogre::Vector3 offsetRandom; NodePtr emitter; diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 813c1660d9..eed320756c 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -756,7 +756,12 @@ class NIFObjectLoader Ogre::ParticleEmitter *emitter = partsys->addEmitter("Nif"); emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f, partctrl->velocity + partctrl->velocityRandom*0.5f); - emitter->setEmissionRate(partctrl->emitRate); + + if (partctrl->emitFlags & Nif::NiParticleSystemController::NoAutoAdjust) + emitter->setEmissionRate(partctrl->emitRate); + else + emitter->setEmissionRate(partctrl->numParticles / (partctrl->lifetime + partctrl->lifetimeRandom/2)); + emitter->setTimeToLive(partctrl->lifetime, partctrl->lifetime + partctrl->lifetimeRandom); emitter->setParameter("width", Ogre::StringConverter::toString(partctrl->offsetRandom.x)); From 96ba4a414f67d63cd9124c331c40c808e3ae9a56 Mon Sep 17 00:00:00 2001 From: slothlife Date: Tue, 24 Jun 2014 00:13:34 -0500 Subject: [PATCH 167/226] Fix MSVC size_t conversion warning. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 70ca1c7d79..9de8efeb54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -613,6 +613,7 @@ if (WIN32) 4127 # Conditional expression is constant 4242 # Storing value in a variable of a smaller type, possible loss of data 4244 # Storing value of one type in variable of another (size_t in int, for example) + 4267 # Conversion from 'size_t' to 'int', possible loss of data 4305 # Truncating value (double to float, for example) 4309 # Variable overflow, trying to store 128 in a signed char for example 4351 # New behavior: elements of array 'array' will be default initialized (desired behavior) From 36132e054afa8e85c5bd2ec4517908c810e580b1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 24 Jun 2014 15:00:15 +0200 Subject: [PATCH 168/226] Implement ashstorm, blightstorm, snow and blizzard effects (Feature #41) --- apps/openmw/mwrender/sky.cpp | 41 ++++++++++++++++++++++++++++++++- apps/openmw/mwrender/sky.hpp | 5 ++++ apps/openmw/mwworld/weather.cpp | 23 ++++++++++-------- apps/openmw/mwworld/weather.hpp | 7 +++++- 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 90c08c2995..545d34bdf3 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -8,10 +8,11 @@ #include #include #include -#include +#include #include #include #include +#include #include @@ -234,6 +235,7 @@ SkyManager::SkyManager(Ogre::SceneNode *root, Ogre::Camera *pCamera) , mCreated(false) , mCloudAnimationTimer(0.f) , mMoonRed(false) + , mParticleNode(NULL) { mSceneMgr = root->getCreator(); mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); @@ -372,6 +374,12 @@ void SkyManager::update(float duration) if (!mEnabled) return; const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback(); + if (!mParticle.isNull()) + { + for (unsigned int i=0; imControllers.size(); ++i) + mParticle->mControllers[i].update(); + } + // UV Scroll the clouds mCloudAnimationTimer += duration * mCloudSpeed; sh::Factory::getInstance().setSharedParameter ("cloudAnimationTimer", @@ -441,6 +449,37 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) { if (!mCreated) return; + if (mCurrentParticleEffect != weather.mParticleEffect) + { + mCurrentParticleEffect = weather.mParticleEffect; + + if (mCurrentParticleEffect.empty()) + { + mParticle.setNull(); + } + else + { + if (!mParticleNode) + { + mParticleNode = mCamera->getParentSceneNode()->createChildSceneNode(); + mParticleNode->setInheritOrientation(false); + } + + mParticle = NifOgre::Loader::createObjects(mParticleNode, mCurrentParticleEffect); + for(size_t i = 0; i < mParticle->mParticles.size(); ++i) + { + ParticleSystem* particle = mParticle->mParticles[i]; + particle->setRenderQueueGroup(RQG_Alpha); + particle->setVisibilityFlags(RV_Sky); + } + for (size_t i = 0; i < mParticle->mControllers.size(); ++i) + { + if (mParticle->mControllers[i].getSource().isNull()) + mParticle->mControllers[i].setSource(Ogre::ControllerManager::getSingleton().getFrameTimeSource()); + } + } + } + if (mClouds != weather.mCloudTexture) { sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", "textures\\"+weather.mCloudTexture); diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 965907a979..07edc461ee 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -200,6 +200,9 @@ namespace MWRender std::vector mObjects; + Ogre::SceneNode* mParticleNode; + NifOgre::ObjectScenePtr mParticle; + // remember some settings so we don't have to apply them again if they didnt change Ogre::String mClouds; Ogre::String mNextClouds; @@ -211,6 +214,8 @@ namespace MWRender Ogre::ColourValue mSkyColour; Ogre::ColourValue mFogColour; + std::string mCurrentParticleEffect; + Ogre::Light* mLightning; float mRemainingTransitionTime; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 25f523bee9..ab5ec00044 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -58,6 +58,11 @@ void WeatherManager::setFallbackWeather(Weather& weather,const std::string& name weather.mWindSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Wind_Speed"); weather.mCloudSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Cloud_Speed"); weather.mGlareView = mFallback->getFallbackFloat("Weather_"+upper+"_Glare_View"); + weather.mCloudTexture = mFallback->getFallbackString("Weather_"+upper+"_Cloud_Texture"); + size_t offset = weather.mCloudTexture.find(".tga"); + if (offset != std::string::npos) + weather.mCloudTexture.replace(offset, weather.mCloudTexture.length() - offset, ".dds"); + mWeatherSettings[name] = weather; } @@ -123,48 +128,42 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa //Weather Weather clear; - clear.mCloudTexture = "tx_sky_clear.dds"; setFallbackWeather(clear,"clear"); Weather cloudy; - cloudy.mCloudTexture = "tx_sky_cloudy.dds"; setFallbackWeather(cloudy,"cloudy"); Weather foggy; - foggy.mCloudTexture = "tx_sky_foggy.dds"; setFallbackWeather(foggy,"foggy"); Weather thunderstorm; - thunderstorm.mCloudTexture = "tx_sky_thunder.dds"; thunderstorm.mRainLoopSoundID = "rain heavy"; setFallbackWeather(thunderstorm,"thunderstorm"); Weather rain; - rain.mCloudTexture = "tx_sky_rainy.dds"; rain.mRainLoopSoundID = "rain"; setFallbackWeather(rain,"rain"); Weather overcast; - overcast.mCloudTexture = "tx_sky_overcast.dds"; setFallbackWeather(overcast,"overcast"); Weather ashstorm; - ashstorm.mCloudTexture = "tx_sky_ashstorm.dds"; ashstorm.mAmbientLoopSoundID = "ashstorm"; + ashstorm.mParticleEffect = "meshes\\ashcloud.nif"; setFallbackWeather(ashstorm,"ashstorm"); Weather blight; - blight.mCloudTexture = "tx_sky_blight.dds"; blight.mAmbientLoopSoundID = "blight"; + blight.mParticleEffect = "meshes\\blightcloud.nif"; setFallbackWeather(blight,"blight"); Weather snow; - snow.mCloudTexture = "tx_bm_sky_snow.dds"; + snow.mParticleEffect = "meshes\\snow.nif"; setFallbackWeather(snow, "snow"); Weather blizzard; - blizzard.mCloudTexture = "tx_bm_sky_blizzard.dds"; blizzard.mAmbientLoopSoundID = "BM Blizzard"; + blizzard.mParticleEffect = "meshes\\blizzard.nif"; setFallbackWeather(blizzard,"blizzard"); } @@ -214,6 +213,8 @@ void WeatherManager::setResult(const String& weatherType) mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mSunColor = current.mSunDiscSunsetColor; + mResult.mParticleEffect = current.mParticleEffect; + mResult.mNight = (mHour < mSunriseTime || mHour > mNightStart - 1); mResult.mFogDepth = mResult.mNight ? current.mLandFogNightDepth : current.mLandFogDayDepth; @@ -316,6 +317,8 @@ void WeatherManager::transition(float factor) mResult.mNightFade = lerp(current.mNightFade, other.mNightFade, factor); mResult.mNight = current.mNight; + + mResult.mParticleEffect = current.mParticleEffect; } void WeatherManager::update(float duration) diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 3e9df504ba..4d0458249f 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -58,6 +58,8 @@ namespace MWWorld float mNightFade; // fading factor for night skybox std::string mAmbientLoopSoundID; + + std::string mParticleEffect; }; @@ -119,7 +121,10 @@ namespace MWWorld // Rain sound effect std::string mRainLoopSoundID; - /// \todo disease chance + std::string mParticleEffect; + + // Note: For Weather Blight, there is a "Disease Chance" (=0.1) setting. But according to MWSFD this feature + // is broken in the vanilla game and was disabled. }; /// From b52977e44c1685f8763b35cc64ff9fe525d9899a Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 24 Jun 2014 15:09:13 +0200 Subject: [PATCH 169/226] Add dummy Face implementation for now (Bug #1541) --- apps/openmw/mwscript/aiextensions.cpp | 13 +++++++++++++ apps/openmw/mwscript/docs/vmformat.txt | 4 +++- components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 2 ++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 9a3387a00d..d844138d69 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -477,6 +477,16 @@ namespace MWScript } }; + template + class OpFace : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime& runtime) + { + /// \todo implement + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment3 (Compiler::Ai::opcodeAIActivate, new OpAiActivate); @@ -538,6 +548,9 @@ namespace MWScript interpreter.installSegment5 (Compiler::Ai::opcodeGetFleeExplicit, new OpGetAiSetting(2)); interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarm, new OpGetAiSetting(3)); interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarmExplicit, new OpGetAiSetting(3)); + + interpreter.installSegment5 (Compiler::Ai::opcodeFace, new OpFace); + interpreter.installSegment5 (Compiler::Ai::opcodeFaceExplicit, new OpFace); } } } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 4b2a91a958..319feee0e0 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -401,5 +401,7 @@ op 0x2000248: BetaComment, explicit op 0x2000249: OnMurder op 0x200024a: OnMurder, explicit op 0x200024b: ToggleMenus +op 0x200024c: Face +op 0x200024d: Face, explicit -opcodes 0x200024c-0x3ffffff unused +opcodes 0x200024e-0x3ffffff unused diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index f38c3320c1..4cd77cf1db 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -70,6 +70,7 @@ namespace Compiler extensions.registerFunction ("getlineofsight", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); extensions.registerFunction ("getlos", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); extensions.registerFunction("gettarget", 'l', "c", opcodeGetTarget, opcodeGetTargetExplicit); + extensions.registerInstruction("face", "llX", opcodeFace, opcodeFaceExplicit); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index b097a017bd..85ac578f34 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -59,6 +59,8 @@ namespace Compiler const int opcodeStartCombatExplicit = 0x200023b; const int opcodeStopCombat = 0x200023c; const int opcodeStopCombatExplicit = 0x200023d; + const int opcodeFace = 0x200024c; + const int opcodeFaceExplicit = 0x200024d; } namespace Animation From 36135293e887ab412acdc7686e71d1f6e61b6ecf Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 24 Jun 2014 15:29:36 +0200 Subject: [PATCH 170/226] Fix moving object from an inactive to another inactive cell --- apps/openmw/mwworld/worldimp.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c4e4e3d3a8..30024d3109 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -967,7 +967,7 @@ namespace MWWorld Ogre::Vector3 vec(x, y, z); - CellStore *currCell = ptr.isInCell() ? ptr.getCell() : NULL; + CellStore *currCell = ptr.isInCell() ? ptr.getCell() : NULL; // currCell == NULL should only happen for player, during initial startup bool isPlayer = ptr == mPlayer->getPlayer(); bool haveToMove = isPlayer || mWorldScene->isCellActive(*currCell); @@ -989,7 +989,9 @@ namespace MWWorld } else { - if (!mWorldScene->isCellActive(*currCell) && mWorldScene->isCellActive(*newCell)) + bool currCellActive = mWorldScene->isCellActive(*currCell); + bool newCellActive = mWorldScene->isCellActive(*newCell); + if (!currCellActive && newCellActive) { MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, *newCell, pos); mWorldScene->addObjectToScene(newPtr); @@ -1000,7 +1002,7 @@ namespace MWWorld } addContainerScripts(newPtr, newCell); } - else if (!mWorldScene->isCellActive(*newCell) && mWorldScene->isCellActive(*currCell)) + else if (!newCellActive && currCellActive) { mWorldScene->removeObjectFromScene(ptr); mLocalScripts.remove(ptr); @@ -1011,7 +1013,9 @@ namespace MWWorld .copyToCell(ptr, *newCell); newPtr.getRefData().setBaseNode(0); } - else + else if (!currCellActive && !newCellActive) + ptr.getClass().copyToCell(ptr, *newCell); + else // both cells active { MWWorld::Ptr copy = ptr.getClass().copyToCell(ptr, *newCell, pos); From 1c157b86f6405a90d9a392bef372972ed0d449ba Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 24 Jun 2014 18:14:27 +0200 Subject: [PATCH 171/226] Fix dead bodies blocking hits (again) --- libs/openengine/bullet/physic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index d00441fdf6..31530ebada 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -677,7 +677,7 @@ namespace Physic btCollisionObject *object) { DeepestNotMeContactTestResultCallback callback(filter, origin); - callback.m_collisionFilterGroup = 0xff; + callback.m_collisionFilterGroup = CollisionType_Actor; callback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor; mDynamicsWorld->contactTest(object, callback); return std::make_pair(callback.mObject, callback.mContactPoint); From 693a097b21fef5f89b0601cc2f36a988c9d49248 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 24 Jun 2014 18:37:38 +0200 Subject: [PATCH 172/226] Implement idlestorm animation (Feature #41) --- apps/openmw/mwbase/world.hpp | 6 ++++ apps/openmw/mwmechanics/character.cpp | 42 +++++++++++++++++++++++++++ apps/openmw/mwmechanics/character.hpp | 1 + apps/openmw/mwrender/sky.cpp | 2 +- apps/openmw/mwworld/refdata.hpp | 2 +- apps/openmw/mwworld/weather.cpp | 15 ++++++++++ apps/openmw/mwworld/weather.hpp | 15 +++++++++- apps/openmw/mwworld/worldimp.cpp | 17 +++++++++++ apps/openmw/mwworld/worldimp.hpp | 6 ++++ 9 files changed, 103 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f42edafd49..311d072c51 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -521,6 +521,12 @@ namespace MWBase const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0; virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; + + /// @see MWWorld::WeatherManager::isInStorm + virtual bool isInStorm() const = 0; + + /// @see MWWorld::WeatherManager::getStormDirection + virtual Ogre::Vector3 getStormDirection() const = 0; }; } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index ddfba51ce1..81183da3c2 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -20,6 +20,7 @@ #include "character.hpp" #include +#include #include "movement.hpp" #include "npcstats.hpp" @@ -234,6 +235,8 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat 1.0f, "start", "stop", 0.0f, ~0ul); } + updateIdleStormState(); + if(force && mJumpState != JumpState_None) { std::string jump; @@ -550,6 +553,45 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr) mPtr = ptr; } +void CharacterController::updateIdleStormState() +{ + bool inStormDirection = false; + if (MWBase::Environment::get().getWorld()->isInStorm()) + { + Ogre::Vector3 stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); + Ogre::Vector3 characterDirection = mPtr.getRefData().getBaseNode()->getOrientation().yAxis(); + inStormDirection = stormDirection.angleBetween(characterDirection) < Ogre::Degree(40); + } + if (inStormDirection && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm")) + { + float complete = 0; + mAnimation->getInfo("idlestorm", &complete); + + if (complete == 0) + mAnimation->play("idlestorm", Priority_Torch, MWRender::Animation::Group_RightArm, false, + 1.0f, "start", "loop start", 0.0f, 0); + else if (complete == 1) + mAnimation->play("idlestorm", Priority_Torch, MWRender::Animation::Group_RightArm, false, + 1.0f, "loop start", "loop stop", 0.0f, ~0ul); + } + else + { + if (mUpperBodyState == UpperCharState_Nothing) + { + if (mAnimation->isPlaying("idlestorm")) + { + if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) + { + mAnimation->play("idlestorm", Priority_Torch, MWRender::Animation::Group_RightArm, true, + 1.0f, "loop stop", "stop", 0.0f, 0); + } + } + } + else + mAnimation->disable("idlestorm"); + } +} + bool CharacterController::updateCreatureState() { const MWWorld::Class &cls = mPtr.getClass(); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 5cefe13bc8..59c20db8fe 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -178,6 +178,7 @@ class CharacterController bool updateWeaponState(); bool updateCreatureState(); + void updateIdleStormState(); void updateVisibility(); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 545d34bdf3..000a1d4090 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -461,7 +461,7 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) { if (!mParticleNode) { - mParticleNode = mCamera->getParentSceneNode()->createChildSceneNode(); + mParticleNode = mCamera->getParentSceneNode()->createChildSceneNode(Ogre::Vector3(0,0,100)); mParticleNode->setInheritOrientation(false); } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 753230e7f1..db66c091bb 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -54,7 +54,7 @@ namespace MWWorld RefData(); /// @param cellRef Used to copy constant data such as position into this class where it can - /// be altered without effecting the original data. This makes it possible + /// be altered without affecting the original data. This makes it possible /// to reset the position as the orignal data is still held in the CellRef RefData (const ESM::CellRef& cellRef); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index ab5ec00044..df78711ad8 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -63,6 +63,8 @@ void WeatherManager::setFallbackWeather(Weather& weather,const std::string& name if (offset != std::string::npos) weather.mCloudTexture.replace(offset, weather.mCloudTexture.length() - offset, ".dds"); + weather.mIsStorm = (name == "ashstorm" || name == "blight"); + mWeatherSettings[name] = weather; } @@ -213,6 +215,8 @@ void WeatherManager::setResult(const String& weatherType) mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mSunColor = current.mSunDiscSunsetColor; + mResult.mIsStorm = current.mIsStorm; + mResult.mParticleEffect = current.mParticleEffect; mResult.mNight = (mHour < mSunriseTime || mHour > mNightStart - 1); @@ -318,6 +322,7 @@ void WeatherManager::transition(float factor) mResult.mNight = current.mNight; + mResult.mIsStorm = current.mIsStorm; mResult.mParticleEffect = current.mParticleEffect; } @@ -771,3 +776,13 @@ void WeatherManager::switchToNextWeather(bool instantly) } } } + +bool WeatherManager::isInStorm() const +{ + return mResult.mIsStorm; +} + +Ogre::Vector3 WeatherManager::getStormDirection() const +{ + return Ogre::Vector3(0,-1,0); +} diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 4d0458249f..8aa3d26353 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -57,6 +57,8 @@ namespace MWWorld bool mNight; // use night skybox float mNightFade; // fading factor for night skybox + bool mIsStorm; + std::string mAmbientLoopSoundID; std::string mParticleEffect; @@ -102,7 +104,7 @@ namespace MWWorld // Duration of weather transition (in days) float mTransitionDelta; - // No idea what this one is used for? + // Used by scripts to animate signs, etc based on the wind (GetWindSpeed) float mWindSpeed; // Cloud animation speed multiplier @@ -121,6 +123,12 @@ namespace MWWorld // Rain sound effect std::string mRainLoopSoundID; + // Is this an ash storm / blight storm? This controls two things: + // - The particle node will be oriented so that the particles appear to come from the Red Mountain. (not implemented yet) + // - Characters will animate their hand to protect eyes from the storm when looking in its direction (idlestorm animation) + // Possible effect on movement speed? + bool mIsStorm; + std::string mParticleEffect; // Note: For Weather Blight, there is a "Disease Chance" (=0.1) setting. But according to MWSFD this feature @@ -156,6 +164,11 @@ namespace MWWorld float getWindSpeed() const; + /// Are we in an ash or blight storm? + bool isInStorm() const; + + Ogre::Vector3 getStormDirection() const; + void advanceTime(double hours) { mTimePassed += hours*3600; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 30024d3109..35042b3ff4 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1998,6 +1998,22 @@ namespace MWWorld return 0.f; } + bool World::isInStorm() const + { + if (isCellExterior() || isCellQuasiExterior()) + return mWeatherManager->isInStorm(); + else + return false; + } + + Ogre::Vector3 World::getStormDirection() const + { + if (isCellExterior() || isCellQuasiExterior()) + return mWeatherManager->getStormDirection(); + else + return Ogre::Vector3(0,1,0); + } + void World::getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out) { const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); @@ -2313,6 +2329,7 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + // TODO: this only works for the player MWWorld::Ptr target = getFacedObject(); std::string selectedSpell = stats.getSpells().getSelectedSpell(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 8cb2ac18a3..69b72b5330 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -591,6 +591,12 @@ namespace MWWorld const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName); virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor); + + /// @see MWWorld::WeatherManager::isInStorm + virtual bool isInStorm() const; + + /// @see MWWorld::WeatherManager::getStormDirection + virtual Ogre::Vector3 getStormDirection() const; }; } From e2743145485c125297aaa1d26078375f7b9b5ad6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 24 Jun 2014 19:51:30 +0200 Subject: [PATCH 173/226] Ignore alpha modifier for particle materials This makes the tx_ash_flake.dds particles from ashcloud.nif appear. --- components/nifogre/material.cpp | 6 ++++-- components/nifogre/material.hpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index b1ae83107b..cc06d65f35 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -106,7 +106,7 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, const Nif::NiZBufferProperty *zprop, const Nif::NiSpecularProperty *specprop, const Nif::NiWireframeProperty *wireprop, - bool &needTangents, bool disableLighting) + bool &needTangents, bool particleMaterial) { Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton(); Ogre::MaterialPtr material = matMgr.getByName(name); @@ -245,8 +245,10 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, } } - if (disableLighting) + if (particleMaterial) { + alpha = 1.f; // Apparently ignored, might be overridden by particle vertex colors? + ambient = Ogre::Vector3(0.f); diffuse = Ogre::Vector3(0.f); specular = Ogre::Vector3(0.f); diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp index 890255dcc6..abe1982eb9 100644 --- a/components/nifogre/material.hpp +++ b/components/nifogre/material.hpp @@ -49,7 +49,7 @@ public: const Nif::NiZBufferProperty *zprop, const Nif::NiSpecularProperty *specprop, const Nif::NiWireframeProperty *wireprop, - bool &needTangents, bool disableLighting=false); + bool &needTangents, bool particleMaterial=false); }; } From 8a8ecce1e550efe9c94696b881024fd6ee10a57e Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jun 2014 00:11:25 +0200 Subject: [PATCH 174/226] Fix some uninitialized data written to savegames --- apps/openmw/mwstate/statemanagerimp.cpp | 7 +++++++ components/esm/cellref.cpp | 9 +++++---- components/esm/esmwriter.cpp | 5 +++++ components/esm/esmwriter.hpp | 5 +++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index a459c87bbb..a3604cc66e 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -201,6 +201,13 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 writer.setFormat (ESM::Header::CurrentFormat); + + // all unused + writer.setVersion(0); + writer.setType(0); + writer.setAuthor(""); + writer.setDescription(""); + int recordCount = 1 // saved game header +MWBase::Environment::get().getJournal()->countSavedGameRecords() +MWBase::Environment::get().getWorld()->countSavedGameRecords() diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index d785fb2b60..84c638d9c7 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -108,13 +108,13 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons esm.writeHNT("NAM9", mGoldValue); } - if (mTeleport && !inInventory) + if (!inInventory && mTeleport) { esm.writeHNT("DODT", mDoorDest); esm.writeHNOCString("DNAM", mDestCell); } - if (mLockLevel != 0 && !inInventory) { + if (!inInventory && mLockLevel != 0) { esm.writeHNT("FLTV", mLockLevel); } @@ -127,13 +127,13 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons if (mReferenceBlocked != -1) esm.writeHNT("UNAM", mReferenceBlocked); - if (mFltv != 0 && !inInventory) + if (!inInventory && mFltv != 0) esm.writeHNT("FLTV", mFltv); if (!inInventory) esm.writeHNT("DATA", mPos, 24); - if (mNam0 != 0 && !inInventory) + if (!inInventory && mNam0 != 0) esm.writeHNT("NAM0", mNam0); } @@ -158,6 +158,7 @@ void ESM::CellRef::blank() mReferenceBlocked = 0; mFltv = 0; mNam0 = 0; + mTeleport = false; for (int i=0; i<3; ++i) { diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index 9d8d943d97..06572ce8f4 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -18,6 +18,11 @@ namespace ESM mHeader.mData.version = ver; } + void ESMWriter::setType(int type) + { + mHeader.mData.type = type; + } + void ESMWriter::setAuthor(const std::string& auth) { mHeader.mData.author.assign (auth); diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index ca4f422172..e57c6e45d8 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -25,10 +25,15 @@ class ESMWriter ESMWriter(); unsigned int getVersion() const; + + // Set various header data (ESM::Header::Data). All of the below functions must be called before writing, + // otherwise this data will be left uninitialized. void setVersion(unsigned int ver = 0x3fa66666); + void setType(int type); void setEncoder(ToUTF8::Utf8Encoder *encoding); void setAuthor(const std::string& author); void setDescription(const std::string& desc); + // Set the record count for writing it in the file header void setRecordCount (int count); // Counts how many records we have actually written. From 7f1d0fc2a2ef53c82e8696634217a742ef2f6454 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jun 2014 00:11:51 +0200 Subject: [PATCH 175/226] Fix disposition for npcs in same faction, by Hrnchamd --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 652f5363e8..6e515142de 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -501,7 +501,8 @@ namespace MWMechanics { if (!playerStats.getExpelled(npcFaction)) { - reaction = playerStats.getFactionReputation(npcFaction); + // faction reaction towards itself. yes, that exists + reaction = MWBase::Environment::get().getDialogueManager()->getFactionReaction(npcFaction, npcFaction); rank = playerStats.getFactionRanks().find(npcFaction)->second; } From 3d9bdad8ba55fb525f918b540dcbc11d847a2558 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jun 2014 00:51:02 +0200 Subject: [PATCH 176/226] Correct run speed for creatures (by Hrnchamd - Fixes #1136) --- apps/openmw/mwclass/creature.cpp | 4 +-- apps/openmw/mwmechanics/character.cpp | 50 ++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 1f286f7e6d..feb6a27147 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -537,8 +537,8 @@ namespace MWClass bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run); - float runSpeed = walkSpeed*6; - runSpeed = std::min(gmst.fMaxWalkSpeedCreature->getFloat(), runSpeed); // flame atronach runs way too fast without this + // The Run speed difference for creatures comes from the animation speed difference (see runStateToWalkState in character.cpp) + float runSpeed = walkSpeed; float moveSpeed; if(normalizedEncumbrance >= 1.0f) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 81183da3c2..5f8013b14a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -55,6 +55,43 @@ std::string getBestAttack (const ESM::Weapon* weapon) return "thrust"; } +// Converts a movement Run state to its equivalent Walk state. +MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState state) +{ + using namespace MWMechanics; + CharacterState ret = state; + switch (state) + { + case CharState_RunForward: + ret = CharState_WalkForward; + break; + case CharState_RunBack: + ret = CharState_WalkBack; + break; + case CharState_RunLeft: + ret = CharState_WalkLeft; + break; + case CharState_RunRight: + ret = CharState_WalkRight; + break; + case CharState_SwimRunForward: + ret = CharState_SwimWalkForward; + break; + case CharState_SwimRunBack: + ret = CharState_SwimWalkBack; + break; + case CharState_SwimRunLeft: + ret = CharState_SwimWalkLeft; + break; + case CharState_SwimRunRight: + ret = CharState_SwimWalkRight; + break; + default: + break; + } + return ret; +} + } namespace MWMechanics @@ -323,7 +360,18 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run); - if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f) + // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity + // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. + std::string anim = mCurrentMovement; + if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() + && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) + { + CharacterState walkState = runStateToWalkState(mMovementState); + const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState)); + anim = stateinfo->groupname; + } + + if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) { mMovementAnimVelocity = vel; speedmult = mMovementSpeed / vel; From b259c5def26cc371b13fdb048591f3a257981f59 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jun 2014 01:12:57 +0200 Subject: [PATCH 177/226] Make GetFactionReaction garbage argument optional --- apps/openmw/mwscript/dialogueextensions.cpp | 3 --- components/compiler/extensions0.cpp | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 45eeccf182..ecaf4253e8 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -227,9 +227,6 @@ namespace MWScript std::string faction2 = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - // ignore extra garbage argument - runtime.pop(); - runtime.push(MWBase::Environment::get().getDialogueManager() ->getFactionReaction(faction1, faction2)); } diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 4cd77cf1db..e095958d18 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -180,7 +180,7 @@ namespace Compiler extensions.registerFunction("samefaction", 'l', "", opcodeSameFaction, opcodeSameFactionExplicit); extensions.registerInstruction("modfactionreaction", "ccl", opcodeModFactionReaction); - extensions.registerFunction("getfactionreaction", 'l', "ccl", opcodeGetFactionReaction); + extensions.registerFunction("getfactionreaction", 'l', "ccX", opcodeGetFactionReaction); extensions.registerInstruction("clearinfoactor", "", opcodeClearInfoActor, opcodeClearInfoActorExplicit); } } From ee098de0a69c6fbf538fe1841598715e82c3f53a Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jun 2014 01:14:00 +0200 Subject: [PATCH 178/226] Don't ignore lighting values of particles not attached to a character --- apps/openmw/mwrender/animation.cpp | 68 +++++++++++++++++++++++++----- components/nifogre/material.cpp | 5 --- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 78703172e8..83fe766a94 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1169,20 +1169,68 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con params.mObjects->mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationTime())); } - if (!texture.empty()) + + // Do some manual adjustments on the created entities/particle systems + + // It looks like vanilla MW totally ignores lighting settings for effects attached to characters. + // If we don't do this, some effects will look way too dark depending on the environment + // (e.g. magic_cast_dst.nif). They were clearly meant to use emissive lighting. + // We used to have this hack in the NIF material loader, but for effects not attached to characters + // (e.g. ash storms) the lighting settings do seem to be in use. Is there maybe a flag we have missed? + Ogre::ColourValue ambient = Ogre::ColourValue(0.f, 0.f, 0.f); + Ogre::ColourValue diffuse = Ogre::ColourValue(0.f, 0.f, 0.f); + Ogre::ColourValue specular = Ogre::ColourValue(0.f, 0.f, 0.f); + Ogre::ColourValue emissive = Ogre::ColourValue(1.f, 1.f, 1.f); + for(size_t i = 0;i < params.mObjects->mParticles.size(); ++i) { - for(size_t i = 0;i < params.mObjects->mParticles.size(); ++i) + Ogre::ParticleSystem* partSys = params.mObjects->mParticles[i]; + + Ogre::MaterialPtr mat = params.mObjects->mMaterialControllerMgr.getWritableMaterial(partSys); + + for (int t=0; tgetNumTechniques(); ++t) { - Ogre::ParticleSystem* partSys = params.mObjects->mParticles[i]; - - Ogre::MaterialPtr mat = params.mObjects->mMaterialControllerMgr.getWritableMaterial(partSys); - - for (int t=0; tgetNumTechniques(); ++t) + Ogre::Technique* tech = mat->getTechnique(t); + for (int p=0; pgetNumPasses(); ++p) { - Ogre::Technique* tech = mat->getTechnique(t); - for (int p=0; pgetNumPasses(); ++p) + Ogre::Pass* pass = tech->getPass(p); + + pass->setAmbient(ambient); + pass->setDiffuse(diffuse); + pass->setSpecular(specular); + pass->setEmissive(emissive); + + if (!texture.empty()) + { + for (int tex=0; texgetNumTextureUnitStates(); ++tex) + { + Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); + tus->setTextureName("textures\\" + texture); + } + } + } + } + } + for(size_t i = 0;i < params.mObjects->mEntities.size(); ++i) + { + Ogre::Entity* ent = params.mObjects->mEntities[i]; + if (ent == params.mObjects->mSkelBase) + continue; + Ogre::MaterialPtr mat = params.mObjects->mMaterialControllerMgr.getWritableMaterial(ent); + + for (int t=0; tgetNumTechniques(); ++t) + { + Ogre::Technique* tech = mat->getTechnique(t); + for (int p=0; pgetNumPasses(); ++p) + { + Ogre::Pass* pass = tech->getPass(p); + + pass->setAmbient(ambient); + pass->setDiffuse(diffuse); + pass->setSpecular(specular); + pass->setEmissive(emissive); + + if (!texture.empty()) { - Ogre::Pass* pass = tech->getPass(p); for (int tex=0; texgetNumTextureUnitStates(); ++tex) { Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index cc06d65f35..44831c13bb 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -248,11 +248,6 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, if (particleMaterial) { alpha = 1.f; // Apparently ignored, might be overridden by particle vertex colors? - - ambient = Ogre::Vector3(0.f); - diffuse = Ogre::Vector3(0.f); - specular = Ogre::Vector3(0.f); - emissive = Ogre::Vector3(1.f); } { From ec64f1a53a5ab38f39b5879acfe8f393c92e2afa Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jun 2014 02:46:39 +0200 Subject: [PATCH 179/226] Reset accumulation root when its animation finishes Fixes a position flicker after standing up from knockdown. --- apps/openmw/mwrender/animation.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 83fe766a94..8bf2160e3b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1027,7 +1027,11 @@ Ogre::Vector3 Animation::runAnimation(float duration) if(!state.mPlaying && state.mAutoDisable) { + if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName()) + mAccumRoot->setPosition(0.f,0.f,0.f); + mStates.erase(stateiter++); + resetActiveGroups(); } else From 2bcbc6ab7d630dcad9e5f73140a16db05edbcda9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jun 2014 14:52:12 +0200 Subject: [PATCH 180/226] Potential crash fix, either way should have a better error message now --- apps/openmw/mwworld/physicssystem.cpp | 5 +++++ components/nifbullet/bulletnifloader.cpp | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 7f26fa75a9..c4b904ca95 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -45,7 +45,12 @@ void animateCollisionShapes (std::mapsearchPtrViaHandle(it->first->mName); + if (ptr.isEmpty()) // Shouldn't happen + throw std::runtime_error("can't find Ptr"); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); + if (!animation) // Shouldn't happen either, since keyframe-controlled objects are not batched in StaticGeometry + throw std::runtime_error("can't find Animation for " + ptr.getCellRef().getRefId()); OEngine::Physic::AnimatedShapeInstance& instance = it->second; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 64febc3c2d..7f78c04aa4 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -181,7 +181,8 @@ void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, // the flags we currently use, at least. flags |= node->flags; - if (!node->controller.empty() && node->controller->recType == Nif::RC_NiKeyframeController) + if (!node->controller.empty() && node->controller->recType == Nif::RC_NiKeyframeController + && (node->controller->flags & Nif::NiNode::ControllerFlag_Active)) isAnimated = true; if (!raycasting) From 09926a86cbfd0032d3c4b401f8aaf0aecccd17ff Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jun 2014 18:10:26 +0200 Subject: [PATCH 181/226] Fix comparing outdated listener position with up-to-date cell (Fixes #1499) --- apps/openmw/mwsound/soundmanagerimp.cpp | 13 ++++++++----- apps/openmw/mwsound/soundmanagerimp.hpp | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 35b8579d79..200c50e692 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -42,6 +42,7 @@ namespace MWSound , mListenerPos(0,0,0) , mListenerDir(1,0,0) , mListenerUp(0,0,1) + , mListenerUnderwater(false) { if(!useSound) return; @@ -584,12 +585,8 @@ namespace MWSound if(!isMusicPlaying()) startRandomTitle(); - MWWorld::Ptr player = - MWBase::Environment::get().getWorld()->getPlayerPtr(); - const ESM::Cell *cell = player.getCell()->getCell(); - Environment env = Env_Normal; - if((cell->mData.mFlags&cell->HasWater) && mListenerPos.z < cell->mWater) + if (mListenerUnderwater) { env = Env_Underwater; //play underwater sound @@ -682,6 +679,12 @@ namespace MWSound mListenerPos = pos; mListenerDir = dir; mListenerUp = up; + + MWWorld::Ptr player = + MWBase::Environment::get().getWorld()->getPlayerPtr(); + const ESM::Cell *cell = player.getCell()->getCell(); + + mListenerUnderwater = ((cell->mData.mFlags&cell->HasWater) && mListenerPos.z < cell->mWater); } void SoundManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 558b6966af..380cfe2552 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -50,6 +50,7 @@ namespace MWSound MWBase::SoundPtr mUnderwaterSound; + bool mListenerUnderwater; Ogre::Vector3 mListenerPos; Ogre::Vector3 mListenerDir; Ogre::Vector3 mListenerUp; From 4a6dbe6f8971b49bf2a426a7d0d57dcfd15d9e7f Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jun 2014 18:15:56 +0200 Subject: [PATCH 182/226] Use the actor's collision group/mask for traces Fixes dead bodies being lifted up when standing below them. --- libs/openengine/bullet/physic.cpp | 2 +- libs/openengine/bullet/trace.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 31530ebada..6bc544af4a 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -180,7 +180,7 @@ namespace Physic { mEngine->mDynamicsWorld->removeRigidBody(mBody); mEngine->mDynamicsWorld->addRigidBody(mBody, CollisionType_Actor, - CollisionType_Raycasting); + CollisionType_World|CollisionType_HeightMap); } void PhysicActor::enableCollisionBody() diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index 46db4c6b87..ad140f1f7e 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -65,9 +65,9 @@ void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, to.setOrigin(btend); ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0)); - newTraceCallback.m_collisionFilterGroup = CollisionType_Actor; - newTraceCallback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | - CollisionType_Actor; + // Inherit the actor's collision group and mask + newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; + newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; btCollisionShape *shape = actor->getCollisionShape(); assert(shape->isConvex()); @@ -100,9 +100,9 @@ void ActorTracer::findGround(const OEngine::Physic::PhysicActor* actor, const Og btTransform to(trans.getBasis(), btend); ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionBody(), btstart-btend, btScalar(0.0)); - newTraceCallback.m_collisionFilterGroup = CollisionType_Actor; - newTraceCallback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | - CollisionType_Actor; + // Inherit the actor's collision group and mask + newTraceCallback.m_collisionFilterGroup = actor->getCollisionBody()->getBroadphaseHandle()->m_collisionFilterGroup; + newTraceCallback.m_collisionFilterMask = actor->getCollisionBody()->getBroadphaseHandle()->m_collisionFilterMask; btVector3 halfExtents(actor->getHalfExtents().x, actor->getHalfExtents().y, actor->getHalfExtents().z); From f6a568c995c46cc5f8490e2358afc542561e9399 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jun 2014 18:20:21 +0200 Subject: [PATCH 183/226] Implement rain (Feature #41) --- apps/openmw/mwrender/sky.cpp | 83 ++++++++++++++++++++++++++++++++- apps/openmw/mwrender/sky.hpp | 13 ++++++ apps/openmw/mwworld/weather.cpp | 32 +++++++++++-- apps/openmw/mwworld/weather.hpp | 15 ++++++ 4 files changed, 139 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 000a1d4090..2bbc95c899 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -236,6 +236,10 @@ SkyManager::SkyManager(Ogre::SceneNode *root, Ogre::Camera *pCamera) , mCloudAnimationTimer(0.f) , mMoonRed(false) , mParticleNode(NULL) + , mRainEnabled(false) + , mRainTimer(0) + , mRainSpeed(0) + , mRainFrequency(1) { mSceneMgr = root->getCreator(); mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); @@ -351,6 +355,7 @@ void SkyManager::create() SkyManager::~SkyManager() { + clearRain(); delete mSun; delete mSunGlare; delete mMasser; @@ -369,6 +374,66 @@ int SkyManager::getSecundaPhase() const return mSecunda->getPhaseInt(); } +void SkyManager::clearRain() +{ + for (std::map::iterator it = mRainModels.begin(); it != mRainModels.end();) + { + it->second.setNull(); + Ogre::SceneNode* parent = it->first->getParentSceneNode(); + mSceneMgr->destroySceneNode(it->first); + mSceneMgr->destroySceneNode(parent); + mRainModels.erase(it++); + } +} + +void SkyManager::updateRain(float dt) +{ + // Move existing rain + // Note: if rain gets disabled, we let the existing rain drops finish falling down. + float minHeight = 200; + for (std::map::iterator it = mRainModels.begin(); it != mRainModels.end();) + { + Ogre::Vector3 pos = it->first->getPosition(); + pos.z -= mRainSpeed * dt; + it->first->setPosition(pos); + if (pos.z < -minHeight) + { + it->second.setNull(); + Ogre::SceneNode* parent = it->first->getParentSceneNode(); + mSceneMgr->destroySceneNode(it->first); + mSceneMgr->destroySceneNode(parent); + mRainModels.erase(it++); + } + else + ++it; + } + + // Spawn new rain + float rainFrequency = mRainFrequency; + float startHeight = 700; + if (mRainEnabled) + { + mRainTimer += dt; + if (mRainTimer >= 1.f/rainFrequency) + { + mRainTimer = 0; + + const float rangeRandom = 100; + float xOffs = (std::rand()/(RAND_MAX+1.0)) * rangeRandom - (rangeRandom/2); + float yOffs = (std::rand()/(RAND_MAX+1.0)) * rangeRandom - (rangeRandom/2); + Ogre::SceneNode* sceneNode = mCamera->getParentSceneNode()->createChildSceneNode(); + sceneNode->setInheritOrientation(false); + + // Create a separate node to control the offset, since a node with setInheritOrientation(false) will still + // consider the orientation of the parent node for its position, just not for its orientation + Ogre::SceneNode* offsetNode = sceneNode->createChildSceneNode(Ogre::Vector3(xOffs,yOffs,startHeight)); + + NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(offsetNode, mRainEffect); + mRainModels[offsetNode] = objects; + } + } +} + void SkyManager::update(float duration) { if (!mEnabled) return; @@ -380,6 +445,8 @@ void SkyManager::update(float duration) mParticle->mControllers[i].update(); } + updateRain(duration); + // UV Scroll the clouds mCloudAnimationTimer += duration * mCloudSpeed; sh::Factory::getInstance().setSharedParameter ("cloudAnimationTimer", @@ -430,13 +497,22 @@ void SkyManager::enable() if (!mCreated) create(); + if (mParticleNode) + mParticleNode->setVisible(true); + mRootNode->setVisible(true); mEnabled = true; } void SkyManager::disable() { + if (mParticleNode) + mParticleNode->setVisible(false); + + clearRain(); + mRootNode->setVisible(false); + mEnabled = false; } @@ -449,6 +525,11 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) { if (!mCreated) return; + mRainEffect = weather.mRainEffect; + mRainEnabled = !mRainEffect.empty(); + mRainFrequency = weather.mRainFrequency; + mRainSpeed = weather.mRainSpeed; + if (mCurrentParticleEffect != weather.mParticleEffect) { mCurrentParticleEffect = weather.mParticleEffect; @@ -461,7 +542,7 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) { if (!mParticleNode) { - mParticleNode = mCamera->getParentSceneNode()->createChildSceneNode(Ogre::Vector3(0,0,100)); + mParticleNode = mCamera->getParentSceneNode()->createChildSceneNode(); mParticleNode->setInheritOrientation(false); } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 07edc461ee..f81a42782c 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -148,6 +148,8 @@ namespace MWRender void sunDisable(); + void setRainSpeed(float speed); + void setSunDirection(const Ogre::Vector3& direction); void setMasserDirection(const Ogre::Vector3& direction); @@ -176,6 +178,9 @@ namespace MWRender void create(); ///< no need to call this, automatically done on first enable() + void updateRain(float dt); + void clearRain(); + bool mCreated; bool mMoonRed; @@ -203,6 +208,9 @@ namespace MWRender Ogre::SceneNode* mParticleNode; NifOgre::ObjectScenePtr mParticle; + std::map mRainModels; + float mRainTimer; + // remember some settings so we don't have to apply them again if they didnt change Ogre::String mClouds; Ogre::String mNextClouds; @@ -223,6 +231,11 @@ namespace MWRender float mGlare; // target float mGlareFade; // actual + bool mRainEnabled; + std::string mRainEffect; + float mRainSpeed; + float mRainFrequency; + bool mEnabled; bool mSunEnabled; bool mMasserEnabled; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index df78711ad8..6050e0c298 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -59,6 +59,21 @@ void WeatherManager::setFallbackWeather(Weather& weather,const std::string& name weather.mCloudSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Cloud_Speed"); weather.mGlareView = mFallback->getFallbackFloat("Weather_"+upper+"_Glare_View"); weather.mCloudTexture = mFallback->getFallbackString("Weather_"+upper+"_Cloud_Texture"); + + bool usesPrecip = mFallback->getFallbackBool("Weather_"+upper+"_Using_Precip"); + if (usesPrecip) + weather.mRainEffect = "meshes\\raindrop.nif"; + weather.mRainSpeed = mRainSpeed; + weather.mRainFrequency = mFallback->getFallbackFloat("Weather_"+upper+"_Rain_Entrance_Speed"); + /* +Unhandled: +Rain Diameter=600 ? +Rain Height Min=200 ? +Rain Height Max=700 ? +Rain Threshold=0.6 ? +Max Raindrops=650 ? +*/ + size_t offset = weather.mCloudTexture.find(".tga"); if (offset != std::string::npos) weather.mCloudTexture.replace(offset, weather.mCloudTexture.length() - offset, ".dds"); @@ -100,7 +115,7 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa mHour(14), mCurrentWeather("clear"), mNextWeather(""), mFirstUpdate(true), mWeatherUpdateTime(0), mThunderFlash(0), mThunderChance(0), mThunderChanceNeeded(50), mThunderSoundDelay(0), mRemainingTransitionTime(0), - mTimePassed(0), mFallback(fallback), mWindSpeed(0.f), mRendering(rendering) + mTimePassed(0), mFallback(fallback), mWindSpeed(0.f), mRendering(rendering), mIsStorm(false) { //Globals mThunderSoundID0 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_0"); @@ -117,6 +132,8 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa mThunderThreshold = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Threshold"); mThunderSoundDelay = 0.25; + mRainSpeed = mFallback->getFallbackFloat("Weather_Precip_Gravity"); + //Some useful values /* TODO: Use pre-sunrise_time, pre-sunset_time, * post-sunrise_time, and post-sunset_time to better @@ -140,10 +157,12 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa Weather thunderstorm; thunderstorm.mRainLoopSoundID = "rain heavy"; + thunderstorm.mRainEffect = "meshes\\raindrop.nif"; setFallbackWeather(thunderstorm,"thunderstorm"); Weather rain; rain.mRainLoopSoundID = "rain"; + rain.mRainEffect = "meshes\\raindrop.nif"; setFallbackWeather(rain,"rain"); Weather overcast; @@ -217,7 +236,11 @@ void WeatherManager::setResult(const String& weatherType) mResult.mIsStorm = current.mIsStorm; + mResult.mRainSpeed = current.mRainSpeed; + mResult.mRainFrequency = current.mRainFrequency; + mResult.mParticleEffect = current.mParticleEffect; + mResult.mRainEffect = current.mRainEffect; mResult.mNight = (mHour < mSunriseTime || mHour > mNightStart - 1); @@ -324,6 +347,9 @@ void WeatherManager::transition(float factor) mResult.mIsStorm = current.mIsStorm; mResult.mParticleEffect = current.mParticleEffect; + mResult.mRainEffect = current.mRainEffect; + mResult.mRainSpeed = current.mRainSpeed; + mResult.mRainFrequency = current.mRainFrequency; } void WeatherManager::update(float duration) @@ -361,6 +387,7 @@ void WeatherManager::update(float duration) setResult(mCurrentWeather); mWindSpeed = mResult.mWindSpeed; + mIsStorm = mResult.mIsStorm; mRendering->configureFog(mResult.mFogDepth, mResult.mFogColor); @@ -499,7 +526,6 @@ void WeatherManager::update(float duration) mRendering->getSkyManager()->setWeather(mResult); - // Play sounds if (mNextWeather == "") { @@ -779,7 +805,7 @@ void WeatherManager::switchToNextWeather(bool instantly) bool WeatherManager::isInStorm() const { - return mResult.mIsStorm; + return mIsStorm; } Ogre::Vector3 WeatherManager::getStormDirection() const diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 8aa3d26353..9693014a91 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -62,6 +62,10 @@ namespace MWWorld std::string mAmbientLoopSoundID; std::string mParticleEffect; + + std::string mRainEffect; + float mRainSpeed; + float mRainFrequency; }; @@ -129,8 +133,17 @@ namespace MWWorld // Possible effect on movement speed? bool mIsStorm; + // How fast does rain travel down? + // In Morrowind.ini this is set globally, but we may want to change it per weather later. + float mRainSpeed; + + // How often does a new rain mesh spawn? + float mRainFrequency; + std::string mParticleEffect; + std::string mRainEffect; + // Note: For Weather Blight, there is a "Disease Chance" (=0.1) setting. But according to MWSFD this feature // is broken in the vanilla game and was disabled. }; @@ -188,6 +201,7 @@ namespace MWWorld private: float mHour; float mWindSpeed; + bool mIsStorm; MWWorld::Fallback* mFallback; void setFallbackWeather(Weather& weather,const std::string& name); MWRender::RenderingManager* mRendering; @@ -226,6 +240,7 @@ namespace MWWorld typedef std::map > RegionModMap; RegionModMap mRegionMods; + float mRainSpeed; float mSunriseTime; float mSunsetTime; float mSunriseDuration; From 5bd2f30a99dfe5e60fd4f4684826b9df35592a88 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jun 2014 18:32:06 +0200 Subject: [PATCH 184/226] CellRef uninitialized fixes --- apps/openmw/mwworld/manualref.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index 3842e7ff1b..b77257e47e 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -35,6 +35,8 @@ namespace MWWorld cellRef.mTeleport = false; cellRef.mLockLevel = 0; cellRef.mReferenceBlocked = 0; + cellRef.mFltv = 0; + cellRef.mNam0 = 0; LiveCellRef ref(cellRef, base); From b25026299cff8dc745e5efbdfbc4b2f2f3e94cd0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 25 Jun 2014 20:33:03 +0200 Subject: [PATCH 185/226] Why was Hrnchamd not in credits? --- credits.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/credits.txt b/credits.txt index 791db04339..5c757f957a 100644 --- a/credits.txt +++ b/credits.txt @@ -106,6 +106,7 @@ sir_herrbatka - Forum Administrator Formula Research: +Hrnchamd Epsilon fragonard Greendogo From 2d17d8f61a4a8df792e4cb19d0b4e496d4ce4afc Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jun 2014 01:19:03 +0200 Subject: [PATCH 186/226] Savegame: start in (0,0) cell if player's cell no longer exists This happens frequently when saves are loaded that were created in a different MW language. --- apps/openmw/mwworld/player.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index b88483bfe8..12908ca9da 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -246,7 +246,15 @@ namespace MWWorld MWBase::World& world = *MWBase::Environment::get().getWorld(); - mCellStore = world.getCell (player.mCellId); + try + { + mCellStore = world.getCell (player.mCellId); + } + catch (...) + { + // Cell no longer exists. Place the player in a default cell. + mCellStore = world.getExterior(0,0); + } if (!player.mBirthsign.empty() && !world.getStore().get().search (player.mBirthsign)) From 15b486e1499fad91875722e03589cf2cb6617b81 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jun 2014 01:21:15 +0200 Subject: [PATCH 187/226] Don't trigger changed flag if a script with no locals is configured --- apps/openmw/mwscript/locals.cpp | 5 +++++ apps/openmw/mwscript/locals.hpp | 3 +++ apps/openmw/mwworld/refdata.cpp | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index 094fe085a2..177536fe9e 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -21,6 +21,11 @@ namespace MWScript mFloats.resize (script.mData.mNumFloats, 0); } + bool Locals::isEmpty() const + { + return (mShorts.empty() && mLongs.empty() && mFloats.empty()); + } + int Locals::getIntVar(const std::string &script, const std::string &var) { Compiler::Locals locals = MWBase::Environment::get().getScriptManager()->getLocals(script); diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index d17d1be2dc..1e8c6e12a4 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -20,6 +20,9 @@ namespace MWScript std::vector mLongs; std::vector mFloats; + /// Are there any locals? + bool isEmpty() const; + void configure (const ESM::Script& script); bool setVarByInt(const std::string& script, const std::string& var, int val); int getIntVar (const std::string& script, const std::string& var); ///< if var does not exist, returns 0 diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 3b7521e8dd..f4bc64b708 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -152,7 +152,8 @@ namespace MWWorld { mLocals.configure (script); mHasLocals = true; - mChanged = true; + if (!mLocals.isEmpty()) + mChanged = true; } } From 697329f12812c21efa74fa7a15f46e95af454022 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jun 2014 03:33:09 +0200 Subject: [PATCH 188/226] Fix crash for in_dagoth_bridge00.nif (Fixes #1561) This one is causing trouble by using an unnamed node, will need some more work. --- apps/openmw/mwworld/physicssystem.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index c4b904ca95..87ec397073 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -60,6 +60,12 @@ void animateCollisionShapes (std::mapgetNode(shapeIt->first); + // FIXME: this will happen for nodes with empty names. Ogre's SkeletonInstance::cloneBoneAndChildren + // will assign an auto-generated name if the bone name was empty. We could use the bone handle instead of + // the bone name, but that is a bit tricky to retrieve. + if (bone == NULL) + continue; + btCompoundShape* compound = dynamic_cast(instance.mCompound); btTransform trans; From bd252d0aec944e6ced8e4f723647f486f52ae3d8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 26 Jun 2014 11:41:21 +0200 Subject: [PATCH 189/226] display record count in loading window --- apps/opencs/model/doc/documentmanager.cpp | 4 +-- apps/opencs/model/doc/documentmanager.hpp | 5 ++-- apps/opencs/model/doc/loader.cpp | 14 +++++++--- apps/opencs/model/doc/loader.hpp | 6 +++-- apps/opencs/view/doc/loader.cpp | 31 +++++++++++++++-------- apps/opencs/view/doc/loader.hpp | 10 +++++--- apps/opencs/view/doc/viewmanager.cpp | 4 +-- 7 files changed, 47 insertions(+), 27 deletions(-) diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 096864b772..4020a8f72c 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -31,8 +31,8 @@ CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& con &mLoader, SLOT (loadDocument (CSMDoc::Document *))); connect (&mLoader, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int)), this, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int))); - connect (&mLoader, SIGNAL (nextRecord (CSMDoc::Document *)), - this, SIGNAL (nextRecord (CSMDoc::Document *))); + connect (&mLoader, SIGNAL (nextRecord (CSMDoc::Document *, int)), + this, SIGNAL (nextRecord (CSMDoc::Document *, int))); connect (this, SIGNAL (cancelLoading (CSMDoc::Document *)), &mLoader, SLOT (abortLoading (CSMDoc::Document *))); connect (&mLoader, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&)), diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index 9b675826a2..d6824112bb 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -79,9 +79,10 @@ namespace CSMDoc void loadingStopped (CSMDoc::Document *document, bool completed, const std::string& error); - void nextStage (CSMDoc::Document *document, const std::string& name, int steps); + void nextStage (CSMDoc::Document *document, const std::string& name, + int totalRecords); - void nextRecord (CSMDoc::Document *document); + void nextRecord (CSMDoc::Document *document, int records); void cancelLoading (CSMDoc::Document *document); diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index 6fb10426c5..ca9c456968 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -8,7 +8,7 @@ #include "document.hpp" #include "state.hpp" -CSMDoc::Loader::Stage::Stage() : mFile (0), mRecordsLeft (false) {} +CSMDoc::Loader::Stage::Stage() : mFile (0), mRecordsLoaded (0), mRecordsLeft (false) {} CSMDoc::Loader::Loader() @@ -59,17 +59,21 @@ void CSMDoc::Loader::load() iter->second.mRecordsLeft = false; break; } + else + ++(iter->second.mRecordsLoaded); CSMWorld::UniversalId log (CSMWorld::UniversalId::Type_LoadErrorLog, 0); + { // silence a g++ warning for (CSMDoc::Stage::Messages::const_iterator iter (messages.begin()); iter!=messages.end(); ++iter) { document->getReport (log)->add (iter->first, iter->second); emit loadMessage (document, iter->second); } + } - emit nextRecord (document); + emit nextRecord (document, iter->second.mRecordsLoaded); return; } @@ -80,15 +84,17 @@ void CSMDoc::Loader::load() int steps = document->getData().startLoading (path, iter->second.mFile!=editedIndex, false); iter->second.mRecordsLeft = true; + iter->second.mRecordsLoaded = 0; - emit nextStage (document, path.filename().string(), steps/batchingSize); + emit nextStage (document, path.filename().string(), steps); } else if (iter->second.mFile==size) { int steps = document->getData().startLoading (document->getProjectPath(), false, true); iter->second.mRecordsLeft = true; + iter->second.mRecordsLoaded = 0; - emit nextStage (document, "Project File", steps/batchingSize); + emit nextStage (document, "Project File", steps); } else { diff --git a/apps/opencs/model/doc/loader.hpp b/apps/opencs/model/doc/loader.hpp index c276e14ff4..d1ee38f9f1 100644 --- a/apps/opencs/model/doc/loader.hpp +++ b/apps/opencs/model/doc/loader.hpp @@ -18,6 +18,7 @@ namespace CSMDoc struct Stage { int mFile; + int mRecordsLoaded; bool mRecordsLeft; Stage(); @@ -56,9 +57,10 @@ namespace CSMDoc ///< Document load has been interrupted either because of a call to abortLoading /// or a problem during loading). In the former case error will be an empty string. - void nextStage (CSMDoc::Document *document, const std::string& name, int steps); + void nextStage (CSMDoc::Document *document, const std::string& name, + int totalRecords); - void nextRecord (CSMDoc::Document *document); + void nextRecord (CSMDoc::Document *document, int records); ///< \note This signal is only given once per group of records. The group size is /// approximately the total number of records divided by the steps value of the /// previous nextStage signal. diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp index 7e4754ddf8..ca7c93f9df 100644 --- a/apps/opencs/view/doc/loader.cpp +++ b/apps/opencs/view/doc/loader.cpp @@ -45,7 +45,7 @@ CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) mFileProgress->setValue (0); // record progress - mLayout->addWidget (new QLabel ("Records", this)); + mLayout->addWidget (mRecords = new QLabel ("Records", this)); mRecordProgress = new QProgressBar (this); @@ -75,22 +75,30 @@ CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) connect (mButtons, SIGNAL (rejected()), this, SLOT (cancel())); } -void CSVDoc::LoadingDocument::nextStage (const std::string& name, int steps) +void CSVDoc::LoadingDocument::nextStage (const std::string& name, int totalRecords) { mFile->setText (QString::fromUtf8 (("Loading: " + name).c_str())); mFileProgress->setValue (mFileProgress->value()+1); mRecordProgress->setValue (0); - mRecordProgress->setMaximum (steps>0 ? steps : 1); + mRecordProgress->setMaximum (totalRecords>0 ? totalRecords : 1); + + mTotalRecords = totalRecords; } -void CSVDoc::LoadingDocument::nextRecord() +void CSVDoc::LoadingDocument::nextRecord (int records) { - int value = mRecordProgress->value()+1; + if (records<=mTotalRecords) + { + mRecordProgress->setValue (records); - if (value<=mRecordProgress->maximum()) - mRecordProgress->setValue (value); + std::ostringstream stream; + + stream << "Records: " << records << " of " << mTotalRecords; + + mRecords->setText (QString::fromUtf8 (stream.str().c_str())); + } } void CSVDoc::LoadingDocument::abort (const std::string& error) @@ -168,20 +176,21 @@ void CSVDoc::Loader::loadingStopped (CSMDoc::Document *document, bool completed, } } -void CSVDoc::Loader::nextStage (CSMDoc::Document *document, const std::string& name, int steps) +void CSVDoc::Loader::nextStage (CSMDoc::Document *document, const std::string& name, + int totalRecords) { std::map::iterator iter = mDocuments.find (document); if (iter!=mDocuments.end()) - iter->second->nextStage (name, steps); + iter->second->nextStage (name, totalRecords); } -void CSVDoc::Loader::nextRecord (CSMDoc::Document *document) +void CSVDoc::Loader::nextRecord (CSMDoc::Document *document, int records) { std::map::iterator iter = mDocuments.find (document); if (iter!=mDocuments.end()) - iter->second->nextRecord(); + iter->second->nextRecord (records); } void CSVDoc::Loader::loadMessage (CSMDoc::Document *document, const std::string& message) diff --git a/apps/opencs/view/doc/loader.hpp b/apps/opencs/view/doc/loader.hpp index 69a8b48ba3..e004007c99 100644 --- a/apps/opencs/view/doc/loader.hpp +++ b/apps/opencs/view/doc/loader.hpp @@ -26,6 +26,7 @@ namespace CSVDoc CSMDoc::Document *mDocument; QLabel *mFile; + QLabel *mRecords; QProgressBar *mFileProgress; QProgressBar *mRecordProgress; bool mAborted; @@ -33,6 +34,7 @@ namespace CSVDoc QLabel *mError; QListWidget *mMessages; QVBoxLayout *mLayout; + int mTotalRecords; private: @@ -42,9 +44,9 @@ namespace CSVDoc LoadingDocument (CSMDoc::Document *document); - void nextStage (const std::string& name, int steps); + void nextStage (const std::string& name, int totalRecords); - void nextRecord(); + void nextRecord (int records); void abort (const std::string& error); @@ -88,9 +90,9 @@ namespace CSVDoc void loadingStopped (CSMDoc::Document *document, bool completed, const std::string& error); - void nextStage (CSMDoc::Document *document, const std::string& name, int steps); + void nextStage (CSMDoc::Document *document, const std::string& name, int totalRecords); - void nextRecord (CSMDoc::Document *document); + void nextRecord (CSMDoc::Document *document, int records); void loadMessage (CSMDoc::Document *document, const std::string& message); }; diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 02f6a54679..f4d6fc6cb5 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -97,8 +97,8 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) &mLoader, SLOT (nextStage (CSMDoc::Document *, const std::string&, int))); connect ( - &mDocumentManager, SIGNAL (nextRecord (CSMDoc::Document *)), - &mLoader, SLOT (nextRecord (CSMDoc::Document *))); + &mDocumentManager, SIGNAL (nextRecord (CSMDoc::Document *, int)), + &mLoader, SLOT (nextRecord (CSMDoc::Document *, int))); connect ( &mDocumentManager, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&)), From 06e89d8bd30f04af52d3c0e1fdad485faa6cffb1 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 26 Jun 2014 11:43:21 +0200 Subject: [PATCH 190/226] reduced loading batch size (making the the loading progress bars a bit more responsive --- apps/opencs/model/doc/loader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index ca9c456968..712deb9dfc 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -46,7 +46,7 @@ void CSMDoc::Loader::load() bool done = false; - const int batchingSize = 100; + const int batchingSize = 50; try { From e5254ff8efe8571f31bb0ffc06526c7e611a4736 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jun 2014 14:26:53 +0200 Subject: [PATCH 191/226] Set render queue for rain --- apps/openmw/mwrender/sky.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 66a99feb74..67e301fdc4 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -429,6 +429,16 @@ void SkyManager::updateRain(float dt) Ogre::SceneNode* offsetNode = sceneNode->createChildSceneNode(Ogre::Vector3(xOffs,yOffs,startHeight)); NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(offsetNode, mRainEffect); + for (unsigned int i=0; imEntities.size(); ++i) + { + objects->mEntities[i]->setRenderQueueGroup(RQG_Alpha); + objects->mEntities[i]->setVisibilityFlags(RV_Sky); + } + for (unsigned int i=0; imParticles.size(); ++i) + { + objects->mParticles[i]->setRenderQueueGroup(RQG_Alpha); + objects->mParticles[i]->setVisibilityFlags(RV_Sky); + } mRainModels[offsetNode] = objects; } } From 869fa08a328450f5edc390fcdbb2cdec6873df36 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jun 2014 14:30:08 +0200 Subject: [PATCH 192/226] Fix disarming a trap when key is used (Fixes #1556) --- apps/openmw/mwclass/door.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 0a5b774f5f..e435511b99 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -127,7 +127,7 @@ namespace MWClass MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}"); unlock(ptr); //Call the function here. because that makes sense. // using a key disarms the trap - ptr.getCellRef().getTrap() = ""; + ptr.getCellRef().setTrap(""); } if (!needKey || hasKey) From 4aab4e1c26e3b63ff71450005c4e9ea7711ea214 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jun 2014 14:59:33 +0200 Subject: [PATCH 193/226] Ignore item condition when determining value, except in trade (Fixes #1557) --- apps/openmw/mwclass/armor.cpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 2 +- apps/openmw/mwclass/probe.cpp | 2 +- apps/openmw/mwclass/repair.cpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- apps/openmw/mwgui/tradewindow.cpp | 17 +++++++++++++++-- 6 files changed, 20 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index a7a4593826..b29bf36b2e 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -168,7 +168,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue; } void Armor::registerSelf() diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 5d91db9e23..bc68551291 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -86,7 +86,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue; } void Lockpick::registerSelf() diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 5db0d7a798..ed8625eec0 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -85,7 +85,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue; } void Probe::registerSelf() diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index f8b72be373..d7a0805349 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -76,7 +76,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue; } void Repair::registerSelf() diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 98b02b9142..66affa599e 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -154,7 +154,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue; } void Weapon::registerSelf() diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 7ef472282f..c0a51311f5 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -23,6 +23,19 @@ #include "countdialog.hpp" #include "dialogue.hpp" +namespace +{ + + int getEffectiveValue (MWWorld::Ptr item, int count) + { + int price = item.getClass().getValue(item) * count; + if (item.getClass().hasItemHealth(item)) + price *= (static_cast(item.getClass().getItemHealth(item)) / item.getClass().getItemMaxHealth(item)); + return price; + } + +} + namespace MWGui { const float TradeWindow::sBalanceChangeInitialPause = 0.5; @@ -465,7 +478,7 @@ namespace MWGui void TradeWindow::sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem) { - int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, item.getClass().getValue(item) * count, boughtItem); + int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(item, count), boughtItem); mCurrentBalance += diff; mCurrentMerchantOffer += diff; @@ -475,7 +488,7 @@ namespace MWGui void TradeWindow::buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem) { - int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, item.getClass().getValue(item) * count, !soldItem); + int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(item, count), !soldItem); mCurrentBalance -= diff; mCurrentMerchantOffer -= diff; From 44fd526c98ad2b0e9af7e885b19f5f0078554aaf Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jun 2014 15:36:19 +0200 Subject: [PATCH 194/226] Don't show disposition bar for creatures --- apps/openmw/mwgui/dialogue.cpp | 21 ++++++++++++++++++++- apps/openmw/mwgui/dialogue.hpp | 2 +- files/mygui/openmw_dialogue_window_skin.xml | 1 + 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index e7dd74eee3..a6ab1f122b 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -586,13 +586,32 @@ namespace MWGui //Clear the list of topics mTopicsList->clear(); - if (mPtr.getTypeName() == typeid(ESM::NPC).name()) + bool dispositionVisible = false; + if (mPtr.getClass().isNpc()) { + dispositionVisible = true; mDispositionBar->setProgressRange(100); mDispositionBar->setProgressPosition(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)); mDispositionText->eraseText(0, mDispositionText->getTextLength()); mDispositionText->addText("#B29154"+boost::lexical_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr))+std::string("/100")+"#B29154"); } + + bool dispositionWasVisible = mDispositionBar->getVisible(); + + if (dispositionVisible && !dispositionWasVisible) + { + mDispositionBar->setVisible(true); + float offset = mDispositionBar->getHeight()+5; + mTopicsList->setCoord(mTopicsList->getCoord() + MyGUI::IntCoord(0,offset,0,-offset)); + mTopicsList->adjustSize(); + } + else if (!dispositionVisible && dispositionWasVisible) + { + mDispositionBar->setVisible(false); + float offset = mDispositionBar->getHeight()+5; + mTopicsList->setCoord(mTopicsList->getCoord() - MyGUI::IntCoord(0,offset,0,-offset)); + mTopicsList->adjustSize(); + } } void DialogueWindow::goodbye() diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 4e0ca5dde9..516c04942c 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -170,7 +170,7 @@ namespace MWGui BookPage* mHistory; Widgets::MWList* mTopicsList; MyGUI::ScrollBar* mScrollBar; - MyGUI::ProgressPtr mDispositionBar; + MyGUI::Progress* mDispositionBar; MyGUI::EditBox* mDispositionText; PersuasionDialog mPersuasionDialog; diff --git a/files/mygui/openmw_dialogue_window_skin.xml b/files/mygui/openmw_dialogue_window_skin.xml index 4f68a90faf..5e16fd1f69 100644 --- a/files/mygui/openmw_dialogue_window_skin.xml +++ b/files/mygui/openmw_dialogue_window_skin.xml @@ -13,6 +13,7 @@ + From 865f4648b0c31a48bbe2b2a17bd08a6375542031 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jun 2014 17:15:07 +0200 Subject: [PATCH 195/226] Fix flying creatures not falling on death --- apps/openmw/mwworld/physicssystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 87ec397073..cf587c5c42 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -246,7 +246,7 @@ namespace MWWorld // Early-out for totally static creatures // (Not sure if gravity should still apply?) - if (!ptr.getClass().canWalk(ptr) && !isFlying && !ptr.getClass().canSwim(ptr)) + if (!ptr.getClass().canWalk(ptr) && !ptr.getClass().canFly(ptr) && !ptr.getClass().canSwim(ptr)) return position; /* Anything to collide with? */ From e19bbfd1b54241ce9f67df05712d248e881c65a9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jun 2014 20:49:22 +0200 Subject: [PATCH 196/226] Fix OpenCS crash when drag and drop is used with unexpected data, e.g. plain text (Fixes #1543) --- apps/opencs/view/filter/filterbox.cpp | 8 +++++-- apps/opencs/view/render/worldspacewidget.cpp | 4 +++- apps/opencs/view/world/regionmap.cpp | 5 +++- apps/opencs/view/world/scriptedit.cpp | 25 ++++++++++++++++---- apps/opencs/view/world/table.cpp | 3 +++ apps/opencs/view/world/tablesubview.cpp | 3 +++ apps/opencs/view/world/util.cpp | 3 +++ 7 files changed, 43 insertions(+), 8 deletions(-) diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index e588770b11..7a42ef0a57 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -35,7 +35,11 @@ void CSVFilter::FilterBox::setRecordFilter (const std::string& filter) void CSVFilter::FilterBox::dropEvent (QDropEvent* event) { - std::vector data = dynamic_cast (event->mimeData())->getData(); + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; + + std::vector data = mime->getData(); emit recordDropped(data, event->proposedAction()); } @@ -54,4 +58,4 @@ void CSVFilter::FilterBox::createFilterRequest (std::vector< std::pair< std::str Qt::DropAction action) { mRecordFilterBox->createFilterRequest(filterSource, action); -} \ No newline at end of file +} diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 59b82bb678..afc6b4603c 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -120,9 +120,11 @@ void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; if (mime->fromDocument (mDocument)) { emit dataDropped(mime->getData()); } //not handling drops from different documents at the moment -} \ No newline at end of file +} diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 849a1988af..9497e40544 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -382,6 +382,9 @@ void CSVWorld::RegionMap::dropEvent (QDropEvent* event) } const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; + if (mime->fromDocument(mDocument) && mime->holdsType(CSMWorld::UniversalId::Type_Region)) { CSMWorld::UniversalId record (mime->returnMatching (CSMWorld::UniversalId::Type_Region)); @@ -402,4 +405,4 @@ void CSVWorld::RegionMap::dropEvent (QDropEvent* event) mRegionId = record.getId(); } -} \ No newline at end of file +} diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index b1528d5254..23bc76000d 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -45,19 +45,36 @@ CSVWorld::ScriptEdit::ScriptEdit (QWidget* parent, const CSMDoc::Document& docum void CSVWorld::ScriptEdit::dragEnterEvent (QDragEnterEvent* event) { - setTextCursor (cursorForPosition (event->pos())); - event->acceptProposedAction(); + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) + QTextEdit::dragEnterEvent(event); + else + { + setTextCursor (cursorForPosition (event->pos())); + event->acceptProposedAction(); + } } void CSVWorld::ScriptEdit::dragMoveEvent (QDragMoveEvent* event) { - setTextCursor (cursorForPosition (event->pos())); - event->accept(); + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) + QTextEdit::dragMoveEvent(event); + else + { + setTextCursor (cursorForPosition (event->pos())); + event->accept(); + } } void CSVWorld::ScriptEdit::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + { + QTextEdit::dropEvent(event); + return; + } setTextCursor (cursorForPosition (event->pos())); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 877fd51c07..d0849d6313 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -464,6 +464,9 @@ void CSVWorld::Table::dropEvent(QDropEvent *event) } const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; + if (mime->fromDocument (mDocument)) { CSMWorld::ColumnBase::Display display = static_cast diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index a0bef366b8..327fb1c0e4 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -131,6 +131,9 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) { QDropEvent* drop = dynamic_cast(event); const CSMWorld::TableMimeData* data = dynamic_cast(drop->mimeData()); + if (!data) // May happen when non-records (e.g. plain text) are dragged and dropped + return false; + bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); if (handled) { diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index ea8a7c5415..6f3999363e 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -255,6 +255,9 @@ void CSVWorld::DropLineEdit::dragMoveEvent(QDragMoveEvent *event) void CSVWorld::DropLineEdit::dropEvent(QDropEvent *event) { const CSMWorld::TableMimeData* data(dynamic_cast(event->mimeData())); + if (!data) // May happen when non-records (e.g. plain text) are dragged and dropped + return; + emit tableMimeDataDropped(data->getData(), data->getDocumentPtr()); //WIP } From 9eb8addc706c014ccc0fb72f44d55940a8a87e76 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jun 2014 17:26:33 +0200 Subject: [PATCH 197/226] Make flying creatures fall when paralyzed (Fixes #1494) --- apps/openmw/mwmechanics/character.cpp | 3 +++ apps/openmw/mwworld/worldimp.cpp | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 5f8013b14a..5d5377669c 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1395,6 +1395,9 @@ void CharacterController::update(float duration) if (mMovementAnimVelocity == 0) world->queueMovement(mPtr, vec); } + else + // We must always queue movement, even if there is none, to apply gravity. + world->queueMovement(mPtr, Ogre::Vector3(0.0f)); movement = vec; cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = cls.getMovementSettings(mPtr).mPosition[2] = 0; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 35042b3ff4..fdac643be3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1764,6 +1764,9 @@ namespace MWWorld bool World::isFlying(const MWWorld::Ptr &ptr) const { + const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); + bool isParalyzed = (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0); + if(!ptr.getClass().isActor()) return false; @@ -1771,9 +1774,8 @@ namespace MWWorld return false; if (ptr.getClass().canFly(ptr)) - return true; + return !isParalyzed; - const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).mMagnitude > 0 && isLevitationEnabled()) return true; From 07d0f4458def1fdf5c44d6787ea3eb55494ba188 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jun 2014 19:01:49 +0200 Subject: [PATCH 198/226] Rotate clouds/particles to come from red mountain (Fixes #245) --- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwrender/sky.cpp | 22 +++++++++++++++++++--- apps/openmw/mwrender/sky.hpp | 8 ++++++++ apps/openmw/mwworld/weather.cpp | 16 ++++++++++++++-- apps/openmw/mwworld/weather.hpp | 3 +++ files/materials/clouds.shader | 22 ++++++++++++---------- files/materials/stars.shader | 21 +++++++++++---------- 7 files changed, 68 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 5d5377669c..c15cc540ff 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -608,7 +608,7 @@ void CharacterController::updateIdleStormState() { Ogre::Vector3 stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); Ogre::Vector3 characterDirection = mPtr.getRefData().getBaseNode()->getOrientation().yAxis(); - inStormDirection = stormDirection.angleBetween(characterDirection) < Ogre::Degree(40); + inStormDirection = stormDirection.angleBetween(characterDirection) > Ogre::Degree(120); } if (inStormDirection && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm")) { diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 67e301fdc4..8354cca5d7 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -219,6 +219,7 @@ SkyManager::SkyManager(Ogre::SceneNode *root, Ogre::Camera *pCamera) , mSceneMgr(NULL) , mAtmosphereDay(NULL) , mAtmosphereNight(NULL) + , mCloudNode(NULL) , mClouds() , mNextClouds() , mCloudBlendFactor(0.0f) @@ -240,10 +241,11 @@ SkyManager::SkyManager(Ogre::SceneNode *root, Ogre::Camera *pCamera) , mRainTimer(0) , mRainSpeed(0) , mRainFrequency(1) + , mStormDirection(0,-1,0) + , mIsStorm(false) { mSceneMgr = root->getCreator(); mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - mRootNode->setInheritOrientation(false); } void SkyManager::create() @@ -335,8 +337,8 @@ void SkyManager::create() mObjects.push_back(objects); // Clouds - SceneNode* clouds_node = mRootNode->createChildSceneNode(); - objects = NifOgre::Loader::createObjects(clouds_node, "meshes\\sky_clouds_01.nif"); + mCloudNode = mRootNode->createChildSceneNode(); + objects = NifOgre::Loader::createObjects(mCloudNode, "meshes\\sky_clouds_01.nif"); for(size_t i = 0;i < objects->mEntities.size();i++) { Entity* clouds_ent = objects->mEntities[i]; @@ -453,8 +455,16 @@ void SkyManager::update(float duration) { for (unsigned int i=0; imControllers.size(); ++i) mParticle->mControllers[i].update(); + + if (mIsStorm) + mParticleNode->setOrientation(Ogre::Vector3::UNIT_Y.getRotationTo(mStormDirection)); } + if (mIsStorm) + mCloudNode->setOrientation(Ogre::Vector3::UNIT_Y.getRotationTo(mStormDirection)); + else + mCloudNode->setOrientation(Ogre::Quaternion::IDENTITY); + updateRain(duration); // UV Scroll the clouds @@ -539,6 +549,7 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) mRainEnabled = !mRainEffect.empty(); mRainFrequency = weather.mRainFrequency; mRainSpeed = weather.mRainSpeed; + mIsStorm = weather.mIsStorm; if (mCurrentParticleEffect != weather.mParticleEffect) { @@ -676,6 +687,11 @@ void SkyManager::sunDisable() mSunEnabled = false; } +void SkyManager::setStormDirection(const Vector3 &direction) +{ + mStormDirection = direction; +} + void SkyManager::setSunDirection(const Vector3& direction, bool is_moon) { if (!mCreated) return; diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index f752011c0f..7c31150f3d 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -150,6 +150,8 @@ namespace MWRender void setRainSpeed(float speed); + void setStormDirection(const Ogre::Vector3& direction); + void setSunDirection(const Ogre::Vector3& direction, bool is_moon); void setMasserDirection(const Ogre::Vector3& direction); @@ -185,6 +187,8 @@ namespace MWRender bool mMoonRed; + bool mIsStorm; + float mHour; int mDay; int mMonth; @@ -203,6 +207,8 @@ namespace MWRender Ogre::SceneNode* mAtmosphereDay; Ogre::SceneNode* mAtmosphereNight; + Ogre::SceneNode* mCloudNode; + std::vector mObjects; Ogre::SceneNode* mParticleNode; @@ -211,6 +217,8 @@ namespace MWRender std::map mRainModels; float mRainTimer; + Ogre::Vector3 mStormDirection; + // remember some settings so we don't have to apply them again if they didnt change Ogre::String mClouds; Ogre::String mNextClouds; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index e2862eb87e..fb45cb0343 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -115,7 +115,8 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa mHour(14), mCurrentWeather("clear"), mNextWeather(""), mFirstUpdate(true), mWeatherUpdateTime(0), mThunderFlash(0), mThunderChance(0), mThunderChanceNeeded(50), mThunderSoundDelay(0), mRemainingTransitionTime(0), - mTimePassed(0), mFallback(fallback), mWindSpeed(0.f), mRendering(rendering), mIsStorm(false) + mTimePassed(0), mFallback(fallback), mWindSpeed(0.f), mRendering(rendering), mIsStorm(false), + mStormDirection(0,1,0) { //Globals mThunderSoundID0 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_0"); @@ -389,6 +390,17 @@ void WeatherManager::update(float duration) mWindSpeed = mResult.mWindSpeed; mIsStorm = mResult.mIsStorm; + if (mIsStorm) + { + MWWorld::Ptr player = world->getPlayerPtr(); + Ogre::Vector3 playerPos (player.getRefData().getPosition().pos); + Ogre::Vector3 redMountainPos (19950, 72032, 27831); + + mStormDirection = (playerPos - redMountainPos); + mStormDirection.z = 0; + mRendering->getSkyManager()->setStormDirection(mStormDirection); + } + mRendering->configureFog(mResult.mFogDepth, mResult.mFogColor); // disable sun during night @@ -812,5 +824,5 @@ bool WeatherManager::isInStorm() const Ogre::Vector3 WeatherManager::getStormDirection() const { - return Ogre::Vector3(0,-1,0); + return mStormDirection; } diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 9693014a91..3cc58aa4c5 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace ESM { @@ -202,6 +203,8 @@ namespace MWWorld float mHour; float mWindSpeed; bool mIsStorm; + Ogre::Vector3 mStormDirection; + MWWorld::Fallback* mFallback; void setFallbackWeather(Weather& weather,const std::string& name); MWRender::RenderingManager* mRendering; diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader index b76f2ded72..d3e5f083c7 100644 --- a/files/materials/clouds.shader +++ b/files/materials/clouds.shader @@ -3,25 +3,27 @@ #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM - shUniform(float4x4, view) @shAutoConstant(view, view_matrix) -shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix) + shUniform(float4x4, worldview) @shAutoConstant(worldview, worldview_matrix) + shUniform(float4x4, proj) @shAutoConstant(proj, projection_matrix) shVertexInput(float2, uv0) shOutput(float2, UV) shOutput(float, alphaFade) SH_START_PROGRAM { - float4x4 viewFixed = view; + float4x4 worldviewFixed = worldview; + #if !SH_GLSL - viewFixed[0][3] = 0; - viewFixed[1][3] = 0; - viewFixed[2][3] = 0; + worldviewFixed[0][3] = 0; + worldviewFixed[1][3] = 0; + worldviewFixed[2][3] = 0; #else - viewFixed[3][0] = 0; - viewFixed[3][1] = 0; - viewFixed[3][2] = 0; + worldviewFixed[3][0] = 0; + worldviewFixed[3][1] = 0; + worldviewFixed[3][2] = 0; #endif - shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shInputPosition)); + + shOutputPosition = shMatrixMult(proj, shMatrixMult(worldviewFixed, shInputPosition)); UV = uv0; alphaFade = (shInputPosition.z <= 200.f) ? ((shInputPosition.z <= 100.f) ? 0.0 : 0.25) : 1.0; } diff --git a/files/materials/stars.shader b/files/materials/stars.shader index 33f22bd14b..b84d371358 100644 --- a/files/materials/stars.shader +++ b/files/materials/stars.shader @@ -3,8 +3,8 @@ #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM - shUniform(float4x4, view) @shAutoConstant(view, view_matrix) -shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix) + shUniform(float4x4, worldview) @shAutoConstant(worldview, worldview_matrix) + shUniform(float4x4, proj) @shAutoConstant(proj, projection_matrix) shVertexInput(float2, uv0) shOutput(float2, UV) @@ -12,17 +12,18 @@ shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix) SH_START_PROGRAM { - float4x4 viewFixed = view; + float4x4 worldviewFixed = worldview; #if !SH_GLSL - viewFixed[0][3] = 0; - viewFixed[1][3] = 0; - viewFixed[2][3] = 0; + worldviewFixed[0][3] = 0; + worldviewFixed[1][3] = 0; + worldviewFixed[2][3] = 0; #else - viewFixed[3][0] = 0; - viewFixed[3][1] = 0; - viewFixed[3][2] = 0; + worldviewFixed[3][0] = 0; + worldviewFixed[3][1] = 0; + worldviewFixed[3][2] = 0; #endif - shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shInputPosition)); + + shOutputPosition = shMatrixMult(proj, shMatrixMult(worldviewFixed, shInputPosition)); UV = uv0; fade = (shInputPosition.z > 50) ? 1 : 0; From 8b340ddd5e734852b6855bcda0fd09d9f560a0d9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Jun 2014 20:26:46 +0200 Subject: [PATCH 199/226] Implement slower movement against storms (Closes #41) --- apps/openmw/mwworld/physicssystem.cpp | 15 +++++++++++---- apps/openmw/mwworld/weather.hpp | 6 +++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index cf587c5c42..9984633cd6 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -315,15 +315,22 @@ namespace MWWorld wasOnGround = physicActor->getOnGround(); // store current state tracer.doTrace(colobj, position, position - Ogre::Vector3(0,0,2), engine); // check if down 2 possible if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) + { isOnGround = true; + // if we're on the ground, don't try to fall any more + velocity.z = std::max(0.0f, velocity.z); + } } } - // NOTE: isOnGround was initialised false, so should stay false if falling or sliding horizontally - if(isOnGround) + // Now that we have the effective movement vector, apply wind forces to it + if (MWBase::Environment::get().getWorld()->isInStorm()) { - // if we're on the ground, don't try to fall any more - velocity.z = std::max(0.0f, velocity.z); // NOTE: two different velocity assignments above + Ogre::Vector3 stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); + Ogre::Degree angle = stormDirection.angleBetween(velocity); + static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get() + .find("fStromWalkMult")->getFloat(); + velocity *= 1.f-(fStromWalkMult * (angle.valueDegrees()/180.f)); } Ogre::Vector3 newPosition = position; diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 3cc58aa4c5..292d06747f 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -128,10 +128,10 @@ namespace MWWorld // Rain sound effect std::string mRainLoopSoundID; - // Is this an ash storm / blight storm? This controls two things: - // - The particle node will be oriented so that the particles appear to come from the Red Mountain. (not implemented yet) + // Is this an ash storm / blight storm? If so, the following will happen: + // - The particles and clouds will be oriented so they appear to come from the Red Mountain. // - Characters will animate their hand to protect eyes from the storm when looking in its direction (idlestorm animation) - // Possible effect on movement speed? + // - Slower movement when walking against the storm (fStromWalkMult) bool mIsStorm; // How fast does rain travel down? From 3100206b28811b10e61873eba71352795fa261d3 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Jun 2014 08:37:41 +0200 Subject: [PATCH 200/226] replaced an assert with an exception --- apps/openmw/mwscript/miscextensions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 17824fe491..76b99d04ab 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -876,8 +876,8 @@ namespace MWScript else { std::vector contentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); - assert (contentFiles.size() > ptr.getCellRef().getRefNum().mContentFile); - msg << contentFiles[ptr.getCellRef().getRefNum().mContentFile] << std::endl; + + msg << contentFiles.at (ptr.getCellRef().getRefNum().mContentFile) << std::endl; } msg << "RefID: " << ptr.getCellRef().getRefId() << std::endl; From 0c1ad54e68ef7951505c477e5dee4865bac0ef25 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Jun 2014 09:12:26 +0200 Subject: [PATCH 201/226] refactored object rendering into a separate class --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/object.cpp | 217 ++++++++++++++++++++++ apps/opencs/view/render/object.hpp | 80 ++++++++ apps/opencs/view/render/previewwidget.cpp | 151 +++------------ apps/opencs/view/render/previewwidget.hpp | 20 +- apps/opencs/view/world/previewsubview.cpp | 4 +- 6 files changed, 329 insertions(+), 145 deletions(-) create mode 100644 apps/opencs/view/render/object.cpp create mode 100644 apps/opencs/view/render/object.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index ddaca7e718..a8cc4ceaea 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -70,7 +70,7 @@ opencs_units (view/render opencs_units_noqt (view/render navigation navigation1st navigationfree navigationorbit lighting lightingday lightingnight - lightingbright + lightingbright object ) opencs_units_noqt (view/world diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp new file mode 100644 index 0000000000..6fa7e77072 --- /dev/null +++ b/apps/opencs/view/render/object.cpp @@ -0,0 +1,217 @@ + +#include "object.hpp" + +#include +#include +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/ref.hpp" +#include "../../model/world/refidcollection.hpp" + +void CSVRender::Object::clearSceneNode (Ogre::SceneNode *node) +{ + for (Ogre::SceneNode::ObjectIterator iter = node->getAttachedObjectIterator(); + iter.hasMoreElements(); ) + { + Ogre::MovableObject* object = dynamic_cast (iter.getNext()); + node->getCreator()->destroyMovableObject (object); + } + + for (Ogre::SceneNode::ChildNodeIterator iter = node->getChildIterator(); + iter.hasMoreElements(); ) + { + Ogre::SceneNode* childNode = dynamic_cast (iter.getNext()); + clearSceneNode (childNode); + node->getCreator()->destroySceneNode (childNode); + } +} + +void CSVRender::Object::clear() +{ + mObject.setNull(); + + clearSceneNode (mBase); +} + +void CSVRender::Object::update() +{ + clear(); + + std::string model; + int error = 0; // 1 referemceanöe does not exist, 2 referenceable does not specify a mesh + + const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); + + int index = referenceables.searchId (mReferenceableId); + + if (index==-1) + error = 1; + else + { + // xxx check for Deleted state (error 1) + + model = referenceables.getData (index, + referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model)). + toString().toUtf8().constData(); + + if (model.empty()) + error = 2; + } + + if (error) + { + Ogre::Entity* entity = mBase->getCreator()->createEntity (Ogre::SceneManager::PT_CUBE); + entity->setMaterialName("BaseWhite"); /// \todo adjust material according to error + + mBase->attachObject (entity); + } + else + { + mObject = NifOgre::Loader::createObjects (mBase, "Meshes\\" + model); + } +} + +void CSVRender::Object::adjust() +{ + if (mReferenceId.empty()) + return; + + const CSMWorld::CellRef& reference = getReference(); + + // position + if (!mForceBaseToZero) + { + Ogre::Vector3 (reference.mPos.pos[0], reference.mPos.pos[1], reference.mPos.pos[2]); + + mBase->setPosition (Ogre::Vector3 ( + reference.mPos.pos[0], reference.mPos.pos[1], reference.mPos.pos[2])); + } + + // orientation + Ogre::Quaternion xr (Ogre::Radian (-reference.mPos.pos[0]), Ogre::Vector3::UNIT_X); + + Ogre::Quaternion yr (Ogre::Radian (-reference.mPos.pos[1]), Ogre::Vector3::UNIT_Y); + + Ogre::Quaternion zr (Ogre::Radian (-reference.mPos.pos[2]), Ogre::Vector3::UNIT_Z); + + mBase->setOrientation (xr*yr*zr); + + // scale + mBase->setScale (reference.mScale, reference.mScale, reference.mScale); +} + +const CSMWorld::CellRef& CSVRender::Object::getReference() const +{ + if (mReferenceId.empty()) + throw std::logic_error ("object does not represent a reference"); + + return mData.getReferences().getRecord (mReferenceId).get(); +} + +CSVRender::Object::Object (const CSMWorld::Data& data, Ogre::SceneNode *cellNode, + const std::string& id, bool referenceable, bool forceBaseToZero) +: mData (data), mBase (0), mForceBaseToZero (forceBaseToZero) +{ + mBase = cellNode->createChildSceneNode(); + + if (referenceable) + { + mReferenceableId = id; + } + else + { + mReferenceId = id; + mReferenceableId = getReference().mRefID; + } + + update(); + adjust(); +} + +CSVRender::Object::~Object() +{ + clear(); + + if (mBase) + mBase->getCreator()->destroySceneNode (mBase); +} + +bool CSVRender::Object::ReferenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); + + int index = referenceables.searchId (mReferenceableId); + + if (index!=-1 && index>=topLeft.row() && index<=bottomRight.row()) + { + update(); + adjust(); + return true; + } + + return false; +} + +bool CSVRender::Object::ReferenceableAboutToBeRemoved (const QModelIndex& parent, int start, + int end) +{ + const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); + + int index = referenceables.searchId (mReferenceableId); + + if (index!=-1 && index>=start && index<=end) + { + // Deletion of referenceable-type objects is handled outside of Object. + if (!mReferenceId.empty()) + { + update(); + adjust(); + return true; + } + } + + return false; +} + +bool CSVRender::Object::ReferenceDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + if (mReferenceId.empty()) + return false; + + const CSMWorld::RefCollection& references = mData.getReferences(); + + int index = references.searchId (mReferenceId); + + if (index!=-1 && index>=topLeft.row() && index<=bottomRight.row()) + { + int columnIndex = + references.findColumnIndex (CSMWorld::Columns::ColumnId_ReferenceableId); + + if (columnIndex>=topLeft.column() && columnIndex<=bottomRight.row()) + { + mReferenceableId = + references.getData (index, columnIndex).toString().toUtf8().constData(); + + update(); + } + + adjust(); + + return true; + } + + return false; +} + +std::string CSVRender::Object::getReferenceId() const +{ + return mReferenceId; +} + +std::string CSVRender::Object::getReferenceableId() const +{ + return mReferenceableId; +} \ No newline at end of file diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp new file mode 100644 index 0000000000..d4089307f4 --- /dev/null +++ b/apps/opencs/view/render/object.hpp @@ -0,0 +1,80 @@ +#ifndef OPENCS_VIEW_OBJECT_H +#define OPENCS_VIEW_OBJECT_H + +#include + +class QModelIndex; + +namespace Ogre +{ + class SceneNode; +} + +namespace CSMWorld +{ + class Data; + class CellRef; +} + +namespace CSVRender +{ + class Object + { + const CSMWorld::Data& mData; + std::string mReferenceId; + std::string mReferenceableId; + Ogre::SceneNode *mBase; + NifOgre::ObjectScenePtr mObject; + bool mForceBaseToZero; + + /// Not implemented + Object (const Object&); + + /// Not implemented + Object& operator= (const Object&); + + /// Destroy all scene nodes and movable objects attached to node. + static void clearSceneNode (Ogre::SceneNode *node); + + /// Remove object from node (includes deleting) + void clear(); + + /// Update model + void update(); + + /// Adjust position, orientation and scale + void adjust(); + + /// Throws an exception if *this was constructed with referenceable + const CSMWorld::CellRef& getReference() const; + + public: + + Object (const CSMWorld::Data& data, Ogre::SceneNode *cellNode, + const std::string& id, bool referenceable, bool forceBaseToZero = false); + /// \param forceBaseToZero If this is a reference ignore the coordinates and place + /// it at 0, 0, 0 instead. + + ~Object(); + + /// \return Did this call result in a modification of the visual representation of + /// this object? + bool ReferenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight); + + /// \return Did this call result in a modification of the visual representation of + /// this object? + bool ReferenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + /// \return Did this call result in a modification of the visual representation of + /// this object? + bool ReferenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + /// Returns an empty string if this is a refereceable-type object. + std::string getReferenceId() const; + + std::string getReferenceableId() const; + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index 99e57ea11c..ccc6e4acf8 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -7,15 +7,13 @@ #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" -void CSVRender::PreviewWidget::setup() +CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, + const std::string& id, bool referenceable, QWidget *parent) +: SceneWidget (parent), mData (data), + mObject (data, getSceneManager()->getRootSceneNode(), id, referenceable, true) { setNavigation (&mOrbit); - mNode = getSceneManager()->getRootSceneNode()->createChildSceneNode(); - mNode->setPosition (Ogre::Vector3 (0, 0, 0)); - - setModel(); - QAbstractItemModel *referenceables = mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables); @@ -23,142 +21,57 @@ void CSVRender::PreviewWidget::setup() this, SLOT (ReferenceableDataChanged (const QModelIndex&, const QModelIndex&))); connect (referenceables, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (ReferenceableAboutToBeRemoved (const QModelIndex&, int, int))); -} -void CSVRender::PreviewWidget::setModel() -{ - if (mNode) + if (!referenceable) { - mObject.setNull(); + QAbstractItemModel *references = + mData.getTableModel (CSMWorld::UniversalId::Type_References); - if (mReferenceableId.empty()) - return; - - int column = - mData.getReferenceables().findColumnIndex (CSMWorld::Columns::ColumnId_Model); - - int row = mData.getReferenceables().searchId (mReferenceableId); - - if (row==-1) - return; - - QVariant value = mData.getReferenceables().getData (row, column); - - if (!value.isValid()) - return; - - std::string model = value.toString().toUtf8().constData(); - - if (model.empty()) - return; - - mObject = NifOgre::Loader::createObjects (mNode, "Meshes\\" + model); + connect (references, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (ReferenceDataChanged (const QModelIndex&, const QModelIndex&))); + connect (references, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (ReferenceAboutToBeRemoved (const QModelIndex&, int, int))); } } -void CSVRender::PreviewWidget::adjust() -{ - if (mNode) - { - int row = mData.getReferences().getIndex (mReferenceId); - - float scale = mData.getReferences().getData (row, mData.getReferences(). - findColumnIndex (CSMWorld::Columns::ColumnId_Scale)).toFloat(); - float rotX = mData.getReferences().getData (row, mData.getReferences(). - findColumnIndex (CSMWorld::Columns::ColumnId_PositionXRot)).toFloat(); - float rotY = mData.getReferences().getData (row, mData.getReferences(). - findColumnIndex (CSMWorld::Columns::ColumnId_PositionYRot)).toFloat(); - float rotZ = mData.getReferences().getData (row, mData.getReferences(). - findColumnIndex (CSMWorld::Columns::ColumnId_PositionZRot)).toFloat(); - - mNode->setScale (scale, scale, scale); - - Ogre::Quaternion xr (Ogre::Radian(-rotX), Ogre::Vector3::UNIT_X); - - Ogre::Quaternion yr (Ogre::Radian(-rotY), Ogre::Vector3::UNIT_Y); - - Ogre::Quaternion zr (Ogre::Radian(-rotZ), Ogre::Vector3::UNIT_Z); - - mNode->setOrientation (xr*yr*zr); - } -} - -CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, - const std::string& referenceableId, QWidget *parent) -: SceneWidget (parent), mData (data), mNode (0), mReferenceableId (referenceableId) -{ - setup(); -} - -CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, - const std::string& referenceableId, const std::string& referenceId, QWidget *parent) -: SceneWidget (parent), mData (data), mReferenceableId (referenceableId), - mReferenceId (referenceId) -{ - setup(); - - adjust(); - - QAbstractItemModel *references = - mData.getTableModel (CSMWorld::UniversalId::Type_References); - - connect (references, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (ReferenceDataChanged (const QModelIndex&, const QModelIndex&))); - connect (references, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (ReferenceAboutToBeRemoved (const QModelIndex&, int, int))); -} - void CSVRender::PreviewWidget::ReferenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - if (mReferenceableId.empty()) - return; - - CSMWorld::IdTable& referenceables = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables)); - - QModelIndex index = referenceables.getModelIndex (mReferenceableId, 0); - - if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) - { - /// \todo possible optimisation; check columns and only update if relevant columns have - /// changed - setModel(); + if (mObject.ReferenceableDataChanged (topLeft, bottomRight)) flagAsModified(); - } } void CSVRender::PreviewWidget::ReferenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) { - if (mReferenceableId.empty()) + if (mObject.ReferenceableAboutToBeRemoved (parent, start, end)) + flagAsModified(); + + if (mObject.getReferenceableId().empty()) return; CSMWorld::IdTable& referenceables = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables)); - QModelIndex index = referenceables.getModelIndex (mReferenceableId, 0); + QModelIndex index = referenceables.getModelIndex (mObject.getReferenceableId(), 0); if (index.row()>=start && index.row()<=end) { - if (mReferenceId.empty()) + if (mObject.getReferenceId().empty()) { // this is a preview for a referenceble emit closeRequest(); } - else - { - // this is a preview for a reference - mObject.setNull(); - flagAsModified(); - } } } void CSVRender::PreviewWidget::ReferenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - if (mReferenceId.empty()) + if (mObject.ReferenceDataChanged (topLeft, bottomRight)) + flagAsModified(); + + if (mObject.getReferenceId().empty()) return; CSMWorld::IdTable& references = dynamic_cast ( @@ -166,35 +79,23 @@ void CSVRender::PreviewWidget::ReferenceDataChanged (const QModelIndex& topLeft, int columnIndex = references.findColumnIndex (CSMWorld::Columns::ColumnId_ReferenceableId); - QModelIndex index = references.getModelIndex (mReferenceId, columnIndex); + QModelIndex index = references.getModelIndex (mObject.getReferenceId(), columnIndex); if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) - { - /// \todo possible optimisation; check columns and only update if relevant columns have - /// changed - adjust(); - if (index.column()>=topLeft.column() && index.column()<=bottomRight.row()) - { - mReferenceableId = references.data (index).toString().toUtf8().constData(); - emit referenceableIdChanged (mReferenceableId); - setModel(); - } - - flagAsModified(); - } + emit referenceableIdChanged (mObject.getReferenceableId()); } void CSVRender::PreviewWidget::ReferenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) { - if (mReferenceId.empty()) + if (mObject.getReferenceId().empty()) return; CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); - QModelIndex index = references.getModelIndex (mReferenceId, 0); + QModelIndex index = references.getModelIndex (mObject.getReferenceId(), 0); if (index.row()>=start && index.row()<=end) emit closeRequest(); diff --git a/apps/opencs/view/render/previewwidget.hpp b/apps/opencs/view/render/previewwidget.hpp index 7a63d8fb12..09cb19dc7a 100644 --- a/apps/opencs/view/render/previewwidget.hpp +++ b/apps/opencs/view/render/previewwidget.hpp @@ -1,11 +1,10 @@ #ifndef OPENCS_VIEW_PREVIEWWIDGET_H #define OPENCS_VIEW_PREVIEWWIDGET_H -#include - #include "scenewidget.hpp" #include "navigationorbit.hpp" +#include "object.hpp" class QModelIndex; @@ -22,26 +21,13 @@ namespace CSVRender CSMWorld::Data& mData; CSVRender::NavigationOrbit mOrbit; - NifOgre::ObjectScenePtr mObject; - Ogre::SceneNode *mNode; - std::string mReferenceId; - std::string mReferenceableId; - - void setup(); - - void setModel(); - - void adjust(); - ///< Adjust referenceable preview according to the reference + Object mObject; public: - PreviewWidget (CSMWorld::Data& data, const std::string& referenceableId, + PreviewWidget (CSMWorld::Data& data, const std::string& id, bool referenceable, QWidget *parent = 0); - PreviewWidget (CSMWorld::Data& data, const std::string& referenceableId, - const std::string& referenceId, QWidget *parent = 0); - signals: void closeRequest(); diff --git a/apps/opencs/view/world/previewsubview.cpp b/apps/opencs/view/world/previewsubview.cpp index e9a30e65d2..49e6d93618 100644 --- a/apps/opencs/view/world/previewsubview.cpp +++ b/apps/opencs/view/world/previewsubview.cpp @@ -23,10 +23,10 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo referenceableIdChanged (referenceableId); mScene = - new CSVRender::PreviewWidget (document.getData(), referenceableId, id.getId(), this); + new CSVRender::PreviewWidget (document.getData(), id.getId(), false, this); } else - mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), this); + mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), true, this); SceneToolbar *toolbar = new SceneToolbar (48+6, this); From 36c1170d09e9d68d64ff2e04a098d004c9b0e150 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Jun 2014 10:35:00 +0200 Subject: [PATCH 202/226] fixed some function names --- apps/opencs/view/render/object.cpp | 6 +++--- apps/opencs/view/render/object.hpp | 6 +++--- apps/opencs/view/render/previewwidget.cpp | 22 +++++++++++----------- apps/opencs/view/render/previewwidget.hpp | 8 ++++---- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 6fa7e77072..59cfd965de 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -137,7 +137,7 @@ CSVRender::Object::~Object() mBase->getCreator()->destroySceneNode (mBase); } -bool CSVRender::Object::ReferenceableDataChanged (const QModelIndex& topLeft, +bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); @@ -154,7 +154,7 @@ bool CSVRender::Object::ReferenceableDataChanged (const QModelIndex& topLeft, return false; } -bool CSVRender::Object::ReferenceableAboutToBeRemoved (const QModelIndex& parent, int start, +bool CSVRender::Object::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) { const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); @@ -175,7 +175,7 @@ bool CSVRender::Object::ReferenceableAboutToBeRemoved (const QModelIndex& parent return false; } -bool CSVRender::Object::ReferenceDataChanged (const QModelIndex& topLeft, +bool CSVRender::Object::referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (mReferenceId.empty()) diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index d4089307f4..df39d7393d 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -59,16 +59,16 @@ namespace CSVRender /// \return Did this call result in a modification of the visual representation of /// this object? - bool ReferenceableDataChanged (const QModelIndex& topLeft, + bool referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); /// \return Did this call result in a modification of the visual representation of /// this object? - bool ReferenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + bool referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); /// \return Did this call result in a modification of the visual representation of /// this object? - bool ReferenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + bool referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); /// Returns an empty string if this is a refereceable-type object. std::string getReferenceId() const; diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index ccc6e4acf8..76e9971dd9 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -18,9 +18,9 @@ CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables); connect (referenceables, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (ReferenceableDataChanged (const QModelIndex&, const QModelIndex&))); + this, SLOT (referenceableDataChanged (const QModelIndex&, const QModelIndex&))); connect (referenceables, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (ReferenceableAboutToBeRemoved (const QModelIndex&, int, int))); + this, SLOT (referenceableAboutToBeRemoved (const QModelIndex&, int, int))); if (!referenceable) { @@ -28,23 +28,23 @@ CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, mData.getTableModel (CSMWorld::UniversalId::Type_References); connect (references, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (ReferenceDataChanged (const QModelIndex&, const QModelIndex&))); + this, SLOT (referenceDataChanged (const QModelIndex&, const QModelIndex&))); connect (references, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (ReferenceAboutToBeRemoved (const QModelIndex&, int, int))); + this, SLOT (referenceAboutToBeRemoved (const QModelIndex&, int, int))); } } -void CSVRender::PreviewWidget::ReferenceableDataChanged (const QModelIndex& topLeft, +void CSVRender::PreviewWidget::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - if (mObject.ReferenceableDataChanged (topLeft, bottomRight)) + if (mObject.referenceableDataChanged (topLeft, bottomRight)) flagAsModified(); } -void CSVRender::PreviewWidget::ReferenceableAboutToBeRemoved (const QModelIndex& parent, int start, +void CSVRender::PreviewWidget::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) { - if (mObject.ReferenceableAboutToBeRemoved (parent, start, end)) + if (mObject.referenceableAboutToBeRemoved (parent, start, end)) flagAsModified(); if (mObject.getReferenceableId().empty()) @@ -65,10 +65,10 @@ void CSVRender::PreviewWidget::ReferenceableAboutToBeRemoved (const QModelIndex& } } -void CSVRender::PreviewWidget::ReferenceDataChanged (const QModelIndex& topLeft, +void CSVRender::PreviewWidget::referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - if (mObject.ReferenceDataChanged (topLeft, bottomRight)) + if (mObject.referenceDataChanged (topLeft, bottomRight)) flagAsModified(); if (mObject.getReferenceId().empty()) @@ -86,7 +86,7 @@ void CSVRender::PreviewWidget::ReferenceDataChanged (const QModelIndex& topLeft, emit referenceableIdChanged (mObject.getReferenceableId()); } -void CSVRender::PreviewWidget::ReferenceAboutToBeRemoved (const QModelIndex& parent, int start, +void CSVRender::PreviewWidget::referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) { if (mObject.getReferenceId().empty()) diff --git a/apps/opencs/view/render/previewwidget.hpp b/apps/opencs/view/render/previewwidget.hpp index 09cb19dc7a..dd6a99c0f9 100644 --- a/apps/opencs/view/render/previewwidget.hpp +++ b/apps/opencs/view/render/previewwidget.hpp @@ -36,14 +36,14 @@ namespace CSVRender private slots: - void ReferenceableDataChanged (const QModelIndex& topLeft, + void referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - void ReferenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); - void ReferenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - void ReferenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); }; } From a2f514f024856abd524465927611a1ea6599e006 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Jun 2014 11:38:34 +0200 Subject: [PATCH 203/226] disabled preview function for deleted records --- apps/opencs/view/world/table.cpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 877fd51c07..0605e87ff1 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -115,12 +115,12 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (selectedRows.size()==1) { + int row = selectedRows.begin()->row(); + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + if (mModel->getFeatures() & CSMWorld::IdTable::Feature_View) { - int row = selectedRows.begin()->row(); - - row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); - CSMWorld::UniversalId id = mModel->view (row).first; int index = mDocument.getData().getCells().searchId (id.getId()); @@ -132,7 +132,16 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) } if (mModel->getFeatures() & CSMWorld::IdTable::Feature_Preview) - menu.addAction (mPreviewAction); + { + QModelIndex index = mModel->index (row, + mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); + + CSMWorld::RecordBase::State state = static_cast ( + mModel->data (index).toInt()); + + if (state!=CSMWorld::RecordBase::State_Deleted) + menu.addAction (mPreviewAction); + } } menu.exec (event->globalPos()); @@ -377,7 +386,12 @@ void CSVWorld::Table::previewRecord() { std::string id = getUniversalId (selectedRows.begin()->row()).getId(); - emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Preview, id) , ""); + QModelIndex index = mModel->getModelIndex (id, + mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); + + if (mModel->data (index)!=CSMWorld::RecordBase::State_Deleted) + emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Preview, id), + ""); } } From 6020edf60f7408f214d415ca66a310b0055cf521 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Jun 2014 11:55:02 +0200 Subject: [PATCH 204/226] make preview watch for changes in modification state of relevant records --- apps/opencs/view/render/previewwidget.cpp | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index 76e9971dd9..75b4e93967 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -39,6 +39,18 @@ void CSVRender::PreviewWidget::referenceableDataChanged (const QModelIndex& topL { if (mObject.referenceableDataChanged (topLeft, bottomRight)) flagAsModified(); + + if (mObject.getReferenceId().empty()) + { + CSMWorld::IdTable& referenceables = dynamic_cast ( + *mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables)); + + QModelIndex index = referenceables.getModelIndex (mObject.getReferenceableId(), + referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); + + if (referenceables.data (index).toInt()==CSMWorld::RecordBase::State_Deleted) + emit closeRequest(); + } } void CSVRender::PreviewWidget::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, @@ -77,6 +89,18 @@ void CSVRender::PreviewWidget::referenceDataChanged (const QModelIndex& topLeft, CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + // check for deleted state + { + QModelIndex index = references.getModelIndex (mObject.getReferenceId(), + references.findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); + + if (references.data (index).toInt()==CSMWorld::RecordBase::State_Deleted) + { + emit closeRequest(); + return; + } + } + int columnIndex = references.findColumnIndex (CSMWorld::Columns::ColumnId_ReferenceableId); QModelIndex index = references.getModelIndex (mObject.getReferenceId(), columnIndex); From 3541d0380957802e714d9de5815e34e754c0d3ef Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Jun 2014 12:29:23 +0200 Subject: [PATCH 205/226] fixed deleting of referenceables (modification state column was incorrectly flagged as non-editable) --- apps/opencs/model/world/refidcollection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index cb2027880c..46a22edede 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -44,7 +44,7 @@ CSMWorld::RefIdCollection::RefIdCollection() ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false)); baseColumns.mId = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_Modification, ColumnBase::Display_RecordState, - ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false)); + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true, false)); baseColumns.mModified = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_RecordType, ColumnBase::Display_RefRecordType, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false)); From 6a900e0aad2575f76a5858043741e5273c6e3eae Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 27 Jun 2014 03:11:37 +0200 Subject: [PATCH 206/226] Update weapon and shield controllers for creatures --- apps/openmw/mwrender/creatureanimation.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 0eb883953e..f2447cb702 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -184,6 +184,18 @@ Ogre::Vector3 CreatureWeaponAnimation::runAnimation(float duration) { Ogre::Vector3 ret = Animation::runAnimation(duration); pitchSkeleton(mPtr.getRefData().getPosition().rot[0], mSkelBase->getSkeleton()); + + if (!mWeapon.isNull()) + { + for (unsigned int i=0; imControllers.size(); ++i) + mWeapon->mControllers[i].update(); + } + if (!mShield.isNull()) + { + for (unsigned int i=0; imControllers.size(); ++i) + mShield->mControllers[i].update(); + } + return ret; } From 37c85f0af41fedd2642be8c9d1b713f76c67d086 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 27 Jun 2014 03:12:07 +0200 Subject: [PATCH 207/226] Don't update object root controllers with no time source (Fixes #1564) --- apps/openmw/mwrender/animation.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 8bf2160e3b..57a2ac1ec9 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -144,12 +144,6 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) } else mAttachedObjects.clear(); - - for(size_t i = 0;i < mObjectRoot->mControllers.size();i++) - { - if(mObjectRoot->mControllers[i].getSource().isNull()) - mObjectRoot->mControllers[i].setSource(mAnimationTimePtr[0]); - } } struct AddGlow @@ -309,6 +303,12 @@ void Animation::addAnimSource(const std::string &model) ctrls[i].setSource(mAnimationTimePtr[grp]); grpctrls[grp].push_back(ctrls[i]); } + + for (unsigned int i = 0; i < mObjectRoot->mControllers.size(); ++i) + { + if (mObjectRoot->mControllers[i].getSource().isNull()) + mObjectRoot->mControllers[i].setSource(mAnimationTimePtr[0]); + } } void Animation::clearAnimSources() @@ -1039,7 +1039,10 @@ Ogre::Vector3 Animation::runAnimation(float duration) } for(size_t i = 0;i < mObjectRoot->mControllers.size();i++) - mObjectRoot->mControllers[i].update(); + { + if(!mObjectRoot->mControllers[i].getSource().isNull()) + mObjectRoot->mControllers[i].update(); + } // Apply group controllers for(size_t grp = 0;grp < sNumGroups;grp++) From 7f26843dc3f651845d47fdfeb02f7a320f7baae1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 27 Jun 2014 18:18:58 +0200 Subject: [PATCH 208/226] Reset key focus when the key focus widget is hidden (Fixes #1568) --- apps/openmw/mwgui/windowbase.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index cc18e6694e..4af0afc1f6 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -21,6 +21,17 @@ void WindowBase::setVisible(bool visible) open(); else if (wasVisible && !visible) close(); + + // This is needed as invisible widgets can retain key focus. + if (!visible) + { + MyGUI::Widget* keyFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + while (keyFocus != mMainWidget && keyFocus != NULL) + keyFocus = keyFocus->getParent(); + + if (keyFocus == mMainWidget) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); + } } bool WindowBase::isVisible() From 2451eead8afacd74f70070869976782cfcb7b4a6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 27 Jun 2014 20:07:19 +0200 Subject: [PATCH 209/226] Some additional wrapping for faction rank instructions --- apps/openmw/mwscript/statsextensions.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 1d3a8bc4b2..5c47dea0f2 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -623,7 +623,8 @@ namespace MWScript MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) != player.getClass().getNpcStats(player).getFactionRanks().end()) { - player.getClass().getNpcStats(player).getFactionRanks()[factionID] = player.getClass().getNpcStats(player).getFactionRanks()[factionID] -1; + player.getClass().getNpcStats(player).getFactionRanks()[factionID] = + std::max(0, player.getClass().getNpcStats(player).getFactionRanks()[factionID]-1); } } } @@ -1031,7 +1032,7 @@ namespace MWScript return; std::map& ranks = ptr.getClass().getNpcStats(ptr).getFactionRanks (); - ranks[factionID] = ranks[factionID]+1; + ranks[factionID] = std::min(9, ranks[factionID]+1); } }; @@ -1058,7 +1059,7 @@ namespace MWScript return; std::map& ranks = ptr.getClass().getNpcStats(ptr).getFactionRanks (); - ranks[factionID] = ranks[factionID]-1; + ranks[factionID] = std::max(0, ranks[factionID]-1); } }; From 78d02d97da1c0aa67c8f3b85963d8ccba7de7e62 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 28 Jun 2014 00:06:08 +0200 Subject: [PATCH 210/226] Find text keys in reverse (Bug #1578) --- apps/openmw/mwrender/animation.cpp | 55 +++++++++++++++++++----------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 57a2ac1ec9..62303fd422 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -467,16 +467,17 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node const std::string stop = groupname+": stop"; float starttime = std::numeric_limits::max(); float stoptime = 0.0f; - NifOgre::TextKeyMap::const_iterator keyiter(keys.begin()); - while(keyiter != keys.end()) + // Have to find keys in reverse (see reset method) + NifOgre::TextKeyMap::const_reverse_iterator keyiter(keys.rbegin()); + while(keyiter != keys.rend()) { if(keyiter->second == start || keyiter->second == loopstart) - starttime = keyiter->first; - else if(keyiter->second == loopstop || keyiter->second == stop) { - stoptime = keyiter->first; + starttime = keyiter->first; break; } + else if(keyiter->second == loopstop || keyiter->second == stop) + stoptime = keyiter->first; ++keyiter; } @@ -592,31 +593,39 @@ void Animation::updatePosition(float oldtime, float newtime, Ogre::Vector3 &posi bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint) { - const NifOgre::TextKeyMap::const_iterator groupstart = findGroupStart(keys, groupname); + // Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two + // separate walkforward keys, and the last one is supposed to be used. + NifOgre::TextKeyMap::const_reverse_iterator groupend(keys.rbegin()); + for(;groupend != keys.rend();++groupend) + { + if(groupend->second.compare(0, groupname.size(), groupname) == 0 && + groupend->second.compare(groupname.size(), 2, ": ") == 0) + break; + } std::string starttag = groupname+": "+start; - NifOgre::TextKeyMap::const_iterator startkey(groupstart); - while(startkey != keys.end() && startkey->second != starttag) + NifOgre::TextKeyMap::const_reverse_iterator startkey(groupend); + while(startkey != keys.rend() && startkey->second != starttag) ++startkey; - if(startkey == keys.end() && start == "loop start") + if(startkey == keys.rend() && start == "loop start") { starttag = groupname+": start"; - startkey = groupstart; - while(startkey != keys.end() && startkey->second != starttag) + startkey = groupend; + while(startkey != keys.rend() && startkey->second != starttag) ++startkey; } - if(startkey == keys.end()) + if(startkey == keys.rend()) return false; const std::string stoptag = groupname+": "+stop; - NifOgre::TextKeyMap::const_iterator stopkey(groupstart); - while(stopkey != keys.end() + NifOgre::TextKeyMap::const_reverse_iterator stopkey(groupend); + while(stopkey != keys.rend() // We have to ignore extra garbage at the end. // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop". // Why, just why? :( && (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag)) ++stopkey; - if(stopkey == keys.end()) + if(stopkey == keys.rend()) return false; if(startkey->first > stopkey->first) @@ -628,18 +637,24 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s state.mStopTime = stopkey->first; state.mTime = state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint); + + // mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation + // (see handleTextKey). But if startpoint is already past these keys, we need to assign them now. if(state.mTime > state.mStartTime) { const std::string loopstarttag = groupname+": loop start"; const std::string loopstoptag = groupname+": loop stop"; - NifOgre::TextKeyMap::const_iterator key(groupstart); - while(key->first <= state.mTime && key != stopkey) + + NifOgre::TextKeyMap::const_reverse_iterator key(groupend); + for (; key != startkey && key != keys.rend(); ++key) { - if(key->second == loopstarttag) + if (key->first > state.mTime) + continue; + + if (key->second == loopstarttag) state.mLoopStartTime = key->first; - else if(key->second == loopstoptag) + else if (key->second == loopstoptag) state.mLoopStopTime = key->first; - ++key; } } From 911bd0e3407d2f7ef2f67c208037091299020dcf Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 28 Jun 2014 00:07:13 +0200 Subject: [PATCH 211/226] Use walk animation as fallback if there's no run animation (Fixes #1578) --- apps/openmw/mwmechanics/character.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c15cc540ff..bd076bead1 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -336,7 +336,17 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat { std::string::size_type swimpos = movement.find("swim"); if(swimpos == std::string::npos) - movement.clear(); + { + std::string::size_type runpos = movement.find("run"); + if (runpos != std::string::npos) + { + movement.replace(runpos, runpos+3, "walk"); + if (!mAnimation->hasAnimation(movement)) + movement.clear(); + } + else + movement.clear(); + } else { movegroup = MWRender::Animation::Group_LowerBody; From 1ab02d80720ddbb21a6d5bf320ce8a79e3882042 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 28 Jun 2014 00:10:36 +0200 Subject: [PATCH 212/226] Fix exception for box shapes (Fixes #1580) --- components/nifbullet/bulletnifloader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 7f78c04aa4..31d4e10d64 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -108,6 +108,7 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) for(int i=0; i getChildShape(i)); delete mCompoundShape; + mShape->mAnimatedShapes.clear(); } } else From 516014c071ab3fe1429b91bbbcfa9937cf6d9fe3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 28 Jun 2014 01:06:53 +0200 Subject: [PATCH 213/226] Trigger hit on start key if there's no hit key (Fixes #1574) --- apps/openmw/mwrender/animation.cpp | 39 ++++++++++++++++++++++++++---- apps/openmw/mwrender/animation.hpp | 3 ++- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 62303fd422..872740d74b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -669,7 +669,8 @@ void split(const std::string &s, char delim, std::vector &elems) { } } -void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key) +void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key, + const NifOgre::TextKeyMap& textkeys) { //float time = key->first; const std::string &evt = key->second; @@ -743,6 +744,34 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co else mPtr.getClass().hit(mPtr); } + else if (!groupname.empty() && groupname.compare(0, groupname.size()-1, "attack") == 0 + && evt.compare(off, len, "start") == 0) + { + NifOgre::TextKeyMap::const_iterator hitKey = key; + + // Not all animations have a hit key defined. If there is none, the hit happens with the start key. + bool hasHitKey = false; + while (hitKey != textkeys.end()) + { + if (hitKey->second == groupname + ": hit") + { + hasHitKey = true; + break; + } + if (hitKey->second == groupname + ": stop") + break; + ++hitKey; + } + if (!hasHitKey) + { + if (groupname == "attack1") + mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop); + else if (groupname == "attack2") + mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash); + else if (groupname == "attack3") + mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust); + } + } else if (evt.compare(off, len, "shoot attach") == 0) attachArrow(); else if (evt.compare(off, len, "shoot release") == 0) @@ -821,7 +850,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo NifOgre::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime)); while(textkey != textkeys.end() && textkey->first <= state.mTime) { - handleTextKey(state, groupname, textkey); + handleTextKey(state, groupname, textkey, textkeys); ++textkey; } @@ -836,7 +865,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo textkey = textkeys.lower_bound(state.mTime); while(textkey != textkeys.end() && textkey->first <= state.mTime) { - handleTextKey(state, groupname, textkey); + handleTextKey(state, groupname, textkey, textkeys); ++textkey; } } @@ -1014,7 +1043,7 @@ Ogre::Vector3 Animation::runAnimation(float duration) while(textkey != textkeys.end() && textkey->first <= state.mTime) { - handleTextKey(state, stateiter->first, textkey); + handleTextKey(state, stateiter->first, textkey, textkeys); ++textkey; } @@ -1028,7 +1057,7 @@ Ogre::Vector3 Animation::runAnimation(float duration) textkey = textkeys.lower_bound(state.mTime); while(textkey != textkeys.end() && textkey->first <= state.mTime) { - handleTextKey(state, stateiter->first, textkey); + handleTextKey(state, stateiter->first, textkey, textkeys); ++textkey; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 297af558f7..1544d42c96 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -173,7 +173,8 @@ protected: const std::string &groupname, const std::string &start, const std::string &stop, float startpoint); - void handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key); + void handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key, + const NifOgre::TextKeyMap& map); /* Sets the root model of the object. If 'baseonly' is true, then any meshes or particle * systems in the model are ignored (useful for NPCs, where only the skeleton is needed for From 523c2715e3f4bdd6e32ae3260577050ee862f7a8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 28 Jun 2014 01:31:34 +0200 Subject: [PATCH 214/226] AiCombat: Handle Start to Min and Min to Max durations of 0 (found in Riekling animation) --- apps/openmw/mwmechanics/aicombat.cpp | 13 +++++++++---- apps/openmw/mwmechanics/aicombat.hpp | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index f5881d605f..2606daa88e 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -118,6 +118,7 @@ namespace MWMechanics mStrength = 0; mCell = NULL; mLastTargetPos = Ogre::Vector3(0,0,0); + mMinMaxAttackDurationInitialised = false; } /* @@ -219,8 +220,6 @@ namespace MWMechanics if(smoothTurn(actor, Ogre::Degree(mMovement.mRotation[0]), 0)) mMovement.mRotation[0] = 0; } - mTimerAttack -= duration; - //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f float attacksPeriod = 1.0f; @@ -228,12 +227,16 @@ namespace MWMechanics if(mReadyToAttack) { - if (mMinMaxAttackDuration[0][0] == 0) + if (!mMinMaxAttackDurationInitialised) { + // TODO: this must be updated when a different weapon is equipped getMinMaxAttackDuration(actor, mMinMaxAttackDuration); + mMinMaxAttackDurationInitialised = true; } - if (mTimerAttack <= 0) mAttack = false; + if (mTimerAttack < 0) mAttack = false; + + mTimerAttack -= duration; } else { @@ -326,6 +329,8 @@ namespace MWMechanics else attackType = ESM::Weapon::AT_Chop; // cause it's =0 mStrength = static_cast(rand()) / RAND_MAX; + + // Note: may be 0 for some animations mTimerAttack = mMinMaxAttackDuration[attackType][0] + (mMinMaxAttackDuration[attackType][1] - mMinMaxAttackDuration[attackType][0]) * mStrength; diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 3315998baa..311dee6179 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -65,6 +65,7 @@ namespace MWMechanics float mStrength; // this is actually make sense only in ranged combat float mMinMaxAttackDuration[3][2]; // slash, thrust, chop has different durations + bool mMinMaxAttackDurationInitialised; bool mForceNoShortcut; ESM::Position mShortcutFailPos; From c35b87de953d533a6a0820e7c507b1915677d61c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 28 Jun 2014 11:25:28 +0200 Subject: [PATCH 215/226] various Object class fixes --- apps/opencs/view/render/object.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 59cfd965de..bb7c2f386d 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -49,7 +49,7 @@ void CSVRender::Object::update() error = 1; else { - // xxx check for Deleted state (error 1) + /// \todo check for Deleted state (error 1) model = referenceables.getData (index, referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model)). @@ -81,19 +81,15 @@ void CSVRender::Object::adjust() // position if (!mForceBaseToZero) - { - Ogre::Vector3 (reference.mPos.pos[0], reference.mPos.pos[1], reference.mPos.pos[2]); - mBase->setPosition (Ogre::Vector3 ( reference.mPos.pos[0], reference.mPos.pos[1], reference.mPos.pos[2])); - } // orientation - Ogre::Quaternion xr (Ogre::Radian (-reference.mPos.pos[0]), Ogre::Vector3::UNIT_X); + Ogre::Quaternion xr (Ogre::Radian (-reference.mPos.rot[0]), Ogre::Vector3::UNIT_X); - Ogre::Quaternion yr (Ogre::Radian (-reference.mPos.pos[1]), Ogre::Vector3::UNIT_Y); + Ogre::Quaternion yr (Ogre::Radian (-reference.mPos.rot[1]), Ogre::Vector3::UNIT_Y); - Ogre::Quaternion zr (Ogre::Radian (-reference.mPos.pos[2]), Ogre::Vector3::UNIT_Z); + Ogre::Quaternion zr (Ogre::Radian (-reference.mPos.rot[2]), Ogre::Vector3::UNIT_Z); mBase->setOrientation (xr*yr*zr); From 92f5898b325e8d6b9c05a98131fb35808636d945 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 28 Jun 2014 14:22:27 +0200 Subject: [PATCH 216/226] AiWander: Make sure we have 8 idle values (Fixes #1583) The AiWander instruction may specify more (or less) than 8. --- apps/openmw/mwmechanics/aiwander.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 79151e8961..b97554e2a6 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -26,6 +26,7 @@ namespace MWMechanics AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) { + mIdle.resize(8, 0); init(); } @@ -652,7 +653,7 @@ namespace MWMechanics wander->mData.mDuration = mDuration; wander->mData.mTimeOfDay = mTimeOfDay; wander->mStartTime = mStartTime.toEsm(); - assert (mIdle.size() >= 8); + assert (mIdle.size() == 8); for (int i=0; i<8; ++i) wander->mData.mIdle[i] = mIdle[i]; wander->mData.mShouldRepeat = mRepeat; From 8e361bb879fe11a1a61556a7307bcfebea5f519c Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 28 Jun 2014 14:49:07 +0200 Subject: [PATCH 217/226] Make targeted spells collide with water (Fixes #1500) --- apps/openmw/mwworld/projectilemanager.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3122cb3252..fb376bb930 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -67,6 +67,9 @@ namespace MWWorld Ogre::Vector3 pos(caster.getRefData().getPosition().pos); pos.z += height; + if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible + return; + Ogre::Quaternion orient; if (caster.getClass().isActor()) orient = Ogre::Quaternion(Ogre::Radian(caster.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * @@ -188,6 +191,11 @@ namespace MWWorld hit = true; } + + // Explodes when hitting water + if (MWBase::Environment::get().getWorld()->isUnderwater(MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), newPos)) + hit = true; + if (hit) { MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); From 3b2358888b3279f3bf393656919314ee3e3793ab Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 28 Jun 2014 14:59:33 +0200 Subject: [PATCH 218/226] Attempt to fix player position after using coc/coe --- apps/openmw/mwbase/world.hpp | 3 +++ apps/openmw/mwscript/cellextensions.cpp | 2 ++ apps/openmw/mwworld/physicssystem.cpp | 7 +++---- apps/openmw/mwworld/physicssystem.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 13 ++++++++++++- apps/openmw/mwworld/worldimp.hpp | 3 +++ 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 311d072c51..49ac5bc159 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -274,6 +274,9 @@ namespace MWBase virtual void adjustPosition (const MWWorld::Ptr& ptr) = 0; ///< Adjust position after load to be on ground. Must be called after model load. + virtual void fixPosition (const MWWorld::Ptr& actor) = 0; + ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. + virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 2e2e9b6985..94d7340295 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -47,6 +47,7 @@ namespace MWScript if (world->findExteriorPosition(cell, pos)) { world->changeToExteriorCell(pos); + world->fixPosition(world->getPlayerPtr()); } else { @@ -79,6 +80,7 @@ namespace MWScript pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; world->changeToExteriorCell (pos); + world->fixPosition(world->getPlayerPtr()); } }; diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 9984633cd6..6802d61187 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -215,7 +215,7 @@ namespace MWWorld public: - static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine) + static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine, float maxHeight) { const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); @@ -224,7 +224,6 @@ namespace MWWorld if (!physicActor) return position; - const int maxHeight = 200.f; OEngine::Physic::ActorTracer tracer; tracer.findGround(physicActor, position, position-Ogre::Vector3(0,0,maxHeight), engine); if(tracer.mFraction >= 1.0f) @@ -600,9 +599,9 @@ namespace MWWorld return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName()); } - Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr) + Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight) { - return MovementSolver::traceDown(ptr, mEngine); + return MovementSolver::traceDown(ptr, mEngine, maxHeight); } void PhysicsSystem::addHeightField (float* heights, diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index df97186694..8e0be95d57 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -56,7 +56,7 @@ namespace MWWorld void stepSimulation(float dt); std::vector getCollisions(const MWWorld::Ptr &ptr); ///< get handles this object collides with - Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr); + Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, float maxHeight); std::pair getFacedHandle(float queryDistance); std::pair getHitContact(const std::string &name, diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index fdac643be3..33405b4d86 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1166,7 +1166,7 @@ namespace MWWorld if (!isFlying(ptr)) { - Ogre::Vector3 traced = mPhysics->traceDown(ptr); + Ogre::Vector3 traced = mPhysics->traceDown(ptr, 200); if (traced.z < pos.pos[2]) pos.pos[2] = traced.z; } @@ -1174,6 +1174,17 @@ namespace MWWorld moveObject(ptr, ptr.getCell(), pos.pos[0], pos.pos[1], pos.pos[2]); } + void World::fixPosition(const Ptr &actor) + { + const float dist = 8000; + ESM::Position pos (actor.getRefData().getPosition()); + pos.pos[2] += dist; + actor.getRefData().setPosition(pos); + + Ogre::Vector3 traced = mPhysics->traceDown(actor, dist*1.1); + moveObject(actor, actor.getCell(), traced.x, traced.y, traced.z); + } + void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust) { rotateObjectImp(ptr, Ogre::Vector3(Ogre::Degree(x).valueRadians(), diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 69b72b5330..08d7eb42dd 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -263,6 +263,9 @@ namespace MWWorld virtual void adjustPosition (const Ptr& ptr); ///< Adjust position after load to be on ground. Must be called after model load. + virtual void fixPosition (const Ptr& actor); + ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. + virtual void enable (const Ptr& ptr); virtual void disable (const Ptr& ptr); From 65e36793fc8b3d65976b8b1779a57726b5e26491 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 28 Jun 2014 15:21:04 +0200 Subject: [PATCH 219/226] Fix messagebox on crash showing wrong path --- apps/openmw/crashcatcher.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 7f805f2332..75d2d7953a 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -381,10 +381,7 @@ static void crash_handler(const char *logfile) if(logfile) { - char cwd[MAXPATHLEN]; - getcwd(cwd, MAXPATHLEN); - - std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(cwd) + "/" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !"; + std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !"; SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL); } exit(0); From f929004635a78804234fc5615d38ed9659c4bcbc Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 28 Jun 2014 17:44:52 +0200 Subject: [PATCH 220/226] Search exterior cells in reverse (workaround for duplicate chargen_plank reference in Morrowind.esm and Bloodmoon.esm) --- apps/openmw/mwworld/cells.cpp | 6 ++++-- apps/openmw/mwworld/store.hpp | 21 +++++++-------------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 16c1b497cd..ef3d299a92 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -193,8 +193,10 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) } // Then check cells that are already listed - for (std::map, CellStore>::iterator iter = mExteriors.begin(); - iter!=mExteriors.end(); ++iter) + // Search in reverse, this is a workaround for an ambiguous chargen_plank reference in the vanilla game. + // there is one at -22,16 and one at -2,-9, the latter should be used. + for (std::map, CellStore>::reverse_iterator iter = mExteriors.rbegin(); + iter!=mExteriors.rend(); ++iter) { Ptr ptr = getPtrAndCache (name, iter->second); if (!ptr.isEmpty()) diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index a04267f499..3e7e7a5f98 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -552,23 +552,16 @@ namespace MWWorld template <> class Store : public StoreBase { - struct ExtCmp - { - bool operator()(const ESM::Cell &x, const ESM::Cell &y) { - if (x.mData.mX == y.mData.mX) { - return x.mData.mY < y.mData.mY; - } - return x.mData.mX < y.mData.mX; - } - }; - struct DynamicExtCmp { bool operator()(const std::pair &left, const std::pair &right) const { - if (left.first == right.first) { - return left.second < right.second; - } - return left.first < right.first; + if (left.first == right.first && left.second == right.second) + return false; + + if (left.first == right.first) + return left.second > right.second; + + return left.first > right.first; } }; From 10ef0a34d94909522fd69141f3a55792bd00e503 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 28 Jun 2014 17:54:14 +0200 Subject: [PATCH 221/226] Update effects even when main animation is paused (Fixes #1585) --- apps/openmw/mwmechanics/character.cpp | 2 ++ apps/openmw/mwrender/animation.hpp | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index bd076bead1..2c5d68ceb7 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1449,6 +1449,8 @@ void CharacterController::update(float duration) if(mMovementAnimVelocity > 0) world->queueMovement(mPtr, moved); } + else if (mAnimation) + mAnimation->updateEffects(duration); mSkipAnim = false; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 1544d42c96..e15bd6ecbe 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -226,9 +226,6 @@ public: virtual void preRender (Ogre::Camera* camera); virtual void setAlpha(float alpha) {} -private: - void updateEffects(float duration); - public: void updatePtr(const MWWorld::Ptr &ptr); @@ -301,6 +298,9 @@ public: virtual Ogre::Vector3 runAnimation(float duration); + /// This is typically called as part of runAnimation, but may be called manually if needed. + void updateEffects(float duration); + virtual void showWeapons(bool showWeapon); virtual void showCarriedLeft(bool show) {} virtual void attachArrow() {} From 4949aa1fbb316cb8805bb5df4a6588581a41f3d0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 28 Jun 2014 18:20:57 +0200 Subject: [PATCH 222/226] Add hack required for unnamed animated collision shapes (in_dagoth_bridge00.nif) --- apps/openmw/mwworld/physicssystem.cpp | 13 ++++++++----- components/nifogre/skeleton.cpp | 20 +++++++++++++++++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 6802d61187..2013937612 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -58,13 +58,16 @@ void animateCollisionShapes (std::map::iterator shapeIt = shapes.begin(); shapeIt != shapes.end(); ++shapeIt) { - Ogre::Node* bone = animation->getNode(shapeIt->first); - // FIXME: this will happen for nodes with empty names. Ogre's SkeletonInstance::cloneBoneAndChildren - // will assign an auto-generated name if the bone name was empty. We could use the bone handle instead of - // the bone name, but that is a bit tricky to retrieve. + Ogre::Node* bone; + if (shapeIt->first.empty()) + // HACK: see NifSkeletonLoader::buildBones + bone = animation->getNode(" "); + else + bone = animation->getNode(shapeIt->first); + if (bone == NULL) - continue; + throw std::runtime_error("can't find bone"); btCompoundShape* compound = dynamic_cast(instance.mCompound); diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp index 26647e595d..c96f03950f 100644 --- a/components/nifogre/skeleton.cpp +++ b/components/nifogre/skeleton.cpp @@ -14,10 +14,24 @@ namespace NifOgre void NIFSkeletonLoader::buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *parent) { Ogre::Bone *bone; - if(!skel->hasBone(node->name)) - bone = skel->createBone(node->name); + if (node->name.empty()) + { + // HACK: use " " instead of empty name, otherwise Ogre will replace it with an auto-generated + // name in SkeletonInstance::cloneBoneAndChildren. + static const char* emptyname = " "; + if (!skel->hasBone(emptyname)) + bone = skel->createBone(emptyname); + else + bone = skel->createBone(); + } else - bone = skel->createBone(); + { + if(!skel->hasBone(node->name)) + bone = skel->createBone(node->name); + else + bone = skel->createBone(); + } + if(parent) parent->addChild(bone); mNifToOgreHandleMap[node->recIndex] = bone->getHandle(); From e25fa6c157e97baa6e49de90163da629ea29c0ed Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 29 Jun 2014 02:42:36 +0200 Subject: [PATCH 223/226] Refactor non-distant land terrain path to a grid based implementation (Fixes #1562) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 18 +- apps/openmw/mwrender/terraingrid.cpp | 165 +++++++++ apps/openmw/mwrender/terraingrid.hpp | 75 +++++ apps/openmw/mwrender/terrainstorage.hpp | 4 +- components/CMakeLists.txt | 2 +- components/terrain/backgroundloader.cpp | 0 components/terrain/chunk.cpp | 14 +- components/terrain/chunk.hpp | 10 +- components/terrain/defaultworld.cpp | 315 +++++++++++++++++ components/terrain/defaultworld.hpp | 156 +++++++++ components/terrain/material.cpp | 4 +- components/terrain/material.hpp | 7 +- components/terrain/quadtreenode.cpp | 16 +- components/terrain/quadtreenode.hpp | 8 +- components/terrain/world.cpp | 390 +++------------------- components/terrain/world.hpp | 134 ++------ 17 files changed, 825 insertions(+), 495 deletions(-) create mode 100644 apps/openmw/mwrender/terraingrid.cpp create mode 100644 apps/openmw/mwrender/terraingrid.hpp create mode 100644 components/terrain/backgroundloader.cpp create mode 100644 components/terrain/defaultworld.cpp create mode 100644 components/terrain/defaultworld.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 7c047adefb..5c5b0d16ca 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows characterpreview globalmap videoplayer ripplesimulation refraction - terrainstorage renderconst effectmanager weaponanimation + terrainstorage renderconst effectmanager weaponanimation terraingrid ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ee1cbfe5db..23edb3a7fb 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -45,6 +45,7 @@ #include "globalmap.hpp" #include "terrainstorage.hpp" #include "effectmanager.hpp" +#include "terraingrid.hpp" using namespace MWRender; using namespace Ogre; @@ -223,6 +224,9 @@ MWRender::Camera* RenderingManager::getCamera() const void RenderingManager::removeCell (MWWorld::CellStore *store) { + if (store->isExterior()) + mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + mLocalMap->saveFogOfWar(store); mObjects->removeCell(store); mActors->removeCell(store); @@ -241,6 +245,9 @@ bool RenderingManager::toggleWater() void RenderingManager::cellAdded (MWWorld::CellStore *store) { + if (store->isExterior()) + mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + mObjects->buildStaticGeometry (*store); sh::Factory::getInstance().unloadUnreferencedMaterials(); mDebugging->cellAdded(store); @@ -1039,9 +1046,12 @@ void RenderingManager::enableTerrain(bool enable) { if (!mTerrain) { - mTerrain = new Terrain::World(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, - Settings::Manager::getBool("distant land", "Terrain"), - Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); + if (Settings::Manager::getBool("distant land", "Terrain")) + mTerrain = new Terrain::DefaultWorld(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); + else + mTerrain = new MWRender::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY); mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), Settings::Manager::getBool("split", "Shadows")); mTerrain->update(mRendering.getCamera()->getRealPosition()); diff --git a/apps/openmw/mwrender/terraingrid.cpp b/apps/openmw/mwrender/terraingrid.cpp new file mode 100644 index 0000000000..4688fbfd9f --- /dev/null +++ b/apps/openmw/mwrender/terraingrid.cpp @@ -0,0 +1,165 @@ +#include "terraingrid.hpp" + +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include + +namespace MWRender +{ + +TerrainGrid::TerrainGrid(Ogre::SceneManager *sceneMgr, Terrain::Storage *storage, int visibilityFlags, bool shaders, Terrain::Alignment align) + : Terrain::World(sceneMgr, storage, visibilityFlags, shaders, align) + , mVisible(true) +{ + mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); +} + +TerrainGrid::~TerrainGrid() +{ + while (!mGrid.empty()) + { + unloadCell(mGrid.begin()->first.first, mGrid.begin()->first.second); + } + + mSceneMgr->destroySceneNode(mRootNode); +} + +void TerrainGrid::update(const Ogre::Vector3 &cameraPos) +{ +} + +void TerrainGrid::loadCell(int x, int y) +{ + if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) + return; // already loaded + + Ogre::Vector2 center(x+0.5, y+0.5); + float minH, maxH; + if (!mStorage->getMinMaxHeights(1, center, minH, maxH)) + return; // no terrain defined + + Ogre::Vector3 min (-0.5*mStorage->getCellWorldSize(), + -0.5*mStorage->getCellWorldSize(), + minH); + Ogre::Vector3 max (0.5*mStorage->getCellWorldSize(), + 0.5*mStorage->getCellWorldSize(), + maxH); + + Ogre::AxisAlignedBox bounds(min, max); + + GridElement element; + + Ogre::Vector2 worldCenter = center*mStorage->getCellWorldSize(); + element.mSceneNode = mRootNode->createChildSceneNode(Ogre::Vector3(worldCenter.x, worldCenter.y, 0)); + + std::vector positions; + std::vector normals; + std::vector colours; + mStorage->fillVertexBuffers(0, 1, center, mAlign, positions, normals, colours); + + element.mChunk = new Terrain::Chunk(mCache.getUVBuffer(), bounds, positions, normals, colours); + element.mChunk->setIndexBuffer(mCache.getIndexBuffer(0)); + + std::vector blendmaps; + std::vector layerList; + mStorage->getBlendmaps(1, center, mShaders, blendmaps, layerList); + + element.mMaterialGenerator.setLayerList(layerList); + + // upload blendmaps to GPU + std::vector blendTextures; + for (std::vector::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) + { + static int count=0; + Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/" + + Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format); + + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true)); + map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format); + blendTextures.push_back(map); + } + + element.mMaterialGenerator.setBlendmapList(blendTextures); + + element.mSceneNode->attachObject(element.mChunk); + updateMaterial(element); + + mGrid[std::make_pair(x,y)] = element; +} + +void TerrainGrid::updateMaterial(GridElement &element) +{ + element.mMaterialGenerator.enableShadows(getShadowsEnabled()); + element.mMaterialGenerator.enableSplitShadows(getSplitShadowsEnabled()); + element.mChunk->setMaterial(element.mMaterialGenerator.generate()); +} + +void TerrainGrid::unloadCell(int x, int y) +{ + Grid::iterator it = mGrid.find(std::make_pair(x,y)); + if (it == mGrid.end()) + return; + + GridElement& element = it->second; + delete element.mChunk; + element.mChunk = NULL; + + const std::vector& blendmaps = element.mMaterialGenerator.getBlendmapList(); + for (std::vector::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) + Ogre::TextureManager::getSingleton().remove((*it)->getName()); + + mSceneMgr->destroySceneNode(element.mSceneNode); + element.mSceneNode = NULL; + + mGrid.erase(it); +} + +void TerrainGrid::applyMaterials(bool shadows, bool splitShadows) +{ + mShadows = shadows; + mSplitShadows = splitShadows; + for (Grid::iterator it = mGrid.begin(); it != mGrid.end(); ++it) + { + updateMaterial(it->second); + } +} + +bool TerrainGrid::getVisible() +{ + return mVisible; +} + +void TerrainGrid::setVisible(bool visible) +{ + mVisible = visible; + mRootNode->setVisible(visible); +} + +Ogre::AxisAlignedBox TerrainGrid::getWorldBoundingBox (const Ogre::Vector2& center) +{ + int cellX, cellY; + MWBase::Environment::get().getWorld()->positionToIndex(center.x, center.y, cellX, cellY); + + Grid::iterator it = mGrid.find(std::make_pair(cellX, cellY)); + if (it == mGrid.end()) + return Ogre::AxisAlignedBox::BOX_NULL; + + Terrain::Chunk* chunk = it->second.mChunk; + Ogre::SceneNode* node = it->second.mSceneNode; + Ogre::AxisAlignedBox box = chunk->getBoundingBox(); + box = Ogre::AxisAlignedBox(box.getMinimum() + node->getPosition(), box.getMaximum() + node->getPosition()); + return box; +} + +void TerrainGrid::syncLoad() +{ + +} + +} diff --git a/apps/openmw/mwrender/terraingrid.hpp b/apps/openmw/mwrender/terraingrid.hpp new file mode 100644 index 0000000000..1b5250dcfe --- /dev/null +++ b/apps/openmw/mwrender/terraingrid.hpp @@ -0,0 +1,75 @@ +#ifndef OPENMW_MWRENDER_TERRAINGRID_H +#define OPENMW_MWRENDER_TERRAINGRID_H + +#include +#include + +namespace Terrain +{ + class Chunk; +} + +namespace MWRender +{ + + struct GridElement + { + Ogre::SceneNode* mSceneNode; + + Terrain::MaterialGenerator mMaterialGenerator; + + Terrain::Chunk* mChunk; + }; + + /// @brief Simple terrain implementation that loads cells in a grid, with no LOD + class TerrainGrid : public Terrain::World + { + public: + /// @note takes ownership of \a storage + /// @param sceneMgr scene manager to use + /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) + /// @param visbilityFlags visibility flags for the created meshes + /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually + /// faster so this is just here for compatibility. + /// @param align The align of the terrain, see Alignment enum + TerrainGrid(Ogre::SceneManager* sceneMgr, + Terrain::Storage* storage, int visibilityFlags, bool shaders, Terrain::Alignment align); + ~TerrainGrid(); + + /// Update chunk LODs according to this camera position + virtual void update (const Ogre::Vector3& cameraPos); + + virtual void loadCell(int x, int y); + virtual void unloadCell(int x, int y); + + /// Get the world bounding box of a chunk of terrain centered at \a center + virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); + + /// Show or hide the whole terrain + /// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden + virtual void setVisible(bool visible); + virtual bool getVisible(); + + /// Recreate materials used by terrain chunks. This should be called whenever settings of + /// the material factory are changed. (Relying on the factory to update those materials is not + /// enough, since turning a feature on/off can change the number of texture units available for layer/blend + /// textures, and to properly respond to this we may need to change the structure of the material, such as + /// adding or removing passes. This can only be achieved by a full rebuild.) + virtual void applyMaterials(bool shadows, bool splitShadows); + + /// Wait until all background loading is complete. + virtual void syncLoad(); + + private: + void updateMaterial (GridElement& element); + + typedef std::map, GridElement> Grid; + Grid mGrid; + + Ogre::SceneNode* mRootNode; + bool mVisible; + }; + +} + +#endif diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index b5e6012f3a..c1fb744451 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -46,7 +46,7 @@ namespace MWRender /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from *one* background thread. + /// @note May be called from background threads. /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - @@ -62,7 +62,7 @@ namespace MWRender /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from *one* background thread. + /// @note May be called from background threads. /// @param nodes A collection of nodes for which to retrieve the aforementioned data /// @param out Output vector /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 060e3472d6..b8ebb84b19 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -72,7 +72,7 @@ add_component_dir (translation add_definitions(-DTERRAIN_USE_SHADER=1) add_component_dir (terrain - quadtreenode chunk world storage material buffercache defs + quadtreenode chunk world defaultworld storage material buffercache defs backgroundloader ) add_component_dir (loadinglistener diff --git a/components/terrain/backgroundloader.cpp b/components/terrain/backgroundloader.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index bb8710b872..9c60ee0173 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -8,19 +8,17 @@ #include - -#include "world.hpp" // FIXME: for LoadResponseData, move to backgroundloader.hpp - namespace Terrain { - Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, const LoadResponseData& data) + Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, + const std::vector& positions, const std::vector& normals, const std::vector& colours) : mBounds(bounds) , mOwnMaterial(false) { mVertexData = OGRE_NEW Ogre::VertexData; mVertexData->vertexStart = 0; - mVertexData->vertexCount = data.mPositions.size()/3; + mVertexData->vertexCount = positions.size()/3; // Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc) Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration; @@ -48,9 +46,9 @@ namespace Terrain Ogre::HardwareVertexBufferSharedPtr colourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &data.mPositions[0], true); - normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &data.mNormals[0], true); - colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &data.mColours[0], true); + vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true); + normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true); + colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colours[0], true); mVertexData->vertexBufferBinding->setBinding(0, vertexBuffer); mVertexData->vertexBufferBinding->setBinding(1, normalBuffer); diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp index 9550b30460..9b2ed76ac6 100644 --- a/components/terrain/chunk.hpp +++ b/components/terrain/chunk.hpp @@ -7,16 +7,16 @@ namespace Terrain { - class BufferCache; - struct LoadResponseData; - /** - * @brief Renders a chunk of terrain, either using alpha splatting or a composite map. + * @brief A movable object representing a chunk of terrain. */ class Chunk : public Ogre::Renderable, public Ogre::MovableObject { public: - Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, const LoadResponseData& data); + Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, + const std::vector& positions, + const std::vector& normals, + const std::vector& colours); virtual ~Chunk(); diff --git a/components/terrain/defaultworld.cpp b/components/terrain/defaultworld.cpp new file mode 100644 index 0000000000..9436582353 --- /dev/null +++ b/components/terrain/defaultworld.cpp @@ -0,0 +1,315 @@ +#include "defaultworld.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "storage.hpp" +#include "quadtreenode.hpp" + +namespace +{ + + bool isPowerOfTwo(int x) + { + return ( (x > 0) && ((x & (x - 1)) == 0) ); + } + + int nextPowerOfTwo (int v) + { + if (isPowerOfTwo(v)) return v; + int depth=0; + while(v) + { + v >>= 1; + depth++; + } + return 1 << depth; + } + + Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node) + { + if (center == node->getCenter()) + return node; + + if (center.x > node->getCenter().x && center.y > node->getCenter().y) + return findNode(center, node->getChild(Terrain::NE)); + else if (center.x > node->getCenter().x && center.y < node->getCenter().y) + return findNode(center, node->getChild(Terrain::SE)); + else if (center.x < node->getCenter().x && center.y > node->getCenter().y) + return findNode(center, node->getChild(Terrain::NW)); + else //if (center.x < node->getCenter().x && center.y < node->getCenter().y) + return findNode(center, node->getChild(Terrain::SW)); + } + +} + +namespace Terrain +{ + + const Ogre::uint REQ_ID_CHUNK = 1; + const Ogre::uint REQ_ID_LAYERS = 2; + + DefaultWorld::DefaultWorld(Ogre::SceneManager* sceneMgr, + Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize) + : World(sceneMgr, storage, visibilityFlags, shaders, align) + , mMinBatchSize(minBatchSize) + , mMaxBatchSize(maxBatchSize) + , mVisible(true) + , mMaxX(0) + , mMinX(0) + , mMaxY(0) + , mMinY(0) + , mChunksLoading(0) + , mWorkQueueChannel(0) + , mLayerLoadPending(true) + { +#if TERRAIN_USE_SHADER == 0 + if (mShaders) + std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl; + mShaders = false; +#endif + + mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + + /// \todo make composite map size configurable + Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a"); + mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual( + "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET); + mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget(); + mCompositeMapRenderTarget->setAutoUpdated(false); + mCompositeMapRenderTarget->addViewport(compositeMapCam); + + storage->getBounds(mMinX, mMaxX, mMinY, mMaxY); + + int origSizeX = mMaxX-mMinX; + int origSizeY = mMaxY-mMinY; + + // Dividing a quad tree only works well for powers of two, so round up to the nearest one + int size = nextPowerOfTwo(std::max(origSizeX, origSizeY)); + + // Adjust the center according to the new size + float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f; + float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f; + + mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); + + // While building the quadtree, remember leaf nodes since we need to load their layers + LayersRequestData data; + data.mPack = getShadersEnabled(); + + mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL); + buildQuadTree(mRootNode, data.mNodes); + //loadingListener->indicateProgress(); + mRootNode->initAabb(); + //loadingListener->indicateProgress(); + mRootNode->initNeighbours(); + //loadingListener->indicateProgress(); + + Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); + mWorkQueueChannel = wq->getChannel("LargeTerrain"); + wq->addRequestHandler(mWorkQueueChannel, this); + wq->addResponseHandler(mWorkQueueChannel, this); + + // Start loading layers in the background (for leaf nodes) + wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data)); + } + + DefaultWorld::~DefaultWorld() + { + Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); + wq->removeRequestHandler(mWorkQueueChannel, this); + wq->removeResponseHandler(mWorkQueueChannel, this); + + delete mRootNode; + } + + void DefaultWorld::buildQuadTree(QuadTreeNode *node, std::vector& leafs) + { + float halfSize = node->getSize()/2.f; + + if (node->getSize() <= mMinBatchSize) + { + // We arrived at a leaf + float minZ,maxZ; + Ogre::Vector2 center = node->getCenter(); + float cellWorldSize = getStorage()->getCellWorldSize(); + if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ)) + { + Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ), + Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)); + convertBounds(bounds); + node->setBoundingBox(bounds); + leafs.push_back(node); + } + else + node->markAsDummy(); // no data available for this node, skip it + return; + } + + if (node->getCenter().x - halfSize > mMaxX + || node->getCenter().x + halfSize < mMinX + || node->getCenter().y - halfSize > mMaxY + || node->getCenter().y + halfSize < mMinY ) + // Out of bounds of the actual terrain - this will happen because + // we rounded the size up to the next power of two + { + node->markAsDummy(); + return; + } + + // Not a leaf, create its children + node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f); + node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f)); + node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f)); + node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); + buildQuadTree(node->getChild(SW), leafs); + buildQuadTree(node->getChild(SE), leafs); + buildQuadTree(node->getChild(NW), leafs); + buildQuadTree(node->getChild(NE), leafs); + + // if all children are dummy, we are also dummy + for (int i=0; i<4; ++i) + { + if (!node->getChild((ChildDirection)i)->isDummy()) + return; + } + node->markAsDummy(); + } + + void DefaultWorld::update(const Ogre::Vector3& cameraPos) + { + if (!mVisible) + return; + mRootNode->update(cameraPos); + mRootNode->updateIndexBuffers(); + } + + Ogre::AxisAlignedBox DefaultWorld::getWorldBoundingBox (const Ogre::Vector2& center) + { + if (center.x > mMaxX + || center.x < mMinX + || center.y > mMaxY + || center.y < mMinY) + return Ogre::AxisAlignedBox::BOX_NULL; + QuadTreeNode* node = findNode(center, mRootNode); + return node->getWorldBoundingBox(); + } + + void DefaultWorld::renderCompositeMap(Ogre::TexturePtr target) + { + mCompositeMapRenderTarget->update(); + target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer()); + } + + void DefaultWorld::clearCompositeMapSceneManager() + { + mCompositeMapSceneMgr->destroyAllManualObjects(); + mCompositeMapSceneMgr->clearScene(); + } + + void DefaultWorld::applyMaterials(bool shadows, bool splitShadows) + { + mShadows = shadows; + mSplitShadows = splitShadows; + mRootNode->applyMaterials(); + } + + void DefaultWorld::setVisible(bool visible) + { + if (visible && !mVisible) + mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode); + else if (!visible && mVisible) + mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode); + + mVisible = visible; + } + + bool DefaultWorld::getVisible() + { + return mVisible; + } + + void DefaultWorld::syncLoad() + { + while (mChunksLoading || mLayerLoadPending) + { + OGRE_THREAD_SLEEP(0); + Ogre::Root::getSingleton().getWorkQueue()->processResponses(); + } + } + + Ogre::WorkQueue::Response* DefaultWorld::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ) + { + if (req->getType() == REQ_ID_CHUNK) + { + const LoadRequestData data = Ogre::any_cast(req->getData()); + + QuadTreeNode* node = data.mNode; + + LoadResponseData* responseData = new LoadResponseData(); + + getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), + responseData->mPositions, responseData->mNormals, responseData->mColours); + + return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + } + else // REQ_ID_LAYERS + { + const LayersRequestData data = Ogre::any_cast(req->getData()); + + LayersResponseData* responseData = new LayersResponseData(); + + getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack); + + return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + } + } + + void DefaultWorld::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ) + { + assert(res->succeeded() && "Response failure not handled"); + + if (res->getRequest()->getType() == REQ_ID_CHUNK) + { + LoadResponseData* data = Ogre::any_cast(res->getData()); + + const LoadRequestData requestData = Ogre::any_cast(res->getRequest()->getData()); + + requestData.mNode->load(*data); + + delete data; + + --mChunksLoading; + } + else // REQ_ID_LAYERS + { + LayersResponseData* data = Ogre::any_cast(res->getData()); + + for (std::vector::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it) + { + it->mTarget->loadLayers(*it); + } + + delete data; + + mRootNode->loadMaterials(); + + mLayerLoadPending = false; + } + } + + void DefaultWorld::queueLoad(QuadTreeNode *node) + { + LoadRequestData data; + data.mNode = node; + + Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data)); + ++mChunksLoading; + } +} diff --git a/components/terrain/defaultworld.hpp b/components/terrain/defaultworld.hpp new file mode 100644 index 0000000000..8769a0d88d --- /dev/null +++ b/components/terrain/defaultworld.hpp @@ -0,0 +1,156 @@ +#ifndef COMPONENTS_TERRAIN_H +#define COMPONENTS_TERRAIN_H + +#include +#include +#include + +#include "world.hpp" + +namespace Ogre +{ + class Camera; +} + +namespace Terrain +{ + + class QuadTreeNode; + class Storage; + + /** + * @brief A quadtree-based terrain implementation suitable for large data sets. \n + * Near cells are rendered with alpha splatting, distant cells are merged + * together in batches and have their layers pre-rendered onto a composite map. \n + * Cracks at LOD transitions are avoided using stitching. + * @note Multiple cameras are not supported yet + */ + class DefaultWorld : public World, public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler + { + public: + /// @note takes ownership of \a storage + /// @param sceneMgr scene manager to use + /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) + /// @param visbilityFlags visibility flags for the created meshes + /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually + /// faster so this is just here for compatibility. + /// @param align The align of the terrain, see Alignment enum + /// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree. + /// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree. + DefaultWorld(Ogre::SceneManager* sceneMgr, + Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize); + ~DefaultWorld(); + + /// Update chunk LODs according to this camera position + /// @note Calling this method might lead to composite textures being rendered, so it is best + /// not to call it when render commands are still queued, since that would cause a flush. + virtual void update (const Ogre::Vector3& cameraPos); + + /// Get the world bounding box of a chunk of terrain centered at \a center + virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); + + Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; } + + /// Show or hide the whole terrain + /// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden + virtual void setVisible(bool visible); + virtual bool getVisible(); + + /// Recreate materials used by terrain chunks. This should be called whenever settings of + /// the material factory are changed. (Relying on the factory to update those materials is not + /// enough, since turning a feature on/off can change the number of texture units available for layer/blend + /// textures, and to properly respond to this we may need to change the structure of the material, such as + /// adding or removing passes. This can only be achieved by a full rebuild.) + virtual void applyMaterials(bool shadows, bool splitShadows); + + int getMaxBatchSize() { return mMaxBatchSize; } + + /// Wait until all background loading is complete. + void syncLoad(); + + private: + // Called from a background worker thread + Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ); + // Called from the main thread + void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ); + Ogre::uint16 mWorkQueueChannel; + + bool mVisible; + + QuadTreeNode* mRootNode; + Ogre::SceneNode* mRootSceneNode; + + /// The number of chunks currently loading in a background thread. If 0, we have finished loading! + int mChunksLoading; + + Ogre::SceneManager* mCompositeMapSceneMgr; + + /// Bounds in cell units + float mMinX, mMaxX, mMinY, mMaxY; + + /// Minimum size of a terrain batch along one side (in cell units) + float mMinBatchSize; + /// Maximum size of a terrain batch along one side (in cell units) + float mMaxBatchSize; + + void buildQuadTree(QuadTreeNode* node, std::vector& leafs); + + // Are layers for leaf nodes loaded? This is done once at startup (but in a background thread) + bool mLayerLoadPending; + + public: + // ----INTERNAL---- + Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } + + bool areLayersLoaded() { return !mLayerLoadPending; } + + // Delete all quads + void clearCompositeMapSceneManager(); + void renderCompositeMap (Ogre::TexturePtr target); + + // Adds a WorkQueue request to load a chunk for this node in the background. + void queueLoad (QuadTreeNode* node); + + private: + Ogre::RenderTarget* mCompositeMapRenderTarget; + Ogre::TexturePtr mCompositeMapRenderTexture; + }; + + struct LoadRequestData + { + QuadTreeNode* mNode; + + friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r) + { return o; } + }; + + struct LoadResponseData + { + std::vector mPositions; + std::vector mNormals; + std::vector mColours; + + friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r) + { return o; } + }; + + struct LayersRequestData + { + std::vector mNodes; + bool mPack; + + friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r) + { return o; } + }; + + struct LayersResponseData + { + std::vector mLayerCollections; + + friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r) + { return o; } + }; + +} + +#endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 771dcdf91c..a40e576ed9 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -36,8 +36,8 @@ std::string getBlendmapComponentForLayer (int layerIndex) namespace Terrain { - MaterialGenerator::MaterialGenerator(bool shaders) - : mShaders(shaders) + MaterialGenerator::MaterialGenerator() + : mShaders(true) , mShadows(false) , mSplitShadows(false) , mNormalMapping(true) diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 7f607a7af8..b9000cb1b9 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -11,11 +11,7 @@ namespace Terrain class MaterialGenerator { public: - /// @param layerList layer textures - /// @param blendmapList blend textures - /// @param shaders Whether to use shaders. With a shader, blendmap packing can be used (4 channels instead of one), - /// so if this parameter is true, then the supplied blend maps are expected to be packed. - MaterialGenerator (bool shaders); + MaterialGenerator (); void setLayerList (const std::vector& layerList) { mLayerList = layerList; } bool hasLayers() { return mLayerList.size(); } @@ -23,6 +19,7 @@ namespace Terrain const std::vector& getBlendmapList() { return mBlendmapList; } void setCompositeMap (const std::string& name) { mCompositeMap = name; } + void enableShaders(bool shaders) { mShaders = shaders; } void enableShadows(bool shadows) { mShadows = shadows; } void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; } void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 37c638da0c..44974eeb1d 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -6,7 +6,7 @@ #include #include -#include "world.hpp" +#include "defaultworld.hpp" #include "chunk.hpp" #include "storage.hpp" #include "buffercache.hpp" @@ -142,7 +142,7 @@ namespace } } -QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) +QuadTreeNode::QuadTreeNode(DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) : mMaterialGenerator(NULL) , mIsDummy(false) , mSize(size) @@ -178,7 +178,8 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const mSceneNode->setPosition(sceneNodePos); - mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled()); + mMaterialGenerator = new MaterialGenerator(); + mMaterialGenerator->enableShaders(mTerrain->getShadersEnabled()); } void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 ¢er) @@ -386,11 +387,9 @@ void QuadTreeNode::load(const LoadResponseData &data) { assert (!mChunk); - mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data); - mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); + mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data.mPositions, data.mNormals, data.mColours); + mChunk->setVisibilityFlags(mTerrain->getVisibilityFlags()); mChunk->setCastShadows(true); - if (!mTerrain->getDistantLandEnabled()) - mChunk->setRenderingDistance(8192); mSceneNode->attachObject(mChunk); mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); @@ -550,7 +549,8 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) if (mIsDummy) { // TODO - store this default material somewhere instead of creating one for each empty cell - MaterialGenerator matGen(mTerrain->getShadersEnabled()); + MaterialGenerator matGen; + matGen.enableShaders(mTerrain->getShadersEnabled()); std::vector layer; layer.push_back(mTerrain->getStorage()->getDefaultLayer()); matGen.setLayerList(layer); diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index c575894879..6265727011 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -14,7 +14,7 @@ namespace Ogre namespace Terrain { - class World; + class DefaultWorld; class Chunk; class MaterialGenerator; struct LoadResponseData; @@ -48,7 +48,7 @@ namespace Terrain /// @param size size (in *cell* units!) /// @param center center (in *cell* units!) /// @param parent parent node - QuadTreeNode (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); + QuadTreeNode (DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); ~QuadTreeNode(); /// Rebuild all materials @@ -95,7 +95,7 @@ namespace Terrain const Ogre::AxisAlignedBox& getWorldBoundingBox(); - World* getTerrain() { return mTerrain; } + DefaultWorld* getTerrain() { return mTerrain; } /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them. /// @param force Always choose to render this node, even if not the perfect LOD. @@ -158,7 +158,7 @@ namespace Terrain Chunk* mChunk; - World* mTerrain; + DefaultWorld* mTerrain; Ogre::TexturePtr mCompositeMap; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 3d968470f3..49fb9b5c9b 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -1,356 +1,60 @@ #include "world.hpp" #include -#include -#include -#include -#include -#include -#include #include "storage.hpp" -#include "quadtreenode.hpp" - -namespace -{ - - bool isPowerOfTwo(int x) - { - return ( (x > 0) && ((x & (x - 1)) == 0) ); - } - - int nextPowerOfTwo (int v) - { - if (isPowerOfTwo(v)) return v; - int depth=0; - while(v) - { - v >>= 1; - depth++; - } - return 1 << depth; - } - - Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node) - { - if (center == node->getCenter()) - return node; - - if (center.x > node->getCenter().x && center.y > node->getCenter().y) - return findNode(center, node->getChild(Terrain::NE)); - else if (center.x > node->getCenter().x && center.y < node->getCenter().y) - return findNode(center, node->getChild(Terrain::SE)); - else if (center.x < node->getCenter().x && center.y > node->getCenter().y) - return findNode(center, node->getChild(Terrain::NW)); - else //if (center.x < node->getCenter().x && center.y < node->getCenter().y) - return findNode(center, node->getChild(Terrain::SW)); - } - -} namespace Terrain { - const Ogre::uint REQ_ID_CHUNK = 1; - const Ogre::uint REQ_ID_LAYERS = 2; +World::World(Ogre::SceneManager* sceneMgr, + Storage* storage, int visibilityFlags, bool shaders, Alignment align) + : mStorage(storage) + , mSceneMgr(sceneMgr) + , mVisibilityFlags(visibilityFlags) + , mShaders(shaders) + , mAlign(align) + , mCache(storage->getCellVertices()) +{ +} - World::World(Ogre::SceneManager* sceneMgr, - Storage* storage, int visibilityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize) - : mStorage(storage) - , mMinBatchSize(minBatchSize) - , mMaxBatchSize(maxBatchSize) - , mSceneMgr(sceneMgr) - , mVisibilityFlags(visibilityFlags) - , mDistantLand(distantLand) - , mShaders(shaders) - , mVisible(true) - , mAlign(align) - , mMaxX(0) - , mMinX(0) - , mMaxY(0) - , mMinY(0) - , mChunksLoading(0) - , mWorkQueueChannel(0) - , mCache(storage->getCellVertices()) - , mLayerLoadPending(true) +World::~World() +{ + delete mStorage; +} + +float World::getHeightAt(const Ogre::Vector3 &worldPos) +{ + return mStorage->getHeightAt(worldPos); +} + +void World::convertPosition(float &x, float &y, float &z) +{ + Terrain::convertPosition(mAlign, x, y, z); +} + +void World::convertPosition(Ogre::Vector3 &pos) +{ + convertPosition(pos.x, pos.y, pos.z); +} + +void World::convertBounds(Ogre::AxisAlignedBox& bounds) +{ + switch (mAlign) { -#if TERRAIN_USE_SHADER == 0 - if (mShaders) - std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl; - mShaders = false; -#endif - - mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); - - /// \todo make composite map size configurable - Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a"); - mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual( - "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET); - mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget(); - mCompositeMapRenderTarget->setAutoUpdated(false); - mCompositeMapRenderTarget->addViewport(compositeMapCam); - - storage->getBounds(mMinX, mMaxX, mMinY, mMaxY); - - int origSizeX = mMaxX-mMinX; - int origSizeY = mMaxY-mMinY; - - // Dividing a quad tree only works well for powers of two, so round up to the nearest one - int size = nextPowerOfTwo(std::max(origSizeX, origSizeY)); - - // Adjust the center according to the new size - float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f; - float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f; - - mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - - // While building the quadtree, remember leaf nodes since we need to load their layers - LayersRequestData data; - data.mPack = getShadersEnabled(); - - mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL); - buildQuadTree(mRootNode, data.mNodes); - //loadingListener->indicateProgress(); - mRootNode->initAabb(); - //loadingListener->indicateProgress(); - mRootNode->initNeighbours(); - //loadingListener->indicateProgress(); - - Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); - mWorkQueueChannel = wq->getChannel("LargeTerrain"); - wq->addRequestHandler(mWorkQueueChannel, this); - wq->addResponseHandler(mWorkQueueChannel, this); - - // Start loading layers in the background (for leaf nodes) - wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data)); - } - - World::~World() - { - Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); - wq->removeRequestHandler(mWorkQueueChannel, this); - wq->removeResponseHandler(mWorkQueueChannel, this); - - delete mRootNode; - delete mStorage; - } - - void World::buildQuadTree(QuadTreeNode *node, std::vector& leafs) - { - float halfSize = node->getSize()/2.f; - - if (node->getSize() <= mMinBatchSize) - { - // We arrived at a leaf - float minZ,maxZ; - Ogre::Vector2 center = node->getCenter(); - float cellWorldSize = getStorage()->getCellWorldSize(); - if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ)) - { - Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ), - Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)); - convertBounds(bounds); - node->setBoundingBox(bounds); - leafs.push_back(node); - } - else - node->markAsDummy(); // no data available for this node, skip it - return; - } - - if (node->getCenter().x - halfSize > mMaxX - || node->getCenter().x + halfSize < mMinX - || node->getCenter().y - halfSize > mMaxY - || node->getCenter().y + halfSize < mMinY ) - // Out of bounds of the actual terrain - this will happen because - // we rounded the size up to the next power of two - { - node->markAsDummy(); - return; - } - - // Not a leaf, create its children - node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f); - node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f)); - node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f)); - node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); - buildQuadTree(node->getChild(SW), leafs); - buildQuadTree(node->getChild(SE), leafs); - buildQuadTree(node->getChild(NW), leafs); - buildQuadTree(node->getChild(NE), leafs); - - // if all children are dummy, we are also dummy - for (int i=0; i<4; ++i) - { - if (!node->getChild((ChildDirection)i)->isDummy()) - return; - } - node->markAsDummy(); - } - - void World::update(const Ogre::Vector3& cameraPos) - { - if (!mVisible) - return; - mRootNode->update(cameraPos); - mRootNode->updateIndexBuffers(); - } - - Ogre::AxisAlignedBox World::getWorldBoundingBox (const Ogre::Vector2& center) - { - if (center.x > mMaxX - || center.x < mMinX - || center.y > mMaxY - || center.y < mMinY) - return Ogre::AxisAlignedBox::BOX_NULL; - QuadTreeNode* node = findNode(center, mRootNode); - return node->getWorldBoundingBox(); - } - - void World::renderCompositeMap(Ogre::TexturePtr target) - { - mCompositeMapRenderTarget->update(); - target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer()); - } - - void World::clearCompositeMapSceneManager() - { - mCompositeMapSceneMgr->destroyAllManualObjects(); - mCompositeMapSceneMgr->clearScene(); - } - - float World::getHeightAt(const Ogre::Vector3 &worldPos) - { - return mStorage->getHeightAt(worldPos); - } - - void World::applyMaterials(bool shadows, bool splitShadows) - { - mShadows = shadows; - mSplitShadows = splitShadows; - mRootNode->applyMaterials(); - } - - void World::setVisible(bool visible) - { - if (visible && !mVisible) - mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode); - else if (!visible && mVisible) - mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode); - - mVisible = visible; - } - - bool World::getVisible() - { - return mVisible; - } - - void World::convertPosition(float &x, float &y, float &z) - { - Terrain::convertPosition(mAlign, x, y, z); - } - - void World::convertPosition(Ogre::Vector3 &pos) - { - convertPosition(pos.x, pos.y, pos.z); - } - - void World::convertBounds(Ogre::AxisAlignedBox& bounds) - { - switch (mAlign) - { - case Align_XY: - return; - case Align_XZ: - convertPosition(bounds.getMinimum()); - convertPosition(bounds.getMaximum()); - // Because we changed sign of Z - std::swap(bounds.getMinimum().z, bounds.getMaximum().z); - return; - case Align_YZ: - convertPosition(bounds.getMinimum()); - convertPosition(bounds.getMaximum()); - return; - } - } - - void World::syncLoad() - { - while (mChunksLoading || mLayerLoadPending) - { - OGRE_THREAD_SLEEP(0); - Ogre::Root::getSingleton().getWorkQueue()->processResponses(); - } - } - - Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ) - { - if (req->getType() == REQ_ID_CHUNK) - { - const LoadRequestData data = Ogre::any_cast(req->getData()); - - QuadTreeNode* node = data.mNode; - - LoadResponseData* responseData = new LoadResponseData(); - - getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), - responseData->mPositions, responseData->mNormals, responseData->mColours); - - return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); - } - else // REQ_ID_LAYERS - { - const LayersRequestData data = Ogre::any_cast(req->getData()); - - LayersResponseData* responseData = new LayersResponseData(); - - getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack); - - return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); - } - } - - void World::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ) - { - assert(res->succeeded() && "Response failure not handled"); - - if (res->getRequest()->getType() == REQ_ID_CHUNK) - { - LoadResponseData* data = Ogre::any_cast(res->getData()); - - const LoadRequestData requestData = Ogre::any_cast(res->getRequest()->getData()); - - requestData.mNode->load(*data); - - delete data; - - --mChunksLoading; - } - else // REQ_ID_LAYERS - { - LayersResponseData* data = Ogre::any_cast(res->getData()); - - for (std::vector::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it) - { - it->mTarget->loadLayers(*it); - } - - delete data; - - mRootNode->loadMaterials(); - - mLayerLoadPending = false; - } - } - - void World::queueLoad(QuadTreeNode *node) - { - LoadRequestData data; - data.mNode = node; - - Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data)); - ++mChunksLoading; + case Align_XY: + return; + case Align_XZ: + convertPosition(bounds.getMinimum()); + convertPosition(bounds.getMaximum()); + // Because we changed sign of Z + std::swap(bounds.getMinimum().z, bounds.getMaximum().z); + return; + case Align_YZ: + convertPosition(bounds.getMinimum()); + convertPosition(bounds.getMaximum()); + return; } } + +} diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 26a6d034dc..beca7903ab 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -1,50 +1,38 @@ -#ifndef COMPONENTS_TERRAIN_H -#define COMPONENTS_TERRAIN_H +#ifndef COMPONENTS_TERRAIN_WORLD_H +#define COMPONENTS_TERRAIN_WORLD_H -#include -#include -#include +#include #include "defs.hpp" #include "buffercache.hpp" namespace Ogre { - class Camera; + class SceneManager; } namespace Terrain { - - class QuadTreeNode; class Storage; /** - * @brief A quadtree-based terrain implementation suitable for large data sets. \n - * Near cells are rendered with alpha splatting, distant cells are merged - * together in batches and have their layers pre-rendered onto a composite map. \n - * Cracks at LOD transitions are avoided using stitching. - * @note Multiple cameras are not supported yet + * @brief The basic interface for a terrain world. How the terrain chunks are paged and displayed + * is up to the implementation. */ - class World : public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler + class World { public: /// @note takes ownership of \a storage /// @param sceneMgr scene manager to use /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) /// @param visbilityFlags visibility flags for the created meshes - /// @param distantLand Whether to draw all of the terrain, or only a 3x3 grid around the camera. - /// This is a temporary option until it can be streamlined. /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually /// faster so this is just here for compatibility. /// @param align The align of the terrain, see Alignment enum - /// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree. - /// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree. World(Ogre::SceneManager* sceneMgr, - Storage* storage, int visiblityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize); - ~World(); + Storage* storage, int visiblityFlags, bool shaders, Alignment align); + virtual ~World(); - bool getDistantLandEnabled() { return mDistantLand; } bool getShadersEnabled() { return mShaders; } bool getShadowsEnabled() { return mShadows; } bool getSplitShadowsEnabled() { return mSplitShadows; } @@ -54,138 +42,60 @@ namespace Terrain /// Update chunk LODs according to this camera position /// @note Calling this method might lead to composite textures being rendered, so it is best /// not to call it when render commands are still queued, since that would cause a flush. - void update (const Ogre::Vector3& cameraPos); + virtual void update (const Ogre::Vector3& cameraPos) = 0; + + // This is only a hint and may be ignored by the implementation. + virtual void loadCell(int x, int y) {} + virtual void unloadCell(int x, int y) {} /// Get the world bounding box of a chunk of terrain centered at \a center - Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); + virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center) = 0; Ogre::SceneManager* getSceneManager() { return mSceneMgr; } - Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; } - Storage* getStorage() { return mStorage; } /// Show or hide the whole terrain - /// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden - void setVisible(bool visible); - bool getVisible(); + /// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden + virtual void setVisible(bool visible) = 0; + virtual bool getVisible() = 0; /// Recreate materials used by terrain chunks. This should be called whenever settings of /// the material factory are changed. (Relying on the factory to update those materials is not /// enough, since turning a feature on/off can change the number of texture units available for layer/blend /// textures, and to properly respond to this we may need to change the structure of the material, such as /// adding or removing passes. This can only be achieved by a full rebuild.) - void applyMaterials(bool shadows, bool splitShadows); + virtual void applyMaterials(bool shadows, bool splitShadows) = 0; - int getVisiblityFlags() { return mVisibilityFlags; } - - int getMaxBatchSize() { return mMaxBatchSize; } - - void enableSplattingShader(bool enabled); + int getVisibilityFlags() { return mVisibilityFlags; } Alignment getAlign() { return mAlign; } /// Wait until all background loading is complete. - void syncLoad(); + virtual void syncLoad() {} - private: - // Called from a background worker thread - Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ); - // Called from the main thread - void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ); - Ogre::uint16 mWorkQueueChannel; - - bool mDistantLand; + protected: bool mShaders; bool mShadows; bool mSplitShadows; - bool mVisible; Alignment mAlign; - QuadTreeNode* mRootNode; - Ogre::SceneNode* mRootSceneNode; Storage* mStorage; int mVisibilityFlags; - /// The number of chunks currently loading in a background thread. If 0, we have finished loading! - int mChunksLoading; - Ogre::SceneManager* mSceneMgr; - Ogre::SceneManager* mCompositeMapSceneMgr; - - /// Bounds in cell units - float mMinX, mMaxX, mMinY, mMaxY; - - /// Minimum size of a terrain batch along one side (in cell units) - float mMinBatchSize; - /// Maximum size of a terrain batch along one side (in cell units) - float mMaxBatchSize; - - void buildQuadTree(QuadTreeNode* node, std::vector& leafs); BufferCache mCache; - // Are layers for leaf nodes loaded? This is done once at startup (but in a background thread) - bool mLayerLoadPending; - public: // ----INTERNAL---- - Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } BufferCache& getBufferCache() { return mCache; } - bool areLayersLoaded() { return !mLayerLoadPending; } - - // Delete all quads - void clearCompositeMapSceneManager(); - void renderCompositeMap (Ogre::TexturePtr target); - // Convert the given position from Z-up align, i.e. Align_XY to the wanted align set in mAlign void convertPosition (float& x, float& y, float& z); void convertPosition (Ogre::Vector3& pos); void convertBounds (Ogre::AxisAlignedBox& bounds); - - // Adds a WorkQueue request to load a chunk for this node in the background. - void queueLoad (QuadTreeNode* node); - - private: - Ogre::RenderTarget* mCompositeMapRenderTarget; - Ogre::TexturePtr mCompositeMapRenderTexture; - }; - - struct LoadRequestData - { - QuadTreeNode* mNode; - - friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r) - { return o; } - }; - - struct LoadResponseData - { - std::vector mPositions; - std::vector mNormals; - std::vector mColours; - - friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r) - { return o; } - }; - - struct LayersRequestData - { - std::vector mNodes; - bool mPack; - - friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r) - { return o; } - }; - - struct LayersResponseData - { - std::vector mLayerCollections; - - friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r) - { return o; } }; } From 74ed9cbb2d99a6d28a56dc5de2163075fdad1f31 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 29 Jun 2014 14:18:46 +0200 Subject: [PATCH 224/226] added cell rendering class --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/cell.cpp | 201 +++++++++++++++++++++++++++++++ apps/opencs/view/render/cell.hpp | 73 +++++++++++ 3 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/view/render/cell.cpp create mode 100644 apps/opencs/view/render/cell.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index a8cc4ceaea..c52f9d9bca 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -70,7 +70,7 @@ opencs_units (view/render opencs_units_noqt (view/render navigation navigation1st navigationfree navigationorbit lighting lightingday lightingnight - lightingbright object + lightingbright object cell ) opencs_units_noqt (view/world diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp new file mode 100644 index 0000000000..1e25f42e25 --- /dev/null +++ b/apps/opencs/view/render/cell.cpp @@ -0,0 +1,201 @@ + +#include "cell.hpp" + +#include +#include + +#include + +#include "../../model/world/idtable.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/data.hpp" + +bool CSVRender::Cell::removeObject (const std::string& id) +{ + std::map::iterator iter = + mObjects.find (Misc::StringUtils::lowerCase (id)); + + if (iter==mObjects.end()) + return false; + + delete iter->second; + mObjects.erase (iter); + return true; +} + +bool CSVRender::Cell::addObjects (int start, int end) +{ + CSMWorld::IdTable& references = dynamic_cast ( + *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + + int idColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int cellColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); + int stateColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + + bool modified = false; + + for (int i=start; i<=end; ++i) + { + std::string cell = Misc::StringUtils::lowerCase (references.data ( + references.index (i, cellColumn)).toString().toUtf8().constData()); + + int state = references.data (references.index (i, stateColumn)).toInt(); + + if (cell==mId && state!=CSMWorld::RecordBase::State_Deleted) + { + std::string id = Misc::StringUtils::lowerCase (references.data ( + references.index (i, idColumn)).toString().toUtf8().constData()); + + mObjects.insert (std::make_pair (id, new Object (mData, mCellNode, id, false))); + modified = true; + } + } + + return modified; +} + +CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, + const std::string& id, const Ogre::Vector3& origin) +: mData (data), mId (Misc::StringUtils::lowerCase (id)) +{ + mCellNode = sceneManager->getRootSceneNode()->createChildSceneNode(); + mCellNode->setPosition (origin); + + CSMWorld::IdTable& references = dynamic_cast ( + *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + + int rows = references.rowCount(); + + addObjects (0, rows-1); +} + +CSVRender::Cell::~Cell() +{ + for (std::map::iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + delete iter->second; + + mCellNode->getCreator()->destroySceneNode (mCellNode); +} + +bool CSVRender::Cell::referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + bool modified = false; + + for (std::map::iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + if (iter->second->referenceableDataChanged (topLeft, bottomRight)) + modified = true; + + return modified; +} + +bool CSVRender::Cell::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, + int end) +{ + if (parent.isValid()) + return false; + + bool modified = false; + + for (std::map::iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + if (iter->second->referenceableAboutToBeRemoved (parent, start, end)) + modified = true; + + return modified; +} + +bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + CSMWorld::IdTable& references = dynamic_cast ( + *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + + int idColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int cellColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); + int stateColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + + // list IDs in cell + std::map ids; // id, deleted state + + for (int i=topLeft.row(); i<=bottomRight.row(); ++i) + { + std::string cell = Misc::StringUtils::lowerCase (references.data ( + references.index (i, cellColumn)).toString().toUtf8().constData()); + + if (cell==mId) + { + std::string id = Misc::StringUtils::lowerCase (references.data ( + references.index (i, idColumn)).toString().toUtf8().constData()); + + int state = references.data (references.index (i, stateColumn)).toInt(); + + ids.insert (std::make_pair (id, state==CSMWorld::RecordBase::State_Deleted)); + } + } + + // perform update and remove where needed + bool modified = false; + + for (std::map::iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + { + if (iter->second->referenceDataChanged (topLeft, bottomRight)) + modified = true; + + std::map::iterator iter2 = ids.find (iter->first); + + if (iter2!=ids.end()) + { + if (iter2->second) + { + removeObject (iter->first); + modified = true; + } + + ids.erase (iter2); + } + } + + // add new objects + for (std::map::iterator iter (ids.begin()); iter!=ids.end(); ++iter) + { + mObjects.insert (std::make_pair ( + iter->first, new Object (mData, mCellNode, iter->first, false))); + + modified = true; + } + + return modified; +} + +bool CSVRender::Cell::referenceAboutToBeRemoved (const QModelIndex& parent, int start, + int end) +{ + if (parent.isValid()) + return false; + + CSMWorld::IdTable& references = dynamic_cast ( + *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + + int idColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + bool modified = false; + + for (int row = start; row<=end; ++row) + if (removeObject (references.data ( + references.index (row, idColumn)).toString().toUtf8().constData())) + modified = true; + + return modified; +} + +bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int end) +{ + if (parent.isValid()) + return false; + + return addObjects (start, end); +} \ No newline at end of file diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp new file mode 100644 index 0000000000..70adebe45f --- /dev/null +++ b/apps/opencs/view/render/cell.hpp @@ -0,0 +1,73 @@ +#ifndef OPENCS_VIEW_CELL_H +#define OPENCS_VIEW_CELL_H + +#include +#include + +#include + +#include "object.hpp" + +class QModelIndex; + +namespace Ogre +{ + class SceneManager; + class SceneNode; +} + +namespace CSMWorld +{ + class Data; +} + +namespace CSVRender +{ + class Cell + { + CSMWorld::Data& mData; + std::string mId; + Ogre::SceneNode *mCellNode; + std::map mObjects; + + /// Ignored if cell does not have an object with the given ID. + /// + /// \return Was the object deleted? + bool removeObject (const std::string& id); + + /// Add objects from reference table that are within this cell. + /// + /// \return Have any objects been added? + bool addObjects (int start, int end); + + public: + + Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, + const std::string& id, const Ogre::Vector3& origin = Ogre::Vector3 (0, 0, 0)); + + ~Cell(); + + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight); + + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceAdded (const QModelIndex& parent, int start, int end); + }; +} + +#endif From a6626b94c8a85309aa514fff4d35da3a04fe1762 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 29 Jun 2014 14:19:10 +0200 Subject: [PATCH 225/226] added cell rendering in unpaged worldspaces --- .../view/render/unpagedworldspacewidget.cpp | 63 +++++++++++++++++++ .../view/render/unpagedworldspacewidget.hpp | 19 ++++++ apps/opencs/view/render/worldspacewidget.cpp | 27 ++++++-- apps/opencs/view/render/worldspacewidget.hpp | 15 ++++- 4 files changed, 117 insertions(+), 7 deletions(-) diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 166c85f443..0b656ddc6e 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -21,6 +21,8 @@ void CSVRender::UnpagedWorldspaceWidget::update() setDefaultAmbient (colour); /// \todo deal with mSunlight and mFog/mForDensity + + flagAsModified(); } CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, QWidget* parent) @@ -29,12 +31,17 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& mCellsModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + mReferenceablesModel = &dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables)); + connect (mCellsModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (cellDataChanged (const QModelIndex&, const QModelIndex&))); connect (mCellsModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (cellRowsAboutToBeRemoved (const QModelIndex&, int, int))); update(); + + mCell.reset (new Cell (document.getData(), getSceneManager(), mCellId)); } void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, @@ -72,6 +79,62 @@ void CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vector< CSMWorld mCellId = data.begin()->getId(); update(); emit cellChanged(*data.begin()); + + /// \todo replace mCell +} + +void CSVRender::UnpagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + if (mCell.get()) + if (mCell.get()->referenceableDataChanged (topLeft, bottomRight)) + flagAsModified(); +} + +void CSVRender::UnpagedWorldspaceWidget::referenceableAboutToBeRemoved ( + const QModelIndex& parent, int start, int end) +{ + if (mCell.get()) + if (mCell.get()->referenceableAboutToBeRemoved (parent, start, end)) + flagAsModified(); +} + +void CSVRender::UnpagedWorldspaceWidget::referenceableAdded (const QModelIndex& parent, + int start, int end) +{ + if (mCell.get()) + { + QModelIndex topLeft = mReferenceablesModel->index (start, 0); + QModelIndex bottomRight = + mReferenceablesModel->index (end, mReferenceablesModel->columnCount()); + + if (mCell.get()->referenceableDataChanged (topLeft, bottomRight)) + flagAsModified(); + } +} + +void CSVRender::UnpagedWorldspaceWidget::referenceDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + if (mCell.get()) + if (mCell.get()->referenceDataChanged (topLeft, bottomRight)) + flagAsModified(); +} + +void CSVRender::UnpagedWorldspaceWidget::referenceAboutToBeRemoved (const QModelIndex& parent, + int start, int end) +{ + if (mCell.get()) + if (mCell.get()->referenceAboutToBeRemoved (parent, start, end)) + flagAsModified(); +} + +void CSVRender::UnpagedWorldspaceWidget::referenceAdded (const QModelIndex& parent, int start, + int end) +{ + if (mCell.get()) + if (mCell.get()->referenceAdded (parent, start, end)) + flagAsModified(); } CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget::getDropRequirements (CSVRender::WorldspaceWidget::dropType type) const diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index bb53408452..ee8377fae4 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -2,8 +2,10 @@ #define OPENCS_VIEW_UNPAGEDWORLDSPACEWIDGET_H #include +#include #include "worldspacewidget.hpp" +#include "cell.hpp" class QModelIndex; @@ -25,6 +27,8 @@ namespace CSVRender std::string mCellId; CSMWorld::IdTable *mCellsModel; + CSMWorld::IdTable *mReferenceablesModel; + std::auto_ptr mCell; void update(); @@ -37,6 +41,21 @@ namespace CSVRender virtual void handleDrop(const std::vector& data); + private: + + virtual void referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight); + + virtual void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + virtual void referenceableAdded (const QModelIndex& index, int start, int end); + + virtual void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + virtual void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + virtual void referenceAdded (const QModelIndex& index, int start, int end); + private slots: void cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index afc6b4603c..fee2f0a160 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -10,15 +10,30 @@ #include "../world/scenetoolmode.hpp" #include -CSVRender::WorldspaceWidget::WorldspaceWidget (const CSMDoc::Document& document, QWidget* parent) +CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) : SceneWidget (parent), mDocument(document) { - Ogre::Entity* ent = getSceneManager()->createEntity("cube", Ogre::SceneManager::PT_CUBE); - ent->setMaterialName("BaseWhite"); - - getSceneManager()->getRootSceneNode()->attachObject(ent); - setAcceptDrops(true); + + QAbstractItemModel *referenceables = + document.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables); + + connect (referenceables, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (referenceableDataChanged (const QModelIndex&, const QModelIndex&))); + connect (referenceables, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (referenceableAboutToBeRemoved (const QModelIndex&, int, int))); + connect (referenceables, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (referenceableAdded (const QModelIndex&, int, int))); + + QAbstractItemModel *references = + document.getData().getTableModel (CSMWorld::UniversalId::Type_References); + + connect (references, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (referenceDataChanged (const QModelIndex&, const QModelIndex&))); + connect (references, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (referenceAboutToBeRemoved (const QModelIndex&, int, int))); + connect (references, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (referenceAdded (const QModelIndex&, int, int))); } void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index a14e039154..280f9436d4 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -47,7 +47,7 @@ namespace CSVRender ignored //either mixed cells, or not cells }; - WorldspaceWidget (const CSMDoc::Document& document, QWidget *parent = 0); + WorldspaceWidget (CSMDoc::Document& document, QWidget *parent = 0); CSVWorld::SceneToolMode *makeNavigationSelector (CSVWorld::SceneToolbar *parent); ///< \attention The created tool is not added to the toolbar (via addTool). Doing that @@ -79,6 +79,19 @@ namespace CSVRender void selectNavigationMode (const std::string& mode); + virtual void referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) {} + + virtual void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) {} + + virtual void referenceableAdded (const QModelIndex& index, int start, int end) {} + + virtual void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) {} + + virtual void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) {} + + virtual void referenceAdded (const QModelIndex& index, int start, int end) {} + signals: void closeRequest(); From 2fe2def64c0822e6ac7992484cb7de859a61d03b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 29 Jun 2014 16:00:06 +0200 Subject: [PATCH 226/226] added cell rendering in paged worldspaces --- .../view/render/pagedworldspacewidget.cpp | 134 +++++++++++++++++- .../view/render/pagedworldspacewidget.hpp | 25 ++++ apps/opencs/view/render/scenewidget.cpp | 7 +- apps/opencs/view/render/scenewidget.hpp | 2 + apps/opencs/view/render/worldspacewidget.hpp | 12 +- 5 files changed, 171 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index a3f34d218f..20cd6abb5a 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -3,14 +3,137 @@ #include +#include + #include -#include +#include "../../model/world/tablemimedata.hpp" +#include "../../model/world/idtable.hpp" + +bool CSVRender::PagedWorldspaceWidget::adjustCells() +{ + bool modified = false; + bool setCamera = false; + + { + // remove + std::map::iterator iter (mCells.begin()); + + while (iter!=mCells.end()) + { + if (!mSelection.has (iter->first)) + { + delete iter->second; + mCells.erase (iter++); + modified = true; + } + else + ++iter; + } + } + + if (mCells.begin()==mCells.end()) + setCamera = true; + + // add + for (CSMWorld::CellSelection::Iterator iter (mSelection.begin()); iter!=mSelection.end(); + ++iter) + { + if (mCells.find (*iter)==mCells.end()) + { + if (setCamera) + { + setCamera = false; + getCamera()->setPosition (8192*iter->getX()+4096, 8192*iter->getY()+4096, 0); + } + + mCells.insert (std::make_pair (*iter, + new Cell (mDocument.getData(), getSceneManager(), + iter->getId ("std::default")))); + + modified = true; + } + } + + return modified; +} + +void CSVRender::PagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + if (iter->second->referenceableDataChanged (topLeft, bottomRight)) + flagAsModified(); +} + +void CSVRender::PagedWorldspaceWidget::referenceableAboutToBeRemoved ( + const QModelIndex& parent, int start, int end) +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + if (iter->second->referenceableAboutToBeRemoved (parent, start, end)) + flagAsModified(); +} + +void CSVRender::PagedWorldspaceWidget::referenceableAdded (const QModelIndex& parent, + int start, int end) +{ + CSMWorld::IdTable& referenceables = dynamic_cast ( + *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables)); + + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + { + QModelIndex topLeft = referenceables.index (start, 0); + QModelIndex bottomRight = + referenceables.index (end, referenceables.columnCount()); + + if (iter->second->referenceableDataChanged (topLeft, bottomRight)) + flagAsModified(); + } +} + +void CSVRender::PagedWorldspaceWidget::referenceDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + if (iter->second->referenceDataChanged (topLeft, bottomRight)) + flagAsModified(); +} + +void CSVRender::PagedWorldspaceWidget::referenceAboutToBeRemoved (const QModelIndex& parent, + int start, int end) +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + if (iter->second->referenceAboutToBeRemoved (parent, start, end)) + flagAsModified(); +} + +void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent, int start, + int end) +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + if (iter->second->referenceAdded (parent, start, end)) + flagAsModified(); +} + + CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc::Document& document) -: WorldspaceWidget (document, parent) +: WorldspaceWidget (document, parent), mDocument (document) {} +CSVRender::PagedWorldspaceWidget::~PagedWorldspaceWidget() +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + delete iter->second; +} + void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) { if (!hint.empty()) @@ -47,6 +170,10 @@ void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) void CSVRender::PagedWorldspaceWidget::setCellSelection (const CSMWorld::CellSelection& selection) { mSelection = selection; + + if (adjustCells()) + flagAsModified(); + emit cellSelectionChanged (mSelection); } @@ -72,6 +199,9 @@ void CSVRender::PagedWorldspaceWidget::handleDrop (const std::vector< CSMWorld:: } if (selectionChanged) { + if (adjustCells()) + flagAsModified(); + emit cellSelectionChanged(mSelection); } } diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 0a73c791cb..7f9a66f82b 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -1,9 +1,12 @@ #ifndef OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H #define OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H +#include + #include "../../model/world/cellselection.hpp" #include "worldspacewidget.hpp" +#include "cell.hpp" namespace CSVRender { @@ -11,12 +14,32 @@ namespace CSVRender { Q_OBJECT + CSMDoc::Document& mDocument; CSMWorld::CellSelection mSelection; + std::map mCells; private: std::pair getCoordinatesFromId(const std::string& record) const; + /// Bring mCells into sync with mSelection again. + /// + /// \return Any cells added or removed? + bool adjustCells(); + + virtual void referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight); + + virtual void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + virtual void referenceableAdded (const QModelIndex& index, int start, int end); + + virtual void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + virtual void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + virtual void referenceAdded (const QModelIndex& index, int start, int end); + public: PagedWorldspaceWidget (QWidget *parent, CSMDoc::Document& document); @@ -24,6 +47,8 @@ namespace CSVRender /// no cells are displayed. The cells to be displayed will be specified later through /// hint system. + virtual ~PagedWorldspaceWidget(); + void useViewHint (const std::string& hint); void setCellSelection (const CSMWorld::CellSelection& selection); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index eccaebd000..76f6db385d 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -43,7 +43,7 @@ namespace CSVRender mCamera->setPosition (300, 0, 0); mCamera->lookAt (0, 0, 0); mCamera->setNearClipDistance (0.1); - mCamera->setFarClipDistance (30000); + mCamera->setFarClipDistance (300000); ///< \todo make this configurable mCamera->roll (Ogre::Degree (90)); setLighting (&mLightingDay); @@ -137,6 +137,11 @@ namespace CSVRender return mSceneMgr; } + Ogre::Camera *SceneWidget::getCamera() + { + return mCamera; + } + void SceneWidget::flagAsModified() { mUpdate = true; diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 7f8f104f15..f6b41942f7 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -49,6 +49,8 @@ namespace CSVRender Ogre::SceneManager *getSceneManager(); + Ogre::Camera *getCamera(); + void flagAsModified(); void setDefaultAmbient (const Ogre::ColourValue& colour); diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 280f9436d4..2af90b0feb 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -80,17 +80,17 @@ namespace CSVRender void selectNavigationMode (const std::string& mode); virtual void referenceableDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) {} + const QModelIndex& bottomRight) = 0; - virtual void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) {} + virtual void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; - virtual void referenceableAdded (const QModelIndex& index, int start, int end) {} + virtual void referenceableAdded (const QModelIndex& index, int start, int end) = 0; - virtual void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) {} + virtual void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; - virtual void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) {} + virtual void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; - virtual void referenceAdded (const QModelIndex& index, int start, int end) {} + virtual void referenceAdded (const QModelIndex& index, int start, int end) = 0; signals: