Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

[Script] Graphic Inventory Manager: Multidimensional GUI for characters

1420687408

Edited 1421134424
This is a complete overhaul of traditional tabletop inventory management, replacing a large blank area on a character sheet with a living, breathing and fully responsive graphic user interface. It's designed to simultaneously add precision and realism, but also make tracking these stats even easier than through traditional methods. No pencils, no paper, no slowing down your game. Before I even get into the nitty gritty of this script, I'm going to start with a disclaimer. This is not a small deal. It will require a good bit of work on your end, from creating your items (defining weights and volumes and creating graphics) to setting up the area your players will use. For best results, it is HIGHLY RECOMMENDED to create this system in a separate campaign altogether, and have your players open it up in a separate window during your playtime. While the set up of this system can be quite the task, the end result can drastically improve the flow and realism of your game. If you're not ready to spend a few hours getting this up and running, turn back now and go back to what you're used to. How it Works Using Roll20's built-in functionality, this script automatically calculates a character's carrying weight, carrying volume, fullness of containers (backpacks, pouches, quivers), liquid weights based on volume (waterskins, potions), warmth, degree of comfort during rests, and how likely a character is to have trouble doing dexterous acts. Yes, ALL of those. Your players are also able to throw their inventories and items onto the ground, restricting access to them until the GM gives them back. This is good for situations where a character would drop his or her backpack to aid in combat movement or other dexterous activities. Installation - The Journey to Realism Begins STEP ONE - POPULATE Create your items. If you're playing Dungeons & Dragons, a great place to start is the player's handbook. Create a journal entry (with a default token) for each item in the book (adventurer's gear, weapons, armor, food, tool kits, etc). The green bar (bar1) of each item's token should be its weight in pounds, and the blue bar (bar2) should be it's volume in cubic feet. Leave the max values of these two bars blank, unless specified in the Special Items section below. (Third bar is optional and up to you; I use it to track item HP or durability). While weight of items is pretty easy to find, volume is a little trickier. Try to be accurate, but don't kill yourself over it. Be flexible, and err to the benefit of your players. For example, a nicely folded set of traveler's clothes might only take up 0.1 cubic feet, but a messy heap of clothing could easily occupy 0.3 or more. Give your players the benefit of the doubt and go with the lower value: you're already adding a challenging level of realism by even installing this, so balance out fun with grit. Try to think in terms of quantity, and visualize the items in your mind. Ask yourself, "how many of these healing potions could I fit in an adventurer's backpack (which, conveniently, holds 1 cubic foot) and divide it out. For example, if you think you could fit 50 healing potions in that backpack, divide 1/50 and there's your volume. Remember, it's your world, and the better you become at improvising, the easier it will be for you to create special items for your characters on the fly (think: keys, treasure items, maps, quest items). Both weight and volume will be tracked in real time on a special status token on the player's sheet. Keep special items in mind: Liquid containers, such as waterskins, bottles, vials, flasks, etc. are handled a little differently. Set the bar1 value at 0, and its max at how many pints the container can hold when its full. (We use pints because 1 pt = 1 lb for most water-like liquids). For example, a 5e waterskin holds 4 pts of liquid, so its green bar should be set to 0/4. For simplicity, volume is still static for these items. Finally, put the following in the item's GM Notes (always without quotes): " weight X liquid ", where X = how much the container weighs (in lbs) when it's empty. When it's all set up, your players will be able to adjust the bar1 value depending on how full the container is, and name the container based on what's inside. For example, give your players an empty bottle token, fill it up to its max weight, color it red and name it "Potion of Healing." When they "drink" it (reduce its green bar to 0), the token will automatically revert to its default state (an empty bottle with no name and no color tint!) and they can then choose what to do with the empty container. There are some items that would be silly to put into containers like backpacks, pockets and pouches. For example, your players are not going to put that 10-foot-ladder they picked up into their backpack, despite the fact that it would technically meet the requirements. I also make sure my players aren't stuffing greatclubs and greatswords into their packs, either. To prevent players from stowing these sorts of items, simply put " unstowable " in its GM notes. This will force them to sling these items on their backs or carry them some other way. Certain items are physical representations of the inventories they provide. Backpacks, quivers, pouches, and pockets on a set of clothing are some examples. These items need to be identified using the " storageItems " array in the script. If your item is a backpack, put " backpack " in the GM Notes. If you want your clothing items to have pockets, put "pocket1". "pocket2" and so on depending on how many pockets the clothing has. The first time your player equips one of these "inventory" items, the script will automatically generate a storage area for you, the GM, to move into a nice spot. This will be explained in greater detail in a later section. The default selection of such items includes: " quiver " " boltcase " " pouch " " bagofholding " " pocket1 " " pocket2 " ... " pocket6 " and a few more, including the Handy Haversack " hhh " and its individual sides. You can modify this list as you please in the script. (OPTIONAL) If you want to use this script to calculate currency, coins are handled a bit differently, and they are the only item that is grouped in a single token (by type). You don't need more than a single graphic, but you will need to change the GM Notes of each monetary graphic to " coins " followed by the type, for example "coins copper". Relevant types are " copper " " silver " " gold " " electrum " " platinum " (I'll probably add some configuration here down the line). Do not modify the bar1 and bar2 values of the coin graphics. Instead, change the bar3 value to the amount of coins that graphic represents. The script will then automatically calculate that amount of coins' weight and volume (based on user defined variables in the config, defaults to 5e rules). (OPTIONAL) Wearable items that provide warmth, such as clothing and most armors, need special text in their GM Notes. Think of clothing and armor as how well they protect you from the elements on a scale of 1 to 4, with 4 being something very warm, like traveler's clothes or plate armor. For these items, put in the GM Notes: " warmth X " with X = the value of warmth. Commoner clothes might be 2, while a hat or a breastplate might be 1 or even 0. Use your judgment and have fun with it. When your players put these items in a "worn" inventory slot, it will increase their protection from cold by 10*F for each digit of warmth (you can change these values to Celsius in the script config) and will reflect on a special status token on their sheets. (OPTIONAL) Hospitality items are items such as tents, mess kits, bed rolls, blankets, pillows and anything else you can think of that would improve quality of life for a player during a short/long rest. This creates a simple numerical value that you can use for stat checks, temp HP or any other awesome benefit you can dream up that gives a player tangible incentive for even having these sorts of items. To enable, put " hosp " in an item's GM Notes. That item, when stored anywhere in that player's inventory, will add +1 to their hospitality rating and add to the party's average, which are both displayed on a convenient status token. You can do as much or as little as you want for your item list. I personally have nice artwork for each item and some even have helpful information about them in the journal. As long as the weights and volumes are filled out, you've done enough to move on to the next step. STEP TWO - LAYOUT Create your work space. Each player needs a large "main" area that will encompass all of the other "sub" inventories, which include wearable slots, backpacks, quivers, and anything else. If you want, you can also have special areas of ground where players can "drop" used items (arrows, empty bottles, used spell scrolls). These "drop" areas are programmed to move the item to the map layer and remove them from calculations until the GM decides what to do with them. For example, a clever rogue can "drop" his or her heavy backpack before invading a barracks to help with their stealth and acrobatics checks, and then pick it up later. If you want to model your "main" areas after mine, create a silhouette shape that you can populate with as many worn spaces as you desire, keeping default grid sizes (70 pixel x 70 pixel squares) in mind. Having some experience with Photoshop or similar can be helpful here. My main areas are available here for use. Simplicity is probably a good idea, as you will be piling A LOT of stuff on these, and size is important, as you need to be prepared for your players' crazy ideas, like carrying three backpacks or a barrel full of gear on a sled behind them. You'll need one " main " area for each player, and I choose to stack mine vertically for their ease of access. You need to number them for each player by putting the player number as the value for bar3, starting with 1. Put " main " in the GM Notes for each, and then put them on the map layer and forget about them. You can then decorate them for identification purposes, like adding a text name tag. Create a lightly tinted square (and ideally semi-transparent) graphic for "worn" inventory slots. These will all go on the map layer along with the "main" area. You're free to do as you please, but keeping it open ended works to everyone's benefit. A slot of each shoulder, one for the back, a couple for clothing an armor, two each for the hands and wrists, ring/bracelet slots, a necklace slot, head and cap slots, boot slots, and whatever else you can dream of. Position is more important than actually naming them. For example, I just put a bunch of squares touching every part of the silhouette on the grid, coloring some of them for ease. You can even just cover the silhouette with one big (transparent) rectangle and call it a day. Your players will likely use their logic (daggers in the belt, backpack between the shoulders, etc) and should be able to easily justify their usage. Each of these should have " worn " in the GM Notes. Go back to the "object" layer and create a token to display carrying weight/volume and put " status " in the GM Notes (and if you're using the warmth/hospitality system, make a second token with " status2 " in the GM Notes). Put the token(s) somewhere prominent (I used their character tokens for the status graphic, and a little campfire for the warmth/hosp graphic). Put their player number as the bar3 value (should match the "main" sheet) and bar1's max as the player's max carry weight. This will create a nice at-a-glance meter for your players so they can see how weighed down they are. The status token will also display a little stat called "Profile," which is how "big" their character is (it's the total volume, in square feet, of everything they have on them). I use this number against various dexterity checks, as a character loaded down with a bunch of crap would find these tasks much more difficult. (OPTIONAL) Grab a graphic that represents the ground, put it somewhere on the map layer and put " drop " in the GM notes. These automatically bring items to the map layer and out of the players' calculations, as explained above. When the GM moves these items out of the "drop" area, they will automatically go back to the object layer. STEP THREE - SCRIPT At this point, you should be ready to configure the script. The user configuration section is close to the top. The Graphic Inventory Manager WILL NOT FUNCTION AT ALL without some minimal configuration. Assign player IDs. The variable logPlayerIds should be set to true by default. If it is not, change it to true and save the script. When it starts, the console will log the IDs of every player who has ever joined your campaign. Copy and paste the relevant ones into the playerList array at the top of the user configuration section. Please pay special attention and note that THE MINUS SIGN IS PART OF THE ID and the players should be ordered according to the number you assigned them in descending order. You can have as many or as few players as you want, just make sure that any unused slots are deleted or commented out, like the Sampleton Testificate example is. Also, make sure to match the syntax of the existing IDs, including the quotes and commas. Change the default inventory image. The script will CRASH if you do not properly set your own default inventory box image for the invImg variable. YOU CANNOT USE THE DEFAULT LINK. This is the image that should make up the defining box of almost every inventory on your sheet (probably a semi-transparent square). Follow the instructions on the wiki to properly find the code for the image you want to use. All other configuration is optional or user discretion. This includes changing the temperature unit, the weight and volume of coins, the types of inventories available to your players and their maximum weights and volumes. STEP FOUR - EQUIP It's finally time to begin! Drag items from your item journal to your players individual sheets and have them equip themselves. When they equip a storage item (like a backpack) for the first time, it is YOUR JOB to move and stretch that item to the standards of your game, and decorate it as you please. You can add custom backgrounds and attachment slots to your items, as well, for visual effect. View the instructional video (COMING SOON) below for more information. Good luck, and enjoy! Above all, remember, this system is designed to make your game run SMOOTHER AND FASTER and allow you and your players to easily and precisely track your inventories. It is NOT designed make things more difficult, especially for your players! The burden of this task should be on you, the DM, and you should strive to make these technical details as easy as possible. This script -&gt; less typing -&gt; more storytelling! THIS IS AN OLD VIDEO, BUT IT MIGHT HELP YOU VISUALIZE THE ABOVE STEPS WHILE I GET A NEW ONE MADE: <a href="https://www.youtube.com/watch?v=sp1KACYziFg&feature=youtu.be" rel="nofollow">https://www.youtube.com/watch?v=sp1KACYziFg&feature=youtu.be</a> I AM CURRENTLY CONSTRUCTING THIS OP. PLEASE WAIT UNTIL I RELEASE THE INSTRUCTIONAL AND MORE INFORMATION BEFORE BUG REPORTS/COMPLAINTS/HELPME'S. v.0.1 (2015-01-07) Release ///////////////////////////////////////////////// /***********************************************/ var GIM = { alias: "Graphic Inventory Manager", author: { name: "John C." || "Echo" || "SplenectomY", company: "Team Asshat" || "The Alehounds", contact: "<a href="mailto:echo@TeamAsshat.com" rel="nofollow">echo@TeamAsshat.com</a>", }, version: "0.2", gist: "<a href="https://gist.github.com/SplenectomY/9d1ef745f23927edc930" rel="nofollow">https://gist.github.com/SplenectomY/9d1ef745f23927edc930</a>", forum: "", /***********************************************/ ///////////////////////////////////////////////// /////////BEGIN USER CONFIG///////// playerList: [ "-JeTLkLt_UFt7Boc1DsQ", /* 1: Coille Flitterleaf */ "-JeT529rL-wvu8yT8Q9N", /* 2: Tanion of House Victaria */ "-JeXs7g5ADZiaVjYOdIg", /* 3: Oldstuff McTuggins */ "-Jebn59AIWLWOxW3WMS9", /* 4: Xanneiros Lothammer */ //"-JeIO7XaKP76ZS22z0x3", /* 5: Sampleton Testificate */ ], logPlayerIds: true, invImg: "<a href="https://s3.amazonaws.com/files.d20.io/images/6927416/y5kI_BBCDJrxGCU8FttwEg/thumb.png?1420069394" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/6927416/y5kI_BBCDJrxGCU8FttwEg/thumb.png?1420069394</a>", coin: { wgt: 0.02, /* in lbs. Default is per 5e rules */ vol: 0.0002 /* in cubic feet. (5,000 per ft3) */ }, temperatureUnit: "F", /* Change to C if Celsius is desired */ storageItems: [ backpack = { name: "backpack", max_wgt: 30, max_vol: 1, }, quiver = { name: "quiver", max_wgt: "", max_vol: 0.1, }, boltCase = { name: "boltcase", max_wgt: "", max_vol: 0.15, }, pouch = { name: "pouch", max_wgt: 6, max_vol: .2, }, bagHolding = { name: "bagofholding", max_wgt: 500, max_vol: 64, }, handyHaversackSide1 = { name: "hhhside1", max_wgt: 20, max_vol: 2, }, handyHaversackSide2 = { name: "hhhside2", max_wgt: 20, max_vol: 2, }, handyHaversack = { name: "hhh", max_wgt: 80, max_vol: 8, }, // I have 6 pockets, because thats the max // I would give my characters on any item. // Add more if you wish. pocket1 = { name: "pocket1", max_wgt: 3, max_vol: .1, }, pocket2 = { name: "pocket2", max_wgt: 3, max_vol: .1, }, pocket3 = { name: "pocket3", max_wgt: 3, max_vol: .1, }, pocket4 = { name: "pocket4", max_wgt: 3, max_vol: .1, }, pocket5 = { name: "pocket5", max_wgt: 3, max_vol: .1, }, pocket6 = { name: "pocket6", max_wgt: 3, max_vol: .1, }, ], //////////END USER CONFIG////////// runTimeout: 0, isNewGraphic: false, extraRun: false, objsToBeCreated: [], runMe: function forGreatJustice() { if (GIM.runTimeout &gt; 0) { GIM.runTimeout--; } else { processGIM() for(i1 = 0; i1 &lt; GIM.objsToBeCreated.length; i1++) { create_inv( GIM.objsToBeCreated[i1][0], GIM.objsToBeCreated[i1][1], GIM.objsToBeCreated[i1][2], GIM.objsToBeCreated[i1][3], GIM.objsToBeCreated[i1][4], GIM.objsToBeCreated[i1][5], GIM.objsToBeCreated[i1][6] ); } GIM.objsToBeCreated = []; // This handles any extra runs if (GIM.extraRun == true && GIM.objsToBeCreated.length == 0) { processGIM(); processGIM(); GIM.extraRun = false; } GIM.runTimeout--; } }, isObj: function runFlagChecks(obj,type) { s = obj.get("gmnotes"); var isObjResult, invFlags = [ "main", "inv", "worn", "drop", "ignore" ], statusTokenFlags = [ "status", "status2" ]; if (type == "item") { isObjResult = true; for (i2 = 0; i2 &lt; invFlags.length; i2++) { if (s.indexOf(invFlags[i2]) !== -1) { isObjResult = false; } } for (i3 = 0; i3 &lt; statusTokenFlags.length; i3++) { if (s.indexOf(statusTokenFlags[i3]) !== -1) { isObjResult = false; } } } if (type == "inv") { isObjResult = false; for (i4 = 0; i4 &lt; invFlags.length; i4++) { if (s.indexOf(invFlags[i4]) !== -1) { isObjResult = true; } } } if (type == "status") { isObjResult = false; for (i4 = 0; i4 &lt; statusTokenFlags.length; i4++) { if (s.indexOf(statusTokenFlags[i4]) !== -1) { isObjResult = true; } } } return isObjResult; }, isInside: function allYourBase(x,y) { if (x.get("left") &gt; y.left && x.get("top") &gt; y.top && x.get("left") &lt; y.right && x.get("top") &lt; y.bottom) return true; else return false; } }; fixedCreateObj = (function () { return function () { var obj = createObj.apply(this, arguments); if (obj && !obj.fbpath) { obj.fbpath = obj.changed._fbpath.replace(/([^\/]*\/){4}/, "/"); } return obj; }; }()); function processGIM() { var warmth = [], hosp = [], hosp_average = 0, equipped = [], inv = [], items = [], items_stacked = [], objInvs = filterObjs(function(obj) { if(obj.get("type") == "graphic") { return GIM.isObj(obj,"inv"); } else return false; }), objItems = filterObjs(function(obj) { if(obj.get("type") == "graphic") { return GIM.isObj(obj,"item"); } else return false; }), objStatus = filterObjs(function(obj) { if(obj.get("type") == "graphic") { return GIM.isObj(obj,"status"); } else return false; }), objects = objInvs.concat(objItems,objStatus); for (i5 = 0; i5 &lt; GIM.playerList.length; i5++) { warmth[i5] = 0; hosp[i5] = 0; equipped[i5] = []; } /***************************/ /***** STEP 1 out of 4 *****/ /***************************/ _.each(objInvs, function(obj) { s = obj.get("gmnotes"); if (s.indexOf("main") !== -1) { if (!obj.get("bar1_value")) {obj.set("bar1_value", 0)} inv[inv.length] = { type: "main", id: obj.get("_id"), owner: obj.get("bar3_value"), weight: 0, volume: 0, inv_gmnotes: obj.get("gmnotes"), left: Math.floor(obj.get("left") - Math.floor(obj.get("width") / 2)), top: Math.floor(obj.get("top") - Math.floor(obj.get("height") / 2)), right: Math.floor(obj.get("left") + Math.floor(obj.get("width") / 2)), bottom: Math.floor(obj.get("top") + Math.floor(obj.get("height") / 2)) }; } }); /***************************/ /***** STEP 2 out of 4 *****/ /***************************/ _.each(objInvs, function(obj) { s = obj.get("gmnotes"); for(i6 = 0; i6 &lt; inv.length; i6++) { if (inv[i6].type == "main" && s.indexOf("main") == -1 && GIM.isInside(obj, inv[i6]) == true) { inv[inv.length] = { type: "sub", id: obj.get("_id"), owner: inv[i6].owner, weight: 0, volume: 0, layer: obj.get("layer"), inv_gmnotes: obj.get("gmnotes"), left: Math.floor(obj.get("left") - Math.floor(obj.get("width") / 2)), top: Math.floor(obj.get("top") - Math.floor(obj.get("height") / 2)), right: Math.floor(obj.get("left") + Math.floor(obj.get("width") / 2)), bottom: Math.floor(obj.get("top") + Math.floor(obj.get("height") / 2)) }; } } }); /***************************/ /***** STEP 3 out of 4 *****/ /***************************/ _.each(objItems, function(obj) { s = obj.get("gmnotes"); // ... log it into the item array items[items.length] = { id: obj.get("_id"), left: obj.get("left"), top: obj.get("top") }; // ... make sure the item has wgt/vol values if(!obj.get("bar1_value")) {obj.set("bar1_value",0)} if(!obj.get("bar2_value")) {obj.set("bar2_value",0)} // If it's an empty potion, remove the name and tint if (s.indexOf("liquid") !== -1 && obj.get("bar1_value") == 0) { obj.set("name",""); obj.set("tint_color","transparent"); } // Settings for newly added items if(GIM.isNewGraphic == true) { obj.set("showplayers_name",true); obj.set("showname",true); obj.set("playersedit_bar2",false); obj.set("playersedit_bar3",false); obj.set("showplayers_bar3",false); obj.set("showplayers_bar2",false); if (s.indexOf("liquid") !== -1) { obj.set("playersedit_bar1",true); obj.set("showplayers_bar1",true); } else { obj.set("playersedit_bar1",false); obj.set("showplayers_bar1",false); } } // For each inventory ... for(i7 = 0; i7 &lt; inv.length; i7++) { var q = inv[i7].owner - 1, r = inv[i7].inv_gmnotes; // ... that contains this item object ... if (GIM.isInside(obj, inv[i7]) == true && r.indexOf("ignore") == -1) { // ... do these things: // If the item's containing inventory is hidden, hide the item ... if(inv[i7].layer == "gmlayer" && obj.get("layer") != "gmlayer") { obj.set("layer","gmlayer") // ... or unhide it otherwise. } else if (inv[i7].type == "sub" && inv[i7].inv_gmnotes != "drop" && inv[i7].layer == "objects" && obj.get("layer") != "objects" || inv[i7].layer == "map") { obj.set("layer","objects"); } // If item is on a drop inventory, put it on the map layer if(inv[i7].inv_gmnotes == "drop") { obj.set("layer","map"); } // If item is not stowable, prevent stowing as such if (r.indexOf("inv") !== -1 && r.indexOf("worn") == -1 && s.indexOf("unstowable") !== -1) { obj.set("top",inv[i7].bottom + 35); } else { // This will exclude items that are hidden or dropped. if(obj.get("layer") == "objects") { // If the item is a pile of coins ... if(s.indexOf("coins") !== -1) { // ... adjust the weight and volume ... obj.set("bar1_value",Math.round((parseFloat(obj.get("bar3_value") * GIM.coin.wgt) * 1000) / 1000)); obj.set("bar2_value",Math.round((parseFloat(obj.get("bar3_value") * GIM.coin.vol) * 1000) / 1000)); // ... and let its name show the total if(s.indexOf("copper") !== -1) { obj.set("name","Cp: " + obj.get("bar3_value")); } if(s.indexOf("silver") !== -1) { obj.set("name","Sp: " + obj.get("bar3_value")); } if(s.indexOf("gold") !== -1) { obj.set("name","Gp: " + obj.get("bar3_value")); } if(s.indexOf("electrum") !== -1) { obj.set("name","Ep: " + obj.get("bar3_value")); } if(s.indexOf("platinum") !== -1) { obj.set("name","Pp: " + obj.get("bar3_value")); } } // If the item has a non-changing base weight (empty bottles) ... if (s.indexOf("weight") !== -1) { var weight_array = s.split("%20"), weight_loc = weight_array.indexOf("weight") + 1; // ... add the weight to its containing inventories inv[i7].weight += parseFloat(weight_array[weight_loc]); } // Add weights and volumes of each item to its containing inventories inv[i7].weight += parseFloat(obj.get("bar1_value")); inv[i7].volume += parseFloat(obj.get("bar2_value")); // Calculate how warm a player is based on what he or she is wearing if (inv[i7].inv_gmnotes == 'worn') { if (s.indexOf("warmth") !== -1) { warmth_array = s.split("%20"); warmth_loc = warmth_array.indexOf("warmth") + 1; warmth[q] += parseInt(warmth_array[warmth_loc]); } // Create new inventory boxes if an inv item is equipped for the first time for (k = 0; k &lt; GIM.storageItems.length; k++) { if (s.indexOf(GIM.storageItems[k].name) !== -1) { GIM.objsToBeCreated[GIM.objsToBeCreated.length] = [ obj.get("_pageid"), GIM.storageItems[k].name, obj.get("left"), obj.get("top"), obj.get("_id"), GIM.storageItems[k].max_wgt, GIM.storageItems[k].max_vol, ]; obj.set("gmnotes", s.replace(GIM.storageItems[k].name,"storage_" + obj.get("_id"))); } } // Log any worn "storage_" items into an array. if (s.indexOf("storage_" + obj.get("_id")) !== -1) { equipped[q][(equipped[q]).length] = "rep_" + obj.get("_id"); } } // Give the proper player control of that item if(inv[i7].type == "main") { for(j = 0; j &lt; GIM.playerList.length; j++) { if (inv[i7].owner == j) { obj.set("controlledby",GIM.playerList[j - 1]) } } // If the item gives hospitality, reflect as such if (s.indexOf("hosp") !== -1) { hosp[q]++; } } } } } } }); // Calculate average hospitality for(i8 = 0; i8 &lt; hosp.length; i8++) { hosp_average += hosp[i8]; } Math.floor(hosp_average /= GIM.playerList.length); /***************************/ /***** STEP 4 out of 4 *****/ /***************************/ _.each(objects, function(obj) { s = obj.get("gmnotes"); stacks = 1; for (i9 = 0; i9 &lt; items.length; i9++) { if (obj.get("left") == items[i9].left && obj.get("top") == items[i9].top && obj.get("_id") != items[i9].id && GIM.isObj(obj,"item") == true) { stacks++; } } if (stacks == 1) { obj.set({ status_blue: false, status_red: false }); } else if (stacks &lt; 10) { obj.set({ status_red: false, status_blue: stacks }); } else if (stacks &lt; 100) { var tens = Math.floor(stacks / 10); obj.set({ status_blue: stacks - (tens * 10), status_red: tens }); } // Make sure "unequipped" inventories are hidden. for (i10 = 0; i10 &lt; inv.length; i10++) { var q = inv[i10].owner - 1; is_it_equipped = false; if (inv[i10].id == obj.get("_id")) { if (s.indexOf("rep") !== -1) { item_owner = inv[i10].owner - 1; for (j = 0; j &lt; equipped[item_owner].length; j++){ if (s.indexOf(equipped[item_owner][j]) !== -1) { is_it_equipped = true; } } if (is_it_equipped == true) { if (s.indexOf("ignore") !== -1) { obj.set("layer","map"); } else { obj.set("layer","objects"); } if (GIM.extraRun == true) { } else { GIM.extraRun = true; } } else if (is_it_equipped == false) { obj.set("layer","gmlayer"); if (GIM.extraRun == true) { } else { GIM.extraRun = true; } } } // Now we apply values to bar1 and bar2 of each inventory. obj.set("bar1_value", Math.round(inv[i10].weight * 1000) / 1000); obj.set("bar2_value", Math.round(inv[i10].volume * 1000) / 1000); if (obj.get("bar1_value") &gt; obj.get("bar1_max") && obj.get("bar1_max") &gt; 0) { obj.set({ status_dead: true }); } else if (obj.get("bar2_value") &gt; obj.get("bar2_max") && obj.get("bar2_max") &gt; 0) { obj.set({ status_dead: true }); } else { obj.set({ status_dead: false }); } } // Set status token values if (inv[i10].type == 'main' && obj.get("bar3_value") == inv[i10].owner) { if (obj.get("gmnotes") == "status") { obj.set("bar1_value", Math.round(inv[i10].weight * 10) / 10); obj.set("bar2_value", Math.round(inv[i10].volume * 10) / 10); obj.set("name",'Weight: ' + obj.get("bar1_value") + " | Profile: " + obj.get("bar2_value")); } else if (obj.get("gmnotes") == "status2") { // Temperature values are changed per user preference if (GIM.temperatureUnit == "F") { obj.set("bar1_value", warmth[q] * 10); } else if (GIM.temperatureUnit == "C") { obj.set("bar1_value", Math.floor(((warmth[q] * 10 - 32) * (5 / 9)))); } obj.set("bar2_value", hosp[q]); obj.set("name",'Warmth: ' + obj.get("bar1_value") + "°" + GIM.temperatureUnit + " | Hosp: " + obj.get("bar2_value") + " (" + hosp_average + ")"); } } } }); GIM.runTimeout = GIM.runTimeout + 3; }; function create_inv(g_page_id, g_name, g_left, g_top, g_id, g_wgt, g_vol) { setTimeout(function(){ toBack(fixedCreateObj("graphic",{ name: g_name, imgsrc: GIM.invImg, pageid: g_page_id, left: g_left + 350, top: g_top, width: 70, height: 70, bar1_value: 0, bar1_max: g_wgt, bar2_value: 0, bar2_max: g_vol, layer: "objects", gmnotes: "inv rep_" + g_id })); },5); } on("ready",function(){ if (GIM.logPlayerIds == true) { var players = filterObjs(function(obj) { if (obj.get("type") == "player") return true; else return false; }); log('************* PLAYER IDs *************'); _.each(players, function(obj) { log(obj.get("_displayname") + ": " + obj.get("_id")); }); log('**************************************'); } setInterval(function(){GIM.runMe()},500); on("change:graphic:lastmove",function(obj){ GIM.runTimeout = 2; obj.set({ statusmarkers: "!" }); }); on("add:graphic",function(obj){ GIM.isNewGraphic = true; obj.set("width",70); obj.set("height",70); }); });
:D! The idea is amazing
John C. said: Before I even get into the nitty gritty of this script, I'm going to start with a disclaimer. This is not a small deal. It will require a good bit of work on your end, from creating your items (defining weights and volumes and creating graphics) to setting up the area your players will use. For best results, it is HIGHLY RECOMMENDED to create this system in a separate campaign altogether, and have your players open it up in a separate window during your playtime. If you're not ready to spend a few hours getting this up and running, turn back now and go back to what you're used to. H* no. I took 2 hours off today to start working on this. Good luck man, looks amazing.
Okay, the OP is mostly done. That should give you plenty to do while I work on the video. Good luck!
This looks amazing o.o... *Drools*
This is insane, but very well done. I need to scoop out some time to finish my own API scripts
1420741869
vÍnce
Pro
Sheet Author
Reminds me of the old Ultima Inventory system. Very cool.
This is amazing. Ty for doing this. I had considered starting something similar. Now I don't have to! And I'm glad I don't. I would never have thought of using a 2nd campaign just to track inventory, but it definitely makes sense!
Great work! Works like a charm. (OPTIONAL) If you want to use this script to calculate currency, coins are handled a bit differently, and they are the only item that is grouped in a single token (by type). You don't need more than a single graphic, but you will need to change the GM Notes of each monetary graphic to " coin " followed by the type, for example "coin copper". Relevant types are " copper " " silver " " gold " " electrum " " platinum " (I'll probably add some configuration here down the line). Do not modify the bar1 and bar2 values of the coin graphics. Instead, change the bar3 value to the amount of coins that graphic represents. The script will then automatically calculate that amount of coins' weight and volume (based on user defined variables in the config, defaults to 5e rules). Shouldn't you use coins instead of coin? Lookin @ the script.
Kars said: Great work! Works like a charm. (OPTIONAL) If you want to use this script to calculate currency, coins are handled a bit differently, and they are the only item that is grouped in a single token (by type). You don't need more than a single graphic, but you will need to change the GM Notes of each monetary graphic to " coin " followed by the type, for example "coin copper". Relevant types are " copper " " silver " " gold " " electrum " " platinum " (I'll probably add some configuration here down the line). Do not modify the bar1 and bar2 values of the coin graphics. Instead, change the bar3 value to the amount of coins that graphic represents. The script will then automatically calculate that amount of coins' weight and volume (based on user defined variables in the config, defaults to 5e rules). Shouldn't you use coins instead of coin? Lookin @ the script. You're right! Fixed OP.
1420821593
Ziechael
Forum Champion
Sheet Author
API Scripter
Amazing idea, can't wait to try this out, especially since my campaign officially starts on Monday... a weekend of getting this set up will totally blow my players away! If anyone has dropbox or some such it might be a nice idea for people using this script to upload and share their inventory item tokens, i'll post in here with mine when i get round to making them (i hope this isn't against site rules by the way!). Hell the OP could even charge a small fee to access their tokens via a similar method and i'm sure there would be some uptake on it, i know i'd pay to save the time ;)
Ziechael said: Hell the OP could even charge a small fee to access their tokens via a similar method and i'm sure there would be some uptake on it, i know i'd pay to save the time ;) That's unwise, unless you've 100% made all the artwork from scratch. Fair use means not for profit, and I know I borrowed a lot of art from Skyrim and the like for my item tokens. But I may just upload them all for free! Won't be anytime in the next couple days, but maybe next week.
I have a month or two before my new campaign starts and I begin using this system. So hopefully any bugs are worked out by then. In the meantime, if you do post the tokens you use, make sure to set up a tip jar or something. I'll buy a beer for you since you'll have saved me hours of effort.
1420848542
Ziechael
Forum Champion
Sheet Author
API Scripter
John C. said: Ziechael said: Hell the OP could even charge a small fee to access their tokens via a similar method and i'm sure there would be some uptake on it, i know i'd pay to save the time ;) That's unwise, unless you've 100% made all the artwork from scratch. Fair use means not for profit, and I know I borrowed a lot of art from Skyrim and the like for my item tokens. But I may just upload them all for free! Won't be anytime in the next couple days, but maybe next week. Fair point, i wouldn't want any copyright issues but i just didn't want you to think i was urging you to share without considering that you deserve some recompense for your efforts ;) I'll keep an eye out for any links you might post and in the meantime will upload and link any tokens i make for the community to use if they want :)
This is awesome and I just finished steps 1 and 2, but im at a loss. I've no idea how to find Player ID's or provide my own img URL. Anyone care to walk me through it? :)
Dustin C. said: This is awesome and I just finished steps 1 and 2, but im at a loss. I've no idea how to find Player ID's or provide my own img URL. Anyone care to walk me through it? :) Copy and paste the script into the API section of your campaign. You have to modify certain parts of the script itself, as explained in the OP. I will make a video sometime next week that stars from the beginning and walks all the way through it.
1420915131
Ziechael
Forum Champion
Sheet Author
API Scripter
As for the image URL's check out John's blood and honour script, the video and info there will show you how to get the image url :)
Thanks Ziechael that did help :) Now just to find the Player IDs :)
0.0 Looks amazing ill have to play with this when i get some free time. I'll let you know how it goes. But looks great and thanks for the video great overview. :]
1420935114

Edited 1420935136
Well i found the Player ID's and the Img URL but now when i start the script i get an "Unexpected identifier" Error.
I am simply blown away.... Amazing job!
Dustin C. said: Well i found the Player ID's and the Img URL but now when i start the script i get an "Unexpected identifier" Error. I had that error as well in the beginning but i can't recall how i fixed it. If you want i can take a look @ you script if you post it.
1420981886

Edited 1420981930
Dustin C. said: Well i found the Player ID's and the Img URL but now when i start the script i get an "Unexpected identifier" Error. Yep, likely syntax involving a comma or something. Go ahead and post or PM me your script. I should have time sometime today or tomorrow to squeeze out a nice video.
1421017218

Edited 1421017931
Thank for looking guys, here's the script.... One quick question, will this only work on the Dev server? (i know the handy folder options only exist there) Some of my players are having issues getting to it. ///////////////////////////////////////////////// /***********************************************/ var GIM = { alias: "Graphic Inventory Manager", author: { name: "John C." || "Echo" || "SplenectomY", company: "Team Asshat" || "The Alehounds", contact: "<a href="mailto:echo@TeamAsshat.com" rel="nofollow">echo@TeamAsshat.com</a>", }, version: "0.1", gist: "<a href="https://gist.github.com/SplenectomY/9d1ef745f23927edc930" rel="nofollow">https://gist.github.com/SplenectomY/9d1ef745f23927edc930</a>", forum: "", /***********************************************/ ///////////////////////////////////////////////// /////////BEGIN USER CONFIG///////// playerList: [ "-JfJisgCaKcEixmy6wFH", /* 1: Crayolyous */ "-JfJFiPgWpcnE4BPfZwL", /* 2: Skippy */ "-JfL4-vrt-tOpIEONNs4", /* 3: Marcus */ //"-Jebn59AIWLWOxW3WMS9", /* 4: Meepo */ //"-JeIO7XaKP76ZS22z0x3", /* 5: Travaran */ ], logPlayerIds: true, invImg: "<a href="https://s3.amazonaws.com/files.d20.io/images/7081159/DjaafGIDpWVQDb-jcClbQA/thumb.png?1420919060" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/7081159/DjaafGIDpWVQDb-jcClbQA/thumb.png?1420919060</a>", coin: { wgt: 0.02, /* in lbs. Default is per 5e rules */ vol: 0.0002 /* in cubic feet. (5,000 per ft3) */ }, temperatureUnit: "F", /* Change to C if Celsius is desired */ storageItems: [ backpack = { name: "backpack", max_wgt: 30, max_vol: 2, }, barrel = { name: "barrel", max_wgt: 80, max_vol: 6, }, chest = { name: "chest", max_wgt: 60, max_vol: 4, }, quiver = { name: "quiver", max_wgt: "", max_vol: 0.2, }, boltCase = { name: "boltcase", max_wgt: "", max_vol: 0.3, }, pouch = { name: "pouch", max_wgt: 6, max_vol: .2, }, bagHolding = { name: "bagofholding", max_wgt: 500, max_vol: 64, }, handyHaversackSide1 = { name: "hhhside1", max_wgt: 20, max_vol: 2, }, handyHaversackSide2 = { name: "hhhside2", max_wgt: 20, max_vol: 2, }, handyHaversack = { name: "hhh", max_wgt: 80, max_vol: 8, }, // I have 6 pockets, because thats the max // I would give my characters on any item. // Add more if you wish. pocket1 = { name: "pocket1", max_wgt: 3, max_vol: .1, }, pocket2 = { name: "pocket2", max_wgt: 3, max_vol: .1, }, pocket3 = { name: "pocket3", max_wgt: 3, max_vol: .1, }, pocket4 = { name: "pocket4", max_wgt: 3, max_vol: .1, }, pocket5 = { name: "pocket5", max_wgt: 3, max_vol: .1, }, pocket6 = { name: "pocket6", max_wgt: 3, max_vol: .1, }, ], //////////END USER CONFIG////////// runTimeout: 0, isNewGraphic: false, extraRun: false, objsToBeCreated: [], runMe: function forGreatJustice() { if (GIM.runTimeout &gt; 0) { GIM.runTimeout--; } else { processGIM() for(i1 = 0; i1 &lt; GIM.objsToBeCreated.length; i1++) { create_inv( GIM.objsToBeCreated[i1][0], GIM.objsToBeCreated[i1][1], GIM.objsToBeCreated[i1][2], GIM.objsToBeCreated[i1][3], GIM.objsToBeCreated[i1][4], GIM.objsToBeCreated[i1][5], GIM.objsToBeCreated[i1][6] ); } GIM.objsToBeCreated = []; // This handles any extra runs if (GIM.extraRun == true && GIM.objsToBeCreated.length == 0) { processGIM(); processGIM(); GIM.extraRun = false; } GIM.runTimeout--; } }, isObj: function runFlagChecks(obj,type) { s = obj.get("gmnotes"); var isObjResult, invFlags = [ "main", "inv", "worn", "drop", "ignore" ], statusTokenFlags = [ "status", "status2" ]; if (type == "item") { isObjResult = true; for (i2 = 0; i2 &lt; invFlags.length; i2++) { if (s.indexOf(invFlags[i2]) !== -1) { isObjResult = false; } } for (i3 = 0; i3 &lt; statusTokenFlags.length; i3++) { if (s.indexOf(statusTokenFlags[i3]) !== -1) { isObjResult = false; } } } if (type == "inv") { isObjResult = false; for (i4 = 0; i4 &lt; invFlags.length; i4++) { if (s.indexOf(invFlags[i4]) !== -1) { isObjResult = true; } } } if (type == "status") { isObjResult = false; for (i4 = 0; i4 &lt; statusTokenFlags.length; i4++) { if (s.indexOf(statusTokenFlags[i4]) !== -1) { isObjResult = true; } } } return isObjResult; }, isInside: function allYourBase(x,y) { if (x.get("left") &gt; y.left && x.get("top") &gt; y.top && x.get("left") &lt; y.right && x.get("top") &lt; y.bottom) return true; else return false; } }; fixedCreateObj = (function () { return function () { var obj = createObj.apply(this, arguments); if (obj && !obj.fbpath) { obj.fbpath = obj.changed._fbpath.replace(/([^\/]*\/){4}/, "/"); } return obj; }; }()); function processGIM() { var warmth = [], hosp = [], hosp_average = 0, equipped = [], inv = [], items = [], items_stacked = [], objInvs = filterObjs(function(obj) { if(obj.get("type") == "graphic") { return GIM.isObj(obj,"inv"); } else return false; }), objItems = filterObjs(function(obj) { if(obj.get("type") == "graphic") { return GIM.isObj(obj,"item"); } else return false; }), objStatus = filterObjs(function(obj) { if(obj.get("type") == "graphic") { return GIM.isObj(obj,"status"); } else return false; }), objects = objInvs.concat(objItems,objStatus); for (i5 = 0; i5 &lt; GIM.playerList.length; i5++) { warmth[i5] = 0; hosp[i5] = 0; equipped[i5] = []; } /***************************/ /***** STEP 1 out of 4 *****/ /***************************/ _.each(objInvs, function(obj) { s = obj.get("gmnotes"); if (s.indexOf("main") !== -1) { if (!obj.get("bar1_value")) {obj.set("bar1_value", 0)} inv[inv.length] = { type: "main", id: obj.get("_id"), owner: obj.get("bar3_value"), weight: 0, volume: 0, inv_gmnotes: obj.get("gmnotes"), left: Math.floor(obj.get("left") - Math.floor(obj.get("width") / 2)), top: Math.floor(obj.get("top") - Math.floor(obj.get("height") / 2)), right: Math.floor(obj.get("left") + Math.floor(obj.get("width") / 2)), bottom: Math.floor(obj.get("top") + Math.floor(obj.get("height") / 2)) }; } }); /***************************/ /***** STEP 2 out of 4 *****/ /***************************/ _.each(objInvs, function(obj) { s = obj.get("gmnotes"); for(i6 = 0; i6 &lt; inv.length; i6++) { if (inv[i6].type == "main" && s.indexOf("main") == -1 && GIM.isInside(obj, inv[i6]) == true) { inv[inv.length] = { type: "sub", id: obj.get("_id"), owner: inv[i6].owner, weight: 0, volume: 0, layer: obj.get("layer"), inv_gmnotes: obj.get("gmnotes"), left: Math.floor(obj.get("left") - Math.floor(obj.get("width") / 2)), top: Math.floor(obj.get("top") - Math.floor(obj.get("height") / 2)), right: Math.floor(obj.get("left") + Math.floor(obj.get("width") / 2)), bottom: Math.floor(obj.get("top") + Math.floor(obj.get("height") / 2)) }; } } }); /***************************/ /***** STEP 3 out of 4 *****/ /***************************/ _.each(objItems, function(obj) { s = obj.get("gmnotes"); // ... log it into the item array items[items.length] = { id: obj.get("_id"), left: obj.get("left"), top: obj.get("top") }; // ... make sure the item has wgt/vol values if(!obj.get("bar1_value")) {obj.set("bar1_value",0)} if(!obj.get("bar2_value")) {obj.set("bar2_value",0)} // If it's an empty potion, remove the name and tint if (s.indexOf("liquid") !== -1 && obj.get("bar1_value") == 0) { obj.set("name",""); obj.set("tint_color","transparent"); } // Settings for newly added items if(GIM.isNewGraphic == true) { obj.set("showplayers_name",true); obj.set("showname",true); obj.set("playersedit_bar2",false); obj.set("playersedit_bar3",false); obj.set("showplayers_bar3",false); obj.set("showplayers_bar2",false); if (s.indexOf("liquid") !== -1) { obj.set("playersedit_bar1",true); obj.set("showplayers_bar1",true); } else { obj.set("playersedit_bar1",false); obj.set("showplayers_bar1",false); } } // For each inventory ... for(i7 = 0; i7 &lt; inv.length; i7++) { var q = inv[i7].owner - 1, r = inv[i7].inv_gmnotes; // ... that contains this item object ... if (GIM.isInside(obj, inv[i7]) == true && r.indexOf("ignore") == -1) { // ... do these things: // If the item's containing inventory is hidden, hide the item ... if(inv[i7].layer == "gmlayer" && obj.get("layer") != "gmlayer") { obj.set("layer","gmlayer") // ... or unhide it otherwise. } else if (inv[i7].type == "sub" && inv[i7].inv_gmnotes != "drop" && inv[i7].layer == "objects" && obj.get("layer") != "objects" || inv[i7].layer == "map") { obj.set("layer","objects"); } // If item is on a drop inventory, put it on the map layer if(inv[i7].inv_gmnotes == "drop") { obj.set("layer","map"); } // If item is not stowable, prevent stowing as such if (r.indexOf("inv") !== -1 && r.indexOf("worn") == -1 && s.indexOf("unstowable") !== -1) { obj.set("top",inv[i7].bottom + 35); } else { // This will exclude items that are hidden or dropped. if(obj.get("layer") == "objects") { // If the item is a pile of coins ... if(s.indexOf("coins") !== -1) { // ... adjust the weight and volume ... obj.set("bar1_value",Math.round((parseFloat(obj.get("bar3_value") * GIM.coin.wgt) * 1000) / 1000)); obj.set("bar2_value",Math.round((parseFloat(obj.get("bar3_value") * GIM.coin.vol) * 1000) / 1000)); // ... and let its name show the total if(s.indexOf("copper") !== -1) { obj.set("name","Cp: " + obj.get("bar3_value")); } if(s.indexOf("silver") !== -1) { obj.set("name","Sp: " + obj.get("bar3_value")); } if(s.indexOf("gold") !== -1) { obj.set("name","Gp: " + obj.get("bar3_value")); } if(s.indexOf("electrum") !== -1) { obj.set("name","Ep: " + obj.get("bar3_value")); } if(s.indexOf("platinum") !== -1) { obj.set("name","Pp: " + obj.get("bar3_value")); } } // If the item has a non-changing base weight (empty bottles) ... if (s.indexOf("weight") !== -1) { var weight_array = s.split("%20"), weight_loc = weight_array.indexOf("weight") + 1; // ... add the weight to its containing inventories inv[i7].weight += parseFloat(weight_array[weight_loc]); } // Add weights and volumes of each item to its containing inventories inv[i7].weight += parseFloat(obj.get("bar1_value")); inv[i7].volume += parseFloat(obj.get("bar2_value")); // Calculate how warm a player is based on what he or she is wearing if (inv[i7].inv_gmnotes == 'worn') { if (s.indexOf("warmth") !== -1) { warmth_array = s.split("%20"); warmth_loc = warmth_array.indexOf("warmth") + 1; warmth[q] += parseInt(warmth_array[warmth_loc]); } // Create new inventory boxes if an inv item is equipped for the first time for (k = 0; k &lt; GIM.storageItems.length; k++) { if (s.indexOf(GIM.storageItems[k].name) !== -1) { GIM.objsToBeCreated[GIM.objsToBeCreated.length] = [ obj.get("_pageid"), GIM.storageItems[k].name, obj.get("left"), obj.get("top"), obj.get("_id"), GIM.storageItems[k].max_wgt, GIM.storageItems[k].max_vol, ]; obj.set("gmnotes", s.replace(GIM.storageItems[k].name,"storage_" + obj.get("_id"))); } } // Log any worn "storage_" items into an array. if (s.indexOf("storage_" + obj.get("_id")) !== -1) { equipped[q][(equipped[q]).length] = "rep_" + obj.get("_id"); } } // Give the proper player control of that item if(inv[i7].type == "main") { for(j = 0; j &lt; GIM.playerList.length; j++) { if (inv[i7].owner == j) { obj.set("controlledby",GIM.playerList[j - 1]) } } // If the item gives hospitality, reflect as such if (s.indexOf("hosp") !== -1) { hosp[q]++; } } } } } } }); // Calculate average hospitality for(i8 = 0; i8 &lt; hosp.length; i8++) { var hosp_average += hosp[i8]; } Math.floor(hosp_average /= GIM.playerList.length); /***************************/ /***** STEP 4 out of 4 *****/ /***************************/ _.each(objects, function(obj) { s = obj.get("gmnotes"); stacks = 1; for (i9 = 0; i9 &lt; items.length; i9++) { if (obj.get("left") == items[i9].left && obj.get("top") == items[i9].top && obj.get("_id") != items[i9].id && GIM.isObj(obj,"item") == true) { stacks++; } } if (stacks == 1) { obj.set({ status_blue: false, status_red: false }); } else if (stacks &lt; 10) { obj.set({ status_red: false, status_blue: stacks }); } else if (stacks &lt; 100) { var tens = Math.floor(stacks / 10); obj.set({ status_blue: stacks - (tens * 10), status_red: tens }); } // Make sure "unequipped" inventories are hidden. for (i10 = 0; i10 &lt; inv.length; i10++) { var q = inv[i10].owner - 1; is_it_equipped = false; if (inv[i10].id == obj.get("_id")) { if (s.indexOf("rep") !== -1) { item_owner = inv[i10].owner - 1; for (j = 0; j &lt; equipped[item_owner].length; j++){ if (s.indexOf(equipped[item_owner][j]) !== -1) { is_it_equipped = true; } } if (is_it_equipped == true) { if (s.indexOf("ignore") !== -1) { obj.set("layer","map"); } else { obj.set("layer","objects"); } if (GIM.extraRun == true) { } else { GIM.extraRun = true; } } else if (is_it_equipped == false) { obj.set("layer","gmlayer"); if (GIM.extraRun == true) { } else { GIM.extraRun = true; } } } // Now we apply values to bar1 and bar2 of each inventory. obj.set("bar1_value", Math.round(inv[i10].weight * 1000) / 1000); obj.set("bar2_value", Math.round(inv[i10].volume * 1000) / 1000); if (obj.get("bar1_value") &gt; obj.get("bar1_max") && obj.get("bar1_max") &gt; 0) { obj.set({ status_dead: true }); } else if (obj.get("bar2_value") &gt; obj.get("bar2_max") && obj.get("bar2_max") &gt; 0) { obj.set({ status_dead: true }); } else { obj.set({ status_dead: false }); } } // Set status token values if (inv[i10].type == 'main' && obj.get("bar3_value") == inv[i10].owner) { if (obj.get("gmnotes") == "status") { obj.set("bar1_value", Math.round(inv[i10].weight * 10) / 10); obj.set("bar2_value", Math.round(inv[i10].volume * 10) / 10); obj.set("name",'Weight: ' + obj.get("bar1_value") + " | Profile: " + obj.get("bar2_value")); } else if (obj.get("gmnotes") == "status2") { // Temperature values are changed per user preference if (GIM.temperatureUnit == "F") { obj.set("bar1_value", warmth[q] * 10); } else if (GIM.temperatureUnit == "C") { obj.set("bar1_value", Math.floor(((warmth[q] * 10 - 32) * (5 / 9)))); } obj.set("bar2_value", hosp[q]); obj.set("name",'Warmth: ' + obj.get("bar1_value") + "°" + GIM.temperatureUnit + " | Hosp: " + obj.get("bar2_value") + " (" + hosp_average + ")"); } } } }); GIM.runTimeout = GIM.runTimeout + 3; }; function create_inv(g_page_id, g_name, g_left, g_top, g_id, g_wgt, g_vol) { setTimeout(function(){ toBack(fixedCreateObj("graphic",{ name: g_name, imgsrc: GIM.invImg, pageid: g_page_id, left: g_left + 350, top: g_top, width: 70, height: 70, bar1_value: 0, bar1_max: g_wgt, bar2_value: 0, bar2_max: g_vol, layer: "objects", gmnotes: "inv rep_" + g_id })); },5); } on("ready",function(){ if (GIM.logPlayerIds == true) { var players = filterObjs(function(obj) { if (obj.get("type") == "player") return true; else return false; }); log('************* PLAYER IDs *************'); _.each(players, function(obj) { log(obj.get("_displayname") + ": " + obj.get("_id")); }); log('**************************************'); } setInterval(function(){GIM.runMe()},500); on("change:graphic:lastmove",function(obj){ GIM.runTimeout = 2; obj.set({ statusmarkers: "!" }); }); on("add:graphic",function(obj){ GIM.isNewGraphic = true; obj.set("width",70); obj.set("height",70); }); });
1421064475

Edited 1421071513
Ziechael
Forum Champion
Sheet Author
API Scripter
John, may i ask how you have organised your journal entries so neatly? Is that another script or a function i'm not able to find? Also, anything that requires GM notes, should they go in the journal entry or the token's GM notes? Sorry for so many questions, especially since you are probably recording the how to updated video right now! ;)
1421134168

Edited 1421134556
Ziechael said: John, may i ask how you have organised your journal entries so neatly? Is that another script or a function i'm not able to find? Also, anything that requires GM notes, should they go in the journal entry or the token's GM notes? Sorry for so many questions, especially since you are probably recording the how to updated video right now! ;) The journal entries look nice because I'm using the Dev server. You can read more about that on the forum. GM Notes always refers to a token's GM notes. Hope it's going well for you! Dustin C. said: Thank for looking guys, here's the script.... One quick question, will this only work on the Dev server? (i know the handy folder options only exist there) Some of my players are having issues getting to it. Okay. I've found the error - it was on my end. New script has been posted. You can check the Gist if you want to see what was changed, it was literally one line. Nooby mistake.
1421142871
Ziechael
Forum Champion
Sheet Author
API Scripter
John C. said: The journal entries look nice because I'm using the Dev server. You can read more about that on the forum. GM Notes always refers to a token's GM notes. Hope it's going well for you! I thought it might be the Dev server, not to worry, tags will work nicely for me for now, maybe with some organisation in the titling too such as Weapon - Long Sword, Weapon - Mace etc etc... Thanks for clarifying the notes bit, i had gathered as much from your old vid but just wanted to be 100% before putting the work in ;) Started my campaign last night and the first thing i do as evil GM... strip my players of all their possessions, whoops! One final question from me... Will it be an issue that i've got each inventory on a separate page? They are all numbered 1-6 as required...
Awesome John, its working now :) @Ziec If you players want their privacy you could separate them with Dynamic lighting like I did. If the page thing doesn't work.
1421228041
Ziechael
Forum Champion
Sheet Author
API Scripter
Dustin C. said: @Ziec If you players want their privacy you could separate them with Dynamic lighting like I did. If the page thing doesn't work. That is an inspired idea, i'm hoping the pages work (haven't quite gotten around to adding any items to test yet though lol) but if not i'll be certainly stealing using your method! As promised i will be creating an online album to house my item pics for people to use once i get around to it ;)
1421230739

Edited 1421251131
Ziechael
Forum Champion
Sheet Author
API Scripter
At the risk of continuing to highlight my ignorance or plain stupidity please could i get some clarification on the following: When it's all set up, your players will be able to adjust the bar1 value depending on how full the container is, and name the container based on what's inside. For example, give your players an empty bottle token, fill it up to its max weight, color it red and name it "Potion of Healing." When they "drink" it (reduce its green bar to 0), the token will automatically revert to its default state (an empty bottle with no name and no color tint!) and they can then choose what to do with the empty container I'm not sure how it would be coloured? Do you mean by adding a tint? - EDIT - Played around and tint is obviously the way to do it... D'OH! Sorry... just way too excited to get this set up right as it is PERFECT for my campaign :) However, finally got my items images sorted and decided to start with the backpack, a staple for all adventurers... however nothing happens when i equip it? Please see the screen shots below: Token setup Journal entry info When the backpack is applied to the 'worn' area nothing happens and no stats and added to the status token... while i'm certain i've got it wrong again, i can't for the life of me see where! For reference, i have the area set as main, with worn boxes in the places needed. The ground is set to drop and the character token is set to status with the campfire set to status2. The character token and campfire are at token level while the rest are at map level. The main has a number, in this case 2 as it is the second player (player 1 has yet to log on and therefore his playerid isn't recorded in the script yet. This player has though so i thought it would be a good place to start. I'm now considering the fact that maybe the script won't work across separate pages? Having just reread from top to bottom it may be because the player has to add the item themselves? Drag items from your item journal to your players individual sheets and have them equip themselves. When they equip a storage item (like a backpack) for the first time, it is YOUR JOB to move and stretch that item to the standards of your game, and decorate it as you please. That might be my problem? Although i have assigned an NPC sheet to myself as player 6 to test it but still no joy as GM or player. Here is the only bits of the script i've played with as per the instructions: /////////BEGIN USER CONFIG///////// playerList: [ //"", /* 1: Ash Mo Lean */ "-JfZuA5YWhrOW0jAM3Fs", /* 2: Axilgar */ "-JfTPQnSpjB5dCGc10aN", /* 3: Bertram Tealeaf */ "-JfZdxbBDDcARqde2jBR", /* 4: Lianna Moonshade */ "-JfYB-z8nNg7H5C3PEqa", /* 5: Septimus Langstitch */ "-JcaAKpUDJBP4TNLA1Sb", /* 6: NPC */ ], logPlayerIds: true, invImg: " <a href="https://s3.amazonaws.com/files.d20.io/images/7114849/8apdntBXlft64mulUcg6hQ/thumb.png?1421060048" rel="nofollow">https://s3.amazonaws.com/files.d20.io/images/7114849/8apdntBXlft64mulUcg6hQ/thumb.png?1421060048</a> ", coin: { wgt: 0.01, /* in lbs. Default is per 5e rules */ vol: 0.0002 /* in cubic feet. (5,000 per ft3) */ }, temperatureUnit: "C", /* Change to C if Celsius is desired */ storageItems: [ backpack = { name: "backpack", max_wgt: 30, max_vol: 1, }, quiver = { name: "quiver", max_wgt: "", max_vol: 0.1, }, boltCase = { name: "boltcase", max_wgt: "", max_vol: 0.15, }, pouch = { name: "pouch", max_wgt: 6, max_vol: 0.2, }, coinpurse = { name: "coinpurse", max_wgt: 6, max_vol: 0.2, }, gempouch = { name: "gempouch", max_wgt: 6, max_vol: 0.2, }, componentpouch = { name: "componentpouch", max_wgt: 6, max_vol: 0.2, }, bagHolding = { name: "bagofholding", max_wgt: 500, max_vol: 64, }, handyHaversackSide1 = { name: "hhhside1", max_wgt: 20, max_vol: 2, }, handyHaversackSide2 = { name: "hhhside2", max_wgt: 20, max_vol: 2, }, handyHaversack = { name: "hhh", max_wgt: 80, max_vol: 8, }, // I have 6 pockets, because thats the max // I would give my characters on any item. // Add more if you wish. pocket1 = { name: "pocket1", max_wgt: 3, max_vol: 0.1, }, pocket2 = { name: "pocket2", max_wgt: 3, max_vol: 0.1, }, pocket3 = { name: "pocket3", max_wgt: 3, max_vol: 0.1, }, pocket4 = { name: "pocket4", max_wgt: 3, max_vol: 0.1, }, pocket5 = { name: "pocket5", max_wgt: 3, max_vol: 0.1, }, pocket6 = { name: "pocket6", max_wgt: 3, max_vol: 0.1, }, ], //////////END USER CONFIG////////// Ziechael &lt;= officially the thorn in your side ;)
Thing i've noticed: Your "main" area has to take up the Backpack or storage area too (just like his image file, John shared, the silhouette with a large blank area to the right of it) @Ziechael I see in your backpack token your adding 2gp in your Bar 3 (red circle) I'm pretty sure that incorrect, and used for durability on items (that might be your issue). With that being said, Im running into an error: /home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:1 orts, require, module, __filename, __dirname) { function f(a){throw a;}var j=v ^ Error: Firebase.update failed: First argument contains NaN in property 'bar1_value' at Error (&lt;anonymous&gt;) at Aa (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:9:186) at Aa (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:10:196) at za (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:8:468) at Da (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:10:392) at G.W.update (/home/symbly/www/d20-api-server/node_modules/firebase/lib/firebase-node.js:128:318) at TrackedObj._doSave ( I'm not 100% But i think it happens when i rename a token (Ex i have a jornal entry for a Light Crossbow, but then edit the name to a Light Crossbow +1) and attempt to throw it to the ground.
1421252518

Edited 1421253479
Ziechael
Forum Champion
Sheet Author
API Scripter
Thanks Dustin, my main area does extend far to the right (i used Johns and then messed with the size to fit it to the grid), even Hitler would be surprised at how far right that bad boy goes (too soon?). I did consider the bar 3 issue but since it is only used when currency is in effect and John states that: Third bar is optional and up to you; I use it to track item HP or durability I'm pretty sure it shouldn't affect the script, there is no error messages, no disabling, just nothing happening... does yours work for you when applying things as a GM or have your players had some input on testing, i only ask as my players (bar one) have all just logged to give me their ID and then left me to it, kind souls that they are ;) Is it wrong i kinda feel like a groupie waiting for John to show me the light lol
1421253449
Ziechael
Forum Champion
Sheet Author
API Scripter
for the record here are my inventory items , some are a little rough around the edges and may well improve as i find the time but they are geared towards the lists of weapons, armour and adventuring gear found in the D&D 3.5 core PH1. Most things are covered although i couldn't bring myself to do one for the 10 foot pole, i wouldn't touch it with a... well... 10 foot pole really!
your icons look really good for your inventory I haven't had any problems until the one I mentioned above and I haven't had any player interaction as well most my players are busy throughout the week but I'm hoping Sunday to get some real testing working on it that is if I can figure out that firebase issue
1421339887
Ziechael
Forum Champion
Sheet Author
API Scripter
Thanks they are a mixture of skyrim, runescape (8 years of my life i aren't getting back!) and general google image searches lol, i still can't get anything to function at all... i know its there just waiting to happen but something is just not clicking :(
1421415839
Ziechael
Forum Champion
Sheet Author
API Scripter
Seems to be working now, i've put them all on one page and not sure if this was an issue or not but made sure the main (1) occupied the top left square rather than floating in the middle like before... LOVE this script!
Great idea and nice implementation :) I did find some bugs tho and have posted fixes to your gist (see fork). The 500ms interval seems a little to harsh for recalculating everything. Or am I missing something that prevents this?
I have more or less rewritten your whole script, because I had some issues and ended up fixing lots of bad smells and minor performance issues. Since this was originally your idea, dDo you mind if I push this into the roll20 api scripts on github? I will credit you, if you want :)
How do you set up the "attached" container that's attached to the backpack but doesn't reject unstowable items? The video has it linked up to the backpack such that it is hidden when you take the backpack off, but I can't figure out how to mishmash the attributes like that.
1422959360
Ziechael
Forum Champion
Sheet Author
API Scripter
When a token with the gmnotes of backpack or pouch etc is first put on a 'worn' slot in the main inventory it will create a single square inventory somewhere to the right, always best to keep an eye out for it and be aware that it might create it behind something else if you've a bit of clutter already (like me!). You can then just take that single square, move it somewhere appropriate and resize it to represent the amount of stuff you are likely to need to put in it. For ease of use you can also take the same image you've used for the containers token and place that behind the new inventory space to make it apparent what it represents. The script will automatically 'link' the token in the inventory with the containers inventory so you shouldn't need to do anything else with it. Be aware that it can take a short while for the script to notice you've unequipped something and update the inventory accordingly. Any further problems feel free to send me a link to your inventory campaign and i'll take a look for you as i had to go through all of the same issues to get mine working ;)
Looks amazing. Sadly, I do so much prep work as a DM that making 100's of tokens might not be something I can do. If Roll20 were to incorporate this though . . . woah. Amazing!
I'm assuming having the option to make folders now makes this less confusing. Are you making use of those? Feel like sharing a screen shot?
1426938429

Edited 1426941856
Ziechael
Forum Champion
Sheet Author
API Scripter
HAH! Totally forgot i could do that now... i'll post a screenie when i've rearranged if you like? Edit: Here you go, this is my new and improved lay out (items can still be searched for by various tags too). I just need to go through and remove all the item prefixes next (Weapon - ... Armour - ... etc *sigh*):
How long did it take you to set up all of this?
1427015043
Ziechael
Forum Champion
Sheet Author
API Scripter
With a bit of help getting to grips with it all from John C. himself it was the tweaking that took longest. Once you get into the groove it took less than a minute per item... So a good 3-4 hours overall... Once I knew what I was doing. But since it is for a long term campaign it was totally worth it :)