diff --git a/app/imports/api/creature/creatureProperties/methods/damageProperty.js b/app/imports/api/creature/creatureProperties/methods/damageProperty.js index d9af78f5..075aa34f 100644 --- a/app/imports/api/creature/creatureProperties/methods/damageProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/damageProperty.js @@ -24,7 +24,7 @@ const damageProperty = new ValidatedMethod({ run({ _id, operation, value }) { // Get action context - const prop = CreatureProperties.findOne(_id); + let prop = CreatureProperties.findOne(_id); if (!prop) throw new Meteor.Error( 'Damage property failed', 'Property doesn\'t exist' ); @@ -42,6 +42,14 @@ const damageProperty = new ValidatedMethod({ `Property of type "${prop.type}" can't be damaged` ); } + + // Replace the prop by its actionContext counterpart if possible + if (prop.variableName) { + const actionContextProp = actionContext.scope[prop.variableName]; + if (actionContextProp?._id === prop._id) { + prop = actionContextProp; + } + } const result = damagePropertyWork({ prop, operation, value, actionContext }); @@ -94,6 +102,9 @@ export function damagePropertyWork({ prop, operation, value, actionContext }) { }, { selector: prop }); + // Also write it straight to the prop so that it is updated in the actionContext + prop.damage = damage; + prop.value = newValue; } else if (operation === 'increment'){ let currentValue = prop.value || 0; let currentDamage = prop.damage || 0; @@ -111,6 +122,9 @@ export function damagePropertyWork({ prop, operation, value, actionContext }) { }, { selector: prop }); + // Also write it straight to the prop so that it is updated in the actionContext + prop.damage += increment; + prop.value -= increment; } applyTriggers(actionContext.triggers?.damageProperty?.after, prop, actionContext); diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index 26da462a..8d6bd185 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -20,7 +20,7 @@ export default function applyAction(node, actionContext) { recalculateInlineCalculations(prop.summary, actionContext); content.value = prop.summary.value; } - actionContext.addLog(content); + if (!prop.silent) actionContext.addLog(content); // Spend the resources const failed = spendResources(prop, actionContext); @@ -188,7 +188,7 @@ function applyChildren(node, actionContext) { function spendResources(prop, actionContext){ // Check Uses if (prop.usesLeft <= 0){ - actionContext.addLog({ + if (!prop.silent) actionContext.addLog({ name: 'Error', value: `${prop.name || 'action'} does not have enough uses left`, }); @@ -196,7 +196,7 @@ function spendResources(prop, actionContext){ } // Resources if (prop.insufficientResources){ - actionContext.addLog({ + if (!prop.silent) actionContext.addLog({ name: 'Error', value: 'This creature doesn\'t have sufficient resources to perform this action', }); @@ -257,7 +257,7 @@ function spendResources(prop, actionContext){ }, { selector: prop }); - actionContext.addLog({ + if (!prop.silent) actionContext.addLog({ name: 'Uses left', value: prop.usesLeft - 1, inline: true, @@ -288,12 +288,12 @@ function spendResources(prop, actionContext){ }); // Log all the spending - if (gainLog.length) actionContext.addLog({ + if (gainLog.length && !prop.silent) actionContext.addLog({ name: 'Gained', value: gainLog.join('\n'), inline: true, }); - if (spendLog.length) actionContext.addLog({ + if (spendLog.length && !prop.silent) actionContext.addLog({ name: 'Spent', value: spendLog.join('\n'), inline: true, diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js b/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js index cc63f53a..ede94665 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js @@ -24,7 +24,7 @@ export default function applyAdjustment(node, actionContext){ damageTargets.forEach(target => { let stat = target.variables[prop.stat]; if (!stat?.type) { - actionContext.addLog({ + if (!prop.silent) actionContext.addLog({ name: 'Error', value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set` }); @@ -36,7 +36,7 @@ export default function applyAdjustment(node, actionContext){ value, actionContext, }); - actionContext.addLog({ + if (!prop.silent) actionContext.addLog({ name: 'Attribute damage', value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` + ` ${value}`, @@ -44,7 +44,7 @@ export default function applyAdjustment(node, actionContext){ }); }); } else { - actionContext.addLog({ + if (!prop.silent) actionContext.addLog({ name: 'Attribute damage', value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` + ` ${value}`, diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js b/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js index 3106b88b..ce918c4a 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js @@ -36,25 +36,25 @@ export default function applyBranch(node, actionContext){ break; case 'hit': if (scope['$attackHit']?.value){ - if (!targets.length) actionContext.addLog({value: '**On hit**'}); + if (!targets.length && !prop.silent) actionContext.addLog({value: '**On hit**'}); applyChildren(); } break; case 'miss': if (scope['$attackMiss']?.value){ - if (!targets.length) actionContext.addLog({value: '**On miss**'}); + if (!targets.length && !prop.silent) actionContext.addLog({value: '**On miss**'}); applyChildren(); } break; case 'failedSave': if (scope['$saveFailed']?.value){ - if (!targets.length) actionContext.addLog({value: '**On failed save**'}); + if (!targets.length && !prop.silent) actionContext.addLog({value: '**On failed save**'}); applyChildren(); } break; case 'successfulSave': if (scope['$saveSucceeded']?.value){ - if (!targets.length) actionContext.addLog({value: '**On save**',}); + if (!targets.length && !prop.silent) actionContext.addLog({value: '**On save**',}); applyChildren(); } break; diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js b/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js index 965ef8b3..93bf413c 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js @@ -13,6 +13,7 @@ import logErrors from './shared/logErrors.js'; import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js'; import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; +import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js'; export default function applyBuff(node, actionContext){ applyNodeTriggers(node, 'before', actionContext); @@ -32,7 +33,9 @@ export default function applyBuff(node, actionContext){ }); } addChildrenToPropList(node.children); - crystalizeVariables({propList, actionContext}); + if (!prop.skipCrystalization) { + crystalizeVariables({propList, actionContext}); + } let oldParent = { id: prop.parent.id, @@ -43,7 +46,7 @@ export default function applyBuff(node, actionContext){ copyNodeListToTarget(propList, target, oldParent); //Log the buff - if (prop.name || prop.description?.value){ + if ((prop.name || prop.description?.value) && !prop.silent){ if (target._id === actionContext.creature._id){ // Targeting self actionContext.addLog({ @@ -96,6 +99,7 @@ function crystalizeVariables({propList, actionContext}){ delete prop._skipCrystalize; return; } + // Iterate through all the calculations and crystalize them computedSchemas[prop.type].computedFields().forEach( calcKey => { applyFnToKey(prop, calcKey, (prop, key) => { const calcObj = get(prop, key); @@ -132,5 +136,36 @@ function crystalizeVariables({propList, actionContext}){ calcObj.hash = cyrb53(calcObj.calculation); }); }); + // For each key in the schema + computedSchemas[prop.type].inlineCalculationFields().forEach( calcKey => { + // That ends in .inlineCalculations + applyFnToKey(prop, calcKey, (prop, key) => { + const inlineCalcObj = get(prop, key); + if (!inlineCalcObj) return; + + // If there is no text, skip + if (!inlineCalcObj.text){ + return; + } + + // Replace all the existing calculations + let index = -1; + inlineCalcObj.text = inlineCalcObj.text.replace(INLINE_CALCULATION_REGEX, () => { + index += 1; + return `{${inlineCalcObj.inlineCalculations[index].calculation}}`; + }); + + // Set the value to the uncomputed string + inlineCalcObj.value = inlineCalcObj.text; + + // Write a new hash + const inlineCalcHash = cyrb53(inlineCalcObj.text); + if (inlineCalcHash === inlineCalcObj.hash) { + // Skip if nothing changed + return; + } + inlineCalcObj.hash = inlineCalcHash; + }); + }); }); } diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBuffRemover.js b/app/imports/api/engine/actions/applyPropertyByType/applyBuffRemover.js index 09e4e987..86b60949 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyBuffRemover.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBuffRemover.js @@ -13,7 +13,7 @@ export default function applyBuffRemover(node, actionContext) { const prop = node.node; // Log Name - if (prop.name){ + if (prop.name && !prop.silent){ actionContext.addLog({ name: prop.name }); } @@ -29,7 +29,7 @@ export default function applyBuffRemover(node, actionContext) { }); return; } - removeBuff(nearestBuff, actionContext); + removeBuff(nearestBuff, actionContext, prop); } else { // Get all the buffs targeted by tags const allBuffs = getPropertiesOfType(actionContext.creature._id, 'buff'); @@ -41,7 +41,7 @@ export default function applyBuffRemover(node, actionContext) { if (prop.removeAll) { // Remove all matching buffs targetedBuffs.forEach(buff => { - removeBuff(buff, actionContext); + removeBuff(buff, actionContext, prop); }); } else { // Sort in reverse order @@ -49,7 +49,7 @@ export default function applyBuffRemover(node, actionContext) { // Remove the one with the highest order const buff = targetedBuffs[0]; if (buff) { - removeBuff(buff, actionContext); + removeBuff(buff, actionContext, prop); } } } @@ -60,8 +60,8 @@ export default function applyBuffRemover(node, actionContext) { node.children.forEach(child => applyProperty(child, actionContext)); } -function removeBuff(buff, actionContext) { - actionContext.addLog({ +function removeBuff(buff, actionContext, prop) { + if (!prop.silent) actionContext.addLog({ name: 'Removed', value: `${buff.name || 'Buff'}` }); diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js index 0ee3bbd7..6900fa56 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js @@ -128,7 +128,7 @@ export default function applyDamage(node, actionContext){ // There are no targets, just log the result logValue.push(`**${damage}** ${suffix}`); } - actionContext.addLog({ + if (!prop.silent) actionContext.addLog({ name: logName, value: logValue.join('\n'), inline: true, @@ -219,6 +219,16 @@ function dealDamage({target, damageType, amount, actionContext}){ if (damageType === 'healing') damageLeft = -totalDamage; healthBars.forEach(healthBar => { if (damageLeft === 0) return; + // Replace the healthbar by the one in the action context if we can + // The damagePropertyWork function bashes the prop with the damage + // So we can use the new value in later action properties + if (healthBar.variableName) { + const targetHealthBar = target.variables[healthBar.variableName]; + if (targetHealthBar?._id === healthBar._id) { + healthBar = targetHealthBar; + } + } + // Do the damage let damageAdded = damagePropertyWork({ prop: healthBar, operation: 'increment', diff --git a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js index 6064e3bd..a5aa4bae 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js @@ -20,7 +20,7 @@ export default function applySavingThrow(node, actionContext){ }); return node.children.forEach(child => applyProperty(child, actionContext)); } - actionContext.addLog({ + if (!prop.silent) actionContext.addLog({ name: prop.name, value: `DC **${dc}**`, inline: true, @@ -94,7 +94,7 @@ export default function applySavingThrow(node, actionContext){ } else { scope['$saveFailed'] = {value: true}; } - actionContext.addLog({ + if (!prop.silent) actionContext.addLog({ name: saveSuccess ? 'Successful save' : 'Failed save', value: resultPrefix + '\n**' + result + '**', inline: true, diff --git a/app/imports/api/engine/actions/applyTriggers.js b/app/imports/api/engine/actions/applyTriggers.js index 9343e1ac..8fd6a536 100644 --- a/app/imports/api/engine/actions/applyTriggers.js +++ b/app/imports/api/engine/actions/applyTriggers.js @@ -65,7 +65,7 @@ export function applyTrigger(trigger, prop, actionContext) { recalculateInlineCalculations(trigger.description, actionContext); content.value = trigger.description.value; } - actionContext.addLog(content); + if(!trigger.silent) actionContext.addLog(content); // Get all the trigger's properties and apply them const properties = getPropertyDecendants(actionContext.creature._id, trigger._id); diff --git a/app/imports/api/engine/actions/doCastSpell.js b/app/imports/api/engine/actions/doCastSpell.js index 95169e90..ef71759d 100644 --- a/app/imports/api/engine/actions/doCastSpell.js +++ b/app/imports/api/engine/actions/doCastSpell.js @@ -9,7 +9,6 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js'; import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js'; import { doActionWork } from '/imports/api/engine/actions/doAction.js'; -import { CreatureLogSchema } from '/imports/api/creature/log/CreatureLogs.js'; import ActionContext from '/imports/api/engine/actions/ActionContext.js'; const doAction = new ValidatedMethod({ @@ -65,8 +64,6 @@ const doAction = new ValidatedMethod({ let slotLevel = spell.level || 0; let slot; - actionContext.scope['slotLevel'] = slotLevel; - if (slotId && !spell.castWithoutSpellSlots){ slot = CreatureProperties.findOne(slotId); if (!slot){ @@ -109,6 +106,8 @@ const doAction = new ValidatedMethod({ }); } + actionContext.scope['slotLevel'] = slotLevel; + // Do the action doActionWork({ properties, ancestors, actionContext, methodScope: scope, diff --git a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js index c729bf81..44fc93e9 100644 --- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js @@ -14,6 +14,7 @@ const linkDependenciesByType = { effect: linkEffects, proficiency: linkProficiencies, roll: linkRoll, + pointBuy: linkPointBuy, propertySlot: linkSlot, skill: linkSkill, spell: linkAction, @@ -242,6 +243,28 @@ function linkDamageMultiplier(dependencyGraph, prop) { }); } +function linkPointBuy(dependencyGraph, prop){ + dependOnCalc({ dependencyGraph, prop, key: 'min' }); + dependOnCalc({ dependencyGraph, prop, key: 'max' }); + dependOnCalc({ dependencyGraph, prop, key: 'cost' }); + dependOnCalc({ dependencyGraph, prop, key: 'total' }); + prop.values?.forEach(row => { + // Wrap the document in a new object so we don't bash it unintentionally + const pointBuyRow = { + ...row, + type: 'pointBuyRow', + tableName: prop.name, + tableId: prop._id, + } + dependencyGraph.addNode(row._id, pointBuyRow); + linkVariableName(dependencyGraph, pointBuyRow); + dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.min' }); + dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.max' }); + dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.cost' }); + }); + if (prop.inactive) return; +} + function linkProficiencies(dependencyGraph, prop){ // The stats depend on the proficiency if (prop.inactive) return; diff --git a/app/imports/api/engine/computation/computeComputation/computeByType.js b/app/imports/api/engine/computation/computeComputation/computeByType.js index b6ccff38..7597b976 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType.js @@ -2,6 +2,7 @@ import _variable from './computeByType/computeVariable.js'; import action from './computeByType/computeAction.js'; import attribute from './computeByType/computeAttribute.js'; import skill from './computeByType/computeSkill.js'; +import pointBuy from './computeByType/computePointBuy.js'; import propertySlot from './computeByType/computeSlot.js'; import container from './computeByType/computeContainer.js'; import _calculation from './computeByType/computeCalculation.js'; @@ -13,6 +14,7 @@ export default Object.freeze({ attribute, container, skill, + pointBuy, propertySlot, spell: action, }); diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js b/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js new file mode 100644 index 00000000..c2cc7606 --- /dev/null +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js @@ -0,0 +1,53 @@ +import { has } from 'lodash'; +import evaluateCalculation from '../../utility/evaluateCalculation.js'; + +export default function computePointBuy(computation, node) { + const prop = node.data; + const tableMin = prop.min?.value || null; + const tableMax = prop.max?.value || null; + prop.spent = 0; + prop.values?.forEach(row => { + // Clean up added properties + // delete row.tableId; + // delete row.tableName; + // delete row.type; + + row.spent = 0; + if (row.value === undefined) return; + const min = has(row, 'min.value') ? row.min.value : tableMin; + const max = has(row, 'max.value') ? row.max.value : tableMax; + const costFunction = EJSON.clone(row.cost || prop.cost); + if (costFunction) costFunction.parseLevel = 'reduce'; + + // Check min and max + if (min !== null && row.value < min) { + row.value = min; + } + if (max !== null && row.value > max) { + row.value = max; + } + // Evaluate the cost function + if (!costFunction) return; + evaluateCalculation(costFunction, { ...computation.scope, value: row.value }); + // Write calculation errors + costFunction.errors?.forEach(error => { + if (error?.message) { + row.errors = row.errors || []; + error.message = 'Cost calculation error.\n' + error.message; + row.errors.push(error); + } + }); + if (Number.isFinite(costFunction.value)) { + row.spent = costFunction.value; + prop.spent += costFunction.value; + } + }); + prop.pointsLeft = (prop.total?.value || 0) - (prop.spent || 0); + if (prop.spent > prop.total?.value) { + prop.errors = prop.errors || []; + prop.errors.push({ + type: 'pointBuyError', + message: 'Spent more than total points available', + }); + } +} diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeSkill.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeSkill.js index 2e79d18a..9243adb4 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeSkill.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeSkill.js @@ -4,7 +4,7 @@ // by computeVariableAsSkill export default function computeSkill(computation, node){ const prop = node.data; - prop.proficiency = prop.baseProficiency; + prop.proficiency = prop.baseProficiency || 0; let profBonus = computation.scope['proficiencyBonus']?.value || 0; // Multiply the proficiency bonus by the actual proficiency if(prop.proficiency === 0.49){ diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateDefinition.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateDefinition.js index 66fbe79a..d665b284 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateDefinition.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateDefinition.js @@ -8,7 +8,13 @@ export default function aggregateDefinition({node, linkedNode, link}){ // get current defining prop const definingProp = node.data.definingProp; // Find the last defining prop - if (!definingProp || prop.order > definingProp.order){ + if ( + !definingProp || + prop.type !== 'pointBuyRow' && ( + definingProp.type === 'pointBuyRow' || + prop.order > definingProp.order + ) + ) { // override the current defining prop overrideProp(definingProp, node); // set this prop as the new defining prop @@ -18,9 +24,32 @@ export default function aggregateDefinition({node, linkedNode, link}){ } // Aggregate the base value due to the defining properties - const propBaseValue = prop.baseValue?.value; + let propBaseValue = prop.baseValue?.value; + // Point buy rows use prop.value instead of prop.baseValue + if (prop.type === 'pointBuyRow') { + propBaseValue = prop.value; + } if (propBaseValue === undefined) return; + // Store a summary of the definition as a base value effect + node.data.effects = node.data.effects || []; + if (prop.type === 'pointBuyRow') { + node.data.effects.push({ + _id: prop.tableId, + name: prop.tableName, + operation: 'base', + amount: { value: propBaseValue }, + type: 'pointBuy', + }); + } else { + node.data.effects.push({ + _id: prop._id, + name: prop.name, + operation: 'base', + amount: { value: propBaseValue }, + type: prop.type, + }); + } if (node.data.baseValue === undefined || propBaseValue > node.data.baseValue){ node.data.baseValue = propBaseValue; } diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEffect.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEffect.js index 8cf47d36..e4a58317 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEffect.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEffect.js @@ -24,6 +24,7 @@ export default function aggregateEffect({node, linkedNode, link}){ name: linkedNode.data.name, operation: linkedNode.data.operation, amount: linkedNode.data.amount && {value: linkedNode.data.amount.value}, + type: linkedNode.data.type, // ancestors: linkedNode.data.ancestors, }); diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsSkill.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsSkill.js index 37b6afdd..01d34101 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsSkill.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsSkill.js @@ -33,6 +33,9 @@ export default function computeVariableAsSkill(computation, node, prop){ const aggregator = node.data.effectAggregator; const aggregatorBase = aggregator?.base || 0; + // Store effects + prop.effects = node.data.effects; + // If there is no aggregator, determine if the prop can hide, then exit if (!aggregator){ prop.hide = statBase === undefined && @@ -71,8 +74,6 @@ export default function computeVariableAsSkill(computation, node, prop){ prop.fail = aggregator.fail; // Rollbonus prop.rollBonuses = aggregator.rollBonus; - // Store effects - prop.effects = node.data.effects; } function aggregateAbilityEffects({computation, skillNode, abilityNode}){ diff --git a/app/imports/api/engine/computation/utility/getEffectivePropTags.js b/app/imports/api/engine/computation/utility/getEffectivePropTags.js index e3e52bf2..c8ebe65b 100644 --- a/app/imports/api/engine/computation/utility/getEffectivePropTags.js +++ b/app/imports/api/engine/computation/utility/getEffectivePropTags.js @@ -9,6 +9,7 @@ export default function getEffectivePropTags(prop) { } // Tags for some string properties + if (prop.variableName) tags.push(prop.variableName); if (prop.damageType) tags.push(prop.damageType); if (prop.skillType) tags.push(prop.skillType); if (prop.attributeType) tags.push(prop.attributeType); diff --git a/app/imports/api/properties/Actions.js b/app/imports/api/properties/Actions.js index 53e57afa..1ff4d6fd 100644 --- a/app/imports/api/properties/Actions.js +++ b/app/imports/api/properties/Actions.js @@ -114,6 +114,11 @@ let ActionSchema = createPropertySchema({ type: 'fieldToCompute', optional: true, }, + // Prevent the property from showing up in the log + silent: { + type: Boolean, + optional: true, + }, }); const ComputedOnlyActionSchema = createPropertySchema({ diff --git a/app/imports/api/properties/Adjustments.js b/app/imports/api/properties/Adjustments.js index 49c1b248..0d1edb6a 100644 --- a/app/imports/api/properties/Adjustments.js +++ b/app/imports/api/properties/Adjustments.js @@ -31,6 +31,11 @@ const AdjustmentSchema = createPropertySchema({ allowedValues: ['set', 'increment'], defaultValue: 'increment', }, + // Prevent the property from showing up in the log + silent: { + type: Boolean, + optional: true, + }, }); const ComputedOnlyAdjustmentSchema = createPropertySchema({ diff --git a/app/imports/api/properties/Attributes.js b/app/imports/api/properties/Attributes.js index 38721928..51174362 100644 --- a/app/imports/api/properties/Attributes.js +++ b/app/imports/api/properties/Attributes.js @@ -176,6 +176,7 @@ let ComputedOnlyAttributeSchema = createPropertySchema({ effects: { type: Array, optional: true, + removeBeforeCompute: true, }, 'effects.$': { type: Object, diff --git a/app/imports/api/properties/Branches.js b/app/imports/api/properties/Branches.js index 952c7715..386773a1 100644 --- a/app/imports/api/properties/Branches.js +++ b/app/imports/api/properties/Branches.js @@ -37,6 +37,11 @@ let BranchSchema = createPropertySchema({ optional: true, parseLevel: 'compile', }, + // Prevent the property from showing up in the log + silent: { + type: Boolean, + optional: true, + }, }); let ComputedOnlyBranchSchema = createPropertySchema({ diff --git a/app/imports/api/properties/BuffRemovers.js b/app/imports/api/properties/BuffRemovers.js index 8752f750..ae86bf5e 100644 --- a/app/imports/api/properties/BuffRemovers.js +++ b/app/imports/api/properties/BuffRemovers.js @@ -68,6 +68,11 @@ let BuffRemoverSchema = createPropertySchema({ type: String, max: STORAGE_LIMITS.tagLength, }, + // Prevent the property from showing up in the log + silent: { + type: Boolean, + optional: true, + }, }); let ComputedOnlyBuffRemoverSchema = createPropertySchema({}); diff --git a/app/imports/api/properties/Buffs.js b/app/imports/api/properties/Buffs.js index dac2d7ff..2e15d441 100644 --- a/app/imports/api/properties/Buffs.js +++ b/app/imports/api/properties/Buffs.js @@ -29,6 +29,16 @@ let BuffSchema = createPropertySchema({ ], defaultValue: 'target', }, + // Prevent the property from showing up in the log + silent: { + type: Boolean, + optional: true, + }, + // Prevent the children from being crystalized + skipCrystalization: { + type: Boolean, + optional: true, + }, }); let ComputedOnlyBuffSchema = createPropertySchema({ diff --git a/app/imports/api/properties/Damages.js b/app/imports/api/properties/Damages.js index 85cafee0..8f5e2a67 100644 --- a/app/imports/api/properties/Damages.js +++ b/app/imports/api/properties/Damages.js @@ -26,7 +26,12 @@ const DamageSchema = createPropertySchema({ max: STORAGE_LIMITS.calculation, defaultValue: 'slashing', regEx: VARIABLE_NAME_REGEX, - }, + }, + // Prevent the property from showing up in the log + silent: { + type: Boolean, + optional: true, + }, }); const ComputedOnlyDamageSchema = createPropertySchema({ diff --git a/app/imports/api/properties/PointBuys.js b/app/imports/api/properties/PointBuys.js index 95cc1634..db20911a 100644 --- a/app/imports/api/properties/PointBuys.js +++ b/app/imports/api/properties/PointBuys.js @@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js'; import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js'; +import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js'; /* * PointBuys are reason-value attached to skills and abilities @@ -13,13 +14,6 @@ let PointBuySchema = createPropertySchema({ optional: true, max: STORAGE_LIMITS.name, }, - variableName: { - type: String, - optional: true, - regEx: VARIABLE_NAME_REGEX, - min: 2, - max: STORAGE_LIMITS.variableName, - }, ignored: { type: Boolean, optional: true, @@ -27,10 +21,18 @@ let PointBuySchema = createPropertySchema({ 'values': { type: Array, defaultValue: [], + maxCount: STORAGE_LIMITS.pointBuyRowsCount, }, 'values.$': { type: Object, }, + 'values.$._id': { + type: String, + regEx: SimpleSchema.RegEx.Id, + autoValue(){ + if (!this.isSet) return Random.id(); + } + }, 'values.$.name': { type: String, optional: true, @@ -47,6 +49,18 @@ let PointBuySchema = createPropertySchema({ type: Number, optional: true, }, + 'values.$.min': { + type: 'fieldToCompute', + optional: true, + }, + 'values.$.max': { + type: 'fieldToCompute', + optional: true, + }, + 'values.$.cost': { + type: 'fieldToCompute', + optional: true, + }, min: { type: 'fieldToCompute', optional: true, @@ -62,6 +76,7 @@ let PointBuySchema = createPropertySchema({ cost: { type: 'fieldToCompute', optional: true, + parseLevel: 'compile', }, }); @@ -74,11 +89,46 @@ const ComputedOnlyPointBuySchema = createPropertySchema({ type: 'computedOnlyField', optional: true, }, - total: { + cost: { + type: 'computedOnlyField', + optional: true, + parseLevel: 'compile', + }, + 'values': { + type: Array, + defaultValue: [], + maxCount: STORAGE_LIMITS.pointBuyRowsCount, + }, + 'values.$': { + type: Object, + }, + 'values.$.min': { type: 'computedOnlyField', optional: true, }, - cost: { + 'values.$.max': { + type: 'computedOnlyField', + optional: true, + }, + 'values.$.cost': { + type: 'computedOnlyField', + optional: true, + parseLevel: 'compile', + }, + 'values.$.spent': { + type: Number, + optional: true, + removeBeforeCompute: true, + }, + 'values.$.errors': { + type: Array, + optional: true, + removeBeforeCompute: true, + }, + 'values.$.errors.$': { + type: ErrorSchema, + }, + total: { type: 'computedOnlyField', optional: true, }, @@ -87,6 +137,19 @@ const ComputedOnlyPointBuySchema = createPropertySchema({ optional: true, removeBeforeCompute: true, }, + pointsLeft: { + type: Number, + optional: true, + removeBeforeCompute: true, + }, + errors: { + type: Array, + optional: true, + removeBeforeCompute: true, + }, + 'errors.$': { + type: ErrorSchema, + }, }); const ComputedPointBuySchema = new SimpleSchema() diff --git a/app/imports/api/properties/SavingThrows.js b/app/imports/api/properties/SavingThrows.js index f5cfbf70..05210a53 100644 --- a/app/imports/api/properties/SavingThrows.js +++ b/app/imports/api/properties/SavingThrows.js @@ -30,6 +30,11 @@ let SavingThrowSchema = createPropertySchema({ optional: true, max: STORAGE_LIMITS.variableName, }, + // Prevent the property from showing up in the log + silent: { + type: Boolean, + optional: true, + }, }); const ComputedOnlySavingThrowSchema = createPropertySchema({ diff --git a/app/imports/api/properties/Skills.js b/app/imports/api/properties/Skills.js index 47ba2a76..d9f04791 100644 --- a/app/imports/api/properties/Skills.js +++ b/app/imports/api/properties/Skills.js @@ -135,6 +135,7 @@ let ComputedOnlySkillSchema = createPropertySchema({ effects: { type: Array, optional: true, + removeBeforeCompute: true, }, 'effects.$': { type: Object, diff --git a/app/imports/api/properties/Triggers.js b/app/imports/api/properties/Triggers.js index 4b66e422..0bfd7d9d 100644 --- a/app/imports/api/properties/Triggers.js +++ b/app/imports/api/properties/Triggers.js @@ -109,6 +109,11 @@ let TriggerSchema = createPropertySchema({ type: String, max: STORAGE_LIMITS.tagLength, }, + // Prevent the property from showing up in the log + silent: { + type: Boolean, + optional: true, + }, }); const ComputedOnlyTriggerSchema = createPropertySchema({ diff --git a/app/imports/api/properties/computedOnlyPropertySchemasIndex.js b/app/imports/api/properties/computedOnlyPropertySchemasIndex.js index c81f6876..93f3793a 100644 --- a/app/imports/api/properties/computedOnlyPropertySchemasIndex.js +++ b/app/imports/api/properties/computedOnlyPropertySchemasIndex.js @@ -16,6 +16,7 @@ import { ComputedOnlyFeatureSchema } from '/imports/api/properties/Features.js'; import { ComputedOnlyFolderSchema } from '/imports/api/properties/Folders.js'; import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js'; import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js'; +import { ComputedOnlyPointBuySchema } from '/imports/api/properties/PointBuys.js'; import { ComputedOnlyProficiencySchema } from '/imports/api/properties/Proficiencies.js'; import { ComputedOnlyReferenceSchema } from '/imports/api/properties/References.js'; import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js'; @@ -46,6 +47,7 @@ const propertySchemasIndex = { folder: ComputedOnlyFolderSchema, item: ComputedOnlyItemSchema, note: ComputedOnlyNoteSchema, + pointBuy: ComputedOnlyPointBuySchema, proficiency: ComputedOnlyProficiencySchema, propertySlot: ComputedOnlySlotSchema, reference: ComputedOnlyReferenceSchema, diff --git a/app/imports/api/properties/computedPropertySchemasIndex.js b/app/imports/api/properties/computedPropertySchemasIndex.js index 25b480b0..7bdfb302 100644 --- a/app/imports/api/properties/computedPropertySchemasIndex.js +++ b/app/imports/api/properties/computedPropertySchemasIndex.js @@ -16,6 +16,7 @@ import { ComputedFeatureSchema } from '/imports/api/properties/Features.js'; import { FolderSchema } from '/imports/api/properties/Folders.js'; import { ComputedItemSchema } from '/imports/api/properties/Items.js'; import { ComputedNoteSchema } from '/imports/api/properties/Notes.js'; +import { ComputedPointBuySchema } from '/imports/api/properties/PointBuys.js'; import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js'; import { ReferenceSchema } from '/imports/api/properties/References.js'; import { ComputedRollSchema } from '/imports/api/properties/Rolls.js'; @@ -44,6 +45,7 @@ const propertySchemasIndex = { feature: ComputedFeatureSchema, folder: FolderSchema, note: ComputedNoteSchema, + pointBuy: ComputedPointBuySchema, proficiency: ProficiencySchema, propertySlot: ComputedSlotSchema, reference: ReferenceSchema, diff --git a/app/imports/api/properties/propertySchemasIndex.js b/app/imports/api/properties/propertySchemasIndex.js index 23fa4bf4..fdccedc9 100644 --- a/app/imports/api/properties/propertySchemasIndex.js +++ b/app/imports/api/properties/propertySchemasIndex.js @@ -14,6 +14,7 @@ import { EffectSchema } from '/imports/api/properties/Effects.js'; import { FeatureSchema } from '/imports/api/properties/Features.js'; import { FolderSchema } from '/imports/api/properties/Folders.js'; import { NoteSchema } from '/imports/api/properties/Notes.js'; +import { PointBuySchema } from '/imports/api/properties/PointBuys.js'; import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js'; import { ReferenceSchema } from '/imports/api/properties/References.js'; import { RollSchema } from '/imports/api/properties/Rolls.js'; @@ -44,6 +45,7 @@ const propertySchemasIndex = { feature: FeatureSchema, folder: FolderSchema, note: NoteSchema, + pointBuy: PointBuySchema, proficiency: ProficiencySchema, propertySlot: SlotSchema, reference: ReferenceSchema, diff --git a/app/imports/constants/PROPERTIES.js b/app/imports/constants/PROPERTIES.js index b0a91040..30b3248c 100644 --- a/app/imports/constants/PROPERTIES.js +++ b/app/imports/constants/PROPERTIES.js @@ -102,7 +102,13 @@ const PROPERTIES = Object.freeze({ icon: 'mdi-note-outline', name: 'Note', helpText: 'Notes about your character and their adventures', - suggestedParents: ['folder'], + suggestedParents: ['note', 'folder'], + }, + pointBuy: { + icon: 'mdi-table', + name: 'Point Buy', + helpText: 'A point buy table that allows the user to select an array of values that match a given cost', + suggestedParents: [], }, proficiency: { icon: 'mdi-brightness-1', diff --git a/app/imports/constants/STORAGE_LIMITS.js b/app/imports/constants/STORAGE_LIMITS.js index 6e116b07..cbc647e6 100644 --- a/app/imports/constants/STORAGE_LIMITS.js +++ b/app/imports/constants/STORAGE_LIMITS.js @@ -32,6 +32,7 @@ const STORAGE_LIMITS = Object.freeze({ tagCount: 64, writersCount: 20, libraryCollectionCount: 32, + pointBuyRowsCount: 32, }); export default STORAGE_LIMITS; diff --git a/app/imports/server/rest/apiPublications/creature.js b/app/imports/server/rest/apiPublications/creature.js index f805c92b..a35ae634 100644 --- a/app/imports/server/rest/apiPublications/creature.js +++ b/app/imports/server/rest/apiPublications/creature.js @@ -1,6 +1,7 @@ import SimpleSchema from 'simpl-schema'; import Creatures from '/imports/api/creature/creatures/Creatures.js'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; +import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables'; import { assertViewPermission } from '/imports/api/creature/creatures/creaturePermissions.js'; import computeCreature from '/imports/api/engine/computeCreature.js'; import VERSION from '/imports/constants/VERSION.js'; @@ -40,6 +41,9 @@ Meteor.publish('api-creature', function(creatureId){ CreatureProperties.find({ 'ancestors.id': creatureId, }), + CreatureVariables.find({ + _creatureId: creatureId, + }), ]; }, { url: 'api/creature/:0' diff --git a/app/imports/ui/components/global/SmartBtn.vue b/app/imports/ui/components/global/SmartBtn.vue new file mode 100644 index 00000000..324fcf3e --- /dev/null +++ b/app/imports/ui/components/global/SmartBtn.vue @@ -0,0 +1,72 @@ + + + diff --git a/app/imports/ui/components/global/SmartInputMixin.js b/app/imports/ui/components/global/SmartInputMixin.js index 5e44cc7b..9cf02830 100644 --- a/app/imports/ui/components/global/SmartInputMixin.js +++ b/app/imports/ui/components/global/SmartInputMixin.js @@ -115,7 +115,7 @@ export default { }, change(val){ this.dirty = true; - if (this.hasChangeListener) this.loading = true; + if (this.hasChangeListener()) this.loading = true; this.$emit('change', val, this.acknowledgeChange); }, hasChangeListener(){ diff --git a/app/imports/ui/components/global/SmartSlider.vue b/app/imports/ui/components/global/SmartSlider.vue new file mode 100644 index 00000000..b9797d9b --- /dev/null +++ b/app/imports/ui/components/global/SmartSlider.vue @@ -0,0 +1,34 @@ + + + diff --git a/app/imports/ui/components/global/globalIndex.js b/app/imports/ui/components/global/globalIndex.js index 05992177..84c28bbe 100644 --- a/app/imports/ui/components/global/globalIndex.js +++ b/app/imports/ui/components/global/globalIndex.js @@ -5,17 +5,21 @@ import IconPicker from '/imports/ui/components/global/IconPicker.vue'; import TextField from '/imports/ui/components/global/TextField.vue'; import TextArea from '/imports/ui/components/global/TextArea.vue'; import SmartSelect from '/imports/ui/components/global/SmartSelect.vue'; +import SmartBtn from '/imports/ui/components/global/SmartBtn.vue'; import SmartCombobox from '/imports/ui/components/global/SmartCombobox.vue'; import SmartCheckbox from '/imports/ui/components/global/SmartCheckbox.vue'; import SmartSwitch from '/imports/ui/components/global/SmartSwitch.vue'; import SvgIcon from '/imports/ui/components/global/SvgIcon.vue'; +import SmartSlider from '/imports/ui/components/global/SmartSlider.vue'; Vue.component('DatePicker', DatePicker); Vue.component('IconPicker', IconPicker); Vue.component('TextField', TextField); Vue.component('TextArea', TextArea); Vue.component('SmartSelect', SmartSelect); +Vue.component('SmartBtn', SmartBtn); Vue.component('SmartCombobox', SmartCombobox); Vue.component('SmartCheckbox', SmartCheckbox); +Vue.component('SmartSlider', SmartSlider); Vue.component('SmartSwitch', SmartSwitch); Vue.component('SvgIcon', SvgIcon); diff --git a/app/imports/ui/creature/buildTree/BuildTreeNode.vue b/app/imports/ui/creature/buildTree/BuildTreeNode.vue index 8f49fb2d..ca359bf2 100644 --- a/app/imports/ui/creature/buildTree/BuildTreeNode.vue +++ b/app/imports/ui/creature/buildTree/BuildTreeNode.vue @@ -55,6 +55,7 @@ />
@@ -147,6 +149,10 @@ export default { type: Array, default: () => [], }, + parentSlotId: { + type: String, + default: undefined, + }, }, data(){return { expanded: false, @@ -197,6 +203,21 @@ export default { } return this.children; }, + computedSlotId() { + if (this.condenseChild) { + if (this.children[0].node.type === 'propertySlot') { + return this.children[0].node._id; + } else { + return undefined; + } + } else { + if (this.node.type === 'propertySlot') { + return this.node._id; + } else { + return undefined; + } + } + }, canExpand(){ return !!this.computedChildren.length || this.canFillWithMany; }, @@ -230,41 +251,41 @@ export default { diff --git a/app/imports/ui/properties/forms/shared/ComputedField.vue b/app/imports/ui/properties/forms/shared/ComputedField.vue index d6b237e4..9d3ac194 100644 --- a/app/imports/ui/properties/forms/shared/ComputedField.vue +++ b/app/imports/ui/properties/forms/shared/ComputedField.vue @@ -6,7 +6,7 @@ @change="(value, ack) => $emit('change', {path: ['calculation'], value, ack})" >