Files
roll20-character-sheets-tester/Supers-Revised-Edition/supers-revised.html
T
2021-09-19 19:16:41 -06:00

535 lines
27 KiB
HTML

<input type='hidden' name='attr_modifier_query' title='@{modifier_query}' value='+[[0?{+/- d6 Modifier|0}d6]]'>
<input type='hidden' name='attr_sheet_view' title='@{sheet_view}' value='character' class='section-control'><input type='hidden' name='attr_template_start' title='@{template_start}' value='&{template:supers} {{character_name=@{character_name}}} {{character_id=@{character_id}}}'>
<div class="main">
<!-- logo -->
<input type="hidden" name="attr_version">
<div class="section logo">
</div>
<div class='section version'>
<h5>Sheet Version</h5>
<span name='attr_version'>1.2</span>
</div>
<label class='input-label automation'>
<span>Success Automation</span>
<input type='checkbox' name='attr_automation' value='1' checked>
</label>
<span class='toggle'>
<h5>Sheet Type</h5>
<label class='input-label'>
<span>Character</span>
<input type='radio' name='attr_sheet_view' title='@{sheet_view}' value='character' checked>
</label>
<label class='input-label'>
<span>Mook</span>
<input type='radio' name='attr_sheet_view' title='@{sheet_view}' value='mook'>
</label>
<select class='mook-type' name='attr_mook_type' title='@{mook_type}'>
<option value='mook' selected>Mook</option>
<option value='citizen'>Citizen</option>
<option value='henchman'>Henchman</option>
<option value='supporting cast'>Supporting Cast</option>
</select>
</span>
<div class='character'>
<!-- name -->
<div class="section name">
<label class='input-label'>
<input class="bold" type="text" name="attr_character_name" title='@{character_name}' />
<h6>Hero Name</h6>
</label>
<label class='input-label'>
<input class="bold" type="text" name="attr_real_name" title='@{real_name}' />
<h6>Real Name</h6>
</label>
</div>
<!-- resistances -->
<div class="section resistances border">
<div class="flex-row">
<h2>RESISTANCES</h2>
</div>
<div class="res-grid">
<div><!-- empty div placeholder --></div>
<span class="bold">MAX</span>
<span class="bold">Current</span>
<div><!-- empty div placeholder --></div>
<span class="small">Notes</span>
<div><span class="resistance bold">&nbsp;COMPOSURE&nbsp;&nbsp;</span>
</div>
<div>
<input class="ref bold" type="number" name="attr_composure_max" title='@{composure_max}'>
</div>
<div>
<input class="ref bold" type="number" name="attr_composure" title='@{composure}'>
</div>
<div>
<button class="d6" type='action' name='act_compute-composure'></button>
</div>
<div>
<input class="notes" type="text" name="attr_composurenotes" title='@{composurenotes}' />
</div>
<div> <span class="resistance bold">&nbsp;FORTITUDE</span>
</div>
<div>
<input class="ref bold" type="number" name="attr_fortitude_max" title='@{fortitude_max}'>
</div>
<div>
<input class="ref bold" type="number" name="attr_fortitude" title='@{fortitude}'>
</div>
<div>
<button class="d6" type='action' name='act_compute-fortitude'></button>
</div>
<div>
<input class="notes" type="text" name="attr_fortitudenotes" title='@{fortitudenotes}' />
</div>
<div><span class="resistance bold">&nbsp;REACTION</span>
</div>
<div>
<input class="ref bold" type="number" name="attr_reaction_max" title='@{reaction_max}'>
</div>
<div>
<input class="ref bold" type="number" name="attr_reaction" title='@{reaction}'>
</div>
<div>
<button class="d6" type='action' name='act_compute-reaction'></button>
</div>
<div>
<input class="notes" type="text" name="attr_reactionnotes" title='@{reactionnotes}' />
</div>
<div>
<span class="resistance bold">&nbsp;WILL</span>
</div>
<div>
<input class="ref bold" type="number" name="attr_will_max" title='@{will_max}'>
</div>
<div>
<input class="ref bold" type="number" name="attr_will" title='@{will}'>
</div>
<div>
<button class="d6" type='action' name='act_compute-will'></button>
</div>
<div>
<input class="notes" type="text" name="attr_willnotes" title='@{willnotes}' />
</div>
</div>
<br />
<div class="flex-row">
<h2>INITIATIVE</h2>
</div>
<br />
<div class="initiative">
<input class="ref bold" type="number" name="attr_initiative" title='@{initiative}'>
<button name="roll_init" title='%{init}' class="d6" type="roll" value="@{template_start} {{name=Initiative check}} {{result=[[@{initiative}d6@{modifier_query} &{tracker}]]}}"></button>
</div>
</div>
<!-- aptitudes -->
<div class="section aptitudes border">
<h2>APTITUDES</h2>
<label class="aptitudes">
<fieldset class="repeating_aptitudes">
<input class="medium bold" type="text" name="attr_aptitudename" title='@{repeating_aptitudes_$X_aptitudename}' />
<input class="ref bold" type="number" name="attr_aptitude" title='@{repeating_aptitudes_$X_aptitude}'>
<button class="d6" type='action' name='act_compute-aptitude'></button>
<input class="longap" type="text" name="attr_aptitudenotes" title='@{aptitudenotes}' />
</fieldset>
</label>
</div>
<!-- advantages -->
<div class="section advantages border">
<h2>ADVANTAGES</h2>
<fieldset class="repeating_advantages">
<input class="mediumadv bold" type="text" name="attr_advantage" title='@{repeating_advantages_$X_advantage}' />
<input class="bold ref" name="attr_advantage-rank" title='@{repeating_advantages_$X_advantage-rank}' type="number" value="1"/>
<input class="notes" type="text" name="attr_advantagenotes" title='@{repeating_advantages_$X_advantagenotes}' />
</fieldset>
</div>
<!-- disadvantages -->
<div class="section disadvantages border">
<h2>DISADVANTAGES</h2>
<fieldset class="repeating_disadvantages">
<input class="mediumdis bold" type="text" name="attr_disadvantage" title='@{repeating_disadvantages_$X_disadvantage}' />
<input class="bold ref" name="attr_disadvantage-rank" title='@{repeating_disadvantages_$X_disadvantage-rank}' type="number" value="1"/>
<input class="notes" type="text" name="attr_disadvantagenotes" title='@{repeating_disadvantages_$X_disadvantagenotes}' />
</fieldset>
</div>
<!-- powers -->
<div class="section powers border">
<h2>POWERS</h2>
<label class="powers">
<fieldset class="repeating_powers">
<input class="mediumpow bold" type="text" name="attr_powername" title='@{repeating_powers_$X_powername}' />
<input class="ref bold" type="number" name="attr_power" title='@{repeating_powers_$X_power}'>
<button class="d6" type='action' name='act_compute-power'></button>
<input class="longpow" type="text" name="attr_powernotes" title='@{repeating_powers_$X_powernotes}' />
</fieldset>
</label>
</div>
<!-- competency dice -->
<div class="section comp-dice flex-down flex-center border">
<h2>COMPETENCY DICE</h2>
<br />
<br />
<div>
<span class="bold">Competency Dice:</span>
<input class="bold ref" type="number" name="attr_comp" title='@{comp}' />/<input class="bold ref" type="number" name="attr_comp_max" title='@{comp_max}' />
</div>
<div>
<span class="bold">Temp Comp Dice:</span>
<input class="bold ref" type="number" name="attr_temp_comp" title='@{temp_comp}' />/<input class="bold ref" type="number" name="attr_temp_comp_max" title='@{temp_comp_max}' />
</div>
</div>
<!-- alt tracker -->
<!-- cost -->
<div class="section cost border flex-down flex-center">
<span class="bold">RESISTANCES</span>
<input class="bold" type="number" name="attr_resist-cost" title='@{resist-cost}' value="0" />
<span class="bold">+ APTITUDES </span>
<input class="bold" type="number" name="attr_apt-cost" title='@{apt-cost}' value="0" />
<span class="bold">+ POWERS</span>
<input class="bold" type="number" name="attr_power-cost" title='@{power-cost}' value="0" />
<span class="bold">+ ADVANTAGES</span>
<input class="bold" type="number" name="attr_adv-cost" title='@{adv-cost}' value="0" />
<span class="bold">+ DISADVANTAGES</span>
<input class="bold" type="number" name="attr_disadv-cost" title='@{disadv-cost}' value="0" />
<span class="bold total">= TOTAL COST</span>
<input class="bold ref total-value" type="number" name="attr_cost" title='@{cost}' value="@{resist-cost}+@{apt-cost}+@{power-cost}+@{adv-cost}+@{disadv-cost}" disabled="true"/>
</div>
</div>
<div class='mook'>
<!-- name -->
<div class="section name">
<label class='input-label'>
<input class="bold" type="text" name="attr_character_name" title='@{character_name}' />
<h6>Name</h6>
</label>
<label class='input-label description'>
<input class="bold" type="text" name="attr_mook_description" title='@{mook_description}' />
<h6>Description</h6>
</label>
</div>
<div class='rating border'>
<h2>Rating</h2>
<button type='action' name='act_compute-rating' class='rating d6'></button>
<label class='input-label stacked over max'>
<h6 class='boxed'>MAX</h6>
<input type='number' class='ref bold boxed' name='attr_rating_max' title='@{rating_max}' value='0'>
</label>
<label class='input-label stacked over current'>
<h6 class='boxed'>CURRENT</h6>
<input type='number' class='ref bold boxed' name='attr_rating' title='@{rating}' value='0'>
</label>
<label class='input-label stacked over initiative'>
<h2>Initiative</h2>
<input type='number' class='ref bold boxed' name='attr_initiative' title='@{initiative}' value='0'>
<button name="roll_init" title='%{init}' class="initiative-button d6" type="roll" value="@{template_start} {{name=Initiative check}} {{result=[[@{initiative}d6@{modifier_query} &{tracker}]]}}"></button>
</label>
</div>
<div class='qualities border'>
<h2>Qualities</h2>
<fieldset class='repeating_quality'>
<input type='text' name='attr_name' title='@{name}'>
<input type='text' name='attr_description' title='@{description}'>
</fieldset>
</div>
<div class='exceptional border'>
<h2>Exceptional Qualities</h2>
<fieldset class='repeating_exceptional'>
<input type='text' name='attr_exceptionalname' title='@{exceptionalname}'>
<input type='number' name='attr_exceptional' title='@{exceptional}' value='0'>
<button class="d6" type='action' name='act_compute-exceptional'></button>
<input type='text' name='attr_description' title='@{exceptionalnotes}'>
</fieldset>
</div>
</div>
<!-- tracker -->
<div class="tracker">
<span>Use Alternative tracker</span>
<input type="checkbox" class="sheet-tracker-switch" name="attr_tracker_switch" title='@{tracker_switch}' value="1">
<table class="def-tracker border">
<tr> <th colspan="10"><h5 class='cell-display'>Success Track</h5></th> </tr>
<tr class='track-row'> <td><div class="cell-display">1</div></td><td><div class="cell-display">7</div></td><td><div class="cell-display">13</div></td><td><div class="cell-display">19</div></td><td><div class="cell-display">25</div></td><td><div class="cell-display">31</div></td><td><div class="cell-display">37</div></td><td><div class="cell-display">43</div></td><td><div class="cell-display">49</div></td><td><div class="cell-display">55</div></td> </tr>
<tr class='track-row'> <td><div class="cell-display">2</div></td><td><div class="cell-display">8</div></td><td><div class="cell-display">14</div></td><td><div class="cell-display">20</div></td><td><div class="cell-display">26</div></td><td><div class="cell-display">32</div></td><td><div class="cell-display">38</div></td><td><div class="cell-display">44</div></td><td><div class="cell-display">50</div></td><td><div class="cell-display">56</div></td> </tr>
<tr class='track-row'> <td><div class="cell-display">3</div></td><td><div class="cell-display">9</div></td><td><div class="cell-display">15</div></td><td><div class="cell-display">21</div></td><td><div class="cell-display">27</div></td><td><div class="cell-display">33</div></td><td><div class="cell-display">39</div></td><td><div class="cell-display">45</div></td><td><div class="cell-display">51</div></td><td><div class="cell-display">57</div></td> </tr>
<tr class='track-row'> <td><div class="cell-display">4</div></td><td><div class="cell-display">10</div></td><td><div class="cell-display">16</div></td><td><div class="cell-display">22</div></td><td><div class="cell-display">28</div></td><td><div class="cell-display">34</div></td><td><div class="cell-display">40</div></td><td><div class="cell-display">46</div></td><td><div class="cell-display">52</div></td><td><div class="cell-display">58</div></td> </tr>
<tr class='track-row'> <td><div class="cell-display">5</div></td><td><div class="cell-display">11</div></td><td><div class="cell-display">17</div></td><td><div class="cell-display">23</div></td><td><div class="cell-display">29</div></td><td><div class="cell-display">35</div></td><td><div class="cell-display">41</div></td><td><div class="cell-display">47</div></td><td><div class="cell-display">53</div></td><td><div class="cell-display">59</div></td> </tr>
<tr class='track-row'> <td><div class="cell-display">6</div></td><td><div class="cell-display">12</div></td><td><div class="cell-display">18</div></td><td><div class="cell-display">24</div></td><td><div class="cell-display">30</div></td><td><div class="cell-display">36</div></td><td><div class="cell-display">42</div></td><td><div class="cell-display">48</div></td><td><div class="cell-display">54</div></td><td><div class="cell-display">60</div></td> </tr>
</table>
<table class="alt-tracker border">
<tr> <th><h5 class='cell-display'>Range of Success</h5></th><th><h5 class='cell-display'>Damage/Effect</h5></th> </tr>
<tr class='track-row'> <td><div class="cell-display">0 - 5</div></td><td><div class="cell-display">1d6 / 1</div></td> </tr>
<tr class='track-row'> <td><div class="cell-display">6 - 11</div></td><td><div class="cell-display">2d6 / 2</div></td> </tr>
<tr class='track-row'> <td><div class="cell-display">12 - 17</div></td><td><div class="cell-display">3d6 / 3</div></td> </tr>
<tr class='track-row'> <td><div class="cell-display">18 - 23</div></td><td><div class="cell-display">4d6 / 4</div></td> </tr>
<tr class='track-row'> <td><div class="cell-display">24 - 29</div></td><td><div class="cell-display">5d6 / 5</div></td> </tr>
<tr class='track-row'> <td><div class="cell-display">30 - 35</div></td><td><div class="cell-display">6d6 / 6</div></td> </tr>
</table>
</div>
</div>
<!-- ROLL TEMPLATE -->
<rolltemplate class="sheet-rolltemplate-supers">
<div class='template-container'>
<div class='header'>
{{#name}}
<h4>{{name}}</h4>
{{/name}}
{{#character_name}}
{{#character_id}}
[{{character_name}}](http://journal.roll20.net/character/{{character_id}})
{{/character_id}}{{^character_id}}
{{character_name}}
{{/character_id}}
{{/character_name}}
</div>
{{#target}}
<div>
<h5>Result</h5><h5>Target Number</h5>
</div>
<div>
<span>{{result}}</span><span>{{target}}</span>
</div>
<div>
<span class="successes">{{computed::result}}</span>
</div>
{{/target}}
{{^target}}
{{#result}}
<div>
<h5>Result</h5><span>{{result}}</span>
</div>
{{/result}}
{{/target}}
{{#allprops() result target character_name character_id}}
<div>
<span>{{key}}</span><span>{{value}}</span>
</div>
{{/allprops() result target character_name character_id}}
</div>
</rolltemplate>
<script type='text/worker'>
//# Roll20Async functions #
//These together allow the use of promises and await/async to allow us to escape callback hell
const setActiveCharacterId = function(charId){
var oldAcid=getActiveCharacterId();
var ev = new CustomEvent("message");
ev.data={"id":"0", "type":"setActiveCharacter", "data":charId};
self.dispatchEvent(ev);
return oldAcid;
};
const _getAttrs = function (props){
var acid=getActiveCharacterId();//save the current activeCharacterID in case it has changed when the promise runs
var prevAcid=null; //local variable defined here, because it needs to be shared across the promise callbacks defined below
return new Promise((resolve,reject)=>{
prevAcid=setActiveCharacterId(acid); //in case the activeCharacterId has changed, restore it to what we were expecting and save the current value to restore later
try{
getAttrs(props,(values)=>{ resolve(values); });
}catch{ reject(); }
}).finally(()=>{
setActiveCharacterId(prevAcid); //restore activeCharcterId to what it was when the promise first ran
});
};
const _getAllAttrs = async function(props,sectionDetails=repeatingSectionDetails){
const [repeats,sections] = await _getSections(sectionDetails);
const attributes = await _getAttrs([...(props||baseGet),...repeats]);
return [attributes,repeats,sections];
}
//use the same pattern for each of the following...
const _setAttrs = function (propObj, options){
var acid=getActiveCharacterId();
var prevAcid=null;
return new Promise((resolve,reject)=>{
prevAcid=setActiveCharacterId(acid);
try{
setAttrs(propObj,options,(values)=>{ resolve(values); });
}
catch{ reject(); }
}).finally(()=>{
setActiveCharacterId(prevAcid);
});
};
const _getSectionIDs = function(sectionName){
var acid=getActiveCharacterId();
var prevAcid=null;
return new Promise((resolve,reject)=>{
prevAcid=setActiveCharacterId(acid);
try{
getSectionIDs(sectionName,(values)=>{ resolve(values); });
}
catch{ reject(); }
}).finally(()=>{
setActiveCharacterId(prevAcid);
});
};
//# Utility Functions #
const log = function(msg){
if(typeof msg === 'string'){
console.log(`%c${sheetName} log| ${msg}`,"background-color:#159ccf");
}else if(typeof msg === 'object'){
Object.keys(msg).forEach((m)=>{
if(typeof msg[m] === 'string'){
console.log(`%c${sheetName} log| ${m}: ${msg[m]}`,"background-color:#159ccf");
}else{
console.log(`%c${sheetName} log| ${typeof msg[m]} ${m}`,"background-color:#159ccf");
console.table(msg[m]);
}
});
}
};
const debug = function(msg,force){
if(!debugMode && !force && version > 0) return;
if(typeof msg === 'string'){
console.log(`%c${sheetName} DEBUG| ${msg}`,"background-color:tan;color:red;");
}else if(typeof msg === 'object'){
Object.keys(msg).forEach((m)=>{
if(typeof msg[m] === 'string'){
console.log(`%c${sheetName} DEBUG| ${m}: ${msg[m]}`,"background-color:tan;color:red;");
}else{
console.log(`%c${sheetName} DEBUG| ${typeof msg[m]} ${m}`,"background-color:tan;color:red;font-weight:bold;");
console.table(msg[m]);
}
});
}
};
const _getSections = function(section_details=repeatingSectionDetails){
let queue = _.clone(section_details);
const worker = async (repeatAttrs=[],sections={})=>{
let detail = queue.shift();
let IDs = await _getSectionIDs(detail.section);
sections[detail.section] = IDs;
let attrs = IDs.forEach((i)=>{
detail.fields.forEach((f)=>{
repeatAttrs.push(`${detail.section}_${i}_${f}`);
});
});
if(queue.length){
return worker(repeatAttrs,sections);
}else{
return [repeatAttrs,sections];
}
};
if(!queue[0]){
return [[],{}];
}
return worker();
};
// Sets the attributes while always calling with {silent:true}
// Can be awaited to get the values returned from _setAttrs
const set = function(obj,vocal){
let setObj = verifyObj(obj);
return _setAttrs(setObj,{silent:vocal ? false : true});
};
//verifies that the property/keys of the setObj are valid
const verifyObj = function(obj){
return Object.keys(obj).reduce((m,p)=>{
if(obj[p] || obj[p]===0 || obj[p]===''){
m[p]=obj[p];
}else{
debug(`WARNING: setObj[${p}] = ${obj[p]}`);
}
return m;
},{})
};
//sanitizes text for use as a regex
sanitizeForRegex = function(text){
return text.replace(/\.|\||\(|\)|\[|\]|\-|\+|\?|\/|\{|\}|\^|\$|\*/g,'\\$&');
};
//converts a value to a number, it's default value, or 0
const value = function(val,def){
return (+val||def||0);
};
//extracts the section, rowID, and field name from a repeating attribute name
/*
e.g. repeating_equipment_-09Jhu89z_bulk would give:
section: equipment
rowID: -09Jhu89z
field: bulk
*/
const parseRepeatName = function(string){
let match = string.match(/(repeating_[^_]+)_([^_]+)_(.+)/);
match.shift();
return match;
};
const parseClickTrigger = string => string.replace(/clicked:/,'');
/*
value='&{template:default} {{name=@{character_name} **Composure** check}} {{result=[[@{composure}d6@{modifier_query}]]}} {{target=@{target_query}}}'
value='&{template:default} {{name=@{character_name} **Fortitude** check}} {{result=[[@{fortitude}d6@{modifier_query}]]}} {{target=@{target_query}}}'
value='&{template:default} {{name=@{character_name} **Reaction** check}} {{result=[[@{reaction}d6@{modifier_query}]]}} {{target=@{target_query}}}'
value='&{template:default} {{name=@{character_name} **Will** check}} {{result=[[@{will}d6@{modifier_query}]]}} {{target=@{target_query}}}'
value='&{template:default} {{name=@{character_name} **@{aptitudename} Check}} {{result=[[@{aptitude}d6k3@{modifier_query}]]}} {{target=@{target_query}}}'
value='&{template:default} {{name=@{character_name} **@{powernamename}** Check}} {{result=[[@{power}d6@{modifier_query}]]}} {{target=@{target_query}}}'
<input type='hidden' name='attr_target_query' title='@{target_query}' value='[[?{Target Number}]]'>
*/
/*
Variables
*/
const sheetName = 'Supers! Revised Edition';
const version = 1.3;
let debugMode = false;
const rollButtons = ['repeating_aptitudes:compute-aptitude','repeating_powers:compute-power','repeating_exceptional:compute-exceptional','compute-composure','compute-fortitude','compute-reaction','compute-will','compute-aptitude','compute-power','compute-rating'];
const repeatingSectionDetails = [
{section:'repeating_aptitudes',fields:['compute-aptitude','aptitudename']},
{section:'repeating_powers',fields:['powernamename','power','powername','compute-power']},
{section:'repeating_exceptional',fields:['name','dice','compute-exceptional']}
];
const baseGet = [];
const calculateSuccess = async function(event){
let [,row,section,rowID,computeName,attrName] = event.triggerName.match(/clicked:(?:(?<row>repeating_(?<section>[^_]+)_(?<rowID>[^_]+))_)?(?<computeName>compute-(?<attrName>.+))/);
const attributes = await _getAttrs(['automation']);
let name;
let capName;
if(row){
name = `${row}_${attrName}`;
capName = `@{${row}_${section.replace(/s$/,'')}name}`;
}else{
name = attrName;
capName = name.replace(/^[a-z]/i,(match)=>{
return match.toUpperCase();
});
}
const computed = {};
const roll = `@{template_start} {{name=${capName} check}} {{result=[[ [[0@{${name}}+0?{+/- d6 Modifier|0}]]d6${/aptitude/.test(row) ? 'kh3' : ''}]]}} ${value(attributes.automation) ? `{{target=[[0?{${capName} Target Number|10}]]}}` : ''}`;
const intermediate = await startRoll(roll);
let rollID = intermediate.rollId;
if(value(attributes.automation)){
let results = intermediate.results;
computed.result = results.result.result-results.target.result;
if(computed.result >= 0){
computed.result = Math.max(Math.ceil((computed.result)/5),1);
}else{
computed.result = 0;
}
computed.result = `${computed.result} Success${computed.result !== 1 ? 'es':''}`;
}
finishRoll(rollID,computed);
};
rollButtons.forEach((button)=>{
on(`clicked:${button}`,calculateSuccess);
});
on('sheet:opened',async ()=>{
const [attributes,repeats,sections] = await _getAllAttrs(['character_name','debug_mode']);
debugMode = value(attributes.debug_mode);
const setObj = {};
debug({sections});
update1x3(attributes,sections,setObj);
setObj.version = version;
debug({setObj});
set(setObj);
});
const update1x3 = function(attributes,sections,setObj){
sections.repeating_powers.forEach((id)=>{
if(attributes[`repeating_powers_${id}_powernamename`]){
setObj[`repeating_powers_${id}_powername`] = attributes[`repeating_powers_${id}_powernamename`];
attributes[`repeating_powers_${id}_powername`] = setObj[`repeating_powers_${id}_powername`];
setObj[`repeating_powers_${id}_powernamename`] = '';
}
});
};
log('Sheet Booted Successfully');
</script>