Jump to content

Overhauling the weapon rack scripts


Sclerocephalus

Recommended Posts

After another test run (no crossbows this time), I stiil get the following message of a shield rack in Hjerim starting with TOC = 2:

[WeaponRackActivateScript < (00102914)>] disabled on cell attach; Trigger marker in empty state; PlacedItemInit = False; TOC = 2

After taking the shield from the rack, TOC went back to 1, which was quite surprising because the left and right plaques are filled with identical weapons in identical positions relative to the trigger. Hence, they aren't the culprits and the trigger is basically working.

 

I went to Hjerim again and emptied the rack, but TOC remained at 1 as expected.

 

I suspected the open lid of the display case placed in front of and below the shield rack, but there's no way that it swings through the trigger volume. Also, I couldn't have activated the rack with the lid open.

 

It seems thus that the shield trigger requires some more tweaking.

 

 

Logics error!

 

When I have made sure that no weapon on adjacent racks overlaps with the trigger (I have!), an OnTriggerLeave event must be related to the mounted weapon and it is of pretty much no interest whether or not there other objects in the trigger zone - unless they are not permanently there, of course. The latter can be checked with a "settled TOC" (as posted earlier).

 

Gonna try this now.

Link to comment
Share on other sites

And even better: the weapons mounted on either side which previously did always interfere have stopped doing so.

 

I'd say that's a great improvement for the sake of getting new racks initialized faster, but let's not assume that there will never be any interference again. I think the script logic should still expect that cross-activation of triggers is always a possibility, because even if it seems like you've solved it for now, all it takes is some user weapon mod with big fat meshes and then they'll once again intersect your modified shield triggers, right?

Link to comment
Share on other sites

If you have specific issues with the carriages, that would be a good point to start: just reduce the value stepwise and see what happens.

The problems with new races and new garb that modders have created -- and/or previously non-playable races that weren't intended to ride carriages -- and a vanilla wedding dress. So beyond my control.

 

an OnTriggerLeave event must be related to the mounted weapon and it is of pretty much no interest whether or not there other objects in the trigger zone

That must be a change in the code, because the current code allowed triggers from anything. So a nearby object should be able to activate the trigger, because that might clear the activator after removal of a nearby object.

 

I'm going to support Talenden on this, don't try to outsmart the software. Always assume the game engine and/or various hardware objects can be wrong.

Link to comment
Share on other sites

Well, for each of those objref pointers, if IsDeleted() then they are in a container or inventory or destroyed, so they can't still be mounted. And if not IsDeleted(), then GetParentCell() should reliably tell whether they're still nearby. The only problem that remains then is if the player grabs a pre-placed item and drops it on the ground in the same cell, or mounts it on a different rack in the same cell. But I wonder if you could just use GetDistance() to handle that? If the item is still on the same rack, that should be close to 0. If it fell off on the ground it might be a little higher, but it seems to me it wouldn't be that bad to just leave it there and call it non-mounted, and let he player pick it up and re-mount it once if they want to; after doing that, it should stick no problem since PlayersDroppedItem/PlacedItemInit will be set.

 

To come back to this issue:

  • GetDistance proved to be not really reliable to discern whether an item fell of or was deliberately moved to another place: (1) weapons falling from a rack above a bed (e.g. the single-weapon plaque above the bed in Honeyside or the CoA rack above the bed in Breezehome) get "havok-reflected" (for lack of a better term since it has not much to do with real-world physics) on the bed and can end up everywhere in the room. (2) NPCs walking by can kick any weapon all over the place at any time.
  • GetDistance could be used however for most racks to detect whether it is still on the rack. On the other hand, this doesn't really bring us much forward, because both mounted and fallen off items need to be handled, so we would still have to check the latter separately.
  • According to your tests, IsDeleted() should return true when the item is in a container/inventory and therefore is a worthwhile addition to the checks in InitActivator(), although it won't catch many situations: at a level where you can buy your own house including decorations, you have no real need for crappy iron swords and likely won't keep them in a chest or carry them around with you.
  • Thus, at the moment I favor the IsDeleted()/GetParentCell() solution.
Link to comment
Share on other sites

 

To come back to this issue:

  • GetDistance proved to be not really reliable to discern whether an item fell of or was deliberately moved to another place: (1) weapons falling from a rack above a bed (e.g. the single-weapon plaque above the bed in Honeyside or the CoA rack above the bed in Breezehome) get "havok-reflected" (for lack of a better term since it has not much to do with real-world physics) on the bed and can end up everywhere in the room. (2) NPCs walking by can kick any weapon all over the place at any time.
  • GetDistance could be used however for most racks to detect whether it is still on the rack. On the other hand, this doesn't really bring us much forward, because both mounted and fallen off items need to be handled, so we would still have to check the latter separately.
  • According to your tests, IsDeleted() should return true when the item is in a container/inventory and therefore is a worthwhile addition to the checks in InitActivator(), although it won't catch many situations: at a level where you can buy your own house including decorations, you have no real need for crappy iron swords and likely won't keep them in a chest or carry them around with you.
  • Thus, at the moment I favor the IsDeleted()/GetParentCell() solution.

 

True, things bounce a lot under havok, so after something falls off I'm sure it's too far away to know anymore whether it should still be on the rack or not. I guess I was thinking that it's fine to just assume that they're not on the rack, because that should only happen if people start a game without the patch, go near a rack so its items fall off, and then load the patch afterward. In that case I think it's fair to say that we can't retroactively fix the fallen items, but they only have to pick them up and re-mount one time and then they'll stick after that. On the other hand, if the script is processing a new rack which does not yet have PlacedItemInit set, and if StartingItem is ~0 distance from the mount point, then we can probably safely say that the item should be mounted even if PlayersDroppedWeapon is not set. But I don't know if that situation ever comes up (PlacedItemInit=False, PlayersDroppedWeapon=None, StartingItem.GetDistance()~=0) so maybe the distance test isn't useful for anything.

 

Also, I think IsDeleted() is true whenever the item is not directly in the world somewhere. So it doesn't only tell you if it's in the player's chest, it also tells you if the player picked it up and sold it to a merchant (so its in the merchant's chest), or destroyed it in crafting (meltdown). Basically if IsDeleted() then I think there is zero chance it is supposed to be on the rack and fell; if IsDeleted() then the rack should forget about it entirely.

Link to comment
Share on other sites

That must be a change in the code, because the current code allowed triggers from anything. So a nearby object should be able to activate the trigger, because that might clear the activator after removal of a nearby object.

 

That's why we actually do all the checks in the trigger script: preventing to clear the activator when the event was triggered by an unrelated object. And the code has not changed, of course: the event can still be triggered by any object.

 

But this object has to move (or disappear from the trigger zone suddenly, e.g. when it is disabled). A static object does not trigger an event. A weapon that is grabbed by the player "disappears" from the trigger zone when it is grabbed: the world model is deleted and the object reference is added to the player's inventory. You can observe this in 3rd person view when you grab something: the object is suddenly gone. That means that a grabbed item only triggers OnTriggerLeave events, and only for the racks on which it is mounted and with which it overlaps. That also makes grabbing items from a rack much easier to monitor with the trigger script than items moved onto a rack: while mounting, the item is not disabled and can trigger a vast number of both OnTriggerLeave and OnTriggerEnter events while it is translated and rotated to its final position (and that's the reason why the trigger has to stay inactive while the activator script handles the item placement).

 

Imagine I could guarantee that the item that triggers an OnTriggerLeave is the item that was mounted on the respective rack and cannot be anything else. In this case, I don't have to care about any unrelated static objects in the trigger zone (because I guarantee in turn that they did not trigger the event). Moreover, I also wouldn't have to care about the trigger object count: when I can guarantee that the leaving item is the item that was mounted, monitoring the trigger object count becomes rather pointless.

 

In other words: this would save us all condition checks on the trigger script and would solve the problem with implementing Taleden's item handling procedure which precludes those condition checks explicitely. So I thought it would be worthwhile trying to make this possible. In fact, the UpdateActivatorState function of the trigger script I'm presently experimenting with looks as follows (don't know whether I will really need the SettledTOC function: this is supposed to help filter unrelated moving objects, but it is still rather experimental, as the rest of the script):

Function UpdateActivatorState (ObjectReference LeavingItem)

;This function does the handling of valid OnTriggerLeave events (i.e. OnTriggerLeave events received while the trigger is in the empty state). Valid OnLeave events
;are attributed to items that have been grabbed from the rack. It updates the properties on the activator script, i.e. resets PlayersDroppedWeapon to "none" and marks
;any pre-placed items as grabbed.


	Int TOC = GetTriggerObjectCount()

	ObjectReference ActivatorRef = GetLinkedRef (WRackActivator)

	WeaponRackActivateScript ActivatorScript = (ActivatorRef As WeaponRackActivateScript)


	If (TOC == SettledTOC())

		ActivatorScript.PlayersDroppedWeapon = None
		ActivatorScript.PlacedItemInit = True
		ActivatorScript.StartingItemHasBeenGrabbed = True
		;When an item is grabbed from the rack, StartingItemHasBeenGrabbed must be true, no matter whether or not the grabbed item was the starting item.

		GoToState ("ActivatorBusy")
		ActivatorRef.Enable()

		Debug.TraceUser ("WeaponRackLog", ActivatorRef + " enabled; Trigger in 'ActivatorBusy' state; TOC = " + TOC)

	Else
		Debug.TraceUser ("WeaponRackLog", ActivatorRef + " remains disabled; TOC = " + TOC)
	EndIf

EndFunction 

;----------------------------------------------------------------------------------------------------------------------------------------------------------------------

Int Function SettledTOC()

	Utility.Wait (0.1)
	Return GetTriggerObjectCount()

EndFunction 

The concept is simple: I'm reshaping the trigger volumes so as to make overlaps with weapons on adjacent racks impossible. The following is the side view of a fully occupied CoA rack in the CK. I have moved the items roughly in the same distance to the rack as they would be placed in the game:

 

CoARack Collision

 

All weapons mounted on the left and right plaques are mounted behind the shield, so I only had to make sure that the trigger volume does not extent beyond the edge of the iron shield (when mounted, this is the closest to the rack).

 

 

I'm going to support Talenden on this, don't try to outsmart the software. Always assume the game engine and/or various hardware objects can be wrong.

 

That's why I will be testing it extensively. How wrong the engine can be is something I had to learn the hard way (did I already mention that it has problems with the inflection points of sine and cosine functions ?)

Link to comment
Share on other sites

The concept is simple: I'm reshaping the trigger volumes so as to make overlaps with weapons on adjacent racks impossible.

 

I think this is the part that I'm concerned about. If overlaps (that is, cross-activation of triggers) were really IMPOSSIBLE, then all of your other reasoning holds, and as soon as the trigger gets OnTriggerLeave(), you could safely say that the placed item must have been grabbed and therefore we can clear the PlayersDroppedWeapon pointer and re-enable the activator.

 

But I am not convinced that overlaps will ever be IMPOSSIBLE. Maybe your edited trigger volumes make them stop happening with the particular weapons and shields you're testing with, but what if a mod adds a weapon or shield with a bigger mesh? Or what if the player "grabs" something on the ground and waves it around near the rack, trying to position it manually? What if an NPC kicks a goblet across the room and it bounces off the mounted shield? All of these would generate spurious OnTriggerLeave events, wouldn't they?

 

Then not only does your reasoning not hold anymore, but it fails in a pretty bad way: the activator gets re-enabled when something is still key-framed on the rack, and now it's very hard or impossible for the player to retrieve that item that's hidden behind the activator volume.

 

So I'm not necessarily opposed to your improved trigger meshes (as long as we can guarantee that a mounted item will always intersect it, no matter what the mounted item's mesh looks like, within reason), but I think the OnTriggerLeave handler still needs to be very cautious and defensive, and not just assume that the event must have been caused by the mounted item, just because the trigger volume is nice and small now.

 

EDIT:

 

In other words: this would save us all condition checks on the trigger script and would solve the problem with implementing Taleden's item handling procedure which precludes those condition checks explicitely.

 

I see your point there, but remember that the scripted item fix does not preclude ALL condition checks in the OnTriggerLeave handler. It is still safe to compare the leaving pointer to the stored/mounted pointer, and I think that comparison will be reliable as long as the handler isn't so slow that the player has a chance to open their inventory and drop the item back out again.

 

The only thing my fix explicitly precludes is calling methods on the leaving pointer such as GetBaseObject(), but the only thing that was used for was to skip one line of code in the case of non-weapon/armor (i.e. the kicked goblet), and to more quickly get shield racks initialized. Those things are nice but I don't think they're crucial.

Link to comment
Share on other sites

True, things bounce a lot under havok, so after something falls off I'm sure it's too far away to know anymore whether it should still be on the rack or not. I guess I was thinking that it's fine to just assume that they're not on the rack, because that should only happen if people start a game without the patch, go near a rack so its items fall off, and then load the patch afterward. In that case I think it's fair to say that we can't retroactively fix the fallen items ....

I have tested an old game where I walked from Unbound Helgen cave to Riverwood with Hadvar, and the items are not on the rack. My current scripts load them on the rack after going into any building. So yes, we can retroactively fix the fallen items.

 

Also, I think IsDeleted() is true whenever the item is not directly in the world somewhere. So it doesn't only tell you if it's in the player's chest, it also tells you if the player picked it up and sold it to a merchant (so its in the merchant's chest), or destroyed it in crafting (meltdown). Basically if IsDeleted() then I think there is zero chance it is supposed to be on the rack and fell; if IsDeleted() then the rack should forget about it entirely.

It's hard to tell for sure, but in my limited testing of selling items off Alvor's rack, isDeleted seems to be working fine. Although I posted my current code, but here it is again with a tiny efficiency improvement:

ObjectReference StartingItem = GetLinkedRef()
If !StartingItemHasBeenGrabbed && StartingItem && StartingItem.IsEnabled()

	If (PlacedItemInit && (PlayersDroppedWeapon != StartingItem))
		; has been previously placed, so repair mismatch
		StartingItemHasBeenGrabbed = True
		Trace (Self + "ActivatorSetup() not starting item; set StartingItemHasBeenGrabbed = True.")

		If PlayersDroppedWeapon
			PlaceItem(PlayersDroppedWeapon)
		EndIf

	ElseIf StartingItem.IsDeleted()
		StartingItemHasBeenGrabbed = True
		Trace (Self + "ActivatorSetup() deleted item; set StartingItemHasBeenGrabbed = True.")
	Else
		Cell parentCell = StartingItem.GetParentCell()
		If parentCell && parentCell == GetParentCell() && CheckFor3D (StartingItem)
			HandleStartingItem (StartingItem)
		EndIf
	EndIf

ElseIf PlayersDroppedWeapon
	PlaceItem(PlayersDroppedWeapon)
EndIf

In other words: this would save us all condition checks on the trigger script and would solve the problem with implementing Taleden's item handling procedure which precludes those condition checks explicitely. So I thought it would be worthwhile trying to make this possible. In fact, the UpdateActivatorState function of the trigger script I'm presently experimenting with looks as follows (don't know whether I will really need the SettledTOC function: this is supposed to help filter unrelated moving objects, but it is still rather experimental, as the rest of the script):

I'd already added a wait similar to your SettledTOC function. Much longer, as I'm awaiting settling all the adjacent racks on the same wall. There's no guarantee that the racks settle in any order. This also puts a limit on how fast the player can re-trigger a rack, which I believe is desirable.

;/
; Give everything a chance to settle before measuring
/;
Utility.Wait (1.0)
int iTOC = TriggerMarker.GetTriggerObjectCount()

If PlacedItemInit
	If (PlayersDroppedWeapon == None)
		EnableNoWait()
		Trace (Self + "ActivatorSetup() enabled; PlacedItemInit = True; TOC = " + iTOC)
	Else
		DisableNoWait()
		(TriggerMarker as WeaponRackTriggerSCRIPT).ActivatorDone ("ActivatorSetup")
		Trace (Self + "ActivatorSetup() disabled; PlacedItemInit = True; TOC = " + iTOC)
	EndIf
ElseIf (iTOC == 0)
	PlayersDroppedWeapon = None
	PlacedItemInit = True
	EnableNoWait()
	Trace (Self + "ActivatorSetup() enabled; PlacedItemInit = True; TOC = Zero")
Else
	; legacy/unused blocked racks. any obstruction(s) must be removed to use rack.
	DisableNoWait()
	(TriggerMarker as WeaponRackTriggerSCRIPT).ActivatorDone ("ActivatorSetup")
	Trace (Self + "ActivatorSetup() disabled; PlacedItemInit = False; TOC = " + iTOC)
EndIf

TriggerMarker = NONE
 

Then not only does your reasoning not hold anymore, but it fails in a pretty bad way: the activator gets re-enabled when something is still key-framed on the rack, and now it's very hard or impossible for the player to retrieve that item that's hidden behind the activator volume.

Strongly agreed. No single point of failures, please.

 

So I'm not necessarily opposed to your improved trigger meshes (as long as we can guarantee that a mounted item will always intersect it, no matter what the mounted item's mesh looks like, within reason), but I think the OnTriggerLeave handler still needs to be very cautious and defensive, and not just assume that the event must have been caused by the mounted item, just because the trigger volume is nice and small now.

I'm in full agreement here. Well said.

Do you have even 1 rack ready for us to test? I've been waiting for your meshes before testing Teleden's fix.

 

The only thing my fix explicitly precludes is calling methods on the leaving pointer such as GetBaseObject(), but the only thing that was used for was to skip one line of code in the case of non-weapon/armor (i.e. the kicked goblet), and to more quickly get shield racks initialized. Those things are nice but I don't think they're crucial.

In fact, that's called "premature optimization" in the industry.
Link to comment
Share on other sites

I think this is the part that I'm concerned about. If overlaps (that is, cross-activation of triggers) were really IMPOSSIBLE, then all of your other reasoning holds, and as soon as the trigger gets OnTriggerLeave(), you could safely say that the placed item must have been grabbed and therefore we can clear the PlayersDroppedWeapon pointer and re-enable the activator.

 

But I am not convinced that overlaps will ever be IMPOSSIBLE. Maybe your edited trigger volumes make them stop happening with the particular weapons and shields you're testing with, but what if a mod adds a weapon or shield with a bigger mesh? Or what if the player "grabs" something on the ground and waves it around near the rack, trying to position it manually? What if an NPC kicks a goblet across the room and it bounces off the mounted shield? All of these would generate spurious OnTriggerLeave events, wouldn't they?

 

Then not only does your reasoning not hold anymore, but it fails in a pretty bad way: the activator gets re-enabled when something is still key-framed on the rack, and now it's very hard or impossible for the player to retrieve that item that's hidden behind the activator volume.

 

So I'm not necessarily opposed to your improved trigger meshes (as long as we can guarantee that a mounted item will always intersect it, no matter what the mounted item's mesh looks like, within reason), but I think the OnTriggerLeave handler still needs to be very cautious and defensive, and not just assume that the event must have been caused by the mounted item, just because the trigger volume is nice and small now.

 

Basically, nothing is impossible. But even then, some things are more likely to be impossible than others.

So let's have a look at what it would take for the racks to fail:

 

(1) To make the shield rack fail, the shield would have to have a diameter more than twice as large as that of an iron shield, plus a design with a short handle that would make the player's arm clip through it when held in combat. With a proper handle design, the shield can be as large as the room the rack is in, or even larger, without ever overlapping with the left and right plaque triggers - unless it would be mounted at an angle that makes it clip through the weapons and into the wall (impossible without an edit to the nodes). I'd bet that this is unlikely to happen as such an item is entirely useless in the game, unless somebody's doing it on purpose to make the rack glitch out deliberately.

(2) On a standard wooden rack, the item would have to have a width 1.8 times as wide as the rack - at the handle! (because it is the handle where all items intersect with the trigger). Dimensions of the blade or hammer head can be as large as the room the rack is in (or even larger) without ever intersecting a neighbouring trigger - unless the item would be mounted in a horizontal position so as to deliberatley intersect with all slots (impossible without a mesh edit, but even then, translation data on the nodes would have to be adjusted very carefully to obtain this effect). Again, an item of those dimensions would have no use in the game, and any edit to the meshes would be a deliberate override of the USKP fixes anyway.

(3) On the single-weapon plaque, the item would have to be mounted perpendicular to the plaque's axis to intersect with the trigger of a plaque mounted above or below (again, impossible without a mesh edit).

 

Thus, I think that I have given ample scope to size and shape variations that the meshes would be able to support, and that everything beyond this does not need to be seriously taken care of. If someone's after provoking a deliberate malfunction, there would be no way to stop him anyway , and everybody else would rarely ever come in situations where the racks are stretched to their limits.

Link to comment
Share on other sites

I'd already added a wait similar to your SettledTOC function. Much longer, as I'm awaiting settling all the adjacent racks on the same wall. There's no guarantee that the racks settle in any order. This also puts a limit on how fast the player can re-trigger a rack, which I believe is desirable.

;/
; Give everything a chance to settle before measuring
/;
Utility.Wait (1.0)
int iTOC = TriggerMarker.GetTriggerObjectCount()

If PlacedItemInit
	If (PlayersDroppedWeapon == None)
		EnableNoWait()
		Trace (Self + "ActivatorSetup() enabled; PlacedItemInit = True; TOC = " + iTOC)
	Else
		DisableNoWait()
		(TriggerMarker as WeaponRackTriggerSCRIPT).ActivatorDone ("ActivatorSetup")
		Trace (Self + "ActivatorSetup() disabled; PlacedItemInit = True; TOC = " + iTOC)
	EndIf
ElseIf (iTOC == 0)
	PlayersDroppedWeapon = None
	PlacedItemInit = True
	EnableNoWait()
	Trace (Self + "ActivatorSetup() enabled; PlacedItemInit = True; TOC = Zero")
Else
	; legacy/unused blocked racks. any obstruction(s) must be removed to use rack.
	DisableNoWait()
	(TriggerMarker as WeaponRackTriggerSCRIPT).ActivatorDone ("ActivatorSetup")
	Trace (Self + "ActivatorSetup() disabled; PlacedItemInit = False; TOC = " + iTOC)
EndIf

TriggerMarker = NONE
 

 

A small side note: you can't disable lone scripts. Some of them are attached to triggers (so you would disable the trigger), and all the player-home triggers are enable-parented ... but it's also exclusively the player-home triggers which have those out-of-place scripts attached  (otherwise I wouldn't even have considered all the trouble with the states).

 

 

 Strongly agreed. No single point of failures, please.

 

Do you have even 1 rack ready for us to test? I've been waiting for your meshes before testing Teleden's fix.

 

No reason to worry. The log I have posted two days ago has all the important details: all racks I tested in a short run through Breezehome and Hjerim had TOC = 1 when occupied and TOC = 0 when empty. I tested with various shields, crossbows (not oversized but the worst cross-activators ever) and an oversized 2H axe from the JaySuS Swords mod (a fine test object since it will trigger two wooden rack slots at once).

 

One exception so far, but this is placement-related since the same racks in other places do work as expected.

 

Unless they aren't shape-optimized and tested for possible failures, I wouldn't be happy to leave you anything. In return, you can be sure that they will work properly in all situations once that is done (this should be a fair compensation for waiting another 1-2 days). In the meantime, I will submit you the code that needs to be added to access the new crossbow nodes:

 

Crossbow Nodes

Link to comment
Share on other sites

Proposal: eliminate misnamed StartingItemHasBeenGrabbed.

First of all, it's an indication that any item has been grabbed, not just starting items. Badly named.

That is, it's an indication the rack is empty. But we already have an indication the rack is empty and ready to be filled: isEnabled().

Finally, it is used in exactly 1 spot, to skip mounting starting items.

If !StartingItemHasBeenGrabbed && StartingItem && StartingItem.IsEnabled()

	If (PlacedItemInit && (PlayersDroppedWeapon != StartingItem))

But there are only 2 cases:

  1. there is no starting item.
    • That is handled by the next test "&& StartingItem".
  2. there is a starting item, but it has been removed or replaced by another item.
    • That is handled by the "PlayersDroppedWeapon != StartingItem" test.
      1. If removed, PlayersDroppedWeapon == None.
      2. If replaced, PlayersDroppedWeapon != None.
    • Very simple. Completely compatible with vanilla and USKP 1.3.3 and USKP 2.0.0.

In fact, PlacedItemInit is useless here, too. It should be replaced with "PlayersDroppedWeapon &&" at this location. Vanilla games, or locations entered before USKP 1.3.3 will fail the PlacedItemInit test, but pass the None test.

Link to comment
Share on other sites

I have tested an old game where I walked from Unbound Helgen cave to Riverwood with Hadvar, and the items are not on the rack. My current scripts load them on the rack after going into any building. So yes, we can retroactively fix the fallen items.

 

Is there any previous version of these scripts, including any vanilla iterations and any previous USKP versions, where an item could be keyframed on the rack but PlayersDroppedWeapon no longer points to it? Or is there anything that can cause the script properties to reset but leave an item keyframed in the cell? I can't remember what scenario I was thinking about when I said retroactively re-mounting the starting item might not be safe, and that's all I can come up with at the moment. But if that situation never arises then sure, I guess I see no harm in re-mounting StartingItem if it's still in the cell somewhere.

 

(because it is the handle where all items intersect with the trigger)

 

I'll go back and try to reproduce this with the current USKP 2.0.0 scripts, but I remember noticing a few times that it was possible to get a weapon to mount at the wrong angle. I think what I did was I mounted a sword (it was lined up correctly at this point), then picked it off the rack again, dropped it on the ground, z-key "grabbed" it and waved it around a little, let it fall on the ground again, then picked it up and mounted it. That second time, the angle was wrong, as if something about its rotation from being bounced around earlier had been retained and the model was therefore still offset from its "anchor" point (the point whose coordinates are set by MoveTo, I guess).

 

So if that's fixable, then maybe your tiny little handle-only triggers will be reliable, but otherwise I'm still worried about things being mounted askew and throwing off your expectations.

Link to comment
Share on other sites

A small side note: you can't disable lone scripts. Some of them are attached to triggers (so you would disable the trigger), and all the player-home triggers are enable-parented ... but it's also exclusively the player-home triggers which have those out-of-place scripts attached  (otherwise I wouldn't even have considered all the trouble with the states).

The code you cited does not disable a "script" -- it disables the ObjectReference the script is placed upon. In this case, the activator.

 

Those "Self." that you often place on your Enables and Disables is entirely unneeded.

 

In fact, that code is functionally identical to yours. If mine fails, yours fails.

Link to comment
Share on other sites

Is there any previous version of these scripts, including any vanilla iterations and any previous USKP versions, where an item could be keyframed on the rack but PlayersDroppedWeapon no longer points to it? Or is there anything that can cause the script properties to reset but leave an item keyframed in the cell?

Assuming you're talking about non-starting items. Certainly not that I've seen.

Sclero could speak as to anything he did in 1.3.2 -- since those scripts were removed and replaced with

Scriptname USKPWeaponRackHelperAliasScript extends ReferenceAlias

;This script is defunct.

Scriptname USKPWeaponRackHelperQuestScript extends Quest

;This script is defunct.

BTW, that's where I'd put your new scripts.... someday! :)

 

I can't remember what scenario I was thinking about when I said retroactively re-mounting the starting item might not be safe, and that's all I can come up with at the moment. But if that situation never arises then sure, I guess I see no harm in re-mounting StartingItem if it's still in the cell somewhere.

However, I don't see the harm in the case of starting items -- they will move into their new positions, and the PlayersDroppedWeapon will be re-filled.

In fact, that's how the game starts. Starting items are hanging in the air next to the racks, the pointers are empty, and they are filled during OnAttach() -- or OnLoad() in 2.0.0.

Link to comment
Share on other sites

However, I don't see the harm in the case of starting items -- they will move into their new positions, and the PlayersDroppedWeapon will be re-filled.

In fact, that's how the game starts. Starting items are hanging in the air next to the racks, the pointers are empty, and they are filled during OnAttach() -- or OnLoad() in 2.0.0.

 

Right -- my point was that if a player-placed item is still key-framed in that same spot when this happens, and we just don't know about it anymore because PlayersDroppedWeapon was cleared, then when you re-mount the starting item it will mask the previous item. Worse, when the new item is grabbed, it will re-enable the activator which will still mask the previous item.

 

But again, this is a contrived edge-case, I've just been trying to come up with possible error scenarios so that we can be sure to prepare for them. If you guys don't think that can possibly happen, and nobody has seen it in testing, then I'm satisfied.

Link to comment
Share on other sites

Proposal: eliminate misnamed StartingItemHasBeenGrabbed.

First of all, it's an indication that any item has been grabbed, not just starting items. Badly named.

That is, it's an indication the rack is empty. But we already have an indication the rack is empty and ready to be filled: isEnabled().

Finally, it is used in exactly 1 spot, to skip mounting starting items.

If !StartingItemHasBeenGrabbed && StartingItem && StartingItem.IsEnabled()

	If (PlacedItemInit && (PlayersDroppedWeapon != StartingItem))

But there are only 2 cases:

  1. there is no starting item.
    • That is handled by the next test "&& StartingItem".
  2. there is a starting item, but it has been removed or replaced by another item.
    • That is handled by the "PlayersDroppedWeapon != StartingItem" test.
      1. If removed, PlayersDroppedWeapon == None.
      2. If replaced, PlayersDroppedWeapon != None.
    • Very simple. Completely compatible with vanilla and USKP 1.3.3 and USKP 2.0.0.

In fact, PlacedItemInit is useless here, too. It should be replaced with "PlayersDroppedWeapon &&" at this location. Vanilla games, or locations entered before USKP 1.3.3 will fail the PlacedItemInit test, but pass the None test.

 

(1) Agreed. StartingItemHasBeenGrabbed can go.

 

 

(2) There is a third case, and it is very common:

 

There is a starting item, and it is still in place: This is the case in all interiors that are not player homes and in most exteriors, e.g. the Warmaiden's, Alvor's shop, the western watchtower, etc. (to name just a few, actually this is the common case):

 

Unless you steal them all at once, they will be permanently there. And even if you take them, they are replaced upon cell reset.

 

 

(3) After failing the PlacedItemInit test, they do not all pass the none test, because there is no guarantee that a rack is empty when you upgrade from vanilla (how should it ?). With the vanilla meshes, the TOC = 0 check caught only a minority of racks, because even on empty racks, TOC was rarely zero, due to the various problems with the triggers. This will be significantly better with the new meshes, but even if we know that there's an item on the rack, we do not know which one. Thus PlayersDroppedWeapon should remain uninitialized at this point. Now, because there's no third "kind of value" other than "something" (i.e. an arbitrary object reference) and "none", we still need a property to reflect that we are uncertain about PlayersDroppedWeapon. As long as we are, the (PlayersDroppedWeapon != StartingItem) check has to be skipped.

Link to comment
Share on other sites

In this case, the activator.

 

This would be true when all activator scripts were running on activators, but they don't. Some are running on triggers (sic! - they were set up that way by Bethesda - I know it makes no sense, but they did it). Please check the formIDs 0010291A and 0010291B: those are triggers with a WeaponRackActivateScript attached, and they are enable-parented. USKP removes the misplaced scripts, but this fix is not retroactive.

Link to comment
Share on other sites

Assuming you're talking about non-starting items. Certainly not that I've seen.

Sclero could speak as to anything he did in 1.3.2 -- since those scripts were removed and replaced with

Scriptname USKPWeaponRackHelperAliasScript extends ReferenceAlias

;This script is defunct.

Scriptname USKPWeaponRackHelperQuestScript extends Quest

;This script is defunct.

BTW, that's where I'd put your new scripts.... someday! :)

 

However, I don't see the harm in the case of starting items -- they will move into their new positions, and the PlayersDroppedWeapon will be re-filled.

In fact, that's how the game starts. Starting items are hanging in the air next to the racks, the pointers are empty, and they are filled during OnAttach() -- or OnLoad() in 2.0.0.

 

This had nothing to do with my scripts. Arthmoor got them from LukeH who had a fix mod on Steam that claimed to solve the ebony blade problem and integrated them in USKP 1.3.2, but in the long run, this method failed with a number of items. The early pages of this thread have many details about this. All that was thrown out before USKP 1.3.3 went live.

 

EDIT:

As a further information: I did not do anything with the weapon rack scripts before USKP 1.3.3.

Link to comment
Share on other sites

Is there any previous version of these scripts, including any vanilla iterations and any previous USKP versions, where an item could be keyframed on the rack but PlayersDroppedWeapon no longer points to it? Or is there anything that can cause the script properties to reset but leave an item keyframed in the cell? I can't remember what scenario I was thinking about when I said retroactively re-mounting the starting item might not be safe, and that's all I can come up with at the moment. But if that situation never arises then sure, I guess I see no harm in re-mounting StartingItem if it's still in the cell somewhere.

 

This has never been observed. Well, actually something similar has been observed, but this was due to a mesh bug which is now fixed: The large display cases had no node for staves, but the script did not exclude staves from being placed in display cases. Thus, when you tried, the placement procedure failed to find the node to move the staff to and the staff would end up floating in mid-air. At this point, the pointer was in fact broken. Other than that, I've never seen this happen though.

 

 

 

I'll go back and try to reproduce this with the current USKP 2.0.0 scripts, but I remember noticing a few times that it was possible to get a weapon to mount at the wrong angle. I think what I did was I mounted a sword (it was lined up correctly at this point), then picked it off the rack again, dropped it on the ground, z-key "grabbed" it and waved it around a little, let it fall on the ground again, then picked it up and mounted it. That second time, the angle was wrong, as if something about its rotation from being bounced around earlier had been retained and the model was therefore still offset from its "anchor" point (the point whose coordinates are set by MoveTo, I guess).

 

So if that's fixable, then maybe your tiny little handle-only triggers will be reliable, but otherwise I'm still worried about things being mounted askew and throwing off your expectations.

 

Some nodes had rotation angles set which the engine dislikes. When the overall rotation offset (i.e. Z angle of rack + node rotation offset) is equal to +90° or -90°, the engine adds another 180° and the item displays upside down. When I worked on the staff nodes on wooden racks, I once had an issue with Forsworn staves which displayed correctly in Honeyside, but upside down in Hjerim. When I inverted the sign of the rotation offset on the node, it was the other way around (no joke, sad truth). To prevent that, we meanwhile place most items slightly asymmetrically, i.e. rotated somewhat out-of-plane.

Link to comment
Share on other sites

(1) Agreed. StartingItemHasBeenGrabbed can go.

Done.

 

ObjectReference StartingItem = GetLinkedRef()
If StartingItem && StartingItem.IsEnabled()

	If PlayersDroppedWeapon && PlayersDroppedWeapon != StartingItem
		Trace (Self + "ActivatorSetup() not starting item.")
		PlaceItem(PlayersDroppedWeapon)

	ElseIf StartingItem.IsDeleted()
		; maybe previously placed, so prevent mismatch
		Trace (Self + "ActivatorSetup() starting item was deleted.")
		PlayersDroppedWeapon = None

	Else
		Cell parentCell = StartingItem.GetParentCell()
		If parentCell && parentCell == GetParentCell() && CheckFor3D (StartingItem)
			HandleStartingItem (StartingItem)
		EndIf
	EndIf

ElseIf PlayersDroppedWeapon
	PlaceItem(PlayersDroppedWeapon)
EndIf

(2) There is a third case, and it is very common:

 

There is a starting item, and it is still in place: This is the case in all interiors that are not player homes and in most exteriors, e.g. the Warmaiden's, Alvor's shop, the western watchtower, etc. (to name just a few, actually this is the common case):

 

Unless you steal them all at once, they will be permanently there. And even if you take them, they are replaced upon cell reset.

Yes, that is handled by the code above. I have no idea why you'd need to steal them all at once. That seems a non sequitur.

 

(3) After failing the PlacedItemInit test, they do not all pass the none test, because there is no guarantee that a rack is empty when you upgrade from vanilla (how should it ?).

Yes, that is handled by the code above. The failed PlacedItemInit test has been replaced, as I mentioned.

 

With the vanilla meshes, the TOC = 0 check caught only a minority of racks, because even on empty racks, TOC was rarely zero, due to the various problems with the triggers. This will be significantly better with the new meshes, but even if we know that there's an item on the rack, we do not know which one. Thus PlayersDroppedWeapon should remain uninitialized at this point. Now, because there's no third "kind of value" other than "something" (i.e. an arbitrary object reference) and "none", we still need a property to reflect that we are uncertain about PlayersDroppedWeapon. As long as we are, the (PlayersDroppedWeapon != StartingItem) check has to be skipped.

Please look at the code, and just that code. The TOC test is later, and was in another message.

 

This would be true when all activator scripts were running on activators, but they don't. Some are running on triggers (sic! - they were set up that way by Bethesda - I know it makes no sense, but they did it). Please check the formIDs 0010291A and 0010291B: those are triggers with a WeaponRackActivateScript attached, and they are enable-parented. USKP removes the misplaced scripts, but this fix is not retroactive.

If my TOC code fails, yours fails. They are functionally identical.

Link to comment
Share on other sites

This has never been observed. Well, actually something similar has been observed, but this was due to a mesh bug which is now fixed: The large display cases had no node for staves, but the script did not exclude staves from being placed in display cases. Thus, when you tried, the placement procedure failed to find the node to move the staff to and the staff would end up floating in mid-air. At this point, the pointer was in fact broken. Other than that, I've never seen this happen though.

I see nothing in vanilla code that would cause the pointer to be broken. In fact, the pointer is set before trying to place the staff. And I changed my code to match (long ago), as I felt that was a bug in your code.

Vanilla:

PlayersDroppedWeapon.SetMotionType(Motion_Keyframed, false)
; Tell the weapon to ignore all forms of physic interaction
Trace("DARYL - " + self + " Disabling physics on " + PlayersDroppedWeapon)

TriggerMarker = GetLinkedRef(WRackTrigger)

; Handle the placement of the weapon
if PlayersDroppedWeapon.HasKeyword(WeaponTypeSword)
	; 1H Sword
Mine:

; At this point, the WeaponRackTriggerSCRIPT isDisabled tests will
; prevent further access to PlayersDroppedWeapon, so it's OK to
; use here to match vanilla script:
PlayersDroppedWeapon = ItemToPlace

PlayersDroppedWeapon.SetMotionType(Motion_Keyframed, false)
; Tell the weapon to ignore all forms of physic interaction
; (this won't stop OnTriggerLeave events from firing though)

; Handle the placement of the weapon
if PlayersDroppedWeapon.HasKeyword(WeaponTypeSword)
	; 1H Sword
Link to comment
Share on other sites

Done.

 

ObjectReference StartingItem = GetLinkedRef()
If StartingItem && StartingItem.IsEnabled()

	If PlayersDroppedWeapon && PlayersDroppedWeapon != StartingItem
		Trace (Self + "ActivatorSetup() not starting item.")
		PlaceItem(PlayersDroppedWeapon)

	ElseIf StartingItem.IsDeleted()
		; maybe previously placed, so prevent mismatch
		Trace (Self + "ActivatorSetup() starting item was deleted.")
		PlayersDroppedWeapon = None

	Else
		Cell parentCell = StartingItem.GetParentCell()
		If parentCell && parentCell == GetParentCell() && CheckFor3D (StartingItem)
			HandleStartingItem (StartingItem)
		EndIf
	EndIf

ElseIf PlayersDroppedWeapon
	PlaceItem(PlayersDroppedWeapon)
EndIf
Yes, that is handled by the code above. I have no idea why you'd need to steal them all at once. That seems a non sequitur.

 

Yes, that is handled by the code above. The failed PlacedItemInit test has been replaced, as I mentioned.

 

Please look at the code, and just that code. The TOC test is later, and was in another message.

 

 

Agreed. Just wanted to make sure.

 

 

If my TOC code fails, yours fails. They are functionally identical.

 

I beg your pardon, but in this point they are not. I have good reasons to insist here, because we already had much trouble with this issue in the past.

Please do me the favour, take the two minutes and look at the form IDs in TES5Edit; look at the script and the links, then look at the base object and you will immediately see what I mean. In return, I'll try to get the meshes ready until tomorrow.

Link to comment
Share on other sites

I see nothing in vanilla code that would cause the pointer to be broken. In fact, the pointer is set before trying to place the staff. And I changed my code to match (long ago), as I felt that was a bug in your code.

Vanilla:

PlayersDroppedWeapon.SetMotionType(Motion_Keyframed, false)
; Tell the weapon to ignore all forms of physic interaction
Trace("DARYL - " + self + " Disabling physics on " + PlayersDroppedWeapon)

TriggerMarker = GetLinkedRef(WRackTrigger)

; Handle the placement of the weapon
if PlayersDroppedWeapon.HasKeyword(WeaponTypeSword)
	; 1H Sword
Mine:

; At this point, the UpdateActivatorState isDisabled test will
; prevent further access to PlayersDroppedWeapon, so it's OK to
; use here to match vanilla script:
PlayersDroppedWeapon = ItemToPlace

PlayersDroppedWeapon.SetMotionType(Motion_Keyframed, false)
; Tell the weapon to ignore all forms of physic interaction
; (this won't stop OnTriggerLeave events from firing though)

; Handle the placement of the weapon
if PlayersDroppedWeapon.HasKeyword(WeaponTypeSword)
	; 1H Sword

 

Yes, you're right. Forgot about this.

 

I moved this past the placement procedure because the weapons were not always moved onto the nodes. Ghostblade etc. fell off and were never placed, so I had a reference in PlayersDroppedWeapon (even worse, a broken one, as found out later) and no item on the rack. Meanwhile, I agree that it is absolutely safe to move it back up.

Link to comment
Share on other sites

Here's the code for the crossbows (they are handled together with bows as they do not have their own keyword):

 

In the PlaceItem function:

                :
		ElseIf ItemToPlace.HasKeyword (WeaponTypeBow)
			PlaceBow (ItemToPlace, TriggerMarker)
                :

And the new PlaceBow function:

Function PlaceBow (ObjectReference Bow, ObjectReference TriggerMarker)

;This function selects the matching node for a bow (or crossbow) to be placed, then moves the bow to that node. It is called from the PlaceItem function.


	If Bow.HasNode ("CrossbowRoot") && TriggerMarker.HasNode ("CrossbowPivot01")
		If Bow.HasNode ("DwarvenCrossbow.nif")
			Bow.MoveToNode (TriggerMarker, "CrossbowPivot02")
		Else
			Bow.MoveToNode (TriggerMarker, "CrossbowPivot01")
		EndIf
	Else
		Bow.MoveToNode (TriggerMarker, "BowPivot01")
	EndIf

EndFunction
Link to comment
Share on other sites

I beg your pardon, but in this point they are not. I have good reasons to insist here, because we already had much trouble with this issue in the past.

Please do me the favour, take the two minutes and look at the form IDs in TES5Edit; look at the script and the links, then look at the base object and you will immediately see what I mean. In return, I'll try to get the meshes ready until tomorrow.

Your code in 2.0.0:

TOC = TriggerMarker.GetTriggerObjectCount()

If PlacedItemInit
	If (PlayersDroppedWeapon == None)
		Self.Enable()
	Else
		Self.Disable()
		TriggerMarker.GoToState ("")
	EndIf
ElseIf (TOC == 0)
	PlayersDroppedWeapon = None
	PlacedItemInit = True
	Self.Enable()
Else
	Self.Disable()
	TriggerMarker.GoToState ("")
EndIf
My code (again as seen earlier):

;/
; Give everything a chance to settle before measuring
/;
Utility.Wait (1.0)
int iTOC = TriggerMarker.GetTriggerObjectCount()

If PlacedItemInit
	If (PlayersDroppedWeapon == None)
		EnableNoWait()
		Trace (Self + "ActivatorSetup() enabled; PlacedItemInit = True; TOC = " + iTOC)
	Else
		DisableNoWait()
		(TriggerMarker as WeaponRackTriggerSCRIPT).ActivatorDone ("ActivatorSetup")
		Trace (Self + "ActivatorSetup() disabled; PlacedItemInit = True; TOC = " + iTOC)
	EndIf
ElseIf (iTOC == 0)
	PlayersDroppedWeapon = None
	PlacedItemInit = True
	EnableNoWait()
	Trace (Self + "ActivatorSetup() enabled; PlacedItemInit = True; TOC = Zero")
Else
	; legacy/unused blocked racks. any obstruction(s) must be removed to use rack.
	DisableNoWait()
	(TriggerMarker as WeaponRackTriggerSCRIPT).ActivatorDone ("ActivatorSetup")
	Trace (Self + "ActivatorSetup() disabled; PlacedItemInit = False; TOC = " + iTOC)
EndIf
If my code fails, your code fails. They are functionally identical.

 

Here's the code for the crossbows (they are handled together with bows as they do not have their own keyword):

Thanks muchly. Now where are those meshes?

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...