Fixed experience not appearing as a variable after computation

This commit is contained in:
Stefan Zermatten
2022-02-23 11:44:59 +02:00
parent 78c67a4fd6
commit 52453b46e9
6 changed files with 236 additions and 96 deletions

View File

@@ -61,7 +61,7 @@ const insertExperienceForCreature = function({experience, creatureId, userId}){
}
experience.creatureId = creatureId;
let id = Experiences.insert(experience);
recomputeCreatureById(creatureId);
computeCreature(creatureId);
return id;
};
@@ -135,7 +135,7 @@ const removeExperience = new ValidatedMethod({
}
experience.creatureId = creatureId;
let numRemoved = Experiences.remove(experienceId);
recomputeCreatureById(creatureId);
computeCreature(creatureId);
return numRemoved;
},
});

View File

@@ -2,6 +2,7 @@ import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
import CreatureProperties,
{ DenormalisedOnlyCreaturePropertySchema as denormSchema }
from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import Creatures from '/imports/api/creature/creatures/Creatures.js';
import computedOnlySchemas from '/imports/api/properties/computedOnlyPropertySchemasIndex.js';
import computedSchemas from '/imports/api/properties/computedPropertySchemasIndex.js';
import linkInventory from './buildComputation/linkInventory.js';
@@ -30,8 +31,9 @@ import removeSchemaFields from './buildComputation/removeSchemaFields.js';
*/
export default function buildCreatureComputation(creatureId){
const creature = getCreature(creatureId);
const properties = getProperties(creatureId);
const computation = buildComputationFromProps(properties);
const computation = buildComputationFromProps(properties, creature);
return computation;
}
@@ -44,7 +46,13 @@ function getProperties(creatureId){
}).fetch();
}
export function buildComputationFromProps(properties){
function getCreature(creatureId){
return Creatures.findOne(creatureId, {
denormalizedStats: 1,
});
}
export function buildComputationFromProps(properties, creature){
const computation = new CreatureComputation(properties);
// Dependency graph where edge(a, b) means a depends on b
@@ -55,6 +63,22 @@ export function buildComputationFromProps(properties){
// Each link's data is a string representing the link type
const dependencyGraph = computation.dependencyGraph;
// Link the denormalizedStats from the creature
if (creature && creature.denormalizedStats){
if (creature.denormalizedStats.xp){
dependencyGraph.addNode('xp', {
baseValue: creature.denormalizedStats.xp,
type: '_variable'
});
}
if (creature.denormalizedStats.milestoneLevels){
dependencyGraph.addNode('milestoneLevels', {
baseValue: creature.denormalizedStats.milestoneLevels,
type: '_variable'
});
}
}
// Process the properties one by one
properties.forEach(prop => {

View File

@@ -19,6 +19,10 @@ import { restore } from '/imports/api/parenting/softRemove.js';
let LibraryNodes = new Mongo.Collection('libraryNodes');
let LibraryNodeSchema = new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
type: {
type: String,
allowedValues: Object.keys(propertySchemasIndex),

View File

@@ -201,7 +201,7 @@ const transformsByPropType = {
function getComputedPropertyTransforms(key, toKey){
if (!toKey) toKey = key;
return [
{from: key, to: `${key}.calculation`, up: calculationUp, down: calculationDown},
{from: key, to: `${toKey}.calculation`, up: calculationUp, down: calculationDown},
{from: `${key}Result`, to: `${toKey}.value`, up: nanToNull},
{from: `${key}Errors`, to: `${toKey}.errors`, up: trimErrors},
];
@@ -209,7 +209,7 @@ function getComputedPropertyTransforms(key, toKey){
function getInlineComputationTransforms(key){
return [
{from: key, to: `${key}.text`},
{from: key, to: `${key}.text`, up: calculationUp, down: calculationDown},
{from: `${key}Calculations`, to: `${key}.inlineCalculations`, up: calculationUp, down: calculationDown},
{from: `${key}Calculations.$.result`, to: `${key}.inlineCalculations.$.value`},
];

View File

@@ -1,118 +1,217 @@
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { migrateProperty } from './dbv1.js';
import { assert } from 'chai';
import {
migrateProperty
} from './dbv1.js';
import {
assert
} from 'chai';
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
const exampleAction = {
'_id':'hY5MKZ4ivaoTRpNWy',
'actionType':'bonus',
'target':'singleTarget',
'tags':[],
'resources':{
'itemsConsumed':[],
'attributesConsumed':[{
'_id':'FaK6jXEj3pSe7mNuu',
'quantity': '1',
'variableName':'HunterTech',
'statName':'Hunter\'s Technique',
'available':5
}],
},
'type':'action',
'name':'Hexblade\\\'s Curse',
'parent':{
'id':'JqtDmqa5Zd3xpts5G',
'collection':'creatureProperties'
},
'ancestors':[
{
'collection':'creatures',
'id':'X9rzFhsgFhodYfHmG'
},
],
'order':315,
'summary':'Curse a creature for 1 minute. The curse ends early if {warlock.level >14 ? "" : "the target dies, or"} you are incapacitated. \nGain the following benefits: \n- *Bonus to damage rolls against the cursed target of* **+{proficiencyBonus}**. \n- Any attack roll you make against the cursed target is a **critical hit on a roll of 19 or 20**. \n- If the cursed target dies, you **regain {warlock.level+charisma.modifier} hit points**. \n{warlock.level <9 ? "" : "- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."}',
'uses':'1',
'usesResult':1,
'reset':'shortRest',
'usesUsed':0,
'description':'Starting at 1st level, you gain the ability to place a baleful curse on someone. As a bonus action, choose one creature you can see within 30 feet of you. The target is cursed for 1 minute. The curse ends early if the target dies, you die, or you are incapacitated. Until the curse ends, you gain the following benefits:\n\n- You gain a bonus to damage rolls against the cursed target. The bonus equals your proficiency bonus.\n- Any attack roll you make against the cursed target is a critical hit on a roll of 19 or 20 on the d20.\n- If the cursed target dies, you regain hit points equal to your warlock level + your Charisma modifier (minimum of 1 hit point). \n{warlock.level <10 ? "" :"- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."} \nYou can\\\'t use this feature again until you finish a short or long rest.',
'color':'#8e24aa',
'descriptionCalculations':[
{
'calculation':'warlock.level <10 ? "" :"- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."',
'result':'- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses.'
}
],
'summaryCalculations':[
{
'calculation':'warlock.level >14 ? "" : "the target dies, or"',
'result':'the target dies, or'
},
{
'calculation':'proficiencyBonus',
'result':'4'
},
{
'calculation':'warlock.level+charisma.modifier',
'result':'15'
},
{
'calculation':'warlock.level <9 ? "" : "- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."',
'result':'- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses.'
}
]
'_id': 'hY5MKZ4ivaoTRpNWy',
'actionType': 'bonus',
'target': 'singleTarget',
'tags': [],
'resources': {
'itemsConsumed': [],
'attributesConsumed': [{
'_id': 'FaK6jXEj3pSe7mNuu',
'quantity': '1',
'variableName': 'HunterTech',
'statName': 'Hunter\'s Technique',
'available': 5
}],
},
'type': 'action',
'name': 'Hexblade\\\'s Curse',
'parent': {
'id': 'JqtDmqa5Zd3xpts5G',
'collection': 'creatureProperties'
},
'ancestors': [{
'collection': 'creatures',
'id': 'X9rzFhsgFhodYfHmG'
}, ],
'order': 315,
'summary': 'Curse a creature for 1 minute. The curse ends early if {warlock.level >14 ? "" : "the target dies, or"} you are incapacitated. \nGain the following benefits: \n- *Bonus to damage rolls against the cursed target of* **+{proficiencyBonus}**. \n- Any attack roll you make against the cursed target is a **critical hit on a roll of 19 or 20**. \n- If the cursed target dies, you **regain {warlock.level+charisma.modifier} hit points**. \n{warlock.level <9 ? "" : "- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."}',
'uses': '1',
'usesResult': 1,
'reset': 'shortRest',
'usesUsed': 0,
'description': 'Starting at 1st level, you gain the ability to place a baleful curse on someone. As a bonus action, choose one creature you can see within 30 feet of you. The target is cursed for 1 minute. The curse ends early if the target dies, you die, or you are incapacitated. Until the curse ends, you gain the following benefits:\n\n- You gain a bonus to damage rolls against the cursed target. The bonus equals your proficiency bonus.\n- Any attack roll you make against the cursed target is a critical hit on a roll of 19 or 20 on the d20.\n- If the cursed target dies, you regain hit points equal to your warlock level + your Charisma modifier (minimum of 1 hit point). \n{warlock.level <10 ? "" :"- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."} \nYou can\\\'t use this feature again until you finish a short or long rest.',
'color': '#8e24aa',
'descriptionCalculations': [{
'calculation': 'warlock.level <10 ? "" :"- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."',
'result': '- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses.'
}],
'summaryCalculations': [{
'calculation': 'warlock.level >14 ? "" : "the target dies, or"',
'result': 'the target dies, or'
},
{
'calculation': 'proficiencyBonus',
'result': '4'
},
{
'calculation': 'warlock.level+charisma.modifier',
'result': '15'
},
{
'calculation': 'warlock.level <9 ? "" : "- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."',
'result': '- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses.'
}
]
};
const exampleAttribute = {
_id:'idRWyoj5oxCv73feM',
name:'Hit Dice',
variableName:'clericHitDice',
attributeType:'hitDice',
type:'attribute',
hitDiceSize:'d8',
baseValueCalculation:'cleric.level',
parent:{'id':'8jSWKxvgQyKbunFtD','collection':'creatureProperties'},
ancestors:[
{'collection':'creatures','id':'m9sdCvs6iDf7qRaGv'},
{'id':'8jSWKxvgQyKbunFtD','collection':'creatureProperties'}
_id: 'idRWyoj5oxCv73feM',
name: 'Hit Dice',
variableName: 'clericHitDice',
attributeType: 'hitDice',
type: 'attribute',
hitDiceSize: 'd8',
baseValueCalculation: 'cleric.level',
parent: {
'id': '8jSWKxvgQyKbunFtD',
'collection': 'creatureProperties'
},
ancestors: [{
'collection': 'creatures',
'id': 'm9sdCvs6iDf7qRaGv'
},
{
'id': '8jSWKxvgQyKbunFtD',
'collection': 'creatureProperties'
}
],
order: 84,
value: 20,
tags:[],
tags: [],
baseValue: 20,
damage: 3,
currentValue: 17,
constitutionMod: 2,
dependencies: ['8jSWKxvgQyKbunFtD','qPP5yQXPxS7uhuXo3']
dependencies: ['8jSWKxvgQyKbunFtD', 'qPP5yQXPxS7uhuXo3']
};
const expectedMigratedAttribute = {
_id:'idRWyoj5oxCv73feM',
name:'Hit Dice',
variableName:'clericHitDice',
attributeType:'hitDice',
type:'attribute',
hitDiceSize:'d8',
_id: 'idRWyoj5oxCv73feM',
name: 'Hit Dice',
variableName: 'clericHitDice',
attributeType: 'hitDice',
type: 'attribute',
hitDiceSize: 'd8',
baseValue: {
calculation: 'cleric.level',
value: 20
},
parent:{'id':'8jSWKxvgQyKbunFtD','collection':'creatureProperties'},
ancestors:[
{'collection':'creatures','id':'m9sdCvs6iDf7qRaGv'},
{'id':'8jSWKxvgQyKbunFtD','collection':'creatureProperties'}
parent: {
'id': '8jSWKxvgQyKbunFtD',
'collection': 'creatureProperties'
},
ancestors: [{
'collection': 'creatures',
'id': 'm9sdCvs6iDf7qRaGv'
},
{
'id': '8jSWKxvgQyKbunFtD',
'collection': 'creatureProperties'
}
],
order: 84,
total: 20,
tags:[],
tags: [],
damage: 3,
value: 17,
constitutionMod: 2,
}
describe('migrateProperty', function () {
it('Migrates actions reversibly', function () {
const action = {...exampleAction};
const exampleAttack = {
'_id': 'vw23EnJwBRcXEJg7i',
'actionType': 'attack',
'target': 'singleTarget',
'tags': ['attack'],
'results': {
'adjustments': [],
'damages': [{
'_id': 'RGJMeNJXBeqZsGmAw',
'damage': '1d4 + strength.modifier',
'target': 'every',
'damageType': 'slashing'
}],
'buffs': []
},
'resources': {
'itemsConsumed': [],
'attributesConsumed': []
},
'rollBonus': 'dexterity.modifier + proficiencyBonus + 2 - hp.value + hp.currentValue',
'type': 'attack',
'name': 'Claws',
'parent': {
'id': 'Jpx8q3WjM5SCoGBm8',
'collection': 'creatureProperties'
},
'ancestors': [{
'collection': 'creatures',
'id': 'm9sdCvs6iDf7qRaGv'
}, {
'id': '3WS2xsSPAqB4eF9YH',
'collection': 'creatureProperties'
}, {
'id': 'rhYLEycvtHjcioaQL',
'collection': 'creatureProperties'
}, {
'id': 'Jpx8q3WjM5SCoGBm8',
'collection': 'creatureProperties'
}],
'order': 56,
'rollBonusResult': 6,
'usesUsed': 2,
'dependencies': ['pg6cK5ghHTFvo8uyK', 'gAJBKYqXz2BPc9Aqf']
}
const expectedMigratedAttack = {
'_id': 'vw23EnJwBRcXEJg7i',
'actionType': 'attack',
'target': 'singleTarget',
'tags': ['attack'],
'resources': {
'itemsConsumed': [],
'attributesConsumed': []
},
'attackRoll': {
calculation: 'dexterity.modifier + proficiencyBonus + 2 - hp.total + hp.value',
},
'type': 'action',
'name': 'Claws',
'parent': {
'id': 'Jpx8q3WjM5SCoGBm8',
'collection': 'creatureProperties'
},
'ancestors': [{
'collection': 'creatures',
'id': 'm9sdCvs6iDf7qRaGv'
}, {
'id': '3WS2xsSPAqB4eF9YH',
'collection': 'creatureProperties'
}, {
'id': 'rhYLEycvtHjcioaQL',
'collection': 'creatureProperties'
}, {
'id': 'Jpx8q3WjM5SCoGBm8',
'collection': 'creatureProperties'
}],
'order': 56,
'usesUsed': 2,
libraryTags: [],
}
describe('migrateProperty', function() {
it('Migrates actions reversibly', function() {
const action = {
...exampleAction
};
const newAction = migrateProperty({
collection: CreatureProperties,
prop: action
@@ -125,8 +224,10 @@ describe('migrateProperty', function () {
assert.deepEqual(action, exampleAction, 'action should not be bashed');
assert.deepEqual(exampleAction, reversedAction, 'operation should be reversible');
});
it ('Migrates attributes as expected', function(){
const attribute = {...exampleAttribute};
it('Migrates attributes as expected', function() {
const attribute = {
...exampleAttribute
};
const newAttribute = migrateProperty({
collection: CreatureProperties,
prop: attribute
@@ -134,4 +235,15 @@ describe('migrateProperty', function () {
assert.deepEqual(newAttribute, expectedMigratedAttribute,
'Attribute should match the expected result');
});
it('Migrates attacks as expected', function() {
const attribute = {
...exampleAttack
};
const newAttribute = migrateProperty({
collection: LibraryNodes,
prop: attribute
});
assert.deepEqual(newAttribute, expectedMigratedAttack,
'Attribute should match the expected result');
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "dicecloud",
"version": "2.0-beta.33",
"version": "2.0.33",
"description": "Unofficial Online Realtime D&D 5e App",
"license": "GPL-3.0",
"repository": {