Compare commits
61 Commits
2.0-beta.1
...
2.0-beta.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11a2851ac4 | ||
|
|
313382fb82 | ||
|
|
b9ae337a64 | ||
|
|
4dc0a6159b | ||
|
|
e00dfe1532 | ||
|
|
28e1fcabd5 | ||
|
|
2c0496b44b | ||
|
|
89adda60ec | ||
|
|
8c3710cda3 | ||
|
|
b501b9d830 | ||
|
|
574f8373e7 | ||
|
|
a7ecdecec1 | ||
|
|
0aa59a4bfc | ||
|
|
8f0ff3245e | ||
|
|
9a2d10b7ed | ||
|
|
a8aa1923a8 | ||
|
|
57fa162c89 | ||
|
|
4d548c901c | ||
|
|
a97be2f93a | ||
|
|
1276f872a0 | ||
|
|
7daab97297 | ||
|
|
2e3704d096 | ||
|
|
7283a27727 | ||
|
|
3517636b8b | ||
|
|
e617ef9b75 | ||
|
|
cd45ae1442 | ||
|
|
bcedd548c7 | ||
|
|
dc53e38efe | ||
|
|
e381b3b24d | ||
|
|
111d971bc2 | ||
|
|
bf4ce4f9f7 | ||
|
|
2a983b0a94 | ||
|
|
a5460bba0b | ||
|
|
df361236f5 | ||
|
|
e1d670fe9f | ||
|
|
1e9f0515e5 | ||
|
|
0404020335 | ||
|
|
c248d8f4a0 | ||
|
|
8d95da8b7a | ||
|
|
e11ab39864 | ||
|
|
331fcef9ad | ||
|
|
7e3bff9677 | ||
|
|
1b650b26b6 | ||
|
|
5925605962 | ||
|
|
dee1265b69 | ||
|
|
3d3ec3bcf2 | ||
|
|
dce2c92516 | ||
|
|
0fe2780983 | ||
|
|
e126cdd3cb | ||
|
|
d69ada0db4 | ||
|
|
858915b25b | ||
|
|
d10a7eca14 | ||
|
|
671d17018c | ||
|
|
f2883d320f | ||
|
|
aad0c7249e | ||
|
|
612fcca68c | ||
|
|
12939c46de | ||
|
|
3801b17fde | ||
|
|
88133a2fa3 | ||
|
|
d00eedac19 | ||
|
|
6571fb860a |
@@ -92,8 +92,32 @@ let CreatureSchema = new SimpleSchema({
|
||||
type: SimpleSchema.Integer,
|
||||
defaultValue: 0,
|
||||
},
|
||||
// Sum of all weights of items and containers that are carried
|
||||
'denormalizedStats.weightCarried': {
|
||||
// Inventory
|
||||
'denormalizedStats.weightTotal': {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
},
|
||||
'denormalizedStats.weightEquipment': {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
},
|
||||
'denormalizedStats.weightCarried': {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
},
|
||||
'denormalizedStats.valueTotal': {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
},
|
||||
'denormalizedStats.valueEquipment': {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
},
|
||||
'denormalizedStats.valueCarried': {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
},
|
||||
'denormalizedStats.itemsAttuned': {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
},
|
||||
|
||||
@@ -13,42 +13,43 @@ export default function applyAdjustment({
|
||||
...creature.variables,
|
||||
...actionContext,
|
||||
};
|
||||
try {
|
||||
var {result, errors} = evaluateString(prop.amount, scope, 'reduce');
|
||||
if (typeof result !== 'number') {
|
||||
log.content.push({
|
||||
name: 'Attribute damage',
|
||||
error: errors.join(', ') || 'Something went wrong',
|
||||
});
|
||||
}
|
||||
} catch (e){
|
||||
var {result, context} = evaluateString({
|
||||
string: prop.amount,
|
||||
scope,
|
||||
fn: 'reduce'
|
||||
});
|
||||
context.errors.forEach(e => {
|
||||
log.content.push({
|
||||
name: 'Attribute damage',
|
||||
error: e.toString(),
|
||||
name: 'Attribute damage error',
|
||||
error: e.message || e.toString(),
|
||||
});
|
||||
}
|
||||
});
|
||||
if (damageTargets) {
|
||||
damageTargets.forEach(target => {
|
||||
if (prop.target === 'each'){
|
||||
result = evaluateString(prop.amount, scope, 'reduce');
|
||||
({result} = evaluateString({
|
||||
string: prop.amount,
|
||||
scope,
|
||||
fn: 'reduce'
|
||||
}));
|
||||
}
|
||||
damagePropertiesByName.call({
|
||||
creatureId: target._id,
|
||||
variableName: prop.stat,
|
||||
operation: prop.operation || 'increment',
|
||||
value: result
|
||||
value: result.value,
|
||||
});
|
||||
log.content.push({
|
||||
name: 'Attribute damage',
|
||||
resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`,
|
||||
result: `${-result}`,
|
||||
result: `${result.isNumber ? -result.value : result.toString()}`,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
log.content.push({
|
||||
name: 'Attribute damage',
|
||||
resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`,
|
||||
result: `${-result}`,
|
||||
result: `${result.isNumber ? -result.value : result.toString()}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,19 @@ export default function applyAttack({
|
||||
prop,
|
||||
log,
|
||||
actionContext,
|
||||
creature,
|
||||
}){
|
||||
let value = roll(1, 20)[0];
|
||||
actionContext.attackRoll = {value};
|
||||
let criticalHitTarget = creature.variables.criticalHitTarget &&
|
||||
creature.variables.criticalHitTarget.currentValue || 20;
|
||||
let criticalHit = value >= criticalHitTarget;
|
||||
if (criticalHit) actionContext.criticalHit = {value: true};
|
||||
let result = value + prop.rollBonusResult;
|
||||
actionContext.toHit = {value: result};
|
||||
|
||||
log.content.push({
|
||||
name: 'To Hit',
|
||||
name: criticalHit ? 'Critical Hit!' : 'To Hit',
|
||||
resultPrefix: `1d20 [${value}] + ${prop.rollBonusResult} = `,
|
||||
result,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
||||
import dealDamage from '/imports/api/creature/creatureProperties/methods/dealDamage.js';
|
||||
import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import { CompilationContext } from '/imports/parser/parser.js';
|
||||
|
||||
export default function applyDamage({
|
||||
prop,
|
||||
@@ -14,36 +15,79 @@ export default function applyDamage({
|
||||
...creature.variables,
|
||||
...actionContext,
|
||||
};
|
||||
try {
|
||||
var {result, errors} = evaluateString(prop.amount, scope, 'reduce');
|
||||
if (typeof result !== 'number') {
|
||||
log.content.push({
|
||||
error: errors.join(', '),
|
||||
});
|
||||
}
|
||||
} catch (e){
|
||||
log.content.push({
|
||||
error: e.toString(),
|
||||
});
|
||||
// Add the target's variables to the scope
|
||||
if (targets.length === 1){
|
||||
scope.target = targets[0].variables;
|
||||
}
|
||||
// Determine if the hit is critical
|
||||
let criticalHit = !!(
|
||||
actionContext.criticalHit &&
|
||||
actionContext.criticalHit.value &&
|
||||
prop.damageType !== 'healing' // Can't critically heal
|
||||
);
|
||||
// Double the damage rolls if the hit is critical
|
||||
let context = new CompilationContext({
|
||||
doubleRolls: criticalHit,
|
||||
});
|
||||
|
||||
// Compute the roll the first time, logging any errors
|
||||
var {result} = evaluateString({
|
||||
string: prop.amount,
|
||||
scope,
|
||||
fn: 'reduce',
|
||||
context
|
||||
});
|
||||
|
||||
// If the result is an error bail out now
|
||||
if (result.constructor.name === 'ErrorNode'){
|
||||
log.content.push({
|
||||
name: 'Damage error',
|
||||
error: result.toString(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Memoise the damage suffix for the log
|
||||
let suffix = (criticalHit ? ' critical ' : '') +
|
||||
prop.damageType +
|
||||
(prop.damageType !== 'healing' ? ' damage': '');
|
||||
|
||||
if (damageTargets && damageTargets.length) {
|
||||
// Iterate through all the targets
|
||||
damageTargets.forEach(target => {
|
||||
let name = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
||||
let suffix = prop.damageType +
|
||||
prop.damageType !== 'healing' ? ' damage': '';
|
||||
|
||||
// Reroll the damage if needed
|
||||
if (prop.target === 'each'){
|
||||
result = evaluateString(prop.amount, scope, 'reduce');
|
||||
({result, context} = evaluateString({
|
||||
string: prop.amount,
|
||||
scope,
|
||||
fn: 'reduce'
|
||||
}));
|
||||
}
|
||||
// If the result is an error or not a number bail out now
|
||||
if (result.constructor.name === 'ErrorNode' || !result.isNumber){
|
||||
log.content.push({
|
||||
name: 'Damage error',
|
||||
error: result.toString(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Deal the damage to the target
|
||||
let damageDealt = dealDamage.call({
|
||||
creatureId: target._id,
|
||||
damageType: prop.damageType,
|
||||
amount: result,
|
||||
amount: result.value,
|
||||
});
|
||||
|
||||
// Log the damage done
|
||||
if (target._id === creature._id){
|
||||
// Target is same as self, log damage as such
|
||||
log.content.push({
|
||||
name,
|
||||
result: damageDealt,
|
||||
details: suffix + 'to self',
|
||||
details: suffix + ' to self',
|
||||
});
|
||||
} else {
|
||||
log.content.push({
|
||||
@@ -52,6 +96,7 @@ export default function applyDamage({
|
||||
result: damageDealt,
|
||||
details: suffix + `${target.name && ' to '}${target.name}`,
|
||||
});
|
||||
// Log the damage received on that creature's log as well
|
||||
insertCreatureLog.call({
|
||||
log: {
|
||||
content: [{
|
||||
@@ -66,10 +111,11 @@ export default function applyDamage({
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// There are no targets, just log the result
|
||||
log.content.push({
|
||||
name: prop.damageType === 'healing' ? 'Healing' : 'Damage',
|
||||
result,
|
||||
details: `${prop.damageType}${prop.damageType !== 'healing'? ' damage': ''}`,
|
||||
result: result.toString(),
|
||||
details: suffix,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
import applyAction from '/imports/api/creature/actions/applyAction.js';
|
||||
import applyAdjustment from '/imports/api/creature/actions/applyAdjustment.js';
|
||||
import applyAttack from '/imports/api/creature/actions/applyAttack.js';
|
||||
import applyDamage from '/imports/api/creature/actions/applyDamage.js';
|
||||
import applyBuff from '/imports/api/creature/actions/applyBuff.js';
|
||||
import applyDamage from '/imports/api/creature/actions/applyDamage.js';
|
||||
import applyRoll from '/imports/api/creature/actions/applyRoll.js';
|
||||
import applyToggle from '/imports/api/creature/actions/applyToggle.js';
|
||||
import applySave from '/imports/api/creature/actions/applySave.js';
|
||||
|
||||
function applyProperty(options){
|
||||
let prop = options.prop;
|
||||
if (prop.type === 'buff'){
|
||||
// ignore only applied buffs
|
||||
// ignore only applied buffs, don't apply them again
|
||||
if (prop.applied === true){
|
||||
return false;
|
||||
}
|
||||
// Only ignore toggles if they wont be computed
|
||||
} else if (prop.type === 'toggle') {
|
||||
if (prop.disabled) return false;
|
||||
if (prop.enabled) return true;
|
||||
if (!prop.condition) return false;
|
||||
// Ignore inactive props of other types
|
||||
} else if (prop.inactive === true){
|
||||
} else if (prop.deactivatedBySelf === true){
|
||||
return false;
|
||||
}
|
||||
switch (prop.type){
|
||||
@@ -32,41 +40,42 @@ function applyProperty(options){
|
||||
break;
|
||||
case 'buff':
|
||||
applyBuff(options);
|
||||
break;
|
||||
return false;
|
||||
case 'toggle':
|
||||
return applyToggle(options);
|
||||
case 'roll':
|
||||
// applyRoll(options);
|
||||
applyRoll(options);
|
||||
break;
|
||||
case 'savingThrow':
|
||||
// applySavingThrow(options);
|
||||
break;
|
||||
return applySave(options);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export default function applyProperties({
|
||||
forest,
|
||||
creature,
|
||||
targets,
|
||||
actionContext,
|
||||
log,
|
||||
}){
|
||||
forest.forEach(child => {
|
||||
let walkChildren = applyProperty({
|
||||
prop: child.node,
|
||||
children: child.children,
|
||||
creature,
|
||||
targets,
|
||||
actionContext,
|
||||
log,
|
||||
});
|
||||
if (walkChildren){
|
||||
applyProperties({
|
||||
forest: child.children,
|
||||
creature,
|
||||
targets,
|
||||
actionContext,
|
||||
log,
|
||||
function applyPropertyAndWalkChildren({prop, children, targets, ...options}){
|
||||
let shouldKeepWalking = applyProperty({ prop, children, targets, ...options });
|
||||
if (shouldKeepWalking){
|
||||
applyProperties({ forest: children, targets, ...options,});
|
||||
}
|
||||
}
|
||||
|
||||
export default function applyProperties({ forest, targets, ...options}){
|
||||
forest.forEach(node => {
|
||||
let prop = node.node;
|
||||
let children = node.children;
|
||||
if (shouldSplit(prop) && targets.length){
|
||||
targets.forEach(target => {
|
||||
let targets = [target]
|
||||
applyPropertyAndWalkChildren({ targets, prop, children, ...options});
|
||||
});
|
||||
} else {
|
||||
applyPropertyAndWalkChildren({prop, children, targets, ...options});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function shouldSplit(prop){
|
||||
if (prop.target === 'each'){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
26
app/imports/api/creature/actions/applyRoll.js
Normal file
26
app/imports/api/creature/actions/applyRoll.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
||||
|
||||
export default function applyRoll({
|
||||
prop,
|
||||
creature,
|
||||
actionContext,
|
||||
log,
|
||||
}){
|
||||
let scope = {
|
||||
...creature.variables,
|
||||
...actionContext,
|
||||
};
|
||||
var {result} = evaluateString({
|
||||
string: prop.roll,
|
||||
scope,
|
||||
fn: 'reduce'
|
||||
});
|
||||
if (result.isNumber){
|
||||
actionContext[prop.variableName] = result.value;
|
||||
}
|
||||
log.content.push({
|
||||
name: prop.name,
|
||||
resultPrefix: prop.variableName + ' = ' + prop.roll + ' = ',
|
||||
result: result.toString(),
|
||||
});
|
||||
}
|
||||
77
app/imports/api/creature/actions/applySave.js
Normal file
77
app/imports/api/creature/actions/applySave.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
||||
import CreaturesProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import roll from '/imports/parser/roll.js';
|
||||
|
||||
export default function applySave({
|
||||
prop,
|
||||
creature,
|
||||
actionContext,
|
||||
log,
|
||||
}){
|
||||
let scope = {
|
||||
...creature.variables,
|
||||
...actionContext,
|
||||
};
|
||||
try {
|
||||
// Calculate the DC
|
||||
var {result} = evaluateString({
|
||||
string: prop.dc,
|
||||
scope,
|
||||
fn: 'reduce'
|
||||
});
|
||||
let dc = result.value;
|
||||
log.content.push({
|
||||
name: prop.name,
|
||||
resultPrefix: ' DC ',
|
||||
result: result.toString(),
|
||||
});
|
||||
if (prop.target === 'self'){
|
||||
let save = CreaturesProperties.findOne({
|
||||
'ancestors.id': creature._id,
|
||||
type: 'skill',
|
||||
skillType: 'save',
|
||||
variableName: prop.stat,
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
});
|
||||
if (!save){
|
||||
log.content.push({
|
||||
error: 'No saving throw found: ' + prop.stat,
|
||||
});
|
||||
return;
|
||||
}
|
||||
let value, values, resultPrefix;
|
||||
if (save.advantage === 1){
|
||||
values = roll(2, 20).sort().reverse();
|
||||
value = values[0];
|
||||
resultPrefix = `Advantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
|
||||
} else if (save.advantage === -1){
|
||||
values = roll(2, 20).sort();
|
||||
value = values[0];
|
||||
resultPrefix = `Disadvantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
|
||||
} else {
|
||||
values = roll(1, 20);
|
||||
value = values[0];
|
||||
resultPrefix = `1d20 [${value}] + ${save.value} = `
|
||||
}
|
||||
actionContext.savingThrowRoll = {value};
|
||||
let result = value + save.value;
|
||||
actionContext.savingThrow = {value: result};
|
||||
let saveSuccess = result >= dc;
|
||||
log.content.push({
|
||||
name: 'Save',
|
||||
resultPrefix,
|
||||
result,
|
||||
details: saveSuccess ? 'Passed' : 'Failed'
|
||||
});
|
||||
return !saveSuccess;
|
||||
} else {
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
} catch (e){
|
||||
log.content.push({
|
||||
error: e.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
34
app/imports/api/creature/actions/applyToggle.js
Normal file
34
app/imports/api/creature/actions/applyToggle.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
||||
|
||||
export default function applyToggle({
|
||||
prop,
|
||||
creature,
|
||||
actionContext,
|
||||
log,
|
||||
}){
|
||||
let scope = {
|
||||
...creature.variables,
|
||||
...actionContext,
|
||||
};
|
||||
if (Number.isFinite(+prop.condition)){
|
||||
return !!+prop.condition;
|
||||
}
|
||||
var {result} = evaluateString({
|
||||
string: prop.condition,
|
||||
scope,
|
||||
fn: 'reduce'
|
||||
});
|
||||
if (result.constructor.name === 'ErrorNode') {
|
||||
log.content.push({
|
||||
name: 'Toggle error',
|
||||
error: result.toString(),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
log.content.push({
|
||||
name: prop.name || 'Toggle',
|
||||
resultPrefix: prop.condition + ' = ',
|
||||
result: result.toString(),
|
||||
});
|
||||
return !!result.value;
|
||||
}
|
||||
@@ -65,7 +65,7 @@ const castSpellWithSlot = new ValidatedMethod({
|
||||
action: spell,
|
||||
context: {slotLevel},
|
||||
creature,
|
||||
target,
|
||||
targets: [target],
|
||||
method: this,
|
||||
});
|
||||
// Note this only recomputes the top-level creature, not the nearest one
|
||||
|
||||
@@ -9,6 +9,8 @@ import { assertEditPermission } from '/imports/api/creature/creaturePermissions.
|
||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import { nodesToTree } from '/imports/api/parenting/parenting.js';
|
||||
import applyProperties from '/imports/api/creature/actions/applyProperties.js';
|
||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
|
||||
|
||||
const doAction = new ValidatedMethod({
|
||||
name: 'creatureProperties.doAction',
|
||||
@@ -43,9 +45,16 @@ const doAction = new ValidatedMethod({
|
||||
});
|
||||
doActionWork({action, creature, targets, method: this});
|
||||
|
||||
// The acting creature might have used ammo
|
||||
recomputeInventory(creature._id);
|
||||
|
||||
// The action might add properties which need to be activated
|
||||
recomputeInactiveProperties(creature._id);
|
||||
|
||||
// recompute creatures
|
||||
recomputeCreatureByDoc(creature);
|
||||
targets.forEach(target => {
|
||||
recomputeInactiveProperties(target._id);
|
||||
recomputeCreatureByDoc(target);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function spendResources({prop, log}){
|
||||
});
|
||||
log.content.push({
|
||||
name: 'Uses left',
|
||||
result: prop.usesResult - prop.usesUsed - 1,
|
||||
result: prop.usesResult - (prop.usesUsed || 0) - 1,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ export default function embedInlineCalculations(string, calculations){
|
||||
if (!string) return '';
|
||||
if (!calculations) return string;
|
||||
let index = 0;
|
||||
return string.replace(INLINE_CALCULATION_REGEX, () => {
|
||||
return string.replace(INLINE_CALCULATION_REGEX, substring => {
|
||||
let comp = calculations && calculations[index++];
|
||||
return comp && comp.result ? comp.result : string;
|
||||
return (comp && 'result' in comp) ? comp.result : substring;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,28 +1,67 @@
|
||||
import { parse, CompilationContext } from '/imports/parser/parser.js';
|
||||
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
|
||||
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
|
||||
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
|
||||
|
||||
export default function evaluateString(string, scope, fn = 'compile'){
|
||||
let errors = [];
|
||||
//TODO replace constants with their parsed node
|
||||
|
||||
export default function evaluateString({string, scope, fn = 'compile', context}){
|
||||
if (!context){
|
||||
context = new CompilationContext({});
|
||||
}
|
||||
if (!string){
|
||||
errors.push('No string provided');
|
||||
return {result: string, errors};
|
||||
context.storeError('No string provided');
|
||||
return {result: {value: string}, context};
|
||||
}
|
||||
|
||||
if (!scope) errors.push('No scope provided');
|
||||
if (!scope) context.storeError('No scope provided');
|
||||
|
||||
// Parse the string using mathjs
|
||||
let node;
|
||||
try {
|
||||
node = parse(string);
|
||||
} catch (e) {
|
||||
errors.push(e);
|
||||
return {result: string, errors};
|
||||
context.storeError(e);
|
||||
return {result: {value: string}, context};
|
||||
}
|
||||
let context = new CompilationContext();
|
||||
node = replaceConstants({calc: node, context, scope});
|
||||
let result = node[fn](scope, context);
|
||||
if (result instanceof ConstantNode){
|
||||
return {result: result.value, errors: context.errors}
|
||||
} else {
|
||||
return {result: result.toString(), errors: context.errors};
|
||||
}
|
||||
return {result, context};
|
||||
}
|
||||
|
||||
// Replace constants in the calc with the right ParseNodes
|
||||
function replaceConstants({calc, context, scope}){
|
||||
let constFailed = [];
|
||||
calc = calc.replaceNodes(node => {
|
||||
if (!(node instanceof SymbolNode)) return;
|
||||
let constant = scope[node.name];
|
||||
// replace constants that aren't overridden by stats or disabled by a toggle
|
||||
if (constant && constant.type === 'constant'){
|
||||
// Fail if the constant has errors
|
||||
if (constant.errors && constant.errors.length){
|
||||
constFailed.push(node.name);
|
||||
return;
|
||||
}
|
||||
let parsedConstantNode;
|
||||
try {
|
||||
parsedConstantNode = parse(constant.calculation);
|
||||
} catch(e){
|
||||
constFailed.push(node.name);
|
||||
return;
|
||||
}
|
||||
if (!parsedConstantNode) constFailed.push(node.name);
|
||||
return parsedConstantNode;
|
||||
}
|
||||
});
|
||||
constFailed.forEach(name => {
|
||||
context.storeError({
|
||||
type: 'error',
|
||||
message: `${name} is a constant property with parsing errors`
|
||||
});
|
||||
});
|
||||
let failed = !!constFailed.length;
|
||||
if (failed){
|
||||
calc = new ErrorNode({error: 'Failed to replace constants'});
|
||||
}
|
||||
return calc;
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
||||
|
||||
// Strings can have computations in bracers like so: {computation}
|
||||
export default function evalutateStringWithEmbeddedCalculations(string, scope){
|
||||
console.warn('evalutateStringWithEmbeddedCalculations should be replaced with ' +
|
||||
'fetching the result from the compuations on the property doc');
|
||||
if (!string) return string;
|
||||
// Compute everything inside bracers
|
||||
return string.replace(/\{([^{}]*)\}/g, function(match, p1){
|
||||
let {result} = evaluateString(p1, scope);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
@@ -7,6 +7,7 @@ export default class ComputationMemo {
|
||||
constructor(props, creature){
|
||||
this.statsByVariableName = {};
|
||||
this.constantsByVariableName = {};
|
||||
this.constantsById = {};
|
||||
this.extraStatsByVariableName = {};
|
||||
this.statsById = {};
|
||||
this.originalPropsById = {};
|
||||
@@ -77,11 +78,7 @@ export default class ComputationMemo {
|
||||
}
|
||||
addConstant(prop){
|
||||
prop = this.registerProperty(prop);
|
||||
if (
|
||||
!this.constantsByVariableName[prop.variableName]
|
||||
){
|
||||
this.constantsByVariableName[prop.variableName] = prop
|
||||
}
|
||||
this.constantsById[prop._id] = prop;
|
||||
}
|
||||
registerProperty(prop){
|
||||
this.originalPropsById[prop._id] = cloneDeep(prop);
|
||||
@@ -256,7 +253,6 @@ const propDetailsByType = {
|
||||
default(){
|
||||
return {
|
||||
toggleAncestors: [],
|
||||
disabledByToggle: false,
|
||||
};
|
||||
},
|
||||
toggle(){
|
||||
@@ -264,7 +260,6 @@ const propDetailsByType = {
|
||||
computed: false,
|
||||
busyComputing: false,
|
||||
toggleAncestors: [],
|
||||
disabledByToggle: false,
|
||||
};
|
||||
},
|
||||
attribute(){
|
||||
@@ -273,7 +268,6 @@ const propDetailsByType = {
|
||||
busyComputing: false,
|
||||
effects: [],
|
||||
toggleAncestors: [],
|
||||
disabledByToggle: false,
|
||||
idsOfSameName: [],
|
||||
};
|
||||
},
|
||||
@@ -284,7 +278,6 @@ const propDetailsByType = {
|
||||
effects: [],
|
||||
proficiencies: [],
|
||||
toggleAncestors: [],
|
||||
disabledByToggle: false,
|
||||
idsOfSameName: [],
|
||||
};
|
||||
},
|
||||
@@ -293,26 +286,22 @@ const propDetailsByType = {
|
||||
computed: false,
|
||||
busyComputing: false,
|
||||
toggleAncestors: [],
|
||||
disabledByToggle: false,
|
||||
};
|
||||
},
|
||||
classLevel(){
|
||||
return {
|
||||
computed: true,
|
||||
toggleAncestors: [],
|
||||
disabledByToggle: false,
|
||||
};
|
||||
},
|
||||
proficiency(){
|
||||
return {
|
||||
toggleAncestors: [],
|
||||
disabledByToggle: false,
|
||||
};
|
||||
},
|
||||
denormalizedStat(){
|
||||
return {
|
||||
toggleAncestors: [],
|
||||
disabledByToggle: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class EffectAggregator{
|
||||
prop: stat,
|
||||
memo
|
||||
});
|
||||
this.statBaseValue = result.value;
|
||||
this.statBaseValue = +result.value;
|
||||
stat.dependencies = union(
|
||||
stat.dependencies,
|
||||
dependencies,
|
||||
|
||||
@@ -2,6 +2,11 @@ import computeToggle from '/imports/api/creature/computation/engine/computeToggl
|
||||
import { union } from 'lodash';
|
||||
|
||||
export default function applyToggles(prop, memo){
|
||||
// If it used to be inactive delete those fields
|
||||
if (prop.inactive) prop.inactive = undefined;
|
||||
if (prop.deactivatedByAncestor) prop.deactivatedByAncestor = undefined;
|
||||
if (prop.deactivatedByToggle) prop.deactivatedByToggle = undefined;
|
||||
// Iterate through the toggle ancestors from oldest to nearest
|
||||
prop.computationDetails.toggleAncestors.forEach(toggleId => {
|
||||
let toggle = memo.togglesById[toggleId];
|
||||
computeToggle(toggle, memo);
|
||||
@@ -10,8 +15,11 @@ export default function applyToggles(prop, memo){
|
||||
[toggle._id],
|
||||
toggle.dependencies,
|
||||
);
|
||||
// Deactivate if the toggle is false
|
||||
if (!toggle.toggleResult){
|
||||
prop.computationDetails.disabledByToggle = true;
|
||||
prop.inactive = true;
|
||||
prop.deactivatedByAncestor = true;
|
||||
prop.deactivatedByToggle = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ function combineSkill(stat, aggregator, memo){
|
||||
let prof = stat.computationDetails.proficiencies[i];
|
||||
applyToggles(prof, memo);
|
||||
if (
|
||||
!prof.computationDetails.disabledByToggle &&
|
||||
!prof.deactivatedByToggle &&
|
||||
prof.value > stat.proficiency
|
||||
){
|
||||
stat.proficiency = prof.value;
|
||||
@@ -112,13 +112,14 @@ function combineSkill(stat, aggregator, memo){
|
||||
let profBonus = profBonusStat && profBonusStat.value;
|
||||
|
||||
if (typeof profBonus !== 'number' && memo.statsByVariableName['level']){
|
||||
let level = memo.statsByVariableName['level'].value;
|
||||
let levelProp = memo.statsByVariableName['level'];
|
||||
let level = levelProp.value;
|
||||
profBonus = Math.ceil(level / 4) + 1;
|
||||
if (level._id){
|
||||
stat.dependencies = union(stat.dependencies, [level._id]);
|
||||
if (levelProp._id){
|
||||
stat.dependencies = union(stat.dependencies, [levelProp._id]);
|
||||
}
|
||||
if (level.dependencies){
|
||||
stat.dependencies = union(stat.dependencies, level.dependencies);
|
||||
if (levelProp.dependencies){
|
||||
stat.dependencies = union(stat.dependencies, levelProp.dependencies);
|
||||
}
|
||||
} else {
|
||||
stat.dependencies = union(
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
|
||||
|
||||
export default function computeConstant(constant, memo){
|
||||
// Apply any toggles
|
||||
applyToggles(constant, memo);
|
||||
if (constant.deactivatedByToggle) return;
|
||||
if (
|
||||
!memo.constantsByVariableName[constant.variableName]
|
||||
){
|
||||
memo.constantsByVariableName[constant.variableName] = constant
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
|
||||
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
|
||||
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
|
||||
import { union } from 'lodash';
|
||||
|
||||
export default function computeEndStepProperty(prop, memo){
|
||||
applyToggles(prop, memo);
|
||||
|
||||
switch (prop.type){
|
||||
case 'action':
|
||||
case 'spell':
|
||||
@@ -14,16 +17,22 @@ export default function computeEndStepProperty(prop, memo){
|
||||
break;
|
||||
case 'attack':
|
||||
computeAction(prop, memo);
|
||||
computeAttack(prop, memo);
|
||||
computePropertyField(prop, memo, 'rollBonus');
|
||||
break;
|
||||
case 'savingThrow':
|
||||
computeSavingThrow(prop, memo);
|
||||
computePropertyField(prop, memo, 'dc');
|
||||
break;
|
||||
case 'spellList':
|
||||
computeSpellList(prop, memo);
|
||||
computePropertyField(prop, memo, 'maxPrepared');
|
||||
computePropertyField(prop, memo, 'attackRollBonus');
|
||||
computePropertyField(prop, memo, 'dc');
|
||||
break;
|
||||
case 'propertySlot':
|
||||
computeSlot(prop, memo);
|
||||
computePropertyField(prop, memo, 'quantityExpected');
|
||||
computePropertyField(prop, memo, 'slotCondition');
|
||||
break;
|
||||
case 'roll':
|
||||
computePropertyField(prop, memo, 'roll', 'compile');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -69,26 +78,33 @@ function computeAction(prop, memo){
|
||||
});
|
||||
// Items consumed
|
||||
prop.resources.itemsConsumed.forEach((itemConsumed, i) => {
|
||||
let item = itemConsumed.itemId && memo.equipmentById[itemConsumed.itemId];
|
||||
prop.resources.itemsConsumed[i].itemId = item && item._id;
|
||||
let available = item && item.quantity || 0;
|
||||
let item = itemConsumed.itemId ?
|
||||
memo.equipmentById[itemConsumed.itemId] :
|
||||
undefined;
|
||||
let available = item ? item.quantity : 0;
|
||||
prop.resources.itemsConsumed[i].available = available;
|
||||
let name = item && item.name;
|
||||
if (item && item.quantity !== 1 && item.plural){
|
||||
name = item.plural;
|
||||
}
|
||||
prop.resources.itemsConsumed[i].itemName = name;
|
||||
prop.resources.itemsConsumed[i].itemIcon = item && item.icon;
|
||||
prop.resources.itemsConsumed[i].itemColor = item && item.color;
|
||||
if (!item || available < itemConsumed.quantity){
|
||||
prop.insufficientResources = true;
|
||||
}
|
||||
if (item){
|
||||
prop.resources.itemsConsumed[i].itemId = item._id;
|
||||
let name = item.name;
|
||||
if (item.quantity !== 1 && item.plural){
|
||||
name = item.plural;
|
||||
}
|
||||
if (name) prop.resources.itemsConsumed[i].itemName = name;
|
||||
if (item.icon) prop.resources.itemsConsumed[i].itemIcon = item.icon;
|
||||
if (item.color) prop.resources.itemsConsumed[i].itemColor = item.color;
|
||||
prop.dependencies = union(
|
||||
prop.dependencies,
|
||||
[item._id],
|
||||
item.dependencies
|
||||
);
|
||||
} else {
|
||||
delete prop.resources.itemsConsumed[i].itemId;
|
||||
delete prop.resources.itemsConsumed[i].itemName;
|
||||
delete prop.resources.itemsConsumed[i].itemIcon;
|
||||
delete prop.resources.itemsConsumed[i].itemColor;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -111,19 +127,3 @@ function computePropertyField(prop, memo, fieldName, fn){
|
||||
delete prop[`${fieldName}Errors`];
|
||||
}
|
||||
}
|
||||
|
||||
function computeAttack(prop, memo){
|
||||
computePropertyField(prop, memo, 'rollBonus');
|
||||
}
|
||||
|
||||
function computeSavingThrow(prop, memo){
|
||||
computePropertyField(prop, memo, 'dc');
|
||||
}
|
||||
|
||||
function computeSpellList(prop, memo){
|
||||
computePropertyField(prop, memo, 'maxPrepared');
|
||||
}
|
||||
|
||||
function computeSlot(prop, memo){
|
||||
computePropertyField(prop, memo, 'slotCondition');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { forOwn, has, union } from 'lodash';
|
||||
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
|
||||
|
||||
export default function computeLevels(memo){
|
||||
computeClassLevels(memo);
|
||||
@@ -7,11 +8,13 @@ export default function computeLevels(memo){
|
||||
|
||||
function computeClassLevels(memo){
|
||||
forOwn(memo.classLevelsById, classLevel => {
|
||||
applyToggles(classLevel, memo);
|
||||
// class levels are mutually dependent
|
||||
classLevel.dependencies = union(
|
||||
classLevel.dependencies,
|
||||
Object.keys(memo.classLevelsById)
|
||||
);
|
||||
if (classLevel.deactivatedByToggle) return;
|
||||
let name = classLevel.variableName;
|
||||
let stat = memo.statsByVariableName[name];
|
||||
if (!stat){
|
||||
@@ -29,7 +32,7 @@ function computeClassLevels(memo){
|
||||
|
||||
function computeTotalLevel(memo){
|
||||
let currentLevel = memo.statsByVariableName['level'];
|
||||
if (!currentLevel){
|
||||
if (!currentLevel || currentLevel.deactivatedByToggle){
|
||||
currentLevel = {
|
||||
value: 0,
|
||||
dependencies: [],
|
||||
|
||||
@@ -5,8 +5,13 @@ import computeEffect from '/imports/api/creature/computation/engine/computeEffec
|
||||
import computeToggle from '/imports/api/creature/computation/engine/computeToggle.js';
|
||||
import computeEndStepProperty from '/imports/api/creature/computation/engine/computeEndStepProperty.js';
|
||||
import computeInlineCalculations from '/imports/api/creature/computation/engine/computeInlineCalculations.js';
|
||||
import computeConstant from '/imports/api/creature/computation/engine/computeConstant.js';
|
||||
|
||||
export default function computeMemo(memo){
|
||||
// Compute all constants that could be used
|
||||
forOwn(memo.constantsById, constant => {
|
||||
computeConstant (constant, memo);
|
||||
});
|
||||
// Compute level
|
||||
computeLevels(memo);
|
||||
// Compute all stats, even if they are overriden
|
||||
|
||||
@@ -22,28 +22,25 @@ export default function computeStat(stat, 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._id){
|
||||
stat.dependencies = union(
|
||||
stat.dependencies,
|
||||
[effect._id]
|
||||
);
|
||||
}
|
||||
// Compute and aggregate all the effects
|
||||
let aggregator = new EffectAggregator(stat, memo)
|
||||
each(stat.computationDetails.effects, (effect) => {
|
||||
computeEffect(effect, memo);
|
||||
if (effect.deactivatedByToggle) return;
|
||||
if (effect._id){
|
||||
stat.dependencies = union(
|
||||
stat.dependencies,
|
||||
effect.dependencies
|
||||
)
|
||||
if (!effect.computationDetails.disabledByToggle){
|
||||
aggregator.addEffect(effect);
|
||||
}
|
||||
});
|
||||
// Conglomerate all the effects to compute the final stat values
|
||||
combineStat(stat, aggregator, memo);
|
||||
}
|
||||
[effect._id]
|
||||
);
|
||||
}
|
||||
stat.dependencies = union(
|
||||
stat.dependencies,
|
||||
effect.dependencies
|
||||
)
|
||||
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;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
|
||||
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
|
||||
import { union } from 'lodash';
|
||||
|
||||
export default function computeToggle(toggle, memo){
|
||||
@@ -16,6 +17,9 @@ export default function computeToggle(toggle, memo){
|
||||
// Before doing any work, mark this toggle as busy
|
||||
toggle.computationDetails.busyComputing = true;
|
||||
|
||||
// Apply any parent toggles
|
||||
applyToggles(toggle, memo);
|
||||
|
||||
// Do work
|
||||
delete toggle.errors;
|
||||
if (toggle.enabled){
|
||||
@@ -41,6 +45,11 @@ export default function computeToggle(toggle, memo){
|
||||
toggle.errors = context.errors;
|
||||
}
|
||||
}
|
||||
if (!toggle.toggleResult){
|
||||
toggle.inactive = true;
|
||||
toggle.deactivatedBySelf = true;
|
||||
toggle.deactivatedByToggle = true;
|
||||
}
|
||||
toggle.computationDetails.computed = true;
|
||||
toggle.computationDetails.busyComputing = false;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ export default function evaluateCalculation({
|
||||
context,
|
||||
dependencies,
|
||||
};
|
||||
if (typeof string !== 'string'){
|
||||
string = string.toString();
|
||||
}
|
||||
// Parse the string
|
||||
let calc;
|
||||
try {
|
||||
@@ -68,8 +71,8 @@ function replaceConstants({calc, memo, prop, dependencies, context}){
|
||||
} else if (node.name === '#constant'){
|
||||
constant = findAncestorByType({type: 'constant', prop, memo});
|
||||
}
|
||||
// replace constants that aren't overridden by stats
|
||||
if (constant && !stat){
|
||||
// replace constants that aren't overridden by stats or disabled by a toggle
|
||||
if (constant && !constant.deactivatedByToggle && !stat){
|
||||
dependencies = union(dependencies, [
|
||||
constant._id,
|
||||
...constant.dependencies
|
||||
@@ -119,10 +122,14 @@ function computeSymbols({calc, memo, prop, dependencies}){
|
||||
computeStat(stat, memo);
|
||||
}
|
||||
if (stat){
|
||||
dependencies = union(dependencies, [
|
||||
stat._id || node.name,
|
||||
...stat.dependencies
|
||||
]);
|
||||
if (stat.dependencies){
|
||||
dependencies = union(dependencies, [
|
||||
stat._id || node.name,
|
||||
...stat.dependencies
|
||||
]);
|
||||
} else {
|
||||
dependencies = union(dependencies, [stat._id || node.name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
|
||||
export default function getComputationProperties(creatureId){
|
||||
// find ids of all toggles that have conditions, even if they are inactive
|
||||
let toggleIds = CreatureProperties.find({
|
||||
'ancestors.id': creatureId,
|
||||
type: 'toggle',
|
||||
removed: {$ne: true},
|
||||
condition: { $exists: true },
|
||||
}, {
|
||||
fields: {_id: 1},
|
||||
}).map(t => t._id);
|
||||
// Find all the relevant properties
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': creatureId,
|
||||
@@ -17,17 +8,15 @@ export default function getComputationProperties(creatureId){
|
||||
$or: [
|
||||
// All active properties
|
||||
{inactive: {$ne: true}},
|
||||
// All active and inactive toggles with conditions
|
||||
// Same as {$in: toggleIds}, but should be slightly faster
|
||||
{type: 'toggle', condition: { $exists: true }},
|
||||
// All decendents of the above toggles
|
||||
{'ancestors.id': {$in: toggleIds}},
|
||||
// Unless they were deactivated because of a toggle
|
||||
{deactivatedByToggle: true},
|
||||
]
|
||||
}, {
|
||||
// Filter out fields never used by calculations
|
||||
fields: {
|
||||
icon: 0,
|
||||
},
|
||||
// Obey tree order
|
||||
sort: {
|
||||
order: 1,
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ export default function writeAlteredProperties(memo){
|
||||
// Loop through all properties on the memo
|
||||
forOwn(memo.propsById, changed => {
|
||||
let schema = propertySchemasIndex[changed.type];
|
||||
if (!schema) return;
|
||||
if (!schema){
|
||||
console.warn('No schema for ' + changed.type);
|
||||
return;
|
||||
}
|
||||
let extraIds = changed.computationDetails.idsOfSameName;
|
||||
let ids;
|
||||
if (extraIds && extraIds.length){
|
||||
@@ -19,7 +22,14 @@ export default function writeAlteredProperties(memo){
|
||||
ids.forEach(id => {
|
||||
let op = undefined;
|
||||
let original = memo.originalPropsById[id];
|
||||
let keys = ['dependencies', ...schema.objectKeys()];
|
||||
let keys = [
|
||||
'dependencies',
|
||||
'inactive',
|
||||
'deactivatedBySelf',
|
||||
'deactivatedByAncestor',
|
||||
'deactivatedByToggle',
|
||||
...schema.objectKeys(),
|
||||
];
|
||||
op = addChangedKeysToOp(op, keys, original, changed);
|
||||
if (op){
|
||||
bulkWriteOperations.push(op);
|
||||
|
||||
@@ -4,36 +4,46 @@ import VERSION from '/imports/constants/VERSION.js';
|
||||
|
||||
export default function writeCreatureVariables(memo, creatureId, fullRecompute = true) {
|
||||
const fields = [
|
||||
'name',
|
||||
'attributeType',
|
||||
'baseValue',
|
||||
'spellSlotLevelValue',
|
||||
'damage',
|
||||
'decimal',
|
||||
'reset',
|
||||
'resetMultiplier',
|
||||
'value',
|
||||
'currentValue',
|
||||
'modifier',
|
||||
'ability',
|
||||
'skillType',
|
||||
'baseProficiency',
|
||||
'abilityMod',
|
||||
'advantage',
|
||||
'passiveBonus',
|
||||
'proficiency',
|
||||
'attributeType',
|
||||
'baseProficiency',
|
||||
'baseValue',
|
||||
'calculation',
|
||||
'conditionalBenefits',
|
||||
'rollBonuses',
|
||||
'currentValue',
|
||||
'damage',
|
||||
'decimal',
|
||||
'fail',
|
||||
'level',
|
||||
'modifier',
|
||||
'name',
|
||||
'passiveBonus',
|
||||
'proficiency',
|
||||
'reset',
|
||||
'resetMultiplier',
|
||||
'rollBonuses',
|
||||
'skillType',
|
||||
'spellSlotLevelValue',
|
||||
'type',
|
||||
'value',
|
||||
];
|
||||
|
||||
if (fullRecompute){
|
||||
memo.creatureVariables = {};
|
||||
forOwn(memo.statsByVariableName, (stat, variableName) => {
|
||||
// Don't save context variables
|
||||
if (variableName[0] === '#') return;
|
||||
let condensedStat = pick(stat, fields);
|
||||
memo.creatureVariables[variableName] = condensedStat;
|
||||
});
|
||||
forOwn(memo.constantsByVariableName, (stat, variableName) => {
|
||||
let condensedStat = pick(stat, fields);
|
||||
if (!memo.creatureVariables[variableName]){
|
||||
memo.creatureVariables[variableName] = condensedStat;
|
||||
}
|
||||
});
|
||||
Creatures.update(creatureId, {$set: {
|
||||
variables: memo.creatureVariables,
|
||||
computeVersion: VERSION,
|
||||
|
||||
@@ -89,7 +89,6 @@ export function recomputeCreatureByDoc(creature){
|
||||
writeCreatureVariables(computationMemo, creatureId);
|
||||
recomputeDamageMultipliersById(creatureId);
|
||||
recomputeSlotFullness(creatureId);
|
||||
recomputeInactiveProperties(creatureId);
|
||||
return computationMemo;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,20 @@ let CreaturePropertySchema = new SimpleSchema({
|
||||
optional: true,
|
||||
index: 1,
|
||||
},
|
||||
// Denormalised flag if this property was made inactive because of its own
|
||||
// state
|
||||
deactivatedBySelf: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
index: 1,
|
||||
},
|
||||
// Denormalised flag if this property was made inactive because of a toggle
|
||||
// calculation. Either an ancestor toggle calculation or its own.
|
||||
deactivatedByToggle: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
index: 1,
|
||||
},
|
||||
// Denormalised list of all properties or creatures this property depends on
|
||||
dependencies: {
|
||||
type: Array,
|
||||
|
||||
@@ -4,7 +4,8 @@ import SimpleSchema from 'simpl-schema';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { recomputePropertyDependencies } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
|
||||
const adjustQuantity = new ValidatedMethod({
|
||||
name: 'creatureProperties.adjustQuantity',
|
||||
@@ -30,8 +31,10 @@ const adjustQuantity = new ValidatedMethod({
|
||||
// Do work
|
||||
adjustQuantityWork({property, operation, value});
|
||||
|
||||
// Changing quantity does not change dependencies, recompute deps
|
||||
recomputePropertyDependencies(property);
|
||||
// Changing quantity does not change dependencies, but recomputing the
|
||||
// inventory changes many deps at once, so recompute fully
|
||||
recomputeCreatureByDoc(rootCreature);
|
||||
recomputeInventory(rootCreature._id);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ const damagePropertiesByName = new ValidatedMethod({
|
||||
damagePropertyWork({property, operation, value});
|
||||
lastProperty = property;
|
||||
});
|
||||
recomputePropertyDependencies(lastProperty);
|
||||
if (lastProperty) recomputePropertyDependencies(lastProperty);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -24,6 +24,9 @@ const damageProperty = new ValidatedMethod({
|
||||
run({_id, operation, value}) {
|
||||
// Check permissions
|
||||
let property = CreatureProperties.findOne(_id);
|
||||
if (!property) throw new Meteor.Error(
|
||||
'Damage property failed', 'Property doesn\'t exist'
|
||||
);
|
||||
let rootCreature = getRootCreatureAncestor(property);
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
// Check if property can take damage
|
||||
@@ -34,9 +37,10 @@ const damageProperty = new ValidatedMethod({
|
||||
`Property of type "${property.type}" can't be damaged`
|
||||
);
|
||||
}
|
||||
damagePropertyWork({property, operation, value});
|
||||
let result = damagePropertyWork({property, operation, value});
|
||||
// Dependencies can't be changed through damage, only recompute deps
|
||||
recomputePropertyDependencies(property);
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { organizeDoc } from '/imports/api/parenting/organizeMethods.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
|
||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||
import INVENTORY_TAGS from '/imports/constants/INVENTORY_TAGS.js';
|
||||
|
||||
export function getParentRefByTag(creatureId, tag){
|
||||
@@ -49,7 +52,7 @@ const equipItem = new ValidatedMethod({
|
||||
});
|
||||
let tag = equipped ? INVENTORY_TAGS.equipment : INVENTORY_TAGS.carried;
|
||||
let parentRef = getParentRefByTag(creature._id, tag);
|
||||
// organizeDoc handles recompuation
|
||||
|
||||
organizeDoc.call({
|
||||
docRef: {
|
||||
id: _id,
|
||||
@@ -57,7 +60,12 @@ const equipItem = new ValidatedMethod({
|
||||
},
|
||||
parentRef,
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
skipRecompute: true,
|
||||
});
|
||||
|
||||
recomputeInactiveProperties(creature._id);
|
||||
recomputeInventory(creature._id);
|
||||
recomputeCreatureByDoc(creature);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js
|
||||
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
|
||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||
|
||||
const insertProperty = new ValidatedMethod({
|
||||
name: 'creatureProperties.insert',
|
||||
@@ -35,6 +36,11 @@ export function insertPropertyWork({property, creature}){
|
||||
});
|
||||
// Inserting the active status of the property needs to be denormalised
|
||||
recomputeInactiveProperties(creature._id);
|
||||
|
||||
// Recompute the inventory if it has changed
|
||||
if (property.type === 'item' || property.type === 'container'){
|
||||
recomputeInventory(creature._id);
|
||||
}
|
||||
// Inserting a creature property invalidates dependencies: full recompute
|
||||
recomputeCreatureByDoc(creature);
|
||||
return _id;
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from '/imports/api/parenting/parenting.js';
|
||||
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||
import { setDocToLastOrder } from '/imports/api/parenting/order.js';
|
||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||
|
||||
const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||
name: 'creatureProperties.insertPropertyFromLibraryNode',
|
||||
@@ -97,6 +98,8 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||
|
||||
// The library properties need to denormalise which of them are inactive
|
||||
recomputeInactiveProperties(rootId);
|
||||
// Some of the library properties may be items or containers
|
||||
recomputeInventory(rootCreature._id);
|
||||
// Inserting a creature property invalidates dependencies: full recompute
|
||||
recomputeCreatureByDoc(rootCreature);
|
||||
// Return the docId of the last property, the inserted root property
|
||||
|
||||
@@ -7,6 +7,7 @@ import { restore } from '/imports/api/parenting/softRemove.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
|
||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||
|
||||
const restoreProperty = new ValidatedMethod({
|
||||
name: 'creatureProperties.restore',
|
||||
@@ -27,6 +28,8 @@ const restoreProperty = new ValidatedMethod({
|
||||
// Do work
|
||||
restore({_id, collection: CreatureProperties});
|
||||
|
||||
// Items and containers might be restored
|
||||
recomputeInventory(rootCreature._id);
|
||||
// Parents active status may have changed while it was deleted
|
||||
recomputeInactiveProperties(rootCreature._id);
|
||||
// Changes dependency tree by restoring children
|
||||
|
||||
@@ -6,6 +6,7 @@ import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js
|
||||
import { softRemove } from '/imports/api/parenting/softRemove.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||
|
||||
const softRemoveProperty = new ValidatedMethod({
|
||||
name: 'creatureProperties.softRemove',
|
||||
@@ -26,6 +27,8 @@ const softRemoveProperty = new ValidatedMethod({
|
||||
// Do work
|
||||
softRemove({_id, collection: CreatureProperties});
|
||||
|
||||
// Potentially changes items and containers
|
||||
recomputeInventory(rootCreature._id);
|
||||
// Changes dependency tree by removing children
|
||||
recomputeCreatureByDoc(rootCreature);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
|
||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||
|
||||
const updateCreatureProperty = new ValidatedMethod({
|
||||
name: 'creatureProperties.update',
|
||||
@@ -52,8 +53,14 @@ const updateCreatureProperty = new ValidatedMethod({
|
||||
].includes(path[0])){
|
||||
recomputeInactiveProperties(rootCreature._id);
|
||||
}
|
||||
|
||||
if (property.type === 'item' || property.type === 'container'){
|
||||
// Potentially changes items and containers
|
||||
recomputeInventory(rootCreature._id);
|
||||
}
|
||||
// Updating a property is likely to change dependencies, do a full recompute
|
||||
recomputeCreatureByDoc(rootCreature);
|
||||
// denormalised stats might change, so fetch the creature again
|
||||
recomputeCreatureById(rootCreature._id);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ export default function recomputeInactiveProperties(ancestorId){
|
||||
{disabled: true}, // Everything can be disabled
|
||||
{type: 'buff', applied: false}, // Buffs can be applied
|
||||
{type: 'item', equipped: {$ne: true}},
|
||||
{type: 'toggle', toggleResult: false},
|
||||
{type: 'spell', prepared: {$ne: true}, alwaysPrepared: {$ne: true}},
|
||||
],
|
||||
};
|
||||
@@ -20,9 +19,16 @@ export default function recomputeInactiveProperties(ancestorId){
|
||||
CreatureProperties.update({
|
||||
'ancestors.id': ancestorId,
|
||||
'_id': {$in: disabledIds},
|
||||
$or: [{inactive: {$ne: true}}, {deactivatedByAncestor: true}],
|
||||
$or: [
|
||||
{inactive: {$ne: true}},
|
||||
{deactivatedBySelf: {$ne: true}},
|
||||
{deactivatedByAncestor: true},
|
||||
],
|
||||
}, {
|
||||
$set: {inactive: true},
|
||||
$set: {
|
||||
inactive: true,
|
||||
deactivatedBySelf: true,
|
||||
},
|
||||
$unset: {deactivatedByAncestor: 1},
|
||||
}, {
|
||||
multi: true,
|
||||
@@ -31,7 +37,10 @@ export default function recomputeInactiveProperties(ancestorId){
|
||||
// Decendants of inactive properties
|
||||
CreatureProperties.update({
|
||||
'ancestors.id': {$eq: ancestorId, $in: disabledIds},
|
||||
$or: [{inactive: {$ne: true}}, {deactivatedByAncestor: {$ne: true}}],
|
||||
$or: [
|
||||
{inactive: {$ne: true}},
|
||||
{deactivatedByAncestor: {$ne: true}},
|
||||
],
|
||||
}, {
|
||||
$set: {
|
||||
inactive: true,
|
||||
@@ -46,11 +55,18 @@ export default function recomputeInactiveProperties(ancestorId){
|
||||
CreatureProperties.update({
|
||||
'ancestors.id': {$eq: ancestorId, $nin: disabledIds},
|
||||
'_id': {$nin: disabledIds},
|
||||
$or: [{inactive: true}, {deactivatedByAncestor: true}],
|
||||
// if it was a toggle responsible, we leave it alone
|
||||
deactivatedByToggle: {$ne: true},
|
||||
$or: [
|
||||
{inactive: true},
|
||||
{deactivatedByAncestor: true},
|
||||
{deactivatedBySelf: true}
|
||||
],
|
||||
}, {
|
||||
$unset: {
|
||||
inactive: 1,
|
||||
deactivatedByAncestor: 1,
|
||||
deactivatedBySelf: 1,
|
||||
},
|
||||
}, {
|
||||
multi: true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import nodesToTree from '/imports/api/parenting/parenting.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import { nodesToTree } from '/imports/api/parenting/parenting.js';
|
||||
|
||||
export default function recomputeInventory(creatureId){
|
||||
let inventoryForest = nodesToTree({
|
||||
@@ -10,27 +11,27 @@ export default function recomputeInventory(creatureId){
|
||||
},
|
||||
deactivatedByAncestor: {$ne: true},
|
||||
});
|
||||
return getChildrenInventoryData(inventoryForest);
|
||||
}
|
||||
|
||||
function getChildrenInventoryData(forest){
|
||||
let data = {
|
||||
weightTotal: 0,
|
||||
weightEquipment: 0,
|
||||
weightCarried: 0,
|
||||
valueTotal: 0,
|
||||
valueEquipment: 0,
|
||||
valueCarried: 0,
|
||||
}
|
||||
forest.forEach(tree => {
|
||||
let treeData = getInventoryData(tree);
|
||||
for (let key in data){
|
||||
data[key] += treeData[key];
|
||||
}
|
||||
let containersToWrite = [];
|
||||
let data = getChildrenInventoryData(inventoryForest, containersToWrite);
|
||||
containersToWrite.forEach(container => {
|
||||
CreatureProperties.update(container._id, {$set: {
|
||||
contentsWeight: container.contentsWeight,
|
||||
contentsValue: container.contentsValue,
|
||||
}}, {selector: {type: 'container'}});
|
||||
});
|
||||
Creatures.update(creatureId, {$set: {
|
||||
'denormalizedStats.weightTotal': data.weightTotal,
|
||||
'denormalizedStats.weightEquipment': data.weightEquipment,
|
||||
'denormalizedStats.weightCarried': data.weightCarried,
|
||||
'denormalizedStats.valueTotal': data.valueTotal,
|
||||
'denormalizedStats.valueEquipment': data.valueEquipment,
|
||||
'denormalizedStats.valueCarried': data.valueCarried,
|
||||
'denormalizedStats.itemsAttuned': data.itemsAttuned,
|
||||
}});
|
||||
return data;
|
||||
}
|
||||
|
||||
function getInventoryData(tree){
|
||||
function getChildrenInventoryData(forest, containersToWrite){
|
||||
let data = {
|
||||
weightTotal: 0,
|
||||
weightEquipment: 0,
|
||||
@@ -40,24 +41,41 @@ function getInventoryData(tree){
|
||||
valueCarried: 0,
|
||||
itemsAttuned: 0,
|
||||
}
|
||||
let childData = getChildrenInventoryData(tree.children);
|
||||
forest.forEach(tree => {
|
||||
let treeData = getInventoryData(tree, containersToWrite);
|
||||
for (let key in data){
|
||||
data[key] += treeData[key] || 0;
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function getInventoryData(tree, containersToWrite){
|
||||
let data = {
|
||||
weightTotal: 0,
|
||||
weightEquipment: 0,
|
||||
weightCarried: 0,
|
||||
valueTotal: 0,
|
||||
valueEquipment: 0,
|
||||
valueCarried: 0,
|
||||
itemsAttuned: 0,
|
||||
}
|
||||
let childData = getChildrenInventoryData(tree.children, containersToWrite);
|
||||
let node = tree.node;
|
||||
if (node.type === 'container'){
|
||||
data.weightTotal += node.weight;
|
||||
data.valueTotal += node.value;
|
||||
if (node.carried){
|
||||
data.weightCarried += node.weight;
|
||||
data.valueCarried += node.valueCarried;
|
||||
}
|
||||
storeContentsData(node, childData);
|
||||
data.weightTotal += node.weight || 0;
|
||||
data.valueTotal += node.value || 0;
|
||||
data.weightCarried += node.weight || 0;
|
||||
data.valueCarried += node.value || 0;
|
||||
storeContentsData(node, childData, containersToWrite);
|
||||
} else if (node.type === 'item'){
|
||||
data.weightTotal += node.weight * node.quantity;
|
||||
data.valueTotal += node.value * node.quantity;
|
||||
data.weightCarried += node.weight * node.quantity;
|
||||
data.valueCarried += node.valueCarried * node.quantity;
|
||||
data.weightTotal += (node.weight * node.quantity) || 0;
|
||||
data.valueTotal += (node.value * node.quantity) || 0;
|
||||
data.weightCarried += (node.weight * node.quantity) || 0;
|
||||
data.valueCarried += (node.value * node.quantity) || 0;
|
||||
if (node.equipped){
|
||||
data.weightEquipment += node.weight * node.quantity;
|
||||
data.valueEquipment += node.valueCarried * node.quantity;
|
||||
data.weightEquipment += (node.weight * node.quantity) || 0;
|
||||
data.valueEquipment += (node.value * node.quantity) || 0;
|
||||
}
|
||||
if (node.attuned){
|
||||
data.itemsAttuned += 1;
|
||||
@@ -66,16 +84,18 @@ function getInventoryData(tree){
|
||||
for (let key in data){
|
||||
data[key] += childData[key];
|
||||
}
|
||||
if (node.contentsWeightless){
|
||||
data.weightCarried = node.weight;
|
||||
}
|
||||
if (node.carried === false){
|
||||
data.weightCarried = 0;
|
||||
data.valueCarried = 0;
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
function storeContentsData(node, childData){
|
||||
let newContentsWeight;
|
||||
if (node.contentsWeightless){
|
||||
newContentsWeight = 0;
|
||||
} else {
|
||||
newContentsWeight = childData.weightCarried
|
||||
}
|
||||
function storeContentsData(node, childData, containersToWrite){
|
||||
let newContentsWeight = childData.weightCarried
|
||||
if (node.contentsWeight !== newContentsWeight){
|
||||
node.contentsWeight = newContentsWeight;
|
||||
node.contentsWeightChanged = true;
|
||||
@@ -85,4 +105,7 @@ function storeContentsData(node, childData){
|
||||
node.contentsValue = newContentsValue;
|
||||
node.contentsValueChanged = true;
|
||||
}
|
||||
if (node.contentsWeightChanged || node.contentsValueChanged){
|
||||
containersToWrite.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,14 @@ export default function recomputeSlotFullness(ancestorId){
|
||||
}
|
||||
});
|
||||
let spaceLeft;
|
||||
if (slot.quantityExpected === 0){
|
||||
let expected = slot.quantityExpectedResult;
|
||||
if (typeof expected !== 'number'){
|
||||
expected = 1;
|
||||
}
|
||||
if (expected === 0){
|
||||
spaceLeft = null;
|
||||
} else {
|
||||
spaceLeft = slot.quantityExpected - totalFilled;
|
||||
spaceLeft = expected - totalFilled;
|
||||
}
|
||||
if (slot.totalFilled !== totalFilled || slot.spaceLeft !== spaceLeft){
|
||||
CreatureProperties.update(slot._id, {
|
||||
|
||||
@@ -28,10 +28,14 @@ const removeCreature = new ValidatedMethod({
|
||||
},
|
||||
run({charId}) {
|
||||
assertOwnership(charId, this.userId)
|
||||
Creatures.remove(charId);
|
||||
this.unblock();
|
||||
removeRelatedDocuments(charId);
|
||||
this.unblock();
|
||||
removeCreatureWork(charId)
|
||||
},
|
||||
});
|
||||
|
||||
export function removeCreatureWork(creatureId){
|
||||
Creatures.remove(creatureId);
|
||||
removeRelatedDocuments(creatureId);
|
||||
}
|
||||
|
||||
export default removeCreature;
|
||||
|
||||
@@ -118,10 +118,14 @@ const removeLibrary = new ValidatedMethod({
|
||||
run({_id}){
|
||||
let library = Libraries.findOne(_id);
|
||||
assertOwnership(library, this.userId);
|
||||
Libraries.remove(_id);
|
||||
this.unblock();
|
||||
LibraryNodes.remove({'ancestors.id': _id});
|
||||
this.unblock();
|
||||
removeLibaryWork(_id)
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
export function removeLibaryWork(libraryId){
|
||||
Libraries.remove(libraryId);
|
||||
LibraryNodes.remove({'ancestors.id': libraryId});
|
||||
}
|
||||
|
||||
export { LibrarySchema, insertLibrary, setLibraryDefault, updateLibraryName, removeLibrary };
|
||||
|
||||
@@ -10,7 +10,7 @@ import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||
import getCollectionByName from '/imports/api/parenting/getCollectionByName.js';
|
||||
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
|
||||
|
||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||
const organizeDoc = new ValidatedMethod({
|
||||
name: 'organize.organizeDoc',
|
||||
validate: new SimpleSchema({
|
||||
@@ -20,13 +20,17 @@ const organizeDoc = new ValidatedMethod({
|
||||
type: Number,
|
||||
// Should end in 0.5 to place it reliably between two existing documents
|
||||
},
|
||||
skipRecompute: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({docRef, parentRef, order}) {
|
||||
run({docRef, parentRef, order, skipRecompute}) {
|
||||
let doc = fetchDocByRef(docRef);
|
||||
let collection = getCollectionByName(docRef.collection);
|
||||
// The user must be able to edit both the doc and its parent to move it
|
||||
@@ -52,15 +56,20 @@ const organizeDoc = new ValidatedMethod({
|
||||
// Figure out which creatures need to be recalculated after this move
|
||||
let docCreatures = getCreatureAncestors(doc);
|
||||
let parentCreatures = getCreatureAncestors(parent);
|
||||
let creaturesToRecompute = union(docCreatures, parentCreatures);
|
||||
// Recompute the creatures
|
||||
creaturesToRecompute.forEach(id => {
|
||||
// The active status of some properties might change due to a change in
|
||||
// ancestry
|
||||
recomputeInactiveProperties(id);
|
||||
// Some Dependencies depend on ancestry, so a full recompute is needed
|
||||
recomputeCreatureById(id);
|
||||
});
|
||||
if (!skipRecompute){
|
||||
let creaturesToRecompute = union(docCreatures, parentCreatures);
|
||||
// Recompute the creatures
|
||||
creaturesToRecompute.forEach(id => {
|
||||
// The active status of some properties might change due to a change in
|
||||
// ancestry
|
||||
recomputeInactiveProperties(id);
|
||||
if (doc.type === 'container' || doc.type === 'item'){
|
||||
recomputeInventory(id);
|
||||
}
|
||||
// Some Dependencies depend on ancestry, so a full recompute is needed
|
||||
recomputeCreatureById(id);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||
|
||||
/**
|
||||
* Rolls are children to actions or other rolls, they are triggered with 0 or
|
||||
@@ -20,6 +21,17 @@ import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
* child rolls are applied
|
||||
*/
|
||||
let RollSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
defaultValue: 'New Roll',
|
||||
},
|
||||
// The technical, lowercase, single-word name used in formulae
|
||||
variableName: {
|
||||
type: String,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
min: 2,
|
||||
defaultValue: 'newRoll',
|
||||
},
|
||||
// The roll, can be simplified, but only computed in context
|
||||
roll: {
|
||||
type: String,
|
||||
|
||||
@@ -8,11 +8,22 @@ let SavingThrowSchema = new SimpleSchema ({
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
// The computed DC
|
||||
dc: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
// The variable name of ability the save to roll
|
||||
// Who this saving throw 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 variable name of save to roll
|
||||
stat: {
|
||||
type: String,
|
||||
optional: true,
|
||||
|
||||
@@ -22,9 +22,9 @@ let SlotSchema = new SimpleSchema({
|
||||
type: String,
|
||||
},
|
||||
quantityExpected: {
|
||||
type: SimpleSchema.Integer,
|
||||
defaultValue: 1,
|
||||
min: 0,
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: '1',
|
||||
},
|
||||
ignored: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -23,6 +23,16 @@ let SpellListSchema = new SimpleSchema({
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
// Calculation of The attack roll bonus used by spell attacks in this list
|
||||
attackRollBonus: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
// Calculation of the save dc used by spells in this list
|
||||
dc: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlySpellListSchema = new SimpleSchema({
|
||||
@@ -33,6 +43,7 @@ const ComputedOnlySpellListSchema = new SimpleSchema({
|
||||
},
|
||||
'descriptionCalculations.$': InlineComputationSchema,
|
||||
|
||||
// maxPrepared
|
||||
maxPreparedResult: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
@@ -44,6 +55,32 @@ const ComputedOnlySpellListSchema = new SimpleSchema({
|
||||
'maxPreparedErrors.$':{
|
||||
type: ErrorSchema,
|
||||
},
|
||||
|
||||
// attackRollBonus
|
||||
attackRollBonusResult: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
attackRollBonusErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
},
|
||||
'attackRollBonusErrors.$':{
|
||||
type: ErrorSchema,
|
||||
},
|
||||
|
||||
// dc
|
||||
dcResult: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
dcErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
},
|
||||
'dcErrors.$':{
|
||||
type: ErrorSchema,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedSpellListSchema = new SimpleSchema()
|
||||
|
||||
@@ -4,21 +4,22 @@ 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 { ClassLevelSchema } from '/imports/api/properties/ClassLevels.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';
|
||||
import { ComputedOnlyDamageSchema } from '/imports/api/properties/Damages.js';
|
||||
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
|
||||
import { ComputedOnlyEffectSchema } from '/imports/api/properties/Effects.js';
|
||||
import { ComputedOnlyFeatureSchema } from '/imports/api/properties/Features.js';
|
||||
// import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||
import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js';
|
||||
import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js';
|
||||
// import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||
import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js';
|
||||
import { ComputedOnlySavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
||||
import { ComputedOnlySkillSchema } from '/imports/api/properties/Skills.js';
|
||||
import { ComputedOnlySlotSchema } from '/imports/api/properties/Slots.js';
|
||||
// import { SlotFillerSchema } from '/imports/api/properties/SlotFillers.js';
|
||||
import { SlotFillerSchema } from '/imports/api/properties/SlotFillers.js';
|
||||
import { ComputedOnlySpellSchema } from '/imports/api/properties/Spells.js';
|
||||
import { ComputedOnlySpellListSchema } from '/imports/api/properties/SpellLists.js';
|
||||
import { ComputedOnlyToggleSchema } from '/imports/api/properties/Toggles.js';
|
||||
@@ -29,23 +30,25 @@ const propertySchemasIndex = {
|
||||
attack: ComputedOnlyAttackSchema,
|
||||
attribute: ComputedOnlyAttributeSchema,
|
||||
buff: ComputedOnlyBuffSchema,
|
||||
// classLevel: ClassLevelSchema,
|
||||
classLevel: ClassLevelSchema,
|
||||
constant: ConstantSchema,
|
||||
container: ComputedOnlyContainerSchema,
|
||||
damage: ComputedOnlyDamageSchema,
|
||||
damageMultiplier: DamageMultiplierSchema,
|
||||
effect: ComputedOnlyEffectSchema,
|
||||
feature: ComputedOnlyFeatureSchema,
|
||||
// folder: FolderSchema,
|
||||
folder: FolderSchema,
|
||||
item: ComputedOnlyItemSchema,
|
||||
note: ComputedOnlyNoteSchema,
|
||||
// proficiency: ProficiencySchema,
|
||||
proficiency: ProficiencySchema,
|
||||
propertySlot: ComputedOnlySlotSchema,
|
||||
roll: ComputedOnlyRollSchema,
|
||||
savingThrow: ComputedOnlySavingThrowSchema,
|
||||
skill: ComputedOnlySkillSchema,
|
||||
slotFiller: SlotFillerSchema,
|
||||
spellList: ComputedOnlySpellListSchema,
|
||||
spell: ComputedOnlySpellSchema,
|
||||
toggle: ComputedOnlyToggleSchema,
|
||||
container: ComputedOnlyContainerSchema,
|
||||
item: ComputedOnlyItemSchema,
|
||||
any: new SimpleSchema({}),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import '/imports/api/users/deleteMyAccount.js';
|
||||
|
||||
const userSchema = new SimpleSchema({
|
||||
username: {
|
||||
|
||||
61
app/imports/api/users/deleteMyAccount.js
Normal file
61
app/imports/api/users/deleteMyAccount.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Libraries, {removeLibaryWork} from '/imports/api/library/Libraries.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import {removeCreatureWork} from '/imports/api/creature/removeCreature.js';
|
||||
|
||||
Meteor.users.deleteMyAccount = new ValidatedMethod({
|
||||
name: 'users.deleteMyAccount',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 1,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run(){
|
||||
let userId = Meteor.userId();
|
||||
if (!userId) throw new Meteor.Error('No user',
|
||||
'You must be logged into to delete your account');
|
||||
|
||||
// Delete all creatures
|
||||
let creatures = Creatures.find({owner: userId}, {fields: {_id: 1}}).fetch();
|
||||
creatures.forEach(creature => removeCreatureWork(creature._id));
|
||||
|
||||
// Remove permissions from all creatures
|
||||
Creatures.update({
|
||||
$or: [
|
||||
{writers: userId},
|
||||
{readers: userId},
|
||||
],
|
||||
}, {
|
||||
$pull: {
|
||||
writers: userId,
|
||||
readers: userId
|
||||
},
|
||||
}, {
|
||||
multi: true,
|
||||
});
|
||||
|
||||
// Delete all libraries
|
||||
let libraries = Libraries.find({owner: userId}, {fields: {_id: 1}}).fetch();
|
||||
libraries.forEach(library => removeLibaryWork(library._id));
|
||||
|
||||
// Remove permissions from all creatures
|
||||
Libraries.update({
|
||||
$or: [
|
||||
{writers: userId},
|
||||
{readers: userId},
|
||||
],
|
||||
}, {
|
||||
$pull: {
|
||||
writers: userId,
|
||||
readers: userId
|
||||
},
|
||||
}, {
|
||||
multi: true,
|
||||
});
|
||||
|
||||
// delete the account
|
||||
Meteor.users.remove(userId);
|
||||
}
|
||||
});
|
||||
@@ -91,6 +91,10 @@ const SVG_ICONS = Object.freeze({
|
||||
name: 'weight',
|
||||
shape: 'M256 46c-45.074 0-82 36.926-82 82 0 25.812 12.123 48.936 30.938 64H128L32 480h448l-96-288h-76.938C325.877 176.936 338 153.812 338 128c0-45.074-36.926-82-82-82zm0 36c25.618 0 46 20.382 46 46s-20.382 46-46 46-46-20.382-46-46 20.382-46 46-46z',
|
||||
},
|
||||
'weightless': {
|
||||
name: 'weightless',
|
||||
shape: 'M470.72 20L368.186 49.813l41.563-28.094c-26.254 5.922-59.36 17.502-100.97 36.186l-67.874 70.78L264.97 79.25c-23.247 12.958-47.95 29.99-71.814 49.844l-15.78 64.312L174 145.844c-23.55 21.548-45.624 45.6-63.875 70.812-19.25 26.59-34.28 54.506-41.813 82.438L40.19 280.28c6.138 19.613 11.892 39.232 22.906 58.845.032 1.468.1 2.944.187 4.406L29.657 333.19c11.227 18.284 23.577 35.893 43 49.125.45 1.003.953 1.973 1.438 2.968-11.838 33.33-20.568 67.004-26.53 101.69l18.405 3.155c4.952-28.808 11.836-56.842 20.905-84.563.04.053.084.105.125.157 44.277-156.11 142.813-266.846 287.03-324l6.876 17.374c-129.048 51.143-219.303 145.15-265.78 279.062 18.106.102 35.796-2.088 52.218-6.22l4.875-60.967 13.093 55.5c10.84-3.922 20.88-8.762 29.812-14.376l-20.688-43.47 32.782 34.813c7.944-6.468 14.613-13.678 19.624-21.53 30.308-47.507 62.195-94.728 124.75-134.188l-45.72-16.25 70.157 2.124c2.044-1.085 4.087-2.18 6.19-3.25 9.087-4.63 17.916-10.182 26.31-16.375L378.814 150l74.718-17.625c5.788-5.81 11.174-11.836 16.033-17.97 17.384-21.94 29.034-44.784 26.28-65.56-1.376-10.39-7.556-20.154-17.624-25.626-2.333-1.27-4.832-2.337-7.5-3.22zM106.25 406c-.89 3.06-1.778 6.122-2.625 9.22l2.625-9.22z'
|
||||
}
|
||||
});
|
||||
|
||||
export default SVG_ICONS;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import ArrayNode from '/imports/parser/parseTree/ArrayNode.js';
|
||||
|
||||
export default {
|
||||
'abs': {
|
||||
comment: 'Returns the absolute value of a number',
|
||||
@@ -5,7 +7,7 @@ export default {
|
||||
{input: 'abs(9)', result: '9'},
|
||||
{input: 'abs(-3)', result: '3'},
|
||||
],
|
||||
argumentType: 'number',
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
fn: Math.abs,
|
||||
},
|
||||
@@ -15,21 +17,21 @@ export default {
|
||||
{input: 'sqrt(16)', result: '4'},
|
||||
{input: 'sqrt(10)', result: '3.1622776601683795'},
|
||||
],
|
||||
argumentType: 'number',
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
fn: Math.sqrt,
|
||||
},
|
||||
'max': {
|
||||
comment: 'Returns the largest of the given numbers',
|
||||
examples: [{input: 'min(12, 6, 3, 168)', result: '168'}],
|
||||
argumentType: 'number',
|
||||
examples: [{input: 'max(12, 6, 3, 168)', result: '168'}],
|
||||
arguments: anyNumberOf('number'),
|
||||
resultType: 'number',
|
||||
fn: Math.max,
|
||||
},
|
||||
'min': {
|
||||
comment: 'Returns the smallest of the given numbers',
|
||||
examples: [{input: 'min(12, 6, 3, 168)', result: '3'}],
|
||||
argumentType: 'number',
|
||||
arguments: anyNumberOf('number'),
|
||||
resultType: 'number',
|
||||
fn: Math.min,
|
||||
},
|
||||
@@ -40,7 +42,7 @@ export default {
|
||||
{input: 'round(5.5)', result: '6'},
|
||||
{input: 'round(5.05)', result: '5'},
|
||||
],
|
||||
argumentType: 'number',
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
fn: Math.round,
|
||||
},
|
||||
@@ -52,7 +54,7 @@ export default {
|
||||
{input: 'floor(5)', result: '5'},
|
||||
{input: 'floor(-5.5)', result: '-6'},
|
||||
],
|
||||
argumentType: 'number',
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
fn: Math.floor,
|
||||
},
|
||||
@@ -64,7 +66,7 @@ export default {
|
||||
{input: 'ceil(5)', result: '5'},
|
||||
{input: 'ceil(-5.5)', result: '-5'},
|
||||
],
|
||||
argumentType: 'number',
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
fn: Math.ceil,
|
||||
},
|
||||
@@ -76,7 +78,7 @@ export default {
|
||||
{input: 'trunc(5)', result: '5'},
|
||||
{input: 'trunc(-5.5)', result: '-5'},
|
||||
],
|
||||
argumentType: 'number',
|
||||
arguments:[ 'number'],
|
||||
resultType: 'number',
|
||||
fn: Math.trunc,
|
||||
},
|
||||
@@ -87,8 +89,32 @@ export default {
|
||||
{input: 'sign(3)', result: '1'},
|
||||
{input: 'sign(0)', result: '0'},
|
||||
],
|
||||
argumentType: 'number',
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
fn: Math.sign,
|
||||
},
|
||||
'tableLookup': {
|
||||
comment: 'Returns the index of the last value in the array that is less than the specified amount',
|
||||
examples: [
|
||||
{input: 'tableLookup([100, 300, 900], 457)', result: '2'},
|
||||
{input: 'tableLookup([100, 300, 900], 23)', result: '0'},
|
||||
{input: 'tableLookup([100, 300, 900, 1200], 900)', result: '3'},
|
||||
{input: 'tableLookup([100, 300], 594)', result: '2'},
|
||||
],
|
||||
arguments: [ArrayNode, 'number'],
|
||||
resultType: 'number',
|
||||
fn: function tableLookup(arrayNode, number){
|
||||
for(let i in arrayNode.values){
|
||||
let node = arrayNode.values[i];
|
||||
if (node.value > number) return i;
|
||||
}
|
||||
return arrayNode.values.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function anyNumberOf(type){
|
||||
let argumentArray = [type];
|
||||
argumentArray.anyLength = true;
|
||||
return argumentArray;
|
||||
}
|
||||
|
||||
@@ -11,40 +11,64 @@ export default class CallNode extends ParseNode {
|
||||
}
|
||||
resolve(fn, scope, context){
|
||||
let func = functions[this.functionName];
|
||||
// Check that the function exists
|
||||
if (!func) return new ErrorNode({
|
||||
node: this,
|
||||
error: `${this.functionName} is not a function`,
|
||||
error: `${this.functionName} is not a supported function`,
|
||||
context,
|
||||
});
|
||||
let args = castArgsToType({fn, scope, context, args: this.args, type: func.argumentType});
|
||||
if (args.failed){
|
||||
if (fn === 'reduce'){
|
||||
|
||||
// Resolve the arguments
|
||||
let resolvedArgs = this.args.map(node => node[fn](scope, context));
|
||||
|
||||
// Check that the arguments match what is expected
|
||||
let checkFailed = this.checkArugments({
|
||||
fn,
|
||||
context,
|
||||
resolvedArgs,
|
||||
argumentsExpected: func.arguments
|
||||
});
|
||||
|
||||
if (checkFailed){
|
||||
if (fn !== 'reduce'){
|
||||
return new ErrorNode({
|
||||
node: this,
|
||||
error: 'Could not convert all arguments to the correct type',
|
||||
context,
|
||||
error: `Invalid arguments to ${this.functionName} function`,
|
||||
});
|
||||
} else {
|
||||
return new CallNode({
|
||||
functionName: this.functionName,
|
||||
args: args,
|
||||
args: resolvedArgs,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
let value = func.fn.apply(null, args);
|
||||
return new ConstantNode({
|
||||
value,
|
||||
type: 'number',
|
||||
previousNodes: [this],
|
||||
});
|
||||
} catch (error) {
|
||||
return new ErrorNode({
|
||||
node: this,
|
||||
error,
|
||||
context,
|
||||
});
|
||||
}
|
||||
|
||||
// Map contant nodes to constants before attempting to run the function
|
||||
let mappedArgs = resolvedArgs.map(node => {
|
||||
if (node instanceof ConstantNode){
|
||||
return node.value;
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// Run the function
|
||||
let value = func.fn.apply(null, mappedArgs);
|
||||
|
||||
let type = typeof value;
|
||||
if (type === 'number' || type === 'string' || type === 'boolean'){
|
||||
// Convert constant results into constant nodes
|
||||
return new ConstantNode({ value, type });
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
} catch (error) {
|
||||
return new ErrorNode({
|
||||
node: this,
|
||||
error: error.message || error,
|
||||
context,
|
||||
});
|
||||
}
|
||||
}
|
||||
toString(){
|
||||
@@ -57,20 +81,47 @@ export default class CallNode extends ParseNode {
|
||||
replaceChildren(fn){
|
||||
this.args = this.args.map(arg => arg.replaceNodes(fn));
|
||||
}
|
||||
}
|
||||
checkArugments({fn, context, argumentsExpected, resolvedArgs}){
|
||||
// Check that the number of arguments matches the number expected
|
||||
if (
|
||||
!argumentsExpected.anyLength &&
|
||||
argumentsExpected.length !== resolvedArgs.length
|
||||
){
|
||||
context.storeError({
|
||||
type: 'error',
|
||||
message: 'Incorrect number of arguments ' +
|
||||
`to ${this.functionName} function, ` +
|
||||
`expected ${argumentsExpected.length} got ${resolvedArgs.length}`
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function castArgsToType({fn, scope, context, args, type}){
|
||||
let resolvedArgs = args.map(node => node[fn](scope, context))
|
||||
let result = [];
|
||||
if (type === 'number'){
|
||||
resolvedArgs.forEach(node => {
|
||||
if (node.isNumber){
|
||||
result.push(node.value);
|
||||
let failed = false;
|
||||
// Check that each argument is of the correct type
|
||||
resolvedArgs.forEach((node, index) => {
|
||||
let type;
|
||||
if (argumentsExpected.anyLength){
|
||||
type = argumentsExpected[0];
|
||||
} else {
|
||||
resolvedArgs.failed = true;
|
||||
type = argumentsExpected[index];
|
||||
}
|
||||
})
|
||||
if (typeof type === 'string'){
|
||||
// Type being a string means a constant node with matching type
|
||||
if (node.type !== type) failed = true;
|
||||
} else {
|
||||
// Otherwise check that the node is an instance of the given type
|
||||
if (!(node instanceof type)) failed = true;
|
||||
}
|
||||
if (failed && fn === 'reduce'){
|
||||
let typeName = typeof type === 'string' ? type : type.constructor.name;
|
||||
let nodeName = node.type || node.constructor.name
|
||||
context.storeError({
|
||||
type: 'error',
|
||||
message: `Incorrect arguments to ${this.functionName} function` +
|
||||
`expected ${typeName} got ${nodeName}`
|
||||
});
|
||||
}
|
||||
});
|
||||
return failed;
|
||||
}
|
||||
if (resolvedArgs.failed) return resolvedArgs;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
|
||||
import ArrayNode from '/imports/parser/parseTree/ArrayNode.js';
|
||||
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
|
||||
|
||||
export default class IndexNode extends ParseNode {
|
||||
constructor({array, index}) {
|
||||
@@ -8,16 +10,42 @@ export default class IndexNode extends ParseNode {
|
||||
}
|
||||
resolve(fn, scope, context){
|
||||
let index = this.index[fn](scope, context);
|
||||
if (index.isInteger){
|
||||
let selection = this.array.values[index.value - 1];
|
||||
let array = this.array[fn](scope, context);
|
||||
|
||||
if (index.isInteger && array instanceof ArrayNode){
|
||||
if (index.value < 1 || index.value > array.values.length){
|
||||
if (context){
|
||||
context.storeError({
|
||||
type: 'warning',
|
||||
message: `Index of ${index.value} is out of range for an array` +
|
||||
` of length ${array.values.length}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
let selection = array.values[index.value - 1];
|
||||
if (selection){
|
||||
let result = selection[fn](scope, context);
|
||||
return result;
|
||||
}
|
||||
} else if (fn === 'reduce'){
|
||||
if (!(array instanceof ArrayNode)){
|
||||
return new ErrorNode({
|
||||
node: this,
|
||||
error: 'Can not get the index of a non-array node: ' +
|
||||
this.array.toString() + ' = ' + array.toString(),
|
||||
context,
|
||||
});
|
||||
} else if (!index.isInteger){
|
||||
return new ErrorNode({
|
||||
node: this,
|
||||
error: array.toString() + ' is not an integer index of the array',
|
||||
context,
|
||||
});
|
||||
}
|
||||
}
|
||||
return new IndexNode({
|
||||
index,
|
||||
array: this.array[fn](scope, context),
|
||||
array,
|
||||
previousNodes: [this],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export default class ParenthesisNode extends ParseNode {
|
||||
resolve(fn, scope, context){
|
||||
let content = this.content[fn](scope, context);
|
||||
if (
|
||||
fn === 'reduce' ||
|
||||
content.constructor.name === 'ConstantNode' ||
|
||||
content.constructor.name === 'ErrorNode'
|
||||
){
|
||||
|
||||
@@ -10,7 +10,6 @@ export default function sendWebhook({webhookURL, data = {}}){
|
||||
|
||||
const hook = new Discord.WebhookClient(id, token);
|
||||
// Send a message using the webhook
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
hook.send(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,3 +7,4 @@ import '/imports/server/publications/users.js';
|
||||
import '/imports/server/publications/icons.js';
|
||||
import '/imports/server/publications/tabletops.js';
|
||||
import '/imports/server/publications/slotFillers.js'
|
||||
import '/imports/server/publications/ownedDocuments.js'
|
||||
|
||||
@@ -25,31 +25,48 @@ Meteor.publish('libraries', function(){
|
||||
{owner: this.userId},
|
||||
{writers: this.userId},
|
||||
{readers: this.userId},
|
||||
{_id: {$in: subs}},
|
||||
{ _id: {$in: subs}, public: true },
|
||||
]
|
||||
}, {
|
||||
sort: {name: 1}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.publish('library', function(libraryId){
|
||||
if (!libraryId) return [];
|
||||
libraryIdSchema.validate({libraryId});
|
||||
this.autorun(function (){
|
||||
let userId = this.userId;
|
||||
let library = Libraries.findOne(libraryId);
|
||||
try { assertViewPermission(library, userId) }
|
||||
catch(e){
|
||||
return this.error(e);
|
||||
}
|
||||
return Libraries.find({
|
||||
_id: libraryId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let libraryIdSchema = new SimpleSchema({
|
||||
libraryId: {
|
||||
libraryId:{
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.publish('library', function(libraryId){
|
||||
Meteor.publish('libraryNodes', function(libraryId){
|
||||
if (!libraryId) return [];
|
||||
libraryIdSchema.validate({libraryId});
|
||||
this.autorun(function (){
|
||||
let userId = this.userId;
|
||||
let libraryCursor = Libraries.find({
|
||||
_id: libraryId,
|
||||
});
|
||||
let library = libraryCursor.fetch()[0];
|
||||
let library = Libraries.findOne(libraryId);
|
||||
try { assertViewPermission(library, userId) }
|
||||
catch(e){ return [] }
|
||||
catch(e){
|
||||
return this.error(e);
|
||||
}
|
||||
return [
|
||||
libraryCursor,
|
||||
LibraryNodes.find({
|
||||
'ancestors.id': libraryId,
|
||||
}, {
|
||||
|
||||
15
app/imports/server/publications/ownedDocuments.js
Normal file
15
app/imports/server/publications/ownedDocuments.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
|
||||
Meteor.publish('ownedDocuments', function(){
|
||||
this.autorun(function (){
|
||||
let userId = this.userId;
|
||||
if (!userId) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
Creatures.find({owner: userId}),
|
||||
Libraries.find({owner: userId}),
|
||||
]
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import { assertViewPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import recomputeInvetory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import VERSION from '/imports/constants/VERSION.js';
|
||||
|
||||
@@ -25,7 +26,10 @@ Meteor.publish('singleCharacter', function(creatureId){
|
||||
try { assertViewPermission(creature, userId) }
|
||||
catch(e){ return [] }
|
||||
if (creature.computeVersion !== VERSION){
|
||||
try { recomputeCreatureById(creatureId) }
|
||||
try {
|
||||
recomputeInvetory(creatureId);
|
||||
recomputeCreatureById(creatureId)
|
||||
}
|
||||
catch(e){ console.error(e) }
|
||||
}
|
||||
return [
|
||||
|
||||
@@ -50,7 +50,7 @@ Meteor.publish('slotFillers', function(slotId){
|
||||
}
|
||||
this.autorun(function(){
|
||||
// Get the limit of the documents the user can fetch
|
||||
var limit = self.data('limit') || 16;
|
||||
var limit = self.data('limit') || 20;
|
||||
check(limit, Number);
|
||||
|
||||
// Get the search term
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0,
|
||||
},
|
||||
open: Boolean,
|
||||
flat: Boolean,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
fab
|
||||
small
|
||||
v-bind="$attrs"
|
||||
:disabled="disabled"
|
||||
:style="disabled ? 'background-color: #616161 !important;' : ''"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<v-icon>{{ icon }}</v-icon>
|
||||
@@ -18,7 +20,7 @@
|
||||
* component creates a v-btn with a label.
|
||||
*/
|
||||
export default {
|
||||
props: ['icon', 'label'],
|
||||
props: ['icon', 'label', 'disabled'],
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<v-layout
|
||||
v-if="editing && model"
|
||||
key="edit-buttons"
|
||||
style="flex-shrink: 0;"
|
||||
>
|
||||
<v-spacer />
|
||||
<color-picker
|
||||
|
||||
104
app/imports/ui/creature/character/CharacterSheetFab.vue
Normal file
104
app/imports/ui/creature/character/CharacterSheetFab.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template lang="html">
|
||||
<v-speed-dial
|
||||
v-if="speedDials && speedDials.length"
|
||||
v-model="fab"
|
||||
direction="bottom"
|
||||
>
|
||||
<template #activator>
|
||||
<v-btn
|
||||
v-model="fab"
|
||||
color="primary"
|
||||
fab
|
||||
data-id="insert-creature-property-fab"
|
||||
small
|
||||
>
|
||||
<v-icon>add</v-icon>
|
||||
<v-icon>close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<labeled-fab
|
||||
v-for="type in speedDials"
|
||||
:key="type"
|
||||
color="primary"
|
||||
:data-id="`insert-creature-property-type-${type}`"
|
||||
:label="'New ' + properties[type].name"
|
||||
:icon="properties[type].icon"
|
||||
:disabled="!editPermission"
|
||||
@click="insertPropertyOfType(type)"
|
||||
/>
|
||||
</v-speed-dial>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LabeledFab from '/imports/ui/components/LabeledFab.vue';
|
||||
import { setDocToLastOrder } from '/imports/api/parenting/order.js';
|
||||
import insertProperty from '/imports/api/creature/creatureProperties/methods/insertProperty.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||
|
||||
const tabs = [
|
||||
'stats',
|
||||
'features',
|
||||
'inventory',
|
||||
'spells',
|
||||
'character',
|
||||
'tree',
|
||||
];
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LabeledFab,
|
||||
},
|
||||
props: {
|
||||
editPermission: Boolean,
|
||||
},
|
||||
data(){return {
|
||||
fab: false,
|
||||
};},
|
||||
computed: {
|
||||
creatureId(){
|
||||
return this.$route.params.id;
|
||||
},
|
||||
tabNumber(){
|
||||
return this.$store.getters.tabById(this.creatureId);
|
||||
},
|
||||
speedDials(){
|
||||
return this.speedDialsByTab[tabs[this.tabNumber]];
|
||||
},
|
||||
speedDialsByTab() { return {
|
||||
'stats': ['attribute', 'skill', 'action', 'attack'],
|
||||
'features': ['feature'],
|
||||
'inventory': ['item', 'container'],
|
||||
'spells': ['spellList', 'spell'],
|
||||
'character': ['note'],
|
||||
};},
|
||||
properties(){
|
||||
return PROPERTIES;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
insertPropertyOfType(type){
|
||||
let that = this;
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-creation-dialog',
|
||||
elementId: 'insert-creature-property-type-' + type,
|
||||
data: {
|
||||
forcedType: type,
|
||||
},
|
||||
callback(creatureProperty){
|
||||
if (!creatureProperty) return;
|
||||
creatureProperty.parent = {collection: 'creatures', id: that.creatureId};
|
||||
creatureProperty.ancestors = [ {collection: 'creatures', id: that.creatureId}];
|
||||
setDocToLastOrder({collection: CreatureProperties, doc: creatureProperty});
|
||||
let id = insertProperty.call({creatureProperty});
|
||||
return id;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -5,15 +5,15 @@
|
||||
right
|
||||
clipped
|
||||
>
|
||||
<log-tab :creature-id="$route.params.id" />
|
||||
<character-log :creature-id="$route.params.id" />
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LogTab from '/imports/ui/log/CharacterLog.vue';
|
||||
import CharacterLog from '/imports/ui/log/CharacterLog.vue';
|
||||
export default {
|
||||
components: {
|
||||
LogTab,
|
||||
CharacterLog,
|
||||
},
|
||||
computed: {
|
||||
drawer: {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
:color="toolbarColor"
|
||||
:dark="isDark"
|
||||
:light="!isDark"
|
||||
extended
|
||||
tabs
|
||||
dense
|
||||
>
|
||||
@@ -73,11 +74,12 @@
|
||||
>
|
||||
<div
|
||||
:key="$route.meta.title"
|
||||
style="width: 100%"
|
||||
class="layout row"
|
||||
>
|
||||
<v-tabs
|
||||
v-if="creature"
|
||||
slot="extension"
|
||||
class="flex"
|
||||
style="min-width: 0"
|
||||
centered
|
||||
grow
|
||||
max="100px"
|
||||
@@ -106,6 +108,11 @@
|
||||
Tree
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<v-spacer />
|
||||
<character-sheet-fab
|
||||
class="character-sheet-fab"
|
||||
:edit-permission="editPermission"
|
||||
/>
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
</v-toolbar>
|
||||
@@ -119,8 +126,15 @@ import { theme } from '/imports/ui/theme.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import { updateUserSharePermissions } from '/imports/api/sharing/sharing.js';
|
||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||
import CharacterSheetFab from '/imports/ui/creature/character/CharacterSheetFab.vue';
|
||||
|
||||
export default {
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
components: {
|
||||
CharacterSheetFab,
|
||||
},
|
||||
data(){return {
|
||||
theme,
|
||||
}},
|
||||
@@ -231,4 +245,8 @@ export default {
|
||||
.character-sheet-toolbar .v-tabs__bar {
|
||||
background: none !important;
|
||||
}
|
||||
.character-sheet-fab {
|
||||
bottom: -24px;
|
||||
margin-right: -8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
>
|
||||
Level {{ creature.variables.level.value }}
|
||||
</v-card-title>
|
||||
<v-list>
|
||||
<v-list two-line>
|
||||
<v-list-tile>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title
|
||||
@@ -52,7 +52,14 @@
|
||||
>
|
||||
{{ creature.variables.milestoneLevels.value }} Milestone levels
|
||||
</v-list-tile-title>
|
||||
<v-list-tile-title v-else>
|
||||
<v-list-tile-title
|
||||
v-if="
|
||||
!(creature.variables.milestoneLevels &&
|
||||
creature.variables.milestoneLevels.value) ||
|
||||
(creature.variables.xp &&
|
||||
creature.variables.xp.value)
|
||||
"
|
||||
>
|
||||
{{
|
||||
creature.variables.xp &&
|
||||
creature.variables.xp.value ||
|
||||
|
||||
@@ -1,6 +1,59 @@
|
||||
<template lang="html">
|
||||
<div class="inventory">
|
||||
<column-layout wide-columns>
|
||||
<div>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-tile>
|
||||
<v-list-tile-avatar>
|
||||
<v-icon>$vuetify.icons.injustice</v-icon>
|
||||
</v-list-tile-avatar>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
Weight Carried
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
<v-list-tile-action>
|
||||
<v-list-tile-title>
|
||||
{{ creature.denormalizedStats.weightCarried || 0 }} lb
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-action>
|
||||
</v-list-tile>
|
||||
<v-list-tile>
|
||||
<v-list-tile-avatar>
|
||||
<v-icon>$vuetify.icons.cash</v-icon>
|
||||
</v-list-tile-avatar>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
Net worth
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
<v-list-tile-action>
|
||||
<v-list-tile-title>
|
||||
<coin-value
|
||||
:value="creature.denormalizedStats.valueTotal || 0"
|
||||
/>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-action>
|
||||
</v-list-tile>
|
||||
<v-list-tile v-if="creature.denormalizedStats.itemsAttuned">
|
||||
<v-list-tile-avatar>
|
||||
<v-icon>$vuetify.icons.spell</v-icon>
|
||||
</v-list-tile-avatar>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
Items attuned
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
<v-list-tile-action>
|
||||
<v-list-tile-title>
|
||||
{{ creature.denormalizedStats.itemsAttuned }}
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-action>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</div>
|
||||
<div>
|
||||
<toolbar-card
|
||||
:color="creature.color"
|
||||
@@ -53,6 +106,7 @@ import ToolbarCard from '/imports/ui/components/ToolbarCard.vue';
|
||||
import ItemList from '/imports/ui/properties/components/inventory/ItemList.vue';
|
||||
import { getParentRefByTag } from '/imports/api/creature/creatureProperties/methods/equipItem.js';
|
||||
import INVENTORY_TAGS from '/imports/constants/INVENTORY_TAGS.js';
|
||||
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -60,6 +114,7 @@ export default {
|
||||
ContainerCard,
|
||||
ToolbarCard,
|
||||
ItemList,
|
||||
CoinValue,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
@@ -82,7 +137,10 @@ export default {
|
||||
});
|
||||
},
|
||||
creature(){
|
||||
return Creatures.findOne(this.creatureId, {fields: {color: 1}});
|
||||
return Creatures.findOne(this.creatureId, {fields: {
|
||||
color: 1,
|
||||
denormalizedStats: 1,
|
||||
}});
|
||||
},
|
||||
containersWithoutAncestorContainers(){
|
||||
return CreatureProperties.find({
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<template lang="html">
|
||||
<selectable-property-dialog v-model="type">
|
||||
<creature-property-insert-form
|
||||
:type="type"
|
||||
:property-name="getPropertyName(type)"
|
||||
@back="type = undefined"
|
||||
/>
|
||||
</selectable-property-dialog>
|
||||
<selectable-property-dialog
|
||||
:value="forcedType || type"
|
||||
@input="e => type = e"
|
||||
>
|
||||
<creature-property-insert-form
|
||||
:type="forcedType || type"
|
||||
:property-name="getPropertyName(forcedType || type)"
|
||||
@back="back"
|
||||
/>
|
||||
</selectable-property-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -14,15 +17,28 @@ import CreaturePropertyInsertForm from '/imports/ui/creature/creatureProperties/
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
|
||||
export default {
|
||||
data() { return {
|
||||
type: undefined,
|
||||
};},
|
||||
components: {
|
||||
SelectablePropertyDialog,
|
||||
CreaturePropertyInsertForm,
|
||||
},
|
||||
props: {
|
||||
forcedType: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data() { return {
|
||||
type: undefined,
|
||||
};},
|
||||
methods: {
|
||||
getPropertyName,
|
||||
back(){
|
||||
if (this.forcedType){
|
||||
this.$store.dispatch('popDialogStack');
|
||||
} else {
|
||||
this.type = undefined;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -70,14 +70,22 @@ export default {
|
||||
};},
|
||||
watch: {
|
||||
type(newType){
|
||||
if (!newType) return;
|
||||
this.schema = propertySchemasIndex[newType];
|
||||
this.validationContext = this.schema.newContext();
|
||||
let model = this.schema.clean({});
|
||||
model.type = newType;
|
||||
this.model = model;
|
||||
this.changeType(newType);
|
||||
},
|
||||
},
|
||||
mounted(){
|
||||
this.changeType(this.type);
|
||||
},
|
||||
methods:{
|
||||
changeType(type){
|
||||
if (!type) return;
|
||||
this.schema = propertySchemasIndex[type];
|
||||
this.validationContext = this.schema.newContext();
|
||||
let model = this.schema.clean({});
|
||||
model.type = type;
|
||||
this.model = model;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -143,10 +143,10 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
import { parse, CompilationContext } from '/imports/parser/parser.js';
|
||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue'
|
||||
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -196,7 +196,7 @@ export default {
|
||||
},
|
||||
loadMore(){
|
||||
if (this.currentLimit >= this.countAll) return;
|
||||
this._subs['slotFillers'].setData('limit', this.currentLimit + 16);
|
||||
this._subs['slotFillers'].setData('limit', this.currentLimit + 20);
|
||||
},
|
||||
insert(){
|
||||
if (!this.selectedNode) return;
|
||||
@@ -243,15 +243,12 @@ export default {
|
||||
// the quantity to fill
|
||||
nodes = nodes.filter(node => {
|
||||
if (node.slotFillerCondition){
|
||||
let context = new CompilationContext();
|
||||
let conditionResult;
|
||||
try {
|
||||
conditionResult = parse(node.slotFillerCondition)
|
||||
.reduce(this.creature.variables, context);
|
||||
} catch (e){
|
||||
console.warn(e);
|
||||
}
|
||||
if (conditionResult && !conditionResult.value) return false;
|
||||
let {result} = evaluateString({
|
||||
string: node.slotFillerCondition,
|
||||
scope: this.creature.variables,
|
||||
fn: 'reduce',
|
||||
});
|
||||
if (!result.value) return false;
|
||||
}
|
||||
if (
|
||||
node.type === 'slotFiller' &&
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<h3 class="layout row align-center">
|
||||
{{ slot.name }}
|
||||
<v-spacer />
|
||||
<span v-if="slot.quantityExpected > 1">
|
||||
{{ slot.totalFilled }} / {{ slot.quantityExpected }}
|
||||
<span v-if="slot.quantityExpectedResult > 1">
|
||||
{{ slot.totalFilled }} / {{ slot.quantityExpectedResult }}
|
||||
</span>
|
||||
</h3>
|
||||
<v-list v-if="slot.children.length">
|
||||
@@ -38,7 +38,7 @@
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
<v-btn
|
||||
v-if="!slot.quantityExpected || slot.spaceLeft"
|
||||
v-if="!slot.quantityExpectedResult || slot.spaceLeft"
|
||||
icon
|
||||
:data-id="`slot-add-button-${slot._id}`"
|
||||
class="slot-add-button"
|
||||
@@ -130,7 +130,7 @@ export default {
|
||||
}).map(slot => {
|
||||
if (
|
||||
!this.showHiddenSlots &&
|
||||
slot.quantityExpected === 0 &&
|
||||
slot.quantityExpectedResult === 0 &&
|
||||
slot.hideWhenFull
|
||||
){
|
||||
slot.children = []
|
||||
@@ -144,11 +144,12 @@ export default {
|
||||
}
|
||||
return slot;
|
||||
}).filter(slot => !( // Hide full and ignored slots
|
||||
!this.showHiddenSlots &&
|
||||
slot.hideWhenFull &&
|
||||
slot.quantityExpected > 0 &&
|
||||
slot.totalFilled >= slot.quantityExpected ||
|
||||
slot.ignored
|
||||
!this.showHiddenSlots && (
|
||||
slot.hideWhenFull &&
|
||||
slot.quantityExpectedResult > 0 &&
|
||||
slot.spaceLeft <= 0 ||
|
||||
slot.ignored
|
||||
)
|
||||
));
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import CreaturePropertyCreationDialog from '/imports/ui/creature/creaturePropert
|
||||
import CreaturePropertyDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue'
|
||||
import CreaturePropertyFromLibraryDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue'
|
||||
import DeleteConfirmationDialog from '/imports/ui/dialogStack/DeleteConfirmationDialog.vue';
|
||||
import DeleteUserAccountDialog from '/imports/ui/user/DeleteUserAccountDialog.vue';
|
||||
import ExperienceInsertDialog from '/imports/ui/creature/experiences/ExperienceInsertDialog.vue';
|
||||
import ExperienceListDialog from '/imports/ui/creature/experiences/ExperienceListDialog.vue';
|
||||
import InviteDialog from '/imports/ui/user/InviteDialog.vue';
|
||||
@@ -26,6 +27,7 @@ export default {
|
||||
CreaturePropertyDialog,
|
||||
CreaturePropertyFromLibraryDialog,
|
||||
DeleteConfirmationDialog,
|
||||
DeleteUserAccountDialog,
|
||||
ExperienceInsertDialog,
|
||||
ExperienceListDialog,
|
||||
InviteDialog,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
name="toolbar"
|
||||
/>
|
||||
<v-toolbar
|
||||
v-if="!$route.matched[0].components.toolbar"
|
||||
v-if="!$route.matched[0] || !$route.matched[0].components.toolbar"
|
||||
app
|
||||
color="secondary"
|
||||
dark
|
||||
|
||||
@@ -19,13 +19,27 @@
|
||||
>
|
||||
<v-spacer />
|
||||
<v-switch
|
||||
v-if="!libraryId || canEditLibrary"
|
||||
v-model="organize"
|
||||
label="Organize"
|
||||
class="mx-3"
|
||||
style="flex-grow: 0; height: 32px;"
|
||||
/>
|
||||
</v-toolbar>
|
||||
<div
|
||||
v-if="libraryId"
|
||||
style="width: 100%; height: 100%; overflow: auto;"
|
||||
>
|
||||
<library-contents-container
|
||||
:library-id="libraryId"
|
||||
:organize-mode="organize"
|
||||
:selected-node-id="selected"
|
||||
should-subscribe
|
||||
@selected="clickNode"
|
||||
/>
|
||||
</div>
|
||||
<library-browser
|
||||
v-else
|
||||
edit-mode
|
||||
:organize-mode="organize"
|
||||
:selected-node-id="selected"
|
||||
@@ -53,17 +67,24 @@ import LibraryBrowser from '/imports/ui/library/LibraryBrowser.vue';
|
||||
import LibraryNodeDialog from '/imports/ui/library/LibraryNodeDialog.vue';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import LibraryContentsContainer from '/imports/ui/library/LibraryContentsContainer.vue';
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeDetailLayout,
|
||||
LibraryBrowser,
|
||||
LibraryNodeDialog,
|
||||
LibraryContentsContainer,
|
||||
},
|
||||
props: {
|
||||
selection: Boolean,
|
||||
libraryId: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data(){ return {
|
||||
organize: false,
|
||||
@@ -113,13 +134,33 @@ export default {
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'libraries': [],
|
||||
'library'(){
|
||||
if (this.libraryId){
|
||||
return [this.libraryId]
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
},
|
||||
libraries(){
|
||||
return Libraries.find({}, {
|
||||
sort: {name: 1}
|
||||
}).fetch();
|
||||
},
|
||||
library(){
|
||||
let libraryId = this.libraryId;
|
||||
if (!libraryId) return;
|
||||
return Libraries.findOne(libraryId);
|
||||
},
|
||||
canEditLibrary(){
|
||||
if (!this.libraryId) return;
|
||||
try {
|
||||
assertEditPermission(this.library, Meteor.userId());
|
||||
return true;
|
||||
} catch (e){
|
||||
return false;
|
||||
}
|
||||
},
|
||||
selectedNode(){
|
||||
return LibraryNodes.findOne({
|
||||
_id: this.selected,
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
"
|
||||
>
|
||||
<v-expansion-panel
|
||||
v-model="expandedLibrary"
|
||||
style="box-shadow: none;"
|
||||
expand
|
||||
>
|
||||
<v-expansion-panel-content
|
||||
v-for="library in libraries"
|
||||
v-for="(library, index) in libraries"
|
||||
:key="library._id"
|
||||
v-model="expandedLibrary[index]"
|
||||
lazy
|
||||
:data-id="library._id"
|
||||
>
|
||||
@@ -24,9 +24,10 @@
|
||||
<v-card flat>
|
||||
<library-contents-container
|
||||
:library-id="library._id"
|
||||
:organize-mode="organizeMode"
|
||||
:organize-mode="organizeMode && editPermission(library)"
|
||||
:edit-mode="editMode"
|
||||
:selected-node-id="selectedNodeId"
|
||||
:should-subscribe="expandedLibrary[index]"
|
||||
@selected="e => $emit('selected', e)"
|
||||
/>
|
||||
<v-card-actions>
|
||||
@@ -47,16 +48,16 @@
|
||||
small
|
||||
icon
|
||||
:disabled="!editPermission(library)"
|
||||
@click="editLibrary(library._id)"
|
||||
@click="$router.push(`/library/${library._id}`)"
|
||||
>
|
||||
<v-icon>create</v-icon>
|
||||
<v-icon>arrow_forward</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
<v-btn
|
||||
v-show="expandedLibrary === null"
|
||||
v-show="noLibrariesExpanded"
|
||||
v-if="editMode"
|
||||
flat
|
||||
color="primary"
|
||||
@@ -86,11 +87,25 @@ export default {
|
||||
props: {
|
||||
organizeMode: Boolean,
|
||||
editMode: Boolean,
|
||||
selectedNodeId: String,
|
||||
selectedNodeId: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data(){ return {
|
||||
expandedLibrary: null,
|
||||
expandedLibrary: [],
|
||||
expandedLibraryContent: [],
|
||||
};},
|
||||
computed: {
|
||||
noLibrariesExpanded(){
|
||||
if (!this.expandedLibrary) return true;
|
||||
let noneExpanded = true;
|
||||
this.expandedLibrary.forEach(lib => {
|
||||
if(lib) noneExpanded = false;
|
||||
});
|
||||
return noneExpanded;
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'libraries': [],
|
||||
@@ -106,6 +121,7 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
log: console.log,
|
||||
insertLibrary(){
|
||||
if (this.paidBenefits){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
|
||||
@@ -1,13 +1,30 @@
|
||||
<template lang="html">
|
||||
<tree-node-list
|
||||
group="library"
|
||||
:children="libraryChildren"
|
||||
:organize="organizeMode"
|
||||
:selected-node-id="selectedNodeId"
|
||||
@selected="e => $emit('selected', e)"
|
||||
@reordered="reordered"
|
||||
@reorganized="reorganized"
|
||||
/>
|
||||
<v-fade-transition
|
||||
hide-on-leave
|
||||
>
|
||||
<tree-node-list
|
||||
v-if="slowShouldSubscribe && $subReady.libraryNodes"
|
||||
group="library"
|
||||
:children="libraryChildren"
|
||||
:organize="organizeMode"
|
||||
:selected-node-id="selectedNodeId"
|
||||
@selected="e => $emit('selected', e)"
|
||||
@reordered="reordered"
|
||||
@reorganized="reorganized"
|
||||
/>
|
||||
<v-layout
|
||||
v-else
|
||||
row
|
||||
align-center
|
||||
justify-center
|
||||
style="width: 100%;"
|
||||
>
|
||||
<v-progress-circular
|
||||
color="primary"
|
||||
:indeterminate="slowShouldSubscribe"
|
||||
/>
|
||||
</v-layout>
|
||||
</v-fade-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -25,13 +42,36 @@
|
||||
libraryId: String,
|
||||
organizeMode: Boolean,
|
||||
selectedNodeId: String,
|
||||
shouldSubscribe: Boolean,
|
||||
},
|
||||
data(){return {
|
||||
slowShouldSubscribe: this.shouldSubscribe,
|
||||
};},
|
||||
watch:{
|
||||
shouldSubscribe(newValue){
|
||||
if (this.timeoutId){
|
||||
clearTimeout(this.timeoutId);
|
||||
delete this.timeoutId;
|
||||
}
|
||||
if (newValue){
|
||||
this.slowShouldSubscribe = newValue
|
||||
} else {
|
||||
this.timeoutId = setTimeout(()=>{
|
||||
this.slowShouldSubscribe = newValue
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'library'(){
|
||||
return [this.libraryId]
|
||||
},
|
||||
},
|
||||
$subscribe: {
|
||||
'libraryNodes'(){
|
||||
if (this.slowShouldSubscribe){
|
||||
return [this.libraryId];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
library(){
|
||||
return Libraries.findOne(this.libraryId);
|
||||
},
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="layout row"
|
||||
style="background-color: inherit;"
|
||||
>
|
||||
<div
|
||||
class="layout column"
|
||||
style="
|
||||
background-color: inherit;
|
||||
width: initial;
|
||||
max-width: 100%;
|
||||
min-width: 320px;
|
||||
"
|
||||
>
|
||||
<v-toolbar
|
||||
dense
|
||||
flat
|
||||
>
|
||||
<v-spacer />
|
||||
<v-switch
|
||||
v-model="organize"
|
||||
label="Organize"
|
||||
class="mx-3"
|
||||
style="flex-grow: 0; height: 32px;"
|
||||
/>
|
||||
</v-toolbar>
|
||||
<library-contents-container
|
||||
:library-id="$route.params.id"
|
||||
:organize-mode="organize"
|
||||
:selected-node-id="selected"
|
||||
@selected="e => selected = e"
|
||||
/>
|
||||
</div>
|
||||
<v-divider vertical />
|
||||
<div
|
||||
style="width: 100%; background-color: inherit;"
|
||||
data-id="selected-node-card"
|
||||
>
|
||||
<v-toolbar
|
||||
dense
|
||||
flat
|
||||
>
|
||||
<property-icon
|
||||
:model="selectedNode"
|
||||
class="mr-2"
|
||||
/>
|
||||
<div class="title">
|
||||
{{ getPropertyName(selectedNode && selectedNode.type) }}
|
||||
</div>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="selectedNode"
|
||||
flat
|
||||
icon
|
||||
@click="editLibraryNode"
|
||||
>
|
||||
<v-icon>create</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-card-text style="overflow-y: auto;">
|
||||
<property-viewer :model="selectedNode" />
|
||||
</v-card-text>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PropertyViewer from '/imports/ui/properties/shared/PropertyViewer.vue';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
import LibraryContentsContainer from '/imports/ui/library/LibraryContentsContainer.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LibraryContentsContainer,
|
||||
PropertyViewer,
|
||||
PropertyIcon,
|
||||
},
|
||||
data(){ return {
|
||||
organize: false,
|
||||
selected: undefined,
|
||||
};},
|
||||
watch:{
|
||||
selectedNode(val){
|
||||
this.$emit('selected', val)
|
||||
},
|
||||
'library.name'(value){
|
||||
this.$store.commit('setPageTitle', value || 'Library');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
editLibraryNode(){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'library-node-edit-dialog',
|
||||
elementId: 'selected-node-card',
|
||||
data: {_id: this.selected},
|
||||
});
|
||||
},
|
||||
getPropertyName,
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'libraries': [],
|
||||
},
|
||||
library(){
|
||||
return Libraries.findOne(this.$route.params.id);
|
||||
},
|
||||
selectedNode(){
|
||||
return LibraryNodes.findOne({
|
||||
_id: this.selected,
|
||||
removed: {$ne: true}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -1,28 +1,53 @@
|
||||
<template lang="html">
|
||||
<v-toolbar-items>
|
||||
<v-btn
|
||||
v-if="showSubscribeButton"
|
||||
flat
|
||||
:loading="loading"
|
||||
@click="subscribe(!subscribed)"
|
||||
>
|
||||
{{ subscribed ? 'Unsubscribe' : 'Subscribe' }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="canEdit"
|
||||
flat
|
||||
icon
|
||||
data-id="library-edit-button"
|
||||
@click="editLibrary(library._id)"
|
||||
>
|
||||
<v-icon>create</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar-items>
|
||||
<v-toolbar
|
||||
app
|
||||
color="secondary"
|
||||
dark
|
||||
tabs
|
||||
extended
|
||||
dense
|
||||
>
|
||||
<v-toolbar-side-icon @click="toggleDrawer" />
|
||||
<v-toolbar-items>
|
||||
<v-btn
|
||||
flat
|
||||
icon
|
||||
@click="$router.push('/library')"
|
||||
>
|
||||
<v-icon>arrow_back</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar-items>
|
||||
<v-toolbar-title>
|
||||
{{ library && library.name }}
|
||||
</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-toolbar-items>
|
||||
<v-btn
|
||||
v-if="showSubscribeButton"
|
||||
flat
|
||||
:loading="loading"
|
||||
@click="subscribe(!subscribed)"
|
||||
>
|
||||
{{ subscribed ? 'Unsubscribe' : 'Subscribe' }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="canEdit"
|
||||
flat
|
||||
icon
|
||||
data-id="library-edit-button"
|
||||
@click="editLibrary(library._id)"
|
||||
>
|
||||
<v-icon>settings</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar-items>
|
||||
</v-toolbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { mapMutations } from 'vuex';
|
||||
|
||||
export default {
|
||||
data(){ return {
|
||||
loading: false,
|
||||
@@ -33,8 +58,10 @@ export default {
|
||||
},
|
||||
subscribed(){
|
||||
let libraryId = this.$route.params.id;
|
||||
let subs = Meteor.user().subscribedLibraries;
|
||||
return subs.includes(libraryId);
|
||||
let user = Meteor.user();
|
||||
if (!user) return false;
|
||||
let subs = user.subscribedLibraries;
|
||||
return subs && subs.includes(libraryId);
|
||||
},
|
||||
showSubscribeButton(){
|
||||
let userId = Meteor.userId();
|
||||
@@ -60,6 +87,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([
|
||||
'toggleDrawer',
|
||||
]),
|
||||
subscribe(value){
|
||||
this.loading = true;
|
||||
Meteor.users.subscribeToLibrary.call({
|
||||
@@ -3,8 +3,10 @@
|
||||
style="height: 100%; overflow: hidden;"
|
||||
class="character-log layout column justify-end"
|
||||
>
|
||||
<div
|
||||
class="log flex layout column reverse align-end pa-3"
|
||||
<v-slide-y-reverse-transition
|
||||
group
|
||||
hide-on-leave
|
||||
class="log-entries flex layout column reverse align-end pa-3"
|
||||
style="overflow: auto;"
|
||||
>
|
||||
<log-entry
|
||||
@@ -12,7 +14,7 @@
|
||||
:key="log._id"
|
||||
:model="log"
|
||||
/>
|
||||
</div>
|
||||
</v-slide-y-reverse-transition>
|
||||
<v-card>
|
||||
<v-text-field
|
||||
v-model="input"
|
||||
@@ -120,4 +122,10 @@ export default {
|
||||
.log-tab p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.theme--dark .log-entries {
|
||||
background: #303030;
|
||||
}
|
||||
.log-entries {
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -141,6 +141,19 @@
|
||||
</template>
|
||||
</v-list>
|
||||
</template>
|
||||
<v-layout
|
||||
row
|
||||
justify-end
|
||||
class="mt-3"
|
||||
>
|
||||
<v-btn
|
||||
color="error"
|
||||
data-id="delete-account-btn"
|
||||
@click="deleteAccount"
|
||||
>
|
||||
Delete Account
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -270,6 +283,12 @@
|
||||
this.updatePatreonLoading = false;
|
||||
if (error) this.updatePatreonError = error;
|
||||
});
|
||||
},
|
||||
deleteAccount(){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'delete-user-account-dialog',
|
||||
elementId: 'delete-account-btn',
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template lang="html">
|
||||
<single-card-layout>
|
||||
<library-and-node />
|
||||
<library-and-node
|
||||
:library-id="$route.params.id"
|
||||
/>
|
||||
</single-card-layout>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<template lang="html">
|
||||
<div>
|
||||
<v-card class="ma-4">
|
||||
<single-library />
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SingleLibrary from '/imports/ui/library/SingleLibrary.vue';
|
||||
export default {
|
||||
components: {
|
||||
SingleLibrary,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -146,7 +146,7 @@ export default {
|
||||
return Math.max(this.model.usesResult, 0);
|
||||
},
|
||||
usesLeft(){
|
||||
return Math.max(this.model.usesResult - this.model.usesUsed, 0);
|
||||
return Math.max(this.model.usesResult - (this.model.usesUsed || 0), 0);
|
||||
},
|
||||
propertyName(){
|
||||
return getPropertyName(this.model.type);
|
||||
|
||||
@@ -9,6 +9,31 @@
|
||||
{{ model.name }}
|
||||
</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-toolbar-title>
|
||||
<v-icon
|
||||
small
|
||||
style="width: 16px;"
|
||||
class="mr-1"
|
||||
>
|
||||
$vuetify.icons.weight
|
||||
</v-icon>
|
||||
{{ (model.contentsWeightless ? 0 : model.contentsWeight || 0) + (model.weight || 0) }}
|
||||
</v-toolbar-title>
|
||||
<v-toolbar-title
|
||||
class="layout row align-center"
|
||||
style="flex-grow: 0;"
|
||||
>
|
||||
<v-icon
|
||||
small
|
||||
style="width: 16px;"
|
||||
class="mr-1"
|
||||
>
|
||||
$vuetify.icons.two_coins
|
||||
</v-icon>
|
||||
<coin-value
|
||||
:value="(model.contentsValue || 0) + (model.value || 0)"
|
||||
/>
|
||||
</v-toolbar-title>
|
||||
</template>
|
||||
<v-card-text class="px-0">
|
||||
<item-list
|
||||
@@ -23,11 +48,13 @@
|
||||
import ToolbarCard from '/imports/ui/components/ToolbarCard.vue';
|
||||
import ItemList from '/imports/ui/properties/components/inventory/ItemList.vue';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ToolbarCard,
|
||||
ItemList,
|
||||
CoinValue,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
|
||||
@@ -15,7 +15,13 @@
|
||||
{{ title }}
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
<v-list-tile-action>
|
||||
<v-list-tile-action
|
||||
v-if="model.attuned"
|
||||
style="min-width: 40px;"
|
||||
>
|
||||
<v-icon>$vuetify.icons.spell</v-icon>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-action style="min-width: 40px;">
|
||||
<increment-button
|
||||
v-if="context.creatureId && model.showIncrement"
|
||||
icon
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template lang="html">
|
||||
<div class="resources-form">
|
||||
<div
|
||||
v-if="model.attributesConsumed.length"
|
||||
v-if="model.attributesConsumed && model.attributesConsumed.length"
|
||||
class="subheading"
|
||||
>
|
||||
Attributes
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
<template lang="html">
|
||||
<div class="roll-form">
|
||||
<div class="layout row wrap">
|
||||
<text-field
|
||||
label="Name"
|
||||
:value="model.name"
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
<text-field
|
||||
label="Variable name"
|
||||
:value="model.variableName"
|
||||
style="flex-basis: 300px;"
|
||||
hint="Use this name in action formulae to refer to the result of this roll"
|
||||
:error-messages="errors.variableName"
|
||||
@change="change('variableName', ...arguments)"
|
||||
/>
|
||||
</div>
|
||||
<text-field
|
||||
ref="focusFirst"
|
||||
label="Roll"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
<text-field
|
||||
ref="focusFirst"
|
||||
label="DC"
|
||||
:value="model.dc"
|
||||
:error-messages="errors.dc"
|
||||
@@ -21,6 +22,15 @@
|
||||
:error-messages="errors.stat"
|
||||
@change="change('stat', ...arguments)"
|
||||
/>
|
||||
<smart-select
|
||||
label="Target"
|
||||
:hint="targetOptionHint"
|
||||
:items="targetOptions"
|
||||
:value="model.target"
|
||||
:error-messages="errors.target"
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
@change="change('target', ...arguments)"
|
||||
/>
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
class="mr-2"
|
||||
@@ -40,5 +50,34 @@ import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormM
|
||||
|
||||
export default {
|
||||
mixins: [saveListMixin, propertyFormMixin],
|
||||
computed: {
|
||||
targetOptions(){
|
||||
return [
|
||||
{
|
||||
text: 'Self',
|
||||
value: 'self',
|
||||
}, {
|
||||
text: 'Roll once for each target',
|
||||
value: 'each',
|
||||
}, {
|
||||
text: 'Roll once and apply to every target',
|
||||
value: 'every',
|
||||
},
|
||||
];
|
||||
},
|
||||
targetOptionHint(){
|
||||
let hints = {
|
||||
self: 'The damage will be applied to the character\'s own attribute when taking the action',
|
||||
target: 'The damage will be applied to the target of the action',
|
||||
each: 'The damage will be rolled separately for each of the targets of the action',
|
||||
every: 'The damage will be rolled once and applied to each of the targets of the action',
|
||||
};
|
||||
if (this.parentTarget === 'singleTarget'){
|
||||
hints.each = hints.target;
|
||||
hints.every = hints.target;
|
||||
}
|
||||
return hints[this.model.target];
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
/>
|
||||
<text-field
|
||||
label="Quantity"
|
||||
type="number"
|
||||
min="0"
|
||||
hint="How many matching properties must be used to fill this slot, 0 is unlimited"
|
||||
:value="model.quantityExpected"
|
||||
:error-messages="errors.quantityExpected"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
@change="change('description', ...arguments)"
|
||||
/>
|
||||
<calculation-error-list :calculations="model.descriptionCalculations" />
|
||||
|
||||
|
||||
<text-field
|
||||
label="Maximum prepared spells"
|
||||
:value="model.maxPrepared"
|
||||
@@ -35,6 +35,24 @@
|
||||
/>
|
||||
<calculation-error-list :errors="model.maxPreparedErrors" />
|
||||
|
||||
<text-field
|
||||
label="Spell save DC"
|
||||
:value="model.dc"
|
||||
hint="The spell save DC of spells in this list"
|
||||
:error-messages="errors.dc"
|
||||
@change="change('dc', ...arguments)"
|
||||
/>
|
||||
<calculation-error-list :errors="model.dcErrors" />
|
||||
|
||||
<text-field
|
||||
label="Attack roll bonus"
|
||||
:value="model.attackRollBonus"
|
||||
hint="The attack roll bonus of spell attacks made by spells in this list"
|
||||
:error-messages="errors.attackRollBonus"
|
||||
@change="change('attackRollBonus', ...arguments)"
|
||||
/>
|
||||
<calculation-error-list :errors="model.attackRollBonusErrors" />
|
||||
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
multiple
|
||||
|
||||
@@ -10,7 +10,7 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted(){
|
||||
if (this.$refs.focusFirst){
|
||||
if (this.$refs.focusFirst && this.$refs.focusFirst.focus){
|
||||
setTimeout(() => this.$refs.focusFirst.focus(), 300);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<v-toolbar-title slot="toolbar">
|
||||
Add Library Content
|
||||
</v-toolbar-title>
|
||||
<property-selector @select="property => $emit('input', property)" />
|
||||
<property-selector @select="type => $emit('input', type)" />
|
||||
</dialog-base>
|
||||
<div
|
||||
v-show="value"
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
<div
|
||||
class="text-no-wrap text-truncate"
|
||||
>
|
||||
{{ model.amountResult }} {{ model.stat }} damage
|
||||
<span v-if="model.amountResult < 0">+</span>
|
||||
{{ absoluteAmount }} {{ model.stat }}
|
||||
<span v-if="typeof absoluteAmount === 'string' || absoluteAmount >= 0">
|
||||
damage
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -20,5 +24,14 @@ import treeNodeViewMixin from '/imports/ui/properties/treeNodeViews/treeNodeView
|
||||
|
||||
export default {
|
||||
mixins: [treeNodeViewMixin],
|
||||
computed: {
|
||||
absoluteAmount(){
|
||||
if (typeof this.model.amountResult === 'number'){
|
||||
return Math.abs(this.model.amountResult);
|
||||
} else {
|
||||
return this.model.amountResult;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -168,7 +168,7 @@ export default {
|
||||
return Math.max(this.model.usesResult, 0);
|
||||
},
|
||||
usesLeft(){
|
||||
return Math.max(this.model.usesResult - this.model.usesUsed, 0);
|
||||
return Math.max(this.model.usesResult - (this.model.usesUsed || 0), 0);
|
||||
},
|
||||
actionTypeIcon() {
|
||||
return `$vuetify.icons.${this.model.actionType}`;
|
||||
|
||||
@@ -1,14 +1,81 @@
|
||||
<template lang="html">
|
||||
<div class="adjustment-viewer layout row align-center">
|
||||
{{ model.amountResult }} {{ model.stat }} damage
|
||||
</div>
|
||||
<v-list-tile class="effect-viewer">
|
||||
<v-list-tile-avatar>
|
||||
<v-tooltip bottom>
|
||||
<template
|
||||
v-if="effectIcon !== 'remove'"
|
||||
#activator="{ on }"
|
||||
>
|
||||
<v-icon
|
||||
class="mx-2"
|
||||
style="cursor: default;"
|
||||
v-on="on"
|
||||
>
|
||||
{{ effectIcon }}
|
||||
</v-icon>
|
||||
</template>
|
||||
<span>{{ tooltip }}</span>
|
||||
</v-tooltip>
|
||||
</v-list-tile-avatar>
|
||||
<v-list-tile-action class="headline">
|
||||
{{ displayedValue }}
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<code>{{ model.stat }}</code>
|
||||
<template v-if="effectIcon === 'remove'">
|
||||
damage
|
||||
</template>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js';
|
||||
import getEffectIcon from '/imports/ui/utility/getEffectIcon.js';
|
||||
|
||||
export default {
|
||||
mixins: [propertyViewerMixin],
|
||||
computed: {
|
||||
effectIcon(){
|
||||
let effectOp = this.model.operation === 'increment' ? 'add' : 'base';
|
||||
let value = this.value;
|
||||
if (typeof value === 'string'){
|
||||
value = 1;
|
||||
}
|
||||
return getEffectIcon(effectOp, -value);
|
||||
},
|
||||
value(){
|
||||
return 'amountResult' in this.model ?
|
||||
this.model.amountResult :
|
||||
this.model.amount;
|
||||
},
|
||||
displayedValue(){
|
||||
if (
|
||||
typeof this.value === 'number' &&
|
||||
this.model.operation !== 'set'
|
||||
){
|
||||
return Math.abs(this.value);
|
||||
} else {
|
||||
return this.value;
|
||||
}
|
||||
},
|
||||
tooltip(){
|
||||
if (this.model.operation === 'increment'){
|
||||
if (
|
||||
typeof this.value === 'string' ||
|
||||
this.value >= 0
|
||||
){
|
||||
return 'Minus';
|
||||
} else {
|
||||
return 'Add'
|
||||
}
|
||||
} else {
|
||||
return 'Set'
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
23
app/imports/ui/properties/viewers/ConstantViewer.vue
Normal file
23
app/imports/ui/properties/viewers/ConstantViewer.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template lang="html">
|
||||
<div class="buff-viewer">
|
||||
<property-name :value="model.name" />
|
||||
<property-variable-name :value="model.variableName" />
|
||||
<property-field
|
||||
name="Calculation"
|
||||
:value="model.calculation"
|
||||
/>
|
||||
<calculation-error-list :errors="model.errors" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
|
||||
import CalculationErrorList from '/imports/ui/properties/forms/shared/CalculationErrorList.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CalculationErrorList,
|
||||
},
|
||||
mixins: [propertyViewerMixin],
|
||||
}
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user