1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-04-09 21:44:54 +00:00

Merge branch 'lua_create' into 'master'

Lua commands to create/move/remove objects; consistent handling of disabled objects (#6726, #6893)

See merge request OpenMW/openmw!2627
This commit is contained in:
psi29a 2023-01-22 13:27:10 +00:00
commit a9dbb023d7
51 changed files with 458 additions and 239 deletions

View File

@ -16,6 +16,7 @@
Bug #6645: Enemy block sounds align with animation instead of blocked hits Bug #6645: Enemy block sounds align with animation instead of blocked hits
Bug #6661: Saved games that have no preview screenshot cause issues or crashes Bug #6661: Saved games that have no preview screenshot cause issues or crashes
Bug #6807: Ultimate Galleon is not working properly Bug #6807: Ultimate Galleon is not working properly
Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands
Bug #6939: OpenMW-CS: ID columns are too short Bug #6939: OpenMW-CS: ID columns are too short
Bug #6949: Sun Damage effect doesn't work in quasi exteriors Bug #6949: Sun Damage effect doesn't work in quasi exteriors
Bug #6964: Nerasa Dralor Won't Follow Bug #6964: Nerasa Dralor Won't Follow
@ -35,6 +36,7 @@
Bug #7172: Current music playlist continues playing indefinitely if next playlist is empty Bug #7172: Current music playlist continues playing indefinitely if next playlist is empty
Feature #5492: Let rain and snow collide with statics Feature #5492: Let rain and snow collide with statics
Feature #6447: Add LOD support to Object Paging Feature #6447: Add LOD support to Object Paging
Feature #6726: Lua API for creating new objects
Feature #6922: Improve launcher appearance Feature #6922: Improve launcher appearance
Feature #6933: Support high-resolution cursor textures Feature #6933: Support high-resolution cursor textures
Feature #6945: Support S3TC-compressed and BGR/BGRA NiPixelData Feature #6945: Support S3TC-compressed and BGR/BGRA NiPixelData

View File

@ -170,7 +170,7 @@ namespace MWClass
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng);
if (hasInventory) if (hasInventory)
getInventoryStore(ptr).autoEquip(ptr); getInventoryStore(ptr).autoEquip();
} }
} }
@ -507,14 +507,16 @@ namespace MWClass
MWWorld::ContainerStore& Creature::getContainerStore(const MWWorld::Ptr& ptr) const MWWorld::ContainerStore& Creature::getContainerStore(const MWWorld::Ptr& ptr) const
{ {
ensureCustomData(ptr); ensureCustomData(ptr);
auto& store = *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore;
return *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore; if (hasInventoryStore(ptr))
static_cast<MWWorld::InventoryStore&>(store).setActor(ptr);
return store;
} }
MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr& ptr) const MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr& ptr) const
{ {
if (hasInventoryStore(ptr)) if (hasInventoryStore(ptr))
return dynamic_cast<MWWorld::InventoryStore&>(getContainerStore(ptr)); return static_cast<MWWorld::InventoryStore&>(getContainerStore(ptr));
else else
throw std::runtime_error("this creature has no inventory store"); throw std::runtime_error("this creature has no inventory store");
} }

View File

@ -17,6 +17,7 @@
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/manualref.hpp" #include "../mwworld/manualref.hpp"
#include "../mwworld/nullaction.hpp" #include "../mwworld/nullaction.hpp"
#include "../mwworld/worldmodel.hpp"
#include "../mwgui/tooltips.hpp" #include "../mwgui/tooltips.hpp"
#include "../mwgui/ustring.hpp" #include "../mwgui/ustring.hpp"
@ -208,6 +209,7 @@ namespace MWClass
newPtr.getRefData().setCount(count); newPtr.getRefData().setCount(count);
} }
newPtr.getCellRef().unsetRefNum(); newPtr.getCellRef().unsetRefNum();
MWBase::Environment::get().getWorldModel()->registerPtr(newPtr);
return newPtr; return newPtr;
} }

View File

@ -414,7 +414,7 @@ namespace MWClass
auto& prng = MWBase::Environment::get().getWorld()->getPrng(); auto& prng = MWBase::Environment::get().getWorld()->getPrng();
getInventoryStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); getInventoryStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng);
getInventoryStore(ptr).autoEquip(ptr); getInventoryStore(ptr).autoEquip();
} }
} }
@ -862,7 +862,7 @@ namespace MWClass
// Armor broken? unequip it // Armor broken? unequip it
if (armorhealth == 0) if (armorhealth == 0)
armor = *inv.unequipItem(armor, ptr); armor = *inv.unequipItem(armor);
} }
if (ptr == MWMechanics::getPlayer()) if (ptr == MWMechanics::getPlayer())
@ -984,15 +984,17 @@ namespace MWClass
MWWorld::ContainerStore& Npc::getContainerStore(const MWWorld::Ptr& ptr) const MWWorld::ContainerStore& Npc::getContainerStore(const MWWorld::Ptr& ptr) const
{ {
ensureCustomData(ptr); ensureCustomData(ptr);
auto& store = ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore;
return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; store.setActor(ptr);
return store;
} }
MWWorld::InventoryStore& Npc::getInventoryStore(const MWWorld::Ptr& ptr) const MWWorld::InventoryStore& Npc::getInventoryStore(const MWWorld::Ptr& ptr) const
{ {
ensureCustomData(ptr); ensureCustomData(ptr);
auto& store = ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore;
return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; store.setActor(ptr);
return store;
} }
const ESM::RefId& Npc::getScript(const MWWorld::ConstPtr& ptr) const const ESM::RefId& Npc::getScript(const MWWorld::ConstPtr& ptr) const
@ -1172,7 +1174,7 @@ namespace MWClass
MWMechanics::CastSpell cast(actor, actor); MWMechanics::CastSpell cast(actor, actor);
const ESM::RefId& recordId = consumable.getCellRef().getRefId(); const ESM::RefId& recordId = consumable.getCellRef().getRefId();
MWBase::Environment::get().getLuaManager()->itemConsumed(consumable, actor); MWBase::Environment::get().getLuaManager()->itemConsumed(consumable, actor);
actor.getClass().getContainerStore(actor).remove(consumable, 1, actor); actor.getClass().getContainerStore(actor).remove(consumable, 1);
return cast.cast(recordId); return cast.cast(recordId);
} }

View File

@ -571,8 +571,8 @@ namespace MWDialogue
if (gold) if (gold)
{ {
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, gold, player); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, gold);
mActor.getClass().getContainerStore(mActor).add(MWWorld::ContainerStore::sGoldId, gold, mActor); mActor.getClass().getContainerStore(mActor).add(MWWorld::ContainerStore::sGoldId, gold);
} }
} }

View File

@ -211,7 +211,7 @@ namespace MWGui
if (invStore.isEquipped(item.mBase) == false) if (invStore.isEquipped(item.mBase) == false)
continue; continue;
invStore.unequipItem(item.mBase, mPtr); invStore.unequipItem(item.mBase);
} }
} }

View File

@ -105,7 +105,7 @@ namespace MWGui
MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first);
if (item.mBase.getContainerStore() == &store) if (item.mBase.getContainerStore() == &store)
throw std::runtime_error("Item to copy needs to be from a different container!"); throw std::runtime_error("Item to copy needs to be from a different container!");
return *store.add(item.mBase, count, source.first, allowAutoEquip); return *store.add(item.mBase, count, allowAutoEquip);
} }
void ContainerItemModel::removeItem(const ItemStack& item, size_t count) void ContainerItemModel::removeItem(const ItemStack& item, size_t count)
@ -125,7 +125,7 @@ namespace MWGui
if (quantity < 0 && mTrading) if (quantity < 0 && mTrading)
toRemove += quantity; toRemove += quantity;
else else
toRemove -= store.remove(*it, toRemove, source.first); toRemove -= store.remove(*it, toRemove);
if (toRemove <= 0) if (toRemove <= 0)
return; return;
} }

View File

@ -50,7 +50,7 @@ namespace MWGui
{ {
if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor)) if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor))
throw std::runtime_error("Item to copy needs to be from a different container!"); throw std::runtime_error("Item to copy needs to be from a different container!");
return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor, allowAutoEquip); return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, allowAutoEquip);
} }
void InventoryItemModel::removeItem(const ItemStack& item, size_t count) void InventoryItemModel::removeItem(const ItemStack& item, size_t count)
@ -60,12 +60,12 @@ namespace MWGui
if (mActor.getClass().hasInventoryStore(mActor)) if (mActor.getClass().hasInventoryStore(mActor))
{ {
MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor); MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor);
removed = store.remove(item.mBase, count, mActor, true); removed = store.remove(item.mBase, count, true);
} }
else else
{ {
MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor); MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor);
removed = store.remove(item.mBase, count, mActor); removed = store.remove(item.mBase, count);
} }
std::stringstream error; std::stringstream error;

View File

@ -322,7 +322,7 @@ namespace MWGui
if (item.mType == ItemStack::Type_Equipped) if (item.mType == ItemStack::Type_Equipped)
{ {
MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::Ptr newStack = *invStore.unequipItemQuantity(item.mBase, mPtr, count); MWWorld::Ptr newStack = *invStore.unequipItemQuantity(item.mBase, count);
// The unequipped item was re-stacked. We have to update the index // The unequipped item was re-stacked. We have to update the index
// since the item pointed does not exist anymore. // since the item pointed does not exist anymore.
@ -743,7 +743,7 @@ namespace MWGui
// add to player inventory // add to player inventory
// can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object // can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object
MWWorld::Ptr newObject MWWorld::Ptr newObject
= *player.getClass().getContainerStore(player).add(object, object.getRefData().getCount(), player); = *player.getClass().getContainerStore(player).add(object, object.getRefData().getCount());
// remove from world // remove from world
MWBase::Environment::get().getWorld()->deleteObject(object); MWBase::Environment::get().getWorld()->deleteObject(object);

View File

@ -136,7 +136,7 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Repair")); MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Repair"));
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price);
// add gold to NPC trading gold pool // add gold to NPC trading gold pool
MWMechanics::CreatureStats& actorStats = mActor.getClass().getCreatureStats(mActor); MWMechanics::CreatureStats& actorStats = mActor.getClass().getCreatureStats(mActor);

View File

@ -444,7 +444,7 @@ namespace MWGui
} }
else if (key->type == Type_HandToHand) else if (key->type == Type_HandToHand)
{ {
store.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, player); store.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight);
MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState::Weapon); MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState::Weapon);
} }
} }

View File

@ -152,7 +152,7 @@ namespace MWGui
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells(); MWMechanics::Spells& spells = stats.getSpells();
spells.add(mSpellsWidgetMap.find(_sender)->second); spells.add(mSpellsWidgetMap.find(_sender)->second);
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price);
// add gold to NPC trading gold pool // add gold to NPC trading gold pool
MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr);

View File

@ -408,7 +408,7 @@ namespace MWGui
mSpell.mName = mNameEdit->getCaption(); mSpell.mName = mNameEdit->getCaption();
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price);
// add gold to NPC trading gold pool // add gold to NPC trading gold pool
MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr);

View File

@ -259,11 +259,11 @@ namespace MWGui
if (amount > 0) if (amount > 0)
{ {
store.add(MWWorld::ContainerStore::sGoldId, amount, actor); store.add(MWWorld::ContainerStore::sGoldId, amount);
} }
else else
{ {
store.remove(MWWorld::ContainerStore::sGoldId, -amount, actor); store.remove(MWWorld::ContainerStore::sGoldId, -amount);
} }
} }

View File

@ -176,7 +176,7 @@ namespace MWGui
pcStats.increaseSkill(skillId, *class_, true); pcStats.increaseSkill(skillId, *class_, true);
// remove gold // remove gold
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price);
// add gold to NPC trading gold pool // add gold to NPC trading gold pool
MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats(mPtr); MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats(mPtr);

View File

@ -166,7 +166,7 @@ namespace MWGui
// Interior cell -> mages guild transport // Interior cell -> mages guild transport
MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("mysticism cast")); MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("mysticism cast"));
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price);
// add gold to NPC trading gold pool // add gold to NPC trading gold pool
MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr);

View File

@ -75,6 +75,8 @@ namespace MWLua
const CellT& cell, sol::optional<sol::table> type) { const CellT& cell, sol::optional<sol::table> type) {
ObjectIdList res = std::make_shared<std::vector<ObjectId>>(); ObjectIdList res = std::make_shared<std::vector<ObjectId>>();
auto visitor = [&](const MWWorld::Ptr& ptr) { auto visitor = [&](const MWWorld::Ptr& ptr) {
if (ptr.getRefData().isDeleted())
return true;
MWBase::Environment::get().getWorldModel()->registerPtr(ptr); MWBase::Environment::get().getWorldModel()->registerPtr(ptr);
if (getLiveCellRefType(ptr.mRef) == ptr.getType()) if (getLiveCellRefType(ptr.mRef) == ptr.getType())
res->push_back(getId(ptr)); res->push_back(getId(ptr));

View File

@ -23,6 +23,7 @@ namespace MWLua
LocalScripts(LuaUtil::LuaState* lua, const LObject& obj); LocalScripts(LuaUtil::LuaState* lua, const LObject& obj);
MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; } MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; }
const MWWorld::Ptr& getPtr() const { return mData.ptr(); }
struct SelfObject : public LObject struct SelfObject : public LObject
{ {

View File

@ -7,7 +7,10 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwworld/scene.hpp"
#include "../mwworld/store.hpp" #include "../mwworld/store.hpp"
#include "eventqueue.hpp" #include "eventqueue.hpp"
@ -48,7 +51,7 @@ namespace MWLua
{ {
auto* lua = context.mLua; auto* lua = context.mLua;
sol::table api(lua->sol(), sol::create); sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = 32; api["API_REVISION"] = 33;
api["quit"] = [lua]() { api["quit"] = [lua]() {
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
MWBase::Environment::get().getStateManager()->requestQuit(); MWBase::Environment::get().getStateManager()->requestQuit();
@ -83,7 +86,17 @@ namespace MWLua
api["getExteriorCell"] api["getExteriorCell"]
= [](int x, int y) { return GCell{ MWBase::Environment::get().getWorldModel()->getExterior(x, y) }; }; = [](int x, int y) { return GCell{ MWBase::Environment::get().getWorldModel()->getExterior(x, y) }; };
api["activeActors"] = GObjectList{ worldView->getActorsInScene() }; api["activeActors"] = GObjectList{ worldView->getActorsInScene() };
// TODO: add world.placeNewObject(recordId, cell, pos, [rot]) api["createObject"] = [](std::string_view recordId, sol::optional<int> count) -> GObject {
// Doesn't matter which cell to use because the new object will be in disabled state.
MWWorld::CellStore* cell = MWBase::Environment::get().getWorldScene()->getCurrentCell();
MWWorld::ManualRef mref(
MWBase::Environment::get().getWorld()->getStore(), ESM::RefId::stringRefId(recordId));
const MWWorld::Ptr& ptr = mref.getPtr();
ptr.getRefData().disable();
MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, *cell, count.value_or(1));
return GObject(getId(newPtr));
};
return LuaUtil::makeReadOnly(api); return LuaUtil::makeReadOnly(api);
} }

View File

@ -154,6 +154,9 @@ namespace MWLua
mWorldView.update(); mWorldView.update();
std::erase_if(mActiveLocalScripts,
[](const LocalScripts* l) { return l->getPtr().isEmpty() || l->getPtr().getRefData().isDeleted(); });
mGlobalScripts.statsNextFrame(); mGlobalScripts.statsNextFrame();
for (LocalScripts* scripts : mActiveLocalScripts) for (LocalScripts* scripts : mActiveLocalScripts)
scripts->statsNextFrame(); scripts->statsNextFrame();

View File

@ -20,6 +20,8 @@ namespace MWLua
std::string ptrToString(const MWWorld::Ptr& ptr) std::string ptrToString(const MWWorld::Ptr& ptr)
{ {
std::string res = "object"; std::string res = "object";
if (ptr.getRefData().isDeleted())
res = "deleted object";
res.append(idToString(getId(ptr))); res.append(idToString(getId(ptr)));
res.append(" ("); res.append(" (");
res.append(getLuaObjectTypeName(ptr)); res.append(getLuaObjectTypeName(ptr));

View File

@ -7,6 +7,7 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp" #include "../mwworld/containerstore.hpp"
#include "../mwworld/player.hpp" #include "../mwworld/player.hpp"
#include "../mwworld/scene.hpp"
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
@ -87,6 +88,8 @@ namespace MWLua
{ {
MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos); MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos);
world->rotateObject(newObj, mRot); world->rotateObject(newObj, mRot);
if (!newObj.getRefData().isEnabled())
world->enable(newObj);
} }
} }
@ -198,6 +201,32 @@ namespace MWLua
context.mLuaManager->addAction(std::make_unique<ActivateAction>(context.mLua, o.id(), actor.id())); context.mLuaManager->addAction(std::make_unique<ActivateAction>(context.mLua, o.id(), actor.id()));
}; };
auto isEnabled = [](const ObjectT& o) { return o.ptr().getRefData().isEnabled(); };
auto setEnabled = [context](const GObject& object, bool enable) {
if (enable && object.ptr().getRefData().isDeleted())
throw std::runtime_error("Object is removed");
context.mLuaManager->addAction([object, enable] {
if (object.ptr().isInCell())
{
if (enable)
MWBase::Environment::get().getWorld()->enable(object.ptr());
else
MWBase::Environment::get().getWorld()->disable(object.ptr());
}
else
{
if (enable)
object.ptr().getRefData().enable();
else
throw std::runtime_error("Objects in containers can't be disabled");
}
});
};
if constexpr (std::is_same_v<ObjectT, GObject>)
objectT["enabled"] = sol::property(isEnabled, setEnabled);
else
objectT["enabled"] = sol::readonly_property(isEnabled);
if constexpr (std::is_same_v<ObjectT, GObject>) if constexpr (std::is_same_v<ObjectT, GObject>)
{ // Only for global scripts { // Only for global scripts
objectT["addScript"] = [context](const GObject& object, std::string_view path, sol::object initData) { objectT["addScript"] = [context](const GObject& object, std::string_view path, sol::object initData) {
@ -243,11 +272,74 @@ namespace MWLua
localScripts->removeScript(*scriptId); localScripts->removeScript(*scriptId);
}; };
objectT["teleport"] = [context](const GObject& object, std::string_view cell, const osg::Vec3f& pos, auto removeFn = [context](const GObject& object, int countToRemove) {
const sol::optional<osg::Vec3f>& optRot) {
MWWorld::Ptr ptr = object.ptr(); MWWorld::Ptr ptr = object.ptr();
int currentCount = ptr.getRefData().getCount();
if (countToRemove <= 0 || countToRemove > currentCount)
throw std::runtime_error("Can't remove " + std::to_string(countToRemove) + " of "
+ std::to_string(currentCount) + " items");
ptr.getRefData().setCount(currentCount - countToRemove); // Immediately change count
if (ptr.getContainerStore() || currentCount == countToRemove)
{
// Delayed action to trigger side effects
context.mLuaManager->addAction([object, countToRemove] {
MWWorld::Ptr ptr = object.ptr();
// Restore original count
ptr.getRefData().setCount(ptr.getRefData().getCount() + countToRemove);
// And now remove properly
if (ptr.getContainerStore())
ptr.getContainerStore()->remove(ptr, countToRemove);
else
{
MWBase::Environment::get().getWorld()->disable(object.ptr());
MWBase::Environment::get().getWorld()->deleteObject(ptr);
}
});
}
};
objectT["remove"] = [removeFn](const GObject& object, sol::optional<int> count) {
removeFn(object, count.value_or(object.ptr().getRefData().getCount()));
};
objectT["split"] = [removeFn](const GObject& object, int count) -> GObject {
removeFn(object, count);
// Doesn't matter which cell to use because the new instance will be in disabled state.
MWWorld::CellStore* cell = MWBase::Environment::get().getWorldScene()->getCurrentCell();
const MWWorld::Ptr& ptr = object.ptr();
MWWorld::Ptr splitted = ptr.getClass().copyToCell(ptr, *cell, count);
splitted.getRefData().disable();
return GObject(getId(splitted));
};
objectT["moveInto"] = [removeFn, context](const GObject& object, const Inventory<GObject>& inventory) {
// Currently moving to or from containers makes a copy and removes the original.
// TODO(#6148): actually move rather than copy and preserve RefNum
int count = object.ptr().getRefData().getCount();
removeFn(object, count);
context.mLuaManager->addAction([item = object, count, cont = inventory.mObj] {
auto& refData = item.ptr().getRefData();
refData.setCount(count); // temporarily undo removal to run ContainerStore::add
cont.ptr().getClass().getContainerStore(cont.ptr()).add(item.ptr(), count, false);
refData.setCount(0);
});
};
objectT["teleport"] = [removeFn, context](const GObject& object, std::string_view cell,
const osg::Vec3f& pos, const sol::optional<osg::Vec3f>& optRot) {
MWWorld::Ptr ptr = object.ptr();
if (ptr.getRefData().isDeleted())
throw std::runtime_error("Object is removed");
if (ptr.getContainerStore())
{
// Currently moving to or from containers makes a copy and removes the original.
// TODO(#6148): actually move rather than copy and preserve RefNum
auto* cellStore = MWBase::Environment::get().getWorldModel()->getCellByPosition(pos, cell);
MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, *cellStore, ptr.getRefData().getCount());
newPtr.getRefData().disable();
removeFn(object, ptr.getRefData().getCount());
ptr = newPtr;
}
osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3(); osg::Vec3f rot = optRot ? *optRot : ptr.getRefData().getPosition().asRotationVec3();
auto action = std::make_unique<TeleportAction>(context.mLua, object.id(), cell, pos, rot); auto action = std::make_unique<TeleportAction>(context.mLua, getId(ptr), cell, pos, rot);
if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())
context.mLuaManager->addTeleportPlayerAction(std::move(action)); context.mLuaManager->addTeleportPlayerAction(std::move(action));
else else
@ -335,24 +427,40 @@ namespace MWLua
return ObjectList<ObjectT>{ list }; return ObjectList<ObjectT>{ list };
}; };
inventoryT["countOf"] = [](const InventoryT& inventory, const std::string& recordId) { inventoryT["countOf"] = [](const InventoryT& inventory, std::string_view recordId) {
const MWWorld::Ptr& ptr = inventory.mObj.ptr(); const MWWorld::Ptr& ptr = inventory.mObj.ptr();
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
return store.count(ESM::RefId::stringRefId(recordId)); return store.count(ESM::RefId::stringRefId(recordId));
}; };
inventoryT["find"] = [](const InventoryT& inventory, std::string_view recordId) -> sol::optional<ObjectT> {
if constexpr (std::is_same_v<ObjectT, GObject>) const MWWorld::Ptr& ptr = inventory.mObj.ptr();
{ // Only for global scripts MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
// TODO auto itemId = ESM::RefId::stringRefId(recordId);
// obj.inventory:drop(obj2, [count]) for (const MWWorld::Ptr& item : store)
// obj.inventory:drop(recordId, [count]) {
// obj.inventory:addNew(recordId, [count]) if (item.getCellRef().getRefId() == itemId)
// obj.inventory:remove(obj/recordId, [count]) {
/*objectT["moveInto"] = [](const GObject& obj, const InventoryT& inventory) {}; MWBase::Environment::get().getWorldModel()->registerPtr(item);
inventoryT["drop"] = [](const InventoryT& inventory) {}; return ObjectT(getId(item));
inventoryT["addNew"] = [](const InventoryT& inventory) {}; }
inventoryT["remove"] = [](const InventoryT& inventory) {};*/ }
} return sol::nullopt;
};
inventoryT["findAll"] = [](const InventoryT& inventory, std::string_view recordId) {
const MWWorld::Ptr& ptr = inventory.mObj.ptr();
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
auto itemId = ESM::RefId::stringRefId(recordId);
ObjectIdList list = std::make_shared<std::vector<ObjectId>>();
for (const MWWorld::Ptr& item : store)
{
if (item.getCellRef().getRefId() == itemId)
{
MWBase::Environment::get().getWorldModel()->registerPtr(item);
list->push_back(getId(item));
}
}
return ObjectList<ObjectT>{ list };
};
} }
template <class ObjectT> template <class ObjectT>

View File

@ -39,7 +39,7 @@ namespace MWLua
std::fill(usedSlots.begin(), usedSlots.end(), false); std::fill(usedSlots.begin(), usedSlots.end(), false);
static constexpr int anySlot = -1; static constexpr int anySlot = -1;
auto tryEquipToSlot = [&actor, &store, &usedSlots](int slot, const Item& item) -> bool { auto tryEquipToSlot = [&store, &usedSlots](int slot, const Item& item) -> bool {
auto old_it = slot != anySlot ? store.getSlot(slot) : store.end(); auto old_it = slot != anySlot ? store.getSlot(slot) : store.end();
MWWorld::Ptr itemPtr; MWWorld::Ptr itemPtr;
if (std::holds_alternative<ObjectId>(item)) if (std::holds_alternative<ObjectId>(item))
@ -88,7 +88,7 @@ namespace MWLua
if (it == store.end()) // should never happen if (it == store.end()) // should never happen
throw std::logic_error("Item not found in container"); throw std::logic_error("Item not found in container");
store.equip(slot, it, actor); store.equip(slot, it);
return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was
// changed // changed
}; };
@ -100,7 +100,7 @@ namespace MWLua
if (new_it == mEquipment.end()) if (new_it == mEquipment.end())
{ {
if (old_it != store.end()) if (old_it != store.end())
store.unequipSlot(slot, actor); store.unequipSlot(slot);
continue; continue;
} }
if (tryEquipToSlot(slot, new_it->second)) if (tryEquipToSlot(slot, new_it->second))

View File

@ -199,7 +199,7 @@ namespace
continue; continue;
// Set the soul on just one of the gems, not the whole stack // Set the soul on just one of the gems, not the whole stack
gem->getContainerStore()->unstack(*gem, caster); gem->getContainerStore()->unstack(*gem);
gem->getCellRef().setSoul(creature.getCellRef().getRefId()); gem->getCellRef().setSoul(creature.getCellRef().getRefId());
// Restack the gem with other gems with the same soul // Restack the gem with other gems with the same soul
@ -1021,14 +1021,14 @@ namespace MWMechanics
{ {
// For non-hostile NPCs, unequip whatever is in the left slot in favor of a light. // For non-hostile NPCs, unequip whatever is in the left slot in favor of a light.
if (heldIter != inventoryStore.end() && heldIter->getType() != ESM::Light::sRecordId) if (heldIter != inventoryStore.end() && heldIter->getType() != ESM::Light::sRecordId)
inventoryStore.unequipItem(*heldIter, ptr); inventoryStore.unequipItem(*heldIter);
} }
else if (heldIter == inventoryStore.end() || heldIter->getType() == ESM::Light::sRecordId) else if (heldIter == inventoryStore.end() || heldIter->getType() == ESM::Light::sRecordId)
{ {
// For hostile NPCs, see if they have anything better to equip first // For hostile NPCs, see if they have anything better to equip first
auto shield = inventoryStore.getPreferredShield(ptr); auto shield = inventoryStore.getPreferredShield();
if (shield != inventoryStore.end()) if (shield != inventoryStore.end())
inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, shield, ptr); inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, shield);
} }
heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
@ -1036,7 +1036,7 @@ namespace MWMechanics
// If we have a torch and can equip it, then equip it now. // If we have a torch and can equip it, then equip it now.
if (heldIter == inventoryStore.end()) if (heldIter == inventoryStore.end())
{ {
inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torchIter, ptr); inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torchIter);
} }
} }
} }
@ -1046,7 +1046,7 @@ namespace MWMechanics
{ {
// At day, unequip lights and auto equip shields or other suitable items // At day, unequip lights and auto equip shields or other suitable items
// (Note: autoEquip will ignore lights) // (Note: autoEquip will ignore lights)
inventoryStore.autoEquip(ptr); inventoryStore.autoEquip();
} }
} }
} }
@ -1069,7 +1069,7 @@ namespace MWMechanics
timeRemaining -= duration; timeRemaining -= duration;
if (timeRemaining <= 0.f) if (timeRemaining <= 0.f)
{ {
inventoryStore.remove(*heldIter, 1, ptr); // remove it inventoryStore.remove(*heldIter, 1); // remove it
return; return;
} }
@ -1080,7 +1080,7 @@ namespace MWMechanics
// Both NPC and player lights extinguish in water. // Both NPC and player lights extinguish in water.
if (world->isSwimming(ptr)) if (world->isSwimming(ptr))
{ {
inventoryStore.remove(*heldIter, 1, ptr); // remove it inventoryStore.remove(*heldIter, 1); // remove it
// ...But, only the player makes a sound. // ...But, only the player makes a sound.
if (isPlayer) if (isPlayer)

View File

@ -106,8 +106,7 @@ namespace MWMechanics
if (actor.getClass().hasInventoryStore(actor)) if (actor.getClass().hasInventoryStore(actor))
{ {
if (mWeapon.isEmpty()) if (mWeapon.isEmpty())
actor.getClass().getInventoryStore(actor).unequipSlot( actor.getClass().getInventoryStore(actor).unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight);
MWWorld::InventoryStore::Slot_CarriedRight, actor);
else else
{ {
MWWorld::ActionEquip equip(mWeapon); MWWorld::ActionEquip equip(mWeapon);

View File

@ -275,7 +275,7 @@ void MWMechanics::Alchemy::removeIngredients()
for (TIngredientsContainer::iterator iter(mIngredients.begin()); iter != mIngredients.end(); ++iter) for (TIngredientsContainer::iterator iter(mIngredients.begin()); iter != mIngredients.end(); ++iter)
if (!iter->isEmpty()) if (!iter->isEmpty())
{ {
iter->getContainerStore()->remove(*iter, 1, mAlchemist); iter->getContainerStore()->remove(*iter, 1);
if (iter->getRefData().getCount() < 1) if (iter->getRefData().getCount() < 1)
*iter = MWWorld::Ptr(); *iter = MWWorld::Ptr();
@ -317,7 +317,7 @@ void MWMechanics::Alchemy::addPotion(const std::string& name)
if (!record) if (!record)
record = MWBase::Environment::get().getWorld()->createRecord(newRecord); record = MWBase::Environment::get().getWorld()->createRecord(newRecord);
mAlchemist.getClass().getContainerStore(mAlchemist).add(record->mId, 1, mAlchemist); mAlchemist.getClass().getContainerStore(mAlchemist).add(record->mId, 1);
} }
void MWMechanics::Alchemy::increaseSkill() void MWMechanics::Alchemy::increaseSkill()

View File

@ -140,7 +140,7 @@ namespace MWMechanics
shieldhealth -= std::min(shieldhealth, int(damage)); shieldhealth -= std::min(shieldhealth, int(damage));
shield->getCellRef().setCharge(shieldhealth); shield->getCellRef().setCharge(shieldhealth);
if (shieldhealth == 0) if (shieldhealth == 0)
inv.unequipItem(*shield, blocker); inv.unequipItem(*shield);
// Reduce blocker fatigue // Reduce blocker fatigue
static const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->mValue.getFloat(); static const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->mValue.getFloat();
static const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->mValue.getFloat(); static const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->mValue.getFloat();
@ -285,7 +285,7 @@ namespace MWMechanics
static const float fProjectileThrownStoreChance static const float fProjectileThrownStoreChance
= gmst.find("fProjectileThrownStoreChance")->mValue.getFloat(); = gmst.find("fProjectileThrownStoreChance")->mValue.getFloat();
if (Misc::Rng::rollProbability(world->getPrng()) < fProjectileThrownStoreChance / 100.f) if (Misc::Rng::rollProbability(world->getPrng()) < fProjectileThrownStoreChance / 100.f)
victim.getClass().getContainerStore(victim).add(projectile, 1, victim); victim.getClass().getContainerStore(victim).add(projectile, 1);
} }
victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true); victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true);
@ -422,7 +422,7 @@ namespace MWMechanics
// Weapon broken? unequip it // Weapon broken? unequip it
if (weaphealth == 0) if (weaphealth == 0)
weapon = *attacker.getClass().getInventoryStore(attacker).unequipItem(weapon, attacker); weapon = *attacker.getClass().getInventoryStore(attacker).unequipItem(weapon);
} }
} }

View File

@ -70,12 +70,12 @@ namespace MWMechanics
enchantment.mData.mType = mCastStyle; enchantment.mData.mType = mCastStyle;
enchantment.mData.mCost = getBaseCastCost(); enchantment.mData.mCost = getBaseCastCost();
store.remove(mSoulGemPtr, 1, player); store.remove(mSoulGemPtr, 1);
// Exception for Azura Star, new one will be added after enchanting // Exception for Azura Star, new one will be added after enchanting
auto azurasStarId = ESM::RefId::stringRefId("Misc_SoulGem_Azura"); auto azurasStarId = ESM::RefId::stringRefId("Misc_SoulGem_Azura");
if (mSoulGemPtr.get<ESM::Miscellaneous>()->mBase->mId == azurasStarId) if (mSoulGemPtr.get<ESM::Miscellaneous>()->mBase->mId == azurasStarId)
store.add(azurasStarId, 1, player); store.add(azurasStarId, 1);
if (mSelfEnchanting) if (mSelfEnchanting)
{ {
@ -105,8 +105,8 @@ namespace MWMechanics
= mOldItemPtr.getClass().applyEnchantment(mOldItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); = mOldItemPtr.getClass().applyEnchantment(mOldItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName);
// Add the new item to player inventory and remove the old one // Add the new item to player inventory and remove the old one
store.remove(mOldItemPtr, count, player); store.remove(mOldItemPtr, count);
store.add(newItemId, count, player); store.add(newItemId, count);
if (!mSelfEnchanting) if (!mSelfEnchanting)
payForEnchantment(); payForEnchantment();
@ -399,7 +399,7 @@ namespace MWMechanics
const MWWorld::Ptr& player = getPlayer(); const MWWorld::Ptr& player = getPlayer();
MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);
store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice(), player); store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice());
// add gold to NPC trading gold pool // add gold to NPC trading gold pool
CreatureStats& enchanterStats = mEnchanter.getClass().getCreatureStats(mEnchanter); CreatureStats& enchanterStats = mEnchanter.getClass().getCreatureStats(mEnchanter);

View File

@ -267,8 +267,8 @@ namespace MWMechanics
// equippable // equippable
MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
for (int i = 0; i < MWWorld::InventoryStore::Slots; ++i) for (int i = 0; i < MWWorld::InventoryStore::Slots; ++i)
invStore.unequipAll(ptr); invStore.unequipAll();
invStore.autoEquip(ptr); invStore.autoEquip();
} }
MechanicsManager::MechanicsManager() MechanicsManager::MechanicsManager()
@ -1033,8 +1033,8 @@ namespace MWMechanics
MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);
// move items from player to owner and report about theft // move items from player to owner and report about theft
victim.getClass().getContainerStore(victim).add(item, toRemove, victim); victim.getClass().getContainerStore(victim).add(item, toRemove);
store.remove(item, toRemove, player); store.remove(item, toRemove);
commitCrime( commitCrime(
player, victim, OT_Theft, item.getCellRef().getFaction(), item.getClass().getValue(item) * toRemove); player, victim, OT_Theft, item.getCellRef().getFaction(), item.getClass().getValue(item) * toRemove);
} }
@ -1063,8 +1063,8 @@ namespace MWMechanics
int toMove = it->getRefData().getCount() - itemCount; int toMove = it->getRefData().getCount() - itemCount;
containerStore.add(*it, toMove, targetContainer); containerStore.add(*it, toMove);
store.remove(*it, toMove, player); store.remove(*it, toMove);
} }
// TODO: unhardcode the locklevel // TODO: unhardcode the locklevel
targetContainer.getCellRef().lock(50); targetContainer.getCellRef().lock(50);
@ -1836,14 +1836,14 @@ namespace MWMechanics
if (werewolf) if (werewolf)
{ {
inv.unequipAll(actor); inv.unequipAll();
inv.equip(MWWorld::InventoryStore::Slot_Robe, inv.equip(MWWorld::InventoryStore::Slot_Robe,
inv.ContainerStore::add(ESM::RefId::stringRefId("werewolfrobe"), 1, actor), actor); inv.ContainerStore::add(ESM::RefId::stringRefId("werewolfrobe"), 1));
} }
else else
{ {
inv.unequipSlot(MWWorld::InventoryStore::Slot_Robe, actor); inv.unequipSlot(MWWorld::InventoryStore::Slot_Robe);
inv.ContainerStore::remove(ESM::RefId::stringRefId("werewolfrobe"), 1, actor); inv.ContainerStore::remove(ESM::RefId::stringRefId("werewolfrobe"), 1);
} }
if (actor == player->getPlayer()) if (actor == player->getPlayer())

View File

@ -83,7 +83,7 @@ namespace MWMechanics
} }
player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, 0); player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, 0);
gem.getContainerStore()->remove(gem, 1, player); gem.getContainerStore()->remove(gem, 1);
if (gem.getRefData().getCount() == 0) if (gem.getRefData().getCount() == 0)
{ {
@ -100,7 +100,7 @@ namespace MWMechanics
const ESM::RefId soulGemAzura = ESM::RefId::stringRefId("Misc_SoulGem_Azura"); const ESM::RefId soulGemAzura = ESM::RefId::stringRefId("Misc_SoulGem_Azura");
// special case: readd Azura's Star // special case: readd Azura's Star
if (gem.get<ESM::Miscellaneous>()->mBase->mId == soulGemAzura) if (gem.get<ESM::Miscellaneous>()->mBase->mId == soulGemAzura)
player.getClass().getContainerStore(player).add(soulGemAzura, 1, player); player.getClass().getContainerStore(player).add(soulGemAzura, 1);
} }
return true; return true;

View File

@ -23,7 +23,7 @@ namespace MWMechanics
MWWorld::LiveCellRef<ESM::Repair>* ref = mTool.get<ESM::Repair>(); MWWorld::LiveCellRef<ESM::Repair>* ref = mTool.get<ESM::Repair>();
// unstack tool if required // unstack tool if required
player.getClass().getContainerStore(player).unstack(mTool, player); player.getClass().getContainerStore(player).unstack(mTool);
// reduce number of uses left // reduce number of uses left
int uses = mTool.getClass().getItemHealth(mTool); int uses = mTool.getClass().getItemHealth(mTool);
@ -85,7 +85,7 @@ namespace MWMechanics
{ {
MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);
store.remove(mTool, 1, player); store.remove(mTool, 1);
std::string message = MWBase::Environment::get() std::string message = MWBase::Environment::get()
.getWorld() .getWorld()

View File

@ -72,7 +72,7 @@ namespace MWMechanics
lockpick.getCellRef().setCharge(--uses); lockpick.getCellRef().setCharge(--uses);
if (!uses) if (!uses)
lockpick.getContainerStore()->remove(lockpick, 1, mActor); lockpick.getContainerStore()->remove(lockpick, 1);
} }
void Security::probeTrap(const MWWorld::Ptr& trap, const MWWorld::Ptr& probe, std::string_view& resultMessage, void Security::probeTrap(const MWWorld::Ptr& trap, const MWWorld::Ptr& probe, std::string_view& resultMessage,
@ -124,7 +124,7 @@ namespace MWMechanics
probe.getCellRef().setCharge(--uses); probe.getCellRef().setCharge(--uses);
if (!uses) if (!uses)
probe.getContainerStore()->remove(probe, 1, mActor); probe.getContainerStore()->remove(probe, 1);
} }
} }

View File

@ -360,7 +360,7 @@ namespace MWMechanics
else if (type == ESM::Enchantment::CastOnce) else if (type == ESM::Enchantment::CastOnce)
{ {
if (!godmode) if (!godmode)
item.getContainerStore()->remove(item, 1, mCaster); item.getContainerStore()->remove(item, 1);
} }
else if (type == ESM::Enchantment::WhenStrikes) else if (type == ESM::Enchantment::WhenStrikes)
{ {

View File

@ -151,9 +151,9 @@ namespace
{ {
// Will unequip the broken item and try to find a replacement // Will unequip the broken item and try to find a replacement
if (ptr != MWMechanics::getPlayer()) if (ptr != MWMechanics::getPlayer())
inv.autoEquip(ptr); inv.autoEquip();
else else
inv.unequipItem(*item, ptr); inv.unequipItem(*item);
} }
return true; return true;
@ -173,7 +173,7 @@ namespace
void addBoundItem(const ESM::RefId& itemId, const MWWorld::Ptr& actor) void addBoundItem(const ESM::RefId& itemId, const MWWorld::Ptr& actor)
{ {
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1);
int slot = getBoundItemSlot(boundPtr); int slot = getBoundItemSlot(boundPtr);
auto prevItem = slot >= 0 ? store.getSlot(slot) : store.end(); auto prevItem = slot >= 0 ? store.getSlot(slot) : store.end();
@ -217,9 +217,9 @@ namespace
bool wasEquipped = currentItem != store.end() && currentItem->getCellRef().getRefId() == itemId; bool wasEquipped = currentItem != store.end() && currentItem->getCellRef().getRefId() == itemId;
if (wasEquipped) if (wasEquipped)
store.remove(*currentItem, 1, actor); store.remove(*currentItem, 1);
else else
store.remove(itemId, 1, actor); store.remove(itemId, 1);
if (actor != MWMechanics::getPlayer()) if (actor != MWMechanics::getPlayer())
{ {
@ -240,7 +240,7 @@ namespace
if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
return; return;
actor.getClass().getInventoryStore(actor).autoEquip(actor); actor.getClass().getInventoryStore(actor).autoEquip();
return; return;
} }
@ -517,7 +517,7 @@ namespace MWMechanics
if (target.getClass().hasInventoryStore(target)) if (target.getClass().hasInventoryStore(target))
{ {
auto& store = target.getClass().getInventoryStore(target); auto& store = target.getClass().getInventoryStore(target);
store.unequipAll(target); store.unequipAll();
} }
else else
invalid = true; invalid = true;
@ -1072,7 +1072,7 @@ namespace MWMechanics
break; break;
case ESM::MagicEffect::ExtraSpell: case ESM::MagicEffect::ExtraSpell:
if (magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) if (magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f)
target.getClass().getInventoryStore(target).autoEquip(target); target.getClass().getInventoryStore(target).autoEquip();
break; break;
case ESM::MagicEffect::TurnUndead: case ESM::MagicEffect::TurnUndead:
{ {

View File

@ -52,7 +52,7 @@ namespace MWRender
if (animated) if (animated)
addAnimSource(model, model); addAnimSource(model, model);
mPtr.getClass().getInventoryStore(mPtr).setInvListener(this, mPtr); mPtr.getClass().getInventoryStore(mPtr).setInvListener(this);
updateParts(); updateParts();
} }

View File

@ -120,7 +120,7 @@ namespace MWRender
if (mObjects.emplace(ptr.mRef, anim).second) if (mObjects.emplace(ptr.mRef, anim).second)
{ {
ptr.getClass().getInventoryStore(ptr).setInvListener(anim.get(), ptr); ptr.getClass().getInventoryStore(ptr).setInvListener(anim.get());
ptr.getClass().getInventoryStore(ptr).setContListener(anim.get()); ptr.getClass().getInventoryStore(ptr).setContListener(anim.get());
} }
} }
@ -140,7 +140,7 @@ namespace MWRender
if (ptr.getClass().isActor()) if (ptr.getClass().isActor())
{ {
if (ptr.getClass().hasInventoryStore(ptr)) if (ptr.getClass().hasInventoryStore(ptr))
ptr.getClass().getInventoryStore(ptr).setInvListener(nullptr, ptr); ptr.getClass().getInventoryStore(ptr).setInvListener(nullptr);
ptr.getClass().getContainerStore(ptr).setContListener(nullptr); ptr.getClass().getContainerStore(ptr).setContListener(nullptr);
} }
@ -163,7 +163,7 @@ namespace MWRender
if (ptr.getClass().isActor() && ptr.getRefData().getCustomData()) if (ptr.getClass().isActor() && ptr.getRefData().getCustomData())
{ {
if (ptr.getClass().hasInventoryStore(ptr)) if (ptr.getClass().hasInventoryStore(ptr))
ptr.getClass().getInventoryStore(ptr).setInvListener(nullptr, ptr); ptr.getClass().getInventoryStore(ptr).setInvListener(nullptr);
ptr.getClass().getContainerStore(ptr).setContListener(nullptr); ptr.getClass().getContainerStore(ptr).setContListener(nullptr);
} }

View File

@ -140,7 +140,7 @@ namespace MWRender
showWeapon(false); showWeapon(false);
inv.remove(*weapon, 1, actor); inv.remove(*weapon, 1);
} }
else else
{ {
@ -167,7 +167,7 @@ namespace MWRender
MWBase::Environment::get().getWorld()->launchProjectile( MWBase::Environment::get().getWorld()->launchProjectile(
actor, ammoPtr, launchPos, orient, weaponPtr, speed, attackStrength); actor, ammoPtr, launchPos, orient, weaponPtr, speed, attackStrength);
inv.remove(ammoPtr, 1, actor); inv.remove(ammoPtr, 1);
mAmmunition.reset(); mAmmunition.reset();
} }
} }

View File

@ -35,23 +35,21 @@
namespace namespace
{ {
void addToStore( void addToStore(const MWWorld::Ptr& itemPtr, int count, MWWorld::ContainerStore& store, bool resolve = true)
const MWWorld::Ptr& itemPtr, int count, MWWorld::Ptr& ptr, MWWorld::ContainerStore& store, bool resolve = true)
{ {
if (itemPtr.getClass().getScript(itemPtr).empty()) if (itemPtr.getClass().getScript(itemPtr).empty())
{ {
store.add(itemPtr, count, ptr, true, resolve); store.add(itemPtr, count, true, resolve);
} }
else else
{ {
// Adding just one item per time to make sure there isn't a stack of scripted items // Adding just one item per time to make sure there isn't a stack of scripted items
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
store.add(itemPtr, 1, ptr, true, resolve); store.add(itemPtr, 1, true, resolve);
} }
} }
void addRandomToStore(const MWWorld::Ptr& itemPtr, int count, MWWorld::Ptr& owner, MWWorld::ContainerStore& store, void addRandomToStore(const MWWorld::Ptr& itemPtr, int count, MWWorld::ContainerStore& store, bool topLevel = true)
bool topLevel = true)
{ {
if (itemPtr.getType() == ESM::ItemLevList::sRecordId) if (itemPtr.getType() == ESM::ItemLevList::sRecordId)
{ {
@ -60,7 +58,7 @@ namespace
if (topLevel && count > 1 && levItemList->mFlags & ESM::ItemLevList::Each) if (topLevel && count > 1 && levItemList->mFlags & ESM::ItemLevList::Each)
{ {
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
addRandomToStore(itemPtr, 1, owner, store, true); addRandomToStore(itemPtr, 1, store, true);
} }
else else
{ {
@ -70,11 +68,11 @@ namespace
if (itemId.empty()) if (itemId.empty())
return; return;
MWWorld::ManualRef manualRef(MWBase::Environment::get().getWorld()->getStore(), itemId, 1); MWWorld::ManualRef manualRef(MWBase::Environment::get().getWorld()->getStore(), itemId, 1);
addRandomToStore(manualRef.getPtr(), count, owner, store, false); addRandomToStore(manualRef.getPtr(), count, store, false);
} }
} }
else else
addToStore(itemPtr, count, owner, store); addToStore(itemPtr, count, store);
} }
} }
@ -141,20 +139,20 @@ namespace MWScript
{ {
if (store.isResolved()) if (store.isResolved())
{ {
addRandomToStore(itemPtr, count, ptr, store); addRandomToStore(itemPtr, count, store);
} }
} }
else else
addToStore(itemPtr, count, ptr, store, store.isResolved()); addToStore(itemPtr, count, store, store.isResolved());
} }
} }
return; return;
} }
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
if (isLevelledList) if (isLevelledList)
addRandomToStore(itemPtr, count, ptr, store); addRandomToStore(itemPtr, count, store);
else else
addToStore(itemPtr, count, ptr, store); addToStore(itemPtr, count, store);
// Spawn a messagebox (only for items added to player's inventory and if player is talking to someone) // 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()) if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())
@ -246,7 +244,7 @@ namespace MWScript
auto& store = container.getClass().getContainerStore(container); auto& store = container.getClass().getContainerStore(container);
// Note that unlike AddItem, RemoveItem only removes from unresolved containers // Note that unlike AddItem, RemoveItem only removes from unresolved containers
if (!store.isResolved()) if (!store.isResolved())
store.remove(item, count, ptr, false, false); store.remove(item, count, false, false);
} }
} }
return; return;
@ -263,7 +261,7 @@ namespace MWScript
} }
} }
int numRemoved = store.remove(item, count, ptr); int numRemoved = store.remove(item, count);
// Spawn a messagebox (only for items removed from player's inventory) // Spawn a messagebox (only for items removed from player's inventory)
if ((numRemoved > 0) && (ptr == MWMechanics::getPlayer())) if ((numRemoved > 0) && (ptr == MWMechanics::getPlayer()))
@ -318,7 +316,7 @@ namespace MWScript
if (found == invStore.end()) if (found == invStore.end())
{ {
MWWorld::ManualRef ref(store, item, 1); MWWorld::ManualRef ref(store, item, 1);
found = ptr.getClass().getContainerStore(ptr).add(ref.getPtr(), 1, ptr, false); found = ptr.getClass().getContainerStore(ptr).add(ref.getPtr(), 1, false);
Log(Debug::Warning) << "Implicitly adding one " << item << " to the inventory store of " Log(Debug::Warning) << "Implicitly adding one " << item << " to the inventory store of "
<< ptr.getCellRef().getRefId() << ptr.getCellRef().getRefId()
<< " to fulfill the requirements of Equip instruction"; << " to fulfill the requirements of Equip instruction";

View File

@ -580,10 +580,10 @@ namespace MWScript
store.get<ESM::Creature>().find( store.get<ESM::Creature>().find(
creature); // This line throws an exception if it can't find the creature creature); // This line throws an exception if it can't find the creature
MWWorld::Ptr item = *ptr.getClass().getContainerStore(ptr).add(gem, 1, ptr); MWWorld::Ptr item = *ptr.getClass().getContainerStore(ptr).add(gem, 1);
// Set the soul on just one of the gems, not the whole stack // Set the soul on just one of the gems, not the whole stack
item.getContainerStore()->unstack(item, ptr); item.getContainerStore()->unstack(item);
item.getCellRef().setSoul(creature); item.getCellRef().setSoul(creature);
// Restack the gem with other gems with the same soul // Restack the gem with other gems with the same soul
@ -614,7 +614,7 @@ namespace MWScript
{ {
if (it->getCellRef().getSoul() == soul) if (it->getCellRef().getSoul() == soul)
{ {
store.remove(*it, 1, ptr); store.remove(*it, 1);
return; return;
} }
} }
@ -667,7 +667,7 @@ namespace MWScript
if (it != store.end() && it->getCellRef().getRefId() == item) if (it != store.end() && it->getCellRef().getRefId() == item)
{ {
int numToRemove = std::min(amount - numNotEquipped, it->getRefData().getCount()); int numToRemove = std::min(amount - numNotEquipped, it->getRefData().getCount());
store.unequipItemQuantity(*it, ptr, numToRemove); store.unequipItemQuantity(*it, numToRemove);
numNotEquipped += numToRemove; numNotEquipped += numToRemove;
} }
} }
@ -676,7 +676,7 @@ namespace MWScript
{ {
if (iter->getCellRef().getRefId() == item && !store.isEquipped(*iter)) if (iter->getCellRef().getRefId() == item && !store.isEquipped(*iter))
{ {
int removed = store.remove(*iter, amount, ptr); int removed = store.remove(*iter, amount);
MWWorld::Ptr dropped MWWorld::Ptr dropped
= MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); = MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed);
dropped.getCellRef().setOwner(ESM::RefId::sEmpty); dropped.getCellRef().setOwner(ESM::RefId::sEmpty);
@ -732,7 +732,7 @@ namespace MWScript
if (iter->getCellRef().getSoul() == soul) if (iter->getCellRef().getSoul() == soul)
{ {
MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, 1); MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, 1);
store.remove(*iter, 1, ptr); store.remove(*iter, 1);
break; break;
} }
} }

View File

@ -75,7 +75,7 @@ namespace MWWorld
if (invStore.getSlot(*slot) == invStore.end()) if (invStore.getSlot(*slot) == invStore.end())
{ {
// slot is not occupied // slot is not occupied
invStore.equip(*slot, it, actor); invStore.equip(*slot, it);
break; break;
} }
} }
@ -88,14 +88,14 @@ namespace MWWorld
bool reEquip = false; bool reEquip = false;
for (slot = slots_.first.begin(); slot != slots_.first.end(); ++slot) for (slot = slots_.first.begin(); slot != slots_.first.end(); ++slot)
{ {
invStore.unequipSlot(*slot, actor, false); invStore.unequipSlot(*slot, false);
if (slot + 1 != slots_.first.end()) if (slot + 1 != slots_.first.end())
{ {
invStore.equip(*slot, invStore.getSlot(*(slot + 1)), actor); invStore.equip(*slot, invStore.getSlot(*(slot + 1)));
} }
else else
{ {
invStore.equip(*slot, it, actor); invStore.equip(*slot, it);
} }
// Fix for issue of selected enchated item getting remmoved on cycle // Fix for issue of selected enchated item getting remmoved on cycle

View File

@ -42,8 +42,8 @@ namespace MWWorld
// not work for a last item in the container - empty harvested containers are considered as "allowed to // not work for a last item in the container - empty harvested containers are considered as "allowed to
// use". // use".
MWBase::Environment::get().getMechanicsManager()->itemTaken(actor, *it, target, itemCount); MWBase::Environment::get().getMechanicsManager()->itemTaken(actor, *it, target, itemCount);
actorStore.add(*it, itemCount, actor); actorStore.add(*it, itemCount);
store.remove(*it, itemCount, getTarget()); store.remove(*it, itemCount);
std::string name{ it->getClass().getName(*it) }; std::string name{ it->getClass().getName(*it) };
takenMap[name] += itemCount; takenMap[name] += itemCount;
} }

View File

@ -33,7 +33,7 @@ namespace MWWorld
MWBase::Environment::get().getMechanicsManager()->itemTaken( MWBase::Environment::get().getMechanicsManager()->itemTaken(
actor, getTarget(), MWWorld::Ptr(), getTarget().getRefData().getCount()); actor, getTarget(), MWWorld::Ptr(), getTarget().getRefData().getCount());
MWWorld::Ptr newitem MWWorld::Ptr newitem
= *actor.getClass().getContainerStore(actor).add(getTarget(), getTarget().getRefData().getCount(), actor); = *actor.getClass().getContainerStore(actor).add(getTarget(), getTarget().getRefData().getCount());
MWBase::Environment::get().getWorld()->deleteObject(getTarget()); MWBase::Environment::get().getWorld()->deleteObject(getTarget());
setTarget(newitem); setTarget(newitem);
} }

View File

@ -52,6 +52,7 @@
#include "class.hpp" #include "class.hpp"
#include "containerstore.hpp" #include "containerstore.hpp"
#include "esmstore.hpp" #include "esmstore.hpp"
#include "inventorystore.hpp"
#include "ptr.hpp" #include "ptr.hpp"
#include "worldmodel.hpp" #include "worldmodel.hpp"
@ -264,10 +265,9 @@ namespace
if (!iter->mData.isEnabled()) if (!iter->mData.isEnabled())
{ {
iter->mData.enable(); iter->mData.enable();
MWBase::Environment::get().getWorld()->disable(MWWorld::Ptr(&*iter, cellstore)); MWBase::Environment::get().getWorld()->disable(ptr);
} }
else MWBase::Environment::get().getWorldModel()->registerPtr(ptr);
MWBase::Environment::get().getWorldModel()->registerPtr(MWWorld::Ptr(&*iter, cellstore));
return; return;
} }
@ -444,7 +444,11 @@ namespace MWWorld
mMovedToAnotherCell.insert(std::make_pair(object.getBase(), cellToMoveTo)); mMovedToAnotherCell.insert(std::make_pair(object.getBase(), cellToMoveTo));
updateMergedRefs(); updateMergedRefs();
return MWWorld::Ptr(object.getBase(), cellToMoveTo); MWWorld::Ptr ptr(object.getBase(), cellToMoveTo);
const Class& cls = ptr.getClass();
if (cls.hasInventoryStore(ptr))
cls.getInventoryStore(ptr).setActor(ptr);
return ptr;
} }
struct MergeVisitor struct MergeVisitor

View File

@ -15,8 +15,10 @@
#include "actiontake.hpp" #include "actiontake.hpp"
#include "containerstore.hpp" #include "containerstore.hpp"
#include "failedaction.hpp" #include "failedaction.hpp"
#include "inventorystore.hpp"
#include "nullaction.hpp" #include "nullaction.hpp"
#include "ptr.hpp" #include "ptr.hpp"
#include "worldmodel.hpp"
#include "../mwgui/tooltips.hpp" #include "../mwgui/tooltips.hpp"
@ -372,6 +374,9 @@ namespace MWWorld
Ptr newPtr = copyToCellImpl(ptr, cell); Ptr newPtr = copyToCellImpl(ptr, cell);
newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference
newPtr.getRefData().setCount(count); newPtr.getRefData().setCount(count);
MWBase::Environment::get().getWorldModel()->registerPtr(newPtr);
if (hasInventoryStore(newPtr))
getInventoryStore(newPtr).setActor(newPtr);
return newPtr; return newPtr;
} }

View File

@ -192,6 +192,12 @@ int MWWorld::ContainerStore::count(const ESM::RefId& id) const
return total; return total;
} }
void MWWorld::ContainerStore::clearRefNums()
{
for (const auto& iter : *this)
iter.getCellRef().unsetRefNum();
}
MWWorld::ContainerStoreListener* MWWorld::ContainerStore::getContListener() const MWWorld::ContainerStoreListener* MWWorld::ContainerStore::getContListener() const
{ {
return mListener; return mListener;
@ -202,7 +208,7 @@ void MWWorld::ContainerStore::setContListener(MWWorld::ContainerStoreListener* l
mListener = listener; mListener = listener;
} }
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr& ptr, const Ptr& container, int count) MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr& ptr, int count)
{ {
resolve(); resolve();
if (ptr.getRefData().getCount() <= count) if (ptr.getRefData().getCount() <= count)
@ -212,7 +218,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr& ptr,
if (!script.empty()) if (!script.empty())
MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it);
remove(ptr, ptr.getRefData().getCount() - count, container); remove(ptr, ptr.getRefData().getCount() - count);
return it; return it;
} }
@ -285,14 +291,14 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2)
&& cls2.getItemHealth(ptr2) == cls2.getItemMaxHealth(ptr2))); && cls2.getItemHealth(ptr2) == cls2.getItemMaxHealth(ptr2)));
} }
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const ESM::RefId& id, int count, const Ptr& actorPtr) MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const ESM::RefId& id, int count)
{ {
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count);
return add(ref.getPtr(), count, actorPtr); return add(ref.getPtr(), count);
} }
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add( MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(
const Ptr& itemPtr, int count, const Ptr& actorPtr, bool /*allowAutoEquip*/, bool resolve) const Ptr& itemPtr, int count, bool /*allowAutoEquip*/, bool resolve)
{ {
Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
@ -326,7 +332,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(
const ESM::RefId& script = item.getClass().getScript(item); const ESM::RefId& script = item.getClass().getScript(item);
if (!script.empty()) if (!script.empty())
{ {
if (actorPtr == player) if (mActor == player)
{ {
// Items in player's inventory have cell set to 0, so their scripts will never be removed // Items in player's inventory have cell set to 0, so their scripts will never be removed
item.mCell = nullptr; item.mCell = nullptr;
@ -335,7 +341,10 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(
{ {
// Set mCell to the cell of the container/actor, so that the scripts are removed properly when // Set mCell to the cell of the container/actor, so that the scripts are removed properly when
// the cell of the container/actor goes inactive // the cell of the container/actor goes inactive
item.mCell = actorPtr.getCell(); if (!mPtr.isEmpty())
item.mCell = mPtr.getCell();
else if (!mActor.isEmpty())
item.mCell = mActor.getCell();
} }
item.mContainerStore = this; item.mContainerStore = this;
@ -344,12 +353,12 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(
// Set OnPCAdd special variable, if it is declared // Set OnPCAdd special variable, if it is declared
// Make sure to do this *after* we have added the script to LocalScripts // Make sure to do this *after* we have added the script to LocalScripts
if (actorPtr == player) if (mActor == player)
item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1); item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1);
} }
// we should not fire event for InventoryStore yet - it has some custom logic // we should not fire event for InventoryStore yet - it has some custom logic
if (mListener && !actorPtr.getClass().hasInventoryStore(actorPtr)) if (mListener && !(!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor)))
mListener->itemAdded(item, count); mListener->itemAdded(item, count);
return it; return it;
@ -504,8 +513,7 @@ void MWWorld::ContainerStore::updateRechargingItems()
} }
} }
int MWWorld::ContainerStore::remove( int MWWorld::ContainerStore::remove(const ESM::RefId& itemId, int count, bool equipReplacement, bool resolveFirst)
const ESM::RefId& itemId, int count, const Ptr& actor, bool equipReplacement, bool resolveFirst)
{ {
if (resolveFirst) if (resolveFirst)
resolve(); resolve();
@ -513,7 +521,7 @@ int MWWorld::ContainerStore::remove(
for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter)
if (iter->getCellRef().getRefId() == itemId) if (iter->getCellRef().getRefId() == itemId)
toRemove -= remove(*iter, toRemove, actor, equipReplacement, resolveFirst); toRemove -= remove(*iter, toRemove, equipReplacement, resolveFirst);
flagAsModified(); flagAsModified();
@ -532,8 +540,7 @@ bool MWWorld::ContainerStore::hasVisibleItems() const
return false; return false;
} }
int MWWorld::ContainerStore::remove( int MWWorld::ContainerStore::remove(const Ptr& item, int count, bool equipReplacement, bool resolveFirst)
const Ptr& item, int count, const Ptr& actor, bool equipReplacement, bool resolveFirst)
{ {
assert(this == item.getContainerStore()); assert(this == item.getContainerStore());
if (resolveFirst) if (resolveFirst)
@ -556,7 +563,7 @@ int MWWorld::ContainerStore::remove(
flagAsModified(); flagAsModified();
// we should not fire event for InventoryStore yet - it has some custom logic // we should not fire event for InventoryStore yet - it has some custom logic
if (mListener && !actor.getClass().hasInventoryStore(actor)) if (mListener && !(!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor)))
mListener->itemRemoved(item, count - toRemove); mListener->itemRemoved(item, count - toRemove);
// number of removed items // number of removed items

View File

@ -102,12 +102,21 @@ namespace MWWorld
protected: protected:
ContainerStoreListener* mListener; ContainerStoreListener* mListener;
// Used in clone() to unset refnums of copies.
// (RefNum should be unique, copy can not have the same RefNum).
void clearRefNums();
// (item, max charge) // (item, max charge)
typedef std::vector<std::pair<ContainerStoreIterator, float>> TRechargingItems; typedef std::vector<std::pair<ContainerStoreIterator, float>> TRechargingItems;
TRechargingItems mRechargingItems; TRechargingItems mRechargingItems;
bool mRechargingItemsUpToDate; bool mRechargingItemsUpToDate;
// Non-empty only if is InventoryStore.
// The actor whose inventory it is.
// TODO: Consider merging mActor and mPtr.
MWWorld::Ptr mActor;
private: private:
MWWorld::CellRefList<ESM::Potion> potions; MWWorld::CellRefList<ESM::Potion> potions;
MWWorld::CellRefList<ESM::Apparatus> appas; MWWorld::CellRefList<ESM::Apparatus> appas;
@ -128,7 +137,7 @@ namespace MWWorld
bool mModified; bool mModified;
bool mResolved; bool mResolved;
unsigned int mSeed; unsigned int mSeed;
MWWorld::Ptr mPtr; MWWorld::Ptr mPtr; // Container that contains this store. Set in MWClass::Container::getContainerStore
std::weak_ptr<ResolutionListener> mResolutionListener; std::weak_ptr<ResolutionListener> mResolutionListener;
ContainerStoreIterator addImp(const Ptr& ptr, int count, bool markModified = true); ContainerStoreIterator addImp(const Ptr& ptr, int count, bool markModified = true);
@ -160,7 +169,12 @@ namespace MWWorld
virtual ~ContainerStore(); virtual ~ContainerStore();
virtual std::unique_ptr<ContainerStore> clone() { return std::make_unique<ContainerStore>(*this); } virtual std::unique_ptr<ContainerStore> clone()
{
auto res = std::make_unique<ContainerStore>(*this);
res->clearRefNums();
return res;
}
ConstContainerStoreIterator cbegin(int mask = Type_All) const; ConstContainerStoreIterator cbegin(int mask = Type_All) const;
ConstContainerStoreIterator cend() const; ConstContainerStoreIterator cend() const;
@ -173,7 +187,7 @@ namespace MWWorld
bool hasVisibleItems() const; bool hasVisibleItems() const;
virtual ContainerStoreIterator add( virtual ContainerStoreIterator add(
const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true, bool resolve = true); const Ptr& itemPtr, int count, bool allowAutoEquip = true, bool resolve = true);
///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed)
/// ///
/// \note The item pointed to is not required to exist beyond this function call. /// \note The item pointed to is not required to exist beyond this function call.
@ -184,17 +198,15 @@ namespace MWWorld
/// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to
/// the newly inserted item. /// the newly inserted item.
ContainerStoreIterator add(const ESM::RefId& id, int count, const Ptr& actorPtr); ContainerStoreIterator add(const ESM::RefId& id, int count);
///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true) ///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true)
int remove( int remove(const ESM::RefId& itemId, int count, bool equipReplacement = 0, bool resolve = true);
const ESM::RefId& itemId, int count, const Ptr& actor, bool equipReplacement = 0, bool resolve = true);
///< Remove \a count item(s) designated by \a itemId from this container. ///< Remove \a count item(s) designated by \a itemId from this container.
/// ///
/// @return the number of items actually removed /// @return the number of items actually removed
virtual int remove( virtual int remove(const Ptr& item, int count, bool equipReplacement = 0, bool resolve = true);
const Ptr& item, int count, const Ptr& actor, bool equipReplacement = 0, bool resolve = true);
///< Remove \a count item(s) designated by \a item from this inventory. ///< Remove \a count item(s) designated by \a item from this inventory.
/// ///
/// @return the number of items actually removed /// @return the number of items actually removed
@ -202,7 +214,7 @@ namespace MWWorld
void rechargeItems(float duration); void rechargeItems(float duration);
///< Restore charge on enchanted items. Note this should only be done for the player. ///< Restore charge on enchanted items. Note this should only be done for the player.
ContainerStoreIterator unstack(const Ptr& ptr, const Ptr& container, int count = 1); ContainerStoreIterator unstack(const Ptr& ptr, int count = 1);
///< Unstack an item in this container. The item's count will be set to count, then a new stack will be added ///< Unstack an item in this container. The item's count will be set to count, then a new stack will be added
///< with (origCount-count). ///< with (origCount-count).
/// ///

View File

@ -128,18 +128,18 @@ MWWorld::InventoryStore& MWWorld::InventoryStore::operator=(const InventoryStore
} }
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add( MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(
const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip, bool resolve) const Ptr& itemPtr, int count, bool allowAutoEquip, bool resolve)
{ {
const MWWorld::ContainerStoreIterator& retVal const MWWorld::ContainerStoreIterator& retVal
= MWWorld::ContainerStore::add(itemPtr, count, actorPtr, allowAutoEquip, resolve); = MWWorld::ContainerStore::add(itemPtr, count, allowAutoEquip, resolve);
// Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves // Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves
if (allowAutoEquip && actorPtr != MWMechanics::getPlayer() && actorPtr.getClass().isNpc() if (allowAutoEquip && mActor != MWMechanics::getPlayer() && mActor.getClass().isNpc()
&& !actorPtr.getClass().getNpcStats(actorPtr).isWerewolf()) && !mActor.getClass().getNpcStats(mActor).isWerewolf())
{ {
auto type = itemPtr.getType(); auto type = itemPtr.getType();
if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId) if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId)
autoEquip(actorPtr); autoEquip();
} }
if (mListener) if (mListener)
@ -148,7 +148,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(
return retVal; return retVal;
} }
void MWWorld::InventoryStore::equip(int slot, const ContainerStoreIterator& iterator, const Ptr& actor) void MWWorld::InventoryStore::equip(int slot, const ContainerStoreIterator& iterator)
{ {
if (iterator == end()) if (iterator == end())
throw std::runtime_error("can't equip end() iterator, use unequip function instead"); throw std::runtime_error("can't equip end() iterator, use unequip function instead");
@ -167,31 +167,31 @@ void MWWorld::InventoryStore::equip(int slot, const ContainerStoreIterator& iter
throw std::runtime_error("invalid slot"); throw std::runtime_error("invalid slot");
if (mSlots[slot] != end()) if (mSlots[slot] != end())
unequipSlot(slot, actor); unequipSlot(slot);
// unstack item pointed to by iterator if required // unstack item pointed to by iterator if required
if (iterator != end() && !slots_.second if (iterator != end() && !slots_.second
&& iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped
{ {
unstack(*iterator, actor); unstack(*iterator);
} }
mSlots[slot] = iterator; mSlots[slot] = iterator;
flagAsModified(); flagAsModified();
fireEquipmentChangedEvent(actor); fireEquipmentChangedEvent();
} }
void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) void MWWorld::InventoryStore::unequipAll()
{ {
mUpdatesEnabled = false; mUpdatesEnabled = false;
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
unequipSlot(slot, actor); unequipSlot(slot);
mUpdatesEnabled = true; mUpdatesEnabled = true;
fireEquipmentChangedEvent(actor); fireEquipmentChangedEvent();
} }
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot(int slot) MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot(int slot)
@ -223,14 +223,14 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::findSlot(int slot) cons
return mSlots[slot]; return mSlots[slot];
} }
void MWWorld::InventoryStore::autoEquipWeapon(const MWWorld::Ptr& actor, TSlots& slots_) void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_)
{ {
if (!actor.getClass().isNpc()) if (!mActor.getClass().isNpc())
{ {
// In original game creatures do not autoequip weapon, but we need it for weapon sheathing. // In original game creatures do not autoequip weapon, but we need it for weapon sheathing.
// The only case when the difference is noticable - when this creature sells weapon. // The only case when the difference is noticable - when this creature sells weapon.
// So just disable weapon autoequipping for creatures which sells weapon. // So just disable weapon autoequipping for creatures which sells weapon.
int services = actor.getClass().getServices(actor); int services = mActor.getClass().getServices(mActor);
bool sellsWeapon = services & (ESM::NPC::Weapon | ESM::NPC::MagicItems); bool sellsWeapon = services & (ESM::NPC::Weapon | ESM::NPC::MagicItems);
if (sellsWeapon) if (sellsWeapon)
return; return;
@ -285,7 +285,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(const MWWorld::Ptr& actor, TSlots&
for (int j = 0; j < static_cast<int>(weaponSkillsLength); ++j) for (int j = 0; j < static_cast<int>(weaponSkillsLength); ++j)
{ {
float skillValue = actor.getClass().getSkill(actor, static_cast<int>(weaponSkills[j])); float skillValue = mActor.getClass().getSkill(mActor, static_cast<int>(weaponSkills[j]));
if (skillValue > max && !weaponSkillVisited[j]) if (skillValue > max && !weaponSkillVisited[j])
{ {
max = skillValue; max = skillValue;
@ -328,7 +328,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(const MWWorld::Ptr& actor, TSlots&
} }
} }
if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, actor).first) if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, mActor).first)
{ {
// Do not equip ranged weapons, if there is no suitable ammo // Do not equip ranged weapons, if there is no suitable ammo
bool hasAmmo = true; bool hasAmmo = true;
@ -360,7 +360,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(const MWWorld::Ptr& actor, TSlots&
{ {
if (weapon->getRefData().getCount() > 1) if (weapon->getRefData().getCount() > 1)
{ {
unstack(*weapon, actor); unstack(*weapon);
} }
} }
@ -379,13 +379,13 @@ void MWWorld::InventoryStore::autoEquipWeapon(const MWWorld::Ptr& actor, TSlots&
} }
} }
void MWWorld::InventoryStore::autoEquipArmor(const MWWorld::Ptr& actor, TSlots& slots_) void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_)
{ {
// Only NPCs can wear armor for now. // Only NPCs can wear armor for now.
// For creatures we equip only shields. // For creatures we equip only shields.
if (!actor.getClass().isNpc()) if (!mActor.getClass().isNpc())
{ {
autoEquipShield(actor, slots_); autoEquipShield(slots_);
return; return;
} }
@ -395,7 +395,7 @@ void MWWorld::InventoryStore::autoEquipArmor(const MWWorld::Ptr& actor, TSlots&
static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat();
static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat();
float unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored); float unarmoredSkill = mActor.getClass().getSkill(mActor, ESM::Skill::Unarmored);
float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill); float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill);
for (ContainerStoreIterator iter(begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter != end(); for (ContainerStoreIterator iter(begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter != end();
@ -403,7 +403,7 @@ void MWWorld::InventoryStore::autoEquipArmor(const MWWorld::Ptr& actor, TSlots&
{ {
Ptr test = *iter; Ptr test = *iter;
switch (test.getClass().canBeEquipped(test, actor).first) switch (test.getClass().canBeEquipped(test, mActor).first)
{ {
case 0: case 0:
continue; continue;
@ -412,7 +412,7 @@ void MWWorld::InventoryStore::autoEquipArmor(const MWWorld::Ptr& actor, TSlots&
} }
if (iter.getType() == ContainerStore::Type_Armor if (iter.getType() == ContainerStore::Type_Armor
&& test.getClass().getEffectiveArmorRating(test, actor) <= std::max(unarmoredRating, 0.f)) && test.getClass().getEffectiveArmorRating(test, mActor) <= std::max(unarmoredRating, 0.f))
{ {
continue; continue;
} }
@ -437,8 +437,8 @@ void MWWorld::InventoryStore::autoEquipArmor(const MWWorld::Ptr& actor, TSlots&
if (old.get<ESM::Armor>()->mBase->mData.mType == test.get<ESM::Armor>()->mBase->mData.mType) if (old.get<ESM::Armor>()->mBase->mData.mType == test.get<ESM::Armor>()->mBase->mData.mType)
{ {
if (old.getClass().getEffectiveArmorRating(old, actor) if (old.getClass().getEffectiveArmorRating(old, mActor)
>= test.getClass().getEffectiveArmorRating(test, actor)) >= test.getClass().getEffectiveArmorRating(test, mActor))
// old armor had better armor rating // old armor had better armor rating
continue; continue;
} }
@ -483,7 +483,7 @@ void MWWorld::InventoryStore::autoEquipArmor(const MWWorld::Ptr& actor, TSlots&
// unstack item pointed to by iterator if required // unstack item pointed to by iterator if required
if (iter->getRefData().getCount() > 1) if (iter->getRefData().getCount() > 1)
{ {
unstack(*iter, actor); unstack(*iter);
} }
} }
@ -494,13 +494,13 @@ void MWWorld::InventoryStore::autoEquipArmor(const MWWorld::Ptr& actor, TSlots&
} }
} }
void MWWorld::InventoryStore::autoEquipShield(const MWWorld::Ptr& actor, TSlots& slots_) void MWWorld::InventoryStore::autoEquipShield(TSlots& slots_)
{ {
for (ContainerStoreIterator iter(begin(ContainerStore::Type_Armor)); iter != end(); ++iter) for (ContainerStoreIterator iter(begin(ContainerStore::Type_Armor)); iter != end(); ++iter)
{ {
if (iter->get<ESM::Armor>()->mBase->mData.mType != ESM::Armor::Shield) if (iter->get<ESM::Armor>()->mBase->mData.mType != ESM::Armor::Shield)
continue; continue;
if (iter->getClass().canBeEquipped(*iter, actor).first != 1) if (iter->getClass().canBeEquipped(*iter, mActor).first != 1)
continue; continue;
std::pair<std::vector<int>, bool> shieldSlots = iter->getClass().getEquipmentSlots(*iter); std::pair<std::vector<int>, bool> shieldSlots = iter->getClass().getEquipmentSlots(*iter);
int slot = shieldSlots.first[0]; int slot = shieldSlots.first[0];
@ -515,7 +515,7 @@ void MWWorld::InventoryStore::autoEquipShield(const MWWorld::Ptr& actor, TSlots&
} }
} }
void MWWorld::InventoryStore::autoEquip(const MWWorld::Ptr& actor) void MWWorld::InventoryStore::autoEquip()
{ {
TSlots slots_; TSlots slots_;
initSlots(slots_); initSlots(slots_);
@ -527,8 +527,8 @@ void MWWorld::InventoryStore::autoEquip(const MWWorld::Ptr& actor)
// Equipping lights is handled in Actors::updateEquippedLight based on environment light. // Equipping lights is handled in Actors::updateEquippedLight based on environment light.
// Note: creatures ignore equipment armor rating and only equip shields // Note: creatures ignore equipment armor rating and only equip shields
// Use custom logic for them - select shield based on its health instead of armor rating // Use custom logic for them - select shield based on its health instead of armor rating
autoEquipWeapon(actor, slots_); autoEquipWeapon(slots_);
autoEquipArmor(actor, slots_); autoEquipArmor(slots_);
bool changed = false; bool changed = false;
@ -545,16 +545,16 @@ void MWWorld::InventoryStore::autoEquip(const MWWorld::Ptr& actor)
if (changed) if (changed)
{ {
mSlots.swap(slots_); mSlots.swap(slots_);
fireEquipmentChangedEvent(actor); fireEquipmentChangedEvent();
flagAsModified(); flagAsModified();
} }
} }
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getPreferredShield(const MWWorld::Ptr& actor) MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getPreferredShield()
{ {
TSlots slots; TSlots slots;
initSlots(slots); initSlots(slots);
autoEquipArmor(actor, slots); autoEquipArmor(slots);
return slots[Slot_CarriedLeft]; return slots[Slot_CarriedLeft];
} }
@ -588,9 +588,9 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem(
return mSelectedEnchantItem; return mSelectedEnchantItem;
} }
int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement, bool resolve) int MWWorld::InventoryStore::remove(const Ptr& item, int count, bool equipReplacement, bool resolve)
{ {
int retCount = ContainerStore::remove(item, count, actor, equipReplacement, resolve); int retCount = ContainerStore::remove(item, count, equipReplacement, resolve);
bool wasEquipped = false; bool wasEquipped = false;
if (!item.getRefData().getCount()) if (!item.getRefData().getCount())
@ -602,7 +602,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
if (*mSlots[slot] == item) if (*mSlots[slot] == item)
{ {
unequipSlot(slot, actor); unequipSlot(slot);
wasEquipped = true; wasEquipped = true;
break; break;
} }
@ -612,12 +612,12 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
// If an armor/clothing item is removed, try to find a replacement, // If an armor/clothing item is removed, try to find a replacement,
// but not for the player nor werewolves, and not if the RemoveItem script command // but not for the player nor werewolves, and not if the RemoveItem script command
// was used (equipReplacement is false) // was used (equipReplacement is false)
if (equipReplacement && wasEquipped && (actor != MWMechanics::getPlayer()) && actor.getClass().isNpc() if (equipReplacement && wasEquipped && (mActor != MWMechanics::getPlayer()) && mActor.getClass().isNpc()
&& !actor.getClass().getNpcStats(actor).isWerewolf()) && !mActor.getClass().getNpcStats(mActor).isWerewolf())
{ {
auto type = item.getType(); auto type = item.getType();
if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId) if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId)
autoEquip(actor); autoEquip();
} }
if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() && *mSelectedEnchantItem == item) if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() && *mSelectedEnchantItem == item)
@ -631,8 +631,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
return retCount; return retCount;
} }
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot( MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, bool applyUpdates)
int slot, const MWWorld::Ptr& actor, bool applyUpdates)
{ {
if (slot < 0 || slot >= static_cast<int>(mSlots.size())) if (slot < 0 || slot >= static_cast<int>(mSlots.size()))
throw std::runtime_error("slot number out of range"); throw std::runtime_error("slot number out of range");
@ -650,7 +649,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(
{ {
retval = restack(*it); retval = restack(*it);
if (actor == MWMechanics::getPlayer()) if (mActor == MWMechanics::getPlayer())
{ {
// Unset OnPCEquip Variable on item's script, if it has a script with that variable declared // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared
const ESM::RefId& script = it->getClass().getScript(*it); const ESM::RefId& script = it->getClass().getScript(*it);
@ -666,7 +665,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(
if (applyUpdates) if (applyUpdates)
{ {
fireEquipmentChangedEvent(actor); fireEquipmentChangedEvent();
} }
return retval; return retval;
@ -675,21 +674,19 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(
return it; return it;
} }
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem( MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWorld::Ptr& item)
const MWWorld::Ptr& item, const MWWorld::Ptr& actor)
{ {
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
{ {
MWWorld::ContainerStoreIterator equipped = getSlot(slot); MWWorld::ContainerStoreIterator equipped = getSlot(slot);
if (equipped != end() && *equipped == item) if (equipped != end() && *equipped == item)
return unequipSlot(slot, actor); return unequipSlot(slot);
} }
throw std::runtime_error("attempt to unequip an item that is not currently equipped"); throw std::runtime_error("attempt to unequip an item that is not currently equipped");
} }
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity( MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(const Ptr& item, int count)
const Ptr& item, const Ptr& actor, int count)
{ {
if (!isEquipped(item)) if (!isEquipped(item))
throw std::runtime_error("attempt to unequip an item that is not currently equipped"); throw std::runtime_error("attempt to unequip an item that is not currently equipped");
@ -699,7 +696,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(
throw std::runtime_error("attempt to unequip more items than equipped"); throw std::runtime_error("attempt to unequip more items than equipped");
if (count == item.getRefData().getCount()) if (count == item.getRefData().getCount())
return unequipItem(item, actor); return unequipItem(item);
// Move items to an existing stack if possible, otherwise split count items out into a new stack. // Move items to an existing stack if possible, otherwise split count items out into a new stack.
// Moving counts manually here, since ContainerStore's restack can't target unequipped stacks. // Moving counts manually here, since ContainerStore's restack can't target unequipped stacks.
@ -713,7 +710,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(
} }
} }
return unstack(item, actor, item.getRefData().getCount() - count); return unstack(item, item.getRefData().getCount() - count);
} }
MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() const MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() const
@ -721,12 +718,12 @@ MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() const
return mInventoryListener; return mInventoryListener;
} }
void MWWorld::InventoryStore::setInvListener(InventoryStoreListener* listener, const Ptr& actor) void MWWorld::InventoryStore::setInvListener(InventoryStoreListener* listener)
{ {
mInventoryListener = listener; mInventoryListener = listener;
} }
void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor) void MWWorld::InventoryStore::fireEquipmentChangedEvent()
{ {
if (!mUpdatesEnabled) if (!mUpdatesEnabled)
return; return;
@ -735,7 +732,7 @@ void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor)
// if player, update inventory window // if player, update inventory window
/* /*
if (actor == MWMechanics::getPlayer()) if (mActor == MWMechanics::getPlayer())
{ {
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView();
} }

View File

@ -67,9 +67,9 @@ namespace MWWorld
TSlots mSlots; TSlots mSlots;
void autoEquipWeapon(const MWWorld::Ptr& actor, TSlots& slots_); void autoEquipWeapon(TSlots& slots_);
void autoEquipArmor(const MWWorld::Ptr& actor, TSlots& slots_); void autoEquipArmor(TSlots& slots_);
void autoEquipShield(const MWWorld::Ptr& actor, TSlots& slots_); void autoEquipShield(TSlots& slots_);
// selected magic item (for using enchantments of type "Cast once" or "Cast when used") // selected magic item (for using enchantments of type "Cast once" or "Cast when used")
ContainerStoreIterator mSelectedEnchantItem; ContainerStoreIterator mSelectedEnchantItem;
@ -78,7 +78,7 @@ namespace MWWorld
void initSlots(TSlots& slots_); void initSlots(TSlots& slots_);
void fireEquipmentChangedEvent(const Ptr& actor); void fireEquipmentChangedEvent();
void storeEquipmentState( void storeEquipmentState(
const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const override; const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const override;
@ -94,10 +94,18 @@ namespace MWWorld
InventoryStore& operator=(const InventoryStore& store); InventoryStore& operator=(const InventoryStore& store);
std::unique_ptr<ContainerStore> clone() override { return std::make_unique<InventoryStore>(*this); } const MWWorld::Ptr& getActor() const { return mActor; }
void setActor(const MWWorld::Ptr& actor) { mActor = actor; }
ContainerStoreIterator add(const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true, std::unique_ptr<ContainerStore> clone() override
bool resolve = true) override; {
auto res = std::make_unique<InventoryStore>(*this);
res->clearRefNums();
return res;
}
ContainerStoreIterator add(
const Ptr& itemPtr, int count, bool allowAutoEquip = true, bool resolve = true) override;
///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed)
/// Auto-equip items if specific conditions are fulfilled and allowAutoEquip is true (see the implementation). /// Auto-equip items if specific conditions are fulfilled and allowAutoEquip is true (see the implementation).
/// ///
@ -109,7 +117,7 @@ namespace MWWorld
/// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to
/// the newly inserted item. /// the newly inserted item.
void equip(int slot, const ContainerStoreIterator& iterator, const Ptr& actor); void equip(int slot, const ContainerStoreIterator& iterator);
///< \warning \a iterator can not be an end()-iterator, use unequip function instead ///< \warning \a iterator can not be an end()-iterator, use unequip function instead
bool isEquipped(const MWWorld::ConstPtr& item); bool isEquipped(const MWWorld::ConstPtr& item);
@ -126,30 +134,29 @@ namespace MWWorld
ContainerStoreIterator getSlot(int slot); ContainerStoreIterator getSlot(int slot);
ConstContainerStoreIterator getSlot(int slot) const; ConstContainerStoreIterator getSlot(int slot) const;
ContainerStoreIterator getPreferredShield(const MWWorld::Ptr& actor); ContainerStoreIterator getPreferredShield();
void unequipAll(const MWWorld::Ptr& actor); void unequipAll();
///< Unequip all currently equipped items. ///< Unequip all currently equipped items.
void autoEquip(const MWWorld::Ptr& actor); void autoEquip();
///< Auto equip items according to stats and item value. ///< Auto equip items according to stats and item value.
bool stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const override; bool stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const override;
///< @return true if the two specified objects can stack with each other ///< @return true if the two specified objects can stack with each other
using ContainerStore::remove; using ContainerStore::remove;
int remove( int remove(const Ptr& item, int count, bool equipReplacement = 0, bool resolve = true) override;
const Ptr& item, int count, const Ptr& actor, bool equipReplacement = 0, bool resolve = true) override;
///< Remove \a count item(s) designated by \a item from this inventory. ///< Remove \a count item(s) designated by \a item from this inventory.
/// ///
/// @return the number of items actually removed /// @return the number of items actually removed
ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool applyUpdates = true); ContainerStoreIterator unequipSlot(int slot, bool applyUpdates = true);
///< Unequip \a slot. ///< Unequip \a slot.
/// ///
/// @return an iterator to the item that was previously in the slot /// @return an iterator to the item that was previously in the slot
ContainerStoreIterator unequipItem(const Ptr& item, const Ptr& actor); ContainerStoreIterator unequipItem(const Ptr& item);
///< Unequip an item identified by its Ptr. An exception is thrown ///< Unequip an item identified by its Ptr. An exception is thrown
/// if the item is not currently equipped. /// if the item is not currently equipped.
/// ///
@ -157,7 +164,7 @@ namespace MWWorld
/// (it can be re-stacked so its count may be different than when it /// (it can be re-stacked so its count may be different than when it
/// was equipped). /// was equipped).
ContainerStoreIterator unequipItemQuantity(const Ptr& item, const Ptr& actor, int count); ContainerStoreIterator unequipItemQuantity(const Ptr& item, int count);
///< Unequip a specific quantity of an item identified by its Ptr. ///< Unequip a specific quantity of an item identified by its Ptr.
/// An exception is thrown if the item is not currently equipped, /// An exception is thrown if the item is not currently equipped,
/// if count <= 0, or if count > the item stack size. /// if count <= 0, or if count > the item stack size.
@ -166,7 +173,7 @@ namespace MWWorld
/// in the slot (they can be re-stacked so its count may be different /// in the slot (they can be re-stacked so its count may be different
/// than the requested count). /// than the requested count).
void setInvListener(InventoryStoreListener* listener, const Ptr& actor); void setInvListener(InventoryStoreListener* listener);
///< Set a listener for various events, see \a InventoryStoreListener ///< Set a listener for various events, see \a InventoryStoreListener
InventoryStoreListener* getInvListener() const; InventoryStoreListener* getInvListener() const;

View File

@ -788,8 +788,6 @@ namespace MWWorld
void World::enable(const Ptr& reference) void World::enable(const Ptr& reference)
{ {
MWBase::Environment::get().getWorldModel()->registerPtr(reference);
if (!reference.isInCell()) if (!reference.isInCell())
return; return;
@ -840,7 +838,6 @@ namespace MWWorld
if (reference == getPlayerPtr()) if (reference == getPlayerPtr())
throw std::runtime_error("can not disable player object"); throw std::runtime_error("can not disable player object");
MWBase::Environment::get().getWorldModel()->deregisterPtr(reference);
reference.getRefData().disable(); reference.getRefData().disable();
if (reference.getCellRef().getRefNum().hasContentFile()) if (reference.getCellRef().getRefNum().hasContentFile())
@ -2426,7 +2423,7 @@ namespace MWWorld
mRendering->renderPlayer(player); mRendering->renderPlayer(player);
MWRender::NpcAnimation* anim = static_cast<MWRender::NpcAnimation*>(mRendering->getAnimation(player)); MWRender::NpcAnimation* anim = static_cast<MWRender::NpcAnimation*>(mRendering->getAnimation(player));
player.getClass().getInventoryStore(player).setInvListener(anim, player); player.getClass().getInventoryStore(player).setInvListener(anim);
player.getClass().getInventoryStore(player).setContListener(anim); player.getClass().getInventoryStore(player).setContListener(anim);
scaleObject(player, player.getCellRef().getScale(), true); // apply race height scaleObject(player, player.getCellRef().getScale(), true); // apply race height

View File

@ -80,7 +80,6 @@
-- @usage -- @usage
-- # DataFiles/l10n/MyMod/en.yaml -- # DataFiles/l10n/MyMod/en.yaml
-- good_morning: 'Good morning.' -- good_morning: 'Good morning.'
--
-- you_have_arrows: |- -- you_have_arrows: |-
-- {count, plural, -- {count, plural,
-- one {You have one arrow.} -- one {You have one arrow.}
@ -107,11 +106,12 @@
-- Any object that exists in the game world and has a specific location. -- Any object that exists in the game world and has a specific location.
-- Player, actors, items, and statics are game objects. -- Player, actors, items, and statics are game objects.
-- @type GameObject -- @type GameObject
-- @field #boolean enabled Whether the object is enabled or disabled. Global scripts can set the value. Items in containers or inventories can't be disabled.
-- @field openmw.util#Vector3 position Object position. -- @field openmw.util#Vector3 position Object position.
-- @field openmw.util#Vector3 rotation Object rotation (ZXY order). -- @field openmw.util#Vector3 rotation Object rotation (ZXY order).
-- @field #Cell cell The cell where the object currently is. During loading a game and for objects in an inventory or a container `cell` is nil. -- @field #Cell cell The cell where the object currently is. During loading a game and for objects in an inventory or a container `cell` is nil.
-- @field #table type Type of the object (one of the tables from the package @{openmw.types#types}). -- @field #table type Type of the object (one of the tables from the package @{openmw.types#types}).
-- @field #number count Count (makes sense if stored in a container). -- @field #number count Count (>1 means a stack of objects).
-- @field #string recordId Returns record ID of the object in lowercase. -- @field #string recordId Returns record ID of the object in lowercase.
--- ---
@ -163,13 +163,39 @@
--- ---
-- Moves object to given cell and position. -- Moves object to given cell and position.
-- Can be called only from a global script.
-- The effect is not immediate: the position will be updated only in the next -- The effect is not immediate: the position will be updated only in the next
-- frame. Can be called only from a global script. -- frame. Can be called only from a global script. Enables object if it was disabled.
-- Can be used to move objects from an inventory or a container to the world.
-- @function [parent=#GameObject] teleport -- @function [parent=#GameObject] teleport
-- @param self -- @param self
-- @param #string cellName Name of the cell to teleport into. For exteriors can be empty. -- @param #string cellName Name of the cell to teleport into. For exteriors can be empty.
-- @param openmw.util#Vector3 position New position -- @param openmw.util#Vector3 position New position
-- @param openmw.util#Vector3 rotation New rotation. Optional argument. If missed, then the current rotation is used. -- @param openmw.util#Vector3 rotation New rotation. Optional argument. If missing, then the current rotation is used.
---
-- Moves object into a container or an inventory. Enables if was disabled.
-- Can be called only from a global script.
-- @function [parent=#GameObject] moveInto
-- @param self
-- @param #Inventory dest
-- @usage item:moveInto(types.Actor.inventory(actor))
---
-- Removes an object or reduces a stack of objects.
-- Can be called only from a global script.
-- @function [parent=#GameObject] remove
-- @param self
-- @param #number count (optional) the number of items to remove (if not specified then the whole stack)
---
-- Splits a stack of items. Original stack is reduced by `count`. Returns a new stack with `count` items.
-- Can be called only from a global script.
-- @function [parent=#GameObject] split
-- @param self
-- @param #number count The number of items to return.
-- @usage -- take 50 coins from `money` and put to the container `cont`
-- money:split(50):moveInto(types.Container.content(cont))
--- ---
@ -246,6 +272,22 @@
-- local all = playerInventory:getAll() -- local all = playerInventory:getAll()
-- local weapons = playerInventory:getAll(types.Weapon) -- local weapons = playerInventory:getAll(types.Weapon)
---
-- Get first item with given recordId from the inventory. Returns nil if not found.
-- @function [parent=#Inventory] find
-- @param self
-- @param #string recordId
-- @return #GameObject
-- @usage inventory:find('gold_001')
---
-- Get all items with given recordId from the inventory.
-- @function [parent=#Inventory] findAll
-- @param self
-- @param #string recordId
-- @return #ObjectList
-- @usage for _, item in ipairs(inventory:findAll('common_shirt_01')) do ... end
return nil return nil

View File

@ -59,5 +59,19 @@
-- @function [parent=#world] isWorldPaused -- @function [parent=#world] isWorldPaused
-- @return #boolean -- @return #boolean
---
-- Create a new instance of the given record.
-- After creation the object is in the disabled state. Use :teleport to place to the world or :moveInto to put it into a container or an inventory.
-- @function [parent=#world] createObject
-- @param #string recordId Record ID in lowercase
-- @param #number count (optional, 1 by default) The number of objects in stack
-- @return openmw.core#GameObject
-- @usage -- put 100 gold on the ground at the position of `actor`
-- money = world.createObject('gold_001', 100)
-- money:teleport(actor.cell.name, actor.position)
-- @usage -- put 50 gold into the actor's inventory
-- money = world.createObject('gold_001', 50)
-- money:moveInto(types.Actor.inventory(actor))
return nil return nil