From 55a9ab4f52848cb601ff6883dbded627ad46c295 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 27 Aug 2023 09:21:36 +0200 Subject: [PATCH] Add `obj.parentContainer` in Lua. Refactor ContainerStore::mPtr, ContainerStore::mActor. --- apps/openmw/mwclass/container.cpp | 10 +++++-- apps/openmw/mwclass/creature.cpp | 11 ++++--- apps/openmw/mwclass/npc.cpp | 24 +++++++++------- apps/openmw/mwlua/objectbindings.cpp | 7 +++++ apps/openmw/mwworld/cellstore.cpp | 6 +--- apps/openmw/mwworld/class.cpp | 4 --- apps/openmw/mwworld/containerstore.cpp | 40 ++++++++++++++------------ apps/openmw/mwworld/containerstore.hpp | 11 ++++--- apps/openmw/mwworld/inventorystore.cpp | 36 ++++++++++++----------- apps/openmw/mwworld/inventorystore.hpp | 3 -- apps/openmw/mwworld/ptr.hpp | 5 ++-- files/lua_api/openmw/core.lua | 1 + 12 files changed, 85 insertions(+), 73 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 19132f30ee..e947951e33 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -21,6 +21,7 @@ #include "../mwworld/failedaction.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/nullaction.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwgui/tooltips.hpp" #include "../mwgui/ustring.hpp" @@ -68,10 +69,12 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); MWWorld::LiveCellRef* ref = ptr.get(); // store ptr.getRefData().setCustomData(std::make_unique(*ref->mBase, ptr.getCell())); + getContainerStore(ptr).setPtr(ptr); MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell()); } @@ -223,9 +226,7 @@ namespace MWClass MWWorld::ContainerStore& Container::getContainerStore(const MWWorld::Ptr& ptr) const { ensureCustomData(ptr); - auto& data = ptr.getRefData().getCustomData()->asContainerCustomData(); - data.mStore.mPtr = ptr; - return data.mStore; + return ptr.getRefData().getCustomData()->asContainerCustomData().mStore; } ESM::RefId Container::getScript(const MWWorld::ConstPtr& ptr) const @@ -312,6 +313,9 @@ namespace MWClass const ESM::ContainerState& containerState = state.asContainerState(); ptr.getRefData().setCustomData(std::make_unique(containerState.mInventory)); + + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + getContainerStore(ptr).setPtr(ptr); } void Container::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 672b198704..2ee412a190 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -39,6 +39,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/localscripts.hpp" #include "../mwworld/ptr.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -117,6 +118,7 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); auto tempData = std::make_unique(); CreatureCustomData* data = tempData.get(); MWMechanics::CreatureCustomDataResetter resetter{ ptr }; @@ -161,6 +163,7 @@ namespace MWClass data->mContainerStore = std::make_unique(); else data->mContainerStore = std::make_unique(); + data->mContainerStore->setPtr(ptr); data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); @@ -505,10 +508,7 @@ namespace MWClass MWWorld::ContainerStore& Creature::getContainerStore(const MWWorld::Ptr& ptr) const { ensureCustomData(ptr); - auto& store = *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore; - if (hasInventoryStore(ptr)) - static_cast(store).setActor(ptr); - return store; + return *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore; } MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr& ptr) const @@ -807,6 +807,9 @@ namespace MWClass else data->mContainerStore = std::make_unique(); + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + data->mContainerStore->setPtr(ptr); + ptr.getRefData().setCustomData(std::move(data)); } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b9e03aa45c..6b79f3ab7e 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -297,6 +297,7 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); bool recalculate = false; auto tempData = std::make_unique(); NpcCustomData* data = tempData.get(); @@ -397,9 +398,10 @@ namespace MWClass // inventory // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - getInventoryStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); - - getInventoryStore(ptr).autoEquip(); + MWWorld::InventoryStore& inventory = getInventoryStore(ptr); + inventory.setPtr(ptr); + inventory.fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); + inventory.autoEquip(); } } @@ -956,18 +958,13 @@ namespace MWClass MWWorld::ContainerStore& Npc::getContainerStore(const MWWorld::Ptr& ptr) const { - ensureCustomData(ptr); - auto& store = ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; - store.setActor(ptr); - return store; + return getInventoryStore(ptr); } MWWorld::InventoryStore& Npc::getInventoryStore(const MWWorld::Ptr& ptr) const { ensureCustomData(ptr); - auto& store = ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; - store.setActor(ptr); - return store; + return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; } ESM::RefId Npc::getScript(const MWWorld::ConstPtr& ptr) const @@ -1362,8 +1359,13 @@ namespace MWClass if (npcState.mCreatureStats.mMissingACDT) ensureCustomData(ptr); else + { // Create a CustomData, but don't fill it from ESM records (not needed) - ptr.getRefData().setCustomData(std::make_unique()); + auto data = std::make_unique(); + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + data->mInventoryStore.setPtr(ptr); + ptr.getRefData().setCustomData(std::move(data)); + } } } else diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 32bd0b95d4..a7b2252a31 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -190,6 +190,13 @@ namespace MWLua else return sol::nullopt; }); + objectT["parentContainer"] = sol::readonly_property([](const ObjectT& o) -> sol::optional { + const MWWorld::Ptr& ptr = o.ptr(); + if (ptr.getContainerStore()) + return ObjectT(ptr.getContainerStore()->getPtr()); + else + return sol::nullopt; + }); objectT["position"] = sol::readonly_property( [](const ObjectT& o) -> osg::Vec3f { return o.ptr().getRefData().getPosition().asVec3(); }); objectT["scale"] diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index d5868f5ee3..193071259a 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -495,11 +495,7 @@ namespace MWWorld mMovedToAnotherCell.insert(std::make_pair(object.getBase(), cellToMoveTo)); requestMergedRefsUpdate(); - MWWorld::Ptr ptr(object.getBase(), cellToMoveTo); - const Class& cls = ptr.getClass(); - if (cls.hasInventoryStore(ptr)) - cls.getInventoryStore(ptr).setActor(ptr); - return ptr; + return MWWorld::Ptr(object.getBase(), cellToMoveTo); } struct MergeVisitor diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index b360e90471..de3c2b011d 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -376,8 +376,6 @@ namespace MWWorld newPtr.getRefData().setCount(count); newPtr.getRefData().setLuaScripts(nullptr); MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); - if (hasInventoryStore(newPtr)) - getInventoryStore(newPtr).setActor(newPtr); return newPtr; } @@ -386,8 +384,6 @@ namespace MWWorld Ptr newPtr = copyToCellImpl(ptr, cell); ptr.getRefData().setLuaScripts(nullptr); MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); - if (hasInventoryStore(newPtr)) - getInventoryStore(newPtr).setActor(newPtr); return newPtr; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index f5e45415ae..d76dc36c15 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -350,7 +350,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add( const ESM::RefId& script = item.getClass().getScript(item); if (!script.empty()) { - if (mActor == player) + const Ptr& contPtr = getPtr(); + if (contPtr == player) { // Items in player's inventory have cell set to 0, so their scripts will never be removed item.mCell = nullptr; @@ -359,10 +360,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add( { // 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 - if (!mPtr.isEmpty()) - item.mCell = mPtr.getCell(); - else if (!mActor.isEmpty()) - item.mCell = mActor.getCell(); + if (!contPtr.isEmpty()) + item.mCell = contPtr.getCell(); } item.mContainerStore = this; @@ -371,12 +370,12 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add( // Set OnPCAdd special variable, if it is declared // Make sure to do this *after* we have added the script to LocalScripts - if (mActor == player) + if (contPtr == player) item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1); } // we should not fire event for InventoryStore yet - it has some custom logic - if (mListener && !(!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor))) + if (mListener && typeid(*this) == typeid(ContainerStore)) mListener->itemAdded(item, count); return it; @@ -415,8 +414,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp(const Ptr& ptr, for (MWWorld::ContainerStoreIterator iter(begin(type)); iter != end(); ++iter) { // Don't stack with equipped items - if (!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor)) - if (mActor.getClass().getInventoryStore(mActor).isEquipped(*iter)) + if (auto* inventoryStore = dynamic_cast(this)) + if (inventoryStore->isEquipped(*iter)) continue; if (stacks(*iter, ptr)) @@ -586,7 +585,7 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, bool equipReplac flagAsModified(); // we should not fire event for InventoryStore yet - it has some custom logic - if (mListener && !(!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor))) + if (mListener && typeid(*this) == typeid(ContainerStore)) mListener->itemRemoved(item, count - toRemove); // number of removed items @@ -694,13 +693,14 @@ bool MWWorld::ContainerStore::isResolved() const void MWWorld::ContainerStore::resolve() { - if (!mResolved && !mPtr.isEmpty()) + const Ptr& container = getPtr(); + if (!mResolved && !container.isEmpty() && container.getType() == ESM::REC_CONT) { for (const auto&& ptr : *this) ptr.getRefData().setCount(0); Misc::Rng::Generator prng{ mSeed }; - fill(mPtr.get()->mBase->mInventory, ESM::RefId(), prng); - addScripts(*this, mPtr.mCell); + fill(container.get()->mBase->mInventory, ESM::RefId(), prng); + addScripts(*this, container.mCell); } mModified = true; } @@ -715,13 +715,14 @@ MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily() listener = std::make_shared(*this); mResolutionListener = listener; } - if (!mResolved && !mPtr.isEmpty()) + const Ptr& container = getPtr(); + if (!mResolved && !container.isEmpty() && container.getType() == ESM::REC_CONT) { for (const auto&& ptr : *this) ptr.getRefData().setCount(0); Misc::Rng::Generator prng{ mSeed }; - fill(mPtr.get()->mBase->mInventory, ESM::RefId(), prng); - addScripts(*this, mPtr.mCell); + fill(container.get()->mBase->mInventory, ESM::RefId(), prng); + addScripts(*this, container.mCell); } return { listener }; } @@ -731,12 +732,13 @@ void MWWorld::ContainerStore::unresolve() if (mModified) return; - if (mResolved && !mPtr.isEmpty()) + const Ptr& container = getPtr(); + if (mResolved && !container.isEmpty() && container.getType() == ESM::REC_CONT) { for (const auto&& ptr : *this) ptr.getRefData().setCount(0); - fillNonRandom(mPtr.get()->mBase->mInventory, ESM::RefId(), mSeed); - addScripts(*this, mPtr.mCell); + fillNonRandom(container.get()->mBase->mInventory, ESM::RefId(), mSeed); + addScripts(*this, container.mCell); mResolved = false; } } diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index bced5820fa..a8392d38b8 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -125,11 +125,6 @@ namespace MWWorld bool mRechargingItemsUpToDate; - // Non-empty only if is InventoryStore. - // The actor whose inventory it is. - // TODO: Consider merging mActor and mPtr. - MWWorld::Ptr mActor; - private: MWWorld::CellRefList potions; MWWorld::CellRefList appas; @@ -150,7 +145,7 @@ namespace MWWorld bool mModified; bool mResolved; unsigned int mSeed; - MWWorld::Ptr mPtr; // Container that contains this store. Set in MWClass::Container::getContainerStore + MWWorld::SafePtr mPtr; // Container or actor that holds this store. std::weak_ptr mResolutionListener; ContainerStoreIterator addImp(const Ptr& ptr, int count, bool markModified = true); @@ -189,6 +184,10 @@ namespace MWWorld return res; } + // Container or actor that holds this store. + const Ptr& getPtr() const { return mPtr.ptrOrEmpty(); } + void setPtr(const Ptr& ptr) { mPtr = SafePtr(ptr); } + ConstContainerStoreIterator cbegin(int mask = Type_All) const; ConstContainerStoreIterator cend() const; ConstContainerStoreIterator begin(int mask = Type_All) const; diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 99949a73d4..9b3470a835 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -133,8 +133,9 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add( = MWWorld::ContainerStore::add(itemPtr, count, allowAutoEquip, resolve); // Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves - if (allowAutoEquip && mActor != MWMechanics::getPlayer() && mActor.getClass().isNpc() - && !mActor.getClass().getNpcStats(mActor).isWerewolf()) + const Ptr& actor = getPtr(); + if (allowAutoEquip && actor != MWMechanics::getPlayer() && actor.getClass().isNpc() + && !actor.getClass().getNpcStats(actor).isWerewolf()) { auto type = itemPtr.getType(); if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId) @@ -220,12 +221,13 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::findSlot(int slot) cons void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_) { - if (!mActor.getClass().isNpc()) + const Ptr& actor = getPtr(); + if (!actor.getClass().isNpc()) { // 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. // So just disable weapon autoequipping for creatures which sells weapon. - int services = mActor.getClass().getServices(mActor); + int services = actor.getClass().getServices(actor); bool sellsWeapon = services & (ESM::NPC::Weapon | ESM::NPC::MagicItems); if (sellsWeapon) return; @@ -280,7 +282,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_) for (int j = 0; j < static_cast(weaponSkillsLength); ++j) { - float skillValue = mActor.getClass().getSkill(mActor, weaponSkills[j]); + float skillValue = actor.getClass().getSkill(actor, weaponSkills[j]); if (skillValue > max && !weaponSkillVisited[j]) { max = skillValue; @@ -323,7 +325,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_) } } - if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, mActor).first) + if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, actor).first) { // Do not equip ranged weapons, if there is no suitable ammo bool hasAmmo = true; @@ -378,7 +380,8 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) { // Only NPCs can wear armor for now. // For creatures we equip only shields. - if (!mActor.getClass().isNpc()) + const Ptr& actor = getPtr(); + if (!actor.getClass().isNpc()) { autoEquipShield(slots_); return; @@ -389,7 +392,7 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); - float unarmoredSkill = mActor.getClass().getSkill(mActor, ESM::Skill::Unarmored); + float unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored); float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill); for (ContainerStoreIterator iter(begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter != end(); @@ -397,7 +400,7 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) { Ptr test = *iter; - switch (test.getClass().canBeEquipped(test, mActor).first) + switch (test.getClass().canBeEquipped(test, actor).first) { case 0: continue; @@ -406,7 +409,7 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) } if (iter.getType() == ContainerStore::Type_Armor - && test.getClass().getEffectiveArmorRating(test, mActor) <= std::max(unarmoredRating, 0.f)) + && test.getClass().getEffectiveArmorRating(test, actor) <= std::max(unarmoredRating, 0.f)) { continue; } @@ -431,8 +434,8 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) if (old.get()->mBase->mData.mType == test.get()->mBase->mData.mType) { - if (old.getClass().getEffectiveArmorRating(old, mActor) - >= test.getClass().getEffectiveArmorRating(test, mActor)) + if (old.getClass().getEffectiveArmorRating(old, actor) + >= test.getClass().getEffectiveArmorRating(test, actor)) // old armor had better armor rating continue; } @@ -494,7 +497,7 @@ void MWWorld::InventoryStore::autoEquipShield(TSlots& slots_) { if (iter->get()->mBase->mData.mType != ESM::Armor::Shield) continue; - if (iter->getClass().canBeEquipped(*iter, mActor).first != 1) + if (iter->getClass().canBeEquipped(*iter, getPtr()).first != 1) continue; std::pair, bool> shieldSlots = iter->getClass().getEquipmentSlots(*iter); int slot = shieldSlots.first[0]; @@ -606,8 +609,9 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, bool equipReplac // 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 // was used (equipReplacement is false) - if (equipReplacement && wasEquipped && (mActor != MWMechanics::getPlayer()) && mActor.getClass().isNpc() - && !mActor.getClass().getNpcStats(mActor).isWerewolf()) + const Ptr& actor = getPtr(); + if (equipReplacement && wasEquipped && (actor != MWMechanics::getPlayer()) && actor.getClass().isNpc() + && !actor.getClass().getNpcStats(actor).isWerewolf()) { auto type = item.getType(); if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId) @@ -643,7 +647,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, b { retval = restack(*it); - if (mActor == MWMechanics::getPlayer()) + if (getPtr() == MWMechanics::getPlayer()) { // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared const ESM::RefId& script = it->getClass().getScript(*it); diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index c0723d319c..6df5fa1e5a 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -94,9 +94,6 @@ namespace MWWorld InventoryStore& operator=(const InventoryStore& store); - const MWWorld::Ptr& getActor() const { return mActor; } - void setActor(const MWWorld::Ptr& actor) { mActor = actor; } - std::unique_ptr clone() override { auto res = std::make_unique(*this); diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 1adaa3cbf9..85fef8d9fb 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -153,8 +153,9 @@ namespace MWWorld class SafePtr { public: - using Id = ESM::RefNum; + using Id = ESM::FormId; + SafePtr() = default; explicit SafePtr(Id id) : mId(id) { @@ -172,7 +173,7 @@ namespace MWWorld } private: - const Id mId; + Id mId; mutable Ptr mPtr; mutable size_t mLastUpdate = 0; diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 7a43cd2c3f..f018e4dd21 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -159,6 +159,7 @@ -- @field #string ownerFactionId Faction who owns the object (nil if missing). Global and self scripts can set the value. -- @field #number ownerFactionRank Rank required to be allowed to pick up the object. Global and self scripts can set the value. -- @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 #GameObject parentContainer Container or actor that contains (or has in inventory) this object. It is nil if the object is in a cell. -- @field #any type Type of the object (one of the tables from the package @{openmw.types#types}). -- @field #number count Count (>1 means a stack of objects). -- @field #string recordId Returns record ID of the object in lowercase.