mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-20 06:53:52 +00:00
3bd8aa82fe
Whenever an item is added to or removed from the player's ContainerStore, that player sends a PlayerInventory packet with just that addition or removal.
This eliminates all the unnecessary packet spam related to oversized PlayerInventory packets that had existed in one form or another since the initial implementation of inventory sync in 1b259e2d33
Additionally, move booleans from BasePlayer to LocalPlayer when they are only needed on the client, and make the usage of the isReceivingQuickKeys boolean consistent with the new isReceivingInventory boolean by having them both in the processors of their associated packets.
828 lines
30 KiB
C++
828 lines
30 KiB
C++
#include "inventorywindow.hpp"
|
|
|
|
#include <cmath>
|
|
#include <stdexcept>
|
|
|
|
#include <MyGUI_Window.h>
|
|
#include <MyGUI_ImageBox.h>
|
|
#include <MyGUI_RenderManager.h>
|
|
#include <MyGUI_InputManager.h>
|
|
#include <MyGUI_Button.h>
|
|
|
|
#include <osg/Texture2D>
|
|
|
|
#include <components/misc/stringops.hpp>
|
|
|
|
#include <components/myguiplatform/myguitexture.hpp>
|
|
|
|
#include <components/settings/settings.hpp>
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Include additional headers for multiplayer purposes
|
|
*/
|
|
#include "../mwmp/Main.hpp"
|
|
#include "../mwmp/Networking.hpp"
|
|
#include "../mwmp/ObjectList.hpp"
|
|
#include "../mwmp/LocalPlayer.hpp"
|
|
#include "../mwworld/cellstore.hpp"
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
#include "../mwbase/world.hpp"
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
|
#include "../mwbase/scriptmanager.hpp"
|
|
|
|
#include "../mwworld/inventorystore.hpp"
|
|
#include "../mwworld/class.hpp"
|
|
#include "../mwworld/actionequip.hpp"
|
|
#include "../mwscript/interpretercontext.hpp"
|
|
|
|
#include "../mwmechanics/actorutil.hpp"
|
|
#include "../mwmechanics/creaturestats.hpp"
|
|
|
|
#include "itemview.hpp"
|
|
#include "inventoryitemmodel.hpp"
|
|
#include "sortfilteritemmodel.hpp"
|
|
#include "tradeitemmodel.hpp"
|
|
#include "countdialog.hpp"
|
|
#include "tradewindow.hpp"
|
|
#include "draganddrop.hpp"
|
|
#include "widgets.hpp"
|
|
|
|
namespace
|
|
{
|
|
|
|
bool isRightHandWeapon(const MWWorld::Ptr& item)
|
|
{
|
|
if (item.getClass().getTypeName() != typeid(ESM::Weapon).name())
|
|
return false;
|
|
std::vector<int> equipmentSlots = item.getClass().getEquipmentSlots(item).first;
|
|
return (!equipmentSlots.empty() && equipmentSlots.front() == MWWorld::InventoryStore::Slot_CarriedRight);
|
|
}
|
|
|
|
}
|
|
|
|
namespace MWGui
|
|
{
|
|
|
|
InventoryWindow::InventoryWindow(DragAndDrop* dragAndDrop, osg::Group* parent, Resource::ResourceSystem* resourceSystem)
|
|
: WindowPinnableBase("openmw_inventory_window.layout")
|
|
, mDragAndDrop(dragAndDrop)
|
|
, mSelectedItem(-1)
|
|
, mSortModel(NULL)
|
|
, mTradeModel(NULL)
|
|
, mGuiMode(GM_Inventory)
|
|
, mLastXSize(0)
|
|
, mLastYSize(0)
|
|
, mPreview(new MWRender::InventoryPreview(parent, resourceSystem, MWMechanics::getPlayer()))
|
|
, mTrading(false)
|
|
, mScaleFactor(1.0f)
|
|
{
|
|
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
|
|
if (uiScale > 1.0)
|
|
mScaleFactor = uiScale;
|
|
|
|
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));
|
|
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");
|
|
|
|
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);
|
|
|
|
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();
|
|
mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr());
|
|
|
|
if (mSortModel) // reuse existing SortModel when possible to keep previous category/filter settings
|
|
mSortModel->setSourceModel(mTradeModel);
|
|
else
|
|
mSortModel = new SortFilterItemModel(mTradeModel);
|
|
|
|
mItemView->setModel(mSortModel);
|
|
|
|
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 = NULL;
|
|
mSortModel = NULL;
|
|
mItemView->setModel(NULL);
|
|
}
|
|
|
|
void InventoryWindow::setGuiMode(GuiMode mode)
|
|
{
|
|
std::string setting = "inventory";
|
|
mGuiMode = mode;
|
|
switch(mode) {
|
|
case GM_Container:
|
|
setPinButtonVisible(false);
|
|
setting += " container";
|
|
break;
|
|
case GM_Companion:
|
|
setPinButtonVisible(false);
|
|
setting += " companion";
|
|
break;
|
|
case GM_Barter:
|
|
setPinButtonVisible(false);
|
|
setting += " barter";
|
|
break;
|
|
case GM_Inventory:
|
|
default:
|
|
setPinButtonVisible(true);
|
|
break;
|
|
}
|
|
|
|
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
|
MyGUI::IntPoint pos(static_cast<int>(Settings::Manager::getFloat(setting + " x", "Windows") * viewSize.width),
|
|
static_cast<int>(Settings::Manager::getFloat(setting + " y", "Windows") * viewSize.height));
|
|
MyGUI::IntSize size(static_cast<int>(Settings::Manager::getFloat(setting + " w", "Windows") * viewSize.width),
|
|
static_cast<int>(Settings::Manager::getFloat(setting + " h", "Windows") * 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);
|
|
std::string 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.getTypeName() == typeid(ESM::Weapon).name();
|
|
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}";
|
|
dialog->openCountDialog(object.getClass().getName(object), message, count);
|
|
dialog->eventOkClicked.clear();
|
|
if (mTrading)
|
|
dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::sellItem);
|
|
else
|
|
dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::dragItem);
|
|
mSelectedItem = index;
|
|
}
|
|
else
|
|
{
|
|
mSelectedItem = index;
|
|
if (mTrading)
|
|
sellItem (NULL, count);
|
|
else
|
|
dragItem (NULL, 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, mPtr, 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, int count)
|
|
{
|
|
ensureSelectedItemUnequipped(count);
|
|
mDragAndDrop->startDrag(mSelectedItem, mSortModel, mTradeModel, mItemView, count);
|
|
notifyContentChanged();
|
|
}
|
|
|
|
void InventoryWindow::sellItem(MyGUI::Widget* sender, int count)
|
|
{
|
|
ensureSelectedItemUnequipped(count);
|
|
const ItemStack& item = mTradeModel->getItem(mSelectedItem);
|
|
std::string 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()
|
|
{
|
|
if (!mPtr.isEmpty())
|
|
{
|
|
updateEncumbranceBar();
|
|
mItemView->update();
|
|
notifyContentChanged();
|
|
}
|
|
adjustPanes();
|
|
}
|
|
|
|
void InventoryWindow::onWindowResize(MyGUI::Window* _sender)
|
|
{
|
|
adjustPanes();
|
|
std::string setting = "inventory";
|
|
switch(mGuiMode) {
|
|
case GM_Container:
|
|
setting += " container";
|
|
break;
|
|
case GM_Companion:
|
|
setting += " companion";
|
|
break;
|
|
case GM_Barter:
|
|
setting += " barter";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
|
float x = _sender->getPosition().left / float(viewSize.width);
|
|
float y = _sender->getPosition().top / float(viewSize.height);
|
|
float w = _sender->getSize().width / float(viewSize.width);
|
|
float h = _sender->getSize().height / float(viewSize.height);
|
|
Settings::Manager::setFloat(setting + " x", "Windows", x);
|
|
Settings::Manager::setFloat(setting + " y", "Windows", y);
|
|
Settings::Manager::setFloat(setting + " w", "Windows", w);
|
|
Settings::Manager::setFloat(setting + " h", "Windows", h);
|
|
|
|
if (mMainWidget->getSize().width != mLastXSize || mMainWidget->getSize().height != mLastYSize)
|
|
{
|
|
mLastXSize = mMainWidget->getSize().width;
|
|
mLastYSize = mMainWidget->getSize().height;
|
|
|
|
updatePreviewSize();
|
|
updateArmorRating();
|
|
}
|
|
}
|
|
|
|
void InventoryWindow::updateArmorRating()
|
|
{
|
|
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()
|
|
{
|
|
MyGUI::IntSize size = mAvatarImage->getSize();
|
|
int width = std::min(mPreview->getTextureWidth(), size.width);
|
|
int height = std::min(mPreview->getTextureHeight(), size.height);
|
|
mPreview->setViewport(int(width*mScaleFactor), int(height*mScaleFactor));
|
|
|
|
mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f,
|
|
width*mScaleFactor/float(mPreview->getTextureWidth()), height*mScaleFactor/float(mPreview->getTextureHeight())));
|
|
}
|
|
|
|
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::Manager::setBool("inventory pin", "Windows", mPinned);
|
|
|
|
MWBase::Environment::get().getWindowManager()->setWeaponVisibility(!mPinned);
|
|
}
|
|
|
|
void InventoryWindow::onTitleDoubleClicked()
|
|
{
|
|
if (!mPinned)
|
|
MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory);
|
|
}
|
|
|
|
void InventoryWindow::useItem(const MWWorld::Ptr &ptr, bool force)
|
|
{
|
|
const std::string& script = ptr.getClass().getScript(ptr);
|
|
|
|
MWWorld::Ptr player = MWMechanics::getPlayer();
|
|
|
|
// early-out for items that need to be equipped, but can't be equipped: we don't want to set OnPcEquip in that case
|
|
if (!ptr.getClass().getEquipmentSlots(ptr).first.empty())
|
|
{
|
|
if (ptr.getClass().hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0)
|
|
{
|
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage1}");
|
|
updateItemView();
|
|
return;
|
|
}
|
|
|
|
if (!force)
|
|
{
|
|
std::pair<int, std::string> canEquip = ptr.getClass().canBeEquipped(ptr, player);
|
|
|
|
if (canEquip.first == 0)
|
|
{
|
|
MWBase::Environment::get().getWindowManager()->messageBox(canEquip.second);
|
|
updateItemView();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the item has a script, set its OnPcEquip to 1
|
|
if (!script.empty()
|
|
// Another morrowind oddity: when an item has skipped equipping and pcskipequip is reset to 0 afterwards,
|
|
// the next time it is equipped will work normally, but will not set onpcequip
|
|
&& (ptr != mSkippedToEquip || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 1))
|
|
ptr.getRefData().getLocals().setVarByInt(script, "onpcequip", 1);
|
|
|
|
// Give the script a chance to run once before we do anything else
|
|
// this is important when setting pcskipequip as a reaction to onpcequip being set (bk_treasuryreport does this)
|
|
if (!script.empty() && MWBase::Environment::get().getWorld()->getScriptsEnabled())
|
|
{
|
|
MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr);
|
|
MWBase::Environment::get().getScriptManager()->run (script, interpreterContext);
|
|
}
|
|
|
|
mSkippedToEquip = MWWorld::Ptr();
|
|
if (ptr.getRefData().getCount()) // make sure the item is still there, the script might have removed it
|
|
{
|
|
if (script.empty() || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 0)
|
|
{
|
|
std::shared_ptr<MWWorld::Action> action = ptr.getClass().use(ptr, force);
|
|
action->execute(player);
|
|
}
|
|
else
|
|
mSkippedToEquip = ptr;
|
|
}
|
|
|
|
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;
|
|
|
|
mDragAndDrop->finish();
|
|
|
|
if (mDragAndDrop->mSourceModel != mTradeModel)
|
|
{
|
|
// Move item to the player's inventory
|
|
ptr = mDragAndDrop->mSourceModel->moveItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount, mTradeModel);
|
|
}
|
|
|
|
/*
|
|
Start of tes3mp change (major)
|
|
|
|
Instead of unilaterally using an item, send an ID_PLAYER_ITEM_USE packet and let the server
|
|
decide if the item actually gets used
|
|
*/
|
|
//useItem(ptr);
|
|
mwmp::Main::get().getLocalPlayer()->sendItemUse(ptr);
|
|
/*
|
|
End of tes3mp change (major)
|
|
*/
|
|
|
|
// If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 item
|
|
if ((ptr.getTypeName() == typeid(ESM::Potion).name() ||
|
|
ptr.getTypeName() == typeid(ESM::Ingredient).name())
|
|
&& 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)
|
|
{
|
|
// convert to OpenGL lower-left origin
|
|
y = (mAvatarImage->getHeight()-1) - y;
|
|
|
|
// Scale coordinates
|
|
x = int(x*mScaleFactor);
|
|
y = int(y*mScaleFactor);
|
|
|
|
int slot = mPreview->getSlotSelected (x, 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)
|
|
{
|
|
updateEncumbranceBar();
|
|
}
|
|
|
|
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
|
|
std::string type = object.getTypeName();
|
|
if ( (type != typeid(ESM::Apparatus).name())
|
|
&& (type != typeid(ESM::Armor).name())
|
|
&& (type != typeid(ESM::Book).name())
|
|
&& (type != typeid(ESM::Clothing).name())
|
|
&& (type != typeid(ESM::Ingredient).name())
|
|
&& (type != typeid(ESM::Light).name())
|
|
&& (type != typeid(ESM::Miscellaneous).name())
|
|
&& (type != typeid(ESM::Lockpick).name())
|
|
&& (type != typeid(ESM::Probe).name())
|
|
&& (type != typeid(ESM::Repair).name())
|
|
&& (type != typeid(ESM::Weapon).name())
|
|
&& (type != typeid(ESM::Potion).name()))
|
|
return;
|
|
|
|
if (object.getClass().getName(object) == "") // objects without name presented to user can never be picked up
|
|
return;
|
|
|
|
int count = object.getRefData().getCount();
|
|
|
|
MWWorld::Ptr player = MWMechanics::getPlayer();
|
|
MWBase::Environment::get().getWorld()->breakInvisibility(player);
|
|
|
|
if (!object.getRefData().activate())
|
|
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, object.getRefData().getCount(), player);
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Send an ID_OBJECT_DELETE packet every time an item from the world is picked up
|
|
by the player through the inventory HUD
|
|
*/
|
|
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
|
|
objectList->reset();
|
|
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
|
|
objectList->addObjectDelete(object);
|
|
objectList->sendObjectDelete();
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
// 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");
|
|
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(new 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;
|
|
std::string lastId;
|
|
if (selected != -1)
|
|
lastId = model.getItem(selected).mBase.getCellRef().getRefId();
|
|
ItemModel::ModelIndex cycled = selected;
|
|
for (unsigned int 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 (Misc::StringUtils::ciEqual(lastId, item.getCellRef().getRefId()))
|
|
continue;
|
|
|
|
lastId = item.getCellRef().getRefId();
|
|
|
|
if (item.getClass().getTypeName() == typeid(ESM::Weapon).name() &&
|
|
isRightHandWeapon(item) &&
|
|
item.getClass().canBeEquipped(item, player).first)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found || selected == cycled)
|
|
return;
|
|
|
|
/*
|
|
Start of tes3mp change (major)
|
|
|
|
Instead of unilaterally using an item, send an ID_PLAYER_ITEM_USE packet and let the server
|
|
decide if the item actually gets used
|
|
*/
|
|
//useItem(model.getItem(cycled).mBase);
|
|
mwmp::Main::get().getLocalPlayer()->sendItemUse(model.getItem(cycled).mBase);
|
|
/*
|
|
End of tes3mp change (major)
|
|
*/
|
|
}
|
|
|
|
void InventoryWindow::rebuildAvatar()
|
|
{
|
|
mPreview->rebuild();
|
|
}
|
|
}
|