SKKmods Posted November 3, 2018 Share Posted November 3, 2018 In empathy one of my fast start quest bypass mods was generating 8 minutes (that's 2 hours of game-time) of on screen quest start notifications due to setting quest bypass stages in its 10 second run time. With perseverance I got that down to one message @ 30 seconds (damn you, foul MQ104 Stage 50). An imperfect compromise, but good enough for government work. Link to comment Share on other sites More sharing options...
Reneer Posted November 3, 2018 Share Posted November 3, 2018 (edited) I just had this idea (edit: added in -1 count as per Wolfmark's reminder below):Function RemoveAllItemsSilent(ObjectReference akObjRef, ObjectReference DestContainerRef) akObjRef.RemoveItem(KeywordFormList, -1, true, DestContainerRef) EndFunction And if the above doesn't work, the longer version:Function RemoveAllItemsSilent(ObjectReference akObjRef, ObjectReference DestContainerRef) int i = 0 while (i < KeywordFormList.GetSize()) Keyword currentkey = KeywordFormList.GetAt(i) As Keyword akObjRef.RemoveItem(currentkey, -1, true, DestContainerRef) i += 1 endWhile EndFunction Edited November 3, 2018 by Reneer Link to comment Share on other sites More sharing options...
DieFeM Posted November 3, 2018 Share Posted November 3, 2018 (edited) At this point, the only posible way is by using script extender's GetInventoryItems(): Form[] Property BlackList Auto Const Function RemoveAllItemsSilent(ObjectReference akActorRef, ObjectReference DestContainerRef) Form[] InventoryItems = akActorRef.GetInventoryItems() Int i = 0 While i < InventoryItems.Length Form CurrentItem = InventoryItems[i] If(BlackList.Find(CurrentItem) < 0) Int ItemCount = akActorRef.GetItemCount(akItem = CurrentItem) akActorRef.RemoveItem(akItemToRemove = CurrentItem, aiCount = ItemCount, abSilent = True, akOtherContainer = DestContainerRef) EndIf i += 1 EndWhile EndFunction I've added property which stores a blacklist of the objects that you don't want to be removed from the actor, so you can add the Pipboy, or whatever object, to this array, and it will be ignored. Unfortunately its still too slow: PS: At the moment I've started to test it, the answer from Reneer wasn't there. ^^ You may want to try it. Edited November 3, 2018 by DieFeM Link to comment Share on other sites More sharing options...
SKKmods Posted November 3, 2018 Share Posted November 3, 2018 I had that exact same latency with my auto inventory junk scrapper mod until I split the search/find/evaluation list into 6 shards with 100 MISC items in each list and ran seperate threads on them. You may be able to refactor this code: ; ****** Multithread the item list evaluation and move to interim container Var[] vParams = new Var[3] vParams[0] = SourceREF as Objectreference vParams[1] = TempContainerREF as Objectreference vParams[2] = akItemListToUse as FormList vParams[2] = pSKK_ScrapperSafeItemList01 as FormList pSKK_ScrapperThread01.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperSafeItemList02 as FormList pSKK_ScrapperThread02.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperSafeItemList03 as FormList pSKK_ScrapperThread03.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperSafeItemList04 as FormList pSKK_ScrapperThread04.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperSafeItemList05 as FormList pSKK_ScrapperThread05.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperSafeItemList06 as FormList pSKK_ScrapperThread06.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperDLC03ItemList as FormList pSKK_ScrapperThread07.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperDLC04ItemList as FormList pSKK_ScrapperThread08.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperModItemList as FormList pSKK_ScrapperThread09.CallFunctionNoWait("MoveItems", vParams) Utility.WaitMenuMode(1) ; wait for threadlocks to engage While pSKK_ScrapperThread01.ThreadLocked() && pSKK_ScrapperThread02.ThreadLocked() && pSKK_ScrapperThread03.ThreadLocked() && pSKK_ScrapperThread04.ThreadLocked() && pSKK_ScrapperThread05.ThreadLocked() && pSKK_ScrapperThread06.ThreadLocked() && pSKK_ScrapperThread07.ThreadLocked() && pSKK_ScrapperThread08.ThreadLocked() && pSKK_ScrapperThread09.ThreadLocked() Utility.WaitMenuMode(1) ; wait for threadlocks to complete EndWhile Link to comment Share on other sites More sharing options...
Wolfmark Posted November 3, 2018 Share Posted November 3, 2018 The aiCount parameter for RemoveItem can be -1, which means "all items".There's no need to call GetItemCount() to obtain the number and then call RemoveItem() with that number. A faster solution: 1. Create a form list with all ObjectTypeXXX keywords and all scrap components (c_XXX records). Add few other common keywords, like UnscrappableObject, BobbleheadKeyword, FeaturedItem etc...2. Call RemoveItem() with this form list and -1 for aiCount parameter.3. I would create a new form list with all other vanilla items that don't have a keyword or components. Like all holotapes, all keys, all books etc...4. Call RemoveItem() with this second form list and -1 for aiCount parameter. (you can merge the first and second form lists) This should remove most of junk items + weapons + ammo + armor + chems + holotapes + keys + notes. With only 1-2 RemoveItem() calls. There are few exceptions, for example some ammo records don't have ObjectTypeAmmo keyword (flares, gamma rounds...). Then if F4SE is installed: 5. Create an empty form list property.6. Use ObjectReference.GetInventoryItems() to get the base forms for all remaining items and move them to the form list. This is the slow part and this is why is better to remove as much as possible in first 1-4 steps...7. Call RemoveItem with the new form list and -1 for aiCount parameter. Without F4SE installed the idea is to minimize the number of notification messages: 5. I would create a form list with most vanilla items that were not included in steps 1-4.6. RemoveItem with this form list and -1 for aiCount parameter.7. RemoveAllItems() for remaining items. Link to comment Share on other sites More sharing options...
Reneer Posted November 3, 2018 Share Posted November 3, 2018 The aiCount parameter for RemoveItem can be -1, which means "all items". There's no need to call GetItemCount() to obtain the number and then call RemoveItem() with that number.I always forget about that little trick. Either way, nice explanation of how the code I wrote works. Link to comment Share on other sites More sharing options...
DiodeLadder Posted November 3, 2018 Author Share Posted November 3, 2018 (edited) I just had this idea: Function RemoveAllItemsSilent(ObjectReference akObjRef, ObjectReference DestContainerRef) akObjRef.RemoveItem(KeywordFormList, akObjRef.GetItemCount(KeywordFormList), true, DestContainerRef) EndFunction And if the above doesn't work, the longer version: Function RemoveAllItemsSilent(ObjectReference akObjRef, ObjectReference DestContainerRef) int i = 0 while (i < KeywordFormList.GetSize()) Keyword currentkey = KeywordFormList.GetAt(i) As Keyword int count = akObjRef.GetItemCount(currentkey) akObjRef.RemoveItem(currentkey, count, true, DestContainerRef) i += 1 endWhile EndFunction Thank you very much Reneer! The first one was actually something I have tried myself earlier, but catching everything was the part I couldn't figure out at the time, because not everything in player inventory has attached keywords. Would you happen to know a way to catch everything? If I can catch everything with this, I think I can make it work. I think I threw in all the keywords I could think of, but misc items (and maybe few other things?) were the ones I couldn't catch. It does work with a formlist without doing the while loop, BTW, the first one worked for me. By the way, for the RemoveItem, I could use a negative integer value as the item count to remove all the items with keywords on the formlist in one shot if I remember correctly, and I think there was no need to make calls to get the actual item counts. At this point, the only posible way is by using script extender's GetInventoryItems(): Form[] Property BlackList Auto Const Function RemoveAllItemsSilent(ObjectReference akActorRef, ObjectReference DestContainerRef) Form[] InventoryItems = akActorRef.GetInventoryItems() Int i = 0 While i < InventoryItems.Length Form CurrentItem = InventoryItems[i] If(BlackList.Find(CurrentItem) < 0) Int ItemCount = akActorRef.GetItemCount(akItem = CurrentItem) akActorRef.RemoveItem(akItemToRemove = CurrentItem, aiCount = ItemCount, abSilent = True, akOtherContainer = DestContainerRef) EndIf i += 1 EndWhile EndFunction I've added property which stores a blacklist of the objects that you don't want to be removed from the actor, so you can add the Pipboy, or whatever object, to this array, and it will be ignored. Unfortunately its still too slow: PS: At the moment I've started to test it, the answer from Reneer wasn't there. ^^ You may want to try it. Thank you very much DieFeM, for even taking time to make a video! Using SE is something I would want to avoid in this mod, though.... That sure would be handy to have, however, and tempting. If I remember correctly, RemoveItem by itself would not remove the equipped items, and I think what I learned from another forum post was to use a loop to unequip items on armor slot 0-29 and avoid 30 which is Pip-Boy (I've read that the actual slot numbers used by Papyrus are different from what you see in CK, which are 30 through 60). UnequipAll was a no-go for me because it removed Pip-boy. Quest items getting removed is okay I think, because they would be in the destination container anyway, so it maybe possible to get away without defining the blacklist items. ...Maybe that would give a good opportunity to ditch the stuff from Cambridge Polymer, lol. I had that exact same latency with my auto inventory junk scrapper mod until I split the search/find/evaluation list into 6 shards with 100 MISC items in each list and ran seperate threads on them. You may be able to refactor this code: ; ****** Multithread the item list evaluation and move to interim container Var[] vParams = new Var[3] vParams[0] = SourceREF as Objectreference vParams[1] = TempContainerREF as Objectreference vParams[2] = akItemListToUse as FormList vParams[2] = pSKK_ScrapperSafeItemList01 as FormList pSKK_ScrapperThread01.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperSafeItemList02 as FormList pSKK_ScrapperThread02.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperSafeItemList03 as FormList pSKK_ScrapperThread03.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperSafeItemList04 as FormList pSKK_ScrapperThread04.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperSafeItemList05 as FormList pSKK_ScrapperThread05.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperSafeItemList06 as FormList pSKK_ScrapperThread06.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperDLC03ItemList as FormList pSKK_ScrapperThread07.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperDLC04ItemList as FormList pSKK_ScrapperThread08.CallFunctionNoWait("MoveItems", vParams) vParams[2] = pSKK_ScrapperModItemList as FormList pSKK_ScrapperThread09.CallFunctionNoWait("MoveItems", vParams) Utility.WaitMenuMode(1) ; wait for threadlocks to engage While pSKK_ScrapperThread01.ThreadLocked() && pSKK_ScrapperThread02.ThreadLocked() && pSKK_ScrapperThread03.ThreadLocked() && pSKK_ScrapperThread04.ThreadLocked() && pSKK_ScrapperThread05.ThreadLocked() && pSKK_ScrapperThread06.ThreadLocked() && pSKK_ScrapperThread07.ThreadLocked() && pSKK_ScrapperThread08.ThreadLocked() && pSKK_ScrapperThread09.ThreadLocked() Utility.WaitMenuMode(1) ; wait for threadlocks to complete EndWhile Thank you very much SKK, but this is totally over my head, lol. So you've broken up the list of items to shorten the scanning time...? Is it necessary to create a formlist with all individual misc items to catch them? That was the part I couldn't figure out, because they don't have any keywords attached. I guess the problem with relying on keywords and pre-made formlist is that, if the player has non-vanilla items without keywords, those would not be removed. The SE function DieFeM mentioned would be able to really catch everything on the other hand... Edited November 3, 2018 by DiodeLadder Link to comment Share on other sites More sharing options...
DiodeLadder Posted November 3, 2018 Author Share Posted November 3, 2018 (edited) Ah, wait, there are more replies now. Thanks guys - I'm a very slow poster here, lol, English is not my native language. Edited November 3, 2018 by DiodeLadder Link to comment Share on other sites More sharing options...
DiodeLadder Posted November 3, 2018 Author Share Posted November 3, 2018 The aiCount parameter for RemoveItem can be -1, which means "all items".There's no need to call GetItemCount() to obtain the number and then call RemoveItem() with that number. A faster solution: 1. Create a form list with all ObjectTypeXXX keywords and all scrap components (c_XXX records). Add few other common keywords, like UnscrappableObject, BobbleheadKeyword, FeaturedItem etc...2. Call RemoveItem() with this form list and -1 for aiCount parameter.3. I would create a new form list with all other vanilla items that don't have a keyword or components. Like all holotapes, all keys, all books etc...4. Call RemoveItem() with this second form list and -1 for aiCount parameter. (you can merge the first and second form lists) This should remove most of junk items + weapons + ammo + armor + chems + holotapes + keys + notes. With only 1-2 RemoveItem() calls. There are few exceptions, for example some ammo records don't have ObjectTypeAmmo keyword (flares, gamma rounds...). Then if F4SE is installed: 5. Create an empty form list property.6. Use ObjectReference.GetInventoryItems() to get the base forms for all remaining items and move them to the form list. This is the slow part and this is why is better to remove as much as possible in first 1-4 steps...7. Call RemoveItem with the new form list and -1 for aiCount parameter. Without F4SE installed the idea is to minimize the number of notification messages: 5. I would create a form list with most vanilla items that were not included in steps 1-4.6. RemoveItem with this form list and -1 for aiCount parameter.7. RemoveAllItems() for remaining items. Thank you very much for chiming in Wolfmark! Yeah, how to deal with those items without keywords was what I couldn't figure out. So I really do need to make a formlist with them then... Thank you for clarifying! Link to comment Share on other sites More sharing options...
DieFeM Posted November 3, 2018 Share Posted November 3, 2018 (edited) After reading Wolfmark's reply, still using F4SE, I've implemented some of the things he said: FormList Property ScriptFilledFormList Auto Const Form Property Pipboy Auto Const Bool Function FillFormList(Form[] Items) ScriptFilledFormList.Revert() Int i = 0 While i < Items.Length Form CurrentForm = Items[i] If(Pipboy != CurrentForm) ScriptFilledFormList.AddForm(CurrentForm) EndIf i += 1 EndWhile Return True EndFunction Function RemoveAllItemsSilent(ObjectReference akActorRef, ObjectReference DestContainerRef) If(FillFormList(akActorRef.GetInventoryItems())) akActorRef.RemoveItem(akItemToRemove = ScriptFilledFormList, aiCount = -1, abSilent = True, akOtherContainer = DestContainerRef) EndIf EndFunction It changes the behavior of the function as is expected: takes a second or 2 to fill the FormList, but then it removes everything in a fraction of second. Edited November 3, 2018 by DieFeM Link to comment Share on other sites More sharing options...
Recommended Posts