[Papyrus] Function running on NULL object?

Post » Sun Mar 22, 2015 1:00 pm

Per the title, I/anyone using my mod will sometimes have a function running on a non-existent (NULL) object. Is this bad? Should I explore other options?

I have provided my code below. It is an adaptation of Amethyst Deceivers Universal Item Display Script.

A quick run-down of how it works: The player can display almost any item by interacting with a given activator which gates access to the container providing nothing is blocking the item's destination coordinates (an item already being displayed for example). The container then "drops" the item and "mounts" it at its own origin and rotation.

Aside from tweaking the functionality to my liking, it always irked me that unless you only added one single item to the scripted container it would correctly reject surplus items but it would never also display the original valid item. For that item to be displayed the player would have to retrieve the item from the container, then add it, and only it, to the container once more. Ease of use, and being "foolproof" is a very big deal to me, so a user-end workaround is not acceptable to me.

To get around this small quirk, I thought it would be a good idea to also queue the MountCurrentItem function whenever any items get removed from the container. The good news is that this seems to work perfectly when surplus items are added to an empty container. The bad news (I think?) is that the MountCurrentItem function will also run whenever the player legitimately removes an item from being displayed, and thus runs on a NULL object. I must assume this will happen very frequently, will this cause any problems?

Spoiler
Scriptname MDHDisplayScriptCONTAINER extends ObjectReference  {Script for display containers}Bool Property Blocked = False  Auto HiddenMessage Property MessageWarning  AutoMessage Property MessageCount  AutoKeyword Property ArmorShield  AutoKeyword Property WeapTypeDagger  AutoKeyword Property WeapTypeSword  AutoKeyword Property WeapTypeGreatSword  AutoKeyword Property ClothingRing  AutoKeyword Property VendorItemPotion  AutoKeyword Property VendorItemPoison  AutoKeyword Property VendorItemGem  AutoKeyword Property ClothingNecklace AutoFormList Property ClawList  AutoFormList Property WhitePhial  AutoFormList Property PoisonList  AutoFormList Property UniqueJarList AutoInt Property PlacedItem = 0  Auto HiddenInt Property DisplayType = 1  Auto{1 = Weapon rack2 = Dagger case3 = Jewel display4 = Shield plaque5 = Potion/Poison6 = Dragon claw7 = COA weapon8 = Unique Jar9 = Necklace}ObjectReference ItemRefForm ItemBaseEvent OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)Actor PlayerRef = Game.GetPlayer()If (akSourceContainer == PlayerRef)  If (AllowedItems(akBaseItem))   If (aiItemCount == 1) && (PlacedItem == 0)        PlacedItem += aiItemCount        ItemBase = akBaseItem        ItemRef = akItemReference        RegisterForSingleUpdate(0.1)   Else        MessageCount.Show()        PlacedItem += aiItemCount        RemoveItem(akBaseItem, aiItemCount, False, PlayerRef)		;if I register for a single update in the OnItemRemoved Event it should force a recheck of the now singular item   EndIf  Else   MessageWarning.Show()   PlacedItem += aiItemCount   RemoveItem(akBaseItem, aiItemCount, False, PlayerRef)  EndIfElse  RemoveItem(akBaseItem, aiItemCount, False, akSourceContainer)EndIfEndEventEvent OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)If (akDestContainer == Game.GetPlayer())  UnregisterForUpdate()EndIfPlacedItem -= aiItemCountRegisterForSingleUpdate(0.1)			;This event only fires when the player attempts to add more than one item to the container, and when the player physically removes a displayed item.EndEvent								;The latter won't have any object to "drop" but the MountCurrentItem function will still fire, is this bad?Event OnUpdate()MountCurrentItem(ItemBase, ItemRef)EndEventFunction MountCurrentItem(Form akBaseItem, ObjectReference akItemReference)ObjectReference MountedItemInt i = 0If (akItemReference)  DropObject(akBaseItem)  MountedItem = akItemReferenceElse  MountedItem = DropObject(akBaseItem)EndIfIf (MountedItem != None)  Blocked = True  While(!MountedItem.Is3DLoaded()) && (i < 10)   Utility.Wait(0.1)   i += 1  EndWhile		;If Staff use MoveToNode		;else use original code below  MountedItem.SetMotionType(Motion_Keyframed, False)  MountedItem.TranslateToRef(Self, 2000.0, 0.0)  Utility.Wait(1)  Blocked = False  ItemRef = NoneEndIfEndFunctionBool Function AllowedItems(Form akBaseItem)If (DisplayType == 1)  Return (akBaseItem as Weapon)ElseIf (DisplayType == 2)  Return (akBaseItem.HasKeyword(WeapTypeDagger))ElseIf (DisplayType == 3)  Return ((akBaseItem.HasKeyword(ClothingRing)) || (akBaseItem.HasKeyword(VendorItemGem)))ElseIf (DisplayType == 4)  Return (akBaseItem.HasKeyword(ArmorShield))ElseIf (DisplayType == 5)  Return ((akBaseItem.HasKeyword(VendorItemPotion)) || (akBaseItem.HasKeyword(VendorItemPoison)) || (WhitePhial.HasForm(akBaseItem)) || (PoisonList.HasForm(akBaseItem)))ElseIf (DisplayType == 6)  Return (ClawList.HasForm(akBaseItem))ElseIf (DisplayType == 7)  Return ((akBaseItem.HasKeyword(WeapTypeSword)) || (akBaseItem.HasKeyword(WeapTypeGreatSword)))ElseIf (DisplayType == 8)  Return (UniqueJarList .HasForm(akBaseItem))ElseIf (DisplayType == 9)  Return (akBaseItem.HasKeyword(ClothingNecklace))EndIfEndFunction 

EDIT: From my limited tests in-game I could not detect any adverse effects.

User avatar
Tammie Flint
 
Posts: 3336
Joined: Mon Aug 14, 2006 12:12 am

Post » Sun Mar 22, 2015 2:08 pm

Wouldn't it be easier to just place the first item (assuming it matches) and return the others using a state to lock the transations?

And what keeps people from displaying an item, closing the container, then displaying another? I don't see anything in that script that tracks the placed item long-term.

Here's my version based on my shelf displays. I haven't actually tested this particular version, but it does at least compile and the logic matches a script that I know works.

Spoiler

Scriptname MDHDisplayScriptCONTAINER extends ObjectReference  {Script for display containers}Actor Property PlayerRef AutoMessage Property MessageWarning  AutoMessage Property MessageCount  AutoKeyword Property ArmorShield  AutoKeyword Property WeapTypeDagger  AutoKeyword Property WeapTypeSword  AutoKeyword Property WeapTypeGreatSword  AutoKeyword Property ClothingRing  AutoKeyword Property VendorItemPotion  AutoKeyword Property VendorItemPoison  AutoKeyword Property VendorItemGem  AutoKeyword Property ClothingNecklace AutoFormList Property ClawList  AutoFormList Property WhitePhial  AutoFormList Property PoisonList  AutoFormList Property UniqueJarList AutoInt Property DisplayType = 1  Auto{1 = Weapon rack2 = Dagger case3 = Jewel display4 = Shield plaque5 = Potion/Poison6 = Dragon claw7 = COA weapon8 = Unique Jar9 = Necklace}ObjectReference PlacedObjectState SkipMultiples; All but the first get returnedEvent OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)	if akSourceContainer == PlayerRef		MessageCount.Show()  ; Only one item can be displayed at a time!	elseif !akSourceContainer		akSourceContainer = PlayerRef		MessageWarning.Show() ; wrong type message	endif	if akItemReference		RemoveItem(akItemReference, aiItemCount, true, akSourceContainer)	else		RemoveItem(akBaseItem, aiItemCount, true, akSourceContainer)	endifEndEventEndStateEvent OnLoad()	BlockActivation()EndEventEvent OnActivate(ObjectReference akActionRef)	if PlacedObject && PlacedObject.GetParentCell() == GetParentCell() && (PlacedObject.Is3dLoaded() || PlacedObject.IsDisabled())		MessageCount.Show()  ; There's a already an item on display	else		PlacedObject = None		Activate(akActionRef, true)	endifEndEventEvent OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)	GoToState("SkipMultiples")	; only the player can add items for display, it must be a single object, and there can't already be something in place	if akSourceContainer != PlayerRef || aiItemCount != 1 || PlacedObject		OnItemAdded(akBaseItem, aiItemCount, akItemReference, akSourceContainer) ; call the version that returns items	; also don't want it if it's the wrong time	elseif !AllowedItems(akBaseItem)		OnItemAdded(akBaseItem, aiItemCount, akItemReference, None) ; call the version that returns items	; it's something that needs to be placed	else		if akItemReference			PlacedObject = DropObject(akItemReference, 1)		else			PlacedObject = DropObject(akBaseItem, 1)		endif		if PlacedObject			int i = 10			while i && !PlacedObject.Is3DLoaded()				Utility.Wait(0.1)				i -= 1			endwhile			PlacedObject.SetMotionType(Motion_Keyframed, False)			PlacedObject.TranslateToRef(Self, 2000.0, 0.0)		endif	endif	GoToState("")EndEventBool Function AllowedItems(Form akBaseItem)If (DisplayType == 1)  Return (akBaseItem as Weapon)ElseIf (DisplayType == 2)  Return (akBaseItem.HasKeyword(WeapTypeDagger))ElseIf (DisplayType == 3)  Return ((akBaseItem.HasKeyword(ClothingRing)) || (akBaseItem.HasKeyword(VendorItemGem)))ElseIf (DisplayType == 4)  Return (akBaseItem.HasKeyword(ArmorShield))ElseIf (DisplayType == 5)  Return ((akBaseItem.HasKeyword(VendorItemPotion)) || (akBaseItem.HasKeyword(VendorItemPoison)) || (WhitePhial.HasForm(akBaseItem)) || (PoisonList.HasForm(akBaseItem)))ElseIf (DisplayType == 6)  Return (ClawList.HasForm(akBaseItem))ElseIf (DisplayType == 7)  Return ((akBaseItem.HasKeyword(WeapTypeSword)) || (akBaseItem.HasKeyword(WeapTypeGreatSword)))ElseIf (DisplayType == 8)  Return (UniqueJarList .HasForm(akBaseItem))ElseIf (DisplayType == 9)  Return (akBaseItem.HasKeyword(ClothingNecklace))EndIfEndFunction


The basic logic is that the first add will change state so that any further adds will be blocked until the first one is finished. If the first item isn't any good it gets returned and the state gets restored to the default fairly quickly. If an item can be placed, then the blocking state will be maintained until after the container menu is closed and the new object is put into place. A variable pointing to the placed object is kept so that when the container is accessed again a quick check can be made to see if it's still in the same cell, so if the player wants to swap out items the old one has to be picked up before adding the new one.
User avatar
Julia Schwalbe
 
Posts: 3557
Joined: Wed Apr 11, 2007 3:02 pm


Return to V - Skyrim