Skip to content

Instantly share code, notes, and snippets.

@SplenectomY
Last active November 4, 2017 02:36
Show Gist options
  • Save SplenectomY/9d1ef745f23927edc930 to your computer and use it in GitHub Desktop.
Save SplenectomY/9d1ef745f23927edc930 to your computer and use it in GitHub Desktop.
Graphic Inventory Manager - Multidimensional token GUI for Roll20.net
state.SplenectomY = state.SplenectomY || {};
state.SplenectomY.inventory = state.SplenectomY.inventory || {};
state.SplenectomY.inventory.pageID = state.SplenectomY.inventory.pageID || {};
state.SplenectomY.players = state.SplenectomY.players || {};
state.SplenectomY.inventory.currentCaller = state.SplenectomY.inventory.currentCaller || {};
state.SplenectomY.inventory.currentCallerid = state.SplenectomY.inventory.currentCallerid || {};
state.SplenectomY.inventory.message = state.SplenectomY.inventory.message || {};
state.SplenectomY.inventory.currentPlayerNum = state.SplenectomY.inventory.currentPlayerNum || {};
state.SplenectomY.players[1] = state.SplenectomY.players[1] || {};
state.SplenectomY.players[2] = state.SplenectomY.players[2] || {};
state.SplenectomY.players[3] = state.SplenectomY.players[3] || {};
state.SplenectomY.players[4] = state.SplenectomY.players[4] || {};
state.SplenectomY.players[1].id = '-KxAuSiFNH1gFiHAE9eT'; /* 1: Reece. (CURRENTLY JOHN C) */
state.SplenectomY.players[2].id = '-KxBOmnHdgmm6gYVuH6j'; /* 2: Mack */
state.SplenectomY.players[3].id = '-KxjC230daTpBLL_hZlw'; /* 3: Rhi */
state.SplenectomY.players[4].id = ''; /* 4: Stevie */
/////////////////////////////////////////////////
/***********************************************/
var Localization = {
'coins': 'Coins',
'coin_copper': 'Copper',
'coin_silver': 'Silver',
'coin_electrum': 'Electrum',
'coin_gold': 'Gold',
'coin_platinum': 'Platinum',
'weight': 'Weight',
'warmth': 'Warmth',
'hospitality': 'Hospitality',
'profile': 'Profile',
'storage_backpack': 'Backpack',
'storage_quiver': 'Quiver',
'storage_boltcase': 'Bolt case',
'storage_pouch': 'Pouch',
'storage_bagofholding': 'Bag of Holding',
'storage_hhh': 'Handysack',
'storage_hhhside1': 'Handysack 1',
'storage_hhhside2': 'Handysack 2',
'storage_chest': 'Chest',
'storage_cart': 'Cart',
'storage_pocket_common': 'Common Clothes Pocket',
'storage_pocket_fine': 'Fine Clothes Pocket',
'storage_pocket_costume': 'Costume Clothes Pocket',
'storage_pocket_robes': 'Robes Pocket',
'storage_pocket_traveler': 'Traveler Clothes Pocket'
};
/////////////////////////////////////////////////
/***********************************************/
var RoubysInventoryManager = {
/////////BEGIN USER CONFIG/////////
playerList: [
state.SplenectomY.players[1].id, /* 1: Reece. (CURRENTLY JOHN C) */
state.SplenectomY.players[2].id, /* 2: Mack */
state.SplenectomY.players[3].id, /* 3: Rhi */
state.SplenectomY.players[4].id /* 4: Stevie */
],
logPlayerIds: true,
inventoryTokenImage: "https://s3.amazonaws.com/files.d20.io/images/6927416/y5kI_BBCDJrxGCU8FttwEg/thumb.png?1420069394",
coin: {
weight: 0.02, // in lbs
volume: 0.00004, // in cubic feet (25,000 per ft3)
value_copper: 0.01,
value_silver: 0.1,
value_electrum: 0.2,
value_gold: 1,
value_platinum: 10
},
temperatureUnit: 'F', // change to F if fahrenheit is desired
storageItems: [
{
name: 'backpack', // key of the storagekind, does not contain storage!
maxWeight: 15, // maximum weight allowed in this kind
maxVolume: 1 // maximum of contained volume allowed
},
{
name: 'quiver',
maxWeight: 1,
maxVolume: .2
},
{
name: 'boltcase',
maxWeight: 1.5,
maxVolume: .2
},
{
name: 'pouch',
maxWeight: 6,
maxVolume: .2
},
{
name: 'pocket_common',
maxWeight: 3,
maxVolume: .1
},
{
name: 'pocket_costume',
maxWeight: 6,
maxVolume: .2
},
{
name: 'pocket_fine',
maxWeight: 6,
maxVolume: .2
},
{
name: 'pocket_traveler',
maxWeight: 10,
maxVolume: .3
},
{
name: 'pocket_robes',
maxWeight: 10,
maxVolume: .3
},
{
name: 'bagofholding',
maxWeight: 500,
maxVolume: 64
},
{
name: 'hhh',
maxWeight: 80,
maxVolume: 8
},
{
name: 'hhhside',
maxWeight: 20,
maxVolume: 2
},
{
name: 'chest',
maxWeight: 300,
maxVolume: 12
},
{
name: 'cart',
maxWeight: 600,
maxVolume: 50
}
],
//////////END USER CONFIG//////////
debugMode: false,
objsToBeCreated: [],
lastRun: Date.now(),
run: function ()
{
var time = Date.now();
if (time - RoubysInventoryManager.lastRun > 1000)
{
RoubysInventoryManager.process();
_.each(RoubysInventoryManager.objsToBeCreated, function (o)
{
RoubysInventoryManager.createInventory(o.pageId, o.name, o.left, o.top, o.id, o.weight, o.volume);
});
RoubysInventoryManager.objsToBeCreated = [];
RoubysInventoryManager.lastRun = time;
}
},
createInventory: function (g_page_id, g_name, g_left, g_top, g_id, g_wgt, g_vol)
{
setTimeout(function ()
{
toBack(RoubysInventoryManager.fixedCreateObj('graphic', {
name: g_name,
imgsrc: RoubysInventoryManager.inventoryTokenImage,
pageid: g_page_id,
left: g_left + 280,
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);
},
fixedCreateObj: function ()
{
var obj = createObj.apply(this, arguments);
if (obj && !obj.fbpath)
{
obj.fbpath = obj.changed._fbpath.replace(/([^\/]*\/){4}/, '/');
}
return obj;
},
process: function ()
{
var typeRegEx = /(main)|(inv)|(worn)|(drop)|(ignore)|(character)|(status\d?)/,
modifierRegEx = /(unstowable)|(constantweight)|(liquid)|(hosp)|(storage)/ig,
paramModifierRegEx = /(?:(weight)%20([\d\.,]+))|(?:(warmth)%20(\d+))|(?:(coins)%20(\S+))|(?:(rep)_(\S+))|(?:(weightfactor)%20([\d\.,]+))/ig,
amountRegEx = /(\d+)x/;
function getType(notes)
{
return _.reduceRight(typeRegEx.exec(notes), function (memo, val) { return memo || val; }, null) || 'item';
}
function getModifier(notes)
{
var match = null,
results = {
unstowable: false,
constantweight: false,
liquid: false,
hosp: false,
storage: false
};
modifierRegEx.lastIndex = 0;
while (match = modifierRegEx.exec(notes))
results[match[0].toLowerCase()] = !!_.reduceRight(match, function (memo, val) { return memo || val; }, null);
return results;
}
function getParamModifier(notes)
{
var match = null,
results = {
weight: null,
warmth: null,
coins: null,
rep: null,
weightfactor: null
};
paramModifierRegEx.lastIndex = 0;
while (match = paramModifierRegEx.exec(notes))
{
var index = -1,
value = _.reduceRight(match, function (memo, val, i) { if (!memo) index = i; return memo || val; }, null);
results[match[index - 1].toLowerCase()] = value;
}
return results;
}
// returns true if the center of 'item' lies inside of 'container' dimensions
function itemIsInContainer(item, container)
{
var halfWidth = container.get('width') / 2,
halfHeight = container.get('height') / 2,
left = container.get('left') - halfWidth,
top = container.get('top') - halfHeight,
right = container.get('left') + halfWidth,
bottom = container.get('top') + halfHeight;
return item.get('left') >= left &&
item.get('top') >= top &&
item.get('left') <= right &&
item.get('top') <= bottom;
}
var characterAreas = [],
statuses = [],
equipmentSlots = [],
inventories = [],
items = [],
equipped = [];
/***
* Go thru all tokens on the current page and sort them into their groups, parse modifiers
* and store informations in their objects for the algorithm
**/
var pageGraphics = findObjs({
_pageid: state.SplenectomY.inventory.pageID,
_type: 'graphic',
});
_.each(pageGraphics, function (graphic)
{
var notes = graphic.get('gmnotes'),
specialType = getType(notes),
modifier = getModifier(notes),
paramModifier = getParamModifier(notes);
var obj = {
type: specialType,
graphic: graphic
};
var options = {
statusmarkers: '!',
showplayers_name: true,
showname: true,
showplayers_bar1: false,
showplayers_bar2: false,
showplayers_bar3: false,
playersedit_bar1: false,
playersedit_bar2: false,
playersedit_bar3: false,
};
switch (specialType)
{
case 'main':
characterAreas.push(obj);
options.showname = false;
break;
case 'character':
case 'status':
case 'status2':
case 'status3':
statuses.push(obj);
if (graphic.get('bar1_max') > 0)
options.showplayers_bar1 = true;
break;
case 'worn':
obj.properties = {
isSlot: true
};
equipmentSlots.push(obj);
break;
case 'inv':
obj.properties = {
representId: paramModifier.rep
};
options.showplayers_bar1 = true;
options.showplayers_bar2 = true;
case 'drop':
inventories.push(obj);
break;
case 'ignore':
break;
default:
obj.properties = {
equippedAt: null,
isLiquidContainer: modifier.liquid,
isUnstowable: modifier.unstowable,
coinType: paramModifier.coins,
coinAmount: paramModifier.coins ? parseFloat(graphic.get('bar3_value')) : 0,
baseWeight: parseFloat(paramModifier.weight || 0),
warmthValue: parseInt(paramModifier.warmth || 0),
grantsHospitality: modifier.hosp,
amount: parseInt(_.reduceRight(amountRegEx.exec(graphic.get('name')), function (memo, val) { return memo || val; }, null) || 1),
hasInventory: modifier.storage,
inventories: [],
hasConstantWeight: modifier.constantweight
};
items.push(obj);
break;
}
// show the name if the namefield is set (and longer than a char)
options.showname = options.showname && _.isString(graphic.get('name')) && graphic.get('name').length > 1;
graphic.set(options);
});
var stackMarkers = filterObjs(function(obj) {
if(obj.get("_type") == "text" && obj.get("font_family") == "Candal" && obj.get("font_size") == 26 && obj.get("_pageid") == state.SplenectomY.inventory.pageID) return true;
else return false;
});
_.each(stackMarkers, function(marker)
{
marker.remove();
});
state.SplenectomY.inventory.message = "/w "+state.SplenectomY.inventory.currentCaller+" &{template:default} {{name=Your Inventory}}{{Worn";
/***
* Go thru all items (tokens that represent bare items that one can carry) and calculate their weights / containers
**/
_.each(items, function (item)
{
// update coin weight and volume based on amount
if (item.properties.coinType)
{
item.graphic.set('bar1_value', Math.ceil(RoubysInventoryManager.coin.weight * item.properties.coinAmount * 10000) / 10000);
item.graphic.set('bar2_value', Math.ceil(RoubysInventoryManager.coin.volume * item.properties.coinAmount * 10000) / 10000);
item.graphic.set('name', Localization['coin_' + item.properties.coinType] + ': ' + item.properties.coinAmount);
}
// if the item is an empty liquid container, reset its tint and name
if (item.properties.isLiquidContainer)
{
if (item.graphic.get('bar1_value') == 0)
{
// pull the default name from the character it represents (if any)
var character = getObj('character', item.graphic.get('represents'));
var defaultName = character ? character.get('name') : '';
item.graphic.set('name', defaultName);
item.graphic.set('tint_color', 'transparent');
}
}
// check if this item is equipped
_.each(equipmentSlots, function (slot)
{
if (itemIsInContainer(item.graphic, slot.graphic))
{
item.properties.equippedAt = slot;
_.each(characterAreas, function (area)
{
if(area.graphic.get("bar3_value") == state.SplenectomY.inventory.currentPlayerNum && itemIsInContainer(item.graphic, area.graphic))
{
var name = item.graphic.get("name");
state.SplenectomY.inventory.message += "\n<code>"+name+"</code>";
}
});
// if this item has an inventory, add its id to the equipped list, otherwise check if a storage has to be created
if (item.properties.hasInventory)
{
//item.properties.hasConstantWeight
equipped.push(item.graphic.get('_id'));
}
else
{
var notes = item.graphic.get('gmnotes');
_.each(RoubysInventoryManager.storageItems, function (storageKind)
{
for (var i = 0; i < 10 && notes.indexOf(storageKind.name) !== -1; ++i)
{
RoubysInventoryManager.objsToBeCreated.push({
pageId: item.graphic.get('_pageid'),
name: Localization['storage_' + storageKind.name],
left: item.graphic.get('left'),
top: item.graphic.get('top'),
id: item.graphic.get('_id'),
weight: storageKind.maxWeight,
volume: storageKind.maxVolume
});
notes = notes.replace(storageKind.name, 'storage');
}
});
item.graphic.set('gmnotes', notes);
}
}
});
});
var itemLocations = [];
state.SplenectomY.inventory.message += "}}";
_.each(inventories, function (inventory)
{
inventory.containedItems = [];
inventory.containedWeight = 0;
inventory.containedVolume = 0;
inventory.containedWeightMax = parseFloat(inventory.graphic.get('bar1_max'));
inventory.containedVolumeMax = parseFloat(inventory.graphic.get('bar2_max'));
var messageItems = [],
messageInvUsed = false;
_.each(characterAreas, function (area)
{
if(area.graphic.get("bar3_value") == state.SplenectomY.inventory.currentPlayerNum && itemIsInContainer(inventory.graphic, area.graphic))
{
var name = inventory.graphic.get("name");
state.SplenectomY.inventory.message += "{{"+name;
messageInvUsed = true;
}
});
_.each(items, function (item)
{
var itemIsPresent = false;
if (itemIsInContainer(item.graphic, inventory.graphic))
{
if (inventory.type === 'inv' && item.properties.isUnstowable)
{
item.graphic.set('top', inventory.graphic.get('top') + inventory.graphic.get('height') / 2 + 35);
}
else
{
inventory.containedItems.push(item);
inventory.containedWeight += parseFloat(item.graphic.get('bar1_value') * item.properties.amount) + item.properties.baseWeight;
inventory.containedVolume += parseFloat(item.graphic.get('bar2_value') * item.properties.amount);
}
var locationIsStored = false;
_.each(itemLocations, function (location)
{
if(location.coords_top == item.graphic.get("top") && location.coords_left == item.graphic.get("left"))
{
location.items++;
locationIsStored = true;
}
});
if(!locationIsStored)
{
var location = {
coords_top: item.graphic.get("top"),
coords_left: item.graphic.get("left"),
items: 1
};
itemLocations.push(location);
}
_.each(characterAreas, function (area)
{
if(area.graphic.get("bar3_value") == state.SplenectomY.inventory.currentPlayerNum && itemIsInContainer(item.graphic, area.graphic))
{
_.each(messageItems, function(msgItem)
{
if(msgItem.graphic.get("name") == item.graphic.get("name"))
{
msgItem.amount += 1;
itemIsPresent = true;
}
});
if(!itemIsPresent)
{
messageItems.push(item);
_.each(messageItems, function(msgItem)
{
if(msgItem.graphic.get("name") == item.graphic.get("name"))
{
msgItem.amount = 1;
}
});
}
}
});
}
else if (inventory.type === 'inv')
{
if (item.properties.hasInventory && item.graphic.get('_id') === inventory.properties.representId)
{
item.properties.inventories.push(inventory);
}
}
});
_.each(messageItems, function(msgItem)
{
var name = msgItem.graphic.get("name"),
amount = msgItem.amount;
if(amount > 1)
{
state.SplenectomY.inventory.message += "\n<code>"+name+" x"+amount+"</code>";
}
else
{
state.SplenectomY.inventory.message += "\n<code>"+name+"</code>";
}
});
if(messageInvUsed) {state.SplenectomY.inventory.message += "}}"};
// set weight and volume for backpack inventories and such
if (inventory.type === 'inv')
{
var isEquipped = equipped.indexOf(inventory.properties.representId) !== -1,
isFull = (inventory.containedWeight > inventory.containedWeightMax) ||
(inventory.containedVolume > inventory.containedVolumeMax);
inventory.graphic.set('bar1_value', Math.ceil(inventory.containedWeight * 100) / 100);
inventory.graphic.set('bar2_value', Math.ceil(inventory.containedVolume * 100) / 100);
inventory.graphic.set({ status_dead: isFull });
// hide the inventory, if its representative is not equipped
if (!isEquipped)
{
inventory.graphic.set('layer', 'gmlayer');
}
else
{
inventory.graphic.set('layer', 'objects');
}
}
else if (inventory.type === 'drop')
{
inventory.graphic.set('layer', 'map');
}
_.each(inventory.containedItems, function (item)
{
// copy layer from containing inventory
item.graphic.set('layer', inventory.graphic.get('layer'));
});
});
_.each(itemLocations, function (location)
{
if(location.items > 1)
{
var marker = createObj("text", {
top: location.coords_top + 25,
left: location.coords_left + 40,
text: "x" + location.items,
font_size: 26,
color: "rgb(255, 0, 0)",
font_family: "Candal",
_pageid: state.SplenectomY.inventory.pageID,
layer: "objects"
});
}
});
_.each(characterAreas, function (charArea)
{
charArea.containedWeight = 0;
charArea.containedVolume = 0;
charArea.containedCoinValue = 0;
charArea.containedWarmth = 0;
_.each(items, function (item)
{
if (itemIsInContainer(item.graphic, charArea.graphic) && item.graphic.get('layer') === 'objects')
{
var controller = RoubysInventoryManager.playerList[parseInt(charArea.graphic.get('bar3_value')) - 1] || '';
item.graphic.set('controlledby', controller);
if (!item.properties.containedIn && item.properties.equippedAt)
{
charArea.containedWeight += parseFloat(item.graphic.get('bar1_value') * item.properties.amount) + item.properties.baseWeight;
charArea.containedVolume += parseFloat(item.graphic.get('bar2_value') * item.properties.amount);
charArea.containedWarmth += item.properties.warmthValue;
if (item.properties.coinType)
charArea.containedCoinValue += RoubysInventoryManager.coin['value_' + item.properties.coinType] * item.properties.coinAmount;
if (item.properties.hasInventory && !item.properties.hasConstantWeight)
{
_.each(item.properties.inventories, function (inventory)
{
_.each(inventory.containedItems, function (containerItem)
{
containerItem.graphic.set('controlledby', controller);
charArea.containedWeight += parseFloat(containerItem.graphic.get('bar1_value') * containerItem.properties.amount) + containerItem.properties.baseWeight;
charArea.containedVolume += parseFloat(containerItem.graphic.get('bar2_value') * containerItem.properties.amount);
if (containerItem.properties.coinType)
charArea.containedCoinValue += RoubysInventoryManager.coin['value_' + containerItem.properties.coinType] * containerItem.properties.coinAmount;
})
});
}
}
}
});
_.each(statuses, function (status)
{
var player = charArea.graphic.get('bar3_value'),
totalWeight = Math.ceil(charArea.containedWeight * 100) / 100;
if (status.graphic.get('bar3_value') === player)
switch (status.type)
{
case 'status':
var totalVolume = Math.ceil(charArea.containedVolume * 100) / 100;
if (!state.SplenectomY.players[player].wgt_max) {var maxWeight = parseFloat(status.graphic.get('bar1_max'))}
else {var maxWeight = state.SplenectomY.players[player].wgt_max};
maxVolume = parseFloat(status.graphic.get('bar2_max')),
isFull = totalWeight > maxWeight && maxWeight > 0 ||
totalVolume > maxVolume && maxVolume > 0;
status.graphic.set('name', Localization.weight + ': ' + totalWeight + ' | ' + Localization.profile + ': ' + totalVolume);
status.graphic.set('bar1_value', Math.ceil(totalWeight * 100) / 100);
if (!state.SplenectomY.players[player].wgt_max) {}else{status.graphic.set('bar1_max',state.SplenectomY.players[player].wgt_max)};
status.graphic.set('bar2_value', Math.ceil(totalVolume * 100) / 100);
status.graphic.set({ status_dead: isFull });
if(status.graphic.get('bar3_value') == state.SplenectomY.inventory.currentPlayerNum)
{
state.SplenectomY.inventory.message += "\n You are carrying <code>" + totalWeight + " lbs</code> and <code>" + totalVolume + " ft<sup>3</sup></code> of gear.";
state.SplenectomY.inventory.message += "\n You have a <code>-" + Math.floor(totalVolume) + "</code> penalty to movement checks (Stealth, Acrobatics, Swimming, Climbing, etc.)";
}
break;
case 'status2':
status.graphic.set('name', 'Warmth' + ': ' + Math.floor(charArea.containedWarmth));
break;
case 'status3':
status.graphic.set('name', Localization.coins + ': ' + Math.floor(charArea.containedCoinValue));
break;
}
if(status.graphic.get("gmnotes").indexOf("character") != -1 && itemIsInContainer(status.graphic, charArea.graphic)) {
state.SplenectomY.players[player].sheetID = status.graphic.get("represents");
var results = filterObjs(function(obj) {
if(obj.get("type") == "attribute" && obj.get("_characterid") == state.SplenectomY.players[player].sheetID)
{
if(obj.get("name") == "carrying_capacity_mod") {state.SplenectomY.players[player].carryMod = obj.get("current");}
if(obj.get("name") == "strength_base") {state.SplenectomY.players[player].str = obj.get("current");}
if(obj.get("name") == "weighttotal") {obj.set("current",totalWeight)}
}
return false;
});
if(state.SplenectomY.players[player].carryMod == "0" || undefined || null) {state.SplenectomY.players[player].carryMod = 1};
if(!state.SplenectomY.players[player].carryMod) {state.SplenectomY.players[player].carryMod = 1};
if(!state.SplenectomY.players[player].str) {state.SplenectomY.players[player].str = 10};
state.SplenectomY.players[player].wgt_max = state.SplenectomY.players[player].str * 15 * state.SplenectomY.players[player].carryMod;
};
});
});
}
};
on('ready', function ()
{
var results = filterObjs(function(obj) {
if(obj.get("_type") == "page" && obj.get("name") == "Inventory") {
state.SplenectomY.inventory.pageID = obj.get("_id");
log("Found the Inventory Page: " + obj.get("_id"));
};
return false;
});
if (RoubysInventoryManager.logPlayerIds)
{
log('************* PLAYER IDs *************');
filterObjs(function (obj)
{
if (obj.get('type') == 'player')
log(obj.get('_displayname') + ': ' + obj.get('_id'));
return false;
});
log('**************************************');
}
on('change:graphic:lastmove', function (obj)
{
if(obj.get("_pageid") == state.SplenectomY.inventory.pageID)
{
RoubysInventoryManager.run();
}
});
on("chat:message", function(msg) {
if(msg.type == "api" && msg.content.indexOf("!inv") !== -1)
{
state.SplenectomY.inventory.currentCaller = msg.who;
state.SplenectomY.inventory.message = {};
state.SplenectomY.inventory.currentCallerid = msg.playerid;
_.each(state.SplenectomY.players, function (player, key)
{
if(player.id == state.SplenectomY.inventory.currentCallerid)
{
state.SplenectomY.inventory.currentPlayerNum = key;
}
});
RoubysInventoryManager.run();
sendChat("", state.SplenectomY.inventory.message);
}
});
on('add:graphic', function (obj)
{
if(obj.get("_pageid") == state.SplenectomY.inventory.pageID)
{
obj.set('width', 70);
obj.set('height', 70);
}
});
});
@Rouby
Copy link

Rouby commented Jan 28, 2015

I have made a fix to your script, since it did not update the controlledBy for the last player in the list. See my fork for the changes.

@Rouby
Copy link

Rouby commented Jan 28, 2015

Actually I have just made another correction. The coin weights and volumes did not get rounded correctly. The parentheses were a little bit off (something along the lines of round(X * 1000 / 1000)). I fixed that and increased the rounding factor.

https://gist.github.com/Rouby/8e7953cbbf6c6094f915

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment