diff --git a/app/imports/api/creature/creatureProperties/methods/damageProperty.js b/app/imports/api/creature/creatureProperties/methods/damageProperty.js index 2aa1c554..b05823a9 100644 --- a/app/imports/api/creature/creatureProperties/methods/damageProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/damageProperty.js @@ -47,17 +47,17 @@ const damageProperty = new ValidatedMethod({ export function damagePropertyWork({property, operation, value}){ let damage, newValue; if (operation === 'set'){ - const currentValue = property.value; + const total = property.total; // Set represents what we want the value to be after damage // So we need the actual damage to get to that value - damage = currentValue - value; + damage = total - value; // Damage can't exceed total value - if (damage > currentValue) damage = currentValue; + if (damage > total) damage = total; // Damage must be positive if (damage < 0) damage = 0; newValue = property.total - damage; } else if (operation === 'increment'){ - let currentValue = property.value - (property.damage || 0); + let currentValue = property.value; let currentDamage = property.damage; let increment = value; // Can't increase damage above the remaining value @@ -74,6 +74,7 @@ export function damagePropertyWork({property, operation, value}){ }, { selector: property }); + return damage; } export default damageProperty; diff --git a/app/imports/api/creature/creatureProperties/methods/dealDamage.js b/app/imports/api/creature/creatureProperties/methods/dealDamage.js index 173132c2..45eb8583 100644 --- a/app/imports/api/creature/creatureProperties/methods/dealDamage.js +++ b/app/imports/api/creature/creatureProperties/methods/dealDamage.js @@ -25,7 +25,6 @@ const dealDamage = new ValidatedMethod({ // permissions let creature = Creatures.findOne(creatureId, { fields: { - damageMultipliers: 1, owner: 1, readers: 1, writers: 1, @@ -33,37 +32,42 @@ const dealDamage = new ValidatedMethod({ }); assertEditPermission(creature, this.userId); - // Get all the health bars and do damage to them - let healthBars = CreatureProperties.find({ - 'ancestors.id': creatureId, - type: 'attribute', - attributeType:'healthBar', - removed: {$ne: true}, - inactive: {$ne: true}, - }, { - sort: {order: -1}, - }); - let multiplier = creature.damageMultipliers[damageType]; - if (multiplier === undefined) multiplier = 1; - let totalDamage = Math.floor(amount * multiplier); - let damageLeft = totalDamage; - if (damageType === 'healing') damageLeft = -totalDamage; - let propertyIds = []; - let propertiesDependedAponIds = []; - healthBars.forEach(healthBar => { - if (damageLeft === 0) return; - let damageAdded = damagePropertyWork({ - property: healthBar, - operation: 'increment', - value: damageLeft, - }); - damageLeft -= damageAdded; - propertyIds.push(healthBar._id); - propertiesDependedAponIds.push(...healthBar.dependencies); - }); + const totalDamage = dealDamageWork({creature, damageType, amount}) computeCreature(creatureId); return totalDamage; }, }); +export function dealDamageWork({creature, damageType, amount}){ + console.log({damageType, amount}) + // Get all the health bars and do damage to them + let healthBars = CreatureProperties.find({ + 'ancestors.id': creature._id, + type: 'attribute', + attributeType:'healthBar', + removed: {$ne: true}, + inactive: {$ne: true}, + }, { + sort: {order: -1}, + }); + //let multiplier = creature.damageMultipliers[damageType]; + //if (multiplier === undefined) multiplier = 1; + //let totalDamage = Math.floor(amount * multiplier); + const totalDamage = amount; + let damageLeft = totalDamage; + if (damageType === 'healing') damageLeft = -totalDamage; + let propertyIds = []; + healthBars.forEach(healthBar => { + if (damageLeft === 0) return; + let damageAdded = damagePropertyWork({ + property: healthBar, + operation: 'increment', + value: damageLeft, + }); + damageLeft -= damageAdded; + propertyIds.push(healthBar._id); + }); + return totalDamage; +} + export default dealDamage; diff --git a/app/imports/api/creature/creatures/defaultCharacterProperties.js b/app/imports/api/creature/creatures/defaultCharacterProperties.js index a38f24ea..dd3d5499 100644 --- a/app/imports/api/creature/creatures/defaultCharacterProperties.js +++ b/app/imports/api/creature/creatures/defaultCharacterProperties.js @@ -10,10 +10,10 @@ export default function defaultCharacterProperties(creatureId){ { type: 'propertySlot', name: 'Ruleset', - description: 'Choose a starting point for your character, this will define the basic setup of your character sheet. Without a base, your sheet will be empty.', + description: {text: 'Choose a starting point for your character, this will define the basic setup of your character sheet. Without a base, your sheet will be empty.'}, slotTags: ['base'], tags: [], - quantityExpected: 1, + quantityExpected: {calculation: '1'}, hideWhenFull: true, spaceLeft: 1, totalFilled: 0, diff --git a/app/imports/api/creature/log/CreatureLogs.js b/app/imports/api/creature/log/CreatureLogs.js index c464a71d..b434b7f7 100644 --- a/app/imports/api/creature/log/CreatureLogs.js +++ b/app/imports/api/creature/log/CreatureLogs.js @@ -121,6 +121,7 @@ export function insertCreatureLogWork({log, creature, method}){ if (typeof log === 'string'){ log = {content: [{value: log}]}; } + if (!log.content?.length) return; log.date = new Date(); // Insert it let id = CreatureLogs.insert(log); diff --git a/app/imports/api/engine/actions/applyProperty.js b/app/imports/api/engine/actions/applyProperty.js index fe88c208..23094897 100644 --- a/app/imports/api/engine/actions/applyProperty.js +++ b/app/imports/api/engine/actions/applyProperty.js @@ -3,6 +3,7 @@ import adjustment from './applyPropertyByType/applyAdjustment.js'; import branch from './applyPropertyByType/applyBranch.js'; import buff from './applyPropertyByType/applyBuff.js'; import damage from './applyPropertyByType/applyDamage.js'; +import note from './applyPropertyByType/applyNote.js'; import roll from './applyPropertyByType/applyRoll.js'; import savingThrow from './applyPropertyByType/applySavingThrow.js'; import toggle from './applyPropertyByType/applyToggle.js'; @@ -13,6 +14,7 @@ const applyPropertyByType = { branch, buff, damage, + note, roll, savingThrow, spell: action, diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index 08cfb17e..3b6ac32e 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -9,15 +9,22 @@ import { damagePropertyWork } from '/imports/api/creature/creatureProperties/met export default function applyAction(node, {creature, targets, scope, log}){ const prop = node.node; if (prop.target === 'self') targets = [creature]; + + // Log the name and description + let content = { name: prop.name }; + if (prop.description?.text){ + recalculateInlineCalculations(prop.description, scope, log); + content.value = prop.description.value; + } + if (content.name || content.value){ + log.content.push(content); + } + + // Spend the resources const failed = spendResources({prop, log, scope}); if (failed) return; - let content = { name: prop.name }; - if (prop.summary?.text){ - recalculateInlineCalculations(prop.summary, scope, log); - content.value = prop.summary.value; - } - log.content.push(content); + // Attack if there is an attack roll if (prop.attackRoll && prop.attackRoll.calculation){ if (targets.length){ targets.forEach(target => { @@ -29,6 +36,8 @@ export default function applyAction(node, {creature, targets, scope, log}){ applyAttackWithoutTarget({prop, scope, log}); applyChildren(node, {creature, targets, scope, log}); } + } else { + applyChildren(node, {creature, targets, scope, log}); } } @@ -39,18 +48,18 @@ function applyAttackWithoutTarget({prop, scope, log}){ delete scope['$criticalMiss']; delete scope['$attackRoll']; - recalculateCalculation(prop.rollBonus, scope, log); + recalculateCalculation(prop.attackRoll, scope, log); let value = rollDice(1, 20)[0]; scope['$attackRoll'] = {value}; let criticalHitTarget = scope.criticalHitTarget?.value || 20; let criticalHit = value >= criticalHitTarget; if (criticalHit) scope['$criticalHit'] = {value: true}; - let result = value + prop.rollBonus.value; + let result = value + prop.attackRoll.value; scope['$toHit'] = {value: result}; log.content.push({ name: criticalHit ? 'Critical Hit!' : 'To Hit', - value: `1d20 {${value}} + ${prop.rollBonus.value} = ` + result, + value: `1d20 [${value}] + ${prop.attackRoll.value} = ` + result, }); } @@ -62,7 +71,7 @@ function applyAttackToTarget({prop, target, scope, log}){ delete scope['$attackDiceRoll']; delete scope['$attackRoll']; - recalculateCalculation(prop.rollBonus, scope, log); + recalculateCalculation(prop.attackRoll, scope, log); const value = rollDice(1, 20)[0]; scope['$attackDiceRoll'] = {value}; @@ -71,7 +80,7 @@ function applyAttackToTarget({prop, target, scope, log}){ const criticalMiss = value === 1; if (criticalHit) scope['$criticalHit'] = {value: true}; if (criticalMiss) scope['$criticalMiss'] = {value: true}; - const result = value + prop.rollBonus.value; + const result = value + prop.attackRoll.value; scope['$attackRoll'] = {value: result}; if (target.variables.armor){ const armor = target.variables.armor.value; @@ -81,7 +90,7 @@ function applyAttackToTarget({prop, target, scope, log}){ 'Miss!' log.content.push({ name, - value: `1d20 {${value}} + ${prop.rollBonus.value} = ` + result, + value: `1d20 {${value}} + ${prop.attackRoll.value} = ` + result, }); if ((result > armor) || (criticalHit)){ scope['$attackHit'] = true; @@ -95,7 +104,7 @@ function applyAttackToTarget({prop, target, scope, log}){ }); log.content.push({ name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical miss!' : 'To Hit', - value: `1d20 {${value}} + ${prop.rollBonus.value} = ` + result, + value: `1d20 {${value}} + ${prop.attackRoll.value} = ` + result, }); } } @@ -106,7 +115,7 @@ function applyChildren(node, args){ function spendResources({prop, log, scope}){ // Check Uses - if (prop.usesUsed >= prop.uses?.value){ + if (prop.usesLeft < 0){ log.content.push({ name: 'Error', value: `${prop.name || 'action'} does not have enough uses left`, @@ -127,6 +136,7 @@ function spendResources({prop, log, scope}){ let gainLog = []; try { prop.resources.itemsConsumed.forEach(itemConsumed => { + recalculateCalculation(itemConsumed.quantity, scope, log); if (!itemConsumed.itemId){ throw 'No ammo was selected for this prop'; } @@ -166,7 +176,7 @@ function spendResources({prop, log, scope}){ itemQuantityAdjustments.forEach(adjustQuantityWork); // Use uses - if (prop.usesResult){ + if (prop.usesLeft){ CreatureProperties.update(prop._id, { $inc: {usesUsed: 1} }, { @@ -174,24 +184,29 @@ function spendResources({prop, log, scope}){ }); log.content.push({ name: 'Uses left', - value: prop.usesResult - (prop.usesUsed || 0) - 1, + value: prop.usesLeft - (prop.usesUsed || 0) - 1, }); } // Damage stats prop.resources.attributesConsumed.forEach(attConsumed => { - if (!attConsumed.quantity) return; + recalculateCalculation(attConsumed.quantity, scope, log); + + if (!attConsumed.quantity?.value) return; let stat = scope[attConsumed.variableName]; - if (!stat) return; + if (!stat){ + spendLog.push(stat.name + ': ' + ' not found'); + return; + } damagePropertyWork({ property: stat, operation: 'increment', - value: attConsumed.quantity, + value: attConsumed.quantity.value, }); - if (attConsumed.quantity > 0){ - spendLog.push(stat.name + ': ' + attConsumed.quantity); - } else if (attConsumed.quantity < 0){ - gainLog.push(stat.name + ': ' + -attConsumed.quantity); + if (attConsumed.quantity.value > 0){ + spendLog.push(stat.name + ': ' + attConsumed.quantity.value); + } else if (attConsumed.quantity.value < 0){ + gainLog.push(stat.name + ': ' + -attConsumed.quantity.value); } }); diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js b/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js index 1f2880ec..bea1c945 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAdjustment.js @@ -14,12 +14,8 @@ export default function applyAdjustment(node, { // Evaluate the amount recalculateCalculation(prop.amount, scope, log); - prop.amount.errors?.forEach(error => { - if (error.type !== 'info'){ - log.content.push({name: 'Error', value: error.message}); - } - }); - const value = prop.amount.value; + + const value = +prop.amount.value; if (!isFinite(value)) { return applyChildren(node, {creature, targets, scope, log}); } @@ -32,12 +28,12 @@ export default function applyAdjustment(node, { name: 'Error', value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set` }); - return; + return applyChildren(node, {creature, targets, scope, log}); } damagePropertyWork({ property: stat, operation: prop.operation, - value, + value: value, }); log.content.push({ name: 'Attribute damage', diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js index 6131da80..87ef98c2 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js @@ -1,8 +1,8 @@ import applyProperty from '../applyProperty.js'; -import dealDamage from '/imports/api/creature/creatureProperties/methods/dealDamage.js'; +import { dealDamageWork } from '/imports/api/creature/creatureProperties/methods/dealDamage.js'; import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js'; -import recalculateCalculation from './shared/recalculateCalculation.js'; -import { Context } from '/imports/parser/resolve.js'; +import resolve, { Context, toString } from '/imports/parser/resolve.js'; +import logErrors from './shared/logErrors.js'; export default function applyDamage(node, { creature, targets, scope, log @@ -14,6 +14,12 @@ export default function applyDamage(node, { }; const prop = node.node; + + // Skip if there is no parse node to work with + if (!prop.amount.parseNode) return; + + // Choose target + let damageTargets = prop.target === 'self' ? [creature] : targets; // Determine if the hit is critical let criticalHit = scope['$criticalHit']?.value && @@ -23,40 +29,66 @@ export default function applyDamage(node, { let context = new Context({ options: {doubleRolls: criticalHit}, }); - recalculateCalculation(prop.amount, scope, log, context); - // If we didn't end up with a finite amount, give up - if (!isFinite(prop.amount?.value)) return applyChildren(); + // Gather all the lines we need to log into an array + const logValue = []; + const logName = prop.damageType === 'healing' ? 'Healing' : 'Damage'; + + // Compile the dice roll and store that string first + const {result: compiled} = resolve('compiled', prop.amount.parseNode, scope, context); + logValue.push(toString(compiled)); + logErrors(context.errors, log); + + // roll the dice only and store that string + const {result: rolled} = resolve('roll', prop.amount.parseNode, scope, context); + logValue.push(toString(rolled)); + logErrors(context.errors, log); + + // Reset the errors so we don't log the same errors twice + context.errors = []; + + // Resolve the roll to a final value + const {result: reduced} = resolve('reduce', rolled, scope, context); + logErrors(context.errors, log); + + // Store the result + if (reduced.parseType === 'constant'){ + prop.amount.value = reduced.value; + } else if (reduced.parseType === 'error'){ + prop.amount.value = null; + } else { + prop.amount.value = toString(reduced); + } + + const damage = +reduced.value; + + // If we didn't end up with a constant of finite amount, give up + if (reduced?.parseType !== 'constant' && !isFinite(reduced.value)){ + return applyChildren(); + } // Memoise the damage suffix for the log let suffix = (criticalHit ? ' critical ' : ' ') + prop.damageType + - (prop.damageType !== ' healing ' ? ' damage ': ''); + (prop.damageType !== 'healing' ? ' damage ': ''); if (damageTargets && damageTargets.length) { // Iterate through all the targets damageTargets.forEach(target => { - let name = prop.damageType === 'healing' ? 'Healing' : 'Damage'; // Deal the damage to the target - let damageDealt = dealDamage.call({ - creatureId: target._id, + let damageDealt = dealDamageWork({ + creature: target, damageType: prop.damageType, - amount: prop.amount.value, + amount: damage, }); // Log the damage done if (target._id === creature._id){ // Target is same as self, log damage as such - log.content.push({ - name, - value: damageDealt + suffix + ' to self', - }); + logValue.push(damageDealt + suffix + ' to self'); } else { - log.content.push({ - name, - value: 'Dealt ' + damageDealt + suffix + ` ${target.name && ' to '}${target.name}`, - }); + logValue.push('Dealt ' + damageDealt + suffix + ` ${target.name && ' to '}${target.name}`); // Log the damage received on that creature's log as well insertCreatureLog.call({ log: { @@ -71,10 +103,11 @@ export default function applyDamage(node, { }); } else { // There are no targets, just log the result - log.content.push({ - name: prop.damageType === 'healing' ? 'Healing' : 'Damage', - value: prop.amount.value + suffix, - }); + logValue.push(damage + suffix); } + log.content.push({ + name: logName, + value: logValue.join('\n'), + }); return applyChildren(); } diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyNote.js b/app/imports/api/engine/actions/applyPropertyByType/applyNote.js new file mode 100644 index 00000000..2d460ee2 --- /dev/null +++ b/app/imports/api/engine/actions/applyPropertyByType/applyNote.js @@ -0,0 +1,25 @@ +import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js'; +import applyProperty from '../applyProperty.js'; + +export default function applyNote(node, {creature, targets, scope, log}){ + const prop = node.node; + + // Log Name, summary + let content = { name: prop.name }; + if (prop.summary?.text){ + recalculateInlineCalculations(prop.summary, scope, log); + content.value = prop.summary.value; + } + if (content.name || content.value){ + log.content.push(content); + } + // Log description + if (prop.description?.text){ + recalculateInlineCalculations(prop.description, scope, log); + log.content.push({value: prop.description.value}); + } + // Apply children + node.children.forEach(child => applyProperty(child, { + creature, targets, scope, log + })); +} diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js b/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js index 05996784..67e2e42f 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js @@ -5,14 +5,14 @@ export default function applyRoll(node, {creature, targets, scope, log}){ const prop = node.node; if (prop.roll?.calculation){ - recalculateCalculation(prop.roll, scope, log, context); + recalculateCalculation(prop.roll, scope, log); if (isFinite(prop.roll.value)){ scope[prop.variableName] = prop.roll.value; } log.content.push({ name: prop.name, - value: prop.variableName + ' = ' + prop.roll + ' = ' + prop.roll.value, + value: prop.variableName + ' = ' + prop.roll.calculation + ' = ' + prop.roll.value, }); } return node.children.forEach(child => applyProperty(child, { diff --git a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js index 1e0ad6f6..b6855e19 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js @@ -3,11 +3,11 @@ import recalculateCalculation from './shared/recalculateCalculation.js'; import applyProperty from '../applyProperty.js'; export default function applySavingThrow(node, {creature, targets, scope, log}){ - let saveTargets = prop.target === 'self' ? [creature] : targets; - const prop = node.node; - recalculateCalculation(prop.dc, scope, log, context); + let saveTargets = prop.target === 'self' ? [creature] : targets; + + recalculateCalculation(prop.dc, scope, log); const dc = (prop.dc?.value); if (!isFinite(dc)){ diff --git a/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation.js b/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation.js index 4481069d..4ac1565a 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation.js +++ b/app/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation.js @@ -2,7 +2,7 @@ import evaluateCalculation from '/imports/api/engine/computation/utility/evaluat import logErrors from './logErrors.js'; export default function recalculateCalculation(calc, scope, log, context){ - if (!calc.parseNode) return; + if (!calc?.parseNode) return; calc._parseLevel = 'reduce'; evaluateCalculation(calc, scope, context); logErrors(calc.errors, log); diff --git a/app/imports/api/engine/actions/doAction.js b/app/imports/api/engine/actions/doAction.js index 8643e154..6a275b34 100644 --- a/app/imports/api/engine/actions/doAction.js +++ b/app/imports/api/engine/actions/doAction.js @@ -72,9 +72,9 @@ const doAction = new ValidatedMethod({ doActionWork({creature, targets, properties, ancestors, method: this}); // Recompute all involved creatures - Meteor.defer(() => computeCreature(creature._id)); + computeCreature(creature._id); targets.forEach(target => { - Meteor.defer(() => computeCreature(target._id)); + computeCreature(target._id); }); }, }); diff --git a/app/imports/api/engine/actions/doAction.test.js b/app/imports/api/engine/actions/doAction.test.js new file mode 100644 index 00000000..19560704 --- /dev/null +++ b/app/imports/api/engine/actions/doAction.test.js @@ -0,0 +1,11 @@ +import '/imports/api/simpleSchemaConfig.js'; +//import testTypes from './testTypes/index.js'; +import { doActionWork } from './doAction.js'; +import createAction from './tests/createAction.testFn.js'; + +describe('Do Action', function(){ + it('Does an empty action', function(){ + doActionWork(createAction({properties: [{type: 'action'}]})); + }); + //testTypes.forEach(test => it(test.text, test.fn)); +}); diff --git a/app/imports/api/engine/actions/tests/createAction.testFn.js b/app/imports/api/engine/actions/tests/createAction.testFn.js new file mode 100644 index 00000000..84ffa907 --- /dev/null +++ b/app/imports/api/engine/actions/tests/createAction.testFn.js @@ -0,0 +1,26 @@ +import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; +import Creatures from '/imports/api/creature/creatures/Creatures.js'; + +export default function createAction({ + creature = {_id: 'creatureId'}, + targets = [], + properties = [], + ancestors = [], + method +} = {}){ + properties = properties.map(cleanProp); + ancestors = ancestors.map(cleanProp); + creature = cleanCreature(creature); + ancestors = ancestors.map(cleanCreature); + return {creature, targets, properties, ancestors, method}; +} + +function cleanProp(prop){ + let schema = CreatureProperties.simpleSchema(prop); + return schema.clean(prop); +} + +function cleanCreature(creature){ + let schema = Creatures.simpleSchema(creature); + return schema.clean(creature); +} diff --git a/app/imports/api/engine/actions/tests/testTypes/applyAction.testFn.js b/app/imports/api/engine/actions/tests/testTypes/applyAction.testFn.js new file mode 100644 index 00000000..e69de29b diff --git a/app/imports/api/engine/actions/tests/testTypes/index.testFn.js b/app/imports/api/engine/actions/tests/testTypes/index.testFn.js new file mode 100644 index 00000000..e8a4b486 --- /dev/null +++ b/app/imports/api/engine/actions/tests/testTypes/index.testFn.js @@ -0,0 +1,6 @@ +import applyAction from './applyAction.testFn.js'; + +export default [{ + text: 'Applies actions', + fn: applyAction, +},]; diff --git a/app/imports/api/engine/computation/buildComputation/computeInactiveStatus.js b/app/imports/api/engine/computation/buildComputation/computeInactiveStatus.js index 0ec51aa4..06847b46 100644 --- a/app/imports/api/engine/computation/buildComputation/computeInactiveStatus.js +++ b/app/imports/api/engine/computation/buildComputation/computeInactiveStatus.js @@ -3,9 +3,9 @@ import walkDown from '/imports/api/engine/computation/utility/walkdown.js'; export default function computeInactiveStatus(node){ const prop = node.node; if (isActive(prop)) return; - // Unequipped items disable their children, but are not disabled themselves - // All notes do the same - if (prop.type !== 'item' && prop.type !== 'note' ){ + // Unequipped items, notes, and actions disable their children, + // but are not disabled themselves + if (prop.type !== 'item' && prop.type !== 'note' && prop.type !== 'action' ){ prop.inactive = true; prop.deactivatedBySelf = true; } @@ -23,6 +23,7 @@ function isActive(prop){ case 'item': return !!prop.equipped; case 'spell': return !!prop.prepared || !!prop.alwaysPrepared; case 'note': return false; + case 'action': return false; default: return true; } } diff --git a/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js b/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js index 500afeee..20244a8c 100644 --- a/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js +++ b/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js @@ -1,7 +1,7 @@ import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js'; import { prettifyParseError, parse } from '/imports/parser/parser.js'; import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js'; -import { get } from 'lodash'; +import { get, unset } from 'lodash'; import errorNode from '/imports/parser/parseTree/error.js'; import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js'; @@ -53,6 +53,11 @@ function parseAllCalculationFields(prop, schemas){ applyFnToKey(prop, calcKey, (prop, key) => { const calcObj = get(prop, key); if (!calcObj) return; + // Delete the whole calculation object if the calculation string isn't set + if (!calcObj.calculation){ + unset(prop, calcKey); + return; + } // Store a reference to all the calculations prop._computationDetails.calculations.push(calcObj); // Store the level to compute down to later @@ -64,12 +69,6 @@ function parseAllCalculationFields(prop, schemas){ } function parseCalculation(calcObj){ - // If there is no calculation clear the cached parse node and error - if (!calcObj.calculation){ - delete calcObj.hash; - delete calcObj.parseError; - return; - } const calcHash = cyrb53(calcObj.calculation); // If the cached parse calculation is equal to the calculation, skip if (calcHash === calcObj.hash){ diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js index df042f9c..70cdae63 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js @@ -2,6 +2,9 @@ export default function computeAction(computation, node){ const prop = node.data; if (prop.uses){ prop.usesLeft = prop.uses.value - (prop.usesUsed || 0); + if (!prop.usesLeft){ + prop.insufficientResources = true; + } } computeResources(computation, node); if (!prop.resources) return; diff --git a/app/imports/api/engine/computation/computeComputation/tests/computeAction.testFn.js b/app/imports/api/engine/computation/computeComputation/tests/computeAction.testFn.js index 9b299c16..096db3d4 100644 --- a/app/imports/api/engine/computation/computeComputation/tests/computeAction.testFn.js +++ b/app/imports/api/engine/computation/computeComputation/tests/computeAction.testFn.js @@ -14,7 +14,7 @@ export default function(){ assert.equal(prop.usesLeft, 2); const rolled = computation.propsById['rolledDescriptionId']; - assert.equal(rolled.summary.value, 'test roll gets compiled 1d4 + 4 properly'); + assert.equal(rolled.summary.value, 'test roll gets compiled d4 + 4 properly'); const itemConsumed = prop.resources.itemsConsumed[0]; assert.equal(itemConsumed.quantity.value, 3); @@ -26,7 +26,6 @@ export default function(){ const attConsumed = prop.resources.attributesConsumed[0]; assert.equal(attConsumed.quantity.value, 4); assert.equal(attConsumed.available, 9); - assert.equal(attConsumed.statId, 'resourceVarId'); assert.equal(attConsumed.statName, 'Resource Var'); } diff --git a/app/imports/migrations/server/2.0-beta.33-dbv1.js b/app/imports/migrations/server/2.0-beta.33-dbv1.js index f5ceffb3..2d7fddb5 100644 --- a/app/imports/migrations/server/2.0-beta.33-dbv1.js +++ b/app/imports/migrations/server/2.0-beta.33-dbv1.js @@ -38,7 +38,7 @@ export default function migrateProperty({collection, reversed, prop}){ {from: 'dependencies'} ]; let migratedProp = transformFields(prop, transforms, reversed); - const schema = collection.simpleSchema({type: prop.type}); + const schema = collection.simpleSchema({type: migratedProp.type}); // Only clean if the schema version matches our destination version if(!reversed && SCHEMA_VERSION === 1){ try { @@ -80,7 +80,9 @@ const transformsByPropType = { {from: 'type', to: 'type', up: () => 'action'}, ], 'attribute': [ - ...getComputedPropertyTransforms('baseValue'), + {from: 'baseValueCalculation', to: 'baseValue.calculation'}, + {from: 'baseValue', to: 'baseValue.value', up: nanToNull}, + {from: 'baseValueErrors', to: 'baseValue.errors', up: trimErrors}, ...getComputedPropertyTransforms('spellSlotLevel'), ...getInlineComputationTransforms('description'), {from: 'value', to: 'total', up: nanToNull}, diff --git a/app/imports/migrations/server/2.0-beta.33-dbv1.test.js b/app/imports/migrations/server/2.0-beta.33-dbv1.test.js index ce3512a7..bb09dd78 100644 --- a/app/imports/migrations/server/2.0-beta.33-dbv1.test.js +++ b/app/imports/migrations/server/2.0-beta.33-dbv1.test.js @@ -13,7 +13,6 @@ const exampleAction = { '_id':'FaK6jXEj3pSe7mNuu', 'quantity': '1', 'variableName':'HunterTech', - 'statId':'qccf9j5tfNJjZ3GGn', 'statName':'Hunter\'s Technique', 'available':5 }], diff --git a/app/imports/parser/parseTree/roll.js b/app/imports/parser/parseTree/roll.js index 09817d8f..c4e028c4 100644 --- a/app/imports/parser/parseTree/roll.js +++ b/app/imports/parser/parseTree/roll.js @@ -22,7 +22,7 @@ const rollNode = { }, toString(node){ if ( - node.left.parseType === 'number' && node.left.value === 1 + node.left.valueType === 'number' && node.left.value === 1 ){ return `d${toString(node.right)}`; } else { @@ -32,10 +32,10 @@ const rollNode = { roll(node, scope, context){ const {result: left} = resolve('reduce', node.left, scope, context); const {result: right} = resolve('reduce', node.right, scope, context); - if (left.parseType !== 'number' && !Number.isInteger(left.value)){ + if (left.valueType !== 'number' && !Number.isInteger(left.value)){ return errorResult('Number of dice is not an integer', node, context); } - if (!right.isInteger){ + if (right.valueType !== 'number' && !Number.isInteger(right.value)){ return errorResult('Dice size is not an integer', node, context); } let number = left.value; @@ -49,7 +49,7 @@ const rollNode = { let diceSize = right.value; let values = rollDice(number, diceSize); if (context){ - context.storeRoll({number, diceSize, values}); + context.rolls.push({number, diceSize, values}); } return { result: rollArray.create({ diff --git a/app/imports/parser/parseTree/rollArray.js b/app/imports/parser/parseTree/rollArray.js index 158e101e..ba737cc4 100644 --- a/app/imports/parser/parseTree/rollArray.js +++ b/app/imports/parser/parseTree/rollArray.js @@ -1,7 +1,7 @@ import constant from './constant.js'; const rollArray = { - constructor({values, diceSize, diceNum}) { + create({values, diceSize, diceNum}) { return { parseType: 'rollArray', values, diff --git a/app/imports/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue b/app/imports/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue index c7c4b7e0..14f8715f 100644 --- a/app/imports/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue +++ b/app/imports/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue @@ -35,10 +35,10 @@ {{ typeName || 'Type' }} - Library + Create - Create + Library + + + + + - - - - - @@ -73,17 +73,7 @@ - - @@ -91,9 +81,7 @@ diff --git a/app/imports/ui/properties/forms/AdjustmentForm.vue b/app/imports/ui/properties/forms/AdjustmentForm.vue index 5bc0e484..19387a28 100644 --- a/app/imports/ui/properties/forms/AdjustmentForm.vue +++ b/app/imports/ui/properties/forms/AdjustmentForm.vue @@ -1,25 +1,35 @@ + + + + diff --git a/app/imports/ui/properties/forms/shared/schemaFormMixin.js b/app/imports/ui/properties/forms/shared/schemaFormMixin.js index 525a585c..2cb8b332 100644 --- a/app/imports/ui/properties/forms/shared/schemaFormMixin.js +++ b/app/imports/ui/properties/forms/shared/schemaFormMixin.js @@ -4,14 +4,23 @@ */ import { get, toPath } from 'lodash'; -function resolvePath(model, path){ +function resolvePath(model, path, set){ let arrayPath = toPath(path); if (arrayPath.length === 1){ return { object: model, key: arrayPath[0] }; } - let objectPath = arrayPath.slice(0, -1); let key = arrayPath.slice(-1); - let object = get(model, objectPath); + let objectPath = arrayPath.slice(0, -1); + let object = model; + // Ensure that nested objects exist before navigating them + objectPath.forEach(pathKey => { + let newObject = object[pathKey]; + if (!newObject){ + newObject = {}; + set(object, pathKey, newObject); + } + object = newObject; + }); return {object, key}; } @@ -41,7 +50,8 @@ const schemaFormMixin = { methods: { // Sets the value at the given path change({path, value, ack}){ - let {object, key} = resolvePath(this.model, path); + let {object, key} = resolvePath(this.model, path, this.$set); + this.$set(object, key, value); if (ack) ack(); }, @@ -54,7 +64,7 @@ const schemaFormMixin = { if (ack) ack(); }, pull({path, ack}){ - let {object, key} = resolvePath(this.model, path); + let {object, key} = resolvePath(this.model, path, this.$set); if (!object || !object.splice){ throw `${path.join('.')} is ${object}, doesnt have "splice"` } diff --git a/app/imports/ui/properties/viewers/ActionViewer.vue b/app/imports/ui/properties/viewers/ActionViewer.vue index 7f1baacd..ce094084 100644 --- a/app/imports/ui/properties/viewers/ActionViewer.vue +++ b/app/imports/ui/properties/viewers/ActionViewer.vue @@ -119,7 +119,7 @@