Overhauled computations to allow for toggles :'( that sucked

This commit is contained in:
Thaum Rystra
2020-05-16 22:03:21 +02:00
parent 7024adecaf
commit 5c0a2a4d6c
30 changed files with 468 additions and 251 deletions

View File

@@ -1,22 +1,45 @@
import { includes, cloneDeep, has } from 'lodash';
import { includes, cloneDeep } from 'lodash';
// The computation memo is an in-memory data structure used only during the
// computation process
export default class ComputationMemo {
constructor(props){
this.statsByVariableName = {};
this.extraStatsByVariableName = {};
this.statsById = {};
this.originalPropsById = {};
this.propsById = {};
this.skillsByAbility = {};
this.unassignedEffects = [];
this.classes = {};
props.filter((prop) => {
// skip effects, proficiencies, and class levels for the next pass
this.classLevelsById = {};
this.togglesById = {};
this.toggleIds = new Set();
// First note all the ids of all the toggles
props.forEach((prop) => {
if (
prop.type === 'effect' ||
prop.type === 'proficiency' ||
prop.type === 'classLevel'
) return true;
// Add all the stats
this.addStat(prop);
prop.type === 'toggle'
) {
this.toggleIds.add(prop._id);
}
});
props.filter((prop) => {
if (
prop.type === 'toggle'
) {
this.addToggle(prop);
} else {
return true;
}
}).filter((prop) => {
if (
prop.type === 'attribute' ||
prop.type === 'skill'
) {
// Add all the stats
this.addStat(prop);
} else {
return true;
}
}).forEach((prop) => {
// Now add all effects and proficiencies
if (prop.type === 'effect'){
@@ -32,8 +55,14 @@ export default class ComputationMemo {
this.originalPropsById[prop._id] = cloneDeep(prop);
this.propsById[prop._id] = prop;
prop.computationDetails = propDetails(prop);
prop.ancestors.forEach(ancestor => {
if (this.toggleIds.has(ancestor.id)){
prop.computationDetails.toggleAncestors.push(ancestor.id);
}
});
return prop;
}
/*
storeHighestClassLevel(name, prop, isBaseClass){
// Only store the highest level classLevel
let stat = this.statsByVariableName[name]
@@ -72,33 +101,51 @@ export default class ComputationMemo {
level += this.classes[name].level || 0;
}
this.statsByVariableName['level'].value = level;
}*/
addToggle(prop){
prop = this.registerProperty(prop);
this.togglesById[prop._id] = prop;
}
addClassLevel(prop){
prop = this.registerProperty(prop);
if (prop.variableName){
this.storeHighestClassLevel(prop.variableName, prop);
}
if (prop.baseClass){
this.storeHighestClassLevel(prop.baseClass, prop, true);
}
this.classLevelsById[prop._id] = prop;
}
addStat(prop){
prop = this.registerProperty(prop);
let variableName = prop.variableName;
if (!variableName) return;
if (this.statsByVariableName[variableName]){
prop.value = NaN;
prop.computationDetails.error = 'variableNameCollision';
if (Meteor.isClient) console.warn('variableNameCollision', prop);
return;
}
this.statsByVariableName[variableName] = prop;
if (
prop.type === 'skill' &&
isSkillCheck(prop) &&
prop.ability
){
this.addSkillToAbility(prop, prop.ability)
let existingStat = this.statsByVariableName[variableName];
if (existingStat){
existingStat.computationDetails.idsOfSameName.push(prop._id);
this.originalPropsById[prop._id] = cloneDeep(prop);
if (prop.baseValueCalculation){
existingStat.computationDetails.effects.push({
operation: 'base',
calculation: prop.baseValueCalculation,
stats: [variableName],
computationDetails: propDetailsByType.effect(),
statBase: true,
});
}
if (prop.baseProficiency){
existingStat.computationDetails.proficiencies.push({
value: prop.baseProficiency,
stats: [variableName],
computationDetails: propDetailsByType.proficiency(),
type: 'proficiency',
statBase: true,
});
}
} else {
prop = this.registerProperty(prop);
this.statsById[prop._id] = prop;
this.statsByVariableName[variableName] = prop;
if (
prop.type === 'skill' &&
isSkillCheck(prop) &&
prop.ability
){
this.addSkillToAbility(prop, prop.ability)
}
}
}
addSkillToAbility(prop, ability){
@@ -152,9 +199,14 @@ export default class ComputationMemo {
let target = this.statsByVariableName[statName];
if (!target) return;
targets.add(target);
if (isAbility(target) && isSkillCheck(prop)) {
if (isAbility(target)) {
let extras = this.skillsByAbility[statName] || [];
targets.add(...extras)
extras.forEach(ex =>{
// Only pass on ability proficiencies to skills and checks
if (ex.skillType === 'skill' || ex.skillType === 'check'){
targets.add(ex)
}
});
}
});
return targets;
@@ -188,11 +240,22 @@ function propDetails(prop){
}
const propDetailsByType = {
toggle(){
return {
computed: false,
busyComputing: false,
toggleAncestors: [],
disabledByToggle: false,
};
},
attribute(){
return {
computed: false,
busyComputing: false,
effects: [],
toggleAncestors: [],
disabledByToggle: false,
idsOfSameName: [],
};
},
skill(){
@@ -201,19 +264,30 @@ const propDetailsByType = {
busyComputing: false,
effects: [],
proficiencies: [],
toggleAncestors: [],
disabledByToggle: false,
idsOfSameName: [],
};
},
effect(){
return {
computed: false,
busyComputing: false,
toggleAncestors: [],
disabledByToggle: false,
};
},
classLevel(){
return {
computed: true,
toggleAncestors: [],
disabledByToggle: false,
};
},
proficiency(){
return {};
return {
toggleAncestors: [],
disabledByToggle: false,
};
},
}

View File

@@ -25,6 +25,9 @@ export default class EffectAggregator{
case 'base':
// Take the largest base value
this.base = result > this.base ? result : this.base;
if (effect.statBase){
this.statBaseValue = result > this.statBaseValue ? result : this.statBaseValue;
}
break;
case 'add':
// Add all adds together

View File

@@ -0,0 +1,11 @@
import computeToggle from '/imports/api/creature/computation/computeToggle.js';
export default function applyToggles(prop, memo){
prop.computationDetails.toggleAncestors.forEach(toggleId => {
let toggle = memo.togglesById[toggleId];
computeToggle(toggle, memo);
if (!toggle.toggleResult){
prop.computationDetails.disabledByToggle = true;
}
});
}

View File

@@ -1,5 +1,5 @@
import computeStat from '/imports/api/creature/computation/computeStat.js';
import applyToggles from '/imports/api/creature/computation/applyToggles.js';
export default function combineStat(stat, aggregator, memo){
if (stat.type === 'attribute'){
@@ -37,7 +37,13 @@ function combineSkill(stat, aggregator, memo){
stat.proficiency = stat.baseProficiency || 0;
for (let i in stat.computationDetails.proficiencies){
let prof = stat.computationDetails.proficiencies[i];
if (prof.value > stat.proficiency) stat.proficiency = prof.value;
applyToggles(prof, memo);
if (
!prof.computationDetails.disabledByToggle &&
prof.value > stat.proficiency
){
stat.proficiency = prof.value;
}
}
// Get the character's proficiency bonus to apply
let profBonusStat = memo.statsByVariableName['proficiencyBonus'];

View File

@@ -1,7 +1,25 @@
import evaluateCalculation from '/imports/api/creature/computation/evaluateCalculation.js';
import applyToggles from '/imports/api/creature/computation/applyToggles.js';
export default function computeEffect(effect, memo){
if (effect.computationDetails.computed) return;
if (effect.computationDetails.busyComputing){
// Trying to compute this effect again while it is already computing.
// We must be in a dependency loop.
effect.computationDetails.computed = true;
effect.result = NaN;
effect.computationDetails.busyComputing = false;
effect.computationDetails.error = 'dependencyLoop';
if (Meteor.isClient) console.warn('dependencyLoop', effect);
return;
}
// Before doing any work, mark this effect as busy
effect.computationDetails.busyComputing = true;
// Apply any toggles
applyToggles(effect, memo);
// Determine result of effect calculation
if (!effect.calculation){
if(effect.operation === 'add' || effect.operation === 'base'){
effect.result = 0;
@@ -16,4 +34,5 @@ export default function computeEffect(effect, memo){
effect.result = evaluateCalculation(effect.calculation, memo);
}
effect.computationDetails.computed = true;
effect.computationDetails.busyComputing = false;
}

View File

@@ -1,12 +1,19 @@
import { each, forOwn } from 'lodash';
import computeStat from '/imports/api/creature/computation/computeStat.js';
import computeEffect from '/imports/api/creature/computation/computeEffect.js';
import computeToggle from '/imports/api/creature/computation/computeToggle.js';
export default function computeMemo(memo){
forOwn(memo.statsByVariableName, (stat) => {
// Compute all stats, even if they are overriden
forOwn(memo.statsById, stat => {
computeStat (stat, memo);
});
each(memo.unassignedEffects, (effect) => {
// Compute effects which didn't end up targeting a stat
each(memo.unassignedEffects, effect => {
computeEffect(effect, memo);
});
forOwn(memo.togglesById, toggle => {
computeToggle(toggle, memo);
});
// Compute class levels
}

View File

@@ -1,6 +1,7 @@
import combineStat from '/imports/api/creature/computation/combineStat.js';
import computeEffect from '/imports/api/creature/computation/computeEffect.js';
import EffectAggregator from '/imports/api/creature/computation/EffectAggregator.js';
import applyToggles from '/imports/api/creature/computation/applyToggles.js';
import { each } from 'lodash';
export default function computeStat(stat, memo){
@@ -18,14 +19,21 @@ export default function computeStat(stat, memo){
}
// Before doing any work, mark this stat as busy
stat.computationDetails.busyComputing = true;
// Compute and aggregate all the effects
let aggregator = new EffectAggregator(stat, memo)
each(stat.computationDetails.effects, (effect) => {
computeEffect(effect, memo);
aggregator.addEffect(effect);
});
// Conglomerate all the effects to compute the final stat values
combineStat(stat, aggregator, memo);
// Apply any toggles
applyToggles(stat, memo);
if (!stat.computationDetails.disabledByToggle){
// Compute and aggregate all the effects
let aggregator = new EffectAggregator(stat, memo)
each(stat.computationDetails.effects, (effect) => {
computeEffect(effect, memo);
if (!effect.computationDetails.disabledByToggle){
aggregator.addEffect(effect);
}
});
// Conglomerate all the effects to compute the final stat values
combineStat(stat, aggregator, memo);
}
// Mark the attribute as computed
stat.computationDetails.computed = true;
stat.computationDetails.busyComputing = false;

View File

@@ -0,0 +1,30 @@
import evaluateCalculation from '/imports/api/creature/computation/evaluateCalculation.js';
export default function computeToggle(toggle, memo){
if (toggle.computationDetails.computed) return;
if (toggle.computationDetails.busyComputing){
// Trying to compute this effect again while it is already computing.
// We must be in a dependency loop.
toggle.computationDetails.computed = true;
toggle.result = false;
toggle.computationDetails.busyComputing = false;
toggle.computationDetails.error = 'dependencyLoop';
if (Meteor.isClient) console.warn('dependencyLoop', toggle);
return;
}
// Before doing any work, mark this toggle as busy
toggle.computationDetails.busyComputing = true;
// Do work
if (toggle.enabled){
toggle.toggleResult = true;
} else if (!toggle.condition){
toggle.toggleResult = false;
} else if (Number.isFinite(+toggle.condition)){
toggle.toggleResult = !!+toggle.condition;
} else {
toggle.toggleResult = evaluateCalculation(toggle.condition, memo);
}
toggle.computationDetails.computed = true;
toggle.computationDetails.busyComputing = false;
}

View File

@@ -32,6 +32,7 @@ const calculationPropertyTypes = [
'effect',
'proficiency',
'classLevel',
'toggle',
];
/**
@@ -71,7 +72,11 @@ const calculationPropertyTypes = [
* - Write the computed results back to the database
*/
export function recomputeCreatureById(creatureId){
let props = getActiveProperties(creatureId, {type: {$in: calculationPropertyTypes}});
let props = getActiveProperties({
ancestorId: creatureId,
filter: {type: {$in: calculationPropertyTypes}},
includeUntoggled: true,
});
let computationMemo = new ComputationMemo(props);
computeMemo(computationMemo);
writeAlteredProperties(computationMemo);

View File

@@ -3,51 +3,60 @@ import { isEqual, forOwn } from 'lodash';
import { ComputedOnlySkillSchema } from '/imports/api/properties/Skills.js';
import { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.js';
import { ComputedOnlyEffectSchema } from '/imports/api/properties/Effects.js';
import { ComputedOnlyToggleSchema } from '/imports/api/properties/Toggles.js';
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
const schemasByType = {
'skill': ComputedOnlySkillSchema,
'attribute': ComputedOnlyAttributeSchema,
'effect': ComputedOnlyEffectSchema,
'toggle': ComputedOnlyToggleSchema,
};
export default function writeAlteredProperties(memo){
let bulkWriteOperations = [];
// Loop through all properties on the memo
forOwn(memo.originalPropsById, (original, _id) => {
let changed = memo.propsById[_id];
let schema;
switch (changed.type){
case 'skill':
schema = ComputedOnlySkillSchema;
break;
case 'attribute':
schema = ComputedOnlyAttributeSchema;
break;
case 'effect':
schema = ComputedOnlyEffectSchema;
break;
default:
return;
forOwn(memo.propsById, changed => {
let schema = schemasByType[changed.type];
if (!schema) return;
let extraIds = changed.computationDetails.idsOfSameName;
let ids;
if (extraIds && extraIds.length){
ids = [changed._id, ...extraIds];
} else {
ids = [changed._id];
}
let op = undefined;
// Loop through all keys that can be changed by computation
// and compile an operation that sets all those keys
for (let key of schema.objectKeys()){
if (!isEqual(original[key], changed[key])){
if (!op) op = newOperation(_id, changed.type);
let value = changed[key];
if (value === undefined){
// Unset values that become undefined
addUnsetOp(op, key);
} else {
// Set values that changed to something else
addSetOp(op, key, value);
}
ids.forEach(id => {
let op = undefined;
let original = memo.originalPropsById[id];
op = addChangedKeysToOp(op, schema.objectKeys(), original, changed);
if (op){
bulkWriteOperations.push(op);
}
}
if (op){
bulkWriteOperations.push(op);
}
});
});
bulkWriteProperties(bulkWriteOperations);
}
function addChangedKeysToOp(op, keys, original, changed) {
// Loop through all keys that can be changed by computation
// and compile an operation that sets all those keys
for (let key of keys){
if (!isEqual(original[key], changed[key])){
if (!op) op = newOperation(original._id, changed.type);
let value = changed[key];
if (value === undefined){
// Unset values that become undefined
addUnsetOp(op, key);
} else {
// Set values that changed to something else
addSetOp(op, key, value);
}
}
}
return op;
}
function newOperation(_id, type){
let newOp = {
updateOne: {
@@ -92,7 +101,8 @@ function bulkWriteProperties(bulkWriteOps){
);
} else {
bulkWriteOps.forEach(op => {
CreatureProperties.update(op.updateOne.filter, op.updateOne.update, {
let updateOneOrMany = op.updateOne || op.updateMany;
CreatureProperties.update(updateOneOrMany.filter, updateOneOrMany.update, {
// The server code is bypassing collection 2 validation, so do the same
// on the client
// include this if bypass is off:

View File

@@ -24,7 +24,10 @@ export const recomputeDamageMultipliers = new ValidatedMethod({
export function recomputeDamageMultipliersById(creatureId){
if (!creatureId) throw 'Creature ID is required';
let props = getActiveProperties(creatureId, {type: 'damageMultiplier'});
let props = getActiveProperties({
ancestorId: creatureId,
filter: {type: 'damageMultiplier'},
});
// Count of how many weakness, resistances and immunities each damage type has
let multipliersByName = {};

View File

@@ -1,19 +1,28 @@
import Creatures from '/imports/api/creature/Creatures.js';
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
export default function getActiveProperties(ancestorId, filter = {}, options){
export default function getActiveProperties({
ancestorId,
filter = {},
options,
includeUntoggled = false
}){
if (!ancestorId){
throw 'Ancestor Id is required to get active properties'
}
// First get ids of disabled properties, unequiped items, unapplied buffs
let disabledAncestorIds = CreatureProperties.find({
let disabledAncestorsFilter = {
'ancestors.id': ancestorId,
$or: [
{disabled: true},
{equipped: false},
{applied: false},
],
}, {
};
if (!includeUntoggled){
disabledAncestorsFilter.$or.push({toggleResult: false});
}
let disabledAncestorIds = CreatureProperties.find(disabledAncestorsFilter, {
fields: {_id: 1},
}).map(prop => prop._id);

View File

@@ -1,6 +1,5 @@
import SimpleSchema from 'simpl-schema';
import ResourcesSchema from '/imports/api/properties/subSchemas/ResourcesSchema.js'
import ResultsSchema from '/imports/api/properties/subSchemas/ResultsSchema.js';
/*
* Actions are things a character can do
@@ -41,10 +40,6 @@ let ActionSchema = new SimpleSchema({
'tags.$': {
type: String,
},
results: {
type: ResultsSchema,
defaultValue: {},
},
resources: {
type: ResourcesSchema,
defaultValue: {},

View File

@@ -0,0 +1,27 @@
import SimpleSchema from 'simpl-schema';
const AdjustmentSchema = new SimpleSchema({
// The roll that determines how much to change the attribute
adjustment: {
type: String,
optional: true,
defaultValue: '1',
},
// Who this adjustment applies to
target: {
type: String,
defaultValue: 'every',
allowedValues: [
'self', // the character who took the action
'each', // rolled once for `each` target
'every', // rolled once and applied to `every` target
],
},
// The stat this rolls applies to
stat: {
type: String,
optional: true,
},
});
export { AdjustmentSchema };

View File

@@ -1,6 +1,4 @@
import SimpleSchema from 'simpl-schema';
import { Random } from 'meteor/random';
import { StoredEffectSchema } from '/imports/api/properties/Effects.js';
let BuffSchema = new SimpleSchema({
name: {
@@ -15,20 +13,12 @@ let BuffSchema = new SimpleSchema({
type: String,
optional: true,
},
});
// The effects in the stored buff need to be resolved to a number before being
// placed on other characters, if they are applied to self, they can remain as
// calculations, provided they don't contain any rolls
let StoredBuffSchema = new SimpleSchema({
effects: {
type: Array,
defaultValue: [],
},
'effects.$': {
type: StoredEffectSchema,
},
target: {
applied: {
type: Boolean,
defaultValue: false,
index: 1,
},
target: {
type: String,
allowedValues: [
'self', // the character who took the buff
@@ -37,21 +27,14 @@ let StoredBuffSchema = new SimpleSchema({
],
defaultValue: 'every',
},
}).extend(BuffSchema);
let StoredBuffWithIdSchema = new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue(){
if (!this.isSet){
return Random.id();
}
},
},
}).extend(StoredBuffSchema);
});
let AppliedBuffSchema = new SimpleSchema({
applied: {
type: Boolean,
defaultValue: true,
index: 1,
},
durationSpent: {
type: Number,
optional: true,
@@ -73,4 +56,4 @@ let AppliedBuffSchema = new SimpleSchema({
},
}).extend(BuffSchema);
export { AppliedBuffSchema, StoredBuffSchema, StoredBuffWithIdSchema };
export { AppliedBuffSchema, BuffSchema };

View File

@@ -1,22 +1,14 @@
import SimpleSchema from 'simpl-schema';
import { Random } from 'meteor/random';
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
const DamageSchema = new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue(){
if (!this.isSet) return Random.id();
}
},
// The roll that determines how much to damage the attribute
damage: {
type: String,
optional: true,
defaultValue: '1d8 + strength.modifier',
},
// Who this adjustment applies to
// Who this damage applies to
target: {
type: String,
defaultValue: 'every',
@@ -33,4 +25,4 @@ const DamageSchema = new SimpleSchema({
},
});
export default DamageSchema;
export { DamageSchema };

View File

@@ -1,5 +1,4 @@
import SimpleSchema from 'simpl-schema';
import { Random } from 'meteor/random';
/*
* Effects are reason-value attached to skills and abilities
@@ -42,16 +41,6 @@ let EffectSchema = new SimpleSchema({
},
});
const StoredEffectSchema = new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue(){
if (!this.isSet) return Random.id();
}
},
}).extend(EffectSchema);
const ComputedOnlyEffectSchema = new SimpleSchema({
// The computed result of the effect
result: {
@@ -64,4 +53,4 @@ const ComputedEffectSchema = new SimpleSchema()
.extend(ComputedOnlyEffectSchema)
.extend(EffectSchema);
export { EffectSchema, StoredEffectSchema, ComputedEffectSchema, ComputedOnlyEffectSchema };
export { EffectSchema, ComputedEffectSchema, ComputedOnlyEffectSchema };

View File

@@ -0,0 +1,15 @@
import SimpleSchema from 'simpl-schema';
const ResultSchema = new SimpleSchema({
name: {
type: String,
optional: true,
},
// Expression of whether or not to apply the children
comparison: {
type: String,
optional: true,
},
});
export { ResultSchema };

View File

@@ -1,5 +1,4 @@
import SimpleSchema from 'simpl-schema';
import RollResultsSchema from '/imports/api/properties/subSchemas/RollResultsSchema.js'
/**
* Rolls are children to actions or other rolls, they are triggered with 0 or
@@ -34,13 +33,6 @@ let RollSchema = new SimpleSchema({
'tags.$': {
type: String,
},
rollResults: {
type: Array,
defaultValue: [],
},
'rollResults.$': {
type: RollResultsSchema,
},
});
export { RollSchema };

View File

@@ -1,5 +1,4 @@
import SimpleSchema from 'simpl-schema';
import ResultsSchema from '/imports/api/properties/subSchemas/ResultsSchema.js';
// These are the rolls made when saves are called for
// For the saving throw bonus or proficiency, see ./Skills.js
@@ -8,18 +7,10 @@ let SavingThrowSchema = new SimpleSchema ({
type: String,
optional: true,
},
// The variable name of ability the saving throw relies on
ability: {
// The variable name of ability the save to roll
stat: {
type: String,
optional: true,
},
passResults: {
type: ResultsSchema,
defaultValue: {},
},
failResults: {
type: ResultsSchema,
defaultValue: {},
},
});

View File

@@ -0,0 +1,36 @@
import SimpleSchema from 'simpl-schema';
const ToggleSchema = new SimpleSchema({
name: {
type: String,
optional: true,
},
disabled: {
type: Boolean,
optional: true,
},
enabled: {
type: Boolean,
optional: true,
},
// if neither disabled or enabled, the condition will be run to determine
// if the children of the toggle should be active
condition: {
type: String,
optional: true,
},
});
const ComputedOnlyToggleSchema = new SimpleSchema({
// The computed result of the effect
toggleResult: {
type: SimpleSchema.oneOf(Number, String, Boolean),
optional: true,
},
});
const ComputedToggleSchema = new SimpleSchema()
.extend(ComputedOnlyToggleSchema)
.extend(ToggleSchema);
export { ToggleSchema, ComputedOnlyToggleSchema, ComputedToggleSchema };

View File

@@ -1,9 +1,11 @@
import SimpleSchema from 'simpl-schema';
import { ActionSchema } from '/imports/api/properties/Actions.js';
import { AdjustmentSchema } from '/imports/api/properties/Adjustments.js';
import { AttackSchema } from '/imports/api/properties/Attacks.js';
import { ComputedAttributeSchema } from '/imports/api/properties/Attributes.js';
import { AppliedBuffSchema } from '/imports/api/properties/Buffs.js';
import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
import { DamageSchema } from '/imports/api/properties/Damages.js';
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
import { ComputedEffectSchema } from '/imports/api/properties/Effects.js';
import { ExperienceSchema } from '/imports/api/properties/Experiences.js';
@@ -11,21 +13,25 @@ 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 { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
import { ResultSchema } from '/imports/api/properties/Results.js';
import { RollSchema } from '/imports/api/properties/Rolls.js';
import { SavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
import { ComputedSkillSchema } from '/imports/api/properties/Skills.js';
import { SlotSchema } from '/imports/api/properties/Slots.js';
import { SpellListSchema } from '/imports/api/properties/SpellLists.js';
import { SpellSchema } from '/imports/api/properties/Spells.js';
import { ToggleSchema } from '/imports/api/properties/Toggles.js';
import { ContainerSchema } from '/imports/api/properties/Containers.js';
import { ItemSchema } from '/imports/api/properties/Items.js';
const propertySchemasIndex = {
action: ActionSchema,
adjustment: AdjustmentSchema,
attack: AttackSchema,
attribute: ComputedAttributeSchema,
buff: AppliedBuffSchema,
classLevel: ClassLevelSchema,
damage: DamageSchema,
damageMultiplier: DamageMultiplierSchema,
effect: ComputedEffectSchema,
experience: ExperienceSchema,
@@ -33,12 +39,14 @@ const propertySchemasIndex = {
folder: FolderSchema,
note: NoteSchema,
proficiency: ProficiencySchema,
result: ResultSchema,
roll: RollSchema,
savingThrow: SavingThrowSchema,
skill: ComputedSkillSchema,
slot: SlotSchema,
spellList: SpellListSchema,
spell: SpellSchema,
toggle: ToggleSchema,
container: ContainerSchema,
item: ItemSchema,
any: new SimpleSchema({}),

View File

@@ -1,9 +1,11 @@
import SimpleSchema from 'simpl-schema';
import { ActionSchema } from '/imports/api/properties/Actions.js';
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 { StoredBuffSchema } from '/imports/api/properties/Buffs.js';
import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
import { DamageSchema } from '/imports/api/properties/Damages.js';
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
import { EffectSchema } from '/imports/api/properties/Effects.js';
import { ExperienceSchema } from '/imports/api/properties/Experiences.js';
@@ -11,21 +13,25 @@ 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 { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
import { ResultSchema } from '/imports/api/properties/Results.js';
import { RollSchema } from '/imports/api/properties/Rolls.js';
import { SavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
import { SkillSchema } from '/imports/api/properties/Skills.js';
import { SlotSchema } from '/imports/api/properties/Slots.js';
import { SpellListSchema } from '/imports/api/properties/SpellLists.js';
import { SpellSchema } from '/imports/api/properties/Spells.js';
import { ToggleSchema } from '/imports/api/properties/Toggles.js';
import { ContainerSchema } from '/imports/api/properties/Containers.js';
import { ItemSchema } from '/imports/api/properties/Items.js';
const propertySchemasIndex = {
action: ActionSchema,
adjustment: AdjustmentSchema,
attack: AttackSchema,
attribute: AttributeSchema,
buff: StoredBuffSchema,
classLevel: ClassLevelSchema,
damage: DamageSchema,
damageMultiplier: DamageMultiplierSchema,
effect: EffectSchema,
experience: ExperienceSchema,
@@ -33,12 +39,14 @@ const propertySchemasIndex = {
folder: FolderSchema,
note: NoteSchema,
proficiency: ProficiencySchema,
result: ResultSchema,
roll: RollSchema,
savingThrow: SavingThrowSchema,
skill: SkillSchema,
slot: SlotSchema,
spellList: SpellListSchema,
spell: SpellSchema,
toggle: ToggleSchema,
container: ContainerSchema,
item: ItemSchema,
any: new SimpleSchema({}),

View File

@@ -1,36 +0,0 @@
import SimpleSchema from 'simpl-schema';
import AdjustmentSchema from '/imports/api/properties/subSchemas/AdjustmentSchema.js';
import DamageSchema from '/imports/api/properties/subSchemas/DamageSchema.js';
import { StoredBuffWithIdSchema } from '/imports/api/properties/Buffs.js';
let ResultsSchema = new SimpleSchema({
// Adjustments applied when taking this action
// Ideally, if these adjustments can't be made, the action should be unusable
adjustments: {
type: Array,
defaultValue: [],
},
'adjustments.$': {
type: AdjustmentSchema,
},
// Damage is done to hitpoints or hitpoint-like stats
// has a damage type, can be mitigated by resistances, etc.
damages: {
type: Array,
defaultValue: [],
},
'damages.$': {
type: DamageSchema,
},
// Buffs applied when taking this action
buffs: {
type: Array,
defaultValue: [],
},
'buffs.$': {
type: StoredBuffWithIdSchema,
},
});
export default ResultsSchema;

View File

@@ -75,6 +75,10 @@ const PROPERTIES = Object.freeze({
icon: 'category',
name: 'Item'
},
toggle: {
icon: 'power_settings_new',
name: 'Toggle'
},
});
export default PROPERTIES;

View File

@@ -276,13 +276,14 @@
import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue';
import ActionListTile from '/imports/ui/properties/components/actions/ActionListTile.vue';
import AttackListTile from '/imports/ui/properties/components/actions/AttackListTile.vue';
import getActiveProperties from '/imports/api/creature/getActiveProperties.js';
const getProperties = function(creatureId, filter = {}){
filter['ancestors.id'] = creatureId;
filter.removed = {$ne: true};
return CreatureProperties.find(filter, {
sort: {order: 1}
});
const getProperties = function(creatureId, filter){
return getActiveProperties({
ancestorId: creatureId,
filter,
options: {sort: {order: 1}},
});
};
const getAttributeOfType = function(creatureId, type){

View File

@@ -1,6 +1,9 @@
<template lang="html">
<div class="results-form">
<div class="subheading" v-if="model.damages.length">
<div
v-if="model.damages.length"
class="subheading"
>
Damage
</div>
<damage-list-form
@@ -10,7 +13,10 @@
@push="({path, value, ack}) => $emit('push', {path: ['damages', ...path], value, ack})"
@pull="({path, ack}) => $emit('pull', {path: ['damages', ...path], ack})"
/>
<div class="subheading" v-if="model.adjustments.length">
<div
v-if="model.adjustments.length"
class="subheading"
>
Adjustments
</div>
<adjustment-list-form
@@ -20,7 +26,10 @@
@push="({path, value, ack}) => $emit('push', {path: ['adjustments', ...path], value, ack})"
@pull="({path, ack}) => $emit('pull', {path: ['adjustments', ...path], ack})"
/>
<div class="subheading" v-if="model.buffs.length">
<div
v-if="model.buffs.length"
class="subheading"
>
Buffs
</div>
<buff-list-form
@@ -32,8 +41,13 @@
@pull="({path, ack}) => $emit('pull', {path: ['buffs', ...path], ack})"
/>
<div class="layout row justify-center">
<v-menu origin="center center" transition="scale-transition" nudge-top="50%" nudge-left="50%">
<template v-slot:activator="{ on }">
<v-menu
origin="center center"
transition="scale-transition"
nudge-top="50%"
nudge-left="50%"
>
<template #activator="{ on }">
<v-btn
:loading="addResultLoading"
:disabled="addResultLoading"
@@ -48,13 +62,16 @@
<v-list>
<v-list-tile@click="addDamage">
<v-list-tile-title>Add Damage</v-list-tile-title>
</v-list-tile>
<v-list-tile@click="addAdjustment">
<v-list-tile-title>Add Adjustment</v-list-tile-title>
</v-list-tile>
<v-list-tile@click="addBuff">
<v-list-tile-title>Add Buff</v-list-tile-title>
</v-list-tile>
</v-list-tile>
<v-list-tile@click="addAdjustment">
<v-list-tile-title>Add Adjustment</v-list-tile-title>
</v-list-tile>
<v-list-tile@click="addBuff">
<v-list-tile-title>Add Buff</v-list-tile-title>
</v-list-tile>
</v-list-tile@click="addbuff">
</v-list-tile@click="addadjustment">
</v-list-tile@click="adddamage">
</v-list>
</v-menu>
</div>
@@ -65,9 +82,6 @@
import AdjustmentListForm from '/imports/ui/properties/forms/AdjustmentListForm.vue';
import DamageListForm from '/imports/ui/properties/forms/DamageListForm.vue';
import BuffListForm from '/imports/ui/properties/forms/BuffListForm.vue';
import ResultsSchema from '/imports/api/properties/subSchemas/ResultsSchema.js';
import DamageSchema from '/imports/api/properties/subSchemas/DamageSchema.js';
import AdjustmentSchema from '/imports/api/properties/subSchemas/AdjustmentSchema.js';
import { StoredBuffWithIdSchema } from '/imports/api/properties/Buffs.js';
export default {
@@ -76,13 +90,10 @@
DamageListForm,
BuffListForm,
},
data(){return {
addResultLoading: false,
}},
props: {
model: {
type: Object,
default: () => (ResultsSchema.clean({})),
default: () => ({}),
},
parentTarget: {
type: String,
@@ -96,6 +107,9 @@
default: undefined,
},
},
data(){return {
addResultLoading: false,
}},
methods: {
acknowledgeAddResult(){
this.addResultLoading = false;

View File

@@ -34,24 +34,6 @@
<v-icon>delete</v-icon>
</v-btn>
</div>
<results-form
:model="rollResult.results"
:parent-target="model.target"
@change="({path, value, ack}) => $emit('change', {
path: ['rollResults', index, 'results', ...path],
value,
ack
})"
@push="({path, value, ack}) => $emit('push', {
path: ['rollResults', index, 'results', ...path],
value,
ack
})"
@pull="({path, ack}) => $emit('pull', {
path: ['rollResults', index, 'results', ...path],
ack
})"
/>
<v-divider class="my-4" />
</div>
</v-slide-x-transition>
@@ -84,14 +66,11 @@
<script>
import FormSection, {FormSections} from '/imports/ui/properties/forms/shared/FormSection.vue';
import RollResultsSchema from '/imports/api/properties/subSchemas/RollResultsSchema.js';
import ResultsForm from '/imports/ui/properties/forms/ResultsForm.vue';
export default {
components: {
FormSection,
FormSections,
ResultsForm,
},
props: {
stored: {
@@ -117,14 +96,6 @@
acknowledgeAddResult(){
this.addResultLoading = false;
},
addResult(){
this.addResultLoading = true;
this.$emit('push', {
path: ['rollResults'],
value: RollResultsSchema.clean({}),
ack: this.acknowledgeAddResult,
});
},
},
};
</script>

View File

@@ -0,0 +1,40 @@
<template lang="html">
<div class="feature-form">
<text-field
label="Name"
:value="model.name"
:error-messages="errors.name"
:debounce-time="debounceTime"
@change="(value, ack) => $emit('change', {path: ['name'], value, ack})"
/>
<text-field
label="Condition"
:value="model.condition"
:error-messages="errors.condition"
:debounce-time="debounceTime"
@change="(value, ack) => $emit('change', {path: ['condition'], value, ack})"
/>
</div>
</template>
<script>
export default {
props: {
model: {
type: Object,
default: () => ({}),
},
errors: {
type: Object,
default: () => ({}),
},
debounceTime: {
type: Number,
default: undefined,
},
},
};
</script>
<style lang="css" scoped>
</style>

View File

@@ -17,6 +17,7 @@ import SavingThrowForm from '/imports/ui/properties/forms/SavingThrowForm.vue';
import SkillForm from '/imports/ui/properties/forms/SkillForm.vue';
import SpellListForm from '/imports/ui/properties/forms/SpellListForm.vue';
import SpellForm from '/imports/ui/properties/forms/SpellForm.vue';
import ToggleForm from '/imports/ui/properties/forms/ToggleForm.vue';
export default {
action: ActionForm,
@@ -38,4 +39,5 @@ export default {
skill: SkillForm,
spellList: SpellListForm,
spell: SpellForm,
toggle: ToggleForm,
};