#include "containerextensions.hpp" #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/levelledlist.hpp" #include "interpretercontext.hpp" #include "ref.hpp" namespace { void addToStore(const MWWorld::Ptr& itemPtr, int count, MWWorld::ContainerStore& store, bool resolve = true) { if (itemPtr.getClass().getScript(itemPtr).empty()) { store.add(itemPtr, count, true, resolve); } else { // Adding just one item per time to make sure there isn't a stack of scripted items for (int i = 0; i < count; i++) store.add(itemPtr, 1, true, resolve); } } void addRandomToStore(const MWWorld::Ptr& itemPtr, int count, MWWorld::ContainerStore& store, bool topLevel = true) { if (itemPtr.getType() == ESM::ItemLevList::sRecordId) { const ESM::ItemLevList* levItemList = itemPtr.get()->mBase; if (topLevel && count > 1 && levItemList->mFlags & ESM::ItemLevList::Each) { for (int i = 0; i < count; i++) addRandomToStore(itemPtr, 1, store, true); } else { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); const ESM::RefId& itemId = MWMechanics::getLevelledItem(itemPtr.get()->mBase, false, prng); if (itemId.empty()) return; MWWorld::ManualRef manualRef(*MWBase::Environment::get().getESMStore(), itemId, 1); addRandomToStore(manualRef.getPtr(), count, store, false); } } else addToStore(itemPtr, count, store); } } namespace MWScript { namespace Container { template class OpAddItem : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId item = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); if (!MWBase::Environment::get().getESMStore()->find(item)) { runtime.getContext().report("Failed to add item '" + item.getRefIdString() + "': unknown ID"); return; } if (count < 0) count = static_cast(count); // no-op if (count == 0) return; if (item == "gold_005" || item == "gold_010" || item == "gold_025" || item == "gold_100") item = MWWorld::ContainerStore::sGoldId; // Check if "item" can be placed in a container MWWorld::ManualRef manualRef(*MWBase::Environment::get().getESMStore(), item, 1); MWWorld::Ptr itemPtr = manualRef.getPtr(); bool isLevelledList = itemPtr.getClass().getType() == ESM::ItemLevList::sRecordId; if (!isLevelledList) MWWorld::ContainerStore::getType(itemPtr); // Explicit calls to non-unique actors affect the base record if (!R::implicit && ptr.getClass().isActor() && MWBase::Environment::get().getESMStore()->getRefCount(ptr.getCellRef().getRefId()) > 1) { ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, count); return; } // Calls to unresolved containers affect the base record if (ptr.getClass().getType() == ESM::Container::sRecordId && (!ptr.getRefData().getCustomData() || !ptr.getClass().getContainerStore(ptr).isResolved())) { ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, count); const ESM::Container* baseRecord = MWBase::Environment::get().getESMStore()->get().find( ptr.getCellRef().getRefId()); const auto& ptrs = MWBase::Environment::get().getWorld()->getAll(ptr.getCellRef().getRefId()); for (const auto& container : ptrs) { // use the new base record container.get()->mBase = baseRecord; if (container.getRefData().getCustomData()) { auto& store = container.getClass().getContainerStore(container); if (isLevelledList) { if (store.isResolved()) { addRandomToStore(itemPtr, count, store); } } else addToStore(itemPtr, count, store, store.isResolved()); } } return; } MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); if (isLevelledList) addRandomToStore(itemPtr, count, store); else addToStore(itemPtr, count, store); // 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()) { // The two GMST entries below expand to strings informing the player of what, and how many of it has // been added to their inventory std::string msgBox; std::string_view itemName = itemPtr.getClass().getName(itemPtr); if (count == 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}"); msgBox = ::Misc::StringUtils::format(msgBox, itemName); } else { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage61}"); msgBox = ::Misc::StringUtils::format(msgBox, count, itemName); } MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); } } }; template class OpGetItemCount : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId item = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); if (item == "gold_005" || item == "gold_010" || item == "gold_025" || item == "gold_100") item = MWWorld::ContainerStore::sGoldId; MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); runtime.push(store.count(item)); } }; template class OpRemoveItem : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId item = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); if (!MWBase::Environment::get().getESMStore()->find(item)) { runtime.getContext().report("Failed to remove item '" + item.getRefIdString() + "': unknown ID"); return; } if (count < 0) count = static_cast(count); // no-op if (count == 0) return; if (item == "gold_005" || item == "gold_010" || item == "gold_025" || item == "gold_100") item = MWWorld::ContainerStore::sGoldId; // Explicit calls to non-unique actors affect the base record if (!R::implicit && ptr.getClass().isActor() && MWBase::Environment::get().getESMStore()->getRefCount(ptr.getCellRef().getRefId()) > 1) { ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count); return; } // Calls to unresolved containers affect the base record instead else if (ptr.getClass().getType() == ESM::Container::sRecordId && (!ptr.getRefData().getCustomData() || !ptr.getClass().getContainerStore(ptr).isResolved())) { ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count); const ESM::Container* baseRecord = MWBase::Environment::get().getESMStore()->get().find( ptr.getCellRef().getRefId()); const auto& ptrs = MWBase::Environment::get().getWorld()->getAll(ptr.getCellRef().getRefId()); for (const auto& container : ptrs) { container.get()->mBase = baseRecord; if (container.getRefData().getCustomData()) { auto& store = container.getClass().getContainerStore(container); // Note that unlike AddItem, RemoveItem only removes from unresolved containers if (!store.isResolved()) store.remove(item, count, false, false); } } return; } MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); std::string_view itemName; for (MWWorld::ConstContainerStoreIterator iter(store.cbegin()); iter != store.cend(); ++iter) { if (iter->getCellRef().getRefId() == item) { itemName = iter->getClass().getName(*iter); break; } } int numRemoved = store.remove(item, count); // Spawn a messagebox (only for items removed from player's inventory) if ((numRemoved > 0) && (ptr == MWMechanics::getPlayer())) { // The two GMST entries below expand to strings informing the player of what, and how many of it has // been removed from their inventory std::string msgBox; if (numRemoved > 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}"); msgBox = ::Misc::StringUtils::format(msgBox, numRemoved, itemName); } else { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage62}"); msgBox = ::Misc::StringUtils::format(msgBox, itemName); } MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); } } }; template class OpEquip : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId item = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); auto found = invStore.end(); const auto& store = *MWBase::Environment::get().getESMStore(); // With soul gems we prefer filled ones. for (auto it = invStore.begin(); it != invStore.end(); ++it) { if (it->getCellRef().getRefId() == item) { found = it; const ESM::RefId& soul = it->getCellRef().getSoul(); if (!it->getClass().isSoulGem(*it) || (!soul.empty() && store.get().search(soul))) break; } } if (found == invStore.end()) { MWWorld::ManualRef ref(store, item, 1); found = ptr.getClass().getContainerStore(ptr).add(ref.getPtr(), 1, false); Log(Debug::Warning) << "Implicitly adding one " << item << " to the inventory store of " << ptr.getCellRef().getRefId() << " to fulfill the requirements of Equip instruction"; } if (ptr == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->useItem(*found, true); else { std::unique_ptr action = found->getClass().use(*found, true); action->execute(ptr, true); } } }; template class OpGetArmorType : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer location = runtime[0].mInteger; runtime.pop(); int slot; switch (location) { case 0: slot = MWWorld::InventoryStore::Slot_Helmet; break; case 1: slot = MWWorld::InventoryStore::Slot_Cuirass; break; case 2: slot = MWWorld::InventoryStore::Slot_LeftPauldron; break; case 3: slot = MWWorld::InventoryStore::Slot_RightPauldron; break; case 4: slot = MWWorld::InventoryStore::Slot_Greaves; break; case 5: slot = MWWorld::InventoryStore::Slot_Boots; break; case 6: slot = MWWorld::InventoryStore::Slot_LeftGauntlet; break; case 7: slot = MWWorld::InventoryStore::Slot_RightGauntlet; break; case 8: slot = MWWorld::InventoryStore::Slot_CarriedLeft; // shield break; case 9: slot = MWWorld::InventoryStore::Slot_LeftGauntlet; break; case 10: slot = MWWorld::InventoryStore::Slot_RightGauntlet; break; default: throw std::runtime_error("armor index out of range"); } const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); MWWorld::ConstContainerStoreIterator it = invStore.getSlot(slot); if (it == invStore.end() || it->getType() != ESM::Armor::sRecordId) { runtime.push(-1); return; } ESM::RefId skill = it->getClass().getEquipmentSkill(*it); if (skill == ESM::Skill::HeavyArmor) runtime.push(2); else if (skill == ESM::Skill::MediumArmor) runtime.push(1); else if (skill == ESM::Skill::LightArmor) runtime.push(0); else runtime.push(-1); } }; template class OpHasItemEquipped : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId item = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { MWWorld::ConstContainerStoreIterator it = invStore.getSlot(slot); if (it != invStore.end() && it->getCellRef().getRefId() == item) { runtime.push(1); return; } } runtime.push(0); } }; template class OpHasSoulGem : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); int count = 0; const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(MWWorld::ContainerStore::Type_Miscellaneous); it != invStore.cend(); ++it) { if (it->getCellRef().getSoul() == name) count += it->getCellRef().getCount(); } runtime.push(count); } }; template class OpGetWeaponType : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); MWWorld::ConstContainerStoreIterator it = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (it == invStore.end()) { runtime.push(-1); return; } else if (it->getType() != ESM::Weapon::sRecordId) { if (it->getType() == ESM::Lockpick::sRecordId) { runtime.push(-2); } else if (it->getType() == ESM::Probe::sRecordId) { runtime.push(-3); } else { runtime.push(-1); } return; } runtime.push(it->get()->mBase->mData.mType); } }; void installOpcodes(Interpreter::Interpreter& interpreter) { interpreter.installSegment5>(Compiler::Container::opcodeAddItem); interpreter.installSegment5>(Compiler::Container::opcodeAddItemExplicit); interpreter.installSegment5>(Compiler::Container::opcodeGetItemCount); interpreter.installSegment5>(Compiler::Container::opcodeGetItemCountExplicit); interpreter.installSegment5>(Compiler::Container::opcodeRemoveItem); interpreter.installSegment5>(Compiler::Container::opcodeRemoveItemExplicit); interpreter.installSegment5>(Compiler::Container::opcodeEquip); interpreter.installSegment5>(Compiler::Container::opcodeEquipExplicit); interpreter.installSegment5>(Compiler::Container::opcodeGetArmorType); interpreter.installSegment5>(Compiler::Container::opcodeGetArmorTypeExplicit); interpreter.installSegment5>(Compiler::Container::opcodeHasItemEquipped); interpreter.installSegment5>( Compiler::Container::opcodeHasItemEquippedExplicit); interpreter.installSegment5>(Compiler::Container::opcodeHasSoulGem); interpreter.installSegment5>(Compiler::Container::opcodeHasSoulGemExplicit); interpreter.installSegment5>(Compiler::Container::opcodeGetWeaponType); interpreter.installSegment5>(Compiler::Container::opcodeGetWeaponTypeExplicit); } } }