diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index edbeab0a1a..045f504a74 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -28,7 +28,7 @@ add_openmw_dir (mwgui dialogue_history window_base stats_window messagebox journalwindow charactercreation map_window window_pinnable_base cursorreplace tooltips scrollwindow bookwindow list formatting inventorywindow container hud countdialog tradewindow settingswindow - confirmationdialog alchemywindow referenceinterface + confirmationdialog alchemywindow referenceinterface spellwindow ) add_openmw_dir (mwdialogue diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 1cfbc1b2b0..bf6b4add04 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -347,7 +347,6 @@ void ContainerBase::setFilter(ContainerBase::Filter filter) void ContainerBase::openContainer(MWWorld::Ptr container) { mPtr = container; - drawItems(); } void ContainerBase::drawItems() @@ -535,6 +534,8 @@ void ContainerBase::drawItems() MyGUI::IntSize size = MyGUI::IntSize(std::max(mItemView->getSize().width, x+42), mItemView->getSize().height); mItemView->setCanvasSize(size); mContainerWidget->setSize(size); + + notifyContentChanged(); } std::string ContainerBase::getCountString(const int count) @@ -636,6 +637,7 @@ void ContainerWindow::open(MWWorld::Ptr container) { openContainer(container); setTitle(MWWorld::Class::get(container).getName(container)); + drawItems(); } void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 5cd8167c24..88e445a7bb 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -120,6 +120,8 @@ namespace MWGui virtual bool ignoreEquippedItems() { return false; } virtual std::vector itemsToIgnore() { return std::vector(); } + + virtual void notifyContentChanged() { ; } }; class ContainerWindow : public ContainerBase, public WindowBase diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 7276218d6b..14212c2f9d 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -44,15 +44,18 @@ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) , mCellNameTimer(0.0f) , mCellNameBox(NULL) , mMapVisible(true) + , mWeaponVisible(true) + , mSpellVisible(true) { setCoord(0,0, width, height); // Energy bars + getWidget(mHealthFrame, "HealthFrame"); getWidget(health, "Health"); getWidget(magicka, "Magicka"); getWidget(stamina, "Stamina"); - hmsBaseLeft = health->getLeft(); + hmsBaseLeft = mHealthFrame->getLeft(); MyGUI::Widget *healthFrame, *magickaFrame, *fatigueFrame; getWidget(healthFrame, "HealthFrame"); @@ -62,6 +65,8 @@ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) magickaFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked); fatigueFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked); + const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + // Item and spell images and status bars getWidget(weapBox, "WeapBox"); getWidget(weapImage, "WeapImage"); @@ -77,16 +82,17 @@ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) getWidget(effectBox, "EffectBox"); getWidget(effect1, "Effect1"); - effectBoxBaseRight = effectBox->getRight(); + effectBoxBaseRight = viewSize.width - effectBox->getRight(); effectBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMagicClicked); getWidget(minimapBox, "MiniMapBox"); - minimapBoxBaseRight = minimapBox->getRight(); + minimapBoxBaseRight = viewSize.width - minimapBox->getRight(); minimapBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked); getWidget(minimap, "MiniMap"); getWidget(compass, "Compass"); getWidget(mCellNameBox, "CellName"); + getWidget(mWeaponSpellBox, "WeaponSpellName"); getWidget(crosshair, "Crosshair"); @@ -95,12 +101,6 @@ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) getWidget(trianglecounter, "TriangleCounter"); getWidget(batchcounter, "BatchCounter"); - // These are just demo values, you should replace these with - // real calls from outside the class later. - setWeapIcon("icons\\w\\tx_knife_iron.dds"); - setWeapStatus(90, 100); - setSpellIcon("icons\\s\\b_tx_s_rstor_health.dds"); - setSpellStatus(65, 100); setEffect("icons\\s\\tx_s_chameleon.dds"); LocalMapBase::init(minimap, compass, this); @@ -112,6 +112,8 @@ HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) void HUD::setFpsLevel(int level) { + fpscounter = 0; + MyGUI::Widget* fps; getWidget(fps, "FPSBoxAdv"); fps->setVisible(false); @@ -134,7 +136,8 @@ void HUD::setFpsLevel(int level) void HUD::setFPS(float fps) { - fpscounter->setCaption(boost::lexical_cast((int)fps)); + if (fpscounter) + fpscounter->setCaption(boost::lexical_cast((int)fps)); } void HUD::setTriangleCount(size_t count) @@ -147,28 +150,6 @@ void HUD::setBatchCount(size_t count) batchcounter->setCaption(boost::lexical_cast(count)); } -void HUD::setWeapIcon(const char *str) -{ - weapImage->setImageTexture(str); -} - -void HUD::setSpellIcon(const char *str) -{ - spellImage->setImageTexture(str); -} - -void HUD::setWeapStatus(int s, int smax) -{ - weapStatus->setProgressRange(smax); - weapStatus->setProgressPosition(s); -} - -void HUD::setSpellStatus(int s, int smax) -{ - spellStatus->setProgressRange(smax); - spellStatus->setProgressPosition(s); -} - void HUD::setEffect(const char *img) { effect1->setImageTexture(img); @@ -217,7 +198,12 @@ void HUD::setBottomLeftVisibility(bool hmsVisible, bool weapVisible, bool spellV spellDx = weapDx = weapBoxBaseLeft - hmsBaseLeft; if (!weapVisible) - spellDx -= spellBoxBaseLeft - weapBoxBaseLeft; + spellDx += spellBoxBaseLeft - weapBoxBaseLeft; + + mWeaponVisible = weapVisible; + mSpellVisible = spellVisible; + if (!mWeaponVisible && !mSpellVisible) + mWeaponSpellBox->setVisible(false); health->setVisible(hmsVisible); stamina->setVisible(hmsVisible); @@ -230,14 +216,16 @@ void HUD::setBottomLeftVisibility(bool hmsVisible, bool weapVisible, bool spellV void HUD::setBottomRightVisibility(bool effectBoxVisible, bool minimapBoxVisible) { + const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + // effect box can have variable width -> variable left coordinate int effectsDx = 0; if (!minimapBoxVisible) - effectsDx = minimapBoxBaseRight - effectBoxBaseRight; + effectsDx = (viewSize.width - minimapBoxBaseRight) - (viewSize.width - effectBoxBaseRight); mMapVisible = minimapBoxVisible; minimapBox->setVisible(minimapBoxVisible); - effectBox->setPosition(effectBoxBaseRight - effectBox->getWidth() + effectsDx, effectBox->getTop()); + effectBox->setPosition((viewSize.width - effectBoxBaseRight) - effectBox->getWidth() + effectsDx, effectBox->getTop()); effectBox->setVisible(effectBoxVisible); } @@ -346,11 +334,150 @@ void HUD::setCellName(const std::string& cellName) void HUD::onFrame(float dt) { mCellNameTimer -= dt; + mWeaponSpellTimer -= dt; if (mCellNameTimer < 0) mCellNameBox->setVisible(false); + if (mWeaponSpellTimer < 0) + mWeaponSpellBox->setVisible(false); } void HUD::onResChange(int width, int height) { setCoord(0, 0, width, height); } + +void HUD::setSelectedSpell(const std::string& spellId, int successChancePercent) +{ + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId); + std::string spellName = spell->name; + if (spellName != mSpellName && mSpellVisible) + { + mWeaponSpellTimer = 5.0f; + mSpellName = spellName; + mWeaponSpellBox->setCaption(mSpellName); + mWeaponSpellBox->setVisible(true); + } + + spellStatus->setProgressRange(100); + spellStatus->setProgressPosition(successChancePercent); + + if (spellImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(spellImage->getChildAt(0)); + + spellBox->setUserString("ToolTipType", "Spell"); + spellBox->setUserString("Spell", spellId); + + // use the icon of the first effect + const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().magicEffects.find(spell->effects.list.front().effectID); + std::string icon = effect->icon; + int slashPos = icon.find("\\"); + icon.insert(slashPos+1, "b_"); + icon = std::string("icons\\") + icon; + Widgets::fixTexturePath(icon); + spellImage->setImageTexture(icon); +} + +void HUD::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) +{ + std::string itemName = MWWorld::Class::get(item).getName(item); + if (itemName != mSpellName && mSpellVisible) + { + mWeaponSpellTimer = 5.0f; + mSpellName = itemName; + mWeaponSpellBox->setCaption(mSpellName); + mWeaponSpellBox->setVisible(true); + } + + spellStatus->setProgressRange(100); + spellStatus->setProgressPosition(chargePercent); + + if (spellImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(spellImage->getChildAt(0)); + + spellBox->setUserString("ToolTipType", "ItemPtr"); + spellBox->setUserData(item); + + spellImage->setImageTexture("textures\\menu_icon_magic_mini.dds"); + MyGUI::ImageBox* itemBox = spellImage->createWidgetReal("ImageBox", MyGUI::FloatCoord(0,0,1,1) + , MyGUI::Align::Stretch); + + std::string path = std::string("icons\\"); + path+=MWWorld::Class::get(item).getInventoryIcon(item); + Widgets::fixTexturePath(path); + itemBox->setImageTexture(path); + itemBox->setNeedMouseFocus(false); +} + +void HUD::setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent) +{ + std::string itemName = MWWorld::Class::get(item).getName(item); + if (itemName != mWeaponName && mWeaponVisible) + { + mWeaponSpellTimer = 5.0f; + mWeaponName = itemName; + mWeaponSpellBox->setCaption(mWeaponName); + mWeaponSpellBox->setVisible(true); + } + + weapBox->setUserString("ToolTipType", "ItemPtr"); + weapBox->setUserData(item); + + weapStatus->setProgressRange(100); + weapStatus->setProgressPosition(durabilityPercent); + + if (weapImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(weapImage->getChildAt(0)); + + std::string path = std::string("icons\\"); + path+=MWWorld::Class::get(item).getInventoryIcon(item); + Widgets::fixTexturePath(path); + + if (MWWorld::Class::get(item).getEnchantment(item) != "") + { + weapImage->setImageTexture("textures\\menu_icon_magic_mini.dds"); + MyGUI::ImageBox* itemBox = weapImage->createWidgetReal("ImageBox", MyGUI::FloatCoord(0,0,1,1) + , MyGUI::Align::Stretch); + itemBox->setImageTexture(path); + itemBox->setNeedMouseFocus(false); + } + else + weapImage->setImageTexture(path); +} + +void HUD::unsetSelectedSpell() +{ + std::string spellName = "#{sNone}"; + if (spellName != mSpellName && mSpellVisible) + { + mWeaponSpellTimer = 5.0f; + mSpellName = spellName; + mWeaponSpellBox->setCaptionWithReplacing(mSpellName); + mWeaponSpellBox->setVisible(true); + } + + if (spellImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(spellImage->getChildAt(0)); + spellStatus->setProgressRange(100); + spellStatus->setProgressPosition(0); + spellImage->setImageTexture(""); + spellBox->clearUserStrings(); +} + +void HUD::unsetSelectedWeapon() +{ + std::string itemName = "#{sSkillHandtohand}"; + if (itemName != mWeaponName && mWeaponVisible) + { + mWeaponSpellTimer = 5.0f; + mWeaponName = itemName; + mWeaponSpellBox->setCaptionWithReplacing(mWeaponName); + mWeaponSpellBox->setVisible(true); + } + + if (weapImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(weapImage->getChildAt(0)); + weapStatus->setProgressRange(100); + weapStatus->setProgressPosition(0); + weapImage->setImageTexture("icons\\k\\stealth_handtohand.dds"); + weapBox->clearUserStrings(); +} diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 16749114c5..47bd93eef7 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -3,6 +3,7 @@ #include #include "../mwmechanics/stat.hpp" +#include "../mwworld/ptr.hpp" namespace MWGui { @@ -12,10 +13,6 @@ namespace MWGui { public: HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop); - void setWeapIcon(const char *str); - void setSpellIcon(const char *str); - void setWeapStatus(int s, int smax); - void setSpellStatus(int s, int smax); void setEffect(const char *img); void setValue (const std::string& id, const MWMechanics::DynamicStat& value); void setFPS(float fps); @@ -25,12 +22,19 @@ namespace MWGui void setBottomRightVisibility(bool effectBoxVisible, bool minimapVisible); void setFpsLevel(const int level); + void setSelectedSpell(const std::string& spellId, int successChancePercent); + void setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent); + void setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent); + void unsetSelectedSpell(); + void unsetSelectedWeapon(); + void onFrame(float dt); void onResChange(int width, int height); void setCellName(const std::string& cellName); MyGUI::ProgressPtr health, magicka, stamina; + MyGUI::Widget* mHealthFrame; MyGUI::Widget *weapBox, *spellBox; MyGUI::ImageBox *weapImage, *spellImage; MyGUI::ProgressPtr weapStatus, spellStatus; @@ -40,6 +44,7 @@ namespace MWGui MyGUI::ImageBox* compass; MyGUI::ImageBox* crosshair; MyGUI::TextBox* mCellNameBox; + MyGUI::TextBox* mWeaponSpellBox; MyGUI::WidgetPtr fpsbox; MyGUI::TextBox* fpscounter; @@ -57,7 +62,13 @@ namespace MWGui std::string mCellName; float mCellNameTimer; + std::string mWeaponName; + std::string mSpellName; + float mWeaponSpellTimer; + bool mMapVisible; + bool mWeaponVisible; + bool mSpellVisible; void onWorldClicked(MyGUI::Widget* _sender); void onWorldMouseOver(MyGUI::Widget* _sender, int x, int y); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 2b224ab2cc..103f5ebf3d 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -20,6 +20,7 @@ #include "widgets.hpp" #include "bookwindow.hpp" #include "scrollwindow.hpp" +#include "spellwindow.hpp" namespace { @@ -91,7 +92,7 @@ namespace MWGui mFilterAll->setStateSelected(true); - setCoord(0, 342, 600, 258); + setCoord(0, 342, 498, 258); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); openContainer(player); @@ -187,6 +188,15 @@ namespace MWGui mWindowManager.setDragDrop(false); drawItems(); + + // update selected weapon icon + MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weaponSlot = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponSlot == invStore.end()) + mWindowManager.unsetSelectedWeapon(); + else + mWindowManager.setSelectedWeapon(*weaponSlot, 100); /// \todo track weapon durability + } } @@ -259,4 +269,20 @@ namespace MWGui { mTrading = true; } + + void InventoryWindow::notifyContentChanged() + { + // update the spell window just in case new enchanted items were added to inventory + if (mWindowManager.getSpellWindow()) + mWindowManager.getSpellWindow()->updateSpells(); + + // update selected weapon icon + MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weaponSlot = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponSlot == invStore.end()) + mWindowManager.unsetSelectedWeapon(); + else + mWindowManager.setSelectedWeapon(*weaponSlot, 100); /// \todo track weapon durability + + } } diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index ecff75f101..bc4cb08ef2 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -50,6 +50,8 @@ namespace MWGui virtual void _unequipItem(MWWorld::Ptr item); virtual void onReferenceUnavailable() { ; } + + virtual void notifyContentChanged(); }; } diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp new file mode 100644 index 0000000000..502754feb0 --- /dev/null +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -0,0 +1,449 @@ +#include "spellwindow.hpp" + +#include +#include +#include + +#include "../mwworld/world.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwbase/environment.hpp" +#include "../mwmechanics/spells.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/spellsuccess.hpp" +#include "../mwsound/soundmanager.hpp" + +#include "window_manager.hpp" +#include "inventorywindow.hpp" +#include "confirmationdialog.hpp" + +namespace +{ + bool sortSpells(const std::string& left, const std::string& right) + { + const ESM::Spell* a = MWBase::Environment::get().getWorld()->getStore().spells.find(left); + const ESM::Spell* b = MWBase::Environment::get().getWorld()->getStore().spells.find(right); + + int cmp = a->name.compare(b->name); + return cmp < 0; + } + + bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right) + { + int cmp = MWWorld::Class::get(left).getName(left).compare( + MWWorld::Class::get(right).getName(right)); + return cmp < 0; + } +} + +namespace MWGui +{ + SpellWindow::SpellWindow(WindowManager& parWindowManager) + : WindowPinnableBase("openmw_spell_window_layout.xml", parWindowManager) + , mHeight(0) + , mWidth(0) + { + getWidget(mSpellView, "SpellView"); + getWidget(mEffectBox, "EffectsBox"); + + setCoord(498, 300, 302, 300); + + updateSpells(); + + mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SpellWindow::onWindowResize); + } + + void SpellWindow::onPinToggled() + { + mWindowManager.setSpellVisibility(!mPinned); + } + + void SpellWindow::open() + { + updateSpells(); + } + + void SpellWindow::updateSpells() + { + 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()->getPlayer().getPlayer(); + MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); + MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWMechanics::Spells& spells = stats.mSpells; + + // the following code switches between selected enchanted item and selected spell (only one of these + // can be active at a time) + std::string selectedSpell = spells.getSelectedSpell(); + MWWorld::Ptr selectedItem; + if (store.getSelectedEnchantItem() != store.end()) + { + selectedSpell = ""; + selectedItem = *store.getSelectedEnchantItem(); + + bool allowSelectedItem = true; + + // if the selected item can be equipped, make sure that it actually is equipped + std::pair, bool> slots; + slots = MWWorld::Class::get(selectedItem).getEquipmentSlots(selectedItem); + if (!slots.first.empty()) + { + bool equipped = false; + for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) + { + if (store.getSlot(i) != store.end() && *store.getSlot(i) == selectedItem) + { + equipped = true; + break; + } + } + + if (!equipped) + allowSelectedItem = false; + } + + if (!allowSelectedItem) + { + store.setSelectedEnchantItem(store.end()); + spells.setSelectedSpell(""); + mWindowManager.unsetSelectedSpell(); + selectedItem = MWWorld::Ptr(); + } + } + + + + for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + spellList.push_back(*it); + } + + std::vector powers; + std::vector::iterator it = spellList.begin(); + while (it != spellList.end()) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(*it); + if (spell->data.type == ESM::Spell::ST_Power) + { + powers.push_back(*it); + it = spellList.erase(it); + } + else if (spell->data.type == ESM::Spell::ST_Ability) + { + // abilities are always active and don't show in the spell window. + 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 = MWWorld::Class::get(*it).getEnchantment(*it); + if (enchantId != "") + { + // only add items with "Cast once" or "Cast on use" + const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().enchants.find(enchantId); + int type = enchant->data.type; + 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 = MWBase::Environment::get().getWorld()->getStore().spells.find(*it); + MyGUI::Button* t = mSpellView->createWidget("SpellText", + MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + t->setCaption(spell->name); + 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 == selectedSpell) + 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 = MWBase::Environment::get().getWorld()->getStore().spells.find(*it); + MyGUI::Button* t = mSpellView->createWidget("SpellText", + MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + t->setCaption(spell->name); + 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); + t->setStateSelected(*it == selectedSpell); + + // cost / success chance + MyGUI::Button* costChance = mSpellView->createWidget("SpellText", + MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + std::string cost = boost::lexical_cast(spell->data.cost); + 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 == selectedSpell); + + + 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 = MWBase::Environment::get().getWorld()->getStore().enchants.find(MWWorld::Class::get(item).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; + } + } + + MyGUI::Button* t = mSpellView->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", + MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + t->setCaption(MWWorld::Class::get(item).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); + t->setStateSelected(item == selectedItem); + + // cost / charge + MyGUI::Button* costCharge = mSpellView->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", + MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + + std::string cost = boost::lexical_cast(enchant->data.cost); + std::string charge = boost::lexical_cast(enchant->data.charge); /// \todo track current charge + if (enchant->data.type == ESM::Enchantment::CastOnce) + { + // this is Morrowind behaviour + cost = "100"; + charge = "100"; + } + + costCharge->setCaption(cost + "/" + charge); + costCharge->setTextAlign(MyGUI::Align::Right); + costCharge->setNeedMouseFocus(false); + costCharge->setStateSelected(item == selectedItem); + + mHeight += spellHeight; + } + + mSpellView->setCanvasSize(mSpellView->getWidth(), std::max(mSpellView->getHeight(), mHeight)); + } + + 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); + } + + mHeight += 24; + } + + void SpellWindow::onWindowResize(MyGUI::Window* _sender) + { + updateSpells(); + } + + void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); + MWMechanics::Spells& spells = stats.mSpells; + MWWorld::Ptr item = *_sender->getUserData(); + + // retrieve ContainerStoreIterator to the item + MWWorld::ContainerStoreIterator it = store.begin(); + for (; it != store.end(); ++it) + { + if (*it == item) + { + break; + } + } + assert(it != store.end()); + + // equip, if it can be equipped and is not already equipped + if (_sender->getUserString("Equipped") == "false" + && !MWWorld::Class::get(item).getEquipmentSlots(item).first.empty()) + { + // sound + MWBase::Environment::get().getSoundManager()->playSound(MWWorld::Class::get(item).getUpSoundId(item), 1.0, 1.0); + + // Note: can't use Class::use here because enchanted scrolls for example would then open the scroll window instead of equipping + + /// \todo the following code is pretty much copy&paste from ActionEquip, put it in a function? + // slots that this item can be equipped in + std::pair, bool> slots = MWWorld::Class::get(item).getEquipmentSlots(item); + + // equip the item in the first free slot + for (std::vector::const_iterator slot=slots.first.begin(); + slot!=slots.first.end(); ++slot) + { + // if all slots are occupied, replace the last slot + if (slot == --slots.first.end()) + { + store.equip(*slot, it); + break; + } + + if (store.getSlot(*slot) == store.end()) + { + // slot is not occupied + store.equip(*slot, it); + break; + } + } + /// \todo scripts? + + // since we changed equipping status, update the inventory window + mWindowManager.getInventoryWindow()->drawItems(); + } + + store.setSelectedEnchantItem(it); + spells.setSelectedSpell(""); + mWindowManager.setSelectedEnchantItem(item, 100); /// \todo track charge % + + updateSpells(); + } + + void SpellWindow::onSpellSelected(MyGUI::Widget* _sender) + { + std::string spellId = _sender->getUserString("Spell"); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); + MWMechanics::Spells& spells = stats.mSpells; + + if (MyGUI::InputManager::getInstance().isShiftPressed()) + { + // delete spell, if allowed + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId); + if (spell->data.flags & ESM::Spell::F_Always + || spell->data.type == ESM::Spell::ST_Power) + { + mWindowManager.messageBox("#{sDeleteSpellError}", std::vector()); + } + else + { + // ask for confirmation + mSpellToDelete = spellId; + ConfirmationDialog* dialog = mWindowManager.getConfirmationDialog(); + std::string question = mWindowManager.getGameSettingString("sQuestionDeleteSpell", "Delete %s?"); + question = boost::str(boost::format(question) % spell->name); + dialog->open(question); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &SpellWindow::onDeleteSpellAccept); + dialog->eventCancelClicked.clear(); + } + } + else + { + spells.setSelectedSpell(spellId); + store.setSelectedEnchantItem(store.end()); + mWindowManager.setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); + } + + 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()->getPlayer().getPlayer(); + MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWMechanics::Spells& spells = stats.mSpells; + + if (spells.getSelectedSpell() == mSpellToDelete) + { + spells.setSelectedSpell(""); + mWindowManager.unsetSelectedSpell(); + } + + spells.remove(mSpellToDelete); + + updateSpells(); + } +} diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp new file mode 100644 index 0000000000..87e4783ba2 --- /dev/null +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -0,0 +1,39 @@ +#ifndef MWGUI_SPELLWINDOW_H +#define MWGUI_SPELLWINDOW_H + +#include "window_pinnable_base.hpp" + +namespace MWGui +{ + class SpellWindow : public WindowPinnableBase + { + public: + SpellWindow(WindowManager& parWindowManager); + + void updateSpells(); + + protected: + MyGUI::ScrollView* mSpellView; + MyGUI::Widget* mEffectBox; + + int mHeight; + int mWidth; + + 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 onDeleteSpellAccept(); + + virtual void onPinToggled(); + virtual void open(); + }; +} + +#endif diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index bdce61a44f..0f68921905 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -67,6 +67,11 @@ void ToolTips::onFrame(float frameDuration) if (!mGameMode) { const MyGUI::IntPoint& mousePos = InputManager::getInstance().getMousePosition(); + const MyGUI::IntPoint& lastPressed = InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left); + + if (mousePos == lastPressed) // mouseclick makes tooltip disappear + return; + if (mousePos.left == mLastMouseX && mousePos.top == mLastMouseY) { mRemainingDelay -= frameDuration; @@ -397,7 +402,6 @@ IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) TextBox* chargeText = enchantArea->createWidget("SandText", IntCoord(0, 0, 10, 18), Align::Default, "ToolTipEnchantChargeText"); chargeText->setCaption(store.gameSettings.search("sCharges")->str); - chargeText->setProperty("Static", "true"); const int chargeTextWidth = chargeText->getTextSize().width + 5; const int chargeAndTextWidth = chargeWidth + chargeTextWidth; diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 0a12a82a08..2089ed4aff 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -104,6 +104,8 @@ namespace MWGui ContainerBase::openContainer(actor); updateLabels(); + + drawItems(); } void TradeWindow::onFilterChanged(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index 4e786978f3..8ddbfe9298 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -19,6 +19,7 @@ #include "settingswindow.hpp" #include "confirmationdialog.hpp" #include "alchemywindow.hpp" +#include "spellwindow.hpp" #include "../mwmechanics/mechanicsmanager.hpp" #include "../mwinput/inputmanager.hpp" @@ -56,6 +57,7 @@ WindowManager::WindowManager( , mSettingsWindow(NULL) , mConfirmationDialog(NULL) , mAlchemyWindow(NULL) + , mSpellWindow(NULL) , mCharGen(NULL) , playerClass() , playerName() @@ -124,6 +126,7 @@ WindowManager::WindowManager( mSettingsWindow = new SettingsWindow(*this); mConfirmationDialog = new ConfirmationDialog(*this); mAlchemyWindow = new AlchemyWindow(*this); + mSpellWindow = new SpellWindow(*this); // The HUD is always on hud->setVisible(true); @@ -141,6 +144,9 @@ WindowManager::WindowManager( playerSkillValues.insert(std::make_pair(ESM::Skill::skillIds[i], MWMechanics::Stat())); } + unsetSelectedSpell(); + unsetSelectedWeapon(); + // Set up visibility updateVisible(); } @@ -167,6 +173,7 @@ WindowManager::~WindowManager() delete mSettingsWindow; delete mConfirmationDialog; delete mAlchemyWindow; + delete mSpellWindow; cleanupGarbage(); } @@ -187,12 +194,10 @@ void WindowManager::cleanupGarbage() void WindowManager::update() { cleanupGarbage(); - if (showFPSLevel > 0) - { - hud->setFPS(mFPS); - hud->setTriangleCount(mTriangleCount); - hud->setBatchCount(mBatchCount); - } + + hud->setFPS(mFPS); + hud->setTriangleCount(mTriangleCount); + hud->setBatchCount(mBatchCount); } void WindowManager::updateVisible() @@ -211,6 +216,7 @@ void WindowManager::updateVisible() mTradeWindow->setVisible(false); mSettingsWindow->setVisible(false); mAlchemyWindow->setVisible(false); + mSpellWindow->setVisible(false); // Mouse is visible whenever we're not in game mode MyGUI::PointerManager::getInstance().setVisible(isGuiMode()); @@ -224,7 +230,7 @@ void WindowManager::updateVisible() setMinimapVisibility((allowed & GW_Map) && !map->pinned()); setWeaponVisibility((allowed & GW_Inventory) && !mInventoryWindow->pinned()); - setSpellVisibility((allowed & GW_Magic)); /// \todo add pin state when spells window is implemented + setSpellVisibility((allowed & GW_Magic) && !mSpellWindow->pinned()); setHMSVisibility((allowed & GW_Stats) && !mStatsWindow->pinned()); // If in game mode, don't show anything. @@ -271,9 +277,10 @@ void WindowManager::updateVisible() int eff = shown & allowed; // Show the windows we want - map -> setVisible( (eff & GW_Map) ); - mStatsWindow -> setVisible( (eff & GW_Stats) ); - mInventoryWindow->setVisible( (eff & GW_Inventory)); + map -> setVisible(eff & GW_Map); + mStatsWindow -> setVisible(eff & GW_Stats); + mInventoryWindow->setVisible(eff & GW_Inventory); + mSpellWindow->setVisible(eff & GW_Magic); break; } case GM_Container: @@ -670,3 +677,34 @@ void WindowManager::removeGuiMode(GuiMode mode) updateVisible(); } + +void WindowManager::setSelectedSpell(const std::string& spellId, int successChancePercent) +{ + hud->setSelectedSpell(spellId, successChancePercent); + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId); + mSpellWindow->setTitle(spell->name); +} + +void WindowManager::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) +{ + hud->setSelectedEnchantItem(item, chargePercent); + mSpellWindow->setTitle(MWWorld::Class::get(item).getName(item)); +} + +void WindowManager::setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent) +{ + hud->setSelectedWeapon(item, durabilityPercent); + mInventoryWindow->setTitle(MWWorld::Class::get(item).getName(item)); +} + +void WindowManager::unsetSelectedSpell() +{ + hud->unsetSelectedSpell(); + mSpellWindow->setTitle("#{sNone}"); +} + +void WindowManager::unsetSelectedWeapon() +{ + hud->unsetSelectedWeapon(); + mInventoryWindow->setTitle("#{sSkillHandtohand}"); +} diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index f3e6a436e3..037022b42b 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -81,6 +81,7 @@ namespace MWGui class SettingsWindow; class ConfirmationDialog; class AlchemyWindow; + class SpellWindow; struct ClassPoint { @@ -153,6 +154,7 @@ namespace MWGui MWGui::CountDialog* getCountDialog() {return mCountDialog;} MWGui::ConfirmationDialog* getConfirmationDialog() {return mConfirmationDialog;} MWGui::TradeWindow* getTradeWindow() {return mTradeWindow;} + MWGui::SpellWindow* getSpellWindow() {return mSpellWindow;} MyGUI::Gui* getGui() const { return gui; } @@ -202,6 +204,12 @@ namespace MWGui void setWeaponVisibility(bool visible); void setSpellVisibility(bool visible); + void setSelectedSpell(const std::string& spellId, int successChancePercent); + void setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent); + void setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent); + void unsetSelectedSpell(); + void unsetSelectedWeapon(); + template void removeDialog(T*& dialog); ///< Casts to OEngine::GUI::Layout and calls removeDialog, then resets pointer to nullptr. void removeDialog(OEngine::GUI::Layout* dialog); ///< Hides dialog and schedules dialog to be deleted. @@ -250,6 +258,7 @@ namespace MWGui SettingsWindow* mSettingsWindow; ConfirmationDialog* mConfirmationDialog; AlchemyWindow* mAlchemyWindow; + SpellWindow* mSpellWindow; CharacterCreation* mCharGen; diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index af43cdfb55..70eb786392 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -28,7 +28,7 @@ namespace MWMechanics void Spells::add (const std::string& spellId) { - if (std::find (mSpells.begin(), mSpells.end(), spellId)!=mSpells.end()) + if (std::find (mSpells.begin(), mSpells.end(), spellId)==mSpells.end()) mSpells.push_back (spellId); } diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp new file mode 100644 index 0000000000..11ac7cda76 --- /dev/null +++ b/apps/openmw/mwmechanics/spellsuccess.hpp @@ -0,0 +1,99 @@ +#ifndef MWMECHANICS_SPELLSUCCESS_H +#define MWMECHANICS_SPELLSUCCESS_H + +#include "../mwworld/ptr.hpp" +#include "../mwworld/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwmechanics/creaturestats.hpp" + +#include "npcstats.hpp" + +namespace MWMechanics +{ + inline int spellSchoolToSkill(int school) + { + std::map schoolSkillMap; // maps spell school to skill id + schoolSkillMap[0] = 11; // alteration + schoolSkillMap[1] = 13; // conjuration + schoolSkillMap[3] = 12; // illusion + schoolSkillMap[2] = 10; // destruction + schoolSkillMap[4] = 14; // mysticism + schoolSkillMap[5] = 15; // restoration + assert(schoolSkillMap.find(school) != schoolSkillMap.end()); + return schoolSkillMap[school]; + } + + inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId); + NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); + + // determine the spell's school + // this is always the school where the player's respective skill is the least advanced + // out of all the magic effects' schools + const std::vector& effects = spell->effects.list; + int school = -1; + int skillLevel = -1; + for (std::vector::const_iterator it = effects.begin(); + it != effects.end(); ++it) + { + const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().magicEffects.find(it->effectID); + int _school = effect->data.school; + int _skillLevel = stats.mSkill[spellSchoolToSkill(_school)].getModified(); + + if (school == -1) + { + school = _school; + skillLevel = _skillLevel; + } + else if (_skillLevel < skillLevel) + { + school = _school; + skillLevel = _skillLevel; + } + } + + return school; + } + + + // UESP wiki / Morrowind/Spells: + // Chance of success is (Spell's skill * 2 + Willpower / 5 + Luck / 10 - Spell cost - Sound magnitude) * (Current fatigue + Maximum Fatigue * 1.5) / Maximum fatigue * 2 + /** + * @param spellId ID of spell + * @param actor calculate spell success chance for this actor (depends on actor's skills) + * @attention actor has to be an NPC and not a creature! + * @return success chance from 0 to 100 (in percent) + */ + inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId); + + if (spell->data.flags & ESM::Spell::F_Always // spells with this flag always succeed (usually birthsign spells) + || spell->data.type == ESM::Spell::ST_Power) // powers always succeed, but can be cast only once per day + return 100.0; + + NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); + CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor); + + int skillLevel = stats.mSkill[getSpellSchool(spellId, actor)].getModified(); + + // Sound magic effect (reduces spell casting chance) + int soundMagnitude = creatureStats.mMagicEffects.get (MWMechanics::EffectKey (48)).mMagnitude; + + int willpower = creatureStats.mAttributes[ESM::Attribute::Willpower].getModified(); + int luck = creatureStats.mAttributes[ESM::Attribute::Luck].getModified(); + int currentFatigue = creatureStats.mDynamic[2].getCurrent(); + int maxFatigue = creatureStats.mDynamic[2].getModified(); + int spellCost = spell->data.cost; + + // There we go, all needed variables are there, lets go + float chance = (float(skillLevel * 2) + float(willpower)/5.0 + float(luck)/ 10.0 - spellCost - soundMagnitude) * (float(currentFatigue + maxFatigue * 1.5)) / float(maxFatigue * 2.0); + + chance = std::max(0.0f, std::min(100.0f, chance)); // clamp to 0 .. 100 + + return chance; + } +} + +#endif diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index b669508b25..3304d0e6d0 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -61,6 +61,7 @@ bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) /// \todo add current weapon/armor health, remaining lockpick/repair uses, current enchantment charge here as soon as they are implemented if ( ptr1.mCellRef->refID == ptr2.mCellRef->refID && MWWorld::Class::get(ptr1).getScript(ptr1) == "" // item with a script never stacks + && MWWorld::Class::get(ptr1).getEnchantment(ptr1) == "" // item with enchantment never stacks (we could revisit this later, but for now it makes selecting items in the spell window much easier) && ptr1.mCellRef->owner == ptr2.mCellRef->owner && ptr1.mCellRef->soul == ptr2.mCellRef->soul && ptr1.mCellRef->charge == ptr2.mCellRef->charge) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 98ab1665b4..6df73004be 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -39,15 +39,18 @@ void MWWorld::InventoryStore::initSlots (TSlots& slots) } MWWorld::InventoryStore::InventoryStore() : mMagicEffectsUpToDate (false) + , mSelectedEnchantItem(end()) { initSlots (mSlots); } MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) : ContainerStore (store) + , mSelectedEnchantItem(end()) { mMagicEffects = store.mMagicEffects; mMagicEffectsUpToDate = store.mMagicEffectsUpToDate; + mSelectedEnchantItem = store.mSelectedEnchantItem; copySlots (store); } @@ -260,3 +263,13 @@ bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) return true; } + +void MWWorld::InventoryStore::setSelectedEnchantItem(const ContainerStoreIterator& iterator) +{ + mSelectedEnchantItem = iterator; +} + +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem() +{ + return mSelectedEnchantItem; +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index dcfb21f30f..45b3cab268 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -50,6 +50,9 @@ namespace MWWorld mutable TSlots mSlots; + // selected magic item (for using enchantments of type "Cast once" or "Cast when used") + ContainerStoreIterator mSelectedEnchantItem; + void copySlots (const InventoryStore& store); void initSlots (TSlots& slots); @@ -63,7 +66,15 @@ namespace MWWorld InventoryStore& operator= (const InventoryStore& store); void equip (int slot, const ContainerStoreIterator& iterator); - ///< \note \a iteartor can be an end-iterator + ///< \note \a iterator can be an end-iterator + + 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 + + ContainerStoreIterator getSelectedEnchantItem(); + ///< @return selected magic item (for using enchantments of type "Cast once" or "Cast when used") + /// \note if no item selected, return end() iterator ContainerStoreIterator getSlot (int slot); diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 6b16e5f599..dad4afb466 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -60,6 +60,7 @@ configure_file("${SDIR}/openmw_trade_window_layout.xml" "${DDIR}/openmw_trade_wi configure_file("${SDIR}/openmw_settings_window_layout.xml" "${DDIR}/openmw_settings_window_layout.xml" COPYONLY) configure_file("${SDIR}/openmw_confirmation_dialog_layout.xml" "${DDIR}/openmw_confirmation_dialog_layout.xml" COPYONLY) configure_file("${SDIR}/openmw_alchemy_window_layout.xml" "${DDIR}/openmw_alchemy_window_layout.xml" COPYONLY) +configure_file("${SDIR}/openmw_spell_window_layout.xml" "${DDIR}/openmw_spell_window_layout.xml" COPYONLY) configure_file("${SDIR}/atlas1.cfg" "${DDIR}/atlas1.cfg" COPYONLY) configure_file("${SDIR}/smallbars.png" "${DDIR}/smallbars.png" COPYONLY) configure_file("${SDIR}/EBGaramond-Regular.ttf" "${DDIR}/EBGaramond-Regular.ttf" COPYONLY) diff --git a/files/mygui/openmw_hud_layout.xml b/files/mygui/openmw_hud_layout.xml index d0026bf251..cf353a205e 100644 --- a/files/mygui/openmw_hud_layout.xml +++ b/files/mygui/openmw_hud_layout.xml @@ -28,6 +28,14 @@ + + + + + + + + diff --git a/files/mygui/openmw_spell_window_layout.xml b/files/mygui/openmw_spell_window_layout.xml new file mode 100644 index 0000000000..d489f41b8a --- /dev/null +++ b/files/mygui/openmw_spell_window_layout.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index bfa970de52..e29483e35b 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -99,6 +99,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp index d9eefe0510..e6feb3d0ed 100644 --- a/libs/openengine/gui/layout.hpp +++ b/libs/openengine/gui/layout.hpp @@ -118,7 +118,7 @@ namespace GUI void setTitle(const std::string& title) { // NOTE: this assume that mMainWidget is of type Window. - static_cast(mMainWidget)->setCaption(title); + static_cast(mMainWidget)->setCaptionWithReplacing(title); adjustWindowCaption(); }