Creature computations working again

This commit is contained in:
Stefan Zermatten
2020-03-23 11:59:04 +02:00
parent 74fef2bd39
commit 2981813751
20 changed files with 107 additions and 154 deletions

View File

@@ -1,6 +1,6 @@
import SimpleSchema from 'simpl-schema';
import ChildSchema, { RefSchema } from '/imports/api/parenting/ChildSchema.js';
import { recomputeCreature } from '/imports/api/creature/computation/creatureComputation.js';
import { recomputeCreature } from '/imports/api/creature/computation/recomputeCreature.js';
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { softRemove } from '/imports/api/parenting/softRemove.js';

View File

@@ -34,6 +34,7 @@ export default class ComputationMemo {
if (this.statsByVariableName[variableName]){
prop.value = NaN;
prop.computationDetails.error = 'variableNameCollision';
console.warn('variableNameCollision', prop);
return;
}
this.statsByVariableName[variableName] = prop;

View File

@@ -25,7 +25,6 @@ function combineAttribute(stat, aggregator){
function combineSkill(stat, aggregator, memo){
// Skills are based on some ability Modifier
let abilityMod = 0;
let ability = memo.statsByVariableName[stat.ability]
if (stat.ability && ability){
if (!ability.computationDetails.computed){
@@ -48,9 +47,9 @@ function combineSkill(stat, aggregator, memo){
// Multiply the proficiency bonus by the actual proficiency
profBonus *= stat.proficiency;
// Combine everything to get the final result
let result = (abilityMod + profBonus + stat.add) * stat.mul;
if (result < stat.min) result = stat.min;
if (result > stat.max) result = stat.max;
let result = (stat.abilityMod + profBonus + aggregator.add) * aggregator.mul;
if (result < aggregator.min) result = aggregator.min;
if (result > aggregator.max) result = aggregator.max;
result = Math.floor(result);
if (stat.base > result) result = stat.base;
stat.value = result;

View File

@@ -1,10 +1,10 @@
import evaluateCalculation from '/imports/api/creature/computation/evaluateCalculation.js';
export default function computeEffect(effect, memo){
if (effect.computed) return;
if (effect.computationDetails.computed) return;
if (_.isFinite(effect.calculation)){
effect.result = +effect.calculation;
} else if(effect.operation === "conditional" || effect.operation === "rollBonuses"){
} else if(effect.operation === "conditional" || effect.operation === "rollBonus"){
effect.result = effect.calculation;
} else if(_.contains(["advantage", "disadvantage", "fail"], effect.operation)){
effect.result = 1;

View File

@@ -13,6 +13,7 @@ export default function computeStat(stat, memo){
stat.value = NaN;
stat.computationDetails.busyComputing = false;
stat.computationDetails.error = 'dependencyLoop';
console.warn('dependencyLoop', stat);
return;
}
// Compute and aggregate all the effects

View File

@@ -4,7 +4,7 @@ export default function computedValueOfVariableName(sub, memo){
const stat = memo.statsByVariableName[sub];
if (!stat) return null;
if (!stat.computationDetails.computed){
computeStat(stat, char);
computeStat(stat, memo);
}
return stat.result;
return stat.value;
}

View File

@@ -1,4 +1,5 @@
import computedValueOfVariableName from '/imports/api/creature/computation/computedValueOfVariableName.js'
import * as math from 'mathjs';
export default function evaluateCalculation(string, memo){
if (!string) return string;
@@ -7,6 +8,7 @@ export default function evaluateCalculation(string, memo){
try {
calc = math.parse(string);
} catch (e) {
console.error(e);
return string;
}
// Replace all symbols with known values
@@ -20,7 +22,6 @@ export default function evaluateCalculation(string, memo){
return node;
}
});
// Evaluate the expression to a number or return with substitutions
try {
return substitutedCalc.eval();

View File

@@ -1,16 +0,0 @@
import { isEqual, forOwn } from 'lodash';
import { ComputedOnlySkilLSchema } from '/imports/api/properties/Skills.js';
export default function logAlterations(memo){
forOwn(memo.originalPropsById, old => {
let changed = memo.propsById[old._id];
delete changed.computationDetails;
if (!isEqual(old, changed)){
console.log({change: {old, changed}})
}
});
}
// TODO use this as a starting point to write only computed fields that have
// changed

View File

@@ -1,14 +1,10 @@
// TODO allow abilities to get advantage/disadvantage, making all skills that are based
// on them disadvantaged as well
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import SimpleSchema from 'simpl-schema';
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
import ComputationMemo from '/imports/api/creature/computation/ComputationMemo.js';
import computeMemo from '/imports/api/creature/computation/computeMemo.js';
import getCalculationProperties from '/imports/api/creature/computation/getCalculationProperties.js';
import logAlterations from '/imports/api/creature/computation/logAlterations.js';
import * as math from 'mathjs';
import writeAlteredProperties from '/imports/api/creature/computation/writeAlteredProperties.js';
export const recomputeCreature = new ValidatedMethod({
@@ -67,10 +63,7 @@ export const recomputeCreature = new ValidatedMethod({
export function recomputeCreatureById(creatureId){
let props = getCalculationProperties(creatureId);
let computationMemo = new ComputationMemo(props);
console.log({toCompute: computationMemo});
computeMemo(computationMemo);
console.log({computed: computationMemo});
logAlterations(computationMemo);
//writeAlteredProps(computationMemo);
writeAlteredProperties(computationMemo);
return computationMemo;
}

View File

@@ -0,0 +1,71 @@
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 CreatureProperties from '/imports/api/creature/CreatureProperties.js';
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;
}
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);
op.updateOne.update.$set[key] = changed[key];
}
}
if (op){
bulkWriteOperations.push(op);
}
});
bulkWriteProperties(bulkWriteOperations);
}
function newOperation(_id, type){
let newOp = {
updateOne: {
filter: {_id},
update: {$set: {}},
}
};
if (Meteor.isClient){
newOp.type = type;
}
return newOp;
};
function bulkWriteProperties(bulkWriteOps){
if (!bulkWriteOps.length) return;
if (Meteor.isServer){
CreatureProperties.rawCollection().bulkWrite(
bulkWriteOps,
{ordered : false},
function(e){
if (e) console.error(e);
}
);
} else {
_.each(bulkWriteOps, op => {
CreatureProperties.update(op.updateOne.filter, op.updateOne.update, {
selector: {type: op.type}
});
});
}
}

View File

@@ -1,98 +0,0 @@
function writeCreature(char) {
//TODO these functions don't filter the stats before trying to write
writeAttributes(char);
writeSkills(char);
writeDamageMultipliers(char);
writeEffects(char);
}
/*
* Write all the attributes from the in-memory char object to the Attirbute docs
*/
function writeAttributes(char) {
let bulkWriteOps = _.map(char.atts, (att, variableName) => {
let op = {
updateMany: {
filter: {'ancestors.id': char.id, variableName},
update: {'$set': {
value: att.result,
rollBonuses: skill.rollBonus,
}},
}
};
if (typeof att.mod === 'number'){
op.updateMany.update.$set.mod = att.mod;
} else {
op.updateMany.update.$unset = {mod: 1};
}
return op;
});
bulkWriteProperties({bulkWriteOps, selectorType: 'attribute'});
}
function writeSkills(char) {
let bulkWriteOps = _.map(char.skills, (skill, variableName) => {
let op = {
updateMany: {
filter: {'ancestors.id': char.id, variableName},
update: {$set: {
value: skill.result,
abilityMod: skill.abilityMod,
advantage: skill.advantage,
passiveBonus: skill.passiveAdd,
proficiency: skill.proficiency,
conditionalBenefits: skill.conditional,
rollBonuses: skill.rollBonus,
fail: skill.fail,
}},
}
};
return op;
});
bulkWriteProperties({bulkWriteOps, selectorType: 'skill'});
}
function writeDamageMultipliers(char) {
let bulkWriteOps = _.map(char.dms, (dm, variableName) => {
let op = {
updateMany: {
filter: {'ancestors.id': char.id, variableName},
update: {$set: {
value: dm.result,
}},
}
};
return op;
});
bulkWriteProperties({bulkWriteOps, selectorType: 'damageMultiplier'});
}
function writeEffects(char){
let bulkWriteOps = _.map(char.computedEffects, effect => ({
updateOne: {
filter: {_id: effect._id},
update: {$set: {
result: effect.result,
}},
},
}));
if (!bulkWriteOps.length) return;
bulkWriteProperties({bulkWriteOps, selectorType: 'effect'});
}
function bulkWriteProperties({bulkWriteOps, selectorType}){
if (!bulkWriteOps.length) return;
if (Meteor.isServer){
CreatureProperties.rawCollection().bulkWrite(bulkWriteOps, {ordered : false}, function(e){
if (e) console.error(e);
});
} else {
_.each(bulkWriteOps, op => {
CreatureProperties.update(op.updateMany.filter, op.updateMany.update, {
multi: true,
selector: {type: selectorType}
});
});
}
}

View File

@@ -1,4 +1,4 @@
import {computeCreature} from "./creatureComputation.js";
import {computeCreature} from "./recomputeCreature.js";
import assert from "assert";
const makeEffect = function(operation, value){

View File

@@ -1,4 +1,4 @@
import { recomputeCreatureById } from '/imports/api/creature/computation/creatureComputation.js';
import { recomputeCreatureById } from '/imports/api/creature/computation/recomputeCreature.js';
export default function recomputeCreatureMixin(methodOptions){
let runFunc = methodOptions.run;

View File

@@ -196,13 +196,6 @@ export function updateParent({docRef, parentRef}){
});
}
// TODO move these functions to character properties collection
export function findEnabled(collection, query, options){
query.enabled = true;
query['ancestors.$.enabled'] = {$not: false};
return collection.find(query, options);
}
export function getName(doc){
if (doc.name) return name;
var i = doc.ancestors.length;

View File

@@ -67,7 +67,7 @@ let AttributeSchema = new SimpleSchema({
},
});
let ComputedAttributeSchema = new SimpleSchema({
let ComputedOnlyAttributeSchema = new SimpleSchema({
// The computed value of the attribute
value: {
type: Number,
@@ -78,6 +78,10 @@ let ComputedAttributeSchema = new SimpleSchema({
type: SimpleSchema.Integer,
optional: true,
},
}).extend(AttributeSchema);
});
export { AttributeSchema, ComputedAttributeSchema };
const ComputedAttributeSchema = new SimpleSchema()
.extend(ComputedOnlyAttributeSchema)
.extend(AttributeSchema);
export { AttributeSchema, ComputedOnlyAttributeSchema, ComputedAttributeSchema };

View File

@@ -1,6 +1,5 @@
import SimpleSchema from 'simpl-schema';
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
// TODO consider damage types as an array that applies to multiple types at once
/*
* DamageMultipliers are multipliers that affect how much damage is taken from

View File

@@ -51,12 +51,16 @@ const StoredEffectSchema = new SimpleSchema({
},
}).extend(EffectSchema);
const ComputedEffectSchema = new SimpleSchema({
const ComputedOnlyEffectSchema = new SimpleSchema({
// The computed result of the effect
result: {
type: SimpleSchema.oneOf(Number, String),
optional: true,
},
}).extend(EffectSchema);
})
export { EffectSchema, StoredEffectSchema, ComputedEffectSchema };
const ComputedEffectSchema = new SimpleSchema()
.extend(ComputedOnlyEffectSchema)
.extend(EffectSchema);
export { EffectSchema, StoredEffectSchema, ComputedEffectSchema, ComputedOnlyEffectSchema };

View File

@@ -79,7 +79,7 @@ let ComputedOnlySkillSchema = new SimpleSchema({
allowedValues: [0, 0.5, 1, 2],
defaultValue: 0,
},
// Computed number of total conditional benefits
// Compiled text of all conditional benefits
conditionalBenefits: {
type: Array,
optional: true,
@@ -87,7 +87,7 @@ let ComputedOnlySkillSchema = new SimpleSchema({
'conditionalBenefits.$': {
type: String,
},
// Computed number of things forcing this skill to fail
// Compiled text of all roll bonuses
rollBonuses: {
type: Array,
optional: true,
@@ -102,6 +102,8 @@ let ComputedOnlySkillSchema = new SimpleSchema({
},
})
let ComputedSkillSchema = ComputedOnlySkillSchema.extend(SkillSchema);
const ComputedSkillSchema = new SimpleSchema()
.extend(ComputedOnlySkillSchema)
.extend(SkillSchema);
export { SkillSchema, ComputedSkillSchema, ComputedOnlySkillSchema };

View File

@@ -102,7 +102,7 @@
import SpellsTab from '/imports/ui/creature/character/characterSheetTabs/SpellsTab.vue';
import PersonaTab from '/imports/ui/creature/character/characterSheetTabs/PersonaTab.vue';
import TreeTab from '/imports/ui/creature/character/characterSheetTabs/TreeTab.vue';
import { recomputeCreature } from '/imports/api/creature/computation/creatureComputation.js';
import { recomputeCreature } from '/imports/api/creature/computation/recomputeCreature.js';
export default {
props: {

View File

@@ -1,5 +1,4 @@
import "/imports/server/publications/index.js";
import "/imports/api/creature/computation/creatureComputation.js";
import "/imports/api/parenting/deleteRemovedDocuments.js";
import "/imports/server/config/simpleSchemaDebug.js";
import "/imports/api/parenting/organizeMethods.js";