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

Set an input box max value to be the same as an attr?

1599530917

Edited 1599532725
Steve
Pro
Sheet Author
I am trying to set the maximum value of a number input to be the same as another attribute, meaning that the player can't elect to go below 0 defence when using this ability. This looks right to me, and mostly works as written, except for the max part (i.e. it goes up in increments of 3, won't go below 0, beserkatkpos calculates properly with the sheet worker, but I can 'click past' the value of defence). "Defence" is a whole number set in an earlier input box. <div class="barbarianmelee">     Berserk <input type="number" name="attr_beserkdefneg" step="3" min="0" max="@{defence}" value="0"> <span type="number" name="attr_beserkatkpos" ></span> </div>
1599544077
GiGs
Pro
Sheet Author
API Scripter
The min and max dont recognise attribute calls - they are just basic html. So that max is ignored. The reliable way to do this is in roll20 to have a sheet worker check the attribute when changed, and reset it to 0 if below, or defence score, if above. Something like: on('change: beserkdefneg', () => {     getAttrs([' beserkdefneg', 'defense'], v=> {         let def = parseInt(v.defense) || 0;         let defneg = parseInt(v.beserkdefneg) || 0;         let score = (defneg > def) ? def : (defneg < 0 ? 0 : defneg);         setAttrs({             beserkdefneg: score         }, {silent:true});     }); }); There's a couple of things going on here. This line:         let score = (defneg > def) ? def : (defneg < 0) ? 0 : defneg; is doing the same as this: let score = defneg; if(score > def) {     score = def; } else if (score < 0) {     score = 0; } Its a ternary operator , a way of doing if statements that return a value very compactly. The format is     let variable = (expression to check) ? (value if true) : (value if false); and you can chain multiple ternary expressions together. I've put a second in in the false value of the first one. The second part: in the setAttrs function there's a second part: {silent:true}. This means the sheet worker wont trigger itself. So it will change the value on the sheet, and not cause the same worker to trigger again. Just a small efficiency optimisation - you often want a sheet worker to trigger other changes, but not here. It does check the below 0 value, because there are ways to set a value below 0 even with the the min=0.
1599561399
Steve
Pro
Sheet Author
Thanks for clarifying GiGs! however the script still doesn't seem to work. It always reverts to a value of 0, no matter what I set the input box too. (Had to change spelling of defense back to defence :p )
1599563452

Edited 1599563535
GiGs
Pro
Sheet Author
API Scripter
haha, the defence spelling is funny because being British, I always type it with a c in my own writing, but can randomly use s or c when typing on international forums. Sorry about that! PS: if your name had been MediaevalSteve, I likely wouldnt have made that mistake...
1599563962

Edited 1599564150
Steve
Pro
Sheet Author
Yeah GiGs, I'm an Aussie so I get to pick and choose wether I use UK/US spellings of words on whatever whim takes me at the time. Sorry for sending mixed messages there. I made a couple of minor improvements to your excellent code, thought I might share for anybody else finding this answer in the future. on('change:beserkdefneg', () => {     getAttrs(['beserkdefneg', 'defencemodded'], v=> {         let def = parseInt(v.defencemodded) || 0;         let defneg = parseInt(v.beserkdefneg) || 0;         let score = (defneg > def) ? def : (defneg < 0 ? 0 : defneg);         let atkpos = Math.floor(score / 3);         setAttrs({             beserkdefneg: Math.floor(score/3)*3,             beserkatkpos: atkpos         }, {silent:true});     }); }); The mechanic behind this is: for every 3 points you drop your defence, you gain 1 attack. The sheet worker ensures that you can't go negative, and can't take more negatives than your total defence score. By adding the Math.floor(score/3)*3 calculation, it also ensures that you don't take a partial negative to your defence eg, if you have a defence score of 10 it will only let you take a total of a -9 because taking -10 has not benefit.
1599566300
GiGs
Pro
Sheet Author
API Scripter
To be fair, mediaeval is archaic even in the UK :) Nice work :)
1599615772
Steve
Pro
Sheet Author
Allright, I now feel like I am going insane... Besides the Bloodrage being pointless at the moment as I amstill building that function), why does the first one work but the next two don't? As far as I can tell the syntax is exactly the same, so they should all equally work? on('change:beserkdefneg', () => {     getAttrs(['beserkdefneg', 'defencemodded'], v=> {         let def = parseInt(v.defencemodded) || 0;         let defneg = parseInt(v.beserkdefneg) || 0;         let score = (defneg > def) ? def : (defneg < 0 ? 0 : defneg);         let atkpos = Math.floor(score / 3);         setAttrs({             beserkdefneg: Math.floor(score/3)*3,             beserkatkpos: atkpos,             bloodrageatkpos: def,             bloodragedefneg: def         }, {silent:true});     }); }); on('change:precisedefneg', () => {     getAttrs(['precisedefneg','defencemodded'], v=> {         let precdef = parseInt(v.defencemodded) || 0;         let precdefneg = parseInt(v.precisedefneg) || 0;         let precscore = (precdefneg > precdef) ? precdef : (precdefneg < 0 ? 0 : precdefneg);         let precatkpos = Math.floor(score / 3);         setAttrs({             precisedefneg: Math.floor(precscore/3)*3,             preciseatkpos: precatkpos         }, {silent:true});     }); }); on('change:stilnessdefneg', () => {     getAttrs(['stillnessdefneg', 'defencemodded'], v=> {         let stilldef = parseInt(v.defencemodded) || 0;         let stilldefneg = parseInt(v.stillnessdefneg) || 0;         let stillscore = (stilldefneg > stilldef) ? stilldef : (stilldefneg < 0 ? 0 : stilldefneg);         let stillstlpos = Math.floor(stillscore / 2);                setAttrs({             stillnessdefneg: Math.floor(stillscore/2)*2,             stillnessstlpos: stillstlpos,         }, {silent:true});     }); });
1599627460

Edited 1599635188
GiGs
Pro
Sheet Author
API Scripter
In the second one you use Math.floor(score/3) but score isnt defined there. That will cause that worker to fail. In the 3rd worker you have a different error -  setAttrs({             stillnessdefneg: Math.floor(stillscore/2)*2,             stillnessstlpos: stillstlpos, // <-- that comma         }, {silent:true}); The last attribute you set in setAttrs must not have a comma at the end. That should be setAttrs({             stillnessdefneg: Math.floor(stillscore/2)*2,             stillnessstlpos: stillstlpos         }, {silent:true}); I hope also there's a typo here:  stillnessstlpos . I think that should be  stillnessatlpos
1599629670

Edited 1599635111
GiGs
Pro
Sheet Author
API Scripter
If you have multiple sets of attributes doing the same thing, it can be worth it to build a Universal Sheet Worker . It looks as though yours has some variations like the bloodrage stuff in the first one. Though it looks like bloodrage isnt really dependent on  Since they each depend on defencemodded, and one other stat, another approach to improve efficiency would be to put them into one sheet worker. At the moment, whenever defencemodded changes, that triggers 3 separate sheet workers. If you made a single sheet worker, it would only trigger one. Though that makes me notice you dont have defencemodded on the change line - which you really should have. Changes in that attribute wont change these scores otherwise. Here's an all-in-one sheet worker that does all the work of three workers you posted above: on('change:defencemodded change:beserkdefneg change:precisedefneg change:stilnessdefneg', () => {     getAttrs(['beserkdefneg', 'precisedefneg', 'stillnessdefneg', 'defencemodded'], v=> {         const stats = ['precise', 'beserk', 'stillness'];         const output = {};         const def = parseInt(v.defencemodded) || 0;         stats.forEach(stat => {             let neg = parseInt(v[`${stat}defneg`]) || 0;              let score = (neg > def) ? def : (neg < 0 ? 0 : neg);             let factor = (stat === 'stillness') ? 2 : 3;             let pos = Math.floor(score / factor);             output[`${stat}defneg`] = Math.floor(score/factor)*factor;             output[`${stat}atkpos`] = pos;         });         output['bloodrageatkpos'] = def;         output['bloodragedefneg'] = def;         setAttrs(output, {silent:true});     }); }); I'd love to explain all the bits of how it works, but dont have time right now. Ask questions about anything that confuses you and I'll reply when i have time :) Comparing to your original sheet workers should reveal a lot.
1599629717
GiGs
Pro
Sheet Author
API Scripter
ps: berserk is misspelled in your attribute names...
1599637033

Edited 1599637049
Steve
Pro
Sheet Author
Thanks again Gigs, I spent hours staring at those lines and missing those two bloody errors. I also repeated my spelling mistake over and over though, so I suppose it's not that surprising. I can see how the Universal Sheet Worker operates, now that you have set it out for me. I'm pretty sure I understand the logic of the language, I just don't know the language yet (and seem to have some kind of brain blockage when it comes to editing).
1599640700
GiGs
Pro
Sheet Author
API Scripter
I mentioned in another post yesterday: one very handy thing to do when writing javascript is to use an editor that includes syntax checking, like VS Code or SublimeText. So instead of writing in notepad or whatever, writing in those, and they'll show you common syntax errors and show corrections. I just copied your code above into a .js file open in VS Code, and it highlighted the errors and told me what they were. (I spotted the berserk typo myself - such editors wont do everything!). Those syntax errors are really  easy to mistake, and i know the feeling of spending forever searching code to find one you know exists somewhere . These editors wont fix everything, but they cut down on so much of that.
1599648805
Steve
Pro
Sheet Author
Thanks for that advice. I am on a Mac and have been using BBEdit at somebody else's advice, but just downloaded VS Code and, wow, is it better.
1599650340
GiGs
Pro
Sheet Author
API Scripter
One downside of VS Code is that it doesnt do the error checking of sheet workers within a html page, you have to load it in a separate js file to get proper syntax support. Apparently sublimetext does support that (or there's a way to make it support it). I still prefer vs code for its other features, but it's something to be aware of.
1599659558
Oosh
Sheet Author
API Scripter
GiGs said: One downside of VS Code is that it doesnt do the error checking of sheet workers within a html page, Yeah, but GiGs does.
1599661441
Andreas J.
Forum Champion
Sheet Author
Translator
Copy-pasting your JavaScript into this closure compiler is also an alternative to check for errors: <a href="https://closure-compiler.appspot.com/home" rel="nofollow">https://closure-compiler.appspot.com/home</a>
1599661848
GiGs
Pro
Sheet Author
API Scripter
Oosh said: GiGs said: One downside of VS Code is that it doesnt do the error checking of sheet workers within a html page, Yeah, but GiGs does. lol, true :) 📜🗡Andreas J.🏹📜 said: Copy-pasting your JavaScript into this closure compiler is also an alternative to check for errors: <a href="https://closure-compiler.appspot.com/home" rel="nofollow">https://closure-compiler.appspot.com/home</a> I find VS Code's reports much easier to use than closure compiler, and they give instant feedback. But closer compiler is great if you need a browser solution.
1599695318
Steve
Pro
Sheet Author
Cheers Andreas, I was using JSHint, but the error codes it was giving me didn't seem to make much sense to me. Okay, my next question related to the same code. I want it to do this: Let hunterneg = (preciseneg + stillnessneg) If &nbsp; hunterneg &lt; def then ok to proceed else &nbsp; don’t allow change ((and if possible, display text “Total Negative Modifiers to Defence can not be greater than your current Defence score”.)) Should I use a Switch block to do so? I am trying to learn how to do this properly (I'm using W3 schools as I go), so if you could point me in the right direction to start and give me a couple of hints, I'll have a go at writing it myself.
1599711800
GiGs
Pro
Sheet Author
API Scripter
It's not clear exactly what its doing. "ok to proceed" - what happens after? Where do you want to display that text?&nbsp; &nbsp;&nbsp;
1599712847
Steve
Pro
Sheet Author
Sorry about being unclear, hopefully this helps: In this case I want the sheet to check if the player is trying to do something illegal and block them. The player is allowed to use both (Precise Shot and Stillness) abilities at the same time, but are not allowed to take more negatives than their total Defence score (eg, Total Defence is 12, they can take a -6 for +2 Attack, and another -6 for +3 Stealth; but can't take -12 for +4 Attack and another -12 for +6 Stealth). So if the total input negatives from precise + total negatives from stillness are under Defence then the sheet allows it, but if the player tries to input more negatives than they are allowed the sheet does not allow the change. Ideally then I'd have an error message (of any kind, eg a popup window) saying why it was disallowed.
1599714212
GiGs
Pro
Sheet Author
API Scripter
You might be getting a bit ambitious for roll20 character sheets. You cant have popups as such. If you want to provide information from a sheet worker, you need to put that information in an attribute value. You can achieve the effect of a popup with a mixture of html, css, and sheet workers. For instant, update an attribute with some text, and in the html assign a class to the attribute, then in CSS check the value of that class, and if it matches, have the text styled in such a way that it appears on the character sheet like a popup. The only way to block players assigning certain values, is to make sure your sheet worker limits those values within an allowed range. If you allow illegal values to be assigned, you could disable the button (via CSS) so it cant be rolled directly - but if players have created their own macro, they can still use that. If you can list out the steps you want to happen - including attribute names (as they are in the character sheet html) and values precisely - we can provide better help. But this sounds like the kind of thing that you would either handle by talking (as you would in a face-to-face rpg game), or with a custom API script which gives more control and flexibility than character sheets. Or by handling it purely through html and the roll macro. I recommend handling it by talking - just telling players they cant take a bigger modifier than is allowed. I would suggest limiting it in the roll formula, by having a query that asks for the penalty they take, and then uses the drop highest roll modifier to ensure it is never higher than the defence. But it looks like they can spend defence in more than one way, so it wont work for that.
1599718284
Steve
Pro
Sheet Author
No worries, I probably am trying to cram in too many rules for a character sheet. I suppose I should let the players do something themselves sometimes.
1599718834
GiGs
Pro
Sheet Author
API Scripter
How else are they going to hang themselves, if you dont give them just enough rope and step back to watch what happens?
1599832386
Steve
Pro
Sheet Author
Allrighty then... I have checked and compiled and checked this, and everything is coming up green but it is not returning a value for finalattack and finaldefence on('change:attack change:defence change:attackmodded change:defencemodded change:berserkneg change:preciseneg change:stillnessneg change:bloodrageselect change:nonprofselect change:deathvowselect sheet:opened', () =&gt; { getAttrs(['defencemodded','attackmodded','berserkneg','berserkpos','preciseneg','precisepos','stillnessneg','bloodrageselect','nonprofselect','deathvowselect'], v=&gt; { const output = {}; const def = parseInt(v.defencemodded) || 0; const atk = parseInt(v.attackmodded) || 0; let brs = parseInt(v.bloodrageselect) || 0;; let atm = parseInt(v.attackmodded) || 0;; let dfm = parseInt(v.defencemodded) || 0;; let bep = parseInt(v.berserkpos) || 0;; let ben = parseInt(v.berserkneg) || 0;; let nes = parseInt(v.nonprofselect) || 0;; let dvs = parseInt(v.deathvowselect) || 0;; let prp = parseInt(v.precisepos) || 0;; let prn = parseInt(v.preciseneg) || 0;; let stn = parseInt(v.stillnessneg) || 0;; let brm = (brs = 1) ? (dfm) : (0); output[finalattack] = Math.round(atm += bep += brm -= nes += dvs += prp); output[finaldefence] = Math.round(dfm -= ben -= brm -= prn -= stn); setAttrs(output, {silent:true}); }); });
1599839616
GiGs
Pro
Sheet Author
API Scripter
First the non-breaking issues: You have a lot of double semi-colons there (;;), that wont break it but are unneeded. You are using very short variable names - especially when you are using you many, its very hard to tell what is what. VS Code tells me the first two (def and atk) aren't actually used, so its possible you have gotten confused over what each is. A rule of thumb: variable names should be long enough to understand what they mean. In early programming years, it was often necessary to keep variable names short - those years are long gone. You can make variable names 50-letters long if necessary to help you remember what they are for. Finally, the {silent:true} in setAttrs: this might not be a problem, but you need to make sure its actually necessary when you use it. Don't include it in workers automatically. When you include it, it means none of the attributes changed in this worker will trigger other sheet workers - but you often want other sheet workers to respond to attribute changes. Now the breaking issues. All three lines below are faulty. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;brm&nbsp;=&nbsp;(brs&nbsp;=&nbsp;1)&nbsp;?&nbsp;(dfm)&nbsp;:&nbsp;(0); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output[finalattack]&nbsp;=&nbsp;Math.round(atm&nbsp;+=&nbsp;bep&nbsp;+=&nbsp;brm&nbsp;-=&nbsp;nes&nbsp;+=&nbsp;dvs&nbsp;+=&nbsp;prp); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output[finaldefence]&nbsp;=&nbsp;Math.round(dfm&nbsp;-=&nbsp;ben&nbsp;-=&nbsp;brm&nbsp;-=&nbsp;prn&nbsp;-=&nbsp;stn); The first line should be let&nbsp;brm&nbsp;=&nbsp;(brs&nbsp;===&nbsp;1)&nbsp;?&nbsp;(dfm)&nbsp;:&nbsp;(0); In javascript, a single = always&nbsp; assigns&nbsp; a value. So with&nbsp;&nbsp; (brs&nbsp;=&nbsp;1) &nbsp;it is not checking if brs equals 1 - it is setting brs to a value of 1. That means the true value (dfm) is always used. You need to use double or triple equality to do a comparison. == checks the value, but not the type, and is inherently unsafe. Strings will be coerced to integers, and other data types will be transformed to find a match. You almost always dont want this to happen. === compares the value and the data type, so&nbsp; (brs&nbsp;===&nbsp;1) &nbsp;will return true if brs is 1, but false if brs is "1" (a string). If you're writing your script properly, the data should be in the correct data type, and when its in the wrong data type, it is a clue something is wring so its best to use the triple equality. Also, this will cause an error: atm += bep += brm -= nes += dvs += prp The += and -= shorthand are great, but you can only have one of them. It's useful when you are doing something like a = a + 3; You can instead do one of a += 3; a =+ 3; But when you are adding a chain of things together, it's just arithmetic and you use the normal rules: atm + bep + brm - nes + dvs + prp Finally, (and this one is very tricky): output[finalattack] This syntax in itself is fine, when you have a variable called finalattack, which has a value.&nbsp;But there is no finalattack variable here. You actually are wanting to give the string, 'finalattack'. That should be output['finalattack'] or output.finalattack So that sheet workers should probably &nbsp; be on('change:attack&nbsp;change:defence&nbsp;change:attackmodded&nbsp;change:defencemodded&nbsp;change:berserkneg&nbsp;change:preciseneg&nbsp;change:stillnessneg&nbsp;change:bloodrageselect&nbsp;change:nonprofselect&nbsp;change:deathvowselect&nbsp;sheet:opened',&nbsp;()&nbsp;=&gt;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;getAttrs(['defencemodded','attackmodded','berserkneg','berserkpos','preciseneg','precisepos','stillnessneg','bloodrageselect','nonprofselect','deathvowselect'],&nbsp;v=&gt;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;output&nbsp;=&nbsp;{}; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;brs&nbsp;=&nbsp;parseInt(v.bloodrageselect)&nbsp;||&nbsp;0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;atm&nbsp;=&nbsp;parseInt(v.attackmodded)&nbsp;||&nbsp;0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;dfm&nbsp;=&nbsp;parseInt(v.defencemodded)&nbsp;||&nbsp;0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;bep&nbsp;=&nbsp;parseInt(v.berserkpos)&nbsp;||&nbsp;0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;ben&nbsp;=&nbsp;parseInt(v.berserkneg)&nbsp;||&nbsp;0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;nes&nbsp;=&nbsp;parseInt(v.nonprofselect)&nbsp;||&nbsp;0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;dvs&nbsp;=&nbsp;parseInt(v.deathvowselect)&nbsp;||&nbsp;0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;prp&nbsp;=&nbsp;parseInt(v.precisepos)&nbsp;||&nbsp;0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;prn&nbsp;=&nbsp;parseInt(v.preciseneg)&nbsp;||&nbsp;0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;stn&nbsp;=&nbsp;parseInt(v.stillnessneg)&nbsp;||&nbsp;0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let&nbsp;brm&nbsp;=&nbsp;(brs&nbsp;===&nbsp;1)&nbsp;?&nbsp;(dfm)&nbsp;:&nbsp;(0); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output['finalattack']&nbsp;=&nbsp;Math.round(atm&nbsp;+&nbsp;bep&nbsp;+&nbsp;brm&nbsp;-&nbsp;nes&nbsp;+&nbsp;dvs&nbsp;+&nbsp;prp); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output['finaldefence']&nbsp;=&nbsp;Math.round(dfm&nbsp;-&nbsp;ben&nbsp;-&nbsp;brm&nbsp;-&nbsp;prn&nbsp;-&nbsp;stn); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setAttrs(output,&nbsp;{silent:true}); &nbsp;&nbsp;&nbsp;&nbsp;}); });&nbsp; With the caveat that you probably dont want the {silent:true} part, and you need to check those arithmetic sections are doing what you want. I notice that in finaldefence, you have -brm, and brm sometimes equals dfm (defencemodded), so you might have in there dfm - dfm, or 0. I just want to check that's intended.
1599879985
Steve
Pro
Sheet Author
Thanks GiG's, the double semis have come from so much copy/paste in compile/syntax checking, as did the redundant def and atk. I've expanded the var names to be more descriptive, that was just me being lazy. I dumped silent:true &nbsp;from this, my understanding of it was faulty -&nbsp; I thought it just stopped the script from listening to itself and looping by accident, but it's unnecessary anyway (I am pretty sure...). the += stuff was added late in the game as a desperate 'why isn't this calculation working!', thanks for clarifying how those symbols work. The calculation cancelling itself is intentional (dfm-brm), if a certain ability checkbox is selected then their defence score is negated and their attack score is increased in an all or nothing attack. I have now got it working, I tried your fixed code first and still didn't get the calculation to do what I wanted it to. So I then reverted back to 'un-fancy' code. This is what it looks like now: on('change:attack change:defence change:attackmodded change:defencemodded change:berserkneg change:preciseneg change:stillnessneg change:bloodrageselect change:nonprofselect change:deathvowselect sheet:opened', function() { getAttrs(['finalattack','finaldefence','defencemodded','attackmodded','berserkneg','berserkpos','preciseneg','precisepos','stillnessneg','bloodrageselect','nonprofselect','deathvowselect'], function(v) { let bloodrage = parseInt(v.bloodrageselect) || 0; let attackmod = parseInt(v.attackmodded) || 0; let defencemod = parseInt(v.defencemodded) || 0; let berserkattack = parseInt(v.berserkpos) || 0; let berserkdefence = parseInt(v.berserkneg) || 0; let nonprofmod = parseInt(v.nonprofselect) || 0; let deathvowmod = parseInt(v.deathvowselect) || 0; let preciseattack = parseInt(v.precisepos) || 0; let precisedefence = parseInt(v.preciseneg) || 0; let stillnessdefence = parseInt(v.stillnessneg) || 0; let bloodragemod = (bloodrage === 1) ? (defencemod) : (0); let fatk = Math.round(attackmod + berserkattack - nonprofmod + deathvowmod + preciseattack + bloodragemod); let fdef = Math.round(defencemod - berserkdefence - precisedefence - stillnessdefence - bloodragemod); setAttrs({ "finalattack": parseInt(fatk), "finaldefence": parseInt(fdef) }); }); }); It might not be optimal now, but it works :)
1599893643
GiGs
Pro
Sheet Author
API Scripter
If it works, it works! :) The setAttrs can be simplified: setAttrs({ finalattack: fatk, finaldefence: fdef }); You dont need quotes around those attribute names. As long as the attribute name contains only characters that are legal for javascript variable names, you don't need to quotes. Basically numbers, letters, and the underscore are legal, the dash is not. You also dont need to parseInt on those variables, because they are made by arithmetic from variables which are all already integers. javascript doesnt change the data type if all types are the same, so fatk and fdef are automatically integers. This has made me notice you dont actually need Math.round. All of the numbers are integers (that is, whole numbers without decimals or fractions), so there's no need to round them off. Those lines should be &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let fatk = attackmod + berserkattack - nonprofmod + deathvowmod + preciseattack + bloodragemod; let fdef = defencemod - berserkdefence - precisedefence - stillnessdefence - bloodragemod;
1599963983

Edited 1599964141
Steve
Pro
Sheet Author
So GiGs... I was following a piece of code you gave somebody else on the forums here , and modifying it for my own purposes but it doesn't seem to work for me. I just want to grab the value of "magicmeleeweaponbonus" if the checkbox "equippedwep" is ever ticked in a repeating section: on("change:repeating_weapons:equippedwep", function(eventInfo) { getAttrs(["repeating_weapons_magicmeleeweaponbonus","repeating_weapons_equippedwep"], function(v) { let check = parseInt(v.repeating_weapons_equippedwep)||0; if(check === 1) { let weaponbonus = parseInt(v.repeating_weapons_magicmeleeweaponbonus)||0; setAttrs({ "magicweaponbonus": weaponbonus, "repeating_weapons_equippedwep":0 }); } }); }); Here's the HTML to go with it: &lt;div class="weapons"&gt; &lt;label&gt;Weapons &lt;/label&gt; &lt;fieldset class="repeating_weapons"&gt; &lt;input class="eightmidtextinput" type="text" name="attr_Weapon" value="0" /&gt; Magic Bonus &lt;input type="number" name="attr_magicmeleeweaponbonus" min="0" max="3" value="0" /&gt; Armour Bypass Roll &lt;input class="fourshorttextinput" type="text" name="attr_ABR" value="0" /&gt; Damage &lt;input class="fourshorttextinput" type="text" name="attr_Damage" value="0" size="4" /&gt; Short &lt;input type="number" name="attr_WeaponShortRange" value="0" /&gt; Medium &lt;input type="number" name="attr_WeaponMediumRange" value="0" /&gt; Long &lt;input type="number" name="attr_WeaponLongRange" value="0" /&gt; Equipped &lt;input type="checkbox" name="attr_equippedwep" value="1" /&gt; &lt;input type="hidden" name="attr_WeaponWeight" value="1" /&gt; &lt;/fieldset&gt; Current Equipped Weapon Bonus &lt;input type="number" name="attr_magicweaponbonus" value="0" /&gt; &lt;label&gt;Ammunition &lt;/label&gt; &lt;fieldset class="repeating_ammo"&gt; Ammunition Type &lt;input class=sixteenlongtextinput type="text" name="attr_ammotype" value="0" /&gt; Quantity &lt;input type="number" name="attr_ammoqty" value="0" /&gt; &lt;/fieldset&gt; &lt;/div&gt; I was also trying to figure out how I could use your code from&nbsp; here &nbsp;to solve the 'quick and dirty' issue of the boxes unchecking themselves.
1599976366
GiGs
Pro
Sheet Author
API Scripter
The boxes are unchecking themselves because of the second attribute in setAttrs. If you remove that, they wont uncheck, like so &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;setAttrs({ "magicweaponbonus": weaponbonus }); But that will create a new problem. If you have a repeating section with multiple rows, and a user checks the box on one row, that magic bonus will get put in the magicweaponbonus. But then if the user checks a different row, a new magicweaponbonus will be filled in, but the old one in the repeating section will still be checked. You'll now have have that checkbox in two separate rows checked. If you want only the current weapon checked, you'll need to use getSectionIDs, so when one box is checked, the other boxes are unchecked. That code isnt too hard. But before working on that, back to your current problem: you said the code above isnt working for you. Is it doing anything? Without testing, it looks like it should work.
1599980158
Steve
Pro
Sheet Author
That'll teach me to not start a new testing session each time I login, after I posted the above I closed everything and logged off for a while. Now that it's all properly restarted the code is working as intended. I have tried playing around with the getSectionIDs code from the above linked post, but don't truly understand it. As far as I can tell, this is the basics of it which I need to incorporate into the script above: on("change:repeating_weapons:equippedwep", function (event) { if (event.sourceType === 'sheetworker' || event.newValue === '0' ) return; const playerclickedid = event.sourceAttribute.split('_')[2] || ''; getSectionIDs("repeating_weapons", function (ids) { const output = {}; ids.forEach(id =&gt; { output[`repeating_weapons_${id}_equippedwep`] = (playerclickedid.toLowerCase() == id.toLowerCase()) ? 1 : 0; }); setAttrs(output, {silent: true}); }); });
1599984921

Edited 1599985124
GiGs
Pro
Sheet Author
API Scripter
Your script has different demands than the previous one, so the event.newValue === '0' part isnt appropriate.That stops the sheet worker running when someone unchecks the checkbox. But you probably want the worker to keep running, and empty the magicweaponbonus, because nothing is now selected. Give this untested sheetworker a try (I left VS Code's color coding intact because it makes it a bit more readable, I think - anything in green is an explanatory comment that you can delete, or leave in place for reference) on ( 'change:repeating_weapons:equippedwep' ,&nbsp; function ( event )&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;stop&nbsp;the&nbsp;worker&nbsp;if&nbsp;it&nbsp;is&nbsp;being&nbsp;triggered&nbsp;by&nbsp;itself! &nbsp;&nbsp;&nbsp;&nbsp; if &nbsp;( event . sourceType &nbsp;===&nbsp; 'sheetworker' &nbsp;)&nbsp; return ; &nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;get&nbsp;the&nbsp;id&nbsp;of&nbsp;the&nbsp;row&nbsp;just&nbsp;clicked &nbsp;&nbsp;&nbsp;&nbsp; const &nbsp; playerclickedid &nbsp;=&nbsp;( event . sourceAttribute . split ( '_' )[ 2 ]&nbsp;||&nbsp; '' ). toLowerCase ();&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;now&nbsp;loop&nbsp;through&nbsp;the&nbsp;repeating&nbsp;section&nbsp;the&nbsp;first&nbsp;time. &nbsp;&nbsp;&nbsp;&nbsp; getSectionIDs ( 'repeating_weapons' ,&nbsp; function &nbsp;( ids )&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const &nbsp; fieldnames &nbsp;=&nbsp;[]; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;build&nbsp;an&nbsp;array&nbsp;of&nbsp;all&nbsp;attributes&nbsp;within&nbsp;the&nbsp;repeating&nbsp;section&nbsp;we&nbsp;need&nbsp;to&nbsp;look&nbsp;at &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ids . forEach ( id &nbsp; =&gt; &nbsp; fieldnames . push ( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; `repeating_weapons_ ${ id } _equippedwep` , &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; `repeating_weapons_ ${ id } _magicmeleeweaponbonus` &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;now&nbsp;get&nbsp;the&nbsp;values&nbsp;of&nbsp;all&nbsp;those&nbsp;attributes &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getAttrs ( fieldnames ,&nbsp; values &nbsp; =&gt; &nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;create&nbsp;a&nbsp;variable&nbsp;to&nbsp;hold&nbsp;the&nbsp;values&nbsp;of&nbsp;all&nbsp;attributes&nbsp;we&nbsp;want&nbsp;to&nbsp;change &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const &nbsp; output &nbsp;=&nbsp;{}; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;now&nbsp;loop&nbsp;through&nbsp;each&nbsp;row&nbsp;of&nbsp;the&nbsp;section&nbsp;again,&nbsp;this&nbsp;time&nbsp;to&nbsp;make&nbsp;changes &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ids . forEach ( id &nbsp; =&gt; &nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;get&nbsp;the&nbsp;values&nbsp;of&nbsp;the&nbsp;attributes&nbsp;on&nbsp;the&nbsp;current&nbsp;row&nbsp;in&nbsp;case&nbsp;needed &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const &nbsp; checkbox &nbsp;=&nbsp; parseInt ( values [ `repeating_weapons_ ${ id } _equippedwep` ])&nbsp;||&nbsp; 0 ; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const &nbsp; magicbonus &nbsp;=&nbsp; parseInt ( values [ `repeating_weapons_ ${ id } _magicmeleeweaponbonus` ])&nbsp;||&nbsp; 0 ; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;now&nbsp;check&nbsp;if&nbsp;row&nbsp;id&nbsp;matches&nbsp;the&nbsp;row&nbsp;the&nbsp;player&nbsp;clicked &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ( id . toLowerCase ()&nbsp;===&nbsp; playerclickedid )&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;if&nbsp;this&nbsp;checkbox&nbsp;is&nbsp;checked,&nbsp;use&nbsp;this&nbsp;magicbonus,&nbsp;otherwise&nbsp;set&nbsp;it&nbsp;to&nbsp;0. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; output . magicweaponbonus &nbsp;=&nbsp; checkbox &nbsp;?&nbsp; magicbonus &nbsp;:&nbsp; 0 ; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;if&nbsp;its&nbsp;any&nbsp;other&nbsp;row,&nbsp;and&nbsp;the&nbsp;box&nbsp;for&nbsp;this&nbsp;row&nbsp;is&nbsp;checked&nbsp;we&nbsp;need&nbsp;to&nbsp;uncheck&nbsp;it &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp; else &nbsp; if ( checkbox )&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; output [ `repeating_weapons_ ${ id } _equippedwep` ]&nbsp;=&nbsp; 0 ; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //now&nbsp;update&nbsp;the&nbsp;sheet&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setAttrs ( output ); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}); &nbsp;&nbsp;&nbsp;&nbsp;}); });
1599988212

Edited 1599988702
Steve
Pro
Sheet Author
Perfect, cheers GiGs. I love the comments, I'll be able to adapt it later when I need to.
1599988317
GiGs
Pro
Sheet Author
API Scripter
great :)