diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 1cfbc1b2b..33ee8bf49 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -535,6 +535,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) diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 5cd8167c2..88e445a7b 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 7276218d6..fdd1c8821 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -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) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 1a715657a..e1d4cc998 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 { @@ -259,4 +260,11 @@ 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(); + } } diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index ecff75f10..bc4cb08ef 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 index 9c903134d..f761e00db 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -1,20 +1,378 @@ #include "spellwindow.hpp" +#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 "../mwsound/soundmanager.hpp" + #include "window_manager.hpp" +#include "inventorywindow.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()); + 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->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->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); + + if (*it == selectedSpell) + t->setStateSelected(true); + + // cost / success chance + MyGUI::TextBox* 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); + costChance->setCaption(cost + "/" + "100"); /// \todo + costChance->setTextAlign(MyGUI::Align::Right); + costChance->setNeedMouseFocus(false); + + + mHeight += spellHeight; + } + + + // enchanted items + addGroup("#{sMagicItem}", "#{sCostCharge}"); + + 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 = 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->setStateSelected(item == selectedItem); + + // cost / charge + MyGUI::TextBox* costCharge = mSpellView->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", + MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + + const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().enchants.find(MWWorld::Class::get(item).getEnchantment(item)); + std::string cost = boost::lexical_cast(enchant->data.cost); + std::string charge = boost::lexical_cast(enchant->data.charge); /// \todo track current charge + costCharge->setCaption(cost + "/" + charge); + costCharge->setTextAlign(MyGUI::Align::Right); + costCharge->setNeedMouseFocus(false); + + 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 is not already equipped + if (_sender->getUserString("Equipped") == "false") + { + // 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(""); + + updateSpells(); + } + + void SpellWindow::onSpellSelected(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; + + spells.setSelectedSpell(_sender->getUserString("Spell")); + store.setSelectedEnchantItem(store.end()); + + updateSpells(); + } + + int SpellWindow::estimateHeight(int numSpells) const + { + int height = 0; + height += 24 * 3 + 18 * 2; // group headings + height += numSpells * 18; + return height; + } } diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index 0450be473..16a1c7ff8 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -10,11 +10,24 @@ namespace MWGui public: SpellWindow(WindowManager& parWindowManager); + void updateSpells(); + protected: MyGUI::ScrollView* mSpellView; MyGUI::Widget* mEffectBox; + int mHeight; + int mWidth; + + void addGroup(const std::string& label, const std::string& label2); + + int estimateHeight(int numSpells) const; + virtual void onPinToggled(); + void onWindowResize(MyGUI::Window* _sender); + void onEnchantedItemSelected(MyGUI::Widget* _sender); + void onSpellSelected(MyGUI::Widget* _sender); + virtual void open(); }; } diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index bdce61a44..dc34ee86c 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; diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index 2db71438a..4eaaf72a4 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -191,12 +191,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() diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index 872f46f3d..2a7794f70 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -154,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; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index b669508b2..3304d0e6d 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 98ab1665b..6df73004b 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 dcfb21f30..45b3cab26 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/openmw_spell_window_layout.xml b/files/mygui/openmw_spell_window_layout.xml index 66a685c38..d489f41b8 100644 --- a/files/mygui/openmw_spell_window_layout.xml +++ b/files/mygui/openmw_spell_window_layout.xml @@ -12,6 +12,7 @@ + diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index bfa970de5..e29483e35 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -99,6 +99,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +