Backbone v1 for Backwater games

This commit is contained in:
Kurohyou
2022-12-22 11:26:42 -07:00
parent 5ab0ea8eb4
commit 2f490ee94e
173 changed files with 27960 additions and 0 deletions
@@ -0,0 +1,149 @@
const { k } = require('..');
describe('Calculation.js',()=>{
describe('calcAttribute({trigger,attributes})',()=>{
Object.entries({
vigilance:[{logic:2,willpower:2},{logic:0,willpower:-1},{logic:-1,willpower:-1}],
build:[{size:2,stamina:2},{size:2,stamina:-1},{size:-1,stamina:-1}],
intuition:[{charisma:2,wisdom:2},{charisma:2,wisdom:-1},{charisma:-1,wisdom:-1}],
reflex:[{athletics:2,aptitude:2},{athletics:2,aptitude:-1},{athletics:-1,aptitude:-1}]
}).forEach(([attr,propArray])=>{
propArray.forEach(obj => {
const entries = Object.entries(obj);
const [prop1,value1,prop2,value2] = entries.flat();
const expectedVal = 10 + value1 + value2;
const itMsg = `Given ${value1} ${prop1} and ${value2} ${prop2}, should return ${expectedVal} for ${attr}`
it(itMsg,()=>{
k.getAllAttrs({
callback:(attributes,sections,casc)=>{
attributes[prop1] = value1;
attributes[prop2] = value2;
const retVal = k.callFunc('calcAttribute',{trigger:{name:`${attr}_goal`},attributes});
expect(retVal).toBe(expectedVal);
}
});
});
});
});
});
describe('calcAttributeMod({trigger,attributes})',()=>{
Object.entries({
vigilance:[9,11,14],
build:[9,11,14],
intuition:[9,11,14],
reflex:[9,11,14],
}).forEach(([attr,numArr]) => {
numArr.forEach((num)=>{
const expectedVal = num - 11;
const itMsg = `Given ${num} ${attr}, should return ${expectedVal} for ${attr}_mod`;
it(itMsg,()=>{
k.getAllAttrs({
callback:(attributes,sections,casc)=>{
attributes[`${attr}_goal`] = num;
expect(k.callFunc('calcAttributeMod',{trigger:{name:attr},attributes})).toBe(expectedVal);
}
});
});
});
});
});
describe('calcMovement({attributes})',()=>{
[-1,0,2].forEach(num => {
const expectedVal = 20 + num * 5;
const itMsg = `Given ${num} athletics, should return ${expectedVal}`;
it(itMsg,()=>{
k.getAllAttrs({
callback:(attributes,sections,casc) => {
attributes.athletics = num;
expect(k.callFunc('calcMovement',{attributes})).toBe(expectedVal);
}
})
})
});
});
describe('calcSprint({attributes})',()=>{
[-1,0,2].forEach(num => {
const expectedVal = 10 + num * 5;
const itMsg = `Given ${num} athletics, should return ${expectedVal}`;
it(itMsg,()=>{
k.getAllAttrs({
callback:(attributes,sections,casc) => {
attributes.athletics = num;
expect(k.callFunc('calcSprint',{attributes})).toBe(expectedVal);
}
});
});
});
});
describe('calcItemSlotsMax({attributes})',()=>{
it('Max item slots should equal the largest of SIZ + 3 or SIZ + ATH + 1',()=>{
k.getAllAttrs({
callback:(attributes,sections,casc)=>{
// siz + athletics + 1 < siz + 3
attributes.attributes.size = 4;
attributes.attributes.athletics = 1;
const sizExpected = 7;
const sizReceived = k.callFunc('calcItemSlotsMax',{attributes});
expect(sizReceived).toBe(sizExpected);
// siz + athletics + 1 === siz +3
attributes.attributes.size = 4;
attributes.attributes.athletics = 2;
const equalExpected = 7;
const equalReceived = k.callFunc('calcItemSlotsMax',{attributes});
expect(equalReceived).toBe(equalExpected);
// siz + athletics + 1 > siz +3
attributes.attributes.size = 4;
attributes.attributes.athletics = 4;
const athExpected = 9;
const athReceived = k.callFunc('calcItemSlotsMax',{attributes});
expect(athReceived).toBe(athExpected);
}
});
});
});
describe('calcUsedSlots({attributes,sections})',()=>{
it('used Item slots should equal the number of equipped items',()=>{
k.getAllAttrs({
callback:(attributes,sections,casc)=>{
// no equipped items
const emptyUsed = k.callFunc('calcUsedSlots',{attributes,sections});
expect(emptyUsed).toBe(0);
// 3 equipped gear pieces; 1 that doesn't use a slot, no weapons
const equippedRows = [1,2,3].map(n=>k.generateRowID('repeating_equipped',sections));
equippedRows.forEach((row,n)=>
attributes.attributes[`${row}_uses_slot`] =
n === 1 ?
0 :
1
);
const equipUsed = k.callFunc('calcUsedSlots',{attributes,sections});
expect(equipUsed).toBe(2);
// 3 equipped gear pieces; 1 that doesn't use a slot, and 2 weapons; 1 that doesn't use a slot
const weaponRows = [1,2].map(n=>k.generateRowID('repeating_weapon',sections));
weaponRows.forEach((row,n)=>
attributes.attributes[`${row}_uses_slot`] =
n === 1 ?
0 :
1
);
const weaponUsed = k.callFunc('calcUsedSlots',{attributes,sections});
expect(weaponUsed).toBe(3);
}
})
})
});
});
+5
View File
@@ -0,0 +1,5 @@
include ./variables.js
include ./calculations.js
include ./triggeredFuncs.js
include ./rollFunctions.js
include ./navFuncs.js
+113
View File
@@ -0,0 +1,113 @@
/**
* Function to calculate the raw attribute score
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const calcAttribute = function({trigger,attributes,sections,casc}){
const [charac1,charac2] = attributesDetails[trigger.name];
return 10 + attributes[charac1] + attributes[charac2];
};
k.registerFuncs({calcAttribute});
/**
* Function to calculate the attribute modifier
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const calcAttributeMod = function({trigger,attributes,sections,casc}){
const baseName = `${trigger.name}_goal`;
return attributes[baseName] - 11;
};
k.registerFuncs({calcAttributeMod});
/**
* Function to calculate the sprint speed of a character
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const calcSprint = function({trigger,attributes,sections,casc}){
return attributes.athletics * 5 + 10;
};
k.registerFuncs({calcSprint});
/**
* Function to calculate the movement speed of a character
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const calcMovement = function({trigger,attributes,sections,casc}){
return attributes.athletics * 5 + 20;
};
k.registerFuncs({calcMovement});
/**
* Calculates the maximum item slots that can be used without penalty
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const calcItemSlotsMax = function({trigger,attributes,sections,casc}){
return Math.max(
attributes.size + 3,
attributes.athletics + attributes.size + 1
);
};
k.registerFuncs({calcItemSlotsMax});
/**
* Calculates how many slots have been used based on the equipped gear and weapon sections
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const calcUsedSlots = function({trigger,attributes,sections,casc}){
const equippedIDs = [
...sections.repeating_equipped.map(id => `repeating_equipped_${id}`),
...sections.repeating_weapon.map(id => `repeating_weapon_${id}`)
];
let freeWeapons = 3;
return equippedIDs
.reduce((total,section) => {
if(
attributes[`${section}_uses_slot`] &&
(
!section.startsWith('repeating_weapon') ||
freeWeapons <= 0
)
){
total += 1;
}
if(
section.startsWith('repeating_weapon') &&
freeWeapons > 0
){
freeWeapons--;
}
return total;
},0);
};
k.registerFuncs({calcUsedSlots});
/**
* Calculates whether the character is overburdened or not, returning 1 if they are and 0 otherwise
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const calcOverburdened = function({trigger,attributes,sections,casc}){
return attributes.item_slots_max - attributes.item_slots < 0 ?
1 :
0;
};
k.registerFuncs({calcOverburdened});
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
/**
* Resets the fresh state for animation containers so that they do not run on reloading a page.
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const refreshAnimations = function({trigger,attributes,sections,casc}){
$20('#pc-conditions .sidebar-toggle,#pc-conditions').addClass('fresh');
};
k.registerFuncs({ refreshAnimations });
+164
View File
@@ -0,0 +1,164 @@
/**
*
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const rollHealthCheck = async function({trigger,attributes,sections,casc}){
const rollName = trigger.name.replace(/-action$/,'');
const statVal = attributes[rollName];
const rollObj = {
title:`^{${rollName} roll}`,
pure_success:true,
roll:`[[@{roll_state}cs<${statVal}cf>[[${statVal} + 1]]]]`
};
const roll = await executeRoll({rollObj,attributes,sections});
finishRoll(roll.rollId);
};
k.registerFuncs({rollHealthCheck});
/**
* Starts the process of rolling a basic attribute check
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const rollAttribute = async function({trigger,attributes,sections,casc}){
const rollName = trigger.name.replace(/-action$/,'');
const rollAttr = rollName.replace(/-/g,'_');
const rollTransKey = rollName.replace(/-/g,' ');
const rollObj = {
title:`^{${rollName}}`,
roll:`[[@{roll_state} + ${attributes[rollAttr]}[${k.capitalize(getTranslationByKey(rollTransKey))}]]]`
};
const roll = await executeRoll({rollObj,attributes,sections});
finishRoll(roll.rollId);
};
k.registerFuncs({rollAttribute});
/**
* Makes rolls for the repeating sections (weapons and abilities)
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const rollRepeating = async function({trigger,attributes,sections,casc}){
const [section,rowID] = k.parseTriggerName(trigger.name);
const row = `${section}_${rowID}`;
const rollName = attributes[`${row}_name`];
const skillName = attributes[`${row}_skill`];
const [modifier] = getSkillRollModifier(skillName,attributes);
const rollObj = {
title:rollName,
roll_name:`^{${skillName}}`,
roll:`[[@{roll_state}${modifier}]]]`
};
if(attributes[`${row}_damage`]){
rollObj.damage = attributes[`${row}_damage`];
if(attributes[`${row}_damage_characteristic`]){
rollObj.damage += `+ @{${attributes[`${row}_damage_characteristic`]}}[${k.capitalize(getTranslationByKey(attributes[`${row}_damage_characteristic`]))}]`;
}
rollObj.damage = `[[${rollObj.damage}]]`;
}else{
rollObj.description = attributes[`${row}_description`];
}
const roll = await executeRoll({rollObj,attributes,sections});
finishRoll(roll.rollId);
};
k.registerFuncs({rollRepeating});
const getSkillRollModifier = (rollName,attributes,row) => {
row = row ? `${row}_` : '';
const rollAttr = `${row}${rollName.replace(/-/g,'_')}`.replace(/_$/,'');
const rollTransKey = row ?
'':
rollName.replace(/-/g,' ');
const characteristicName = row ?
attributes[`${row}characteristic`] :
skills[rollTransKey].characteristic;
const characteristic = attributes[characteristicName] || 0;
const mastery = attributes[`${rollAttr}_mastery`];
const profLabel = rollTransKey ?
k.capitalize(getTranslationByKey(rollTransKey)) :
attributes[`${row}name`];
const charLabel = k.capitalize(getTranslationByKey(characteristicName));
const profBonus = attributes.roll_state === '1D20' ?
Math.max(attributes[`${rollAttr}_proficiency`] * 2, 1) :
attributes[`${rollAttr}_proficiency`];
const rollMsg = `${mastery ? 'cs>19' : ''} + ${profBonus}[${profLabel}] + ${characteristic}[${charLabel}`;
return [
rollMsg,
characteristicName,
rollTransKey];
};
/**
* Starts the process of rolling a basic skill check
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const rollSkill = async function({trigger,attributes,sections,casc}){
const [section,rowID,button] = k.parseTriggerName(trigger.name);
const row = section ?
`${section}_${rowID}` :
'';
const rollName = button.replace(/-?action$/,'');
const [modifier,,rollTransKey] = getSkillRollModifier(rollName,attributes,row);
const rollObj = {
title:section ?
attributes[`${row}_name`]:
`^{${rollTransKey}}`,
roll:`[[@{roll_state}${modifier}]]]`
};
const roll = await executeRoll({rollObj,attributes,sections});
finishRoll(roll.rollId);
};
k.registerFuncs({rollSkill});
/**
* Rolls for luck and displays `lucky` or `unlucky`
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const rollLuck = async function({trigger,attributes,sections,casc}){
const luckTarget = attributes.luck === 'odd' ?
1 :
0;
const rollObj = {
title:'^{luck}',
luck:`[[@{roll_state}]]`
};
const roll = await executeRoll({rollObj,attributes,sections});
const computeObj = {};
const rollModulus = roll.results.luck.result % 2;
computeObj.luck = rollModulus === luckTarget ?
1 :
0;
finishRoll(roll.rollId,computeObj);
};
k.registerFuncs({rollLuck});
const assembleRoll = (rollObj,attributes,sections) => {
return Object.entries(rollObj).reduce((str,[field,content])=>{
return str += ` {{${field}=${content}}}`;
},'@{template_start}');
};
/**
* Executes the roll. Uses a callback if any computations are required.
* @param {object} rollObj - Object describing the roll fields to be used
* @param {object} attributes - The character attributes
* @param {object[]} sections - The sections array
* @returns {Promise} - Resolves to the roll results object
*/
const executeRoll = async ({rollObj,attributes,sections}) => {
const rollString = assembleRoll(rollObj,attributes,sections);
return startRoll(rollString);
};
@@ -0,0 +1,220 @@
/**
* (un)equips armor items
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const equipArmor = function({trigger,attributes,sections,casc}){
const [section,rowID,attr] = k.parseTriggerName(trigger.name);
const row = section ?
`${section}_${rowID}` :
k.generateRowID('repeating_stored',sections);
const armorAttrs = {
equipped:{
'armor':attributes[`${row}_armor_bonus`],
'armor_name':attributes[`${row}_name`],
'armor_details':attributes[`${row}_description`],
'armor_size':attributes[`${row}_item_size`],
},
stored:{
[`${row}_armor_bonus`]:attributes.armor,
[`${row}_name`]:attributes.armor_name,
[`${row}_description`]:attributes.armor_details,
[`${row}_item_size`]:attributes.armor_size,
}
};
const actionType = section ? 'equipped' : 'stored';
const attrMap = armorAttrs[actionType];
// If we are equipping a stored armor, then store the currently equipped armor in the stored items repeating section.
if(section){
equipArmor({trigger:{name:'armor_name'},attributes,sections,casc});
}
if(
!section && attributes.armor_name
){
attrMap[`${row}_type`] = 'armor';
}
// copy the values to the target
Object.entries(attrMap)
.forEach(([key,val]) => {
if(section || val){
attributes[key] = val;
}
});
// If we equipped a stored armor, remove it from the stored gear repeating section.
if(section){
k.removeRepeatingRow(row,attributes,sections);
}else{
Object.keys(armorAttrs.equipped).forEach(
attr => attributes[attr] = ''
);
}
};
k.registerFuncs({equipArmor});
/**
* Moves gear/weapon/armor items around on the sheet equip/unequip them.
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const equipItem = function({trigger,attributes,sections,casc}){
const [section,rowID,attr] = k.parseTriggerName(trigger.name);
const row = `${section}_${rowID}`;
if(
section === 'repeating_stored' &&
attributes[`${row}_type`] === 'armor'){
equipArmor({trigger,attributes,sections,casc});
return;
}
const newSection = section === 'repeating_stored' ?
(
attributes[`${row}_type`] === 'weapon' ?
'repeating_weapon' :
'repeating_equipped'
):
'repeating_stored';
const newRow = k.generateRowID(newSection,sections);
Object.keys(casc)
.forEach(cascKey => {
if(cascKey.startsWith(`attr_${row}`)){
const attrRef = cascKey.replace(/attr_repeating_[^_]+_[^_]+_/,'');
k.debug({[attrRef]:attributes[`${row}_${attrRef}`]})
attributes[`${newRow}_${attrRef}`] = attributes[`${row}_${attrRef}`];
}
});
attributes[`${newRow}_equipped`] = newSection.endsWith('stored') ?
0 :
1;
debugger;
if(section !== 'repeating_stored'){
attributes[`${newRow}_type`] = section.endsWith('equipped') ?
'gear' :
'weapon';
}else{
attributes[`${newRow}_type`] = attributes[`${newRow}_type`] || 'gear';
}
k.removeRepeatingRow(row,attributes,sections);
};
k.registerFuncs({equipItem});
/**
* Manipulates the classes on the npc skill containers to ensure that only those with an entered value are displayed in collapsed mode
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const displayNPCSkill = function({trigger,attributes,sections,casc}){
const skill = trigger.name;
const rawSkillValue = attributes.attributes[skill];
const action = rawSkillValue ?
'removeClass' :
'addClass';
const $elem = $20(`.npc-${skill.replace(/\s+/g,'-').replace(/\(|\)|\//g,'')}-container`);
$elem[action]('expanded');
};
k.registerFuncs({displayNPCSkill});
/**
* Sets up the npc skill display based on the collected values by calling displayNPCSkill for each skill
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const NPCSkillSetup = function({trigger,attributes,sections,casc}){
Object
.keys(skills)
.forEach((skill) =>
displayNPCSkill({trigger:{name:skill},attributes,sections,casc}));
};
k.registerFuncs({NPCSkillSetup},{type:['opener']});
/**
*
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const showConditions = function({trigger,attributes,sections,casc}){
$20('#pc-conditions .sidebar-toggle,#pc-conditions').removeClass('fresh');
$20('#pc-conditions').toggleClass('active');
};
k.registerFuncs({showConditions});
/**
* Resets the tracker for a condition to 0 when the condition is turned off
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const resetCondition = function({trigger,attributes,sections,casc}){
if(attributes[trigger.name]){
attributes[`track_${trigger.name}`] = '';
}
};
k.registerFuncs({resetCondition});
/**
* Decrements the indicated input
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const decrementInput = function({trigger,attributes,sections,casc}){
const [section,rowID,buttonName] = k.parseTriggerName(trigger.name);
const attrName = buttonName.replace(/^decrement-/,'').replace(/-/g,'_');
attributes[attrName] -= 1;
};
k.registerFuncs({decrementInput});
/**
* Increments the indicated input
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const incrementInput = function({trigger,attributes,sections,casc}){
const [section,rowID,buttonName] = k.parseTriggerName(trigger.name);
const attrName = buttonName.replace(/^increment-/,'').replace(/-/g,'_');
attributes[attrName] += 1;
};
k.registerFuncs({incrementInput});
/**
* Sets the pc/npc specific button displays
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const sheetTypeDisplay = function({trigger,attributes,sections,casc}){
if(attributes.character_type === 'pc'){
$20('.tabs--backbone__button--pc').removeClass('inactive');
$20('.tabs--backbone__button--npc').addClass('inactive');
}else{
$20('.tabs--backbone__button--pc').addClass('inactive');
$20('.tabs--backbone__button--npc').removeClass('inactive');
}
};
k.registerFuncs({sheetTypeDisplay},{type:['opener']});
/**
* Handles the necessary setup for adding a weapon
* @param {object} trigger - The trigger that caused the function to be called
* @param {object} attributes - The attribute values of the character
* @param {object[]} sections - All the repeating section IDs
* @param {object} casc - Expanded cascade object
*/
const addWeapon = function({trigger,attributes,sections,casc,newRow}){
attributes[`${newRow}_type`] = 'weapon';
};
k.registerFuncs({addWeapon});
+1
View File
@@ -0,0 +1 @@
k.version = 1;