ScottyDoesKnow Posted May 18, 2024 Share Posted May 18, 2024 Getting game crashes when I try to do this, here's the relevant method: bool TESDeathEventHandler::SetAmmo(const Actor* actor, const AmmoStack ammoStack) { const auto inventory = actor->inventoryList; if (!inventory) return false; BSWriteLocker locker(&inventory->inventoryLock); auto items = inventory->items; for (UInt32 i = 0; i < items.count; ++i) { BGSInventoryItem item; if (!items.GetNthItem(i, item)) return false; // If one fails, all the rest will fail if (item.form == ammoStack.ammo) { const SInt32 wepAmmo = ammoStack.count * wepAmmoPercent / 100; const SInt32 invAmmo = item.stack->count * invAmmoPercent / 100; const SInt32 total = wepAmmo + invAmmo; if (total > 0) item.stack->count = total; else items.Remove(i); return true; } } return false; } As far as I can tell, I'm doing everything correctly. The remove seems to work fine and the method returns, as well as the handler method calling it. But then Fallout crashes with "Exception thrown at 0x00007FF7AAA96329 in Fallout4.exe: 0xC0000005: Access violation reading location 0x0000000000000025." The "thrown at" and "reading location" change each time. I also tried just setting the stack count to 0, but that results in a bugged item rather than removing it. Anyone know what's wrong? Or a better way to debug the crashes? I attached the debugger and stepped through, but it fails outside my code and the call stack is all just random addresses in hex. Link to comment Share on other sites More sharing options...
LarannKiar Posted May 18, 2024 Share Posted May 18, 2024 Use TESObjectREFR virtual function Unk_6D (should be ---> virtual ObjectRefHandle RemoveItem(RemoveItemData& a_data), look at the CommonLib) to remove inventory items. I don't remember if I've tried but you shouldn't remove BGSInventoryItem itself from the inventory list array. It won't send inventory updated or changed event to the native code. If having a delay isn't an issue, you can also call Papyrus ObjectReference function to remove it with something like this. // TESObjectREFR* SourceRef = nullptr; // TESObjectREFR* TargetRef = nullptr; // TESForm* itemToRemove = nullptr; // int removeCount = 0; if (SourceRef && TargetRef && itemToRemove && removeCount > 0) { auto SourceRefPapyrusHandle = PapyrusVM::GetHandleFromObject(SourceRef, TESObjectREFR::kTypeID); bool bSilent = true; SendPapyrusEvent4<TESForm*, int, bool, TESObjectREFR*>(SourceRefPapyrusHandle, "ObjectReference", "RemoveItem", itemToRemove, removeCount, bSilent, TargetRef); } Link to comment Share on other sites More sharing options...
ScottyDoesKnow Posted May 18, 2024 Author Share Posted May 18, 2024 1 hour ago, LarannKiar said: Use TESObjectREFR virtual function Unk_6D (should be ---> virtual ObjectRefHandle RemoveItem(RemoveItemData& a_data), look at the CommonLib) to remove inventory items. I don't remember if I've tried but you shouldn't remove BGSInventoryItem itself from the inventory list array. It won't send inventory updated or changed event to the native code. If having a delay isn't an issue, you can also call Papyrus ObjectReference function to remove it with something like this. You're awesome! Was having trouble finding anyone who knew about this stuff. I haven't used the CommonLib so I'm not aware of how to call Unk_6D since it doesn't take parameters. The Papyrus option should be fine, I assume the delay is on coding terms and not real-time terms? Basically this runs when an NPC dies and removes ammo from its inventory, so as long as it's gone by the time the player tries to loot it that should be fine. I can't test it immediately because I'm a bit busy and hitting some compile error where I'll have to include something else, but does this look generally correct? source is a TESObjectREFR* (event->source), the void* should be a different type of cast but that's what GetHandleFromObject wants, and target is null because I just want it gone: const auto handle = PapyrusVM::GetHandleFromObject((void*)source, TESObjectREFR::kTypeID); TESForm* form = item.form; int count = item.stack->count; bool silent = true; TESObjectREFR* target = nullptr; SendPapyrusEvent4<TESForm*, int, bool, TESObjectREFR*>(handle, "ObjectReference", "RemoveItem", form, count, silent, target); Link to comment Share on other sites More sharing options...
LarannKiar Posted May 19, 2024 Share Posted May 19, 2024 I haven't tried to call this exact function and I couldn't find any that sends nullptr as parameter either (maybe there's one somewhere in the vanilla F4SE sources) but it should work. The delay is done by Papyrus. SendPapyrusEvent returns immediatelly and doesn't wait for Papyrus to call the native RemoveItem. Link to comment Share on other sites More sharing options...
ScottyDoesKnow Posted May 19, 2024 Author Share Posted May 19, 2024 16 hours ago, LarannKiar said: I haven't tried to call this exact function and I couldn't find any that sends nullptr as parameter either (maybe there's one somewhere in the vanilla F4SE sources) but it should work. The delay is done by Papyrus. SendPapyrusEvent returns immediatelly and doesn't wait for Papyrus to call the native RemoveItem. Thanks a ton! It works and it's fast enough that if I kill an enemy point blank I never see the ammo in the inventory. Just had to use SInt32 and make my source non-const for the static_cast. const auto handle = PapyrusVM::GetHandleFromObject(static_cast<void*>(source), TESObjectREFR::kTypeID); bool silent = true; TESObjectREFR* target = nullptr; SendPapyrusEvent4<TESForm*, SInt32, bool, TESObjectREFR*>(handle, "ObjectReference", "RemoveItem", item.form, item.stack->count, silent, target); Link to comment Share on other sites More sharing options...
Recommended Posts