Files
roll20-character-sheets/under_the_neighborhood/source/javascript/rollfunctions.js
T
2022-06-15 13:30:18 -06:00

344 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/
/*jshint -W014,-W084,-W030,-W033*/
//Roll Listeners
const rollBasicMove = async function({trigger,attributes,sections}){
const [,,field,activeEffects,rollObj,dice] = await baseRollInfo({trigger,attributes,sections});
let statRef = getMoveResults(field.replace(/-/g,' '),rollObj,attributes);
rollObj.roll = assembleRoll(dice,statRef,activeEffects,attributes.is_weird);
executeRoll({rollObj});
};
k.registerFuncs({rollBasicMove});
const getMoveResults = function(move,rollObj,attributes){
const possibleResults = basicMoves[move];
['critical','success','mixed','failure','fumble'].forEach((result)=>{
if(result !== 'stat'){
rollObj[`${result}_result`] = possibleResults[result] ?
`^{${possibleResults[result]}}` :
'';
}
});
return attributes[`${move.replace(/\s+/g,'_')}_stat`];
};
const rollStat = async function({trigger,attributes,sections}){
const [,,field,activeEffects,rollObj,dice] = await baseRollInfo({trigger,attributes,sections});
rollObj.roll = assembleRoll(dice,`@{${field}}`,activeEffects,attributes.is_weird);
executeRoll({rollObj});
};
k.registerFuncs({rollStat});
const rollSkill = async function({trigger,attributes,sections}){
const [section,rowID,field,activeEffects,rollObj,dice] = await baseRollInfo({trigger,attributes,sections,isSkill:true});
let statRef;
rollObj.header = attributes[`${section}_${rowID}_${field === 'inability' ? field : 'name'}`];
if(attributes.skill_basic_move){
const query = k.capitalize(getTranslationByKey('move query'));
const options = Object.keys(basicMoves)
.map((key) => {
return `${k.capitalize(getTranslationByKey(key))},${key}`
})
.join('|');
const move = await k.extractQueryResult(`${query}|${options}`);
statRef = getMoveResults(move,rollObj,attributes);
}else{
await k.pseudoQuery('');
statRef = section === 'repeating_skill' ?
(/inability/.test(field) ?
'inability_stat' :
'skill_stat'
) :
'stat';
statRef = attributes[`${section}_${rowID}_${statRef}`];
}
k.debug({section,rowID,field});
rollObj.roll = assembleRoll(dice,statRef,activeEffects,attributes.is_weird);
k.debug({skillObj:rollObj});
executeRoll({rollObj});
};
k.registerFuncs({rollSkill});
const rollHelpFriend = async function({trigger,attributes,sections}){
const [section,rowID,field,activeEffects,rollObj,dice] = await baseRollInfo({trigger,attributes,sections});
const relationScore = attributes[`${section}_${rowID}_score`];
rollObj.header = getTranslationByKey('help a friend roll header').replace('{{0}}',attributes[`${section}_${rowID}_name`]);
k.debug({activeEffects});
rollObj.roll = assembleRoll(dice,relationScore,activeEffects,attributes.is_weird);
executeRoll({rollObj});
};
k.registerFuncs({rollHelpFriend});
const rollCharacterMove = async function({trigger,attributes,sections}){
const [section,rowID,field,activeEffects,rollObj,dice] = await baseRollInfo({trigger,attributes,sections});
rollObj.roll = assembleRoll(dice,'?{query}',activeEffects,attributes.is_weird);
const fieldSwitch = {
critical_result:attributes[`${section}_${rowID}_critical_success`] || attributes[`${section}_${rowID}_full_success`] || basicMoves['another move'].critical,
success_result:attributes[`${section}_${rowID}_full_success`] || basicMoves['another move'].success,
mixed_result:attributes[`${section}_${rowID}_mixed_success`] || basicMoves['another move'].mixed,
failure_result:attributes[`${section}_${rowID}_failure`] || basicMoves['another move'].failure,
fumble_result:attributes[`${section}_${rowID}_critical_failure`] || attributes[`${section}_${rowID}_failure`] || basicMoves['another move'].fumble
};
rollObj.header = attributes[`${section}_${rowID}_name`];
Object.entries(fieldSwitch).forEach(([fieldName,content])=>{
rollObj[fieldName] = content;
});
executeRoll({rollObj});
};
k.registerFuncs({rollCharacterMove});
const rollInitiative = async function({trigger,attributes,sections}){
const [section,rowID,field,activeEffects,rollObj,dice] = await baseRollInfo({trigger,attributes,sections});
rollObj.roll = assembleRoll(dice,attributes.fierce,activeEffects,attributes.is_weird)
.replace(/\]\]/,'&{tracker}]]');
executeRoll({rollObj});
};
k.registerFuncs({rollInitiative});
//Parse Functions
const baseRollInfo = async function({trigger,attributes,sections,isSkill}){
const [section,rowID,button] = k.parseTriggerName(trigger.name);
const field = button.replace(/-?action/,'');
const baseEffects = {advantage:0,bonus:0,text:[]};
if(isSkill){
baseEffects.advantage = field === 'inability' ?
-1 - attributes[`${section}_${rowID}_${field}_super`] :
1 + attributes[`${section}_${rowID}_super`];
}
const activeEffects = await countEffects(attributes,sections,baseEffects);
const rollObj = {
header:`^{${field.replace(/-/g,' ')}}`,
result:`[[0[computed value]]]`
};
if(baseEffects.text?.length){
rollObj.description = baseEffects.text.map((text)=>`^{${text}}`).join('\n');
}
Object.entries(basicMoves['another move']).forEach(([result,effect])=>{
if(result !== 'stat'){
rollObj[`${result}_result`] = `^{${effect}}`;
}
});
const dice = await parseAdvantage(attributes.advantage,activeEffects,attributes,rollObj);
return [section,rowID,field,activeEffects,rollObj,dice];
};
/*
The Compatibility Scores and their effects are as follows:
-1 (Frenemies)
The other PC rolls with Disadvantage. Each of you gains 1 AP.
+0 (Acquaintances)
The other PC gets +1 on their roll.
+1 (Friends)
CHOOSE 1
The other PC gets +1 on their roll.
The consequences of the other PCs actions are “bigger” (Full Successes come with a small bonus, Failures come with an extra complication).
+2 (Good Friends)
CHOOSE 1
The other PC gets +2 on their roll.
The other PC rolls with Advantage.
The consequences of the other PCs actions are “bigger” (Full Successes come with a small bonus, Failures come with an extra complication).
+3 (Family)
CHOOSE UP TO 2
The other PC gets +2 on their roll.
The other PC rolls with Advantage.
The consequences of the other PCs move are “bigger” (Full Successes come with a small bonus, Failures come with an extra complication).
*/
const compatibilityQuery = async function(baseEffects,attributes,sections){
const compatibleEffects = {
'-1':{optNum:1,options:['disadvantage']},
'0':{optNum:1,options:['+1']},
'1':{optNum:1,options:['+1','bigger consequences']},
'2':{optNum:1,options:['+1','advantage','bigger consequences']},
'3':{optNum:2,options:['+2','advantage','bigger consequences']}
}
const activeID = sections.repeating_compatibility.reduce((memo,id)=>{
return attributes[`repeating_compatibility_${id}_activate`] &&
(!memo || attributes[`repeating_compatibility_${id}_score`] < attributes[`repeating_compatibility_${id}_score`]) ?
id :
memo;
},'');
const ordinals = {
'1':'1st',
'2':'2nd'
}
if(activeID){
const activeEffect = compatibleEffects[attributes[`repeating_compatibility_${activeID}_score`]];
k.debug({activeID,[`compatibleEffects[${attributes[`repeating_compatibility_${activeID}_score`]}]`]:activeEffect})
const selectedEffects = [];
if(activeEffect.options.length > 1){
const effectWorker = async (iteration = 1)=>{
const availableOptions = activeEffect.options.filter((opt)=>selectedEffects.indexOf(opt) === -1);
const promptKey = activeEffect.optNum === 1 ?
'Which compatibility effect' :
`${ordinals[iteration]} compatible effect`;
const selectedOption = await k.extractQueryResult(`${getTranslationByKey(promptKey)}|${availableOptions.map((opt)=>`${getTranslationByKey(`compatibility prompt ${opt}`)},${opt}`).join('|')}`);
k.debug({selectedOption});
selectedOption.replace(/\+?(.+)/,(match,effect)=>{
if(+effect){
baseEffects.bonus += +effect;
}else if(/dis/.test(effect)){
baseEffects.advantage--;
}else if(/advantage/.test(effect)){
baseEffects.advantage++;
}else{
baseEffects.text.push(effect);
}
});
selectedEffects.push(selectedOption);
iteration++;
if(iteration > activeEffect.optNum){
return true;
}else{
return effectWorker(iteration);
}
}
await effectWorker();
}else{
selectedEffects.push(activeEffect.options[0]);
await k.pseudoQuery();
}
}else{
if(attributes.interference){
baseEffects.advantage--;
baseEffects.text.push('interference roll message');
}
await k.pseudoQuery();
}
};
const countEffects = async function(attributes,sections,effectObj = {advantage:0,bonus:0}){
let idArray;
const baseEffects = {...effectObj};
if(attributes.use_roll_modifiers){
idArray = sections.repeating_modifier;
}else{
idArray = [];
baseEffects.bonus = attributes.generic_bonus;
}
await compatibilityQuery(baseEffects,attributes,sections);
k.debug({baseEffects});
return idArray.reduce((memo,id)=>{
k.debug({[`attributes[repeating_modifier_${id}_activate]`]:attributes[`repeating_modifier_${id}_activate`]});
if(attributes[`repeating_modifier_${id}_activate`]){
const effect = attributes[`repeating_modifier_${id}_effect`];
k.debug({effect});
const advs = effect.match(/(?:^|\s)(?:super )?advantage/ig) || [];
const disadvs = effect.match(/(?:super )?disadvantage/ig) || [];
const bonuses = effect.match(/(?:[+\-]\s*)?\d+/g) || [];
k.debug({advs,disadvs,bonuses});
memo.advantage = advs.reduce((total,adv)=>total += /super/.test(adv) ? 2 : 1,memo.advantage);
memo.advantage = disadvs.reduce((total,adv)=>total -= /super/.test(adv) ? 2 : 1,memo.advantage);
memo.bonus = bonuses.reduce((total,bonus)=>{
const val = +bonus.replace(/\+/,'').trim();
return total += val;
},memo.bonus);
}
return memo;
},{...baseEffects});
};
const parseAdvantage = async function(advantageState,activeEffects,attributes,rollObj){
if(attributes.use_physical_dice){
const result = +(await k.extractQueryResult(getTranslationByKey('physical roll query')));
if(result === 12){
rollObj.result = '[[1]]';
}else if(result === 2){
rollObj.result = '[[-1]]';
}else{
rollObj.result = '[[0]]';
}
return result;
}else{
await k.pseudoQuery('');
if(advantageState.startsWith('?{')){
//If the advantage state is a query, translate the query.
const options = Object.entries(advantageTypes).map(([aType,aValue])=>{
return `${k.capitalize(getTranslationByKey(`${aType} option`))},${aValue}`;
}).join('|');
return `?{${k.capitalize(getTranslationByKey('roll'))}|${options}}`;
}else{
//otherwise, just use the advantage as is.
return activeEffects.advantage ?
(() => {
const diceNum = 2 + Math.abs(activeEffects.advantage);
const keep = activeEffects.advantage > 0 ?
'kh' :
'kl';
return `${diceNum}d6${keep}2`;
})() :
advantageState;
}
}
};
const assembleRoll = function(dice,chosenStat,activeEffects,isWeird){
let stat;
k.debug({chosenStat,activeEffects});
if(`${chosenStat}`.startsWith('?{')){
//If the advantage state is a query, translate the query.
const options = attributeNames.map((attr)=>{
if(attr !== 'weird' || isWeird){
return `${k.capitalize(getTranslationByKey(attr))},@{${attr}}`;
}else{
return null;
}
})
.filter((a)=>a)
.join('|');
stat = `+ ?{${k.capitalize(getTranslationByKey('which stat'))}|${options}}`;
}else{
//otherwise, just use the advantage as is.
stat = `+ ${chosenStat}`;
}
const bonus = stringifyBonus(activeEffects.bonus);
return `[[${dice}${stat}${bonus}]]`;
};
const stringifyBonus = function(bonus){
k.debug({bonus});
return bonus < 0 ?
bonus :
`+ ${bonus}`.replace(/\+ 0/,'');
};
const executeRoll = async function({rollObj}){
k.debug({toExecute:rollObj});
let message = `@{template_start} ${Object.entries(rollObj).map(([field,content])=>`{{${field}=${content}}}`).join('')}`;
k.debug({message});
const rolled = await startRoll(message);
const computeObj = {};
if(rolled.results.roll && rolled.results.result && /computed value/.test(rollObj.result)){
const expression = rolled.results.roll.expression;
let rollState;
let rollQuant = 2;
expression.replace(/d6k([hl])(\d+)/,(match,target,num)=>{
rollState = target;
rollQuant = +num || 2;
});
const diceRoll = rolled.results.roll.rolls[0];
const rollTotal = diceRoll.results.sort((a,b)=>{
if(rollState === 'l'){
return a - b;
}else if(rollState === 'h'){
return b - a;
}else{
return 0;
}
})
.slice(0,rollQuant);
k.debug({rollTotal});
const uniqueRolls = [...new Set(rollTotal)];
if(uniqueRolls.length === 1){
if(uniqueRolls[0] === 6){
computeObj.result = 1;
}else if(uniqueRolls[0] === 1){
computeObj.result = -1;
}
}
}
finishRoll(rolled.rollId,computeObj);
};