forked from mirror/openmw-tes3mp
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.
472 lines
20 KiB
C++
472 lines
20 KiB
C++
#include "containerextensions.hpp"
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <boost/format.hpp>
|
|
|
|
#include <MyGUI_LanguageManager.h>
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Include additional headers for multiplayer purposes
|
|
*/
|
|
#include "../mwmp/Main.hpp"
|
|
#include "../mwmp/Networking.hpp"
|
|
#include "../mwmp/LocalPlayer.hpp"
|
|
#include "../mwmp/PlayerList.hpp"
|
|
#include "../mwmp/ObjectList.hpp"
|
|
#include "../mwmp/ScriptController.hpp"
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
#include <components/compiler/extensions.hpp>
|
|
#include <components/compiler/opcodes.hpp>
|
|
|
|
#include <components/interpreter/interpreter.hpp>
|
|
#include <components/interpreter/opcodes.hpp>
|
|
|
|
#include <components/misc/stringops.hpp>
|
|
|
|
#include <components/esm/loadskil.hpp>
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
|
|
#include "../mwworld/actionequip.hpp"
|
|
#include "../mwworld/class.hpp"
|
|
#include "../mwworld/containerstore.hpp"
|
|
#include "../mwworld/inventorystore.hpp"
|
|
|
|
#include "../mwmechanics/actorutil.hpp"
|
|
|
|
#include "interpretercontext.hpp"
|
|
#include "ref.hpp"
|
|
|
|
namespace MWScript
|
|
{
|
|
namespace Container
|
|
{
|
|
template<class R>
|
|
class OpAddItem : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute (Interpreter::Runtime& runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
|
runtime.pop();
|
|
|
|
Interpreter::Type_Integer count = runtime[0].mInteger;
|
|
runtime.pop();
|
|
|
|
if (count<0)
|
|
throw std::runtime_error ("second argument for AddItem must be non-negative");
|
|
|
|
// no-op
|
|
if (count == 0)
|
|
return;
|
|
|
|
if(::Misc::StringUtils::ciEqual(item, "gold_005")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_010")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_025")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_100"))
|
|
item = "gold_001";
|
|
|
|
/*
|
|
Start of tes3mp change (major)
|
|
|
|
Disable unilateral item addition on this client and expect the server's reply to our
|
|
packet to do it instead, except for changes to player inventories which still require
|
|
the PlayerInventory to be reworked
|
|
*/
|
|
// Spawn a messagebox (only for items added to player's inventory and if player is talking to someone)
|
|
if (ptr == MWBase::Environment::get().getWorld ()->getPlayerPtr() )
|
|
{
|
|
MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore(ptr).add(item, count, ptr);
|
|
/*
|
|
End of tes3mp change (major)
|
|
*/
|
|
|
|
// The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory
|
|
std::string msgBox;
|
|
std::string itemName = itemPtr.getClass().getName(itemPtr);
|
|
if (count == 1)
|
|
{
|
|
msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}");
|
|
msgBox = boost::str(boost::format(msgBox) % itemName);
|
|
}
|
|
else
|
|
{
|
|
msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage61}");
|
|
msgBox = boost::str(boost::format(msgBox) % count % itemName);
|
|
}
|
|
MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only);
|
|
}
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Send an ID_CONTAINER packet every time an item is added to a Ptr
|
|
that doesn't belong to a DedicatedPlayer
|
|
*/
|
|
else if (!ptr.getClass().isActor() || !mwmp::PlayerList::isDedicatedPlayer(ptr))
|
|
{
|
|
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
|
|
objectList->reset();
|
|
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
|
|
objectList->cell = *ptr.getCell()->getCell();
|
|
objectList->action = mwmp::BaseObjectList::ADD;
|
|
objectList->containerSubAction = mwmp::BaseObjectList::NONE;
|
|
|
|
mwmp::BaseObject baseObject = objectList->getBaseObject(ptr);
|
|
mwmp::ContainerItem containerItem;
|
|
containerItem.refId = item;
|
|
containerItem.count = count;
|
|
containerItem.charge = -1;
|
|
containerItem.enchantmentCharge = -1;
|
|
containerItem.soul = "";
|
|
|
|
baseObject.containerItems.push_back(containerItem);
|
|
objectList->addObject(baseObject);
|
|
objectList->sendContainer();
|
|
}
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
}
|
|
};
|
|
|
|
template<class R>
|
|
class OpGetItemCount : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute (Interpreter::Runtime& runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
|
runtime.pop();
|
|
|
|
if(::Misc::StringUtils::ciEqual(item, "gold_005")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_010")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_025")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_100"))
|
|
item = "gold_001";
|
|
|
|
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr);
|
|
|
|
runtime.push (store.count(item));
|
|
}
|
|
};
|
|
|
|
template<class R>
|
|
class OpRemoveItem : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute (Interpreter::Runtime& runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
|
runtime.pop();
|
|
|
|
Interpreter::Type_Integer count = runtime[0].mInteger;
|
|
runtime.pop();
|
|
|
|
if (count<0)
|
|
throw std::runtime_error ("second argument for RemoveItem must be non-negative");
|
|
|
|
// no-op
|
|
if (count == 0)
|
|
return;
|
|
|
|
if(::Misc::StringUtils::ciEqual(item, "gold_005")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_010")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_025")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_100"))
|
|
item = "gold_001";
|
|
|
|
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr);
|
|
|
|
std::string itemName;
|
|
for (MWWorld::ConstContainerStoreIterator iter(store.cbegin()); iter != store.cend(); ++iter)
|
|
if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item))
|
|
itemName = iter->getClass().getName(*iter);
|
|
|
|
/*
|
|
Start of tes3mp change (major)
|
|
|
|
Disable unilateral item removal on this client and expect the server's reply to our
|
|
packet to do it instead, except for changes to player inventories which still require
|
|
the PlayerInventory to be reworked
|
|
*/
|
|
int numRemoved = 0;
|
|
|
|
if (ptr == MWMechanics::getPlayer())
|
|
numRemoved = store.remove(item, count, ptr);
|
|
|
|
// Spawn a messagebox (only for items removed from player's inventory)
|
|
if ((numRemoved > 0)
|
|
&& (ptr == MWMechanics::getPlayer()))
|
|
{
|
|
/*
|
|
End of tes3mp change (major)
|
|
*/
|
|
|
|
// The two GMST entries below expand to strings informing the player of what, and how many of it has been removed from their inventory
|
|
std::string msgBox;
|
|
|
|
if(numRemoved > 1)
|
|
{
|
|
msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}");
|
|
msgBox = boost::str (boost::format(msgBox) % numRemoved % itemName);
|
|
}
|
|
else
|
|
{
|
|
msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage62}");
|
|
msgBox = boost::str (boost::format(msgBox) % itemName);
|
|
}
|
|
MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only);
|
|
}
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Send an ID_CONTAINER packet every time an item is removed from a Ptr
|
|
that doesn't belong to a DedicatedPlayer
|
|
*/
|
|
else if (!ptr.getClass().isActor() || !mwmp::PlayerList::isDedicatedPlayer(ptr))
|
|
{
|
|
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
|
|
objectList->reset();
|
|
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
|
|
objectList->cell = *ptr.getCell()->getCell();
|
|
objectList->action = mwmp::BaseObjectList::REMOVE;
|
|
objectList->containerSubAction = mwmp::BaseObjectList::NONE;
|
|
|
|
mwmp::BaseObject baseObject = objectList->getBaseObject(ptr);
|
|
mwmp::ContainerItem containerItem;
|
|
containerItem.refId = item;
|
|
containerItem.count = 0;
|
|
containerItem.actionCount = count;
|
|
containerItem.charge = -1;
|
|
containerItem.enchantmentCharge = -1;
|
|
containerItem.soul = "";
|
|
|
|
baseObject.containerItems.push_back(containerItem);
|
|
objectList->addObject(baseObject);
|
|
objectList->sendContainer();
|
|
}
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
}
|
|
};
|
|
|
|
template <class R>
|
|
class OpEquip : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute(Interpreter::Runtime &runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
|
runtime.pop();
|
|
|
|
MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
|
|
MWWorld::ContainerStoreIterator it = invStore.begin();
|
|
for (; it != invStore.end(); ++it)
|
|
{
|
|
if (::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item))
|
|
break;
|
|
}
|
|
if (it == invStore.end())
|
|
{
|
|
it = ptr.getClass().getContainerStore (ptr).add (item, 1, ptr);
|
|
std::cerr << "Implicitly adding one " << item << " to container "
|
|
"to fulfil requirements of Equip instruction" << std::endl;
|
|
}
|
|
|
|
if (ptr == MWMechanics::getPlayer())
|
|
MWBase::Environment::get().getWindowManager()->useItem(*it, true);
|
|
else
|
|
{
|
|
std::shared_ptr<MWWorld::Action> action = it->getClass().use(*it, true);
|
|
action->execute(ptr, true);
|
|
}
|
|
}
|
|
};
|
|
|
|
template <class R>
|
|
class OpGetArmorType : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute(Interpreter::Runtime &runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
Interpreter::Type_Integer location = runtime[0].mInteger;
|
|
runtime.pop();
|
|
|
|
int slot;
|
|
switch (location)
|
|
{
|
|
case 0:
|
|
slot = MWWorld::InventoryStore::Slot_Helmet;
|
|
break;
|
|
case 1:
|
|
slot = MWWorld::InventoryStore::Slot_Cuirass;
|
|
break;
|
|
case 2:
|
|
slot = MWWorld::InventoryStore::Slot_LeftPauldron;
|
|
break;
|
|
case 3:
|
|
slot = MWWorld::InventoryStore::Slot_RightPauldron;
|
|
break;
|
|
case 4:
|
|
slot = MWWorld::InventoryStore::Slot_Greaves;
|
|
break;
|
|
case 5:
|
|
slot = MWWorld::InventoryStore::Slot_Boots;
|
|
break;
|
|
case 6:
|
|
slot = MWWorld::InventoryStore::Slot_LeftGauntlet;
|
|
break;
|
|
case 7:
|
|
slot = MWWorld::InventoryStore::Slot_RightGauntlet;
|
|
break;
|
|
case 8:
|
|
slot = MWWorld::InventoryStore::Slot_CarriedLeft; // shield
|
|
break;
|
|
case 9:
|
|
slot = MWWorld::InventoryStore::Slot_LeftGauntlet;
|
|
break;
|
|
case 10:
|
|
slot = MWWorld::InventoryStore::Slot_RightGauntlet;
|
|
break;
|
|
default:
|
|
throw std::runtime_error ("armor index out of range");
|
|
}
|
|
|
|
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
|
|
MWWorld::ConstContainerStoreIterator it = invStore.getSlot (slot);
|
|
|
|
if (it == invStore.end() || it->getTypeName () != typeid(ESM::Armor).name())
|
|
{
|
|
runtime.push(-1);
|
|
return;
|
|
}
|
|
|
|
int skill = it->getClass().getEquipmentSkill (*it) ;
|
|
if (skill == ESM::Skill::HeavyArmor)
|
|
runtime.push(2);
|
|
else if (skill == ESM::Skill::MediumArmor)
|
|
runtime.push(1);
|
|
else if (skill == ESM::Skill::LightArmor)
|
|
runtime.push(0);
|
|
else
|
|
runtime.push(-1);
|
|
}
|
|
};
|
|
|
|
template <class R>
|
|
class OpHasItemEquipped : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute(Interpreter::Runtime &runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
|
runtime.pop();
|
|
|
|
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
|
|
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
|
{
|
|
MWWorld::ConstContainerStoreIterator it = invStore.getSlot (slot);
|
|
if (it != invStore.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item))
|
|
{
|
|
runtime.push(1);
|
|
return;
|
|
}
|
|
}
|
|
runtime.push(0);
|
|
}
|
|
};
|
|
|
|
template <class R>
|
|
class OpHasSoulGem : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute(Interpreter::Runtime &runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
const std::string &name = runtime.getStringLiteral (runtime[0].mInteger);
|
|
runtime.pop();
|
|
|
|
int count = 0;
|
|
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
|
|
for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(MWWorld::ContainerStore::Type_Miscellaneous);
|
|
it != invStore.cend(); ++it)
|
|
{
|
|
if (::Misc::StringUtils::ciEqual(it->getCellRef().getSoul(), name))
|
|
count += it->getRefData().getCount();
|
|
}
|
|
runtime.push(count);
|
|
}
|
|
};
|
|
|
|
template <class R>
|
|
class OpGetWeaponType : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute(Interpreter::Runtime &runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
|
|
MWWorld::ConstContainerStoreIterator it = invStore.getSlot (MWWorld::InventoryStore::Slot_CarriedRight);
|
|
if (it == invStore.end() || it->getTypeName () != typeid(ESM::Weapon).name())
|
|
{
|
|
runtime.push(-1);
|
|
return;
|
|
}
|
|
|
|
runtime.push(it->get<ESM::Weapon>()->mBase->mData.mType);
|
|
}
|
|
};
|
|
|
|
|
|
void installOpcodes (Interpreter::Interpreter& interpreter)
|
|
{
|
|
interpreter.installSegment5 (Compiler::Container::opcodeAddItem, new OpAddItem<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeAddItemExplicit, new OpAddItem<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeGetItemCount, new OpGetItemCount<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeGetItemCountExplicit, new OpGetItemCount<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeRemoveItem, new OpRemoveItem<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeRemoveItemExplicit, new OpRemoveItem<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeEquip, new OpEquip<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeEquipExplicit, new OpEquip<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeGetArmorType, new OpGetArmorType<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeGetArmorTypeExplicit, new OpGetArmorType<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeHasItemEquipped, new OpHasItemEquipped<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeHasItemEquippedExplicit, new OpHasItemEquipped<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeHasSoulGem, new OpHasSoulGem<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeHasSoulGemExplicit, new OpHasSoulGem<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeGetWeaponType, new OpGetWeaponType<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeGetWeaponTypeExplicit, new OpGetWeaponType<ExplicitRef>);
|
|
}
|
|
}
|
|
}
|