Sorting Scripts

Post » Fri May 13, 2011 9:29 am

Alright, what follows is a method for creating easily expandable, very adaptable script for sorting (or just checking) on records contained in a form list. This will require that the reader has a pretty decent understanding of scripting and fose. A good understanding of form lists helps as well.

A bit of background on this first. I came up with this method while making my http://www.fallout3nexus.com/downloads/file.php?id=10294. I needed a way to sort items into many containers. My first method involved a unique script for each container. This script had an if block for each item to be sorted. It was cumbersome, difficult to maintain, and overall not a very good method. I set out to find a solution that was easy and modular.

The script I used requires a single form list. The list should contain everything that is to be sorted (or had work done with it). The other thing that is required (for sorting) is a named ref container to add the items to. The script here is written so that only two edits need to be made to allow it to work. The two lines that must be changed are set srcList to and set destCont to. So an example would look like this.

scn GTKitchenSCRIPT;variablesshort listlengthshort countlengthshort listCountref listItemref srcListref destContBegin OnActivate	set srcList to GTKitchenList	set destCont to GTKitchenRef	if (isControlPressed 27)				set listlength to ListGetCount srcList		set countlength to 0		Label 1		set listItem to ListGetNthForm srcList countlength		set listCount to Player.getItemCount listItem		if listCount > 0			Player.RemoveItem listItem listCount 1			destCont.addItem listItem listCount		Endif		set countlength to (1+countlength)		if countlength < listlength			Goto 1		Endif		showMessage GTKitchenSortedMsg	else		destCont.activate player 0	endifEND

What is happening here? Well, upon activating the container srcList is set to GTKitchenList, in this case, a form list made up of kitchen items (ex. Pots, pans, spatulas, cups, etc). The destList is set to a named container that is running this script (in this usage, the container would have to be a unique container made specifically for this script. Or, the script could be run from a unique activator, and the container could just be a named ref that the items go into. Theoretically, this could be run from a console as well.)

The main body of the script only runs if the player is pressing the grab key. If they are, then the script first sets a variable to the length of the list, then initializes a count variable (this is only necessary if you use multiple sort blocks in the same activate. I only did this because that's how I was taught to code. Fallout automatically initializes all variables to 0). I'll break the rest down into a line-by-line description of what is going on, for those who want to know.

Here we will assume that the list GTKitchenList contains the following items in this order: Pot, Spatula, Spoon, Drinking Glass.

This next part is where the magic happens. Starting at set listItem to, the code is now setting a variable to the first object contained in the list (the Pot). ListGetNthForm gets the Nth form the of specified list. In this instance, it is getting the countLength (0) form of the srcList (GTKitcheList), the pot.

The next line, set listCount to determines how many Pots the player has in their inventory, and sets the variable listCount to that number.

This part is only run if the player actually has any of the items in their inventory (this helps keep the impact of the script to a minimum). If the player does in fact have Pot with them, then the item is removed from their inventory and added to the specified container.

Here we increment a counter countLength. This counter is used for two things. First, it is used to determine if we have reached the end of the list. Second, it also tells us which item in the list we are on when using ListGetNthForm.

The next if statement checks if the countLength is less than the listLength. If it is, then we go back to the predetermined point designated as Label 1. From here, the entire process is repeated until countLength is equal to listLength, at which point we know that we have gone through the entire list, and can now end. Once done, it is set to display a message, telling the player that the desired action has been completed. As setup now, this message will actually be displayed even if no items are actually removed.

To fix this you could add a flag variable in the if listCount > 0 block that would be used to determine if an item has been removed. You would then alter the message section to be along the lines of:

if flag > 0	showMessage GTKitchenSortedMsgendif

The last line before the END is the action to perform if the grab key was not pressed down. In this instance, we want to activate the container in the normal way.


Now, the beauty of this code is that it's not just tied into being used only for sorting items. With a few very basic tweaks, it can be used to perform a check on any form list. In this next example, we are checking a list that contains all the different ants in the game, and incrementing a counter to determine how many total have been killed.

Begin GameMode				set srcList to AntList								set listlength to ListGetCount srcList					set countlength to 0					Label 1					set listItem to ListGetNthForm srcList countlength					set tempKill to getKillCount listItem					set killCount to (killCount + tempKill)					set countlength to (1+countlength)					if countlength < listlength						Goto 1					Endif					END

Here I am using a list of ants. The first part is the same, the only change here is that, instead of only doing the work when an items is found, we want to increment a variable for every item in the list. To do this, I have a variable tempKill that is set to the getKillCount for the first item in the list. The variable killCount is then incremented by this number. In order to just add the number to any previous numbers, we add the tempKill to killCount and then set killCount to this new number. The rest is the same as before, where we increment the counter, and check if that puts us at the end of the list or not.

I'm sure there are plenty more uses for this method of list checking. And I'm pretty sure that I'm not the first one to do this (though I haven't found an example on the forums). However, I've explained it as best I can, and have given an idea for other uses of this method of list crawling. I'm sure it's not the most streamlined method, and would probably cause a bit of lag on a list containing hundreds of items (due to the goto command) but it works very well for lists containing less than 100 items.

As stated, I have used this in my own mod, and it works flawlessly. The beauty of this method is how easy it is to add items to the list from another mod. I'll explain how to do that tomorrow.

Also, I would like to thank kaburke for his help in streamlining this method. I managed to get the basics in, but he helped get it cleaned up.
User avatar
OJY
 
Posts: 3462
Joined: Wed May 30, 2007 3:11 pm

Post » Fri May 13, 2011 5:13 pm

I posted a little while back regarding sorting algorithms. If you wanted to use what you have here for sorting weapons and armor, you would run into the problem where condition is restored to full for the sorted items. I think I found a good way around that.

The removeallitems command seems to be the only way to actually move an item from one container to another in the same condition that it's in. Also, it will not touch quest items - that's why you still have them in your inventory when you enter the Pitt and MZ.

If you designate an item as a quest item, then use removeallitems to clear everything else out of the players inventory, you can then clear it as a quest item, and use removeallitems again to move that item to its sorted storage container. Then use removeallitems to return the rest of the stuff to the player's inventory, move on to setting the next item as a quest item, and repeat the process.

I think the method you have here would make writing this kind of sort much less tedious. It would only work if destCont could be updated via a form list of containers in the same manner that listItem is - and apparently form lists can contain references as well as base forms, so I would think its possible.

The only remaining issue that I can think of is message spam - you'd be getting "item added" messages for about an hour, but I've read there are ways around that.
User avatar
Bird
 
Posts: 3492
Joined: Fri Nov 30, 2007 12:45 am

Post » Fri May 13, 2011 7:58 pm

I posted a little while back regarding sorting algorithms. If you wanted to use what you have here for sorting weapons and armor, you would run into the problem where condition is restored to full for the sorted items. I think I found a good way around that.

The removeallitems command seems to be the only way to actually move an item from one container to another in the same condition that it's in. Also, it will not touch quest items - that's why you still have them in your inventory when you enter the Pitt and MZ.

If you designate an item as a quest item, then use removeallitems to clear everything else out of the players inventory, you can then clear it as a quest item, and use removeallitems again to move that item to its sorted storage container. Then use removeallitems to return the rest of the stuff to the player's inventory, move on to setting the next item as a quest item, and repeat the process.

I think the method you have here would make writing this kind of sort much less tedious. It would only work if destCont could be updated via a form list of containers in the same manner that listItem is - and apparently form lists can contain references as well as base forms, so I would think its possible.
Using a couple of formLists as registries works well. I have Companion Core using a setup with two formlists, one with a faction at a given index and one with a formlist full of 'function activators' added to it at the same index. Then it determines what 'function activator' it should call by scanning through list 1 until it finds the faction a given NPC is in, then looking up the same index on the second formlist, pulling the 'function activator' formlist from it and then doing searches on that one.

You could use the same principle for that too- one list has container refs, paired up with a list of lists of 'desired contents'.
'course, the desired contents lists aren't going to be all-inclusive, so for weapons and armour it might be better to just scan the inventory for armour with/without the Power Armour flag, or with a given skill or animation type.
User avatar
ijohnnny
 
Posts: 3412
Joined: Sun Oct 22, 2006 12:15 am

Post » Fri May 13, 2011 1:02 pm


'course, the desired contents lists aren't going to be all-inclusive, so for weapons and armour it might be better to just scan the inventory for armour with/without the Power Armour flag, or with a given skill or animation type.



Yeah, universal compatibility's the real trick. I don't know how you would designate things as quest items without having an explicit list. Can you do a ref walk through the player's inventory?
User avatar
Micah Judaeah
 
Posts: 3443
Joined: Tue Oct 24, 2006 6:22 pm

Post » Fri May 13, 2011 4:46 pm

The latest FOSE added base object walking, yeah. I'd forgotten it was undocumented ATM- it's ref.GetInventoryObject index. You can't pick out specific instances of a base object in inventory (they're stored as ExtraData which we can't access yet) but otherwise it works fine.
User avatar
Jessica Nash
 
Posts: 3424
Joined: Tue Dec 19, 2006 10:18 pm


Return to Fallout 3