0 votes
by (870 points)

I installed F2Andy's inventory into my game and had it working perfectly, but I realized I wanted a bit more than simply buying and acquiring items. I've searched through a few threads but I haven't found any working off of this system/were simple enough for me to understand. If it's necessary that I make something from scratch I'm totally willing to try! I'm just not sure where at all to start.

I'd like to have a standard pack/shop setup, where the player has a bag where they may hold items and eventually a home passage where they might also store their things. I'd like to be able to add properties to the items themselves (which I couldn't figure out how to do for F2Andy's system) like:

<<set $potion = {
  name : "healing potion",
  category : "magic",
  cost : 25,
  description : "A red potion that tastes like candy and increases your health when ingested.",
  uses : 1
}>>

<<set $enchantment = {
  name : "stony shield",
  category : "magic",
  cost : 100,
  description : "An enchantment that creates a wall of stone between you and an enemy.",
  uses : 4
}>>

so the items can be reusable, get deleted when they're used up, be carried or stored or destroyed by the player and potentially sold back to shops in exchange for currency. Is that doable? With or without F2Andy's inventory system.

1 Answer

0 votes
by (44.7k points)
edited by
 
Best answer

If you're interested, I'm working on a "plugin" for Twine/SugarCube 2 to make it easier to work with inventories.  It's called the "Universal Inventory System", or "UInv" for short.  You can download the current version of UInv from my GitHub section here.  To add it to your Twine project, just copy the contents of the "UniversalInventorySystem.js" to the bottom of your JavaScript section, edit in your default items and bags, and then you're ready to use it.

Default versions of your items would go in the "ItemData" function like this:

Items.healingpotion = {
  name : "healing potion",
  plural : "healing potions",
  category : "magic",
  cost : 25,
  description : "A red potion that tastes like candy and increases your health when ingested."
};

Items.stonyshield = {
  name : "stony shield",
  plural : "stony shields",
  category : "magic",
  cost : 100,
  description : "An enchantment that creates a wall of stone between you and an enemy."
};

Items.coins = {
  name : "coin",
  plural : "coins",
  cost : 1,
  description : "The coin is the realm's standard unit of trade."
};

You can also use the "Item Builder" in the UInv Help File to build items like this (the help file is included in the .zip file you can get from the GitHub site).

Then, you could give the player an inventory like this:

<<set UInv.CreateBag("backpack")>>
<<set UInv.AddItem("backpack", "stonyshield")>>
<<set UInv.AddItem("backpack", "healingpotion", 4)>>
Your inventory includes <<=UInv.DisplayItemList("backpack", "plural", "nothing", ",", "and", "name")>>.

That would create a bag called "backpack", then add 1 "stonyshield" and 4 "healingpotion" to it, and then display the contents of the backpack to the player.

If a player wanted to use the "stonyshield" item, then you could do something like this:

/* use up a "stony shield" enchantment, if you have one */
<<if UInv.BagHasItem("backpack", "stonyshield")>>
	<<set UInv.DeleteItem("backpack", "stonyshield", 1)>>
	/* add your code here for what effect that would have */
<</if>>
/* if that was the last "stony shield" enchantment then you won't see it in the item list anymore */
Your inventory includes <<=UInv.DisplayItemList("backpack", "plural", "nothing", ",", "and", "name")>>.

And then there are a bunch of other things you could do:

/* create a chest and add a "stony shield" enchantment to its contents */
<<set UInv.CreateBag("chest")>>
<<set UInv.AddItem("chest", "stonyshield")>>

/* move a "healing potion" from the backpack to the chest */
<<set UInv.MoveItem("backpack", "chest", "healingpotion", 1)>>

/* move all of the "stony shields" from the chest to the backpack */
<<set UInv.MoveItem("chest", "backpack", "stonyshield")>>
Your backpack contains <<=UInv.DisplayItemList("backpack", "plural", "nothing", ",", "and", "name")>>.
Your chest contains <<=UInv.DisplayItemList("chest", "plural", "nothing", ",", "and", "name")>>.

/* sell a "healing potion" item to a store for 90% of cost (rounded down) */
<<if UInv.BagHasItem("backpack", "healingpotion")>>
	<<set UInv.AddItem("backpack", "coins", Math.trunc( UInv.GetItemPropertyValue("backpack", "healingpotion", "cost") * 0.9) )>>
	<<set UInv.DeleteItem("backpack", "healingpotion", 1)>>
<</if>>
Your backpack contains <<=UInv.DisplayItemList("backpack", "plural", "nothing", ",", "and", "name")>>.

UInv isn't quite complete yet, so some things may change, but it's far enough along that you could use it for stuff like the above.  See the UInv Help File if you have any questions on the UInv functions (note: the help file is pretty bare bones right now, it will get filled out when it's close to complete).

Currently I'm adding the ability for items to have "pockets".  Once I'm done with that I plan to add the following:

  • "health bars" (which you can see a preview of in the "UInv_Image_Pre-Load_Test.html" file)
  • a "clothing mannequin" for dressing and equipping characters
  • an improved drop-down menu (see a preview of it in the help file's "item builder" when adding images)
  • a "shop" to make it easy for people to implement buying and selling items.

You could do those on your own currently, but I'd like to make an interface so that it's easy for anyone to create those things.

Hope that helps!  :-)

by (870 points)
edited by
Ohh, that's really neat! Thanks so much! I'm going to give it a shot. C:
by (870 points)

Sorry, double post bc I'm trying to test everything to make sure I understand.

So from what I see, 

Your inventory includes <<=UInv.DisplayItemList("backpack", "plural", "nothing", ",", "and", "name")>>.

is something I should place in PassageFooter? If I have to paste it into every passage where the bag is relevant, that would be best, right?

Second, I believe the code you gave me was for only one stony shield, but when I use the "use up" option 

/* use up a "stony shield" enchantment, if you have one */
<<if UInv.BagHasItem("backpack", "stonyshield")>>
	<<set UInv.AddItem("backpack", "stonyshield", -1)>>
	/* add your code here for what effect that would have */
<</if>>
/* if that was the last "stony shield" enchantment then you won't see it in the item list anymore */
Your inventory includes <<=UInv.DisplayItemList("backpack", "plural", "nothing", ",", "and", "name")>>.

The final caption still remains: "Your inventory includes stony shield and four healing potions." and doesn't remove the singular stony shield from my inventory. I thought it might not set the action until the next passage, so I added one after the "use shield" act but it still doesn't remove the stony shield from my inventory. Did I bork it up somewhere?

by (44.7k points)
edited by

Where you want to put the code that displays the item list is up to you.  Put it wherever you think you'd need it if you want to show what's currently in the inventory.  If you might have a large inventory, you might want to make an inventory page you can bring up at any time by clicking a button on the UI bar (if you have more than one such button, then you should check out the SugarCube "Tips" section on "Arbitrarily long returns").

Alternately, if' you've looked at the "UInv_Sample_Code.html" you'd see that you can even set up an inventory bar that shows images for the items in an item table.  You could use one of those and display that at a fixed position on the screen if you wanted to.  It's really up to you.

 

As for adding negative items, I forgot that I took that out.  You can only add positive numbers of items with the AddItem function currently (I'll note that in the help file in the next release).  Just change:

	<<set UInv.AddItem("backpack", "stonyshield", -1)>>

to:

	<<set UInv.DeleteItem("backpack", "stonyshield", 1)>>

and that will delete one of them correctly.  The code is probably more readable that way anyways.  (I'll change my earlier post as well.)

That said, if you do come across any bugs, feel free to report them in the "Issues" section of GitHub, and I'll make sure they get fixed in the next release.

by (870 points)

Ha! That worked, thanks.

If you don't mind me bugging you further: how would I go about listing all the items in an inventory passage? Instead of "apple, boots, key" something similar to:

apple

boots

key

And once that's done, would it be possible to add options next to the items themselves? Similar to

apple [inspect | eat | toss]

boots [inspect | wear | toss]

key [inspect | wear | toss]

with "inspect" giving the item description when clicked. :/ I fear I'm making things too complicated for myself.

I also had some trouble figuring out how to make a working shop? This is what I had so far. (Same setup I had with F2Andy's code). The items in the javascript:

Items.apple = {
  name : "apple",
  plural : "apples",
  type : "food",
  cost : 10,
  description : "A sweet red fruit."
}

Items.boots = {
  name : "boots",
  type : "clothes",
  cost : 100,
  description : "Leather boots, excellent for walking."
}

Items.key = {
  name : "key",
  plural : "keys",
  type : "misc",
  cost : 20,
  description : "A shiny golden key. No one knows what it opens."
}
:: StoryInit
<<set $coin to 100>>
<<set UInv.CreateBag("backpack")>>

:: Shop
<<if $gameDate.getUTCHours() gte 8 and $gameDate.getUTCHours() lte 17>>[[shop open]]<<else>><<return "sorry, we're closed>><</if>>

<<if GameDays[$gameDate.getDay()] is "MONDAY">> Open
<<elseif GameDays[$gameDate.getDay()] neq "MONDAY">> Closed
<</if>>

Welcome to the shop! You have <<print $coin>> coins.

<<if $coin gte 10>>Apples $10 <<link "Buy" `passage()`>><<set $coin -= 10>><<set UInv.AddItem("backpack", "apple")>><</link>><<else>>you don't have enough money, sorry.<</if>>
<<if $coin gte 100>>Boots $100 <<link "Buy" `passage()`>><<set $coin -= 100>><<set UInv.AddItem("backpack", "boots")>><</link>><<else>>you don't have enough money, sorry.<</if>>
<<if $coin gte 20>>Key $20 <<link "Buy" `passage()`>><<set $coin -= 20>><<set UInv.AddItem("backpack", "key")>><</link>><<else>>you don't have enough money, sorry.<</if>>

:: Backpack
You have <<=UInv.DisplayItemList("backpack", "plural", "nothing", ",", "and", "name")>> in your backpack.

When clicked it removes the amount of coins for the purchase, but when I go to the backpack passage, it says I have nothing in my backpack.

by (44.7k points)
edited by

You can use GetItemsArray() to get the list of items in a bag into an array, and then display information about those items however you'd like.  For example:

:: your_passage
<<inventory>>

:: widgets[widget nobr]
<<widget "inventory">>
	<span id="inventory"><<include "inventory">></span>
<</widget>>

:: inventory
<<set _Items = UInv.GetItemsArray("backpack")>>
<<if _Items.length == 0>>
	Your backpack is empty.
<<else>>
	Your backpack contains:
	<<capture _i>>
		\<<for _i = 0; _i < _Items.length; _i++>>
			_Items[_i]<<set _q = UInv.BagHasItem("backpack",_Items[_i])>><<if _q > 1>> (_q)<</if>> [
				\<<link "inspect">>
					<<run alert(UInv.GetItemPropertyValue("backpack", _Items[_i], "description"))>>
				\<</link>>
				\<<switch UInv.GetItemPropertyValue("backpack", _Items[_i], "type")>>
				\<<case "food">>
				\ | <<link "eat">>
					/* code to handle eating food here */
					<<set UInv.DeleteItem("backpack", _Items[_i], 1)>>
					<<replace "#inventory">><<include "inventory">><</replace>>
				\<</link>>
				\<<case "clothes">>
				\ | <<link "wear">>
					<<run alert(UInv.GetItemPropertyValue("backpack", _Items[_i], "description"))>>
				\<</link>>
				\<<default>>
					\/* handle default switch case here */
				\<</switch>>
				\| <<link "toss all">>
					<<set UInv.DeleteItem("backpack", _Items[_i])>>
					<<replace "#inventory">><<include "inventory">><</replace>>
				\<</link>>
			\ ]
		\<</for>>
	\<</capture>>
<</if>>

This creates an <<inventory>> macro you can use whenever you need to show your inventory (make sure you include the "widget" and "nobr" tags on the "widgets" passage).  It uses the <<replace>> macro so that it can update the inventory display without changing passages.

As for why the items aren't getting added in your code, I'm not quite sure.  It looks like it should work.  It could be the item data isn't in the ItemData() function properly, which would prevent AddItem() from being able to work.  Try adding the following to your "StoryInit" passage:

<<set UInv.SetUserAlerts(UInv.ERROR_THROW_ERROR)>>

That will cause UInv to throw errors when something goes wrong, instead of silently handling the problem.  (See "SetUserAlerts" in the UInv help file for the various options for error handling.)

I'd also recommend you import the "UInv_Sample_Code.html" into Twine and take a look at the source there for further examples of how to use UInv.

Hope that helps!  :-)

by (870 points)
edited by

Thank you! Thanks so much for taking the time and having the patience to help me out, I'm learning a bunch and I really appreciate it.

I got the shop feature working for the most part. Half the time I get the error <<set>>: bad evaluation: UInv Error: AddItem failed. Item Type "apple" is not a default item. I'll also get <<=>> : bad evaluation: UInv Error: GetItemPropertyValue cannot find property "name" in item "belt". This specific error goes away if I buy two belts, so I thought maybe it had something to do with the singular/plural but I've got no clue what's making it happen. I have the property name in all of my items. If I get these errors and go to my inventory, only the "inspect" and "toss all" options come up, not eat/wear if it's food or clothing.

Items.apples = {
  name : "apple",
  plural : "apples",
  type : "food",
  cost : 10,
  description : "A sweet and firm red fruit."
}

Items.belt = {
  name : "belt",
  singular : "belt",
  plural : "belts",
  type : "clothes",
  cost : 10,
  place : "hips1",
  size : 2,
  description : "A sweet and firm red fruit."
}


Sometimes I get these errors and sometimes I don't (without changing anything regarding the code?) so idk what's up but I'm combing through all the links you've dropped me. 

edit: okay, so changing the property value from "type" to "category" seems to have fixed things for now, so yay!

edit 2: spoke too soon, but i'll get it lol

edit 3: okay i changed it back to "type" and it's working so far. 

edit 4: freaking semicolons, wily bastards. 

by (44.7k points)
Re: "UInv Error: AddItem failed. Item Type "apple" is not a default item." - You defined the item's default type as "apples", and not "apple".

Also, you might want to change the description on your belt.  ;-)

Is everything working now?
by (870 points)

Haha, I literally just realized that about a half hour ago. I'm still having some issues, though. :/

Current error: 

<<set>> : bad evaluation: UInv Error: AddItem failed. Item type "apple" is not a default item.

I get that for every item except for the belt when I try to purchase things in the shop passage.

items

Items.apple = {
  name : "apple",
  plural : "apples",
	type : "food",
	size : 1,
	nutrition : 1,
  cost : 10,
  description : "A sweet and firm red fruit."
};

Items['apple pie'] = {
  name : "apple pie",
  plural : "apple pies",
	type : "food",
	size : 2,
	nutrition : 3,
  cost : 10,
  description : "A sweet and tart pie."
};

Items.belt = {
  name : "belt",
  plural : "belts",
	type : "clothes",
	size : 1,
  cost : 10,
	place : "hips1",
  description : "A simple leather belt with a scratched buckle."
};

Items.cider = {
  name : "cup of cider",
  plural : "cups of cider",
	size : 2,
	nutrition : 1,
  cost : 25,
  description : "A sweet and mildly spiced drink."
};

Items.cloak = {
  name : "cloak",
  plural : "cloaks",
	type : "clothes",
	size : 2,
  cost : 10,
	place : "shoulders",
  description : "A light, unembellished cloak."
};

:: shop

Welcome to the shop! You have <<print $coin>> coins.

<<if $coin gte 10>>apple $10 <<link "buy" `passage()`>><<set $coin -= 10>><<set UInv.AddItem("backpack", "apple")>><</link>><<else>>you don't have enough money, sorry.<</if>>
<<if $coin gte 10>>apple pie $10 <<link "buy" `passage()`>><<set $coin -= 10>><<set UInv.AddItem("backpack", "apple pie")>><</link>><<else>>you don't have enough money, sorry.<</if>>
<<if $coin gte 10>>belt $10 <<link "buy" `passage()`>><<set $coin -= 10>><<set UInv.AddItem("backpack", "belt", 1)>><</link>><<else>>you don't have enough money, sorry.<</if>>
<<if $coin gte 10>>cider $10 <<link "buy" `passage()`>><<set $coin -= 10>><<set UInv.AddItem("backpack", "cider")>><</link>><<else>>you don't have enough money, sorry.<</if>>
<<if $coin gte 10>>cloak $10 <<link "buy" `passage()`>><<set $coin -= 10>><<set UInv.AddItem("backpack", "cloak")>><</link>><<else>>you don't have enough money, sorry.<</if>>

What I tried to, too, do was set up a chest passage for when the player goes home, to remove things from their backpack and place it in their chest/take things from their chest and place it in their backpack. It works! Except when I try to take things out of the chest (if the backpack is empty).

:: inventory

<div id="inventory"><<set _Items = UInv.GetItemsArray("backpack")>>
<<if _Items.length == 0>>
	Your backpack is empty.
<<else>>
	Your backpack contains:
	<<capture _i>>
		\<<for _i = 0; _i < _Items.length; _i++>>
			_Items[_i]<<set _q = UInv.BagHasItem("backpack",_Items[_i])>><<if _q > 1>> (_q)<</if>> [ 
				\<<link "inspect">>
					<<run alert(UInv.GetItemPropertyValue("backpack", _Items[_i], "description"))>>
				\<</link>>
				\<<switch UInv.GetItemPropertyValue("backpack", _Items[_i], "type")>>
				\<<case "food">>
				\  | <<link "eat">>
					/* code to handle eating food here */
					<<if UInv.GetItemPropertyValue("backpack", _Items[_i], "nutrition") is 1>><<set $hunger += 2>> $hunger
					<<elseif UInv.GetItemPropertyValue("backpack", _Items[_i], "nutrition") is 2>><<set $hunger += 10>> $hunger
					<<elseif UInv.GetItemPropertyValue("backpack", _Items[_i], "nutrition") is 3>><<set $hunger += 20>> $hunger
					<</if>>
					<<set UInv.DeleteItem("backpack", _Items[_i], 1)>>
					<<replace "#inventory">><<include "inventory">><</replace>>
				\<</link>>
				\<<case "clothes">>
				\  | <<link "wear">>
					<<run alert(UInv.GetItemPropertyValue("backpack", _Items[_i], "description"))>>
				\<</link>>
				\<<default>>
					\ /* handle default switch case here */
				\<</switch>>
				\ | <<link "toss all">>
					<<set UInv.DeleteItem("backpack", _Items[_i])>>
					<<replace "#inventory">><<include "inventory">><</replace>>
				\<</link>>
			\ ]
		\<</for>>
	\<</capture>>
<</if>>
\<<return>></div>

:: chest 

<div id="chest"><<set _Items = UInv.GetItemsArray("chest")>>
<<if _Items.length == 0>>
	Your chest is empty.
<<else>>
	Your chest contains:
	<<capture _i>>
		\<<for _i = 0; _i < _Items.length; _i++>>
			_Items[_i]<<set _q = UInv.BagHasItem("chest",_Items[_i])>><<if _q > 1>> (_q)<</if>> [ 
				\<<link "inspect">>
					<<run alert(UInv.GetItemPropertyValue("chest", _Items[_i], "description"))>>
				\<</link>>
				\  | 
				\<<link "take">>
				<<set UInv.MoveItem("chest", "backpack", _Items[_i], 1)>>
				<<replace "#chest">><<include "chest">><</replace>>
				\<</link>>
			\ ]
		\<</for>>
	\<</capture>>
<</if>>

<<set _Items = UInv.GetItemsArray("backpack")>>
<<if _Items.length == 0>>
	Your backpack is empty.
<<else>>
	Your backpack contains:
	<<capture _i>>
		\<<for _i = 0; _i < _Items.length; _i++>>
			_Items[_i]<<set _q = UInv.BagHasItem("backpack",_Items[_i])>><<if _q > 1>> (_q)<</if>> [ 
				\<<link "inspect">>
				<<run alert(UInv.GetItemPropertyValue("backpack", _Items[_i], "description"))>>
				\<</link>>
				\  | 
				\<<link "store">>
				<<set UInv.MoveItem("backpack", "chest", _Items[_i], 1)>>
				<<replace "#chest">><<include "chest">><</replace>>
				\<</link>>
			\ ]
		\<</for>>
	\<</capture>>
<</if>>
\<<return>></div>

For example, since belts are the only thing I can buy: if I have three belts and put two in the chest, I can take two back out of the chest and put into the backpack no problem. If I store all three belts in the chest and try to take one back into the backpack, it doesn't work. I get this error:

<<set>> : bad evaluation: UInv Error: Name passed to MoveItem is not a string.

This has me stumped.

by (44.7k points)

It sounds like you're still not setting the item data in the correct location.  Make sure your item data is inside the UInv ItemData function, near the end of the UInv JavaScript.  It should go just after the line that says:

		// End of example items.

You should probably get rid of all of the example items and bags too.  (Look for "// Start of example variable items." and "// Start of example bags." to find the other example objects.)

The reason why belts would work, but not apples, is that "belt" is one of the example items.

Also, I note that the "cider" item is missing a "type: "food",".

As for why it's failing to be able to take items out of the chest, the problems is that you used the "_Items" variable for both the chest and the backpack, so the values in the "_Items" array will have changed from the "chest" items to the "backpack" items by the time the user clicks the <<link>>.  To fix this you just need to capture the correct value by changing:

	<<capture _i>>

to:

	<<capture _i _Items>>

The <<capture>> macro "captures" the value of a variable at the time the code is originally executed, as opposed to the value it has at the time the user clicks the <<link>> (or whatever) later on.  This will fix the "Name passed to MoveItem is not a string." error.

That should do it!

by (870 points)
Ahhh, ty ty ty, that fixed everything! :D You're the absolute best. Now i can work on equipping/clothes.
...