From 414f7ea02cea63ac239fa480d189090dcbcb9fb2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 19 Nov 2014 12:09:40 +0100 Subject: [PATCH 001/167] Ignore mouse movements during video playback (Fixes #2139) --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 29b166a6a7..6cb3a5ec59 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -603,7 +603,7 @@ namespace MWInput MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel); } - if (mMouseLookEnabled) + if (mMouseLookEnabled && !mControlsDisabled) { resetIdleTime(); From c1e6b8608b619b9231adf2faf881b4da66fda45e Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 23 Nov 2014 16:17:58 +0100 Subject: [PATCH 002/167] Always create a skeleton if there's an "ArrowBone" node (Fixes #2117) --- apps/openmw/mwrender/weaponanimation.cpp | 1 + components/nifogre/skeleton.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index a409e88073..c59a93feba 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -55,6 +55,7 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor) return; std::string model = ammo->getClass().getModel(*ammo); + assert(weapon->mSkelBase && "Need a skeleton to attach the arrow to"); mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", weapon->mSkelBase->getParentSceneNode(), model); configureAddedObject(mAmmunition, *ammo, MWWorld::InventoryStore::Slot_Ammunition); } diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp index 4f6921d89d..a3fade5b2c 100644 --- a/components/nifogre/skeleton.cpp +++ b/components/nifogre/skeleton.cpp @@ -111,7 +111,7 @@ bool NIFSkeletonLoader::needSkeleton(const Nif::Node *node) /* We need to be a little aggressive here, since some NIFs have a crap-ton * of nodes and Ogre only supports 256 bones. We will skip a skeleton if: * There are no bones used for skinning, there are no keyframe controllers, there - * are no nodes named "AttachLight", and the tree consists of NiNode, + * are no nodes named "AttachLight" or "ArrowBone", and the tree consists of NiNode, * NiTriShape, and RootCollisionNode types only. */ if(node->boneTrafo) @@ -126,7 +126,7 @@ bool NIFSkeletonLoader::needSkeleton(const Nif::Node *node) } while(!(ctrl=ctrl->next).empty()); } - if (node->name == "AttachLight") + if (node->name == "AttachLight" || node->name == "ArrowBone") return true; if(node->recType == Nif::RC_NiNode || node->recType == Nif::RC_RootCollisionNode) From ada4e375564d72d2bf739ee0b46622fe590fbff6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 24 Nov 2014 01:02:18 +0100 Subject: [PATCH 003/167] Fix race preview texture not being destroyed properly (Fixes #2098) --- apps/openmw/mwgui/race.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 2bfc4f1416..444b572b68 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -162,6 +162,9 @@ namespace MWGui void RaceDialog::close() { + mPreviewImage->setImageTexture(""); + const std::string textureName = "CharacterHeadPreview"; + MyGUI::RenderManager::getInstance().destroyTexture(MyGUI::RenderManager::getInstance().getTexture(textureName)); mPreview.reset(NULL); } From d36dfbe7798ee4e8e5b4fa86361d7c4218257efc Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 25 Nov 2014 02:41:54 +0100 Subject: [PATCH 004/167] Apply the frame time cap to Ogre's ControllerManager (Fixes #2154) This fixes particle systems getting out of whack due to a particularly long frame time, e.g. after a loading screen. --- apps/openmw/engine.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 2eebb8c28f..d7b23c0966 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -79,8 +79,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) { try { - float frametime = std::min(evt.timeSinceLastFrame, 0.2f); - + float frametime = evt.timeSinceLastFrame; mEnvironment.setFrameDuration (frametime); // update input @@ -478,9 +477,15 @@ void OMW::Engine::go() } // Start the main rendering loop + Ogre::Timer timer; while (!MWBase::Environment::get().getStateManager()->hasQuitRequest()) - Ogre::Root::getSingleton().renderOneFrame(); + { + float dt = timer.getMilliseconds()/1000.f; + dt = std::min(dt, 0.2f); + timer.reset(); + Ogre::Root::getSingleton().renderOneFrame(dt); + } // Save user settings settings.saveUser(settingspath); From ff8bdd74ed32e2b2151156cd9149648d94bb094e Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 25 Nov 2014 15:54:55 +0100 Subject: [PATCH 005/167] Fix strange bitflags handling --- apps/wizard/componentselectionpage.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index d372f677d0..956f0f2379 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -62,7 +62,7 @@ void Wizard::ComponentSelectionPage::initializePage() if (field(QLatin1String("installation.new")).toBool() == true) { - morrowindItem->setFlags(morrowindItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); + morrowindItem->setFlags(morrowindItem->flags() & ~Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); morrowindItem->setData(Qt::CheckStateRole, Qt::Checked); componentsList->addItem(morrowindItem); @@ -77,7 +77,7 @@ void Wizard::ComponentSelectionPage::initializePage() if (mWizard->mInstallations[path].hasMorrowind) { morrowindItem->setText(tr("Morrowind\t\t(installed)")); - morrowindItem->setFlags(morrowindItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); + morrowindItem->setFlags(morrowindItem->flags() & ~Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); morrowindItem->setData(Qt::CheckStateRole, Qt::Unchecked); } else { morrowindItem->setText(tr("Morrowind")); @@ -88,7 +88,7 @@ void Wizard::ComponentSelectionPage::initializePage() if (mWizard->mInstallations[path].hasTribunal) { tribunalItem->setText(tr("Tribunal\t\t(installed)")); - tribunalItem->setFlags(tribunalItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); + tribunalItem->setFlags(tribunalItem->flags() & ~Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); tribunalItem->setData(Qt::CheckStateRole, Qt::Unchecked); } else { tribunalItem->setText(tr("Tribunal")); @@ -99,7 +99,7 @@ void Wizard::ComponentSelectionPage::initializePage() if (mWizard->mInstallations[path].hasBloodmoon) { bloodmoonItem->setText(tr("Bloodmoon\t\t(installed)")); - bloodmoonItem->setFlags(bloodmoonItem->flags() & !Qt::ItemIsEnabled & Qt::ItemIsUserCheckable); + bloodmoonItem->setFlags(bloodmoonItem->flags() & ~Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); bloodmoonItem->setData(Qt::CheckStateRole, Qt::Unchecked); } else { bloodmoonItem->setText(tr("Bloodmoon")); From 0a466ad643887b8fe1fdd0770736652a1dae2904 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 28 Nov 2014 14:45:35 +0100 Subject: [PATCH 006/167] Make recalculation of magicka less aggressive (Fixes #2155) --- apps/openmw/mwmechanics/actors.cpp | 9 ------ apps/openmw/mwmechanics/creaturestats.cpp | 34 +++++++++++++++-------- apps/openmw/mwmechanics/creaturestats.hpp | 3 +- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 2e835d57e4..a3cfbfd49a 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -396,11 +396,7 @@ namespace MWMechanics { CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); - int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getModified(); int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified(); - int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getModified(); - int agility = creatureStats.getAttribute(ESM::Attribute::Agility).getModified(); - int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getModified(); float base = 1.f; if (ptr.getCellRef().getRefId() == "player") @@ -415,11 +411,6 @@ namespace MWMechanics float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); magicka.modify(diff); creatureStats.setMagicka(magicka); - - DynamicStat fatigue = creatureStats.getFatigue(); - diff = (strength+willpower+agility+endurance) - fatigue.getBase(); - fatigue.modify(diff); - creatureStats.setFatigue(fatigue); } void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, bool sleep) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index c4d316ad62..6d342833e6 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -20,7 +20,7 @@ namespace MWMechanics mAttacked (false), mAttackingOrSpell(false), mIsWerewolf(false), - mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mKnockdownOneFrame(false), + mFallHeight(0), mRecalcMagicka(false), mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f), mLastRestock(0,0), mGoldPool(0), mActorId(-1), @@ -147,10 +147,22 @@ namespace MWMechanics if (value != currentValue) { - if (index != ESM::Attribute::Luck - && index != ESM::Attribute::Personality - && index != ESM::Attribute::Speed) - mRecalcDynamicStats = true; + if (index == ESM::Attribute::Intelligence) + mRecalcMagicka = true; + else if (index == ESM::Attribute::Strength || + index == ESM::Attribute::Willpower || + index == ESM::Attribute::Agility || + index == ESM::Attribute::Endurance) + { + int strength = getAttribute(ESM::Attribute::Strength).getModified(); + int willpower = getAttribute(ESM::Attribute::Willpower).getModified(); + int agility = getAttribute(ESM::Attribute::Agility).getModified(); + int endurance = getAttribute(ESM::Attribute::Endurance).getModified(); + DynamicStat fatigue = getFatigue(); + float diff = (strength+willpower+agility+endurance) - fatigue.getBase(); + fatigue.modify(diff); + setFatigue(fatigue); + } } if(!mIsWerewolf) @@ -199,7 +211,7 @@ namespace MWMechanics { if (effects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier() != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).getModifier()) - mRecalcDynamicStats = true; + mRecalcMagicka = true; mMagicEffects.setModifiers(effects); } @@ -360,9 +372,9 @@ namespace MWMechanics bool CreatureStats::needToRecalcDynamicStats() { - if (mRecalcDynamicStats) + if (mRecalcMagicka) { - mRecalcDynamicStats = false; + mRecalcMagicka = false; return true; } return false; @@ -370,7 +382,7 @@ namespace MWMechanics void CreatureStats::setNeedRecalcDynamicStats(bool val) { - mRecalcDynamicStats = val; + mRecalcMagicka = val; } void CreatureStats::setKnockedDown(bool value) @@ -497,7 +509,7 @@ namespace MWMechanics state.mAttackStrength = mAttackStrength; state.mFallHeight = mFallHeight; // TODO: vertical velocity (move from PhysicActor to CreatureStats?) state.mLastHitObject = mLastHitObject; - state.mRecalcDynamicStats = mRecalcDynamicStats; + state.mRecalcDynamicStats = mRecalcMagicka; state.mDrawState = mDrawState; state.mLevel = mLevel; state.mActorId = mActorId; @@ -545,7 +557,7 @@ namespace MWMechanics mAttackStrength = state.mAttackStrength; mFallHeight = state.mFallHeight; mLastHitObject = state.mLastHitObject; - mRecalcDynamicStats = state.mRecalcDynamicStats; + mRecalcMagicka = state.mRecalcDynamicStats; mDrawState = DrawState_(state.mDrawState); mLevel = state.mLevel; mActorId = state.mActorId; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 5e169ffb03..f830dd310c 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -53,8 +53,7 @@ namespace MWMechanics std::string mLastHitObject; // The last object to hit this actor - // Do we need to recalculate stats derived from attributes or other factors? - bool mRecalcDynamicStats; + bool mRecalcMagicka; // For merchants: the last time items were restocked and gold pool refilled. MWWorld::TimeStamp mLastRestock; From d7220cdc2fd45f196467aed8ee4fbe23741df211 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 28 Nov 2014 14:48:03 +0100 Subject: [PATCH 007/167] Do not allow decrease below zero in modCurrentMagicka and modCurrentHealth (Fixes #2158) --- apps/openmw/mwscript/statsextensions.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index befb8d82ea..d5647db10d 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -278,7 +278,9 @@ namespace MWScript MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) .getDynamic (mIndex)); - stat.setCurrent (diff + current, true); + // for fatigue, a negative current value is allowed and means the actor will be knocked down + bool allowDecreaseBelowZero = (mIndex == 2); + stat.setCurrent (diff + current, allowDecreaseBelowZero); ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); } From ea8f617508ceb536225c8a097c6bcc1bf7b7b7f0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 28 Nov 2014 15:54:38 +0100 Subject: [PATCH 008/167] Add missing player control enabled checks (Fixes #2152) --- apps/openmw/mwinput/inputmanagerimp.cpp | 28 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 6cb3a5ec59..68682abeac 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -190,14 +190,12 @@ namespace MWInput int action = channel->getNumber(); - if (action == A_Use) + if (mControlSwitch["playercontrols"]) { - mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).setAttackingOrSpell(currentValue); - } - - if (action == A_Jump) - { - mAttemptJump = (currentValue == 1.0 && previousValue == 0.0); + if (action == A_Use) + mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).setAttackingOrSpell(currentValue); + else if (action == A_Jump) + mAttemptJump = (currentValue == 1.0 && previousValue == 0.0); } if (currentValue == 1) @@ -622,7 +620,7 @@ namespace MWInput mPlayer->pitch(y); } - if (arg.zrel && mControlSwitch["playerviewswitch"]) //Check to make sure you are allowed to zoomout and there is a change + if (arg.zrel && mControlSwitch["playerviewswitch"] && mControlSwitch["playercontrols"]) //Check to make sure you are allowed to zoomout and there is a change { MWBase::Environment::get().getWorld()->changeVanityModeScale(arg.zrel); MWBase::Environment::get().getWorld()->setCameraDistance(arg.zrel, true, true); @@ -680,7 +678,7 @@ namespace MWInput if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; // Not allowed before the magic window is accessible - if (!mControlSwitch["playermagic"]) + if (!mControlSwitch["playermagic"] || !mControlSwitch["playercontrols"]) return; // Not allowed if no spell selected @@ -701,7 +699,7 @@ namespace MWInput if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; // Not allowed before the inventory window is accessible - if (!mControlSwitch["playerfighting"]) + if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"]) return; MWMechanics::DrawState_ state = mPlayer->getDrawState(); @@ -713,6 +711,9 @@ namespace MWInput void InputManager::rest() { + if (!mControlSwitch["playercontrols"]) + return; + if (!MWBase::Environment::get().getWindowManager()->getRestEnabled () || MWBase::Environment::get().getWindowManager()->isGuiMode ()) return; @@ -734,6 +735,9 @@ namespace MWInput void InputManager::toggleInventory() { + if (!mControlSwitch["playercontrols"]) + return; + if (MyGUI::InputManager::getInstance ().isModalAny()) return; @@ -770,6 +774,8 @@ namespace MWInput void InputManager::toggleJournal() { + if (!mControlSwitch["playercontrols"]) + return; if (MyGUI::InputManager::getInstance ().isModalAny()) return; @@ -787,6 +793,8 @@ namespace MWInput void InputManager::quickKey (int index) { + if (!mControlSwitch["playercontrols"]) + return; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if (player.getClass().getNpcStats(player).isWerewolf()) { From 4fd3a994e9eb199ce8154ac9fba0f22242824e54 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 28 Nov 2014 16:02:43 +0100 Subject: [PATCH 009/167] Add model and script information to BetaComment --- apps/openmw/mwscript/miscextensions.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index c7d221139c..e8784f8de2 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -964,6 +964,9 @@ namespace MWScript msg << "Grid: " << cell->getCell()->getGridX() << " " << cell->getCell()->getGridY() << std::endl; Ogre::Vector3 pos (ptr.getRefData().getPosition().pos); msg << "Coordinates: " << pos << std::endl; + msg << "Model: " << ptr.getClass().getModel(ptr) << std::endl; + if (!ptr.getClass().getScript(ptr).empty()) + msg << "Script: " << ptr.getClass().getScript(ptr) << std::endl; } std::string notes = runtime.getStringLiteral (runtime[0].mInteger); From 5f5fcc2feff49cce7933b6b5df79d8c856e015ae Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 28 Nov 2014 17:15:31 +0100 Subject: [PATCH 010/167] Make PlayGroup use an indefinite number of loops (Fixes #2156) --- apps/openmw/mwmechanics/character.cpp | 2 ++ apps/openmw/mwrender/animation.cpp | 11 +++++++++++ apps/openmw/mwrender/animation.hpp | 4 ++++ apps/openmw/mwscript/animationextensions.cpp | 2 +- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 72a9bbfde2..11484ac49a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1645,6 +1645,8 @@ void CharacterController::playGroup(const std::string &groupname, int mode, int } else if(mode == 0) { + if (!mAnimQueue.empty()) + mAnimation->stopLooping(mAnimQueue.front().first); mAnimQueue.resize(1); mAnimQueue.push_back(std::make_pair(groupname, count-1)); } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 29db648d03..fc147ce59a 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -842,6 +842,17 @@ void Animation::changeGroups(const std::string &groupname, int groups) return; } } + +void Animation::stopLooping(const std::string& groupname) +{ + AnimStateMap::iterator stateiter = mStates.find(groupname); + if(stateiter != mStates.end()) + { + stateiter->second.mLoopCount = 0; + return; + } +} + void Animation::play(const std::string &groupname, int priority, int groups, bool autodisable, float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops) { if(!mSkelBase || mAnimSources.empty()) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 8ca3582dc1..a8a9ee11e6 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -260,6 +260,10 @@ public: float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops); + /** If the given animation group is currently playing, set its remaining loop count to '0'. + */ + void stopLooping(const std::string& groupName); + /** Adjust the speed multiplier of an already playing animation. */ void adjustSpeedMult (const std::string& groupname, float speedmult); diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index 52de8e0421..613cf7d24e 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -55,7 +55,7 @@ namespace MWScript throw std::runtime_error ("animation mode out of range"); } - MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, 1); + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, std::numeric_limits::max()); } }; From ad38345de4aa9cf82e0042fafb93d63582429854 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 28 Nov 2014 22:23:42 +0100 Subject: [PATCH 011/167] Clean up listener in destruction of OgrePlatform (Fixes #2145) --- extern/shiny/Platforms/Ogre/OgrePlatform.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp index eab8f93e28..aa01c8ba14 100644 --- a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp +++ b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp @@ -54,6 +54,8 @@ namespace sh OgrePlatform::~OgrePlatform () { + Ogre::MaterialManager::getSingleton().removeListener(this); + delete sSerializer; } From 33c454e0734d0592eb46ae416c1e775185922dc8 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 29 Nov 2014 20:39:25 +1100 Subject: [PATCH 012/167] Check whether any subrecords remain after skipping moved references. Should resolve bug #2070. --- components/esm/loadcell.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index bbd6696f11..f8966ad207 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -182,6 +182,13 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool& deleted) // That should be it, I haven't seen any other fields yet. } + // If moved references are not handled then it is possible that CellRef::load() can lead + // to strange results because ESMReader::isNextSub("xxx") will always return false when + // there are no more subrecords. When moved references are handled properly by OpenCS + // below 2 lines can be removed. + if (!esm.hasMoreSubs()) + return false; + ref.load (esm); // Identify references belonging to a parent file and adapt the ID accordingly. From 5fa7536427d724c9ffbec43e29e56c44d6b5dad9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 29 Nov 2014 16:50:42 +0100 Subject: [PATCH 013/167] Fix incorrect box shape translation reset Fixes incorrect placement of collision box for "azura spirit_trib" --- components/nifbullet/bulletnifloader.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index b366216de1..3abe0c1716 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -133,8 +133,6 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) mResourceName = mShape->getName(); mShape->mCollide = false; mBoundingBox = NULL; - mShape->mBoxTranslation = Ogre::Vector3(0,0,0); - mShape->mBoxRotation = Ogre::Quaternion::IDENTITY; mStaticMesh = NULL; mCompoundShape = NULL; From d3d5c1fd1546ae3d4731453c920d74d9fda2eabb Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 29 Nov 2014 16:51:45 +0100 Subject: [PATCH 014/167] Disable debug drawing for raycasting shapes This reduces performance too much, and seeing both shapes overlaid on top of each other is confusing anyway. This can be reintroduced via a setting if necessary. --- libs/openengine/bullet/physic.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index d7db81595b..0b08e28d95 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -514,6 +514,7 @@ namespace Physic assert (mRaycastingObjectMap.find(name) == mRaycastingObjectMap.end()); mRaycastingObjectMap[name] = body; mDynamicsWorld->addRigidBody(body,CollisionType_Raycasting,CollisionType_Raycasting|CollisionType_Projectile); + body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_DISABLE_VISUALIZE_OBJECT); } return body; From 5ae1554a75cf1433e567355609346fedd1cb69fc Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 30 Nov 2014 04:00:06 +1100 Subject: [PATCH 015/167] Simplify skipping moved references (thanks scrawl) --- components/esm/loadcell.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index f8966ad207..347f3fde4a 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -177,17 +177,10 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool& deleted) // NOTE: We should not need this check. It is a safety check until we have checked // more plugins, and how they treat these moved references. if (esm.isNextSub("MVRF")) { - esm.skipRecord(); // skip MVRF - esm.skipRecord(); // skip CNDT - // That should be it, I haven't seen any other fields yet. - } - - // If moved references are not handled then it is possible that CellRef::load() can lead - // to strange results because ESMReader::isNextSub("xxx") will always return false when - // there are no more subrecords. When moved references are handled properly by OpenCS - // below 2 lines can be removed. - if (!esm.hasMoreSubs()) + // skip rest of cell record (moved references), they are handled elsewhere + esm.skipRecord(); // skip MVRF, CNDT return false; + } ref.load (esm); From 4a9d2038fac8983b23b74b1c4a49914d218a0b4e Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 30 Nov 2014 14:33:39 +0100 Subject: [PATCH 016/167] load land for non-base content files immediately --- apps/opencs/model/world/data.cpp | 13 ++++++++++++- apps/opencs/model/world/idcollection.hpp | 18 +++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 9cb0299c4c..87dbaa16d1 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -713,7 +713,18 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Stage::Messages& messages) case ESM::REC_PGRD: mPathgrids.load (*mReader, mBase); break; case ESM::REC_LTEX: mLandTextures.load (*mReader, mBase); break; - case ESM::REC_LAND: mLand.load(*mReader, mBase); break; + + case ESM::REC_LAND: + { + int index = mLand.load(*mReader, mBase); + + if (index!=-1 && !mBase) + mLand.getRecord (index).mModified.mLand->loadData ( + ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | + ESM::Land::DATA_VTEX); + + break; + } case ESM::REC_CELL: { diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 0129ba3d8d..f00ea447aa 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -15,12 +15,15 @@ namespace CSMWorld public: - void load (ESM::ESMReader& reader, bool base); + /// \return Index of loaded record (-1 if no record was loaded) + int load (ESM::ESMReader& reader, bool base); /// \param index Index at which the record can be found. /// Special values: -2 index unknown, -1 record does not exist yet and therefore /// does not have an index - void load (const ESXRecordT& record, bool base, int index = -2); + /// + /// \return index + int load (const ESXRecordT& record, bool base, int index = -2); bool tryDelete (const std::string& id); ///< Try deleting \a id. If the id does not exist or can't be deleted the call is ignored. @@ -36,7 +39,7 @@ namespace CSMWorld } template - void IdCollection::load (ESM::ESMReader& reader, bool base) + int IdCollection::load (ESM::ESMReader& reader, bool base) { std::string id = reader.getHNOString ("NAME"); @@ -64,6 +67,8 @@ namespace CSMWorld record.mState = RecordBase::State_Deleted; this->setRecord (index, record); } + + return -1; } else { @@ -88,12 +93,12 @@ namespace CSMWorld index = newIndex; } - load (record, base, index); + return load (record, base, index); } } template - void IdCollection::load (const ESXRecordT& record, bool base, + int IdCollection::load (const ESXRecordT& record, bool base, int index) { if (index==-2) @@ -106,6 +111,7 @@ namespace CSMWorld record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record2.mBase : record2.mModified) = record; + index = this->getSize(); this->appendRecord (record2); } else @@ -120,6 +126,8 @@ namespace CSMWorld this->setRecord (index, record2); } + + return index; } template From db17dbe3242938f3a1dc995c0924f4b4b149b59d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 30 Nov 2014 18:04:18 +0100 Subject: [PATCH 017/167] don't store esm readers for non-base content files --- apps/opencs/model/world/data.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 87dbaa16d1..737376f047 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -678,9 +678,15 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Stage::Messages& messages) if (!mReader->hasMoreRecs()) { - // Don't delete the Reader yet. Some record types store a reference to the Reader to handle on-demand loading - boost::shared_ptr ptr(mReader); - mReaders.push_back(ptr); + if (mBase) + { + // Don't delete the Reader yet. Some record types store a reference to the Reader to handle on-demand loading. + // We don't store non-base reader, because everything going into modified will be + // fully loaded during the initial loading process. + boost::shared_ptr ptr(mReader); + mReaders.push_back(ptr); + } + mReader = 0; mDialogue = 0; From 2720e5ea9df4aa2f8582bbb341792c24159218e7 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 1 Dec 2014 06:44:12 +1100 Subject: [PATCH 018/167] Remove PhysicsManager singleton and use shared_ptr instead. Resolves the issue where sometimes destructors were called in an unexpected sequence resulting in a crash while exiting the application. --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/editor.cpp | 3 +- apps/opencs/editor.hpp | 2 - apps/opencs/model/doc/document.cpp | 12 +- apps/opencs/model/doc/document.hpp | 9 ++ apps/opencs/view/doc/view.cpp | 3 - apps/opencs/view/doc/viewmanager.cpp | 6 +- apps/opencs/view/render/cell.cpp | 2 +- apps/opencs/view/render/cell.hpp | 6 +- apps/opencs/view/render/mousestate.cpp | 2 +- apps/opencs/view/render/mousestate.hpp | 3 +- apps/opencs/view/render/object.cpp | 2 +- apps/opencs/view/render/object.hpp | 7 +- .../view/render/pagedworldspacewidget.cpp | 2 +- .../view/render/unpagedworldspacewidget.cpp | 4 +- apps/opencs/view/render/worldspacewidget.cpp | 12 +- apps/opencs/view/render/worldspacewidget.hpp | 6 +- apps/opencs/view/world/physicsmanager.cpp | 110 ------------------ apps/opencs/view/world/physicsmanager.hpp | 54 --------- 19 files changed, 47 insertions(+), 200 deletions(-) delete mode 100644 apps/opencs/view/world/physicsmanager.cpp delete mode 100644 apps/opencs/view/world/physicsmanager.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 9d8dd89e1a..bdfefbb83f 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -68,7 +68,7 @@ opencs_units (view/world opencs_units_noqt (view/world subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate - scripthighlighter idvalidator dialoguecreator physicssystem physicsmanager + scripthighlighter idvalidator dialoguecreator physicssystem ) opencs_units (view/widget diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index bef83b8ac7..e756cb5dfc 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -21,7 +21,7 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) : mUserSettings (mCfgMgr), mOverlaySystem (0), mDocumentManager (mCfgMgr), - mViewManager (mDocumentManager), mPhysicsManager (0), + mViewManager (mDocumentManager), mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) { std::pair > config = readConfig(); @@ -34,7 +34,6 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) ogreInit.init ((mCfgMgr.getUserConfigPath() / "opencsOgre.log").string()); mOverlaySystem.reset (new CSVRender::OverlaySystem); - mPhysicsManager.reset (new CSVWorld::PhysicsManager); Bsa::registerResources (Files::Collections (config.first, !mFsStrict), config.second, true, mFsStrict); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index d55b0e873e..cd39d53a48 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -28,7 +28,6 @@ #include "view/settings/dialog.hpp" #include "view/render/overlaysystem.hpp" -#include "view/world/physicsmanager.hpp" namespace OgreInit { @@ -45,7 +44,6 @@ namespace CS Files::ConfigurationManager mCfgMgr; CSMSettings::UserSettings mUserSettings; std::auto_ptr mOverlaySystem; - std::auto_ptr mPhysicsManager; CSMDoc::DocumentManager mDocumentManager; CSVDoc::ViewManager mViewManager; CSVDoc::StartupDialogue mStartup; diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 4fdfd2e5e4..4abd67a505 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -9,6 +9,8 @@ #include #endif +#include "../../view/world/physicssystem.hpp" + void CSMDoc::Document::addGmsts() { static const char *gmstFloats[] = @@ -2253,7 +2255,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), mSaving (*this, mProjectPath, encoding), - mRunner (mProjectPath) + mRunner (mProjectPath), mPhysics(boost::shared_ptr()) { if (mContentFiles.empty()) throw std::runtime_error ("Empty content file sequence"); @@ -2464,3 +2466,11 @@ void CSMDoc::Document::progress (int current, int max, int type) { emit progress (current, max, type, 1, this); } + +boost::shared_ptr CSMDoc::Document::getPhysics () +{ + if(!mPhysics) + mPhysics = boost::shared_ptr (new CSVWorld::PhysicsSystem()); + + return mPhysics; +} diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index c5f6d10067..e5aa5eea58 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -3,6 +3,7 @@ #include +#include #include #include @@ -39,6 +40,11 @@ namespace CSMWorld class ResourcesManager; } +namespace CSVWorld +{ + class PhysicsSystem; +} + namespace CSMDoc { class Document : public QObject @@ -57,6 +63,7 @@ namespace CSMDoc boost::filesystem::path mResDir; Blacklist mBlacklist; Runner mRunner; + boost::shared_ptr mPhysics; // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is // using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late. @@ -129,6 +136,8 @@ namespace CSMDoc QTextDocument *getRunLog(); + boost::shared_ptr getPhysics(); + signals: void stateChanged (int state, CSMDoc::Document *document); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index d64d36aec7..0d2b6060ef 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -16,7 +16,6 @@ #include "../../model/world/idtable.hpp" #include "../world/subviews.hpp" -#include "../world/physicsmanager.hpp" #include "../tools/subviews.hpp" @@ -407,8 +406,6 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to mSubViewFactory.add (CSMWorld::UniversalId::Type_RunLog, new SubViewFactory); connect (mOperations, SIGNAL (abortOperation (int)), this, SLOT (abortOperation (int))); - - CSVWorld::PhysicsManager::instance()->setupPhysics(document); } CSVDoc::View::~View() diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index c4fd668843..55fd38c182 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include "../../model/doc/documentmanager.hpp" #include "../../model/doc/document.hpp" #include "../../model/world/columns.hpp" @@ -16,7 +18,6 @@ #include "../world/vartypedelegate.hpp" #include "../world/recordstatusdelegate.hpp" #include "../world/idtypedelegate.hpp" -#include "../world/physicsmanager.hpp" #include "../../model/settings/usersettings.hpp" @@ -219,7 +220,8 @@ void CSVDoc::ViewManager::removeDocAndView (CSMDoc::Document *document) mDocumentManager.removeDocument(document); (*iter)->deleteLater(); mViews.erase (iter); - CSVWorld::PhysicsManager::instance()->removeDocument(document); + // cleanup global resources used by OEngine + delete OEngine::Physic::BulletShapeManager::getSingletonPtr(); updateIndices(); return; diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 75e11cc10a..1fb7809be1 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -60,7 +60,7 @@ bool CSVRender::Cell::addObjects (int start, int end) } CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, - const std::string& id, CSVWorld::PhysicsSystem *physics, const Ogre::Vector3& origin) + const std::string& id, boost::shared_ptr physics, const Ogre::Vector3& origin) : mData (data), mId (Misc::StringUtils::lowerCase (id)), mSceneMgr(sceneManager), mPhysics(physics) { mCellNode = sceneManager->getRootSceneNode()->createChildSceneNode(); diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index d38f0c68d1..9f38b0d9f3 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include @@ -38,7 +40,7 @@ namespace CSVRender Ogre::SceneNode *mCellNode; std::map mObjects; std::auto_ptr mTerrain; - CSVWorld::PhysicsSystem *mPhysics; + boost::shared_ptr mPhysics; Ogre::SceneManager *mSceneMgr; int mX; int mY; @@ -56,7 +58,7 @@ namespace CSVRender public: Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, const std::string& id, - CSVWorld::PhysicsSystem *physics, const Ogre::Vector3& origin = Ogre::Vector3 (0, 0, 0)); + boost::shared_ptr physics, const Ogre::Vector3& origin = Ogre::Vector3 (0, 0, 0)); ~Cell(); diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index 45e846f74a..988819fcb1 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -56,7 +56,7 @@ namespace CSVRender // MouseState::MouseState(WorldspaceWidget *parent) - : mParent(parent), mPhysics(parent->getPhysics()), mSceneManager(parent->getSceneManager()) + : mParent(parent), mPhysics(parent->mDocument.getPhysics()), mSceneManager(parent->getSceneManager()) , mCurrentObj(""), mMouseState(Mouse_Default), mOldPos(0,0), mMouseEventTimer(0), mPlane(0) , mGrabbedSceneNode(""), mOrigObjPos(Ogre::Vector3()), mOrigMousePos(Ogre::Vector3()) , mCurrentMousePos(Ogre::Vector3()), mOffset(0.0f) diff --git a/apps/opencs/view/render/mousestate.hpp b/apps/opencs/view/render/mousestate.hpp index 27907bb331..70e18427f3 100644 --- a/apps/opencs/view/render/mousestate.hpp +++ b/apps/opencs/view/render/mousestate.hpp @@ -2,6 +2,7 @@ #define OPENCS_VIEW_MOUSESTATE_H #include +#include #include #include @@ -43,7 +44,7 @@ namespace CSVRender MouseStates mMouseState; WorldspaceWidget *mParent; - CSVWorld::PhysicsSystem *mPhysics; // local copy + boost::shared_ptr mPhysics; Ogre::SceneManager *mSceneManager; // local copy QPoint mOldPos; diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 21219db8f4..af3777d0c8 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -132,7 +132,7 @@ const CSMWorld::CellRef& CSVRender::Object::getReference() const } CSVRender::Object::Object (const CSMWorld::Data& data, Ogre::SceneNode *cellNode, - const std::string& id, bool referenceable, CSVWorld::PhysicsSystem *physics, + const std::string& id, bool referenceable, boost::shared_ptr physics, bool forceBaseToZero) : mData (data), mBase (0), mForceBaseToZero (forceBaseToZero), mPhysics(physics) { diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index eba2dc8148..05a32fbeab 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -1,6 +1,8 @@ #ifndef OPENCS_VIEW_OBJECT_H #define OPENCS_VIEW_OBJECT_H +#include + #include class QModelIndex; @@ -31,7 +33,7 @@ namespace CSVRender Ogre::SceneNode *mBase; NifOgre::ObjectScenePtr mObject; bool mForceBaseToZero; - CSVWorld::PhysicsSystem *mPhysics; + boost::shared_ptr mPhysics; /// Not implemented Object (const Object&); @@ -58,7 +60,8 @@ namespace CSVRender Object (const CSMWorld::Data& data, Ogre::SceneNode *cellNode, const std::string& id, bool referenceable, - CSVWorld::PhysicsSystem *physics = NULL, bool forceBaseToZero = false); + boost::shared_ptr physics = boost::shared_ptr (), + bool forceBaseToZero = false); /// \param forceBaseToZero If this is a reference ignore the coordinates and place /// it at 0, 0, 0 instead. diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index e5d5428581..bae0fcacf4 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -111,7 +111,7 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() mCells.find (*iter)==mCells.end()) { Cell *cell = new Cell (mDocument.getData(), getSceneManager(), - iter->getId (mWorldspace), getPhysics()); + iter->getId (mWorldspace), mDocument.getPhysics()); mCells.insert (std::make_pair (*iter, cell)); float height = cell->getTerrainHeightAt(Ogre::Vector3( diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 07acbe493c..3f8a455481 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -49,7 +49,7 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& update(); - mCell.reset (new Cell (document.getData(), getSceneManager(), mCellId, getPhysics())); + mCell.reset (new Cell (document.getData(), getSceneManager(), mCellId, document.getPhysics())); } void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, @@ -91,7 +91,7 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vectorgetId(); - mCell.reset (new Cell (getDocument().getData(), getSceneManager(), mCellId, getPhysics())); + mCell.reset (new Cell (getDocument().getData(), getSceneManager(), mCellId, getDocument().getPhysics())); update(); emit cellChanged(*data.begin()); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 1f7c415122..9ea582f3da 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -16,7 +16,6 @@ #include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolrun.hpp" -#include "../world/physicsmanager.hpp" #include "../world/physicssystem.hpp" #include "elements.hpp" @@ -56,9 +55,7 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); - // associate WorldSpaceWidgets (and their SceneManagers) with Documents - // then create physics if there is a new document - mPhysics = CSVWorld::PhysicsManager::instance()->addSceneWidget(document, this); + mPhysics = document.getPhysics(); // create physics if one doesn't exist mPhysics->addSceneManager(getSceneManager(), this); mMouse = new MouseState(this); } @@ -67,7 +64,6 @@ CSVRender::WorldspaceWidget::~WorldspaceWidget () { delete mMouse; mPhysics->removeSceneManager(getSceneManager()); - CSVWorld::PhysicsManager::instance()->removeSceneWidget(this); } void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) @@ -370,12 +366,6 @@ void CSVRender::WorldspaceWidget::updateOverlay() { } -CSVWorld::PhysicsSystem *CSVRender::WorldspaceWidget::getPhysics() -{ - assert(mPhysics); - return mPhysics; -} - void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) { if(event->buttons() & Qt::RightButton) diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 550b5d4a9a..b19197e36b 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -1,6 +1,8 @@ #ifndef OPENCS_VIEW_WORLDSPACEWIDGET_H #define OPENCS_VIEW_WORLDSPACEWIDGET_H +#include + #include "scenewidget.hpp" #include "mousestate.hpp" @@ -40,7 +42,7 @@ namespace CSVRender CSVWidget::SceneToolToggle2 *mSceneElements; CSVWidget::SceneToolRun *mRun; CSMDoc::Document& mDocument; - CSVWorld::PhysicsSystem *mPhysics; + boost::shared_ptr mPhysics; MouseState *mMouse; unsigned int mInteractionMask; @@ -115,8 +117,6 @@ namespace CSVRender virtual void updateOverlay(); - CSVWorld::PhysicsSystem *getPhysics(); - virtual void mouseMoveEvent (QMouseEvent *event); virtual void mousePressEvent (QMouseEvent *event); virtual void mouseReleaseEvent (QMouseEvent *event); diff --git a/apps/opencs/view/world/physicsmanager.cpp b/apps/opencs/view/world/physicsmanager.cpp deleted file mode 100644 index fa8db9e1e0..0000000000 --- a/apps/opencs/view/world/physicsmanager.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "physicsmanager.hpp" - -#include - -#include - -#include "../render/worldspacewidget.hpp" -#include "physicssystem.hpp" - -namespace CSVWorld -{ - PhysicsManager *PhysicsManager::mPhysicsManagerInstance = 0; - - PhysicsManager::PhysicsManager() - { - assert(!mPhysicsManagerInstance); - mPhysicsManagerInstance = this; - } - - PhysicsManager::~PhysicsManager() - { - std::map::iterator iter = mPhysics.begin(); - for(; iter != mPhysics.end(); ++iter) - delete iter->second; // shouldn't be any left but just in case - } - - PhysicsManager *PhysicsManager::instance() - { - assert(mPhysicsManagerInstance); - return mPhysicsManagerInstance; - } - - // create a physics instance per document, called from CSVDoc::View() to get Document* - void PhysicsManager::setupPhysics(CSMDoc::Document *doc) - { - std::map >::iterator iter = mSceneWidgets.find(doc); - if(iter == mSceneWidgets.end()) - { - mSceneWidgets[doc] = std::list (); // zero elements - mPhysics[doc] = new PhysicsSystem(); - } - } - - // destroy physics, called from CSVDoc::ViewManager - void PhysicsManager::removeDocument(CSMDoc::Document *doc) - { - std::map::iterator iter = mPhysics.find(doc); - if(iter != mPhysics.end()) - { - delete iter->second; - mPhysics.erase(iter); - } - - std::map >::iterator it = mSceneWidgets.find(doc); - if(it != mSceneWidgets.end()) - { - mSceneWidgets.erase(it); - } - - // cleanup global resources used by OEngine - if(mPhysics.empty()) - { - delete OEngine::Physic::BulletShapeManager::getSingletonPtr(); - } - } - - // called from CSVRender::WorldspaceWidget() to get widgets' association with Document& - PhysicsSystem *PhysicsManager::addSceneWidget(CSMDoc::Document &doc, CSVRender::WorldspaceWidget *widget) - { - CSVRender::SceneWidget *sceneWidget = static_cast(widget); - - std::map >::iterator iter = mSceneWidgets.begin(); - for(; iter != mSceneWidgets.end(); ++iter) - { - if((*iter).first == &doc) - { - (*iter).second.push_back(sceneWidget); - return mPhysics[(*iter).first]; // TODO: consider using shared_ptr instead - } - } - - throw std::runtime_error("No physics system found for the given document."); - } - - // deprecated by removeDocument() and may be deleted in future code updates - // however there may be some value in removing the deleted scene widgets from the - // list so that the list does not grow forever - void PhysicsManager::removeSceneWidget(CSVRender::WorldspaceWidget *widget) - { - CSVRender::SceneWidget *sceneWidget = static_cast(widget); - - std::map >::iterator iter = mSceneWidgets.begin(); - for(; iter != mSceneWidgets.end(); ++iter) - { - std::list::iterator itWidget = (*iter).second.begin(); - for(; itWidget != (*iter).second.end(); ++itWidget) - { - if((*itWidget) == sceneWidget) - { - (*iter).second.erase(itWidget); - - //if((*iter).second.empty()) // last one for the document - // NOTE: do not delete physics until the document itself is closed - - break; - } - } - } - } -} diff --git a/apps/opencs/view/world/physicsmanager.hpp b/apps/opencs/view/world/physicsmanager.hpp deleted file mode 100644 index e17c9ac84a..0000000000 --- a/apps/opencs/view/world/physicsmanager.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef CSV_WORLD_PHYSICSMANAGER_H -#define CSV_WORLD_PHYSICSMANAGER_H - -#include -#include - -namespace Ogre -{ - class SceneManager; -} - -namespace CSMDoc -{ - class Document; -} - -namespace CSVRender -{ - class WorldspaceWidget; - class SceneWidget; -} - -namespace CSVWorld -{ - class PhysicsSystem; -} - -namespace CSVWorld -{ - class PhysicsManager - { - static PhysicsManager *mPhysicsManagerInstance; - - std::map > mSceneWidgets; - std::map mPhysics; - - public: - - PhysicsManager(); - ~PhysicsManager(); - - static PhysicsManager *instance(); - - void setupPhysics(CSMDoc::Document *); - - PhysicsSystem *addSceneWidget(CSMDoc::Document &doc, CSVRender::WorldspaceWidget *widget); - - void removeSceneWidget(CSVRender::WorldspaceWidget *widget); - - void removeDocument(CSMDoc::Document *doc); - }; -} - -#endif // CSV_WORLD_PHYSICSMANAGER_H From a4e32d23c6b6377a813a3029fa7fbfcd1eeb3a9a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 30 Nov 2014 20:52:34 +0100 Subject: [PATCH 019/167] Fix old profile content not being cleared correctly when saving content file selection (Fixes #2173) --- apps/launcher/datafilespage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 17951f2e49..f45b444707 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -137,6 +137,7 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) void Launcher::DataFilesPage::removeProfile(const QString &profile) { mLauncherSettings.remove(QString("Profiles/") + profile); + mLauncherSettings.remove(QString("Profiles/") + profile + QString("/content")); } QAbstractItemModel *Launcher::DataFilesPage::profilesModel() const From dffa8c6c149d1c90e9acde23a27d25d689fa38a7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 30 Nov 2014 22:02:25 +0100 Subject: [PATCH 020/167] Re-insert existing DialInfo records when they are modified by another content file (Fixes #2170) --- components/esm/loaddial.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index ff0362aa21..f2da8f3775 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -63,6 +63,8 @@ void Dialogue::readInfo(ESMReader &esm, bool merge) std::map::iterator lookup; lookup = mLookup.find(id); + + ESM::DialInfo info; if (lookup != mLookup.end()) { it = lookup->second; @@ -70,13 +72,17 @@ void Dialogue::readInfo(ESMReader &esm, bool merge) // Merge with existing record. Only the subrecords that are present in // the new record will be overwritten. it->load(esm); - return; - } + info = *it; - // New record - ESM::DialInfo info; - info.mId = id; - info.load(esm); + // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record + mInfo.erase(it); + mLookup.erase(lookup); + } + else + { + info.mId = id; + info.load(esm); + } if (info.mNext.empty()) { From 9fb4b1f499689c77cdeb76c6021dfd2fb5fcb105 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 1 Dec 2014 08:15:17 +1100 Subject: [PATCH 021/167] Initialise null shared_ptr --- apps/opencs/view/render/worldspacewidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 9ea582f3da..879200b5dc 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -22,7 +22,7 @@ #include "editmode.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) -: SceneWidget (parent), mDocument(document), mSceneElements(0), mRun(0), mPhysics(0), mMouse(0), +: SceneWidget (parent), mDocument(document), mSceneElements(0), mRun(0), mPhysics(boost::shared_ptr()), mMouse(0), mInteractionMask (0) { setAcceptDrops(true); From 2d229c70cb163637c0057f67021a01eab8b872ad Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 1 Dec 2014 09:41:03 +1100 Subject: [PATCH 022/167] Another missed null shared_ptr conversion for gcc. --- apps/opencs/view/render/previewwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index f972c6361b..da18e7c895 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -10,7 +10,7 @@ CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, const std::string& id, bool referenceable, QWidget *parent) : SceneWidget (parent), mData (data), - mObject (data, getSceneManager()->getRootSceneNode(), id, referenceable, NULL, true) + mObject (data, getSceneManager()->getRootSceneNode(), id, referenceable, boost::shared_ptr(), true) { setNavigation (&mOrbit); From 44b11163d18dd43b7e8bc3e1203049373480476f Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 1 Dec 2014 10:07:02 +1100 Subject: [PATCH 023/167] Do not delete physics objects if it was never created (e.g. preview window) --- apps/opencs/view/render/object.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index af3777d0c8..d92b4aaa2d 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -156,7 +156,8 @@ CSVRender::Object::~Object() { clear(); - mPhysics->removeObject(mBase->getName()); + if(mPhysics) // preview may not have physics enabled + mPhysics->removeObject(mBase->getName()); if (mBase) mBase->getCreator()->destroySceneNode (mBase); From 64e1594b4179688e79a5fa01193239af73c9174a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 1 Dec 2014 14:08:27 +1100 Subject: [PATCH 024/167] Move the destruction of global resources, being used by multiple documents, to the editor. --- apps/opencs/editor.cpp | 7 ++++++- apps/opencs/view/doc/viewmanager.cpp | 4 ---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index e756cb5dfc..53c6865eb4 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -1,6 +1,8 @@ #include "editor.hpp" +#include + #include #include #include @@ -69,7 +71,10 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) } CS::Editor::~Editor () -{} +{ + // cleanup global resources used by OEngine + delete OEngine::Physic::BulletShapeManager::getSingletonPtr(); +} void CS::Editor::setupDataFiles (const Files::PathContainer& dataDirs) { diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 55fd38c182..5f6b6b46a4 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -6,8 +6,6 @@ #include #include -#include - #include "../../model/doc/documentmanager.hpp" #include "../../model/doc/document.hpp" #include "../../model/world/columns.hpp" @@ -220,8 +218,6 @@ void CSVDoc::ViewManager::removeDocAndView (CSMDoc::Document *document) mDocumentManager.removeDocument(document); (*iter)->deleteLater(); mViews.erase (iter); - // cleanup global resources used by OEngine - delete OEngine::Physic::BulletShapeManager::getSingletonPtr(); updateIndices(); return; From 3b5cd286f6ee51b50baf136040f4033ad589ad81 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 1 Dec 2014 14:09:22 +1100 Subject: [PATCH 025/167] Do not destroy overlay if it was never created (e.g. due to an Ogre exception). --- apps/opencs/view/render/pagedworldspacewidget.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index bae0fcacf4..e7954491f2 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -362,8 +362,11 @@ CSVRender::PagedWorldspaceWidget::~PagedWorldspaceWidget() delete iter->second; } - removeRenderTargetListener(mOverlayMask); - delete mOverlayMask; + if(mOverlayMask) + { + removeRenderTargetListener(mOverlayMask); + delete mOverlayMask; + } } void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) From 6e1a11f3220761c5ac808019d78eb69c4560ec67 Mon Sep 17 00:00:00 2001 From: MiroslavR Date: Mon, 1 Dec 2014 19:13:04 +0100 Subject: [PATCH 026/167] Queue screen fade operations invoked by scripts --- apps/openmw/mwbase/windowmanager.hpp | 6 +++--- apps/openmw/mwgui/windowmanagerimp.cpp | 15 +++++++++------ apps/openmw/mwgui/windowmanagerimp.hpp | 6 +++--- apps/openmw/mwscript/miscextensions.cpp | 6 +++--- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index bfc4f3b333..d4f1afa32d 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -330,11 +330,11 @@ namespace MWBase virtual void pinWindow (MWGui::GuiWindow window) = 0; /// Fade the screen in, over \a time seconds - virtual void fadeScreenIn(const float time) = 0; + virtual void fadeScreenIn(const float time, bool clearQueue=true) = 0; /// Fade the screen out to black, over \a time seconds - virtual void fadeScreenOut(const float time) = 0; + virtual void fadeScreenOut(const float time, bool clearQueue=true) = 0; /// Fade the screen to a specified percentage of black, over \a time seconds - virtual void fadeScreenTo(const int percent, const float time) = 0; + virtual void fadeScreenTo(const int percent, const float time, bool clearQueue=true) = 0; /// Darken the screen to a specified percentage virtual void setBlindness(const int percent) = 0; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 6ab8b94c5c..48f28d300c 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1748,21 +1748,24 @@ namespace MWGui updateVisible(); } - void WindowManager::fadeScreenIn(const float time) + void WindowManager::fadeScreenIn(const float time, bool clearQueue) { - mScreenFader->clearQueue(); + if (clearQueue) + mScreenFader->clearQueue(); mScreenFader->fadeOut(time); } - void WindowManager::fadeScreenOut(const float time) + void WindowManager::fadeScreenOut(const float time, bool clearQueue) { - mScreenFader->clearQueue(); + if (clearQueue) + mScreenFader->clearQueue(); mScreenFader->fadeIn(time); } - void WindowManager::fadeScreenTo(const int percent, const float time) + void WindowManager::fadeScreenTo(const int percent, const float time, bool clearQueue) { - mScreenFader->clearQueue(); + if (clearQueue) + mScreenFader->clearQueue(); mScreenFader->fadeTo(percent, time); } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index aa5bd0fc91..94d8a93db2 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -325,11 +325,11 @@ namespace MWGui virtual void pinWindow (MWGui::GuiWindow window); /// Fade the screen in, over \a time seconds - virtual void fadeScreenIn(const float time); + virtual void fadeScreenIn(const float time, bool clearQueue); /// Fade the screen out to black, over \a time seconds - virtual void fadeScreenOut(const float time); + virtual void fadeScreenOut(const float time, bool clearQueue); /// Fade the screen to a specified percentage of black, over \a time seconds - virtual void fadeScreenTo(const int percent, const float time); + virtual void fadeScreenTo(const int percent, const float time, bool clearQueue); /// Darken the screen to a specified percentage virtual void setBlindness(const int percent); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index e8784f8de2..aa80213de8 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -246,7 +246,7 @@ namespace MWScript Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getWindowManager()->fadeScreenIn(time); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(time, false); } }; @@ -259,7 +259,7 @@ namespace MWScript Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getWindowManager()->fadeScreenOut(time); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(time, false); } }; @@ -275,7 +275,7 @@ namespace MWScript Interpreter::Type_Float time = runtime[0].mFloat; runtime.pop(); - MWBase::Environment::get().getWindowManager()->fadeScreenTo(alpha, time); + MWBase::Environment::get().getWindowManager()->fadeScreenTo(alpha, time, false); } }; From 48d5789aeb94c5916395b16929438350146ba09f Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 22:10:06 +0100 Subject: [PATCH 027/167] Use a separate flag for references deleted by a content file (Fixes #2018) The flag must be separate so as to not contaminate the user's savegame. Fixes the following use cases that were broken before: - Content file edits a reference that was already deleted by a previously loaded content file -> reference must stay deleted - Changed or new content file deletes a reference that is already present in the user's savegame -> reference must be deleted - Said content file is disabled again - reference must be undeleted --- apps/openmw/mwworld/cellstore.cpp | 2 +- apps/openmw/mwworld/cellstore.hpp | 2 +- apps/openmw/mwworld/localscripts.cpp | 2 +- apps/openmw/mwworld/refdata.cpp | 19 ++++++++++++++++--- apps/openmw/mwworld/refdata.hpp | 11 ++++++++++- apps/openmw/mwworld/scene.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- 7 files changed, 31 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index ce8d77758c..52e70fef26 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -156,7 +156,7 @@ namespace MWWorld LiveRef liveCellRef (ref, ptr); if (deleted) - liveCellRef.mData.setCount (0); + liveCellRef.mData.setDeleted(true); if (iter != mList.end()) *iter = liveCellRef; diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index e322ef4a40..05e7b0b2e0 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -196,7 +196,7 @@ namespace MWWorld for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); ++iter) { - if (!iter->mData.getCount()) + if (iter->mData.isDeleted()) continue; if (!functor (MWWorld::Ptr(&*iter, this))) return false; diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index f3a6471249..d74aab6943 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -17,7 +17,7 @@ namespace cellRefList.mList.begin()); iter!=cellRefList.mList.end(); ++iter) { - if (!iter->mBase->mScript.empty() && iter->mData.getCount()) + if (!iter->mBase->mScript.empty() && !iter->mData.isDeleted()) { localScripts.add (iter->mBase->mScript, MWWorld::Ptr (&*iter, cell)); } diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index f4bc64b708..78fea2b868 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -23,6 +23,7 @@ namespace MWWorld mPosition = refData.mPosition; mLocalRotation = refData.mLocalRotation; mChanged = refData.mChanged; + mDeleted = refData.mDeleted; mCustomData = refData.mCustomData ? refData.mCustomData->clone() : 0; } @@ -36,7 +37,7 @@ namespace MWWorld } RefData::RefData() - : mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false) + : mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false), mDeleted(false) { for (int i=0; i<3; ++i) { @@ -49,7 +50,8 @@ namespace MWWorld RefData::RefData (const ESM::CellRef& cellRef) : mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mPosition (cellRef.mPos), mCustomData (0), - mChanged(false) // Loading from ESM/ESP files -> assume unchanged + mChanged(false), // Loading from ESM/ESP files -> assume unchanged + mDeleted(false) { mLocalRotation.rot[0]=0; mLocalRotation.rot[1]=0; @@ -59,7 +61,8 @@ namespace MWWorld RefData::RefData (const ESM::ObjectState& objectState) : mBaseNode (0), mHasLocals (false), mEnabled (objectState.mEnabled), mCount (objectState.mCount), mPosition (objectState.mPosition), mCustomData (0), - mChanged(true) // Loading from a savegame -> assume changed + mChanged(true), // Loading from a savegame -> assume changed + mDeleted(false) { for (int i=0; i<3; ++i) mLocalRotation.rot[i] = objectState.mLocalRotation[i]; @@ -167,6 +170,16 @@ namespace MWWorld mCount = count; } + void RefData::setDeleted(bool deleted) + { + mDeleted = deleted; + } + + bool RefData::isDeleted() const + { + return mDeleted || mCount == 0; + } + MWScript::Locals& RefData::getLocals() { return mLocals; diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index db66c091bb..1ed3cd79d9 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -37,6 +37,8 @@ namespace MWWorld bool mEnabled; int mCount; // 0: deleted + bool mDeleted; // separate delete flag used for deletion by a content file + ESM::Position mPosition; LocalRotation mLocalRotation; @@ -86,12 +88,19 @@ namespace MWWorld void setLocals (const ESM::Script& script); void setCount (int count); - /// Set object count (an object pile is a simple object with a count >1). + ///< Set object count (an object pile is a simple object with a count >1). /// /// \warning Do not call setCount() to add or remove objects from a /// container or an actor's inventory. Call ContainerStore::add() or /// ContainerStore::remove() instead. + /// This flag is only used for content stack loading and will not be stored in the savegame. + /// If the object was deleted by gameplay, then use setCount(0) instead. + void setDeleted(bool deleted); + + /// Returns true if the object was either deleted by the content file or by gameplay. + bool isDeleted() const; + MWScript::Locals& getLocals(); bool isEnabled() const; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 6f18a6ef3a..02c9db9ea4 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -75,7 +75,7 @@ namespace ptr.getCellRef().setScale(2); } - if (ptr.getRefData().getCount() && ptr.getRefData().isEnabled()) + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) { try { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4c2bd669b6..7baf1d3e5c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1057,7 +1057,7 @@ namespace MWWorld void World::deleteObject (const Ptr& ptr) { - if (ptr.getRefData().getCount() > 0) + if (!ptr.getRefData().isDeleted()) { ptr.getRefData().setCount(0); From cbcd6a26d568d6a4b161fc2f849cba701bc4bef1 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 1 Dec 2014 22:57:32 +0100 Subject: [PATCH 028/167] memory leak fix --- apps/opencs/model/world/data.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 737376f047..f6bd8e13b9 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -686,6 +686,8 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Stage::Messages& messages) boost::shared_ptr ptr(mReader); mReaders.push_back(ptr); } + else + delete mReader; mReader = 0; From 61d1aa78ce94c60414b31e1ac88614f35ebf8748 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 13:34:42 +0100 Subject: [PATCH 029/167] Move AiWander path finder to temporary storage (Fixes #2082) --- apps/openmw/mwmechanics/aipackage.hpp | 1 + apps/openmw/mwmechanics/aiwander.cpp | 44 ++++++++++++++------------- apps/openmw/mwmechanics/aiwander.hpp | 5 +-- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index df970f8013..f1c9ec7d25 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -69,6 +69,7 @@ namespace MWMechanics /** \return If the actor has arrived at his destination **/ bool pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration); + // TODO: all this does not belong here, move into temporary storage PathFinder mPathFinder; ObstacleCheck mObstacleCheck; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index a70200833c..a5be250f74 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -57,6 +57,8 @@ namespace MWMechanics bool mWalking; unsigned short mPlayedIdle; + + PathFinder mPathFinder; AiWanderStorage(): mTargetAngle(0), @@ -211,9 +213,9 @@ namespace MWMechanics // Are we there yet? bool& chooseAction = storage.mChooseAction; if(walking && - mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) + storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { - stopWalking(actor); + stopWalking(actor, storage); moveNow = false; walking = false; chooseAction = true; @@ -225,7 +227,7 @@ namespace MWMechanics if(walking) // have not yet reached the destination { // turn towards the next point in mPath - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + zTurn(actor, Ogre::Degree(storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // Returns true if evasive action needs to be taken @@ -236,9 +238,9 @@ namespace MWMechanics { // remove allowed points then select another random destination mTrimCurrentNode = true; - trimAllowedNodes(mAllowedNodes, mPathFinder); + trimAllowedNodes(mAllowedNodes, storage.mPathFinder); mObstacleCheck.clear(); - mPathFinder.clearPath(); + storage.mPathFinder.clearPath(); walking = false; moveNow = true; } @@ -249,7 +251,7 @@ namespace MWMechanics actor.getClass().getMovementSettings(actor).mPosition[0] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; // change the angle a bit, too - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); + zTurn(actor, Ogre::Degree(storage.mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); } mStuckCount++; // TODO: maybe no longer needed } @@ -260,7 +262,7 @@ namespace MWMechanics //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; mObstacleCheck.clear(); - stopWalking(actor); + stopWalking(actor, storage); moveNow = false; walking = false; chooseAction = true; @@ -300,7 +302,7 @@ namespace MWMechanics { if(!mRepeat) { - stopWalking(actor); + stopWalking(actor, storage); return true; } else @@ -310,7 +312,7 @@ namespace MWMechanics { if(!mRepeat) { - stopWalking(actor); + stopWalking(actor, storage); return true; } else @@ -411,7 +413,7 @@ namespace MWMechanics chooseAction = false; idleNow = false; - if (!mPathFinder.isPathConstructed()) + if (!storage.mPathFinder.isPathConstructed()) { Ogre::Vector3 destNodePos = mReturnPosition; @@ -427,9 +429,9 @@ namespace MWMechanics start.mZ = pos.pos[2]; // don't take shortcuts for wandering - mPathFinder.buildPath(start, dest, actor.getCell(), false); + storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); - if(mPathFinder.isPathConstructed()) + if(storage.mPathFinder.isPathConstructed()) { moveNow = false; walking = true; @@ -517,7 +519,7 @@ namespace MWMechanics if(walking) { - stopWalking(actor); + stopWalking(actor, storage); moveNow = false; walking = false; mObstacleCheck.clear(); @@ -567,7 +569,7 @@ namespace MWMechanics if(moveNow && mDistance) { // Construct a new path if there isn't one - if(!mPathFinder.isPathConstructed()) + if(!storage.mPathFinder.isPathConstructed()) { assert(mAllowedNodes.size()); unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size()); @@ -589,16 +591,16 @@ namespace MWMechanics start.mZ = pos.pos[2]; // don't take shortcuts for wandering - mPathFinder.buildPath(start, dest, actor.getCell(), false); + storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); - if(mPathFinder.isPathConstructed()) + if(storage.mPathFinder.isPathConstructed()) { // buildPath inserts dest in case it is not a pathgraph point // index which is a duplicate for AiWander. However below code // does not work since getPath() returns a copy of path not a // reference - //if(mPathFinder.getPathSize() > 1) - //mPathFinder.getPath().pop_back(); + //if(storage.mPathFinder.getPathSize() > 1) + //storage.mPathFinder.getPath().pop_back(); // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; @@ -616,7 +618,7 @@ namespace MWMechanics // Choose a different node and delete this one from possible nodes because it is uncreachable: else mAllowedNodes.erase(mAllowedNodes.begin() + randNode); - } + } } return false; // AiWander package not yet completed @@ -653,9 +655,9 @@ namespace MWMechanics return TypeIdWander; } - void AiWander::stopWalking(const MWWorld::Ptr& actor) + void AiWander::stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage) { - mPathFinder.clearPath(); + storage.mPathFinder.clearPath(); actor.getClass().getMovementSettings(actor).mPosition[1] = 0; } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 0600909bae..b9b394a264 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -27,6 +27,8 @@ namespace MWMechanics { + struct AiWanderStorage; + /// \brief Causes the Actor to wander within a specified range class AiWander : public AiPackage { @@ -65,7 +67,7 @@ namespace MWMechanics // NOTE: mDistance and mDuration must be set already void init(); - void stopWalking(const MWWorld::Ptr& actor); + void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage); void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); void getRandomIdle(unsigned short& playedIdle); @@ -101,7 +103,6 @@ namespace MWMechanics void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); -// PathFinder mPathFinder; // ObstacleCheck mObstacleCheck; float mDoorCheckDuration; From 6960cac5eb279562c7791a92e3b7bb9af93a00f3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 13:38:47 +0100 Subject: [PATCH 030/167] Disable third person zoom feature by default due to usability issues (Fixes #2129) --- 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 68682abeac..48acd22ba3 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -623,7 +623,9 @@ namespace MWInput if (arg.zrel && mControlSwitch["playerviewswitch"] && mControlSwitch["playercontrols"]) //Check to make sure you are allowed to zoomout and there is a change { MWBase::Environment::get().getWorld()->changeVanityModeScale(arg.zrel); - MWBase::Environment::get().getWorld()->setCameraDistance(arg.zrel, true, true); + + if (Settings::Manager::getBool("allow third person zoom", "Input")) + MWBase::Environment::get().getWorld()->setCameraDistance(arg.zrel, true, true); } } } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 12b52d3db6..7566994e29 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -179,6 +179,8 @@ camera y multiplier = 1.0 always run = false +allow third person zoom = false + [Game] # Always use the most powerful attack when striking with a weapon (chop, slash or thrust) best attack = false From c684c99a95f3462ee96926af8a8eafbbf3b39975 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 13:43:11 +0100 Subject: [PATCH 031/167] Combat AI: Don't attempt to cast spells when impossible to succeed (Fixes #2059) --- apps/openmw/mwmechanics/aicombataction.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index cc8279b1eb..6b4ede3059 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -9,6 +9,7 @@ #include "../mwworld/actionequip.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellcasting.hpp" #include #include @@ -166,6 +167,9 @@ namespace MWMechanics { const CreatureStats& stats = actor.getClass().getCreatureStats(actor); + if (MWMechanics::getSpellSuccessChance(spell, actor) == 0) + return 0.f; + if (spell->mData.mType != ESM::Spell::ST_Spell) return 0.f; From 077c619611ace83df33ced9066931d8086b55f89 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 14:36:36 +0100 Subject: [PATCH 032/167] Implement Clamp mode for NiTexturingProperty (Fixes #2050) --- components/nifogre/material.cpp | 30 ++++++++++++++++++++++++++---- files/materials/objects.mat | 4 ++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 517f29f4e3..04eb86ba6f 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -54,6 +54,28 @@ static const char *getTestMode(int mode) return "less_equal"; } +static void setTextureProperties(sh::MaterialInstance* material, const std::string& textureSlotName, const Nif::NiTexturingProperty::Texture& tex) +{ + material->setProperty(textureSlotName + "UVSet", sh::makeProperty(new sh::IntValue(tex.uvSet))); + const std::string clampMode = textureSlotName + "ClampMode"; + switch (tex.clamp) + { + case 0: + material->setProperty(clampMode, sh::makeProperty(new sh::StringValue("clamp clamp"))); + break; + case 1: + material->setProperty(clampMode, sh::makeProperty(new sh::StringValue("clamp wrap"))); + break; + case 2: + material->setProperty(clampMode, sh::makeProperty(new sh::StringValue("wrap clamp"))); + break; + case 3: + default: + material->setProperty(clampMode, sh::makeProperty(new sh::StringValue("wrap wrap"))); + break; + } +} + Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, const Ogre::String &name, const Ogre::String &group, const Nif::NiTexturingProperty *texprop, @@ -294,22 +316,22 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, if (!texName[Nif::NiTexturingProperty::BaseTexture].empty()) { instance->setProperty("use_diffuse_map", sh::makeProperty(new sh::BooleanValue(true))); - instance->setProperty("diffuseMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::BaseTexture].uvSet))); + setTextureProperties(instance, "diffuseMap", texprop->textures[Nif::NiTexturingProperty::BaseTexture]); } if (!texName[Nif::NiTexturingProperty::GlowTexture].empty()) { instance->setProperty("use_emissive_map", sh::makeProperty(new sh::BooleanValue(true))); - instance->setProperty("emissiveMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::GlowTexture].uvSet))); + setTextureProperties(instance, "emissiveMap", texprop->textures[Nif::NiTexturingProperty::GlowTexture]); } if (!texName[Nif::NiTexturingProperty::DetailTexture].empty()) { instance->setProperty("use_detail_map", sh::makeProperty(new sh::BooleanValue(true))); - instance->setProperty("detailMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::DetailTexture].uvSet))); + setTextureProperties(instance, "detailMap", texprop->textures[Nif::NiTexturingProperty::DetailTexture]); } if (!texName[Nif::NiTexturingProperty::DarkTexture].empty()) { instance->setProperty("use_dark_map", sh::makeProperty(new sh::BooleanValue(true))); - instance->setProperty("darkMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::DarkTexture].uvSet))); + setTextureProperties(instance, "darkMap", texprop->textures[Nif::NiTexturingProperty::DarkTexture]); } bool useParallax = !texName[Nif::NiTexturingProperty::BumpTexture].empty() diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 932c7e25f2..149160760b 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -73,6 +73,7 @@ material openmw_objects_base direct_texture $diffuseMap create_in_ffp $use_diffuse_map tex_coord_set $diffuseMapUVSet + tex_address_mode $diffuseMapClampMode } texture_unit normalMap @@ -89,6 +90,7 @@ material openmw_objects_base alpha_op_ex modulate src_current src_texture direct_texture $darkMap tex_coord_set $darkMapUVSet + tex_address_mode $darkMapClampMode } texture_unit detailMap @@ -97,6 +99,7 @@ material openmw_objects_base colour_op_ex modulate_x2 src_current src_texture direct_texture $detailMap tex_coord_set $detailMapUVSet + tex_address_mode $detailMapClampMode } texture_unit emissiveMap @@ -105,6 +108,7 @@ material openmw_objects_base colour_op add direct_texture $emissiveMap tex_coord_set $emissiveMapUVSet + tex_address_mode $emissiveMapClampMode } texture_unit envMap From 59cde9b4314234b22d8b24f8993035692b8222bf Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 14:38:50 +0100 Subject: [PATCH 033/167] Don't use transparency override if there's no transparency (rug fix for Bug #2050) --- components/nifogre/material.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 04eb86ba6f..072f16344b 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -354,7 +354,7 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, instance->setProperty("has_vertex_colour", sh::makeProperty(new sh::BooleanValue(true))); // Override alpha flags based on our override list (transparency-overrides.cfg) - if (!texName[0].empty()) + if ((alphaFlags&1) && !texName[0].empty()) { NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName[0]); if (result.first) From 8103d25b09710f1d4fa7d7bec727d0bec60ece93 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 14:48:53 +0100 Subject: [PATCH 034/167] Make ToggleMenus close open windows (Fixes #2045) --- apps/openmw/mwscript/guiextensions.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index afc745beb9..1d34adbca0 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -210,6 +210,12 @@ namespace MWScript { bool state = MWBase::Environment::get().getWindowManager()->toggleGui(); runtime.getContext().report(state ? "GUI -> On" : "GUI -> Off"); + + if (!state) + { + while (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_None) // don't use isGuiMode, or we get an infinite loop for modal message boxes! + MWBase::Environment::get().getWindowManager()->popGuiMode(); + } } }; From f9ae0d9d665b7ad08327017e417cc4572b0b48d7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 14:56:13 +0100 Subject: [PATCH 035/167] Fix dialogue goodbye link conflicting with choice links --- apps/openmw/mwgui/dialogue.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 6526b200f9..eb548d596d 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -533,9 +533,11 @@ namespace MWGui if (mGoodbye) { + Goodbye* link = new Goodbye(); + mLinks.push_back(link); std::string goodbye = MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->getString(); BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, linkNormal, linkHot, linkActive, - TypesetBook::InteractiveId(mLinks.back())); + TypesetBook::InteractiveId(link)); typesetter->lineBreak(); typesetter->write(questionStyle, to_utf8_span(goodbye.c_str())); } @@ -654,7 +656,6 @@ namespace MWGui void DialogueWindow::goodbye() { - mLinks.push_back(new Goodbye()); mGoodbye = true; mEnabled = false; updateHistory(); From a1226501fac2f1f25213ffad546d2565fede51fd Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 15:08:55 +0100 Subject: [PATCH 036/167] AiWander: move idle animation handling to non-delayed section (Fixes #2073) --- apps/openmw/mwmechanics/aiwander.cpp | 105 ++++++++++++++------------- 1 file changed, 53 insertions(+), 52 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index a5be250f74..c8a0c85d58 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -282,6 +282,58 @@ namespace MWMechanics rotate = false; } + // Check if idle animation finished + short unsigned& playedIdle = storage.mPlayedIdle; + GreetingState& greetingState = storage.mSaidGreeting; + if(idleNow && !checkIdle(actor, playedIdle) && (greetingState == Greet_Done || greetingState == Greet_None)) + { + playedIdle = 0; + idleNow = false; + chooseAction = true; + } + + MWBase::World *world = MWBase::Environment::get().getWorld(); + + if(chooseAction) + { + playedIdle = 0; + getRandomIdle(playedIdle); // NOTE: sets mPlayedIdle with a random selection + + if(!playedIdle && mDistance) + { + chooseAction = false; + moveNow = true; + } + else + { + // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: + MWWorld::TimeStamp currentTime = world->getTimeStamp(); + mStartTime = currentTime; + playIdle(actor, playedIdle); + chooseAction = false; + idleNow = true; + + // Play idle voiced dialogue entries randomly + int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); + if (hello > 0) + { + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + // Don't bother if the player is out of hearing range + static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() + .get().find("fVoiceIdleOdds")->getFloat(); + + // Only say Idle voices when player is in LOS + // A bit counterintuitive, likely vanilla did this to reduce the appearance of + // voices going through walls? + if (roll < fVoiceIdleOdds && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500 + && MWBase::Environment::get().getWorld()->getLOS(player, actor)) + MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); + } + } + } + float& lastReaction = storage.mReaction; lastReaction += duration; if(lastReaction < REACTION_INTERVAL) @@ -293,7 +345,6 @@ namespace MWMechanics // NOTE: everything below get updated every REACTION_INTERVAL seconds - MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) { // End package if duration is complete or mid-night hits: @@ -439,48 +490,6 @@ namespace MWMechanics } } - AiWander::GreetingState& greetingState = storage.mSaidGreeting; - short unsigned& playedIdle = storage.mPlayedIdle; - if(chooseAction) - { - playedIdle = 0; - getRandomIdle(playedIdle); // NOTE: sets mPlayedIdle with a random selection - - if(!playedIdle && mDistance) - { - chooseAction = false; - moveNow = true; - } - else - { - // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: - MWWorld::TimeStamp currentTime = world->getTimeStamp(); - mStartTime = currentTime; - playIdle(actor, playedIdle); - chooseAction = false; - idleNow = true; - - // Play idle voiced dialogue entries randomly - int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); - if (hello > 0) - { - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - - // Don't bother if the player is out of hearing range - static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() - .get().find("fVoiceIdleOdds")->getFloat(); - - // Only say Idle voices when player is in LOS - // A bit counterintuitive, likely vanilla did this to reduce the appearance of - // voices going through walls? - if (roll < fVoiceIdleOdds && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500 - && MWBase::Environment::get().getWorld()->getLOS(player, actor)) - MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); - } - } - } - // Allow interrupting a walking actor to trigger a greeting if(idleNow || walking) { @@ -496,7 +505,7 @@ namespace MWMechanics Ogre::Vector3 playerPos(player.getRefData().getPosition().pos); Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); float playerDistSqr = playerPos.squaredDistance(actorPos); - + int& greetingTimer = storage.mGreetingTimer; if (greetingState == Greet_None) { @@ -556,14 +565,6 @@ namespace MWMechanics if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset) greetingState = Greet_None; } - - // Check if idle animation finished - if(!checkIdle(actor, playedIdle) && (playerDistSqr > helloDistance*helloDistance || greetingState == MWMechanics::AiWander::Greet_Done)) - { - playedIdle = 0; - idleNow = false; - chooseAction = true; - } } if(moveNow && mDistance) From ed686ddd2f403dccd713aaa8c97aded49aca1631 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 15:37:34 +0100 Subject: [PATCH 037/167] Don't update nodes with an empty name from the skeleton source (Fixes #2125) --- apps/openmw/mwrender/animation.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index fc147ce59a..548906cdf2 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -574,7 +574,8 @@ float Animation::getVelocity(const std::string &groupname) const static void updateBoneTree(const Ogre::SkeletonInstance *skelsrc, Ogre::Bone *bone) { - if(skelsrc->hasBone(bone->getName())) + if(bone->getName() != " " // really should be != "", but see workaround in skeleton.cpp for empty node names + && skelsrc->hasBone(bone->getName())) { Ogre::Bone *srcbone = skelsrc->getBone(bone->getName()); if(!srcbone->getParent() || !bone->getParent()) From 507cbcfae320a02e7b2af2651faac4571f09b5e6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 16:04:00 +0100 Subject: [PATCH 038/167] Remove incorrect implementation of the iAlarm* GMSTs, not used by vanilla MW (Fixes #2064) According to Hrnchamd, these are unused. The real mechanics are not fully documented, but from a quick test only NPCs with an alarm value of 100 will report crimes. --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index f7949f1ece..b242327476 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -930,19 +930,6 @@ namespace MWMechanics const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - // What amount of alarm did this crime generate? - int alarm = 0; - if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) - alarm = esmStore.get().find("iAlarmTresspass")->getInt(); - else if (type == OT_Pickpocket) - alarm = esmStore.get().find("iAlarmPickPocket")->getInt(); - else if (type == OT_Assault) - alarm = esmStore.get().find("iAlarmAttack")->getInt(); - else if (type == OT_Murder) - alarm = esmStore.get().find("iAlarmKilling")->getInt(); - else if (type == OT_Theft) - alarm = esmStore.get().find("iAlarmStealing")->getInt(); - bool reported = false; // Find all the actors within the alarm radius @@ -988,7 +975,7 @@ namespace MWMechanics continue; // Will the witness report the crime? - if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) + if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= 100) { reported = true; } From 46d93f1b08984c234a090a4207f40e8480c4147a Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 16:36:01 +0100 Subject: [PATCH 039/167] Crime update: NPCs can report crimes if they didn't see the crime, but were alerted by someone who saw it and did not report it themselves. --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index b242327476..b4edf44aa9 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -930,7 +930,6 @@ namespace MWMechanics const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - bool reported = false; // Find all the actors within the alarm radius std::vector neighbors; @@ -947,6 +946,8 @@ namespace MWMechanics bool victimAware = false; // Find actors who directly witnessed the crime + bool crimeSeen = false; + bool reported = false; for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) { if (*it == player) @@ -974,15 +975,17 @@ namespace MWMechanics if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim)) continue; - // Will the witness report the crime? - if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= 100) - { - reported = true; - } + crimeSeen = true; + } + + // Will the witness report the crime? + if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= 100) + { + reported = true; } } - if (reported) + if (crimeSeen && reported) reportCrime(player, victim, type, arg); else if (victimAware && !victim.isEmpty() && type == OT_Assault) startCombat(victim, player); From b9d0552166c7ad283b166d38731aaf5b1875c64e Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 16:43:55 +0100 Subject: [PATCH 040/167] Fix positionCell rotation argument when used on the player This fixes the player's initial orientation on the starting boat, to properly face Jiub. --- .../mwscript/transformationextensions.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index e74388effc..8e6d925b7c 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -319,12 +319,11 @@ namespace MWScript ptr = MWWorld::Ptr(ptr.getBase(), store); float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); - if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity - { - ax = ax/60.; - ay = ay/60.; + // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) + // except for when you position the player, then degrees must be used. + // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. + if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) zRot = zRot/60.; - } MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); ptr.getClass().adjustPosition(ptr, false); @@ -378,12 +377,11 @@ namespace MWScript float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); - if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity - { - ax = ax/60.; - ay = ay/60.; + // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) + // except for when you position the player, then degrees must be used. + // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. + if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) zRot = zRot/60.; - } MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); ptr.getClass().adjustPosition(ptr, false); } From fadbb5ad2196418c558abf977b3f2db0ed87489e Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 20:31:33 +0100 Subject: [PATCH 041/167] Add particle and sound fading for weather transitions (Fixes #2130) --- apps/openmw/mwrender/sky.cpp | 40 ++++++++++++++++ apps/openmw/mwworld/weather.cpp | 82 ++++++++++++++++++--------------- apps/openmw/mwworld/weather.hpp | 16 ++++--- 3 files changed, 94 insertions(+), 44 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index ccef74efb4..1841021279 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -34,6 +34,41 @@ using namespace MWRender; using namespace Ogre; +namespace +{ + +void setAlpha (NifOgre::ObjectScenePtr scene, Ogre::MovableObject* movable, float alpha) +{ + Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(movable); + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + Ogre::ColourValue diffuse = pass->getDiffuse(); + diffuse.a = alpha; + pass->setDiffuse(diffuse); + } + } + +} + +void setAlpha (NifOgre::ObjectScenePtr scene, float alpha) +{ + for(size_t i = 0; i < scene->mParticles.size(); ++i) + setAlpha(scene, scene->mParticles[i], alpha); + for(size_t i = 0; i < scene->mEntities.size(); ++i) + { + if (scene->mEntities[i] != scene->mSkelBase) + setAlpha(scene, scene->mEntities[i], alpha); + } +} + +} + BillboardObject::BillboardObject( const String& textureName, const float initialSize, const Vector3& position, @@ -660,6 +695,11 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) mSun->setVisibility(weather.mGlareView * strength); mAtmosphereNight->setVisible(weather.mNight && mEnabled); + + if (mParticle.get()) + setAlpha(mParticle, weather.mEffectFade); + for (std::map::iterator it = mRainModels.begin(); it != mRainModels.end(); ++it) + setAlpha(it->second, weather.mEffectFade); } void SkyManager::setGlare(const float glare) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index f738734b11..3f9b7d5623 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -6,6 +6,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwsound/sound.hpp" + #include "../mwrender/renderingmanager.hpp" #include "player.hpp" @@ -152,12 +154,12 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa setFallbackWeather(foggy,"foggy"); Weather thunderstorm; - thunderstorm.mRainLoopSoundID = "rain heavy"; + thunderstorm.mAmbientLoopSoundID = "rain heavy"; thunderstorm.mRainEffect = "meshes\\raindrop.nif"; setFallbackWeather(thunderstorm,"thunderstorm"); Weather rain; - rain.mRainLoopSoundID = "rain"; + rain.mAmbientLoopSoundID = "rain"; rain.mRainEffect = "meshes\\raindrop.nif"; setFallbackWeather(rain,"rain"); @@ -186,7 +188,7 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa WeatherManager::~WeatherManager() { - stopSounds(true); + stopSounds(); } void WeatherManager::setWeather(const String& weather, bool instant) @@ -228,6 +230,8 @@ void WeatherManager::setResult(const String& weatherType) mResult.mCloudSpeed = current.mCloudSpeed; mResult.mGlareView = current.mGlareView; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; + mResult.mAmbientSoundVolume = 1.f; + mResult.mEffectFade = 1.f; mResult.mSunColor = current.mSunDiscSunsetColor; mResult.mIsStorm = current.mIsStorm; @@ -341,11 +345,30 @@ void WeatherManager::transition(float factor) mResult.mNight = current.mNight; - mResult.mIsStorm = current.mIsStorm; - mResult.mParticleEffect = current.mParticleEffect; - mResult.mRainEffect = current.mRainEffect; - mResult.mRainSpeed = current.mRainSpeed; - mResult.mRainFrequency = current.mRainFrequency; + if (factor < 0.5) + { + mResult.mIsStorm = current.mIsStorm; + mResult.mParticleEffect = current.mParticleEffect; + mResult.mRainEffect = current.mRainEffect; + mResult.mParticleEffect = current.mParticleEffect; + mResult.mRainSpeed = current.mRainSpeed; + mResult.mRainFrequency = current.mRainFrequency; + mResult.mAmbientSoundVolume = 1-(factor*2); + mResult.mEffectFade = mResult.mAmbientSoundVolume; + mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; + } + else + { + mResult.mIsStorm = other.mIsStorm; + mResult.mParticleEffect = other.mParticleEffect; + mResult.mRainEffect = other.mRainEffect; + mResult.mParticleEffect = other.mParticleEffect; + mResult.mRainSpeed = other.mRainSpeed; + mResult.mRainFrequency = other.mRainFrequency; + mResult.mAmbientSoundVolume = 2*(factor-0.5); + mResult.mEffectFade = mResult.mAmbientSoundVolume; + mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID; + } } void WeatherManager::update(float duration, bool paused) @@ -361,7 +384,7 @@ void WeatherManager::update(float duration, bool paused) { mRendering->skyDisable(); mRendering->getSkyManager()->setLightningStrength(0.f); - stopSounds(true); + stopSounds(); return; } @@ -541,40 +564,25 @@ void WeatherManager::update(float duration, bool paused) mRendering->getSkyManager()->setWeather(mResult); // Play sounds - if (mNextWeather == "") + if (mPlayingSoundID != mResult.mAmbientLoopSoundID) { - std::string ambientSnd = mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID; - if (!ambientSnd.empty() && std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), ambientSnd) == mSoundsPlaying.end()) - { - mSoundsPlaying.push_back(ambientSnd); - MWBase::Environment::get().getSoundManager()->playSound(ambientSnd, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - } + stopSounds(); + if (!mResult.mAmbientLoopSoundID.empty()) + mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound(mResult.mAmbientLoopSoundID, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - std::string rainSnd = mWeatherSettings[mCurrentWeather].mRainLoopSoundID; - if (!rainSnd.empty() && std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), rainSnd) == mSoundsPlaying.end()) - { - mSoundsPlaying.push_back(rainSnd); - MWBase::Environment::get().getSoundManager()->playSound(rainSnd, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - } + mPlayingSoundID = mResult.mAmbientLoopSoundID; } - - stopSounds(false); + if (mAmbientSound.get()) + mAmbientSound->setVolume(mResult.mAmbientSoundVolume); } -void WeatherManager::stopSounds(bool stopAll) +void WeatherManager::stopSounds() { - std::vector::iterator it = mSoundsPlaying.begin(); - while (it!=mSoundsPlaying.end()) + if (mAmbientSound.get()) { - if (stopAll || - !((*it == mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID) || - (*it == mWeatherSettings[mCurrentWeather].mRainLoopSoundID))) - { - MWBase::Environment::get().getSoundManager()->stopSound(*it); - it = mSoundsPlaying.erase(it); - } - else - ++it; + MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); + mAmbientSound.reset(); + mPlayingSoundID.clear(); } } @@ -764,7 +772,7 @@ bool WeatherManager::readRecord(ESM::ESMReader& reader, int32_t type) state.load(reader); // reset other temporary state, now that we loaded successfully - stopSounds(true); // let's hope this never throws + stopSounds(); // let's hope this never throws mRegionOverrides.clear(); mRegionMods.clear(); mThunderFlash = 0.0; diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 97897fda92..dee9fc52ae 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -7,6 +7,8 @@ #include #include +#include "../mwbase/soundmanager.hpp" + namespace ESM { struct Region; @@ -61,10 +63,12 @@ namespace MWWorld bool mIsStorm; std::string mAmbientLoopSoundID; + float mAmbientSoundVolume; std::string mParticleEffect; - std::string mRainEffect; + float mEffectFade; + float mRainSpeed; float mRainFrequency; }; @@ -125,9 +129,6 @@ namespace MWWorld // This is used for Blight, Ashstorm and Blizzard (Bloodmoon) std::string mAmbientLoopSoundID; - // Rain sound effect - std::string mRainLoopSoundID; - // 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) @@ -173,7 +174,7 @@ namespace MWWorld */ void update(float duration, bool paused = false); - void stopSounds(bool stopAll); + void stopSounds(); void setHour(const float hour); @@ -206,6 +207,9 @@ namespace MWWorld bool mIsStorm; Ogre::Vector3 mStormDirection; + MWBase::SoundPtr mAmbientSound; + std::string mPlayingSoundID; + MWWorld::Fallback* mFallback; void setFallbackWeather(Weather& weather,const std::string& name); MWRender::RenderingManager* mRendering; @@ -214,8 +218,6 @@ namespace MWWorld std::map mRegionOverrides; - std::vector mSoundsPlaying; - std::string mCurrentWeather; std::string mNextWeather; From 406cf2b9814cbce5202871103ce7d4dfff1b55e2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 2 Dec 2014 11:17:39 +0100 Subject: [PATCH 042/167] disable element visibility buttons that do not apply to the respective cell type --- apps/opencs/view/render/pagedworldspacewidget.cpp | 9 +++++++++ apps/opencs/view/render/pagedworldspacewidget.hpp | 2 ++ apps/opencs/view/render/unpagedworldspacewidget.cpp | 8 ++++++++ apps/opencs/view/render/unpagedworldspacewidget.hpp | 4 ++++ apps/opencs/view/render/worldspacewidget.cpp | 2 -- apps/opencs/view/widget/scenetooltoggle2.cpp | 7 +++++-- apps/opencs/view/widget/scenetooltoggle2.hpp | 2 +- 7 files changed, 29 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index e5d5428581..764320a703 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -22,6 +22,7 @@ #include "../widget/scenetooltoggle.hpp" #include "../widget/scenetoolmode.hpp" +#include "../widget/scenetooltoggle2.hpp" #include "editmode.hpp" #include "elements.hpp" @@ -212,6 +213,14 @@ void CSVRender::PagedWorldspaceWidget::mouseDoubleClickEvent (QMouseEvent *event WorldspaceWidget::mouseDoubleClickEvent(event); } +void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons ( + CSVWidget::SceneToolToggle2 *tool) +{ + WorldspaceWidget::addVisibilitySelectorButtons (tool); + tool->addButton (Element_Terrain, "Terrain"); + tool->addButton (Element_Fog, "Fog", "", true); +} + void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( CSVWidget::SceneToolMode *tool) { diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index ca618d1220..3db6ee4edb 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -83,6 +83,8 @@ namespace CSVRender protected: + virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); + virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); virtual void updateOverlay(); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 07acbe493c..62cbdf1e1f 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -153,6 +153,14 @@ void CSVRender::UnpagedWorldspaceWidget::referenceAdded (const QModelIndex& pare flagAsModified(); } +void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( + CSVWidget::SceneToolToggle2 *tool) +{ + WorldspaceWidget::addVisibilitySelectorButtons (tool); + tool->addButton (Element_Terrain, "Terrain", "", true); + tool->addButton (Element_Fog, "Fog"); +} + std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() { Ogre::Vector3 position = getCamera()->getPosition(); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 237cb8f46f..d01c3e7667 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -60,6 +60,10 @@ namespace CSVRender virtual std::string getStartupInstruction(); + protected: + + virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); + 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 1f7c415122..51dfcd5066 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -263,10 +263,8 @@ void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { tool->addButton (Element_Reference, "References"); - tool->addButton (Element_Terrain, "Terrain"); tool->addButton (Element_Water, "Water"); tool->addButton (Element_Pathgrid, "Pathgrid"); - tool->addButton (Element_Fog, "Fog"); } void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool) diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index 1c5c11a4da..313e519cb4 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -72,7 +72,7 @@ void CSVWidget::SceneToolToggle2::showPanel (const QPoint& position) } void CSVWidget::SceneToolToggle2::addButton (unsigned int id, - const QString& name, const QString& tooltip) + const QString& name, const QString& tooltip, bool disabled) { std::ostringstream stream; stream << mSingleIcon << id; @@ -84,6 +84,9 @@ void CSVWidget::SceneToolToggle2::addButton (unsigned int id, button->setIconSize (QSize (mIconSize, mIconSize)); button->setFixedSize (mButtonSize, mButtonSize); + if (disabled) + button->setDisabled (true); + mLayout->addWidget (button); ButtonDesc desc; @@ -95,7 +98,7 @@ void CSVWidget::SceneToolToggle2::addButton (unsigned int id, connect (button, SIGNAL (clicked()), this, SLOT (selected())); - if (mButtons.size()==1) + if (mButtons.size()==1 && !disabled) mFirst = button; } diff --git a/apps/opencs/view/widget/scenetooltoggle2.hpp b/apps/opencs/view/widget/scenetooltoggle2.hpp index 4bd9ba26f6..0bae780f97 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.hpp +++ b/apps/opencs/view/widget/scenetooltoggle2.hpp @@ -56,7 +56,7 @@ namespace CSVWidget /// \attention After the last button has been added, setSelection must be called at /// least once to finalise the layout. void addButton (unsigned int id, - const QString& name, const QString& tooltip = ""); + const QString& name, const QString& tooltip = "", bool disabled = false); unsigned int getSelection() const; From bfa048e687586af7829f2d3626fbf185ad20be34 Mon Sep 17 00:00:00 2001 From: Paulo Viadanna Date: Tue, 2 Dec 2014 12:42:01 -0200 Subject: [PATCH 043/167] Fix #1734: AI will stop combat if target disappear --- apps/openmw/mwmechanics/aicombat.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 67fd544566..6249606322 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -325,6 +325,11 @@ namespace MWMechanics currentAction = prepareNextAction(actor, target); actionCooldown = currentAction->getActionCooldown(); } + + // Stop attacking if target is not seen + if (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(target, actor)) + return true; + if (currentAction.get()) currentAction->getCombatRange(rangeAttack, rangeFollow); From 4e756a2f4a971c62fd9e756d8e6fc7c44f26f5fc Mon Sep 17 00:00:00 2001 From: root Date: Wed, 3 Dec 2014 01:03:27 +0400 Subject: [PATCH 044/167] path to game get through jni --- components/files/androidpath.h | 19 +++++++++++++++++++ components/files/androidpath.hpp | 7 ++++++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 components/files/androidpath.h diff --git a/components/files/androidpath.h b/components/files/androidpath.h new file mode 100644 index 0000000000..3157c067f5 --- /dev/null +++ b/components/files/androidpath.h @@ -0,0 +1,19 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include + +#ifndef _Included_org_libsdl_app_SDLActivity_getPathToJni +#define _Included_org_libsdl_app_SDLActivity_getPathToJni +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: Java_org_libsdl_app_SDLActivity_getPathToJni + * Method: getPathToJni + * Signature: (I)I + */ +JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_getPathToJni(JNIEnv *env, jobject obj, jstring prompt); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/components/files/androidpath.hpp b/components/files/androidpath.hpp index 792462fc65..9d47d6d947 100644 --- a/components/files/androidpath.hpp +++ b/components/files/androidpath.hpp @@ -7,12 +7,17 @@ /** * \namespace Files */ + + namespace Files { - +class getJniPath{ +public: const char *getPathFromJni; +}; struct AndroidPath { AndroidPath(const std::string& application_name); + /** * \brief Return path to the user directory. From 85b8fca1f0112a1ea1b84f929007e48c8a7a81a4 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 3 Dec 2014 01:11:50 +0400 Subject: [PATCH 045/167] fixes --- components/files/androidpath.cpp | 46 ++++++++++++++++++++++++++++---- components/files/androidpath.hpp | 4 +-- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/components/files/androidpath.cpp b/components/files/androidpath.cpp index e2f9e948a1..1176260952 100644 --- a/components/files/androidpath.cpp +++ b/components/files/androidpath.cpp @@ -5,11 +5,37 @@ #include #include #include +#include "androidpath.h" #include #include + +class Buffer { + public: + static void setData(char const *data); + static char const * getData(); +}; +static char const *path; + +void Buffer::setData(char const *data) +{ + path=data; +} +char const * Buffer::getData() +{ + return path; +} + + +JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_getPathToJni(JNIEnv *env, jobject obj, jstring prompt) +{ + jboolean iscopy; + Buffer::setData((env)->GetStringUTFChars(prompt, &iscopy)); +} + namespace { + boost::filesystem::path getUserHome() { const char* dir = getenv("HOME"); @@ -53,22 +79,30 @@ AndroidPath::AndroidPath(const std::string& application_name) boost::filesystem::path AndroidPath::getUserConfigPath() const { - return getEnv("XDG_CONFIG_HOME", "/sdcard/libopenmw/config") / mName; + std::string buffer = ""; + buffer = buffer + Buffer::getData() +"/config"; + return getEnv("XDG_CONFIG_HOME", buffer) / mName; } boost::filesystem::path AndroidPath::getUserDataPath() const { - return getEnv("XDG_DATA_HOME", "/sdcard/libopenmw/share") / mName; + std::string buffer = ""; + buffer = buffer + Buffer::getData() +"/share"; + return getEnv("XDG_DATA_HOME", buffer) / mName; } boost::filesystem::path AndroidPath::getCachePath() const { - return getEnv("XDG_CACHE_HOME", "/sdcard/libopenmw/cache") / mName; + std::string buffer = ""; + buffer = buffer + Buffer::getData() +"/cache"; + return getEnv("XDG_CACHE_HOME", buffer) / mName; } boost::filesystem::path AndroidPath::getGlobalConfigPath() const { - boost::filesystem::path globalPath("/sdcard/libopenmw/"); + std::string buffer = ""; + buffer = buffer + Buffer::getData() +"/"; + boost::filesystem::path globalPath(buffer); return globalPath / mName; } @@ -79,7 +113,9 @@ boost::filesystem::path AndroidPath::getLocalPath() const boost::filesystem::path AndroidPath::getGlobalDataPath() const { - boost::filesystem::path globalDataPath("/sdcard/libopenmw/data"); + std::string buffer = ""; + buffer = buffer + Buffer::getData() +"/data"; + boost::filesystem::path globalDataPath(buffer); return globalDataPath / mName; } diff --git a/components/files/androidpath.hpp b/components/files/androidpath.hpp index 9d47d6d947..a8124e6db9 100644 --- a/components/files/androidpath.hpp +++ b/components/files/androidpath.hpp @@ -11,9 +11,7 @@ namespace Files { -class getJniPath{ -public: const char *getPathFromJni; -}; + struct AndroidPath { AndroidPath(const std::string& application_name); From e755f692cc015c1700b6c6b479b3def7352a2626 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 3 Dec 2014 09:42:12 +0100 Subject: [PATCH 046/167] silenced some annoying warnings --- apps/wizard/componentselectionpage.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index 956f0f2379..1fcde78579 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -62,7 +62,7 @@ void Wizard::ComponentSelectionPage::initializePage() if (field(QLatin1String("installation.new")).toBool() == true) { - morrowindItem->setFlags(morrowindItem->flags() & ~Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); + morrowindItem->setFlags((morrowindItem->flags() & ~Qt::ItemIsEnabled) | Qt::ItemIsUserCheckable); morrowindItem->setData(Qt::CheckStateRole, Qt::Checked); componentsList->addItem(morrowindItem); @@ -77,7 +77,7 @@ void Wizard::ComponentSelectionPage::initializePage() if (mWizard->mInstallations[path].hasMorrowind) { morrowindItem->setText(tr("Morrowind\t\t(installed)")); - morrowindItem->setFlags(morrowindItem->flags() & ~Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); + morrowindItem->setFlags((morrowindItem->flags() & ~Qt::ItemIsEnabled) | Qt::ItemIsUserCheckable); morrowindItem->setData(Qt::CheckStateRole, Qt::Unchecked); } else { morrowindItem->setText(tr("Morrowind")); @@ -88,7 +88,7 @@ void Wizard::ComponentSelectionPage::initializePage() if (mWizard->mInstallations[path].hasTribunal) { tribunalItem->setText(tr("Tribunal\t\t(installed)")); - tribunalItem->setFlags(tribunalItem->flags() & ~Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); + tribunalItem->setFlags((tribunalItem->flags() & ~Qt::ItemIsEnabled) | Qt::ItemIsUserCheckable); tribunalItem->setData(Qt::CheckStateRole, Qt::Unchecked); } else { tribunalItem->setText(tr("Tribunal")); @@ -99,7 +99,7 @@ void Wizard::ComponentSelectionPage::initializePage() if (mWizard->mInstallations[path].hasBloodmoon) { bloodmoonItem->setText(tr("Bloodmoon\t\t(installed)")); - bloodmoonItem->setFlags(bloodmoonItem->flags() & ~Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); + bloodmoonItem->setFlags((bloodmoonItem->flags() & ~Qt::ItemIsEnabled) | Qt::ItemIsUserCheckable); bloodmoonItem->setData(Qt::CheckStateRole, Qt::Unchecked); } else { bloodmoonItem->setText(tr("Bloodmoon")); From 58b6e757e39708f016fd7b386cad8466ee42277f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 3 Dec 2014 15:24:37 +0100 Subject: [PATCH 047/167] fixed another case folding problem regarding OpenCS resources handling --- apps/opencs/model/world/resources.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 8e255bc964..aeef59fe7a 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -55,7 +55,8 @@ CSMWorld::Resources::Resources (const std::string& baseDirectory, UniversalId::T std::string file = iter->substr (baseSize+1); mFiles.push_back (file); - mIndex.insert (std::make_pair (file, static_cast (mFiles.size())-1)); + mIndex.insert (std::make_pair ( + Misc::StringUtils::lowerCase (file), static_cast (mFiles.size())-1)); } } } From f2d991505ef97c80c1667256ed6a641351885c05 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 3 Dec 2014 15:31:00 +0100 Subject: [PATCH 048/167] handle other Windows-specific path issues regarding OpenCS resources handling --- apps/opencs/model/world/resources.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index aeef59fe7a..13c8df84d1 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -55,6 +56,7 @@ CSMWorld::Resources::Resources (const std::string& baseDirectory, UniversalId::T std::string file = iter->substr (baseSize+1); mFiles.push_back (file); + std::replace (file.begin(), file.end(), '\\', '/'); mIndex.insert (std::make_pair ( Misc::StringUtils::lowerCase (file), static_cast (mFiles.size())-1)); } @@ -90,6 +92,8 @@ int CSMWorld::Resources::searchId (const std::string& id) const { std::string id2 = Misc::StringUtils::lowerCase (id); + std::replace (id2.begin(), id2.end(), '\\', '/'); + std::map::const_iterator iter = mIndex.find (id2); if (iter==mIndex.end()) From 329a3558bfb073d670aa05ec88490e3b153fbe29 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 3 Dec 2014 15:36:07 +0100 Subject: [PATCH 049/167] updated credits file --- credits.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/credits.txt b/credits.txt index c6a5c71696..76efb2760a 100644 --- a/credits.txt +++ b/credits.txt @@ -96,6 +96,7 @@ terrorfisch Thomas Luppi (Digmaster) Tom Mason (wheybags) Torben Leif Carrington (TorbenC) +viadanna Vincent Heuken vocollapse From dd0cea21b0ead17a13198f3e584d343c6bb31875 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 1 Dec 2014 23:25:25 +0100 Subject: [PATCH 050/167] Implement overwriting pathgrid records (Fixes #2175) --- apps/openmw/mwworld/store.hpp | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 55c5b8191f..469b93f88e 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -876,8 +876,36 @@ namespace MWWorld public: void load(ESM::ESMReader &esm, const std::string &id) { - mStatic.push_back(ESM::Pathgrid()); - mStatic.back().load(esm); + + ESM::Pathgrid pathgrid; + pathgrid.load(esm); + + // Try to overwrite existing record + // Can't use search() because we aren't sorted yet + if (!pathgrid.mCell.empty()) + { + for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) + { + if ((*it).mCell == pathgrid.mCell) + { + (*it) = pathgrid; + return; + } + } + } + else + { + for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) + { + if ((*it).mData.mX == pathgrid.mData.mX && (*it).mData.mY == pathgrid.mData.mY) + { + (*it) = pathgrid; + return; + } + } + } + + mStatic.push_back(pathgrid); } size_t getSize() const { From 7faa849cef8ac27f423f037ef5d0fcb89cefbd16 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 2 Dec 2014 01:19:35 +0100 Subject: [PATCH 051/167] Fix fatigue recalculation using older value (oops) --- apps/openmw/mwmechanics/creaturestats.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 7bf0277325..21fd2203fd 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -147,6 +147,11 @@ namespace MWMechanics if (value != currentValue) { + if(!mIsWerewolf) + mAttributes[index] = value; + else + mWerewolfAttributes[index] = value; + if (index == ESM::Attribute::Intelligence) mRecalcMagicka = true; else if (index == ESM::Attribute::Strength || @@ -164,11 +169,6 @@ namespace MWMechanics setFatigue(fatigue); } } - - if(!mIsWerewolf) - mAttributes[index] = value; - else - mWerewolfAttributes[index] = value; } void CreatureStats::setHealth(const DynamicStat &value) From 3519d23518f9647b1e972d8e73f703c87b1bcd95 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 2 Dec 2014 01:37:19 +0100 Subject: [PATCH 052/167] Race dialog: remove incorrect assumption about numeric index in head/hair record IDs --- apps/openmw/mwgui/race.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 444b572b68..ec83b47a7d 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -128,11 +128,17 @@ namespace MWGui setRaceId(proto.mRace); recountParts(); - std::string index = proto.mHead.substr(proto.mHead.size() - 2, 2); - mFaceIndex = boost::lexical_cast(index) - 1; + for (unsigned int i=0; i(index) - 1; + for (unsigned int i=0; isetImageTexture (textureName); From e6c59f5585e73781c0c78c7d74f035fed8878e0a Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 2 Dec 2014 18:07:32 +0100 Subject: [PATCH 053/167] Revert "Allow NIF rotation matrices that include scale values" This reverts commit f57ddec6a22baf8823b93bd5e99df16796ac7d61. Conflicts: components/nif/nifstream.hpp (Fixes #2168) --- components/nif/data.hpp | 6 +++--- components/nif/nifstream.cpp | 2 +- components/nif/niftypes.hpp | 2 +- components/nif/node.cpp | 5 ++--- components/nifogre/mesh.cpp | 7 +++---- components/nifogre/skeleton.cpp | 14 +++----------- 6 files changed, 13 insertions(+), 23 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index f3b5a27f8f..5efd0d6c99 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -272,7 +272,7 @@ class NiSkinData : public Record public: struct BoneTrafo { - Ogre::Matrix3 rotationScale; // Rotation offset from bone, non-uniform scale + Ogre::Matrix3 rotation; // Rotation offset from bone? Ogre::Vector3 trans; // Translation float scale; // Probably scale (always 1) }; @@ -295,7 +295,7 @@ public: void read(NIFStream *nif) { - trafo.rotationScale = nif->getMatrix3(); + trafo.rotation = nif->getMatrix3(); trafo.trans = nif->getVector3(); trafo.scale = nif->getFloat(); @@ -307,7 +307,7 @@ public: { BoneInfo &bi = bones[i]; - bi.trafo.rotationScale = nif->getMatrix3(); + bi.trafo.rotation = nif->getMatrix3(); bi.trafo.trans = nif->getVector3(); bi.trafo.scale = nif->getFloat(); bi.unknown = nif->getVector4(); diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 527d1a2aff..a6fd5ef5ae 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -76,7 +76,7 @@ Transformation NIFStream::getTrafo() { Transformation t; t.pos = getVector3(); - t.rotationScale = getMatrix3(); + t.rotation = getMatrix3(); t.scale = getFloat(); return t; } diff --git a/components/nif/niftypes.hpp b/components/nif/niftypes.hpp index f9235ec45d..786c48b65e 100644 --- a/components/nif/niftypes.hpp +++ b/components/nif/niftypes.hpp @@ -35,7 +35,7 @@ namespace Nif struct Transformation { Ogre::Vector3 pos; - Ogre::Matrix3 rotationScale; + Ogre::Matrix3 rotation; float scale; static const Transformation& getIdentity() diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 7529e602d9..b7ca981133 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -42,9 +42,8 @@ void Node::getProperties(const Nif::NiTexturingProperty *&texprop, Ogre::Matrix4 Node::getLocalTransform() const { - Ogre::Matrix4 mat4 = Ogre::Matrix4(trafo.rotationScale); - mat4.setTrans(trafo.pos); - mat4.setScale(Ogre::Vector3(trafo.rotationScale[0][0], trafo.rotationScale[1][1], trafo.rotationScale[2][2]) * trafo.scale); + Ogre::Matrix4 mat4 = Ogre::Matrix4(Ogre::Matrix4::IDENTITY); + mat4.makeTransform(trafo.pos, Ogre::Vector3(trafo.scale), Ogre::Quaternion(trafo.rotation)); return mat4; } diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp index c952e664db..af73df637d 100644 --- a/components/nifogre/mesh.cpp +++ b/components/nifogre/mesh.cpp @@ -138,10 +138,9 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape const Nif::NodeList &bones = skin->bones; for(size_t b = 0;b < bones.length();b++) { - const Ogre::Matrix3& rotationScale = data->bones[b].trafo.rotationScale; - Ogre::Matrix4 mat (rotationScale); - mat.setTrans(data->bones[b].trafo.trans); - mat.setScale(Ogre::Vector3(rotationScale[0][0], rotationScale[1][1], rotationScale[2][2]) * data->bones[b].trafo.scale); + Ogre::Matrix4 mat; + mat.makeTransform(data->bones[b].trafo.trans, Ogre::Vector3(data->bones[b].trafo.scale), + Ogre::Quaternion(data->bones[b].trafo.rotation)); mat = bones[b]->getWorldTransform() * mat; const std::vector &weights = data->bones[b].weights; diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp index a3fade5b2c..db6a753c52 100644 --- a/components/nifogre/skeleton.cpp +++ b/components/nifogre/skeleton.cpp @@ -36,17 +36,9 @@ void NIFSkeletonLoader::buildBones(Ogre::Skeleton *skel, const Nif::Node *node, if(parent) parent->addChild(bone); mNifToOgreHandleMap[node->recIndex] = bone->getHandle(); - // decompose the local transform into position, scale and orientation. - // this is required for cases where the rotationScale matrix includes scaling, which the NIF format allows :( - // the code would look a bit nicer if Ogre allowed setting the transform matrix of a Bone directly, but we can't do that. - Ogre::Matrix4 mat(node->getLocalTransform()); - Ogre::Vector3 position, scale; - Ogre::Quaternion orientation; - mat.decomposition(position, scale, orientation); - bone->setOrientation(orientation); - bone->setPosition(position); - bone->setScale(scale); - + bone->setOrientation(node->trafo.rotation); + bone->setPosition(node->trafo.pos); + bone->setScale(Ogre::Vector3(node->trafo.scale)); bone->setBindingPose(); if(!(node->recType == Nif::RC_NiNode || /* Nothing special; children traversed below */ From 14ae6d28b09489ca7fc9599d5b27979f6c6fd8c1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 2 Dec 2014 18:42:13 +0100 Subject: [PATCH 054/167] Fix being able to jump when overencumbered --- apps/openmw/mwclass/npc.cpp | 3 ++ apps/openmw/mwinput/inputmanagerimp.cpp | 1 + apps/openmw/mwmechanics/character.cpp | 45 +++++++++++++------------ 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d3f86c03b0..641edcc834 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -969,6 +969,9 @@ namespace MWClass float Npc::getJump(const MWWorld::Ptr &ptr) const { + if(getEncumbrance(ptr) > getCapacity(ptr)) + return 0.f; + const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const GMST& gmst = getGmst(); const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 48acd22ba3..b0d2bfefff 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -371,6 +371,7 @@ namespace MWInput { mPlayer->setUpDown (1); triedToMove = true; + mOverencumberedMessageDelay = 0.f; } if (mAlwaysRunActive) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 11484ac49a..2e4f76c1a1 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1414,29 +1414,32 @@ void CharacterController::update(float duration) { // Started a jump. float z = cls.getJump(mPtr); - if(vec.x == 0 && vec.y == 0) - vec = Ogre::Vector3(0.0f, 0.0f, z); - else + if (z > 0) { - Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy(); - vec = Ogre::Vector3(lat.x, lat.y, 1.0f) * z * 0.707f; + if(vec.x == 0 && vec.y == 0) + vec = Ogre::Vector3(0.0f, 0.0f, z); + else + { + Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy(); + vec = Ogre::Vector3(lat.x, lat.y, 1.0f) * z * 0.707f; + } + + // advance acrobatics + if (mPtr.getRefData().getHandle() == "player") + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); + + // decrease fatigue + const MWWorld::Store &gmst = world->getStore().get(); + const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->getFloat(); + const float fatigueJumpMult = gmst.find("fFatigueJumpMult")->getFloat(); + float normalizedEncumbrance = mPtr.getClass().getNormalizedEncumbrance(mPtr); + if (normalizedEncumbrance > 1) + normalizedEncumbrance = 1; + const int fatigueDecrease = fatigueJumpBase + (1 - normalizedEncumbrance) * fatigueJumpMult; + DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); + fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); + cls.getCreatureStats(mPtr).setFatigue(fatigue); } - - // advance acrobatics - if (mPtr.getRefData().getHandle() == "player") - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); - - // decrease fatigue - const MWWorld::Store &gmst = world->getStore().get(); - const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->getFloat(); - const float fatigueJumpMult = gmst.find("fFatigueJumpMult")->getFloat(); - float normalizedEncumbrance = mPtr.getClass().getNormalizedEncumbrance(mPtr); - if (normalizedEncumbrance > 1) - normalizedEncumbrance = 1; - const int fatigueDecrease = fatigueJumpBase + (1 - normalizedEncumbrance) * fatigueJumpMult; - DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); - fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); - cls.getCreatureStats(mPtr).setFatigue(fatigue); } else if(mJumpState == JumpState_InAir) { From b650338d6934be67ccdebdf9c6350e8b39b050d7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 2 Dec 2014 18:42:40 +0100 Subject: [PATCH 055/167] Implement drawMode of NiStencilProperty (Feature #1057) --- components/nif/node.cpp | 7 +++++-- components/nif/node.hpp | 3 ++- components/nifogre/material.cpp | 28 ++++++++++++++++++++++++++++ components/nifogre/material.hpp | 2 ++ components/nifogre/mesh.cpp | 5 +++-- components/nifogre/ogrenifloader.cpp | 8 +++++--- files/materials/objects.mat | 1 + 7 files changed, 46 insertions(+), 8 deletions(-) diff --git a/components/nif/node.cpp b/components/nif/node.cpp index b7ca981133..fb68da548d 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -9,10 +9,11 @@ void Node::getProperties(const Nif::NiTexturingProperty *&texprop, const Nif::NiVertexColorProperty *&vertprop, const Nif::NiZBufferProperty *&zprop, const Nif::NiSpecularProperty *&specprop, - const Nif::NiWireframeProperty *&wireprop) const + const Nif::NiWireframeProperty *&wireprop, + const Nif::NiStencilProperty *&stencilprop) const { if(parent) - parent->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + parent->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop); for(size_t i = 0;i < props.length();i++) { @@ -35,6 +36,8 @@ void Node::getProperties(const Nif::NiTexturingProperty *&texprop, specprop = static_cast(pr); else if(pr->recType == Nif::RC_NiWireframeProperty) wireprop = static_cast(pr); + else if (pr->recType == Nif::RC_NiStencilProperty) + stencilprop = static_cast(pr); else std::cerr<< "Unhandled property type: "<recName <colors.size() != 0); @@ -205,6 +207,20 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, } } + if(stencilprop) + { + drawMode = stencilprop->data.drawMode; + if (stencilprop->data.enabled) + warn("Unhandled stencil test in "+name); + + Nif::ControllerPtr ctrls = stencilprop->controller; + while(!ctrls.empty()) + { + warn("Unhandled stencil controller "+ctrls->recName+" in "+name); + ctrls = ctrls->next; + } + } + // Material if(matprop) { @@ -249,8 +265,13 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, for(int i = 0;i < 7;i++) { if(!texName[i].empty()) + { boost::hash_combine(h, texName[i]); + boost::hash_combine(h, texprop->textures[i].clamp); + boost::hash_combine(h, texprop->textures[i].uvSet); + } } + boost::hash_combine(h, drawMode); boost::hash_combine(h, vertexColour); boost::hash_combine(h, alphaFlags); boost::hash_combine(h, alphaTest); @@ -308,6 +329,13 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, instance->setProperty("polygon_mode", sh::makeProperty(new sh::StringValue("wireframe"))); } + if (drawMode == 1) + instance->setProperty("cullmode", sh::makeProperty(new sh::StringValue("clockwise"))); + else if (drawMode == 2) + instance->setProperty("cullmode", sh::makeProperty(new sh::StringValue("anticlockwise"))); + else if (drawMode == 3) + instance->setProperty("cullmode", sh::makeProperty(new sh::StringValue("none"))); + instance->setProperty("diffuseMap", sh::makeProperty(texName[Nif::NiTexturingProperty::BaseTexture])); instance->setProperty("normalMap", sh::makeProperty(texName[Nif::NiTexturingProperty::BumpTexture])); instance->setProperty("detailMap", sh::makeProperty(texName[Nif::NiTexturingProperty::DetailTexture])); diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp index d485439cf2..6be52d1a56 100644 --- a/components/nifogre/material.hpp +++ b/components/nifogre/material.hpp @@ -18,6 +18,7 @@ namespace Nif class NiZBufferProperty; class NiSpecularProperty; class NiWireframeProperty; + class NiStencilProperty; } namespace NifOgre @@ -41,6 +42,7 @@ public: const Nif::NiZBufferProperty *zprop, const Nif::NiSpecularProperty *specprop, const Nif::NiWireframeProperty *wireprop, + const Nif::NiStencilProperty *stencilprop, bool &needTangents, bool particleMaterial=false); }; diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp index af73df637d..4932dd0098 100644 --- a/components/nifogre/mesh.cpp +++ b/components/nifogre/mesh.cpp @@ -320,13 +320,14 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape const Nif::NiZBufferProperty *zprop = NULL; const Nif::NiSpecularProperty *specprop = NULL; const Nif::NiWireframeProperty *wireprop = NULL; + const Nif::NiStencilProperty *stencilprop = NULL; bool needTangents = false; - shape->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + shape->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop); std::string matname = NIFMaterialLoader::getMaterial(data, mesh->getName(), mGroup, texprop, matprop, alphaprop, vertprop, zprop, specprop, - wireprop, needTangents); + wireprop, stencilprop, needTangents); if(matname.length() > 0) sub->setMaterialName(matname); diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index dcc34f6273..053adfb5b8 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -732,7 +732,8 @@ class NIFObjectLoader const Nif::NiZBufferProperty *zprop = NULL; const Nif::NiSpecularProperty *specprop = NULL; const Nif::NiWireframeProperty *wireprop = NULL; - node->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + const Nif::NiStencilProperty *stencilprop = NULL; + node->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop); Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : @@ -889,13 +890,14 @@ class NIFObjectLoader const Nif::NiZBufferProperty *zprop = NULL; const Nif::NiSpecularProperty *specprop = NULL; const Nif::NiWireframeProperty *wireprop = NULL; + const Nif::NiStencilProperty *stencilprop = NULL; bool needTangents = false; - partnode->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + partnode->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop); partsys->setMaterialName(NIFMaterialLoader::getMaterial(particledata, fullname, group, texprop, matprop, alphaprop, vertprop, zprop, specprop, - wireprop, needTangents, + wireprop, stencilprop, needTangents, // MW doesn't light particles, but the MaterialProperty // used still has lighting, so that must be ignored. true)); diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 149160760b..7d3085b0f2 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -67,6 +67,7 @@ material openmw_objects_base depth_check $depth_check transparent_sorting $transparent_sorting polygon_mode $polygon_mode + cull_hardware $cullmode texture_unit diffuseMap { From 75b0da5dce41960b3e9af9fdb69db1d9b3e20fdc Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 2 Dec 2014 21:04:38 +0100 Subject: [PATCH 056/167] Don't updateBoneTree for non-skinned parts (Fixes #2124) --- apps/openmw/mwrender/npcanimation.cpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index c43d3663eb..363a363763 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -60,6 +60,21 @@ std::string getVampireHead(const std::string& race, bool female) return "meshes\\" + sVampireMapping[thisCombination]->mModel; } +bool isSkinned (NifOgre::ObjectScenePtr scene) +{ + if (scene->mSkelBase == NULL) + return false; + for(size_t j = 0; j < scene->mEntities.size(); j++) + { + Ogre::Entity *ent = scene->mEntities[j]; + if(scene->mSkelBase != ent && ent->hasSkeleton()) + { + return true; + } + } + return false; +} + } @@ -611,10 +626,11 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) for(;ctrl != mObjectParts[i]->mControllers.end();++ctrl) ctrl->update(); - Ogre::Entity *ent = mObjectParts[i]->mSkelBase; - if(!ent) continue; - updateSkeletonInstance(baseinst, ent->getSkeleton()); - ent->getAllAnimationStates()->_notifyDirty(); + if (!isSkinned(mObjectParts[i])) + continue; + + updateSkeletonInstance(baseinst, mObjectParts[i]->mSkelBase->getSkeleton()); + mObjectParts[i]->mSkelBase->getAllAnimationStates()->_notifyDirty(); } return ret; @@ -697,7 +713,8 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g } } - updateSkeletonInstance(mSkelBase->getSkeleton(), skel); + if (isSkinned(mObjectParts[type])) + updateSkeletonInstance(mSkelBase->getSkeleton(), skel); } std::vector >::iterator ctrl(mObjectParts[type]->mControllers.begin()); From fee08f97edf41eab9f28e538ae9bca4205b0877f Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 3 Dec 2014 00:02:14 +0100 Subject: [PATCH 057/167] Fix crash in character preview for non-existing meshes (Fixes #2128) --- apps/openmw/mwgui/race.cpp | 10 +++++++++- apps/openmw/mwrender/characterpreview.cpp | 5 ++++- apps/openmw/mwrender/npcanimation.cpp | 10 +++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index ec83b47a7d..bcb766f8f3 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -313,7 +313,15 @@ namespace MWGui record.mHead = mAvailableHeads[mFaceIndex]; record.mHair = mAvailableHairs[mHairIndex]; - mPreview->setPrototype(record); + try + { + mPreview->setPrototype(record); + } + catch (std::exception& e) + { + std::cerr << "Error creating preview: " << e.what() << std::endl; + } + mPreviewDirty = true; } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index d9c953133e..92d0bcd557 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -115,8 +115,8 @@ namespace MWRender void CharacterPreview::rebuild() { - assert(mAnimation); delete mAnimation; + mAnimation = NULL; mAnimation = new NpcAnimation(mCharacter, mNode, 0, true, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); @@ -187,6 +187,9 @@ namespace MWRender void InventoryPreview::update() { + if (!mAnimation) + return; + mAnimation->updateParts(); MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 363a363763..d20e89324d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -676,7 +676,15 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g removeIndividualPart(type); mPartslots[type] = group; mPartPriorities[type] = priority; - mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type), enchantedGlow, glowColor); + try + { + mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type), enchantedGlow, glowColor); + } + catch (std::exception& e) + { + std::cerr << "Error adding NPC part: " << e.what() << std::endl; + return false; + } if (!mSoundsDisabled) { From d67ad9037e6586306b5d82d792e8924c89e4280b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 3 Dec 2014 20:55:33 +0100 Subject: [PATCH 058/167] increased version number --- CMakeLists.txt | 4 ++-- readme.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a923c6312..9587c652c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,8 +12,8 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 33) -set(OPENMW_VERSION_RELEASE 1) +set(OPENMW_VERSION_MINOR 34) +set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") diff --git a/readme.txt b/readme.txt index 810a0e055e..f4493c80e7 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. -Version: 0.33.1 +Version: 0.34.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org From 41ec6e42cbbebc7cd11c3bf41328397b8497d3e6 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 3 Dec 2014 20:58:44 +0100 Subject: [PATCH 059/167] updated changelog --- readme.txt | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/readme.txt b/readme.txt index f4493c80e7..91ec7962f1 100644 --- a/readme.txt +++ b/readme.txt @@ -98,6 +98,82 @@ Allowed options: CHANGELOG +0.34.0 + +Bug #904: omwlauncher doesn't allow installing Tribunal and Bloodmoon if only MW is installed +Bug #1061: "Browse to CD..." launcher crash +Bug #1135: Launcher crashes if user does not have write permission +Bug #1231: Current installer in launcher does not correctly import russian Morrowind.ini settings from setup.inx +Bug #1288: Fix the Alignment of the Resolution Combobox +Bug #1343: BIK videos occasionally out of sync with audio +Bug #1734: NPC in fight with invisible/sneaking player +Bug #1982: Long class names are cut off in the UI +Bug #2012: Editor: OpenCS script compiler sometimes fails to find IDs +Bug #2015: Running while levitating does not affect speed but still drains fatigue +Bug #2018: OpenMW don´t reset modified cells to vanilla when a plugin is deselected and don´t apply changes to cells already visited. +Bug #2045: ToggleMenus command should close dialogue windows +Bug #2046: Crash: light_de_streetlight_01_223 +Bug #2047: Buglamp tooltip minor correction +Bug #2050: Roobrush floating texture bits +Bug #2053: Slaves react negatively to PC picking up slave's bracers +Bug #2055: Dremora corpses use the wrong model +Bug #2056: Mansilamat Vabdas's corpse is floating in the water +Bug #2057: "Quest: Larius Varro Tells A Little Story": Bounty not completely removed after finishing quest +Bug #2059: Silenced enemies try to cast spells anyway +Bug #2060: Editor: Special case implementation for top level window with single sub-window should be optional +Bug #2061: Editor: SubView closing that is not directly triggered by the user isn't handled properly +Bug #2063: Tribunal: Quest 'The Warlords' doesn't work +Bug #2064: Sneak attack on hostiles causes bounty +Bug #2065: Editor: Qt signal-slot error when closing a dialogue subview +Bug #2070: Loading ESP in OpenMW works but fails in OpenCS +Bug #2071: CTD in 0.33 +Bug #2073: Storm atronach animation stops now and then +Bug #2075: Molag Amur Region, Map shows water on solid ground +Bug #2080: game won't work with fair magicka regen +Bug #2082: NPCs appear frozen or switched off after leaving and quickly reentering a cell +Bug #2088: OpenMW is unable to play OGG files. +Bug #2093: Darth Gares talks to you in Ilunibi even when he's not there, screwing up the Main Quests +Bug #2095: Coordinate and rotation editing in the Reference table does not work. +Bug #2096: Some overflow fun and bartering exploit +Bug #2098: [D3D] Game crash on maximize +Bug #2099: Activate, player seems not to work +Bug #2104: Only labels are sensitive in buttons +Bug #2107: "Slowfall" effect is too weak +Bug #2114: OpenCS doesn't load an ESP file full of errors even though Vanilla MW Construction Set can +Bug #2117: Crash when encountering bandits on opposite side of river from the egg mine south of Balmora +Bug #2124: [Mod: Baldurians Transparent Glass Amor] Armor above head +Bug #2125: Unnamed NiNodes in weapons problem in First Person +Bug #2126: Dirty dialog script in tribunal.esm causing bug in Tribunal MQ +Bug #2128: Crash when picking character's face +Bug #2129: Disable the third-person zoom feature by default +Bug #2130: Ash storm particles shown too long during transition to clear sky +Bug #2137: Editor: exception caused by following the Creature column of a SoundGen record +Bug #2139: Mouse movement should be ignored during intro video +Bug #2143: Editor: Saving is broken +Bug #2145: OpenMW - crash while exiting x64 debug build +Bug #2152: You can attack Almalexia during her final monologue +Bug #2154: Visual effects behave weirdly after loading/taking a screenshot +Bug #2155: Vivec has too little magicka +Bug #2156: Azura's spirit fades away too fast +Bug #2158: [Mod]Julan Ashlander Companion 2.0: Negative magicka +Bug #2161: Editor: combat/magic/stealth values of creature not displayed correctly +Bug #2163: OpenMW can't detect death if the NPC die by the post damage effect of a magic weapon. +Bug #2168: Westly's Master Head Pack X – Some hairs aren't rendered correctly. +Bug #2170: Mods using conversations to update PC inconsistant +Bug #2173: Launcher: disabling plugin files is broken +Bug #2175: Pathgrid mods do not overwrite the existing pathgrid +Bug #2180: Editor: Verifier doesn't handle Windows-specific path issues when dealing with resources +Feature #238: Add UI to run INI-importer from the launcher +Feature #854: Editor: Add user setting to show status bar +Feature #987: Launcher: first launch instructions for CD need to be more explicit +Feature #1232: There is no way to set the "encoding" option using launcher UI. +Feature #1281: Editor: Render cell markers +Feature #1918: Editor: Functionality for Double-Clicking in Tables +Feature #1966: Editor: User Settings dialogue grouping/labelling/tooltips +Feature #2097: Editor: Edit position of references in 3D scene +Feature #2121: Editor: Add edit mode button to scene toolbar +Task #1965: Editor: Improve layout of user settings dialogue + 0.33.1 Bug #2108: OpenCS fails to build From e67cf96250db8d1d11dc860d664a4b0682e8f39e Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 5 Dec 2014 01:09:42 +1100 Subject: [PATCH 060/167] Allow only one instance of OpenCS. Only tested on windows x64. --- apps/opencs/editor.cpp | 57 +++++++++++++++++++++++++++++++++++++++--- apps/opencs/editor.hpp | 4 +++ apps/opencs/main.cpp | 2 +- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index bef83b8ac7..dc4044fea9 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -22,7 +22,7 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) : mUserSettings (mCfgMgr), mOverlaySystem (0), mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), mPhysicsManager (0), - mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) + mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL), mPid(""), mLock() { std::pair > config = readConfig(); @@ -70,7 +70,10 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) } CS::Editor::~Editor () -{} +{ + if(mServer && boost::filesystem::exists(mPid)) + remove(mPid.string().c_str()); // ignore error +} void CS::Editor::setupDataFiles (const Files::PathContainer& dataDirs) { @@ -233,7 +236,54 @@ void CS::Editor::showSettings() bool CS::Editor::makeIPCServer() { - mServer = new QLocalServer(this); + try + { + mPid = boost::filesystem::temp_directory_path(); + mPid += "opencs.pid"; + bool pidExists = boost::filesystem::exists(mPid); + + boost::filesystem::ofstream tempFile(mPid); + + mLock = boost::interprocess::file_lock(mPid.string().c_str()); + if(!mLock.try_lock()) + { + std::cerr << "OpenCS already running." << std::endl; + return false; + } + +#ifdef _WIN32 + tempFile << GetCurrentProcessId() << std::endl; +#else + tempFile << getpid() << std::endl; +#endif + tempFile.close(); + + mServer = new QLocalServer(this); + + if(pidExists) + { + // hack to get the temp directory path + mServer->listen("dummy"); + QString fullPath = mServer->fullServerName(); + mServer->close(); + fullPath.remove(QRegExp("dummy$")); + fullPath += mIpcServerName; + if(boost::filesystem::exists(fullPath.toStdString().c_str())) + { + // TODO: compare pid of the current process with that in the file + std::cout << "Detected unclean shutdown." << std::endl; + // delete the stale file + if(remove(fullPath.toStdString().c_str())) + std::cerr << "ERROR removing stale connection file" << std::endl; + } + } + } + + catch(const std::exception& e) + { + std::cerr << "ERROR " << e.what() << std::endl; + return false; + } if(mServer->listen(mIpcServerName)) { @@ -242,6 +292,7 @@ bool CS::Editor::makeIPCServer() } mServer->close(); + mServer = NULL; return false; } diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index d55b0e873e..c8a6c43c3f 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -3,6 +3,8 @@ #include +#include + #include #include #include @@ -54,6 +56,8 @@ namespace CS CSVDoc::FileDialog mFileDialog; boost::filesystem::path mLocal; boost::filesystem::path mResources; + boost::filesystem::path mPid; + boost::interprocess::file_lock mLock; bool mFsStrict; void setupDataFiles (const Files::PathContainer& dataDirs); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index b184a1ef1e..dd20324d12 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -83,7 +83,7 @@ int main(int argc, char *argv[]) if(!editor.makeIPCServer()) { editor.connectToIPCServer(); - // return 0; + return 0; } shinyFactory = editor.setupGraphics(); From 07f10a014071bbe642ac463683c79daf8e964762 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 5 Dec 2014 01:32:20 +1100 Subject: [PATCH 061/167] Use append syntax compatible with older versions of boost. --- apps/opencs/editor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index dc4044fea9..2aee21d382 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -239,7 +239,7 @@ bool CS::Editor::makeIPCServer() try { mPid = boost::filesystem::temp_directory_path(); - mPid += "opencs.pid"; + mPid /= "opencs.pid"; bool pidExists = boost::filesystem::exists(mPid); boost::filesystem::ofstream tempFile(mPid); From 6731afc79c44a591887b0ef91d9c2c6c2468162c Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 5 Dec 2014 03:59:16 +1100 Subject: [PATCH 062/167] Use float for setting skill use values. Should resolve bug #2183. --- apps/opencs/model/world/columnimp.hpp | 2 +- apps/opencs/view/world/util.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 6f830e6e3e..eba73cf0b0 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -272,7 +272,7 @@ namespace CSMWorld { ESXRecordT record2 = record.get(); - record2.mData.mUseValue[mIndex] = data.toInt(); + record2.mData.mUseValue[mIndex] = data.toFloat(); record.setModified (record2); } diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index f987c8d9f2..c65e12c609 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -173,6 +173,8 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO { QDoubleSpinBox *dsb = new QDoubleSpinBox(parent); dsb->setRange(FLT_MIN, FLT_MAX); + dsb->setSingleStep(0.01f); + dsb->setDecimals(3); return dsb; } From ab693f1f649c497f9d0b00e2491ed23eb26a7c86 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 5 Dec 2014 07:50:03 +1100 Subject: [PATCH 063/167] Workaround file lock being lost if the same file is closed elsewhere in the program (see https://svn.boost.org/trac/boost/ticket/3582) --- apps/opencs/editor.cpp | 11 ++++++----- apps/opencs/editor.hpp | 2 ++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 9fe974d86b..f609b80b74 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -72,8 +72,10 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) CS::Editor::~Editor () { + mPidFile.close(); + if(mServer && boost::filesystem::exists(mPid)) - remove(mPid.string().c_str()); // ignore error + remove(mPid.string().c_str()); // ignore any error // cleanup global resources used by OEngine delete OEngine::Physic::BulletShapeManager::getSingletonPtr(); @@ -246,7 +248,7 @@ bool CS::Editor::makeIPCServer() mPid /= "opencs.pid"; bool pidExists = boost::filesystem::exists(mPid); - boost::filesystem::ofstream tempFile(mPid); + mPidFile.open(mPid); mLock = boost::interprocess::file_lock(mPid.string().c_str()); if(!mLock.try_lock()) @@ -256,11 +258,10 @@ bool CS::Editor::makeIPCServer() } #ifdef _WIN32 - tempFile << GetCurrentProcessId() << std::endl; + mPidFile << GetCurrentProcessId() << std::endl; #else - tempFile << getpid() << std::endl; + mPidFile << getpid() << std::endl; #endif - tempFile.close(); mServer = new QLocalServer(this); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 30a031309b..273f0825b8 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -56,6 +57,7 @@ namespace CS boost::filesystem::path mResources; boost::filesystem::path mPid; boost::interprocess::file_lock mLock; + boost::filesystem::ofstream mPidFile; bool mFsStrict; void setupDataFiles (const Files::PathContainer& dataDirs); From 83dcf9ce4bb7fd2d78c5e1ef0395eceb58505f8d Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Dec 2014 02:17:15 +0100 Subject: [PATCH 064/167] Overwrite existing records in IndexedStore (Fixes #2182) --- apps/openmw/mwgui/tooltips.cpp | 4 +- .../mwmechanics/mechanicsmanagerimp.cpp | 4 +- apps/openmw/mwworld/store.hpp | 66 ++++--------------- 3 files changed, 18 insertions(+), 56 deletions(-) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 4608010ac3..396c8fa489 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -633,8 +633,8 @@ namespace MWGui MWWorld::Store::iterator it = skills.begin(); for (; it != skills.end(); ++it) { - if (it->mData.mSpecialization == specId) - specText += std::string("\n#{") + ESM::Skill::sSkillNameIds[it->mIndex] + "}"; + if (it->second.mData.mSpecialization == specId) + specText += std::string("\n#{") + ESM::Skill::sSkillNameIds[it->first] + "}"; } widget->setUserString("Caption_CenteredCaptionText", specText); widget->setUserString("ToolTipLayout", "TextWithCenteredCaptionToolTip"); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index b4edf44aa9..9cafe9b3ce 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -143,9 +143,9 @@ namespace MWMechanics MWWorld::Store::iterator iter = skills.begin(); for (; iter != skills.end(); ++iter) { - if (iter->mData.mSpecialization==class_->mData.mSpecialization) + if (iter->second.mData.mSpecialization==class_->mData.mSpecialization) { - int index = iter->mIndex; + int index = iter->first; if (index>=0 && index<27) { diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 469b93f88e..907d9bd430 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -1015,24 +1015,15 @@ namespace MWWorld template class IndexedStore { - struct Compare - { - bool operator()(const T &x, const T &y) const { - return x.mIndex < y.mIndex; - } - }; protected: - std::vector mStatic; + typedef typename std::map Static; + Static mStatic; public: - typedef typename std::vector::const_iterator iterator; + typedef typename std::map::const_iterator iterator; IndexedStore() {} - IndexedStore(unsigned int size) { - mStatic.reserve(size); - } - iterator begin() const { return mStatic.begin(); } @@ -1041,10 +1032,14 @@ namespace MWWorld return mStatic.end(); } - /// \todo refine loading order void load(ESM::ESMReader &esm) { - mStatic.push_back(T()); - mStatic.back().load(esm); + T record; + record.load(esm); + + // Try to overwrite existing record + std::pair found = mStatic.insert(std::make_pair(record.mIndex, record)); + if (found.second) + found.first->second = record; } int getSize() const { @@ -1052,40 +1047,13 @@ namespace MWWorld } void setUp() { - /// \note This method sorts indexed values for further - /// searches. Every loaded item is present in storage, but - /// latest loaded shadows any previous while searching. - /// If memory cost will be too high, it is possible to remove - /// unused values. - - Compare cmp; - - std::stable_sort(mStatic.begin(), mStatic.end(), cmp); - - typename std::vector::iterator first, next; - next = first = mStatic.begin(); - - while (first != mStatic.end() && ++next != mStatic.end()) { - while (next != mStatic.end() && !cmp(*first, *next)) { - ++next; - } - if (first != --next) { - std::swap(*first, *next); - } - first = ++next; - } } const T *search(int index) const { - T item; - item.mIndex = index; - - iterator it = - std::lower_bound(mStatic.begin(), mStatic.end(), item, Compare()); - if (it != mStatic.end() && it->mIndex == index) { - return &(*it); - } - return 0; + typename Static::const_iterator it = mStatic.find(index); + if (it != mStatic.end()) + return &(it->second); + return NULL; } const T *find(int index) const { @@ -1103,18 +1071,12 @@ namespace MWWorld struct Store : public IndexedStore { Store() {} - Store(unsigned int size) - : IndexedStore(size) - {} }; template <> struct Store : public IndexedStore { Store() {} - Store(unsigned int size) - : IndexedStore(size) - {} }; template <> From a67e7c64eadbfe23aa20214f44a5a68c95a40e35 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Dec 2014 15:58:22 +0100 Subject: [PATCH 065/167] Optimize pathgrid store --- apps/openmw/mwworld/store.hpp | 124 ++++++---------------------------- 1 file changed, 20 insertions(+), 104 deletions(-) diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 907d9bd430..5966a99b9e 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -842,36 +842,12 @@ namespace MWWorld template <> class Store : public StoreBase { - public: - typedef std::vector::const_iterator iterator; - private: - std::vector mStatic; + typedef std::map Interior; + typedef std::map, ESM::Pathgrid> Exterior; - std::vector::iterator mIntBegin, mIntEnd, mExtBegin, mExtEnd; - - struct IntExtOrdering - { - bool operator()(const ESM::Pathgrid &x, const ESM::Pathgrid &y) const { - // interior pathgrids precedes exterior ones (x < y) - if ((x.mData.mX == 0 && x.mData.mY == 0) && - (y.mData.mX != 0 || y.mData.mY != 0)) - { - return true; - } - return false; - } - }; - - struct ExtCompare - { - bool operator()(const ESM::Pathgrid &x, const ESM::Pathgrid &y) const { - if (x.mData.mX == y.mData.mX) { - return x.mData.mY < y.mData.mY; - } - return x.mData.mX < y.mData.mX; - } - }; + Interior mInt; + Exterior mExt; public: @@ -881,65 +857,33 @@ namespace MWWorld pathgrid.load(esm); // Try to overwrite existing record - // Can't use search() because we aren't sorted yet if (!pathgrid.mCell.empty()) { - for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) - { - if ((*it).mCell == pathgrid.mCell) - { - (*it) = pathgrid; - return; - } - } + std::pair found = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); + if (found.second) + found.first->second = pathgrid; } else { - for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) - { - if ((*it).mData.mX == pathgrid.mData.mX && (*it).mData.mY == pathgrid.mData.mY) - { - (*it) = pathgrid; - return; - } - } + std::pair found = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), + pathgrid)); + if (found.second) + found.first->second = pathgrid; } - - mStatic.push_back(pathgrid); } size_t getSize() const { - return mStatic.size(); + return mInt.size() + mExt.size(); } void setUp() { - IntExtOrdering cmp; - std::sort(mStatic.begin(), mStatic.end(), cmp); - - ESM::Pathgrid pg; - pg.mData.mX = pg.mData.mY = 1; - mExtBegin = - std::lower_bound(mStatic.begin(), mStatic.end(), pg, cmp); - mExtEnd = mStatic.end(); - - mIntBegin = mStatic.begin(); - mIntEnd = mExtBegin; - - std::sort(mIntBegin, mIntEnd, RecordCmp()); - std::sort(mExtBegin, mExtEnd, ExtCompare()); } const ESM::Pathgrid *search(int x, int y) const { - ESM::Pathgrid pg; - pg.mData.mX = x; - pg.mData.mY = y; - - iterator it = - std::lower_bound(mExtBegin, mExtEnd, pg, ExtCompare()); - if (it != mExtEnd && it->mData.mX == x && it->mData.mY == y) { - return &(*it); - } - return 0; + Exterior::const_iterator it = mExt.find(std::make_pair(x,y)); + if (it != mExt.end()) + return &(it->second); + return NULL; } const ESM::Pathgrid *find(int x, int y) const { @@ -953,14 +897,10 @@ namespace MWWorld } const ESM::Pathgrid *search(const std::string &name) const { - ESM::Pathgrid pg; - pg.mCell = name; - - iterator it = std::lower_bound(mIntBegin, mIntEnd, pg, RecordCmp()); - if (it != mIntEnd && Misc::StringUtils::ciEqual(it->mCell, name)) { - return &(*it); - } - return 0; + Interior::const_iterator it = mInt.find(name); + if (it != mInt.end()) + return &(it->second); + return NULL; } const ESM::Pathgrid *find(const std::string &name) const { @@ -986,30 +926,6 @@ namespace MWWorld } return find(cell.mData.mX, cell.mData.mY); } - - iterator begin() const { - return mStatic.begin(); - } - - iterator end() const { - return mStatic.end(); - } - - iterator interiorPathsBegin() const { - return mIntBegin; - } - - iterator interiorPathsEnd() const { - return mIntEnd; - } - - iterator exteriorPathsBegin() const { - return mExtBegin; - } - - iterator exteriorPathsEnd() const { - return mExtEnd; - } }; template From 65536f085768fd1d966a603a7e7f4d48f407da8b Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Dec 2014 18:00:30 +0100 Subject: [PATCH 066/167] Load initial particle system state from NIF files (Fixes #2178) --- apps/openmw/mwrender/animation.cpp | 4 -- apps/openmw/mwrender/npcanimation.cpp | 4 -- components/nifogre/ogrenifloader.cpp | 82 +++++++++++++++++++++++++++ components/nifogre/ogrenifloader.hpp | 4 ++ 4 files changed, 86 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 548906cdf2..ecaaba0b9d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -114,10 +114,6 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, mdlname) : NifOgre::Loader::createObjectBase(mInsert, mdlname)); - // Fast forward auto-play particles, which will have been set up as Emitting by the loader. - for (unsigned int i=0; imParticles.size(); ++i) - mObjectRoot->mParticles[i]->fastForward(1, 0.1); - if(mObjectRoot->mSkelBase) { mSkelBase = mObjectRoot->mSkelBase; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d20e89324d..f2bc3df95f 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -571,10 +571,6 @@ NifOgre::ObjectScenePtr NpcAnimation::insertBoundedPart(const std::string &model std::for_each(objects->mEntities.begin(), objects->mEntities.end(), SetObjectGroup(group)); std::for_each(objects->mParticles.begin(), objects->mParticles.end(), SetObjectGroup(group)); - // Fast forward auto-play particles, which will have been set up as Emitting by the loader. - for (unsigned int i=0; imParticles.size(); ++i) - objects->mParticles[i]->fastForward(1, 0.1); - if(objects->mSkelBase) { Ogre::AnimationStateSet *aset = objects->mSkelBase->getAllAnimationStates(); diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 053adfb5b8..b18a88d140 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -161,6 +161,38 @@ void ObjectScene::rotateBillboardNodes(Ogre::Camera *camera) } } +void ObjectScene::_notifyAttached() +{ + // convert initial particle positions to world space for world-space particle systems + // this can't be done on creation because the particle system is not in its correct world space position yet + for (std::vector::iterator it = mParticles.begin(); it != mParticles.end(); ++it) + { + Ogre::ParticleSystem* psys = *it; + if (psys->getKeepParticlesInLocalSpace()) + continue; + Ogre::ParticleIterator pi = psys->_getIterator(); + while (!pi.end()) + { + Ogre::Particle *p = pi.getNext(); + +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + Ogre::Vector3& position = p->mPosition; + Ogre::Vector3& direction = p->mDirection; +#else + Ogre::Vector3& position = p->position; + Ogre::Vector3& direction = p->direction; +#endif + + position = + (psys->getParentNode()->_getDerivedOrientation() * + (psys->getParentNode()->_getDerivedScale() * position)) + + psys->getParentNode()->_getDerivedPosition(); + direction = + (psys->getParentNode()->_getDerivedOrientation() * direction); + } + } +} + // Animates a texture class FlipController { @@ -949,6 +981,8 @@ class NIFObjectLoader createParticleEmitterAffectors(partsys, partctrl, trgtbone, scene->mSkelBase->getName()); } + createParticleInitialState(partsys, particledata, partctrl); + Ogre::ControllerValueRealPtr srcval((partflags&Nif::NiNode::ParticleFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); @@ -975,6 +1009,50 @@ class NIFObjectLoader createMaterialControllers(partnode, partsys, animflags, scene); } + static void createParticleInitialState(Ogre::ParticleSystem* partsys, const Nif::NiAutoNormalParticlesData* particledata, + const Nif::NiParticleSystemController* partctrl) + { + partsys->_update(0.f); // seems to be required to allocate mFreeParticles + int i=0; + for (std::vector::const_iterator it = partctrl->particles.begin(); + iactiveCount && it != partctrl->particles.end(); ++it, ++i) + { + const Nif::NiParticleSystemController::Particle& particle = *it; + + Ogre::Particle* created = partsys->createParticle(); + if (!created) + break; + +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + Ogre::Vector3& position = created->mPosition; + Ogre::Vector3& direction = created->mDirection; + Ogre::ColourValue& colour = created->mColour; + float& totalTimeToLive = created->mTotalTimeToLive; + float& timeToLive = created->mTimeToLive; +#else + Ogre::Vector3& position = created->position; + Ogre::Vector3& direction = created->direction; + Ogre::ColourValue& colour = created->colour; + float& totalTimeToLive = created->totalTimeToLive; + float& timeToLive = created->timeToLive; +#endif + + direction = particle.velocity; + position = particledata->vertices.at(particle.vertex); + + if (particle.vertex < int(particledata->colors.size())) + { + Ogre::Vector4 partcolour = particledata->colors.at(particle.vertex); + colour = Ogre::ColourValue(partcolour.x, partcolour.y, partcolour.z, partcolour.w); + } + else + colour = Ogre::ColourValue(1.f, 1.f, 1.f, 1.f); + float size = particledata->sizes.at(particle.vertex); + created->setDimensions(size, size); + totalTimeToLive = std::max(0.f, particle.lifespan); + timeToLive = std::max(0.f, particle.lifespan - particle.lifetime); + } + } static void createNodeControllers(const Nif::NIFFilePtr& nif, const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags) { @@ -1275,6 +1353,8 @@ ObjectScenePtr Loader::createObjects(Ogre::SceneNode *parentNode, std::string na parentNode->attachObject(entity); } + scene->_notifyAttached(); + return scene; } @@ -1342,6 +1422,8 @@ ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bo } } + scene->_notifyAttached(); + return scene; } diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp index abadd38de5..485495a388 100644 --- a/components/nifogre/ogrenifloader.hpp +++ b/components/nifogre/ogrenifloader.hpp @@ -84,6 +84,10 @@ struct ObjectScene { void rotateBillboardNodes(Ogre::Camera* camera); void setVisibilityFlags (unsigned int flags); + + // This is called internally by the OgreNifLoader once all elements of the + // scene have been attached to their respective nodes. + void _notifyAttached(); }; typedef Ogre::SharedPtr ObjectScenePtr; From 5a2564907634f37ff63b87b3bf752ce8ee5c1df6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Dec 2014 20:58:03 +0100 Subject: [PATCH 067/167] Implement XYZ rotation keys support (Fixes #1067) --- components/nif/data.hpp | 16 ++++++++-------- components/nif/nifkey.hpp | 6 ++---- components/nifogre/ogrenifloader.cpp | 21 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 5efd0d6c99..d9de12fb54 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -350,8 +350,11 @@ struct NiMorphData : public Record struct NiKeyframeData : public Record { QuaternionKeyMap mRotations; - //\FIXME mXYZ_Keys are read, but not used. - FloatKeyMap mXYZ_Keys; + + FloatKeyMap mXRotations; + FloatKeyMap mYRotations; + FloatKeyMap mZRotations; + Vector3KeyMap mTranslations; FloatKeyMap mScales; @@ -362,12 +365,9 @@ struct NiKeyframeData : public Record { //Chomp unused float nif->getFloat(); - for(size_t i=0;i<3;++i) - { - //Read concatenates items together. - mXYZ_Keys.read(nif,true); - } - nif->file->warn("XYZ_ROTATION_KEY read, but not used!"); + mXRotations.read(nif, true); + mYRotations.read(nif, true); + mZRotations.read(nif, true); } mTranslations.read(nif); mScales.read(nif); diff --git a/components/nif/nifkey.hpp b/components/nif/nifkey.hpp index b0db80914f..cb81427201 100644 --- a/components/nif/nifkey.hpp +++ b/components/nif/nifkey.hpp @@ -49,9 +49,7 @@ struct KeyMapT { if(count == 0 && !force) return; - //If we aren't forcing things, make sure that read clears any previous keys - if(!force) - mKeys.clear(); + mKeys.clear(); mInterpolationType = nif->getUInt(); @@ -88,7 +86,7 @@ struct KeyMapT { //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. + // Re-runs the read function 3 more times. // When it does that it's reading in a bunch of sLinearInterpolation keys, not sXYZInterpolation. else if(mInterpolationType == sXYZInterpolation) { diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index b18a88d140..1d1e1a22d0 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -442,6 +442,9 @@ public: { private: const Nif::QuaternionKeyMap* mRotations; + const Nif::FloatKeyMap* mXRotations; + const Nif::FloatKeyMap* mYRotations; + const Nif::FloatKeyMap* mZRotations; const Nif::Vector3KeyMap* mTranslations; const Nif::FloatKeyMap* mScales; Nif::NIFFilePtr mNif; // Hold a SharedPtr to make sure key lists stay valid @@ -472,11 +475,25 @@ public: return keys.rbegin()->second.mValue; } + Ogre::Quaternion getXYZRotation(float time) const + { + float xrot = interpKey(mXRotations->mKeys, time); + float yrot = interpKey(mYRotations->mKeys, time); + float zrot = interpKey(mZRotations->mKeys, time); + Ogre::Quaternion xr(Ogre::Radian(xrot), Ogre::Vector3::UNIT_X); + Ogre::Quaternion yr(Ogre::Radian(yrot), Ogre::Vector3::UNIT_Y); + Ogre::Quaternion zr(Ogre::Radian(zrot), Ogre::Vector3::UNIT_Z); + return (xr*yr*zr); + } + public: /// @note The NiKeyFrameData must be valid as long as this KeyframeController exists. Value(Ogre::Node *target, const Nif::NIFFilePtr& nif, const Nif::NiKeyframeData *data) : NodeTargetValue(target) , mRotations(&data->mRotations) + , mXRotations(&data->mXRotations) + , mYRotations(&data->mYRotations) + , mZRotations(&data->mZRotations) , mTranslations(&data->mTranslations) , mScales(&data->mScales) , mNif(nif) @@ -486,6 +503,8 @@ public: { if(mRotations->mKeys.size() > 0) return interpKey(mRotations->mKeys, time); + else if (!mXRotations->mKeys.empty() || !mYRotations->mKeys.empty() || !mZRotations->mKeys.empty()) + return getXYZRotation(time); return mNode->getOrientation(); } @@ -513,6 +532,8 @@ public: { if(mRotations->mKeys.size() > 0) mNode->setOrientation(interpKey(mRotations->mKeys, time)); + else if (!mXRotations->mKeys.empty() || !mYRotations->mKeys.empty() || !mZRotations->mKeys.empty()) + mNode->setOrientation(getXYZRotation(time)); if(mTranslations->mKeys.size() > 0) mNode->setPosition(interpKey(mTranslations->mKeys, time)); if(mScales->mKeys.size() > 0) From e313ed3cefea5c41865d4d3d45f6abf8f9564312 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Dec 2014 20:58:33 +0100 Subject: [PATCH 068/167] Support animated container models --- apps/openmw/mwclass/container.cpp | 7 +++++-- apps/openmw/mwrender/activatoranimation.cpp | 13 ++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 179070aedf..59e51e4613 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -7,6 +7,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/failedaction.hpp" @@ -21,7 +22,7 @@ #include "../mwgui/tooltips.hpp" -#include "../mwrender/objects.hpp" +#include "../mwrender/actors.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwmechanics/npcstats.hpp" @@ -87,7 +88,8 @@ namespace MWClass { const std::string model = getModel(ptr); if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model); + MWRender::Actors& actors = renderingInterface.getActors(); + actors.insertActivator(ptr); } } @@ -96,6 +98,7 @@ namespace MWClass const std::string model = getModel(ptr); if(!model.empty()) physics.addObject(ptr); + MWBase::Environment::get().getMechanicsManager()->add(ptr); } std::string Container::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwrender/activatoranimation.cpp b/apps/openmw/mwrender/activatoranimation.cpp index de0457e578..540876a3ec 100644 --- a/apps/openmw/mwrender/activatoranimation.cpp +++ b/apps/openmw/mwrender/activatoranimation.cpp @@ -4,6 +4,8 @@ #include "../mwbase/world.hpp" +#include "../mwworld/class.hpp" + #include "renderconst.hpp" namespace MWRender @@ -16,17 +18,14 @@ ActivatorAnimation::~ActivatorAnimation() ActivatorAnimation::ActivatorAnimation(const MWWorld::Ptr &ptr) : Animation(ptr, ptr.getRefData().getBaseNode()) { - MWWorld::LiveCellRef *ref = mPtr.get(); + const std::string& model = mPtr.getClass().getModel(mPtr); - assert(ref->mBase != NULL); - if(!ref->mBase->mModel.empty()) + if(!model.empty()) { - const std::string name = "meshes\\"+ref->mBase->mModel; - - setObjectRoot(name, false); + setObjectRoot(model, false); setRenderProperties(mObjectRoot, RV_Misc, RQG_Main, RQG_Alpha); - addAnimSource(name); + addAnimSource(model); } } From 416d549568789db9eef0ff11278f6052b2530794 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Dec 2014 22:02:18 +0100 Subject: [PATCH 069/167] Fix animation glitch caused by knockdown If the player was knocked down while having no weapon, spell nor fists ready, the animation state would incorrectly shift to "weapon equipped" even though no weapon is equipped. --- 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 2e4f76c1a1..46e06f4604 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1072,7 +1072,8 @@ bool CharacterController::updateWeaponState() } else if (mHitState == CharState_KnockDown) { - mUpperBodyState = UpperCharState_WeapEquiped; + if (mUpperBodyState > UpperCharState_WeapEquiped) + mUpperBodyState = UpperCharState_WeapEquiped; mAnimation->disable(mCurrentWeapon); } } From 3f0bc6eecbef10d63fc54a3282c3b61823fd008f Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 5 Dec 2014 23:36:06 +0100 Subject: [PATCH 070/167] Ignore extra bytes after the SCVR string list (Fixes #2184) --- components/esm/loadscpt.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 19e3f3bb3a..07561c2eaf 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -30,7 +30,14 @@ void Script::load(ESMReader &esm) int s = mData.mStringTableSize; std::vector tmp (s); - esm.getHExact (&tmp[0], s); + // not using getHExact, vanilla doesn't seem to mind unused bytes at the end + esm.getSubHeader(); + int left = esm.getSubSize(); + if (left < s) + esm.fail("SCVR string list is smaller than specified"); + esm.getExact(&tmp[0], s); + if (left > s) + esm.skip(left-s); // skip the leftover junk // Set up the list of variable names mVarNames.resize(mData.mNumShorts + mData.mNumLongs + mData.mNumFloats); From 7c59ea629694fde2322d0c6b7082e1e6f5642a7b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 6 Dec 2014 13:01:55 +0100 Subject: [PATCH 071/167] added specialised report table --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/tools/reportsubview.cpp | 36 ++++------------ apps/opencs/view/tools/reportsubview.hpp | 23 ++-------- apps/opencs/view/tools/reporttable.cpp | 53 ++++++++++++++++++++++++ apps/opencs/view/tools/reporttable.hpp | 44 ++++++++++++++++++++ 5 files changed, 108 insertions(+), 50 deletions(-) create mode 100644 apps/opencs/view/tools/reporttable.cpp create mode 100644 apps/opencs/view/tools/reporttable.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index bdfefbb83f..fcc549002a 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -92,7 +92,7 @@ opencs_hdrs_noqt (view/render opencs_units (view/tools - reportsubview + reportsubview reporttable ) opencs_units_noqt (view/tools diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index 5a2523789e..df1a5298cd 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -1,31 +1,15 @@ #include "reportsubview.hpp" -#include -#include - -#include "../../model/tools/reportmodel.hpp" - -#include "../../view/world/idtypedelegate.hpp" +#include "reporttable.hpp" CSVTools::ReportSubView::ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: CSVDoc::SubView (id), mModel (document.getReport (id)) +: CSVDoc::SubView (id) { - setWidget (mTable = new QTableView (this)); - mTable->setModel (mModel); + setWidget (mTable = new ReportTable (document, id, this)); - mTable->horizontalHeader()->setResizeMode (QHeaderView::Interactive); - mTable->verticalHeader()->hide(); - mTable->setSortingEnabled (true); - mTable->setSelectionBehavior (QAbstractItemView::SelectRows); - mTable->setSelectionMode (QAbstractItemView::ExtendedSelection); - - mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate ( - document, this); - - mTable->setItemDelegateForColumn (0, mIdTypeDelegate); - - connect (mTable, SIGNAL (doubleClicked (const QModelIndex&)), this, SLOT (show (const QModelIndex&))); + connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), + SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); } void CSVTools::ReportSubView::setEditLock (bool locked) @@ -33,13 +17,7 @@ void CSVTools::ReportSubView::setEditLock (bool locked) // ignored. We don't change document state anyway. } -void CSVTools::ReportSubView::updateUserSetting - (const QString &name, const QStringList &list) +void CSVTools::ReportSubView::updateUserSetting (const QString &name, const QStringList &list) { - mIdTypeDelegate->updateUserSetting (name, list); -} - -void CSVTools::ReportSubView::show (const QModelIndex& index) -{ - focusId (mModel->getUniversalId (index.row()), ""); + mTable->updateUserSetting (name, list); } diff --git a/apps/opencs/view/tools/reportsubview.hpp b/apps/opencs/view/tools/reportsubview.hpp index 9f6a4c1da3..7e8a08e3cd 100644 --- a/apps/opencs/view/tools/reportsubview.hpp +++ b/apps/opencs/view/tools/reportsubview.hpp @@ -11,27 +11,15 @@ namespace CSMDoc class Document; } -namespace CSMTools -{ - class ReportModel; -} - -namespace CSVWorld -{ - class CommandDelegate; -} - namespace CSVTools { - class Table; + class ReportTable; class ReportSubView : public CSVDoc::SubView { Q_OBJECT - CSMTools::ReportModel *mModel; - QTableView *mTable; - CSVWorld::CommandDelegate *mIdTypeDelegate; + ReportTable *mTable; public: @@ -39,12 +27,7 @@ namespace CSVTools virtual void setEditLock (bool locked); - virtual void updateUserSetting - (const QString &, const QStringList &); - - private slots: - - void show (const QModelIndex& index); + virtual void updateUserSetting (const QString &, const QStringList &); }; } diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp new file mode 100644 index 0000000000..cc0ced57fc --- /dev/null +++ b/apps/opencs/view/tools/reporttable.cpp @@ -0,0 +1,53 @@ + +#include "reporttable.hpp" + +#include + +#include "../../model/tools/reportmodel.hpp" + +#include "../../view/world/idtypedelegate.hpp" + +CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, + const CSMWorld::UniversalId& id, QWidget *parent) +: CSVWorld::DragRecordTable (document, parent), mModel (document.getReport (id)) +{ + horizontalHeader()->setResizeMode (QHeaderView::Interactive); + verticalHeader()->hide(); + setSortingEnabled (true); + setSelectionBehavior (QAbstractItemView::SelectRows); + setSelectionMode (QAbstractItemView::ExtendedSelection); + + setModel (mModel); + + mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate ( + document, this); + + setItemDelegateForColumn (0, mIdTypeDelegate); + + connect (this, SIGNAL (doubleClicked (const QModelIndex&)), this, SLOT (show (const QModelIndex&))); +} + +std::vector CSVTools::ReportTable::getDraggedRecords() const +{ + std::vector ids; + + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + ++iter) + { + ids.push_back (mModel->getUniversalId (iter->row())); + } + + return ids; +} + +void CSVTools::ReportTable::updateUserSetting (const QString& name, const QStringList& list) +{ + mIdTypeDelegate->updateUserSetting (name, list); +} + +void CSVTools::ReportTable::show (const QModelIndex& index) +{ + emit editRequest (mModel->getUniversalId (index.row()), ""); +} \ No newline at end of file diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp new file mode 100644 index 0000000000..18365f58ee --- /dev/null +++ b/apps/opencs/view/tools/reporttable.hpp @@ -0,0 +1,44 @@ +#ifndef CSV_TOOLS_REPORTTABLE_H +#define CSV_TOOLS_REPORTTABLE_H + +#include "../world/dragrecordtable.hpp" + +namespace CSMTools +{ + class ReportModel; +} + +namespace CSVWorld +{ + class CommandDelegate; +} + +namespace CSVTools +{ + class ReportTable : public CSVWorld::DragRecordTable + { + Q_OBJECT + + CSMTools::ReportModel *mModel; + CSVWorld::CommandDelegate *mIdTypeDelegate; + + public: + + ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, + QWidget *parent = 0); + + virtual std::vector getDraggedRecords() const; + + void updateUserSetting (const QString& name, const QStringList& list); + + private slots: + + void show (const QModelIndex& index); + + signals: + + void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); + }; +} + +#endif From 6c18a3b0b5d1fecb102eee68dbbeed441c4e6e99 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 6 Dec 2014 13:19:43 +0100 Subject: [PATCH 072/167] allow drags from report table --- apps/opencs/view/tools/reporttable.cpp | 6 ++++++ apps/opencs/view/tools/reporttable.hpp | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index cc0ced57fc..e4acc26298 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -7,6 +7,12 @@ #include "../../view/world/idtypedelegate.hpp" +void CSVTools::ReportTable::mouseMoveEvent (QMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) + startDrag (*this); +} + CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, QWidget *parent) : CSVWorld::DragRecordTable (document, parent), mModel (document.getReport (id)) diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index 18365f58ee..acd1ed3cc7 100644 --- a/apps/opencs/view/tools/reporttable.hpp +++ b/apps/opencs/view/tools/reporttable.hpp @@ -22,6 +22,10 @@ namespace CSVTools CSMTools::ReportModel *mModel; CSVWorld::CommandDelegate *mIdTypeDelegate; + private: + + void mouseMoveEvent (QMouseEvent *event); + public: ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, From 350b0cb93c7315dd3015b2688a500f7470644c88 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 6 Dec 2014 13:45:47 +0100 Subject: [PATCH 073/167] added hidden hint column to report model --- apps/opencs/model/tools/reportmodel.cpp | 27 +++++++++++++++++++------ apps/opencs/model/tools/reportmodel.hpp | 7 +++++-- apps/opencs/view/tools/reporttable.cpp | 3 ++- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index 75545a7c7a..1354206128 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -16,7 +16,7 @@ int CSMTools::ReportModel::columnCount (const QModelIndex & parent) const if (parent.isValid()) return 0; - return 2; + return 3; } QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const @@ -26,8 +26,11 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const if (index.column()==0) return static_cast (mRows.at (index.row()).first.getType()); - else - return mRows.at (index.row()).second.c_str(); + + if (index.column()==1) + return QString::fromUtf8 (mRows.at (index.row()).second.first.c_str()); + + return QString::fromUtf8 (mRows.at (index.row()).second.second.c_str()); } QVariant CSMTools::ReportModel::headerData (int section, Qt::Orientation orientation, int role) const @@ -38,7 +41,13 @@ QVariant CSMTools::ReportModel::headerData (int section, Qt::Orientation orienta if (orientation==Qt::Vertical) return QVariant(); - return tr (section==0 ? "Type" : "Description"); + if (section==0) + return "Type"; + + if (section==1) + return "Description"; + + return "Hint"; } bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& parent) @@ -51,11 +60,12 @@ bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& p return true; } -void CSMTools::ReportModel::add (const CSMWorld::UniversalId& id, const std::string& message) +void CSMTools::ReportModel::add (const CSMWorld::UniversalId& id, const std::string& message, + const std::string& hint) { beginInsertRows (QModelIndex(), mRows.size(), mRows.size()); - mRows.push_back (std::make_pair (id, message)); + mRows.push_back (std::make_pair (id, std::make_pair (message, hint))); endInsertRows(); } @@ -64,3 +74,8 @@ const CSMWorld::UniversalId& CSMTools::ReportModel::getUniversalId (int row) con { return mRows.at (row).first; } + +std::string CSMTools::ReportModel::getHint (int row) const +{ + return mRows.at (row).second.second; +} \ No newline at end of file diff --git a/apps/opencs/model/tools/reportmodel.hpp b/apps/opencs/model/tools/reportmodel.hpp index 0f000245e1..709e024a72 100644 --- a/apps/opencs/model/tools/reportmodel.hpp +++ b/apps/opencs/model/tools/reportmodel.hpp @@ -14,7 +14,7 @@ namespace CSMTools { Q_OBJECT - std::vector > mRows; + std::vector > > mRows; public: @@ -28,9 +28,12 @@ namespace CSMTools virtual bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()); - void add (const CSMWorld::UniversalId& id, const std::string& message); + void add (const CSMWorld::UniversalId& id, const std::string& message, + const std::string& hint = ""); const CSMWorld::UniversalId& getUniversalId (int row) const; + + std::string getHint (int row) const; }; } diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index e4acc26298..5bf38f020f 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -24,6 +24,7 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, setSelectionMode (QAbstractItemView::ExtendedSelection); setModel (mModel); + setColumnHidden (2, true); mIdTypeDelegate = CSVWorld::IdTypeDelegateFactory().makeDelegate ( document, this); @@ -55,5 +56,5 @@ void CSVTools::ReportTable::updateUserSetting (const QString& name, const QStrin void CSVTools::ReportTable::show (const QModelIndex& index) { - emit editRequest (mModel->getUniversalId (index.row()), ""); + emit editRequest (mModel->getUniversalId (index.row()), mModel->getHint (index.row())); } \ No newline at end of file From f2fc6933250d4cfa6b4a1459463657a349e00f76 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 6 Dec 2014 14:17:56 +0100 Subject: [PATCH 074/167] added context menu to report table --- apps/opencs/view/tools/reporttable.cpp | 28 ++++++++++++++++++++++++++ apps/opencs/view/tools/reporttable.hpp | 7 +++++++ 2 files changed, 35 insertions(+) diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index 5bf38f020f..ec2ba74d20 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -2,11 +2,26 @@ #include "reporttable.hpp" #include +#include +#include #include "../../model/tools/reportmodel.hpp" #include "../../view/world/idtypedelegate.hpp" +void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + // create context menu + QMenu menu (this); + + if (!selectedRows.empty()) + menu.addAction (mShowAction); + + menu.exec (event->globalPos()); +} + void CSVTools::ReportTable::mouseMoveEvent (QMouseEvent *event) { if (event->buttons() & Qt::LeftButton) @@ -31,6 +46,10 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, setItemDelegateForColumn (0, mIdTypeDelegate); + mShowAction = new QAction (tr ("Show"), this); + connect (mShowAction, SIGNAL (triggered()), this, SLOT (showSelection())); + addAction (mShowAction); + connect (this, SIGNAL (doubleClicked (const QModelIndex&)), this, SLOT (show (const QModelIndex&))); } @@ -57,4 +76,13 @@ void CSVTools::ReportTable::updateUserSetting (const QString& name, const QStrin void CSVTools::ReportTable::show (const QModelIndex& index) { emit editRequest (mModel->getUniversalId (index.row()), mModel->getHint (index.row())); +} + +void CSVTools::ReportTable::showSelection() +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + ++iter) + show (*iter); } \ No newline at end of file diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index acd1ed3cc7..75b6e85532 100644 --- a/apps/opencs/view/tools/reporttable.hpp +++ b/apps/opencs/view/tools/reporttable.hpp @@ -3,6 +3,8 @@ #include "../world/dragrecordtable.hpp" +class QAction; + namespace CSMTools { class ReportModel; @@ -21,9 +23,12 @@ namespace CSVTools CSMTools::ReportModel *mModel; CSVWorld::CommandDelegate *mIdTypeDelegate; + QAction *mShowAction; private: + void contextMenuEvent (QContextMenuEvent *event); + void mouseMoveEvent (QMouseEvent *event); public: @@ -39,6 +44,8 @@ namespace CSVTools void show (const QModelIndex& index); + void showSelection(); + signals: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); From 58f4cc882fdf80f46c4965d321f23d9f145058fa Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 6 Dec 2014 14:30:40 +0100 Subject: [PATCH 075/167] added remove action to report table context menu --- apps/opencs/view/tools/reporttable.cpp | 22 ++++++++++++++++++++++ apps/opencs/view/tools/reporttable.hpp | 3 +++ 2 files changed, 25 insertions(+) diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index ec2ba74d20..eee1da35f5 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -1,6 +1,8 @@ #include "reporttable.hpp" +#include + #include #include #include @@ -17,7 +19,10 @@ void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) QMenu menu (this); if (!selectedRows.empty()) + { menu.addAction (mShowAction); + menu.addAction (mRemoveAction); + } menu.exec (event->globalPos()); } @@ -50,6 +55,10 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, connect (mShowAction, SIGNAL (triggered()), this, SLOT (showSelection())); addAction (mShowAction); + mRemoveAction = new QAction (tr ("Remove from list"), this); + connect (mRemoveAction, SIGNAL (triggered()), this, SLOT (removeSelection())); + addAction (mRemoveAction); + connect (this, SIGNAL (doubleClicked (const QModelIndex&)), this, SLOT (show (const QModelIndex&))); } @@ -85,4 +94,17 @@ void CSVTools::ReportTable::showSelection() for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) show (*iter); +} + +void CSVTools::ReportTable::removeSelection() +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + std::reverse (selectedRows.begin(), selectedRows.end()); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + ++iter) + mModel->removeRows (iter->row(), 1); + + selectionModel()->clear(); } \ No newline at end of file diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index 75b6e85532..2f53425d43 100644 --- a/apps/opencs/view/tools/reporttable.hpp +++ b/apps/opencs/view/tools/reporttable.hpp @@ -24,6 +24,7 @@ namespace CSVTools CSMTools::ReportModel *mModel; CSVWorld::CommandDelegate *mIdTypeDelegate; QAction *mShowAction; + QAction *mRemoveAction; private: @@ -46,6 +47,8 @@ namespace CSVTools void showSelection(); + void removeSelection(); + signals: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); From 6a67aba33655539c2bed83af06e269557473b7a5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 6 Dec 2014 15:08:51 +0100 Subject: [PATCH 076/167] added double click with modifier actions in report table --- apps/opencs/view/tools/reporttable.cpp | 42 +++++++++++++++++++++----- apps/opencs/view/tools/reporttable.hpp | 4 +-- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index eee1da35f5..4cd11925e0 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -33,6 +33,39 @@ void CSVTools::ReportTable::mouseMoveEvent (QMouseEvent *event) startDrag (*this); } +void CSVTools::ReportTable::mouseDoubleClickEvent (QMouseEvent *event) +{ + Qt::KeyboardModifiers modifiers = + event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier); + + QModelIndex index = currentIndex(); + + selectionModel()->select (index, + QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Rows); + + switch (modifiers) + { + case 0: + + event->accept(); + showSelection(); + break; + + case Qt::ShiftModifier: + + event->accept(); + removeSelection(); + break; + + case Qt::ControlModifier: + + event->accept(); + showSelection(); + removeSelection(); + break; + } +} + CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, QWidget *parent) : CSVWorld::DragRecordTable (document, parent), mModel (document.getReport (id)) @@ -58,8 +91,6 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, mRemoveAction = new QAction (tr ("Remove from list"), this); connect (mRemoveAction, SIGNAL (triggered()), this, SLOT (removeSelection())); addAction (mRemoveAction); - - connect (this, SIGNAL (doubleClicked (const QModelIndex&)), this, SLOT (show (const QModelIndex&))); } std::vector CSVTools::ReportTable::getDraggedRecords() const @@ -82,18 +113,13 @@ void CSVTools::ReportTable::updateUserSetting (const QString& name, const QStrin mIdTypeDelegate->updateUserSetting (name, list); } -void CSVTools::ReportTable::show (const QModelIndex& index) -{ - emit editRequest (mModel->getUniversalId (index.row()), mModel->getHint (index.row())); -} - void CSVTools::ReportTable::showSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) - show (*iter); + emit editRequest (mModel->getUniversalId (iter->row()), mModel->getHint (iter->row())); } void CSVTools::ReportTable::removeSelection() diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index 2f53425d43..7a5b232f98 100644 --- a/apps/opencs/view/tools/reporttable.hpp +++ b/apps/opencs/view/tools/reporttable.hpp @@ -32,6 +32,8 @@ namespace CSVTools void mouseMoveEvent (QMouseEvent *event); + virtual void mouseDoubleClickEvent (QMouseEvent *event); + public: ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, @@ -43,8 +45,6 @@ namespace CSVTools private slots: - void show (const QModelIndex& index); - void showSelection(); void removeSelection(); From 9a1b7cbe52c082c923d19bfdd87e623cbb70db27 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 6 Dec 2014 16:50:09 +0100 Subject: [PATCH 077/167] Add SharedStateButton, used in spell window and controls box to apply mouseover effect to all buttons within one row (Fixes #1986) --- apps/openmw/mwgui/settingswindow.cpp | 11 +- apps/openmw/mwgui/spellwindow.cpp | 42 +++++--- components/CMakeLists.txt | 2 +- components/widgets/sharedstatebutton.cpp | 128 +++++++++++++++++++++++ components/widgets/sharedstatebutton.hpp | 51 +++++++++ components/widgets/widgets.cpp | 2 + 6 files changed, 221 insertions(+), 15 deletions(-) create mode 100644 components/widgets/sharedstatebutton.cpp create mode 100644 components/widgets/sharedstatebutton.hpp diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 948423a356..8ef0a331a4 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -9,6 +9,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" @@ -454,16 +456,21 @@ namespace MWGui std::string binding = MWBase::Environment::get().getInputManager()->getActionBindingName (*it); - MyGUI::TextBox* leftText = mControlsBox->createWidget("SandText", MyGUI::IntCoord(0,curH,w,h), MyGUI::Align::Default); + Gui::SharedStateButton* leftText = mControlsBox->createWidget("SandTextButton", MyGUI::IntCoord(0,curH,w,h), MyGUI::Align::Default); leftText->setCaptionWithReplacing(desc); - MyGUI::Button* rightText = mControlsBox->createWidget("SandTextButton", MyGUI::IntCoord(0,curH,w,h), MyGUI::Align::Default); + Gui::SharedStateButton* rightText = mControlsBox->createWidget("SandTextButton", MyGUI::IntCoord(0,curH,w,h), MyGUI::Align::Default); rightText->setCaptionWithReplacing(binding); rightText->setTextAlign (MyGUI::Align::Right); rightText->setUserData(*it); // save the action id for callbacks rightText->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onRebindAction); rightText->eventMouseWheel += MyGUI::newDelegate(this, &SettingsWindow::onInputTabMouseWheel); curH += h; + + Gui::ButtonGroup group; + group.push_back(leftText); + group.push_back(rightText); + Gui::SharedStateButton::createButtonGroup(group); } // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 97ebbcde2f..7904c249ab 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -177,7 +179,7 @@ namespace MWGui for (std::vector::const_iterator it = spellList.begin(); it != spellList.end(); ++it) { const ESM::Spell* spell = esmStore.get().find(*it); - MyGUI::Button* t = mSpellView->createWidget("SandTextButton", + Gui::SharedStateButton* t = mSpellView->createWidget("SandTextButton", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); t->setCaption(spell->mName); t->setTextAlign(MyGUI::Align::Left); @@ -185,20 +187,28 @@ namespace MWGui t->setUserString("Spell", *it); t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); - t->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()); // cost / success chance - MyGUI::Button* costChance = mSpellView->createWidget("SandTextButton", + Gui::SharedStateButton* costChance = mSpellView->createWidget("SandTextButton", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); std::string cost = boost::lexical_cast(spell->mData.mCost); std::string chance = boost::lexical_cast(int(MWMechanics::getSpellSuccessChance(*it, player))); costChance->setCaption(cost + "/" + chance); costChance->setTextAlign(MyGUI::Align::Right); - costChance->setNeedMouseFocus(false); - costChance->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()); + costChance->setUserString("ToolTipType", "Spell"); + costChance->setUserString("Spell", *it); + costChance->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); + costChance->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); t->setSize(mWidth-12-costChance->getTextSize().width, t->getHeight()); + Gui::ButtonGroup group; + group.push_back(t); + group.push_back(costChance); + Gui::SharedStateButton::createButtonGroup(group); + + t->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()); + mHeight += spellHeight; } @@ -224,7 +234,7 @@ namespace MWGui } } - MyGUI::Button* t = mSpellView->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", + Gui::SharedStateButton* t = mSpellView->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); t->setCaption(item.getClass().getName(item)); t->setTextAlign(MyGUI::Align::Left); @@ -233,12 +243,9 @@ namespace MWGui t->setUserString("Equipped", equipped ? "true" : "false"); t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected); t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - if (store.getSelectedEnchantItem() != store.end()) - t->setStateSelected(item == *store.getSelectedEnchantItem()); - // cost / charge - MyGUI::Button* costCharge = mSpellView->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", + Gui::SharedStateButton* costCharge = mSpellView->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); float enchantCost = enchant->mData.mCost; @@ -257,11 +264,22 @@ namespace MWGui charge = "100"; } + + costCharge->setUserData(item); + costCharge->setUserString("ToolTipType", "ItemPtr"); + costCharge->setUserString("Equipped", equipped ? "true" : "false"); + costCharge->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected); + costCharge->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); costCharge->setCaption(cost + "/" + charge); costCharge->setTextAlign(MyGUI::Align::Right); - costCharge->setNeedMouseFocus(false); + + Gui::ButtonGroup group; + group.push_back(t); + group.push_back(costCharge); + Gui::SharedStateButton::createButtonGroup(group); + if (store.getSelectedEnchantItem() != store.end()) - costCharge->setStateSelected(item == *store.getSelectedEnchantItem()); + t->setStateSelected(item == *store.getSelectedEnchantItem()); t->setSize(mWidth-12-costCharge->getTextSize().width, t->getHeight()); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6a1db371d1..234325718d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -96,7 +96,7 @@ add_component_dir (ogreinit ) add_component_dir (widgets - box imagebutton tags list numericeditbox widgets + box imagebutton tags list numericeditbox sharedstatebutton widgets ) add_component_dir (fontloader diff --git a/components/widgets/sharedstatebutton.cpp b/components/widgets/sharedstatebutton.cpp new file mode 100644 index 0000000000..6859a3065a --- /dev/null +++ b/components/widgets/sharedstatebutton.cpp @@ -0,0 +1,128 @@ +#include "sharedstatebutton.hpp" + +namespace Gui +{ + + SharedStateButton::SharedStateButton() + : mIsMousePressed(false) + , mIsMouseFocus(false) + { + + } + + void SharedStateButton::shutdownOverride() + { + ButtonGroup group = mSharedWith; // make a copy so that we don't nuke the vector during iteration + for (ButtonGroup::iterator it = group.begin(); it != group.end(); ++it) + { + (*it)->shareStateWith(ButtonGroup()); + } + } + + void SharedStateButton::shareStateWith(ButtonGroup shared) + { + mSharedWith = shared; + } + + void SharedStateButton::onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id) + { + mIsMousePressed = true; + Base::onMouseButtonPressed(_left, _top, _id); + updateButtonState(); + } + + void SharedStateButton::onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id) + { + mIsMousePressed = false; + Base::onMouseButtonReleased(_left, _top, _id); + updateButtonState(); + } + + void SharedStateButton::onMouseSetFocus(MyGUI::Widget *_old) + { + mIsMouseFocus = true; + Base::onMouseSetFocus(_old); + updateButtonState(); + } + + void SharedStateButton::onMouseLostFocus(MyGUI::Widget *_new) + { + mIsMouseFocus = false; + Base::onMouseLostFocus(_new); + updateButtonState(); + } + + void SharedStateButton::baseUpdateEnable() + { + Base::baseUpdateEnable(); + updateButtonState(); + } + + void SharedStateButton::setStateSelected(bool _value) + { + Base::setStateSelected(_value); + updateButtonState(); + + for (ButtonGroup::iterator it = mSharedWith.begin(); it != mSharedWith.end(); ++it) + { + (*it)->MyGUI::Button::setStateSelected(getStateSelected()); + } + } + + bool SharedStateButton::_setState(const std::string &_value) + { + bool ret = _setWidgetState(_value); + if (ret) + { + for (ButtonGroup::iterator it = mSharedWith.begin(); it != mSharedWith.end(); ++it) + { + (*it)->_setWidgetState(_value); + } + } + return ret; + } + + void SharedStateButton::updateButtonState() + { + if (getStateSelected()) + { + if (!getInheritedEnabled()) + { + if (!_setState("disabled_checked")) + _setState("disabled"); + } + else if (mIsMousePressed) + { + if (!_setState("pushed_checked")) + _setState("pushed"); + } + else if (mIsMouseFocus) + { + if (!_setState("highlighted_checked")) + _setState("pushed"); + } + else + _setState("normal_checked"); + } + else + { + if (!getInheritedEnabled()) + _setState("disabled"); + else if (mIsMousePressed) + _setState("pushed"); + else if (mIsMouseFocus) + _setState("highlighted"); + else + _setState("normal"); + } + } + + void SharedStateButton::createButtonGroup(ButtonGroup group) + { + for (ButtonGroup::iterator it = group.begin(); it != group.end(); ++it) + { + (*it)->shareStateWith(group); + } + } + +} diff --git a/components/widgets/sharedstatebutton.hpp b/components/widgets/sharedstatebutton.hpp new file mode 100644 index 0000000000..3d7fbc84e4 --- /dev/null +++ b/components/widgets/sharedstatebutton.hpp @@ -0,0 +1,51 @@ +#ifndef OPENMW_WIDGETS_SHAREDSTATEBUTTON_HPP +#define OPENMW_WIDGETS_SHAREDSTATEBUTTON_HPP + +#include + +namespace Gui +{ + + class SharedStateButton; + + typedef std::vector ButtonGroup; + + /// @brief A button that applies its own state changes to other widgets, to do this you define it as part of a ButtonGroup. + class SharedStateButton : public MyGUI::Button + { + MYGUI_RTTI_DERIVED(SharedStateButton) + + public: + SharedStateButton(); + + protected: + void updateButtonState(); + + virtual void onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id); + virtual void onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id); + virtual void onMouseSetFocus(MyGUI::Widget* _old); + virtual void onMouseLostFocus(MyGUI::Widget* _new); + virtual void baseUpdateEnable(); + + virtual void shutdownOverride(); + + bool _setState(const std::string &_value); + + public: + void shareStateWith(ButtonGroup shared); + + /// @note The ButtonGroup connection will be destroyed when any widget in the group gets destroyed. + static void createButtonGroup(ButtonGroup group); + + //! Set button selected state + void setStateSelected(bool _value); + + private: + ButtonGroup mSharedWith; + + bool mIsMousePressed; + bool mIsMouseFocus; + }; +} + +#endif diff --git a/components/widgets/widgets.cpp b/components/widgets/widgets.cpp index b35dc88a4d..82839c6c96 100644 --- a/components/widgets/widgets.cpp +++ b/components/widgets/widgets.cpp @@ -6,6 +6,7 @@ #include "numericeditbox.hpp" #include "box.hpp" #include "imagebutton.hpp" +#include "sharedstatebutton.hpp" namespace Gui { @@ -20,6 +21,7 @@ namespace Gui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); } } From 41542dedf7686bd222aa99df4270fc5b3f20e928 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 6 Dec 2014 17:24:05 +0100 Subject: [PATCH 078/167] Fix map insert return value mixup (Fixes #2192) --- apps/openmw/mwworld/store.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 5966a99b9e..c8fa087c20 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -859,16 +859,16 @@ namespace MWWorld // Try to overwrite existing record if (!pathgrid.mCell.empty()) { - std::pair found = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); - if (found.second) - found.first->second = pathgrid; + std::pair ret = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); + if (!ret.second) + ret.first->second = pathgrid; } else { - std::pair found = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), + std::pair ret = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), pathgrid)); - if (found.second) - found.first->second = pathgrid; + if (!ret.second) + ret.first->second = pathgrid; } } @@ -953,9 +953,9 @@ namespace MWWorld record.load(esm); // Try to overwrite existing record - std::pair found = mStatic.insert(std::make_pair(record.mIndex, record)); - if (found.second) - found.first->second = record; + std::pair ret = mStatic.insert(std::make_pair(record.mIndex, record)); + if (!ret.second) + ret.first->second = record; } int getSize() const { From 2952a0e2aa7d9fa58bc37c5f235a61f28afd6a29 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 6 Dec 2014 19:53:24 +0100 Subject: [PATCH 079/167] Make Resurrect function reset most of the runtime state (Fixes #2181) --- apps/openmw/mwscript/statsextensions.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index d5647db10d..63405e5b85 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1179,7 +1179,14 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - ptr.getClass().getCreatureStats(ptr).resurrect(); + + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + ptr.getClass().getCreatureStats(ptr).resurrect(); + else if (ptr.getClass().getCreatureStats(ptr).isDead()) + { + // resets runtime state such as inventory, stats and AI. does not reset position in the world + ptr.getRefData().setCustomData(NULL); + } } }; From f49fde3d5de464c546aa41e69172c5063cf600dd Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 6 Dec 2014 21:08:18 +0100 Subject: [PATCH 080/167] Add support for undeleting references (Fixes #2193) Deleted references should be accessible via an explicit reference, and can be undeleted using "setdelete 0". Also the Resurrect function implicitely undeletes the given reference. --- apps/openmw/mwbase/world.hpp | 1 + apps/openmw/mwscript/miscextensions.cpp | 4 ++++ apps/openmw/mwscript/statsextensions.cpp | 1 + apps/openmw/mwworld/cellreflist.hpp | 4 ++-- apps/openmw/mwworld/cellstore.hpp | 2 +- apps/openmw/mwworld/refdata.cpp | 5 +++++ apps/openmw/mwworld/refdata.hpp | 2 ++ apps/openmw/mwworld/worldimp.cpp | 19 +++++++++++++++++++ apps/openmw/mwworld/worldimp.hpp | 1 + 9 files changed, 36 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c1a889913a..2dd135f3da 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -279,6 +279,7 @@ namespace MWBase ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; + virtual void undeleteObject (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/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index aa80213de8..ec2048e11f 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -571,6 +571,10 @@ namespace MWScript if (parameter == 1) MWBase::Environment::get().getWorld()->deleteObject(ptr); + else if (parameter == 0) + MWBase::Environment::get().getWorld()->undeleteObject(ptr); + else + throw std::runtime_error("SetDelete: unexpected parameter"); } }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 63405e5b85..09ab0183c7 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1184,6 +1184,7 @@ namespace MWScript ptr.getClass().getCreatureStats(ptr).resurrect(); else if (ptr.getClass().getCreatureStats(ptr).isDead()) { + MWBase::Environment::get().getWorld()->undeleteObject(ptr); // resets runtime state such as inventory, stats and AI. does not reset position in the world ptr.getRefData().setCustomData(NULL); } diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 9c3370f08a..cf12896548 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -27,7 +27,7 @@ namespace MWWorld LiveRef *find (const std::string& name) { for (typename List::iterator iter (mList.begin()); iter!=mList.end(); ++iter) - if (iter->mData.getCount() > 0 && iter->mRef.getRefId() == name) + if (iter->mRef.getRefId() == name) return &*iter; return 0; @@ -42,7 +42,7 @@ namespace MWWorld LiveCellRef *searchViaHandle (const std::string& handle) { for (typename List::iterator iter (mList.begin()); iter!=mList.end(); ++iter) - if (iter->mData.getCount()>0 && iter->mData.getBaseNode() && + if (iter->mData.getBaseNode() && iter->mData.getHandle()==handle) return &*iter; diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 05e7b0b2e0..eba627b3ee 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -196,7 +196,7 @@ namespace MWWorld for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); ++iter) { - if (iter->mData.isDeleted()) + if (iter->mData.isDeletedByContentFile()) continue; if (!functor (MWWorld::Ptr(&*iter, this))) return false; diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 78fea2b868..c2a5e5f837 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -180,6 +180,11 @@ namespace MWWorld return mDeleted || mCount == 0; } + bool RefData::isDeletedByContentFile() const + { + return mDeleted; + } + MWScript::Locals& RefData::getLocals() { return mLocals; diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 1ed3cd79d9..da7986ba03 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -100,6 +100,8 @@ namespace MWWorld /// Returns true if the object was either deleted by the content file or by gameplay. bool isDeleted() const; + /// Returns true if the object was deleted by a content file. + bool isDeletedByContentFile() const; MWScript::Locals& getLocals(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 7baf1d3e5c..d783857f10 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1072,6 +1072,25 @@ namespace MWWorld } } + void World::undeleteObject(const Ptr& ptr) + { + if (ptr.getCellRef().getRefNum().mContentFile == -1) + return; + if (ptr.getRefData().isDeleted()) + { + ptr.getRefData().setCount(1); + if (mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end() + && ptr.getRefData().isEnabled()) + { + mWorldScene->addObjectToScene(ptr); + std::string script = ptr.getClass().getScript(ptr); + if (!script.empty()) + mLocalScripts.add(script, ptr); + addContainerScripts(ptr, ptr.getCell()); + } + } + } + void World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z) { ESM::Position pos = ptr.getRefData().getPosition(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index fef2797050..2da6a6e05f 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -338,6 +338,7 @@ namespace MWWorld virtual std::pair getHitContact(const MWWorld::Ptr &ptr, float distance); virtual void deleteObject (const Ptr& ptr); + virtual void undeleteObject (const Ptr& ptr); virtual void moveObject (const Ptr& ptr, float x, float y, float z); virtual void moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z); From 109fbab54688a65c500937993f39e87ee2a1e798 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 7 Dec 2014 16:02:28 +0100 Subject: [PATCH 081/167] changed column/row numbering in script compiler error messages from being starting at 0 to starting at 1 --- components/compiler/streamerrorhandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/compiler/streamerrorhandler.cpp b/components/compiler/streamerrorhandler.cpp index 8a74ad0863..fc1a059432 100644 --- a/components/compiler/streamerrorhandler.cpp +++ b/components/compiler/streamerrorhandler.cpp @@ -16,7 +16,7 @@ namespace Compiler mStream << "warning "; mStream - << "line " << loc.mLine << ", column " << loc.mColumn + << "line " << loc.mLine+1 << ", column " << loc.mColumn+1 << " (" << loc.mLiteral << ")" << std::endl << " " << message << std::endl; } From 3a847732b44048ab474330c7b6330974ce55b9a5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 7 Dec 2014 18:57:47 +0100 Subject: [PATCH 082/167] abstracted message collection into a class --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/doc/loader.cpp | 8 +-- apps/opencs/model/doc/messages.cpp | 28 ++++++++++ apps/opencs/model/doc/messages.hpp | 44 +++++++++++++++ apps/opencs/model/doc/operation.cpp | 6 +-- apps/opencs/model/doc/stage.hpp | 4 +- apps/opencs/model/tools/birthsigncheck.cpp | 2 +- apps/opencs/model/tools/birthsigncheck.hpp | 2 +- apps/opencs/model/tools/bodypartcheck.cpp | 2 +- apps/opencs/model/tools/bodypartcheck.hpp | 2 +- apps/opencs/model/tools/classcheck.cpp | 2 +- apps/opencs/model/tools/classcheck.hpp | 2 +- apps/opencs/model/tools/factioncheck.cpp | 2 +- apps/opencs/model/tools/factioncheck.hpp | 2 +- apps/opencs/model/tools/mandatoryid.cpp | 5 +- apps/opencs/model/tools/mandatoryid.hpp | 2 +- apps/opencs/model/tools/racecheck.cpp | 6 +-- apps/opencs/model/tools/racecheck.hpp | 6 +-- .../opencs/model/tools/referenceablecheck.cpp | 54 +++++++++---------- .../opencs/model/tools/referenceablecheck.hpp | 54 +++++++++---------- apps/opencs/model/tools/regioncheck.cpp | 2 +- apps/opencs/model/tools/regioncheck.hpp | 2 +- apps/opencs/model/tools/scriptcheck.cpp | 2 +- apps/opencs/model/tools/scriptcheck.hpp | 4 +- apps/opencs/model/tools/skillcheck.cpp | 2 +- apps/opencs/model/tools/skillcheck.hpp | 2 +- apps/opencs/model/tools/soundcheck.cpp | 2 +- apps/opencs/model/tools/soundcheck.hpp | 2 +- apps/opencs/model/tools/spellcheck.cpp | 2 +- apps/opencs/model/tools/spellcheck.hpp | 2 +- apps/opencs/model/world/data.cpp | 13 +++-- apps/opencs/model/world/data.hpp | 2 +- apps/opencs/model/world/refcollection.cpp | 5 +- apps/opencs/model/world/refcollection.hpp | 2 +- 34 files changed, 174 insertions(+), 105 deletions(-) create mode 100644 apps/opencs/model/doc/messages.cpp create mode 100644 apps/opencs/model/doc/messages.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index fcc549002a..468e172cd2 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -11,7 +11,7 @@ opencs_units (model/doc ) opencs_units_noqt (model/doc - stage savingstate savingstages blacklist + stage savingstate savingstages blacklist messages ) opencs_hdrs_noqt (model/doc diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index 712deb9dfc..43f3b850ea 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -52,7 +52,7 @@ void CSMDoc::Loader::load() { if (iter->second.mRecordsLeft) { - CSMDoc::Stage::Messages messages; + CSMDoc::Messages messages; for (int i=0; igetData().continueLoading (messages)) { @@ -65,11 +65,11 @@ void CSMDoc::Loader::load() CSMWorld::UniversalId log (CSMWorld::UniversalId::Type_LoadErrorLog, 0); { // silence a g++ warning - for (CSMDoc::Stage::Messages::const_iterator iter (messages.begin()); + for (CSMDoc::Messages::Iterator iter (messages.begin()); iter!=messages.end(); ++iter) { - document->getReport (log)->add (iter->first, iter->second); - emit loadMessage (document, iter->second); + document->getReport (log)->add (iter->mId, iter->mMessage); + emit loadMessage (document, iter->mMessage); } } diff --git a/apps/opencs/model/doc/messages.cpp b/apps/opencs/model/doc/messages.cpp new file mode 100644 index 0000000000..1fb423145a --- /dev/null +++ b/apps/opencs/model/doc/messages.cpp @@ -0,0 +1,28 @@ + +#include "messages.hpp" + +void CSMDoc::Messages::add (const CSMWorld::UniversalId& id, const std::string& message, + const std::string& hint) +{ + Message data; + data.mId = id; + data.mMessage = message; + data.mHint = hint; + + mMessages.push_back (data); +} + +void CSMDoc::Messages::push_back (const std::pair& data) +{ + add (data.first, data.second); +} + +CSMDoc::Messages::Iterator CSMDoc::Messages::begin() const +{ + return mMessages.begin(); +} + +CSMDoc::Messages::Iterator CSMDoc::Messages::end() const +{ + return mMessages.end(); +} \ No newline at end of file diff --git a/apps/opencs/model/doc/messages.hpp b/apps/opencs/model/doc/messages.hpp new file mode 100644 index 0000000000..0f36c73a73 --- /dev/null +++ b/apps/opencs/model/doc/messages.hpp @@ -0,0 +1,44 @@ +#ifndef CSM_DOC_MESSAGES_H +#define CSM_DOC_MESSAGES_H + +#include +#include + +#include "../world/universalid.hpp" + +namespace CSMDoc +{ + class Messages + { + public: + + struct Message + { + CSMWorld::UniversalId mId; + std::string mMessage; + std::string mHint; + }; + + typedef std::vector Collection; + + typedef Collection::const_iterator Iterator; + + private: + + Collection mMessages; + + public: + + void add (const CSMWorld::UniversalId& id, const std::string& message, + const std::string& hint = ""); + + /// \deprecated Use add instead. + void push_back (const std::pair& data); + + Iterator begin() const; + + Iterator end() const; + }; +} + +#endif diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index 7f77e8ac9a..a997579f83 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -84,7 +84,7 @@ void CSMDoc::Operation::abort() void CSMDoc::Operation::executeStage() { - Stage::Messages messages; + Messages messages; while (mCurrentStage!=mStages.end()) { @@ -112,8 +112,8 @@ void CSMDoc::Operation::executeStage() emit progress (mCurrentStepTotal, mTotalSteps ? mTotalSteps : 1, mType); - for (Stage::Messages::const_iterator iter (messages.begin()); iter!=messages.end(); ++iter) - emit reportMessage (iter->first, iter->second, mType); + for (Messages::Iterator iter (messages.begin()); iter!=messages.end(); ++iter) + emit reportMessage (iter->mId, iter->mMessage, mType); if (mCurrentStage==mStages.end()) exit(); diff --git a/apps/opencs/model/doc/stage.hpp b/apps/opencs/model/doc/stage.hpp index ca34c22299..126823ae91 100644 --- a/apps/opencs/model/doc/stage.hpp +++ b/apps/opencs/model/doc/stage.hpp @@ -6,14 +6,14 @@ #include "../world/universalid.hpp" +#include "messages.hpp" + namespace CSMDoc { class Stage { public: - typedef std::vector > Messages; - virtual ~Stage(); virtual int setup() = 0; diff --git a/apps/opencs/model/tools/birthsigncheck.cpp b/apps/opencs/model/tools/birthsigncheck.cpp index db20ce4bcd..1d72e24b87 100644 --- a/apps/opencs/model/tools/birthsigncheck.cpp +++ b/apps/opencs/model/tools/birthsigncheck.cpp @@ -17,7 +17,7 @@ int CSMTools::BirthsignCheckStage::setup() return mBirthsigns.getSize(); } -void CSMTools::BirthsignCheckStage::perform (int stage, Messages& messages) +void CSMTools::BirthsignCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mBirthsigns.getRecord (stage); diff --git a/apps/opencs/model/tools/birthsigncheck.hpp b/apps/opencs/model/tools/birthsigncheck.hpp index 1030e5c021..16d4c666fd 100644 --- a/apps/opencs/model/tools/birthsigncheck.hpp +++ b/apps/opencs/model/tools/birthsigncheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, Messages& messages); + virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/bodypartcheck.cpp b/apps/opencs/model/tools/bodypartcheck.cpp index a26945acf6..68a09485f2 100644 --- a/apps/opencs/model/tools/bodypartcheck.cpp +++ b/apps/opencs/model/tools/bodypartcheck.cpp @@ -14,7 +14,7 @@ int CSMTools::BodyPartCheckStage::setup() return mBodyParts.getSize(); } -void CSMTools::BodyPartCheckStage::perform ( int stage, Messages &messages ) +void CSMTools::BodyPartCheckStage::perform (int stage, CSMDoc::Messages &messages) { const CSMWorld::Record &record = mBodyParts.getRecord(stage); diff --git a/apps/opencs/model/tools/bodypartcheck.hpp b/apps/opencs/model/tools/bodypartcheck.hpp index d72badfdf7..0a6ca959ac 100644 --- a/apps/opencs/model/tools/bodypartcheck.hpp +++ b/apps/opencs/model/tools/bodypartcheck.hpp @@ -27,7 +27,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform( int stage, Messages &messages ); + virtual void perform( int stage, CSMDoc::Messages &messages ); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/classcheck.cpp b/apps/opencs/model/tools/classcheck.cpp index cea4f3a686..5b872a2668 100644 --- a/apps/opencs/model/tools/classcheck.cpp +++ b/apps/opencs/model/tools/classcheck.cpp @@ -18,7 +18,7 @@ int CSMTools::ClassCheckStage::setup() return mClasses.getSize(); } -void CSMTools::ClassCheckStage::perform (int stage, Messages& messages) +void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mClasses.getRecord (stage); diff --git a/apps/opencs/model/tools/classcheck.hpp b/apps/opencs/model/tools/classcheck.hpp index ec50ba35d1..b76da3f13d 100644 --- a/apps/opencs/model/tools/classcheck.hpp +++ b/apps/opencs/model/tools/classcheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, Messages& messages); + virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp index ba8cfe1f9b..0dfdee7754 100644 --- a/apps/opencs/model/tools/factioncheck.cpp +++ b/apps/opencs/model/tools/factioncheck.cpp @@ -18,7 +18,7 @@ int CSMTools::FactionCheckStage::setup() return mFactions.getSize(); } -void CSMTools::FactionCheckStage::perform (int stage, Messages& messages) +void CSMTools::FactionCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mFactions.getRecord (stage); diff --git a/apps/opencs/model/tools/factioncheck.hpp b/apps/opencs/model/tools/factioncheck.hpp index ccc44e6a92..321a4d6d80 100644 --- a/apps/opencs/model/tools/factioncheck.hpp +++ b/apps/opencs/model/tools/factioncheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, Messages& messages); + virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/mandatoryid.cpp b/apps/opencs/model/tools/mandatoryid.cpp index 412e9f2f02..87d19401ba 100644 --- a/apps/opencs/model/tools/mandatoryid.cpp +++ b/apps/opencs/model/tools/mandatoryid.cpp @@ -15,10 +15,9 @@ int CSMTools::MandatoryIdStage::setup() return mIds.size(); } -void CSMTools::MandatoryIdStage::perform (int stage, Messages& messages) +void CSMTools::MandatoryIdStage::perform (int stage, CSMDoc::Messages& messages) { if (mIdCollection.searchId (mIds.at (stage))==-1 || mIdCollection.getRecord (mIds.at (stage)).isDeleted()) - messages.push_back (std::make_pair (mCollectionId, - "Missing mandatory record: " + mIds.at (stage))); + messages.add (mCollectionId, "Missing mandatory record: " + mIds.at (stage)); } \ No newline at end of file diff --git a/apps/opencs/model/tools/mandatoryid.hpp b/apps/opencs/model/tools/mandatoryid.hpp index a8afea62af..86015c9824 100644 --- a/apps/opencs/model/tools/mandatoryid.hpp +++ b/apps/opencs/model/tools/mandatoryid.hpp @@ -30,7 +30,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, Messages& messages); + virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/racecheck.cpp b/apps/opencs/model/tools/racecheck.cpp index 47aeda1e69..143d617721 100644 --- a/apps/opencs/model/tools/racecheck.cpp +++ b/apps/opencs/model/tools/racecheck.cpp @@ -7,7 +7,7 @@ #include "../world/universalid.hpp" -void CSMTools::RaceCheckStage::performPerRecord (int stage, Messages& messages) +void CSMTools::RaceCheckStage::performPerRecord (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mRaces.getRecord (stage); @@ -46,7 +46,7 @@ void CSMTools::RaceCheckStage::performPerRecord (int stage, Messages& messages) /// \todo check data members that can't be edited in the table view } -void CSMTools::RaceCheckStage::performFinal (Messages& messages) +void CSMTools::RaceCheckStage::performFinal (CSMDoc::Messages& messages) { CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Races); @@ -64,7 +64,7 @@ int CSMTools::RaceCheckStage::setup() return mRaces.getSize()+1; } -void CSMTools::RaceCheckStage::perform (int stage, Messages& messages) +void CSMTools::RaceCheckStage::perform (int stage, CSMDoc::Messages& messages) { if (stage==mRaces.getSize()) performFinal (messages); diff --git a/apps/opencs/model/tools/racecheck.hpp b/apps/opencs/model/tools/racecheck.hpp index c68b283be4..3e67b75771 100644 --- a/apps/opencs/model/tools/racecheck.hpp +++ b/apps/opencs/model/tools/racecheck.hpp @@ -15,9 +15,9 @@ namespace CSMTools const CSMWorld::IdCollection& mRaces; bool mPlayable; - void performPerRecord (int stage, Messages& messages); + void performPerRecord (int stage, CSMDoc::Messages& messages); - void performFinal (Messages& messages); + void performFinal (CSMDoc::Messages& messages); public: @@ -26,7 +26,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, Messages& messages); + virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index 1816d0808a..5190aacd59 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -18,7 +18,7 @@ CSMTools::ReferenceableCheckStage::ReferenceableCheckStage( { } -void CSMTools::ReferenceableCheckStage::perform (int stage, Messages& messages) +void CSMTools::ReferenceableCheckStage::perform (int stage, CSMDoc::Messages& messages) { //Checks for books, than, when stage is above mBooksSize goes to other checks, with (stage - PrevSum) as stage. const int bookSize(mReferencables.getBooks().getSize()); @@ -232,7 +232,7 @@ int CSMTools::ReferenceableCheckStage::setup() void CSMTools::ReferenceableCheckStage::bookCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Book >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -250,7 +250,7 @@ void CSMTools::ReferenceableCheckStage::bookCheck( void CSMTools::ReferenceableCheckStage::activatorCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Activator >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -270,7 +270,7 @@ void CSMTools::ReferenceableCheckStage::activatorCheck( void CSMTools::ReferenceableCheckStage::potionCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Potion >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -290,7 +290,7 @@ void CSMTools::ReferenceableCheckStage::potionCheck( void CSMTools::ReferenceableCheckStage::apparatusCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Apparatus >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -310,7 +310,7 @@ void CSMTools::ReferenceableCheckStage::apparatusCheck( void CSMTools::ReferenceableCheckStage::armorCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Armor >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -336,7 +336,7 @@ void CSMTools::ReferenceableCheckStage::armorCheck( void CSMTools::ReferenceableCheckStage::clothingCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Clothing >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -353,7 +353,7 @@ void CSMTools::ReferenceableCheckStage::clothingCheck( void CSMTools::ReferenceableCheckStage::containerCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Container >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -381,7 +381,7 @@ void CSMTools::ReferenceableCheckStage::containerCheck( void CSMTools::ReferenceableCheckStage::creatureCheck ( int stage, const CSMWorld::RefIdDataContainer< ESM::Creature >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -448,7 +448,7 @@ void CSMTools::ReferenceableCheckStage::creatureCheck ( void CSMTools::ReferenceableCheckStage::doorCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Door >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -469,7 +469,7 @@ void CSMTools::ReferenceableCheckStage::doorCheck( void CSMTools::ReferenceableCheckStage::ingredientCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Ingredient >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -487,7 +487,7 @@ void CSMTools::ReferenceableCheckStage::ingredientCheck( void CSMTools::ReferenceableCheckStage::creaturesLevListCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::CreatureLevList >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -505,7 +505,7 @@ void CSMTools::ReferenceableCheckStage::creaturesLevListCheck( void CSMTools::ReferenceableCheckStage::itemLevelledListCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::ItemLevList >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -522,7 +522,7 @@ void CSMTools::ReferenceableCheckStage::itemLevelledListCheck( void CSMTools::ReferenceableCheckStage::lightCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Light >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -547,7 +547,7 @@ void CSMTools::ReferenceableCheckStage::lightCheck( void CSMTools::ReferenceableCheckStage::lockpickCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Lockpick >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -567,7 +567,7 @@ void CSMTools::ReferenceableCheckStage::lockpickCheck( void CSMTools::ReferenceableCheckStage::miscCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Miscellaneous >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -584,7 +584,7 @@ void CSMTools::ReferenceableCheckStage::miscCheck( void CSMTools::ReferenceableCheckStage::npcCheck ( int stage, const CSMWorld::RefIdDataContainer< ESM::NPC >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -701,7 +701,7 @@ void CSMTools::ReferenceableCheckStage::npcCheck ( void CSMTools::ReferenceableCheckStage::weaponCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Weapon >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord (stage); @@ -778,7 +778,7 @@ void CSMTools::ReferenceableCheckStage::weaponCheck( void CSMTools::ReferenceableCheckStage::probeCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Probe >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -796,7 +796,7 @@ void CSMTools::ReferenceableCheckStage::probeCheck( void CSMTools::ReferenceableCheckStage::repairCheck ( int stage, const CSMWorld::RefIdDataContainer< ESM::Repair >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord (stage); @@ -812,7 +812,7 @@ void CSMTools::ReferenceableCheckStage::repairCheck ( void CSMTools::ReferenceableCheckStage::staticCheck ( int stage, const CSMWorld::RefIdDataContainer< ESM::Static >& records, - Messages& messages) + CSMDoc::Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord (stage); @@ -828,7 +828,7 @@ void CSMTools::ReferenceableCheckStage::staticCheck ( //final check -void CSMTools::ReferenceableCheckStage::finalCheck (Messages& messages) +void CSMTools::ReferenceableCheckStage::finalCheck (CSMDoc::Messages& messages) { if (!mPlayerPresent) messages.push_back (std::make_pair (CSMWorld::UniversalId::Type_Referenceables, @@ -839,7 +839,7 @@ void CSMTools::ReferenceableCheckStage::finalCheck (Messages& messages) //Templates begins here template void CSMTools::ReferenceableCheckStage::inventoryItemCheck ( - const Item& someItem, Messages& messages, const std::string& someID, bool enchantable) + const Item& someItem, CSMDoc::Messages& messages, const std::string& someID, bool enchantable) { if (someItem.mName.empty()) messages.push_back (std::make_pair (someID, someItem.mId + " has an empty name")); @@ -865,7 +865,7 @@ template void CSMTools::ReferenceableCheckStage::inventoryItemChe } template void CSMTools::ReferenceableCheckStage::inventoryItemCheck ( - const Item& someItem, Messages& messages, const std::string& someID) + const Item& someItem, CSMDoc::Messages& messages, const std::string& someID) { if (someItem.mName.empty()) messages.push_back (std::make_pair (someID, someItem.mId + " has an empty name")); @@ -888,7 +888,7 @@ template void CSMTools::ReferenceableCheckStage::inventoryItemChe } template void CSMTools::ReferenceableCheckStage::toolCheck ( - const Tool& someTool, Messages& messages, const std::string& someID, bool canBeBroken) + const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID, bool canBeBroken) { if (someTool.mData.mQuality <= 0) messages.push_back (std::make_pair (someID, someTool.mId + " has non-positive quality")); @@ -899,14 +899,14 @@ template void CSMTools::ReferenceableCheckStage::toolCheck ( } template void CSMTools::ReferenceableCheckStage::toolCheck ( - const Tool& someTool, Messages& messages, const std::string& someID) + const Tool& someTool, CSMDoc::Messages& messages, const std::string& someID) { if (someTool.mData.mQuality <= 0) messages.push_back (std::make_pair (someID, someTool.mId + " has non-positive quality")); } template void CSMTools::ReferenceableCheckStage::listCheck ( - const List& someList, Messages& messages, const std::string& someID) + const List& someList, CSMDoc::Messages& messages, const std::string& someID) { for (unsigned i = 0; i < someList.mList.size(); ++i) { diff --git a/apps/opencs/model/tools/referenceablecheck.hpp b/apps/opencs/model/tools/referenceablecheck.hpp index b0129fc2a5..ac7ed70821 100644 --- a/apps/opencs/model/tools/referenceablecheck.hpp +++ b/apps/opencs/model/tools/referenceablecheck.hpp @@ -17,56 +17,56 @@ namespace CSMTools const CSMWorld::IdCollection& classes, const CSMWorld::IdCollection& factions); - virtual void perform(int stage, Messages& messages); + virtual void perform(int stage, CSMDoc::Messages& messages); virtual int setup(); private: //CONCRETE CHECKS - void bookCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Book >& records, Messages& messages); - void activatorCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Activator >& records, Messages& messages); - void potionCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void apparatusCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void armorCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void clothingCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void containerCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void creatureCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void doorCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void ingredientCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void creaturesLevListCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void itemLevelledListCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void lightCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void lockpickCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void miscCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void npcCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void weaponCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void probeCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void repairCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); - void staticCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void bookCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Book >& records, CSMDoc::Messages& messages); + void activatorCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Activator >& records, CSMDoc::Messages& messages); + void potionCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void apparatusCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void armorCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void clothingCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void containerCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void creatureCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void doorCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void ingredientCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void creaturesLevListCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void itemLevelledListCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void lightCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void lockpickCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void miscCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void npcCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void weaponCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void probeCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void repairCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); + void staticCheck(int stage, const CSMWorld::RefIdDataContainer& records, CSMDoc::Messages& messages); //FINAL CHECK - void finalCheck (Messages& messages); + void finalCheck (CSMDoc::Messages& messages); //TEMPLATE CHECKS template void inventoryItemCheck(const ITEM& someItem, - Messages& messages, + CSMDoc::Messages& messages, const std::string& someID, bool enchantable); //for all enchantable items. template void inventoryItemCheck(const ITEM& someItem, - Messages& messages, + CSMDoc::Messages& messages, const std::string& someID); //for non-enchantable items. template void toolCheck(const TOOL& someTool, - Messages& messages, + CSMDoc::Messages& messages, const std::string& someID, bool canbebroken); //for tools with uses. template void toolCheck(const TOOL& someTool, - Messages& messages, + CSMDoc::Messages& messages, const std::string& someID); //for tools without uses. template void listCheck(const LIST& someList, - Messages& messages, + CSMDoc::Messages& messages, const std::string& someID); const CSMWorld::RefIdData& mReferencables; diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index 07df204701..091836d0d7 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -17,7 +17,7 @@ int CSMTools::RegionCheckStage::setup() return mRegions.getSize(); } -void CSMTools::RegionCheckStage::perform (int stage, Messages& messages) +void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mRegions.getRecord (stage); diff --git a/apps/opencs/model/tools/regioncheck.hpp b/apps/opencs/model/tools/regioncheck.hpp index a12903e7d4..8ba32e1377 100644 --- a/apps/opencs/model/tools/regioncheck.hpp +++ b/apps/opencs/model/tools/regioncheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, Messages& messages); + virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index d2c647bdab..fe371a3689 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -58,7 +58,7 @@ int CSMTools::ScriptCheckStage::setup() return mDocument.getData().getScripts().getSize(); } -void CSMTools::ScriptCheckStage::perform (int stage, Messages& messages) +void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages) { mId = mDocument.getData().getScripts().getId (stage); diff --git a/apps/opencs/model/tools/scriptcheck.hpp b/apps/opencs/model/tools/scriptcheck.hpp index 75f11b9d4f..3fe12fc9a4 100644 --- a/apps/opencs/model/tools/scriptcheck.hpp +++ b/apps/opencs/model/tools/scriptcheck.hpp @@ -23,7 +23,7 @@ namespace CSMTools CSMWorld::ScriptContext mContext; std::string mId; std::string mFile; - Messages *mMessages; + CSMDoc::Messages *mMessages; virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type); ///< Report error to the user. @@ -38,7 +38,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, Messages& messages); + virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/skillcheck.cpp b/apps/opencs/model/tools/skillcheck.cpp index 630516c72a..e061e042cc 100644 --- a/apps/opencs/model/tools/skillcheck.cpp +++ b/apps/opencs/model/tools/skillcheck.cpp @@ -16,7 +16,7 @@ int CSMTools::SkillCheckStage::setup() return mSkills.getSize(); } -void CSMTools::SkillCheckStage::perform (int stage, Messages& messages) +void CSMTools::SkillCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mSkills.getRecord (stage); diff --git a/apps/opencs/model/tools/skillcheck.hpp b/apps/opencs/model/tools/skillcheck.hpp index cf5d53b5c9..93b06fe71f 100644 --- a/apps/opencs/model/tools/skillcheck.hpp +++ b/apps/opencs/model/tools/skillcheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, Messages& messages); + virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/soundcheck.cpp b/apps/opencs/model/tools/soundcheck.cpp index 3d222e9092..e122ced915 100644 --- a/apps/opencs/model/tools/soundcheck.cpp +++ b/apps/opencs/model/tools/soundcheck.cpp @@ -16,7 +16,7 @@ int CSMTools::SoundCheckStage::setup() return mSounds.getSize(); } -void CSMTools::SoundCheckStage::perform (int stage, Messages& messages) +void CSMTools::SoundCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mSounds.getRecord (stage); diff --git a/apps/opencs/model/tools/soundcheck.hpp b/apps/opencs/model/tools/soundcheck.hpp index a82a0eb6d7..52f2d3714a 100644 --- a/apps/opencs/model/tools/soundcheck.hpp +++ b/apps/opencs/model/tools/soundcheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, Messages& messages); + virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/spellcheck.cpp b/apps/opencs/model/tools/spellcheck.cpp index 3d0be46fdf..0b59dc862a 100644 --- a/apps/opencs/model/tools/spellcheck.cpp +++ b/apps/opencs/model/tools/spellcheck.cpp @@ -17,7 +17,7 @@ int CSMTools::SpellCheckStage::setup() return mSpells.getSize(); } -void CSMTools::SpellCheckStage::perform (int stage, Messages& messages) +void CSMTools::SpellCheckStage::perform (int stage, CSMDoc::Messages& messages) { const CSMWorld::Record& record = mSpells.getRecord (stage); diff --git a/apps/opencs/model/tools/spellcheck.hpp b/apps/opencs/model/tools/spellcheck.hpp index 182f1888b2..9c3ea88855 100644 --- a/apps/opencs/model/tools/spellcheck.hpp +++ b/apps/opencs/model/tools/spellcheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, Messages& messages); + virtual void perform (int stage, CSMDoc::Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index f6bd8e13b9..67f6822c7c 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -671,7 +671,7 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base return mReader->getRecordCount(); } -bool CSMWorld::Data::continueLoading (CSMDoc::Stage::Messages& messages) +bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) { if (!mReader) throw std::logic_error ("can't continue loading, because no load has been started"); @@ -794,8 +794,8 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Stage::Messages& messages) } else { - messages.push_back (std::make_pair (UniversalId::Type_None, - "Trying to delete dialogue record " + id + " which does not exist")); + messages.add (UniversalId::Type_None, + "Trying to delete dialogue record " + id + " which does not exist"); } } else @@ -811,8 +811,8 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Stage::Messages& messages) { if (!mDialogue) { - messages.push_back (std::make_pair (UniversalId::Type_None, - "Found info record not following a dialogue record")); + messages.add (UniversalId::Type_None, + "Found info record not following a dialogue record"); mReader->skipRecord(); break; @@ -855,8 +855,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Stage::Messages& messages) if (unhandledRecord) { - messages.push_back (std::make_pair (UniversalId::Type_None, - "Unsupported record type: " + n.toString())); + messages.add (UniversalId::Type_None, "Unsupported record type: " + n.toString()); mReader->skipRecord(); } diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 37d4d4b8a7..02f7bc4526 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -244,7 +244,7 @@ namespace CSMWorld /// ///< \return estimated number of records - bool continueLoading (CSMDoc::Stage::Messages& messages); + bool continueLoading (CSMDoc::Messages& messages); ///< \return Finished? bool hasId (const std::string& id) const; diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index c516e2c3ec..47f0276c6c 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -11,7 +11,7 @@ #include "record.hpp" void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base, - std::map& cache, CSMDoc::Stage::Messages& messages) + std::map& cache, CSMDoc::Messages& messages) { Record cell = mCells.getRecord (cellIndex); @@ -36,8 +36,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, mCells.getId (cellIndex)); - messages.push_back (std::make_pair (id, - "Attempt to delete a non-existing reference")); + messages.add (id, "Attempt to delete a non-existing reference"); continue; } diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index 63d369ed98..4ecc32b2f9 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -28,7 +28,7 @@ namespace CSMWorld void load (ESM::ESMReader& reader, int cellIndex, bool base, std::map& cache, - CSMDoc::Stage::Messages& messages); + CSMDoc::Messages& messages); ///< Load a sequence of references. std::string getNewId(); From a64b741af209c88d2f4269f528525690266e0a66 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 7 Dec 2014 20:53:09 +0100 Subject: [PATCH 083/167] store hints from operations in report model --- apps/opencs/model/doc/document.cpp | 6 +++--- apps/opencs/model/doc/document.hpp | 2 +- apps/opencs/model/doc/operation.cpp | 4 ++-- apps/opencs/model/doc/operation.hpp | 2 +- apps/opencs/model/tools/tools.cpp | 8 ++++---- apps/opencs/model/tools/tools.hpp | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 4abd67a505..e688a9474a 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2301,8 +2301,8 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); connect ( - &mSaving, SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, int)), - this, SLOT (reportMessage (const CSMWorld::UniversalId&, const std::string&, int))); + &mSaving, SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int)), + this, SLOT (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int))); connect (&mRunner, SIGNAL (runStateChanged()), this, SLOT (runStateChanged())); } @@ -2387,7 +2387,7 @@ void CSMDoc::Document::modificationStateChanged (bool clean) } void CSMDoc::Document::reportMessage (const CSMWorld::UniversalId& id, const std::string& message, - int type) + const std::string& hint, int type) { /// \todo find a better way to get these messages to the user. std::cout << message << std::endl; diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index e5aa5eea58..f3aef6db63 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -149,7 +149,7 @@ namespace CSMDoc void modificationStateChanged (bool clean); void reportMessage (const CSMWorld::UniversalId& id, const std::string& message, - int type); + const std::string& hint, int type); void operationDone (int type, bool failed); diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index a997579f83..e728050f4f 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -101,7 +101,7 @@ void CSMDoc::Operation::executeStage() } catch (const std::exception& e) { - emit reportMessage (CSMWorld::UniversalId(), e.what(), mType); + emit reportMessage (CSMWorld::UniversalId(), e.what(), "", mType); abort(); } @@ -113,7 +113,7 @@ void CSMDoc::Operation::executeStage() emit progress (mCurrentStepTotal, mTotalSteps ? mTotalSteps : 1, mType); for (Messages::Iterator iter (messages.begin()); iter!=messages.end(); ++iter) - emit reportMessage (iter->mId, iter->mMessage, mType); + emit reportMessage (iter->mId, iter->mMessage, iter->mHint, mType); if (mCurrentStage==mStages.end()) exit(); diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index d5a7d4e098..3c94677545 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -52,7 +52,7 @@ namespace CSMDoc void progress (int current, int max, int type); void reportMessage (const CSMWorld::UniversalId& id, const std::string& message, - int type); + const std::string& hint, int type); void done (int type, bool failed); diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 79b0b18b0e..6e157f664f 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -48,8 +48,8 @@ CSMDoc::Operation *CSMTools::Tools::getVerifier() connect (mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); connect (mVerifier, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); connect (mVerifier, - SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, int)), - this, SLOT (verifierMessage (const CSMWorld::UniversalId&, const std::string&, int))); + SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int)), + this, SLOT (verifierMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int))); std::vector mandatoryIds; // I want C++11, damn it! mandatoryIds.push_back ("Day"); @@ -155,11 +155,11 @@ CSMTools::ReportModel *CSMTools::Tools::getReport (const CSMWorld::UniversalId& } void CSMTools::Tools::verifierMessage (const CSMWorld::UniversalId& id, const std::string& message, - int type) + const std::string& hint, int type) { std::map::iterator iter = mActiveReports.find (type); if (iter!=mActiveReports.end()) - mReports[iter->second]->add (id, message); + mReports[iter->second]->add (id, message, hint); } diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 7ca30e6b97..5125a36381 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -64,7 +64,7 @@ namespace CSMTools private slots: void verifierMessage (const CSMWorld::UniversalId& id, const std::string& message, - int type); + const std::string& hint, int type); signals: From 9f90a1e44bba06c581eb423172fe0648d4a646e9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Dec 2014 22:37:50 +0100 Subject: [PATCH 084/167] Remove script access to deleted references that have no content file In original MW these objects are permanently deleted and can not be accessed anymore. --- apps/openmw/mwworld/cellreflist.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index cf12896548..037de8645e 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -27,7 +27,9 @@ namespace MWWorld LiveRef *find (const std::string& name) { for (typename List::iterator iter (mList.begin()); iter!=mList.end(); ++iter) - if (iter->mRef.getRefId() == name) + if (!iter->mData.isDeletedByContentFile() + && (iter->mRef.getRefNum().mContentFile != -1 || iter->mData.getCount() > 0) + && iter->mRef.getRefId() == name) return &*iter; return 0; From e6307a5151abff084e4bc09be6b66ccef8fef9a0 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 8 Dec 2014 12:29:23 +0100 Subject: [PATCH 085/167] move cursor in scripteditor to position of error --- apps/opencs/model/tools/scriptcheck.cpp | 6 +++++- apps/opencs/view/world/scriptsubview.cpp | 26 ++++++++++++++++++++++++ apps/opencs/view/world/scriptsubview.hpp | 2 ++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index fe371a3689..d9dea7f43c 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -28,7 +28,11 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, const Compi << ", line " << loc.mLine << ", column " << loc.mColumn << " (" << loc.mLiteral << "): " << message; - mMessages->push_back (std::make_pair (id, stream.str())); + std::ostringstream hintStream; + + hintStream << "l:" << loc.mLine << " " << loc.mColumn; + + mMessages->add (id, stream.str(), hintStream.str()); } void CSMTools::ScriptCheckStage::report (const std::string& message, Type type) diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 22d8e7e51e..9b50a61f89 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -47,6 +47,32 @@ void CSVWorld::ScriptSubView::setEditLock (bool locked) mEditor->setReadOnly (locked); } +void CSVWorld::ScriptSubView::useHint (const std::string& hint) +{ + if (hint.empty()) + return; + + if (hint[0]=='l') + { + std::istringstream stream (hint.c_str()+1); + + char ignore; + int line; + int column; + + if (stream >> ignore >> line >> column) + { + QTextCursor cursor = mEditor->textCursor(); + + cursor.movePosition (QTextCursor::Start); + if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) + cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); + + mEditor->setTextCursor (cursor); + } + } +} + void CSVWorld::ScriptSubView::textChanged() { if (mEditor->isChangeLocked()) diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 77127d9bee..16ffc7b80b 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -34,6 +34,8 @@ namespace CSVWorld virtual void setEditLock (bool locked); + virtual void useHint (const std::string& hint); + public slots: void textChanged(); From fbed429b257503417ecc32ef26f03c90a728a0d5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Dec 2014 17:24:45 +0100 Subject: [PATCH 086/167] Use GMSTs for sound fading distance --- apps/openmw/mwsound/soundmanagerimp.cpp | 36 ++++++++++++++++++------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 781bfb5d5a..251b05890b 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -103,24 +103,31 @@ namespace MWSound std::string SoundManager::lookup(const std::string &soundId, float &volume, float &min, float &max) { - const ESM::Sound *snd = - MWBase::Environment::get().getWorld()->getStore().get().find(soundId); + MWBase::World* world = MWBase::Environment::get().getWorld(); + const ESM::Sound *snd = world->getStore().get().find(soundId); volume *= pow(10.0, (snd->mData.mVolume/255.0*3348.0 - 3348.0) / 2000.0); if(snd->mData.mMinRange == 0 && snd->mData.mMaxRange == 0) { - min = 100.0f; - max = 2000.0f; + static const float fAudioDefaultMinDistance = world->getStore().get().find("fAudioDefaultMinDistance")->getFloat(); + static const float fAudioDefaultMaxDistance = world->getStore().get().find("fAudioDefaultMaxDistance")->getFloat(); + min = fAudioDefaultMinDistance; + max = fAudioDefaultMaxDistance; } else { - min = snd->mData.mMinRange * 20.0f; - max = snd->mData.mMaxRange * 50.0f; - min = std::max(min, 1.0f); - max = std::max(min, max); + min = snd->mData.mMinRange; + max = snd->mData.mMaxRange; } + static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); + static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); + min *= fAudioMinDistanceMult; + max *= fAudioMaxDistanceMult; + min = std::max(min, 1.0f); + max = std::max(min, max); + return "Sound/"+snd->mSound; } @@ -250,8 +257,19 @@ namespace MWSound const ESM::Position &pos = ptr.getRefData().getPosition(); const Ogre::Vector3 objpos(pos.pos); + MWBase::World* world = MWBase::Environment::get().getWorld(); + static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); + static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); + static const float fAudioVoiceDefaultMinDistance = world->getStore().get().find("fAudioVoiceDefaultMinDistance")->getFloat(); + static const float fAudioVoiceDefaultMaxDistance = world->getStore().get().find("fAudioVoiceDefaultMaxDistance")->getFloat(); + + float minDistance = fAudioVoiceDefaultMinDistance * fAudioMinDistanceMult; + float maxDistance = fAudioVoiceDefaultMaxDistance * fAudioMaxDistanceMult; + minDistance = std::max(minDistance, 1.f); + maxDistance = std::max(minDistance, maxDistance); + MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f, - 20.0f, 1500.0f, Play_Normal|Play_TypeVoice, 0, true); + minDistance, maxDistance, Play_Normal|Play_TypeVoice, 0, true); mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } catch(std::exception &e) From cf85cbbc8e48f790e41faad5a765d94219debaef Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Dec 2014 17:43:56 +0100 Subject: [PATCH 087/167] Switch sound distance model to AL_INVERSE_DISTANCE --- apps/openmw/mwsound/openal_output.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 3d2795ce10..fcdc60ee39 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -628,9 +628,7 @@ void OpenAL_Sound3D::update() { ALfloat gain = mVolume*mBaseVolume; ALfloat pitch = mPitch; - if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) - gain = 0.0f; - else if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) { gain *= 0.9f; pitch *= 0.7f; @@ -696,7 +694,7 @@ void OpenAL_Output::init(const std::string &devname) fail(std::string("Failed to setup context: ")+alcGetString(mDevice, alcGetError(mDevice))); } - alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); + alDistanceModel(AL_INVERSE_DISTANCE); throwALerror(); ALCint maxmono=0, maxstereo=0; From 6c8a662042786171d3d17a6178d69a7f7e05533d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 8 Dec 2014 20:08:59 +0100 Subject: [PATCH 088/167] label local/global openmw.cfg files (Fixes #2196) --- files/openmw.cfg | 4 ++++ files/openmw.cfg.local | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/files/openmw.cfg b/files/openmw.cfg index b60c1ba728..3a9bd87628 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -1,3 +1,7 @@ +# This is the global openmw.cfg file. Do not modify! +# Modifications should be done on the user openmw.cfg file instead +# (see: https://wiki.openmw.org/index.php?title=Paths) + data="?mw?Data Files" data=${MORROWIND_DATA_FILES} data-local="?userdata?data" diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index 9d86174d9e..71cd3bfbfa 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -1,3 +1,7 @@ +# This is the local openmw.cfg file. Do not modify! +# Modifications should be done on the user openmw.cfg file instead +# (see: https://wiki.openmw.org/index.php?title=Paths) + data="?global?data" data="?mw?Data Files" data=./data From ddad963312d4d21113be21792c9703655c071cd8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 8 Dec 2014 20:14:00 +0100 Subject: [PATCH 089/167] adjusted changelog (removed a regression that was specific to 0.34.0) --- readme.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/readme.txt b/readme.txt index 91ec7962f1..e91455aebe 100644 --- a/readme.txt +++ b/readme.txt @@ -160,7 +160,6 @@ Bug #2161: Editor: combat/magic/stealth values of creature not displayed correct Bug #2163: OpenMW can't detect death if the NPC die by the post damage effect of a magic weapon. Bug #2168: Westly's Master Head Pack X – Some hairs aren't rendered correctly. Bug #2170: Mods using conversations to update PC inconsistant -Bug #2173: Launcher: disabling plugin files is broken Bug #2175: Pathgrid mods do not overwrite the existing pathgrid Bug #2180: Editor: Verifier doesn't handle Windows-specific path issues when dealing with resources Feature #238: Add UI to run INI-importer from the launcher From f6960debcbf5d6b3758a6dca8da35c4e89daee1c Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Dec 2014 23:26:09 +0100 Subject: [PATCH 090/167] Attach sound listener to the player head instead of camera --- apps/openmw/mwrender/camera.cpp | 11 ----------- apps/openmw/mwrender/camera.hpp | 3 --- apps/openmw/mwworld/worldimp.cpp | 13 +++++++++++++ apps/openmw/mwworld/worldimp.hpp | 1 + 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 1850df904b..c7a27dfe8f 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -7,7 +7,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/refdata.hpp" @@ -120,15 +119,6 @@ namespace MWRender setPosition(Ogre::Vector3(x,y,z)); } - void Camera::updateListener() - { - Ogre::Vector3 pos = mCamera->getRealPosition(); - Ogre::Vector3 dir = mCamera->getRealDirection(); - Ogre::Vector3 up = mCamera->getRealUp(); - - MWBase::Environment::get().getSoundManager()->setListenerPosDir(pos, dir, up); - } - void Camera::update(float duration, bool paused) { if (mAnimation->upperBodyReady()) @@ -148,7 +138,6 @@ namespace MWRender } } - updateListener(); if (paused) return; diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index c542dc96ce..691a80862b 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -50,9 +50,6 @@ namespace MWRender bool mVanityToggleQueued; bool mViewModeToggleQueued; - /// Updates sound manager listener data - void updateListener(); - void setPosition(const Ogre::Vector3& position); void setPosition(float x, float y, float z); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d783857f10..1b5d6e002f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1560,6 +1560,8 @@ namespace MWWorld updateWindowManager (); + updateSoundListener(); + if (!paused && mPlayer->getPlayer().getCell()->isExterior()) { ESM::Position pos = mPlayer->getPlayer().getRefData().getPosition(); @@ -1567,6 +1569,17 @@ namespace MWWorld } } + void World::updateSoundListener() + { + Ogre::Vector3 playerPos = mPlayer->getPlayer().getRefData().getBaseNode()->getPosition(); + const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(getPlayerPtr().getRefData().getHandle()); + if(actor) playerPos.z += 1.85*actor->getHalfExtents().z; + Ogre::Quaternion playerOrient = Ogre::Quaternion(Ogre::Radian(getPlayerPtr().getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(getPlayerPtr().getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + MWBase::Environment::get().getSoundManager()->setListenerPosDir(playerPos, playerOrient.yAxis(), + playerOrient.zAxis()); + } + void World::updateWindowManager () { // inform the GUI about focused object diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 2da6a6e05f..555ed7fb74 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -109,6 +109,7 @@ namespace MWWorld Ptr copyObjectToCell(const Ptr &ptr, CellStore* cell, ESM::Position pos, bool adjustPos=true); + void updateSoundListener(); void updateWindowManager (); void performUpdateSceneQueries (); void getFacedHandle(std::string& facedHandle, float maxDistance, bool ignorePlayer=true); From 855fe33c59458b2abc05887ace95733ebc44e1cb Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Dec 2014 23:26:54 +0100 Subject: [PATCH 091/167] Add vanilla-compatible range limiting for playloopsound (Fixes #244, Fixes #1342) --- apps/openmw/mwbase/soundmanager.hpp | 20 +++++++++++++------- apps/openmw/mwscript/soundextensions.cpp | 8 ++++---- apps/openmw/mwsound/openal_output.cpp | 6 ++++-- apps/openmw/mwsound/soundmanagerimp.cpp | 12 ++++++++++++ 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index a02a463dde..bc2f3f1c61 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -42,15 +42,21 @@ namespace MWBase Play_NoTrack = 1<<2, /* (3D only) Play the sound at the given object's position * but do not keep it updated (the sound will not move with * the object and will not stop when the object is deleted. */ - - Play_LoopNoEnv = Play_Loop | Play_NoEnv + Play_RemoveAtDistance = 1<<3, /* (3D only) If the listener gets further than 2000 units away + from the sound source, the sound is removed. + This is weird stuff but apparently how vanilla works for sounds + played by the PlayLoopSound family of script functions. Perhaps we + can make this cut off a more subtle fade later, but have to + be careful to not change the overall volume of areas by too much. */ + Play_LoopNoEnv = Play_Loop | Play_NoEnv, + Play_LoopRemoveAtDistance = Play_Loop | Play_RemoveAtDistance }; enum PlayType { - Play_TypeSfx = 1<<3, /* Normal SFX sound */ - Play_TypeVoice = 1<<4, /* Voice sound */ - Play_TypeFoot = 1<<5, /* Footstep sound */ - Play_TypeMusic = 1<<6, /* Music track */ - Play_TypeMovie = 1<<7, /* Movie audio track */ + Play_TypeSfx = 1<<4, /* Normal SFX sound */ + Play_TypeVoice = 1<<5, /* Voice sound */ + Play_TypeFoot = 1<<6, /* Footstep sound */ + Play_TypeMusic = 1<<7, /* Music track */ + Play_TypeMovie = 1<<8, /* Movie audio track */ Play_TypeMask = Play_TypeSfx|Play_TypeVoice|Play_TypeFoot|Play_TypeMusic|Play_TypeMovie }; diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 73c3ec93a5..606de7aa01 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -121,8 +121,8 @@ namespace MWScript MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, - mLoop ? MWBase::SoundManager::Play_Loop : - MWBase::SoundManager::Play_Normal); + mLoop ? MWBase::SoundManager::Play_LoopRemoveAtDistance + : MWBase::SoundManager::Play_Normal); } }; @@ -150,8 +150,8 @@ namespace MWScript MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, volume, pitch, MWBase::SoundManager::Play_TypeSfx, - mLoop ? MWBase::SoundManager::Play_Loop : - MWBase::SoundManager::Play_Normal); + mLoop ? MWBase::SoundManager::Play_LoopRemoveAtDistance + : MWBase::SoundManager::Play_Normal); } }; diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index fcdc60ee39..bc94789456 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -628,7 +628,9 @@ void OpenAL_Sound3D::update() { ALfloat gain = mVolume*mBaseVolume; ALfloat pitch = mPitch; - if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) + gain = 0.0f; + else if(!(mFlags&MWBase::SoundManager::Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) { gain *= 0.9f; pitch *= 0.7f; @@ -694,7 +696,7 @@ void OpenAL_Output::init(const std::string &devname) fail(std::string("Failed to setup context: ")+alcGetString(mDevice, alcGetError(mDevice))); } - alDistanceModel(AL_INVERSE_DISTANCE); + alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); throwALerror(); ALCint maxmono=0, maxstereo=0; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 251b05890b..d856f41ee6 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -385,6 +385,11 @@ namespace MWSound const ESM::Position &pos = ptr.getRefData().getPosition(); const Ogre::Vector3 objpos(pos.pos); + if ((mode & Play_RemoveAtDistance) && mListenerPos.squaredDistance(objpos) > 2000*2000) + { + return MWBase::SoundPtr(); + } + sound = mOutput->playSound3D(file, objpos, volume, basevol, pitch, min, max, mode|type, offset); if((mode&Play_NoTrack)) mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); @@ -650,6 +655,13 @@ namespace MWSound const ESM::Position &pos = ptr.getRefData().getPosition(); const Ogre::Vector3 objpos(pos.pos); snditer->first->setPosition(objpos); + + if ((snditer->first->mFlags & Play_RemoveAtDistance) + && mListenerPos.squaredDistance(Ogre::Vector3(ptr.getRefData().getPosition().pos)) > 2000*2000) + { + mActiveSounds.erase(snditer++); + continue; + } } //update fade out if(snditer->first->mFadeOutTime>0) From 0fe7500f7437b34a6100a73adf5877daebd8e1aa Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Dec 2014 00:13:56 +0100 Subject: [PATCH 092/167] Work around pathgrid record limitation (Fixes #2195) --- apps/openmw/mwmechanics/aiwander.cpp | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 3 +- apps/openmw/mwmechanics/pathgrid.cpp | 9 +-- apps/openmw/mwmechanics/pathgrid.hpp | 2 +- apps/openmw/mwrender/debugging.cpp | 3 +- apps/openmw/mwworld/cellstore.cpp | 2 +- apps/openmw/mwworld/store.hpp | 87 +++++++++---------------- 7 files changed, 41 insertions(+), 67 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index c8a0c85d58..3224127dfd 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -382,7 +382,7 @@ namespace MWMechanics { // infrequently used, therefore no benefit in caching it as a member const ESM::Pathgrid * - pathgrid = world->getStore().get().search(*cell); + pathgrid = world->getStore().get().search(*cell, world->getCellName(currentCell)); // cache the current cell location cachedCellX = cell->mData.mX; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index f1279c415e..f0e5b1d9de 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -188,7 +188,8 @@ namespace MWMechanics if(mCell != cell || !mPathgrid) { mCell = cell; - mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); + mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell(), + MWBase::Environment::get().getWorld()->getCellName(mCell)); } // Refer to AiWander reseach topic on openmw forums for some background. diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index 4983a4a4f2..d380cba4e3 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -95,7 +95,7 @@ namespace MWMechanics * +----------------> * high cost */ - bool PathgridGraph::load(const ESM::Cell* cell) + bool PathgridGraph::load(const MWWorld::CellStore *cell) { if(!cell) return false; @@ -103,9 +103,10 @@ namespace MWMechanics if(mIsGraphConstructed) return true; - mCell = cell; - mIsExterior = cell->isExterior(); - mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); + mCell = cell->getCell(); + mIsExterior = cell->getCell()->isExterior(); + mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell->getCell(), + MWBase::Environment::get().getWorld()->getCellName(cell)); if(!mPathgrid) return false; diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index 5d01dca009..2742957a68 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -21,7 +21,7 @@ namespace MWMechanics public: PathgridGraph(); - bool load(const ESM::Cell *cell); + bool load(const MWWorld::CellStore *cell); // returns true if end point is strongly connected (i.e. reachable // from start point) both start and end are pathgrid point indexes diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index 4f5536ca32..553a6379f5 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -231,8 +231,9 @@ void Debugging::togglePathgrid() void Debugging::enableCellPathgrid(MWWorld::CellStore *store) { + MWBase::World* world = MWBase::Environment::get().getWorld(); const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*store->getCell()); + world->getStore().get().search(*store->getCell(), world->getCellName(store)); if (!pathgrid) return; Vector3 cellPathGridPos(0, 0, 0); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 52e70fef26..f1a8451ea3 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -413,7 +413,7 @@ namespace MWWorld // 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); + mPathgridGraph.load(this); } } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index c8fa087c20..1aaf902f85 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -843,88 +843,59 @@ namespace MWWorld class Store : public StoreBase { private: - typedef std::map Interior; - typedef std::map, ESM::Pathgrid> Exterior; + // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. + // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. + // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. + // This is why we keep both interior and exterior pathgrids in the same container here. + typedef std::pair > PathgridKey; + typedef std::map Static; - Interior mInt; - Exterior mExt; + Static mStatic; public: void load(ESM::ESMReader &esm, const std::string &id) { - ESM::Pathgrid pathgrid; pathgrid.load(esm); + PathgridKey key = std::make_pair(pathgrid.mCell, std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY)); + // Try to overwrite existing record - if (!pathgrid.mCell.empty()) - { - std::pair ret = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); - if (!ret.second) - ret.first->second = pathgrid; - } - else - { - std::pair ret = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), - pathgrid)); - if (!ret.second) - ret.first->second = pathgrid; - } + std::pair ret = mStatic.insert(std::make_pair(key, pathgrid)); + if (!ret.second) + ret.first->second = pathgrid; } size_t getSize() const { - return mInt.size() + mExt.size(); + return mStatic.size(); } void setUp() { } - const ESM::Pathgrid *search(int x, int y) const { - Exterior::const_iterator it = mExt.find(std::make_pair(x,y)); - if (it != mExt.end()) + const ESM::Pathgrid *search(const ESM::Cell &cell, const std::string& cellName) const { + int x=0,y=0; + if (!(cell.mData.mFlags & ESM::Cell::Interior)) + { + x = cell.mData.mX; + y = cell.mData.mY; + } + PathgridKey key = std::make_pair(cellName, std::make_pair(x,y)); + + Static::const_iterator it = mStatic.find(key); + if (it != mStatic.end()) return &(it->second); return NULL; } - const ESM::Pathgrid *find(int x, int y) const { - const ESM::Pathgrid *ptr = search(x, y); - if (ptr == 0) { + const ESM::Pathgrid *find(const ESM::Cell &cell, const std::string& cellName) const { + const ESM::Pathgrid* pathgrid = search(cell, cellName); + if (pathgrid == 0) { std::ostringstream msg; - msg << "Pathgrid at (" << x << ", " << y << ") not found"; + msg << "Pathgrid in cell '" << cellName << "' not found"; throw std::runtime_error(msg.str()); } - return ptr; - } - - const ESM::Pathgrid *search(const std::string &name) const { - Interior::const_iterator it = mInt.find(name); - if (it != mInt.end()) - return &(it->second); - return NULL; - } - - const ESM::Pathgrid *find(const std::string &name) const { - const ESM::Pathgrid *ptr = search(name); - if (ptr == 0) { - std::ostringstream msg; - msg << "Pathgrid in cell '" << name << "' not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - const ESM::Pathgrid *search(const ESM::Cell &cell) const { - if (cell.mData.mFlags & ESM::Cell::Interior) { - return search(cell.mName); - } - return search(cell.mData.mX, cell.mData.mY); - } - - const ESM::Pathgrid *find(const ESM::Cell &cell) const { - if (cell.mData.mFlags & ESM::Cell::Interior) { - return find(cell.mName); - } - return find(cell.mData.mX, cell.mData.mY); + return pathgrid; } }; From 3ad01899828c42e6c314d291bd7e25cfffeef09f Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Dec 2014 12:06:44 +0100 Subject: [PATCH 093/167] Take sound listener y rotation (roll) into account, though currently unused for actors --- apps/openmw/mwworld/worldimp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1b5d6e002f..2607a6d4d0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1575,7 +1575,8 @@ namespace MWWorld const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(getPlayerPtr().getRefData().getHandle()); if(actor) playerPos.z += 1.85*actor->getHalfExtents().z; Ogre::Quaternion playerOrient = Ogre::Quaternion(Ogre::Radian(getPlayerPtr().getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(getPlayerPtr().getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + Ogre::Quaternion(Ogre::Radian(getPlayerPtr().getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X) * + Ogre::Quaternion(Ogre::Radian(getPlayerPtr().getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y); MWBase::Environment::get().getSoundManager()->setListenerPosDir(playerPos, playerOrient.yAxis(), playerOrient.zAxis()); } From 109a3f78a1b35990f11c80336242f1b8b9918921 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Dec 2014 16:02:07 +0100 Subject: [PATCH 094/167] Adjust AiFollow distance for groups of multiple followers (Fixes #1637) --- apps/openmw/mwbase/mechanicsmanager.hpp | 1 + apps/openmw/mwmechanics/actors.cpp | 30 ++++++++++++++++ apps/openmw/mwmechanics/actors.hpp | 3 ++ apps/openmw/mwmechanics/aifollow.cpp | 35 +++++++++++++++---- apps/openmw/mwmechanics/aifollow.hpp | 5 +++ .../mwmechanics/mechanicsmanagerimp.cpp | 5 +++ .../mwmechanics/mechanicsmanagerimp.hpp | 1 + 7 files changed, 73 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index ce213b316a..b7af1cbf72 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -183,6 +183,7 @@ namespace MWBase ///return the list of actors which are following the given actor /**ie AiFollow is active and the target is the actor**/ virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; + virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0; ///Returns a list of actors who are fighting the given actor within the fAlarmDistance /** ie AiCombat is active and the target is the actor **/ diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a3cfbfd49a..ae430e5c6a 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1470,6 +1470,36 @@ namespace MWMechanics return list; } + std::list Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) + { + std::list list; + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + { + const MWWorld::Class &cls = iter->first.getClass(); + CreatureStats &stats = cls.getCreatureStats(iter->first); + if (stats.isDead()) + continue; + + // An actor counts as following if AiFollow is the current AiPackage, or there are only Combat packages before the AiFollow package + for (std::list::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) + { + if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) + { + MWWorld::Ptr followTarget = dynamic_cast(*it)->getTarget(); + if (followTarget.isEmpty()) + continue; + if (followTarget == actor) + list.push_back(dynamic_cast(*it)->getFollowIndex()); + else + break; + } + else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) + break; + } + } + return list; + } + std::list Actors::getActorsFighting(const MWWorld::Ptr& actor) { std::list list; std::vector neighbors; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 0ccfaad78a..a24095c702 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -112,6 +112,9 @@ namespace MWMechanics /**ie AiFollow is active and the target is the actor **/ std::list getActorsFollowing(const MWWorld::Ptr& actor); + /// Get the list of AiFollow::mFollowIndex for all actors following this target + std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); + ///Returns the list of actors which are fighting the given actor /**ie AiCombat is active and the target is the actor **/ std::list getActorsFighting(const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f309dc7402..8f3e19b465 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -6,6 +6,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "creaturestats.hpp" @@ -15,28 +16,31 @@ #include "steering.hpp" +int MWMechanics::AiFollow::mFollowIndexCounter = 0; + MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) : mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mActorRefId(actorId), mCellId(""), mActorId(-1) +, mActorRefId(actorId), mCellId(""), mActorId(-1), mFollowIndex(mFollowIndexCounter++) { } MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) : mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mActorRefId(actorId), mCellId(cellId), mActorId(-1) +, mActorRefId(actorId), mCellId(cellId), mActorId(-1), mFollowIndex(mFollowIndexCounter++) { } MWMechanics::AiFollow::AiFollow(const std::string &actorId, bool commanded) : mAlwaysFollow(true), mCommanded(commanded), mRemainingDuration(0), mX(0), mY(0), mZ(0) -, mActorRefId(actorId), mCellId(""), mActorId(-1) +, mActorRefId(actorId), mCellId(""), mActorId(-1), mFollowIndex(mFollowIndexCounter++) { + } 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) , mActorRefId(follow->mTargetId), mActorId(-1), mCellId(follow->mCellId) - , mCommanded(follow->mCommanded) + , mCommanded(follow->mCommanded), mFollowIndex(mFollowIndexCounter++) { } @@ -48,12 +52,24 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, if (target.isEmpty() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered // with the MechanicsManager ) - return true; //Target doesn't exist + return false; // Target is not here right now, wait for it to return actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + float followDistance = 180; + // When there are multiple actors following the same target, they form a group with each group member at 180*(i+1) distance to the target + int i=0; + std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingIndices(target); + followers.sort(); + for (std::list::iterator it = followers.begin(); it != followers.end(); ++it) + { + if (*it == mFollowIndex) + followDistance *= (i+1); + ++i; + } + if(!mAlwaysFollow) //Update if you only follow for a bit { //Check if we've run out of time @@ -66,7 +82,7 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, if((pos.pos[0]-mX)*(pos.pos[0]-mX) + (pos.pos[1]-mY)*(pos.pos[1]-mY) + - (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) //Close-ish to final position + (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < followDistance*followDistance) //Close-ish to final position { if(actor.getCell()->isExterior()) //Outside? { @@ -84,7 +100,7 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, //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 + if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < followDistance) //Stop when you get close actor.getClass().getMovementSettings(actor).mPosition[1] = 0; else { pathTo(actor, dest, duration); //Go to the destination @@ -159,3 +175,8 @@ MWWorld::Ptr MWMechanics::AiFollow::getTarget() else return MWWorld::Ptr(); } + +int MWMechanics::AiFollow::getFollowIndex() const +{ + return mFollowIndex; +} diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index d5dd42826d..23b159b882 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -46,6 +46,8 @@ namespace MWMechanics bool isCommanded() const; + int getFollowIndex() const; + private: /// This will make the actor always follow. /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ @@ -58,6 +60,9 @@ namespace MWMechanics std::string mActorRefId; int mActorId; std::string mCellId; + int mFollowIndex; + + static int mFollowIndexCounter; }; } #endif diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 9cafe9b3ce..1dbe6f950e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1268,6 +1268,11 @@ namespace MWMechanics return mActors.getActorsFollowing(actor); } + std::list MechanicsManager::getActorsFollowingIndices(const MWWorld::Ptr& actor) + { + return mActors.getActorsFollowingIndices(actor); + } + std::list MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { return mActors.getActorsFighting(actor); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 9f9e85c5af..1eec26c8ac 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -147,6 +147,7 @@ namespace MWMechanics virtual void getActorsInRange(const Ogre::Vector3 &position, float radius, std::vector &objects); virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); + virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); virtual std::list getActorsFighting(const MWWorld::Ptr& actor); From e0c6f845464f211fc7f87a11e5b2b9c9a3b3d554 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 9 Dec 2014 22:25:28 +0100 Subject: [PATCH 095/167] AiFollow: target has to be seen in order to start following (Fixes #1637) --- apps/openmw/mwmechanics/aifollow.cpp | 77 +++++++++++++++++++++------- apps/openmw/mwmechanics/aifollow.hpp | 1 + components/esm/aisequence.cpp | 4 ++ components/esm/aisequence.hpp | 2 + 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 8f3e19b465..161f4bb905 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -13,39 +13,51 @@ #include "movement.hpp" #include +#include #include "steering.hpp" -int MWMechanics::AiFollow::mFollowIndexCounter = 0; +namespace MWMechanics +{ -MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) + +struct AiFollowStorage : AiTemporaryBase +{ + float mTimer; + + AiFollowStorage() : mTimer(0.f) {} +}; + +int AiFollow::mFollowIndexCounter = 0; + +AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) : mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mActorRefId(actorId), mCellId(""), mActorId(-1), mFollowIndex(mFollowIndexCounter++) +, mActorRefId(actorId), mCellId(""), mActorId(-1), mFollowIndex(mFollowIndexCounter++), mActive(false) { } -MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) +AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) : mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mActorRefId(actorId), mCellId(cellId), mActorId(-1), mFollowIndex(mFollowIndexCounter++) +, mActorRefId(actorId), mCellId(cellId), mActorId(-1), mFollowIndex(mFollowIndexCounter++), mActive(false) { } -MWMechanics::AiFollow::AiFollow(const std::string &actorId, bool commanded) +AiFollow::AiFollow(const std::string &actorId, bool commanded) : mAlwaysFollow(true), mCommanded(commanded), mRemainingDuration(0), mX(0), mY(0), mZ(0) -, mActorRefId(actorId), mCellId(""), mActorId(-1), mFollowIndex(mFollowIndexCounter++) +, mActorRefId(actorId), mCellId(""), mActorId(-1), mFollowIndex(mFollowIndexCounter++), mActive(false) { } -MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) +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) , mActorRefId(follow->mTargetId), mActorId(-1), mCellId(follow->mCellId) - , mCommanded(follow->mCommanded), mFollowIndex(mFollowIndexCounter++) + , mCommanded(follow->mCommanded), mFollowIndex(mFollowIndexCounter++), mActive(follow->mActive) { } -bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duration) +bool AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { MWWorld::Ptr target = getTarget(); @@ -56,6 +68,24 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + // AiFollow requires the target to be in range and within sight for the initial activation + if (!mActive) + { + AiFollowStorage& storage = state.get(); + storage.mTimer -= duration; + + if (storage.mTimer < 0) + { + if (Ogre::Vector3(actor.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(target.getRefData().getPosition().pos)) + < 500*500 + && MWBase::Environment::get().getWorld()->getLOS(actor, target)) + mActive = true; + storage.mTimer = 0.5f; + } + } + if (!mActive) + return false; + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor float followDistance = 180; @@ -101,8 +131,16 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < followDistance) //Stop when you get close + { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - else { + + // turn towards target anyway + float directionX = target.getRefData().getPosition().pos[0] - actor.getRefData().getPosition().pos[0]; + float directionY = target.getRefData().getPosition().pos[1] - actor.getRefData().getPosition().pos[1]; + zTurn(actor, Ogre::Math::ATan2(directionX,directionY), Ogre::Degree(5)); + } + else + { pathTo(actor, dest, duration); //Go to the destination } @@ -115,27 +153,27 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, return false; } -std::string MWMechanics::AiFollow::getFollowedActor() +std::string AiFollow::getFollowedActor() { return mActorRefId; } -MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const +AiFollow *MWMechanics::AiFollow::clone() const { return new AiFollow(*this); } -int MWMechanics::AiFollow::getTypeId() const +int AiFollow::getTypeId() const { return TypeIdFollow; } -bool MWMechanics::AiFollow::isCommanded() const +bool AiFollow::isCommanded() const { return mCommanded; } -void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const +void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const { std::auto_ptr follow(new ESM::AiSequence::AiFollow()); follow->mData.mX = mX; @@ -146,6 +184,7 @@ void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) co follow->mCellId = mCellId; follow->mAlwaysFollow = mAlwaysFollow; follow->mCommanded = mCommanded; + follow->mActive = mActive; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Follow; @@ -153,7 +192,7 @@ void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) co sequence.mPackages.push_back(package); } -MWWorld::Ptr MWMechanics::AiFollow::getTarget() +MWWorld::Ptr AiFollow::getTarget() { if (mActorId == -2) return MWWorld::Ptr(); @@ -176,7 +215,9 @@ MWWorld::Ptr MWMechanics::AiFollow::getTarget() return MWWorld::Ptr(); } -int MWMechanics::AiFollow::getFollowIndex() const +int AiFollow::getFollowIndex() const { return mFollowIndex; } + +} diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 23b159b882..68a1f0ea5f 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -60,6 +60,7 @@ namespace MWMechanics std::string mActorRefId; int mActorId; std::string mCellId; + bool mActive; // have we spotted the target? int mFollowIndex; static int mFollowIndexCounter; diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index 0e3e541024..339b390d74 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -60,6 +60,8 @@ namespace AiSequence esm.getHNT (mAlwaysFollow, "ALWY"); mCommanded = false; esm.getHNOT (mCommanded, "CMND"); + mActive = false; + esm.getHNOT (mActive, "ACTV"); } void AiFollow::save(ESMWriter &esm) const @@ -71,6 +73,8 @@ namespace AiSequence esm.writeHNString ("CELL", mCellId); esm.writeHNT ("ALWY", mAlwaysFollow); esm.writeHNT ("CMND", mCommanded); + if (mActive) + esm.writeHNT("ACTV", mActive); } void AiActivate::load(ESMReader &esm) diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index da16bf867a..fbf83c2455 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -98,6 +98,8 @@ namespace ESM bool mAlwaysFollow; bool mCommanded; + bool mActive; + void load(ESMReader &esm); void save(ESMWriter &esm) const; }; From 3c747195ae2f8bcc82d2c978ecdf845cafd10b03 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Dec 2014 17:21:34 +0100 Subject: [PATCH 096/167] Add fall damage for creatures (Fixes #2201) --- apps/openmw/mwclass/npc.cpp | 31 --------------------------- apps/openmw/mwclass/npc.hpp | 3 --- apps/openmw/mwmechanics/character.cpp | 31 ++++++++++++++++++++++++++- apps/openmw/mwworld/class.cpp | 5 ----- apps/openmw/mwworld/class.hpp | 3 --- 5 files changed, 30 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 641edcc834..49f76d25c6 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1002,37 +1002,6 @@ namespace MWClass return x; } - float Npc::getFallDamage(const MWWorld::Ptr &ptr, float fallHeight) const - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &store = world->getStore().get(); - - 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).getMagnitude(); - 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; - x = std::max(0.0f, x); - - float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill); - x = fallDistanceBase + fallDistanceMult * x; - x *= a; - - return x; - } - - return 0; - } - MWMechanics::Movement& Npc::getMovementSettings (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index fd16e6f083..a92e72af59 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -104,9 +104,6 @@ namespace MWClass virtual float getJump(const MWWorld::Ptr &ptr) const; ///< Return jump velocity (not accounting for movement) - virtual float getFallDamage(const MWWorld::Ptr &ptr, float fallHeight) const; - ///< Return amount of health points lost when falling - virtual MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const; ///< Return desired movement. diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 46e06f4604..87d9a5b679 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -92,6 +92,35 @@ MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState sta return ret; } +float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight) +{ + MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Store &store = world->getStore().get(); + + const float fallDistanceMin = store.find("fFallDamageDistanceMin")->getFloat(); + + if (fallHeight >= fallDistanceMin) + { + const float acrobaticsSkill = ptr.getClass().getSkill(ptr, ESM::Skill::Acrobatics); + const float jumpSpellBonus = ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude(); + 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; + x = std::max(0.0f, x); + + float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill); + x = fallDistanceBase + fallDistanceMult * x; + x *= a; + + return x; + } + return 0.f; +} + } namespace MWMechanics @@ -1449,7 +1478,7 @@ void CharacterController::update(float duration) vec.z = 0.0f; float height = cls.getCreatureStats(mPtr).land(); - float healthLost = cls.getFallDamage(mPtr, height); + float healthLost = getFallDamage(mPtr, height); if (healthLost > 0.0f) { const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm(); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 16c2469c14..0a84862097 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -180,11 +180,6 @@ namespace MWWorld throw std::runtime_error ("class does not support enchanting"); } - float Class::getFallDamage(const MWWorld::Ptr &ptr, float fallHeight) const - { - return 0; - } - MWMechanics::Movement& Class::getMovementSettings (const Ptr& ptr) const { throw std::runtime_error ("movement settings not supported by class"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index b66ca74880..dcac16b06e 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -184,9 +184,6 @@ namespace MWWorld virtual float getJump(const MWWorld::Ptr &ptr) const; ///< Return jump velocity (not accounting for movement) - virtual float getFallDamage(const MWWorld::Ptr &ptr, float fallHeight) const; - ///< Return amount of health points lost when falling - virtual MWMechanics::Movement& getMovementSettings (const Ptr& ptr) const; ///< Return desired movement. From ead6bf16011bca8f4b6d35a01b1a6a2a68486d55 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Dec 2014 17:30:33 +0100 Subject: [PATCH 097/167] Enchanting: cast the enchant points for the item to int (Fixes #2202) --- apps/openmw/mwmechanics/enchanting.cpp | 6 +++--- apps/openmw/mwmechanics/enchanting.hpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index c134942b88..759b2a7bba 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -156,7 +156,7 @@ namespace MWMechanics * * Formula on UESPWiki is not entirely correct. */ - float Enchanting::getEnchantPoints() const + int Enchanting::getEnchantPoints() const { if (mEffectList.mList.empty()) // No effects added, cost = 0 @@ -195,7 +195,7 @@ namespace MWMechanics --effectsLeftCnt; } - return enchantmentCost; + return static_cast(enchantmentCost); } @@ -240,7 +240,7 @@ namespace MWMechanics return soul->mData.mSoul; } - float Enchanting::getMaxEnchantValue() const + int Enchanting::getMaxEnchantValue() const { if (itemEmpty()) return 0; diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 01ca1e0e1d..d41305c4ac 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -35,10 +35,10 @@ namespace MWMechanics bool create(); //Return true if created, false if failed. void nextCastStyle(); //Set enchant type to next possible type (for mOldItemPtr object) int getCastStyle() const; - float getEnchantPoints() const; + int getEnchantPoints() const; float getCastCost() const; int getEnchantPrice() const; - float getMaxEnchantValue() const; + int getMaxEnchantValue() const; int getGemCharge() const; float getEnchantChance() const; bool soulEmpty() const; //Return true if empty From 74c345f79057504ed0976648b82954aa21fa95ee Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Dec 2014 17:40:59 +0100 Subject: [PATCH 098/167] Enchanting: fix being able to create On Touch / On Target constant effect enchantments (this combination makes no sense) --- apps/openmw/mwgui/spellcreationdialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 00cab6c455..2124c27247 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -198,11 +198,11 @@ namespace MWGui mRangeButton->setCaptionWithReplacing ("#{sRangeTouch}"); // cycle through range types until we find something that's allowed - if (mEffect.mRange == ESM::RT_Target && !(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget)) + if (mEffect.mRange == ESM::RT_Target && (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) || constantEffect)) onRangeButtonClicked(sender); if (mEffect.mRange == ESM::RT_Self && !(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf)) onRangeButtonClicked(sender); - if (mEffect.mRange == ESM::RT_Touch && !(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch)) + if (mEffect.mRange == ESM::RT_Touch && (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) || constantEffect)) onRangeButtonClicked(sender); if(mEffect.mRange == ESM::RT_Self) From 619ea846b47ad4e45b6a4e90981810a6e9c3ff00 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Dec 2014 17:48:49 +0100 Subject: [PATCH 099/167] Enchanting: fixed case where no range types at all are allowed (e.g. a Constant Effect item with an effect that does not allow the Self range-type) --- apps/openmw/mwgui/spellcreationdialog.cpp | 47 ++++++++++++++--------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 2124c27247..5da33c67ae 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -89,15 +89,22 @@ namespace MWGui void EditEffectDialog::newEffect (const ESM::MagicEffect *effect) { + bool allowSelf = effect->mData.mFlags & ESM::MagicEffect::CastSelf; + bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !constantEffect; + bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !constantEffect; + + if (!allowSelf && !allowTouch && !allowTarget) + return; // TODO: Show an error message popup? + setMagicEffect(effect); mEditing = false; mDeleteButton->setVisible (false); mEffect.mRange = ESM::RT_Self; - if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf)) + if (!allowSelf) mEffect.mRange = ESM::RT_Touch; - if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch)) + if (!allowTouch) mEffect.mRange = ESM::RT_Target; mEffect.mMagnMin = 1; mEffect.mMagnMax = 1; @@ -118,6 +125,8 @@ namespace MWGui mMagnitudeMinValue->setCaption("1"); mMagnitudeMaxValue->setCaption("- 1"); mAreaValue->setCaption("0"); + + setVisible(true); } void EditEffectDialog::editEffect (ESM::ENAMstruct effect) @@ -190,6 +199,24 @@ namespace MWGui { mEffect.mRange = (mEffect.mRange+1)%3; + // cycle through range types until we find something that's allowed + // does not handle the case where nothing is allowed (this should be prevented before opening the Add Effect dialog) + bool allowSelf = mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf; + bool allowTouch = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) && !constantEffect; + bool allowTarget = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) && !constantEffect; + if (mEffect.mRange == ESM::RT_Self && !allowSelf) + mEffect.mRange = (mEffect.mRange+1)%3; + if (mEffect.mRange == ESM::RT_Touch && !allowTouch) + mEffect.mRange = (mEffect.mRange+1)%3; + if (mEffect.mRange == ESM::RT_Target && !allowTarget) + mEffect.mRange = (mEffect.mRange+1)%3; + + if(mEffect.mRange == ESM::RT_Self) + { + mAreaSlider->setScrollPosition(0); + onAreaChanged(mAreaSlider,0); + } + if (mEffect.mRange == ESM::RT_Self) mRangeButton->setCaptionWithReplacing ("#{sRangeSelf}"); else if (mEffect.mRange == ESM::RT_Target) @@ -197,19 +224,6 @@ namespace MWGui else if (mEffect.mRange == ESM::RT_Touch) mRangeButton->setCaptionWithReplacing ("#{sRangeTouch}"); - // cycle through range types until we find something that's allowed - if (mEffect.mRange == ESM::RT_Target && (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) || constantEffect)) - onRangeButtonClicked(sender); - if (mEffect.mRange == ESM::RT_Self && !(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf)) - onRangeButtonClicked(sender); - if (mEffect.mRange == ESM::RT_Touch && (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) || constantEffect)) - onRangeButtonClicked(sender); - - if(mEffect.mRange == ESM::RT_Self) - { - mAreaSlider->setScrollPosition(0); - onAreaChanged(mAreaSlider,0); - } updateBoxes(); eventEffectModified(mEffect); } @@ -542,7 +556,6 @@ namespace MWGui mAddEffectDialog.newEffect(effect); mAddEffectDialog.setAttribute (mSelectAttributeDialog->getAttributeId()); - mAddEffectDialog.setVisible(true); MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog); mSelectAttributeDialog = 0; } @@ -554,7 +567,6 @@ namespace MWGui mAddEffectDialog.newEffect(effect); mAddEffectDialog.setSkill (mSelectSkillDialog->getSkillId()); - mAddEffectDialog.setVisible(true); MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog); mSelectSkillDialog = 0; } @@ -611,7 +623,6 @@ namespace MWGui else { mAddEffectDialog.newEffect(effect); - mAddEffectDialog.setVisible(true); } } From 623783cd6ac77da581a8106aee5abfa02bec2feb Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Dec 2014 18:05:11 +0100 Subject: [PATCH 100/167] Enchanting: fix cast cost for "on use" enchantments being set incorrectly --- apps/openmw/mwgui/enchantingdialog.cpp | 4 ++-- apps/openmw/mwmechanics/enchanting.cpp | 6 +++--- apps/openmw/mwmechanics/enchanting.hpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 30d67f5540..56caa6513b 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -109,8 +109,8 @@ namespace MWGui mCharge->setCaption(boost::lexical_cast(mEnchanting.getGemCharge())); std::stringstream castCost; - castCost << std::setprecision(1) << std::fixed << mEnchanting.getCastCost(); - mCastCost->setCaption(boost::lexical_cast(castCost.str())); + castCost << mEnchanting.getCastCost(); + mCastCost->setCaption(castCost.str()); mPrice->setCaption(boost::lexical_cast(mEnchanting.getEnchantPrice())); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 759b2a7bba..8c85e5eef5 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -55,7 +55,7 @@ namespace MWMechanics enchantment.mData.mCharge = getGemCharge(); enchantment.mData.mAutocalc = 0; enchantment.mData.mType = mCastStyle; - enchantment.mData.mCost = getEnchantPoints(); + enchantment.mData.mCost = getCastCost(); store.remove(mSoulGemPtr, 1, player); @@ -199,7 +199,7 @@ namespace MWMechanics } - float Enchanting::getCastCost() const + int Enchanting::getCastCost() const { if (mCastStyle == ESM::Enchantment::ConstantEffect) return 0; @@ -215,7 +215,7 @@ namespace MWMechanics */ const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); - return (castCost < 1) ? 1 : castCost; + return static_cast((castCost < 1) ? 1 : castCost); } diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index d41305c4ac..2ee5ccce4e 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -36,7 +36,7 @@ namespace MWMechanics void nextCastStyle(); //Set enchant type to next possible type (for mOldItemPtr object) int getCastStyle() const; int getEnchantPoints() const; - float getCastCost() const; + int getCastCost() const; int getEnchantPrice() const; int getMaxEnchantValue() const; int getGemCharge() const; From 6eebe9b44c2bb041b05854e70b27b4340a608181 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Dec 2014 20:51:32 +0100 Subject: [PATCH 101/167] Read NiFogProperty, but don't use it yet (Feature #920) --- components/nif/niffile.cpp | 1 + components/nif/property.hpp | 16 ++++++++++++++++ components/nif/record.hpp | 1 + 3 files changed, 18 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index c689e27b3a..9d63ac7ab5 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -57,6 +57,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiCamera", &construct , RC_NiCamera )); newFactory.insert(makeEntry("RootCollisionNode", &construct , RC_RootCollisionNode )); newFactory.insert(makeEntry("NiTexturingProperty", &construct , RC_NiTexturingProperty )); + newFactory.insert(makeEntry("NiFogProperty", &construct , RC_NiFogProperty )); newFactory.insert(makeEntry("NiMaterialProperty", &construct , RC_NiMaterialProperty )); newFactory.insert(makeEntry("NiZBufferProperty", &construct , RC_NiZBufferProperty )); newFactory.insert(makeEntry("NiAlphaProperty", &construct , RC_NiAlphaProperty )); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 2c7747a3ec..77f61d0684 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -155,6 +155,22 @@ public: } }; +class NiFogProperty : public Property +{ +public: + float mFogDepth; + Ogre::Vector3 mColour; + + + void read(NIFStream *nif) + { + Property::read(nif); + + mFogDepth = nif->getFloat(); + mColour = nif->getVector3(); + } +}; + // These contain no other data than the 'flags' field in Property class NiShadeProperty : public Property { }; class NiDitherProperty : public Property { }; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 079b335f05..07d7540f85 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -44,6 +44,7 @@ enum RecordType RC_NiBSParticleNode, RC_NiCamera, RC_NiTexturingProperty, + RC_NiFogProperty, RC_NiMaterialProperty, RC_NiZBufferProperty, RC_NiAlphaProperty, From 33019b93b4a17c7facb077abb9836efcfa1409b7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Dec 2014 21:10:14 +0100 Subject: [PATCH 102/167] Fix bug setting current launcher profile on startup (Bug #2188) --- apps/launcher/datafilespage.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index f45b444707..4015579c20 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -238,10 +238,8 @@ void Launcher::DataFilesPage::addProfile (const QString &profile, bool setAsCurr if (profile.isEmpty()) return; - if (ui.profilesComboBox->findText (profile) != -1) - return; - - ui.profilesComboBox->addItem (profile); + if (ui.profilesComboBox->findText (profile) == -1) + ui.profilesComboBox->addItem (profile); if (setAsCurrent) setProfile (ui.profilesComboBox->findText (profile), false); From fb1aa096beb67042d3bcb997fead1be4a9b9e1b6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Dec 2014 21:46:36 +0100 Subject: [PATCH 103/167] Settings: reduce scope for better readability --- components/config/settingsbase.hpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/components/config/settingsbase.hpp b/components/config/settingsbase.hpp index 92ca34cdfe..e6b0908e0a 100644 --- a/components/config/settingsbase.hpp +++ b/components/config/settingsbase.hpp @@ -50,7 +50,7 @@ namespace Config bool readFile(QTextStream &stream) { - mCache.clear(); + Map cache; QString sectionPrefix; @@ -79,31 +79,30 @@ namespace Config mSettings.remove(key); - QStringList values = mCache.values(key); + QStringList values = cache.values(key); if (!values.contains(value)) { if (mMultiValue) { - mCache.insertMulti(key, value); + cache.insertMulti(key, value); } else { - mCache.insert(key, value); + cache.insert(key, value); } } } } if (mSettings.isEmpty()) { - mSettings = mCache; // This is the first time we read a file + mSettings = cache; // This is the first time we read a file return true; } // Merge the changed keys with those which didn't - mSettings.unite(mCache); + mSettings.unite(cache); return true; } private: Map mSettings; - Map mCache; bool mMultiValue; }; From 1937ace1b748e66b9044e679fd3923172601c006 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Dec 2014 21:47:04 +0100 Subject: [PATCH 104/167] Launcher: fix bugs deleting profiles (Fixes #2188) --- apps/launcher/datafilespage.cpp | 18 ++++++++++-------- apps/launcher/datafilespage.hpp | 2 ++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 4015579c20..7192ed7842 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -154,9 +154,11 @@ void Launcher::DataFilesPage::setProfile(int index, bool savePrevious) { if (index >= -1 && index < ui.profilesComboBox->count()) { - QString previous = ui.profilesComboBox->itemText(ui.profilesComboBox->currentIndex()); + QString previous = mPreviousProfile; QString current = ui.profilesComboBox->itemText(index); + mPreviousProfile = current; + setProfile (previous, current, savePrevious); } } @@ -167,9 +169,6 @@ void Launcher::DataFilesPage::setProfile (const QString &previous, const QString if (previous == current) return; - if (previous.isEmpty()) - return; - if (!previous.isEmpty() && savePrevious) saveSettings (previous); @@ -212,7 +211,7 @@ void Launcher::DataFilesPage::slotProfileChanged(int index) void Launcher::DataFilesPage::on_newProfileAction_triggered() { - if (!mProfileDialog->exec() == QDialog::Accepted) + if (mProfileDialog->exec() != QDialog::Accepted) return; QString profile = mProfileDialog->lineEdit()->text(); @@ -222,9 +221,10 @@ void Launcher::DataFilesPage::on_newProfileAction_triggered() saveSettings(); - mSelector->clearCheckStates(); + mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile); addProfile(profile, true); + mSelector->clearCheckStates(); mSelector->setGameFile(); @@ -255,10 +255,12 @@ void Launcher::DataFilesPage::on_deleteProfileAction_triggered() if (!showDeleteMessageBox (profile)) return; - // Remove the profile from the combobox - ui.profilesComboBox->removeItem (ui.profilesComboBox->findText (profile)); + // this should work since the Default profile can't be deleted and is always index 0 + int next = ui.profilesComboBox->currentIndex()-1; + ui.profilesComboBox->setCurrentIndex(next); removeProfile(profile); + ui.profilesComboBox->removeItem(ui.profilesComboBox->findText(profile)); saveSettings(); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 5beeb0e03a..15fa00308d 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -67,6 +67,8 @@ namespace Launcher Config::GameSettings &mGameSettings; Config::LauncherSettings &mLauncherSettings; + QString mPreviousProfile; + QString mDataLocal; void setPluginsCheckstates(Qt::CheckState state); From 04a68fc9765eb57c613f0054c9d8f5198d2699bd Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Dec 2014 00:20:45 +0100 Subject: [PATCH 105/167] Revert "Implement overwriting pathgrid records" This broke pathgrid loading in exterior cells due to an unexpected problem in the pathgrid record system (bug #2195). This reverts commit dd0cea21b0ead17a13198f3e584d343c6bb31875. --- apps/openmw/mwworld/store.hpp | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 469b93f88e..55c5b8191f 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -876,36 +876,8 @@ namespace MWWorld public: void load(ESM::ESMReader &esm, const std::string &id) { - - ESM::Pathgrid pathgrid; - pathgrid.load(esm); - - // Try to overwrite existing record - // Can't use search() because we aren't sorted yet - if (!pathgrid.mCell.empty()) - { - for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) - { - if ((*it).mCell == pathgrid.mCell) - { - (*it) = pathgrid; - return; - } - } - } - else - { - for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) - { - if ((*it).mData.mX == pathgrid.mData.mX && (*it).mData.mY == pathgrid.mData.mY) - { - (*it) = pathgrid; - return; - } - } - } - - mStatic.push_back(pathgrid); + mStatic.push_back(ESM::Pathgrid()); + mStatic.back().load(esm); } size_t getSize() const { From 46cf2a7a774fd96bd15f2f22c48d23dbba332e7d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 11 Dec 2014 09:20:18 +0100 Subject: [PATCH 106/167] updated changelog --- readme.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/readme.txt b/readme.txt index e91455aebe..83f139a1d8 100644 --- a/readme.txt +++ b/readme.txt @@ -160,7 +160,6 @@ Bug #2161: Editor: combat/magic/stealth values of creature not displayed correct Bug #2163: OpenMW can't detect death if the NPC die by the post damage effect of a magic weapon. Bug #2168: Westly's Master Head Pack X – Some hairs aren't rendered correctly. Bug #2170: Mods using conversations to update PC inconsistant -Bug #2175: Pathgrid mods do not overwrite the existing pathgrid Bug #2180: Editor: Verifier doesn't handle Windows-specific path issues when dealing with resources Feature #238: Add UI to run INI-importer from the launcher Feature #854: Editor: Add user setting to show status bar From 9ef6e95bf6a1677f47ca0717a711ab0390bf057d Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Dec 2014 21:47:04 +0100 Subject: [PATCH 107/167] Launcher: fix bugs deleting profiles (Fixes #2188) --- apps/launcher/datafilespage.cpp | 18 ++++++++++-------- apps/launcher/datafilespage.hpp | 2 ++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index f45b444707..063d601975 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -154,9 +154,11 @@ void Launcher::DataFilesPage::setProfile(int index, bool savePrevious) { if (index >= -1 && index < ui.profilesComboBox->count()) { - QString previous = ui.profilesComboBox->itemText(ui.profilesComboBox->currentIndex()); + QString previous = mPreviousProfile; QString current = ui.profilesComboBox->itemText(index); + mPreviousProfile = current; + setProfile (previous, current, savePrevious); } } @@ -167,9 +169,6 @@ void Launcher::DataFilesPage::setProfile (const QString &previous, const QString if (previous == current) return; - if (previous.isEmpty()) - return; - if (!previous.isEmpty() && savePrevious) saveSettings (previous); @@ -212,7 +211,7 @@ void Launcher::DataFilesPage::slotProfileChanged(int index) void Launcher::DataFilesPage::on_newProfileAction_triggered() { - if (!mProfileDialog->exec() == QDialog::Accepted) + if (mProfileDialog->exec() != QDialog::Accepted) return; QString profile = mProfileDialog->lineEdit()->text(); @@ -222,9 +221,10 @@ void Launcher::DataFilesPage::on_newProfileAction_triggered() saveSettings(); - mSelector->clearCheckStates(); + mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile); addProfile(profile, true); + mSelector->clearCheckStates(); mSelector->setGameFile(); @@ -257,10 +257,12 @@ void Launcher::DataFilesPage::on_deleteProfileAction_triggered() if (!showDeleteMessageBox (profile)) return; - // Remove the profile from the combobox - ui.profilesComboBox->removeItem (ui.profilesComboBox->findText (profile)); + // this should work since the Default profile can't be deleted and is always index 0 + int next = ui.profilesComboBox->currentIndex()-1; + ui.profilesComboBox->setCurrentIndex(next); removeProfile(profile); + ui.profilesComboBox->removeItem(ui.profilesComboBox->findText(profile)); saveSettings(); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 5beeb0e03a..15fa00308d 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -67,6 +67,8 @@ namespace Launcher Config::GameSettings &mGameSettings; Config::LauncherSettings &mLauncherSettings; + QString mPreviousProfile; + QString mDataLocal; void setPluginsCheckstates(Qt::CheckState state); From ed66bbb28d4b1dfb2bd00579c2c75aae5ad1b27e Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Dec 2014 21:10:14 +0100 Subject: [PATCH 108/167] Fix bug setting current launcher profile on startup (Bug #2188) --- apps/launcher/datafilespage.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 063d601975..7192ed7842 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -238,10 +238,8 @@ void Launcher::DataFilesPage::addProfile (const QString &profile, bool setAsCurr if (profile.isEmpty()) return; - if (ui.profilesComboBox->findText (profile) != -1) - return; - - ui.profilesComboBox->addItem (profile); + if (ui.profilesComboBox->findText (profile) == -1) + ui.profilesComboBox->addItem (profile); if (setAsCurrent) setProfile (ui.profilesComboBox->findText (profile), false); From cda0363f29f9fa24e603ca0c375e8f8a0e365ba4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 11 Dec 2014 13:51:37 +0100 Subject: [PATCH 109/167] allow a space in the middle of multi-character comparison operators (Fixes #2185) --- components/compiler/scanner.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 16d54ff51d..203f27e6e8 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -391,6 +391,10 @@ namespace Compiler { if (get (c)) { + /// \todo hack to allow a space in comparison operators (add option to disable) + if (c==' ') + get (c); + if (c=='=') special = S_cmpEQ; else @@ -398,7 +402,7 @@ namespace Compiler special = S_cmpEQ; putback (c); // return false; -// Allow = as synonym for ==. \todo optionally disable for post-1.0 scripting improvements. +/// Allow = as synonym for ==. \todo optionally disable for post-1.0 scripting improvements. } } else @@ -411,6 +415,10 @@ namespace Compiler { if (get (c)) { + /// \todo hack to allow a space in comparison operators (add option to disable) + if (c==' ' && !get (c)) + return false; + if (c=='=') special = S_cmpNE; else @@ -441,6 +449,10 @@ namespace Compiler { if (get (c)) { + /// \todo hack to allow a space in comparison operators (add option to disable) + if (c==' ') + get (c); + if (c=='=') { special = S_cmpLE; @@ -461,6 +473,10 @@ namespace Compiler { if (get (c)) { + /// \todo hack to allow a space in comparison operators (add option to disable) + if (c==' ') + get (c); + if (c=='=') { special = S_cmpGE; From 3270f0e932de557a9e5ec72390e180ad53477de6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Dec 2014 15:19:48 +0100 Subject: [PATCH 110/167] Change pathgrid workaround to check for interior cell name --- apps/openmw/mwmechanics/aiwander.cpp | 2 +- apps/openmw/mwmechanics/pathfinding.cpp | 3 +- apps/openmw/mwmechanics/pathgrid.cpp | 4 +- apps/openmw/mwrender/debugging.cpp | 2 +- apps/openmw/mwworld/esmstore.hpp | 2 + apps/openmw/mwworld/store.hpp | 97 ++++++++++++++++++------- 6 files changed, 76 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 3224127dfd..c8a0c85d58 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -382,7 +382,7 @@ namespace MWMechanics { // infrequently used, therefore no benefit in caching it as a member const ESM::Pathgrid * - pathgrid = world->getStore().get().search(*cell, world->getCellName(currentCell)); + pathgrid = world->getStore().get().search(*cell); // cache the current cell location cachedCellX = cell->mData.mX; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index f0e5b1d9de..f1279c415e 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -188,8 +188,7 @@ namespace MWMechanics if(mCell != cell || !mPathgrid) { mCell = cell; - mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell(), - MWBase::Environment::get().getWorld()->getCellName(mCell)); + mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->getCell()); } // Refer to AiWander reseach topic on openmw forums for some background. diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index d380cba4e3..848d2c7a03 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -105,9 +105,7 @@ namespace MWMechanics mCell = cell->getCell(); mIsExterior = cell->getCell()->isExterior(); - mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell->getCell(), - MWBase::Environment::get().getWorld()->getCellName(cell)); - + mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell->getCell()); if(!mPathgrid) return false; diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index 553a6379f5..972c1b6dd0 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -233,7 +233,7 @@ void Debugging::enableCellPathgrid(MWWorld::CellStore *store) { MWBase::World* world = MWBase::Environment::get().getWorld(); const ESM::Pathgrid *pathgrid = - world->getStore().get().search(*store->getCell(), world->getCellName(store)); + world->getStore().get().search(*store->getCell()); if (!pathgrid) return; Vector3 cellPathGridPos(0, 0, 0); diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 90786acd42..83e911174d 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -141,6 +141,8 @@ namespace MWWorld mStores[ESM::REC_SSCR] = &mStartScripts; mStores[ESM::REC_STAT] = &mStatics; mStores[ESM::REC_WEAP] = &mWeapons; + + mPathgrids.setCells(mCells); } void clearDynamic () diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 1aaf902f85..c4070f0327 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -843,60 +843,103 @@ namespace MWWorld class Store : public StoreBase { private: - // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. - // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. - // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. - // This is why we keep both interior and exterior pathgrids in the same container here. - typedef std::pair > PathgridKey; - typedef std::map Static; + typedef std::map Interior; + typedef std::map, ESM::Pathgrid> Exterior; - Static mStatic; + Interior mInt; + Exterior mExt; + + Store* mCells; public: + void setCells(Store& cells) + { + mCells = &cells; + } + void load(ESM::ESMReader &esm, const std::string &id) { ESM::Pathgrid pathgrid; pathgrid.load(esm); - PathgridKey key = std::make_pair(pathgrid.mCell, std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY)); + // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. + // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. + // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. + // Check whether mCell is an interior cell. This isn't perfect, will break if a Region with the same name as an interior cell is created. + // A proper fix should be made for future versions of the file format. + bool interior = mCells->search(pathgrid.mCell) != NULL; // Try to overwrite existing record - std::pair ret = mStatic.insert(std::make_pair(key, pathgrid)); - if (!ret.second) - ret.first->second = pathgrid; + if (interior) + { + std::pair ret = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); + if (!ret.second) + ret.first->second = pathgrid; + } + else + { + std::pair ret = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), pathgrid)); + if (!ret.second) + ret.first->second = pathgrid; + } } size_t getSize() const { - return mStatic.size(); + return mInt.size() + mExt.size(); } void setUp() { } - const ESM::Pathgrid *search(const ESM::Cell &cell, const std::string& cellName) const { - int x=0,y=0; - if (!(cell.mData.mFlags & ESM::Cell::Interior)) - { - x = cell.mData.mX; - y = cell.mData.mY; - } - PathgridKey key = std::make_pair(cellName, std::make_pair(x,y)); - - Static::const_iterator it = mStatic.find(key); - if (it != mStatic.end()) + const ESM::Pathgrid *search(int x, int y) const { + Exterior::const_iterator it = mExt.find(std::make_pair(x,y)); + if (it != mExt.end()) return &(it->second); return NULL; } - const ESM::Pathgrid *find(const ESM::Cell &cell, const std::string& cellName) const { - const ESM::Pathgrid* pathgrid = search(cell, cellName); - if (pathgrid == 0) { + const ESM::Pathgrid *search(const std::string& name) const { + Interior::const_iterator it = mInt.find(name); + if (it != mInt.end()) + return &(it->second); + return NULL; + } + + const ESM::Pathgrid *find(int x, int y) const { + const ESM::Pathgrid* pathgrid = search(x,y); + if (!pathgrid) + { std::ostringstream msg; - msg << "Pathgrid in cell '" << cellName << "' not found"; + msg << "Pathgrid in cell '" << x << " " << y << "' not found"; throw std::runtime_error(msg.str()); } return pathgrid; } + + const ESM::Pathgrid* find(const std::string& name) const { + const ESM::Pathgrid* pathgrid = search(name); + if (!pathgrid) + { + std::ostringstream msg; + msg << "Pathgrid in cell '" << name << "' not found"; + throw std::runtime_error(msg.str()); + } + return pathgrid; + } + + const ESM::Pathgrid *search(const ESM::Cell &cell) const { + if (!(cell.mData.mFlags & ESM::Cell::Interior)) + return search(cell.mData.mX, cell.mData.mY); + else + return search(cell.mName); + } + + const ESM::Pathgrid *find(const ESM::Cell &cell) const { + if (!(cell.mData.mFlags & ESM::Cell::Interior)) + return find(cell.mData.mX, cell.mData.mY); + else + return find(cell.mName); + } }; template From 8ed376af5e09a975895fd858654c43f8318a46ad Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Dec 2014 15:59:27 +0100 Subject: [PATCH 111/167] Launcher: fix changing active profile through the Play page --- apps/launcher/datafilespage.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 7192ed7842..3c4d36de77 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -206,6 +206,10 @@ void Launcher::DataFilesPage::slotProfileRenamed(const QString &previous, const void Launcher::DataFilesPage::slotProfileChanged(int index) { + // in case the event was triggered externally + if (ui.profilesComboBox->currentIndex() != index) + ui.profilesComboBox->setCurrentIndex(index); + setProfile (index, true); } From 7e8ca3fff1837b632145d9f076a5769d0c9a7fd8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Dec 2014 19:27:13 +0100 Subject: [PATCH 112/167] Fix object movement between cells producing a stale Ptr within the script execution (Bug #1942) --- apps/openmw/mwbase/world.hpp | 3 +- apps/openmw/mwscript/interpretercontext.cpp | 6 ++++ apps/openmw/mwscript/interpretercontext.hpp | 3 ++ .../mwscript/transformationextensions.cpp | 36 ++++++++++++------- apps/openmw/mwworld/worldimp.cpp | 10 +++--- apps/openmw/mwworld/worldimp.hpp | 7 ++-- 6 files changed, 45 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 2dd135f3da..c674145aef 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -281,7 +281,8 @@ namespace MWBase virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; - virtual void moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; + virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; + ///< @return an updated Ptr in case the Ptr's cell changes virtual void moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z) = 0; diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index d8d13a9211..430389e30c 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -590,4 +590,10 @@ namespace MWScript { return mTargetId; } + + void InterpreterContext::updatePtr(const MWWorld::Ptr& updated) + { + if (!mReference.isEmpty()) + mReference = updated; + } } diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index b543399656..7f3172dd1b 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -169,6 +169,9 @@ namespace MWScript MWWorld::Ptr getReference(bool required=true); ///< Reference, that the script is running from (can be empty) + void updatePtr(const MWWorld::Ptr& updated); + ///< Update the Ptr stored in mReference, if there is one stored there. Should be called after the reference has been moved to a new cell. + virtual std::string getTargetId() const; }; } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 8e6d925b7c..ac9dea4081 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -224,20 +224,23 @@ namespace MWScript float ay = ptr.getRefData().getPosition().pos[1]; float az = ptr.getRefData().getPosition().pos[2]; + MWWorld::Ptr updated = ptr; if(axis == "x") { - MWBase::Environment::get().getWorld()->moveObject(ptr,pos,ay,az); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr,pos,ay,az); } else if(axis == "y") { - MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az); } else if(axis == "z") { - MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos); } else throw std::runtime_error ("invalid axis: " + axis); + + dynamic_cast(runtime.getContext()).updatePtr(updated); } }; @@ -317,6 +320,8 @@ namespace MWScript { MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); ptr = MWWorld::Ptr(ptr.getBase(), store); + dynamic_cast(runtime.getContext()).updatePtr(ptr); + float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) @@ -365,15 +370,18 @@ namespace MWScript // another morrowind oddity: player will be moved to the exterior cell at this location, // non-player actors will move within the cell they are in. + MWWorld::Ptr updated; if (ptr.getRefData().getHandle() == "player") { - MWBase::Environment::get().getWorld()->moveObject(ptr, - MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z); + MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(cx,cy); + MWBase::Environment::get().getWorld()->moveObject(ptr,cell,x,y,z); + updated = MWWorld::Ptr(ptr.getBase(), cell); } else { - MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z); } + dynamic_cast(runtime.getContext()).updatePtr(updated); float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); @@ -638,8 +646,10 @@ namespace MWScript 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]); + + dynamic_cast(runtime.getContext()).updatePtr( + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], + ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2])); } }; @@ -678,7 +688,8 @@ namespace MWScript throw std::runtime_error ("invalid movement axis: " + axis); Ogre::Vector3 worldPos = ptr.getRefData().getBaseNode()->convertLocalToWorldPosition(posChange); - MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x, worldPos.y, worldPos.z); + dynamic_cast(runtime.getContext()).updatePtr( + MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x, worldPos.y, worldPos.z)); } }; @@ -701,17 +712,18 @@ namespace MWScript const float *objPos = ptr.getRefData().getPosition().pos; + MWWorld::Ptr updated; if (axis == "x") { - MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+movement, objPos[1], objPos[2]); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+movement, objPos[1], objPos[2]); } else if (axis == "y") { - MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1]+movement, objPos[2]); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1]+movement, objPos[2]); } else if (axis == "z") { - MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1], objPos[2]+movement); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1], objPos[2]+movement); } else throw std::runtime_error ("invalid movement axis: " + axis); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 2607a6d4d0..75de050e15 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1187,7 +1187,7 @@ namespace MWWorld } } - bool World::moveObjectImp(const Ptr& ptr, float x, float y, float z) + MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z) { CellStore *cell = ptr.getCell(); @@ -1200,12 +1200,14 @@ namespace MWWorld moveObject(ptr, cell, x, y, z); - return cell != ptr.getCell(); + MWWorld::Ptr updated = ptr; + updated.mCell = cell; + return updated; } - void World::moveObject (const Ptr& ptr, float x, float y, float z) + MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z) { - moveObjectImp(ptr, x, y, z); + return moveObjectImp(ptr, x, y, z); } void World::scaleObject (const Ptr& ptr, float scale) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 555ed7fb74..1548d0c87a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -104,8 +104,8 @@ namespace MWWorld void rotateObjectImp (const Ptr& ptr, Ogre::Vector3 rot, bool adjust); - bool moveObjectImp (const Ptr& ptr, float x, float y, float z); - ///< @return true if the active cell (cell player is in) changed + Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z); + ///< @return an updated Ptr in case the Ptr's cell changes Ptr copyObjectToCell(const Ptr &ptr, CellStore* cell, ESM::Position pos, bool adjustPos=true); @@ -341,7 +341,8 @@ namespace MWWorld virtual void deleteObject (const Ptr& ptr); virtual void undeleteObject (const Ptr& ptr); - virtual void moveObject (const Ptr& ptr, float x, float y, float z); + virtual MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z); + ///< @return an updated Ptr in case the Ptr's cell changes virtual void moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z); virtual void scaleObject (const Ptr& ptr, float scale); From ed2aa5a233e16d3d2fa8419607e866f67a7a5589 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Dec 2014 20:32:05 +0100 Subject: [PATCH 113/167] Fix crash caused by dangling baseNode pointer --- apps/openmw/mwworld/worldimp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 75de050e15..e24e99c305 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1158,6 +1158,7 @@ namespace MWWorld ptr.getClass().copyToCell(ptr, *newCell, pos); mRendering->updateObjectCell(ptr, copy); + ptr.getRefData().setBaseNode(NULL); MWBase::Environment::get().getSoundManager()->updatePtr (ptr, copy); MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager(); From d955017079e69b46b60ba8824c181f3afb543daa Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Dec 2014 20:51:02 +0100 Subject: [PATCH 114/167] Don't report script operation status via messageBox (Bug #1942) --- apps/openmw/mwscript/interpretercontext.cpp | 1 - apps/openmw/mwscript/interpretercontext.hpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index d8d13a9211..88aa46a98f 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -205,7 +205,6 @@ namespace MWScript void InterpreterContext::report (const std::string& message) { - messageBox (message); } bool InterpreterContext::menuMode() diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index b543399656..bcf02e68e9 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -78,7 +78,7 @@ namespace MWScript const std::vector& buttons); virtual void report (const std::string& message); - ///< By default echo via messageBox. + ///< By default, do nothing. virtual bool menuMode(); From be16f1d0a53738f3a67c9278031476a2a9820838 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Dec 2014 20:57:25 +0100 Subject: [PATCH 115/167] Implement PcForce1stPerson, PcForce3rdPerson, PcGet3rdPerson (Bug #2078) --- apps/openmw/mwbase/world.hpp | 1 + apps/openmw/mwscript/docs/vmformat.txt | 5 +++- apps/openmw/mwscript/miscextensions.cpp | 32 +++++++++++++++++++++++++ apps/openmw/mwworld/worldimp.hpp | 4 ++++ components/compiler/extensions0.cpp | 3 +++ components/compiler/opcodes.hpp | 3 +++ 6 files changed, 47 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 2dd135f3da..2bec88998d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -387,6 +387,7 @@ namespace MWBase virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0; virtual void togglePOV() = 0; + virtual bool isFirstPerson() const = 0; virtual void togglePreviewMode(bool enable) = 0; virtual bool toggleVanityMode(bool enable) = 0; virtual void allowVanityMode(bool allow) = 0; diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index b80c84d674..c90f63f7fa 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -433,5 +433,8 @@ op 0x20002c4-0x20002db: ModMagicEffect op 0x20002dc-0x20002f3: ModMagicEffect, explicit op 0x20002f4: ResetActors op 0x20002f5: ToggleWorld +op 0x20002f6: PCForce1stPerson +op 0x20002f7: PCForce3rdPerson +op 0x20002f8: PCGet3rdPerson -opcodes 0x20002f6-0x3ffffff unused +opcodes 0x20002f9-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index ec2048e11f..186aa708db 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -312,6 +312,35 @@ namespace MWScript } }; + class OpPcForce1stPerson : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + if (!MWBase::Environment::get().getWorld()->isFirstPerson()) + MWBase::Environment::get().getWorld()->togglePOV(); + } + }; + + class OpPcForce3rdPerson : public Interpreter::Opcode0 + { + virtual void execute (Interpreter::Runtime& runtime) + { + if (MWBase::Environment::get().getWorld()->isFirstPerson()) + MWBase::Environment::get().getWorld()->togglePOV(); + } + }; + + class OpPcGet3rdPerson : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime& runtime) + { + runtime.push(!MWBase::Environment::get().getWorld()->isFirstPerson()); + } + }; + class OpToggleVanityMode : public Interpreter::Opcode0 { static bool sActivate; @@ -1002,6 +1031,9 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeToggleWater, new OpToggleWater); interpreter.installSegment5 (Compiler::Misc::opcodeToggleWorld, new OpToggleWorld); interpreter.installSegment5 (Compiler::Misc::opcodeDontSaveObject, new OpDontSaveObject); + interpreter.installSegment5 (Compiler::Misc::opcodePcForce1stPerson, new OpPcForce1stPerson); + interpreter.installSegment5 (Compiler::Misc::opcodePcForce3rdPerson, new OpPcForce3rdPerson); + interpreter.installSegment5 (Compiler::Misc::opcodePcGet3rdPerson, new OpPcGet3rdPerson); interpreter.installSegment5 (Compiler::Misc::opcodeToggleVanityMode, new OpToggleVanityMode); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcSleep, new OpGetPcSleep); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcJumping, new OpGetPcJumping); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 555ed7fb74..c93d517c19 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -451,6 +451,10 @@ namespace MWWorld mRendering->togglePOV(); } + virtual bool isFirstPerson() const { + return mRendering->getCamera()->isFirstPerson(); + } + virtual void togglePreviewMode(bool enable) { mRendering->togglePreviewMode(enable); } diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 7531cdd5bf..cd5bdbe695 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -261,6 +261,9 @@ namespace Compiler extensions.registerInstruction ("togglepathgrid", "", opcodeTogglePathgrid); extensions.registerInstruction ("tpg", "", opcodeTogglePathgrid); extensions.registerInstruction ("dontsaveobject", "", opcodeDontSaveObject); + extensions.registerInstruction ("pcforce1stperson", "", opcodePcForce1stPerson); + extensions.registerInstruction ("pcforce3rdperson", "", opcodePcForce3rdPerson); + extensions.registerFunction ("pcget3rdperson", 'l', "", opcodePcGet3rdPerson); extensions.registerInstruction ("togglevanitymode", "", opcodeToggleVanityMode); extensions.registerInstruction ("tvm", "", opcodeToggleVanityMode); extensions.registerFunction ("getpcsleep", 'l', "", opcodeGetPcSleep); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 5063397e11..65efc14fa4 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -215,6 +215,9 @@ namespace Compiler const int opcodeToggleWorld = 0x20002f5; const int opcodeTogglePathgrid = 0x2000146; const int opcodeDontSaveObject = 0x2000153; + const int opcodePcForce1stPerson = 0x20002f6; + const int opcodePcForce3rdPerson = 0x20002f7; + const int opcodePcGet3rdPerson = 0x20002f8; const int opcodeToggleVanityMode = 0x2000174; const int opcodeGetPcSleep = 0x200019f; const int opcodeGetPcJumping = 0x2000233; From a355550cabcf9383f35626d1c9f5f4b10c55a4b1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Dec 2014 21:43:31 +0100 Subject: [PATCH 116/167] Add support for NPCs with missing head/hair models (Fixes #2078) --- apps/openmw/mwrender/npcanimation.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index f2bc3df95f..4b5ddcbec3 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -56,8 +56,13 @@ std::string getVampireHead(const std::string& race, bool female) } } - assert(sVampireMapping[thisCombination]); - return "meshes\\" + sVampireMapping[thisCombination]->mModel; + if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) + sVampireMapping[thisCombination] = NULL; + + const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination]; + if (!bodyPart) + return std::string(); + return "meshes\\" + bodyPart->mModel; } bool isSkinned (NifOgre::ObjectScenePtr scene) @@ -256,10 +261,15 @@ void NpcAnimation::updateNpcBase() { if (isVampire) mHeadModel = getVampireHead(mNpc->mRace, mNpc->mFlags & ESM::NPC::Female); - else + else if (!mNpc->mHead.empty()) mHeadModel = "meshes\\" + store.get().find(mNpc->mHead)->mModel; + else + mHeadModel = ""; - mHairModel = "meshes\\" + store.get().find(mNpc->mHair)->mModel; + if (!mNpc->mHair.empty()) + mHairModel = "meshes\\" + store.get().find(mNpc->mHair)->mModel; + else + mHairModel = ""; } bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; @@ -399,9 +409,9 @@ void NpcAnimation::updateParts() if(mViewMode != VM_FirstPerson) { - if(mPartPriorities[ESM::PRT_Head] < 1) + if(mPartPriorities[ESM::PRT_Head] < 1 && !mHeadModel.empty()) addOrReplaceIndividualPart(ESM::PRT_Head, -1,1, mHeadModel); - if(mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1) + if(mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1 && !mHairModel.empty()) addOrReplaceIndividualPart(ESM::PRT_Hair, -1,1, mHairModel); } if(mViewMode == VM_HeadOnly) From 5f00a3d5c3f0b934f21c4742ec8a7b6d4dec8cd3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Dec 2014 22:00:31 +0100 Subject: [PATCH 117/167] Reset lastHitObject when it is retrieved rather than every frame This seems to be how vanilla MW does it. --- apps/openmw/mwmechanics/actors.cpp | 9 --------- apps/openmw/mwscript/miscextensions.cpp | 2 ++ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index ae430e5c6a..899ab95088 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1122,15 +1122,6 @@ namespace MWMechanics // target lists get updated once every 1.0 sec if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0; - // Reset data from previous frame - for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) - { - // Reset last hit object, which is only valid for one frame - // Note, the new hit object for this frame may be set by CharacterController::update -> Animation::runAnimation - // (below) - iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string()); - } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int hostilesCount = 0; // need to know this to play Battle music diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 186aa708db..1d07c95915 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -732,6 +732,8 @@ namespace MWScript MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitObject())); + + stats.setLastHitObject(std::string()); } }; From 886903d70e62334cfeb5de1939ee457017462ca9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Dec 2014 22:25:41 +0100 Subject: [PATCH 118/167] Implement HitAttemptOnMe function (Bug #2078) --- apps/openmw/mwclass/creature.cpp | 5 +++-- apps/openmw/mwclass/npc.cpp | 5 +++-- apps/openmw/mwmechanics/creaturestats.cpp | 12 ++++++++++++ apps/openmw/mwmechanics/creaturestats.hpp | 3 +++ apps/openmw/mwscript/docs/vmformat.txt | 4 +++- apps/openmw/mwscript/miscextensions.cpp | 21 +++++++++++++++++++++ components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 2 ++ components/esm/creaturestats.cpp | 5 +++++ components/esm/creaturestats.hpp | 1 + 10 files changed, 54 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 5910c471b3..8076e0619c 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -346,10 +346,11 @@ namespace MWClass setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } + if(!object.isEmpty()) + getCreatureStats(ptr).setLastHitAttemptObject(object.getClass().getId(object)); + if(!successful) { - // TODO: Handle HitAttemptOnMe script function - // Missed MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f); return; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 49f76d25c6..6806558833 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -654,10 +654,11 @@ namespace MWClass setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } + if(!object.isEmpty()) + getCreatureStats(ptr).setLastHitAttemptObject(object.getClass().getId(object)); + if(!successful) { - // TODO: Handle HitAttemptOnMe script function - // Missed sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f); return; diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 21fd2203fd..72a710c656 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -359,6 +359,16 @@ namespace MWMechanics return mLastHitObject; } + void CreatureStats::setLastHitAttemptObject(const std::string& objectid) + { + mLastHitAttemptObject = objectid; + } + + const std::string &CreatureStats::getLastHitAttemptObject() const + { + return mLastHitAttemptObject; + } + void CreatureStats::addToFallHeight(float height) { mFallHeight += height; @@ -510,6 +520,7 @@ namespace MWMechanics state.mAttackStrength = mAttackStrength; state.mFallHeight = mFallHeight; // TODO: vertical velocity (move from PhysicActor to CreatureStats?) state.mLastHitObject = mLastHitObject; + state.mLastHitAttemptObject = mLastHitAttemptObject; state.mRecalcDynamicStats = mRecalcMagicka; state.mDrawState = mDrawState; state.mLevel = mLevel; @@ -558,6 +569,7 @@ namespace MWMechanics mAttackStrength = state.mAttackStrength; mFallHeight = state.mFallHeight; mLastHitObject = state.mLastHitObject; + mLastHitAttemptObject = state.mLastHitAttemptObject; mRecalcMagicka = state.mRecalcDynamicStats; mDrawState = DrawState_(state.mDrawState); mLevel = state.mLevel; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index f830dd310c..d13ced3b3a 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -52,6 +52,7 @@ namespace MWMechanics float mFallHeight; std::string mLastHitObject; // The last object to hit this actor + std::string mLastHitAttemptObject; // The last object to attempt to hit this actor bool mRecalcMagicka; @@ -241,7 +242,9 @@ namespace MWMechanics bool getStance (Stance flag) const; void setLastHitObject(const std::string &objectid); + void setLastHitAttemptObject(const std::string &objectid); const std::string &getLastHitObject() const; + const std::string &getLastHitAttemptObject() const; // Note, this is just a cache to avoid checking the whole container store every frame. We don't need to store it in saves. // TODO: Put it somewhere else? diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index c90f63f7fa..800c6e2c7b 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -436,5 +436,7 @@ op 0x20002f5: ToggleWorld op 0x20002f6: PCForce1stPerson op 0x20002f7: PCForce3rdPerson op 0x20002f8: PCGet3rdPerson +op 0x20002f9: HitAttemptOnMe +op 0x20002fa: HitAttemptOnMe, explicit -opcodes 0x20002f9-0x3ffffff unused +opcodes 0x20002fb-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 1d07c95915..c92acff820 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -737,6 +737,25 @@ namespace MWScript } }; + template + class OpHitAttemptOnMe : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string objectID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); + runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitAttemptObject())); + + stats.setLastHitAttemptObject(std::string()); + } + }; + template class OpEnableTeleporting : public Interpreter::Opcode0 { @@ -1085,6 +1104,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeGetWindSpeed, new OpGetWindSpeed); interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMe, new OpHitOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMeExplicit, new OpHitOnMe); + interpreter.installSegment5 (Compiler::Misc::opcodeHitAttemptOnMe, new OpHitAttemptOnMe); + interpreter.installSegment5 (Compiler::Misc::opcodeHitAttemptOnMeExplicit, new OpHitAttemptOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeDisableTeleporting, new OpEnableTeleporting); interpreter.installSegment5 (Compiler::Misc::opcodeEnableTeleporting, new OpEnableTeleporting); interpreter.installSegment5 (Compiler::Misc::opcodeShowVars, new OpShowVars); diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index cd5bdbe695..1e2aebd991 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -295,6 +295,7 @@ namespace Compiler extensions.registerInstruction ("hurtcollidingactor", "f", opcodeHurtCollidingActor, opcodeHurtCollidingActorExplicit); extensions.registerFunction ("getwindspeed", 'f', "", opcodeGetWindSpeed); extensions.registerFunction ("hitonme", 'l', "S", opcodeHitOnMe, opcodeHitOnMeExplicit); + extensions.registerFunction ("hitattemptonme", 'l', "S", opcodeHitAttemptOnMe, opcodeHitAttemptOnMeExplicit); extensions.registerInstruction ("disableteleporting", "", opcodeDisableTeleporting); extensions.registerInstruction ("enableteleporting", "", opcodeEnableTeleporting); extensions.registerInstruction ("showvars", "", opcodeShowVars, opcodeShowVarsExplicit); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 65efc14fa4..da79555e22 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -269,6 +269,8 @@ namespace Compiler const int opcodePayFineThief = 0x2000237; const int opcodeHitOnMe = 0x2000213; const int opcodeHitOnMeExplicit = 0x2000214; + const int opcodeHitAttemptOnMe = 0x20002f9; + const int opcodeHitAttemptOnMeExplicit = 0x20002fa; const int opcodeDisableTeleporting = 0x2000215; const int opcodeEnableTeleporting = 0x2000216; const int opcodeShowVars = 0x200021d; diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 21803e02f9..cc76ef0df7 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -68,6 +68,8 @@ void ESM::CreatureStats::load (ESMReader &esm) mLastHitObject = esm.getHNOString ("LHIT"); + mLastHitAttemptObject = esm.getHNOString ("LHAT"); + mRecalcDynamicStats = false; esm.getHNOT (mRecalcDynamicStats, "CALC"); @@ -179,6 +181,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (!mLastHitObject.empty()) esm.writeHNString ("LHIT", mLastHitObject); + if (!mLastHitAttemptObject.empty()) + esm.writeHNString ("LHAT", mLastHitAttemptObject); + if (mRecalcDynamicStats) esm.writeHNT ("CALC", mRecalcDynamicStats); diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 8f4d4df7b8..7946d0e45b 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -56,6 +56,7 @@ namespace ESM float mAttackStrength; float mFallHeight; std::string mLastHitObject; + std::string mLastHitAttemptObject; bool mRecalcDynamicStats; int mDrawState; unsigned char mDeathAnimation; From 7892ed35f32c4c322043f706be801c537201c34e Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Dec 2014 22:25:53 +0100 Subject: [PATCH 119/167] PlaceItem, PlaceItemCell: Make sure references are placed above terrain (Bug #2078) --- apps/openmw/mwscript/transformationextensions.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 8e6d925b7c..80b13d6bb3 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -433,7 +433,8 @@ namespace MWScript pos.rot[2] = zRot; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + placed.getClass().adjustPosition(placed, true); } else { @@ -480,7 +481,8 @@ namespace MWScript pos.rot[2] = zRot; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + placed.getClass().adjustPosition(placed, true); } }; From f42420bc199322ebcc6f7ff3ba3ce06fac96ebbe Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Dec 2014 01:24:35 +0100 Subject: [PATCH 120/167] Use the Original Creature field for SoundGen lookups --- apps/openmw/mwclass/creature.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 8076e0619c..d3c216c2b2 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -675,13 +675,12 @@ namespace MWClass std::vector sounds; sounds.reserve(8); - std::string ptrid = Creature::getId(ptr); + MWWorld::LiveCellRef* ref = ptr.get(); + MWWorld::Store::iterator sound = store.begin(); while(sound != store.end()) { - if(type == sound->mType && !sound->mCreature.empty() && - Misc::StringUtils::ciEqual(ptrid.substr(0, sound->mCreature.size()), - sound->mCreature)) + if (type == sound->mType && !sound->mCreature.empty() && Misc::StringUtils::ciEqual(ref->mBase->mOriginal, sound->mCreature)) sounds.push_back(&*sound); ++sound; } From cf5fc60e861d946fe2283bd7ac3369952381bf5c Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Dec 2014 01:42:13 +0100 Subject: [PATCH 121/167] Make ToggleMenus affect tooltips (Fixes #1989) --- apps/openmw/mwgui/windowmanagerimp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 48f28d300c..99a43fbffd 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -444,6 +444,7 @@ namespace MWGui mVideoBackground->setVisible(false); mHud->setVisible(mHudEnabled && mGuiEnabled); + mToolTips->setVisible(mGuiEnabled); bool gameMode = !isGuiMode(); From e69cf110292c5a71525cebc446e97a8c2f5d58c8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Dec 2014 02:05:26 +0100 Subject: [PATCH 122/167] Hide tooltips during loading screens --- apps/openmw/mwgui/windowmanagerimp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 99a43fbffd..cbcef3bbd8 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -589,6 +589,7 @@ namespace MWGui break; case GM_LoadingWallpaper: mHud->setVisible(false); + mToolTips->setVisible(false); setCursorVisible(false); break; case GM_Loading: @@ -597,7 +598,7 @@ namespace MWGui mStatsWindow->setVisible(mStatsWindow->pinned() && !(mForceHidden & GW_Stats)); mInventoryWindow->setVisible(mInventoryWindow->pinned() && !(mForceHidden & GW_Inventory)); mSpellWindow->setVisible(mSpellWindow->pinned() && !(mForceHidden & GW_Magic)); - + mToolTips->setVisible(false); setCursorVisible(false); break; default: From 03da21f088da16c322f5046354141082a6d5dfc0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Dec 2014 02:12:49 +0100 Subject: [PATCH 123/167] Remove redundant GUI element showing during loading screens --- apps/openmw/mwgui/windowmanagerimp.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index cbcef3bbd8..acb8b2eb75 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -588,16 +588,10 @@ namespace MWGui mJournal->setVisible(true); break; case GM_LoadingWallpaper: - mHud->setVisible(false); - mToolTips->setVisible(false); - setCursorVisible(false); - break; case GM_Loading: - // Show the pinned windows - mMap->setVisible(mMap->pinned() && !(mForceHidden & GW_Map)); - mStatsWindow->setVisible(mStatsWindow->pinned() && !(mForceHidden & GW_Stats)); - mInventoryWindow->setVisible(mInventoryWindow->pinned() && !(mForceHidden & GW_Inventory)); - mSpellWindow->setVisible(mSpellWindow->pinned() && !(mForceHidden & GW_Magic)); + // Don't need to show anything here - GM_LoadingWallpaper covers everything else anyway, + // GM_Loading uses a texture of the last rendered frame so everything previously visible will be rendered. + mHud->setVisible(false); mToolTips->setVisible(false); setCursorVisible(false); break; From bc85bb32c21cf4a1e932991eebe7e0f3d2dbd0eb Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Dec 2014 02:39:59 +0100 Subject: [PATCH 124/167] Fix vampirism magic effect not applying immediately (Fixes #1984) --- apps/openmw/mwmechanics/character.cpp | 8 +++++--- apps/openmw/mwmechanics/character.hpp | 2 +- apps/openmw/mwrender/animation.hpp | 1 + apps/openmw/mwrender/npcanimation.cpp | 10 ++++++++++ apps/openmw/mwrender/npcanimation.hpp | 2 ++ 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 87d9a5b679..4d74133aa3 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1279,7 +1279,7 @@ void CharacterController::update(float duration) const MWWorld::Class &cls = mPtr.getClass(); Ogre::Vector3 movement(0.0f); - updateVisibility(); + updateMagicEffects(); if(!cls.isActor()) { @@ -1777,7 +1777,7 @@ void CharacterController::updateContinuousVfx() } } -void CharacterController::updateVisibility() +void CharacterController::updateMagicEffects() { if (!mPtr.getClass().isActor()) return; @@ -1794,9 +1794,11 @@ void CharacterController::updateVisibility() { alpha *= std::max(0.2f, (100.f - chameleon)/100.f); } - mAnimation->setAlpha(alpha); + bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f; + mAnimation->setVampire(vampire); + float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude(); mAnimation->setLightEffect(light); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 2a14c53b91..3409efedfd 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -190,7 +190,7 @@ class CharacterController void castSpell(const std::string& spellid); - void updateVisibility(); + void updateMagicEffects(); void playDeath(float startpoint, CharacterState death); void playRandomDeath(float startpoint = 0.0f); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index a8a9ee11e6..d25d4f0b0e 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -228,6 +228,7 @@ public: virtual void preRender (Ogre::Camera* camera); virtual void setAlpha(float alpha) {} + virtual void setVampire(bool vampire) {} public: void updatePtr(const MWWorld::Ptr &ptr); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 4b5ddcbec3..b93b37aeac 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -983,4 +983,14 @@ void NpcAnimation::equipmentChanged() updateParts(); } +void NpcAnimation::setVampire(bool vampire) +{ + if (mNpcType == Type_Werewolf) // we can't have werewolf vampires, can we + return; + if ((mNpcType == Type_Vampire) != vampire) + { + rebuild(); + } +} + } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index ee62fce9cd..6631c8f412 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -168,6 +168,8 @@ public: /// Make the NPC only partially visible virtual void setAlpha(float alpha); + virtual void setVampire(bool vampire); + /// Prepare this animation for being rendered with \a camera (rotates billboard nodes) virtual void preRender (Ogre::Camera* camera); }; From 018f4e6895177c8290d52ca3b49c0319a4ecdfd2 Mon Sep 17 00:00:00 2001 From: Arthur Moore Date: Sun, 30 Nov 2014 18:59:05 -0500 Subject: [PATCH 125/167] Fail early if trying to read a string larger than the nif file size. This is much better than failing after a few minutes with an out of memory error. --- components/nif/nifstream.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index a6fd5ef5ae..1b5f715bc7 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -83,6 +83,11 @@ Transformation NIFStream::getTrafo() std::string NIFStream::getString(size_t length) { + //Make sure we're not reading in too large of a string + unsigned int fileSize = inp->size(); + if(fileSize != 0 && fileSize < length) + file->fail("Attempted to read a string with " + Ogre::StringConverter::toString(length) + "characters , but file is only "+Ogre::StringConverter::toString(fileSize)+ "bytes!"); + std::vector str (length+1, 0); if(inp->read(&str[0], length) != length) From cd835152e109dfc5bcbd41812c6ce80f11ba6c4e Mon Sep 17 00:00:00 2001 From: Arthur Moore Date: Fri, 12 Dec 2014 01:36:10 -0500 Subject: [PATCH 126/167] Fix spacing issue for NIF file errors. --- components/nif/niffile.hpp | 4 ++-- components/nif/nifstream.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 2ef2a6fda2..ceb9984fb8 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -46,14 +46,14 @@ public: /// Used if file parsing fails void fail(const std::string &msg) { - std::string err = "NIFFile Error: " + msg; + std::string err = " NIFFile Error: " + msg; err += "\nFile: " + filename; throw std::runtime_error(err); } /// Used when something goes wrong, but not catastrophically so void warn(const std::string &msg) { - std::cerr << "NIFFile Warning: " << msg <size(); if(fileSize != 0 && fileSize < length) - file->fail("Attempted to read a string with " + Ogre::StringConverter::toString(length) + "characters , but file is only "+Ogre::StringConverter::toString(fileSize)+ "bytes!"); + file->fail("Attempted to read a string with " + Ogre::StringConverter::toString(length) + " characters , but file is only "+Ogre::StringConverter::toString(fileSize)+ " bytes!"); std::vector str (length+1, 0); From b8edd9bac33ad0f0c8f0701ebc8b020e5368ab51 Mon Sep 17 00:00:00 2001 From: Arthur Moore Date: Fri, 12 Dec 2014 01:48:57 -0500 Subject: [PATCH 127/167] Get a nif file's version string regardless of its length. --- components/nif/niffile.cpp | 4 ++-- components/nif/nifstream.cpp | 4 ++++ components/nif/nifstream.hpp | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index c689e27b3a..9a544f3c4e 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -131,9 +131,9 @@ void NIFFile::parse() NIFStream nif (this, Ogre::ResourceGroupManager::getSingleton().openResource(filename)); // Check the header string - std::string head = nif.getString(40); + std::string head = nif.getVersionString(); if(head.compare(0, 22, "NetImmerse File Format") != 0) - fail("Invalid NIF header"); + fail("Invalid NIF header: " + head); // Get BCD version ver = nif.getUInt(); diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 878b6b75f7..e5699db7b9 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -101,6 +101,10 @@ std::string NIFStream::getString() size_t size = read_le32(); return getString(size); } +std::string NIFStream::getVersionString() +{ + return inp->getLine(); +} void NIFStream::getShorts(std::vector &vec, size_t size) { diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 3d6a1319a8..cc14971fd5 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -81,6 +81,8 @@ public: std::string getString(size_t length); ///Read in a string of the length specified in the file std::string getString(); + ///This is special since the version string doesn't start with a number, and ends with "\n" + std::string getVersionString(); void getShorts(std::vector &vec, size_t size); void getFloats(std::vector &vec, size_t size); From 74e341b2bcc3ec516d5ab14e715fc928a057c995 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 12 Dec 2014 10:09:09 +0100 Subject: [PATCH 128/167] updated changelog again --- readme.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.txt b/readme.txt index 83f139a1d8..f5e3e0e59b 100644 --- a/readme.txt +++ b/readme.txt @@ -101,11 +101,13 @@ CHANGELOG 0.34.0 Bug #904: omwlauncher doesn't allow installing Tribunal and Bloodmoon if only MW is installed +Bug #986: Launcher: renaming profile names is broken Bug #1061: "Browse to CD..." launcher crash Bug #1135: Launcher crashes if user does not have write permission Bug #1231: Current installer in launcher does not correctly import russian Morrowind.ini settings from setup.inx Bug #1288: Fix the Alignment of the Resolution Combobox Bug #1343: BIK videos occasionally out of sync with audio +Bug #1684: Morrowind Grass Mod graphical glitches Bug #1734: NPC in fight with invisible/sneaking player Bug #1982: Long class names are cut off in the UI Bug #2012: Editor: OpenCS script compiler sometimes fails to find IDs From d034a079e60b8014802f92522b0dab46be47f3a9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Dec 2014 16:49:22 +0100 Subject: [PATCH 129/167] Allow equipping twohanded weapon and shield at the same time (Fixes #1785) The shield can be equipped, meaning armor rating and item enchantments apply, but can not be blocked with. --- apps/openmw/mwbase/mechanicsmanager.hpp | 2 ++ apps/openmw/mwmechanics/actors.cpp | 9 ++++++ apps/openmw/mwmechanics/actors.hpp | 2 ++ apps/openmw/mwmechanics/character.cpp | 32 ++++++++++++++++--- apps/openmw/mwmechanics/character.hpp | 4 +++ apps/openmw/mwmechanics/combat.cpp | 9 +----- .../mwmechanics/mechanicsmanagerimp.cpp | 5 +++ .../mwmechanics/mechanicsmanagerimp.hpp | 2 ++ apps/openmw/mwrender/characterpreview.cpp | 7 +++- apps/openmw/mwrender/npcanimation.hpp | 2 +- apps/openmw/mwworld/actionequip.cpp | 6 +--- apps/openmw/mwworld/inventorystore.cpp | 6 +--- 12 files changed, 61 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index b7af1cbf72..c92459183d 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -205,6 +205,8 @@ namespace MWBase /// Resurrects the player if necessary virtual void keepPlayerAlive() = 0; + + virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 899ab95088..a64b6c57d4 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1554,4 +1554,13 @@ namespace MWMechanics if (ptr.getClass().isNpc()) calculateNpcStatModifiers(ptr, 0.f); } + + bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const + { + PtrControllerMap::const_iterator it = mActors.find(ptr); + if (it == mActors.end()) + return false; + + return it->second->isReadyToBlock(); + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index a24095c702..a30a9dcf01 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -125,6 +125,8 @@ namespace MWMechanics void clear(); // Clear death counter + bool isReadyToBlock(const MWWorld::Ptr& ptr) const; + private: PtrControllerMap mActors; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 4d74133aa3..06450ddb3c 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -648,7 +648,8 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim mAnimation->showWeapons(true); mAnimation->setWeaponGroup(mCurrentWeapon); } - mAnimation->showCarriedLeft(mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand); + + mAnimation->showCarriedLeft(updateCarriedLeftVisible(mWeaponType)); } if(!cls.getCreatureStats(mPtr).isDead()) @@ -836,6 +837,25 @@ bool CharacterController::updateCreatureState() return false; } +bool CharacterController::updateCarriedLeftVisible(WeaponType weaptype) const +{ + // Shields/torches shouldn't be visible during any operation involving two hands + // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", + // but they are also present in weapon drawing animation. + switch (weaptype) + { + case WeapType_Spell: + case WeapType_BowAndArrow: + case WeapType_Crossbow: + case WeapType_HandToHand: + case WeapType_TwoHand: + case WeapType_TwoWide: + return false; + default: + return true; + } +} + bool CharacterController::updateWeaponState() { const MWWorld::Class &cls = mPtr.getClass(); @@ -850,10 +870,7 @@ bool CharacterController::updateWeaponState() { forcestateupdate = true; - // Shields/torches shouldn't be visible during spellcasting or hand-to-hand - // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", - // but they are also present in weapon drawing animation. - mAnimation->showCarriedLeft(weaptype != WeapType_Spell && weaptype != WeapType_HandToHand); + mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); std::string weapgroup; if(weaptype == WeapType_None) @@ -1818,4 +1835,9 @@ void CharacterController::determineAttackType() } } +bool CharacterController::isReadyToBlock() const +{ + return updateCarriedLeftVisible(mWeaponType); +} + } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 3409efedfd..075db37beb 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -199,6 +199,8 @@ class CharacterController /// @param num if non-NULL, the chosen animation number will be written here std::string chooseRandomGroup (const std::string& prefix, int* num = NULL); + bool updateCarriedLeftVisible(WeaponType weaptype) const; + public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); @@ -224,6 +226,8 @@ public: void forceStateUpdate(); AiState& getAiState() { return mAiState; } + + bool isReadyToBlock() const; }; void getWeaponGroup(WeaponType weaptype, std::string &group); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 9225a57999..4d484469c2 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -62,17 +62,10 @@ namespace MWMechanics || blockerStats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) return false; - // Don't block when in spellcasting state (shield is equipped, but not visible) - if (blockerStats.getDrawState() == DrawState_Spell) + if (!MWBase::Environment::get().getMechanicsManager()->isReadyToBlock(blocker)) return false; MWWorld::InventoryStore& inv = blocker.getClass().getInventoryStore(blocker); - - // Don't block when in hand-to-hand combat (shield is equipped, but not visible) - if (blockerStats.getDrawState() == DrawState_Weapon && - inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight) == inv.end()) - return false; - MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name()) return false; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 1dbe6f950e..cd9f0d0f76 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1352,4 +1352,9 @@ namespace MWMechanics stats.resurrect(); } } + + bool MechanicsManager::isReadyToBlock(const MWWorld::Ptr &ptr) const + { + return mActors.isReadyToBlock(ptr); + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 1eec26c8ac..489da75417 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -169,6 +169,8 @@ namespace MWMechanics virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0, bool ignoreDistance=false); virtual void keepPlayerAlive(); + + virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 92d0bcd557..66052a96ec 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -195,6 +195,7 @@ namespace MWRender MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter); MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); std::string groupname; + bool showCarriedLeft = true; if(iter == inv.end()) groupname = "inventoryhandtohand"; else @@ -224,11 +225,15 @@ namespace MWRender groupname = "inventoryweapontwowide"; else groupname = "inventoryhandtohand"; - } + + showCarriedLeft = (iter->getClass().canBeEquipped(*iter, mCharacter).first != 2); + } else groupname = "inventoryhandtohand"; } + mAnimation->showCarriedLeft(showCarriedLeft); + mCurrentAnimGroup = groupname; mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 6631c8f412..aba01bcfaa 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -143,7 +143,7 @@ public: virtual void setPitchFactor(float factor) { mPitchFactor = factor; } virtual void showWeapons(bool showWeapon); - virtual void showCarriedLeft(bool showa); + virtual void showCarriedLeft(bool show); virtual void attachArrow(); virtual void releaseArrow(); diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 50da1e5e5d..87a4c63084 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -31,11 +31,7 @@ namespace MWWorld { case 0: return; - case 2: - invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, actor); - break; - case 3: - invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor); + default: break; } diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 9032b04e11..c577d4b0d2 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -255,11 +255,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { case 0: continue; - case 2: - slots_[MWWorld::InventoryStore::Slot_CarriedLeft] = end(); - break; - case 3: - // Prefer keeping twohanded weapon + default: break; } From 60aa20914411c96ea13d8949dda041a269d70036 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Dec 2014 17:39:00 +0100 Subject: [PATCH 130/167] Implement drowning when knocked out underwater (Fixes #1228) --- apps/openmw/mwmechanics/actors.cpp | 14 ++++++++++---- apps/openmw/mwmechanics/character.cpp | 5 +++++ apps/openmw/mwmechanics/character.hpp | 1 + 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a64b6c57d4..0730a4660f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -869,13 +869,19 @@ namespace MWMechanics void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration) { - MWBase::World *world = MWBase::Environment::get().getWorld(); + PtrControllerMap::iterator it = mActors.find(ptr); + if (it == mActors.end()) + return; + CharacterController* ctrl = it->second; + NpcStats &stats = ptr.getClass().getNpcStats(ptr); - if(world->isSubmerged(ptr) && - stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).getMagnitude() == 0) + MWBase::World *world = MWBase::Environment::get().getWorld(); + bool knockedOutUnderwater = (ctrl->isKnockedOut() && world->isUnderwater(ptr.getCell(), Ogre::Vector3(ptr.getRefData().getPosition().pos))); + if((world->isSubmerged(ptr) || knockedOutUnderwater) + && stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).getMagnitude() == 0) { float timeLeft = 0.0f; - if(stats.getFatigue().getCurrent() == 0) + if(knockedOutUnderwater) stats.setTimeToStartDrowning(0); else { diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 06450ddb3c..d8693fbcdf 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1840,4 +1840,9 @@ bool CharacterController::isReadyToBlock() const return updateCarriedLeftVisible(mWeaponType); } +bool CharacterController::isKnockedOut() const +{ + return mHitState == CharState_KnockOut; +} + } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 075db37beb..d7028a8441 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -228,6 +228,7 @@ public: AiState& getAiState() { return mAiState; } bool isReadyToBlock() const; + bool isKnockedOut() const; }; void getWeaponGroup(WeaponType weaptype, std::string &group); From ed6face4aa8cf6f1ae4ffc5aa18f0932199ff373 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Dec 2014 22:21:57 +0100 Subject: [PATCH 131/167] Disable activation scripts for actors in combat --- apps/openmw/engine.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index d7b23c0966..15d63eb4b4 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -505,6 +505,9 @@ void OMW::Engine::activate() if (ptr.getClass().getName(ptr) == "") // objects without name presented to user can never be activated return; + if (ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat()) + return; + MWBase::Environment::get().getWorld()->activate(ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); } From 2ebf328dec5b7748a0dd92478c851fb85803cbf9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 13 Dec 2014 00:39:24 +0100 Subject: [PATCH 132/167] Always print the failing dialogue script These aren't usually very long, so printing them shouldn't spam the console by too much. --- 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 eff54fbc01..4aab5003fd 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -227,7 +227,7 @@ namespace MWDialogue success = false; } - if (!success && mScriptVerbose) + if (!success) { std::cerr << "compiling failed (dialogue script)" << std::endl From 0ca11eab1c50dfa7a85c4aad46d213a10e0cc739 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 13 Dec 2014 02:39:56 +0100 Subject: [PATCH 133/167] Ignore extra argument for removeItem (Fixes #2208) --- components/compiler/extensions0.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 1e2aebd991..690e589f73 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -116,7 +116,7 @@ namespace Compiler extensions.registerInstruction ("additem", "clX", opcodeAddItem, opcodeAddItemExplicit); extensions.registerFunction ("getitemcount", 'l', "c", opcodeGetItemCount, opcodeGetItemCountExplicit); - extensions.registerInstruction ("removeitem", "cl", opcodeRemoveItem, + extensions.registerInstruction ("removeitem", "clX", opcodeRemoveItem, opcodeRemoveItemExplicit); extensions.registerInstruction ("equip", "cX", opcodeEquip, opcodeEquipExplicit); extensions.registerFunction ("getarmortype", 'l', "l", opcodeGetArmorType, opcodeGetArmorTypeExplicit); From ba65c6cc7f19ed480a6dfe847b81593fc64d11b3 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 13 Dec 2014 02:47:04 +0100 Subject: [PATCH 134/167] Add --script-all-dialogue switch to compile all dialogue scripts (Fixes #1659) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/engine.cpp | 18 ++- apps/openmw/engine.hpp | 4 + apps/openmw/main.cpp | 4 + apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 2 +- apps/openmw/mwdialogue/filter.cpp | 11 ++ apps/openmw/mwdialogue/filter.hpp | 6 +- apps/openmw/mwdialogue/scripttest.cpp | 124 ++++++++++++++++++ apps/openmw/mwdialogue/scripttest.hpp | 20 +++ apps/openmw/mwscript/compilercontext.hpp | 2 +- 10 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 apps/openmw/mwdialogue/scripttest.cpp create mode 100644 apps/openmw/mwdialogue/scripttest.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 803c743259..06a142f0a1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -44,7 +44,7 @@ add_openmw_dir (mwgui ) add_openmw_dir (mwdialogue - dialoguemanagerimp journalimp journalentry quest topic filter selectwrapper hypertextparser keywordsearch + dialoguemanagerimp journalimp journalentry quest topic filter selectwrapper hypertextparser keywordsearch scripttest ) add_openmw_dir (mwscript diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index d7b23c0966..1b350d7522 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -40,6 +40,7 @@ #include "mwdialogue/dialoguemanagerimp.hpp" #include "mwdialogue/journalimp.hpp" +#include "mwdialogue/scripttest.hpp" #include "mwmechanics/mechanicsmanagerimp.hpp" @@ -174,6 +175,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) + , mCompileAllDialogue (false) , mWarningsMode (1) , mScriptContext (0) , mFSStrict (false) @@ -425,7 +427,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) if (mCompileAll) { std::pair result = MWBase::Environment::get().getScriptManager()->compileAll(); - if (result.first) std::cout << "compiled " << result.second << " of " << result.first << " scripts (" @@ -433,6 +434,16 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) << "%)" << std::endl; } + if (mCompileAllDialogue) + { + std::pair result = MWDialogue::ScriptTest::compileAll(&mExtensions); + if (result.first) + std::cout + << "compiled " << result.second << " of " << result.first << " dialogue script/actor combinations a(" + << 100*static_cast (result.second)/result.first + << "%)" + << std::endl; + } } // Initialise and enter main loop. @@ -535,6 +546,11 @@ void OMW::Engine::setCompileAll (bool all) mCompileAll = all; } +void OMW::Engine::setCompileAllDialogue (bool all) +{ + mCompileAllDialogue = all; +} + void OMW::Engine::setSoundUsage(bool soundUsage) { mUseSound = soundUsage; diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 0ee5a09c5b..6cf31cba89 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -76,6 +76,7 @@ namespace OMW bool mSkipMenu; bool mUseSound; bool mCompileAll; + bool mCompileAllDialogue; int mWarningsMode; std::string mFocusName; std::map mFallbackMap; @@ -178,6 +179,9 @@ namespace OMW /// Compile all scripts (excludign dialogue scripts) at startup? void setCompileAll (bool all); + /// Compile all dialogue scripts at startup? + void setCompileAllDialogue (bool all); + /// Font encoding void setEncoding(const ToUTF8::FromType& encoding); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 744780b258..9382e2516b 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -130,6 +130,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("script-all", bpo::value()->implicit_value(true) ->default_value(false), "compile all scripts (excluding dialogue scripts) at startup") + ("script-all-dialogue", bpo::value()->implicit_value(true) + ->default_value(false), "compile all dialogue scripts at startup") + ("script-console", bpo::value()->implicit_value(true) ->default_value(false), "enable console-only script functionality") @@ -264,6 +267,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat // scripts engine.setCompileAll(variables["script-all"].as()); + engine.setCompileAllDialogue(variables["script-all-dialogue"].as()); engine.setScriptsVerbosity(variables["script-verbose"].as()); engine.setScriptConsoleMode (variables["script-console"].as()); engine.setStartupScript (variables["script-run"].as()); diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index eff54fbc01..196df00393 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -47,7 +47,7 @@ namespace MWDialogue { DialogueManager::DialogueManager (const Compiler::Extensions& extensions, bool scriptVerbose, Translation::Storage& translationDataStorage) : - mCompilerContext (MWScript::CompilerContext::Type_Dialgoue), + mCompilerContext (MWScript::CompilerContext::Type_Dialogue), mErrorStream(std::cout.rdbuf()),mErrorHandler(mErrorStream) , mTemporaryDispositionChange(0.f) , mPermanentDispositionChange(0.f), mScriptVerbose (scriptVerbose) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 629d99cc2a..af8a5754ff 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -603,6 +603,17 @@ const ESM::DialInfo* MWDialogue::Filter::search (const ESM::Dialogue& dialogue, return suitableInfos[0]; } +std::vector MWDialogue::Filter::listAll (const ESM::Dialogue& dialogue) const +{ + std::vector infos; + for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); iter!=dialogue.mInfo.end(); ++iter) + { + if (testActor (*iter)) + infos.push_back(&*iter); + } + return infos; +} + std::vector MWDialogue::Filter::list (const ESM::Dialogue& dialogue, bool fallbackToInfoRefusal, bool searchAll, bool invertDisposition) const { diff --git a/apps/openmw/mwdialogue/filter.hpp b/apps/openmw/mwdialogue/filter.hpp index 7e7f2b6f54..d41b7aa1ef 100644 --- a/apps/openmw/mwdialogue/filter.hpp +++ b/apps/openmw/mwdialogue/filter.hpp @@ -55,7 +55,11 @@ namespace MWDialogue std::vector list (const ESM::Dialogue& dialogue, bool fallbackToInfoRefusal, bool searchAll, bool invertDisposition=false) const; - ///< \note If fallbackToInfoRefusal is used, the returned DialInfo might not be from the supplied ESM::Dialogue. + ///< List all infos that could be used on the given actor, using the current runtime state of the actor. + /// \note If fallbackToInfoRefusal is used, the returned DialInfo might not be from the supplied ESM::Dialogue. + + std::vector listAll (const ESM::Dialogue& dialogue) const; + ///< List all infos that could possibly be used on the given actor, ignoring runtime state filters and ignoring player filters. const ESM::DialInfo* search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const; ///< Get a matching response for the requested dialogue. diff --git a/apps/openmw/mwdialogue/scripttest.cpp b/apps/openmw/mwdialogue/scripttest.cpp new file mode 100644 index 0000000000..a8de21ef97 --- /dev/null +++ b/apps/openmw/mwdialogue/scripttest.cpp @@ -0,0 +1,124 @@ +#include "scripttest.hpp" + +#include "../mwworld/manualref.hpp" +#include "../mwworld/class.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/scriptmanager.hpp" + +#include "../mwscript/compilercontext.hpp" + +#include +#include +#include +#include +#include +#include + +#include "filter.hpp" + +namespace +{ + +void test(const MWWorld::Ptr& actor, int &compiled, int &total, const Compiler::Extensions* extensions) +{ + MWDialogue::Filter filter(actor, 0, false); + + MWScript::CompilerContext compilerContext(MWScript::CompilerContext::Type_Dialogue); + compilerContext.setExtensions(extensions); + std::ostream errorStream(std::cout.rdbuf()); + Compiler::StreamErrorHandler errorHandler(errorStream); + + const MWWorld::Store& dialogues = MWBase::Environment::get().getWorld()->getStore().get(); + for (MWWorld::Store::iterator it = dialogues.begin(); it != dialogues.end(); ++it) + { + std::vector infos = filter.listAll(*it); + + for (std::vector::iterator it = infos.begin(); it != infos.end(); ++it) + { + const ESM::DialInfo* info = *it; + if (!info->mResultScript.empty()) + { + bool success = true; + ++total; + try + { + errorHandler.reset(); + + std::istringstream input (info->mResultScript + "\n"); + + Compiler::Scanner scanner (errorHandler, input, extensions); + + Compiler::Locals locals; + + std::string actorScript = actor.getClass().getScript(actor); + + if (!actorScript.empty()) + { + // grab local variables from actor's script, if available. + locals = MWBase::Environment::get().getScriptManager()->getLocals (actorScript); + } + + Compiler::ScriptParser parser(errorHandler, compilerContext, locals, false); + + scanner.scan (parser); + + if (!errorHandler.isGood()) + success = false; + + ++compiled; + } + catch (const Compiler::SourceException& /* error */) + { + // error has already been reported via error handler + success = false; + } + catch (const std::exception& error) + { + std::cerr << std::string ("Dialogue error: An exception has been thrown: ") + error.what() << std::endl; + success = false; + } + + if (!success) + { + std::cerr + << "compiling failed (dialogue script)" << std::endl + << info->mResultScript + << std::endl << std::endl; + } + } + } + } +} + +} + +namespace MWDialogue +{ + +namespace ScriptTest +{ + + std::pair compileAll(const Compiler::Extensions *extensions) + { + int compiled = 0, total = 0; + const MWWorld::Store& npcs = MWBase::Environment::get().getWorld()->getStore().get(); + for (MWWorld::Store::iterator it = npcs.begin(); it != npcs.end(); ++it) + { + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->mId); + test(ref.getPtr(), compiled, total, extensions); + } + + const MWWorld::Store& creatures = MWBase::Environment::get().getWorld()->getStore().get(); + for (MWWorld::Store::iterator it = creatures.begin(); it != creatures.end(); ++it) + { + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->mId); + test(ref.getPtr(), compiled, total, extensions); + } + return std::make_pair(total, compiled); + } + +} + +} diff --git a/apps/openmw/mwdialogue/scripttest.hpp b/apps/openmw/mwdialogue/scripttest.hpp new file mode 100644 index 0000000000..1ed94c76a5 --- /dev/null +++ b/apps/openmw/mwdialogue/scripttest.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_MWDIALOGUE_SCRIPTTEST_H +#define OPENMW_MWDIALOGUE_SCRIPTTEST_H + +#include + +namespace MWDialogue +{ + +namespace ScriptTest +{ + +/// Attempt to compile all dialogue scripts, use for verification purposes +/// @return A pair containing +std::pair compileAll(const Compiler::Extensions* extensions); + +} + +} + +#endif diff --git a/apps/openmw/mwscript/compilercontext.hpp b/apps/openmw/mwscript/compilercontext.hpp index 95719ab692..010926f451 100644 --- a/apps/openmw/mwscript/compilercontext.hpp +++ b/apps/openmw/mwscript/compilercontext.hpp @@ -12,7 +12,7 @@ namespace MWScript enum Type { Type_Full, // global, local, targetted - Type_Dialgoue, + Type_Dialogue, Type_Console }; From e4f75267d0475b2f7e2b19c698d9b70e85f58e4e Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 13 Dec 2014 15:40:24 +0100 Subject: [PATCH 135/167] in case of arguments not separated with comma the fist token of the next argument was put back incorrectly --- components/compiler/exprparser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 6dcca08df9..f7c3d3ecb5 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -660,7 +660,7 @@ namespace Compiler else { // no comma was used between arguments - scanner.putbackKeyword (code, loc); + scanner.putbackSpecial (code, loc); return false; } } From ed5387fb8c9e21aa115655e6d874b2461bc4f472 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 13 Dec 2014 15:43:40 +0100 Subject: [PATCH 136/167] replaced stay [ ignoring implementation with one that does not interfere with other workarounds (Fixes #2205) --- components/compiler/lineparser.cpp | 7 +++++++ components/compiler/scanner.cpp | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index dc19b9a4b0..37641b014b 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -489,6 +489,13 @@ namespace Compiler bool LineParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { + if (mState==EndState && code==Scanner::S_open) + { + getErrorHandler().warning ("stray '[' or '(' at the end of the line (ignoring it)", + loc); + return true; + } + if (code==Scanner::S_newline && (mState==EndState || mState==BeginState || mState==PotentialEndState)) return false; diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 203f27e6e8..59fae3ccdc 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -528,8 +528,7 @@ namespace Compiler bool Scanner::isWhitespace (char c) { - return c==' ' || c=='\t' - || c=='['; ///< \todo disable this when doing more strict compiling + return c==' ' || c=='\t'; } // constructor From 8f29f2667e4081dd15a4449a700b2dc33cec8747 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 13 Dec 2014 15:49:32 +0100 Subject: [PATCH 137/167] Fix rotation order for XYZ rotation keys (Fixes #1067) --- components/nifogre/ogrenifloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 1d1e1a22d0..cdbc4d8447 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -483,7 +483,7 @@ public: Ogre::Quaternion xr(Ogre::Radian(xrot), Ogre::Vector3::UNIT_X); Ogre::Quaternion yr(Ogre::Radian(yrot), Ogre::Vector3::UNIT_Y); Ogre::Quaternion zr(Ogre::Radian(zrot), Ogre::Vector3::UNIT_Z); - return (xr*yr*zr); + return (zr*yr*xr); } public: From 80c92789c23d899dc78ef4fef850d54763854889 Mon Sep 17 00:00:00 2001 From: Nik Dyonin Date: Sun, 14 Dec 2014 02:58:42 +0300 Subject: [PATCH 138/167] Fix issue when killed NPC cannot be looted if it was in combat mode before killing. --- apps/openmw/engine.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 885d2bb4f1..d8546a2490 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -516,8 +516,13 @@ void OMW::Engine::activate() if (ptr.getClass().getName(ptr) == "") // objects without name presented to user can never be activated return; - if (ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat()) - return; + if (ptr.getClass().isActor()) + { + MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); + + if (stats.getAiSequence().isInCombat() && !stats.isDead()) + return; + } MWBase::Environment::get().getWorld()->activate(ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); } From 88a2e4c04383dfe7c2c3652f6995456564b4f1cd Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Dec 2014 01:53:15 +0100 Subject: [PATCH 139/167] Graceful error handling for missing spells/factions (Fixes #1825, Bug #2176, Bug #2203) --- apps/openmw/mwclass/creature.cpp | 8 ++++++- apps/openmw/mwclass/npc.cpp | 34 +++++++++++++++++++++++------- apps/openmw/mwmechanics/spells.cpp | 16 ++++++++------ apps/openmw/mwmechanics/spells.hpp | 3 +++ 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index d3c216c2b2..25808c991a 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -119,7 +119,13 @@ namespace MWClass // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); iter!=ref->mBase->mSpells.mList.end(); ++iter) - data->mCreatureStats.getSpells().add (*iter); + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter); + if (spell) + data->mCreatureStats.getSpells().add (spell); + else /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility + std::cerr << "Warning: ignoring nonexistent spell '" << spell->mId << "' on creature '" << ref->mBase->mId << "'" << std::endl; + } // inventory if (ref->mBase->mFlags & ESM::Creature::Weapon) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 6806558833..3da7446918 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -300,15 +300,20 @@ namespace MWClass if (!ref->mBase->mFaction.empty()) { std::string faction = ref->mBase->mFaction; - Misc::StringUtils::toLower(faction); - if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + const ESM::Faction* fact = MWBase::Environment::get().getWorld()->getStore().get().search(faction); + if (fact) { - data->mNpcStats.setFactionRank(faction, (int)ref->mBase->mNpdt52.mRank); + if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + { + data->mNpcStats.setFactionRank(fact->mId, (int)ref->mBase->mNpdt52.mRank); + } + else + { + data->mNpcStats.setFactionRank(fact->mId, (int)ref->mBase->mNpdt12.mRank); + } } else - { - data->mNpcStats.setFactionRank(faction, (int)ref->mBase->mNpdt12.mRank); - } + std::cerr << "Warning: ignoring nonexistent faction '" << fact->mId << "' on NPC '" << ref->mBase->mId << "'" << std::endl; } // creature stats @@ -361,7 +366,11 @@ namespace MWClass for (std::vector::const_iterator iter (race->mPowers.mList.begin()); iter!=race->mPowers.mList.end(); ++iter) { - data->mNpcStats.getSpells().add (*iter); + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter); + if (spell) + data->mNpcStats.getSpells().add (spell); + else + std::cerr << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl; } if (data->mNpcStats.getFactionRanks().size()) @@ -385,7 +394,16 @@ namespace MWClass // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); iter!=ref->mBase->mSpells.mList.end(); ++iter) - data->mNpcStats.getSpells().add (*iter); + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter); + if (spell) + data->mNpcStats.getSpells().add (spell); + else + { + /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility + std::cerr << "Warning: ignoring nonexistent spell '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl; + } + } // inventory data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr), "", diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index a1b73bc47c..5953be523b 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -25,12 +25,10 @@ namespace MWMechanics return mSpells.end(); } - void Spells::add (const std::string& spellId) + void Spells::add (const ESM::Spell* spell) { - if (mSpells.find (spellId)==mSpells.end()) + if (mSpells.find (spell->mId)==mSpells.end()) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - std::map random; // Determine the random magnitudes (unless this is a castable spell, in which case @@ -50,13 +48,19 @@ namespace MWMechanics corprus.mWorsenings = 0; corprus.mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; - mCorprusSpells[spellId] = corprus; + mCorprusSpells[spell->mId] = corprus; } - mSpells.insert (std::make_pair (Misc::StringUtils::lowerCase(spellId), random)); + mSpells.insert (std::make_pair (spell->mId, random)); } } + void Spells::add (const std::string& spellId) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + add(spell); + } + void Spells::remove (const std::string& spellId) { std::string lower = Misc::StringUtils::lowerCase(spellId); diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index ab799ffe12..064b2c1e5d 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -79,6 +79,9 @@ namespace MWMechanics void add (const std::string& spell); ///< Adding a spell that is already listed in *this is a no-op. + void add (const ESM::Spell* spell); + ///< Adding a spell that is already listed in *this is a no-op. + void remove (const std::string& spell); ///< If the spell to be removed is the selected spell, the selected spell will be changed to /// no spell (empty string). From 97a4b30b59c232199a87c8b97dd00fb824fd4f20 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 14 Dec 2014 14:59:27 +0100 Subject: [PATCH 140/167] updated credits file --- credits.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/credits.txt b/credits.txt index 76efb2760a..c3eab721fe 100644 --- a/credits.txt +++ b/credits.txt @@ -71,6 +71,7 @@ Michael Papageorgiou (werdanith) Michał Bień (Glorf) Miroslav Puda (pakanek) MiroslavR +Narmo Nathan Jeffords (blunted2night) Nikolay Kasyanov (corristo) nobrakal From c5a604453ec71b3d2d366202ff02ec93f7ce304b Mon Sep 17 00:00:00 2001 From: MiroslavR Date: Sun, 14 Dec 2014 16:25:27 +0100 Subject: [PATCH 141/167] Fix several book formatting issues (Fixes #2204) --- apps/openmw/mwgui/formatting.cpp | 33 ++++++++++++++++++++++++++++---- apps/openmw/mwgui/formatting.hpp | 6 +++++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index c55650c453..5831160034 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -193,6 +193,9 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(parent->getChildAt(0)); } + mTextStyle = TextStyle(); + mBlockStyle = BlockStyle(); + MyGUI::Widget * paper = parent->createWidget("Widget", MyGUI::IntCoord(0, 0, pag.getPageWidth(), pag.getPageHeight()), MyGUI::Align::Left | MyGUI::Align::Top); paper->setNeedMouseFocus(false); @@ -207,8 +210,25 @@ namespace MWGui continue; std::string plainText = parser.getReadyText(); + + // for cases when linebreaks are used to cause a shift to the next page + // if the split text block ends in an empty line, proceeding text block(s) should have leading empty lines removed + if (pag.getIgnoreLeadingEmptyLines()) + { + while (!plainText.empty()) + { + if (plainText[0] == '\n') + plainText.erase(plainText.begin()); + else + { + pag.setIgnoreLeadingEmptyLines(false); + break; + } + } + } + if (plainText.empty()) - brBeforeLastTag = false; + brBeforeLastTag = true; else { // Each block of text (between two tags / boundary and tag) will be displayed in a separate editbox widget, @@ -252,6 +272,8 @@ namespace MWGui { case BookTextParser::Event_ImgTag: { + pag.setIgnoreLeadingEmptyLines(false); + const BookTextParser::Attributes & attr = parser.getAttributes(); if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end()) @@ -331,9 +353,7 @@ namespace MWGui if (attr.find("face") != attr.end()) { std::string face = attr.at("face"); - - if (face != "Magic Cards") - mTextStyle.mFont = face; + mTextStyle.mFont = face; } if (attr.find("size") != attr.end()) { @@ -408,13 +428,18 @@ namespace MWGui // first empty lines that would go to the next page should be ignored // unfortunately, getLineInfo method won't be available until 3.2.2 #if (MYGUI_VERSION >= MYGUI_DEFINE_VERSION(3, 2, 2)) + mPaginator.setIgnoreLeadingEmptyLines(true); + const MyGUI::VectorLineInfo & lines = mEditBox->getSubWidgetText()->castType()->getLineInfo(); for (unsigned int i = lastLine; i < lines.size(); ++i) { if (lines[i].width == 0) ret += lineHeight; else + { + mPaginator.setIgnoreLeadingEmptyLines(false); break; + } } #endif return ret; diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index 5b79250577..0d0f74b720 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -81,7 +81,8 @@ namespace MWGui Paginator(int pageWidth, int pageHeight) : mStartTop(0), mCurrentTop(0), - mPageWidth(pageWidth), mPageHeight(pageHeight) + mPageWidth(pageWidth), mPageHeight(pageHeight), + mIgnoreLeadingEmptyLines(false) { } @@ -89,10 +90,12 @@ namespace MWGui int getCurrentTop() const { return mCurrentTop; } int getPageWidth() const { return mPageWidth; } int getPageHeight() const { return mPageHeight; } + bool getIgnoreLeadingEmptyLines() const { return mIgnoreLeadingEmptyLines; } Pages getPages() const { return mPages; } void setStartTop(int top) { mStartTop = top; } void setCurrentTop(int top) { mCurrentTop = top; } + void setIgnoreLeadingEmptyLines(bool ignore) { mIgnoreLeadingEmptyLines = ignore; } Paginator & operator<<(const Page & page) { @@ -103,6 +106,7 @@ namespace MWGui private: int mStartTop, mCurrentTop; int mPageWidth, mPageHeight; + bool mIgnoreLeadingEmptyLines; Pages mPages; }; From 192626c6f55421d57f20d4c963efde17dc95b0a1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Dec 2014 17:44:03 +0100 Subject: [PATCH 142/167] SoundGen fix: use Original Creature field only if non-empty --- apps/openmw/mwclass/creature.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 25808c991a..b87dfda98b 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -679,14 +679,15 @@ namespace MWClass if(type >= 0) { std::vector sounds; - sounds.reserve(8); MWWorld::LiveCellRef* ref = ptr.get(); + const std::string& ourId = (ref->mBase->mOriginal.empty()) ? getId(ptr) : ref->mBase->mOriginal; + MWWorld::Store::iterator sound = store.begin(); while(sound != store.end()) { - if (type == sound->mType && !sound->mCreature.empty() && Misc::StringUtils::ciEqual(ref->mBase->mOriginal, sound->mCreature)) + if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(ourId, sound->mCreature))) sounds.push_back(&*sound); ++sound; } From 4acc25f59c65101257ad3144e8935f2e345881b7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Dec 2014 17:52:06 +0100 Subject: [PATCH 143/167] Use SoundGen with no creature field as fallback This fixes the adorable "thump" sounds in the Scrib's idle animation not playing. --- apps/openmw/mwclass/creature.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index b87dfda98b..1b005270f2 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -679,6 +679,7 @@ namespace MWClass if(type >= 0) { std::vector sounds; + std::vector fallbacksounds; MWWorld::LiveCellRef* ref = ptr.get(); @@ -689,10 +690,14 @@ namespace MWClass { if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(ourId, sound->mCreature))) sounds.push_back(&*sound); + if (type == sound->mType && sound->mCreature.empty()) + fallbacksounds.push_back(&*sound); ++sound; } if(!sounds.empty()) return sounds[(int)(rand()/(RAND_MAX+1.0)*sounds.size())]->mSound; + if (!fallbacksounds.empty()) + return fallbacksounds[(int)(rand()/(RAND_MAX+1.0)*fallbacksounds.size())]->mSound; } return ""; From 4f3995a4d8f407557bedede17cbf9069fb707ee6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Dec 2014 19:15:43 +0100 Subject: [PATCH 144/167] Fix werewolf AI being able to use items --- apps/openmw/mwmechanics/aicombataction.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 6b4ede3059..73cb276262 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -454,6 +454,8 @@ namespace MWMechanics float bestActionRating = 0.f; // Default to hand-to-hand combat boost::shared_ptr bestAction (new ActionWeapon(MWWorld::Ptr())); + if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + return bestAction; if (actor.getClass().hasInventoryStore(actor)) { From 2b78e9795d346e2d54f6833baba258d7b2a1158c Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Dec 2014 19:35:34 +0100 Subject: [PATCH 145/167] Implement Calm effect removing combat packages (Fixes #1985) --- apps/openmw/mwclass/creature.cpp | 6 ++++++ apps/openmw/mwclass/creature.hpp | 2 ++ apps/openmw/mwclass/npc.cpp | 7 +++++++ apps/openmw/mwclass/npc.hpp | 2 ++ apps/openmw/mwmechanics/actors.cpp | 14 ++++++++++++++ apps/openmw/mwmechanics/aicombataction.cpp | 3 +++ apps/openmw/mwmechanics/aisequence.cpp | 5 ++--- apps/openmw/mwmechanics/aisequence.hpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 5 +++++ apps/openmw/mwworld/class.cpp | 5 +++++ apps/openmw/mwworld/class.hpp | 2 ++ 11 files changed, 49 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 1b005270f2..519216ec9b 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -891,4 +891,10 @@ namespace MWClass MWWorld::ContainerStore& store = getContainerStore(ptr); store.restock(list, ptr, ptr.getCellRef().getRefId(), ptr.getCellRef().getFaction()); } + + int Creature::getBaseFightRating(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + return ref->mBase->mAiData.mFight; + } } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 1820d4ea43..c52e85534b 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -154,6 +154,8 @@ namespace MWClass virtual void respawn (const MWWorld::Ptr& ptr) const; virtual void restock (const MWWorld::Ptr &ptr) const; + + virtual int getBaseFightRating(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 3da7446918..a3ebef5821 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1266,6 +1266,7 @@ namespace MWClass // TODO: I have no idea what these are supposed to do for NPCs since they use // voiced dialog for various conditions like health loss and combat taunts. Maybe // only for biped creatures? + if(name == "moan") return ""; if(name == "roar") @@ -1380,4 +1381,10 @@ namespace MWClass MWWorld::ContainerStore& store = getContainerStore(ptr); store.restock(list, ptr, ptr.getCellRef().getRefId(), ptr.getCellRef().getFaction()); } + + int Npc::getBaseFightRating (const MWWorld::Ptr& ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + return ref->mBase->mAiData.mFight; + } } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index a92e72af59..3bc450088b 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -185,6 +185,8 @@ namespace MWClass virtual void respawn (const MWWorld::Ptr& ptr) const; virtual void restock (const MWWorld::Ptr& ptr) const; + + virtual int getBaseFightRating (const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 0730a4660f..fecf189861 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -677,6 +677,19 @@ namespace MWMechanics // TODO: dirty flag for magic effects to avoid some unnecessary work below? + // any value of calm > 0 will stop the actor from fighting + if ((creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0 && ptr.getClass().isNpc()) + || (creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0 && !ptr.getClass().isNpc())) + { + for (std::list::const_iterator it = creatureStats.getAiSequence().begin(); it != creatureStats.getAiSequence().end(); ) + { + if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + it = creatureStats.getAiSequence().erase(it); + else + ++it; + } + } + // Update bound effects static std::map boundItemsMap; if (boundItemsMap.empty()) @@ -1056,6 +1069,7 @@ namespace MWMechanics // Reset factors to attack creatureStats.setAttacked(false); creatureStats.setAlarmed(false); + creatureStats.setAiSetting(CreatureStats::AI_Fight, ptr.getClass().getBaseFightRating(ptr)); // Update witness crime id npcStats.setCrimeId(-1); diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 73cb276262..356955b221 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -455,7 +455,10 @@ namespace MWMechanics // Default to hand-to-hand combat boost::shared_ptr bestAction (new ActionWeapon(MWWorld::Ptr())); if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + { + bestAction->prepare(actor); return bestAction; + } if (actor.getClass().hasInventoryStore(actor)) { diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 990145c8d5..2ee8984050 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -86,15 +86,14 @@ std::list::const_iterator AiSequence::end() const return mPackages.end(); } -void AiSequence::erase(std::list::const_iterator package) +std::list::const_iterator AiSequence::erase(std::list::const_iterator package) { // Not sure if manually terminated packages should trigger mDone, probably not? for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ++it) { if (package == it) { - mPackages.erase(it); - return; + return mPackages.erase(it); } } throw std::runtime_error("can't find package to erase"); diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 25605ff447..460a411ba8 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -61,7 +61,7 @@ namespace MWMechanics std::list::const_iterator begin() const; std::list::const_iterator end() const; - void erase (std::list::const_iterator package); + std::list::const_iterator erase (std::list::const_iterator package); /// Returns currently executing AiPackage type /** \see enum AiPackage::TypeId **/ diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index cd9f0d0f76..520b6b8a7c 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1096,6 +1096,11 @@ namespace MWMechanics { startCombat(*it, player); + // Apply aggression value to the base Fight rating, so that the actor can continue fighting + // after a Calm spell wears off + int fightBase = it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Fight).getBase(); + it->getClass().getCreatureStats(*it).setAiSetting(CreatureStats::AI_Fight, fightBase + aggression); + // Set the crime ID, which we will use to calm down participants // once the bounty has been paid. it->getClass().getNpcStats(*it).setCrimeId(id); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 0a84862097..7c95858343 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -429,4 +429,9 @@ namespace MWWorld { return std::string(); } + + int Class::getBaseFightRating(const Ptr &ptr) const + { + throw std::runtime_error("class does not support fight rating"); + } } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index dcac16b06e..cc18d830cc 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -339,6 +339,8 @@ namespace MWWorld /// Returns sound id virtual std::string getSound(const MWWorld::Ptr& ptr) const; + + virtual int getBaseFightRating (const MWWorld::Ptr& ptr) const; }; } From 79237d16a7502666e06d963384da759d2800e69d Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Dec 2014 13:13:25 +0100 Subject: [PATCH 146/167] Refactor spell window to use model/view and remove duplicated code in QuickKeysMenu This should also improve window resizing performance, the widgets are now just resized instead of recreated. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwgui/quickkeysmenu.cpp | 218 +----------- apps/openmw/mwgui/quickkeysmenu.hpp | 16 +- apps/openmw/mwgui/spellmodel.cpp | 140 ++++++++ apps/openmw/mwgui/spellmodel.hpp | 56 +++ apps/openmw/mwgui/spellview.cpp | 255 ++++++++++++++ apps/openmw/mwgui/spellview.hpp | 71 ++++ apps/openmw/mwgui/spellwindow.cpp | 321 ++---------------- apps/openmw/mwgui/spellwindow.hpp | 25 +- apps/openmw/mwgui/windowmanagerimp.cpp | 2 + files/mygui/openmw_list.skin.xml | 9 + .../mygui/openmw_magicselection_dialog.layout | 6 +- files/mygui/openmw_spell_window.layout | 5 +- 13 files changed, 585 insertions(+), 541 deletions(-) create mode 100644 apps/openmw/mwgui/spellmodel.cpp create mode 100644 apps/openmw/mwgui/spellmodel.hpp create mode 100644 apps/openmw/mwgui/spellview.cpp create mode 100644 apps/openmw/mwgui/spellview.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 06a142f0a1..28eadc5172 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -40,7 +40,7 @@ add_openmw_dir (mwgui merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog - recharge mode videowidget backgroundimage itemwidget screenfader debugwindow + recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview ) add_openmw_dir (mwdialogue diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 440165e749..7ffb51b5ab 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -13,7 +13,6 @@ #include "../mwbase/world.hpp" #include "../mwmechanics/spellcasting.hpp" -#include "../mwmechanics/spells.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwgui/inventorywindow.hpp" @@ -23,7 +22,8 @@ #include "windowmanagerimp.hpp" #include "itemselection.hpp" -#include "spellwindow.hpp" +#include "spellview.hpp" + #include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" @@ -495,13 +495,15 @@ namespace MWGui MagicSelectionDialog::MagicSelectionDialog(QuickKeysMenu* parent) : WindowModal("openmw_magicselection_dialog.layout") , mParent(parent) - , mWidth(0) - , mHeight(0) { getWidget(mCancelButton, "CancelButton"); getWidget(mMagicList, "MagicList"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onCancelButtonClicked); + mMagicList->setShowCostColumn(false); + mMagicList->setHighlightSelected(false); + mMagicList->eventSpellClicked += MyGUI::newDelegate(this, &MagicSelectionDialog::onModelIndexSelected); + center(); } @@ -519,211 +521,17 @@ namespace MWGui { WindowModal::open(); - while (mMagicList->getChildCount ()) - MyGUI::Gui::getInstance ().destroyWidget (mMagicList->getChildAt (0)); - - mHeight = 0; - - const int spellHeight = 18; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - MWMechanics::Spells& spells = stats.getSpells(); - - /// \todo lots of copy&pasted code from SpellWindow - - // retrieve powers & spells, sort by name - std::vector spellList; - - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) - { - spellList.push_back (it->first); - } - - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); - - std::vector powers; - std::vector::iterator it = spellList.begin(); - while (it != spellList.end()) - { - const ESM::Spell* spell = esmStore.get().find(*it); - if (spell->mData.mType == ESM::Spell::ST_Power) - { - powers.push_back(*it); - it = spellList.erase(it); - } - else if (spell->mData.mType == ESM::Spell::ST_Ability - || spell->mData.mType == ESM::Spell::ST_Blight - || spell->mData.mType == ESM::Spell::ST_Curse - || spell->mData.mType == ESM::Spell::ST_Disease) - { - it = spellList.erase(it); - } - else - ++it; - } - std::sort(powers.begin(), powers.end(), sortSpells); - std::sort(spellList.begin(), spellList.end(), sortSpells); - - // retrieve usable magic items & sort - std::vector items; - for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it) - { - std::string enchantId = it->getClass().getEnchantment(*it); - if (enchantId != "") - { - // only add items with "Cast once" or "Cast on use" - const ESM::Enchantment* enchant = - esmStore.get().find(enchantId); - - int type = enchant->mData.mType; - if (type != ESM::Enchantment::CastOnce - && type != ESM::Enchantment::WhenUsed) - continue; - - items.push_back(*it); - } - } - std::sort(items.begin(), items.end(), sortItems); - - - int height = estimateHeight(items.size() + powers.size() + spellList.size()); - bool scrollVisible = height > mMagicList->getHeight(); - mWidth = mMagicList->getWidth() - scrollVisible * 18; - - - // powers - addGroup("#{sPowers}", ""); - - for (std::vector::const_iterator it = powers.begin(); it != powers.end(); ++it) - { - const ESM::Spell* spell = esmStore.get().find(*it); - MyGUI::Button* t = mMagicList->createWidget("SandTextButton", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(spell->mName); - t->setTextAlign(MyGUI::Align::Left); - t->setUserString("ToolTipType", "Spell"); - t->setUserString("Spell", *it); - t->eventMouseWheel += MyGUI::newDelegate(this, &MagicSelectionDialog::onMouseWheel); - t->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onSpellSelected); - - mHeight += spellHeight; - } - - // other spells - addGroup("#{sSpells}", ""); - for (std::vector::const_iterator it = spellList.begin(); it != spellList.end(); ++it) - { - const ESM::Spell* spell = esmStore.get().find(*it); - MyGUI::Button* t = mMagicList->createWidget("SandTextButton", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(spell->mName); - t->setTextAlign(MyGUI::Align::Left); - t->setUserString("ToolTipType", "Spell"); - t->setUserString("Spell", *it); - t->eventMouseWheel += MyGUI::newDelegate(this, &MagicSelectionDialog::onMouseWheel); - t->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onSpellSelected); - - mHeight += spellHeight; - } - - - // enchanted items - addGroup("#{sMagicItem}", ""); - - for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) - { - MWWorld::Ptr item = *it; - - // check if the item is currently equipped (will display in a different color) - bool equipped = false; - for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) - { - if (store.getSlot(i) != store.end() && *store.getSlot(i) == item) - { - equipped = true; - break; - } - } - - MyGUI::Button* t = mMagicList->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(item.getClass().getName(item)); - t->setTextAlign(MyGUI::Align::Left); - t->setUserData(item); - t->setUserString("ToolTipType", "ItemPtr"); - t->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onEnchantedItemSelected); - t->eventMouseWheel += MyGUI::newDelegate(this, &MagicSelectionDialog::onMouseWheel); - - mHeight += spellHeight; - } - - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden - mMagicList->setVisibleVScroll(false); - mMagicList->setCanvasSize (mWidth, std::max(mMagicList->getHeight(), mHeight)); - mMagicList->setVisibleVScroll(true); + mMagicList->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mMagicList->update(); } - void MagicSelectionDialog::addGroup(const std::string &label, const std::string& label2) + void MagicSelectionDialog::onModelIndexSelected(SpellModel::ModelIndex index) { - if (mMagicList->getChildCount() > 0) - { - MyGUI::ImageBox* separator = mMagicList->createWidget("MW_HLine", - MyGUI::IntCoord(4, mHeight, mWidth-8, 18), - MyGUI::Align::Left | MyGUI::Align::Top); - separator->setNeedMouseFocus(false); - mHeight += 18; - } - - MyGUI::TextBox* groupWidget = mMagicList->createWidget("SandBrightText", - MyGUI::IntCoord(0, mHeight, mWidth, 24), - MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); - groupWidget->setCaptionWithReplacing(label); - groupWidget->setTextAlign(MyGUI::Align::Left); - groupWidget->setNeedMouseFocus(false); - - if (label2 != "") - { - MyGUI::TextBox* groupWidget2 = mMagicList->createWidget("SandBrightText", - MyGUI::IntCoord(0, mHeight, mWidth-4, 24), - MyGUI::Align::Left | MyGUI::Align::Top); - groupWidget2->setCaptionWithReplacing(label2); - groupWidget2->setTextAlign(MyGUI::Align::Right); - groupWidget2->setNeedMouseFocus(false); - } - - mHeight += 24; - } - - - void MagicSelectionDialog::onMouseWheel(MyGUI::Widget* _sender, int _rel) - { - if (mMagicList->getViewOffset().top + _rel*0.3 > 0) - mMagicList->setViewOffset(MyGUI::IntPoint(0, 0)); + const Spell& spell = mMagicList->getModel()->getItem(index); + if (spell.mType == Spell::Type_EnchantedItem) + mParent->onAssignMagicItem(spell.mItem); else - mMagicList->setViewOffset(MyGUI::IntPoint(0, mMagicList->getViewOffset().top + _rel*0.3)); - } - - void MagicSelectionDialog::onEnchantedItemSelected(MyGUI::Widget* _sender) - { - MWWorld::Ptr item = *_sender->getUserData(); - - mParent->onAssignMagicItem (item); - } - - void MagicSelectionDialog::onSpellSelected(MyGUI::Widget* _sender) - { - mParent->onAssignMagic (_sender->getUserString("Spell")); - } - - int MagicSelectionDialog::estimateHeight(int numSpells) const - { - int height = 0; - height += 24 * 3 + 18 * 2; // group headings - height += numSpells * 18; - return height; + mParent->onAssignMagic(spell.mId); } } diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index dc088d3c9a..30e6728911 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -5,6 +5,8 @@ #include "windowbase.hpp" +#include "spellmodel.hpp" + namespace MWGui { @@ -12,6 +14,7 @@ namespace MWGui class ItemSelectionDialog; class MagicSelectionDialog; class ItemWidget; + class SpellView; class QuickKeysMenu : public WindowBase { @@ -94,21 +97,12 @@ namespace MWGui private: MyGUI::Button* mCancelButton; - MyGUI::ScrollView* mMagicList; - - int mWidth; - int mHeight; + SpellView* mMagicList; QuickKeysMenu* mParent; void onCancelButtonClicked (MyGUI::Widget* sender); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); - void onEnchantedItemSelected(MyGUI::Widget* _sender); - void onSpellSelected(MyGUI::Widget* _sender); - int estimateHeight(int numSpells) const; - - - void addGroup(const std::string& label, const std::string& label2); + void onModelIndexSelected(SpellModel::ModelIndex index); }; } diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp new file mode 100644 index 0000000000..0305258978 --- /dev/null +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -0,0 +1,140 @@ +#include "spellmodel.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/spellcasting.hpp" + +#include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/class.hpp" + +namespace +{ + + bool sortSpells(const MWGui::Spell& left, const MWGui::Spell& right) + { + if (left.mType != right.mType) + return left.mType < right.mType; + + int cmp = left.mName.compare(right.mName); + return cmp < 0; + } + +} + +namespace MWGui +{ + + SpellModel::SpellModel(const MWWorld::Ptr &actor) + : mActor(actor) + { + + } + + void SpellModel::update() + { + mSpells.clear(); + + MWMechanics::CreatureStats& stats = mActor.getClass().getCreatureStats(mActor); + const MWMechanics::Spells& spells = stats.getSpells(); + + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); + + for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + const ESM::Spell* spell = esmStore.get().find(it->first); + if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) + continue; + + Spell newSpell; + newSpell.mName = spell->mName; + if (spell->mData.mType == ESM::Spell::ST_Spell) + { + newSpell.mType = Spell::Type_Spell; + std::string cost = boost::lexical_cast(spell->mData.mCost); + std::string chance = boost::lexical_cast(int(MWMechanics::getSpellSuccessChance(spell, mActor))); + newSpell.mCostColumn = cost + "/" + chance; + } + else + newSpell.mType = Spell::Type_Power; + newSpell.mId = it->first; + + newSpell.mSelected = (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == it->first); + newSpell.mActive = true; + mSpells.push_back(newSpell); + } + + MWWorld::InventoryStore& invStore = mActor.getClass().getInventoryStore(mActor); + for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) + { + MWWorld::Ptr item = *it; + const std::string enchantId = item.getClass().getEnchantment(item); + if (enchantId.empty()) + continue; + const ESM::Enchantment* enchant = + esmStore.get().find(item.getClass().getEnchantment(item)); + if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce) + continue; + + Spell newSpell; + newSpell.mItem = item; + newSpell.mId = item.getClass().getId(item); + newSpell.mName = item.getClass().getName(item); + newSpell.mType = Spell::Type_EnchantedItem; + newSpell.mSelected = invStore.getSelectedEnchantItem() == it; + + // FIXME: move to mwmechanics + if (enchant->mData.mType == ESM::Enchantment::CastOnce) + { + newSpell.mCostColumn = "100/100"; + newSpell.mActive = false; + } + else + { + float enchantCost = enchant->mData.mCost; + int eSkill = mActor.getClass().getSkill(mActor, ESM::Skill::Enchant); + int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); + + std::string cost = boost::lexical_cast(castCost); + int currentCharge = int(item.getCellRef().getEnchantmentCharge()); + if (currentCharge == -1) + currentCharge = enchant->mData.mCharge; + std::string charge = boost::lexical_cast(currentCharge); + newSpell.mCostColumn = cost + "/" + charge; + + bool equipped = false; + for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) + { + if (invStore.getSlot(i) != invStore.end() && *invStore.getSlot(i) == item) + { + equipped = true; + break; + } + } + newSpell.mActive = equipped; + } + mSpells.push_back(newSpell); + } + + std::stable_sort(mSpells.begin(), mSpells.end(), sortSpells); + } + + size_t SpellModel::getItemCount() const + { + return mSpells.size(); + } + + Spell SpellModel::getItem(ModelIndex index) const + { + if (index < 0 || index >= int(mSpells.size())) + throw std::runtime_error("invalid spell index supplied"); + return mSpells[index]; + } + +} diff --git a/apps/openmw/mwgui/spellmodel.hpp b/apps/openmw/mwgui/spellmodel.hpp new file mode 100644 index 0000000000..1f7a0cb7c9 --- /dev/null +++ b/apps/openmw/mwgui/spellmodel.hpp @@ -0,0 +1,56 @@ +#ifndef OPENMW_GUI_SPELLMODEL_H +#define OPENMW_GUI_SPELLMODEL_H + +#include "../mwworld/ptr.hpp" + +namespace MWGui +{ + + struct Spell + { + enum Type + { + Type_Power, + Type_Spell, + Type_EnchantedItem + }; + + Type mType; + std::string mName; + std::string mCostColumn; // Cost/chance or Cost/charge + std::string mId; // Item ID or spell ID + MWWorld::Ptr mItem; // Only for Type_EnchantedItem + bool mSelected; // Is this the currently selected spell/item (only one can be selected at a time) + bool mActive; // (Items only) is the item equipped? + + Spell() + : mSelected(false) + , mActive(false) + { + } + }; + + ///@brief Model that lists all usable powers, spells and enchanted items for an actor. + class SpellModel + { + public: + SpellModel(const MWWorld::Ptr& actor); + + typedef int ModelIndex; + + void update(); + + Spell getItem (ModelIndex index) const; + ///< throws for invalid index + + size_t getItemCount() const; + + private: + MWWorld::Ptr mActor; + + std::vector mSpells; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp new file mode 100644 index 0000000000..3e5a01e3a7 --- /dev/null +++ b/apps/openmw/mwgui/spellview.cpp @@ -0,0 +1,255 @@ +#include "spellview.hpp" + +#include +#include +#include +#include + +#include + +namespace MWGui +{ + + SpellView::SpellView() + : mShowCostColumn(true) + , mHighlightSelected(true) + , mScrollView(NULL) + { + } + + void SpellView::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mScrollView, "ScrollView"); + if (mScrollView == NULL) + throw std::runtime_error("Item view needs a scroll view"); + + mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top); + } + + void SpellView::registerComponents() + { + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + } + + void SpellView::setModel(SpellModel *model) + { + mModel.reset(model); + update(); + } + + SpellModel* SpellView::getModel() + { + return mModel.get(); + } + + void SpellView::setShowCostColumn(bool show) + { + if (show != mShowCostColumn) + { + mShowCostColumn = show; + update(); + } + } + + void SpellView::setHighlightSelected(bool highlight) + { + if (highlight != mHighlightSelected) + { + mHighlightSelected = highlight; + update(); + } + } + + void SpellView::update() + { + if (!mModel.get()) + return; + + mModel->update(); + + int curType = -1; + + const int spellHeight = 18; + + mLines.clear(); + + while (mScrollView->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); + + for (SpellModel::ModelIndex i = 0; igetItemCount()); ++i) + { + const Spell& spell = mModel->getItem(i); + if (curType != spell.mType) + { + if (spell.mType == Spell::Type_Power) + addGroup("#{sPowers}", ""); + else if (spell.mType == Spell::Type_Spell) + addGroup("#{sSpells}", "#{sCostChance}"); + else + addGroup("#{sMagicItem}", "#{sCostCharge}"); + curType = spell.mType; + } + + const std::string skin = spell.mActive ? "SandTextButton" : "SpellTextUnequipped"; + + Gui::SharedStateButton* t = mScrollView->createWidget(skin, + MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + t->setCaption(spell.mName); + t->setTextAlign(MyGUI::Align::Left); + adjustSpellWidget(spell, i, t); + + if (!spell.mCostColumn.empty() && mShowCostColumn) + { + Gui::SharedStateButton* costChance = mScrollView->createWidget(skin, + MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + costChance->setCaption(spell.mCostColumn); + costChance->setTextAlign(MyGUI::Align::Right); + adjustSpellWidget(spell, i, costChance); + + Gui::ButtonGroup group; + group.push_back(t); + group.push_back(costChance); + Gui::SharedStateButton::createButtonGroup(group); + + mLines.push_back(std::make_pair(t, costChance)); + } + else + mLines.push_back(std::make_pair(t, (MyGUI::Widget*)NULL)); + + t->setStateSelected(spell.mSelected); + } + + layoutWidgets(); + } + + void SpellView::layoutWidgets() + { + int height = 0; + for (std::vector< std::pair >::iterator it = mLines.begin(); + it != mLines.end(); ++it) + { + height += (it->first)->getHeight(); + } + + bool scrollVisible = height > mScrollView->getHeight(); + int width = mScrollView->getWidth() - (scrollVisible ? 18 : 0); + + height = 0; + for (std::vector< std::pair >::iterator it = mLines.begin(); + it != mLines.end(); ++it) + { + int lineHeight = (it->first)->getHeight(); + (it->first)->setCoord(4, height, width-8, lineHeight); + if (it->second) + { + (it->second)->setCoord(4, height, width-8, lineHeight); + MyGUI::TextBox* second = (it->second)->castType(false); + if (second) + (it->first)->setSize(width-8-second->getTextSize().width, lineHeight); + } + + height += lineHeight; + } + + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mScrollView->setVisibleVScroll(false); + mScrollView->setCanvasSize(mScrollView->getWidth(), std::max(mScrollView->getHeight(), height)); + mScrollView->setVisibleVScroll(true); + } + + void SpellView::addGroup(const std::string &label, const std::string& label2) + { + if (mScrollView->getChildCount() > 0) + { + MyGUI::ImageBox* separator = mScrollView->createWidget("MW_HLine", + MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 18), + MyGUI::Align::Left | MyGUI::Align::Top); + separator->setNeedMouseFocus(false); + mLines.push_back(std::make_pair(separator, (MyGUI::Widget*)NULL)); + } + + MyGUI::TextBox* groupWidget = mScrollView->createWidget("SandBrightText", + MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), + MyGUI::Align::Left | MyGUI::Align::Top); + groupWidget->setCaptionWithReplacing(label); + groupWidget->setTextAlign(MyGUI::Align::Left); + groupWidget->setNeedMouseFocus(false); + + if (label2 != "") + { + MyGUI::TextBox* groupWidget2 = mScrollView->createWidget("SandBrightText", + MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), + MyGUI::Align::Left | MyGUI::Align::Top); + groupWidget2->setCaptionWithReplacing(label2); + groupWidget2->setTextAlign(MyGUI::Align::Right); + groupWidget2->setNeedMouseFocus(false); + + mLines.push_back(std::make_pair(groupWidget, groupWidget2)); + } + else + mLines.push_back(std::make_pair(groupWidget, (MyGUI::Widget*)NULL)); + } + + + void SpellView::setSize(const MyGUI::IntSize &_value) + { + bool changed = (_value.width != getWidth() || _value.height != getHeight()); + Base::setSize(_value); + if (changed) + layoutWidgets(); + } + + void SpellView::setSize(int _width, int _height) + { + setSize(MyGUI::IntSize(_width, _height)); + } + + void SpellView::setCoord(const MyGUI::IntCoord &_value) + { + bool changed = (_value.width != getWidth() || _value.height != getHeight()); + Base::setCoord(_value); + if (changed) + layoutWidgets(); + } + + void SpellView::setCoord(int _left, int _top, int _width, int _height) + { + setCoord(MyGUI::IntCoord(_left, _top, _width, _height)); + } + + void SpellView::adjustSpellWidget(const Spell &spell, SpellModel::ModelIndex index, MyGUI::Widget *widget) + { + if (spell.mType == Spell::Type_EnchantedItem) + { + widget->setUserData(spell.mItem); + widget->setUserString("ToolTipType", "ItemPtr"); + } + else + { + widget->setUserString("ToolTipType", "Spell"); + widget->setUserString("Spell", spell.mId); + } + + widget->setUserString("SpellModelIndex", MyGUI::utility::toString(index)); + + widget->eventMouseWheel += MyGUI::newDelegate(this, &SpellView::onMouseWheel); + widget->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellView::onSpellSelected); + } + + void SpellView::onSpellSelected(MyGUI::Widget* _sender) + { + SpellModel::ModelIndex i = MyGUI::utility::parseInt(_sender->getUserString("SpellModelIndex")); + eventSpellClicked(i); + } + + void SpellView::onMouseWheel(MyGUI::Widget* _sender, int _rel) + { + if (mScrollView->getViewOffset().top + _rel*0.3 > 0) + mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mScrollView->setViewOffset(MyGUI::IntPoint(0, mScrollView->getViewOffset().top + _rel*0.3)); + } + +} diff --git a/apps/openmw/mwgui/spellview.hpp b/apps/openmw/mwgui/spellview.hpp new file mode 100644 index 0000000000..30daf86711 --- /dev/null +++ b/apps/openmw/mwgui/spellview.hpp @@ -0,0 +1,71 @@ +#ifndef OPENMW_GUI_SPELLVIEW_H +#define OPENMW_GUI_SPELLVIEW_H + +#include + +#include "spellmodel.hpp" + +namespace MyGUI +{ + class ScrollView; +} + +namespace MWGui +{ + + class SpellModel; + + ///@brief Displays a SpellModel in a list widget + class SpellView : public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(SpellView) + public: + SpellView(); + + /// Register needed components with MyGUI's factory manager + static void registerComponents (); + + /// Should the cost/chance column be shown? + void setShowCostColumn(bool show); + + void setHighlightSelected(bool highlight); + + /// Takes ownership of \a model + void setModel (SpellModel* model); + + SpellModel* getModel(); + + void update(); + + typedef MyGUI::delegates::CMultiDelegate1 EventHandle_ModelIndex; + /// Fired when a spell was clicked + EventHandle_ModelIndex eventSpellClicked; + + virtual void initialiseOverride(); + + virtual void setSize(const MyGUI::IntSize& _value); + virtual void setCoord(const MyGUI::IntCoord& _value); + void setSize(int _width, int _height); + void setCoord(int _left, int _top, int _width, int _height); + + private: + MyGUI::ScrollView* mScrollView; + + std::auto_ptr mModel; + + std::vector< std::pair > mLines; + + bool mShowCostColumn; + bool mHighlightSelected; + + void layoutWidgets(); + void addGroup(const std::string& label1, const std::string& label2); + void adjustSpellWidget(const Spell& spell, SpellModel::ModelIndex index, MyGUI::Widget* widget); + + void onSpellSelected(MyGUI::Widget* _sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + }; + +} + +#endif diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 7904c249ab..deb971e6d4 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -1,10 +1,7 @@ #include "spellwindow.hpp" -#include #include -#include - #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -19,44 +16,24 @@ #include "spellicons.hpp" #include "inventorywindow.hpp" #include "confirmationdialog.hpp" +#include "spellview.hpp" namespace MWGui { - bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right) - { - int cmp = left.getClass().getName(left).compare( - right.getClass().getName(right)); - return cmp < 0; - } - - bool sortSpells(const std::string& left, const std::string& right) - { - const MWWorld::Store &spells = - MWBase::Environment::get().getWorld()->getStore().get(); - - const ESM::Spell* a = spells.find(left); - const ESM::Spell* b = spells.find(right); - - int cmp = a->mName.compare(b->mName); - return cmp < 0; - } - SpellWindow::SpellWindow(DragAndDrop* drag) : WindowPinnableBase("openmw_spell_window.layout") , NoDrop(drag, mMainWidget) - , mHeight(0) - , mWidth(0) - , mWindowSize(mMainWidget->getSize()) + , mSpellView(NULL) { mSpellIcons = new SpellIcons(); getWidget(mSpellView, "SpellView"); getWidget(mEffectBox, "EffectsBox"); - setCoord(498, 300, 302, 300); + mSpellView->eventSpellClicked += MyGUI::newDelegate(this, &SpellWindow::onModelIndexSelected); - mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SpellWindow::onWindowResize); + setCoord(498, 300, 302, 300); } SpellWindow::~SpellWindow() @@ -84,261 +61,14 @@ namespace MWGui { mSpellIcons->updateWidgets(mEffectBox, false); - const int spellHeight = 18; - - mHeight = 0; - while (mSpellView->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(mSpellView->getChildAt(0)); - - // retrieve all player spells, divide them into Powers and Spells and sort them - std::vector spellList; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - MWMechanics::Spells& spells = stats.getSpells(); - - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) - spellList.push_back (it->first); - - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); - - std::vector powers; - std::vector::iterator it = spellList.begin(); - while (it != spellList.end()) - { - const ESM::Spell* spell = esmStore.get().find(*it); - - if (spell->mData.mType == ESM::Spell::ST_Power) - { - powers.push_back(*it); - it = spellList.erase(it); - } - else if (spell->mData.mType == ESM::Spell::ST_Ability - || spell->mData.mType == ESM::Spell::ST_Blight - || spell->mData.mType == ESM::Spell::ST_Curse - || spell->mData.mType == ESM::Spell::ST_Disease) - { - it = spellList.erase(it); - } - else - ++it; - } - std::sort(powers.begin(), powers.end(), sortSpells); - std::sort(spellList.begin(), spellList.end(), sortSpells); - - // retrieve player's enchanted items - std::vector items; - for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it) - { - std::string enchantId = it->getClass().getEnchantment(*it); - if (enchantId != "") - { - // only add items with "Cast once" or "Cast on use" - const ESM::Enchantment* enchant = - esmStore.get().find(enchantId); - - int type = enchant->mData.mType; - if (type != ESM::Enchantment::CastOnce - && type != ESM::Enchantment::WhenUsed) - continue; - - items.push_back(*it); - } - } - std::sort(items.begin(), items.end(), sortItems); - - - int height = estimateHeight(items.size() + powers.size() + spellList.size()); - bool scrollVisible = height > mSpellView->getHeight(); - mWidth = mSpellView->getWidth() - (scrollVisible ? 18 : 0); - - // powers - addGroup("#{sPowers}", ""); - - for (std::vector::const_iterator it = powers.begin(); it != powers.end(); ++it) - { - const ESM::Spell* spell = esmStore.get().find(*it); - MyGUI::Button* t = mSpellView->createWidget("SandTextButton", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(spell->mName); - t->setTextAlign(MyGUI::Align::Left); - t->setUserString("ToolTipType", "Spell"); - t->setUserString("Spell", *it); - t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); - - if (*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()) - t->setStateSelected(true); - - mHeight += spellHeight; - } - - // other spells - addGroup("#{sSpells}", "#{sCostChance}"); - for (std::vector::const_iterator it = spellList.begin(); it != spellList.end(); ++it) - { - const ESM::Spell* spell = esmStore.get().find(*it); - Gui::SharedStateButton* t = mSpellView->createWidget("SandTextButton", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(spell->mName); - t->setTextAlign(MyGUI::Align::Left); - t->setUserString("ToolTipType", "Spell"); - t->setUserString("Spell", *it); - t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); - - // cost / success chance - Gui::SharedStateButton* costChance = mSpellView->createWidget("SandTextButton", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - std::string cost = boost::lexical_cast(spell->mData.mCost); - std::string chance = boost::lexical_cast(int(MWMechanics::getSpellSuccessChance(*it, player))); - costChance->setCaption(cost + "/" + chance); - costChance->setTextAlign(MyGUI::Align::Right); - costChance->setUserString("ToolTipType", "Spell"); - costChance->setUserString("Spell", *it); - costChance->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - costChance->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); - - t->setSize(mWidth-12-costChance->getTextSize().width, t->getHeight()); - - Gui::ButtonGroup group; - group.push_back(t); - group.push_back(costChance); - Gui::SharedStateButton::createButtonGroup(group); - - t->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()); - - mHeight += spellHeight; - } - - - // enchanted items - addGroup("#{sMagicItem}", "#{sCostCharge}"); - - for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) - { - MWWorld::Ptr item = *it; - - const ESM::Enchantment* enchant = - esmStore.get().find(item.getClass().getEnchantment(item)); - - // check if the item is currently equipped (will display in a different color) - bool equipped = false; - for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) - { - if (store.getSlot(i) != store.end() && *store.getSlot(i) == item) - { - equipped = true; - break; - } - } - - Gui::SharedStateButton* t = mSpellView->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(item.getClass().getName(item)); - t->setTextAlign(MyGUI::Align::Left); - t->setUserData(item); - t->setUserString("ToolTipType", "ItemPtr"); - t->setUserString("Equipped", equipped ? "true" : "false"); - t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected); - t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - - // cost / charge - Gui::SharedStateButton* costCharge = mSpellView->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - - float enchantCost = enchant->mData.mCost; - int eSkill = player.getClass().getSkill(player, ESM::Skill::Enchant); - int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); - - std::string cost = boost::lexical_cast(castCost); - int currentCharge = int(item.getCellRef().getEnchantmentCharge()); - if (currentCharge == -1) - currentCharge = enchant->mData.mCharge; - std::string charge = boost::lexical_cast(currentCharge); - if (enchant->mData.mType == ESM::Enchantment::CastOnce) - { - // this is Morrowind behaviour - cost = "100"; - charge = "100"; - } - - - costCharge->setUserData(item); - costCharge->setUserString("ToolTipType", "ItemPtr"); - costCharge->setUserString("Equipped", equipped ? "true" : "false"); - costCharge->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected); - costCharge->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - costCharge->setCaption(cost + "/" + charge); - costCharge->setTextAlign(MyGUI::Align::Right); - - Gui::ButtonGroup group; - group.push_back(t); - group.push_back(costCharge); - Gui::SharedStateButton::createButtonGroup(group); - - if (store.getSelectedEnchantItem() != store.end()) - t->setStateSelected(item == *store.getSelectedEnchantItem()); - - t->setSize(mWidth-12-costCharge->getTextSize().width, t->getHeight()); - - mHeight += spellHeight; - } - - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden - mSpellView->setVisibleVScroll(false); - mSpellView->setCanvasSize(mSpellView->getWidth(), std::max(mSpellView->getHeight(), mHeight)); - mSpellView->setVisibleVScroll(true); + mSpellView->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mSpellView->update(); } - void SpellWindow::addGroup(const std::string &label, const std::string& label2) - { - if (mSpellView->getChildCount() > 0) - { - MyGUI::ImageBox* separator = mSpellView->createWidget("MW_HLine", - MyGUI::IntCoord(4, mHeight, mWidth-8, 18), - MyGUI::Align::Left | MyGUI::Align::Top); - separator->setNeedMouseFocus(false); - mHeight += 18; - } - - MyGUI::TextBox* groupWidget = mSpellView->createWidget("SandBrightText", - MyGUI::IntCoord(0, mHeight, mWidth, 24), - MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); - groupWidget->setCaptionWithReplacing(label); - groupWidget->setTextAlign(MyGUI::Align::Left); - groupWidget->setNeedMouseFocus(false); - - if (label2 != "") - { - MyGUI::TextBox* groupWidget2 = mSpellView->createWidget("SandBrightText", - MyGUI::IntCoord(0, mHeight, mWidth-4, 24), - MyGUI::Align::Left | MyGUI::Align::Top); - groupWidget2->setCaptionWithReplacing(label2); - groupWidget2->setTextAlign(MyGUI::Align::Right); - groupWidget2->setNeedMouseFocus(false); - - groupWidget->setSize(mWidth-8-groupWidget2->getTextSize().width, groupWidget->getHeight()); - } - - mHeight += 24; - } - - void SpellWindow::onWindowResize(MyGUI::Window* _sender) - { - if (mMainWidget->getSize() != mWindowSize) - { - mWindowSize = mMainWidget->getSize(); - updateSpells(); - } - } - - void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender) + void SpellWindow::onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - MWWorld::Ptr item = *_sender->getUserData(); // retrieve ContainerStoreIterator to the item MWWorld::ContainerStoreIterator it = store.begin(); @@ -352,7 +82,7 @@ namespace MWGui assert(it != store.end()); // equip, if it can be equipped and is not already equipped - if (_sender->getUserString("Equipped") == "false" + if (!alreadyEquipped && !item.getClass().getEquipmentSlots(item).first.empty()) { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); @@ -364,12 +94,21 @@ namespace MWGui updateSpells(); } - void SpellWindow::onSpellSelected(MyGUI::Widget* _sender) + void SpellWindow::onModelIndexSelected(SpellModel::ModelIndex index) { - std::string spellId = _sender->getUserString("Spell"); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + const Spell& spell = mSpellView->getModel()->getItem(index); + if (spell.mType == Spell::Type_EnchantedItem) + { + onEnchantedItemSelected(spell.mItem, spell.mActive); + } + else + { + onSpellSelected(spell.mId); + } + } + void SpellWindow::onSpellSelected(const std::string& spellId) + { if (MyGUI::InputManager::getInstance().isShiftPressed()) { // delete spell, if allowed @@ -396,6 +135,8 @@ namespace MWGui } else { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); store.setSelectedEnchantItem(store.end()); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); } @@ -403,22 +144,6 @@ namespace MWGui updateSpells(); } - int SpellWindow::estimateHeight(int numSpells) const - { - int height = 0; - height += 24 * 3 + 18 * 2; // group headings - height += numSpells * 18; - return height; - } - - void SpellWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) - { - if (mSpellView->getViewOffset().top + _rel*0.3 > 0) - mSpellView->setViewOffset(MyGUI::IntPoint(0, 0)); - else - mSpellView->setViewOffset(MyGUI::IntPoint(0, mSpellView->getViewOffset().top + _rel*0.3)); - } - void SpellWindow::onDeleteSpellAccept() { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index a74847b907..2f7319cb92 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -4,13 +4,12 @@ #include "windowpinnablebase.hpp" #include "../mwworld/ptr.hpp" +#include "spellmodel.hpp" + namespace MWGui { class SpellIcons; - - bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right); - - bool sortSpells(const std::string& left, const std::string& right); + class SpellView; class SpellWindow : public WindowPinnableBase, public NoDrop { @@ -23,30 +22,20 @@ namespace MWGui void onFrame(float dt) { NoDrop::onFrame(dt); } protected: - MyGUI::ScrollView* mSpellView; MyGUI::Widget* mEffectBox; - int mHeight; - int mWidth; - - MyGUI::IntSize mWindowSize; - std::string mSpellToDelete; - void addGroup(const std::string& label, const std::string& label2); - - int estimateHeight(int numSpells) const; - - void onWindowResize(MyGUI::Window* _sender); - void onEnchantedItemSelected(MyGUI::Widget* _sender); - void onSpellSelected(MyGUI::Widget* _sender); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped); + void onSpellSelected(const std::string& spellId); + void onModelIndexSelected(SpellModel::ModelIndex index); void onDeleteSpellAccept(); virtual void onPinToggled(); virtual void onTitleDoubleClicked(); virtual void open(); + SpellView* mSpellView; SpellIcons* mSpellIcons; }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index acb8b2eb75..26f14572f2 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -73,6 +73,7 @@ #include "itemwidget.hpp" #include "screenfader.hpp" #include "debugwindow.hpp" +#include "spellview.hpp" namespace MWGui { @@ -179,6 +180,7 @@ namespace MWGui BookPage::registerMyGUIComponents (); ItemView::registerComponents(); ItemWidget::registerComponents(); + SpellView::registerComponents(); Gui::registerAllWidgets(); MyGUI::FactoryManager::getInstance().registerFactory("Controller"); diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 21b17c8f07..ce8209e3d5 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -157,6 +157,15 @@ + + + + + + + + + diff --git a/files/mygui/openmw_magicselection_dialog.layout b/files/mygui/openmw_magicselection_dialog.layout index a89795473f..bf4cb71d48 100644 --- a/files/mygui/openmw_magicselection_dialog.layout +++ b/files/mygui/openmw_magicselection_dialog.layout @@ -3,12 +3,10 @@ - + - - - + diff --git a/files/mygui/openmw_spell_window.layout b/files/mygui/openmw_spell_window.layout index ec655ace81..21bf74267a 100644 --- a/files/mygui/openmw_spell_window.layout +++ b/files/mygui/openmw_spell_window.layout @@ -10,10 +10,7 @@ - - - - + From 4e0d16da8c4651d006f3b40e05e50e5eea82243e Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Dec 2014 13:34:04 +0100 Subject: [PATCH 147/167] Take Scale field in creature record into account (Fixes #2214) --- apps/openmw/mwclass/creature.cpp | 6 ++++++ apps/openmw/mwclass/creature.hpp | 2 ++ apps/openmw/mwworld/physicssystem.cpp | 7 +++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 519216ec9b..9d07aecf3f 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -897,4 +897,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); return ref->mBase->mAiData.mFight; } + + void Creature::adjustScale(const MWWorld::Ptr &ptr, float &scale) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + scale *= ref->mBase->mScale; + } } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index c52e85534b..4b58864489 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -156,6 +156,8 @@ namespace MWClass virtual void restock (const MWWorld::Ptr &ptr) const; virtual int getBaseFightRating(const MWWorld::Ptr &ptr) const; + + virtual void adjustScale(const MWWorld::Ptr& ptr,float& scale) const; }; } diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index eecc4a02db..61a3b27f81 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -754,8 +754,11 @@ namespace MWWorld if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) { - // NOTE: Ignoring Npc::adjustScale (race height) on purpose. This is a bug in MW and must be replicated for compatibility reasons - act->setScale(ptr.getCellRef().getScale()); + float scale = ptr.getCellRef().getScale(); + if (!ptr.getClass().isNpc()) + // NOTE: Ignoring Npc::adjustScale (race height) on purpose. This is a bug in MW and must be replicated for compatibility reasons + ptr.getClass().adjustScale(ptr, scale); + act->setScale(scale); } } From 4d5adfb5ddec13c21ebead5d405b512a9dc379e9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Dec 2014 13:47:34 +0100 Subject: [PATCH 148/167] Fix being able to use enchantments of items that failed to equip (Fixes #2215) --- apps/openmw/mwgui/inventoryitemmodel.cpp | 10 ++-------- apps/openmw/mwgui/quickkeysmenu.cpp | 4 ++++ apps/openmw/mwgui/spellmodel.cpp | 11 +---------- apps/openmw/mwgui/spellwindow.cpp | 3 +++ apps/openmw/mwgui/tradeitemmodel.cpp | 11 +---------- apps/openmw/mwmechanics/aicombataction.cpp | 4 ++++ apps/openmw/mwworld/inventorystore.cpp | 10 ++++++++++ apps/openmw/mwworld/inventorystore.hpp | 3 +++ 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index f45881770a..cee0dc6eed 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -99,14 +99,8 @@ void InventoryItemModel::update() if (mActor.getClass().hasInventoryStore(mActor)) { MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor); - for (int slot=0; slotgetInventoryWindow()->useItem(item); + + // make sure that item was successfully equipped + if (!store.isEquipped(item)) + return; } store.setSelectedEnchantItem(it); diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 0305258978..fe2f073712 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -108,16 +108,7 @@ namespace MWGui std::string charge = boost::lexical_cast(currentCharge); newSpell.mCostColumn = cost + "/" + charge; - bool equipped = false; - for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) - { - if (invStore.getSlot(i) != invStore.end() && *invStore.getSlot(i) == item) - { - equipped = true; - break; - } - } - newSpell.mActive = equipped; + newSpell.mActive = invStore.isEquipped(item); } mSpells.push_back(newSpell); } diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index deb971e6d4..1da9635db3 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -86,6 +86,9 @@ namespace MWGui && !item.getClass().getEquipmentSlots(item).first.empty()) { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); + // make sure that item was successfully equipped + if (!store.isEquipped(item)) + return; } MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index 3abfac997b..df0cbcac95 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -175,17 +175,8 @@ namespace MWGui // don't show equipped items if(mMerchant.getClass().hasInventoryStore(mMerchant)) { - bool isEquipped = false; MWWorld::InventoryStore& store = mMerchant.getClass().getInventoryStore(mMerchant); - for (int slot=0; slotgetStore().get().find(ptr.getClass().getEnchantment(ptr)); + if (enchantment->mData.mType == ESM::Enchantment::CastOnce) { return rateEffects(enchantment->mEffects, actor, target); } else + { + //if (!ptr.getClass().canBeEquipped(ptr, actor)) return 0.f; + } } float rateEffect(const ESM::ENAMstruct &effect, const MWWorld::Ptr &actor, const MWWorld::Ptr &target) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index c577d4b0d2..fef34d67be 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -643,3 +643,13 @@ void MWWorld::InventoryStore::clear() initSlots (mSlots); ContainerStore::clear(); } + +bool MWWorld::InventoryStore::isEquipped(const MWWorld::Ptr &item) +{ + for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) + { + if (getSlot(i) != end() && *getSlot(i) == item) + return true; + } + return false; +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 16d965cda3..30abc2ea57 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -145,6 +145,9 @@ namespace MWWorld void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor); ///< \warning \a iterator can not be an end()-iterator, use unequip function instead + bool isEquipped(const MWWorld::Ptr& item); + ///< Utility function, returns true if the given item is equipped in any slot + void setSelectedEnchantItem(const ContainerStoreIterator& iterator); ///< set the selected magic item (for using enchantments of type "Cast once" or "Cast when used") /// \note to unset the selected item, call this method with end() iterator From 935cccf9745dc04c37a85321bed9adef557f9831 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Dec 2014 15:23:03 +0100 Subject: [PATCH 149/167] Implement weapon/spell cycling hotkeys (Fixes #1024) --- apps/openmw/mwbase/windowmanager.hpp | 5 +++ apps/openmw/mwgui/inventorywindow.cpp | 48 +++++++++++++++++++++++ apps/openmw/mwgui/inventorywindow.hpp | 3 ++ apps/openmw/mwgui/sortfilteritemmodel.cpp | 32 +++++++++------ apps/openmw/mwgui/sortfilteritemmodel.hpp | 4 ++ apps/openmw/mwgui/spellmodel.cpp | 4 ++ apps/openmw/mwgui/spellwindow.cpp | 21 ++++++++++ apps/openmw/mwgui/spellwindow.hpp | 3 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 10 +++++ apps/openmw/mwgui/windowmanagerimp.hpp | 5 +++ apps/openmw/mwinput/inputmanagerimp.cpp | 25 ++++++++++++ 11 files changed, 148 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index d4f1afa32d..fb35157e85 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -342,6 +342,11 @@ namespace MWBase virtual void setWerewolfOverlay(bool set) = 0; virtual void toggleDebugWindow() = 0; + + /// Cycle to next or previous spell + virtual void cycleSpell(bool next) = 0; + /// Cycle to next or previous weapon + virtual void cycleWeapon(bool next) = 0; }; } diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index d1eeba1573..4fbfc2c67a 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -27,6 +27,19 @@ #include "tradewindow.hpp" #include "container.hpp" +namespace +{ + + bool isRightHandWeapon(const MWWorld::Ptr& item) + { + if (item.getClass().getTypeName() != typeid(ESM::Weapon).name()) + return false; + std::vector equipmentSlots = item.getClass().getEquipmentSlots(item).first; + return (!equipmentSlots.empty() && equipmentSlots.front() == MWWorld::InventoryStore::Slot_CarriedRight); + } + +} + namespace MWGui { @@ -599,4 +612,39 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager()->itemTaken(player, newObject, count); } + + void InventoryWindow::cycle(bool next) + { + ItemModel::ModelIndex selected = 0; + // not using mSortFilterModel as we only need sorting, not filtering + SortFilterItemModel model(new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + model.setSortByType(false); + model.update(); + if (model.getItemCount() == 0) + return; + for (ItemModel::ModelIndex i=0; imData.mCost; int eSkill = mActor.getClass().getSkill(mActor, ESM::Skill::Enchant); int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 1da9635db3..37747f957d 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -160,4 +160,25 @@ namespace MWGui updateSpells(); } + + void SpellWindow::cycle(bool next) + { + mSpellView->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mSpellView->getModel()->update(); + + SpellModel::ModelIndex selected = 0; + for (SpellModel::ModelIndex i = 0; igetModel()->getItemCount()); ++i) + { + if (mSpellView->getModel()->getItem(i).mSelected) + selected = i; + } + + selected += next ? 1 : -1; + int itemcount = mSpellView->getModel()->getItemCount(); + if (itemcount == 0) + return; + selected = (selected + itemcount) % itemcount; + + onModelIndexSelected(selected); + } } diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index 2f7319cb92..650218d307 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -21,6 +21,9 @@ namespace MWGui void onFrame(float dt) { NoDrop::onFrame(dt); } + /// Cycle to next/previous spell + void cycle(bool next); + protected: MyGUI::Widget* mEffectBox; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 26f14572f2..9d24fb19bd 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1820,4 +1820,14 @@ namespace MWGui mDebugWindow->setVisible(!mDebugWindow->isVisible()); } + void WindowManager::cycleSpell(bool next) + { + mSpellWindow->cycle(next); + } + + void WindowManager::cycleWeapon(bool next) + { + mInventoryWindow->cycle(next); + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 94d8a93db2..015f200d58 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -338,6 +338,11 @@ namespace MWGui virtual void toggleDebugWindow(); + /// Cycle to next or previous spell + virtual void cycleSpell(bool next); + /// Cycle to next or previous weapon + virtual void cycleWeapon(bool next); + private: bool mConsoleOnlyScripts; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index b0d2bfefff..2f6149f091 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -286,6 +286,18 @@ namespace MWInput case A_QuickLoad: quickLoad(); break; + case A_CycleSpellLeft: + MWBase::Environment::get().getWindowManager()->cycleSpell(false); + break; + case A_CycleSpellRight: + MWBase::Environment::get().getWindowManager()->cycleSpell(true); + break; + case A_CycleWeaponLeft: + MWBase::Environment::get().getWindowManager()->cycleWeapon(false); + break; + case A_CycleWeaponRight: + MWBase::Environment::get().getWindowManager()->cycleWeapon(true); + break; } } } @@ -894,6 +906,11 @@ namespace MWInput defaultKeyBindings[A_MoveRight] = SDL_SCANCODE_D; defaultKeyBindings[A_ToggleWeapon] = SDL_SCANCODE_F; defaultKeyBindings[A_ToggleSpell] = SDL_SCANCODE_R; + defaultKeyBindings[A_CycleSpellLeft] = SDL_SCANCODE_MINUS; + defaultKeyBindings[A_CycleSpellRight] = SDL_SCANCODE_EQUALS; + defaultKeyBindings[A_CycleWeaponLeft] = SDL_SCANCODE_LEFTBRACKET; + defaultKeyBindings[A_CycleWeaponRight] = SDL_SCANCODE_RIGHTBRACKET; + defaultKeyBindings[A_QuickKeysMenu] = SDL_SCANCODE_F1; defaultKeyBindings[A_Console] = SDL_SCANCODE_GRAVE; defaultKeyBindings[A_Run] = SDL_SCANCODE_LSHIFT; @@ -972,6 +989,10 @@ namespace MWInput descriptions[A_MoveRight] = "sRight"; descriptions[A_ToggleWeapon] = "sReady_Weapon"; descriptions[A_ToggleSpell] = "sReady_Magic"; + descriptions[A_CycleSpellLeft] = "sPrevSpell"; + descriptions[A_CycleSpellRight] = "sNextSpell"; + descriptions[A_CycleWeaponLeft] = "sPrevWeapon"; + descriptions[A_CycleWeaponRight] = "sNextWeapon"; descriptions[A_Console] = "sConsoleTitle"; descriptions[A_Run] = "sRun"; descriptions[A_Sneak] = "sCrouch_Sneak"; @@ -1032,6 +1053,10 @@ namespace MWInput ret.push_back(A_Use); ret.push_back(A_ToggleWeapon); ret.push_back(A_ToggleSpell); + ret.push_back(A_CycleSpellLeft); + ret.push_back(A_CycleSpellRight); + ret.push_back(A_CycleWeaponLeft); + ret.push_back(A_CycleWeaponRight); ret.push_back(A_AutoMove); ret.push_back(A_Jump); ret.push_back(A_Inventory); From c7e1c0b595dd88c4fc61134a00bb3266ba156455 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Dec 2014 17:38:32 +0100 Subject: [PATCH 150/167] Fix weapon cycle getting stuck for same item IDs --- apps/openmw/mwgui/inventorywindow.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 4fbfc2c67a..d3c6073f47 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -615,13 +615,14 @@ namespace MWGui void InventoryWindow::cycle(bool next) { - ItemModel::ModelIndex selected = 0; + ItemModel::ModelIndex selected = -1; // not using mSortFilterModel as we only need sorting, not filtering SortFilterItemModel model(new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); model.setSortByType(false); model.update(); if (model.getItemCount() == 0) return; + for (ItemModel::ModelIndex i=0; i Date: Mon, 15 Dec 2014 18:19:05 +0100 Subject: [PATCH 151/167] Work around particles not being rendered in the first frame --- components/nifogre/ogrenifloader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index cdbc4d8447..22685f5489 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -1033,7 +1033,7 @@ class NIFObjectLoader static void createParticleInitialState(Ogre::ParticleSystem* partsys, const Nif::NiAutoNormalParticlesData* particledata, const Nif::NiParticleSystemController* partctrl) { - partsys->_update(0.f); // seems to be required to allocate mFreeParticles + partsys->_update(0.f); // seems to be required to allocate mFreeParticles. TODO: patch Ogre to handle this better int i=0; for (std::vector::const_iterator it = partctrl->particles.begin(); iactiveCount && it != partctrl->particles.end(); ++it, ++i) @@ -1073,6 +1073,7 @@ class NIFObjectLoader totalTimeToLive = std::max(0.f, particle.lifespan); timeToLive = std::max(0.f, particle.lifespan - particle.lifetime); } + partsys->_update(0.f); // now apparently needs another update, otherwise it won't render in the first frame. TODO: patch Ogre to handle this better } static void createNodeControllers(const Nif::NIFFilePtr& nif, const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags) From e4127aa491fa39eac68b43fc5fd4c4301d085200 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Dec 2014 19:04:53 +0100 Subject: [PATCH 152/167] Use space in ItemView more efficiently --- apps/openmw/mwgui/itemview.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index a51ada2754..6af6a3b3f6 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -53,9 +53,14 @@ void ItemView::layoutWidgets() int x = 0; int y = 0; - int maxHeight = mScrollView->getSize().height - 58; - MyGUI::Widget* dragArea = mScrollView->getChildAt(0); + int maxHeight = dragArea->getHeight(); + + int rows = maxHeight/42; + rows = std::max(rows, 1); + bool showScrollbar = std::ceil(dragArea->getChildCount()/float(rows)) > mScrollView->getWidth()/42; + if (showScrollbar) + maxHeight -= 18; for (unsigned int i=0; igetChildCount(); ++i) { @@ -64,7 +69,8 @@ void ItemView::layoutWidgets() w->setPosition(x, y); y += 42; - if (y > maxHeight) + + if (y > maxHeight-42 && i < dragArea->getChildCount()-1) { x += 42; y = 0; From ec00c830e5a1ae8afc195a8149ae4ba0e62fb6c2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Dec 2014 19:18:14 +0100 Subject: [PATCH 153/167] Fix missing armor rating label update --- apps/openmw/mwgui/inventorywindow.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index d3c6073f47..c1a5ab94a5 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -527,6 +527,14 @@ namespace MWGui void InventoryWindow::doRenderUpdate () { mPreview->onFrame(); + + if (mPreviewResize || mPreviewDirty) + { + mArmorRating->setCaptionWithReplacing ("#{sArmor}: " + + boost::lexical_cast(static_cast(mPtr.getClass().getArmorRating(mPtr)))); + if (mArmorRating->getTextSize().width > mArmorRating->getSize().width) + mArmorRating->setCaptionWithReplacing (boost::lexical_cast(static_cast(mPtr.getClass().getArmorRating(mPtr)))); + } if (mPreviewResize) { mPreviewResize = false; @@ -543,11 +551,6 @@ namespace MWGui mPreview->update (); mAvatarImage->setImageTexture("CharacterPreview"); - - mArmorRating->setCaptionWithReplacing ("#{sArmor}: " - + boost::lexical_cast(static_cast(mPtr.getClass().getArmorRating(mPtr)))); - if (mArmorRating->getTextSize().width > mArmorRating->getSize().width) - mArmorRating->setCaptionWithReplacing (boost::lexical_cast(static_cast(mPtr.getClass().getArmorRating(mPtr)))); } } From 0dc94012698a2713fc89ae2526a1652d6a51d633 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Dec 2014 20:20:17 +0100 Subject: [PATCH 154/167] Fix GUI crash due to outdated spells list --- apps/openmw/mwgui/container.cpp | 2 ++ apps/openmw/mwgui/inventorywindow.cpp | 6 ++++++ apps/openmw/mwgui/spellwindow.cpp | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 58f23c175b..6df8a3f448 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -124,6 +124,8 @@ namespace MWGui if (targetView) targetView->update(); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); + // We need to update the view since an other item could be auto-equipped. mSourceView->update(); } diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index c1a5ab94a5..60f40e6fb3 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -309,6 +309,9 @@ namespace MWGui void InventoryWindow::updateItemView() { + if (MWBase::Environment::get().getWindowManager()->getSpellWindow()) + MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells(); + mItemView->update(); mPreviewDirty = true; } @@ -614,6 +617,9 @@ namespace MWGui mDragAndDrop->startDrag(i, mSortModel, mTradeModel, mItemView, count); MWBase::Environment::get().getMechanicsManager()->itemTaken(player, newObject, count); + + if (MWBase::Environment::get().getWindowManager()->getSpellWindow()) + MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells(); } void InventoryWindow::cycle(bool next) diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 37747f957d..240d0419e3 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -79,7 +79,8 @@ namespace MWGui break; } } - assert(it != store.end()); + if (it == store.end()) + throw std::runtime_error("can't find selected item"); // equip, if it can be equipped and is not already equipped if (!alreadyEquipped From 830ecfc70b9bfa7804addf94eb5211bee91e06c5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 16 Dec 2014 11:27:13 +0100 Subject: [PATCH 155/167] updated changelog once more --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index f5e3e0e59b..5d344c0b50 100644 --- a/readme.txt +++ b/readme.txt @@ -163,6 +163,7 @@ Bug #2163: OpenMW can't detect death if the NPC die by the post damage effect of Bug #2168: Westly's Master Head Pack X – Some hairs aren't rendered correctly. Bug #2170: Mods using conversations to update PC inconsistant Bug #2180: Editor: Verifier doesn't handle Windows-specific path issues when dealing with resources +Bug #2212: Crash or unexpected behavior while closing OpenCS cell render window on OS X Feature #238: Add UI to run INI-importer from the launcher Feature #854: Editor: Add user setting to show status bar Feature #987: Launcher: first launch instructions for CD need to be more explicit From b9e5aa9db677a4ab0dd3913c6213c1a5cad39d97 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 16 Dec 2014 20:44:42 +0100 Subject: [PATCH 156/167] Movement controller: Don't allow stepping up other actors This seems to fix issues with NPCs inadvertently being placed on top of a small creature while fighting it. Note that jumping on top of actors is still possible (Bug #1192) --- apps/openmw/mwworld/physicssystem.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 61a3b27f81..aeaa07a925 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -195,6 +195,9 @@ namespace MWWorld stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-Ogre::Vector3(0.0f,0.0f,sStepSize), engine); if(stepper.mFraction < 1.0f && getSlope(stepper.mPlaneNormal) <= sMaxSlope) { + // don't allow stepping up other actors + if (stepper.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == OEngine::Physic::CollisionType_Actor) + return false; // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. // TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing // NOTE: caller's variables 'position' & 'remainingTime' are modified here From d962f0918dee426a7dce21cf7719de0c6fe0afae Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 16 Dec 2014 20:47:45 +0100 Subject: [PATCH 157/167] Implement NPC head tracking (Fixes #1720) --- apps/openmw/mwmechanics/actors.cpp | 51 ++++++++++++++++++++++ apps/openmw/mwmechanics/actors.hpp | 3 ++ apps/openmw/mwmechanics/character.cpp | 62 +++++++++++++++++++++++++++ apps/openmw/mwmechanics/character.hpp | 7 +++ apps/openmw/mwrender/animation.hpp | 4 ++ apps/openmw/mwrender/npcanimation.cpp | 31 +++++++++++++- apps/openmw/mwrender/npcanimation.hpp | 8 ++++ 7 files changed, 165 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index fecf189861..3a9ba56184 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -4,6 +4,7 @@ #include #include +#include #include @@ -273,6 +274,40 @@ namespace MWMechanics calculateRestoration(ptr, duration); } + void Actors::updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, + MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance) + { + static const float fMaxHeadTrackDistance = MWBase::Environment::get().getWorld()->getStore().get() + .find("fMaxHeadTrackDistance")->getFloat(); + static const float fInteriorHeadTrackMult = MWBase::Environment::get().getWorld()->getStore().get() + .find("fInteriorHeadTrackMult")->getFloat(); + float maxDistance = fMaxHeadTrackDistance; + const ESM::Cell* currentCell = actor.getCell()->getCell(); + if (!currentCell->isExterior() && !(currentCell->mData.mFlags & ESM::Cell::QuasiEx)) + maxDistance *= fInteriorHeadTrackMult; + + const ESM::Position& actor1Pos = actor.getRefData().getPosition(); + const ESM::Position& actor2Pos = targetActor.getRefData().getPosition(); + float sqrDist = Ogre::Vector3(actor1Pos.pos).squaredDistance(Ogre::Vector3(actor2Pos.pos)); + + if (sqrDist > maxDistance*maxDistance) + return; + + // stop tracking when target is behind the actor + Ogre::Vector3 actorDirection (actor.getRefData().getBaseNode()->getOrientation().yAxis()); + Ogre::Vector3 targetDirection (Ogre::Vector3(actor2Pos.pos) - Ogre::Vector3(actor1Pos.pos)); + actorDirection.z = 0; + targetDirection.z = 0; + if (actorDirection.angleBetween(targetDirection) < Ogre::Degree(90) + && sqrDist <= sqrHeadTrackDistance + && MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) // check LOS and awareness last as it's the most expensive function + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor)) + { + sqrHeadTrackDistance = sqrDist; + headTrackTarget = targetActor; + } + } + void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer) { CreatureStats& creatureStats = actor1.getClass().getCreatureStats(actor1); @@ -1138,9 +1173,11 @@ namespace MWMechanics if(!paused) { static float timerUpdateAITargets = 0; + static float timerUpdateHeadTrack = 0; // target lists get updated once every 1.0 sec if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0; + if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -1174,6 +1211,19 @@ namespace MWMechanics engageCombat(iter->first, it->first, it->first == player); } } + if (timerUpdateHeadTrack == 0) + { + float sqrHeadTrackDistance = std::numeric_limits::max(); + MWWorld::Ptr headTrackTarget; + + for(PtrControllerMap::iterator it(mActors.begin()); it != mActors.end(); ++it) + { + if (it->first == iter->first) + continue; + updateHeadTracking(iter->first, it->first, headTrackTarget, sqrHeadTrackDistance); + } + iter->second->setHeadTrackTarget(headTrackTarget); + } if (iter->first.getClass().isNpc() && iter->first != player) updateCrimePersuit(iter->first, duration); @@ -1194,6 +1244,7 @@ namespace MWMechanics } timerUpdateAITargets += duration; + timerUpdateHeadTrack += duration; // Looping magic VFX update // Note: we need to do this before any of the animations are updated. diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index a30a9dcf01..321229571f 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -89,6 +89,9 @@ namespace MWMechanics */ void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer); + void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, + MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance); + void restoreDynamicStats(bool sleep); ///< If the player is sleeping, this should be called every hour. diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d8693fbcdf..d675b0157e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -42,6 +42,15 @@ namespace { +// Wraps a value to (-PI, PI] +void wrap(Ogre::Radian& rad) +{ + if (rad.valueRadians()>0) + rad = Ogre::Radian(std::fmod(rad.valueRadians()+Ogre::Math::PI, 2.0f*Ogre::Math::PI)-Ogre::Math::PI); + else + rad = Ogre::Radian(std::fmod(rad.valueRadians()-Ogre::Math::PI, 2.0f*Ogre::Math::PI)+Ogre::Math::PI); +} + std::string getBestAttack (const ESM::Weapon* weapon) { int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; @@ -1627,6 +1636,8 @@ void CharacterController::update(float duration) cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; // Can't reset jump state (mPosition[2]) here; we don't know for sure whether the PhysicSystem will actually handle it in this frame // due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled. + + updateHeadTracking(duration); } else if(cls.getCreatureStats(mPtr).isDead()) { @@ -1845,4 +1856,55 @@ bool CharacterController::isKnockedOut() const return mHitState == CharState_KnockOut; } +void CharacterController::setHeadTrackTarget(const MWWorld::Ptr &target) +{ + mHeadTrackTarget = target; +} + +void CharacterController::updateHeadTracking(float duration) +{ + Ogre::Node* head = mAnimation->getNode("Bip01 Head"); + if (!head) + return; + Ogre::Radian zAngle (0.f); + Ogre::Radian xAngle (0.f); + if (!mHeadTrackTarget.isEmpty()) + { + Ogre::Vector3 headPos = mPtr.getRefData().getBaseNode()->convertLocalToWorldPosition(head->_getDerivedPosition()); + Ogre::Vector3 targetPos (mHeadTrackTarget.getRefData().getPosition().pos); + if (MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) + { + Ogre::Node* targetHead = anim->getNode("Head"); + if (!targetHead) + targetHead = anim->getNode("Bip01 Head"); + if (targetHead) + targetPos = mHeadTrackTarget.getRefData().getBaseNode()->convertLocalToWorldPosition( + targetHead->_getDerivedPosition()); + } + + Ogre::Vector3 direction = targetPos - headPos; + direction.normalise(); + + const Ogre::Vector3 actorDirection = mPtr.getRefData().getBaseNode()->getOrientation().yAxis(); + + zAngle = Ogre::Math::ATan2(direction.x,direction.y) - + Ogre::Math::ATan2(actorDirection.x, actorDirection.y); + xAngle = -Ogre::Math::ASin(direction.z); + wrap(zAngle); + wrap(xAngle); + xAngle = Ogre::Degree(std::min(xAngle.valueDegrees(), 40.f)); + xAngle = Ogre::Degree(std::max(xAngle.valueDegrees(), -40.f)); + zAngle = Ogre::Degree(std::min(zAngle.valueDegrees(), 30.f)); + zAngle = Ogre::Degree(std::max(zAngle.valueDegrees(), -30.f)); + + } + float factor = duration*5; + factor = std::min(factor, 1.f); + xAngle = (1.f-factor) * mAnimation->getHeadPitch() + factor * (-xAngle); + zAngle = (1.f-factor) * mAnimation->getHeadYaw() + factor * (-zAngle); + + mAnimation->setHeadPitch(xAngle); + mAnimation->setHeadYaw(zAngle); +} + } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index d7028a8441..5b2c57b0a9 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -175,6 +175,8 @@ class CharacterController float mSecondsOfSwimming; float mSecondsOfRunning; + MWWorld::Ptr mHeadTrackTarget; + float mTurnAnimationThreshold; // how long to continue playing turning animation after actor stopped turning std::string mAttackType; // slash, chop or thrust @@ -188,6 +190,8 @@ class CharacterController bool updateCreatureState(); void updateIdleStormState(); + void updateHeadTracking(float duration); + void castSpell(const std::string& spellid); void updateMagicEffects(); @@ -229,6 +233,9 @@ public: bool isReadyToBlock() const; bool isKnockedOut() const; + + /// Make this character turn its head towards \a target. To turn off head tracking, pass an empty Ptr. + void setHeadTrackTarget(const MWWorld::Ptr& target); }; void getWeaponGroup(WeaponType weaptype, std::string &group); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index d25d4f0b0e..1a420582cd 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -306,6 +306,10 @@ public: /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. virtual void setPitchFactor(float factor) {} + virtual void setHeadPitch(Ogre::Radian factor) {} + virtual void setHeadYaw(Ogre::Radian factor) {} + virtual Ogre::Radian getHeadPitch() const { return Ogre::Radian(0.f); } + virtual Ogre::Radian getHeadYaw() const { return Ogre::Radian(0.f); } virtual Ogre::Vector3 runAnimation(float duration); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index b93b37aeac..7c68a45380 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -207,7 +207,9 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v mFirstPersonOffset(0.f, 0.f, 0.f), mAlpha(1.f), mNpcType(Type_Normal), - mSoundsDisabled(disableSounds) + mSoundsDisabled(disableSounds), + mHeadPitch(0.f), + mHeadYaw(0.f) { mNpc = mPtr.get()->mBase; @@ -621,6 +623,13 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) { // In third person mode we may still need pitch for ranged weapon targeting pitchSkeleton(mPtr.getRefData().getPosition().rot[0], baseinst); + + Ogre::Node* node = baseinst->getBone("Bip01 Head"); + if (node) + { + node->rotate(Ogre::Vector3::UNIT_Z, mHeadYaw, Ogre::Node::TS_WORLD); + node->rotate(Ogre::Vector3::UNIT_X, mHeadPitch, Ogre::Node::TS_WORLD); + } } mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. @@ -993,4 +1002,24 @@ void NpcAnimation::setVampire(bool vampire) } } +void NpcAnimation::setHeadPitch(Ogre::Radian pitch) +{ + mHeadPitch = pitch; +} + +void NpcAnimation::setHeadYaw(Ogre::Radian yaw) +{ + mHeadYaw = yaw; +} + +Ogre::Radian NpcAnimation::getHeadPitch() const +{ + return mHeadPitch; +} + +Ogre::Radian NpcAnimation::getHeadYaw() const +{ + return mHeadYaw; +} + } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index aba01bcfaa..f3603fe14b 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -100,6 +100,9 @@ private: float mAlpha; bool mSoundsDisabled; + Ogre::Radian mHeadYaw; + Ogre::Radian mHeadPitch; + void updateNpcBase(); NifOgre::ObjectScenePtr insertBoundedPart(const std::string &model, int group, const std::string &bonename, @@ -142,6 +145,11 @@ public: /// to indicate the facing orientation of the character. virtual void setPitchFactor(float factor) { mPitchFactor = factor; } + virtual void setHeadPitch(Ogre::Radian pitch); + virtual void setHeadYaw(Ogre::Radian yaw); + virtual Ogre::Radian getHeadPitch() const; + virtual Ogre::Radian getHeadYaw() const; + virtual void showWeapons(bool showWeapon); virtual void showCarriedLeft(bool show); From 88c5e1991c13fe3f877335256139fc2040613352 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 16 Dec 2014 23:18:41 +0100 Subject: [PATCH 158/167] Fix being able to stand on top of actors (Fixes #1192) --- apps/openmw/mwworld/physicssystem.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index aeaa07a925..d9941bafdc 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -452,7 +452,8 @@ namespace MWWorld Ogre::Vector3 to = newPosition - (physicActor->getOnGround() ? Ogre::Vector3(0,0,sStepSize+2.f) : Ogre::Vector3(0,0,2.f)); tracer.doTrace(colobj, from, to, engine); - if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) + if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope + && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != OEngine::Physic::CollisionType_Actor) { const btCollisionObject* standingOn = tracer.mHitObject; if (const OEngine::Physic::RigidBody* body = dynamic_cast(standingOn)) @@ -468,7 +469,25 @@ namespace MWWorld isOnGround = true; } else + { + // standing on actors is not allowed (see above). + // in addition to that, apply a sliding effect away from the center of the actor, + // so that we do not stay suspended in air indefinitely. + if (tracer.mFraction < 1.0f && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == OEngine::Physic::CollisionType_Actor) + { + if (Ogre::Vector3(inertia.x, inertia.y, 0).squaredLength() < 100.f*100.f) + { + btVector3 aabbMin, aabbMax; + tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax); + btVector3 center = (aabbMin + aabbMax) / 2.f; + inertia = Ogre::Vector3(position.x - center.x(), position.y - center.y(), 0); + inertia.normalise(); + inertia *= 100; + } + } + isOnGround = false; + } } if(isOnGround || newPosition.z < waterlevel || isFlying) From d642512f71698ff91c297d32a5412bf01f4ffbd6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 17 Dec 2014 00:57:04 +0100 Subject: [PATCH 159/167] Error message fix --- apps/openmw/mwclass/creature.cpp | 5 ++--- apps/openmw/mwclass/npc.cpp | 11 ++++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 9d07aecf3f..5fd5f4dde0 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -120,11 +120,10 @@ namespace MWClass for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); iter!=ref->mBase->mSpells.mList.end(); ++iter) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter); - if (spell) + if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) data->mCreatureStats.getSpells().add (spell); else /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility - std::cerr << "Warning: ignoring nonexistent spell '" << spell->mId << "' on creature '" << ref->mBase->mId << "'" << std::endl; + std::cerr << "Warning: ignoring nonexistent spell '" << *iter << "' on creature '" << ref->mBase->mId << "'" << std::endl; } // inventory diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index a3ebef5821..4814879acd 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -300,8 +300,7 @@ namespace MWClass if (!ref->mBase->mFaction.empty()) { std::string faction = ref->mBase->mFaction; - const ESM::Faction* fact = MWBase::Environment::get().getWorld()->getStore().get().search(faction); - if (fact) + if (const ESM::Faction* fact = MWBase::Environment::get().getWorld()->getStore().get().search(faction)) { if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { @@ -313,7 +312,7 @@ namespace MWClass } } else - std::cerr << "Warning: ignoring nonexistent faction '" << fact->mId << "' on NPC '" << ref->mBase->mId << "'" << std::endl; + std::cerr << "Warning: ignoring nonexistent faction '" << faction << "' on NPC '" << ref->mBase->mId << "'" << std::endl; } // creature stats @@ -366,8 +365,7 @@ namespace MWClass for (std::vector::const_iterator iter (race->mPowers.mList.begin()); iter!=race->mPowers.mList.end(); ++iter) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter); - if (spell) + if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) data->mNpcStats.getSpells().add (spell); else std::cerr << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl; @@ -395,8 +393,7 @@ namespace MWClass for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); iter!=ref->mBase->mSpells.mList.end(); ++iter) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter); - if (spell) + if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) data->mNpcStats.getSpells().add (spell); else { From 31d28e727f004d944c40c58e5f3822415192a265 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 17 Dec 2014 01:05:32 +0100 Subject: [PATCH 160/167] Implement leveled list script functions (Fixes #1546) --- apps/openmw/mwbase/world.hpp | 10 +++ apps/openmw/mwscript/docs/vmformat.txt | 6 +- apps/openmw/mwscript/miscextensions.cpp | 112 ++++++++++++++++++++++++ apps/openmw/mwstate/statemanagerimp.cpp | 2 + apps/openmw/mwworld/esmstore.cpp | 8 +- apps/openmw/mwworld/esmstore.hpp | 15 ++++ apps/openmw/mwworld/store.hpp | 10 +-- apps/openmw/mwworld/worldimp.cpp | 10 +++ apps/openmw/mwworld/worldimp.hpp | 8 ++ components/compiler/extensions0.cpp | 4 + components/compiler/opcodes.hpp | 4 + 11 files changed, 182 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 34be298d3a..32beadf18a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -40,6 +40,8 @@ namespace ESM struct Enchantment; struct Book; struct EffectList; + struct CreatureLevList; + struct ItemLevList; } namespace MWRender @@ -359,6 +361,14 @@ namespace MWBase ///< Create a new record (of type book) in the ESM store. /// \return pointer to created record + virtual const ESM::CreatureLevList *createOverrideRecord (const ESM::CreatureLevList& record) = 0; + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + + virtual const ESM::ItemLevList *createOverrideRecord (const ESM::ItemLevList& record) = 0; + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + virtual void update (float duration, bool paused) = 0; virtual MWWorld::Ptr placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) = 0; diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 800c6e2c7b..172e1b528a 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -438,5 +438,9 @@ op 0x20002f7: PCForce3rdPerson op 0x20002f8: PCGet3rdPerson op 0x20002f9: HitAttemptOnMe op 0x20002fa: HitAttemptOnMe, explicit +op 0x20002fb: AddToLevCreature +op 0x20002fc: RemoveFromLevCreature +op 0x20002fd: AddToLevItem +op 0x20002fe: RemoveFromLevItem -opcodes 0x20002fb-0x3ffffff unused +opcodes 0x20002ff-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index c92acff820..f20c9967df 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -31,6 +31,42 @@ #include "interpretercontext.hpp" #include "ref.hpp" +namespace +{ + + void addToLevList(ESM::LeveledListBase* list, const std::string& itemId, int level) + { + for (std::vector::iterator it = list->mList.begin(); it != list->mList.end();) + { + if (it->mLevel == level && itemId == it->mId) + return; + } + + ESM::LeveledListBase::LevelItem item; + item.mId = itemId; + item.mLevel = level; + list->mList.push_back(item); + } + + void removeFromLevList(ESM::LeveledListBase* list, const std::string& itemId, int level) + { + // level of -1 removes all items with that itemId + for (std::vector::iterator it = list->mList.begin(); it != list->mList.end();) + { + if (level != -1 && it->mLevel != level) + { + ++it; + continue; + } + if (Misc::StringUtils::ciEqual(itemId, it->mId)) + it = list->mList.erase(it); + else + ++it; + } + } + +} + namespace MWScript { namespace Misc @@ -1032,6 +1068,78 @@ namespace MWScript } }; + class OpAddToLevCreature : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + const std::string& creatureId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + int level = runtime[0].mInteger; + runtime.pop(); + + ESM::CreatureLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); + addToLevList(&listCopy, creatureId, level); + MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); + } + }; + + class OpRemoveFromLevCreature : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + const std::string& creatureId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + int level = runtime[0].mInteger; + runtime.pop(); + + ESM::CreatureLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); + removeFromLevList(&listCopy, creatureId, level); + MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); + } + }; + + class OpAddToLevItem : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + const std::string& itemId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + int level = runtime[0].mInteger; + runtime.pop(); + + ESM::ItemLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); + addToLevList(&listCopy, itemId, level); + MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); + } + }; + + class OpRemoveFromLevItem : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + const std::string& itemId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + int level = runtime[0].mInteger; + runtime.pop(); + + ESM::ItemLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); + removeFromLevList(&listCopy, itemId, level); + MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); @@ -1121,6 +1229,10 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeGetPcTraveling, new OpGetPcTraveling); interpreter.installSegment5 (Compiler::Misc::opcodeBetaComment, new OpBetaComment); interpreter.installSegment5 (Compiler::Misc::opcodeBetaCommentExplicit, new OpBetaComment); + interpreter.installSegment5 (Compiler::Misc::opcodeAddToLevCreature, new OpAddToLevCreature); + interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevCreature, new OpRemoveFromLevCreature); + interpreter.installSegment5 (Compiler::Misc::opcodeAddToLevItem, new OpAddToLevItem); + interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevItem, new OpRemoveFromLevItem); } } } diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 18ebe11cee..9746202dc3 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -352,6 +352,8 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl case ESM::REC_PROJ: case ESM::REC_MPRJ: case ESM::REC_ENAB: + case ESM::REC_LEVC: + case ESM::REC_LEVI: MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); break; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 56cb05c646..2a3fd9179e 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -149,7 +149,9 @@ void ESMStore::setUp() +mEnchants.getDynamicSize() +mNpcs.getDynamicSize() +mSpells.getDynamicSize() - +mWeapons.getDynamicSize(); + +mWeapons.getDynamicSize() + +mCreatureLists.getDynamicSize() + +mItemLists.getDynamicSize(); } void ESMStore::write (ESM::ESMWriter& writer, Loading::Listener& progress) const @@ -170,6 +172,8 @@ void ESMStore::setUp() mSpells.write (writer, progress); mWeapons.write (writer, progress); mNpcs.write (writer, progress); + mItemLists.write (writer, progress); + mCreatureLists.write (writer, progress); } bool ESMStore::readRecord (ESM::ESMReader& reader, int32_t type) @@ -185,6 +189,8 @@ void ESMStore::setUp() case ESM::REC_SPEL: case ESM::REC_WEAP: case ESM::REC_NPC_: + case ESM::REC_LEVI: + case ESM::REC_LEVC: mStores[type]->read (reader); diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 83e911174d..5d794db895 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -167,6 +167,7 @@ namespace MWWorld throw std::runtime_error("Storage for this type not exist"); } + /// Insert a custom record (i.e. with a generated ID that will not clash will pre-existing records) template const T *insert(const T &x) { std::ostringstream id; @@ -191,6 +192,20 @@ namespace MWWorld return ptr; } + /// Insert a record with set ID, and allow it to override a pre-existing static record. + template + const T *overrideRecord(const T &x) { + Store &store = const_cast &>(get()); + + T *ptr = store.insert(x); + for (iterator it = mStores.begin(); it != mStores.end(); ++it) { + if (it->second == &store) { + mIds[ptr->mId] = it->first; + } + } + return ptr; + } + template const T *insertStatic(const T &x) { std::ostringstream id; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index c4070f0327..a0d34b228d 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -145,17 +145,17 @@ namespace MWWorld T item; item.mId = Misc::StringUtils::lowerCase(id); + typename Dynamic::const_iterator dit = mDynamic.find(item.mId); + if (dit != mDynamic.end()) { + return &dit->second; + } + typename std::map::const_iterator it = mStatic.find(item.mId); if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { return &(it->second); } - typename Dynamic::const_iterator dit = mDynamic.find(item.mId); - if (dit != mDynamic.end()) { - return &dit->second; - } - return 0; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e24e99c305..17a45f9f12 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1490,6 +1490,16 @@ namespace MWWorld return mStore.insert(record); } + const ESM::CreatureLevList *World::createOverrideRecord(const ESM::CreatureLevList &record) + { + return mStore.overrideRecord(record); + } + + const ESM::ItemLevList *World::createOverrideRecord(const ESM::ItemLevList &record) + { + return mStore.overrideRecord(record); + } + const ESM::NPC *World::createRecord(const ESM::NPC &record) { bool update = false; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index c8e254b2f9..2a0da917b9 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -420,6 +420,14 @@ namespace MWWorld ///< Create a new record (of type book) in the ESM store. /// \return pointer to created record + virtual const ESM::CreatureLevList *createOverrideRecord (const ESM::CreatureLevList& record); + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + + virtual const ESM::ItemLevList *createOverrideRecord (const ESM::ItemLevList& record); + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + virtual void update (float duration, bool paused); virtual MWWorld::Ptr placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount); diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 690e589f73..d08a2bb320 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -308,6 +308,10 @@ namespace Compiler extensions.registerFunction ("getpctraveling", 'l', "", opcodeGetPcTraveling); extensions.registerInstruction ("betacomment", "S", opcodeBetaComment, opcodeBetaCommentExplicit); extensions.registerInstruction ("bc", "S", opcodeBetaComment, opcodeBetaCommentExplicit); + extensions.registerInstruction ("addtolevcreature", "ccl", opcodeAddToLevCreature); + extensions.registerInstruction ("removefromlevcreature", "ccl", opcodeRemoveFromLevCreature); + extensions.registerInstruction ("addtolevitem", "ccl", opcodeAddToLevItem); + extensions.registerInstruction ("removefromlevitem", "ccl", opcodeRemoveFromLevItem); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index da79555e22..bbafd6b13b 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -284,6 +284,10 @@ namespace Compiler const int opcodeExplodeSpellExplicit = 0x200022a; const int opcodeGetPcInJail = 0x200023e; const int opcodeGetPcTraveling = 0x200023f; + const int opcodeAddToLevCreature = 0x20002fb; + const int opcodeRemoveFromLevCreature = 0x20002fc; + const int opcodeAddToLevItem = 0x20002fd; + const int opcodeRemoveFromLevItem = 0x20002fe; } namespace Sky From c2771bc8abf2024779d31afd998bf7f0b797b783 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 17 Dec 2014 02:15:40 +0100 Subject: [PATCH 161/167] Head tracking fix --- apps/openmw/mwrender/npcanimation.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 7c68a45380..3b0b4e08b2 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -626,10 +626,7 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) Ogre::Node* node = baseinst->getBone("Bip01 Head"); if (node) - { - node->rotate(Ogre::Vector3::UNIT_Z, mHeadYaw, Ogre::Node::TS_WORLD); - node->rotate(Ogre::Vector3::UNIT_X, mHeadPitch, Ogre::Node::TS_WORLD); - } + node->rotate(Ogre::Quaternion(mHeadYaw, Ogre::Vector3::UNIT_Z) * Ogre::Quaternion(mHeadPitch, Ogre::Vector3::UNIT_X), Ogre::Node::TS_WORLD); } mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. From 899ae763e667bbe98be1ce459c1e169eb51a3764 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 17 Dec 2014 09:33:40 +0100 Subject: [PATCH 162/167] fixing a travis build problem --- apps/openmw/mwgui/itemview.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index 6af6a3b3f6..ed2002d72c 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -1,5 +1,7 @@ #include "itemview.hpp" +#include + #include #include From 5cb94da9c5a8491d724c9d4eb950e38e3b9c44fa Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 17 Dec 2014 11:56:54 +0100 Subject: [PATCH 163/167] compensate for incorrect minus character in translated dialogue script (Fixes #2207) --- components/compiler/scanner.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 203f27e6e8..488906d8ff 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -445,6 +445,32 @@ namespace Compiler else special = S_minus; } + else if (static_cast (c)==0xe2) + { + /// Workaround for some translator who apparently can't keep his minus in order + /// \todo disable for later script formats + if (get (c) && static_cast (c)==0x80 && + get (c) && static_cast (c)==0x93) + { + if (get (c)) + { + if (c=='>') + special = S_ref; + else + { + putback (c); + special = S_minus; + } + } + else + special = S_minus; + } + else + { + mErrorHandler.error ("Invalid character", mLoc); + return false; + } + } else if (c=='<') { if (get (c)) From b951251572b1cf0f1c473615422d66a6f640aa35 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 17 Dec 2014 15:03:05 +0100 Subject: [PATCH 164/167] handle junk in argument lists (Fixes #2206) --- components/CMakeLists.txt | 4 +-- components/compiler/exprparser.cpp | 11 ++++++- components/compiler/exprparser.hpp | 3 +- components/compiler/extensions.hpp | 1 + components/compiler/extensions0.cpp | 2 +- components/compiler/junkparser.cpp | 48 +++++++++++++++++++++++++++++ components/compiler/junkparser.hpp | 41 ++++++++++++++++++++++++ components/compiler/lineparser.cpp | 2 +- 8 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 components/compiler/junkparser.cpp create mode 100644 components/compiler/junkparser.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 234325718d..6918b87a7a 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -70,7 +70,7 @@ add_component_dir (compiler context controlparser errorhandler exception exprparser extensions fileparser generator lineparser literals locals output parser scanner scriptparser skipparser streamerrorhandler stringparser tokenloc nullerrorhandler opcodes extensions0 declarationparser - quickfileparser discardparser + quickfileparser discardparser junkparser ) add_component_dir (interpreter @@ -123,7 +123,7 @@ if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) launchersettings settingsbase ) - + add_component_qt_dir (process processinvoker ) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 6dcca08df9..18d9e6dc7a 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -17,6 +17,7 @@ #include "extensions.hpp" #include "context.hpp" #include "discardparser.hpp" +#include "junkparser.hpp" namespace Compiler { @@ -752,7 +753,7 @@ namespace Compiler } int ExprParser::parseArguments (const std::string& arguments, Scanner& scanner, - std::vector& code) + std::vector& code, int ignoreKeyword) { bool optional = false; int optionalCount = 0; @@ -760,6 +761,7 @@ namespace Compiler ExprParser parser (getErrorHandler(), getContext(), mLocals, mLiterals, true); StringParser stringParser (getErrorHandler(), getContext(), mLiterals); DiscardParser discardParser (getErrorHandler(), getContext()); + JunkParser junkParser (getErrorHandler(), getContext(), ignoreKeyword); std::stack > stack; @@ -815,6 +817,13 @@ namespace Compiler if (discardParser.isEmpty()) break; } + else if (*iter=='j') + { + /// \todo disable this when operating in strict mode + junkParser.reset(); + + scanner.scan (junkParser); + } else { parser.reset(); diff --git a/components/compiler/exprparser.hpp b/components/compiler/exprparser.hpp index e4e385ff0f..639ca65aab 100644 --- a/components/compiler/exprparser.hpp +++ b/components/compiler/exprparser.hpp @@ -96,11 +96,12 @@ namespace Compiler /// \return Type ('l': integer, 'f': float) int parseArguments (const std::string& arguments, Scanner& scanner, - std::vector& code); + std::vector& code, int ignoreKeyword = -1); ///< Parse sequence of arguments specified by \a arguments. /// \param arguments Uses ScriptArgs typedef /// \see Compiler::ScriptArgs /// \param invert Store arguments in reverted order. + /// \param ignoreKeyword A keyword that is seen as junk /// \return number of optional arguments }; } diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp index a15218d99f..9fb9bdb95a 100644 --- a/components/compiler/extensions.hpp +++ b/components/compiler/extensions.hpp @@ -23,6 +23,7 @@ namespace Compiler x - Optional, ignored string argument X - Optional, ignored numeric expression z - Optional, ignored string or numeric argument + j - A piece of junk (either . or a specific keyword) **/ typedef std::string ScriptArgs; diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index d08a2bb320..234c5b12d4 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -179,7 +179,7 @@ namespace Compiler extensions.registerInstruction ("setjournalindex", "cl", opcodeSetJournalIndex); extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex); extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic); - extensions.registerInstruction ("choice", "/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); + extensions.registerInstruction ("choice", "j/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); extensions.registerInstruction("forcegreeting","",opcodeForceGreeting, opcodeForceGreetingExplicit); extensions.registerInstruction("goodbye", "", opcodeGoodbye); diff --git a/components/compiler/junkparser.cpp b/components/compiler/junkparser.cpp new file mode 100644 index 0000000000..cfa94044e7 --- /dev/null +++ b/components/compiler/junkparser.cpp @@ -0,0 +1,48 @@ + +#include "junkparser.hpp" + +#include "scanner.hpp" + +Compiler::JunkParser::JunkParser (ErrorHandler& errorHandler, const Context& context, + int ignoreKeyword) +: Parser (errorHandler, context), mIgnoreKeyword (ignoreKeyword) +{} + +bool Compiler::JunkParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) +{ + scanner.putbackInt (value, loc); + return false; +} + +bool Compiler::JunkParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner) +{ + scanner.putbackFloat (value, loc); + return false; +} + +bool Compiler::JunkParser::parseName (const std::string& name, const TokenLoc& loc, + Scanner& scanner) +{ + scanner.putbackName (name, loc); + return false; +} + +bool Compiler::JunkParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) +{ + if (keyword==mIgnoreKeyword) + reportWarning ("found junk (ignoring it)", loc); + else + scanner.putbackKeyword (keyword, loc); + + return false; +} + +bool Compiler::JunkParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) +{ + if (code==Scanner::S_member) + reportWarning ("found junk (ignoring it)", loc); + else + scanner.putbackSpecial (code, loc); + + return false; +} diff --git a/components/compiler/junkparser.hpp b/components/compiler/junkparser.hpp new file mode 100644 index 0000000000..6dfbd97af4 --- /dev/null +++ b/components/compiler/junkparser.hpp @@ -0,0 +1,41 @@ +#ifndef COMPILER_JUNKPARSER_H_INCLUDED +#define COMPILER_JUNKPARSER_H_INCLUDED + +#include "parser.hpp" + +namespace Compiler +{ + /// \brief Parse an optional single junk token + class JunkParser : public Parser + { + int mIgnoreKeyword; + + public: + + JunkParser (ErrorHandler& errorHandler, const Context& context, + int ignoreKeyword = -1); + + virtual bool parseInt (int value, const TokenLoc& loc, Scanner& scanner); + ///< Handle an int token. + /// \return fetch another token? + + virtual bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner); + ///< Handle a float token. + /// \return fetch another token? + + virtual bool parseName (const std::string& name, const TokenLoc& loc, + Scanner& scanner); + ///< Handle a name token. + /// \return fetch another token? + + virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner); + ///< Handle a keyword token. + /// \return fetch another token? + + virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner); + ///< Handle a special character token. + /// \return fetch another token? + }; +} + +#endif diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index dc19b9a4b0..2d08ed3fe7 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -304,7 +304,7 @@ namespace Compiler errorDowngrade.reset (new ErrorDowngrade (getErrorHandler())); std::vector code; - int optionals = mExprParser.parseArguments (argumentType, scanner, code); + int optionals = mExprParser.parseArguments (argumentType, scanner, code, keyword); mCode.insert (mCode.end(), code.begin(), code.end()); extensions->generateInstructionCode (keyword, mCode, mLiterals, mExplicit, optionals); From fc1d42a7d2d9b904092cfec7627c96f54a429022 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 18 Dec 2014 09:55:26 +0100 Subject: [PATCH 165/167] fixed exclusion for certain characters at the start of names --- components/compiler/scanner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 59fae3ccdc..9acba861a9 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -314,7 +314,7 @@ namespace Compiler bool Scanner::scanName (char c, std::string& name) { - bool first = false; + bool first = true; bool error = false; name.clear(); From a6d30bc2e3014c77b883de9ceab40d141078f78f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 18 Dec 2014 10:20:15 +0100 Subject: [PATCH 166/167] consider --script-warn when running with --script-all-dialogue --- apps/openmw/engine.cpp | 2 +- apps/openmw/mwdialogue/scripttest.cpp | 9 +++++---- apps/openmw/mwdialogue/scripttest.hpp | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 885d2bb4f1..6ea09d4c9d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -436,7 +436,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } if (mCompileAllDialogue) { - std::pair result = MWDialogue::ScriptTest::compileAll(&mExtensions); + std::pair result = MWDialogue::ScriptTest::compileAll(&mExtensions, mWarningsMode); if (result.first) std::cout << "compiled " << result.second << " of " << result.first << " dialogue script/actor combinations a(" diff --git a/apps/openmw/mwdialogue/scripttest.cpp b/apps/openmw/mwdialogue/scripttest.cpp index a8de21ef97..b2c8f536a2 100644 --- a/apps/openmw/mwdialogue/scripttest.cpp +++ b/apps/openmw/mwdialogue/scripttest.cpp @@ -21,7 +21,7 @@ namespace { -void test(const MWWorld::Ptr& actor, int &compiled, int &total, const Compiler::Extensions* extensions) +void test(const MWWorld::Ptr& actor, int &compiled, int &total, const Compiler::Extensions* extensions, int warningsMode) { MWDialogue::Filter filter(actor, 0, false); @@ -29,6 +29,7 @@ void test(const MWWorld::Ptr& actor, int &compiled, int &total, const Compiler:: compilerContext.setExtensions(extensions); std::ostream errorStream(std::cout.rdbuf()); Compiler::StreamErrorHandler errorHandler(errorStream); + errorHandler.setWarningsMode (warningsMode); const MWWorld::Store& dialogues = MWBase::Environment::get().getWorld()->getStore().get(); for (MWWorld::Store::iterator it = dialogues.begin(); it != dialogues.end(); ++it) @@ -100,21 +101,21 @@ namespace MWDialogue namespace ScriptTest { - std::pair compileAll(const Compiler::Extensions *extensions) + std::pair compileAll(const Compiler::Extensions *extensions, int warningsMode) { int compiled = 0, total = 0; const MWWorld::Store& npcs = MWBase::Environment::get().getWorld()->getStore().get(); for (MWWorld::Store::iterator it = npcs.begin(); it != npcs.end(); ++it) { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->mId); - test(ref.getPtr(), compiled, total, extensions); + test(ref.getPtr(), compiled, total, extensions, warningsMode); } const MWWorld::Store& creatures = MWBase::Environment::get().getWorld()->getStore().get(); for (MWWorld::Store::iterator it = creatures.begin(); it != creatures.end(); ++it) { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->mId); - test(ref.getPtr(), compiled, total, extensions); + test(ref.getPtr(), compiled, total, extensions, warningsMode); } return std::make_pair(total, compiled); } diff --git a/apps/openmw/mwdialogue/scripttest.hpp b/apps/openmw/mwdialogue/scripttest.hpp index 1ed94c76a5..0ac2597256 100644 --- a/apps/openmw/mwdialogue/scripttest.hpp +++ b/apps/openmw/mwdialogue/scripttest.hpp @@ -11,7 +11,7 @@ namespace ScriptTest /// Attempt to compile all dialogue scripts, use for verification purposes /// @return A pair containing -std::pair compileAll(const Compiler::Extensions* extensions); +std::pair compileAll(const Compiler::Extensions* extensions, int warningsMode); } From 120873a66d7607446c56d7338f81ae0536844d31 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 18 Dec 2014 10:40:51 +0100 Subject: [PATCH 167/167] another workaround for script translation messup --- components/compiler/scanner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 2aeaffd9c3..3fdbdb9f01 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -545,7 +545,7 @@ namespace Compiler { return std::isalpha (c) || std::isdigit (c) || c=='_' || /// \todo disable this when doing more stricter compiling - c=='`' || + c=='`' || c=='\'' || /// \todo disable this when doing more stricter compiling. Also, find out who is /// responsible for allowing it in the first place and meet up with that person in /// a dark alley.