icecreamassassin Posted August 15, 2015 Share Posted August 15, 2015 so I have this script which seems to be working perfectly other than one small part, which I hope someone can point out a solution to. The script is attached to a chest which is link chained to various real weapon displays on the wall above it. When you dump an item in it queues up a single update which fires the function to check the contents against the linked chain of displays and compares the model paths, and if they match, the display is enabled. The problem I am having is the code to disable the displays. If you remove them one by one, the code as it is written now works fine, but if you remove them all at once or rapidly spam the take button it causes the OnItemRemoved() queue hold up where it drops random functions, hence leaving stuff on the wall. I'm having a hard time wrapping my head around how to do a nested pair of while loops (like the item added option has) because obviously the items are no longer in the container to compare with any longer, and the standard code of monitoring the item as it leaves and comparing it causes the issues at hand. Would setting up a blank formlist and having the additem function add the base item form reference to the formlist and then have the removeitem update event function fire and check the form list against the container and disable any linked chain item that has a matching model which has 0 count in the container and then removing said entry from the list be a plausible solution? seems a bit convoluted to me, but I can't think of another method Scriptname DD_WeaponArmorDisplayScript extends ObjectReference Form[] BID ObjectReference LR ObjectReference OLR Int AddState Weapon MyWeapon String ItmFilePath String DFilePath Weapon LRW int ITMCNT Event OnInit() Int i = countLinkedRefChain() BID = Utility.CreateFormArray(i) While i i -= 1 BID[i] = GetNthLinkedRef(i).GetBaseObject() As Form EndWhile EndEvent Event onLoad() Int i = BID.Length While i > 1 i -= 1 LR = GetNthLinkedRef(i) if (Self.Is3DLoaded()) LR.BlockActivation(true) LR.setMotionType(Motion_Keyframed, FALSE) LR.MoveToMyEditorLocation() Endif EndWhile EndEvent Event onCellAttach() Int i = BID.Length While i > 1 i -= 1 if (Self.Is3DLoaded()) LR = GetNthLinkedRef(i) LR.BlockActivation(true) LR.MoveToMyEditorLocation() Endif EndWhile EndEvent Event onLoadGame() Int i = BID.Length While i > 1 i -= 1 if (Self.Is3DLoaded()) LR = GetNthLinkedRef(i) LR.MoveToMyEditorLocation() LR.BlockActivation(true) LR.setMotionType(Motion_Keyframed, FALSE) EndIf EndWhile EndEvent Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) if akSourceContainer == Game.getPlayer() RegisterforSingleUpdate(1) debug.notification("Update registered") AddState = 1 endif EndEvent Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer) if akDestContainer == Game.GetPlayer() MyWeapon = akBaseItem as weapon RegisterforSingleUpdate(1) debug.notification("Update registered") ; AddState = 2 RemoveDisplays() endif EndEvent Event OnUpdate() debug.notification("Event Updated") if AddState == 1 AddDisplays() AddState = 0 Elseif AddState == 2 RemoveDisplays() AddState = 0 Endif EndEvent Function AddDisplays() debug.notification("Add display fired") Int i = BID.Length While i > 1 i -= 1 LR = GetNthLinkedRef(i) LRW = LR.GetBaseObject() as Weapon OLR = GetNthLinkedRef(i +1) ITMCNT = GetNumItems() While ITMCNT ITMCNT -= 1 MyWeapon = GetNthForm(ITMCNT) as weapon DFilePath = LRW.GetModelPath() ItmFilePath = MyWeapon.GetModelPath() If DFilePath == ItmFilePath debug.notification("Model paths match") If LR.IsDisabled() LR.Enable() EndIf EndIf EndWhile EndWhile onLoadGame() EndFunction Function RemoveDisplays() debug.notification("remove display fired") If BID.Find(MyWeapon) >= 1 && GetItemCount(MyWeapon) == 0 Int i = BID.Length While i > 1 i -= 1 LR = GetNthLinkedRef(i) If LR.GetBaseObject() == MyWeapon If LR.IsEnabled() LR.Disable() EndIf EndIf EndWhile EndIf EndFunction Link to comment Share on other sites More sharing options...
sLoPpYdOtBiGhOlE Posted August 15, 2015 Share Posted August 15, 2015 Lol, I have the answer, but i don't want to rewrite it from scratch again. A few simple things you can do to catch the removed items (won't catch them all). Use GetNumItems() on the container.If 0 then disable whole chain.There is actually a DisableLinkChain function already supplied so you don't have to write one: http://www.creationkit.com/DisableLinkChain_-_ObjectReference This will catch when a user hits "Take All"where you implement GetNumItems() checks is up to you.But GetNumItems() is a quick returning function so calling checks regularly shouldn't be to consuming.(Maybe when exiting the container do a quick check and disable as needed or each time you get a removeitem event even wouldn't hurt) If you want to be a little less dynamic (eg: customize the script for just this instance of use), then use keywords.Add your keywords as properties or property array (my preference)It'll make it so you can check the container via keyword and disable the link chain by the the same keyword. eg: GetItemCount(Keywrord)will quickly return the count of items in the container with that keyword, if 0 then DisableLinkChain(keywword) The more I look at what you've done in that script the more I see your double handling things.eg: LR = GetNthLinkedRef(i) LRW = LR.GetBaseObject() as Weapon You already have an array of the base objects in the BID array.The BID array indexes are at the same indexes of the linked ref chain.eg; LR = GetNthLinkedRef(i) LRW = BID as Weapon You have lost all focus on what's what.I saw your worn form function the other day and I can't believe how nice that function you ended with was.Then i see the above script and I'm like damn, the same person did not do this...lol Edit (Not related to your problem):Also those Self.Is3dLoaded() will be causing papyrus spam if the item your setting motion type on isn't loaded, since Self is the Container (your not setting motion type on the container).Your needing to check if the item your setting motion type is loaded eg: LR.Is3DLoaded()(I actually did the same mistake and corrected it when I updated it last time)I can see you were trying to save going any further if the container 3d isn't loaded, but you also look that some of those chained references aren't enabled (no 3d) while the container is loaded, so you've skipped the check and it will spam the log. If it is a case of your wanting not to process any further if the container isn't loaded, then I'd suggest move the Self.Is3Dloaded() outside the while loop.This way if the container isn't loaded the while loop never fires.But regardless you still need to check the item chain linked ref 3d is loaded in the while loop.eg: Event onCellAttach() ResetDisplay() EndEvent Event onLoad() ResetDisplay() EndEvent Event onLoadGame() ResetDisplay() EndEvent Function ResetDisplay() If Self.Is3DLoaded() Int i = BID.Length While i > 1 i -= 1 LR = GetNthLinkedRef(i) If LR.Is3DLoaded() LR.BlockActivation(true) LR.setMotionType(Motion_Keyframed, FALSE) LR.MoveToMyEditorLocation() EndIf EndWhile EndIf EndFunctionEdit Again:had a look to see if I could refine that mess of a script a little. I'm assuming your linked chain is changing models on the chain ?Which seems odd when your adding as I thought you'd be using hard set display models (same base id as the form your adding)Otherwise you may as well not give a rats about the disaplay model path and when adding a weapon you would set the display model path of the weapon your adding regardless of the weapon (which is not what your doing). So what actually are you doing, that you have to check model paths when adding? Anyways here's the same thing as you had (untested, but does compile).It's based on checking the model path when adding and removing.Should enable or disable on the amount you have available of a type in the chain. But to be honest you really need to be a bit more informative as to exactly as to what your trying to accomplish.Yeah I can see a base of it, but i had to assume to much and some of what your doing makes no sense (to me) in the way it is atm. Scriptname DD_WeaponArmorDisplayScript extends ObjectReference Form[] BID ObjectReference LR Event OnInit() Int i = countLinkedRefChain() BID = Utility.CreateFormArray(i) While i i -= 1 BID[i] = GetNthLinkedRef(i).GetBaseObject() As Form EndWhile EndEvent Event onCellAttach() ResetDisplay() EndEvent Event onLoad() ResetDisplay() EndEvent Event onLoadGame() ResetDisplay() EndEvent Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) If (akBaseItem As Weapon) Int i Int j While i < aiItemCount j = 1 While j < BID.Length LR = GetNthLinkedRef(j) If (BID[j] As Weapon).GetModelPath() == (akBaseItem As Weapon).GetModelPath() && LR.IsDisabled() LR.Enable() j = BID.Length EndIf j += 1 EndWhile i += 1 EndWhile ResetDisplay() Else ;not a weapon retuurn the akBaseItem of aiItemCount back to the player. EndIf EndEvent Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer) If GetNumItems() == 0 Int i = 1 While i < BID.Length GetNthLinkedRef(i).Disable() i += 1 EndWhile Else Int i Int j While i < aiItemCount j = 1 While j < BID.Length LR = GetNthLinkedRef(j) If (BID[j] As Weapon).GetModelPath() == (akBaseItem As Weapon).GetModelPath() && LR.IsEnabled() LR.Disable() j = BID.Length EndIf j += 1 EndWhile i += 1 EndWhile EndIf EndEvent Function ResetDisplay() If Self.Is3DLoaded() Int i = BID.Length While i > 1 i -= 1 LR = GetNthLinkedRef(i) If LR.Is3DLoaded() LR.BlockActivation(true) LR.setMotionType(Motion_Keyframed, FALSE) LR.MoveToMyEditorLocation() EndIf EndWhile EndIf EndFunction The above still won't sync when removing multiple of the same items.Basically say you have 3 of the same type in the chain and you have 10 item of the the same type in the container.When removing only 3 from the container the 3 dispaly items will disable, but you'd still have 7 left in the container. Are you limititing what you can add to the container so it's only the same amount that can be displayed?(This is along the lines of being more informative that I referred to earlier) Link to comment Share on other sites More sharing options...
icecreamassassin Posted August 15, 2015 Author Share Posted August 15, 2015 thanks for trying to help, but I'll try and be more clear. Firstly the is3Dloaded() on the Self was an oversight, it was supposed to be on the LR as the condition, so thanks for pointing that out. as far as what It's doing overall, it's really quite simple. You place a weapon in a chest and if there is a matching LR in the chain with the same model path, it enables the LR. Likewise when you remove the item from the chest, it will disable the LR with the same model. The reason for the models rather than using a form ID or keywords or base type is it's set to allow display of the item based on what it looks like rather than what the item IS. So if you have an enchanted iron sword of some kind it will display the base iron sword if you plop it in. It's also got proxy weapons set up on the chain for a variety of expanded weaponry and rather than using getformfromfile which varies extensively, the model paths between a few mods that utilize said items are fairly consistent. At any rate, I haven't looked at papyrus yet to debug, but everything in the script seems to be functioning properly like I said other than the removal routine. I am going to just do a simple RegisterForSingleUpdate() that checks if the container is empty and have it disable all of the items on the chain manually that way, and see if quick removal of individual items (all but one) behaves or still has queue loss. Your script above is actually ignoring the key problem I posted about in my OP, which is that having multiple OnItemAdded() or OnItemRemoved() functions back to back causes the system to queue the events and some get dumped and items get ignored. You had this same problem in the example script you made for the liquor bottles. If you dumped all 14 items or whatever into it too quickly or remove them all too fast it would miss enabling and disabling random references on the chain. The RegisterForSingleUpdate() event solved this problem (hence why I put it in) so that the actual check for matching references occurred only once after the menu was closed, that way it's all one while loop check that isn't being interrupted by another OnItemAdded/Removed() event. The multiple Re-registrations of multiple updates back to back causes no issues because registering for a single update merely kicks the previous registration out as opposed to RegisterForUpdate() which just stacks them up. So like I said in the OP, the only part that really has me vexed at this point is trying to accurately pass removed items through to disable the item on the chain, but I'll get there. I'm sure that my solution will probably still miss parts on fast removal of successive items still but I'll probably figure out a way around it. Link to comment Share on other sites More sharing options...
sLoPpYdOtBiGhOlE Posted August 15, 2015 Share Posted August 15, 2015 Do it like the bookcase script.Event OnActivate()Utility.Wait(0.1)UpdateYourDisplayAsTheMenuIsClosedIfI'mCalled()EndEvent Not tested, but something like this (only updates after exiting the container, not while in the container): Scriptname DD_WeaponArmorDisplayScript extends ObjectReference ;Swapped BID for MP string array since your not using base id to compare. String[] MP ObjectReference LR Bool bChange Event OnInit() Int i = countLinkedRefChain() MP = Utility.CreateStringArray(i) While i i -= 1 MP[i] = (GetNthLinkedRef(i).GetBaseObject() As Weapon).GetModelPath() EndWhile EndEvent Event onCellAttach() ResetDisplay() EndEvent Event onLoad() ResetDisplay() EndEvent Event onLoadGame() ResetDisplay() EndEvent Event OnActivate(ObjectReference akActionRef) bChange = False Utility.Wait(0.1) UpdateDisplay() EndEvent ; Add / Remove Only used to notify change, if no change then don't Update the display Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) bChange = True EndEvent Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer) bChange = True EndEvent ;may want to block activation while updating, so the user can't activate the container till it finishes updating. Function UpdateDisplay() If bChange ;Disable all display items, not even checking if they're enabled. Int i = MP.Length While i > 1 i -= 1 GetNthLinkedRef(i).Disable() EndWhile ;Loop through container and set the display items as needed, also return any non weapon items we find back to the player. Form CurForm Int iCnt Int j i = GetNumItems() While i i -= 1 CurForm = GetNthForm(i) If (CurForm As Weapon) iCnt = GetItemCount(CurForm) j = 1 While ((j < MP.Length) && iCnt) LR = GetNthLinkedRef(j) If (MP[j] == (CurForm As Weapon).GetModelPath()) && LR.IsDisabled() LR.Enable() iCnt -= 1 EndIf j += 1 EndWhile Else ;Not weapon give it back to the player Self.Removeitem(CurForm, GetItemCount(CurForm), True, Game.GetPlayer()) EndIf EndWhile ResetDisplay() EndIf EndFunction Function ResetDisplay() Int i = MP.Length While i > 1 i -= 1 LR = GetNthLinkedRef(i) If LR.Is3DLoaded() LR.BlockActivation(true) LR.setMotionType(Motion_Keyframed, FALSE) LR.MoveToMyEditorLocation() EndIf EndWhile EndFunction Link to comment Share on other sites More sharing options...
icecreamassassin Posted August 15, 2015 Author Share Posted August 15, 2015 Well your script you spoilered above seems to enable and disable the items reliably and quickly, but it does not do it accurately. It seems to be mis-matching the models of the current reference and the current item and I can't for the life of me see why. So it will in effect enable and disable incorrect models almost as if one of the variables is not getting reset before running the whole check function again? and instead of starting again, it starts from where it left off, but that just doesn't make sense to be because they are conditioned to not do anything if they aren't matched. I'm not concerned about multiples of the same object on the chain on this, as there are only one of each item in the chain, I'm more concerned with it simply disabling when there are zero items matching the model, that's pretty much it. I think my brain is about to implode, I've been looking at this too long and been way too close to success and now everything I try seems to just make it worse :P Link to comment Share on other sites More sharing options...
cdcooley Posted August 15, 2015 Share Posted August 15, 2015 Based on sLoPpYdOtBiGhOlE's ideas here's my version.It makes use of the Find function on an array of model paths and ensures items aren't missed by keeping of list of which display items should be active which gets updated completely whenever items are added or removed from the container. A Busy state ensures that you don't get quirky behavior if you try to add or remove items while the script is trying to refresh the item display. It compiles and I think it should be right, but I haven't actually run it so there might be errors. ScriptName WeaponDisplayScript extends ObjectReference int DispCount ; Number of linked display items bool[] DispActive ; Tracks which should be displayed string[] DispModel ; Searchable array of the model paths Event OnInit() DispCount = CountLinkedRefChain() DispActive = Utility.CreateBoolArray(DispCount) DispModel = Utility.CreateStringArray(DispCount) int i = DispCount While i > 1 ; item 0 is unused because it is the container itself i -= 1 DispModel[i] = (GetNthLinkedRef(i).GetBaseObject() As Weapon).GetModelPath() EndWhile EndEvent Event OnLoad() RefreshDisplay() EndEvent Event OnCellLoad() RefreshDisplay() EndEvent Event OnCellAttach() RefreshDisplay() EndEvent Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) RegisterforSingleUpdate(0.1) EndEvent Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer) RegisterforSingleUpdate(0.1) EndEvent ; if changes are made while an update is in progress another will run later to catch the new changes State Busy Event OnUpdate() RegisterforSingleUpdate(0.5) EndEvent EndState ; determine which items should be displayed based on items currently in the container Event OnUpdate() RefreshDisplay(true) EndEvent Function RefreshDisplay(bool fullRefresh = false) GoToState("Busy") int i if fullRefresh ; assume none should be displayed i = DispCount While i > 1 i -= 1 DispActive[i] = false EndWhile ; mark for display if any item in the container is a match for the model path i = GetNumItems() While i i -= 1 Weapon item = GetNthForm(i) as Weapon If item int match = DispModel.Find(item.GetModelPath()) If match > 0 DispActive[match] = true EndIf EndIf EndWhile EndIf ; enable and disable the display items i = DispCount While i > 1 i -= 1 ObjectReference LR = GetNthLinkedRef(i) LR.BlockActivation(true) if DispActive[i] If LR.IsDisabled() LR.Enable() EndIf Utility.Wait(0.1) ; give extra time for the item 3d to load completely LR.SetMotionType(Motion_Keyframed, false) LR.MoveToMyEditorLocation() Else If !LR.IsDisabled() LR.Disable() EndIf EndIf EndWhile GoToState("") EndFunction P.S. There is no onLoadGame() event. What that a custom event or were you thinking of OnCellLoad()? Link to comment Share on other sites More sharing options...
sLoPpYdOtBiGhOlE Posted August 16, 2015 Share Posted August 16, 2015 Well your script you spoilered above seems to enable and disable the items reliably and quickly, but it does not do it accurately. It seems to be mis-matching the models of the current reference and the current item and I can't for the life of me see why. So it will in effect enable and disable incorrect models almost as if one of the variables is not getting reset before running the whole check function again? and instead of starting again, it starts from where it left off, but that just doesn't make sense to be because they are conditioned to not do anything if they aren't matched. I'm not concerned about multiples of the same object on the chain on this, as there are only one of each item in the chain, I'm more concerned with it simply disabling when there are zero items matching the model, that's pretty much it. I think my brain is about to implode, I've been looking at this too long and been way too close to success and now everything I try seems to just make it worse :tongue: Hmm, odd, maybe 2 things maybe causing it iCnt or returning items to the player..I removed icnt since your not phased about the count per item in the container matching multiple of the same in the chain as there is only 1 of each in the chain..I ditched the Loop for CurForm and used find as it's quicker if it's only 1 in the chain.Added Busy State when updating so the user can't activate while updating.Moved LR to local in functions instead of global, should stop some possible probs in some certain circumstances.TBH I really should do a test on it and I could probably nail it pretty quick.Just CK irks me some most days and I avoid it like the plague...lol Scriptname DD_WeaponArmorDisplayScript extends ObjectReference String[] MP Event OnInit() Int i = countLinkedRefChain() MP = Utility.CreateStringArray(i) While i i -= 1 MP[i] = (GetNthLinkedRef(i).GetBaseObject() As Weapon).GetModelPath() EndWhile EndEvent Event onCellAttach() ResetDisplay() EndEvent Event onLoad() ResetDisplay() EndEvent Event OnActivate(ObjectReference akActionRef) BlockActivation() Utility.Wait(1.0) UpdateDisplay() BlockActivation(False) EndEvent Function UpdateDisplay() GoToState("Busy") ObjectReference LR Int i = MP.Length While i > 1 i -= 1 GetNthLinkedRef(i).Disable() EndWhile Form CurForm Int j i = GetNumItems() While i i -= 1 CurForm = GetNthForm(i) If (CurForm As Weapon) j = MP.Find((CurForm As Weapon).GetModelPath()) If j >= 1 LR = GetNthLinkedRef(j) If LR.IsDisabled() LR.Enable() EndIf EndIf Else Self.Removeitem(CurForm, GetItemCount(CurForm), True, Game.GetPlayer()) EndIf EndWhile ResetDisplay() GoToState("") EndFunction Function ResetDisplay() ObjectReference LR Int i = MP.Length While i > 1 i -= 1 LR = GetNthLinkedRef(i) If LR.Is3DLoaded() LR.BlockActivation(true) LR.setMotionType(Motion_Keyframed, FALSE) LR.MoveToMyEditorLocation() EndIf EndWhile EndFunction State Busy Event OnActivate(ObjectReference akActionRef) ;updating do nothing EndEvent EndState Have edited this above code like 5 or maybe 10 times since posting lol TBH I would just update the display after exiting the container menu.Even if it's not as pretty as doing it while in the container menu.You could use register for menu and catch the container menu when exiting to update display, this way your not holding the OnActivate event causing weird behavior if needed. Edit Again + 25:Tested the above script and it works for me, quite quickly as well I might add.Makes no diff if I remove all or juggle stuff while in the container, as soon as I exit the container it updates correctly for me.Ditched the OnItemAdd/Remove event bs altogether as they are unreliable to base updates on.So even if you don't add or remove items when in the container menu the chain is updated when exiting.Drawback is the disable and enable showing when exiting the container.But meh, it does what it's meant to. Put 6 different weapons on a wall, chained from the chest attached script and each fires as it's meant to.Can post my test esp but I'm sure I don't need to. Link to comment Share on other sites More sharing options...
icecreamassassin Posted August 16, 2015 Author Share Posted August 16, 2015 Oh man, you are a god! that works perfectly, I can't believe I didn't just set the remove item check to re-poll what was in the container, that's a stroke of genius (and something obvious I should have thought about lol). Thanks so much for clearing this up for me, I and Saerileth greatly appreciate it (and the fans of Druid's Den will be grateful as well, which I'm working on this for). Now I can actually move on to fixing up other stuff :) take care! Link to comment Share on other sites More sharing options...
sLoPpYdOtBiGhOlE Posted August 16, 2015 Share Posted August 16, 2015 Your welcome and glad it's doing what it's meant to. If I wasn't so bloody lazy when it comes to firing up CK i could of saved you the muck around..lol But you got there in the end at least :) Link to comment Share on other sites More sharing options...
icecreamassassin Posted August 16, 2015 Author Share Posted August 16, 2015 Yeah it's funny how the most obvious tactic (just disabling everything and re-enabling stuff that matches the container items) is the one that escapes us for awhile. Glad it occurred to you though :) I appreciate all the help, between this script and reading up a little more I understand building linked arrays much better now. Link to comment Share on other sites More sharing options...
Recommended Posts