Moved inventory computation to after toggles, added class levels computation

This commit is contained in:
Stefan Zermatten
2021-09-14 16:18:36 +02:00
parent 5c84836238
commit 8f93179187
23 changed files with 351 additions and 162 deletions

View File

@@ -3,8 +3,11 @@ import walkDown from '/imports/api/creature/computation/newEngine/utility/walkdo
export default function computeInactiveStatus(node){
const prop = node.node;
if (isActive(prop)) return;
prop.inactive = true;
prop.deactivatedBySelf = true;
// Unequipped items disable their children, but are not disabled themselves
if (prop.type !== 'item'){
prop.inactive = true;
prop.deactivatedBySelf = true;
}
// Mark children as inactive due to ancestor
walkDown(node.children, child => {
child.node.inactive = true;

View File

@@ -1,98 +0,0 @@
/**
* Performs a depth first traversal of the character tree, summing the container
* and inventory contents on the way up the tree
*/
export default function computeInventory(forest, dependencyGraph){
const data = {
weightTotal: 0,
weightEquipment: 0,
weightCarried: 0,
valueTotal: 0,
valueEquipment: 0,
valueCarried: 0,
itemsAttuned: 0,
};
// The stack of properties to still navigate
const stack = [...forest];
// The current containers we are inside of
const containerStack = [];
while(stack.length){
const top = stack[stack.length - 1];
const prop = top.node;
if (prop._computationDetails.inventoryChildrenVisited){
stack.pop();
handleProp(prop, containerStack, data, dependencyGraph);
} else {
// Add all containers to the stack when we first visit them
if (prop.type === 'container'){
containerStack.push(top.node);
setDefaultContainerData(prop);
}
// Push children onto the stack and mark this as children are visited
stack.push(...top.children);
prop._computationDetails.inventoryChildrenVisited = true;
}
}
// Store all the computed values on the dependency graph variables
for (let key in data){
dependencyGraph.addNode(key, {engineValue: data[key]});
}
}
function setDefaultContainerData(container){
container.contentsWeight = 0;
container.carriedWeight = 0;
container.contentsValue = 0;
container.carriedValue = 0;
}
function handleProp(prop, containerStack, data, dependencyGraph){
// Determine if this property is carried, items are carried by default
let carried = prop.type === 'container' ? prop.carried : true;
// Weight and value for this property
const weight = (prop.weight || 0) + (prop.contentsWeight || 0);
const carriedWeight = (prop.weight || 0) + (prop.carriedWeight || 0);
const value = (prop.value || 0) + (prop.value || 0);
const carriedValue = (prop.value || 0) + (prop.carriedValue || 0);
// Sum the item-specific data
if (prop.type === 'item'){
dependencyGraph.addLink('itemsAttuned', prop._id, 'inventory');
if (prop.attuned) data.itemsAttuned += 1;
if (prop.equipped){
dependencyGraph.addLink('weightEquipment', prop._id, 'inventory');
data.weightEquipment += weight;
dependencyGraph.addLink('valueEquipment', prop._id, 'inventory');
data.valueEquipment += value;
}
}
// Get the parent container
const container = containerStack[containerStack.length - 1];
if (container){
// The container depends on this prop for its contents data
dependencyGraph.addLink(container._id, prop._id, 'inventory');
// Add this property's weights and values to the container
if (!container.weightless){
container.contentsWeight += weight;
if (carried) container.carriedWeight += carriedWeight;
}
container.contentsValue += value;
if (carried) container.carriedValue += carriedValue;
} else {
// There is no parent container, add weights/value to the character data
dependencyGraph.addLink('weightTotal', prop._id, 'inventory');
data.weightTotal += weight;
dependencyGraph.addLink('valueTotal', prop._id, 'inventory');
data.valueTotal += value;
if (carried){
dependencyGraph.addLink('weightCarried', prop._id, 'inventory');
data.weightCarried += carriedWeight;
dependencyGraph.addLink('valueCarried', prop._id, 'inventory');
data.valueCarried += carriedValue;
}
}
}

View File

@@ -4,6 +4,7 @@
*/
export default function computeSlotQuantityFilled(node, dependencyGraph){
let slot = node.node;
if (slot.type !== 'propertySlot' || slot.type !== 'characterClass') return;
slot.totalFilled = 0;
node.children.forEach(child => {
let childProp = child.node;

View File

@@ -0,0 +1,59 @@
/**
* Performs a depth first traversal of the character tree, summing the container
* and inventory contents on the way up the tree
*/
export default function linkInventory(forest, dependencyGraph){
// The stack of properties to still navigate
const stack = [...forest];
// The current containers we are inside of
const containerStack = [];
while(stack.length){
const top = stack[stack.length - 1];
const prop = top.node;
if (prop._computationDetails.inventoryChildrenVisited){
stack.pop();
handleProp(prop, containerStack, dependencyGraph);
} else {
// Add all containers to the stack when we first visit them
if (prop.type === 'container'){
containerStack.push(top.node);
}
// Push children onto the stack and mark this as children are visited
stack.push(...top.children);
prop._computationDetails.inventoryChildrenVisited = true;
}
}
}
function handleProp(prop, containerStack, dependencyGraph){
// Determine if this property is carried, items are carried by default
let carried = prop.type === 'container' ? prop.carried : true;
// Item-specific links
if (prop.type === 'item'){
if (prop.attuned){
dependencyGraph.addLink('itemsAttuned', prop._id, 'attunedItem');
}
if (prop.equipped){
dependencyGraph.addLink('weightEquipment', prop._id, 'equippedItem');
dependencyGraph.addLink('valueEquipment', prop._id, 'equippedItem');
}
}
// Get the parent container
const container = containerStack[containerStack.length - 1];
if (container){
// The container depends on this prop for its contents data
dependencyGraph.addLink(container._id, prop._id, 'containerContents');
} else {
// There is no parent container, the character totals depend on this prop
dependencyGraph.addLink('weightTotal', prop._id, 'inventoryStats');
dependencyGraph.addLink('valueTotal', prop._id, 'inventoryStats');
if (carried){
dependencyGraph.addLink('weightCarried', prop._id, 'inventoryStats');
dependencyGraph.addLink('valueCarried', prop._id, 'inventoryStats');
}
}
}

View File

@@ -2,7 +2,8 @@ const linkDependenciesByType = {
action: linkResources,
attack: linkResources,
attribute: linkAttribute,
classLevel: linkVariableName,
characterClass: linkVariableName,
classLevel: linkClassLevel,
constant: linkVariableName,
damageMultiplier: linkDamageMultiplier,
proficiency: linkStats,
@@ -15,6 +16,18 @@ export default function linkTypeDependencies(dependencyGraph, prop){
linkDependenciesByType[prop.type]?.(prop);
}
function linkClassLevel(dependencyGraph, prop){
// The variableName of the prop depends on the prop
if (prop.variableName && prop.level){
dependencyGraph.addLink(prop.variableName, prop._id, 'classLevel');
// The level variable depends on the class variableName variable
let existingLevelLink = dependencyGraph.getLink('level', prop.variableName);
if (!existingLevelLink){
dependencyGraph.addLink('level', prop.variableName, 'level');
}
}
}
function linkVariableName(dependencyGraph, prop){
// The variableName of the prop depends on the prop
if (prop.variableName){

View File

@@ -7,7 +7,7 @@ import computedSchemas from '/imports/api/properties/computedPropertySchemasInde
import applyFnToKey from '/imports/api/creature/computation/newEngine/utility/applyFnToKey.js';
import { cloneDeep, unset } from 'lodash';
import createGraph from 'ngraph.graph';
import computeInventory from '/imports/api/creature/computation/newEngine/buildComputation/computeInventory.js';
import linkInventory from '/imports/api/creature/computation/newEngine/buildComputation/linkInventory.js';
import walkDown from '/imports/api/creature/computation/newEngine/utility/walkdown.js';
import parseCalculationFields from '/imports/api/creature/computation/newEngine/buildComputation/parseCalculationFields.js';
import computeInactiveStatus from '/imports/api/creature/computation/newEngine/buildComputation/computeInactiveStatus.js';
@@ -36,19 +36,26 @@ import computeSlotQuantityFilled from '/imports/api/creature/computation/newEngi
*/
export default function buildCreatureComputation(creatureId){
let properties = CreatureProperties.find({
const properties = getProperties(creatureId);
return buildComputationFromProps(properties);
}
function getProperties(creatureId){
return CreatureProperties.find({
'ancestors.id': creatureId,
'removed': {$ne: true},
}, {
sort: {order: 1}
});
}
export function buildComputationFromProps(properties){
// Dependency graph where edge(a, b) means a depends on b
// The graph includes all dependencies even of inactive properties
// such that any properties changing without changing their dependencies
// can limit the recompute to connected parts of the graph
// Each node's data represents a prop or a virtual prop like a variable
// Each link's data: {type: String, data: Object, requiresComputation: Boolean}
// Each link's data is a string representing the link type
const dependencyGraph = createGraph();
const computation = {
@@ -86,7 +93,7 @@ export default function buildCreatureComputation(creatureId){
};
// Parse all the calculations
parseCalculationFields(prop, computedSchemas)
parseCalculationFields(prop, computedSchemas);
});
// Get all the properties as trees based on their ancestors
@@ -98,8 +105,8 @@ export default function buildCreatureComputation(creatureId){
computeSlotQuantityFilled(node);
});
// Compute the inventory
computeInventory(forest, dependencyGraph);
// Link the inventory dependencies
linkInventory(forest, dependencyGraph);
// Graph functions that rely on the props being stored first
properties.forEach(prop => {

View File

@@ -2,6 +2,7 @@ import aggregate from '/imports/api/creature/computation/newEngine/computeComput
import computeVariableAsAttribute from '/imports/api/creature/computation/newEngine/computeComputation/computeVariableAsType/computeVariableAsAttribute.js';
import computeVariableAsSkill from '/imports/api/creature/computation/newEngine/computeComputation/computeVariableAsType/computeVariableAsSkill.js';
import computeVariableAsConstant from '/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable/computeVariableAsConstant.js';
import computeVariableAsClass from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable/computeVariableAsClass.js';
import computeImplicitVariable from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable/computeImplicitVariable.js';
export default function computeVariable(graph, node, scope){
@@ -26,9 +27,11 @@ function aggregateLinks(graph, node){
if (linkedNode.data.inactive) return;
// Apply all the aggregations
let arg = {node, linkedNode, link};
aggregate.definition(arg);
aggregate.classLevel(arg);
aggregate.damageMultiplier(arg);
aggregate.definition(arg);
aggregate.effect(arg);
aggregate.inventory(arg);
aggregate.proficiency(arg);
},
true // enumerate only outbound links
@@ -50,6 +53,8 @@ function computeVariableProp(node, prop, scope){
computeVariableAsSkill(node, prop, scope)
} else if (prop.type === 'constant'){
computeVariableAsConstant(node, prop, scope)
} else if (prop.type === 'characterClass'){
computeVariableAsClass(node, prop, scope)
}
}

View File

@@ -0,0 +1,15 @@
export default function aggregateClassLevel({node, linkedNode, link}){
if (link.data === 'classLevel'){
if (node.data.inactive) return;
if (!node.data.classLevelAggregator) node.data.classLevelAggregator = {
levelsFilled: [true], // Level 0 is always filled
level: 0,
};
let linkedProp = linkedNode.data;
let aggregator = node.data.classLevelAggregator;
if (linkedProp.level > aggregator.level) aggregator.level = linkedProp.level;
aggregator.levelsFilled[linkedProp.level] = true;
} else if (link.data === 'level'){
node.baseValue = (node.baseValue || 0) + node.data.classLevelAggregator.level;
}
}

View File

@@ -0,0 +1,60 @@
export default function aggregateInventory({node, linkedNode, link}){
let linkedProp = linkedNode.data || {};
const prop = node.data;
switch (link.data){
case 'attunedItem':
prop.baseValue = (prop.baseValue || 0) + 1;
return;
case 'equippedItem':
if (node.id === 'weightEquipment'){
prop.baseValue = (prop.baseValue || 0) + weight(linkedProp);
} else if (node.id === 'valueEquipment'){
prop.baseValue = (prop.baseValue || 0) + value(linkedProp);
}
return;
case 'containerContents':
// Add this property's weights and values to the container
if (!prop.weightless){
prop.contentsWeight = (prop.contentsWeight || 0) + weight(linkedProp);
if (prop.carried){
prop.carriedWeight = (prop.carriedWeight || 0) + carriedWeight(linkedProp);
}
}
prop.contentsValue = (prop.contentsValue || 0) + value(linkedProp);
if (prop.carried) {
prop.carriedValue = (prop.carriedValue || 0) + carriedValue(linkedProp);
}
return;
case 'inventoryStats':
if (node.id === 'weightTotal'){
prop.baseValue = (prop.baseValue || 0) + weight(linkedProp);
} else if (node.id === 'valueTotal'){
prop.baseValue = (prop.baseValue || 0) + value(linkedProp);
} else if (node.did === 'weightCarried'){
prop.baseValue = (prop.baseValue || 0) + carriedWeight(linkedProp);
} else if (node.did === 'valueCarried'){
prop.carriedValue = (prop.carriedValue || 0) + carriedValue(linkedProp);
}
return;
}
}
function weight(prop){
return (prop.weight || 0) + (prop.contentsWeight || 0);
}
function carriedWeight(prop){
return (prop.weight || 0) + (prop.carriedWeight || 0);
}
function value (prop){
return (prop.value || 0) + (prop.value || 0);
}
function carriedValue (prop){
return (prop.value || 0) + (prop.carriedValue || 0);
}

View File

@@ -2,10 +2,14 @@ import definition from './aggregateDefinition.js';
import damageMultiplier from './aggregateDamageMultiplier.js';
import effect from './aggregateEffect.js';
import proficiency from './aggregateProficiency.js';
import classLevel from './aggregateClassLevel.js';
import inventory from './aggregateInventory.js';
export default Object.freeze({
definition,
classLevel,
damageMultiplier,
definition,
effect,
inventory,
proficiency,
});

View File

@@ -11,25 +11,33 @@ import getAggregatorResult from '/imports/api/creature/computation/newEngine/com
prop.value = result;
prop.proficiency = node.data.proficiency;
// denormalise the aggregator fields
const aggregator = node.data.effectAggregator;
if (aggregator.advantage && !aggregator.disadvantage){
prop.advantage = 1;
} else if (aggregator.disadvantage && !aggregator.advantage){
prop.advantage = -1;
} else {
prop.advantage = 0;
// denormalise class level aggregator
let classLevelAgg = node.data.classLevelAggregator;
if (classLevelAgg){
prop.level = classLevelAgg.level;
}
// denormalise the effect aggregator fields
const aggregator = node.data.effectAggregator;
if (aggregator){
if (aggregator.advantage && !aggregator.disadvantage){
prop.advantage = 1;
} else if (aggregator.disadvantage && !aggregator.advantage){
prop.advantage = -1;
} else {
prop.advantage = 0;
}
// Passive bonus
prop.passiveBonus = aggregator.passiveAdd;
// conditional benefits
prop.conditionalBenefits = aggregator.conditional;
// Roll bonuses
prop.rollBonus = aggregator.rollBonus;
// Forced to fail
prop.fail = aggregator.fail;
// Rollbonus
prop.rollBonuses = aggregator.rollBonus;
}
// Passive bonus
prop.passiveBonus = aggregator.passiveAdd;
// conditional benefits
prop.conditionalBenefits = aggregator.conditional;
// Roll bonuses
prop.rollBonus = aggregator.rollBonus;
// Forced to fail
prop.fail = aggregator.fail;
// Rollbonus
prop.rollBonuses = aggregator.rollBonus;
return prop;
}

View File

@@ -10,7 +10,7 @@ export default function computeVariableAsAttribute(node, prop, scope){
// Ability scores get modifiers
if (prop.attributeType === 'ability'){
prop.modifier = Math.floor((prop.currentValue - 10) / 2);
prop.modifier = Math.floor((prop.value - 10) / 2);
}
// Hit dice denormalise constitution modifier

View File

@@ -0,0 +1,12 @@
export default function computeVariableAsAttribute(node, prop){
let classLevelAgg = node.data.classLevelAggregator;
if (!classLevelAgg) return;
prop.level = classLevelAgg.level;
classLevelAgg.levelsFilled.forEach((filled, index) => {
if (!filled){
if (!prop.missingLevels) prop.missingLevels = [];
prop.missingLevels.push(index);
}
});
prop.missingLevels?.sort((a, b) => a - b);
}

View File

@@ -16,8 +16,7 @@ function evaluateCalculation(calculation, scope){
const parseNode = calculation._parsedCalculation;
const fn = calculation._parseLevel || 'reduce';
const calculationScope = {...calculation._localScope, ...scope};
const result = parseNode[fn](calculationScope, context);
calculation.value = result;
calculation.value = parseNode[fn](calculationScope, context);
calculation.errors = context.errors;
}

View File

@@ -4,7 +4,7 @@ import computeByType from '/imports/api/creature/computation/newEngine/computeCo
export default function computeCreatureComputation(computation){
const stack = [];
// dict of computed nodes by id
// Computation scope of {variableName: prop}
const scope = {};
const graph = computation.dependencyGraph;
// Add all nodes to the stack
@@ -44,10 +44,7 @@ function compute(graph, node, scope){
function pushDependenciesToStack(nodeId, graph, stack){
graph.forEachLinkedNode(
nodeId,
(linkedNode, link) => {
// Ignore inventory links, they are already fully computed when they are
// created
if (link.data === 'inventory' || link.data === 'classLevel') return;
(linkedNode) => {
stack.push({
node: linkedNode,
visited: false,

View File

@@ -9,7 +9,6 @@ let ClassLevelSchema = createPropertySchema({
optional: true,
max: STORAGE_LIMITS.name,
},
// Only used by slot filling dialog, not computed
description: {
type: 'inlineCalculationFieldToCompute',
optional: true,
@@ -24,12 +23,7 @@ let ClassLevelSchema = createPropertySchema({
level: {
type: SimpleSchema.Integer,
defaultValue: 1,
},
// Same as in SlotFillers.js
slotFillerCondition: {
type: String,
optional: true,
max: STORAGE_LIMITS.calculation,
max: STORAGE_LIMITS.levelMax,
},
});

View File

@@ -0,0 +1,91 @@
import SimpleSchema from 'simpl-schema';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
// Classes are like slots, except they only take class levels and enforce that
// lower levels are taken before higher levels
let ClassSchema = createPropertySchema({
name: {
type: String,
optional: true,
max: STORAGE_LIMITS.name,
},
description: {
type: 'inlineCalculationFieldToCompute',
optional: true,
},
// Only `classLevel`s with the same variable name can fill the class
variableName: {
type: String,
optional: true,
max: STORAGE_LIMITS.variableName,
},
classType: {
type: String,
allowedValues: ['startingClass', 'multiClass'],
defaultValue: 'startingClass',
},
// Same tag format as Slots to match library classLevels against
slotTags: {
type: Array,
defaultValue: [],
maxCount: STORAGE_LIMITS.tagCount,
},
'slotTags.$': {
type: String,
max: STORAGE_LIMITS.tagLength,
},
extraTags: {
type: Array,
defaultValue: [],
maxCount: STORAGE_LIMITS.extraTagsCount,
},
'extraTags.$': {
type: Object,
},
'extraTags.$._id': {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue(){
if (!this.isSet) return Random.id();
}
},
'extraTags.$.operation': {
type: String,
allowedValues: ['OR', 'NOT'],
defaultValue: 'OR',
},
'extraTags.$.tags': {
type: Array,
defaultValue: [],
maxCount: STORAGE_LIMITS.tagCount,
},
'extraTags.$.tags.$': {
type: String,
max: STORAGE_LIMITS.tagLength,
},
});
const ComputedOnlyClassSchema = createPropertySchema({
description: {
type: 'inlineCalculationFieldToCompute',
optional: true,
},
level: {
type: SimpleSchema.Integer,
optional: true,
},
missingLevels: {
type: Array,
optional: true,
},
'missingLevels.$': {
type: SimpleSchema.Integer,
},
});
const ComputedClassSchema = new SimpleSchema()
.extend(ClassSchema)
.extend(ComputedOnlyClassSchema);
export { ClassSchema, ComputedOnlyClassSchema, ComputedClassSchema };

View File

@@ -17,44 +17,44 @@ let SlotSchema = createPropertySchema({
optional: true,
max: STORAGE_LIMITS.variableName,
},
slotTags: {
slotTags: {
type: Array,
defaultValue: [],
defaultValue: [],
maxCount: STORAGE_LIMITS.tagCount,
},
'slotTags.$': {
type: String,
'slotTags.$': {
type: String,
max: STORAGE_LIMITS.tagLength,
},
extraTags: {
},
extraTags: {
type: Array,
defaultValue: [],
defaultValue: [],
maxCount: STORAGE_LIMITS.extraTagsCount,
},
'extraTags.$': {
type: Object,
},
'extraTags.$': {
type: Object,
},
'extraTags.$._id': {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue(){
if (!this.isSet) return Random.id();
}
},
},
'extraTags.$.operation': {
type: String,
type: String,
allowedValues: ['OR', 'NOT'],
defaultValue: 'OR',
},
},
'extraTags.$.tags': {
type: Array,
type: Array,
defaultValue: [],
maxCount: STORAGE_LIMITS.tagCount,
},
},
'extraTags.$.tags.$': {
type: String,
type: String,
max: STORAGE_LIMITS.tagLength,
},
},
quantityExpected: {
type: 'fieldToCompute',
optional: true,
@@ -88,6 +88,10 @@ let SlotSchema = createPropertySchema({
const ComputedOnlySlotSchema = createPropertySchema({
// Computed fields
description: {
type: 'inlineCalculationFieldToCompute',
optional: true,
},
quantityExpected: {
type: 'computedOnlyField',
optional: true,
@@ -109,7 +113,7 @@ const ComputedOnlySlotSchema = createPropertySchema({
});
const ComputedSlotSchema = new SimpleSchema()
.extend(ComputedOnlySlotSchema)
.extend(SlotSchema);
.extend(ComputedOnlySlotSchema)
.extend(SlotSchema);
export { SlotSchema, ComputedSlotSchema, ComputedOnlySlotSchema };

View File

@@ -4,6 +4,7 @@ import { ComputedOnlyAdjustmentSchema } from '/imports/api/properties/Adjustment
import { ComputedOnlyAttackSchema } from '/imports/api/properties/Attacks.js';
import { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.js';
import { ComputedOnlyBuffSchema } from '/imports/api/properties/Buffs.js';
import { ComputedOnlyClassSchema } from '/imports/api/properties/Classes.js';
import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
import { ConstantSchema } from '/imports/api/properties/Constants.js';
import { ComputedOnlyContainerSchema } from '/imports/api/properties/Containers.js';
@@ -31,6 +32,7 @@ const propertySchemasIndex = {
attack: ComputedOnlyAttackSchema,
attribute: ComputedOnlyAttributeSchema,
buff: ComputedOnlyBuffSchema,
characterClass: ComputedOnlyClassSchema,
classLevel: ClassLevelSchema,
constant: ConstantSchema,
container: ComputedOnlyContainerSchema,

View File

@@ -4,6 +4,7 @@ import { ComputedAdjustmentSchema } from '/imports/api/properties/Adjustments.js
import { ComputedAttackSchema } from '/imports/api/properties/Attacks.js';
import { ComputedAttributeSchema } from '/imports/api/properties/Attributes.js';
import { ComputedBuffSchema } from '/imports/api/properties/Buffs.js';
import { ComputedClassSchema } from '/imports/api/properties/Classes.js';
import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
import { ConstantSchema } from '/imports/api/properties/Constants.js';
import { ComputedContainerSchema } from '/imports/api/properties/Containers.js';
@@ -31,6 +32,7 @@ const propertySchemasIndex = {
attack: ComputedAttackSchema,
attribute: ComputedAttributeSchema,
buff: ComputedBuffSchema,
characterClass: ComputedClassSchema,
classLevel: ClassLevelSchema,
constant: ConstantSchema,
damage: ComputedDamageSchema,

View File

@@ -4,6 +4,7 @@ import { AdjustmentSchema } from '/imports/api/properties/Adjustments.js';
import { AttackSchema } from '/imports/api/properties/Attacks.js';
import { AttributeSchema } from '/imports/api/properties/Attributes.js';
import { BuffSchema } from '/imports/api/properties/Buffs.js';
import { ClassSchema } from '/imports/api/properties/Classes.js';
import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
import { ConstantSchema } from '/imports/api/properties/Constants.js';
import { DamageSchema } from '/imports/api/properties/Damages.js';
@@ -31,6 +32,7 @@ const propertySchemasIndex = {
attack: AttackSchema,
attribute: AttributeSchema,
buff: BuffSchema,
characterClass: ClassSchema,
classLevel: ClassLevelSchema,
constant: ConstantSchema,
damage: DamageSchema,

View File

@@ -30,11 +30,17 @@ const PROPERTIES = Object.freeze({
helpText: 'When a buff is activated as a child of an action, it will copy the properties under itself onto a target character.',
suggestedParents: ['action', 'attack', 'savingThrow', 'spell'],
},
characterClass: {
icon: 'mdi-card-account-details',
name: 'Class',
helpText: 'Your character should ideally have one starting class. Classes hold class levels',
suggestedParents: ['class'],
},
classLevel: {
icon: '$vuetify.icons.class_level',
name: 'Class level',
helpText: 'Class levels represent a single level gained in a class',
suggestedParents: ['class'],
suggestedParents: [],
},
constant: {
icon: 'mdi-anchor',

View File

@@ -13,6 +13,9 @@ const STORAGE_LIMITS = Object.freeze({
url: 256,
variableName: 64,
// Number limits
levelMax: 128,
//Array counts
ancestorCount: 100,
damageTypeCount: 32,