ScottyDoesKnow Posted October 4, 2022 Author Share Posted October 4, 2022 (edited) Why not to use FormId of Fusion Core? It is pretty known If you're still around, I've found a new issue. My code is triggering on weird ammo like what bloatflies fire. And apparently it finds an ammo stack to put it in too. Now this doesn't seem to matter for things like bloatflies, but I'd still rather not run the code. Currently I added checks for weird ammo types like bloatfly larva, mirelurk king sonic wave, etc... which works fine but is obviously not ideal. But that's not the real issue. The real issue is I tried killing a sentry bot and it took the 5.56 ammo out of its gun. So I need some way to check if a gun is lootable. I've found a keyword (ObjectTypeWeapon) which seems to be on all the normal guns but not the weird ones, but I can't figure out how to check for keywords in F4SE. Do you know if it's possible? Currently I'm checking for sentry bot guns and turret guns, which again isn't ideal. At this point it would be easier just to whitelist all the standard guns, but that would mean it doesn't work with mods. So being able to check for keywords seems like the best solution if it's possible, but I'm open to any other ideas as well. Edit: there is a "Can't Drop" flag. I'm checking out TESForm->flags now. Edit2: That did it! Edited October 4, 2022 by ScottyDoesKnow Link to comment Share on other sites More sharing options...
DlinnyLag Posted October 4, 2022 Share Posted October 4, 2022 but I can't figure out how to check for keywords in F4SE. Do you know if it's possible? you can just cast your object (TESForm) to BGSKeywordForm and iterate keywords.Something like that: const BGSKeywordForm* pKeywords = DYNAMIC_CAST(equipData->item, TESForm, BGSKeywordForm); Link to comment Share on other sites More sharing options...
niston Posted October 4, 2022 Share Posted October 4, 2022 Does this cast work on objectreferences? Will one be able to iterate over keywords that have been added to the refr and not the form? Link to comment Share on other sites More sharing options...
DlinnyLag Posted October 5, 2022 Share Posted October 5, 2022 (edited) I don't think that cast will work. TESObjectREFR doesn't inherit BGSKeywordForm. Weapon doesActually, I don't know how ObjectReference.Addkeyword() Papyrus function works, so I can't say what object is used to store adding keywords. Maybe it is a class derived from TBO_InstanceData that is indirectly referenced by TESObjectREFR ( see data-> objectReference->CreateInstance() ) Edited October 5, 2022 by DlinnyLag Link to comment Share on other sites More sharing options...
ScottyDoesKnow Posted October 5, 2022 Author Share Posted October 5, 2022 (edited) you can just cast your object (TESForm) to BGSKeywordForm and iterate keywords.Something like that: const BGSKeywordForm* pKeywords = DYNAMIC_CAST(equipData->item, TESForm, BGSKeywordForm); Thanks for the info. I'm glad I didn't figure it out because the flag check is a much cleaner solution in the end, I later found some weird weapons with that keyword. Now the code checks for the "Can't Drop" flag rather than looking at specific weapons or ammo. It also checks for fusion cores by finding the charge value rather than by editor ID to make it more compatible with mods. Here's (hopefully the final) code if you feel like checking it out. I think I managed to do the upgraded checks pretty cleanly. // https://github.com/powerof3/SKSEPlugins/blob/master/po3_FEC/main.cpp class TESDeathEventHandler : public BSTEventSink<TESDeathEvent> { public: static TESDeathEventHandler* GetSingleton() { static TESDeathEventHandler singleton; return &singleton; } virtual EventResult ReceiveEvent(TESDeathEvent* evn, void* dispatcher) override { if (!evn || !evn->source) { return kEvent_Continue; } Actor* actor = DYNAMIC_CAST(evn->source, TESObjectREFR, Actor); if (!actor) { return kEvent_Continue; } AmmoData ammoData; if (!LoadUnloadAmmo(actor, ammoData, false)) { return kEvent_Continue; } if (!AddAmmo(actor, ammoData)) { LoadUnloadAmmo(actor, ammoData, true); } return kEvent_Continue; } private: static const UInt64 LOWER_32_MASK = 0x00000000FFFFFFFF; static const UInt64 UPPER_32_MASK = 0xFFFFFFFF00000000; static const size_t CANT_DROP_FLAG_POS = 2; static const int AMMO_CHARGE_INDEX = 1; // https://stackoverflow.com/a/1008289 // Using C++ 98, so no delete TESDeathEventHandler() {} TESDeathEventHandler(TESDeathEventHandler const&); void operator=(TESDeathEventHandler const&); struct AmmoData { TESAmmo * ammo; UInt64 ammoCount; }; bool LoadUnloadAmmo(Actor* actor, AmmoData& ammoData, bool load) { auto middleProcess = actor->middleProcess; if (!middleProcess) { return false; } auto unk08 = middleProcess->unk08; if (!unk08) { return false; } SimpleLocker locker(&unk08->lock); auto equipDataArray = unk08->equipData; for (UInt32 i = 0; i < equipDataArray.count; ++i) { Actor::MiddleProcess::Data08::EquipData equipData; if (!equipDataArray.GetNthItem(i, equipData)) { return false; // If one fails, all the rest will fail } auto item = equipData.item; if (!item) { continue; } if (std::bitset<sizeof(UInt32*CHAR_BIT)>(item->flags).test(CANT_DROP_FLAG_POS)) { continue; // Ignore weapons marked as Can't Drop } auto equippedData = equipData.equippedData; if (!equippedData) { continue; } auto ammo = equippedData->ammo; if (!ammo) { continue; } auto unk160 = ammo->unk160; if (unk160 && sizeof(unk160) > AMMO_CHARGE_INDEX) { auto ammoCharge = unk160[AMMO_CHARGE_INDEX] & LOWER_32_MASK; if (ammoCharge != 0) { continue; // Ignore ammo with charge } } auto ammoCount = equippedData->unk18 & LOWER_32_MASK; // Ammo count is lower 32 bits if (load) { if (ammoCount != 0) { continue; // Don't load ammo if ammo isn't empty } if (ammo != ammoData.ammo) { continue; } // Load ammo equippedData->unk18 += ammoData.ammoCount; } else { if (ammoCount == 0) { continue; // Sometimes this method gets called twice, this will skip it on subsequent calls } // Unload ammo equippedData->unk18 &= UPPER_32_MASK; // Clear lower 32 bits // Store data ammoData.ammo = ammo; ammoData.ammoCount = ammoCount; } // Stop searching return true; } return false; } bool AddAmmo(Actor* actor, AmmoData ammoData) { auto inventoryList = actor->inventoryList; if (!inventoryList) { return false; } BSWriteLocker locker(&inventoryList->inventoryLock); auto inventoryItems = inventoryList->items; for (UInt32 j = 0; j < inventoryItems.count; ++j) { BGSInventoryItem item; if (!inventoryItems.GetNthItem(j, item)) { return false; // If one fails, all the rest will fail } if (item.form == ammoData.ammo) { // Add to count item.stack->count += ammoData.ammoCount; // Stop searching return true; } } return false; } }; Edited October 6, 2022 by ScottyDoesKnow Link to comment Share on other sites More sharing options...
ScottyDoesKnow Posted May 24, 2024 Author Share Posted May 24, 2024 On 9/17/2022 at 5:04 AM, DlinnyLag said: You have to include appropriate F4SE .cpp files to you project. Including .h file is not enough to compile binary file if compiler/linker doesn't have access to body of functions that just declared (but not implemented) in a header file. SimpleLock implementation is in the GameTypes.cpp Heap_Allocate implementation is in the GameAPI.cpp It's been quite a while, but I think I found the more correct way than including .cpp files (which maybe you were getting at, I see you mentioned the linker). You just need to add the f4se_<version>.lib in the project's properties under Linker -> Input -> Additional Dependencies. Just let me know if you already knew this and there's some reason not to do it. Link to comment Share on other sites More sharing options...
DlinnyLag Posted May 25, 2024 Share Posted May 25, 2024 (edited) On 5/24/2024 at 5:15 PM, ScottyDoesKnow said: It's been quite a while, but I think I found the more correct way than including .cpp files (which maybe you were getting at, I see you mentioned the linker). You just need to add the f4se_<version>.lib in the project's properties under Linker -> Input -> Additional Dependencies. Just let me know if you already knew this and there's some reason not to do it. I do not see any way how it might work. f4se_1_10_163.lib has only single function for linker - StartF4SE. This is the only function that you can call from f4se_1_10_163.dll I suppose your code do not call it %) Actually, I do not see any code that calls this function. It looks like a rudiment. Perhaps, you have changed process of f4se project building and it produces something different now. Perhaps, you've changed f4se project type from dynamic library to static library, like mentioned in another thread Edited May 25, 2024 by DlinnyLag Link to comment Share on other sites More sharing options...
ScottyDoesKnow Posted May 25, 2024 Author Share Posted May 25, 2024 34 minutes ago, DlinnyLag said: I do not see any way how it might work. f4se_1_10_163.lib has only single function for linker - StartF4SE. This is the only function that you can call from f4se_1_10_163.dll I suppose your code do not call it %) Actually, I do not see any code that calls this function. It looks like a rudiment. Perhaps, you have changed process of f4se project building and it produces something different now. Perhaps, you've changed f4se project type from dynamic library to static library, like mentioned in another thread You are correct. I was trying a bunch of things to make it work but it looks like it's compiling f4se as a static library that actually fixes it, which is great because the f4se lib name changes with each version. Thanks once again! 1 Link to comment Share on other sites More sharing options...
Recommended Posts