1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-12-13 20:13:06 +00:00
openmw/apps/openmw/mwgui/inventorywindow.cpp
elsid 463de2d791
Instantly transfer items if alt key is pressed
Support player's inventory, container and companion windows with
dropping and picking up items from the world.

Add ItemTransfer class to handle transfers similar to DragAndDrop. When
a container window is opened the view is added to the set of targets and
removed when closed. If ALT is pressed instead of starting dragging an
item perform a transfer. If there is 1 target which is not a source move
item there. If there is no target drop item into the world using
WorldItemModel.

Special case is picking up an item. Don't start dragging, just update
the player's inventory view because the item is already there.
2025-07-12 13:34:59 +02:00

909 lines
34 KiB
C++

#include "inventorywindow.hpp"
#include <cmath>
#include <stdexcept>
#include <MyGUI_Button.h>
#include <MyGUI_EditBox.h>
#include <MyGUI_ImageBox.h>
#include <MyGUI_InputManager.h>
#include <MyGUI_RenderManager.h>
#include <MyGUI_Window.h>
#include <osg/Texture2D>
#include <components/misc/strings/algorithm.hpp>
#include <components/myguiplatform/myguitexture.hpp>
#include <components/settings/values.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/action.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "countdialog.hpp"
#include "draganddrop.hpp"
#include "inventoryitemmodel.hpp"
#include "itemtransfer.hpp"
#include "itemview.hpp"
#include "settings.hpp"
#include "sortfilteritemmodel.hpp"
#include "tooltips.hpp"
#include "tradeitemmodel.hpp"
#include "tradewindow.hpp"
namespace
{
bool isRightHandWeapon(const MWWorld::Ptr& item)
{
if (item.getClass().getType() != ESM::Weapon::sRecordId)
return false;
std::vector<int> equipmentSlots = item.getClass().getEquipmentSlots(item).first;
return (!equipmentSlots.empty() && equipmentSlots.front() == MWWorld::InventoryStore::Slot_CarriedRight);
}
}
namespace MWGui
{
namespace
{
WindowSettingValues getModeSettings(GuiMode mode)
{
switch (mode)
{
case GM_Container:
return makeInventoryContainerWindowSettingValues();
case GM_Companion:
return makeInventoryCompanionWindowSettingValues();
case GM_Barter:
return makeInventoryBarterWindowSettingValues();
default:
return makeInventoryWindowSettingValues();
}
}
}
InventoryWindow::InventoryWindow(DragAndDrop& dragAndDrop, ItemTransfer& itemTransfer, osg::Group* parent,
Resource::ResourceSystem* resourceSystem)
: WindowPinnableBase("openmw_inventory_window.layout")
, mDragAndDrop(&dragAndDrop)
, mItemTransfer(&itemTransfer)
, mSelectedItem(-1)
, mSortModel(nullptr)
, mTradeModel(nullptr)
, mGuiMode(GM_Inventory)
, mLastXSize(0)
, mLastYSize(0)
, mPreview(std::make_unique<MWRender::InventoryPreview>(parent, resourceSystem, MWMechanics::getPlayer()))
, mTrading(false)
, mUpdateNextFrame(false)
{
mPreviewTexture
= std::make_unique<osgMyGUI::OSGTexture>(mPreview->getTexture(), mPreview->getTextureStateSet());
mPreview->rebuild();
mMainWidget->castType<MyGUI::Window>()->eventWindowChangeCoord
+= MyGUI::newDelegate(this, &InventoryWindow::onWindowResize);
getWidget(mAvatar, "Avatar");
getWidget(mAvatarImage, "AvatarImage");
getWidget(mEncumbranceBar, "EncumbranceBar");
getWidget(mFilterAll, "AllButton");
getWidget(mFilterWeapon, "WeaponButton");
getWidget(mFilterApparel, "ApparelButton");
getWidget(mFilterMagic, "MagicButton");
getWidget(mFilterMisc, "MiscButton");
getWidget(mLeftPane, "LeftPane");
getWidget(mRightPane, "RightPane");
getWidget(mArmorRating, "ArmorRating");
getWidget(mFilterEdit, "FilterEdit");
mAvatarImage->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked);
mAvatarImage->setRenderItemTexture(mPreviewTexture.get());
mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f));
getWidget(mItemView, "ItemView");
mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected);
mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &InventoryWindow::onBackgroundSelected);
mFilterAll->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged);
mFilterWeapon->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged);
mFilterApparel->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged);
mFilterMagic->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged);
mFilterMisc->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged);
mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &InventoryWindow::onNameFilterChanged);
mFilterAll->setStateSelected(true);
setGuiMode(mGuiMode);
adjustPanes();
}
void InventoryWindow::adjustPanes()
{
const float aspect = 0.5; // fixed aspect ratio for the avatar image
int leftPaneWidth = static_cast<int>((mMainWidget->getSize().height - 44 - mArmorRating->getHeight()) * aspect);
mLeftPane->setSize(leftPaneWidth, mMainWidget->getSize().height - 44);
mRightPane->setCoord(mLeftPane->getPosition().left + leftPaneWidth + 4, mRightPane->getPosition().top,
mMainWidget->getSize().width - 12 - leftPaneWidth - 15, mMainWidget->getSize().height - 44);
}
void InventoryWindow::updatePlayer()
{
mPtr = MWBase::Environment::get().getWorld()->getPlayerPtr();
auto tradeModel = std::make_unique<TradeItemModel>(std::make_unique<InventoryItemModel>(mPtr), MWWorld::Ptr());
mTradeModel = tradeModel.get();
mPtr.getClass().getInventoryStore(mPtr).setContListener(this);
if (mSortModel) // reuse existing SortModel when possible to keep previous category/filter settings
mSortModel->setSourceModel(std::move(tradeModel));
else
{
auto sortModel = std::make_unique<SortFilterItemModel>(std::move(tradeModel));
mSortModel = sortModel.get();
mItemView->setModel(std::move(sortModel));
}
mSortModel->setNameFilter(mFilterEdit->getCaption());
mFilterAll->setStateSelected(true);
mFilterWeapon->setStateSelected(false);
mFilterApparel->setStateSelected(false);
mFilterMagic->setStateSelected(false);
mFilterMisc->setStateSelected(false);
mPreview->updatePtr(mPtr);
mPreview->rebuild();
mPreview->update();
dirtyPreview();
updatePreviewSize();
updateEncumbranceBar();
mItemView->update();
notifyContentChanged();
}
void InventoryWindow::clear()
{
mPtr = MWWorld::Ptr();
mTradeModel = nullptr;
mSortModel = nullptr;
mItemView->setModel(nullptr);
}
void InventoryWindow::toggleMaximized()
{
const WindowSettingValues settings = getModeSettings(mGuiMode);
const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mRegular : settings.mMaximized;
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
const float x = rect.mX * viewSize.width;
const float y = rect.mY * viewSize.height;
const float w = rect.mW * viewSize.width;
const float h = rect.mH * viewSize.height;
MyGUI::Window* window = mMainWidget->castType<MyGUI::Window>();
window->setCoord(x, y, w, h);
settings.mIsMaximized.set(!settings.mIsMaximized);
adjustPanes();
updatePreviewSize();
}
void InventoryWindow::setGuiMode(GuiMode mode)
{
mGuiMode = mode;
const WindowSettingValues settings = getModeSettings(mGuiMode);
setPinButtonVisible(mode != GM_Container && mode != GM_Companion && mode != GM_Barter);
const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mMaximized : settings.mRegular;
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
MyGUI::IntPoint pos(static_cast<int>(rect.mX * viewSize.width), static_cast<int>(rect.mY * viewSize.height));
MyGUI::IntSize size(static_cast<int>(rect.mW * viewSize.width), static_cast<int>(rect.mH * viewSize.height));
bool needUpdate = (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight());
mMainWidget->setPosition(pos);
mMainWidget->setSize(size);
adjustPanes();
if (needUpdate)
updatePreviewSize();
}
SortFilterItemModel* InventoryWindow::getSortFilterModel()
{
return mSortModel;
}
TradeItemModel* InventoryWindow::getTradeModel()
{
return mTradeModel;
}
ItemModel* InventoryWindow::getModel()
{
return mTradeModel;
}
void InventoryWindow::onBackgroundSelected()
{
if (mDragAndDrop->mIsOnDragAndDrop)
mDragAndDrop->drop(mTradeModel, mItemView);
}
void InventoryWindow::onItemSelected(int index)
{
onItemSelectedFromSourceModel(mSortModel->mapToSource(index));
}
void InventoryWindow::onItemSelectedFromSourceModel(int index)
{
if (mDragAndDrop->mIsOnDragAndDrop)
{
mDragAndDrop->drop(mTradeModel, mItemView);
return;
}
const ItemStack& item = mTradeModel->getItem(index);
const ESM::RefId& sound = item.mBase.getClass().getDownSoundId(item.mBase);
MWWorld::Ptr object = item.mBase;
int count = item.mCount;
bool shift = MyGUI::InputManager::getInstance().isShiftPressed();
if (MyGUI::InputManager::getInstance().isControlPressed())
count = 1;
if (mTrading)
{
// Can't give conjured items to a merchant
if (item.mFlags & ItemStack::Flag_Bound)
{
MWBase::Environment::get().getWindowManager()->playSound(sound);
MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog9}");
return;
}
// check if merchant accepts item
int services = MWBase::Environment::get().getWindowManager()->getTradeWindow()->getMerchantServices();
if (!object.getClass().canSell(object, services))
{
MWBase::Environment::get().getWindowManager()->playSound(sound);
MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog4}");
return;
}
}
// If we unequip weapon during attack, it can lead to unexpected behaviour
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPtr))
{
bool isWeapon = item.mBase.getType() == ESM::Weapon::sRecordId;
MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr);
if (isWeapon && invStore.isEquipped(item.mBase))
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sCantEquipWeapWarning}");
return;
}
}
if (count > 1 && !shift)
{
CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog();
std::string message = mTrading ? "#{sQuanityMenuMessage01}" : "#{sTake}";
std::string name{ object.getClass().getName(object) };
name += MWGui::ToolTips::getSoulString(object.getCellRef());
dialog->openCountDialog(name, message, count);
dialog->eventOkClicked.clear();
if (mTrading)
dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::sellItem);
else if (MyGUI::InputManager::getInstance().isAltPressed())
dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::transferItem);
else
dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::dragItem);
mSelectedItem = index;
}
else
{
mSelectedItem = index;
if (mTrading)
sellItem(nullptr, count);
else if (MyGUI::InputManager::getInstance().isAltPressed())
transferItem(nullptr, count);
else
dragItem(nullptr, count);
}
}
void InventoryWindow::ensureSelectedItemUnequipped(int count)
{
const ItemStack& item = mTradeModel->getItem(mSelectedItem);
if (item.mType == ItemStack::Type_Equipped)
{
MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::Ptr newStack = *invStore.unequipItemQuantity(item.mBase, count);
// The unequipped item was re-stacked. We have to update the index
// since the item pointed does not exist anymore.
if (item.mBase != newStack)
{
updateItemView(); // Unequipping can produce a new stack, not yet in the window...
// newIndex will store the index of the ItemStack the item was stacked on
int newIndex = -1;
for (size_t i = 0; i < mTradeModel->getItemCount(); ++i)
{
if (mTradeModel->getItem(i).mBase == newStack)
{
newIndex = i;
break;
}
}
if (newIndex == -1)
throw std::runtime_error("Can't find restacked item");
mSelectedItem = newIndex;
}
}
}
void InventoryWindow::dragItem(MyGUI::Widget* /*sender*/, std::size_t count)
{
ensureSelectedItemUnequipped(count);
mDragAndDrop->startDrag(mSelectedItem, mSortModel, mTradeModel, mItemView, count);
notifyContentChanged();
}
void InventoryWindow::transferItem(MyGUI::Widget* /*sender*/, std::size_t count)
{
ensureSelectedItemUnequipped(count);
mItemTransfer->apply(mTradeModel->getItem(mSelectedItem), count, *mItemView);
notifyContentChanged();
}
void InventoryWindow::sellItem(MyGUI::Widget* /*sender*/, std::size_t count)
{
ensureSelectedItemUnequipped(count);
const ItemStack& item = mTradeModel->getItem(mSelectedItem);
const ESM::RefId& sound = item.mBase.getClass().getUpSoundId(item.mBase);
MWBase::Environment::get().getWindowManager()->playSound(sound);
if (item.mType == ItemStack::Type_Barter)
{
// this was an item borrowed to us by the merchant
mTradeModel->returnItemBorrowedToUs(mSelectedItem, count);
MWBase::Environment::get().getWindowManager()->getTradeWindow()->returnItem(mSelectedItem, count);
}
else
{
// borrow item to the merchant
mTradeModel->borrowItemFromUs(mSelectedItem, count);
MWBase::Environment::get().getWindowManager()->getTradeWindow()->borrowItem(mSelectedItem, count);
}
mItemView->update();
notifyContentChanged();
}
void InventoryWindow::updateItemView()
{
MWBase::Environment::get().getWindowManager()->updateSpellWindow();
mItemView->update();
dirtyPreview();
}
void InventoryWindow::onOpen()
{
// Reset the filter focus when opening the window
MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget();
if (focus == mFilterEdit)
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(nullptr);
if (!mPtr.isEmpty())
{
updateEncumbranceBar();
mItemView->update();
notifyContentChanged();
}
adjustPanes();
mItemTransfer->addTarget(*mItemView);
}
void InventoryWindow::onClose()
{
mItemTransfer->removeTarget(*mItemView);
}
void InventoryWindow::onWindowResize(MyGUI::Window* _sender)
{
WindowBase::clampWindowCoordinates(_sender);
adjustPanes();
const WindowSettingValues settings = getModeSettings(mGuiMode);
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
settings.mRegular.mX.set(_sender->getPosition().left / static_cast<float>(viewSize.width));
settings.mRegular.mY.set(_sender->getPosition().top / static_cast<float>(viewSize.height));
settings.mRegular.mW.set(_sender->getSize().width / static_cast<float>(viewSize.width));
settings.mRegular.mH.set(_sender->getSize().height / static_cast<float>(viewSize.height));
settings.mIsMaximized.set(false);
if (mMainWidget->getSize().width != mLastXSize || mMainWidget->getSize().height != mLastYSize)
{
mLastXSize = mMainWidget->getSize().width;
mLastYSize = mMainWidget->getSize().height;
updatePreviewSize();
updateArmorRating();
}
}
void InventoryWindow::updateArmorRating()
{
if (mPtr.isEmpty())
return;
mArmorRating->setCaptionWithReplacing(
"#{sArmor}: " + MyGUI::utility::toString(static_cast<int>(mPtr.getClass().getArmorRating(mPtr))));
if (mArmorRating->getTextSize().width > mArmorRating->getSize().width)
mArmorRating->setCaptionWithReplacing(
MyGUI::utility::toString(static_cast<int>(mPtr.getClass().getArmorRating(mPtr))));
}
void InventoryWindow::updatePreviewSize()
{
const MyGUI::IntSize viewport = getPreviewViewportSize();
mPreview->setViewport(viewport.width, viewport.height);
mAvatarImage->getSubWidgetMain()->_setUVSet(
MyGUI::FloatRect(0.f, 0.f, viewport.width / float(mPreview->getTextureWidth()),
viewport.height / float(mPreview->getTextureHeight())));
}
void InventoryWindow::onNameFilterChanged(MyGUI::EditBox* _sender)
{
mSortModel->setNameFilter(_sender->getCaption());
mItemView->update();
}
void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender)
{
if (_sender == mFilterAll)
mSortModel->setCategory(SortFilterItemModel::Category_All);
else if (_sender == mFilterWeapon)
mSortModel->setCategory(SortFilterItemModel::Category_Weapon);
else if (_sender == mFilterApparel)
mSortModel->setCategory(SortFilterItemModel::Category_Apparel);
else if (_sender == mFilterMagic)
mSortModel->setCategory(SortFilterItemModel::Category_Magic);
else if (_sender == mFilterMisc)
mSortModel->setCategory(SortFilterItemModel::Category_Misc);
mFilterAll->setStateSelected(false);
mFilterWeapon->setStateSelected(false);
mFilterApparel->setStateSelected(false);
mFilterMagic->setStateSelected(false);
mFilterMisc->setStateSelected(false);
mItemView->update();
_sender->castType<MyGUI::Button>()->setStateSelected(true);
}
void InventoryWindow::onPinToggled()
{
Settings::windows().mInventoryPin.set(mPinned);
MWBase::Environment::get().getWindowManager()->setWeaponVisibility(!mPinned);
}
void InventoryWindow::onTitleDoubleClicked()
{
if (MyGUI::InputManager::getInstance().isShiftPressed())
toggleMaximized();
else if (!mPinned)
MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory);
}
void InventoryWindow::useItem(const MWWorld::Ptr& ptr, bool force)
{
const ESM::RefId& script = ptr.getClass().getScript(ptr);
if (!script.empty())
{
// Don't try to equip the item if PCSkipEquip is set to 1
if (ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 1)
{
ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1);
return;
}
ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 0);
}
MWWorld::Ptr player = MWMechanics::getPlayer();
auto type = ptr.getType();
bool isWeaponOrArmor = type == ESM::Weapon::sRecordId || type == ESM::Armor::sRecordId;
bool isBroken = ptr.getClass().hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0;
// In vanilla, broken armor or weapons cannot be equipped
// tools with 0 charges is equippable
if (isBroken && isWeaponOrArmor)
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}");
return;
}
bool canEquip = ptr.getClass().canBeEquipped(ptr, mPtr).first != 0;
bool shouldSetOnPcEquip = canEquip || force;
// If the item has a script, set OnPCEquip or PCSkipEquip to 1
if (!script.empty() && shouldSetOnPcEquip)
{
// Ingredients, books and repair hammers must not have OnPCEquip set to 1 here
bool isBook = type == ESM::Book::sRecordId;
if (!isBook && type != ESM::Ingredient::sRecordId && type != ESM::Repair::sRecordId)
ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1);
// Books must have PCSkipEquip set to 1 instead
else if (isBook)
ptr.getRefData().getLocals().setVarByInt(script, "pcskipequip", 1);
}
std::unique_ptr<MWWorld::Action> action = ptr.getClass().use(ptr, force);
action->execute(player, !canEquip);
// Handles partial equipping (final part)
if (mEquippedStackableCount.has_value())
{
// the count to unequip
int count = ptr.getCellRef().getCount() - mDragAndDrop->mDraggedCount - mEquippedStackableCount.value();
if (count > 0)
{
MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr);
invStore.unequipItemQuantity(ptr, count);
updateItemView();
}
mEquippedStackableCount.reset();
}
if (isVisible())
{
mItemView->update();
notifyContentChanged();
}
// else: will be updated in open()
}
void InventoryWindow::onAvatarClicked(MyGUI::Widget* _sender)
{
if (mDragAndDrop->mIsOnDragAndDrop)
{
MWWorld::Ptr ptr = mDragAndDrop->mItem.mBase;
auto [canEquipRes, canEquipMsg] = ptr.getClass().canBeEquipped(ptr, mPtr);
if (canEquipRes == 0) // cannot equip
{
mDragAndDrop->drop(mTradeModel, mItemView); // also plays down sound
MWBase::Environment::get().getWindowManager()->messageBox(canEquipMsg);
return;
}
mDragAndDrop->finish();
if (mDragAndDrop->mSourceModel != mTradeModel)
{
// Move item to the player's inventory
ptr = mDragAndDrop->mSourceModel->moveItem(
mDragAndDrop->mItem, mDragAndDrop->mDraggedCount, mTradeModel);
}
// Handles partial equipping
mEquippedStackableCount.reset();
const auto slots = ptr.getClass().getEquipmentSlots(ptr);
if (!slots.first.empty() && slots.second)
{
MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator slotIt = invStore.getSlot(slots.first.front());
// Save the currently equipped count before useItem()
if (slotIt != invStore.end() && slotIt->getCellRef().getRefId() == ptr.getCellRef().getRefId())
mEquippedStackableCount = slotIt->getCellRef().getCount();
else
mEquippedStackableCount = 0;
}
MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer(), false);
// If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1
// item
if ((ptr.getType() == ESM::Potion::sRecordId || ptr.getType() == ESM::Ingredient::sRecordId)
&& mDragAndDrop->mDraggedCount > 1)
{
// Item can be provided from other window for example container.
// But after DragAndDrop::startDrag item automaticly always gets to player inventory.
mSelectedItem = getModel()->getIndex(mDragAndDrop->mItem);
dragItem(nullptr, mDragAndDrop->mDraggedCount - 1);
}
}
else
{
MyGUI::IntPoint mousePos
= MyGUI::InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left);
MyGUI::IntPoint relPos = mousePos - mAvatarImage->getAbsolutePosition();
MWWorld::Ptr itemSelected = getAvatarSelectedItem(relPos.left, relPos.top);
if (itemSelected.isEmpty())
return;
for (size_t i = 0; i < mTradeModel->getItemCount(); ++i)
{
if (mTradeModel->getItem(i).mBase == itemSelected)
{
onItemSelectedFromSourceModel(i);
return;
}
}
throw std::runtime_error("Can't find clicked item");
}
}
MWWorld::Ptr InventoryWindow::getAvatarSelectedItem(int x, int y)
{
const osg::Vec2f viewport_coords = mapPreviewWindowToViewport(x, y);
int slot = mPreview->getSlotSelected(viewport_coords.x(), viewport_coords.y());
if (slot == -1)
return MWWorld::Ptr();
MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr);
if (invStore.getSlot(slot) != invStore.end())
{
MWWorld::Ptr item = *invStore.getSlot(slot);
if (!item.getClass().showsInInventory(item))
return MWWorld::Ptr();
return item;
}
return MWWorld::Ptr();
}
void InventoryWindow::updateEncumbranceBar()
{
MWWorld::Ptr player = MWMechanics::getPlayer();
float capacity = player.getClass().getCapacity(player);
float encumbrance = player.getClass().getEncumbrance(player);
mTradeModel->adjustEncumbrance(encumbrance);
mEncumbranceBar->setValue(std::ceil(encumbrance), static_cast<int>(capacity));
}
void InventoryWindow::onFrame(float dt)
{
if (mUpdateNextFrame)
{
if (mTrading)
{
mTradeModel->updateBorrowed();
MWBase::Environment::get().getWindowManager()->getTradeWindow()->mTradeModel->updateBorrowed();
MWBase::Environment::get().getWindowManager()->getTradeWindow()->updateItemView();
MWBase::Environment::get().getWindowManager()->getTradeWindow()->updateOffer();
}
updateEncumbranceBar();
mDragAndDrop->update();
mItemView->update();
notifyContentChanged();
mUpdateNextFrame = false;
}
}
void InventoryWindow::setTrading(bool trading)
{
mTrading = trading;
}
void InventoryWindow::dirtyPreview()
{
mPreview->update();
updateArmorRating();
}
void InventoryWindow::notifyContentChanged()
{
// update the spell window just in case new enchanted items were added to inventory
MWBase::Environment::get().getWindowManager()->updateSpellWindow();
MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(MWMechanics::getPlayer());
dirtyPreview();
}
void InventoryWindow::pickUpObject(MWWorld::Ptr object)
{
// If the inventory is not yet enabled, don't pick anything up
if (!MWBase::Environment::get().getWindowManager()->isAllowed(GW_Inventory))
return;
// make sure the object is of a type that can be picked up
auto type = object.getType();
if ((type != ESM::Apparatus::sRecordId) && (type != ESM::Armor::sRecordId) && (type != ESM::Book::sRecordId)
&& (type != ESM::Clothing::sRecordId) && (type != ESM::Ingredient::sRecordId)
&& (type != ESM::Light::sRecordId) && (type != ESM::Miscellaneous::sRecordId)
&& (type != ESM::Lockpick::sRecordId) && (type != ESM::Probe::sRecordId) && (type != ESM::Repair::sRecordId)
&& (type != ESM::Weapon::sRecordId) && (type != ESM::Potion::sRecordId))
return;
// An object that can be picked up must have a tooltip.
if (!object.getClass().hasToolTip(object))
return;
int count = object.getCellRef().getCount();
if (object.getClass().isGold(object))
count *= object.getClass().getValue(object);
MWWorld::Ptr player = MWMechanics::getPlayer();
MWBase::Environment::get().getWorld()->breakInvisibility(player);
if (!object.getRefData().activate())
return;
// Player must not be paralyzed, knocked down, or dead to pick up an item.
const MWMechanics::NpcStats& playerStats = player.getClass().getNpcStats(player);
if (playerStats.isParalyzed() || playerStats.getKnockedDown() || playerStats.isDead())
return;
MWBase::Environment::get().getMechanicsManager()->itemTaken(player, object, MWWorld::Ptr(), count);
// add to player inventory
// can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object
MWWorld::Ptr newObject = *player.getClass().getContainerStore(player).add(object, count);
// remove from world
MWBase::Environment::get().getWorld()->deleteObject(object);
// get ModelIndex to the item
mTradeModel->update();
size_t i = 0;
for (; i < mTradeModel->getItemCount(); ++i)
{
if (mTradeModel->getItem(i).mBase == newObject)
break;
}
if (i == mTradeModel->getItemCount())
throw std::runtime_error("Added item not found");
if (mDragAndDrop->mIsOnDragAndDrop)
mDragAndDrop->finish();
if (MyGUI::InputManager::getInstance().isAltPressed())
{
const MWWorld::Ptr item = mTradeModel->getItem(i).mBase;
MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item));
mItemView->update();
}
else
{
mDragAndDrop->startDrag(i, mSortModel, mTradeModel, mItemView, count);
}
MWBase::Environment::get().getWindowManager()->updateSpellWindow();
}
void InventoryWindow::cycle(bool next)
{
MWWorld::Ptr player = MWMechanics::getPlayer();
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player))
return;
const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery())
return;
ItemModel::ModelIndex selected = -1;
// not using mSortFilterModel as we only need sorting, not filtering
SortFilterItemModel model(std::make_unique<InventoryItemModel>(player));
model.setSortByType(false);
model.update();
if (model.getItemCount() == 0)
return;
for (ItemModel::ModelIndex i = 0; i < int(model.getItemCount()); ++i)
{
MWWorld::Ptr item = model.getItem(i).mBase;
if (model.getItem(i).mType & ItemStack::Type_Equipped && isRightHandWeapon(item))
selected = i;
}
int incr = next ? 1 : -1;
bool found = false;
ESM::RefId lastId;
if (selected != -1)
lastId = model.getItem(selected).mBase.getCellRef().getRefId();
ItemModel::ModelIndex cycled = selected;
for (size_t i = 0; i < model.getItemCount(); ++i)
{
cycled += incr;
cycled = (cycled + model.getItemCount()) % model.getItemCount();
MWWorld::Ptr item = model.getItem(cycled).mBase;
// skip different stacks of the same item, or we will get stuck as stacking/unstacking them may change their
// relative ordering
if (lastId == item.getCellRef().getRefId())
continue;
lastId = item.getCellRef().getRefId();
if (item.getClass().getType() == ESM::Weapon::sRecordId && isRightHandWeapon(item)
&& item.getClass().canBeEquipped(item, player).first)
{
found = true;
break;
}
}
if (!found || selected == cycled)
return;
useItem(model.getItem(cycled).mBase);
}
void InventoryWindow::rebuildAvatar()
{
mPreview->rebuild();
}
void InventoryWindow::itemAdded(const MWWorld::ConstPtr& item, int count)
{
mUpdateNextFrame = true;
}
void InventoryWindow::itemRemoved(const MWWorld::ConstPtr& item, int count)
{
mUpdateNextFrame = true;
}
MyGUI::IntSize InventoryWindow::getPreviewViewportSize() const
{
const MyGUI::IntSize previewWindowSize = mAvatarImage->getSize();
const float scale = MWBase::Environment::get().getWindowManager()->getScalingFactor();
return MyGUI::IntSize(std::min<int>(mPreview->getTextureWidth(), previewWindowSize.width * scale),
std::min<int>(mPreview->getTextureHeight(), previewWindowSize.height * scale));
}
osg::Vec2f InventoryWindow::mapPreviewWindowToViewport(int x, int y) const
{
const MyGUI::IntSize previewWindowSize = mAvatarImage->getSize();
const float normalisedX = x / std::max<float>(1.0f, previewWindowSize.width);
const float normalisedY = y / std::max<float>(1.0f, previewWindowSize.height);
const MyGUI::IntSize viewport = getPreviewViewportSize();
return osg::Vec2f(normalisedX * float(viewport.width - 1), (1.0 - normalisedY) * float(viewport.height - 1));
}
}