Advertisement Create a free account

Forbidden Lands sheet beta

1552381614

Edited 1552581544
Vince
Pro
Sheet Author
I know this has been requested more than a few times... I started working on something in November but handed over my code to Tristram W. &nbsp;who was interested in finishing the sheet... However, since I haven't heard anything in over 2 months, I've continued the sheet and would appreciate some feedback. This is still in beta, so some things (weight calc, roll templates(currently uses default), localization, and misc.) I still need to add. Originally I tried to modify the MY0 alternate sheet, but I've since scraped that idea and just created a new one, but there are some similar features. Includes a Stronghold tab ( Tristram W. &nbsp;added this section and more) Please have a look.&nbsp; I'm open to suggestions.&nbsp; Post any bugs.&nbsp; I've never played FL, so it's very likely I may be overlooking some obvious components/mechanics as well. ;-P TIA Beta game :&nbsp; <a href="https://app.roll20.net/join/4295391/ONxtxA" rel="nofollow">https://app.roll20.net/join/4295391/ONxtxA</a> Current Beta Code (while most of this code base will stay, nothing is set in stone yet.&nbsp; Use at your own risk) html:&nbsp; <a href="https://gist.github.com/vince-roll20/e64ae375dce06b9087f2cf040f9e2556" rel="nofollow">https://gist.github.com/vince-roll20/e64ae375dce06b9087f2cf040f9e2556</a> css:&nbsp; <a href="https://gist.github.com/vince-roll20/b4c5f8e4c968ae3e43948390b33cf2bd" rel="nofollow">https://gist.github.com/vince-roll20/b4c5f8e4c968ae3e43948390b33cf2bd</a>
1552389854
Finderski
Pro
Sheet Author
I've not had a ton of time to mess around with the sheet yet (and I haven't played the game...only read the rules--mostly)...I'll get you some concrete recommendations shortly (I hope). There are a couple of things I'd recommend, though:&nbsp; Mount's gear currently uses a double quote instead of an apostrophe Experience--while I know the paper sheet uses a radio-like button, I'd recommend not using them on the sheet because some things cost A LOT of experience to raise and I don't believe there are enough radio buttons, especially if you need to save them up to advance some things. Nice job with the wounds for the base attributes Skill rolls...things get complicated here. It may be easier to use a /r type command, because modifiers an cause a skill die to be negative and you need to roll those negative dice and those results may have consequences, too. you may already be handling this (I don't know...like I said, I haven't messed around with it much) in a much more elegant way than I'm seeing currently. I can think of some ways this might be accomplished with roll templates...I'd have to think about it. Overall, it's looking good.
1552424346

Edited 1552424750
Very nice, started working on my own, but you've obviously come further. Is there a github repo? I could do a pull request with some suggestions, including i18n (I need it to be in Swedish)&nbsp; Things that would be nice to add (that I can take a stab at): Use the API-script for Forbidden Lands rolls if available Internationalization to Swedish Resource Die roller including automatic lowering of Resource Die through a Sheet Worker (probably) XP Unspent / XP Total counter (numeric rather than radio button, as Finderski suggested) Automatic Gear Count /Encumbrance (Sheet Worker again?) I'm not super experienced with the character sheet's (though I've dabbled), but I'm an experienced web developer, so I would love to help. Also, I have a roll20 Forbidden Lands game coming up next week and already played it IRL a few sessions. It's a lovely game!
1552434915

Edited 1552435020
Andreas J.
Pro
Sheet Author
Jens A. said: I could do a pull request with some suggestions, including i18n Depending on the size of the sheet, you might wanna see if running the sheet trough my automation script would reduce the workload while creating the internationalization tags. It's crude, but still have saved some time with the latest sheet it added tags to. If non of you use linux, drop me a line and I can run a sheet through it. I'm not a Pro atm so you'd have to generate the translation.json yourselves. Edit: Oh hi Vince :)
1552449662
Vince
Pro
Sheet Author
Finderski said: I've not had a ton of time to mess around with the sheet yet (and I haven't played the game...only read the rules--mostly)...I'll get you some concrete recommendations shortly (I hope). There are a couple of things I'd recommend, though:&nbsp; Mount's gear currently uses a double quote instead of an apostrophe Experience--while I know the paper sheet uses a radio-like button, I'd recommend not using them on the sheet because some things cost A LOT of experience to raise and I don't believe there are enough radio buttons, especially if you need to save them up to advance some things. Nice job with the wounds for the base attributes Skill rolls...things get complicated here. It may be easier to use a /r type command, because modifiers an cause a skill die to be negative and you need to roll those negative dice and those results may have consequences, too. you may already be handling this (I don't know...like I said, I haven't messed around with it much) in a much more elegant way than I'm seeing currently. I can think of some ways this might be accomplished with roll templates...I'd have to think about it. Overall, it's looking good. Mount's gear ; LOL I did quick find/replace of single quote to doubles for consistency last night.&nbsp; I didn't even think about any contractions. ;-)&nbsp; Fix coming. Experience ; I wasn't sure about the XP.&nbsp; The official sheet uses 20 and a form-fillable one has 10 slots.&nbsp; Looking at the player's rules; you get XP at the end of each session based on 10 different questions at 1xp for each question, with the caveat that the GM ultimately deciding how much is granted.&nbsp; Nothing says you must spend XP so I assume XP rise above these numbers.&nbsp; As much as I like my little checkboxes, a numerical field is probably a better decision. Skill rolls ; I haven't played a game with negative rolls, so this is new territory for me. The base rolls for skills as well as the Advanced Roll section I have taken directly from the MY0 sheet with very little adjustment.&nbsp; I'm open to any suggestions .&nbsp; I believe there are some API dice scripts for MY0 and maybe even Forbidden Lands. I think we might be able to include options to use those scripts for Pro users as well.&nbsp; Thanks for your input/help Fiinderski.
1552450041
Vince
Pro
Sheet Author
Jens A. said: Very nice, started working on my own, but you've obviously come further. Is there a github repo? I could do a pull request with some suggestions, including i18n (I need it to be in Swedish)&nbsp; Things that would be nice to add (that I can take a stab at): Use the API-script for Forbidden Lands rolls if available Internationalization to Swedish Resource Die roller including automatic lowering of Resource Die through a Sheet Worker (probably) XP Unspent / XP Total counter (numeric rather than radio button, as Finderski suggested) Automatic Gear Count /Encumbrance (Sheet Worker again?) I'm not super experienced with the character sheet's (though I've dabbled), but I'm an experienced web developer, so I would love to help. Also, I have a roll20 Forbidden Lands game coming up next week and already played it IRL a few sessions. It's a lovely game! API-script ; I'll definitely look into adding this as an option for Pro users. Internalization ; getting the sheet's i18n data in place and creating a translation.json is already on my list.&nbsp; I will make the json available to you ASAP so you can work up the Swedish translation.&nbsp; Thank you. Automatic Resource Die roller ; should be doable via sheetworker. XP ; will change to a numeric field instead of slots Gear/Encumbrance ; also on my short list. Thank you for your suggestions Jens. I'll start on the localization code as soon as I think I'm fairly close on my release code.
1552450191
Vince
Pro
Sheet Author
Andreas J. said: Jens A. said: I could do a pull request with some suggestions, including i18n Depending on the size of the sheet, you might wanna see if running the sheet trough my automation script would reduce the workload while creating the internationalization tags. It's crude, but still have saved some time with the latest sheet it added tags to. If non of you use linux, drop me a line and I can run a sheet through it. I'm not a Pro atm so you'd have to generate the translation.json yourselves. Edit: Oh hi Vince :) I'm definitely going to use your script Andreas.&nbsp; It saved me hours on the Cyberpunk 2020 sheet.&nbsp; I have a linux mint machine in my house and I'll have a go.&nbsp; I'm not a native linux geek however, so I may need some ELI5 directions. ;-)
1552559789
Vince said: Andreas J. said: Jens A. said: I could do a pull request with some suggestions, including i18n Depending on the size of the sheet, you might wanna see if running the sheet trough my automation script would reduce the workload while creating the internationalization tags. It's crude, but still have saved some time with the latest sheet it added tags to. If non of you use linux, drop me a line and I can run a sheet through it. I'm not a Pro atm so you'd have to generate the translation.json yourselves. Edit: Oh hi Vince :) I'm definitely going to use your script Andreas.&nbsp; It saved me hours on the Cyberpunk 2020 sheet.&nbsp; I have a linux mint machine in my house and I'll have a go.&nbsp; I'm not a native linux geek however, so I may need some ELI5 directions. ;-) Any news, Vince? Looking forward to using this sheet next week, would that be possible?
1552566369
Wes
Pro
Sheet Author
@Vince, I have a partially built roll template that uses a interface from the character sheet to build your rolls using the characters attributes. It uses action buttons to populate the dice to be rolled. I can show it to you later today if you are interested. Wes
1552582332

Edited 1552602041
Vince
Pro
Sheet Author
@Jens I'll post the beta code at the top of the thread(done) if you would like to use it for a custom game.&nbsp; I have a prior commitment this weekend that involves building and then sleeping in snow quinzees (scouts)...&nbsp; I really don't want to push out a half-baked sheet.&nbsp; I believe you could use the current beta to run a game.&nbsp; Auto-calc for gear weight total isn't implemented yet&nbsp; [need to add carried coin weight still]&nbsp;and roll templates are still defaults.&nbsp; Those should not be game breakers IMO.&nbsp; Once I add those features, you should be able to simply change from a custom sheet to the released version.&nbsp; Sheet macros will be updated to use new roll templates, but all other attribute data will still be intact.&nbsp;&nbsp; @Wes I'm definitely interested.&nbsp; I'm in/out with work today and tomorrow, so I'm not sure about a schedule though... &nbsp;&nbsp;Maybe a PM with some code and a quick explanation(EIL5)?&nbsp; :-P&nbsp; Thanks Wes. I should have some more free time tomorrow afternoon/evening.
1552601490
Vince
Pro
Sheet Author
Couple of beta updates; - changed Experience and Willpower to input fields vs radio. - Added auto-calculation of gear and mount's gear (still need to include carried coin weight)
1552603823
GiGs
Pro
Sheet Author
Hi Vince, unless I'm missing something you have a bit of redundancy in the sheet workers. You could change them to&nbsp; const repeatingSum = (destination, section, fields, multiplier = 1) =&gt; { if (!Array.isArray(fields)) fields = [fields]; getSectionIDs(`repeating_${section}`, idArray =&gt; { const attrArray = idArray.reduce( (m,id) =&gt; [...m, ...(fields.map(field =&gt; `repeating_${section}_${id}_${field}`))],[]); getAttrs(attrArray, v =&gt; { console.log("===== values of v: "+ JSON.stringify(v) +" ====="); // getValue: if not a number, returns 1 if it is 'on' (checkbox), otherwise returns 0.. const getValue = (section, id,field) =&gt; parseFloat(v[`repeating_${section}_${id}_${field}`], 10) || (v[`repeating_${section}_${id}_${field}`] === 'on' ? 1 : 0); const sumTotal = idArray.reduce((total, id) =&gt; total + fields.reduce((subtotal,field) =&gt; subtotal * getValue(section, id,field),1),0); setAttrs({[destination]: sumTotal * multiplier}); }); }); }; on('change:repeating_gear remove:repeating_gear sheet_opened', function() { repeatingSum("character_encumbrance_total","gear",["gear_weight","gear_qty","gear_carried"]); console.log("===== gear changed ====="); }); on('change:repeating_mount remove:repeating_mount sheet_opened', function() { repeatingSum("mount_encumbrance_total","mount",["mount_gear_weight","mount_gear_qty","mount_gear_carried"]); console.log("===== mount's gear changed ====="); }); The whole point of the repeatingSum function is that you only need one copy of it. I'm glad you're getting some use out of it, anyway!
1552615333

Edited 1552615462
Vince
Pro
Sheet Author
GiGs said: Hi Vince, unless I'm missing something you have a bit of redundancy in the sheet workers. You could change them to&nbsp; const repeatingSum = (destination, section, fields, multiplier = 1) =&gt; { if (!Array.isArray(fields)) fields = [fields]; getSectionIDs(`repeating_${section}`, idArray =&gt; { const attrArray = idArray.reduce( (m,id) =&gt; [...m, ...(fields.map(field =&gt; `repeating_${section}_${id}_${field}`))],[]); getAttrs(attrArray, v =&gt; { console.log("===== values of v: "+ JSON.stringify(v) +" ====="); // getValue: if not a number, returns 1 if it is 'on' (checkbox), otherwise returns 0.. const getValue = (section, id,field) =&gt; parseFloat(v[`repeating_${section}_${id}_${field}`], 10) || (v[`repeating_${section}_${id}_${field}`] === 'on' ? 1 : 0); const sumTotal = idArray.reduce((total, id) =&gt; total + fields.reduce((subtotal,field) =&gt; subtotal * getValue(section, id,field),1),0); setAttrs({[destination]: sumTotal * multiplier}); }); }); }; on('change:repeating_gear remove:repeating_gear sheet_opened', function() { repeatingSum("character_encumbrance_total","gear",["gear_weight","gear_qty","gear_carried"]); console.log("===== gear changed ====="); }); on('change:repeating_mount remove:repeating_mount sheet_opened', function() { repeatingSum("mount_encumbrance_total","mount",["mount_gear_weight","mount_gear_qty","mount_gear_carried"]); console.log("===== mount's gear changed ====="); }); The whole point of the repeatingSum function is that you only need one copy of it. I'm glad you're getting some use out of it, anyway! Are you sure GiGs?&nbsp; lol I see, no need to create a new sum function for the different totals.&nbsp; repeatingSum only does one thing and you just provide it with attribute data... That makes much more sense.&nbsp; I'll make the adjustments.&nbsp; I've used TAS to do similar functions, but your specefic examples on the wiki were much less confusing for me.&nbsp; Much appreciated.
1552617225
GiGs
Pro
Sheet Author
You're not alone. I mainly wrote this because I dont understand how to use TAS, haha.
Howdy! I played around with the beta sheet a bit. Amazing job! It also looks very nice! A few things I've noticed: Skills: Shouldn't the skills appear in the same order as the physical character sheet? Might (Strength) Endurance (Strength) Melee (Strength) Crafting (Strength) Stealth (Agility) Sleight of Hand (Agility) Move (Agility) Marksmanship (Agility) Scouting (Wits) Lore (Wits) Survival (Wits) Insight (Wits) Manipulation (Empathy) Performance (Empathy) Healing (Empathy) Animal Handling (Empathy) D66 doesn't seem to be working for me. At first it rolled only 1 dice, and then no dice at all. Artifact bonus There's two "Mighty"? Consummables Shouldn't have d4 in there. Forbidden Lands uses only d6 to d12 Is there a way to limit the damage one can apply to an attribute so that the score can never be lower than 0? At the moment, you can click on the skulls in such a way your attribute becomes negative. Now the trick question : how can I use this in my game? :)
1553268338

Edited 1553271493
Vince
Pro
Sheet Author
@Adrramyr Thank you for taking the time to test and comment. Skills :&nbsp; LOL&nbsp; I debated on how best to present the skills (as per the official sheet or alphabeticaly).&nbsp; The official sheet groups them by Ability(that's cool), but then their sub-grouping's aren't even alphabetical...&nbsp; Uggh. That just kills me.&nbsp; It's not set in stone, but after talking with a few people I decided that a master alphabetical listing was more intuitive/expected.&nbsp; I'll can change this to match the oficial sheet if I get some more requests to do so.&nbsp; And/or, on a future update, I can make an option to toggle which view a player/gm would like per sheet. ;-) D66 - seems like a bug.&nbsp; Seems to work intermittently(strange). The "d66" button should definitely roll and display 2 d6's. I will fix this ASAP. &nbsp;[ Fixed ] Mighty x2 - Wes was so cool to give us his Dice Pool code/roll template(I just put the makeup on it), so he may be best to comment on this. I'm guessing there may be times when you need to include 2 d'8s in your pool...&nbsp; But, I'll concur to those more knowledgeable of the game.&nbsp; If this would never happen, let me know and I can remove the option. d4 - I think I might have followed the MY0 on this to be safe and included the d4.&nbsp; I'll make sure to remove it. &nbsp;&nbsp; [ Fixed ] Damage - I haven't actually got to play FL yet... (hint, hint)&nbsp; So I wasn't clear on the nuance of "Broken", that perhaps you could be less than "0" temporarily and be brought back by the end of the turn.&nbsp; Although this is just a visual representation of your Ability damage, I'll address adding a lower setting of "0". Shouldn't be a problem. &nbsp;&nbsp; [ Fixed ] Release - anyone(Pro subscribers) can use my current beta code (link is at the top of the thread or on the beta game's home page) as a custom sheet.&nbsp; I'm actively updating the code, so the code may have issues (ie d66), as we get closer to an initial release.&nbsp; I'm shooting for next week, but no promises.&nbsp; Life and such seems to get in the way... Thanks for the kind words and testing. Vince UPDATE: [ Fixed ] a few of these issues.
1553284457
Finderski
Pro
Sheet Author
Vince said: @Adrramyr Thank you for taking the time to test and comment. Skills :&nbsp; LOL&nbsp; I debated on how best to present the skills (as per the official sheet or alphabeticaly).&nbsp; The official sheet groups them by Ability(that's cool), but then their sub-grouping's aren't even alphabetical...&nbsp; Uggh. That just kills me.&nbsp; It's not set in stone, but after talking with a few people I decided that a master alphabetical listing was more intuitive/expected.&nbsp; I'll can change this to match the oficial sheet if I get some more requests to do so.&nbsp; And/or, on a future update, I can make an option to toggle which view a player/gm would like per sheet. ;-) Sorry I've not been able to contribute more to this...however, just a note that perhaps they were alphabetical in the original language?&nbsp; If I remember correctly, Forbidden Lands originates from Sweden. ;)
1553285485

Edited 1553285579
Vince
Pro
Sheet Author
@Finderski I believe you are correct.(and that would help explain why the sub-groupings are not alphabetical).&nbsp; Perhaps I'll leave it as alphabetical(or at least a togable version) and use localization code for&nbsp; List Ordering &nbsp;to sort according to a given language...
1553286294
Finderski
Pro
Sheet Author
that's cool!&nbsp; I may have to look at that in greater detail to see if I can/should do something like that for Savage Worlds skill list (which I alphabetize (generally speaking)...
1553300731
Wes
Pro
Sheet Author
Vince said: &lt;SNIP&gt; Mighty x2 - Wes was so cool to give us his Dice Pool code/roll template(I just put the makeup on it), so he may be best to comment on this. I'm guessing there may be times when you need to include 2 d'8s in your pool...&nbsp; But, I'll concur to those more knowledgeable of the game.&nbsp; If this would never happen, let me know and I can remove the option. &lt;SNIP&gt; I also haven't had a chance to play, but I included mighty twice as there are talents that allow you to roll a Mighty (1d8) artifact die, and if you have a Mighty weapon you will need the option to roll 2 d8's. Wes
1553302297
Vince
Pro
Sheet Author
More is better.&nbsp; ;-)
1553509398
Awesome work, Vince! Looking really good! Feedback: There is a semi-official&nbsp; Forbidden Lands die rolling API Script (based on a MY0 script) that handles pushing rolls interactively in a very nice way. Could something like that be integrated? Probably as an option as it would only be available to Pro Level subscribers. Also, the actual template used in your character sheet is way nicer, so a combination would be great. The functionality of the API Script (if available) with the design of the template you are using. Item dice don't seem to carry over from the weapon to the roll template If not using the API Script, is there any way to automate the push mechanic to push the last roll made and calculate what dice to reroll? The stronghold tab has a bit of layout overflow problem in the top box
Vince said: @Adrramyr Thank you for taking the time to test and comment. Skills :&nbsp; LOL&nbsp; I debated on how best to present the skills (as per the official sheet or alphabeticaly).&nbsp; The official sheet groups them by Ability(that's cool), but then their sub-grouping's aren't even alphabetical...&nbsp; Uggh. That just kills me.&nbsp; It's not set in stone, but after talking with a few people I decided that a master alphabetical listing was more intuitive/expected.&nbsp; I'll can change this to match the oficial sheet if I get some more requests to do so.&nbsp; And/or, on a future update, I can make an option to toggle which view a player/gm would like per sheet. ;-) Ahh yes, haven't noticed that. Totally make sense. You're probably right, it's probably in alphabetical order in Sweden. Same for the Gear section where all the items are in jumbled order, probably again because of the translation. Better keep them in alphabetical order and grouped by attribute I think. Thanks!
1553523362

Edited 1553523370
Vince
Pro
Sheet Author
Thank you for the feedback.&nbsp;&nbsp; - The gear dice for weapon's hasn't been implemented yet.&nbsp; You can still override using the Dice Pool interface. - I will take a look at the API roller and see if/how I might integrate it with the sheet for Pro users. - My first thought considering the push mechanic is that it can't be done with roll20's default system since there's no way to store results... - I hadn't even noticed the overflow issue on the Stronghold tab. Been focusing on the "guts" (sheetworker's) over the weekend.&nbsp; I'll get that fixed. Thanks again.
1553523947
Thanks for swift response! - No problem with the weapons not being ready yet, we'll do it manually for now. I looked at the code and at a glance it looks like there are some attribute references that are a bit off (repeating_weapon:weapon_skill should be repeating_weapon_weapon_skill for instance, same for the gear dice, which also has to be converted from a an integer to a gear die reference string such as 'zero', 'one', etc) - Since rolls are basically only made at one place in the sheet, it should be pretty straight forward to integrate the fbl-script. If you don't want to maintain multiple versions, I can maintain a fork with it for custom sheets. - I think you're right that if you want to be able to use a push mechanic, you need to use API-scripts so that you can have a state Again, great work! Really looking forward to using this sheet this coming Sunday!
1553573519

Edited 1553573538
Vince
Pro
Sheet Author
@Jens Thanks Jens.&nbsp; I definitely struggle through the js.&nbsp; Luckily GiGs, Scott, Finderski, and many other's freely offer up their knowledge for the rest of us. - Funny you should mention the gear dice.&nbsp; While I was nearing "must-go-to-sleep-because-you-have-work-in-4-hours" last night, I finally noticed that the gear die wasn't using an integer.&nbsp; I haven't looked into it yet... so I'm not sure why @{gear} is set-up to use string vs integer.&nbsp; I'm sure there was a reason.&nbsp; Wes? ;-) &nbsp;- I'll try and get the sheet rolled out ASAP and we can look at adding modifying the sheet and API dice roller to work for Pro users.&nbsp; Again, this is stretching my already "non-programmer" skills, so I'm happy to let you work on that Jen. Stay tuned.
1553589040

Edited 1553589382
Vince
Pro
Sheet Author
A few updates; &nbsp;fixed the weapon attribute links to the Dice Pool. Attribute, Skill, and Bonus/Gear dice now update from the weapon row. &nbsp;Still to do: some small adjustments (update reputation roll, include more weapon info when selecting an attack, sheet.json, image, etc.) localization code (I've been waiting on this until we got closer to a final code base.) Should the armor update dice pool like weapons? It's just a gear set and roll, so not sure it's necessary... Any other suggestions/requests?&nbsp; Bigger things(API dice integration) may have to wait for a later update, but once we're satisfied with the basic sheet, I would like to release it. Thanks, Vince
1553590031
GiGs
Pro
Sheet Author
If you want to streamline the sheet workers and remove some of the duplication, you could replace everything from line 1471 onwards with the code below (though it sounds like you've made your own changes to the weapon worker, so maybe the last worker needs tweaking). I had a couple hours spare, and couldn't help myself. If you don't want to use this, I won't be hurt *sniffle* There are some comments in the code to explain some things. feel free to ask questions. /* ability damage calcs */ const damages = { // an Object Literal: damages['stremgth'] returns 'damage', damages['empathy'] returns 'doubt', etc strength: 'damage', agility: 'fatigue', wits: 'confusion', empathy: 'doubt' }; Object.keys(damages).forEach(stat =&gt; { // creates a sheet worker for each stat in damages - first stat = 'strength', then stat = 'agility', etc // in each worker, stat = the stat (strength, agility, etc) and damages[stat] = the associated damage ('damage'. 'fatigue', etc) on(`change:${stat} change:${damages[stat]}`, function () { console.log(`${stat} or ${damages[stat]} has changed`); getAttrs([stat, damages[stat]], values =&gt; { const stat_score = parseInt(values[stat], 10)||0, damage_score = parseInt(values[damages[stat]], 10)||0; setAttrs({ [stat + '_total']: Math.max(0, stat_score - damage_score ) //Math.max returns the highest value from a comma separated list - it's just like roll20's kh1 dice function. // so you dont need the if( value &gt; 0) test. With math.max, if the value is below 0, it becomes 0. }); }); }); }); /* skill total calcs */ const attributes = { // a javascript Object Literal, containing each skill, and its corresponding attribute 'Animal Handling': 'empathy_total', Crafting: 'strength_total', Endurance: 'strength_total', Healing: 'empathy_total', Insight: 'wits_total', Lore: 'wits_total', Manipulation: 'empathy_total', Marksmanship: 'agility_total', Melee: 'strength_total', Might: 'strength_total', Move: 'agility_total', Performance: 'empathy_total', Scouting: 'wits_total', 'Sleight Of Hand': 'agility_total', Stealth: 'agility_total', Survival: 'wits_total', }, getSkill = (label) =&gt; label.split(' ').join('_').toLowerCase(); // a function to change (for example) "Animal Handling" to "animal_handling" // needed for a couple of functions below Object.keys(attributes).forEach(skill_name =&gt; { // creates a sheet worker for each skill in attributes - first skill = 'strength', then stat = 'agility', etc // in each worker, stat = the stat (strength, agility, etc) and damages[stat] = the associated damage ('damage'. 'fatigue', etc) const skill = getSkill(skill_name); on(`change:${skill} change:${attributes[skill_name]} sheet:opened`, function () { console.log(`${skill} or ${attributes[skill_name]} has changed`); getAttrs([skill, attributes[skill_name]], function(values) { const stat_score = parseInt(values[attributes[skill_name]], 10)||0, skill_score = parseInt(values[skill], 10)||0; setAttrs({ [skill + '_total']: stat_score + skill_score }); }); }); }); /* Wes's Dice Pool */ on("change:attribute", function () { getAttrs(['attribute'], function(values) { const pool = { //if you name the keys below to match the attributes on the character sheet, it makes setAttrs much simpler - see below. 0: {attribute_die_one: 0, attribute_die_two: 0, attribute_die_three: 0, attribute_die_four: 0, attribute_die_five: 0, attribute_die_six: 0}, 1: {attribute_die_one: 1, attribute_die_two: 0, attribute_die_three: 0, attribute_die_four: 0, attribute_die_five: 0, attribute_die_six: 0}, 2: {attribute_die_one: 1, attribute_die_two: 1, attribute_die_three: 0, attribute_die_four: 0, attribute_die_five: 0, attribute_die_six: 0}, 3: {attribute_die_one: 1, attribute_die_two: 1, attribute_die_three: 1, attribute_die_four: 0, attribute_die_five: 0, attribute_die_six: 0}, 4: {attribute_die_one: 1, attribute_die_two: 1, attribute_die_three: 1, attribute_die_four: 1, attribute_die_five: 0, attribute_die_six: 0}, 5: {attribute_die_one: 1, attribute_die_two: 1, attribute_die_three: 1, attribute_die_four: 1, attribute_die_five: 1, attribute_die_six: 0}, 6: {attribute_die_one: 1, attribute_die_two: 1, attribute_die_three: 1, attribute_die_four: 1, attribute_die_five: 1, attribute_die_six: 1} }; let attribute = values.attribute; if (!pool.hasOwnProperty( attribute )) { // I reversed the order from the example above for simplicity console.log("Error: The item " + pool + " is not found within the pool Object."); return; } setAttrs(pool[attribute]); }); }); on("change:skill", function () { getAttrs(['skill'], function(values) { const pool = { "-3": {negative_die_three: 1, negative_die_two: 1, negative_die_one: 1, skill_die_one: 0, skill_die_two: 0, skill_die_three: 0, skill_die_four: 0, skill_die_five: 0, skill_die_six: 0, skill_die_seven: 0}, "-2": {negative_die_three: 0, negative_die_two: 1, negative_die_one: 1, skill_die_one: 0, skill_die_two: 0, skill_die_three: 0, skill_die_four: 0, skill_die_five: 0, skill_die_six: 0, skill_die_seven: 0}, "-1": {negative_die_three: 0, negative_die_two: 0, negative_die_one: 1, skill_die_one: 0, skill_die_two: 0, skill_die_three: 0, skill_die_four: 0, skill_die_five: 0, skill_die_six: 0, skill_die_seven: 0}, 0: {negative_die_three: 0, negative_die_two: 0, negative_die_one: 0, skill_die_one: 0, skill_die_two: 0, skill_die_three: 0, skill_die_four: 0, skill_die_five: 0, skill_die_six: 0, skill_die_seven: 0}, 1: {negative_die_three: 0, negative_die_two: 0, negative_die_one: 0, skill_die_one: 1, skill_die_two: 0, skill_die_three: 0, skill_die_four: 0, skill_die_five: 0, skill_die_six: 0, skill_die_seven: 0}, 2: {negative_die_three: 0, negative_die_two: 0, negative_die_one: 0, skill_die_one: 1, skill_die_two: 1, skill_die_three: 0, skill_die_four: 0, skill_die_five: 0, skill_die_six: 0, skill_die_seven: 0}, 3: {negative_die_three: 0, negative_die_two: 0, negative_die_one: 0, skill_die_one: 1, skill_die_two: 1, skill_die_three: 1, skill_die_four: 0, skill_die_five: 0, skill_die_six: 0, skill_die_seven: 0}, 4: {negative_die_three: 0, negative_die_two: 0, negative_die_one: 0, skill_die_one: 1, skill_die_two: 1, skill_die_three: 1, skill_die_four: 1, skill_die_five: 0, skill_die_six: 0, skill_die_seven: 0}, 5: {negative_die_three: 0, negative_die_two: 0, negative_die_one: 0, skill_die_one: 1, skill_die_two: 1, skill_die_three: 1, skill_die_four: 1, skill_die_five: 1, skill_die_six: 0, skill_die_seven: 0}, 6: {negative_die_three: 0, negative_die_two: 0, negative_die_one: 0, skill_die_one: 1, skill_die_two: 1, skill_die_three: 1, skill_die_four: 1, skill_die_five: 1, skill_die_six: 1, skill_die_seven: 0}, 7: {negative_die_three: 0, negative_die_two: 0, negative_die_one: 0, skill_die_one: 1, skill_die_two: 1, skill_die_three: 1, skill_die_four: 1, skill_die_five: 1, skill_die_six: 1, skill_die_seven: 1} }; let skill = values.skill; if (!pool.hasOwnProperty( skill )) { // I reversed the order from the example above for simplicity console.log("Error: The item " + pool + " is not found within the pool Object."); return; } setAttrs(pool[skill]); }); }); on("change:gear", function () { getAttrs(['gear'], function(values) { const pool = { zero: {gear_die_one: 0, gear_die_two: 0, gear_die_three: 0, gear_die_four: 0, gear_die_five: 0, gear_die_six: 0, gear_die_seven: 0, gear_die_eight: 0, gear_die_nine: 0, gear_die_ten: 0, gear_die_eleven: 0, gear_die_twelve: 0}, one: {gear_die_one: 1, gear_die_two: 0, gear_die_three: 0, gear_die_four: 0, gear_die_five: 0, gear_die_six: 0, gear_die_seven: 0, gear_die_eight: 0, gear_die_nine: 0, gear_die_ten: 0, gear_die_eleven: 0, gear_die_twelve: 0}, two: {gear_die_one: 1, gear_die_two: 1, gear_die_three: 0, gear_die_four: 0, gear_die_five: 0, gear_die_six: 0, gear_die_seven: 0, gear_die_eight: 0, gear_die_nine: 0, gear_die_ten: 0, gear_die_eleven: 0, gear_die_twelve: 0}, three: {gear_die_one: 1, gear_die_two: 1, gear_die_three: 1, gear_die_four: 0, gear_die_five: 0, gear_die_six: 0, gear_die_seven: 0, gear_die_eight: 0, gear_die_nine: 0, gear_die_ten: 0, gear_die_eleven: 0, gear_die_twelve: 0}, four: {gear_die_one: 1, gear_die_two: 1, gear_die_three: 1, gear_die_four: 1, gear_die_five: 0, gear_die_six: 0, gear_die_seven: 0, gear_die_eight: 0, gear_die_nine: 0, gear_die_ten: 0, gear_die_eleven: 0, gear_die_twelve: 0}, five: {gear_die_one: 1, gear_die_two: 1, gear_die_three: 1, gear_die_four: 1, gear_die_five: 1, gear_die_six: 0, gear_die_seven: 0, gear_die_eight: 0, gear_die_nine: 0, gear_die_ten: 0, gear_die_eleven: 0, gear_die_twelve: 0}, six: {gear_die_one: 1, gear_die_two: 1, gear_die_three: 1, gear_die_four: 1, gear_die_five: 1, gear_die_six: 1, gear_die_seven: 0, gear_die_eight: 0, gear_die_nine: 0, gear_die_ten: 0, gear_die_eleven: 0, gear_die_twelve: 0}, seven: {gear_die_one: 1, gear_die_two: 1, gear_die_three: 1, gear_die_four: 1, gear_die_five: 1, gear_die_six: 1, gear_die_seven: 1, gear_die_eight: 0, gear_die_nine: 0, gear_die_ten: 0, gear_die_eleven: 0, gear_die_twelve: 0}, eight: {gear_die_one: 1, gear_die_two: 1, gear_die_three: 1, gear_die_four: 1, gear_die_five: 1, gear_die_six: 1, gear_die_seven: 1, gear_die_eight: 1, gear_die_nine: 0, gear_die_ten: 0, gear_die_eleven: 0, gear_die_twelve: 0}, nine: {gear_die_one: 1, gear_die_two: 1, gear_die_three: 1, gear_die_four: 1, gear_die_five: 1, gear_die_six: 1, gear_die_seven: 1, gear_die_eight: 1, gear_die_nine: 1, gear_die_ten: 0, gear_die_eleven: 0, gear_die_twelve: 0}, ten: {gear_die_one: 1, gear_die_two: 1, gear_die_three: 1, gear_die_four: 1, gear_die_five: 1, gear_die_six: 1, gear_die_seven: 1, gear_die_eight: 1, gear_die_nine: 1, gear_die_ten: 1, gear_die_eleven: 0, gear_die_twelve: 0}, eleven: {gear_die_one: 1, gear_die_two: 1, gear_die_three: 1, gear_die_four: 1, gear_die_five: 1, gear_die_six: 1, gear_die_seven: 1, gear_die_eight: 1, gear_die_nine: 1, gear_die_ten: 1, gear_die_eleven: 1, gear_die_twelve: 0}, twelve: {gear_die_one: 1, gear_die_two: 1, gear_die_three: 1, gear_die_four: 1, gear_die_five: 1, gear_die_six: 1, gear_die_seven: 1, gear_die_eight: 1, gear_die_nine: 1, gear_die_ten: 1, gear_die_eleven: 1, gear_die_twelve: 1} }; let gear = values.gear; if (!pool.hasOwnProperty( gear )) { // I reversed the order from the example above for simplicity console.log("Error: The item " + pool + " is not found within the pool Object."); return; } setAttrs(pool[gear]); }); }); on("clicked:dice_pool_crafting", () =&gt; dicePoolButtons('Crafting')); on("clicked:dice_pool_might", () =&gt; dicePoolButtons('Might')); on("clicked:dice_pool_endurance", () =&gt; dicePoolButtons('Endurance')); on("clicked:dice_pool_melee", () =&gt; dicePoolButtons('Melee')); on("clicked:dice_pool_stealth", () =&gt; dicePoolButtons('Stealth')); on("clicked:dice_pool_sleight_of_hand", () =&gt; dicePoolButtons('Sleight of Hand')); on("clicked:dice_pool_move", () =&gt; dicePoolButtons('Move')); on("clicked:dice_pool_marksmanship", () =&gt; dicePoolButtons('Marksmanship')); on("clicked:dice_pool_scouting", () =&gt; dicePoolButtons('Scouting')); on("clicked:dice_pool_lore", () =&gt; dicePoolButtons('Lore')); on("clicked:dice_pool_survival", () =&gt; dicePoolButtons('Survival')); on("clicked:dice_pool_insight", () =&gt; dicePoolButtons('Insight')); on("clicked:dice_pool_manipulation", () =&gt; dicePoolButtons('Manipulation')); on("clicked:dice_pool_performance", () =&gt; dicePoolButtons('Performance')); on("clicked:dice_pool_healing", () =&gt; dicePoolButtons('Healing')); on("clicked:dice_pool_animal_handling", () =&gt; dicePoolButtons('Animal Handling')); const dicePoolButtons = (which) =&gt; { console.log(`Dice Pool ${which} - button clicked`); getAttrs([attributes[which], getSkill(which)], function (values) { // attributes[which] returns the attribute; e.g attributes['Stealth'] gives 'agility_total' // getSkill(which) converts the skill name to a valid attribute string: getSkill('Animal Handling') gives 'animal_handling' const stat = parseInt(values[attributes[which]], 10) || 0; // stat here first gets attributes['Stealth'] which will be, say, 'agility_total' and // then values['ability_total'] gives you the score from the character sheet. setAttrs({ attribute: stat &gt; 0 ? stat : 0, // this is a Ternary Operator, it starts with a question (e.g. is stat &gt; 0) // and if true, returns the bit after the ? and if false returns the bit after the : // you can often replace simple if/then expressions with a single line ternary operator. skill: parseInt(values[getSkill(which)], 10) || 0, current_preset: which }); }); }; /* end Dice Pool */ /* set weapon dice pool */ on("change:repeating_weapons:attack_roll", function() { console.log("Weapon - button clicked"); getAttrs(['attribute', 'skill', 'repeating_weapons_weapon_skill', 'repeating_weapons_weapon_name' ,'repeating_weapons_weapon_bonus', 'strength_total', 'agility_total', 'wits_total', 'empathy_total', 'marksmanship', 'melee'], function(values) { const weapon_skill = parseInt(values.repeating_weapons_weapon_skill,10)||0, weapon_bonus = parseInt(values.repeating_weapons_weapon_bonus,10)||0, weapon_name = values.repeating_weapons_weapon_name, attribute = (weapon_skill === 0 ? parseInt(values.strength_total,10)||0 : parseInt(values.agility_total,10)||0 ), skill = (weapon_skill === 0 ? parseInt(values.melee,10)||0 : parseInt(values.marksmanship,10)||0 ); setAttrs({ attribute: attribute, skill: skill, gear: weapon_bonus, current_preset: `Weapon Selected - ${weapon_name}` }); }); });
1553607392
Wes
Pro
Sheet Author
Gear Dice: string vs integer, If I recall properly they all started out as string and then I was replacing them to be integer, but gear didn't get done as I had to move on to something else. I love seeing Gigs Code examples I learn so much from Him. Thanks for all you do man!
1553608590

Edited 1553608616
Vince
Pro
Sheet Author
On my cell, so I won't even attempt a gander at your code Gigs, but of course I will be using it. LOL. @Wes NP.&nbsp; Once I realised my assumption were wrong, easy fix.
1553610927
@GiGs Beautiful code, super nice comments Seems like a pretty simple fix to replace the gear die strings with integers for gear dice, for consistency, especially after GiGs refactoring. @Vince, I think armor dice can be rolled directly, using gear dice and the template if possible
1553645453
Vince
Pro
Sheet Author
@GiGs I had a few minutes to look at your re-write.&nbsp; I love how you were able to combine many of the repetitious routines into simple functions.&nbsp; Not to mention how you streamlined much of the other functions as well.&nbsp; I see the beauty/efficiency in it but that's a far leap in my meager sheetworker skills to do that on my own(thanks to your tutelage of many of us on roll20...).&nbsp; Thank you for the help.
1553648205
GiGs
Pro
Sheet Author
You're welcome, Vince. I hope even if you dont fully understand it, you can grasp the stuff there enough to reuse it in similar projects. It can remove a lot of the tedious repetition. (And if you do want to understand more, I'm happy to answer questions.)&nbsp; And thanks also for your comments Jens and Wes, it means a lot.
1553649275
GiGs
Pro
Sheet Author
PS, I just noticed a minor thing on the last function for weapons: the getAttrs line: getAttrs(['attribute', 'skill', 'repeating_weapons_weapon_skill', 'repeating_weapons_weapon_name' ,'repeating_weapons_weapon_bonus', 'strength_total', 'agility_total', 'wits_total', 'empathy_total', 'marksmanship', 'melee'], function(values) { the attribute and skill, erm, attributes are not actually read from the character sheet during the worker itself, they are recreated from scratch. Since you don't need to 'get' them, that line should be getAttrs(['repeating_weapons_weapon_skill', 'repeating_weapons_weapon_name' ,'repeating_weapons_weapon_bonus', 'strength_total', 'agility_total', 'wits_total', 'empathy_total', 'marksmanship', 'melee'], function(values) {
1553650454
Vince
Pro
Sheet Author
lol.&nbsp; I didn't include them at first, but I added them while I was troubleshooting last.&nbsp; I was losing my confidence. ;-P&nbsp; Makes total sense that their not needed.&nbsp;&nbsp; Also, I added back the code for detecting and setting the Dice Pool.&nbsp; I'm sure it was just accidentally left out.&nbsp;&nbsp; on("clicked:dice_pool_clear", function() { console.log("Clear Dice Pool - button clicked"); setAttrs({ attribute: 0, skill: 0, gear: 0, artifact_die_eight: 0, artifact_die_ocho: 0, artifact_die_ten: 0, artifact_die_twelve: 0, current_preset: "Custom" }); });
1553651841
GiGs
Pro
Sheet Author
oh yes, good point, I did overlook that.
1553710398
For reference, I used the following roll-command to interact with the Forbidden Lands Die Roller API Script (replace the Roll button in the Dice Pool section of the character sheet, remove the push button as the script deals with pushes): &nbsp;&lt;button class="dice-pool" type="roll" name="roll_the_dice" value="!forl [[(@{skill})d6]] [[(@{attribute})d6]] [[(@{gear})d6]] [[(@{artifact_die_eight})d8+(@{artifact_die_ocho})d8+(@{artifact_die_ten})d10+(@{artifact_die_twelve})d12]] --Character|@{character_name} --Roll|@{current_preset}" title="Roll the Dice!" /&gt;&lt;span&gt; Roll &lt;/span&gt;&lt;/button&gt;
1553715745

Edited 1553715773
Vince
Pro
Sheet Author
@Jens Not sure how other sheet's deal with the integration of API sheet rolls...&nbsp; Add an option that toggles the display of the API rolls?&nbsp; Expose the macro-text of the API rolls for editing?&nbsp; Thoughts/Suggestions?
1553722120
Finderski
Pro
Sheet Author
Vince said: @Jens Not sure how other sheet's deal with the integration of API sheet rolls...&nbsp; Add an option that toggles the display of the API rolls?&nbsp; Expose the macro-text of the API rolls for editing?&nbsp; Thoughts/Suggestions? Seems people don't like macros and struggle writing them. If you have one that works, I'd go ahead and show/hide the appropriate buttons. I did something like that with the old Savage Worlds Tabbed sheet for people who didn't want to use Roll Templates (that was before Sheet Workers were a thing and I could just change the roll formula, but was lazy and never changed it).
1553760741
The Forbidden Lands API Script is part of the official repository, so it's reasonably vetted and seems well maintained (though it's a bit of a beast code-wise). It is also pretty high quality functionality-wise and has decent design. The possibility to push the roll directly from the chat window really smooths play IMO.
1554720381
Greg
Pro
Great work so far! Thank you Vince for taking the time! Also a thank you to everyone who has contributed! Reading the rules know so I don't know what all is working and what isn't.&nbsp; Anyone want to clue me in on what to look for?&nbsp;&nbsp;
1554741811
Vince
Pro
Sheet Author
Thank you Greg.&nbsp; I haven't played FL yet... so I've been winging it.&nbsp; ;-) I'm hoping to release this for the next round of roll20 pull requests.&nbsp; I believe we have a "workable" sheet that can still be updated as needed after it gets some real-world usage.
1554798286

Edited 1554798623
Vince
Pro
Sheet Author
Forbidden Lands sheet submitted; <a href="https://github.com/Roll20/roll20-character-sheets/pull/4926" rel="nofollow">https://github.com/Roll20/roll20-character-sheets/pull/4926</a> includes integrated dice pool roll template (thank you Wes) optional API dice roller (requires installing 'Forbidden Lands Year-Zero Die Roller' ) includes localization for translations (I've included a translations folder as well as 'placeholder' language files. I wasn't sure if this was necessary...) stronghold page sheetworkers to handle auto-calcs e.g. attribute totals, encumbrance(gear, mounts gear, coins) Thanks to everyone for their help and contributions.
1554981930
Orphansmith
KS Backer
Marketplace Creator
Consumables doesn't appear to work.
1554992386
Vince
Pro
Sheet Author
Orphansmith said: Consumables doesn't appear to work. Oh...&nbsp; On my phone currently.&nbsp; Can you elaborate?&nbsp; Thanks
1555077495
Orphansmith
KS Backer
Marketplace Creator
Sure thing, When I make a new sheet and add resources, then click the roll action, I get the top response (about there being no token). If I add a token and click it, then click the resource button, I get the last two responses ("No character was found for 'selected'" twice, followed by a roll).
1555081654
Vince
Pro
Sheet Author
Thank you.&nbsp; I'll get that fixed ASAP.&nbsp;&nbsp;
1555087079
Orphansmith
KS Backer
Marketplace Creator
Thanks!
1555111832

Edited 1555111985
Vince
Pro
Sheet Author
@Orphansmith All of the sheet rolls are using " selected |" to pull the appropriate attribute values of a&nbsp; linked token .&nbsp; If a token isn't linked to a sheet (using "represents character" from the token settings) and you make the roll from the sheet, you'll see two errors ( No character was found for 'selected' ).&nbsp; One for the missing character name and another for the missing character sheet link.&nbsp; You will also get "selected|character_name" where the character name should be in the roll template. Also, if you don't select any token and make the roll from the sheet, you'll get the general error&nbsp; "You attempted to use a roll command looking for the value of a selected token, but no tokens are selected." I believe the Consumables roll is working, but you must select a linked token.
1555301752

Edited 1555305341
Vince
Pro
Sheet Author
@Orphansmith Upon more contemplation... I don't think there is really a reason that I need to include "selected|..." with the sheet rolls.&nbsp; By removing "selected|..." the rolls will work from the sheet regardless if a token has been linked.&nbsp; I'll go ahead and update with this week's pulls.&nbsp; Thanks for testing.&nbsp; ;-P <a href="https://github.com/Roll20/roll20-character-sheets/pull/4971" rel="nofollow">https://github.com/Roll20/roll20-character-sheets/pull/4971</a>
1555616862
Orphansmith
KS Backer
Marketplace Creator
Sweet, and thank you!