Universal Item Display Script (Weapons, Shields, Potions etc

Post » Thu Jun 21, 2012 1:40 pm

I haven't taken a look at the book case script, but what makes it so resource intensive? Maybe I can try re-writing it too.
User avatar
Alexandra Louise Taylor
Posts: 3449
Joined: Mon Aug 07, 2006 1:48 pm

Post » Thu Jun 21, 2012 6:42 am

the container, trigger and activator all have OnCellLoad events, particularly the container script which iterates through all possible books. harmless when you're talking about 1 or 2 bookcases (as is the case with all vanilla player homes), but multiply all of these event calls by the number of bookcases + number of mannequins + number of vanilla weapon racks all competing for the same event.

in the case with vanilla weapon racks and mannequins, these load events are timing critical, so when there are so many lines of code in the thread queue, the event block for the mannequins and weapon racks may not fire properly, hence the wandering problems and weapon floating/upside down etc (just my theory, but reducing the number of these types of furniture always solves the problem)

the bookcase script just looks like it could use some optimization. i have a feeling it can do away with the cell load event altogether. even having the book iteration triggered by a trigger box instead of a cell load would greatly improve the speed of the load call of the other furniture that needs it more (mannequins)

all of the cellLoad calls for the setnofavorallowed() are completely unnecessary IMO. those can be eliminated entirely. you can just query for player in the OnActivate block instead. as for the cellLoad for BlockActivation() that only needs to be called once afaik, so it can be nested inside a state so that it is called only once when it first loads in the mod, and then never called again
User avatar
Stefanny Cardona
Posts: 3352
Joined: Tue Dec 19, 2006 8:08 pm

Post » Thu Jun 21, 2012 1:17 pm

for other display types such as dagger case, shield rack, potion rack etc, leave a comment and i will detail which properties you need to fill, or if you can decipher the code in the AllowedItems function, it should tell you which properties will be used for that type. but in any case, DisplayType, and both Message properties must be filled for all types.

As a newbie modder, deciphering code is way beyond my level of understanding! If you could explain the properties required for shield racks and potion racks I would be eternally grateful :)
I had no idea that using numerous vanilla display racks/cases/bookshelves could cause issues in game :confused: A few of my mods contain large display areas and libraries so I will be working through and replacing all the vanilla racks using your tutorial.

And thanks for posting this tutorial - I'm sure plenty of people like myself who don't understand scripting will appreciate it!
User avatar
Crystal Clarke
Posts: 3410
Joined: Mon Dec 11, 2006 5:55 am

Post » Thu Jun 21, 2012 11:41 am

for potion racks, set your display type property to a value of 5

you will need to additionally fill the following keyword properties:

pressing auto-fill should fill those automatically (use the Auto-fill in the Edit Value section, NOT the Auto-Fill All button)

you will also need to create a Formlist with all variants of the WhitePhial since that item was never marked with any keywords by bethesda, and they will otherwise be denied when trying to place them on the display. follow the instructions i gave the user above for creating a formlist, but instead of the claws you will need to search "MS12" in the Potion section in the object window (MS12 is the name of the quest that handles the White Phial). after you create your formlist, fill the WhitePhial property with it.

the rest is the same as tutorial. there is a dummy potion marker you can use for the container model in the same folder as the dummy sword.

the shield rack is a bit more complicated. the way i have my shield rack set up in this script is a "coat of arms" style (where you can mount 2 swords behind the shield in a "X") for reference here is a photo how i set mine up in the render window:


I'm using the default weapon plaque model as a Static, and the white trigger boxes as activators (they are in clutter/weaponrack).

you need to make sure you set up the trigger boxes and containers spaced apart enough so that when weapons and shield are mounted, they do not "invade" into the other trigger boxes (that's why i have mine shaped and angled strangely, mainly to accomodate larger shields) if a shield pokes into one of the weapon's triggers it will falsely return as that weapon trigger being "filled" (and no weapons will be allowed to mount in it), so give it enough space to avoid having a shield poke into it. the trigger boxes need to only be big enough to register a partial collision with the display item.

as for properties, the shield and weapons are handled separately. If you are making a shield-only display, you can disregard all the stuff about weapons and the wierd trigger box setup i described above

Shields - fill the following property in addition to the required ones, and set display type to 4:


"Coat of Arms" weapons - fill the following and set type to 7:



(note that only swords and greatswords will be allowed to mount)
User avatar
stevie critchley
Posts: 3404
Joined: Sat Oct 28, 2006 4:36 pm

Post » Thu Jun 21, 2012 12:42 am

the container, trigger and activator all have OnCellLoad events, particularly the container script which iterates through all possible books. harmless when you're talking about 1 or 2 bookcases (as is the case with all vanilla player homes), but multiply all of these event calls by the number of bookcases + number of mannequins + number of vanilla weapon racks all competing for the same event.

The scripts all have a boolean guard to make sure they only go through that once, especially since the player homes don't reset. But I do see your point. I'm not sure that I can re-write the script to make it more efficient, though I could definitely make it shorter.
User avatar
Jessica Thomson
Posts: 3337
Joined: Fri Jul 21, 2006 5:10 am

Post » Thu Jun 21, 2012 4:23 am

i was thinking of moving that load block to a function (called by an external trigger box) or RegisterForSingleUpdate(FloatProperty) in the load block and call the rest in the update event (enough time for mannequins to run their load scripts before bookshelves). the Register/Update will be only called in an "init" state then moved to a new state where it is never called again. the register time as a float property so that you can delay the update event to come in waves (if you really have a ton of them). when you fill the properties of each bookshelf you can use the float property as individually customizable "dynamite fuses" (in theory)

i havent gotten around to testing any of it yet though

if the dynamite fuse works, i will probably apply it to mannequins as well (so that all immediately visible mannys will get the instant OnLoad treatment, and the other secluded ones can get the fuse delay, so they dont all bum rush the load event at once

script-wise, the performance bottlenecking really only happens at the load event (texture ram strain aside)
User avatar
.X chantelle .x Smith
Posts: 3399
Joined: Thu Jun 15, 2006 6:25 pm

Post » Thu Jun 21, 2012 9:51 am

Re-wrote the container script using formlists and arrays, which halves the size. Haven't tested it though.

Scriptname PlayerBookShelfContainerScript extends ObjectReference;;;;;;;;;;;;;;;;;; Properties ;;;;;;;;;;;;;;;;;;Message Property BookShelfFirstActivateMESSAGE Auto{Display message when the player activates a bookshelf for the first time.  Only displays once.}Message Property BookShelfNoMoreRoomMESSAGE Auto{Displayed message for when the amount of books the player is placing excedes the shelf limit.}Message Property BookShelfNotABookMESSAGE Auto{Message displayed when the player places a non book form in the container.}Message Property BookShelfRoomLeftMESSAGE Auto{Notification that tells the player how much room is left on the shelf upon first activating it.}GlobalVariable Property BookShelfGlobal Auto{Global showing whether or not the player has ever activated a bookshelf}FormList Property MarkerKeywords Auto{Formlist containing all the bookmarker keywords}FormList Property TriggerKeywords Auto{Formlist containing all the booktrigger keywords}Form[] Property StoredBooks Auto{Array containing exactly which types of books are in the container}ObjectReference[] Property StoredRefs Auto{Array containing the references to the books on the book shelf}ObjectReference[] Property Markers Auto{Array containing the references to the book markers}ObjectReference[] Property Triggers Auto{Array containing the references to the book shelf triggers}Int Property MaximumBookAmount AutoInt Property CurrentBookAmount Auto;;;;;;;;;;;;;;;;; Functions ;;;;;;;;;;;;;;;;;Function AddBooks(Form BookBase, Int BookAmount)    int i    while (BookAmount)        if !(StoredBooks[i])            StoredBooks[i] = BookBase            BookAmount -= 1        endif        i += 1    endwhileEndFunctionFunction RemoveBooks(Form BookBase, Int BookAmount)    int i = MaximumBookAmount    while (BookAmount)        i -= 1        if (StoredBooks[i] == BookBase)            StoredBooks[i] = None            BookAmount -= 1        endif    endwhileEndFunction;;;;;;;;;;;;;;;;;;;;; Events/States ;;;;;;;;;;;;;;;;;;;;;Auto State SettingUp    Event OnCellAttach()        RegisterForSingleUpdate(Utility.RandomFloat(3, 7))        ;this will hopefully make for less conflicts with other scripts starting up    EndEvent        Event OnUpdate()        BlockActivation()        StoredBooks = new Form[18]        StoredRefs = new ObjectReference[18]        Markers = new ObjectReference[18]        Triggers = new ObjectReference[4]        ObjectReference TempRef        int i        while (i < MarkerKeywords.GetSize())            Markers[i] = GetLinkedRef(MarkerKeywords.GetAt(i) as Keyword)            if (Markers[i])                i += 1                MaximumBookAmount = i            else                i = MarkerKeywords.GetSize()            endif        endwhile        i = 0        while (i < TriggerKeywords.GetSize())            Triggers[i] = GetLinkedRef(TriggerKeywords.GetAt(i) as Keyword)            if (Triggers[i])                i += 1            else                i = TriggerKeywords.GetSize()            endif        endwhile        GoToState("Closed")    EndEventEndStateState Closed    Event OnActivate(ObjectReference akActionRef)        BookShelfRoomLeftMESSAGE.Show((MaximumBookAmount - CurrentBookAmount))        if (BookShelfGlobal.GetValue() == 0)            BookShelfFirstActivateMESSAGE.Show()            BookShelfGlobal.SetValue(1)        endif        int i        while (Triggers[i])            (Triggers[i] as PlayerBookShelfTriggerSCRIPT).GoToState("IgnoreBooks")            i += 1        endwhile        GoToState("Opened")        Activate(akActionRef, True)    EndEvent    EndStateState Opened    ;hope that this event works on containers, otherwise will need an update loop to check for menu mode    Event OnClose(ObjectReference akActionRef)        int i = MaximumBookAmount        while (i > 0)            i -= 1            if (StoredRefs[i])                StoredRefs[i].Disable()                StoredRefs[i].Delete()                StoredRefs[i] = None            endif        endwhile        while (i < CurrentBookAmount)            StoredRefs[i] = Markers[i].PlaceAtMe(StoredBooks[i])            i += 1        endwhile        i = 0        while (Triggers[i])            (Triggers[i] as PlayerBookShelfTriggerSCRIPT).GoToState("WaitForBooks")            i += 1        endwhile        GoToState("Closed")    EndEvent        Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)        if !(akBaseItem as Book)            GoToState("IgnoreAll")            RemoveItem(akBaseItem, aiItemCount, True, akSourceContainer)            BookShelfNotABookMESSAGE.Show()            GoToState("Opened")            Return        elseif (CurrentBookAmount == MaximumBookAmount)            GoToState("IgnoreAll")            RemoveItem(akBaseItem, aiItemCount, True, akSourceContainer)            BookShelfNoMoreRoomMESSAGE.Show()            GoToState("Opened")            Return        endif        if (CurrentBookAmount + aiItemCount > MaximumBookAmount)            GoToState("IgnoreAll")            RemoveItem(akBaseItem, CurrentBookAmount + aiItemCount - MaximumBookAmount, True, akSourceContainer)            aiItemCount = MaximumBookAmount - CurrentBookAmount            GoToState("Opened")        endif        CurrentBookAmount += aiItemCount        AddBooks(akBaseItem, aiItemCount)    EndEvent        Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)        CurrentBookAmount -= aiItemCount        RemoveBooks(akBaseItem, aiItemCount)    EndEventEndStateState IgnoreAllEndState
User avatar
Kristina Campbell
Posts: 3512
Joined: Sun Oct 15, 2006 7:08 am

Post » Thu Jun 21, 2012 4:47 am

looks good, i'll give it a go when i get a chance. really interested to see if the delayed-fuse register/update helps the threading bottleneck
User avatar
Sammi Jones
Posts: 3407
Joined: Thu Nov 23, 2006 7:59 am

Post » Thu Jun 21, 2012 1:00 pm

Hi guys

Another question for you please. I've been wondering if I can have multiple items mounted to the one activator. So for example let's say I have a single shelf and want to have mounting points for all 8 dragon claws on that one shelf. I can create 8 containers and triggers but the activator that they sit on (the shelf) will allow only one item to be stored. When you try to open the next container it just rejects the request as there is already an item stored. Or at least I think that's what is happening. I can get it to store two items by adjusting the script on the activator but both items store on the same container instead of using the next container on the shelf.

So is there a way of using one physical mesh as the activator but having multiple containers/triggers on it? Hope that makes sense...
User avatar
Alex Blacke
Posts: 3460
Joined: Sun Feb 18, 2007 10:46 pm

Post » Thu Jun 21, 2012 3:34 pm

you can but it would require rewriting the activator script. it would need to have a function that tracks which triggers are currently being filled, and which ones are empty, then open the container of the first available empty mount. this is actually closer to the bookshelf type functionality. it would probably be better to use a different script for something like this (adapted version of the bookshelf).
User avatar
Elisabete Gaspar
Posts: 3558
Joined: Thu Aug 31, 2006 1:15 pm

Post » Thu Jun 21, 2012 1:48 am

Now that I think about it, my bookshelf container script svcks. It's messy.

If I wanted to make it compatible with everyone, I should have kept the same properties and not introduced any new ones, so that people can just put the compiled script into their folders instead of needing to create an esp.

If I wanted to actually change it into something that I'm satisfied with, I should have re-wrote the whole system, including the scripts for the triggers. I never did like the way that the game just cloned your books and put them into the bookshelf. That was the reason for the problem with the Oghma Infinium.
User avatar
Sasha Brown
Posts: 3426
Joined: Sat Jan 20, 2007 4:46 pm

Post » Thu Jun 21, 2012 8:35 am

i'm working on a new bookshelf script now, and what i am finding is another huge problem is that for every book on the shelf, you need 2 persistent refs (the marker and the placed book itself), so if your bookshelf holds 24 books, thats at least 48 persistent refs, multiply that by the number of bookshelves and... you get the idea.

i'm trying to instead figure out a way to make a "passive" bookshelf, where you place a book into a container, the reference gets deleted (removeitem) and the base object is sent to a level list on its own, then the dummy book spawns the base book from the level list. getting the book to populate the level list, and pre-linking level lists to their corresponding dummies on the shelf is not a problem, but not sure if they can be spawned in real time. this method would at least eliminate all persistent refs if it actually worked (which, combined with the dynamite fuse OnLoad, could essentialy allow you to have as many bookshelves as your physical ram would allow, you would not have load bottlenecks or persistent ref residue buildup)
User avatar
Andrew Perry
Posts: 3505
Joined: Sat Jul 07, 2007 5:40 am

Post » Thu Jun 21, 2012 3:08 pm

I haven't worked much with dummy items, but I thought that they only spawned (or transformed into) entries from the leveled list when their parent cell is loaded. I suppose you could try with the dummy items initially disabled and then enable them afterwards to see if the leveled list items spawn.

Of course, this way you would need to have a unique leveled list for each bookshelf. Which I suppose isn't too bad of a problem if it works...

But will this work in player homes? I mean, player homes are usually in a no reset zone. If the cell doesn't get reset, then the dummy items don't respawn, and they wouldn't re-populate the bookshelf, right?

EDIT: Never mind, I forgot you can just call http://www.creationkit.com/Reset_-_ObjectReference on them.
User avatar
Posts: 3487
Joined: Thu Sep 21, 2006 1:17 pm

Post » Thu Jun 21, 2012 3:47 am

the dummy/levelitem didnt work out too well. and calling reset on the dummy did not let it spawn the book from the updated level list

using an array + base object formlist (multiple unique dummy books) + Game.FindClosestReferenceOfTypeFromRef is the best i can do.

i'm using a modified Mannequin script ("armorslot" array for placed books) but it doesnt have any OnLoad event, and since i'm using the findClosestRef function, the dummy markers are only persistent for the length of the activation, then released after the OnUpdate is finished.

this script is less of a burden overall, but a little bit more "expensive" per interaction, which IMO is a much better tradeoff (i'd rather the stress be on the single interaction per bookshelf, rather than a bottleneck for all of them simultaneously). once i get it fully working i will post a new thread for it

this is intended to be used as a standalone script, not a replacer for vanilla (most of the vanilla playerhomes with bookshelves only have a couple in there and should be fine as is)
User avatar
Alberto Aguilera
Posts: 3472
Joined: Wed Aug 29, 2007 12:42 am

Post » Thu Jun 21, 2012 6:03 am

ok, ive finished the new bookshelf system

here are the main differences between this one and vanilla:

it uses only 2 scripts (one activator and one container)
no triggers needed
no load calls needed (thus can only be used in noReset zones)
half the number of persistent refs and properties (only the placed books remain persistent in ObjRef properties, no need for dummy marker persistence)
one container for the entire shelf
works independently of shelves (doesnt matter if you are using a 2 or 3 shelf book case, the container script will automatically figure out where to place the books)
very easy to optionally pre-populate the entire shelf with a separate container with pre-loaded books in it
easier to set up than the vanilla system

not backwards compatible with vanilla system at all (no chance of using this for vanilla homes, but is there isnt any need to anyway)
per-activation papyrus strain is slightly heavier than vanilla (since it has to crunch more code and function calls than the vanilla script activation), but IMO this is a better tradeoff for player homes with lots of clutter and multiple bookshelves/mannequins/weapon racks

i will start a new thread with a mini tutorial for any of you who are interested in trying it out
User avatar
lydia nekongo
Posts: 3403
Joined: Wed Jul 19, 2006 1:04 pm

Post » Thu Jun 21, 2012 6:22 am

I had what I thought was an awesome idea to do away with any persistent refs, but it didn't work out. :(

I was going to have the trigger zone scripted with an http://www.creationkit.com/OnTriggerEnter_-_ObjectReference event and have it initially disabled. When I want to find the books on the shelf, I'd just enable the trigger zone and use the event to populate my placed books array.

Unfortunately, the event doesn't fire this way. Which makes no sense, because an initially disabled item in the trigger zone would fire the event when it gets enabled.

Turns out it was just because I forgot to link the trigger zone to my container. :blush:

Here's a video of my script in action: http://youtu.be/iS4u-dRs1io

And these are my two scripts (did not feel like going back to put in comments explaining how they work):
  • The container script goes on the container, with the trigger zone as a linked ref, and two dummy books on either end of the shelf that are linked refs with keywords "BookShelfFirstBook" and "BookShelfLastBook":
Scriptname fg109_Bookshelf_Container_Script extends ObjectReference  Keyword Property BookShelfFirstBook AutoKeyword Property BookShelfLastBook AutoForm[] StoredBooksFloat ShelfLengthFloat CalcLengthInt CurrentBooksBool FullShelfEvent OnInit()	BlockActivation()	ShelfLength = GetLinkedRef(BookShelfFirstBook).GetDistance(GetLinkedRef(BookShelfLastBook)) + (GetLinkedRef(BookShelfFirstBook).GetHeight())EndEventAuto State Active	Event OnActivate(ObjectReference akActionRef)		GoToState("Inactive")		CurrentBooks = (GetLinkedRef() as fg109_Bookshelf_Trigger_Script).MoveIntoContainer()		StoredBooks = (GetLinkedRef() as fg109_Bookshelf_Trigger_Script).StoredBooks		CalcLength = 0.0		FullShelf = False		GoToState("Active")		Activate(akActionRef, True)		Utility.Wait(0.5)		GoToState("Inactive")		PlaceOnShelf()		GoToState("Active")	EndEvent	Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)		if !(akBaseItem as Book)			RemoveItemSilent(akBaseItem, aiItemCount, akSourceContainer)			Debug.MessageBox("The bookshelf only accepts books.")		elseif (FullShelf)			RemoveItemSilent(akBaseItem, aiItemCount, akSourceContainer)			Debug.MessageBox("The bookshelf is full.")		else			int BookCount = CheckShelfSpace(akBaseItem, aiItemCount)			if (BookCount < aiItemCount)				FullShelf = True				RemoveItemSilent(akBaseItem, aiItemCount - BookCount, akSourceContainer)			endif			AddBooks(akBaseItem, BookCount)		endif	EndEvent	Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)		if (akBaseItem as Book)			RemoveBooks(akBaseItem, aiItemCount)			FullShelf = False		endif	EndEventEndStateFunction RemoveItemSilent(Form akItem, Int aiCount, ObjectReference akContainer)	GoToState("Inactive")	RemoveItem(akItem, aiCount, True, akContainer)	GoToState("Active")EndFunctionInt Function CheckShelfSpace(Form MyBook, Int Count)	ObjectReference TempRef = PlaceAtMe(MyBook, 1, False, True)	Float BookWidth = TempRef.GetHeight() + 0.5	TempRef.Delete()	int i = 0	while (i < Count) && (CurrentBooks + i < 128) && (CalcLength <= ShelfLength)		CalcLength += BookWidth		i += 1	endwhile	Return iEndFunctionFunction AddBooks(Form MyBook, Int Count)	int i = 0	CurrentBooks += Count	while (Count)		while (StoredBooks[i])			i += 1		endwhile		StoredBooks[i] = MyBook		Count -= 1	endwhileEndFunctionFunction RemoveBooks(Form MyBook, Int Count)	int i = 0	CurrentBooks -= Count	while (Count)		if (StoredBooks[i] == MyBook)			StoredBooks[i] = None			Count -= 1		endif		i += 1	endwhileEndFunctionFunction PlaceOnShelf()	ObjectReference[] PlacedBooks = new ObjectReference[128]	ObjectReference First = GetLinkedRef(BookShelfFirstBook)	ObjectReference Last = GetLinkedRef(BookShelfLastBook)	Float AngX = First.GetAngleX()	Float AngY = First.GetAngleY()	Float AngZ = First.GetAngleZ()	Float PosX = First.X	Float PosY = First.Y	Float PosZ = First.Z	Float UnitVectorX = (Last.X - PosX) / ShelfLength	Float UnitVectorY = (Last.Y - PosY) / ShelfLength	Float UnitVectorZ = (Last.Z - PosZ) / ShelfLength	Bool PlacedFirst = False	Float OldHeight	Float NewHeight	Float Offset = 0.0	int i = 0	int j = 0	while (CurrentBooks)		if (StoredBooks[i])			PlacedBooks[j] = DropObject(StoredBooks[i])			if (PlacedFirst)				NewHeight = PlacedBooks[j].GetHeight()				Offset += 0.5 * OldHeight + 0.5 * NewHeight + 0.5				OldHeight = NewHeight			else				PlacedFirst = True				OldHeight = PlacedBooks[j].GetHeight()			endif			while !(PlacedBooks[j].Is3DLoaded())				;do nothing			endwhile			PlacedBooks[j].SetMotionType(4)			PlacedBooks[j].TranslateTo(PosX + UnitVectorX * Offset, PosY + UnitVectorY * Offset, PosZ + UnitVectorZ * Offset, AngX, AngY, AngZ, 100000.0)			j += 1			CurrentBooks -= 1		endif		i += 1	endwhile	while (j)		j -= 1		PlacedBooks[j].SetMotionType(1, True)	endwhileEndFunction
  • The trigger script goes on the trigger zone (which has collision layer L_Trigger), and it has the container as a linked ref:
Scriptname fg109_Bookshelf_Trigger_Script extends ObjectReference  Form[] Property StoredBooks AutoObjectReference MyContainerInt CurrentBooksBool LoopingEvent OnTriggerEnter(ObjectReference akTriggerRef)	if (akTriggerRef.GetBaseObject() as Book)		StoredBooks[CurrentBooks] = akTriggerRef.GetBaseObject()		CurrentBooks += 1		MyContainer.AddItem(akTriggerRef)	endif	RegisterForSingleUpdate(0.2)EndEventEvent OnUpdate()	Looping = False	Disable()EndEventInt Function MoveIntoContainer()	MyContainer = GetLinkedRef()	StoredBooks = new Form[128]	CurrentBooks = 0	Looping = True	RegisterForSingleUpdate(0.2)	Enable()	while (Looping)		Utility.Wait(0.1)	endwhile	Return CurrentBooksEndFunction

I also used the "ActivateLinkedChestDummyScript" for my click triggers.

The trigger zone and dummy books are initially disabled. The trigger zone covers the whole shelf area. Of course, there's a collision marker (with collision layer L_Unidentified) at the front of the bookshelf to stop books from falling out.

One good thing about this method is that you can pre-populate bookshelves in the CK just by putting the books inside the trigger zone.

Bad thing would be that the books are always randomly ordered since you can't control which of them fires the OnTriggerEnter event in which order. Would need to use SKSE to create a sorting function based on book names when placing the books.
User avatar
Eliza Potter
Posts: 3481
Joined: Mon Mar 05, 2007 3:20 am

Post » Thu Jun 21, 2012 11:19 am

very interesting approach. very cool with the trigger disable/enable action you are using. this is awesome, i love sharing notes and comparing different approaches to the same problem and seeing varied ways to tackle a complex objective

this was the approach i came up with, although after some testing i found that the boolean guard for Bypass is not thread safe and is not reliable when the functions and while loops are cycling in multiple threads



Scriptname DCVR_AH_BookshelfContainerScript extends ObjectReference ObjectReference[] Property PlacedBooks  Auto HiddenForm[] Property Library  Auto HiddenFormList Property BookMarkerList  AutoMessage Property BookWarning  AutoMessage Property CountWarning  AutoMessage Property DupeWarning  AutoInt Property MaxBooks  AutoBool Property Blocked = False  Auto HiddenBool Property Bypass = False  Auto HiddenEvent OnInit()PlacedBooks = New ObjectReference[33]Library = New Form[33]EndEventEvent OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)Actor PlayerRef = Game.GetPlayer()If (akBaseItem as Book)  If (IsDuplicate(akBaseItem)) || (aiItemCount > 1)   If (akSourceContainer == PlayerRef)    Bypass = True    RemoveItem(akBaseItem, aiItemCount, True, akSourceContainer)    DupeWarning.Show()   Else    Bypass = True    RemoveItem(akBaseItem, aiItemCount)   EndIf  Else   AddToLibrary(akBaseItem, aiItemCount, akSourceContainer)  EndIfElse  RemoveItem(akBaseItem, aiItemCount, True, akSourceContainer)  BookWarning.Show()EndIfIf (akSourceContainer == PlayerRef)  RegisterForSingleUpdate(0.1)EndIfEndEventEvent OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)If (akBaseItem as Book) && (!Bypass)  RemoveFromLibrary(akBaseItem)EndIfBypass = FalseIf (akDestContainer == Game.GetPlayer())  RegisterForSingleUpdate(0.1)EndIfEndEventEvent OnUpdate()SyncBooks()EndEventBool Function IsDuplicate(Form akBaseItem)Int i = 0While (i < MaxBooks)  If (Library[i] == akBaseItem)   Return True  EndIf  i += 1EndWhileReturn FalseEndFunctionFunction AddToLibrary(Form akBaseItem, Int aiItemCount, ObjectReference akSourceContainer)Int i = 0Bool FoundEmptySlot = FalseWhile (i < MaxBooks) && (!FoundEmptySlot)  If (!Library[i])   Library[i] = akBaseItem   FoundEmptySlot = True  EndIf  i += 1EndWhileIf (!FoundEmptySlot)  If (akSourceContainer == Game.GetPlayer())   Bypass = True   CountWarning.Show()   RemoveItem(akBaseItem, aiItemCount, True, akSourceContainer)  Else   Bypass = True   RemoveItem(akBaseItem, aiItemCount)  EndIfEndIfEndFunctionFunction RemoveFromLibrary(Form akBaseItem)Int i = 0Bool FoundMatchingSlot = FalseWhile (i < MaxBooks) && (!FoundMatchingSlot)  If (Library[i] == akBaseItem)   Library[i] = None   FoundMatchingSlot = True  EndIf  i += 1EndWhileEndFunctionFunction SyncBooks()ObjectReference BookMarkerInt i = 0While (i < MaxBooks)  Int n = 0  Bool FoundEmptySlot = False  If (Library[i])   While (n < MaxBooks) && (!FoundEmptySlot)    If (!PlacedBooks[n])	 BookMarker = Game.FindClosestReferenceOfTypeFromRef(BookMarkerList.GetAt(n), Self, 800)	 PlacedBooks[n] = BookMarker.PlaceAtMe(Library[i])	 RemoveItem(Library[i])	 FoundEmptySlot = True    EndIf    n += 1   EndWhile  EndIf  i += 1EndWhileBlocked = FalseEndFunctionFunction ReturnBooks()Blocked = TrueInt i = 0While (i < MaxBooks)  PlacedBooks[i].BlockActivation()  Library[i] = None  i += 1EndWhilei = 0While (i < MaxBooks)  If (PlacedBooks[i])   If (PlacedBooks[i].Is3DLoaded())    AddItem(PlacedBooks[i].GetBaseObject())    PlacedBooks[i].Delete()   EndIf   PlacedBooks[i] = None  EndIf  i += 1EndWhileActivate(Game.GetPlayer())RegisterForSingleUpdate(0.1)EndFunction



Scriptname DCVR_AH_BookshelfActivatorScript extends ObjectReference ObjectReference Property BookContainer  AutoEvent OnActivate(ObjectReference akActionRef)If (akActionRef == Game.GetPlayer())  If (!(BookContainer as DCVR_AH_BookshelfContainerScript).Blocked)   (BookContainer as DCVR_AH_BookshelfContainerScript).ReturnBooks()  EndIfEndIfEndEvent
User avatar
Elle H
Posts: 3407
Joined: Sun Aug 06, 2006 3:15 am

Post » Thu Jun 21, 2012 12:40 am

on your script were you able to get around the issue with multiple books of the same form ID not removing itself properly from the form array?

the problem i found in testing was that if you added the same book twice to the same bookshelf, in the container it will show up as BookName (2), but in the form array it will be considered BookName as 2 separate values. then when you are in inventory menu, if you press the take-all button, since the 2 books are condensed into a single entry in the container's inventory, it will only fire OnItemRemoved one time with akBaseItem as Bookname and aiItemCount as 2. on my script, this removed only one entry from the form array, and left the other one intact, even though both books are physically gone from the container

i tried to combat this by disallowing duplicate books to be placed on the bookcase, so i added a Bypass boolean guard, but it doesnt fire properly due to the functions being called simultaneously in multiple threads, the boolean guard's real-time value cannot keep up with all the threads at once.

maybe if i pass aiItemCount into the remove from form array function, i can tell it to run the while loop for as many times as is in the value for aiItemCount (that way if BookName (2) is taken at once, it will purge BookName from the array twice)
User avatar
Posts: 3489
Joined: Thu Jul 05, 2007 6:29 am

Post » Thu Jun 21, 2012 9:04 am

Your idea is what I did. I passed in aiItemCount to my RemoveBooks() function in order to keep looking through the array until the passed in form has been removed "aiItemCount" times.

I used states instead of a boolean guard, which is better at keeping up with multiple threads. Of course, it's still not great. I found that when I added the placed books into the container, the OnItemAdded event was not keeping up with my while loop, and couldn't properly detect all the books that were added.

That's why I had to fill the array using the trigger zone script. Now that I look at it again, I also need to update my "CalcLength" variable in there too. I forgot to do that when I changed the script.
User avatar
Posts: 3371
Joined: Thu Nov 30, 2006 12:01 pm

Post » Thu Jun 21, 2012 12:50 pm

btw, the trigger method is brilliant, do you mind if i "borrow" it? heheh
User avatar
Nathan Maughan
Posts: 3405
Joined: Sun Jun 10, 2007 11:24 pm

Post » Thu Jun 21, 2012 3:43 am

No prob, use it all you like. :)
User avatar
Melanie Steinberg
Posts: 3365
Joined: Fri Apr 20, 2007 11:25 pm

Post » Wed Jun 20, 2012 11:11 pm

thanks. i think this definitely will make for better burden management for larger player houses

btw, you can get rid of permanent persistence for your book-end markers (linked refs are still flagged permanently persistent) by using Game.Findclosestwhatever, granted your book ends are created as 2 separate new dummy objects. that way you can find them by function call rather than linked ref or objref property

on my formlist i created a list of a ton of separate dummy book base objects, and placed them into the book case in the sequence they should be placed. then used game.findclosest to call them. this eliminates all persistent refs for markers. the formlist lets you populate multiple shelves in the same bookcase with a single container, but then again using individual shelf-specific book ends will probably also let you do the same (your shelf-measuring method is probably cleaner as it uses fewer objects in total).
User avatar
Stefanny Cardona
Posts: 3352
Joined: Tue Dec 19, 2006 8:08 pm

Post » Thu Jun 21, 2012 3:27 am

I'd actually tried not using the dummy books, but it didn't really work out.

I'd used the dummy book mesh for the container, and set it at one end of the shelf. I used it to determine the origin point for book placement and orientation. Then I got the shelf length simply by using GetWidth() on the trigger zone.

The problem with that is I'm using DropObject() and not duplicating books with PlaceAtMe(). When I dropped the books from the container, they popped up ~32 units above the container, right into the books on the shelf above. So whenever I tried to place books in one shelf, the books on the shelf above it would fly off everywhere.


BTW, are you sure that linked refs are permanently persistent?
User avatar
Harry Leon
Posts: 3381
Joined: Tue Jun 12, 2007 3:53 am

Post » Thu Jun 21, 2012 12:24 am

one advantage i thought of with using the dummy object formlist + your trigger method is that you can auto-populate the shelves randomly using the dummies and leveleditems. as long as the dummies in the formlist are base objects, they dont get marked as persistent. they can serve 2 purposes (one as placement marker and one as leveled item placeholder)

i had similar problems with dropobject which is why i switched to placeatme/delete + form alias/clear (really hoping persistence issues are not going to mess everything up when 1.6 goes live otherwise this will cause massive problems, not only scripts like this but the vanilla scripts as well). i have my invisible container embedded into the center of the book case with the 3d model of the book case being the activator itself. originally i had the 3d model for the container just be the book case 3d, but as the script evolved, after placing the books onto the shelves it would read "Search Bookcase - Empty" even though there are visible books sitting on the shelves which might be a bit confusing for the player (the containers contents are never filled while books are placed outside of it, so that there is only ever 1 instance of the book in existence)

edit - 100% sure linked refs are perma-marked. you can tell by looking at persistent location in the cell view window it will show as (all) - which means permanetly persistent. it also shows flagged persistent in tesvsnip as well as show up in the persistence-only GRUP, and will cause an edit on vanilla records if marked as a linked ref (since you are changing that record's persistence permanently and moving the REFR into the persistence-only GRUP of that cell)

basically any time you are given the opportunity to select something in the render window with that red crosshair thing, it will flag that ref as perma-persistent and move it into the perma-GRUP

another fun tidbit is that if a REFR is sent to the perma-GRUP it stays there forever, unless you physically delete it in the CK. even if you un-link the ref or remove it from an objref property, it saves in the esp as perma-persist forever
User avatar
Marquis T
Posts: 3425
Joined: Fri Aug 31, 2007 4:39 pm

Post » Thu Jun 21, 2012 2:35 am

Well... that totally svcks then. It's unbelievable, considering how many linked refs are used in the vanilla game.

I don't really want to use the FindClosestRef functions because they're not very accurate, and no doubt they're resource intensive. I suppose a complicated workaround would be to use GetFormFromFile from 1.6. Don't really want to do that though.
User avatar
Posts: 3489
Joined: Thu Jul 05, 2007 6:29 am


Return to V - Skyrim