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

can't find a 5e variant encumbrance script. anyone want to make one?

The 5e Variant Encumbrance does of course show the player a red warning message at the very bottom of the inventory, but it does not make any adjustments to their character sheet or provide them any in-chat messages. Could it not change their speed, maybe put their speed in red, put a marker on the token, throw out a chat message, or something? Many players seem to never look down at the bottom of their inventory, and as a DM it is not my standard practice to routinely check their encumbrance. Perhaps automating a lot of it would be helpful. The only script I could find that claimed to do this says that it only works with the Shaped Sheet. I'm using OGL
1636621048

Edited 1636970379
Oosh
Sheet Author
API Scripter
Here's a rough script - it just sends a public message with encumbrance level, changes the character's speed and whispers the character their new speed. Note that this stores the base movement of the character the first time it runs for a character. If that speed is not their normal, full speed, it'll be wrong permanently. There's nothing else on the sheet I'm aware of that stores a movement speed, so there's no way to calculate 'normal' if the sheet's speed is wrong. Anyway, it's very basic and rough, and uses the ghastly {{desc}} template. See if it does vaguely what you want and I can add some bells and whistles like command line options, resetting wrongly set base speed, applying token markers etc. Also, if you wanted to give an example of the kind of output you'd like, I'll change the template. I also think Roll20 has the encumbrance level a little wrong. It goes from heavily encumbered (10x Str) to Immobile (15x Str). My reading of the rules is that, at 15x Str, you still have the option of dragging your belongings, as you can drag double your max carry weight, and this is at a speed of 5ft. This obviously relies on not having too many items equipped, and having the heavy stufff in a container that's somewhat draggable. However I couldn't find a definitive RAW on that one. Anyway, here's some computer speak, updated as per below:  Script moved to post below - SNIP -
Hey, that's amazing! It seems like magic to me. Thank you so much. I tested it on a couple of PCs and it worked great. As far as the formatting of the output, honestly I don't have a strong opinion on that, what you did looks fine to me. So much better than when chat just spits out naked text in a blue field. So if someone gets a wrong score stored right now, will deleting and then re-installing the script reset everyone? I hate to ask for any bells or whistles, because it's already pretty awesome that you did this. If you wanted to keep tweaking it... If it is possible to turn their speed score RED while encumbered, that's right at the top of the sheet and they'd see it more easily, reminding them to drop something when they can. I was going to put a marker on their token when encumbered. I am currently thinking of using the Tread icon (the boot, you know, for walking) and I might use the Tread with a "2" in it when they're Heavily encumbered. That's not a big deal for me to do manually, but if you were tweaking anyway and wanted to do something like that, that might be cool. I think your idea to reset a wrong score is an excellent idea, since a lot of PC's base speed will change as they level up. As far as command lines, I'm not exactly a power user (yet) so I don't know what to suggest. I think both your ideas sound really good Reset a "wrong" base speed. Does that mean manually selecting a token and then entering a command line, or does it mean fixing it on the sheet and then just typing in a -reset command? Does it reset everyone at once or only someone identified/selected? I have no idea what the best practice would be there, but I can easily see a monk for example needing to be updated from time to time as they level up. Maybe make the token marker optional, as well as which marker should be applied, since I know some people would want granular control over that kind of thing. Again, thank you so much. My regular group is using the Variant rule starting TODAY in fact, so this could not have come at a better time. We're all still learning and making adjustments to our expectations, so this will help us a LOT. 
I was just thinking... I suppose that if someone is encumbered and we "simply" put a marker on their token, that marker is not going to transfer over to other pages is it? Or does the script constantly check and would be able to update their tokens when we change pages or drag them from the Journal onto the field?
1636759268
Oosh
Sheet Author
API Scripter
Everything you've said should be possible, with the exception of the character sheet speed display - the Styles on the character sheet are baked in. You can change these on your own browser using Stylus (or other CSS hacks), but the only way to change the sheet itself is to use a custom one. Reset a "wrong" base speed. Does that mean manually selecting a token and then entering a command line, or does it mean fixing it on the sheet and then just typing in a -reset command? Does it reset everyone at once or only someone identified/selected? I have no idea what the best practice would be there, but I can easily see a monk for example needing to be updated from time to time as they level up. I'll probably keep it super simple. If you need to reset a character's base speed, enter the correct speed into the character sheet, and enter something like !vEncumber --setbase. That'll reset the @{basespeed} to the current number on the sheet. Joel said: I was just thinking... I suppose that if someone is encumbered and we "simply" put a marker on their token, that marker is not going to transfer over to other pages is it? Or does the script constantly check and would be able to update their tokens when we change pages or drag them from the Journal onto the field? That's correct, tokens are unique across Pages. I can add a handler for when the player ribbon changes though, to update all the tokens on the page.
You could use the TokenSync script to update the conditions across all pages. TokenSync
well that all sounds great. Yeah, too bad about the red text. I hoped it was like how the ability scores can turn blue. Oh well, not at all the end of the world. We had our game today with no issues with the script. I'll watch for updates and be happy to install the next version. Thanks again for all this.
1636771621

Edited 1636771682
Joel
Pro
Ken said: You could use the TokenSync script to update the conditions across all pages. TokenSync thanks for the info. I had not seen that one yet. Sounds like the api refreshing when I move the player ribbon should do the trick,  but I'll check this out as well
1636805139

Edited 1637015689
Oosh
Sheet Author
API Scripter
Right, I successfully procrastinated and avoided doing any of the work I was supposed to do. Hooray! I also updated the script above. It now puts a marker on all the tokens, default is 'tread' / 'tread@2' / 'tread@3' for the levels of encumbrance. It'll also update a new page's tokens on player ribbon move (not DM view move). It does not update NPCs, as they don't have the right speed attribute or an inventory. The command line goes: !varEnc --option1 arguments1 --option2 arguments2 Available options: --chat [public | whisper | off]       toggles the main status update in the template from public posting / whispered (to the affected character) / off.  Default is public --chat speed [public | whisper | off]            toggles the new speed update in chat from public post / whispered (to the affected character) / off. Default is whisper --chat template=" roll20 template macro "     change the template output for the main status update. Replacer String for the main message is %msg%. Default is &{template:desc}{{desc=%msg%}} --rule [none | light | heavy | immobile]=" Your custom rule here "         edit the rules printed to the chat status update for each level of encumbrance. Replacer string for character's name is %name%, e.g. " %name%'s bag is full!" --marker [on | off]     turn the auto-marker on or off. Default is on --marker page [on | off]     turn the auto-marker update on player page move on or off. Default is on -- marker [none | light | heavy | immobile]=" markerName "     change the marker for the supplied level of encumbrance. Optionally supply an @X on the end to print the number X on the icon. Fails if the supplied marker name is not found in your Campaign. -- players [ on | off ]     Toggle allowing players to use --setspeed and --report. All other commands are GM-only, no matter what this setting is. Default is on -- setspeed [ X ]     change the basespeed of the selected token's character sheet. Supply a number to change it to that number, or just "--setspeed" to set basespeed to the currently displayed @{speed} on the character sheet. @{basespeed} is not displayed anywhere on the sheet, but can be called to chat with @{charname|basespeed}. --report [ w | whisper ]     send an inventory report to chat. If an argument is supplied and contains 'w', the report will be whispered to the character. Reporting send info on equipped/unequipped weights, coin weight, "weightless" total (for items with a non-integer in the weight field) and also calculates container weights if you're using "=== Backpack ===" style markers to organise your inventory. Any item with two equals signs in a row "==" will be assumed to be a container, all text in the field that isn't an equals sign is assumed to be the container name. All items below are assumed to belong to that container until another "==" is hit. Example below. Examples: Change to the default template, and modify the text for the 'none' encumbrance rules: !varEnc --chat template="&{template:default}{{name=Alternate View}}{{%msg%}}" --rule none="The sudden release of weight sends %name% hurtling into the Ethereal." Change the modified movement value to public chat, change the selected token's default move speed to 50 feet !varEnc --setspeed 50 --chat speed public Generate a report on a character's inventory. The example shows the container calculation done on a sheet containing "=== Belt ===" style markers, and the bag weight report that's generated if any of these containers are found. The script also calculates bag weights for any "weightless" items; items where the weight has a non-integer at the start of the weight field so the sheet doesn't calculate it. !varEnc --report varEnc API script /* globals */ const varEnc = (() => { // eslint-disable-line no-unused-vars const ver = { M: 0, m: 3, p: 2, getString: function(){ return `${this.M}.${this.m}.${this.p}` }, getFloat: function(){ return parseFloat(`${ver.M}${ver.m}.${ver.p}`) }, }; // Constants, config & data const defaultConfig = { ver: ver.getFloat(), tokenMarker: { apply: 1, watchPage: 1, none: '', light: 'tread', heavy: 'tread@2', immobile: 'tread@3' }, chatConfig: { showStatus: 2, showSpeed: 1, template: `&{template:desc}{{desc=%msg%}}`, rules: { none: `**%name%** is no longer encumbered.`, light: `**%name%** is lightly encumbered. Their speed drops by 10ft.`, heavy: `**%name%** is heavily encumbered. Their speed drops by 20ft, and they have disadvantage on ability checks, attack rolls, and saving throws that use Strength, Dexterity, or Constitution.`, immobile: `**%name%** is immobilised. Their speed is now 0, and they have disadvantage on ability checks, attack rolls, and saving throws that use Strength, Dexterity, or Constitution.`, }, }, chatName: `varEnc API`, playerFunctions: 1, } // Regex const rx = { cliTrue: /\b(on|1|true)\b/i, cliFalse: /\b(off|0|false)\b/i, getQuoteString: /"([^"]+?)"/, encLevels: /\b(none|light|heavy|immobile)\b/i, chatTypes: /\b(public|private|whisper|none|off)\b/i } // State config const checkInstall = () => { // Ver control - no install / pre-versioning install / versioning if (!Object.keys(state).includes('varEnc') || !state.varEnc.chatConfig || !state.varEnc.chatConfig.rules) state.varEnc = defaultConfig; if (!state.varEnc.ver) { Object.assign(state.varEnc, {ver: defaultConfig.ver, playerFunctions: defaultConfig.playerFunctions}); log(`Updating ${scriptName}...`); } let installedVer = state.varEnc.ver; if (installedVer < ver.getFloat()) { // Update Script } // Update variables chatName = state.varEnc.chatName; H.updateChatConfig(); let markerSet = Campaign().get('token_markers')||`[]`; currentPage = Campaign().get('playerpageid'); availableMarkers = JSON.parse(markerSet).map(m => m.name); log(`{ ${scriptName} } v${ver.getString()} initialised`); } const scriptName = `varEnc API`; let chatName = `varEnc API`; let availableMarkers = []; const chatMsg = { template: '', none: '', light: '', heavy: '', immobile: '', create: function(encumbrance, charName='Character') { let enc = this[encumbrance] || `Encumbrance changed on %name%, but no rule for "${encumbrance}" was found.`; return `${this.template.replace(/%msg%/i, enc.replace(/%name%/g, charName))}`; } }; let currentPage; // Handle encumbrance changes const handleAttrChange = (ev, refreshOnly) => { if (!/encumberance/i.test(ev.get('name'))) return; let char = getObj('character', ev.get('characterid')); if (!char) return; let encumberLevel = ev.get('current'), charname = char.get('name'), speedAttr = findObjs({type: 'attribute', name: 'speed', characterid: char.id})[0], speed = speedAttr ? parseInt(speedAttr.get('current')) : null, baseSpeedAttr = findObjs({type: 'attribute', name: 'basespeed', characterid: char.id})[0], baseSpeed = baseSpeedAttr ? parseInt(baseSpeedAttr.get('current')) : null, newSpeed, chatTemplate; if (!baseSpeed && (speed == null || isNaN(speed))) return sendChat(chatName, `/w gm No valid movement speed found on ${charname} for encumberance change!`); if (!baseSpeed) { createObj('attribute', {name: 'basespeed', characterid: char.id, current: speed}); baseSpeed = speed; } newSpeed = /immob/i.test(encumberLevel) ? 0 : /heav/i.test(encumberLevel) ? Math.max(baseSpeed - 20, 0) : /enc/i.test(encumberLevel) ? Math.max(baseSpeed - 10, 0) : baseSpeed; encumberLevel = /immob/i.test(encumberLevel) ? 'immobile' : /heav/i.test(encumberLevel) ? 'heavy' : /enc/i.test(encumberLevel) ? 'light' : 'none'; chatTemplate = chatMsg.create(encumberLevel, charname); if (state.varEnc.tokenMarker.apply) H.handleMarker(char, encumberLevel); if (isNaN(newSpeed)) sendChat(chatName, `/w gm ${charname}'s new speed could not be calculated.`); else { speedAttr.set({current: `${newSpeed}`}); if (newSpeed !== speed) { if (state.varEnc.chatConfig.showSpeed === 2) sendChat(chatName, `${charname}'s movement speed has been changed to ${newSpeed}.`); if (state.varEnc.chatConfig.showSpeed === 1) sendChat(chatName, `/w "${charname}" Your movement speed has been changed to ${newSpeed}.`); } } if (state.varEnc.chatConfig.showStatus === 2 && !refreshOnly) sendChat(chatName, chatTemplate); else if (state.varEnc.chatConfig.showStatus === 1 && !refreshOnly) sendChat(chatName, `/w "${charname}" ${chatTemplate}`); } const handleInput = (msg) => { if ('api' === msg.type && /^!varenc\s/i.test(msg.content) ) { let gmFlag = playerIsGM(msg.playerid), playerFlag = state.varEnc.playerFunctions; if (msg.rolltemplate) sendChat('sdg', msg.rolltemplate); let cmdLine = msg.content.match(/^!varenc\s+(.*)/i)[1], cmds = cmdLine.split(/\s*--\s*/g), msgWho = `"${msg.who.replace(/\s\(gm\).*/i, '')}"`; cmds.shift(); cmds.forEach(cmd => { let opt = (cmd.match(/^(\w+)/)||[])[1], args = opt ? cmd.replace(opt, '') : ''; // GM only settings if (gmFlag) { // Token marker settings if (/^marker/i.test(opt)) { if (/=/i.test(args)) { let tier = (args.match(rx.encLevels)||[])[1], marker = (args.match(rx.getQuoteString)||[])[1], markerName = marker ? marker.replace(/@\d+/, '') : null; if (tier && marker) { if (availableMarkers.includes(markerName)) H.changeSetting(tier, marker, 'tokenMarker'); else sendChat(chatName, `/w ${msgWho} marker "${markerName}" not found in Campaign`); } } else if (/\bpage/.test(args)) { if (rx.cliFalse.test(args)) H.changeSetting('watchPage', 0, 'tokenMarker'); else if (rx.cliTrue.test(args)) H.changeSetting('watchPage', 1, 'tokenMarker'); } else { if (rx.cliFalse.test(args)) H.changeSetting('apply', 0, 'tokenMarker'); else if (rx.cliTrue.test(args)) H.changeSetting('apply', 1, 'tokenMarker'); } // Chat settings } else if (/^chat/i.test(opt)) { // Change template - check for missing &{template} parameter since Roll20 steals it. if (/template\s*=\s*/i.test(args)) { let newTemp = (args.match(rx.getQuoteString)||[])[1].trim(), parsedTemplate = msg.rolltemplate; newTemp = /^\s*\{template:/i.test(newTemp) ? `&${newTemp}` : parsedTemplate ? `&{template:${parsedTemplate}} ${newTemp}` : null; if (newTemp) H.changeSetting('template', newTemp, 'chatConfig'); } else { let chatType = (args.match(rx.chatTypes)||[])[1]; if (chatType) { let settingTarget = /speed/i.test(args) ? 'showSpeed' : 'showStatus'; chatType = /(none|off)/i.test(chatType) ? 0 : /(private|whisper)/i.test(chatType) ? 1 : 2; H.changeSetting(settingTarget, chatType, 'chatConfig'); } } // Encumbrance rule settings } else if (/^rule/i.test(opt)) { let tier = (args.match(rx.encLevels)||[])[1], newRule = (args.match(rx.getQuoteString)||[])[1]; if (tier && newRule) H.changeSetting(tier, newRule, 'chatConfig/rules'); // Reset character base speed } else if (/^player/i.test(opt)) { if (rx.cliFalse.test(args)) H.changeSetting('playerFunctions', 0); else if (rx.cliTrue.test(args)) H.changeSetting('playerFunctions', 1); } } // Players (if enabled) settings if (gmFlag || playerFlag) { // Change base speed if (/^(speed|setspeed)/i.test(opt)) { let char = H.getSelected(msg.selected), newSpeed = args ? args.replace(/\D/g, '') : null; if (!char) return; if (!newSpeed || isNaN(newSpeed)) { let currentSpeedAttr = findObjs({type:'attribute', name:'speed', characterid: char.id})[0], currentSpeed = currentSpeedAttr ? currentSpeedAttr.get('current') : null; newSpeed = parseInt(currentSpeed) || null; } if (!newSpeed) return sendChat(chatName, `/w ${msgWho} Couldn't find a valid speed for ${char.get('name')}`); let baseSpeedAttr = findObjs({type:'attribute', name:'basespeed', characterid:char.id})[0]; if (!baseSpeedAttr) createObj('attribute', {name:'basespeed', characterid:char.id, current: newSpeed}); else baseSpeedAttr.set({current: newSpeed}); sendChat(chatName, `/w ${msgWho} Base speed for ${char.get('name')} set to ${newSpeed}.`); let encumberAttr = findObjs({type:'attribute', characterid: char.id, name:'encumberance'}); if (encumberAttr) handleAttrChange(encumberAttr[0], true); // Send inventory report to chat for selected char } else if (/^report/i.test(opt)) { let char = H.getSelected(msg.selected), report = (char) ? iH.inventoryReport(char) : '', whisper = /w/i.test(args) ? `/w ${msgWho} ${report}` : ``; sendChat(chatName, `${whisper}${report}`); } } }); } } // Helpers const H = (() => { const changeSetting = (key, value, path='', msg) => { let target = `${path}`.split(/\//g).filter(v=>v).reduce((a,v) => { if (a && a[v]) return a[v]; }, state.varEnc); Object.assign(target, {[key]: value}); if (msg !== null) { msg = msg||`State setting "${path}/${key}" changed to "${value}"`; sendChat(chatName, `/w gm ${msg}`); } if (/chat/i.test(`${path}`)) H.updateChatConfig(); } const getSelected = (selection) => { if (!selection || selection.length < 1) return null; let id = selection[0]._id, tok = id ? getObj('graphic', id) : null, reps = tok ? tok.get('represents') : null; return reps ? getObj('character', reps) : null; } const updateChatConfig = () => { Object.assign(chatMsg, state.varEnc.chatConfig.rules, {template: state.varEnc.chatConfig.template}); chatName = state.varEnc.chatName; } const handleMarker = (char, encumberLevel, pageId) => { let active = pageId || Campaign().get('playerpageid') || null, toks = active ? findObjs({type:'graphic', /*_pageid: active,*/ represents: char.id}) : null, allMarkers = ['none','light','heavy','immobile'].map(tier=> { if (tier !== encumberLevel) return state.varEnc.tokenMarker[tier]; } ), newMarker = allMarkers ? state.varEnc.tokenMarker[encumberLevel] : null; if (newMarker != null) { toks.forEach(t => { let mrk = t.get('statusmarkers'), mrkArray = mrk != null ? mrk.split(/\s*,\s*/g) : null; if (mrkArray != null) { mrkArray.forEach((um,i) => { if (allMarkers.includes(um)) mrkArray[i] = null; }); if (!mrkArray.includes(newMarker)) mrkArray.push(newMarker); let newString = mrkArray.filter(v=>v).join(','); t.set('statusmarkers', newString); } }); } else log(`varEnc API: error marking tokens, tokens found: ${toks.length}, marker: ${newMarker}`); } const handlePageMove = () => { currentPage = Campaign().get('playerpageid'); let currentToks = findObjs({type: 'graphic', _pageid: currentPage}), tokIds = currentToks ? currentToks.filter(t => /-[A-Za-z0-9_-]{19}/.test(t.get('represents'))).map(t=>t.get('represents')) : [], npcAttrs = findObjs({type: 'attribute', name: 'npc'}), pcIds = npcAttrs ? npcAttrs.filter(a => a.get('current') != 1).map(a => a.get('characterid')) : []; let activeChars = pcIds.filter(id => tokIds.includes(id)); activeChars.forEach(cid => { let char = getObj('character', cid), currentEncumber = findObjs({type: 'attribute', name: 'encumberance', characterid: cid})[0], encumberLevel = currentEncumber ? currentEncumber.get('current') : null; if (encumberLevel) { encumberLevel = /immob/i.test(encumberLevel) ? 'immobile' : /heav/i.test(encumberLevel) ? 'heavy' : /enc/i.test(encumberLevel) ? 'light' : 'none'; H.handleMarker(char, encumberLevel); } }); } const getOrderedSecIds = (repSecAttrs) => { let reporderAttr = repSecAttrs.find(a => /^_reporder/i.test(a.get('name'))), repOrder = reporderAttr ? reporderAttr.get('current').split(/,/g) : [], sectionIds = []; repSecAttrs.forEach(a => { let rowId = (a.get('name').match(/_(-[A-Za-z0-9-]{19})_/)||[])[1]; if (rowId && !sectionIds.includes(rowId) && !repOrder.includes(rowId)) sectionIds.push(rowId); }); return repOrder.concat(sectionIds); } return { changeSetting, getSelected, updateChatConfig, handleMarker, handlePageMove, getOrderedSecIds } })(); // Inventory Helpers const iH = (() => { const createInventoryObject = (attrArray, idOrder) => { const getAttrVal = (suffix, id, max=false) => { let a = attrArray.find(a => a.get('name').indexOf(`${id}_${suffix}`) > -1); return a ? max ? a.get('max') : a.get('current') : null; } let output = []; idOrder.forEach(id => { let data = { name: getAttrVal('itemname', id), rowId: id, quantity: getAttrVal('itemcount', id) === null ? 1 : getAttrVal('itemcount', id) || 0, equipped: getAttrVal('equipped', id) == 0 ? false : true, weight: `${getAttrVal('itemweight', id)}`.replace(/[^\d.]/g, ''), weightless: /^\s*[A-Za-z]/.test(`${getAttrVal('itemweight', id)}`) ? true : false, }; data.totalWeight = parseInt(data.quantity) * parseFloat(data.weight); output.push(data); }); return output; } const getBagWeights = (inventoryObject) => { let bags = {unpacked: 0, weightless: {}}, currentBag = ''; inventoryObject.forEach(item => { if (/==/.test(item.name)) { let bagName = item.name.replace(/=/g, '').trim(); currentBag = bagName; if (!bags[currentBag]) bags[currentBag] = 0; } else if (item.totalWeight) { let targetBag = currentBag||'unpacked' bags[targetBag] += item.totalWeight||0; if (item.weightless) bags.weightless[targetBag] = 1; } }); return bags; } const getOtherWeights = (inventoryObject) => { let weight = {equipped: 0, unequipped: 0, weightless: {equipped: 0, unequipped: 0, total: 0}}; inventoryObject.forEach(item => { let target = item.equipped ? 'equipped' : 'unequipped'; weight[target] += parseFloat(item.totalWeight)||0; if (item.weightless) weight.weightless[target] += item.totalWeight||0; }); weight.weightless.total = weight.weightless.equipped + weight.weightless.unequipped; return weight; } const inventoryReport = (char) => { let chatTemplate = `&{template:default}{{name=${char.get('name')}'s Inventory Report}}`; let attrs = findObjs({type: 'attribute', characterid: char.id}).filter(a=>/repeating_inventory/i.test(a.get('name'))); if (attrs.length) { let invIds = H.getOrderedSecIds(attrs), inventoryData = createInventoryObject(attrs, invIds); if (inventoryData.length) { let weightTotal = findObjs({type:'attribute', characterid: char.id, name:'weighttotal'})[0], str = findObjs({type:'attribute', characterid: char.id, name:'strength'})[0], coins = findObjs({type: 'attribute', characterid: char.id}).filter(a => /^(cp|sp|ep|gp|pp)$/i.test(a.get('name'))); let coinWeight = coins.reduce((a,v) => a += (parseInt(v.get('current'))||0)*0.02, 0); let report = { total: weightTotal ? parseFloat(weightTotal.get('current')) : null, str: str ? parseInt(str.get('current')) : null, summary: getOtherWeights(inventoryData), bags: getBagWeights(inventoryData), cash: coinWeight.toFixed(2), }; let percentage = (report.total && report.str) ? (report.total/(report.str*15))*100 : null, colour = percentage >= 100 ? 'red' : percentage > 66 ? 'orange' : percentage > 33 ? '#e1da00' : 'green', progressBar = percentage ? `<div style="position:relative; width:100%; height: 1.5em;border: 1px solid black;" class="prog-container"> <div style="position:absolute; top:0%; width:${percentage}%; background-color:${colour}; height: 1.5em;" class="prog-bar"></div> <div style="position:absolute; top:0%; width:33%; right:33%; height:100%;border-left:2px solid black;border-right: 2px solid black;" class="prog-markers"></div> </div>` : '', chatTotal = `{{Total weight=${report.total} / ${report.str*15}pds.}}{{Level=${progressBar}}}`, chatEquipped = report.summary.equipped ? `{{Equipped=${report.summary.equipped}pds.${report.summary.weightless.equipped > 0 ? '*' : ''}}}{{Unequipped=${report.summary.unequipped}pds.${report.summary.weightless.unequipped > 0 ? '*' : ''}}}` : '', chatCoins = report.cash ? `{{Coins=${report.cash}pds.}}` : '', chatWeightless = report.summary.weightless.total ? `{{Weightless=${report.summary.weightless.total}pds.}}` : '', chatBagTitle = `{{--  Containers=**--**}}`, chatBags = Object.keys(report.bags).length > 1 ? Object.entries(report.bags).map(bag => { if (!/weightless/i.test(bag[0])) return `{{${bag[0]}=${bag[1]}pds.${report.bags.weightless[bag[0]] ? '*' : ''}}}`; }) : '', chatWeightlessHint = (Object.keys(report.bags.weightless).length || report.summary.weightless.total > 0) ? `{{=* denotes a total which includes weightless items}}` : ''; return `${chatTemplate}${chatTotal}${chatEquipped}${chatCoins}${chatWeightless}${chatBags ? chatBagTitle : ''}${chatBags}${chatWeightlessHint}`; } } } return { inventoryReport } })(); const init = (() => { //eslint-disable-line no-unused-vars on('ready', () => { checkInstall(); on('chat:message', (msg) => handleInput(msg)); on('change:attribute', (ev) => handleAttrChange(ev)); on('change:campaign', (ev) => { if (ev.get('playerpageid') !== currentPage && state.varEnc.tokenMarker.watchPage) H.handlePageMove(); }); }); })(); })();
Holy cow. Congrats on the procrastination. I totally feel that. What you wrote looks great, I will get a chance to mess with it later today I hope. Thanks again, for the hundredth time, I'm so impressed by how quickly and neatly you pulled this off. Big ups.  
There seems to be a problem with the new version. Heavily Encumbered drops speed by 20ft, Immobile puts it to zero, but Encumbered doesn't change speed from base speed.  Previous version worked fine.
1636836655

Edited 1636836753
Joel
Pro
Testing the newest version.  Seems to correctly identify how encumbered they are (carry weight vs STR). Like Anthony, I seemed to have some trouble with "lightly" encumbered as it did not change the speed. Heavily encumbered and Immobile did change the speed as expected. It marked the token with the correct default Tread (and x2, x3) as expected. On page move, I am not sure that's working. Granted I do not have a player logged in right now, but as DM I moved the ribbon to a new page with the tokens already on it, Then I moved my own DM view to that same page. It did not update the token (which I half-expected but wasn't sure). So pressed the button to log in as a player (instead of as DM) and it still did not update the token. I opened the token's character sheet, unequipped an item and then re-equipped it and then it did update the token. So it didn't seem to carry over from page to page even if I moved the ribbon. (I suppose it's possible it only works if a player who is NOT the DM is logged on and moving to another page with the ribbon? IDK.) Then I typed !varEnc --marker page on (just in case it was not actually set to On as default) and it said it set it to "1", I repeated the above steps onto another page, still no change. I hate to say it but there is some weirdness about --setspeed. It seems to accept the new speed but does not actually update the character sheet. By unequipping and re-equipping some items, I can get it to still do the math and eventually it correctly updates the speed field. But when you first enter a new --setspeed, it seems like it understands behind the scenes what the new speed is, but it does not update the text of the character sheet. I hope that is helpful.
1636848848
Oosh
Sheet Author
API Scripter
Whoops! Refactored a couple of things and didn't really test afterwards. I've updated the post above with updated code.     - Lightly encumbered speed should be applied correctly now.     - I didn't actually have --setspeed doing anything at all to the @{speed} attribute, so the script was working as intended in that regard. I didn't want to assume that the character's current @{speed} was necessarily their base speed. I've updated the script so that updating the @{basespeed} with the --setspeed option will now force a refresh of the main function, and recalculate speed based on current encumbrance.     - The script only updates tokens on a "player ribbon" move - the drag & drop marker on the Page list. DM change of page and individual change of page won't currently trigger anything, and it doesn't matter if any players are logged in, since they can't trigger a ribbon move anyway. I figured this way the DM can move page, pull out fresh tokens for the map if required, then when you're ready & drag the player ribbon to the new page the tokens will be updated. I've removed the 'active page' filter, so tokens will be updated across the whole Campaign. The script only affected PC tokens, and only under specific circumstances, it really wasn't necessary to limit the token updating anyway. Let me know if you find anything else!
I think you got it. Not only does everything seem to work, the tokens do update across pages even without me dragging the player ribbon. If I simply change which page I am viewing as DM, the token has already updated itself to its latest encumbrance. (To be clear, back when it was not working correctly, I was dragging the player ribbon to my DM view, but it didn't work... but it seems to work perfectly now.) Thanks again, this is super! I don't know what the process is to tell Roll20 "Why don't you make this a one-click install?" since their OGL sheet doesn't do this very reasonable thing, but I would think this will be very helpful to anyone using the variant encumbrance. Much appreciate it.
1636970576

Edited 1636970996
Oosh
Sheet Author
API Scripter
Turns out I had some more procrastinating that needed doing to day. It was the kind of procrastination that just can't be put off until tomorrow. So I added a couple of things to the script. The post above is updated with the new commands and new script. --player [on | off]     allow players to use --setspeed and --report --report                   send an inventory report to chat for the selected character.                                Example above in the rambling mess an exceptionally drunk and generous person might call "documentation". The reporting function is superfluous, dumb, a waste of time, and also exactly the kind of thing that really needed doing today. It might briefly entertain a player during a few seconds of downtime. It has a progress bar. Let me know if I managed to break anything else.
1636990189

Edited 1636990398
Joel
Pro
Just a heads up, the "equipped" and "unequipped" part of the report seems wildly off. I'll try to attach some screenshots. In this one, the Unequipped is way off.
1636990283

Edited 1636990429
Joel
Pro
Here is a much simpler inventory, but the Equipped is still way off.
The report on containers is actually quite useful, as we have saddlebags that we just figure out manually right now. The bag calculations seem to be working fine even on a sheet where the "equipped" and "unpacked" are way off (same character as my last example, now with saddlebags)
1637013828

Edited 1637016359
Oosh
Sheet Author
API Scripter
Thanks for the report.... I reckon I know what I've done, I've parsed itemweight as an integer instead of a float so those Arrows and whatnot are all waaaaay heavier than they should be. I'll take a look shortly. Edit - yep, whoops, I was stripping the decimal point from the itemweight before calculating totals, so the arrows were weighing 5 pounds each. Fixed. Thanks for the screenshots, that made it super easy to see the issue - I didn't have any decimal weights in the 4 seconds of testing I did. On another note, I've added a * marker to any subtotal which includes 'weightless' items - those with letters in the weight section, which aren't calculated by the sheet and included in 'Total Weight'. So it's worth pointing out that, when comparing the weights at the top of the table, it may look like equipped & unequipped don't add up to Total Weight. The formula should be (Equipped + Unequipped + Coins - Weightless) = Total Weight. Total weight is pulled straight from the sheet, by the way, so if the above formula doesn't work, the 'Total Weight' should be to correct one and I've got another issue somewhere else. :)