How do I apply a filter to the Tinker's custom work screen?

Freeform discussion about anything related to modding Transcendence.
Post Reply
Arkheias
Commonwealth Pilot
Commonwealth Pilot
Posts: 95
Joined: Mon Jun 02, 2014 8:06 pm

I'm currently developing a mod (Cabbage Corp) which implements stations that offer custom equipment like with the Tinker's Guild stations. My problem here is that I now have over 150+ recipes and this kind of makes the dock screen unwieldy. My current solution involves separating the recipes into categories like weapons, armor, illegal, and another one for everything else. Since I've started this mod I have created two higher level stations and I'm trying to find a more simple way to sort the lists by level so that the lower level stations don't offer recipes for devices that are too advanced for them to install. I could make a new set of lists for each higher level station but each list would effectively contain a copy of each lower level version of itself and each time I created a new recipe I would have to add it to up to three lists.

What I would like to do would be to implement a filter function such as "scrSetListFilter" or "filter" such that the dock screen would only show items from the master list that match a set of criteria defined in the action in which the dockscreen is called up.

From how I understand it (1), once properly implemented the "scrSetListFilter" function would let the dock screen process the entire list but it would only show the items that match the criteria. My problem with it is that from how I understand it, the Tinker's Guild recipe list that my recipe list is based on is actually a list of lists and I have now idea as to where I should slap on this function to get my idea to work. The only places where I saw this function used in vanilla Transcendence were when it was applied to lists of items generated from the contents of stations or the playership using the <ListOptions> block, and since the Tinker's guild dock screen that I am abusing to create my modified version implements a <List> block instead I have no idea if the function can even be implemented.

From how I understand it (2), once properly implemented the "Filter" function would change which parts of the list the dock screen actually processes and is therefore too complicated for me to even consider using. Also I currently have no idea what exactly the @ and ' symbols do, although I believe using the ' symbol before a variable has something to do with defining it as containing a list or something.

Also, my mod currently has two separate versions for whether or not you have Corporate Command installed. I did this because both versions define the Cabbage Corp sovereign and since I based the recipe lists off of the Tinker's Guild, the recipe lists are defined in the sovereign. I have no idea how to be sure of which version of the sovereign will take precedence if it is defined in both extensions and they are loading at the same time, and I have no idea how to make the Corporate Command version (Cabbage Corporate Command) merely append items to the recipe list without having to define the sovereign.

Any help with either of the issues would be appreciated. I have included copies for the parts of the code for calling up the dock screen and the dock screen itself (which is mostly a copy of the Tinker's custom work dock screen with the mission part removed because I did not want to deal with that). After this dock screen is finished, I would have no problems with setting it up as a fancy library/tutorial of its own and putting on Xelerus for anyone to use freely. I would also have no problems with someone else doing this, and long as credit was given to all those involved, which is what I would do if I uploaded it, which I would be hesitant to do as I am slightly OCD and have already spent one hour and forty minutes creating this post, which seems way too long compared to the 4-5 hours I spent trying to solve this problem on my own.

TLDR: I am trying to set up one custom work dock screen to rule them all. My current version involves allowing the specific recipe list to be defined in the action in which the dock screen is called up, but I would also like to be able to pass through criteria to the dock screen from the action, and use those criteria to filter the custom work screen to only show items that match those criteria such as the level of the item and whether or not it is tagged as illegal or military. Any help would be appreciated. Posting a working alternative of this dock screen with these features and a whole bunch of other random bells and whistles would also be appreciated. Please help me.

Code: Select all

							<Action name="Custom Weapons" key="W">
								(block Nil
									(setq gSovereign &svCCCabbageCorp;)
									(setq gRecipes 'WeaponRecipes)
									(scrShowScreen gScreen "&dsCCCabbageCustomWork;")
								)
							</Action>
							
							<Action name="Custom Armor" key="A">
								(block Nil
									(setq gSovereign &svCCCabbageCorp;)
									(setq gRecipes 'ArmorRecipes)
									(scrShowScreen gScreen "&dsCCCabbageCustomWork;")
								)
							</Action>

Code: Select all

	
	<DockScreen UNID="&dsCCCabbageCustomWork;"
			type=           "customItemPicker"
			backgroundID=   "&rsItemListScreen;"
			nestedScreen=   "true"
			>
		<List>
			(map (typGetData gSovereign gRecipes) 'excludeNil theRecipe
				(tinkerCreateItem theRecipe 1)
				)
		</List>
		
		<Panes>
			<Default>
				<OnPaneInit>
					(block (theItem theRecipe theComponents desc componentDesc componentPrice workCost errorDesc missingComponents maxCount availableComponents)
						(setq theItem (scrGetItem gScreen))
						
						;	Find the recipe for this item
						
						(setq theRecipe
							(@ (filter (typGetData gSovereign gRecipes) theRecipe
									(eq (@ theRecipe 'item) (itmGetType theItem))
									)
								0
								)
							)
							
						;	Describe the components needed
						
						(setq theComponents 
							(map (@ theRecipe 'components) theDesc
								(itmCreate (@ theDesc 'item) (@ theDesc 'count))
								)
							)

						(setq componentDesc (strItemList theComponents 0x0108))
							
						;	Compute the total price of the components and compare to the
						;	price of the result.
						
						(setq componentPrice 
							(map theComponents 'reduceSum theComponent 
								;	For devices we assume a damaged device
								(if (itmMatches theComponent "d")
									(multiply (itmGetCount theComponent) (itmGetPrice (itmSetProperty theComponent 'damaged True)))
									(multiply (itmGetCount theComponent) (itmGetPrice theComponent))
									)
								)
							)
						
						;	The total cost of the work compensates for any difference between
						;	the input and output prices.
						
						(setq workCost 
							(add
								(max 
									50
									(intRoundUp (subtract (itmGetPrice theItem) componentPrice) 25)
									)
								(@ theRecipe 'extraCost)
								)
							)
							
						;	For each required component, add up the number of items the player
						;	actually has. Note that we accept damaged items.
						
						(setq availableComponents
							(map theComponents theComponent
								(block (countAvailable)
									(setq countAvailable 0)
									
									;	Add the number of non-damaged items we have
								
									(setq countAvailable (add countAvailable
										(objHasItem gPlayerShip theComponent 1)
										))
										
									;	Now add damaged items
									
									(setq countAvailable (add countAvailable
										(objHasItem gPlayerShip (itmSetProperty theComponent 'damaged True) 1)
										))
										
									;	Compose entry
									
									{	item: theComponent
										available: countAvailable
										}
									)
								)
							)
							
						;	Generate a list of the number of items the player is missing.
						;	Note that we accept damaged items.
						
						(setq missingComponents
							(map availableComponents 'excludeNil theEntry
								(block (countAvailable)
									(switch
										(eq (@ theEntry 'available) 0)
											(cat "\n\nUnfortunately, you do not have any " (itmGetName (@ theEntry 'item) 0x102) ".")
											
										(ls (@ theEntry 'available) (itmGetCount (@ theEntry 'item)))
											(cat "\n\nUnfortunately, you only have " (itmGetName (itmSetCount (@ theEntry 'item) (@ theEntry 'available)) 0x1100) ".")
											
										;	Otherwise we have enough, so these items are not missing
										
										Nil
										)
									)
								)
							)
							
						;	See if the player has the required components and money
						
						(switch
							missingComponents
								(block Nil
									(setq maxCount 0)
									(setq errorDesc (@ missingComponents 0))
									)
								
							(leq (plyGetCredits gPlayer) workCost)
								(block Nil
									(setq maxCount 0)
									(setq errorDesc "\n\nUnfortunately, you cannot afford the cost.")
									)

							(block Nil
								;	Figure out the maximum number of items we could create
						
								(setq maxCount (divide (plyGetCredits gPlayer) workCost))
								(enum availableComponents theEntry
									(setq maxCount (min
										maxCount
										(divide (@ theEntry 'available) (itmGetCount (@ theEntry 'item)))
										))
									)
									
								(setq errorDesc "")
								)
							)
						
						;	Set the description
						
						(switch
							(not componentDesc)
								(scrSetDesc gScreen
									"\"To fabricate " (itmGetName theItem 0x0108) " we charge " workCost " credits."
									errorDesc
									"\""
									)
								
							(scrSetDesc gScreen
								"\"To fabricate " (itmGetName theItem 0x0108) " we need "
								componentDesc
								" plus " workCost " credits."
								errorDesc
								"\""
								)
							)
							
						;	Remember the recipe, the components, and the cost for later
						
						
						(scrSetData gScreen 'recipe theRecipe)
						(scrSetData gScreen 'components theComponents)
						(scrSetData gScreen 'cost workCost)
						(scrSetData gScreen 'maxCount maxCount)
							
						;	Disable Fabricate action if we can't do it
						
						(scrEnableAction gScreen 0 (gr maxCount 0))
						)
				</OnPaneInit>
				
				<Actions>
					<Action name="Fabricate" default="1" key="F">
						(if (gr (scrGetData gScreen 'maxCount) 1)
							(scrShowPane gScreen "FabricateCount")
							
							(block Nil
								(scrSetData gScreen 'result 
									(tinkerFabricate 
										(scrGetData gScreen 'recipe)
										(scrGetData gScreen 'cost)
										1
										)
									)
								(scrShowPane gScreen "FabricateResult")
								)
							)
					</Action>
					
					<Action name="Done" cancel="1" key="D">
						(scrExitScreen gScreen)
					</Action>
				</Actions>
			</Default>
			
			<FabricateCount
					showCounter=	"true"
					>
				<OnPaneInit>
					(block Nil
						(scrSetDesc gScreen (cat "How many items do you wish to fabricate?"))
						(scrSetCounter gScreen (scrGetData gScreen 'maxCount))
						)
				</OnPaneInit>
			
				<Actions>
					<Action name="Fabricate" default="1" key="F">
						(block (count)
							(setq count (scrGetCounter gScreen))
							(if (gr count (scrGetData gScreen 'maxCount))
								(scrSetCounter gScreen (scrGetData gScreen 'maxCount))
								(block Nil
									(scrSetData gScreen 'result 
										(tinkerFabricate 
											(scrGetData gScreen 'recipe)
											(scrGetData gScreen 'cost)
											count
											)
										)
									(scrShowPane gScreen "FabricateResult")
									)
								)
							)
					</Action>
					
					<Action name="Cancel" cancel="1" key="C">
						(scrShowPane gScreen "Default")
					</Action>
				</Actions>
			</FabricateCount>
				
			<FabricateResult
					noListNavigation="true"
					>
				<OnPaneInit>
					(block (theResult)
						(setq theResult (scrGetData gScreen 'result))
						(scrSetDesc gScreen 
							"After much work with the robots the technician returns with their creation: "
							"\"Nothing to it once you know how these machines work.\"\n\n"
							
							"Fabricated: " (itmGetName (@ theResult 'itemsCreated) 0x08) ".\n"
							"Consumed: " (strItemList (@ theResult 'itemsConsumed) 0x08) ".\n"
							"Total Cost: " (@ theResult 'totalCost) " credits."
							)
						)
				</OnPaneInit>
			
				<Actions>
					<Action name="Done" default="1" cancel="1" key="D">
						(scrExitScreen gScreen)
					</Action>
				</Actions>
			</FabricateResult>
			
		</Panes>
	</DockScreen>
Cabbage Corp, the only mod with cabbages!

Please feel free to submit bug reports or issues related to the Cabbage Corp mod on the GitHub page, the forum thread, in a private message or even on the Xelerus page. Suggestions are fine too.
RPC
Fleet Admiral
Fleet Admiral
Posts: 2876
Joined: Thu Feb 03, 2011 5:21 am
Location: Hmm... I'm confused. Anybody have a starmap to the Core?

Can you post the whole mod/ attach the XML? I'm going to work on this but it would be easier to see what you already have and I can work off it from there.
Tutorial List on the Wiki and Installing Mods
Get on Discord for mod help and general chat
Image
Image
Der Tod ist der zeitlose Frieden und das leben ist der Krieg
Wir müssen wissen — wir werden wissen!
I don't want any sort of copyright on my Transcendence mods. Feel free to take/modify whatever you want.
RPC
Fleet Admiral
Fleet Admiral
Posts: 2876
Joined: Thu Feb 03, 2011 5:21 am
Location: Hmm... I'm confused. Anybody have a starmap to the Core?

Here is a response to the "easy" questions you've posed in your post.
What I would like to do would be to implement a filter function such as "scrSetListFilter" or "filter" such that the dock screen would only show items from the master list that match a set of criteria defined in the action in which the dockscreen is called up.
First I'll give you a nice resource for dockscreens
http://forums.kronosaur.com/viewtopic.php?f=8&t=1833

A word of caution:
scrSetListFilter ONLY works with dockscreens that use <ListOptions>
You would need to make your own list and use <list> if you're using a list other than what is hardcoded (which is currently lists from station cargo and playership cargo, etc).
This is why George is using <list> instead of <Listoptions> because listoptions doesn't deal with tinker data normally.

In this cause, <listoptions> is paired with scrSetListFilter.
Example of Listoptions:

Code: Select all

	<ListOptions
		dataFrom=	"station"
		list=		"*"
		>
It seems like what you need is <list> and knowledge of how to store and manipulate data from lists.

TLDR for using <LIST> from the link:
"The list prepared requires a certain format to be displayed as an item: (list [Title] (list [Image]) [Description])"
All you have to do is use a customPicker screen and stick whatever you want into <list> and the rest works itself out.
Also I currently have no idea what exactly the @ and ' symbols do, although I believe using the ' symbol before a variable has something to do with defining it as containing a list or something.
This is a list of functions
http://forums.kronosaur.com/viewtopic.php?f=8&t=6453
@ is a function that looks for an element from a list by its index (the index also starts from 0)
Example:
digdug's funclist defines @ as:
(@ list index)
So we need to give the @ function two things:
1. a list
2. an index number

An index number is defined for lists by a few things.
First, you need to know how you're starting the index. Some indices start with 0 and others start with 1.
For @, the index starts at 0.
This means that the FIRST element is paired with the index number ZERO, and the SECOND element of the list is paired with the index number ONE.
This gets much trickier when you have lists nested in each other.

Examples:
@ example:
Let's have (list 1 2 3 4 5 6).
Then the first element is called by the index 0:
(@ (list 1 2 3 4 5 6) 0) => 1
Here the list is (list 1 2 3 4 5 6) and the index number I used was 0.
The last element is called by the index 5.
(@ (list 1 2 3 4 5 6) 5) => 6

Nested list example:
Let's have more lists. I'll define a list with lists as elements. Let's call it the master list. In this case, I am nesting lists inside each other.
(list (list "I" "AM") (list "SO") (list "META") (list "EVEN") (list "THIS") (list "ACRONYM"))
This is a list with 6 elements, and each element is a list itself.

What happens when I want to get the first element?
(@ (list (list "I" "AM") (list "SO") (list "META") (list "EVEN") (list "THIS") (list "ACRONYM")) 0) => (I AM)
Here I got the first element of the master list. That element is the list consisting of two elements: I and AM.
But wait!
We can go deeper!

What happens when I use @ twice in succession?
(@ (@ (list (list "I" "AM") (list "SO") (list "META") (list "EVEN") (list "THIS") (list "ACRONYM")) 0) 0) => I
Here I get the single element I.
To get AM, I need to change the index I used in the outer @.
(@ (@ (list (list "I" "AM") (list "SO") (list "META") (list "EVEN") (list "THIS") (list "ACRONYM")) 0) 1) => AM

(This is a bit confusing so if you have more questions just ask)
I'll explain how the game evaluates the function above.
In TLISP, order is always inside pair of parentheses first.
The simplest case is (()).
Let's say that this corresponds to (setq TheDoctor (list "TARDIS" "SONICSCREWDRIVER" "FEMALE_COMPANION")).
The game first evaluates the inner pair of parentheses.
This is (list "TARDIS" "SONICSCREWDRIVER" "FEMALE_COMPANION").
Then the game evaluates the outer parentheses, which is
(setq TheDoctor (list "TARDIS" "SONICSCREWDRIVER" "FEMALE_COMPANION")).

In the example above where I got AM, we have a LOT of parentheses, so let me break this down.
By just looking at each pairs of parentheses,
(@ (@ (list (list "I" "AM") (list "SO") (list "META") (list "EVEN") (list "THIS") (list "ACRONYM")) 0) 1)
becomes
(((()()()()()())))
In this case, we have three outer pairs of parentheses and six pairs of parentheses in sequence. Here the innermost pairs of parentheses are the six pairs of parentheses:
()()()()()()
so the game evaluates these first:
(list "I" "AM") (list "SO") (list "META") (list "EVEN") (list "THIS") (list "ACRONYM")
Now the game knows it has six lists.
The outer pair of parentheses encompassing the six lists is also a (list ).
So then the game is told to make the six lists into a bigger list.
Now we have a master list:
(list (list "I" "AM") (list "SO") (list "META") (list "EVEN") (list "THIS") (list "ACRONYM"))
Then we told the game to get the first element of that list with (@ (list (list "I" "AM") (list "SO") (list "META") (list "EVEN") (list "THIS") (list "ACRONYM")) 0), so we take the first element of our masterlist:
The first element of the master list is:
(list "I" "AM")
So now at this step we have yet another @ function, so now the game sees things like this:
(@ (list "I" "AM") 1).
Don't let the 1 confuse you! Since indices are started by 0, now we take the SECOND element of the inner list.
This is AM.
So the whole expression
(@ (@ (list (list "I" "AM") (list "SO") (list "META") (list "EVEN") (list "THIS") (list "ACRONYM")) 0) 1)
reduces to
AM
and if you understand this you can work miracles with storing information with lists.

This is a great explanation of what ' does:
http://forums.kronosaur.com/viewtopic.p ... 165#p55475
TLDR version:
Double quotes ("") are used to define strings properly. Since programmers are lazy, you can also use a single quote (') that tells the game NOT to evaluate the expression which means that for many purposes "string" and 'string are identical. For lists, the example George used is sufficient:
'(add 1 1)
is a list with three elements, add, 1 , 1 while
"(add 1 1)"
is a string that literally means (add 1 1). The game does NOT add 1 and 1 to make two. Treat the string as it is and think about "(add 1 1)" as a sequence of letters consisting of (,a,d,d, ,1, ,1,).
Also, my mod currently has two separate versions for whether or not you have Corporate Command installed.
A neat little trick I learned while making Civilian Crafts is that you can check for unids to see if a mod is installed. If you know which unids are valid, then you can use switches (the function switch [url link]) and code accordingly.
Civilian Crafts link:
http://xelerus.de/index.php?s=mod&id=1250

And lastly:

I noticed that tinkers use staticdata to get information.
I think a good idea would be to replace the staticdata with type data by using typSetData. If you use the same format for your information and replace all calls to the staticdata with the typsetdata version you can just manipulate your typsetdata instead of messing with static data.

I can't really do much more without looking at the mod directly, but hopefully my post helps.
If you have questons, please ask. It's easier to clear up misunderstandings by asking the right questions instead of trying to brute force mastery.
If you want to talk more about modding, summer school finally ended and I have a ~2 week gap to work on stuff so you can make more posts or stop by IRC to ask me and other modders more questions.

Also, I abuse lists a LOT in my mods so if you want to look at some examples I can attach them here. The problem is they're a bit too complicated and I didn't want to confuse so I'm just giving this out as extra credit.
Tutorial List on the Wiki and Installing Mods
Get on Discord for mod help and general chat
Image
Image
Der Tod ist der zeitlose Frieden und das leben ist der Krieg
Wir müssen wissen — wir werden wissen!
I don't want any sort of copyright on my Transcendence mods. Feel free to take/modify whatever you want.
TVR
Militia Commander
Militia Commander
Posts: 334
Joined: Sat Sep 08, 2012 3:26 am

Here is an undocumented feature of (scrSetListFilter) that will be very useful:

Code: Select all

(scrSetListFilter gScreen 
	(lambda (theItem)
		;; Show theItem if it is an ICX-type device
		(if (eq (itmGetCategory theItem) 8) ;; <- This condition can be anything, eg check if theItem is in another list, or can be installed, etc.
			true
			Nil
		)
	)
)
The above, run while an itemPicker dockscreen is open, will set the list filter to show only items that satisfy the custom criteria.

For a custom Tinkerer dockscreen sorted by category, initialize the specific itemStruct lists in an <Globals> or <OnGlobalPlayerEnteredSystem> and use the following code in any dockscreen event to filter the list according the criteria:

Code: Select all

(scrSetListFilter gScreen
	(lambda (theItem)
		(block (listOfItemtypesToAccept)
			;; listOfItemtypesToAccept is a list of item UNIDs loaded from a global variable
			(setq listOfItemtypesToAccept (typGetData &extensionUNID; "listOfItemtypesToAccept"))
			
			;; theItem will only be shown if UNID of theItem is in listOfItemtypesToAccept
			(if (find listOfItemtypesToAccept (itmGetType theItem)) ;; <- This condition can be anything, eg check if theItem is in another list, or can be installed, etc.
				true
				Nil
			)
		)
	)
)
Fiction is reality, simplified for mass consumption.
PGP: 0x940707ED, 5DB8 4CB4 1EF5 E987 18A0 CD99 3554 3C13 9407 07ED
Bitcoin: 1LLDr7pnZDjXVT5mMDrkqRKkAPByPCQiXQ
Post Reply