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

[sheet development] Creating a settings tab for handling optional rules

October 24 (4 years ago)
Onirim
Sheet Author

Hi coders,

I work actually on a character sheet compatible with many editions of Rolemaster (RM1, RM2, RMC, RMX and RMU). For achieving this, I keep the auto calculations to the minimum, but there are some of them to help managing the levelling process.

My problem is certain calculations need to be different between some versions or when using one of the optional (and frequently used) rules.

If I know how to make informations appear or disappear (I already use a tab system), I don't know if I can make a radio button for changing a calculation in an input.

For example, the hp_max input is calculated automatically in the input field like hp_base + hp_base * (mod_con / 100), rounded. But I want to have a setting for being able to disactivate the auto-calculation for some optional rules or edition. 

It is possible? Do you know a character sheet who use a system like this? 

Best regards,

October 24 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

If you use sheet workers to do that calculations, this is very simple.

You just set a checkbox on your settings tab, like

<input type="checkbox" name="attr_hp_calc_on" value="1" >

Then in the sheet worker, you get the stats you want to work with, and make sure you grab the hp_calc_on attribute. Then you can just do

if (!hp_calc_on) return;

That means the sheet will stop the sheet worker before it does any calculations, and will leave that alone.

The complete sheet worker would look something like this (some differences might be needed depending on how your sheet is built):

on('change:hp_base change:mod_con change:hp_calc_on', () => {
    getAttrs(['hp_base','mod_con','hp_calc_on'], values => {
        const base = parseInt( values.hp_base) || 0;
        const mod = parseInt( values.mod_con) || 0;
        const check = parseInt( values.hp_calc_on) || 0;
        // check to see if calculation is on or off. If off, end the sheet worker.
        if(!check) return;         // calculate hp
        const hp = Math.round(base * (1+ (mod / 100)));         // update hp attribute on sheet.
        setAttrs({
            hp: hp
        });
    });
});


If you're use autocalc fields instead, its a fair bit trickier and I really don't recommend it.

October 24 (4 years ago)
Onirim
Sheet Author

Thank you very much, it's working with HP, but I want to calc the max HP value of an attribute, and the Sheet Worker doesn't seems to recognize hp_max as a variable.

How can we put a value on the max attribute instead on the actual value of an attribute ? :)

October 24 (4 years ago)
Andreas J.
Forum Champion
Sheet Author
Translator

Onirim said:

Thank you very much, it's working with HP, but I want to calc the max HP value of an attribute, and the Sheet Worker doesn't seems to recognize hp_max as a variable.

How can we put a value on the max attribute instead on the actual value of an attribute ? :)

Weird, nothing in the Sheetworker documentation indicates there being anything special about the "_max" value. Please show us your code to see what's going on.

Did you manage to make "hp" work at least, even if "hp_max" didn't seem to be recognized?


October 24 (4 years ago)
Onirim
Sheet Author

It's working with hp value, yes! And this is so strange.

There are my inputs:

<label data-i18n="totalhp" style="width: 63px;">PdC total</label><input type="number" name="attr_hp_max" class='sheet-short'/>
<label data-i18n="actualhp">PdC actuels</label><input type="number" name="attr_hp" style="width: 70px;"/>

And the Sheet Worker

	on('change:hp_base change:base_constitution change:race_constitution change:totalhpcalculation', () => {
getAttrs(['hp_base','base_constitution','race_constitution','totalhpcalculation'], values => {
const base = parseInt( values.hp_base) || 0;
const basemod = parseInt( values.base_constitution) || 0;
const racemod = parseInt( values.race_constitution) || 0;
const check = parseInt( values.totalhpcalculation) || 0;
// check to see if calculation is on or off. If off, end the sheet worker.
if(!check) return;
// calculate hp
const hp = Math.round(base * (1+ ((basemod + racemod) / 100)));
// update hp attribute on sheet.
setAttrs({
hp: hp_max
});
});
});

And the error: 

ReferenceError: hp_max is not defined
    at Object.eval

Thank you for the help :)

October 24 (4 years ago)

Edited October 24 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

You have the setattrs reversed. It should be

                        setAttrs({
hp_max: hp
});

If you want to set hp and hp_max, you can do both at the same time

                        setAttrs({
hp_max: hp,                                 hp: hp
});
October 24 (4 years ago)
Andreas J.
Forum Champion
Sheet Author
Translator

GiGs said:

You have the setattrs reversed. It should be

                        setAttrs({
hp_max: hp
});

If you want to set hp and hp_max, you can do both at the same time

                        setAttrs({
hp_max: hp,                                 hp: hp
});

Huh, I wasn't even aware that attributes and their "_max" value are mapped like that in sheetworkers.

Don't think I've ever manipulated "_max" value in any self-crafted sheetworkers.

October 24 (4 years ago)

Edited October 24 (4 years ago)
Onirim
Sheet Author

That's work fine now, thank you very much :) Sorry for the dumb error ^^

Now I need to plug this with the useroption of the sheet.json. I've made a 

"useroptions": 
[
{
"attribute": "totalhpcalculation",
"displayname": "Type of Total HP calculations: ",
"type": "select",
"options": [
"Legacy calculation|1",
"No calculation|0"
],
"default": "1",
"description": "Choose the Total HP calculation. In Legacy, the Total HP equals the Base HP + Base HP * (constitution bonus / 100). If you choose No Calculations, the Total HP calculation is not calculated and you will need to enter the value manually."
}
]

Not really working, changing the option don't change anything in my attr_totalhpcalculation attribute and it not create this attribute.

Sadly I prefer to handle some options by the sheet.json, and some other options by a tab on the character sheet.

I need to add somethink in the html to handle this change ?

October 24 (4 years ago)
Andreas J.
Forum Champion
Sheet Author
Translator

Those options in the Sheet.json, when they exist on the Default Settings section of a campagin, only affects new characters created after the settings are changed. 

If you go in-game to the Settings tab, and scroll to the bottom where the Experimental Features section is, there is a button that says Apply Default Settings, that should apply the settings to all character sheets.

Unsure how well the last part can be tested in a Sheet Sandbox, but generally those things are only available when the sheet exist in the dropdown menu, as you can add sheet.json in your custom games.

October 25 (4 years ago)

Edited October 25 (4 years ago)
Onirim
Sheet Author

Thank you very much, all your advice are really nice, guys :)

I've another one to ask: I want to do the same autocalculation switch but with items on a repeating list. I imagine I can't use the attribute name like for a regular attribute, so how it can be accomplished ?

For example, I've a repeating list with skills and by default I calculate a total professional bonus by multiplying a professional bonus by the level of the character (because in legacy Rolemaster, professional bonus is given by level). But in some options and versions use a flat bonus, so I need to disactivate the multiplication. I now see how to do this, but I don't see how I can call an repeating list attribute.

Any advice for this ?

[EDIT] I've found the wiki pages for this, so I'll read all of them before asking, sorry !

October 25 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

If you're looking at the wiki, you'll need to look at the getSectionIDs function. 

October 25 (4 years ago)
Onirim
Sheet Author

So, I've found how to calculate the professional bonus when I modify the prof_skillname value in my repearing_skills list. It work as intended when I modify a value inside the repeating list. 

But when I modify the level value (who is not in the repeating list) or the switch value named professionbonuscalcmode, I've an error because the Sheet Worker need to know what line I want to calc. And I want to calculate all the profbonus_skillname in my entire repeating list :)

There is a way do ask the Sheet Worker to apply to all lines on the repeating list ?

	on('change:repeating_skills change:professionbonuscalcmode change:niveau', () => {
getAttrs(['repeating_skills_prof_skillname','niveau', 'professionbonuscalcmode'], values => {
const prof = parseInt( values.repeating_skills_prof_skillname) || 0;
const level = parseInt( values.niveau) || 0;
const check = parseInt( values.professionbonuscalcmode) || 0;
// check to see if calculation is on or off. If off, end the sheet worker.
if(check==0) {
const profbonus = prof*level;
setAttrs({
repeating_skills_profbonus_skillname: profbonus
});
}
else if(check==1) {
const profbonus = prof;
setAttrs({
repeating_skills_profbonus_skillname: profbonus
});
}
else return;
});
});
October 25 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

This is where the getSectionIDs function comes in.

There's a couple of odd things about your sheet worker.

Is professionbonuscalcmode a checkbox? If so it can only have a value of 1 or 0; but you have a third branch on your if(check) if statement.


More importantly, you have a potentially big problem with the repeating_skills_prof_skillname attribute. You have it in the change event line, and also in the setAttr line. This means you have an infinite loop here:

when this line runs:

const profbonus = prof*level;

the value will change. That will trigger the on(change) line, and the sheet worker will run again - then the above line will run again, then the new value will be saved, then the event will trigger again, and the sheet worker will run again...

This will run until the sheet worker crashes, and if level is above 1, the profbonus will skyrocket up to infinity.

You cant have an attribute update itself in this way. With that in mind, I present a sheet worker modified from your last post that handles multiple rows. IMPORTANT: this is not the sheet worker to use. It has the critical error of the infinite loop I just described. I am posting it just to show you the structure. I don't know what exactly is meant to happen here and don't know your sheet, so I dont have the information needed to suggest a correction to fix the error.


on('change:repeating_skills:prof_skillname change:professionbonuscalcmode change:niveau', () => {
    getSectionIDs('repeating_skills'ids => {
        // get an array of anmes for the skillname attribute across all rows.
        // getSectionIDs gives an array of the row ids.
        const fieldnames = [];
        ids.forEach(id => fieldnames.push(`repeating_skills_${id}_prof_skillname`));

        // now getAttrs can use that array to get the attributes from all rows
        getAttrs([...fieldnames,'niveau''professionbonuscalcmode'], values => {
            // get the values of the attributes outside the repeating section
            const level = parseIntvalues.niveau) || 0;
            const check = parseIntvalues.professionbonuscalcmode) || 0;
            
            // create a variable to hold all the stats that need to be updated
            const output = {};
            // now loop through the row ids again
            ids.forEach(id => {
                // get the value of the prof_skillname attribute for this specific row.
                const prof = parseIntvalues[`repeating_skills_${id}_prof_skillname`]) || 0;
                
                // check to see if calculation is 1 or 0. Use triple equality to match properly.
                if(check === 0) {
                    const profbonus = prof*level;
                    output[`repeating_skills_profbonus_skillname: profbonus`] = profbonus;
                } else if(check === 1) {
                    const profbonus = prof;
                    output[`repeating_skills_profbonus_skillname: profbonus`] = profbonus;
                }
            });
            // save the collected attributes.
            setAttrs(output);
        });
    });    
});



October 25 (4 years ago)

Edited October 25 (4 years ago)
Onirim
Sheet Author

professionbonuscalcmode  is not a checkbox, the values can be 0 or 1 for the moment, but I can add other calculations method in the future, so I can add others values.

For the loop thing, I don't have a loop when I use the Sheet Worker. I think the value is calculated only 1 time because there is only 1 possible result, so the if a loop appear, the value don't be changed more than 1 time (and is calculated 2 times yes). Or maybe the browser handle this sort of loop? Anyway, I don't see any warning about that in the browser console so... for the moment, no loop! (but really I will test in many ways).

It work with this code : 

	on('change:repeating_skills:prof_skillname change:professionbonuscalcmode change:niveau', () => {
    getSectionIDs('repeating_skills', ids => {
        // get an array of names for the skillname attribute across all rows.
        // getSectionIDs gives an array of the row ids.
        const fieldnames = [];
        ids.forEach(id => fieldnames.push(`repeating_skills_${id}_prof_skillname`));
        // now getAttrs can use that array to get the attributes from all rows
        getAttrs([...fieldnames,'niveau', 'professionbonuscalcmode'], values => {
            // get the values of the attributes outside the repeating section
            const level = parseInt( values.niveau) || 0;
            const check = parseInt( values.professionbonuscalcmode) || 0;
            
            // create a variable to hold all the stats that need to be updated
            const output = {};
            // now loop through the row ids again
            ids.forEach(id => {
                // get the value of the prof_skillname attribute for this specific row.
                const prof = parseInt( values[`repeating_skills_${id}_prof_skillname`]) || 0;
                
                // check to see if calculation is 1 or 0. Use triple equality to match properly.
                if(check === 0) {
                    const profbonus = prof*level;
                    output[`repeating_skills_${id}_profbonus_skillname`] = profbonus;
                } else if(check === 1) {
                    const profbonus = prof;
                    output[`repeating_skills_${id}_profbonus_skillname`] = profbonus;
                }
            });
            // save the collected attributes.
            setAttrs(output);
        });
    });    
});

I'm a happy man!

I will ask you again if I'm in trouble with the sheet :)

Regards,

November 01 (4 years ago)
Onirim
Sheet Author

Hi there!

I've learn so much about Sheet Workers, so now I can do the major changes I need.

But I'm stuck with an option for changing the background style color of my character sheet.

I've added a Sheet Worker for this and the code in the CSS file, but the background color doesn't change, like the CSS doesn't take the value of my attribute.

On the HTML page I've added this :

<input class="sheet-mainBgColor"   name="attr_mainBgColor"   type="hidden" value="default"/>

There is my option butter (inspired by the Ryuutama code for the moment, but I will change that once I get it work)

<label data-i18n="background-color" style="width: 200px;">Background color</label>:
    <select name="attr_mainBgColorSelect" value="default">
        <option value="default"   data-i18n="default"       style="background-color: #eeffff;" selected="selected">Default</option>
        <option value="white"     data-i18n="white"         style="background-color: #fff;">White</option>
        <option value="lilac"     data-i18n="lilac"         style="background-color: #fbeeff;">Lilac</option>
        <option value="grayscale" data-i18n="grayscale"     style="background-color: #f3f3f3;">Grayscale</option>
        <option value="book"      data-i18n="book"          style="background-color: #f3efe6;">Book</option>
        <option value="summer"    data-i18n="season-summer" style="background-color: #fef7ed;">Summer</option>
        <option value="fall"      data-i18n="season-fall"   style="background-color: #fde7e8;">Fall</option>
        <option value="winter"    data-i18n="season-winter" style="background-color: #fafefe;">Winter</option>
        <option value="spring"    data-i18n="season-spring" style="background-color: #f8f9f3;">Spring</option>
    </select>
<p data-i18n="backgroundcolordesc">Choose the main color of the character sheet (changes background and headers).</p>

On the HTML Sheet Worker section I've added this : 

on("change:mainBgColorSelect", function(event) {
            getAttrs(["mainBgColorSelect", "mainBgColor"], function(values) {
                setAttrs({
                    mainBgColor: values.mainBgColorSelect
         });
     });
 });

And in my CSS code, I've added this:

.charsheet {
	background-color: var(--main-bg-color);
	width: 1000px;
}
.charsheet {
    --main-bg-color: #eeffff;
    --header-bg-color: #aaeeaa;
    --divider-color: lightgray;
    --input-bg-color: #fff;
}

.sheet-mainBgColor[value="white"]       ~ .sheet-characterSheet { --main-bg-color: #fff;    --divider-color: #90939c; }
.sheet-mainBgColor[value="grayscale"]   ~ .sheet-characterSheet { --main-bg-color: #f3f3f3; --divider-color: #000; }
.sheet-mainBgColor[value="book"]        ~ .sheet-characterSheet { --main-bg-color: #f3efe6; --divider-color: #424242; }
.sheet-mainBgColor[value="summer"]      ~ .sheet-characterSheet { --main-bg-color: #fef7ed; --divider-color: #424242; }
.sheet-mainBgColor[value="fall"]        ~ .sheet-characterSheet { --main-bg-color: #fde7e8; --divider-color: #cd5142; }
.sheet-mainBgColor[value="winter"]      ~ .sheet-characterSheet { --main-bg-color: #fafefe; }
.sheet-mainBgColor[value="spring"]      ~ .sheet-characterSheet { --main-bg-color: #f8f9f3; --divider-color: #000; }
.sheet-mainBgColor[Value="lilac"]       ~ .sheet-characterSheet { --main-bg-color: #fbeeff; --divider-color: #000; }

I don't really understand where is the step I miss :)

Full code here :

https://github.com/Onirim/Roll20-Rolemaster-2nd-classic

If a kind developer can help!

Thanks :)

November 02 (4 years ago)

Edited November 02 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

The issue is that you are applying the style directly to the .charsheet class (or are you, that sheet-characterSheet in the CSS confuses me).

Remember that the input applying the style needs to be on the same "level" in the CSS hierarchy. And the charsheet class contains everything we create - we cant put anything on the same level as it.

Many, many sheets create a container div to contain everything on that sheet. You can then put the style on that div and put the input before it.

Like

<input class="sheet-mainBgColor"   name="attr_mainBgColor"   type="hidden" value="default"/>
<div class="everything-else">

    <!-- ALL the sheet code goes here -->

</div>

Then your styling would look like

div.sheet-everything-else {
	background-color: var(--main-bg-color);
	width: 1000px;
}
div.sheet-everything-else {
--main-bg-color: #eeffff; --header-bg-color: #aaeeaa; --divider-color: lightgray; --input-bg-color: #fff; }  
input.sheet-mainBgColor[value="white"]       ~ div.sheet-sheet-everything-else { --main-bg-color: #fff;    --divider-color: #90939c; } 


Obviously you can use a different class name than everything-else. Some I've used: .sheet-container, .sheet-wrapper.sheet-main


November 02 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

Also, you dont need the sheet worker. Just change your select name:

<select name="attr_mainBgColor">
        <option value="default"   data-i18n="default"       style="background-color: #eeffff;" selected >Default</option>

The value on the start line doesnt do anything - it's the selected property that sets the default value.

November 02 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

I would replace those styles in the option values with classes. Like

<option value="default"   data-i18n="default"       class="background-default" selected >Default</option>

Then in the CSS have

.sheet-background-default {
    background-color: #eeffff;
}

or

.sheet-background-default { background-color: #eeffff; }


This makes later changes to styling easier, and also allows you to reuse the style if you need to.


November 02 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

Thanks for asking this question, btw. I'd never actually used variables in CSS before, but I just added this to a simple sheet for a homebrew I'm preparing for my group, and it is very nifty. I applied it a little differently. I have a main wrapper div, and a bunch of divs inside that. I applied the class to the inside divs, so it looks like this, when the Fall colour is selected. 



November 02 (4 years ago)
Andreas J.
Forum Champion
Sheet Author
Translator

GiGs said:

Thanks for asking this question, btw. I'd never actually used variables in CSS before, but I just added this to a simple sheet for a homebrew I'm preparing for my group, and it is very nifty. I applied it a little differently. I have a main wrapper div, and a bunch of divs inside that. I applied the class to the inside divs, so it looks like this, when the Fall colour is selected.

I've used them in limited fashion, due to not managing to get the ".charsheet" thing to apply to the whole sheet back when I did things with it. Way back when I got started with the Free Spacer sheet(maybe Christoph have continues and expanded on that later), I did some stuff with it, but ran into trouble and then probably skipped most. more recently used a bit in roll templates for my Stargate sheet.

If doing that wrapper thing makes the CSS variables work anywhere inside the wrapper(regardless of depth) I'm glad, as that makes things easier.

November 02 (4 years ago)
Andreas J.
Forum Champion
Sheet Author
Translator

This color scheme switcher setting got me thinking, we could try to distill this into an simple example for creating a light/dark mode switch for a character sheet. Having a simple example of it would be be great.

November 02 (4 years ago)

Edited November 02 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter

A very simple light/dark mode could like this (though the colours I've used here are just switching black and white - you'd probably want less harsh colours). This is for a checkbox that just has 2 values. The hidden input might not be necessary - you could just check for :checked value, but I prefer using hidden inputs :)

CSS:

div.sheet-colour {
    background-color: var(--main-bg-color);
    color: var(--front-color);
} input[type="hidden"].sheet-colorselector[value="0"]  ~ div.sheet-container { 
    --back-color: white;
    --front-color: black; 
}
input[type="hidden"].sheet-colorselector[value="1"]  ~ div.sheet-container { 
    --back-color: black;
    --front-color: white; 
}


Then set up the HTML, which is really simple:

<input name="attr_backgroundcolour"   type="checkbox" value="1"/>    
<input class="colorselector"   name="attr_backgroundcolour"   type="hidden" value="0"/>    
<div class="container">
    
    <!-- all sheet code here -->
</div>
November 02 (4 years ago)
Onirim
Sheet Author

Thank you very much for your help :)
It's working!

November 02 (4 years ago)
Andreas J.
Forum Champion
Sheet Author
Translator

Added a link to the above example on the CSS Wizardry page

November 02 (4 years ago)
GiGs
Pro
Sheet Author
API Scripter


Onirim said:

Thank you very much for your help :)
It's working!

I'm glad to hear it :)