diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index db45770e9..538f63dc9 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -62,7 +62,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat creaturestats magiceffects movement actors drawstate spells - activespells npcstats aipackage aisequence + activespells npcstats aipackage aisequence alchemy ) add_openmw_dir (mwbase diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 9b1a55025..617689bc6 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -423,21 +423,10 @@ void OMW::Engine::activate() if (handle.empty()) return; - // the faced handle is not updated immediately, so on a cell change it might - // point to an object that doesn't exist anymore - // therefore, we are catching the "Unknown Ogre handle" exception that occurs in this case - MWWorld::Ptr ptr; - try - { - ptr = MWBase::Environment::get().getWorld()->getPtrViaHandle (handle); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle (handle); - if (ptr.isEmpty()) - return; - } - catch (std::runtime_error&) - { + if (ptr.isEmpty()) return; - } MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr); diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 521bbb988..9dcdc312f 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -142,6 +142,9 @@ namespace MWBase virtual MWWorld::Ptr getPtrViaHandle (const std::string& handle) = 0; ///< Return a pointer to a liveCellRef with the given Ogre handle. + virtual MWWorld::Ptr searchPtrViaHandle (const std::string& handle) = 0; + ///< Return a pointer to a liveCellRef with the given Ogre handle or Ptr() if not found + /// \todo enable reference in the OGRE scene virtual void enable (const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 4a419c0c0..192bbd090 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -28,25 +28,25 @@ namespace MWGui { AlchemyWindow::AlchemyWindow(MWBase::WindowManager& parWindowManager) : WindowBase("openmw_alchemy_window.layout", parWindowManager) - , ContainerBase(0) + , ContainerBase(0), mApparatus (4), mIngredients (4) { getWidget(mCreateButton, "CreateButton"); getWidget(mCancelButton, "CancelButton"); - getWidget(mIngredient1, "Ingredient1"); - getWidget(mIngredient2, "Ingredient2"); - getWidget(mIngredient3, "Ingredient3"); - getWidget(mIngredient4, "Ingredient4"); - getWidget(mApparatus1, "Apparatus1"); - getWidget(mApparatus2, "Apparatus2"); - getWidget(mApparatus3, "Apparatus3"); - getWidget(mApparatus4, "Apparatus4"); + getWidget(mIngredients[0], "Ingredient1"); + getWidget(mIngredients[1], "Ingredient2"); + getWidget(mIngredients[2], "Ingredient3"); + getWidget(mIngredients[3], "Ingredient4"); + getWidget(mApparatus[0], "Apparatus1"); + getWidget(mApparatus[1], "Apparatus2"); + getWidget(mApparatus[2], "Apparatus3"); + getWidget(mApparatus[3], "Apparatus4"); getWidget(mEffectsBox, "CreatedEffects"); getWidget(mNameEdit, "NameEdit"); - mIngredient1->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); - mIngredient2->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); - mIngredient3->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); - mIngredient4->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); + mIngredients[0]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); + mIngredients[1]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); + mIngredients[2]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); + mIngredients[3]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); mCreateButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCreateButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked); @@ -62,157 +62,52 @@ namespace MWGui void AlchemyWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { + mAlchemy.clear(); + mWindowManager.removeGuiMode(GM_Alchemy); mWindowManager.removeGuiMode(GM_Inventory); } void AlchemyWindow::onCreateButtonClicked(MyGUI::Widget* _sender) { + std::string name = mNameEdit->getCaption(); + boost::algorithm::trim(name); + + MWMechanics::Alchemy::Result result = mAlchemy.create (mNameEdit->getCaption ()); + + if (result == MWMechanics::Alchemy::Result_NoName) + { + mWindowManager.messageBox("#{sNotifyMessage37}", std::vector()); + return; + } + // check if mortar & pestle is available (always needed) - /// \todo check albemic, calcinator, retort (sometimes needed) - if (!mApparatus1->isUserString("ToolTipType")) + if (result == MWMechanics::Alchemy::Result_NoMortarAndPestle) { mWindowManager.messageBox("#{sNotifyMessage45}", std::vector()); return; } // make sure 2 or more ingredients were selected - int numIngreds = 0; - if (mIngredient1->isUserString("ToolTipType")) - ++numIngreds; - if (mIngredient2->isUserString("ToolTipType")) - ++numIngreds; - if (mIngredient3->isUserString("ToolTipType")) - ++numIngreds; - if (mIngredient4->isUserString("ToolTipType")) - ++numIngreds; - if (numIngreds < 2) + if (result == MWMechanics::Alchemy::Result_LessThanTwoIngredients) { mWindowManager.messageBox("#{sNotifyMessage6a}", std::vector()); return; } - // make sure a name was entered - std::string name = mNameEdit->getCaption(); - boost::algorithm::trim(name); - if (name == "") - { - mWindowManager.messageBox("#{sNotifyMessage37}", std::vector()); - return; - } - - // if there are no created effects, the potion will always fail (but the ingredients won't be destroyed) - if (mEffects.empty()) + if (result == MWMechanics::Alchemy::Result_NoEffects) { mWindowManager.messageBox("#{sNotifyMessage8}", std::vector()); MWBase::Environment::get().getSoundManager()->playSound("potion fail", 1.f, 1.f); return; } - if (rand() % 2 == 0) /// \todo + if (result == MWMechanics::Alchemy::Result_Success) { - ESM::Potion newPotion; - newPotion.mName = mNameEdit->getCaption(); - ESM::EffectList effects; - for (unsigned int i=0; i<4; ++i) - { - if (mEffects.size() >= i+1) - { - ESM::ENAMstruct effect; - effect.mEffectID = mEffects[i].mEffectID; - effect.mArea = 0; - effect.mRange = ESM::RT_Self; - effect.mSkill = mEffects[i].mSkill; - effect.mAttribute = mEffects[i].mAttribute; - effect.mMagnMin = 1; /// \todo - effect.mMagnMax = 10; /// \todo - effect.mDuration = 60; /// \todo - effects.mList.push_back(effect); - } - } - - // UESP Wiki / Morrowind:Alchemy - // "The weight of a potion is an average of the weight of the ingredients, rounded down." - // note by scrawl: not rounding down here, I can't imagine a created potion to - // have 0 weight when using ingredients with 0.1 weight respectively - float weight = 0; - if (mIngredient1->isUserString("ToolTipType")) - weight += mIngredient1->getUserData()->get()->base->mData.mWeight; - if (mIngredient2->isUserString("ToolTipType")) - weight += mIngredient2->getUserData()->get()->base->mData.mWeight; - if (mIngredient3->isUserString("ToolTipType")) - weight += mIngredient3->getUserData()->get()->base->mData.mWeight; - if (mIngredient4->isUserString("ToolTipType")) - weight += mIngredient4->getUserData()->get()->base->mData.mWeight; - newPotion.mData.mWeight = weight / float(numIngreds); - - newPotion.mData.mValue = 100; /// \todo - newPotion.mEffects = effects; - // pick a random mesh and icon - std::vector names; - /// \todo is the mesh/icon dependent on alchemy skill? - names.push_back("standard"); - names.push_back("bargain"); - names.push_back("cheap"); - names.push_back("fresh"); - names.push_back("exclusive"); - names.push_back("quality"); - int random = rand() % names.size(); - newPotion.mModel = "m\\misc_potion_" + names[random ] + "_01.nif"; - newPotion.mIcon = "m\\tx_potion_" + names[random ] + "_01.dds"; - - // check if a similiar potion record exists already - bool found = false; - std::string objectId; - typedef std::map PotionMap; - PotionMap potions = MWBase::Environment::get().getWorld()->getStore().potions.list; - for (PotionMap::const_iterator it = potions.begin(); it != potions.end(); ++it) - { - if (found) break; - - if (it->second.mData.mValue == newPotion.mData.mValue - && it->second.mData.mWeight == newPotion.mData.mWeight - && it->second.mName == newPotion.mName - && it->second.mEffects.mList.size() == newPotion.mEffects.mList.size()) - { - // check effects - for (unsigned int i=0; i < it->second.mEffects.mList.size(); ++i) - { - const ESM::ENAMstruct& a = it->second.mEffects.mList[i]; - const ESM::ENAMstruct& b = newPotion.mEffects.mList[i]; - if (a.mEffectID == b.mEffectID - && a.mArea == b.mArea - && a.mRange == b.mRange - && a.mSkill == b.mSkill - && a.mAttribute == b.mAttribute - && a.mMagnMin == b.mMagnMin - && a.mMagnMax == b.mMagnMax - && a.mDuration == b.mDuration) - { - found = true; - objectId = it->first; - break; - } - } - } - } - - if (!found) - { - std::pair result = MWBase::Environment::get().getWorld()->createRecord(newPotion); - objectId = result.first; - } - - // create a reference and add it to player inventory - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), objectId); - MWWorld::ContainerStore& store = MWWorld::Class::get(mPtr).getContainerStore(mPtr); - ref.getPtr().getRefData().setCount(1); - store.add(ref.getPtr()); - mWindowManager.messageBox("#{sPotionSuccess}", std::vector()); MWBase::Environment::get().getSoundManager()->playSound("potion success", 1.f, 1.f); } - else + else if (result == MWMechanics::Alchemy::Result_RandomFailure) { // potion failed mWindowManager.messageBox("#{sNotifyMessage8}", std::vector()); @@ -220,91 +115,36 @@ namespace MWGui } // reduce count of the ingredients - if (mIngredient1->isUserString("ToolTipType")) - { - MWWorld::Ptr ingred = *mIngredient1->getUserData(); - ingred.getRefData().setCount(ingred.getRefData().getCount()-1); - if (ingred.getRefData().getCount() == 0) - removeIngredient(mIngredient1); - } - if (mIngredient2->isUserString("ToolTipType")) - { - MWWorld::Ptr ingred = *mIngredient2->getUserData(); - ingred.getRefData().setCount(ingred.getRefData().getCount()-1); - if (ingred.getRefData().getCount() == 0) - removeIngredient(mIngredient2); - } - if (mIngredient3->isUserString("ToolTipType")) - { - MWWorld::Ptr ingred = *mIngredient3->getUserData(); - ingred.getRefData().setCount(ingred.getRefData().getCount()-1); - if (ingred.getRefData().getCount() == 0) - removeIngredient(mIngredient3); - } - if (mIngredient4->isUserString("ToolTipType")) - { - MWWorld::Ptr ingred = *mIngredient4->getUserData(); - ingred.getRefData().setCount(ingred.getRefData().getCount()-1); - if (ingred.getRefData().getCount() == 0) - removeIngredient(mIngredient4); + for (int i=0; i<4; ++i) + if (mIngredients[i]->isUserString("ToolTipType")) + { + MWWorld::Ptr ingred = *mIngredients[i]->getUserData(); + ingred.getRefData().setCount(ingred.getRefData().getCount()-1); + if (ingred.getRefData().getCount() == 0) + removeIngredient(mIngredients[i]); } + update(); } void AlchemyWindow::open() { - openContainer(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - setFilter(ContainerBase::Filter_Ingredients); + openContainer (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); // this sets mPtr + setFilter (ContainerBase::Filter_Ingredients); - // pick the best available apparatus - MWWorld::ContainerStore& store = MWWorld::Class::get(mPtr).getContainerStore(mPtr); + mAlchemy.setAlchemist (mPtr); - MWWorld::Ptr bestAlbemic; - MWWorld::Ptr bestMortarPestle; - MWWorld::Ptr bestCalcinator; - MWWorld::Ptr bestRetort; + int index = 0; - for (MWWorld::ContainerStoreIterator it(store.begin(MWWorld::ContainerStore::Type_Apparatus)); - it != store.end(); ++it) + for (MWMechanics::Alchemy::TToolsIterator iter (mAlchemy.beginTools()); + iter!=mAlchemy.endTools() && index (mApparatus.size()); ++iter, ++index) { - MWWorld::LiveCellRef* ref = it->get(); - if (ref->base->mData.mType == ESM::Apparatus::Albemic - && (bestAlbemic.isEmpty() || ref->base->mData.mQuality > bestAlbemic.get()->base->mData.mQuality)) - bestAlbemic = *it; - else if (ref->base->mData.mType == ESM::Apparatus::MortarPestle - && (bestMortarPestle.isEmpty() || ref->base->mData.mQuality > bestMortarPestle.get()->base->mData.mQuality)) - bestMortarPestle = *it; - else if (ref->base->mData.mType == ESM::Apparatus::Calcinator - && (bestCalcinator.isEmpty() || ref->base->mData.mQuality > bestCalcinator.get()->base->mData.mQuality)) - bestCalcinator = *it; - else if (ref->base->mData.mType == ESM::Apparatus::Retort - && (bestRetort.isEmpty() || ref->base->mData.mQuality > bestRetort.get()->base->mData.mQuality)) - bestRetort = *it; - } - - if (!bestMortarPestle.isEmpty()) - { - mApparatus1->setUserString("ToolTipType", "ItemPtr"); - mApparatus1->setUserData(bestMortarPestle); - mApparatus1->setImageTexture(getIconPath(bestMortarPestle)); - } - if (!bestAlbemic.isEmpty()) - { - mApparatus2->setUserString("ToolTipType", "ItemPtr"); - mApparatus2->setUserData(bestAlbemic); - mApparatus2->setImageTexture(getIconPath(bestAlbemic)); - } - if (!bestCalcinator.isEmpty()) - { - mApparatus3->setUserString("ToolTipType", "ItemPtr"); - mApparatus3->setUserData(bestCalcinator); - mApparatus3->setImageTexture(getIconPath(bestCalcinator)); - } - if (!bestRetort.isEmpty()) - { - mApparatus4->setUserString("ToolTipType", "ItemPtr"); - mApparatus4->setUserData(bestRetort); - mApparatus4->setImageTexture(getIconPath(bestRetort)); + if (!iter->isEmpty()) + { + mApparatus.at (index)->setUserString ("ToolTipType", "ItemPtr"); + mApparatus.at (index)->setUserData (*iter); + mApparatus.at (index)->setImageTexture (getIconPath (*iter)); + } } } @@ -323,45 +163,27 @@ namespace MWGui // (which could happen if two similiar ingredients don't stack because of script / owner) bool alreadyAdded = false; std::string name = MWWorld::Class::get(item).getName(item); - if (mIngredient1->isUserString("ToolTipType")) - { - MWWorld::Ptr item2 = *mIngredient1->getUserData(); - std::string name2 = MWWorld::Class::get(item2).getName(item2); - if (name == name2) - alreadyAdded = true; - } - if (mIngredient2->isUserString("ToolTipType")) - { - MWWorld::Ptr item2 = *mIngredient2->getUserData(); - std::string name2 = MWWorld::Class::get(item2).getName(item2); - if (name == name2) - alreadyAdded = true; - } - if (mIngredient3->isUserString("ToolTipType")) - { - MWWorld::Ptr item2 = *mIngredient3->getUserData(); - std::string name2 = MWWorld::Class::get(item2).getName(item2); - if (name == name2) - alreadyAdded = true; - } - if (mIngredient4->isUserString("ToolTipType")) - { - MWWorld::Ptr item2 = *mIngredient4->getUserData(); - std::string name2 = MWWorld::Class::get(item2).getName(item2); - if (name == name2) - alreadyAdded = true; - } + for (int i=0; i<4; ++i) + if (mIngredients[i]->isUserString("ToolTipType")) + { + MWWorld::Ptr item2 = *mIngredients[i]->getUserData(); + std::string name2 = MWWorld::Class::get(item2).getName(item2); + if (name == name2) + alreadyAdded = true; + } + if (alreadyAdded) return; - if (!mIngredient1->isUserString("ToolTipType")) - add = mIngredient1; - if (add == NULL && !mIngredient2->isUserString("ToolTipType")) - add = mIngredient2; - if (add == NULL && !mIngredient3->isUserString("ToolTipType")) - add = mIngredient3; - if (add == NULL && !mIngredient4->isUserString("ToolTipType")) - add = mIngredient4; + for (int i=0; i<4; ++i) + if (!mIngredients[i]->isUserString("ToolTipType")) + { + add = mIngredients[i]; + + mAlchemy.addIngredient(item); + + break; + } if (add != NULL) { @@ -380,50 +202,22 @@ namespace MWGui { std::vector ignore; // don't show ingredients that are currently selected in the "available ingredients" box. - if (mIngredient1->isUserString("ToolTipType")) - ignore.push_back(*mIngredient1->getUserData()); - if (mIngredient2->isUserString("ToolTipType")) - ignore.push_back(*mIngredient2->getUserData()); - if (mIngredient3->isUserString("ToolTipType")) - ignore.push_back(*mIngredient3->getUserData()); - if (mIngredient4->isUserString("ToolTipType")) - ignore.push_back(*mIngredient4->getUserData()); + for (int i=0; i<4; ++i) + if (mIngredients[i]->isUserString("ToolTipType")) + ignore.push_back(*mIngredients[i]->getUserData()); return ignore; } void AlchemyWindow::update() { - Widgets::SpellEffectList effects; - for (int i=0; i<4; ++i) { - MyGUI::ImageBox* ingredient; - if (i==0) - ingredient = mIngredient1; - else if (i==1) - ingredient = mIngredient2; - else if (i==2) - ingredient = mIngredient3; - else if (i==3) - ingredient = mIngredient4; + MyGUI::ImageBox* ingredient = mIngredients[i]; if (!ingredient->isUserString("ToolTipType")) continue; - // add the effects of this ingredient to list of effects - MWWorld::LiveCellRef* ref = ingredient->getUserData()->get(); - for (int i=0; i<4; ++i) - { - if (ref->base->mData.mEffectID[i] < 0) - continue; - MWGui::Widgets::SpellEffectParams params; - params.mEffectID = ref->base->mData.mEffectID[i]; - params.mAttribute = ref->base->mData.mAttributes[i]; - params.mSkill = ref->base->mData.mSkills[i]; - effects.push_back(params); - } - // update ingredient count labels if (ingredient->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(ingredient->getChildAt(0)); @@ -436,50 +230,14 @@ namespace MWGui text->setCaption(getCountString(ingredient->getUserData()->getRefData().getCount())); } - // now remove effects that are only present once - Widgets::SpellEffectList::iterator it = effects.begin(); - while (it != effects.end()) + std::vector effects; + ESM::EffectList list; + list.mList = effects; + for (MWMechanics::Alchemy::TEffectsIterator it = mAlchemy.beginEffects (); it != mAlchemy.endEffects (); ++it) { - Widgets::SpellEffectList::iterator next = it; - ++next; - bool found = false; - for (; next != effects.end(); ++next) - { - if (*next == *it) - found = true; - } - - if (!found) - it = effects.erase(it); - else - ++it; + list.mList.push_back(*it); } - // now remove duplicates, and don't allow more than 4 effects - Widgets::SpellEffectList old = effects; - effects.clear(); - int i=0; - for (Widgets::SpellEffectList::iterator it = old.begin(); - it != old.end(); ++it) - { - bool found = false; - for (Widgets::SpellEffectList::iterator it2 = effects.begin(); - it2 != effects.end(); ++it2) - { - // MW considers all "foritfy attribute" effects as the same effect. See the - // "Can't create multi-state boost potions" discussion on http://www.uesp.net/wiki/Morrowind_talk:Alchemy - // thus, we are only checking effectID here and not attribute or skill - if (it2->mEffectID == it->mEffectID) - found = true; - } - if (!found && i<4) - { - ++i; - effects.push_back(*it); - } - } - mEffects = effects; - while (mEffectsBox->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mEffectsBox->getChildAt(0)); @@ -487,7 +245,9 @@ namespace MWGui Widgets::MWEffectListPtr effectsWidget = mEffectsBox->createWidget ("MW_StatName", coord, MyGUI::Align::Left | MyGUI::Align::Top); effectsWidget->setWindowManager(&mWindowManager); - effectsWidget->setEffectList(effects); + + Widgets::SpellEffectList _list = Widgets::MWEffectList::effectListFromESM(&list); + effectsWidget->setEffectList(_list); std::vector effectItems; effectsWidget->createEffectWidgets(effectItems, mEffectsBox, coord, false, 0); @@ -500,5 +260,9 @@ namespace MWGui static_cast(ingredient)->setImageTexture(""); if (ingredient->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(ingredient->getChildAt(0)); + + for (int i=0; i<4; ++i) + if (mIngredients[i] == ingredient) + mAlchemy.removeIngredient (i); } } diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index 53e7178d5..5f84e73e9 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -1,6 +1,10 @@ #ifndef MWGUI_ALCHEMY_H #define MWGUI_ALCHEMY_H +#include + +#include "../mwmechanics/alchemy.hpp" + #include "window_base.hpp" #include "container.hpp" #include "widgets.hpp" @@ -18,22 +22,10 @@ namespace MWGui MyGUI::Button* mCreateButton; MyGUI::Button* mCancelButton; - MyGUI::ImageBox* mIngredient1; - MyGUI::ImageBox* mIngredient2; - MyGUI::ImageBox* mIngredient3; - MyGUI::ImageBox* mIngredient4; - - MyGUI::ImageBox* mApparatus1; - MyGUI::ImageBox* mApparatus2; - MyGUI::ImageBox* mApparatus3; - MyGUI::ImageBox* mApparatus4; - MyGUI::Widget* mEffectsBox; MyGUI::EditBox* mNameEdit; - Widgets::SpellEffectList mEffects; // effects of created potion - void onCancelButtonClicked(MyGUI::Widget* _sender); void onCreateButtonClicked(MyGUI::Widget* _sender); void onIngredientSelected(MyGUI::Widget* _sender); @@ -46,6 +38,13 @@ namespace MWGui virtual void onReferenceUnavailable() { ; } void update(); + + private: + + MWMechanics::Alchemy mAlchemy; + + std::vector mApparatus; + std::vector mIngredients; }; } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index ed0e59226..66cc6b21a 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -245,15 +245,7 @@ void HUD::onWorldClicked(MyGUI::Widget* _sender) return; std::string handle = MWBase::Environment::get().getWorld()->getFacedHandle(); - MWWorld::Ptr object; - try - { - object = MWBase::Environment::get().getWorld()->getPtrViaHandle(handle); - } - catch (std::exception& /* e */) - { - return; - } + MWWorld::Ptr object = MWBase::Environment::get().getWorld()->searchPtrViaHandle(handle); if (mode == GM_Console) MWBase::Environment::get().getWindowManager()->getConsole()->setSelectedObject(object); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 39afba749..35224613c 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -79,14 +79,10 @@ void ToolTips::onFrame(float frameDuration) || (mWindowManager->getMode() == GM_Inventory))) { std::string handle = MWBase::Environment::get().getWorld()->getFacedHandle(); - try - { - mFocusObject = MWBase::Environment::get().getWorld()->getPtrViaHandle(handle); - } - catch (std::exception /* & e */) - { + + mFocusObject = MWBase::Environment::get().getWorld()->searchPtrViaHandle(handle); + if (mFocusObject.isEmpty ()) return; - } MyGUI::IntSize tooltipSize = getToolTipViaPtr(true); diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp new file mode 100644 index 000000000..df7c786b1 --- /dev/null +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -0,0 +1,490 @@ + +#include "alchemy.hpp" + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/containerstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/manualref.hpp" + +#include "magiceffects.hpp" +#include "creaturestats.hpp" +#include "npcstats.hpp" + +std::set MWMechanics::Alchemy::listEffects() const +{ + std::set effects; + + for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) + { + if (!iter->isEmpty()) + { + const MWWorld::LiveCellRef *ingredient = iter->get(); + + for (int i=0; i<4; ++i) + if (ingredient->base->mData.mEffectID[i]!=-1) + effects.insert (EffectKey ( + ingredient->base->mData.mEffectID[i], ingredient->base->mData.mSkills[i]!=-1 ? + ingredient->base->mData.mSkills[i] : ingredient->base->mData.mAttributes[i])); + } + } + + return effects; +} + +void MWMechanics::Alchemy::filterEffects (std::set& effects) const +{ + std::set::iterator iter = effects.begin(); + + while (iter!=effects.end()) + { + bool remove = false; + + const EffectKey& key = *iter; + + { // dodge pointless g++ warning + for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) + { + bool found = false; + + if (iter->isEmpty()) + continue; + + const MWWorld::LiveCellRef *ingredient = iter->get(); + + for (int i=0; i<4; ++i) + if (key.mId==ingredient->base->mData.mEffectID[i] && + (key.mArg==ingredient->base->mData.mSkills[i] || + key.mArg==ingredient->base->mData.mAttributes[i])) + { + found = true; + break; + } + + if (!found) + { + remove = true; + break; + } + } + } + + if (remove) + effects.erase (iter++); + else + ++iter; + } +} + +void MWMechanics::Alchemy::applyTools (int flags, float& value) const +{ + bool magnitude = !(flags & ESM::MagicEffect::NoMagnitude); + bool duration = !(flags & ESM::MagicEffect::NoDuration); + bool negative = flags & (ESM::MagicEffect::Negative | ESM::MagicEffect::Harmful); + + int tool = negative ? ESM::Apparatus::Retort : ESM::Apparatus::Albemic; + + int setup = 0; + + if (!mTools[tool].isEmpty() && !mTools[ESM::Apparatus::Calcinator].isEmpty()) + setup = 1; + else if (!mTools[tool].isEmpty()) + setup = 2; + else if (!mTools[ESM::Apparatus::Calcinator].isEmpty()) + setup = 3; + else + return; + + float toolQuality = setup==1 || setup==2 ? mTools[tool].get()->base->mData.mQuality : 0; + float calcinatorQuality = setup==1 || setup==3 ? + mTools[ESM::Apparatus::Calcinator].get()->base->mData.mQuality : 0; + + float quality = 1; + + switch (setup) + { + case 1: + + quality = negative ? 2 * toolQuality + 3 * calcinatorQuality : + (magnitude && duration ? + 2 * toolQuality + calcinatorQuality : 2/3.0 * (toolQuality + calcinatorQuality) + 0.5); + break; + + case 2: + + quality = negative ? 1+toolQuality : (magnitude && duration ? toolQuality : toolQuality + 0.5); + break; + + case 3: + + quality = magnitude && duration ? calcinatorQuality : calcinatorQuality + 0.5; + break; + } + + if (setup==3 || !negative) + { + value += quality; + } + else + { + if (quality==0) + throw std::runtime_error ("invalid derived alchemy apparatus quality"); + + value /= quality; + } +} + +void MWMechanics::Alchemy::updateEffects() +{ + mEffects.clear(); + mValue = 0; + + if (countIngredients()<2 || mAlchemist.isEmpty() || mTools[ESM::Apparatus::MortarPestle].isEmpty()) + return; + + // find effects + std::set effects (listEffects()); + filterEffects (effects); + + // general alchemy factor + float x = getChance(); + + x *= mTools[ESM::Apparatus::MortarPestle].get()->base->mData.mQuality; + x *= MWBase::Environment::get().getWorld()->getStore().gameSettings.find ("fPotionStrengthMult")->getFloat(); + + // value + mValue = static_cast ( + x * MWBase::Environment::get().getWorld()->getStore().gameSettings.find ("iAlchemyMod")->getFloat()); + + // build quantified effect list + for (std::set::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().magicEffects.find (iter->mId); + + if (magicEffect->mData.mBaseCost<=0) + throw std::runtime_error ("invalid base cost for magic effect " + iter->mId); + + float fPotionT1MagMul = + MWBase::Environment::get().getWorld()->getStore().gameSettings.find ("fPotionT1MagMult")->getFloat(); + + if (fPotionT1MagMul<=0) + throw std::runtime_error ("invalid gmst: fPotionT1MagMul"); + + float fPotionT1DurMult = + MWBase::Environment::get().getWorld()->getStore().gameSettings.find ("fPotionT1DurMult")->getFloat(); + + if (fPotionT1DurMult<=0) + throw std::runtime_error ("invalid gmst: fPotionT1DurMult"); + + float magnitude = magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude ? + 1 : (x / fPotionT1MagMul) / magicEffect->mData.mBaseCost; + float duration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ? + 1 : (x / fPotionT1DurMult) / magicEffect->mData.mBaseCost; + + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + applyTools (magicEffect->mData.mFlags, magnitude); + + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) + applyTools (magicEffect->mData.mFlags, duration); + + duration = static_cast (duration+0.5); + magnitude = static_cast (magnitude+0.5); + + if (magnitude>0 && duration>0) + { + ESM::ENAMstruct effect; + effect.mEffectID = iter->mId; + + effect.mSkill = effect.mAttribute = iter->mArg; // somewhat hack-ish, but should work + + effect.mRange = 0; + effect.mArea = 0; + + effect.mDuration = duration; + effect.mMagnMin = effect.mMagnMax = magnitude; + + mEffects.push_back (effect); + } + } +} + +const ESM::Potion *MWMechanics::Alchemy::getRecord() const +{ + for (ESMS::RecListWithIDT::MapType::const_iterator iter ( + MWBase::Environment::get().getWorld()->getStore().potions.list.begin()); + iter!=MWBase::Environment::get().getWorld()->getStore().potions.list.end(); ++iter) + { + if (iter->second.mEffects.mList.size()!=mEffects.size()) + continue; + + bool mismatch = false; + + for (int i=0; i (iter->second.mEffects.mList.size()); ++iter) + { + const ESM::ENAMstruct& first = iter->second.mEffects.mList[i]; + const ESM::ENAMstruct& second = mEffects[i]; + + if (first.mEffectID!=second.mEffectID || + first.mArea!=second.mArea || + first.mRange!=second.mRange || + first.mSkill!=second.mSkill || + first.mAttribute!=second.mAttribute || + first.mMagnMin!=second.mMagnMin || + first.mMagnMax!=second.mMagnMax || + first.mDuration!=second.mDuration) + { + mismatch = true; + break; + } + } + + if (!mismatch) + return &iter->second; + } + + return 0; +} + +void MWMechanics::Alchemy::removeIngredients() +{ + bool needsUpdate = false; + + for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) + if (!iter->isEmpty()) + { + iter->getRefData().setCount (iter->getRefData().getCount()-1); + if (iter->getRefData().getCount()<1) + { + needsUpdate = true; + *iter = MWWorld::Ptr(); + } + } + + if (needsUpdate) + updateEffects(); +} + +void MWMechanics::Alchemy::addPotion (const std::string& name) +{ + const ESM::Potion *record = getRecord(); + + if (!record) + { + ESM::Potion newRecord; + + newRecord.mData.mWeight = 0; + + for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter) + if (!iter->isEmpty()) + newRecord.mData.mWeight += iter->get()->base->mData.mWeight; + + newRecord.mData.mWeight /= countIngredients(); + + newRecord.mData.mValue = mValue; + newRecord.mData.mAutoCalc = 0; + + newRecord.mName = name; + + int index = static_cast (std::rand()/static_cast (RAND_MAX)*6); + assert (index>=0 && index<6); + + static const char *name[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; + + newRecord.mModel = "m\\misc_potion_" + std::string (name[index]) + "_01.nif"; + newRecord.mIcon = "m\\tx_potion_" + std::string (name[index]) + "_01.dds"; + + newRecord.mEffects.mList = mEffects; + + record = MWBase::Environment::get().getWorld()->createRecord (newRecord).second; + } + + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), record->mId); + MWWorld::Class::get (mAlchemist).getContainerStore (mAlchemist).add (ref.getPtr()); +} + +void MWMechanics::Alchemy::increaseSkill() +{ + MWWorld::Class::get (mAlchemist).skillUsageSucceeded (mAlchemist, ESM::Skill::Alchemy, 0); +} + +float MWMechanics::Alchemy::getChance() const +{ + const CreatureStats& creatureStats = MWWorld::Class::get (mAlchemist).getCreatureStats (mAlchemist); + const NpcStats& npcStats = MWWorld::Class::get (mAlchemist).getNpcStats (mAlchemist); + + return + (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + + 0.1 * creatureStats.getAttribute (1).getModified() + + 0.1 * creatureStats.getAttribute (7).getModified()); +} + +int MWMechanics::Alchemy::countIngredients() const +{ + int ingredients = 0; + + for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter) + if (!iter->isEmpty()) + ++ingredients; + + return ingredients; +} + +void MWMechanics::Alchemy::setAlchemist (const MWWorld::Ptr& npc) +{ + mAlchemist = npc; + + mIngredients.resize (4); + + std::fill (mIngredients.begin(), mIngredients.end(), MWWorld::Ptr()); + + mTools.resize (4); + + std::fill (mTools.begin(), mTools.end(), MWWorld::Ptr()); + + mEffects.clear(); + + MWWorld::ContainerStore& store = MWWorld::Class::get (npc).getContainerStore (npc); + + for (MWWorld::ContainerStoreIterator iter (store.begin (MWWorld::ContainerStore::Type_Apparatus)); + iter!=store.end(); ++iter) + { + MWWorld::LiveCellRef* ref = iter->get(); + + int type = ref->base->mData.mType; + + if (type<0 || type>=static_cast (mTools.size())) + throw std::runtime_error ("invalid apparatus type"); + + if (!mTools[type].isEmpty()) + if (ref->base->mData.mQuality<=mTools[type].get()->base->mData.mQuality) + continue; + + mTools[type] = *iter; + } +} + +MWMechanics::Alchemy::TToolsIterator MWMechanics::Alchemy::beginTools() const +{ + return mTools.begin(); +} + +MWMechanics::Alchemy::TToolsIterator MWMechanics::Alchemy::endTools() const +{ + return mTools.end(); +} + +MWMechanics::Alchemy::TIngredientsIterator MWMechanics::Alchemy::beginIngredients() const +{ + return mIngredients.begin(); +} + +MWMechanics::Alchemy::TIngredientsIterator MWMechanics::Alchemy::endIngredients() const +{ + return mIngredients.end(); +} + +void MWMechanics::Alchemy::clear() +{ + mAlchemist = MWWorld::Ptr(); + mTools.clear(); + mIngredients.clear(); + mEffects.clear(); +} + +int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient) +{ + // find a free slot + int slot = -1; + + for (int i=0; i (mIngredients.size()); ++i) + if (mIngredients[i].isEmpty()) + { + slot = i; + break; + } + + if (slot==-1) + return -1; + + for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) + if (!iter->isEmpty() && ingredient.get()==iter->get()) + return -1; + + mIngredients[slot] = ingredient; + + updateEffects(); + + return slot; +} + +void MWMechanics::Alchemy::removeIngredient (int index) +{ + if (index>=0 && index (mIngredients.size())) + { + mIngredients[index] = MWWorld::Ptr(); + updateEffects(); + } +} + +MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::beginEffects() const +{ + return mEffects.begin(); +} + +MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::endEffects() const +{ + return mEffects.end(); +} + +std::string MWMechanics::Alchemy::getPotionName() const +{ + if (const ESM::Potion *potion = getRecord()) + return potion->mName; + + return ""; +} + +MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name) +{ + if (mTools[ESM::Apparatus::MortarPestle].isEmpty()) + return Result_NoMortarAndPestle; + + if (countIngredients()<2) + return Result_LessThanTwoIngredients; + + if (name.empty() && getPotionName().empty()) + return Result_NoName; + + if (beginEffects()==endEffects()) + return Result_NoEffects; + + if (getChance() (RAND_MAX)*100) + { + removeIngredients(); + return Result_RandomFailure; + } + + addPotion (name); + + removeIngredients(); + + increaseSkill(); + + return Result_Success; +} diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp new file mode 100644 index 000000000..7f3e2c0ea --- /dev/null +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -0,0 +1,121 @@ +#ifndef GAME_MWMECHANICS_ALCHEMY_H +#define GAME_MWMECHANICS_ALCHEMY_H + +#include +#include + +#include + +#include "../mwworld/ptr.hpp" + +namespace MWMechanics +{ + struct EffectKey; + + /// \brief Potion creation via alchemy skill + class Alchemy + { + public: + + typedef std::vector TToolsContainer; + typedef TToolsContainer::const_iterator TToolsIterator; + + typedef std::vector TIngredientsContainer; + typedef TIngredientsContainer::const_iterator TIngredientsIterator; + + typedef std::vector TEffectsContainer; + typedef TEffectsContainer::const_iterator TEffectsIterator; + + enum Result + { + Result_Success, + + Result_NoMortarAndPestle, + Result_LessThanTwoIngredients, + Result_NoName, + Result_NoEffects, + Result_RandomFailure + }; + + private: + + MWWorld::Ptr mAlchemist; + TToolsContainer mTools; + TIngredientsContainer mIngredients; + TEffectsContainer mEffects; + int mValue; + + std::set listEffects() const; + ///< List all effects of all used ingredients. + + void filterEffects (std::set& effects) const; + ///< Filter out effects not shared by all ingredients. + + void applyTools (int flags, float& value) const; + + void updateEffects(); + + const ESM::Potion *getRecord() const; + ///< Return existing recrod for created potion (may return 0) + + void removeIngredients(); + ///< Remove selected ingredients from alchemist's inventory, cleanup selected ingredients and + /// update effect list accordingly. + + void addPotion (const std::string& name); + ///< Add a potion to the alchemist's inventory. + + void increaseSkill(); + ///< Increase alchemist's skill. + + float getChance() const; + ///< Return chance of success. + + int countIngredients() const; + + public: + + void setAlchemist (const MWWorld::Ptr& npc); + ///< Set alchemist and configure alchemy setup accordingly. \a npc may be empty to indicate that + /// there is no alchemist (alchemy session has ended). + + TToolsIterator beginTools() const; + ///< \attention Iterates over tool slots, not over tools. Some of the slots may be empty. + + TToolsIterator endTools() const; + + TIngredientsIterator beginIngredients() const; + ///< \attention Iterates over ingredient slots, not over ingredients. Some of the slots may be empty. + + TIngredientsIterator endIngredients() const; + + void clear(); + ///< Remove alchemist, tools and ingredients. + + int addIngredient (const MWWorld::Ptr& ingredient); + ///< Add ingredient into the next free slot. + /// + /// \return Slot index or -1, if adding failed because of no free slot or the ingredient type being + /// listed already. + + void removeIngredient (int index); + ///< Remove ingredient from slot (calling this function on an empty slot is a no-op). + + TEffectsIterator beginEffects() const; + + TEffectsIterator endEffects() const; + + std::string getPotionName() const; + ///< Return the name of the potion that would be created when calling create (if a record for such + /// a potion already exists) or return an empty string. + + Result create (const std::string& name); + ///< Try to create a potion from the ingredients, place it in the inventory of the alchemist and + /// adjust the skills of the alchemist accordingly. + /// \param name must not be an empty string, unless there is already a potion record ( + /// getPotionName() does not return an empty string). + }; +} + +#endif + diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 6fc228f0b..2e3d80aa4 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -326,6 +326,14 @@ namespace MWWorld } Ptr World::getPtrViaHandle (const std::string& handle) + { + Ptr res = searchPtrViaHandle (handle); + if (res.isEmpty ()) + throw std::runtime_error ("unknown Ogre handle: " + handle); + return res; + } + + Ptr World::searchPtrViaHandle (const std::string& handle) { if (mPlayer->getPlayer().getRefData().getHandle()==handle) return mPlayer->getPlayer(); @@ -339,7 +347,7 @@ namespace MWWorld return ptr; } - throw std::runtime_error ("unknown Ogre handle: " + handle); + return MWWorld::Ptr(); } void World::enable (const Ptr& reference) @@ -850,12 +858,13 @@ namespace MWWorld mWeatherManager->update (duration); // inform the GUI about focused object - try - { - MWWorld::Ptr object = getPtrViaHandle(mFacedHandle); - MWBase::Environment::get().getWindowManager()->setFocusObject(object); + MWWorld::Ptr object = searchPtrViaHandle(mFacedHandle); - // retrieve object dimensions so we know where to place the floating label + MWBase::Environment::get().getWindowManager()->setFocusObject(object); + + // retrieve object dimensions so we know where to place the floating label + if (!object.isEmpty ()) + { Ogre::SceneNode* node = object.getRefData().getBaseNode(); Ogre::AxisAlignedBox bounds; int i; @@ -871,11 +880,6 @@ namespace MWWorld screenCoords[0], screenCoords[1], screenCoords[2], screenCoords[3]); } } - catch (std::runtime_error&) - { - MWWorld::Ptr null; - MWBase::Environment::get().getWindowManager()->setFocusObject(null); - } if (!mRendering->occlusionQuerySupported()) { diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 90cd2151b..f94882c10 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -163,6 +163,9 @@ namespace MWWorld virtual Ptr getPtrViaHandle (const std::string& handle); ///< Return a pointer to a liveCellRef with the given Ogre handle. + virtual Ptr searchPtrViaHandle (const std::string& handle); + ///< Return a pointer to a liveCellRef with the given Ogre handle or Ptr() if not found + virtual void enable (const Ptr& ptr); virtual void disable (const Ptr& ptr); diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index 00349a355..93c59f9cb 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -26,7 +26,6 @@ struct MagicEffect NonRecastable = 0x4000, // Does not land if parent spell is already affecting target. Shows "you cannot re-cast" message for self target. Unreflectable = 0x10000, // Cannot be reflected, the effect always lands normally. CasterLinked = 0x20000, // Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells. - SpellMaking = 0x0200, Enchanting = 0x0400, Negative = 0x0800 // A harmful effect. Will determine whether