Add-ons / Simple Inventory System

The simple inventory allows Twine authors to create and manipulate array-based inventories for 'key' style items (as opposed to consumables or equipment). For a more advanced inventory plugin check the Universal Inventory System script.

Author ChapelR
Website https://twinelab.net/custom-macros-for-sugarcube-2/#/simple-inventory
Story format SugarCube 2
Last checked Sun Jun 02 2019
License Unlicense
Download sinv.zip

Index

Overview

📝 Note: Simple Inventory v3 is currently in beta! Once it stabilizes, this version of the Simple Inventory will be retired and the simple inventory will no longer be part of this collection.

The simple inventory allows Twine authors to create and manipulate array-based inventories for 'key' style items (as opposed to consumables or equipment). This system provides a great deal of functionality, including sorting, displaying item lists (with drop / transfer links), and creating multiple inventories (for creating 'rooms' or other containers, or party members) and transfering items between them. All of the functionality here has both a JavaScript API and a TwineScript Macro-based API, meaning the features are easily available from within your passages and inside scripts.

⚠️ Attention:

The simple inventory has undergone some pretty major changes since it first debuted. Version 1 was mostly a bit of syntactic sugar over an array system designed to help less-experienced authors utilize standard JavaScript arrays and methods in a scripting style they were more comfortable with (that is, macros). On rewriting this system, it seemed like a good idea to push things a little farther and create something that could be useful even to more experienced authros (hopefully, anyway). The changes make simple inventory a much more robust and feature-rich system, but unfortunately, old code written for v1.x of simple inventory is not compatible with the new simple inventory system.

The options object

The options object can be found near the top of the script (in the pretty version). It looks like this:

var options = {
    tryGlobal  : true, // send constructor to global scope
        defaultStrings : {
        empty     : 'The inventory is empty...',
        listDrop  : 'Discard',
        separator : '\n'
    }
};

It is recommended that you leave the tryGlobal option as true unless you know what you're doing.

Option tryGlobal

The functions, methods, and variables used by these systems all exist on the special SugarCube setup object. For ease of access to authors, everything that might be of use to them is then sent to the global scope as window.Inventory. If you don't want to send this object to the global scope, you can change this option to false.

📝 Note: Even if this option is set to true, the script will check to make sure window.Inventory is undefined before overwriting it.

If the global Inventory object is unavailable, either because you changed this setting or because it was already in use, you can still access things: replace Inventory... with setup.Inventory... in your code, or create your own gobal reference.

Option defaultStings

This set of options represents the default strings used for certain situations:

Macros

<<newinventory>>

<<newinventory>>

Syntax: <<newinventory variableName [itemList]>>

This macro creates a new inventory. Creating a new inventory is much like initializing a variable, and the best place to use this macro is in your StoryInit special passage. You must provide the macro with a valid TwineScript variable, passed in quotes (similar to how you might pass it to a <<textbox>> macro). The new inventory will be stored in the provided variable. You can optionally pass the inventory a list of items and the inventory will be initialized with these items inside it.

Arguments:

Usage:

/% create a new, empty inventory in the $inventory variable %/
<<newinventory '$inventory'>>

/% create an inventory in the $playerInventory variable and place two items in it %/
<<newinventory '$playerInventory' 'a pair of shoes' 'the shirt on your back'>>

/% create an inventory for the kitchen room and call it $kitchenInventory
   and place some items in it %/
<<newinventory '$kitchenInventory' `['a steak knife', 'a blender']` 'a calender'>>

/% create a suit case called $suitCase always contains a key,
   but also includes some random items %/
<<set _items to ['keys', 'a suit', 'a laptop', 'a cellphone', 'a wallet',
                 'sixteen cents', 'a hairpin', 'toothpaste', 'glue'].pluckMany(3)>>
<<newinventory '$suitCase' _items 'car key'>>

READ: Documentation

<<pickup>>

<<pickup>>

Syntax: <<pickup variableName [unique] itemList>>

The <<pickup>> macro adds items to inventory indicated by the $variable. These items are added to the end of the inventory. If the keyword unique is included before the item list, items that are already in the inventory will not be added to it. Caution: if the unique keyword is placed after the first item in the item list, an item called unique will be added to the inventory.

Arguments:

Usage: /% add an item to the inventory %/ <<pickup '$inventory' 'baseball card'>> /% you may wish to check if the player already has the item: %/ @@#link; <<link 'Pick up the gun.'>> <<if $playerInventory.has('a gun')>> <<replace '#links'>>You already have a gun, you don't need another...<> <> <<pickup '$playerInventory' 'a gun'>> <<replace '#link'>>You take the gun and hide it in your coat.<> <> <> @@ /% you can also add several items at once %/ You received your inheritance! <<set _randomItem to either('a bust of George Washinton', 'a pearl necklace', 'a statue of a cherub')>> <<pickup '$maryInventory _randomItem 'a large sum of money' 'a sealed letter'>> /% unique items: %/ :: StoryInit <<newinventory '$inventory' 'sword'>> :: A Later Passage <<pickup '$inventory' unique 'sword' 'shield'>> /% only shield is added %/ :: Some Other Passage /% however, be warned: %/ <<pickup '$inventory' 'armor' unique 'sword'>> /% an item named 'unique' is added to the inventory, and the other items are also added, regardless of whether they were already in the inventory %/

**READ**: [Documentation](https://twinelab.net/custom-macros-for-sugarcube-2/#/simple-inventory?id=macro-ltltpickupgtgt)

<<drop>>

<<drop>>

Syntax: <<drop variableName itemList>>

The <<drop>> macro removes items from the inventory indicated by the $variable. If one or more of the provided items can't be found in the inventory, nothing happens and no error is thrown, so some caution may be required in debugging certain situations.

Arguments:

Usage: /% drop an item %/ <<drop '$inventory' 'baseball card'>> /% have a random item stolen %/ <<set _stolen to $playerInventory.toArray().random()>> <<drop '$playerInventory' _stolen>> /% drop all weapons %/ :: StoryInit <<newinventory '$player' 'a sword'>> <<set $weaponsList to ['a sword', 'a gun', 'shotgun', 'the magic blade of null']>> :: Prison The guards frisk you and take your weapons. <<drop '$player' $weaponList>> /% drops any item from _weaponList, if present %/

**READ**: [Documentation](https://twinelab.net/custom-macros-for-sugarcube-2/#/simple-inventory?id=macro-ltltdropgtgt)

<<dropall>>

<<dropall>>

Syntax: <<dropall variableName>> or <<clear variableName>>

The <<dropall>> macro removes all items from the inventory indicated by the $variable. The <<clear>> macro is an alternative that does the same thing.

Arguments:

Usage: /% clears the inventory %/ <<dropall '$inventory'>> /% <> also works %/ A fire destroyed the mansion's kitchen! <<clear '$kitchenInventory'>>


**READ**: [Documentation](https://twinelab.net/custom-macros-for-sugarcube-2/#/simple-inventory?id=macro-ltltdropallgtgt-and-ltltcleargtgt)

<<clear>>

<<clear>>

Syntax: <<dropall variableName>> or <<clear variableName>>

The <<dropall>> macro removes all items from the inventory indicated by the $variable. The <<clear>> macro is an alternative that does the same thing.

Arguments:

Usage: /% clears the inventory %/ <<dropall '$inventory'>> /% <> also works %/ A fire destroyed the mansion's kitchen! <<clear '$kitchenInventory'>>

**READ**: [Documentation](https://twinelab.net/custom-macros-for-sugarcube-2/#/simple-inventory?id=macro-ltltdropallgtgt-and-ltltcleargtgt)

<<transfer>>

<<transfer>>

Syntax: <<transfer variableName variableName itemList>>

The <<transfer>> macro moves items from one inventory to another. The first inventory argument is the giver, and the second is the receiver. It's essentially the same as pairing a <<pickup>> and <<drop>>, but has a few benefits over doing it that way. For one, if you were to <<drop>> an item from one inventory and have another <<pickup>> the same item, you run the risk of having the reveiving inventory getting an item that the first inventory never had, since <<drop>> does nothing if the item doesn't exist. Using <<transfer>>, if an item isn't present in the first inventory, the second inventory will not receive said item. Like <<drop>> no error will be raised in this case.

Arguments:

Usage: /% containers %/ <<link 'Leave the kitchen knife here?'>> <<transfer '$playerInventory' '$kitchenInventory' 'kitchen knife'>> <> /% transfer weapons, so the player can get them back %/ :: StoryInit <<newinventory '$player' 'a sword'>> <<newinventory '$holding'>> <<set $weaponsList to ['a sword', 'a gun', 'shotgun', 'the magic blade of null']>> :: Prison The guards frisk you and take your weapons. <<trandfer '$player' '$holding' $weaponList>> :: Release You've been released from prison, and your weapons are returned to you. <<transfer '$holding' '$player' $weaponList>>

**READ**: [Documentation](https://twinelab.net/custom-macros-for-sugarcube-2/#/simple-inventory?id=macro-ltlttransfergtgt)

<<sort>>

<<sort>>

Syntax: <<sort variableName>>

The <<sort>> macro sorts the indicated inventory in alphanumeric order.

⚠️ Warning: There's no easy way to restore the default chronological ordering.

Arguments:

Usage: <<sort '$inventory'>>

**READ**: [Documentation](https://twinelab.net/custom-macros-for-sugarcube-2/#/simple-inventory?id=macro-ltltsortgtgt)

<<inventory>>

<<inventory>>

Syntax: <<inventory variableName [separator]>>

The <<inventory>> macro displays a list of the items in the indicated inventory. This list can be separated by a provided string. If no serparator argument is provided, the default separator is used.

Arguments:

Usage: <<inventory '$playerInv' '\n'>> /% or just <<inventory '$playerInv'>> if the default strings aren't changed %/

**READ**: [Documentation](https://twinelab.net/custom-macros-for-sugarcube-2/#/simple-inventory?id=macro-ltltinventorygtgt)

<<linkedinventory>>

<<linkedinventory>>

Syntax: <<linkedinventory actionName variableName [variableName]>>

The <<linkedinventory>> macro creates a list of items from the indicated inventory, and pairs each item with a link. If only one inventory variable is provided, the links, when clicked, will cause the items to be dropped from their current inventory as though the <<drop>> macro had been used. If a second inventory variable is included, items will instead be move from the first inventory to the second, as though the <<transfer>> macro had been called. The actionName argument should be used to contextualize this action for the player.

Arguments:

Usage: /% containers %/ You open the box. Want to take anything with you? <<linkedinventory 'Pick Up' '$boxInventory' '$playerInventory'>> /% let the player drop items %/ <<linkedinventory 'Drop' '$inventory'>> /% let the player place items %/ You open the closet. Lots of space in here. <<linkedinventory 'Store' '$inventory' '$storage'>>

**READ**: [Documentation](https://twinelab.net/custom-macros-for-sugarcube-2/#/simple-inventory?id=macro-ltltlinkedinventorygtgt)

Functions and Methods

The following are the functions and methods that are included in the simple inventory. Most of these allow access to the simple inventory's features in pure JavaScript, while some of these features are only available through this JavaScript API: even if you aren't planning on interacting with this system through JavaScript, you should still read the documentation for Inventory.removeDuplicates(), <inventory>.has(), and <inventory>.hasAll(), all of which are either only available through JavaScript, or contain features that are only available for your TwineScript expressions through JavaScript.

JavaScript API

Inventory()

Inventory()

Syntax: new Inventory([itemList])

Return Value: A new inventory instance.

The Inventory() constructor creates a new inventory just as the <<newinventory>> macro does. While some checks are in place to help forgetful authors, this function should always be called with the new operator, as failing to do so could leak the inventory to the global scope or create other issues. Further, the call should always be saved to a variable (a story $variable, a temporary _variable, or a JavaScript variable) or the call might pollute the global scope.

Arguments:

Usage: All of the following examples are equivalent to <<newinventory '$inventory' 'the shirt on your back'>>:

/% using in a <<set>> macro (also works in <<run>>) %/
<<set $inventory to new Inventory('the shirt on your back')>>

// in JavaScript or in <<script>> macro tags:
State.variables.inventory = new Inventory('the shirt on your back');

// using a function (for some reason):
setup.makeAnInventory = function (var) {
    var sv = State.variables;
    sv[var] = new Inventory('the shirt on your back');
    return sv[var];
};
setup.makeAnInventory(inventory);

Inventory.is()

Inventory.is()

Syntax: Inventory.is(variable)

Return Value: Boolean.

This method checks the provided variable or object and returns true if the variable is a reference to an inventory, and false otherwise. May be useful to some authors creating extensions or new features, but all methods and macros that expect an inventory in the simple inventory system will throw an error if they aren't given an inventory.

Arguments:

Usage:

/% given the following: %/
<<newinventory '$inventory'>>

<<set _treasureList to ['magic sword', 'ancient statue', 'gems']>>
<<set $treasureChest to []>>

<<set $treasureChest[0] to new Inventory(_treasureList.random(), 'some gold')>>
<<set $treasureChest[1] to new Inventory(_treasureList.random(), 'some gold')>>
<<set $treasureChest[2] to new Inventory(_treasureList.random(), 'some gold')>>

/% test the objects %/
<<if Inventory.is($inventory)>> /% true %/
    {{{$inventory}}} is an inventory.
<</if>>

<<if Inventory.is($treasureChest[1])>> /% true %/
    {{{$treasureChest[1]}}} is an inventory.
<</if>>

<<if Inventory.is($treasureChest)>>
    /% false, it's an array of inventories, but not an inventory itself %/
    {{{$treasureChest}}} is an inventory.
<</if>>

<<if Inventory.is(_treasureList)>> /% false %/
    {{{_treasureList}}} is an inventory.
<</if>>

Inventory.log()

Inventory.log()

Syntax: Inventory.log(inventory)

Return Value: String.

This method logs the indicated inventory's contents to the console, and returns a string representation of the same information. If the object or variable passed is not an inventory instance, the string and log will state as much. Mostly useful for debugging purposes; these calls should probably be deleted or omitted in release code.

Arguments:

Usage:

/% log to console %/
<<run Inventory.log($inventory)>>

/% log to console and print %/
<<set $log to Inventory.log($container)>>
<<= $log>>

Inventory.removeDuplicates()

Inventory.removeDuplicates()

Syntax: Inventory.removeDuplicates(inventory)

Return Value: Array.

This method is useful for enforcing uniqueness; that is, preventing doubles or duplicates from being included in the inventory or in displays of the inventory. It returns a new array that includes only unique items that can be used to overwrite the original or print to the player.

Arguments:

Usage:

/% keep the duplicates, but don't display them to the player %/
<<= Inventory.removeDuplicates($inventory).join('\n')>>

/% overwrite the inventory's contents with the unique array %/
<<set _uniqueOnly to Inventory.removeDuplicates($inventory)>>\
<<run $inventory.empty().pickUp(_uniqueOnly)>>

/% same as above using macros where possible: %/
<<set _uniqueOnly to Inventory.removeDuplicates($inventory)>>\
<<dropall '$inventory'>><<pickup '$inventory' _uniqueOnly>>

<inventory>.pickUp()

<inventory>.pickUp()

Syntax: <inventory>.pickUp([unique], itemList)

Return Value: This inventory (chainable).

This method is functionally the same as the <<pickup>> macro and accepts the same type of arguments. Like with the macro version, if the unique keyword (must be quoted in the method call) is the first item in the itemList, it enforces uniqueness. If the keyword appears in any other position in the list, the keyword is instead treated as an item and added to the inventory, and uniqueness is not enforced. See the <<pickup>> macro documentation for more

Arguments:

Usage:

/% pick up an item while enforcing uniqueness %/
<<run $inventory.pickUp('unique', 'marble')>>

// pick up a couple randomized items in JavaScript
var treasures = ['gold ingot', 'statue', 'gem', 'pearl'];
State.variables.playerInv.pickUp(treasures.randomMany(2));

<inventory>.drop()

<inventory>.drop()

Syntax: <inventory>.drop(itemList)

Return Value: This inventory (chainable).

This method is functionally the same as the <<drop>> macro and accepts the same type of arguments.

Arguments:

Usage:

/% drop an item %/
<<run $inventory.drop('marble')>>

// drop a couple randomized items in JavaScript
var pickPocket = State.variables.playerInv.toArray().randomMany(3);
State.variables.playerInv.drop(pickPocket);

<inventory>.empty()

<inventory>.empty()

Syntax: <inventory>.empty()

Return Value: This inventory (chainable).

This method is functionally the same as the <<clear>> and <<dropall>> macros; it removes all items from the calling inventory.

Usage:

/% clear an inventory %/
<<run $inventory.empty()>>

// clear an inventory in JavaScript
State.variables.playerInv.empty();

<inventory>.trasnfer()

<inventory>.trasnfer()

Syntax: <inventory>.transfer(inventory, itemList)

Return Value: This inventory (chainable).

This method is functionally the same as the <<transfer>> macro and accepts the same type of arguments. The listed items are tansferred from the calling inventory to receiving inventory, which must be a valid inventory instance and must be passed as the first argument.

Arguments:

Usage:

/% transfer an item %/
<<run $inventory.transfer($container, 'marble')>>

// tansfer all items in JavaScript
var allItems = State.variables.kitchenInventory.toArray();
State.variables.playerInv.transfer(State.variables.kitchenInventory, allItems);

<inventory>.has()

<inventory>.has()

Syntax: <inventory>.has(itemList)

Return Value: Boolean.

This method is returns whether the calling inventory contains the indicated item or items. If more than one item is passed, the method returns true if any one of the items is found in the calling inventory. To check for all items in a set, you must use <inventory>.hasAll() instead.

Arguments:

Usage:

/% check for an item %/
<<if $playerInv.has('car key')>>
    You can [[start the car|start car]].
<<else>>
    You don't remember where you left the keys.
<</if>>

/% checking for one item out of a group %/
<<if $inventory.has('sledgehammer', 'lock pick', 'office key')>>
    You have found a way into the room!
<</if>>

<inventory>.hasAll()

<inventory>.hasAll()

Syntax: <inventory>.hasAll(itemList)

Return Value: Boolean.

This method is returns whether the calling inventory contains the indicated item or items. If more than one item is passed, the method returns true only if all of the items are found in the calling inventory. To check for any of the items in a set, you must use <inventory>.has() instead.

Arguments:

Usage:

/% check for an item (generally, you should use <inventory>.has()
   for a single item, but this also works)%/
<<if $playerInv.hasAll('car key')>>
    You can [[start the car|start car]].
<<else>>
    You don't remember where you left the keys.
<</if>>

/% checking for all of the required items %/
<<if $inventory.hasAll('lighter', 'kindling', 'branches', 'bucket of water')>>
    You have enough to start a camp fire!
<</if>>

<inventory>.toArray()

<inventory>.toArray()

Syntax: <inventory>.toArray()

Return Value: Array.

This method returns an array made up of the items in the calling inventory. Useful for allowing the author to use standard JavaScript array methods on the inventory. Caution is required because changes to this array will be reflected in the calling inventory, unless the clone() function is used.

Usage:

/% create an array based on the inventory %/
<<set _array to $inventory.toArray()>>

/% pluck random items from the inventory: the inventory will be updated %/
<<set _randomItems to $playerInv.toArray().pluckMany(2)>>

/% using clone() to get an array that doesn't affect the calling inventory %/
<<set _items to clone($container.toArray())>>
<<set _items to ['uh oh']>> /% empty the clone %/
<<= $container.show(', ')>> /% shows the original inventory %/
<<= _items.join(', ')>> /% shows 'uh oh' %/

<inventory>.count()

<inventory>.count()

Syntax: <inventory>.count([item])

Return Value: Integer.

This method returns the total number of items in the inventory. If you pass it a string corresponding to an item name, it will instead check for how many duplicates of that item are in the inventory.

Arguments:

Usage:

/% create an array based on the inventory %/
<<newinventory '$inv' 'this' 'that' 'the other' 'another' 'that'>>

<<= $inv.count()>> /% 5 %/
<<= $inv.count('this')>> /% 1 %/
<<= $inv.count('that')>> /% 2 %/

<inventory>.isEmpty()

<inventory>.isEmpty()

Syntax: <inventory>.isEmpty()

Return Value: Boolean.

This method returns whether or not the inventory is empty (has zero items in it). You can also compare <inventory>.count() to 0 to achieve similar results.

Usage:

/% check if the inventory is empty %/
<<if $inventory.isEmpty()>>
    You aren't carrying anything.
<<else>>
    You have some things on you.
<</if>>

<inventory>.sort()

<inventory>.sort()

Syntax: <inventory>.sort()

Return Value: This inventory (chainable).

This method is the same as the <<sort>> macro, it sorts the calling inventory in alpha-numeric order.

⚠️ Warning:

The default ordering, which is chronological, cannot easily be restored after using this method.

Usage:

/% sort the inventory %/
<<run $inventory.sort()>>

<inventory>.show()

<inventory>.show()

Syntax: <inventory>.show([separator])

Return Value: String.

This method is similar to the <<inventory>> macro, it creates a sting representation of the calling inventory for printing, and allows the author to optionally set the separator.

Arguments:

Usage:

/% yiedls the same output as <<inventory '$inventory'>> %/
<<= $inventory.show()>>

/% yiedls the same output as <<inventory '$playerInv' ', '>> %/
<<= $playerInv.show(', ')>>

Tip

About chaining: Methods that don't return an explicit value will return the inventory they are called on (listed with the return value of 'this inventory' in the below documentation), meaning you can chain many of the instance method calls. For example, <inventory>.pickUp() adds items to the inventory, but doesn't need to return anything in specific, so it returns the inventory object is was called on and allows chaining. On the other hand, <inventory>.show() returns a string, so it can't be chained with other inventory methods. For example:

-> The following is valid:
<<print $inventory.pickUp('toothpick').show()>>

-> The following is not valid and will raise an error
<<run $inventory.show().pickUp('toothpick')>>

Events

The simple inventory automatically triggers one of two events as inventories are manipulated. You can use these events to perform functions and run other code in real time as the inventories are manipulated during gameplay. These events are also targetable by my <<event>> macro set.

The event object

When an event is fired, a variety of information is sent to the event handlers. That information is detailed here:

Event :inventory-init

This event is only triggered when a new inventory is defined. It's context is always 'initialized'.

Event :inventory-update

This event is triggered any time an inventory is altered, but not when it is created. In can never have the context 'initialized'.

Example Event Usage

Automatically updating inventories when to inventory link lists are on the same page:

$(document).on(':inventory-update', function (ev) {
    if (ev.context !== 'transfer') {
        // this event should only occur on transfer events
        return;
    }
    // only the receiving inventory needs updated,
    // but we'll update both for the sake of simplicity
    $('.auto-update').each( function (i, el) {
        // find the inventories marked for auto-updating
        // get the macro's wrapper:
        var $macro = $(el).find('.macro-linkedinventory');
        // get the inventories:
        var inv = State.getVar($macro.attr('data-self'));
        var rec = false;
        if ($macro.attr('data-rec')) {
            rec = State.getVar($macro.attr('data-rec'));
        }
        // reconstruct the list via the `inventory#linkedList()` method
        var $list = inv.linkedList(rec, $macro.attr('data-action'));
        // empty the container and replace it
        $(el).empty().append($list);
    });
});

To use the above:

Items you're carrying:
@@.auto-update;<<linkedinventory '$player' '$room' 'Drop'>>@@

What's laying around in this room:
@@.auto-update;<<linkedinventory '$room' '$player' 'Take'>>@@

Live demo

Demo Twee code:

:: Start
<<newinventory '$playerInventory'>>\
You find a large door.
<<if $playerInventory.has('key')>>You use the old key to open the door.\
<<else>>You can't open the door without a key.<</if>>
<<pickup '$playerInventory' 'key'>>You find an old key.
<<if $playerInventory.has('key')>>You use the old key to open the door.\
<<else>>You can't open the door without a key.<</if>>