Jump to content

Overhauling the weapon rack scripts


Sclerocephalus

Recommended Posts

I wish you'd made it more clear that there was more than just the out of place trigger scripts thing that needed to go. We'll just have to remember to take care of that with the next major update because I REALLY don't want to throw another hotfix for the USKP. It doesn't sound like this is going to be an especially bad thing for now.

 

I didn't want to scare you, so I should better make this clear: that bug is not critical, because nobody will lose any weapons. Plus: it never occurs when you start a new game (after all, that's why it escaped me).

Link to comment
Share on other sites

Semi-related; Is this something you could also fix, Schlerocephalus?

 

Probably the same problem as with the cases in Severin Manor, for which I already filed a bug entry.

Link to comment
Share on other sites

I saw your post in that thread, thanks :)  I think I'll open a bug ticket for this anyway so it doesn't get lost.

Link to comment
Share on other sites

A side note: There is that phantom shape in all rack meshes which the CK complains about. This shape is in a "bhkSimpleShapePhantom" node which always is a child of a "bhkSPCollisionObject" node. NifSkope lists the purpose of both nodes as unknown.

 

bhkSimpleShapePhantom is nothing else than the trigger volume and the bhkSimpleShapePhantom node is what makes an arbitrary object a trigger. The trigger events and GetTriggerObjectCount() all refer to the geometry held in bhkSimpleShapePhantom (and they all fail if the bhkSPCollisionObject node is removed from the mesh).

 

EDIT:

Maybe somebody could pass this on to the nifskope team, unless they did already find it out themselves.

Link to comment
Share on other sites

Back in mid-July you uploaded a file for a UHFP fix. "Hearthfires_CoA_Rack_Fix_v1-0" is the filename. These meshes are not specific to Hearthfire though. Is this still something that should only be sent out with the UHFP or should it get delivered with a USKP update down the road?

Link to comment
Share on other sites

It's not UHFP specific indeed. So far though, the only vanilla rack where this bug has been observed is in the Hearthfires plugin (which also makes this the only place where a possible fix can actually be tested). If the fix works, it should be included in one of the next USKP updates.

Link to comment
Share on other sites

I'll give it a run through then. Is this by chance to fix that one bug where the swords are showing up on the wall above the rack instead of where they belong?

Link to comment
Share on other sites

Is this by chance to fix that one bug where the swords are showing up on the wall above the rack instead of where they belong?

 

Yes, it's that bug.

Link to comment
Share on other sites

Unfortunately the mesh update didn't do anything. Although I can't be 100% certain that isn't due to baked data. I wish it wasn't such a pain to get a HF house up and running for this sort of testing.

Link to comment
Share on other sites

Sigh ...

 

It's very unlikely that the test failed due to baked data: the only thing I did was modifying the rotation data on the greatsword placement nodes (not on any other node, though, so you hopefully did test it with greatswords and not somehing else) to place them slightly out of plane of the plaques. This strategy has proven to be a possible workaround for this bug on wooden racks (I have described this previously for the falmer staves, and it was just the same with the skull of corruption), so it was worth the effort to test it on a CoA rack.

 

What we know for sure meanwhile is that there's an engine bug "at work" and we also have a vague idea of what is causing it, but we're still far from actually understanding what is happening. This means that there's still a good chance to find a safe workaround eventually.

Link to comment
Share on other sites

Oh. Sorry, yeah. I was testing it with a pair of ordinary dwarven swords. I'll go back and try it with a greatsword.

Link to comment
Share on other sites

From the CK Wiki:

 

ObjectReference Function DropObject(Form akObject, int aiCount = 1) native

"If akObject is a permanently-persistent reference, DropObject will not return a Form ID value for the ObjectReference, even though it still physically drops the object from the inventory. If you need to run further ObjectReference Functions onto the dropped object, you will need to refer to that object's reference ID directly."

 

 

I added the following lines to WeaponRackActivateScript:

	ObjectReference PlayerItemRef = PlayerRef.DropObject (PlayerItem, 1)

	Debug.TraceUser ("WeaponRackLog", Self + ": Player item dropped. Ref = " + PlayerItemRef + ".")
	
	If (PlayerItemRef == None)

		scWeaponRackWeaponDisallowedMessage.Show()
		Debug.TraceUser ("WeaponRackLog", Self + ": Handling of player item aborted.")
		ToggleRack (TriggerMarker, True)

	Endif

When trying to handle the ebony blade, I get the following line on the log:

 

[WeaponRackActivateScript < (0010291E)>]: Player item dropped. Ref = [ObjectReference <Item 24 in container  (00000014)>].
 

 

Since the "If (PlayerItemRef == None)" part is never executed (the respective debug message is never printed), "[ObjectReference <Item 24 in container  (00000014)>]" is obviously not equal to none. Thus, the wiki is wrong when it states that DropObject returns no form ID. It is correct though in that the item is physically dropped. It is! It only never makes it to the rack.

 

The code that follows is never executed. The script gets permanently stuck when it tries to check whether the item's 3D is loaded, because it appears that Is3DLoaded() does never return "true" when it runs on "[ObjectReference <Item 24 in container  (00000014)>]". This also explains the mysterious counter in the vanilla script ... (remember that those scripts were plastered with useless comments, but crucial issues like this one were not even vaguely mentioned).

 

When I fixed the issue with GetParentCell() not working for persistent references, I did already implement a function which stops the loop after one second and returns a bool that tells whether the check failed or not. This function also enabled me to check indirectly whether DropObject() returned a meaningful reference or not (a direct check for something like "[ObjectReference <Item 24 in container  (00000014)>]" is obviously impossible). In other words, the script can now identify a problem weapon at runtime (and we also don't have to use that formlist again).

 

It's impossible to get any further than that though, because both SetMotionType and MoveToNode,and also pretty much every other function that can be run on an object reference simply fail on something like "[ObjectReference <Item 24 in container  (00000014)>]".

 

 

Then I did the same with Wuuthrad, which could be placed without any problems as expected. The placement procedure returned the following parameters:

 

Player item dropped. Ref = [ObjectReference < (FF001370)>].
[ObjectReference < (FF001370)>] loaded.
Placed Item = [WEAPON < (000956B5)>]; Ref = [ObjectReference < (FF001370)>]
Player item [WEAPON < (000956B5)>] placed on trigger [WeaponRackTriggerSCRIPT < (0010291F)>].

 

Note the object reference FF001370. This object reference is stored in the PlayersDroppedWeapon property of WeaponRackActivateScript by a simple command: PlayersDroppedWeapon = ItemToPlace (where ItemToPlace is FF001370; all debug messages above have been set up to print 'ItemToPlace').  Thus, when a is set equal to b, one should expect that a = b, or in other words, that PlayersDroppedWeapon = FF001370.

 

Now look at the messages the trigger script returned when I grabbed Wuuthrad from the rack. The trigger script does nothing else but read PlayersDroppedWeapon from the activator script and print it as the reference of 'MountedItem' on the log:
 

Leaving Item = [WEAPON < (000956B5)>]; Ref = [ObjectReference < (FF001370)>]
Mounted Item = [WEAPON < (000956B5)>]; Ref = [ObjectReference <Item 24 in container  (00000014)>]  <- WTF ?!?

 

Then it got really weird: Trying to place Wuuthrad on the same rack again failed, because DropObject returned "[ObjectReference <Item 24 in container  (00000014)>]" instead of a valid reference as in the first trial. Placing it on another rack worked, but also only once. I tried every single rack in Hjerim, and every time, I could place it only once. The second trial always failed.  Since every failed trial left the respective rack in a completely unresponsive state, I eventually left the cell and returned (because the activators reinitialize on cell attach). Once the racks could be activated again, the whole procedure was perfectly repeatable.

 

Perhaps the engine doesn't like it when a persistent reference is stored in a script property ?

Link to comment
Share on other sites

Further testing revealed that this behaviour is not dependent on which rack is tried. Simply speaking, Wuuthrad can be placed on any rack on the (n+1)st try, but refuses to cooperate on every 2nth try, no matter which rack is chosen for the test. Not saving its reference in the PlayersDroppedWeapon property didn't change anything either.

 

Accidentally, I had been testing a triggerless rack system yesterday (just an activator and an arbitrary static mesh with the respective nodes), in which a small quest that is started by sending a script event from the activator replaces the trigger. Only the activator reference is force filled into an alias, while the weapon alias is optional and filled from a script, so the weapon does not become permanently persistent (otherwise, most of the weapons it deals with are persistent anyway).  I applied this to the Wuuthrad case, and while the engine didn't yell at me for filling something like "[ObjectReference <Item 24 in container  (00000014)>]" in the alias, there is never an OnContainerChange event received when I grab the weapon from the floor (where it usually ends up when it can't be placed on a rack). Thus, although displayed physically in the cell, it appears to have never left the player's inventory logically.

 

I had the idea that all this might be related to the perk that it is added to the player by the script that runs on Wuuthrad. Since the perk is removed in an OnUnequip event, I modified the activator script so as to unequip the weapon before trying to remove it ... and then it happened: while activating a rack with Wuuthrad equipped, a mysterious message displayed on the screen:

 

http://www.afkmods.com/index.php?/gallery/image/1296-mysterious-message/

 

This message is not from my script, nor from any other script I know of. As a German language message, it is extremely unlikely to be a debug message of any script at all, but I did also not find it among the Skyrim.esm message objects in the CK. The message says "Wuuthrad was not equipped", which indicates that there is a process (quest ?) running in the background which detected that the activator script did unequip it prior to removing it (or trying to do so) by running DropObject on the player.

 

Otherwise, this modification had no effect. Wuuthrad still refuses to cooperate every second time. The only difference is that this message is now always displayed, irrespective of whether it can be put on the rack or not. I'm strongly leaning towards considering this a specific bug which appears to have entirely different reasons than the ebony blade issue.

 

 

EDIT:

Oops, I used the B word again ...

Link to comment
Share on other sites

That message about not being equipped is probably an internal debug message from one of their native functions inside the engine. Which would be why you can't find it in the Papyrus source anywhere. That's assuming the German equivalent of "was not equipped" doesn't show up somewhere in Skyrim.esm. Leave off the item name to hunt for that since it would be a variable.

 

I don't know what the deal is with "[ObjectReference <Item 24 in container  (00000014)>]" either but it hardly surprises me to see strange output in debug logs. A lot of NPCs show up there as their attached script names instead of their expected base actor IDs. So you could be seeing the debug output listing the transient inventory reference instead of the actual form ID for the item.

Link to comment
Share on other sites

  • 2 weeks later...

I have been further playing around with an automatic detection of problem items, since this would enable the scripts to react to any problem items, even if not yet identified, and to become independent on form lists. During the last week, I did experiment with the following concept:

 

Before the weapon is dropped from the player's inventory, it is transferred into a test container and then back again to the player. The container has been placed in a holding cell and force filled into a specific reference alias (this makes it permanently persistent; otherwise, this method would not work with persistent references) of an item handling quest which starts running on game start.

 

The HandlePlayerItem function on  WeaponRackActivateScript has been modified as follows:

Function HandlePlayerItem (Form PlayerItem, ObjectReference TriggerMarker)

        :
        :

	scleroWRItemHandlingScript ItemHandler = scWRItemHandlingQuest As scleroWRItemHandlingScript


	ReferenceFromQuest = ItemHandler.CheckItemRef (PlayerItem)
	PlayerItemRef = PlayerRef.DropObject (PlayerItem, 1)
	
	If ReferenceFromQuest && (PlayerItemRef != ReferenceFromQuest)

                :

		PlayerItemRef = ReferenceFromQuest

		PlayerItemCharge = PlayerItemRef.GetItemCharge()
		PlayerItemHealth = PlayerItemRef.GetItemHealthPercent()
		PlayerRef.AddItem (PlayerItemRef, 1, True)

		If (ItemHandler.ItemCount < 16)
			ItemHandler.AddNewItem (Self As ObjectReference, TriggerMarker, PlayerItemRef, PlayerItemCharge, PlayerItemHealth, IsTool, 0)
		Else
			scWeaponRackWeaponDisallowedMessage.Show()
			ToggleRack (TriggerMarker, True)
		EndIf

	Else

        :
        :

EndFunction

The CheckItemRef function of the quest script transfers the weapon to the container and back to the player and returns its object reference (which is detected in an OnItemAdded event received by the container alias) to WeaponRackActivateScript. This reference is referred to as "ReferenceFromQuest". Only then, the item is dropped from the player's inventory. The reference returned by the DropObject function is referred to as "PlayerItemRef".

As we already know, DropObject returns nonsense on persistent references (but not "none", as demonstrated previously), and the test container will return "none" when the weapon is not persistent. Thus, if "ReferenceFromQuest" is not none, but also not the same as "PlayerItemRef", we almost certainly have one of the problem weapons (assuming that the test container returns something meaningful when DropObject does not).

 

Now, instead of immediately displaying the "weapon not allowed" message (this would have been wise, as the tests have shown),  I have implemented  a "fake weapon solution" for use in my Extended Racks mod: the item handling quest is called again to replace the player weapon with a dummy, drop the dummy from the player's inventory (to get its object reference, assuming that DropObject can handle the dummy) and place it on the rack, while the player's item is safely kept in a storage container. Once the dummy is grabbed by the player, a script on the dummy alias will silently exchange the items again.

 

I will omit the scripts for the dummy handling here, but you need to know what the test container and the GetItemRef function do in order to understand the debugger logs:

Scriptname scleroWRTestContainerAliasScript extends ReferenceAlias
:
:
Event OnItemAdded (Form Item, Int aiItemCount, ObjectReference ItemRef, ObjectReference akSourceContainer)

	If (akSourceContainer == PlayerRef)	
		Self.GetReference().RemoveAllItems (PlayerRef, True, True)
		(GetOwningQuest() As scleroWRItemHandlingScript).TestReference = ItemRef
	EndIf

EndEvent 
Scriptname scleroWRItemHandlingScript extends Quest

:
:

ObjectReference Function CheckItemRef (Form PlayerItem)

	Int Counter = 0

	ObjectReference TestContainer = ReferenceTestContainer.GetReference()

	TestReference = None

	PlayerRef.RemoveItem (PlayerItem, 1, True, TestContainer)

	While (TestReference == None) && (Counter < 10)
		Utility.Wait (0.1)
		Counter += 1
	EndWhile

	Return TestReference

EndFunction 

This is what I got when I tried to place the ghostblade:

 

Test container: Player item [WEAPON < (00094A2B)>] received from player; Ref = [WE100BaseLetterScript <Item 18 in container  (00000014)>]
Test container: Player item returned to player.
Item handling quest: Player item reference submitted by test container. Counter = 1
[WeaponRackActivateScript < (00102920)>]: Player item ref from quest = [WE100BaseLetterScript <Item 18 in container  (00000014)>]
[WeaponRackActivateScript < (00102920)>]: Player item dropped. Returned ref = [ObjectReference <Item 22 in container  (00000014)>]
[WeaponRackActivateScript < (00102920)>]: Player item (Ref = [WE100BaseLetterScript < (00094A2C)>]) stored in container no. 0
[WeaponRackActivateScript < (00102920)>]: Dummy item added to player's inventory.
[WeaponRackActivateScript < (00102920)>]: Dummy item dropped. Ref = [ObjectReference < (FF0020F6)>]
[WeaponRackActivateScript < (00102920)>]: Placed Item = [WEAPON < (00094A2B)>]; Ref = [ObjectReference < (FF0020F6)>]
[WeaponRackActivateScript < (00102920)>]: Trigger Marker [WeaponRackTriggerSCRIPT < (00102921)>] in empty state.
Item handling quest: Dummy item (Ref = [ObjectReference < (FF0020F6)>]) has been grabbed by the player.
Item handling quest: Dummy (Ref = [ObjectReference < (FF0020F6)>]) removed from player's inventory.
Item handling quest: Player item  returned to player.
[WeaponRackActivateScript < (00102920)>]; Leaving Item = [WEAPON < (00094A2B)>]; Ref = [ObjectReference < (FF0020F6)>]
[WeaponRackActivateScript < (00102920)>]; Mounted Item = [WEAPON < (00094A2B)>]; Ref = [ObjectReference < (FF0020F6)>]
[WeaponRackActivateScript < (00102920)>] enabled; TOC = 0; StartingItemHasBeenGrabbed = True.
 

It worked! The script detected the problem weapon and placed a dummy instead.

Why not trying it again ? That's why:

 

[WeaponRackActivateScript < (000DF577)>]: beginning to handle player item.
Item handling quest: Player item sent to test container.
Test container: Player item [WEAPON < (00094A2B)>] received from player; Ref = [ObjectReference <Item 22 in container  (00000014)>]
Test container: Player item returned to player.
Item handling quest: Player item reference submitted by test container. Counter = 1
[WeaponRackActivateScript < (000DF577)>]: Player item ref from quest = [ObjectReference <None>]
[WeaponRackActivateScript < (000DF577)>]: Player item dropped. Returned ref = [ObjectReference <Item 22 in container  (00000014)>]
[WeaponRackActivateScript < (000DF577)>]: Player item (Ref = [ObjectReference <None>]) stored in container no. 0
[WeaponRackActivateScript < (000DF577)>]: Dummy item added to player's inventory.
[WeaponRackActivateScript < (000DF577)>]: Dummy item dropped. Ref = None

 

The ghostblade went into nirvana. Then, my papyrus log exploded with 700kB of friendly reminders about what papyrus cannot do with "none" references.
I have tried this a countless number of times with various racks in different places: it always worked once and then never again. Same with the ebony blade, by the way, but you should have a look at the logs nonetheless. While some of the references returned while handling the ghostblade were somewhat particular, to say the least, the output for the ebony blade was totally insane. Note the line in bold (the result of grabbing the dummy from the rack):

 

[WeaponRackActivateScript < (00102BDA)>]: beginning to handle player item.
Item handling quest: Player item sent to test container.
Test container: Player item [WEAPON < (0004A38F)>] received from player; Ref = [DefaultAddToLinkonLoadSCRIPT <Item 9 in container  (00000014)>]
Test container: Player item returned to player.
Item handling quest: Player item reference submitted by test container. Counter = 1
[WeaponRackActivateScript < (00102BDA)>]: Player item ref from quest = [DefaultAddToLinkonLoadSCRIPT <Item 9 in container  (00000014)>]
[WeaponRackActivateScript < (00102BDA)>]: Player item dropped. Returned ref = [ObjectReference <Item 22 in container  (00000014)>]
[WeaponRackActivateScript < (00102BDA)>]: Player item charge via dropped item = 0.000000
[WeaponRackActivateScript < (00102BDA)>]: Player item charge via reference from quest = 0.000000
[WeaponRackActivateScript < (00102BDA)>]: Player item health via dropped item = 0.000000
[WeaponRackActivateScript < (00102BDA)>]: Player item health via reference from quest = 1.300000
[WeaponRackActivateScript < (00102BDA)>]: Player item (Ref = [DefaultAddToLinkonLoadSCRIPT < (0004A39D)>]) stored in container no. 0
[WeaponRackActivateScript < (00102BDA)>]: Dummy item added to player's inventory.
[WeaponRackActivateScript < (00102BDA)>]: Dummy item dropped. Ref = [DA08EbonyBladeScript < (FF001AA4)>]
[WeaponRackActivateScript < (00102BDA)>]: Player item attributes copied to dummy item (Charge = 0.000000; Health = 1.300000)
[WeaponRackActivateScript < (00102BDA)>]: Placed Item = [WEAPON < (0004A38F)>]; Ref = [DA08EbonyBladeScript < (FF001AA4)>]
[WeaponRackActivateScript < (00102BDA)>]: Trigger Marker [WeaponRackTriggerSCRIPT < (00102BDB)>] in empty state.
Item handling quest: Dummy item (Ref = [ObjectReference < (FF001AA4)>]) has been grabbed by the player.
Item handling quest: Dummy (Ref = [ObjectReference < (FF001AA4)>]) removed from player's inventory.
Item handling quest: Player item  returned to player.

[WeaponRackActivateScript < (00102BDA)>]; Leaving Item = [WEAPON < (0004A38F)>]; Ref = [ObjectReference < (FF001AA4)>]
[WeaponRackActivateScript < (00102BDA)>]; Mounted Item = None; Ref = [DA08EbonyBladeScript <None>]
[WeaponRackActivateScript < (00102BDA)>]; Starting Item = [WEAPON < (00012EB7)>]; Ref = [ObjectReference < (00102BDC)>]
[WeaponRackActivateScript < (00102BDA)>] enabled; TOC = 0; StartingItemHasBeenGrabbed = True.
[WeaponRackActivateScript < (00102BDA)>]: Trigger Marker [WeaponRackTriggerSCRIPT < (00102BDB)>] in 'activator busy' state.
[WeaponRackActivateScript < (00102BDA)>]: beginning to handle player item.
Item handling quest: Player item sent to test container.
Test container: Player item [WEAPON < (0004A38F)>] received from player; Ref = [ObjectReference <Item 22 in container  (00000014)>]
Test container: Player item returned to player.
Item handling quest: Player item reference submitted by test container. Counter = 1
[WeaponRackActivateScript < (00102BDA)>]: Player item ref from quest = [ObjectReference <None>]
[WeaponRackActivateScript < (00102BDA)>]: Player item dropped. Returned ref = [ObjectReference <Item 22 in container  (00000014)>]
[WeaponRackActivateScript < (00102BDA)>]: Player item charge via dropped item = 0.000000
[WeaponRackActivateScript < (00102BDA)>]: Player item charge via reference from quest = 0.000000
[WeaponRackActivateScript < (00102BDA)>]: Player item health via dropped item = 0.000000
[WeaponRackActivateScript < (00102BDA)>]: Player item health via reference from quest = 0.000000
[WeaponRackActivateScript < (00102BDA)>]: Player item (Ref = [ObjectReference <None>]) stored in container no. 0
[WeaponRackActivateScript < (00102BDA)>]: Dummy item added to player's inventory.
[WeaponRackActivateScript < (00102BDA)>]: Dummy item dropped. Ref = None
 

I'm going to give up here. There's nothing what I didn't already try, so we have to live with those critters never embellishing a rack in Skyrim.

 

On the other hand, the method of detecting a problem weapon has been proven to work. Even if the logs mostly printed nonsensical references, the scripts were always able to use them to return the proper items to the player. Therefore, I consider this a worthwhile addition to the script.

 

EDIT:

Tons of spelling errors

Link to comment
Share on other sites

  • 4 weeks later...

 Quick question - I'm seeing a number of "Weapon Rack processed. Object state was: TRUE" lines in the logs. Are these an oversight or intentional? If they're an oversight shall I create a Tracdown issue for them?

Link to comment
Share on other sites

 Quick question - I'm seeing a number of "Weapon Rack processed. Object state was: TRUE" lines in the logs. Are these an oversight or intentional? If they're an oversight shall I create a Tracdown issue for them?

 

These are from Hearthfires's BYOHDivertPrefabsScript, which checks on cell attach of certain player home cells whether certain racks are occupied with a weapon or not ("True" means occupied, "False" means empty). This is to make sure that weapons on racks are not forgotten to be handled when the racks have to be enabled or disabled (ususally, when the player buys a child's bedroom or switches back to the original decorations later).

 

The messages you see are debug messages that should be silenced.

Link to comment
Share on other sites

It does not run on the Hearthfires homes (there's nothing to get disabled before you construct the child's beds). It runs on the vanilla homes.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...