153 lines
3.8 KiB
JavaScript
153 lines
3.8 KiB
JavaScript
import { includes, cloneDeep } from 'lodash';
|
|
|
|
export default class ComputationMemo {
|
|
constructor(props){
|
|
this.statsByVariableName = {};
|
|
this.originalPropsById = {};
|
|
this.propsById = {};
|
|
this.skillsByAbility = {};
|
|
this.unassignedEffects = [];
|
|
props.filter((prop) => {
|
|
// skip effects and proficiencies for the next pass
|
|
if (prop.type === 'effect' || prop.type === 'proficiency') return true;
|
|
// Add all the stats
|
|
this.addStat(prop);
|
|
}).forEach((prop) => {
|
|
// Now add all effects and proficiencies
|
|
if (prop.type === 'effect'){
|
|
this.addEffect(prop);
|
|
} else if (prop.type === 'proficiency') {
|
|
this.addProficiency(prop);
|
|
}
|
|
});
|
|
}
|
|
registerProperty(prop){
|
|
this.originalPropsById[prop._id] = cloneDeep(prop);
|
|
this.propsById[prop._id] = prop;
|
|
prop.computationDetails = propDetails(prop);
|
|
return prop;
|
|
}
|
|
addStat(prop){
|
|
prop = this.registerProperty(prop);
|
|
let variableName = prop.variableName;
|
|
if (!variableName) return;
|
|
if (this.statsByVariableName[variableName]){
|
|
prop.value = NaN;
|
|
prop.computationDetails.error = 'variableNameCollision';
|
|
console.warn('variableNameCollision', prop);
|
|
return;
|
|
}
|
|
this.statsByVariableName[variableName] = prop;
|
|
if (
|
|
prop.type === 'skill' &&
|
|
includes(['skill', 'check'], prop.skillType) &&
|
|
prop.ability
|
|
){
|
|
this.addSkillToAbility(prop, prop.ability)
|
|
}
|
|
}
|
|
addSkillToAbility(prop, ability){
|
|
if (!this.skillsByAbility[ability]){
|
|
this.skillsByAbility[ability] = [];
|
|
}
|
|
this.skillsByAbility[ability].push(prop);
|
|
}
|
|
addEffect(prop){
|
|
prop = this.registerProperty(prop);
|
|
let targets = this.getEffectTargets(prop);
|
|
targets.forEach(target => {
|
|
target.computationDetails.effects.push(prop);
|
|
});
|
|
if (!targets.size){
|
|
this.unassignedEffects.push(prop);
|
|
}
|
|
}
|
|
getEffectTargets(prop){
|
|
let targets = new Set();
|
|
if (!prop.stats) return targets;
|
|
prop.stats.forEach((statName) => {
|
|
let target = this.statsByVariableName[statName];
|
|
if (!target) return;
|
|
targets.add(target);
|
|
if (isSkillOperation(prop) && isAbility(target)){
|
|
let extras = this.skillsByAbility[statName] || [];
|
|
targets.add(...extras)
|
|
}
|
|
});
|
|
return targets;
|
|
}
|
|
addProficiency(prop){
|
|
prop = this.registerProperty(prop);
|
|
let targets = this.getProficiencyTargets(prop);
|
|
targets.forEach(target => {
|
|
target.computationDetails.proficiencies.push(prop);
|
|
});
|
|
}
|
|
getProficiencyTargets(prop){
|
|
let targets = new Set();
|
|
if (!prop.stats) return targets;
|
|
prop.stats.forEach(statName => {
|
|
let target = this.statsByVariableName[statName];
|
|
if (!target) return;
|
|
targets.add(target);
|
|
if (isAbility(target) && isSkillCheck(prop)) {
|
|
let extras = this.skillsByAbility[statName] || [];
|
|
targets.add(...extras)
|
|
}
|
|
});
|
|
return targets;
|
|
}
|
|
}
|
|
|
|
const skillOperations = [
|
|
'advantage',
|
|
'disadvantage',
|
|
'passiveAdd',
|
|
'fail',
|
|
'conditional',
|
|
'rollBonus',
|
|
];
|
|
|
|
function isAbility(prop){
|
|
return prop.type === 'attribute' &&
|
|
prop.attributeType === 'ability'
|
|
}
|
|
|
|
function isSkillCheck(prop){
|
|
return includes(['skill', 'check'], prop.skillType);
|
|
}
|
|
|
|
function isSkillOperation(prop){
|
|
return skillOperations.includes(prop.operation);
|
|
}
|
|
|
|
function propDetails(prop){
|
|
return propDetailsByType[prop.type]() || {};
|
|
}
|
|
|
|
const propDetailsByType = {
|
|
attribute(){
|
|
return {
|
|
computed: false,
|
|
busyComputing: false,
|
|
effects: [],
|
|
};
|
|
},
|
|
skill(){
|
|
return {
|
|
computed: false,
|
|
busyComputing: false,
|
|
effects: [],
|
|
proficiencies: [],
|
|
};
|
|
},
|
|
effect(){
|
|
return {
|
|
computed: false,
|
|
};
|
|
},
|
|
damageMultiplier(){
|
|
return {};
|
|
},
|
|
}
|