Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Making sidebar Inventory items droppable

Twine version 2.0.8 SugarCube (v2.0.0-beta.4)
Novice user
RE: Inventory Macros by F2Andy: http://strugglingwithtwine.blogspot.ca/2014/03/handling-inventory.html

Hi-
I want to make sidebar Inventory items droppable, so that if your running from the police or whatever you can simply click a blue ITEM link (in the case COFFEE) on the side bar to drop it. My first thought was that it should look something like this (when buying the item from a store.
So, I guess I'm trying to put one Click in another Click and need to wrap it, but I don't know if that is even possible.:

*I added the brackets so your head wouldn't explode reading it.

$10 Coffee <<if $Money gte 10>><<click "Buy">><<set $Coffee to "yes">><<goto "Store">><<addToInv "[[[[<<click "Coffee">><<set $Coffee to "no">><<removeFromInv Coffee>><<goto "Store">><</click>>]]]]">><<set $Money to $Money -10>><</click>><</if>>

Here are the two lines apart:

<<if $Money gte 10>><<click "Buy">><<set $Coffee to "yes">><<goto "Store">><<addToInv "Coffee">><<set $Money to $Money -10>><</click>><</if>>

<<click "Coffee">><<set $Coffee to "no">><<removeFromInv Coffee>><<goto "Store">><</click>>

Not asking anyone to do my homework for me, but didn't want to waste hours for something that can't be done, or would be easier doing elsewhere- like the inventory javascript.

Any thoughts are welcome- thanks!

Comments

  • Haha, should've used doughnuts in my example...
  • edited July 2015
    Please use the code tag for posted code (C in the comment editor's toolbar).


    If you're putting this into the UI bar, I hope you're using the StoryCaption special passage?


    You need to modify the inventory macros, not attempt to jam <<click>> macros into them, because that's never going to work.

    Additionally, the <<click>> macro has a forwarding argument, so there's no need to use <<goto>> with what you were trying to do (which, again, won't work). Also, you probably don't want to be forwarding the player to the "Store" whenever they drop an item.


    I'd suggest something like the following, which cleans up the macros a little and adds a new one <<listInv>> (see below the code for details):
    window.getInv = function () {
    	return state.active.variables.inventory;
    };
    macros.initInv = macros.emptyInv = {
    	handler : function () {
    		state.active.variables.inventory = [];
    	}
    };
    macros.addToInv = {
    	handler : function (place, macroName, params, parser) {
    		if (params.length === 0) {
    			return throwError(place, "<<" + macroName + ">>: no parameters given");
    		}
    		if (state.active.variables.inventory.indexOf(params[0]) === -1) {
    			state.active.variables.inventory.push(params[0]);
    		}
    	}
    };
    macros.removeFromInv = {
    	handler : function (place, macroName, params, parser) {
    		if (params.length === 0) {
    			return throwError(place, "<<" + macroName + ">>: no parameters given");
    		}
    		var index = state.active.variables.inventory.indexOf(params[0]);
    		if (index !== -1) {
    			state.active.variables.inventory.splice(index, 1);
    		}
    	}
    };
    macros.inv = {
    	handler : function (place, macroName, params, parser) {
    		if (state.active.variables.inventory.length === 0) {
    			new Wikifier(place, 'nothing');
    		} else {
    			new Wikifier(place, state.active.variables.inventory.join(', '));
    		}
    	}
    };
    macros.listInv = {
    	handler : function (place, macroName, params, parser) {
    		if (state.active.variables.inventory.length === 0) {
    			new Wikifier(place, 'nothing');
    		} else {
    			var list = state.active.variables.inventory.map(function (item) {
    				return item + ' <<click "(drop)">><<removeFromInv '
    					+ item + '>><<replace "#inventory-list">><<'
    					+ macroName + '>><</replace>><</click>>';
    			});
    			new Wikifier(place, '<div id="inventory-list">' + list.join('\n') + '</div>');
    		}
    	}
    };
    
    The <<listInv>> macro prints a list of the inventory items (one per line) with a drop link for each. Furthormore, each drop link will dynamically update the list generated by <<listInv>>.

    I added <<listInv>> separately, rather than simply replacing <<inv>>, so you could still print a simple comma-separated list of the inventory items without the dropping links and dynamic update bits.

    Usage example:
    Taking rake.<<addToInv rake>>
    Taking spoon.<<addToInv spoon>>
    Taking apple.<<addToInv apple>>
    
    ''Inventory:''
    <<listInv>>
    
  • Wow, thanks Mad. You really didn't have to do that. If it took more than 10mins then I feel horrible, but thanks. I was expecting a "You'll shoot your eye out kid" and a kick in the head, lol.

    I had altered F2Andy's code to create a second 'home' inventory. I updated yours too. The Inventory Home Version I put in the side bar as well, but without the drop command. Instead it can only be dropped from the Home location- since you can't be at work and destroy your home PC. I redid the <<goto Home>> on the Home Version to 'refresh' the home list, while at home, as items are dropped.

    I will post them tonight after I replace Andy's instructions.

    Long term, I'll try to figure out how to move things from inventory to inventory, but I don't really need it right now and I've been messing with the Inventory and Clock for two days and I'm sick of looking at them.

    I got a simple clock to work and was able to set it, but the more complex one that goes in the script passage I could never figure out how to set it. Considering I never looked at code before a week ago, I shouldn't be screwing with either of them; I can't get the theme to Gremlins out of my head as I break things and try to fix them. :)

    Mad Thanks Mad -Thrown
  • Original macros by F2Andy: http://strugglingwithtwine.blogspot.ca/2014/03/handling-inventory.html
    ****Updated with DROP item links by TheMadExile   Set up and instructions by Thrown
    
    ***Condensed version of Thrown’s setup which creates a side bar ‘backpack’ to held items that can be dropped by the player at any time.  It also lists a ‘Home Inventory’ of household items which can’t be dropped here, because the player must go to the Home passage to drop them, as he doesn’t have them on his person.
    
    Here are two sample items for sale in a “Store”. Note: the bullets go to the person and the CoffeePot is for home.
    $50 Bullets  <<if $Money gte 50>><<click "Buy">><<set $Bullets to "yes">><<goto "Store">><<addToInv "Bullets">><<set $Money to $Money -50>><</click>><</if>>
    $50 CoffeePot  <<if $Money gte 50>><<click "Buy">><<set $CoffeePot to "yes">><<goto "Store">><<addToInvHome "CoffeePot">><<set $Money to $Money -50>><</click>><</if>>
    
    Set up these six passages (or add to yours)
    Passage:    Story ini    <<initInv>>	<<initInvHome>>
    Passage:    StoryCaption     Inventory:<<listInv>>     Household Inventory:<<invHome>>
    Passage:    Story Menu    [[Inventory]]   [[InventoryHome]]
    
    Passage: Inventory   
    <<if $inventory.length == 0>>You are not carrying anything.<<else>>You are carrying:
    <<invWithLinks>> <<endif>>
    <<back>>
    
    Passage: InventoryHome  (((Without a space)))
    <<if $inventoryhome.length == 0>>You are not carrying anything.<<else>>You are carrying:
    <<invHomeWithLinks>> <<endif>>
    <<back>>  
    
    Passage: Home (((the PCs house)))
     Inventory:
    <<listInv>> 
    Inventory Home:
    <<listInvHome>>
    
    CUT AND PASTE TO “EDIT STORY JAVASCRIPT “ tab by clicking on name of story at bottom left:
    window.getInv = function () {
    	return state.active.variables.inventory;
    };
    macros.initInv = macros.emptyInv = {
    	handler : function () {
    		state.active.variables.inventory = [];
    	}
    };
    macros.addToInv = {
    	handler : function (place, macroName, params, parser) {
    		if (params.length === 0) {
    			return throwError(place, "<<" + macroName + ">>: no parameters given");
    		}
    		if (state.active.variables.inventory.indexOf(params[0]) === -1) {
    			state.active.variables.inventory.push(params[0]);
    		}
    	}
    };
    macros.removeFromInv = {
    	handler : function (place, macroName, params, parser) {
    		if (params.length === 0) {
    			return throwError(place, "<<" + macroName + ">>: no parameters given");
    		}
    		var index = state.active.variables.inventory.indexOf(params[0]);
    		if (index !== -1) {
    			state.active.variables.inventory.splice(index, 1);
    		}
    	}
    };
    macros.inv = {
    	handler : function (place, macroName, params, parser) {
    		if (state.active.variables.inventory.length === 0) {
    			new Wikifier(place, 'nothing');
    		} else {
    			new Wikifier(place, state.active.variables.inventory.join(', '));
    		}
    	}
    };
    macros.listInv = {
    	handler : function (place, macroName, params, parser) {
    		if (state.active.variables.inventory.length === 0) {
    			new Wikifier(place, 'nothing');
    		} else {
    			var list = state.active.variables.inventory.map(function (item) {
    				return item + ' <<click "(drop)">><<removeFromInv '
    					+ item + '>><<replace "#inventory-list">> <<'
    					+ macroName + '>><</replace>><</click>>';
    			});
    			new Wikifier(place, '<div id="inventory-list">' + list.join('\n') + '</div>');
    		}
    	}
    };
    
    
    
    
    window.getInvHome = function () {
    	return state.active.variables.inventoryhome;
    };
    macros.initInvHome= macros.emptyInvHome = {
    	handler : function () {
    		state.active.variables.inventoryhome = [];
    	}
    };
    macros.addToInvHome = {
    	handler : function (place, macroName, params, parser) {
    		if (params.length === 0) {
    			return throwError(place, "<<" + macroName + ">>: no parameters given");
    		}
    		if (state.active.variables.inventoryhome.indexOf(params[0]) === -1) {
    			state.active.variables.inventoryhome.push(params[0]);
    		}
    	}
    };
    macros.removeFromInvHome = {
    	handler : function (place, macroName, params, parser) {
    		if (params.length === 0) {
    			return throwError(place, "<<" + macroName + ">>: no parameters given");
    		}
    		var index = state.active.variables.inventoryhome.indexOf(params[0]);
    		if (index !== -1) {
    			state.active.variables.inventoryhome.splice(index, 1);
    		}
    	}
    };
    macros.invHome = {
    	handler : function (place, macroName, params, parser) {
    		if (state.active.variables.inventoryhome.length === 0) {
    			new Wikifier(place, 'nothing');
    		} else {
    			new Wikifier(place, state.active.variables.inventoryhome.join(', '));
    		}
    	}
    };
    
    
    macros.listInvHome = {
    	handler : function (place, macroName, params, parser) {
    		if (state.active.variables.inventoryhome.length === 0) {
    			new Wikifier(place, 'nothing');
    		} else {
    			var list = state.active.variables.inventoryhome.map(function (item) {
    				return item + ' <<click "(drop)">><<removeFromInvHome '
    					+ item + '>><<replace "#inventoryhome-list">> <<'
    					+ macroName + '>><</replace>><</click>>';
    			});
    			new Wikifier(place, '<div id="inventoryhome-list">' + list.join('\n') + '</div>');
    		}
    	}
    };
    END THROWN’S set up
    
    
    
  • edited July 2015
    Got around to being able to move things between inventories. Now you can have a personal inventory and home one, or create more. Police Evidence lockers, or whatever. The Take and Leave commands stay if you click Drop(destroy) until you leave 'home'. I liked it that way as a fail safe in case a player destroys a item by mistake instead of leaving it at home. Besides that it seems to work great.
    <<if $Gun is "no">><<click "take your Glock">><<set $Gun to "yes">><<addToInv "Glock">><<removeFromInvHome "Glock">><<goto "Home">><</click>><</if>>
    
        <<if $Gun is "yes">><<click "leave your Glock here">><<set $Gun to "no">><<addToInvHome "Glock">><<removeFromInv "Glock">><<goto "Home">><</click>><</if>>
    
  • <<if $RumHome is "yes">><<click "take your Rum">><<set $RumHome to "no">><<removeFromInvHome "Rum">><<set $Rum to "yes">><<addToInv "Rum">><<goto "Home">><</click>><</if>>
    	
    <<if $Rum is "yes">><<click "leave your Rum here">><<set $Rum to "no">><<removeFromInv "Rum">><<set $RumHome to "yes">><<addToInvHome "Rum">><<goto "Home">><</click>><</if>>
    

    Oops, this works better than the last post. Things move freely back and forth and you no longer own something without buying it the first time, and the links refresh on screen.

    Once you buy Rum the first time, you can take / leave it as much as you want, but if you 'drop / trash' it, you are still able to get it from home. So, for now the Rum is never gone... :) Figure the Javascript needs to update $Rum is "yes" and $RumHome is "yes" to both be no when it's 'dropped', so it appears as it does before purchase. I'll look into that later.
  • Hey, I converted this to sugarcube 2, but I can't quite figure out what's wrong with the listInv code. Something about name losing its definition somehow? In the else branch, this.name doesn't work, so is there some tricky way to get his working?

    Also, I couldn't replicated this shortcut: macros.initInv = macros.emptyInv in the new style of doing things. Is it still possible? I didn't know what object these macros are added to. Is it just the old "macros" object, or something new?

    Lastly, what does window.getInv = function () do? Is it needed?
    window.getInv = function () {
    	return state.active.variables.inventory;
    };
    Macro.add("initInv", {
    	handler: function () {
    		state.active.variables.inventory = [];
    	}
    });
    Macro.add("emptyInv", {
    	handler: function () {
    		state.active.variables.inventory = [];
    	}
    });
    Macro.add("addToInv", {
    	handler: function () {
    		if (this.args.length === 0) {
    			return this.error("no parameters given");
    		}
    		if (state.active.variables.inventory.indexOf(this.args[0]) === -1) {
    			state.active.variables.inventory.push(this.args[0]);
    		}
    	}
    });
    Macro.add("removeFromInv", {
    	handler: function () {
    		if (this.args.length === 0) {
    			return this.error("no parameters given");
    		}
    		var index = state.active.variables.inventory.indexOf(this.args[0]);
    		if (index !== -1) {
    			state.active.variables.inventory.splice(index, 1);
    		}
    	}
    });
    Macro.add("inv", {
    	handler: function () {
    		if (state.active.variables.inventory.length === 0) {
    			new Wikifier(this.output, 'nothing');
    		} else {
    			new Wikifier(this.output, state.active.variables.inventory.join(', '));
    		}
    	}
    });
    Macro.add("listInv", {
    	handler: function () {
    		if (state.active.variables.inventory.length === 0) {
    			new Wikifier(this.output, 'nothing');
    		} else {
    			var list = state.active.variables.inventory.map(function (item) {
    				return item + ' <<click "(drop)">><<removeFromInv '
    					+ item + '>><<replace "#inventory-list">><<'
    					+ this.name + '>><</replace>><</click>>';
    			});
    			new Wikifier(this.output, '<div id="inventory-list">' + list.join('\n') + '</div>');
    		}
    	}
    });
    
  • Right, I got the last function to work:
    Macro.add("listInv", {
    	handler: function () {
    		if (state.active.variables.inventory.length === 0) {
    			new Wikifier(this.output, 'nothing');
    		} else {
    			var parent = this;
    			var list = state.active.variables.inventory.map(function (item) {
    				return item + ' <<click "(drop)">><<removeFromInv '
    					+ item + '>><<replace "#inventory-list">><<'
    					+ parent.name + '>><</replace>><</click>>';
    			});
    			new Wikifier(this.output, '<div id="inventory-list">' + list.join('\n') + '</div>');
    		}
    	}
    });
    

    So, if anyone wants to answer the questions about "window.getInv" and about the name of the parent object, that would be neat.

    Oh, and it's neat code as well.
  • On the subject of dropping things, has anyone done games in twine where you record the locations of where things were dropped? And you can go back and get them at a later point? Is it just a matter of building a database of all the little drops and comparing this each time you enter a passage?

    Would this lead to bloat and slowness? Is it something better to avoid in complex games?
  • edited October 2015
    tryguy wrote: »
    Hey, I converted this to sugarcube 2, but I can't quite figure out what's wrong with the listInv code. Something about name losing its definition somehow? In the else branch, this.name doesn't work, so is there some tricky way to get his working?
    It has nothing to do with the else and everything to do with scope. The binding of this within the anonymous function passed to the map method is not the same as within the handler function. You either need to pass the outer this to map, to use as its inner this, or assign the macro name to a variable, which the anonymous function passed to map will close over. You could also close over this by assigning it to a variable, but that's unnecessary in this case, just capture the name.

    tryguy wrote: »
    Also, I couldn't replicated this shortcut: macros.initInv = macros.emptyInv in the new style of doing things. Is it still possible? I didn't know what object these macros are added to. Is it just the old "macros" object, or something new?
    Making macro aliases is still possible, yes. Macros added via the API exist within the Macro object itself, but the macro definitions are privately scoped, so you can't simply assign references around. The macros object is only for legacy macros.

    You can make macro aliases in a couple of ways. You may do so at the time of initial macro creation, in which case you simply specify an array of names. For example:
    Macro.add([ "initInv", "emptyInv" ], function () { … });
    
    You may also create an alias after the creation of the original by supplying the name of the original as the definition. For example:
    Macro.add("initInv", function () { … });
    Macro.add("emptyInv", "initInv");
    

    tryguy wrote: »
    Lastly, what does window.getInv = function () do? Is it needed?
    When called (i.e. getInv()), it returns a reference to the $inventory (i.e. State.variables.inventory) array. And no, it's not really necessary in SugarCube 2; the macros don't use it and if you need a reference in some other JavaScript, you can use State.variables.inventory (getInv() is just a bit shorter to type).


    Here's a version which is a bit more idiomatic SugarCube 2 (using State.variables and contains). It includes the <<emptyInv>> alias and a working <<listInv>>.
    window.getInv = function () {
    	return State.variables.inventory;
    };
    
    Macro.add([ "initInv", "emptyInv" ], {
    	handler : function () {
    		State.variables.inventory = [];
    	}
    });
    
    Macro.add("addToInv", {
    	handler : function () {
    		if (this.args.length === 0) {
    			return this.error("no inventory item specified");
    		}
    		if (!State.variables.inventory.contains(this.args[0])) {
    			State.variables.inventory.push(this.args[0]);
    		}
    	}
    });
    
    Macro.add("removeFromInv", {
    	handler: function () {
    		if (this.args.length === 0) {
    			return this.error("no inventory item specified");
    		}
    		var index = State.variables.inventory.indexOf(this.args[0]);
    		if (index !== -1) {
    			State.variables.inventory.splice(index, 1);
    		}
    	}
    });
    
    Macro.add("inv", {
    	handler: function () {
    		if (State.variables.inventory.length === 0) {
    			new Wikifier(this.output, 'nothing');
    		} else {
    			new Wikifier(this.output, State.variables.inventory.join(', '));
    		}
    	}
    });
    
    Macro.add("listInv", {
    	handler: function () {
    		if (State.variables.inventory.length === 0) {
    			new Wikifier(this.output, 'nothing');
    		} else {
    			var	macro = this.name,
    				list  = State.variables.inventory.map(function (item) {
    					return item + ' <<click "(drop)">><<removeFromInv '
    						+ item + '>><<replace "#inventory-list">><<'
    						+ macro + '>><</replace>><</click>>';
    				});
    			new Wikifier(this.output, '<div id="inventory-list">' + list.join('\n') + '</div>');
    		}
    	}
    });
    

    tryguy wrote: »
    On the subject of dropping things, has anyone done games in twine where you record the locations of where things were dropped? And you can go back and get them at a later point? Is it just a matter of building a database of all the little drops and comparing this each time you enter a passage?

    Would this lead to bloat and slowness? Is it something better to avoid in complex games?
    You'd have to have a method to show items which had been dropped, obviously, but yes, it's totally doable. I'd probably suggest a generic object or a Map, where the keys are the names of the passages where things have been dropped and the values are arrays of said dropped items.

    It should be neither bloating or slow if done as I suggested above, since the object would only hold key/value pairs for however many passages the player had dropped things in. Even if you put all in-game items in that way (i.e. the player would originally find items via this system as well), it probably wouldn't be an issue.
  • Hi again,

    I modified these for more complex objects, but I came across an issue I'd like some help with. So, here's the current code:
    Macro.add("listInv", {
    	handler : function () {
    		if (State.variables.inventory.length === 0) {
    			new Wikifier(this.output, 'nothing');
    		} else {
    			var	macro = this.name,
    					list  = State.variables.inventory.map(function (obj) {
    						return obj.name + ' <<click "(drop)">><<removeFromInv '
    							+ obj.id + '>><<replace "#inventory-list">><<'
    							+ macro + '>><</replace>><</click>>';
    					});
    			new Wikifier(this.output, '<div id="inventory-list">' + list.join('\n') + '</div>');
    		}
    	}
    });
    
    And here's an example of an object I have:
    
    <<set $leatherArmor to {
    	id			:	"$leatherArmor",
    	name		:	"Leather Armor"
    }
    

    And this works perfectly, but it only works since I explicitly included the id field. Originally, I was only passing obj, like is in the original code. But it it was failing to find the index in removeFromInv. The only way I was able to get "<<removeFromInv obj>>" was to explicity hand it the name (id) of the object ("<<removeFromInv $leatherArmor>>").

    So... is there a reason for this? Also, should I give all my objects a unique name field to reference them by? Or... it's not exactly that. What I mean is, do I really have to embed the object name within the object itself in order to be able to properly call it within the macro? Or... am I just not doing something right? Actually, I don't know how to debug in javascript, so I really have no idea what the obj that's being passed actually looks like.

    Anyway, this might be an issue that's likely to come up again and again, so any info in this would be helpful.
  • Well, actually, I might be going about this wrong. Is it just better to have simple tokens you can pick up and drop and use, and leave the detail in a database for when you bring up buy/sell menus and things? That way, we could just leave the inventory as a simple structure. Is that normal practice?
  • tryguy wrote: »
    Is it just better to have simple tokens you can pick up and drop and use.....
    Yes.

  • This particular key-item inventory system was never intended to work with anything but strings. Trying to stuff objects into it is why you're running into trouble.

    tryguy wrote: »
    And this works perfectly, but it only works since I explicitly included the id field. Originally, I was only passing obj, like is in the original code. But it it was failing to find the index in removeFromInv. The only way I was able to get "<<removeFromInv obj>>" was to explicity hand it the name (id) of the object ("<<removeFromInv $leatherArmor>>").

    So... is there a reason for this?
    See the above. The existing <<listInv>> builds each entry as a string, so trying to concatenate an object into that was never going work. Your workaround works by storing the $variable name within id as a string.

    tryguy wrote: »
    Also, should I give all my objects a unique name field to reference them by? Or... it's not exactly that. What I mean is, do I really have to embed the object name within the object itself in order to be able to properly call it within the macro?
    Little bit of A, little bit of B. Yes, your inventory objects will need unique identifiers. If done correctly, no, they do not have to be names of $variables.


    Try the following updated set which works with objects (and only objects). A couple of notes first:
    1. You still need id and name properties. id is the unique identifier, so the object can be found within the inventory, and name is the display name.
    2. The id may now be whatever value type you wish, as long as it's unique. I'd suggest simply using a descriptive string.
    As as example of what I mean by the id property:
    <<set
    $apples to {
    	id   : "apples.fresh",
    	name : "Bag of apples",
    	cost : 5
    },
    $poisonedApples to {
    	id   : "apples.poisoned",
    	name : "Bag of apples",
    	cost : 5
    }
    >>
    

    Macros for an object-based key-item inventory:
    window.getInv = function () {
    	return State.variables.inventory;
    };
    
    Macro.add([ "initInv", "emptyInv" ], {
    	handler : function () {
    		State.variables.inventory = [];
    	}
    });
    
    Macro.add("addToInv", {
    	handler : function () {
    		if (this.args.length === 0) {
    			return this.error("no inventory item specified");
    		}
    		var obj = this.args[0];
    		if (typeof obj !== "object" || !obj.hasOwnProperty("id")) {
    			return this.error("inventory item malformed");
    		}
    		var idx = State.variables.inventory.findIndex(function (item) {
    			return item.id === obj.id;
    		});
    		if (idx === -1) {
    			State.variables.inventory.push(obj);
    		}
    	}
    });
    
    Macro.add("removeFromInv", {
    	handler : function () {
    		if (this.args.length === 0) {
    			return this.error("no inventory item specified");
    		}
    		var obj = this.args[0];
    		if (typeof obj !== "object" || !obj.hasOwnProperty("id")) {
    			return this.error("inventory item malformed");
    		}
    		var idx = State.variables.inventory.findIndex(function (item) {
    			return item.id === obj.id;
    		});
    		if (idx !== -1) {
    			State.variables.inventory.splice(idx , 1);
    		}
    	}
    });
    
    Macro.add("inv", {
    	handler : function () {
    		if (State.variables.inventory.length === 0) {
    			new Wikifier(this.output, 'nothing');
    		} else {
    			new Wikifier(
    				this.output,
    				State.variables.inventory.map(function (item) {
    					return item.name;
    				}).join(', ')
    			);
    		}
    	}
    });
    
    Macro.add("listInv", {
    	handler : function () {
    		var wrapper = document.createElement("div");
    		$(wrapper)
    			.attr("id", "inventory-list")
    			.appendTo(this.output);
    		this.self.buildList(wrapper);
    	},
    	buildList : function (wrapper) {
    		var	self = this;
    		if (State.variables.inventory.length === 0) {
    			new Wikifier(wrapper, "nothing");
    		} else {
    			State.variables.inventory.forEach(function (item) {
    				var	entry  = document.createDocumentFragment(),
    					dropEl = document.createElement("a");
    				$(entry)
    					.append(item.name)
    					.append("\u00A0")
    					.append(dropEl)
    					.append("<br>");
    				$(dropEl)
    					.text("(drop)")
    					.ariaClick(function () {
    						var obj = item;
    						var idx = State.variables.inventory.findIndex(function (item) {
    							return item.id === obj.id;
    						});
    						if (idx !== -1) {
    							State.variables.inventory.splice(idx , 1);
    						}
    						$(wrapper)
    							.empty()
    							.append(self.buildList.call(self, wrapper));
    					});
    				$(wrapper).append(entry);
    			});
    		}
    	}
    });
    
  • That's some advanced code! But neat. I didn't know about findIndex.

    As for the last macro, that's gonna have to wait until I move on to jquery study. But, even like this, I think I could incorporate some more stuff. Like (equip) to wield or wear the item. I can see how I'd do that. I also want to see about presenting the dropped items in the room again. That's pretty easy too.

    What I'm not sure about right now is if my story would benefit from this unique-item approach or just having generic stuff where it doesn't necessarily matter what one gets deleted. My previous version was a bit like that. So, not much a key-item take on things. Right now, I could go either way, but I'll have to see how the story would fit into the more selective item gathering. It could work, depending.

    Either way, I like seeing your coding. One thing I was considering was depicting wear and tear on weapons and stuff. The object approach would lend itself to that kind of tracking. Not everything would need to be tracked like this though, and I intend for quite a few items to be in-game. A lot of them generic "herb" type crafting things.

    Thanks for the help, Exile. I appreciate it.
  • I played with the code again. This time, I wanted to see if I could have more than one of each kind in there. And, it worked. So, I'm thinking I'll probably go with this, unless there are any obvious problems with it. Oh, and I'll probably add more to it (like (equip) and whatnot, but I need to design the equipping passage first, etc). But, I really do like this presentation.
    window.getInv = function () {
    	return State.variables.inventory;
    };
    
    Macro.add([ "initInv", "emptyInv" ], {
    	handler : function () {
    		State.variables.inventory = [];
    	}
    });
    
    Macro.add("addToInv", {
    	handler : function () {
    		if (this.args.length === 0) {
    			return this.error("no inventory item specified");
    		}
    		var obj = this.args[0];
    		if (typeof obj !== "object" || !obj.hasOwnProperty("id")) {
    			return this.error("inventory item malformed");
    		}
    		var idx = State.variables.inventory.findIndex(function (item) {
    			return item.id === obj.id;
    		});
    		if (idx === -1) {
    			obj.count++;
    			State.variables.inventory.push(obj);
    		}
    		else {
    			State.variables.inventory[idx].count++;
    		}
    	}
    });
    
    Macro.add("removeFromInv", {
    	handler : function () {
    		if (this.args.length === 0) {
    			return this.error("no inventory item specified");
    		}
    		var obj = this.args[0];
    		if (typeof obj !== "object" || !obj.hasOwnProperty("id")) {
    			return this.error("inventory item malformed");
    		}
    		var idx = State.variables.inventory.findIndex(function (item) {
    			return item.id === obj.id;
    		});
    		if (idx !== -1) {
    			if (State.variables.inventory[idx].count > 1) {
    				State.variables.inventory[idx].count--;
    			}
    			else {
    				State.variables.inventory.splice(idx , 1);
    			}
    		}
    	}
    });
    
    Macro.add("inv", {
    	handler : function () {
    		if (State.variables.inventory.length === 0) {
    			new Wikifier(this.output, 'nothing');
    		} else {
    			new Wikifier(
    				this.output,
    				State.variables.inventory.map(function (item) {
    					var temp = [];
    					for (var i = 0; i < item.count; i++) {
    						temp.push(item.name);
    					}
    					return temp.join(', ');
    				}).join(', ')
    			);
    		}
    	}
    });
    
    Macro.add("listInv", {
    	handler : function () {
    		var wrapper = document.createElement("div");
    		$(wrapper)
    			.attr("id", "inventory-list")
    			.appendTo(this.output);
    		this.self.buildList(wrapper);
    	},
    	buildList : function (wrapper) {
    		var	self = this;
    		if (State.variables.inventory.length === 0) {
    			new Wikifier(wrapper, "nothing");
    		} else {
    			State.variables.inventory.forEach(function (item) {
    				var	entry		=	document.createDocumentFragment(),
    						dropEl	=	document.createElement("a");
    				$(entry)
    					.append(item.name)
    					.append("\u00A0")
    					.append("x" + item.count)
    					.append("\u00A0")
    					.append(dropEl)
    					.append("<br>");
    				$(dropEl)
    					.text("(drop)")
    					.ariaClick(function () {
    						var obj = item;
    						var idx = State.variables.inventory.findIndex(function (item) {
    							return item.id === obj.id;
    						});
    						if (idx !== -1) {
    							if (State.variables.inventory[idx].count > 1) {
    								State.variables.inventory[idx].count--;
    							}
    							else {
    								State.variables.inventory.splice(idx , 1);
    							}
    						}
    						$(wrapper)
    							.empty()
    							.append(self.buildList.call(self, wrapper));
    					});
    				$(wrapper).append(entry);
    			});
    		}
    	}
    });
    

    The only real cheat I did was to leave ".append("x" + item.count)" as is. So, you end up with x1 for single items. But, that doesn't look too bad either.

    Here's what I used to test things with:
    <<set
    $apples to {
    	id		:	"apples.fresh",
    	count	:	0,
    	name	:	"Bag of apples",
    	cost	:	5
    },
    $poisonedApples to {
    	id		:	"apples.poisoned",
    	count	:	0,
    	name	:	"Bag of apples",
    	cost	:	5
    }>>
    
    <<click [[Gather the apples|GoddessCaverns1]]>><<addToInv $apples>><</click>>
    <<click [[Scatter the apples again|GoddessCaverns1]]>><<removeFromInv $apples>><</click>>
    
    <<click [[Gather the green apples|GoddessCaverns1]]>><<addToInv $poisonedApples>><</click>>
    <<click [[Scatter the green apples|GoddessCaverns1]]>><<removeFromInv $poisonedApples>><</click>>
    
    ''Inventory:''
    <<inv>>
    
    ''Inventory:''
    <<listInv>>
    

    Thanks again, Exile. I doubt I could have done this without your expertise.
  • Actually, if anyone's tempted to use this in the future, I encountered some odd behaviour when having the apple objects set outside of the passage (say in StoryInit).

    So, to clean that up, I figured out that it was best to have initial counts = 1 (not 0), and take out the obj.count++; line in addToInv macro.

    So, only those changes. Here's the corrected code.
    Macro.add("addToInv", {
    	handler : function () {
    		if (this.args.length === 0) {
    			return this.error("no inventory item specified");
    		}
    		var obj = this.args[0];
    		if (typeof obj !== "object" || !obj.hasOwnProperty("id")) {
    			return this.error("inventory item malformed");
    		}
    		var idx = State.variables.inventory.findIndex(function (item) {
    			return item.id === obj.id;
    		});
    		if (idx === -1) {
    			State.variables.inventory.push(obj);
    		}
    		else {
    			State.variables.inventory[idx].count++;
    		}
    	}
    });
    
    <<set
    $apples to {
    	id		:	"apples.fresh",
    	count	:	1,
    	name	:	"Bag of apples",
    	cost	:	5
    },
    $poisonedApples to {
    	id		:	"apples.poisoned",
    	count	:	1,
    	name	:	"Bag of apples",
    	cost	:	5
    }>>
    

    I mostly have it this way so I can have lots and lots of items. Or the illusion of that, without the actual object overload. Anyway, I'm still just playing with this stuff, but this is something I corrected, so I thought I'd present it here, just in case.
  • To answer your 'is there a reason for this?' question: For what I write- I have no programming experience at all, but have been unemployed long enough to want to learn how to code, just for fun. I am an author first and a programmer second, so it skews my needs to the basic end of getting a book to do X, rather than writing a complex video game to do x,y,z.

    Basically I search all over the net to try to find some code that does something similar to what I need, cannibalize it, and play mad scientist to see how it works. I know enough to make a mess of things and spend 2 weeks trying to overthink a small problem. I present my problem and my solution and ask for people(Mad) to check my work. I'm sure I'll look back later at the way I did things later and think WTF, I used 100 lines of code when I could've done it a different way with 10.

    I did tinker around with dropping things some more and got it to run smoother, but either to store at a set location (home), or to toss it if 'the man' was chasing you. That's all I really needed it for, so that's the solution I was solving for.

    On a different note, my laptop croaked and I finally got around to ordering a new one, so I will begin to pester Mad anew! :)

    I think I might start over, even though I have 20k worth of text in there. I tried to have just one location for multiple classes and times in the game and use a massive amount of IF statements to trigger per character / time. I should have done different character Hubs so there are 2 police station passages, 1 if your a cop and one for a crook. I didn't know when I started that the Grid on the story board expands- I thought I had very limited space.
Sign In or Register to comment.