mirror of
https://github.com/Nighthawk42/roll20-character-sheets.git
synced 2026-08-31 20:22:29 +00:00
268 lines
9.7 KiB
JavaScript
268 lines
9.7 KiB
JavaScript
/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/
|
|
/*jshint -W014,-W084,-W030,-W033,-*/
|
|
//# 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=baseGet,sectionDetails=repeatingSectionDetails){
|
|
const [repeats,sections] = await _getSections(sectionDetails);
|
|
const attributes = await _getAttrs([...props,...repeats]);
|
|
const casc = expandCascade(cascades,sections,attributes);
|
|
return [attributes,repeats,sections,casc];
|
|
}
|
|
//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){
|
|
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 checkRollLog = function(sections,attributes){
|
|
debug({sections});
|
|
return;
|
|
sections.repeating_roll.forEach((id,index)=>{
|
|
let row =`repeating_roll_${id}`;
|
|
if(50 - index >=50 || (Date.now() - value(attributes[`${row}_date`])) > (1000*60*60*24)){
|
|
removeRepeatingRow(row);
|
|
delete attributes[`${row}_date`];
|
|
delete attributes[`${row}_json`];
|
|
}
|
|
});
|
|
};
|
|
|
|
const parseClickTrigger = function(string){
|
|
let match = string.match(/clicked:(?:(repeating_[^_]+)_([^_]+)_)?(.+)/);
|
|
match.shift();
|
|
return match;
|
|
};
|
|
|
|
//Sheet Updaters and styling functions
|
|
const updateSheet = async function(){
|
|
const [attributes,repeats,sections] = await _getAllAttrs(['sheet_version','debug_mode',...baseGet]);
|
|
debugMode = !debugMode ? !!attributes.debug_mode : debugMode;
|
|
const setObj = {
|
|
sheet_version:version,
|
|
};
|
|
if(!value(attributes.sheet_version)){
|
|
initialSetup(attributes,setObj,sections);
|
|
}
|
|
log(`Sheet Update applied. Current Sheet Version ${version}`);
|
|
styleOnOpen(attributes);
|
|
set(setObj);
|
|
};
|
|
const initialSetup = function(attributes,setObj,sections){
|
|
debug('Initial sheet setup');
|
|
['exertion_max','move_speed','reaction_defense','cognitive_resistance'].forEach((attr)=>{
|
|
setObj[attr] = cascades[attr].calculation(_.clone(cascades[attr]),attributes,setObj,sections);
|
|
debug(`default for ${attr} applied`);
|
|
});
|
|
['negotiation/persuade_mod','fashion/etiquette_mod','leadership_mod','disguise/blend_in_mod','intimidate/taunt_mod','showmanship_mod','resist_distress_mod','cognitive_resistance','initiative_mod','intuition_mod','occult/scp_lore_mod'].forEach((skill)=>{
|
|
setObj[`${skill}`] = actionModCalc({name:`${skill}`},attributes,setObj,sections);
|
|
debug({[`${skill}`]:setObj[`${skill}_mod`]});
|
|
});
|
|
debug({appearance:attributes.appearance,reasoning:attributes.reasoning});
|
|
setObj.operative_id = getActiveCharacterId();
|
|
};
|
|
const styleOnOpen = function(attributes){
|
|
styleSecurity({},attributes);
|
|
sectionDisplay({},attributes);
|
|
};
|
|
|
|
/*
|
|
Cascade Expansion functions
|
|
*/
|
|
//Expands the repeating section templates in cascades to reflect the rows actually available
|
|
const expandCascade = function(cascade,sections,attributes){
|
|
return _.keys(cascade).reduce((memo,key)=>{//iterate through cascades and replace references to repeating attributes with correct row ids.
|
|
if(/^repeating/.test(key)){//If the attribute is a repeating attribute, do special logic
|
|
expandRepeating(memo,key,cascade,sections,attributes);
|
|
}else{//for non repeating attributes do this logic
|
|
expandNormal(memo,key,cascade,sections);
|
|
}
|
|
return memo;
|
|
},{});
|
|
};
|
|
|
|
const expandRepeating = function(memo,key,cascade,sections,attributes){
|
|
key.replace(/(repeating_[^_]+)_[^_]+?_(.+)/,(match,section,field)=>{
|
|
sections[section].forEach((id)=>{
|
|
memo[`${section}_${id}_${field}`]=_.clone(cascade[key]);//clone the details so that each row's attributes have correct ids
|
|
memo[`${section}_${id}_${field}`].name = `${section}_${id}_${field}`;
|
|
memo[`${section}_${id}_${field}`].affects = memo[`${section}_${id}_${field}`].affects.reduce((m,affected)=>{
|
|
if(section === affected){//otherwise if the affected attribute is in the same section, simply set the affected attribute to have the same row id.
|
|
m.push(applyID(affected,id));
|
|
}else if(/repeating/.test(affected)){//If the affected attribute isn't in the same repeating section but is still a repeating attribute, add all the rows of that section
|
|
addAllRows(affected,m,sections);
|
|
}else{//otherwise the affected attribute is a non repeating attribute. Simply add it to the computed affected array
|
|
m.push(affected);
|
|
}
|
|
return m;
|
|
},[]);
|
|
});
|
|
});
|
|
};
|
|
|
|
const applyID = function(affected,id){
|
|
return affected.replace(/(repeating_[^_]+_)[^_]+(.+)/,`$1${id}$2`);
|
|
};
|
|
|
|
const expandNormal = function(memo,key,cascade,sections){
|
|
memo[key] = _.clone(cascade[key]);
|
|
memo[key].affects = memo[key].affects.reduce((m,a)=>{
|
|
if(/^repeating/.test(a)){
|
|
addAllRows(a,m,sections);
|
|
}else{
|
|
m.push(a);
|
|
}
|
|
return m;
|
|
},[]);
|
|
};
|
|
|
|
const checkNumber = function(name,casc,attributes,setObj){
|
|
if(!casc[name] || casc[name].type!=='number') return;
|
|
const re = /[^\-\d\.]/g;
|
|
if(re.test(attributes[name])){
|
|
setObj[name] = attributes[name].replace(re,'');
|
|
attributes[name] = setObj[name];
|
|
}
|
|
};
|
|
|
|
const addAllRows = function(affected,memo,sections){
|
|
affected.replace(/(repeating_[^_]+?)_[^_]+?_(.+)/,(match,section,field)=>{
|
|
sections[section].forEach(id=>memo.push(`${section}_${id}_${field}`));
|
|
});
|
|
};
|
|
|
|
let clickDelay = 500;
|
|
const defaultThrottle = callback => _.throttle(callback,clickDelay,{trailing:false});
|
|
const isDieRoll = function(field){
|
|
return /(?:d(?:8|10|12)(?:Crit)?|exertion)/.test(field);
|
|
}; |