From 1361205a7ae667a5b2c2f558b4b8600921948b88 Mon Sep 17 00:00:00 2001 From: ThaumRystra <9525416+ThaumRystra@users.noreply.github.com> Date: Tue, 2 Jan 2024 07:47:06 +0200 Subject: [PATCH] started implementing buffs in new action engine --- .../api/engine/actions/ActionEngine.ts | 157 +++++++++++++++++- .../client/ui/components/tree/TreeNode.vue | 3 - 2 files changed, 156 insertions(+), 4 deletions(-) diff --git a/app/imports/api/engine/actions/ActionEngine.ts b/app/imports/api/engine/actions/ActionEngine.ts index 73b2ecdb..ea73d5d3 100644 --- a/app/imports/api/engine/actions/ActionEngine.ts +++ b/app/imports/api/engine/actions/ActionEngine.ts @@ -5,12 +5,16 @@ import { getCreature, getPropertiesOfType, getPropertyChildren, getSinglePropert import recalculateInlineCalculations from '/imports/api/engine/actions/applyPropertyByType/shared/recalculateInlineCalculations'; import recalculateCalculation, { rollAndReduceCalculation } from '/imports/api/engine/actions/applyPropertyByType/shared/recalculateCalculation'; import rollDice from '/imports/parser/rollDice'; -import { toString } from '/imports/parser/resolve'; import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables'; import { getPropertyName } from '/imports/constants/PROPERTIES'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; import { assertEditPermission } from '/imports/api/sharing/sharingPermissions'; import numberToSignedString from '/imports/api/utility/numberToSignedString'; +import computedSchemas from '/imports/api/properties/computedPropertySchemasIndex'; +import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey'; +import resolve, { map, toString } from '/imports/parser/resolve'; +import accessor from '/imports/parser/parseTree/accessor'; +import cyrb53 from '/imports/api/engine/computation/utility/cyrb53'; /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -756,6 +760,76 @@ const applyPropertyByType = { } }, + async buff(task: PropTask, action: Action, userInput) { + const prop = task.prop; + const buffTargets = task.targetIds; + + // Mark the buff as dirty for recalculation + prop.dirty = true; + + // Then copy the descendants of the buff to the targets + const propList = [prop]; + function addChildrenToPropList(children, { skipCrystalize } = { skipCrystalize: false }) { + children.forEach(child => { + if (skipCrystalize) child.node._skipCrystalize = true; + propList.push(child.node); + // recursively add the child's children, but don't crystalize nested buffs + addChildrenToPropList(child.children, { + skipCrystalize: skipCrystalize || child.node.type === 'buff' + }); + }); + } + addChildrenToPropList(node.children); + if (!prop.skipCrystalization) { + crystalizeVariables({ propList, actionContext }); + } + + buffTargets.forEach(target => { + const targetPropList = EJSON.clone(propList); + // Move the properties to the target by replacing the old subtree parent and root with the ' + // target id + renewDocIds({ + docArray: targetPropList, + idMap: { + [prop.parentId]: target._id, + [prop.root.id]: target._id, + }, + collectionMap: { [prop.root.collection]: 'creatures' } + }); + // Apply the buff + CreatureProperties.batchInsert(targetPropList); + + //Log the buff + let logValue = prop.description?.value + if (prop.description?.text) { + recalculateInlineCalculations(prop.description, actionContext); + logValue = prop.description?.value; + } + if ((prop.name || prop.description?.value) && !prop.silent) { + if (target._id === actionContext.creature._id) { + // Targeting self + actionContext.addLog({ + name: prop.name, + value: logValue, + }); + } else { + // Targeting other + insertCreatureLog.call({ + log: { + creatureId: target._id, + content: [{ + name: prop.name, + value: logValue, + }], + } + }); + } + } + }); + applyNodeTriggers(node, 'after', actionContext); + applyNodeTriggers(node, 'afterChildren', actionContext); + }, + async folder(task: PropTask, action: Action, userInput): Promise { const prop = task.prop; return applyDefaultAfterPropTasks(action, prop, task.targetIds, userInput); @@ -1295,3 +1369,84 @@ async function resetProperties(action: Action, prop: any, result: TaskResult, us }); } } + +/** + * Replaces all variables with their resolved values + * except variables of the form `~target.thing.total` become `thing.total` + */ +function crystalizeVariables({ propList, actionContext }) { + propList.forEach(prop => { + if (prop._skipCrystalize) { + 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); + if (!calcObj?.parseNode) return; + calcObj.parseNode = map(calcObj.parseNode, node => { + // Skip nodes that aren't symbols or accessors + if ( + node.parseType !== 'accessor' + ) return node; + // Handle variables + if (node.name === '~target') { + // strip ~target + if (node.parseType === 'accessor') { + node.name = node.path.shift(); + if (!node.path.length) { + return accessor.create({ name: node.name }) + } + } else { + // Can't strip symbols + actionContext.addLog({ + name: 'Error', + value: 'Variable `~target` should not be used without a property: ~target.property', + }); + } + return node; + } else { + // Resolve all other variables + const { result, context } = resolve('reduce', node, actionContext.scope); + logErrors(context.errors, actionContext); + return result; + } + }); + calcObj.calculation = toString(calcObj.parseNode); + 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/client/ui/components/tree/TreeNode.vue b/app/imports/client/ui/components/tree/TreeNode.vue index 9437032b..940679be 100644 --- a/app/imports/client/ui/components/tree/TreeNode.vue +++ b/app/imports/client/ui/components/tree/TreeNode.vue @@ -36,14 +36,11 @@ :class="selected && 'primary--text'" :disabled="expanded" /> - - {{ node.left }} - {{ node.right }}