Compare commits
14 Commits
2.0-beta.3
...
2.0-beta.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b876c2801d | ||
|
|
698c9c7bbf | ||
|
|
7544243640 | ||
|
|
4b4e3a8928 | ||
|
|
92a588bfcc | ||
|
|
43e956eb6a | ||
|
|
c4429f5dd7 | ||
|
|
4edfe1bcb9 | ||
|
|
473a9f0253 | ||
|
|
94cdca4f31 | ||
|
|
10d0a3f763 | ||
|
|
afe6c044cd | ||
|
|
e6c7d79d7d | ||
|
|
49fa9cc470 |
@@ -17,6 +17,11 @@ let LogContentSchema = new SimpleSchema({
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.summary,
|
||||
},
|
||||
// Inline with other content fields
|
||||
inline: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
context: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
|
||||
@@ -67,7 +67,8 @@ function applyAttackWithoutTarget({attack, scope, log}){
|
||||
}
|
||||
log.content.push({
|
||||
name,
|
||||
value: `${resultPrefix} **${result}**`,
|
||||
value: `${resultPrefix}\n**${result}**`,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -102,7 +103,8 @@ function applyAttackToTarget({attack, target, scope, log}){
|
||||
|
||||
log.content.push({
|
||||
name,
|
||||
value: `${resultPrefix} **${result}**`,
|
||||
value: `${resultPrefix}\n**${result}**`,
|
||||
inline: true,
|
||||
});
|
||||
if ((result > armor) || (criticalHit)){
|
||||
scope['$attackHit'] = true;
|
||||
@@ -116,7 +118,8 @@ function applyAttackToTarget({attack, target, scope, log}){
|
||||
});
|
||||
log.content.push({
|
||||
name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit',
|
||||
value: `${resultPrefix} **${result}**`,
|
||||
value: `${resultPrefix}\n**${result}**`,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -128,23 +131,23 @@ function rollAttack(attack, scope){
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a >= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else if (attack.advantage === -1 || scope['$attackDisadvantage']){
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a <= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else {
|
||||
value = rollDice(1, 20)[0];
|
||||
resultPrefix = `1d20 [${value}] ${rollModifierText} = `
|
||||
resultPrefix = `1d20 [${value}] ${rollModifierText}`
|
||||
}
|
||||
scope['$attackRoll'] = {value};
|
||||
const result = value + attack.value;
|
||||
@@ -178,7 +181,7 @@ function applyChildren(node, args){
|
||||
|
||||
function spendResources({prop, log, scope}){
|
||||
// Check Uses
|
||||
if (prop.usesLeft < 0){
|
||||
if (prop.usesLeft <= 0){
|
||||
log.content.push({
|
||||
name: 'Error',
|
||||
value: `${prop.name || 'action'} does not have enough uses left`,
|
||||
@@ -250,7 +253,8 @@ function spendResources({prop, log, scope}){
|
||||
});
|
||||
log.content.push({
|
||||
name: 'Uses left',
|
||||
value: prop.usesLeft - (prop.usesUsed || 0) - 1,
|
||||
value: prop.usesLeft - 1,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -280,9 +284,11 @@ function spendResources({prop, log, scope}){
|
||||
if (gainLog.length) log.content.push({
|
||||
name: 'Gained',
|
||||
value: gainLog.join('\n'),
|
||||
inline: true,
|
||||
});
|
||||
if (spendLog.length) log.content.push({
|
||||
name: 'Spent',
|
||||
value: spendLog.join('\n'),
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function applyAdjustment(node, {
|
||||
|
||||
// Evaluate the amount
|
||||
recalculateCalculation(prop.amount, scope, log);
|
||||
|
||||
|
||||
const value = +prop.amount.value;
|
||||
if (!isFinite(value)) {
|
||||
return applyChildren(node, {creature, targets, scope, log});
|
||||
@@ -23,8 +23,8 @@ export default function applyAdjustment(node, {
|
||||
if (damageTargets?.length) {
|
||||
damageTargets.forEach(target => {
|
||||
let stat = target.variables[prop.stat];
|
||||
if (!stat) {
|
||||
log({
|
||||
if (!stat?.type) {
|
||||
log.content.push({
|
||||
name: 'Error',
|
||||
value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set`
|
||||
});
|
||||
@@ -39,6 +39,7 @@ export default function applyAdjustment(node, {
|
||||
name: 'Attribute damage',
|
||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
||||
` ${value}`,
|
||||
inline: true,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -46,6 +47,7 @@ export default function applyAdjustment(node, {
|
||||
name: 'Attribute damage',
|
||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
||||
` ${value}`,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
||||
import computedSchemas from '/imports/api/properties/computedPropertySchemasIndex.js';
|
||||
import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js';
|
||||
import { get } from 'lodash';
|
||||
import resolve, { map } from '/imports/parser/resolve.js';
|
||||
import resolve, { map, toString } from '/imports/parser/resolve.js';
|
||||
import symbol from '/imports/parser/parseTree/symbol.js';
|
||||
import logErrors from './shared/logErrors.js';
|
||||
import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js';
|
||||
|
||||
export default function applyBuff(node, {creature, targets, scope, log}){
|
||||
const prop = node.node;
|
||||
@@ -63,7 +65,7 @@ function crystalizeVariables({propList, scope, log}){
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
const calcObj = get(prop, key);
|
||||
if (!calcObj?.parseNode) return;
|
||||
map(calcObj.parseNode, node => {
|
||||
calcObj.parseNode = map(calcObj.parseNode, node => {
|
||||
// Skip nodes that aren't symbols or accessors
|
||||
if (
|
||||
node.parseType !== 'accessor' && node.parseType !== 'symbol'
|
||||
@@ -73,11 +75,14 @@ function crystalizeVariables({propList, scope, log}){
|
||||
// strip $target
|
||||
if (node.parseType === 'accessor'){
|
||||
node.name = node.path.shift();
|
||||
if (!node.path.length){
|
||||
return symbol.create({name: node.name})
|
||||
}
|
||||
} else {
|
||||
// Can't strip symbols
|
||||
log.content.push({
|
||||
name: 'Error',
|
||||
value: 'Variable `$target` should not be used without a property: $target.property'
|
||||
value: 'Variable `$target` should not be used without a property: $target.property',
|
||||
});
|
||||
}
|
||||
return node;
|
||||
@@ -88,6 +93,8 @@ function crystalizeVariables({propList, scope, log}){
|
||||
return result;
|
||||
}
|
||||
});
|
||||
calcObj.calculation = toString(calcObj.parseNode);
|
||||
calcObj.hash = cyrb53(calcObj.calculation);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,6 +110,7 @@ export default function applyDamage(node, {
|
||||
log.content.push({
|
||||
name: logName,
|
||||
value: logValue.join('\n'),
|
||||
inline: true,
|
||||
});
|
||||
return applyChildren();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export default function applyRoll(node, {creature, targets, scope, log}){
|
||||
log.content.push({
|
||||
name: prop.name,
|
||||
value: prop.variableName + ' = ' + prop.roll.calculation + ' = ' + prop.roll.value,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
return node.children.forEach(child => applyProperty(child, {
|
||||
|
||||
@@ -23,6 +23,7 @@ export default function applySavingThrow(node, {creature, targets, scope, log}){
|
||||
log.content.push({
|
||||
name: prop.name,
|
||||
value: ' DC ' + dc,
|
||||
inline: true,
|
||||
});
|
||||
|
||||
saveTargets.forEach(target => {
|
||||
@@ -84,7 +85,8 @@ export default function applySavingThrow(node, {creature, targets, scope, log}){
|
||||
}
|
||||
log.content.push({
|
||||
name: 'Save',
|
||||
value: resultPrefix + result + (saveSuccess ? 'Passed' : 'Failed')
|
||||
value: resultPrefix + result + (saveSuccess ? 'Passed' : 'Failed'),
|
||||
inline: true,
|
||||
});
|
||||
return applyChildren();
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import recalculateCalculation from './recalculateCalculation.js'
|
||||
|
||||
export default function recalculateInlineCalculations(inlineCalcObj, scope, log){
|
||||
// Skip if there are no calculations
|
||||
if (!inlineCalcObj?.calculations?.length) return;
|
||||
if (!inlineCalcObj?.inlineCalculations?.length) return;
|
||||
// Recalculate each calculation with the current scope
|
||||
inlineCalcObj.inlineCalculations.forEach(calc => {
|
||||
recalculateCalculation(calc, scope, log);
|
||||
|
||||
@@ -87,7 +87,7 @@ const doAction = new ValidatedMethod({
|
||||
export default doAction;
|
||||
|
||||
export function doActionWork({
|
||||
creature, targets, properties, ancestors, method, methodScope = {}
|
||||
creature, targets, properties, ancestors, method, methodScope = {}, log
|
||||
}){
|
||||
// get the docs
|
||||
const ancestorScope = getAncestorScope(ancestors);
|
||||
@@ -97,7 +97,7 @@ export function doActionWork({
|
||||
}
|
||||
|
||||
// Create the log
|
||||
let log = CreatureLogSchema.clean({
|
||||
if (!log) log = CreatureLogSchema.clean({
|
||||
creatureId: creature._id,
|
||||
creatureName: creature.name,
|
||||
});
|
||||
|
||||
142
app/imports/api/engine/actions/doCastSpell.js
Normal file
142
app/imports/api/engine/actions/doCastSpell.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
import { doActionWork } from '/imports/api/engine/actions/doAction.js';
|
||||
import computeCreature from '/imports/api/engine/computeCreature.js';
|
||||
import { CreatureLogSchema } from '/imports/api/creature/log/CreatureLogs.js';
|
||||
|
||||
const doAction = new ValidatedMethod({
|
||||
name: 'creatureProperties.doCastSpell',
|
||||
validate: new SimpleSchema({
|
||||
spellId: SimpleSchema.RegEx.Id,
|
||||
slotId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
optional: true,
|
||||
},
|
||||
targetIds: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 20,
|
||||
optional: true,
|
||||
},
|
||||
'targetIds.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
scope: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
optional: true,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 10,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({spellId, slotId, targetIds = [], scope = {}}) {
|
||||
let spell = CreatureProperties.findOne(spellId);
|
||||
// Check permissions
|
||||
let creature = getRootCreatureAncestor(spell);
|
||||
|
||||
assertEditPermission(creature, this.userId);
|
||||
|
||||
// Get all the targets and make sure we can edit them
|
||||
let targets = [];
|
||||
targetIds.forEach(targetId => {
|
||||
let target = Creatures.findOne(targetId);
|
||||
assertEditPermission(target, this.userId);
|
||||
targets.push(target);
|
||||
});
|
||||
|
||||
// Fetch all the action's ancestor creatureProperties
|
||||
const ancestorIds = [];
|
||||
spell.ancestors.forEach(ref => {
|
||||
if (ref.collection === 'creatureProperties') {
|
||||
ancestorIds.push(ref.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Get cursor of ancestors
|
||||
const ancestors = CreatureProperties.find({
|
||||
_id: {$in: ancestorIds},
|
||||
}, {
|
||||
sort: {order: 1},
|
||||
});
|
||||
|
||||
// Get cursor of the properties
|
||||
const properties = CreatureProperties.find({
|
||||
$or: [{_id: spell._id}, {'ancestors.id': spell._id}],
|
||||
removed: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1},
|
||||
});
|
||||
|
||||
// Spend the appropriate slot
|
||||
let slotLevel = spell.level || 0;
|
||||
let slot;
|
||||
if (slotId && !spell.castWithoutSpellSlots){
|
||||
slot = CreatureProperties.findOne(slotId);
|
||||
if (!slot){
|
||||
throw new Meteor.Error('No slot',
|
||||
'Slot not found to cast spell');
|
||||
}
|
||||
if (!slot.value){
|
||||
throw new Meteor.Error('No slot',
|
||||
'Slot depleted');
|
||||
}
|
||||
if (slot.attributeType !== 'spellSlot'){
|
||||
throw new Meteor.Error('Not a slot',
|
||||
'The given property is not a valid spell slot');
|
||||
}
|
||||
if (!slot.spellSlotLevel?.value){
|
||||
throw new Meteor.Error('No slot level',
|
||||
'Slot does not have a spell slot level');
|
||||
}
|
||||
if (slot.spellSlotLevel.value < spell.level){
|
||||
throw new Meteor.Error('Slot too small',
|
||||
'Slot is not large enough to cast spell');
|
||||
}
|
||||
slotLevel = slot.spellSlotLevel.value;
|
||||
damagePropertyWork({
|
||||
property: slot,
|
||||
operation: 'increment',
|
||||
value: 1,
|
||||
});
|
||||
}
|
||||
|
||||
scope['slotLevel'] = slotLevel;
|
||||
|
||||
// Post the slot level spent to the log
|
||||
const log = CreatureLogSchema.clean({
|
||||
creatureId: creature._id,
|
||||
creatureName: creature.name,
|
||||
});
|
||||
if (slot?.spellSlotLevel?.value){
|
||||
log.content.push({
|
||||
name: `Casting using a level ${slotLevel} spell slot`
|
||||
});
|
||||
} else if (slotLevel) {
|
||||
log.content.push({
|
||||
name: `Casting at level ${slotLevel}`
|
||||
});
|
||||
}
|
||||
|
||||
// Do the action
|
||||
doActionWork({creature, targets, properties, ancestors, method: this, methodScope: scope, log});
|
||||
|
||||
// Recompute all involved creatures
|
||||
computeCreature(creature._id);
|
||||
targets.forEach(target => {
|
||||
computeCreature(target._id);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default doAction;
|
||||
2
app/imports/api/engine/actions/index.js
Normal file
2
app/imports/api/engine/actions/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import './doCastSpell.js';
|
||||
import './doCheck.js';
|
||||
@@ -1,54 +0,0 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import computeCreature from '/imports/api/engine/computeCreature.js';
|
||||
import doAction from '../doAction.js';
|
||||
|
||||
const commitAction = new ValidatedMethod({
|
||||
name: 'creatureProperties.doAction',
|
||||
validate: new SimpleSchema({
|
||||
actionId: SimpleSchema.RegEx.Id,
|
||||
targetIds: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 20,
|
||||
optional: true,
|
||||
},
|
||||
'targetIds.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 10,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({actionId, targetIds = []}) {
|
||||
let action = CreatureProperties.findOne(actionId);
|
||||
// Check permissions
|
||||
let creature = getRootCreatureAncestor(action);
|
||||
|
||||
assertEditPermission(creature, this.userId);
|
||||
let targets = [];
|
||||
targetIds.forEach(targetId => {
|
||||
let target = Creatures.findOne(targetId);
|
||||
assertEditPermission(target, this.userId);
|
||||
targets.push(target);
|
||||
});
|
||||
doAction({action, creature, targets, method: this});
|
||||
|
||||
// recompute creatures
|
||||
computeCreature(creature._id);
|
||||
|
||||
targets.forEach(target => {
|
||||
computeCreature(target._id);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default commitAction;
|
||||
@@ -30,7 +30,6 @@ function dependOnCalc({dependencyGraph, prop, key}){
|
||||
let calc = get(prop, key);
|
||||
if (!calc) return;
|
||||
if (calc.type !== '_calculation'){
|
||||
console.log(calc);
|
||||
throw `Expected calculation got ${calc.type}`
|
||||
}
|
||||
dependencyGraph.addLink(prop._id, `${prop._id}.${key}`, 'calculation');
|
||||
@@ -63,7 +62,7 @@ function linkAction(dependencyGraph, prop, {propsById}){
|
||||
dependOnCalc({
|
||||
dependencyGraph,
|
||||
prop,
|
||||
key: `${prop._id}.resources.itemsConsumed.${index}.quantity`,
|
||||
key: `resources.itemsConsumed[${index}].quantity`,
|
||||
});
|
||||
});
|
||||
// Link attributes consumed
|
||||
@@ -74,7 +73,7 @@ function linkAction(dependencyGraph, prop, {propsById}){
|
||||
dependOnCalc({
|
||||
dependencyGraph,
|
||||
prop,
|
||||
key: `${prop._id}.resources.attributesConsumed.${index}.quantity`,
|
||||
key: `resources.attributesConsumed[${index}].quantity`,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -243,6 +242,9 @@ function linkSkill(dependencyGraph, prop){
|
||||
}
|
||||
// Skills depend on the creature's proficiencyBonus
|
||||
dependencyGraph.addLink(prop._id, 'proficiencyBonus', 'skillProficiencyBonus');
|
||||
|
||||
// Depends on base value
|
||||
dependOnCalc({dependencyGraph, prop, key: 'baseValue'});
|
||||
}
|
||||
|
||||
function linkSlot(dependencyGraph, prop){
|
||||
|
||||
@@ -43,18 +43,26 @@ export default function aggregateInventory({node, linkedNode, link}){
|
||||
}
|
||||
}
|
||||
|
||||
function quantity(prop){
|
||||
if (typeof prop.quantity === 'number'){
|
||||
return prop.quantity;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function weight(prop){
|
||||
return (prop.weight || 0) + (prop.contentsWeight || 0);
|
||||
return (prop.weight || 0) * quantity(prop) + (prop.contentsWeight || 0);
|
||||
}
|
||||
|
||||
function carriedWeight(prop){
|
||||
return (prop.weight || 0) + (prop.carriedWeight || 0);
|
||||
return (prop.weight || 0) * quantity(prop) + (prop.carriedWeight || 0);
|
||||
}
|
||||
|
||||
function value (prop){
|
||||
return (prop.value || 0) + (prop.contentsValue || 0);
|
||||
return (prop.value || 0) * quantity(prop) + (prop.contentsValue || 0);
|
||||
}
|
||||
|
||||
function carriedValue (prop){
|
||||
return (prop.value || 0) + (prop.carriedValue || 0);
|
||||
return (prop.value || 0) * quantity(prop) + (prop.carriedValue || 0);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function(){
|
||||
assert.equal(prop.usesLeft, 2);
|
||||
|
||||
const rolled = computation.propsById['rolledDescriptionId'];
|
||||
assert.equal(rolled.summary.value, 'test roll gets compiled d4 + 4 properly');
|
||||
assert.equal(rolled.summary.value, 'test roll gets compiled 8 properly');
|
||||
|
||||
const itemConsumed = prop.resources.itemsConsumed[0];
|
||||
assert.equal(itemConsumed.quantity.value, 3);
|
||||
@@ -67,7 +67,7 @@ var testProperties = [
|
||||
type: 'action',
|
||||
ancestors: [{id: 'charId'}],
|
||||
summary: {
|
||||
text: 'test roll gets compiled {1d4 + (2 + 2)} properly',
|
||||
text: 'test roll gets compiled {4 + (2 + 2)} properly',
|
||||
},
|
||||
}),
|
||||
clean({
|
||||
|
||||
@@ -14,16 +14,14 @@ export default function(){
|
||||
|
||||
assert.equal(scope('itemsAttuned'), 1);
|
||||
|
||||
assert.equal(prop('childContainerId').carriedWeight, 23);
|
||||
assert.equal(prop('childContainerId').contentsWeight, 23);
|
||||
assert.equal(prop('childContainerId').carriedWeight, 69);
|
||||
assert.equal(prop('childContainerId').contentsWeight, 69);
|
||||
|
||||
assert.equal(scope('weightCarried'), 58);
|
||||
assert.equal(scope('weightCarried'), 104);
|
||||
assert.equal(scope('valueCarried'), 129);
|
||||
|
||||
assert.equal(scope('weightCarried'), 58);
|
||||
assert.equal(scope('valueCarried'), 71);
|
||||
|
||||
assert.equal(scope('weightTotal'), 58);
|
||||
assert.equal(scope('valueTotal'), 71);
|
||||
assert.equal(scope('weightTotal'), 104);
|
||||
assert.equal(scope('valueTotal'), 129);
|
||||
}
|
||||
|
||||
var testProperties = [
|
||||
@@ -62,8 +60,9 @@ var testProperties = [
|
||||
clean({
|
||||
_id: 'grandchildItemId',
|
||||
type: 'item',
|
||||
weight: 23,
|
||||
value: 29,
|
||||
weight: 23, // 69 total
|
||||
value: 29, // 87 total
|
||||
quantity: 3,
|
||||
ancestors: [{id: 'charId'}, {id: 'containerId'}, {id: 'childContainerId'}],
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -5,11 +5,20 @@ import writeScope from './computation/writeComputation/writeScope.js';
|
||||
import writeErrors from './computation/writeComputation/writeErrors.js';
|
||||
|
||||
export default function computeCreature(creatureId){
|
||||
if (Meteor.isClient) return;
|
||||
const computation = buildCreatureComputation(creatureId);
|
||||
computeCreatureComputation(computation);
|
||||
writeAlteredProperties(computation);
|
||||
writeScope(creatureId, computation.scope);
|
||||
writeErrors(creatureId, computation.errors);
|
||||
try {
|
||||
computeCreatureComputation(computation);
|
||||
writeAlteredProperties(computation);
|
||||
writeScope(creatureId, computation.scope);
|
||||
} catch (e){
|
||||
computation.errors.push({
|
||||
type: 'crash',
|
||||
details: e.reason,
|
||||
});
|
||||
} finally {
|
||||
writeErrors(creatureId, computation.errors);
|
||||
}
|
||||
}
|
||||
|
||||
// For now just recompute the whole creature, TODO only recompute a single
|
||||
|
||||
@@ -26,6 +26,12 @@ const ClassLevelSchema = createPropertySchema({
|
||||
defaultValue: 1,
|
||||
max: STORAGE_LIMITS.levelMax,
|
||||
},
|
||||
// Filters out of UI if condition isn't met, but isn't otherwise enforced
|
||||
slotFillerCondition: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlyClassLevelSchema = createPropertySchema({
|
||||
|
||||
@@ -13,7 +13,7 @@ function id(x) { return x[0]; }
|
||||
value: s => s.slice(1, -1),
|
||||
},
|
||||
name: {
|
||||
match: /[a-zA-Z_#]*[a-ce-zA-Z_#][a-zA-Z0-9_#]*/,
|
||||
match: /[a-zA-Z_#$]*[a-ce-zA-Z_#$][a-zA-Z0-9_#$]*/,
|
||||
type: moo.keywords({
|
||||
'keywords': ['true', 'false'],
|
||||
}),
|
||||
@@ -109,14 +109,16 @@ let ParserRules = [
|
||||
{"name": "parenthesizedExpression", "symbols": [{"literal":"("}, "_", "expression", "_", {"literal":")"}], "postprocess": d => node.parenthesis.create({content: d[2]})},
|
||||
{"name": "parenthesizedExpression", "symbols": ["accessorExpression"], "postprocess": id},
|
||||
{"name": "accessorExpression$subexpression$1", "symbols": [(lexer.has("name") ? {type: "name"} : name)], "postprocess": d => d[0].value},
|
||||
{"name": "accessorExpression$ebnf$1$subexpression$1", "symbols": [{"literal":"."}, (lexer.has("name") ? {type: "name"} : name)], "postprocess": d => d[1].value},
|
||||
{"name": "accessorExpression$ebnf$1$subexpression$1", "symbols": [{"literal":"."}, "keyExpression"], "postprocess": d => d[1]},
|
||||
{"name": "accessorExpression$ebnf$1", "symbols": ["accessorExpression$ebnf$1$subexpression$1"]},
|
||||
{"name": "accessorExpression$ebnf$1$subexpression$2", "symbols": [{"literal":"."}, (lexer.has("name") ? {type: "name"} : name)], "postprocess": d => d[1].value},
|
||||
{"name": "accessorExpression$ebnf$1$subexpression$2", "symbols": [{"literal":"."}, "keyExpression"], "postprocess": d => d[1]},
|
||||
{"name": "accessorExpression$ebnf$1", "symbols": ["accessorExpression$ebnf$1", "accessorExpression$ebnf$1$subexpression$2"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
|
||||
{"name": "accessorExpression", "symbols": ["accessorExpression$subexpression$1", "accessorExpression$ebnf$1"], "postprocess":
|
||||
d=> node.accessor.create({name: d[0], path: d[1]})
|
||||
},
|
||||
{"name": "accessorExpression", "symbols": ["valueExpression"], "postprocess": id},
|
||||
{"name": "keyExpression", "symbols": ["name"], "postprocess": d => d[0].name},
|
||||
{"name": "keyExpression", "symbols": ["number"], "postprocess": d => d[0].value},
|
||||
{"name": "valueExpression", "symbols": ["name"], "postprocess": id},
|
||||
{"name": "valueExpression", "symbols": ["number"], "postprocess": id},
|
||||
{"name": "valueExpression", "symbols": ["string"], "postprocess": id},
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
value: s => s.slice(1, -1),
|
||||
},
|
||||
name: {
|
||||
match: /[a-zA-Z_#]*[a-ce-zA-Z_#][a-zA-Z0-9_#]*/,
|
||||
match: /[a-zA-Z_#$]*[a-ce-zA-Z_#$][a-zA-Z0-9_#$]*/,
|
||||
type: moo.keywords({
|
||||
'keywords': ['true', 'false'],
|
||||
}),
|
||||
@@ -138,11 +138,14 @@ parenthesizedExpression ->
|
||||
| accessorExpression {% id %}
|
||||
|
||||
accessorExpression ->
|
||||
(%name {% d => d[0].value %}) ( "." %name {% d => d[1].value %} ):+ {%
|
||||
(%name {% d => d[0].value %}) ( "." keyExpression {% d => d[1] %} ):+ {%
|
||||
d=> node.accessor.create({name: d[0], path: d[1]})
|
||||
%}
|
||||
| valueExpression {% id %}
|
||||
|
||||
keyExpression -> name {% d => d[0].name %}
|
||||
| number {% d => d[0].value %}
|
||||
|
||||
valueExpression ->
|
||||
name {% id %}
|
||||
| number {% id %}
|
||||
|
||||
43
app/imports/ui/components/CardHighlight.vue
Normal file
43
app/imports/ui/components/CardHighlight.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
v-if="dark || theme.isDark"
|
||||
class="overlay"
|
||||
:class="{active, 'extra-bright': dark && !theme.isDark}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
export default {
|
||||
inject: {
|
||||
theme: {
|
||||
default: {
|
||||
isDark: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
active: Boolean,
|
||||
dark: Boolean,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
background-color: #fff;
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s linear;
|
||||
}
|
||||
.overlay.active {
|
||||
opacity: 0.08;
|
||||
}
|
||||
.overlay.active.extra-bright {
|
||||
opacity: 0.3;
|
||||
}
|
||||
</style>
|
||||
@@ -7,10 +7,15 @@
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
icon
|
||||
:outlined="!!label"
|
||||
:icon="!label"
|
||||
:min-width="label && 108"
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon>mdi-format-paint</v-icon>
|
||||
{{ label }}
|
||||
<v-icon :right="!!label">
|
||||
mdi-format-paint
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card class="overflow-hidden">
|
||||
@@ -122,6 +127,10 @@
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
}
|
||||
},
|
||||
data(){ return {
|
||||
colors: [
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
v-bind="$attrs"
|
||||
:loading="loading"
|
||||
v-on="on"
|
||||
@click.stop
|
||||
>
|
||||
<slot>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
<v-icon>$vuetify.icons.abacus</v-icon>
|
||||
</slot>
|
||||
</v-btn>
|
||||
</template>
|
||||
@@ -42,6 +43,7 @@ export default {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
loading: Boolean,
|
||||
},
|
||||
data(){return {
|
||||
open: false
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
<v-card
|
||||
:hover="hasClickListener"
|
||||
class="toolbar-card"
|
||||
:class="hovering ? 'elevation-8': ''"
|
||||
:class="{'transparent-toolbar': transparentToolbar, hovering}"
|
||||
:elevation="hovering ? 8 : undefined"
|
||||
@click.native="$emit('click')"
|
||||
>
|
||||
<v-toolbar
|
||||
flat
|
||||
:style="`transform: none; ${hasToolbarClickListener ? 'cursor: pointer;' : ''}`"
|
||||
:color="color"
|
||||
:dark="isDark"
|
||||
:light="!isDark"
|
||||
:class="{}"
|
||||
:color="transparentToolbar ? undefined : color"
|
||||
:dark="transparentToolbar ? undefined : isDark"
|
||||
:light="transparentToolbar ? undefined : !isDark"
|
||||
@click="$emit('toolbarclick')"
|
||||
@mouseover="hoverToolbar(true)"
|
||||
@mouseleave="hoverToolbar(false)"
|
||||
@@ -20,14 +22,19 @@
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
<card-highlight :active="hovering" />
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||
import getThemeColor from '/imports/ui/utility/getThemeColor.js';
|
||||
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CardHighlight,
|
||||
},
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
@@ -35,6 +42,7 @@
|
||||
return getThemeColor('secondary');
|
||||
},
|
||||
},
|
||||
transparentToolbar: Boolean,
|
||||
},
|
||||
data(){ return {
|
||||
hovering: false,
|
||||
@@ -62,9 +70,12 @@
|
||||
|
||||
<style lang="css">
|
||||
.toolbar-card .v-toolbar__title {
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.toolbar-card {
|
||||
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
.toolbar-card.transparent-toolbar .theme--dark.v-toolbar.v-sheet {
|
||||
background-color: #303030;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,21 +8,22 @@
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<div class="layout align-center">
|
||||
<v-label>{{ label }}</v-label>
|
||||
<v-btn
|
||||
:loading="loading"
|
||||
large
|
||||
icon
|
||||
outlined
|
||||
:min-width="108"
|
||||
v-on="on"
|
||||
>
|
||||
{{ label }}
|
||||
<svg-icon
|
||||
v-if="safeValue && safeValue.shape"
|
||||
large
|
||||
right
|
||||
class="ml-2"
|
||||
:shape="safeValue.shape"
|
||||
/>
|
||||
<v-icon
|
||||
v-else
|
||||
large
|
||||
right
|
||||
>
|
||||
mdi-select-search
|
||||
</v-icon>
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
<v-card
|
||||
hover
|
||||
data-id="creature-summary"
|
||||
@mouseover="summaryHover = true"
|
||||
@mouseleave="summaryHover = false"
|
||||
@click="showCharacterForm"
|
||||
>
|
||||
<v-img
|
||||
@@ -18,6 +20,7 @@
|
||||
{{ creature.alignment }}<br>
|
||||
{{ creature.gender }}
|
||||
</v-card-text>
|
||||
<card-highlight :active="summaryHover" />
|
||||
</v-card>
|
||||
</div>
|
||||
<div>
|
||||
@@ -25,9 +28,21 @@
|
||||
data-id="slot-card"
|
||||
@toolbarclick="showSlotDialog"
|
||||
>
|
||||
<v-toolbar-title slot="toolbar">
|
||||
Build
|
||||
</v-toolbar-title>
|
||||
<template slot="toolbar">
|
||||
<v-toolbar-title>
|
||||
Build
|
||||
</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-toolbar-title>
|
||||
<v-icon
|
||||
small
|
||||
style="width: 16px;"
|
||||
class="mr-1"
|
||||
>
|
||||
mdi-pencil
|
||||
</v-icon>
|
||||
</v-toolbar-title>
|
||||
</template>
|
||||
<v-card-text style="background-color: inherit;">
|
||||
<slots :creature-id="creatureId" />
|
||||
</v-card-text>
|
||||
@@ -121,6 +136,7 @@ import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import NoteCard from '/imports/ui/properties/components/persona/NoteCard.vue';
|
||||
import Slots from '/imports/ui/creature/slots/Slots.vue';
|
||||
import ToolbarCard from '/imports/ui/components/ToolbarCard.vue';
|
||||
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -128,6 +144,7 @@ export default {
|
||||
NoteCard,
|
||||
Slots,
|
||||
ToolbarCard,
|
||||
CardHighlight,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
@@ -135,6 +152,9 @@ export default {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
summaryHover: false,
|
||||
}},
|
||||
computed: {
|
||||
highestClassLevels(){
|
||||
let highestLevels = {};
|
||||
|
||||
@@ -55,9 +55,7 @@
|
||||
</v-card>
|
||||
</div>
|
||||
<div>
|
||||
<toolbar-card
|
||||
:color="creature.color"
|
||||
>
|
||||
<toolbar-card transparent-toolbar>
|
||||
<v-toolbar-title slot="toolbar">
|
||||
Equipped
|
||||
</v-toolbar-title>
|
||||
@@ -71,9 +69,7 @@
|
||||
</toolbar-card>
|
||||
</div>
|
||||
<div>
|
||||
<toolbar-card
|
||||
:color="creature.color"
|
||||
>
|
||||
<toolbar-card transparent-toolbar>
|
||||
<v-toolbar-title slot="toolbar">
|
||||
Carried
|
||||
</v-toolbar-title>
|
||||
|
||||
@@ -162,11 +162,14 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="spellSlots && spellSlots.length"
|
||||
v-if="spellSlots && spellSlots.length || hasSpells"
|
||||
class="spell-slots"
|
||||
>
|
||||
<v-card>
|
||||
<v-card
|
||||
data-id="spell-slot-card"
|
||||
>
|
||||
<v-list
|
||||
v-if="spellSlots && spellSlots.length"
|
||||
two-line
|
||||
subheader
|
||||
>
|
||||
@@ -180,6 +183,19 @@
|
||||
@cast="castSpellWithSlot(spellSlot._id)"
|
||||
/>
|
||||
</v-list>
|
||||
<div
|
||||
v-if="hasSpells"
|
||||
class="d-flex justify-end"
|
||||
>
|
||||
<v-btn
|
||||
color="accent"
|
||||
style="width: 100%;"
|
||||
outlined
|
||||
@click="castSpell"
|
||||
>
|
||||
Cast a spell
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
@@ -226,7 +242,7 @@
|
||||
<div
|
||||
v-for="action in actions"
|
||||
:key="action._id"
|
||||
class="actions"
|
||||
class="action"
|
||||
>
|
||||
<action-card
|
||||
:model="action"
|
||||
@@ -237,7 +253,7 @@
|
||||
<div
|
||||
v-for="attack in attacks"
|
||||
:key="attack._id"
|
||||
class="attacks"
|
||||
class="attack"
|
||||
>
|
||||
<action-card
|
||||
attack
|
||||
@@ -348,7 +364,8 @@
|
||||
import RestButton from '/imports/ui/creature/RestButton.vue';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import ToggleCard from '/imports/ui/properties/components/toggles/ToggleCard.vue';
|
||||
//import castSpellWithSlot from '/imports/api/creature/actions/castSpellWithSlot.js';
|
||||
import doCastSpell from '/imports/api/engine/actions/doCastSpell.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
const getProperties = function(creature, filter){
|
||||
if (!creature) return;
|
||||
@@ -432,6 +449,11 @@
|
||||
spellSlots(){
|
||||
return getAttributeOfType(this.creature, 'spellSlot');
|
||||
},
|
||||
hasSpells(){
|
||||
return getProperties(this.creature, {
|
||||
type: 'spell',
|
||||
}).count();
|
||||
},
|
||||
hitDice(){
|
||||
return getAttributeOfType(this.creature, 'hitDice');
|
||||
},
|
||||
@@ -498,18 +520,19 @@
|
||||
if (error) console.error(error);
|
||||
});
|
||||
},
|
||||
castSpellWithSlot(slotId){
|
||||
castSpell(){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'cast-spell-with-slot-dialog',
|
||||
elementId: `spell-slot-cast-btn-${slotId}`,
|
||||
elementId: 'spell-slot-card',
|
||||
data: {
|
||||
creatureId: this.creatureId,
|
||||
slotId,
|
||||
},
|
||||
callback({spellId, slotId} = {}){
|
||||
if (!spellId) return;
|
||||
castSpellWithSlot.call({spellId, slotId}, error => {
|
||||
if (error) console.error(error);
|
||||
doCastSpell.call({spellId, slotId}, error => {
|
||||
if (!error) return;
|
||||
snackbar({text: error.reason});
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<card-highlight :active="hovering" />
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
@@ -107,6 +108,7 @@ import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
||||
import RollPopup from '/imports/ui/components/RollPopup.vue';
|
||||
import MarkdownText from '/imports/ui/components/MarkdownText.vue';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -115,6 +117,7 @@ export default {
|
||||
MarkdownText,
|
||||
PropertyIcon,
|
||||
RollPopup,
|
||||
CardHighlight
|
||||
},
|
||||
inject: {
|
||||
context: {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<v-card
|
||||
:hover="hasClickListener"
|
||||
@click="click"
|
||||
@mouseover="hasClickListener ? hovering = true : undefined"
|
||||
@mouseleave="hasClickListener ? hovering = false : undefined"
|
||||
>
|
||||
<div class="layout align-center">
|
||||
<roll-popup
|
||||
@@ -31,6 +33,7 @@
|
||||
{{ model.name }}
|
||||
</v-card-title>
|
||||
</div>
|
||||
<card-highlight :active="hasClickListener && hovering" />
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
@@ -39,10 +42,12 @@
|
||||
import RollPopup from '/imports/ui/components/RollPopup.vue';
|
||||
import doCheck from '/imports/api/engine/actions/doCheck.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RollPopup,
|
||||
CardHighlight,
|
||||
},
|
||||
inject: {
|
||||
context: {
|
||||
@@ -57,6 +62,7 @@
|
||||
},
|
||||
data(){return {
|
||||
checkLoading: false,
|
||||
hovering: false,
|
||||
}},
|
||||
computed: {
|
||||
hasClickListener(){
|
||||
|
||||
@@ -43,11 +43,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</v-layout>
|
||||
<card-highlight :active="hover" />
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CardHighlight,
|
||||
},
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<template lang="html">
|
||||
<v-list-item
|
||||
:key="model._id"
|
||||
class="spell-slot-list-tile"
|
||||
v-bind="$attrs"
|
||||
:disabled="disabled"
|
||||
v-on="hasClickListener ? {click} : {}"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-if="Number.isFinite(model.total)">
|
||||
<v-list-item-title
|
||||
v-if="Number.isFinite(model.total)"
|
||||
>
|
||||
<div
|
||||
v-if="model.total > 4"
|
||||
class="layout value"
|
||||
@@ -27,6 +32,7 @@
|
||||
<v-icon
|
||||
v-for="i in model.total"
|
||||
:key="i"
|
||||
:disabled="disabled"
|
||||
>
|
||||
{{
|
||||
i > model.value ?
|
||||
@@ -38,24 +44,13 @@
|
||||
</v-list-item-title>
|
||||
<v-list-item-title v-else>
|
||||
<code>
|
||||
{{model.total}}
|
||||
{{ model.total }}
|
||||
</code>
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ model.name }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
<v-list-item-avatar v-if="!hideCastButton">
|
||||
<v-btn
|
||||
icon
|
||||
text
|
||||
class="primary--text"
|
||||
:data-id="`spell-slot-cast-btn-${model._id}`"
|
||||
@click.stop="$emit('cast')"
|
||||
>
|
||||
<v-icon>$vuetify.icons.spell</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-avatar>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
@@ -69,6 +64,7 @@ export default {
|
||||
},
|
||||
dark: Boolean,
|
||||
hideCastButton: Boolean,
|
||||
disabled: Boolean,
|
||||
},
|
||||
computed: {
|
||||
hasClickListener(){
|
||||
|
||||
@@ -28,12 +28,9 @@
|
||||
color="primary"
|
||||
:disabled="context.editPermission === false"
|
||||
:value="model.quantity"
|
||||
:loading="incrementLoading"
|
||||
@change="changeQuantity"
|
||||
>
|
||||
<v-icon>
|
||||
$vuetify.icons.abacus
|
||||
</v-icon>
|
||||
</increment-button>
|
||||
/>
|
||||
</v-list-item-action>
|
||||
<v-list-item-action>
|
||||
<v-icon
|
||||
@@ -52,6 +49,7 @@ import treeNodeViewMixin from '/imports/ui/properties/treeNodeViews/treeNodeView
|
||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||
import adjustQuantity from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
||||
import IncrementButton from '/imports/ui/components/IncrementButton.vue';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components:{
|
||||
@@ -64,6 +62,9 @@ export default {
|
||||
props: {
|
||||
preparingSpells: Boolean,
|
||||
},
|
||||
data(){return {
|
||||
incrementLoading: false,
|
||||
}},
|
||||
computed: {
|
||||
hasClickListener(){
|
||||
return this.$listeners && !!this.$listeners.click;
|
||||
@@ -89,10 +90,17 @@ export default {
|
||||
this.$emit('click', e);
|
||||
},
|
||||
changeQuantity({type, value}) {
|
||||
this.incrementLoading = true;
|
||||
adjustQuantity.call({
|
||||
_id: this.model._id,
|
||||
operation: type,
|
||||
value: value
|
||||
}, error => {
|
||||
this.incrementLoading = false;
|
||||
if (error){
|
||||
snackbar({text: error.reason});
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
:dark="model.color && isDark"
|
||||
:light="model.color && !isDark"
|
||||
@click="clickProperty(model._id)"
|
||||
@mouseover="hover = true"
|
||||
@mouseleave="hover = false"
|
||||
>
|
||||
<v-card-title class="text-h6">
|
||||
{{ model.name }}
|
||||
@@ -16,23 +18,39 @@
|
||||
:model="model.summary"
|
||||
/>
|
||||
</v-card-text>
|
||||
<card-highlight
|
||||
:active="hover"
|
||||
:dark="theme.isDark"
|
||||
/>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PropertyDescription,
|
||||
CardHighlight,
|
||||
},
|
||||
inject: {
|
||||
theme: {
|
||||
default: {
|
||||
isDark: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data(){ return{
|
||||
hover: false,
|
||||
}},
|
||||
computed: {
|
||||
isDark(){
|
||||
return isDarkColor(this.model.color);
|
||||
|
||||
@@ -73,27 +73,35 @@
|
||||
>
|
||||
Slot
|
||||
</div>
|
||||
<v-list-item
|
||||
v-if="!(selectedSpell && selectedSpell.level > 0)"
|
||||
key="cantrip-dummy-slot"
|
||||
class="spell-slot-list-tile"
|
||||
:class="{ 'primary--text': selectedSlotId === undefined}"
|
||||
@click="selectedSlotId = undefined"
|
||||
<v-list-item-group
|
||||
key="slot-list"
|
||||
v-model="selectedSlotId"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="text-h6">
|
||||
Cantrip
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<spell-slot-list-tile
|
||||
v-for="spellSlot in spellSlots"
|
||||
:key="spellSlot._id"
|
||||
:model="spellSlot"
|
||||
:class="{ 'primary--text': selectedSlotId === spellSlot._id }"
|
||||
hide-cast-button
|
||||
@click="selectedSlotId = spellSlot._id"
|
||||
/>
|
||||
<v-list-item
|
||||
key="cantrip-dummy-slot"
|
||||
class="spell-slot-list-tile"
|
||||
:class="{ 'primary--text': selectedSlotId === 'no-slot' }"
|
||||
value="no-slot"
|
||||
:disabled="!canCastSpellWithSlot(selectedSpell, 'no-slot')"
|
||||
@click="selectedSlotId = 'no-slot'"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
Cast without spell slot
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<spell-slot-list-tile
|
||||
v-for="spellSlot in spellSlots"
|
||||
:key="spellSlot._id"
|
||||
:model="spellSlot"
|
||||
:class="{ 'primary--text': selectedSlotId === spellSlot._id }"
|
||||
:value="spellSlot._id"
|
||||
:disabled="!canCastSpellWithSlot(selectedSpell, spellSlot._id, spellSlot)"
|
||||
hide-cast-button
|
||||
@click="selectedSlotId = spellSlot._id"
|
||||
/>
|
||||
</v-list-item-group>
|
||||
</template>
|
||||
<template slot="right">
|
||||
<div
|
||||
@@ -102,25 +110,31 @@
|
||||
>
|
||||
Spell
|
||||
</div>
|
||||
<template v-for="spell in computedSpells">
|
||||
<v-subheader
|
||||
v-if="spell.isSubheader"
|
||||
:key="`${spell.level}-header`"
|
||||
class="item"
|
||||
>
|
||||
{{ spell.level === 0 ? 'Cantrips' : `Level ${spell.level}` }}
|
||||
</v-subheader>
|
||||
<spell-list-tile
|
||||
v-else
|
||||
:key="spell._id"
|
||||
hide-handle
|
||||
show-info-button
|
||||
:class="{ 'primary--text': selectedSpellId === spell._id}"
|
||||
:model="spell"
|
||||
@click="selectedSpellId = spell._id"
|
||||
@show-info="spellDialog(spell._id)"
|
||||
/>
|
||||
</template>
|
||||
<v-list-item-group
|
||||
key="slot-list"
|
||||
v-model="selectedSpellId"
|
||||
>
|
||||
<template v-for="spell in computedSpells">
|
||||
<v-subheader
|
||||
v-if="spell.isSubheader"
|
||||
:key="`${spell.level}-header`"
|
||||
class="item"
|
||||
>
|
||||
{{ spell.level === 0 ? 'Cantrips' : `Level ${spell.level}` }}
|
||||
</v-subheader>
|
||||
<spell-list-tile
|
||||
v-else
|
||||
:key="spell._id"
|
||||
hide-handle
|
||||
show-info-button
|
||||
:model="spell"
|
||||
:value="spell._id"
|
||||
:class="{ 'primary--text': selectedSpellId === spell._id }"
|
||||
:disabled="!canCastSpellWithSlot(spell, selectedSlotId, selectedSlot)"
|
||||
@show-info="spellDialog(spell._id)"
|
||||
/>
|
||||
</template>
|
||||
</v-list-item-group>
|
||||
</template>
|
||||
</split-list-layout>
|
||||
<template slot="actions">
|
||||
@@ -135,10 +149,7 @@
|
||||
text
|
||||
:disabled="!canCast"
|
||||
class="primary--text"
|
||||
@click="$store.dispatch('popDialogStack', {
|
||||
spellId: selectedSpellId,
|
||||
slotId: selectedSlotId,
|
||||
})"
|
||||
@click="cast"
|
||||
>
|
||||
Cast
|
||||
</v-btn>
|
||||
@@ -153,6 +164,16 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
||||
import spellsWithSubheaders from '/imports/ui/properties/components/spells/spellsWithSubheaders.js';
|
||||
import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue';
|
||||
import SpellListTile from '/imports/ui/properties/components/spells/SpellListTile.vue';
|
||||
import { find } from 'lodash';
|
||||
|
||||
const slotFilter = {
|
||||
type: 'attribute',
|
||||
attributeType: 'spellSlot',
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
overridden: {$ne: true},
|
||||
'spellSlotLevel.value': {$gte: 1},
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -179,6 +200,8 @@ export default {
|
||||
searchString: undefined,
|
||||
selectedSlotId: this.slotId,
|
||||
selectedSpellId: this.spellId,
|
||||
selectedSlot: undefined,
|
||||
selectedSpell: undefined,
|
||||
searchValue: undefined,
|
||||
searchError: undefined,
|
||||
filterMenuOpen: false,
|
||||
@@ -195,16 +218,10 @@ export default {
|
||||
return spellsWithSubheaders(this.spells);
|
||||
},
|
||||
canCast(){
|
||||
let spell = this.selectedSpell;
|
||||
let slot = this.selectedSlot;
|
||||
if (!spell) return false;
|
||||
if (spell.level === 0){
|
||||
return this.selectedSlotId === undefined;
|
||||
} else if (!slot) {
|
||||
return false
|
||||
} else {
|
||||
return slot.spellSlotLevelValue >= spell.level;
|
||||
}
|
||||
if (!this.selectedSpell || !this.selectedSlotId) return false;
|
||||
return this.canCastSpellWithSlot(
|
||||
this.selectedSpell, this.selectedSlotId, this.selectedSlot
|
||||
);
|
||||
},
|
||||
filtersApplied(){
|
||||
for (let key in this.booleanFilters){
|
||||
@@ -216,19 +233,61 @@ export default {
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedSpell(spell){
|
||||
if (!spell) return;
|
||||
if(spell.level === 0){
|
||||
this.selectedSlotId = undefined;
|
||||
}
|
||||
selectedSpellId: {
|
||||
handler(spellId){
|
||||
this.selectedSpell = CreatureProperties.findOne(spellId)
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
selectedSlot(slot){
|
||||
if (!slot) return;
|
||||
if (!this.selectedSpell) return;
|
||||
if(slot.spellSlotLevelValue > 0 && this.selectedSpell.level === 0){
|
||||
this.selectedSpellId = undefined;
|
||||
}
|
||||
selectedSpell: {
|
||||
handler(spell){
|
||||
if (!spell) return;
|
||||
if(spell.level === 0 || spell.castWithoutSpellSlots){
|
||||
this.selectedSlotId = 'no-slot';
|
||||
} else if (
|
||||
!this.selectedSlotId ||
|
||||
this.selectedSlotId == 'no-slot' ||
|
||||
this.selectedSlot.spellSlotLevel.value < spell.level
|
||||
) {
|
||||
const newSlot = find(
|
||||
CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
...slotFilter
|
||||
}, {
|
||||
sort: {'spellSlotLevel.value': 1, order: 1},
|
||||
}).fetch(),
|
||||
slot => {
|
||||
return this.canCastSpellWithSlot(spell, slot._id, slot)
|
||||
}
|
||||
);
|
||||
if (newSlot){
|
||||
this.selectedSlotId = newSlot._id;
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
selectedSlotId: {
|
||||
handler(slotId){
|
||||
this.selectedSlot = CreatureProperties.findOne(slotId);
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
selectedSlot:{
|
||||
handler(slot){
|
||||
if (!slot) return;
|
||||
if (!this.selectedSpell) return;
|
||||
if(this.selectedSpell.level > slot.spellSlotLevel.value){
|
||||
this.selectedSpellId = undefined;
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
mounted(){
|
||||
if (this.selectedSpellId){
|
||||
this.$vuetify.goTo('.spell.v-list-item--active', {container: '.right'});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearBooleanFilters(){
|
||||
@@ -247,10 +306,36 @@ export default {
|
||||
this.searchValue = val;
|
||||
setTimeout(ack, 200);
|
||||
},
|
||||
canCastSpellWithSlot(spell, slotId, slot){
|
||||
if (slot && !slot.value) return false;
|
||||
if (!spell) return true;
|
||||
if (!slotId) return true;
|
||||
if (
|
||||
spell.castWithoutSpellSlots &&
|
||||
spell.insufficientResources
|
||||
) return false;
|
||||
return (!spell.level || spell.castWithoutSpellSlots) ? (
|
||||
// Cantrips and no-slot spells
|
||||
slotId && slotId === 'no-slot'
|
||||
) : (
|
||||
// Leveled spells
|
||||
slotId !== 'no-slot' &&
|
||||
slot && spell && (
|
||||
spell.level <= slot.spellSlotLevel.value
|
||||
)
|
||||
)
|
||||
},
|
||||
cast(){
|
||||
let selectedSlotId = this.selectedSlotId;
|
||||
if (selectedSlotId === 'no-slot') selectedSlotId = undefined;
|
||||
this.$store.dispatch('popDialogStack', {
|
||||
spellId: this.selectedSpellId,
|
||||
slotId: selectedSlotId,
|
||||
})
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
spells(){
|
||||
let slotLevel = this.selectedSlot && this.selectedSlot.spellSlotLevelValue || 0;
|
||||
let filter = {
|
||||
'ancestors.id': this.creatureId,
|
||||
removed: {$ne: true},
|
||||
@@ -259,8 +344,8 @@ export default {
|
||||
{prepared: true},
|
||||
{alwaysPrepared: true},
|
||||
],
|
||||
level: {$lte: slotLevel},
|
||||
};
|
||||
|
||||
// Apply the filters from the filter menu
|
||||
for (let key in this.booleanFilters){
|
||||
if (this.booleanFilters[key].enabled){
|
||||
@@ -284,27 +369,13 @@ export default {
|
||||
});
|
||||
},
|
||||
spellSlots(){
|
||||
let filter = {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'attribute',
|
||||
attributeType: 'spellSlot',
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
currentValue: {$gte: 1},
|
||||
};
|
||||
if (this.selectedSpell){
|
||||
filter.spellSlotLevelValue = {$gte: this.selectedSpell.level};
|
||||
}
|
||||
return CreatureProperties.find(filter, {
|
||||
sort: {order: 1},
|
||||
...slotFilter
|
||||
}, {
|
||||
sort: {'spellSlotLevel.value': 1, order: 1},
|
||||
});
|
||||
},
|
||||
selectedSlot(){
|
||||
return CreatureProperties.findOne(this.selectedSlotId);
|
||||
},
|
||||
selectedSpell(){
|
||||
return CreatureProperties.findOne(this.selectedSpellId);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template lang="html">
|
||||
<v-list-item
|
||||
class="spell"
|
||||
v-bind="$attrs"
|
||||
:disabled="disabled"
|
||||
v-on="hasClickListener ? {click} : {}"
|
||||
>
|
||||
<v-list-item-avatar class="spell-avatar">
|
||||
@@ -8,6 +10,7 @@
|
||||
class="mr-2"
|
||||
:model="model"
|
||||
:color="model.color"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
@@ -38,6 +41,7 @@
|
||||
v-else-if="showInfoButton"
|
||||
icon
|
||||
class="info-icon"
|
||||
:disabled="disabled"
|
||||
:data-id="`spell-info-btn-${model._id}`"
|
||||
@click.stop="$emit('show-info')"
|
||||
>
|
||||
@@ -53,13 +57,14 @@ import updateCreatureProperty from '/imports/api/creature/creatureProperties/met
|
||||
|
||||
export default {
|
||||
mixins: [treeNodeViewMixin],
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
props: {
|
||||
preparingSpells: Boolean,
|
||||
hideHandle: Boolean,
|
||||
showInfoButton: Boolean,
|
||||
},
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
disabled: Boolean,
|
||||
},
|
||||
computed: {
|
||||
hasClickListener(){
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
justify="center"
|
||||
class="mb-3"
|
||||
>
|
||||
<v-col cols="1">
|
||||
<v-col cols="12">
|
||||
<icon-color-menu
|
||||
:model="model"
|
||||
:errors="errors"
|
||||
|
||||
@@ -27,6 +27,15 @@
|
||||
@change="change('variableName', ...arguments)"
|
||||
/>
|
||||
</div>
|
||||
<text-field
|
||||
v-if="context.isLibraryForm"
|
||||
label="Condition"
|
||||
hint="A caclulation to determine if this can be added to the character"
|
||||
placeholder="Always active"
|
||||
:value="model.slotFillerCondition"
|
||||
:error-messages="errors.slotFillerCondition"
|
||||
@change="change('slotFillerCondition', ...arguments)"
|
||||
/>
|
||||
|
||||
<inline-computation-field
|
||||
label="Description"
|
||||
@@ -54,6 +63,9 @@
|
||||
|
||||
export default {
|
||||
mixins: [propertyFormMixin],
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
|
||||
<text-area
|
||||
label="Description"
|
||||
:value="model.description"
|
||||
@@ -74,12 +73,8 @@
|
||||
<script lang="js">
|
||||
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
|
||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||
import CalculationErrorList from '/imports/ui/properties/forms/shared/CalculationErrorList.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CalculationErrorList,
|
||||
},
|
||||
mixins: [propertyFormMixin],
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
|
||||
@@ -1,103 +1,227 @@
|
||||
<template lang="html">
|
||||
<div class="spell-form">
|
||||
<div class="layout wrap justify-space-between">
|
||||
<smart-switch
|
||||
label="Always prepared"
|
||||
style="width: 200px; flex-grow: 0;"
|
||||
class="mx-2"
|
||||
:value="model.alwaysPrepared"
|
||||
:error-messages="errors.alwaysPrepared"
|
||||
@change="change('alwaysPrepared', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
<v-row
|
||||
justify="center"
|
||||
class="mb-3"
|
||||
>
|
||||
<v-col cols="12">
|
||||
<icon-color-menu
|
||||
:model="model"
|
||||
:errors="errors"
|
||||
@change="e => $emit('change', e)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
label="Always prepared"
|
||||
:value="model.alwaysPrepared"
|
||||
:error-messages="errors.alwaysPrepared"
|
||||
@change="change('alwaysPrepared', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
v-show="!model.alwaysPrepared"
|
||||
label="Prepared"
|
||||
style="width: 200px; flex-grow: 0;"
|
||||
class="mx-2"
|
||||
:value="model.prepared"
|
||||
:error-messages="errors.prepared"
|
||||
@change="change('prepared', ...arguments)"
|
||||
/>
|
||||
</div>
|
||||
<text-field
|
||||
ref="focusFirst"
|
||||
label="Name"
|
||||
:value="model.name"
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
<div class="layout wrap">
|
||||
<smart-select
|
||||
label="Level"
|
||||
class="mx-1"
|
||||
style="flex-basis: 300px;"
|
||||
hint="The spell level"
|
||||
:items="spellLevels"
|
||||
:value="model.level"
|
||||
:error-messages="errors.level"
|
||||
@change="change('level', ...arguments)"
|
||||
/>
|
||||
<smart-select
|
||||
label="School"
|
||||
class="mx-1"
|
||||
style="flex-basis: 300px;"
|
||||
:items="magicSchools"
|
||||
:value="model.school"
|
||||
:error-messages="errors.school"
|
||||
@change="change('school', ...arguments)"
|
||||
/>
|
||||
</div>
|
||||
<text-field
|
||||
label="Casting Time"
|
||||
:value="model.castingTime"
|
||||
:error-messages="errors.castingTime"
|
||||
@change="change('castingTime', ...arguments)"
|
||||
/>
|
||||
<text-field
|
||||
label="Range"
|
||||
:value="model.range"
|
||||
:error-messages="errors.range"
|
||||
@change="change('range', ...arguments)"
|
||||
/>
|
||||
<div class="layout wrap justify-space-between">
|
||||
<smart-checkbox
|
||||
label="Verbal"
|
||||
:value="model.verbal"
|
||||
:error-messages="errors.verbal"
|
||||
@change="change('verbal', ...arguments)"
|
||||
/>
|
||||
<smart-checkbox
|
||||
label="Somatic"
|
||||
:value="model.somatic"
|
||||
:error-messages="errors.somatic"
|
||||
@change="change('somatic', ...arguments)"
|
||||
/>
|
||||
<smart-checkbox
|
||||
label="Concentration"
|
||||
:value="model.concentration"
|
||||
:error-messages="errors.concentration"
|
||||
@change="change('concentration', ...arguments)"
|
||||
/>
|
||||
<smart-checkbox
|
||||
label="Ritual"
|
||||
:value="model.ritual"
|
||||
:error-messages="errors.ritual"
|
||||
@change="change('ritual', ...arguments)"
|
||||
/>
|
||||
</div>
|
||||
<text-field
|
||||
label="Material"
|
||||
:value="model.material"
|
||||
:error-messages="errors.material"
|
||||
@change="change('material', ...arguments)"
|
||||
/>
|
||||
<text-field
|
||||
label="Duration"
|
||||
:value="model.duration"
|
||||
:error-messages="errors.duration"
|
||||
@change="change('duration', ...arguments)"
|
||||
/>
|
||||
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
label="Prepared"
|
||||
:value="model.prepared"
|
||||
:error-messages="errors.prepared"
|
||||
@change="change('prepared', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
v-show="model.level"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
label="Cast without spell slots"
|
||||
:value="model.castWithoutSpellSlots"
|
||||
:error-messages="errors.castWithoutSpellSlots"
|
||||
@change="change('castWithoutSpellSlots', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<text-field
|
||||
ref="focusFirst"
|
||||
label="Name"
|
||||
:value="model.name"
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<smart-select
|
||||
label="Level"
|
||||
hint="The spell level"
|
||||
:items="spellLevels"
|
||||
:value="model.level"
|
||||
:error-messages="errors.level"
|
||||
@change="change('level', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<smart-select
|
||||
label="School"
|
||||
:items="magicSchools"
|
||||
:value="model.school"
|
||||
:error-messages="errors.school"
|
||||
@change="change('school', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<text-field
|
||||
label="Casting Time"
|
||||
:value="model.castingTime"
|
||||
:error-messages="errors.castingTime"
|
||||
@change="change('castingTime', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<text-field
|
||||
label="Range"
|
||||
:value="model.range"
|
||||
:error-messages="errors.range"
|
||||
@change="change('range', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<text-field
|
||||
label="Duration"
|
||||
:value="model.duration"
|
||||
:error-messages="errors.duration"
|
||||
@change="change('duration', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row class="mt-0">
|
||||
<v-col
|
||||
cols="6"
|
||||
md="3"
|
||||
class="pt-1"
|
||||
>
|
||||
<smart-checkbox
|
||||
label="Verbal"
|
||||
:value="model.verbal"
|
||||
:error-messages="errors.verbal"
|
||||
@change="change('verbal', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="6"
|
||||
md="3"
|
||||
class="pt-1"
|
||||
>
|
||||
<smart-checkbox
|
||||
label="Somatic"
|
||||
:value="model.somatic"
|
||||
:error-messages="errors.somatic"
|
||||
@change="change('somatic', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="6"
|
||||
md="3"
|
||||
class="pt-1"
|
||||
>
|
||||
<smart-checkbox
|
||||
label="Concentration"
|
||||
:value="model.concentration"
|
||||
:error-messages="errors.concentration"
|
||||
@change="change('concentration', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="6"
|
||||
md="3"
|
||||
class="pt-1"
|
||||
>
|
||||
<smart-checkbox
|
||||
label="Ritual"
|
||||
:value="model.ritual"
|
||||
:error-messages="errors.ritual"
|
||||
@change="change('ritual', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col cols="12">
|
||||
<text-field
|
||||
label="Material"
|
||||
:value="model.material"
|
||||
:error-messages="errors.material"
|
||||
@change="change('material', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<smart-select
|
||||
label="Target"
|
||||
:items="targetOptions"
|
||||
:value="model.target"
|
||||
:error-messages="errors.target"
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
@change="change('target', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<v-slide-x-transition mode="out-in">
|
||||
<v-switch
|
||||
v-if="!isAttack"
|
||||
label="Attack roll"
|
||||
:value="attackSwitch"
|
||||
@change="e => attackSwitch = e"
|
||||
/>
|
||||
<computed-field
|
||||
v-else
|
||||
label="To Hit"
|
||||
prefix="1d20 + "
|
||||
hint="The bonus to attack if this action has an attack roll"
|
||||
:model="model.attackRoll"
|
||||
:error-messages="errors.attackRoll"
|
||||
@change="({path, value, ack}) =>
|
||||
$emit('change', {path: ['attackRoll', ...path], value, ack})"
|
||||
/>
|
||||
</v-slide-x-transition>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<inline-computation-field
|
||||
label="Description"
|
||||
:model="model.description"
|
||||
@@ -105,24 +229,69 @@
|
||||
@change="({path, value, ack}) =>
|
||||
$emit('change', {path: ['description', ...path], value, ack})"
|
||||
/>
|
||||
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
hint="Used to let slots find this property in a library, should otherwise be left blank"
|
||||
:value="model.tags"
|
||||
:error-messages="errors.tags"
|
||||
@change="change('tags', ...arguments)"
|
||||
/>
|
||||
<form-sections>
|
||||
<form-section name="Resources">
|
||||
<resources-form
|
||||
:model="model.resources"
|
||||
@change="({path, value, ack}) => $emit('change', {path: ['resources', ...path], value, ack})"
|
||||
@push="({path, value, ack}) => $emit('push', {path: ['resources', ...path], value, ack})"
|
||||
@pull="({path, ack}) => $emit('pull', {path: ['resources', ...path], ack})"
|
||||
/>
|
||||
</form-section>
|
||||
<form-section
|
||||
name="Casting"
|
||||
name="Limit Uses"
|
||||
>
|
||||
<action-form
|
||||
v-bind="$props"
|
||||
v-on="$listeners"
|
||||
<v-row dense>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<computed-field
|
||||
label="Uses"
|
||||
hint="How many times this action can be used before needing to be reset"
|
||||
class="mr-2"
|
||||
:model="model.uses"
|
||||
:error-messages="errors.uses"
|
||||
@change="({path, value, ack}) =>
|
||||
$emit('change', {path: ['uses', ...path], value, ack})"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<text-field
|
||||
label="Uses used"
|
||||
type="number"
|
||||
hint="How many times this action has already been used: should be 0 in most cases"
|
||||
style="flex-basis: 300px;"
|
||||
:value="model.usesUsed"
|
||||
:error-messages="errors.uses"
|
||||
@change="change('usesUsed', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<smart-select
|
||||
label="Reset"
|
||||
clearable
|
||||
hint="When number of uses used should be reset to zero"
|
||||
style="flex-basis: 300px;"
|
||||
:items="resetOptions"
|
||||
:value="model.reset"
|
||||
:error-messages="errors.reset"
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
@change="change('reset', ...arguments)"
|
||||
/>
|
||||
</form-section>
|
||||
<form-section name="Advanced">
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
hint="Used to let slots find this property in a library, should otherwise be left blank"
|
||||
:value="model.tags"
|
||||
@change="change('tags', ...arguments)"
|
||||
/>
|
||||
</form-section>
|
||||
</form-sections>
|
||||
@@ -131,14 +300,16 @@
|
||||
|
||||
<script lang="js">
|
||||
import FormSection, { FormSections } from '/imports/ui/properties/forms/shared/FormSection.vue';
|
||||
import ActionForm from '/imports/ui/properties/forms/ActionForm.vue'
|
||||
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
|
||||
import IconColorMenu from '/imports/ui/properties/forms/shared/IconColorMenu.vue';
|
||||
import ResourcesForm from '/imports/ui/properties/forms/ResourcesForm.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormSections,
|
||||
FormSection,
|
||||
ActionForm,
|
||||
IconColorMenu,
|
||||
ResourcesForm,
|
||||
},
|
||||
mixins: [propertyFormMixin],
|
||||
data(){return {
|
||||
@@ -202,13 +373,39 @@
|
||||
value: 9,
|
||||
},
|
||||
],
|
||||
targetOptions: [
|
||||
{
|
||||
text: 'Self',
|
||||
value: 'self',
|
||||
}, {
|
||||
text: 'Single target',
|
||||
value: 'singleTarget',
|
||||
}, {
|
||||
text: 'Multiple targets',
|
||||
value: 'multipleTargets',
|
||||
},
|
||||
],
|
||||
resetOptions: [
|
||||
{
|
||||
text: 'Short rest',
|
||||
value: 'shortRest',
|
||||
}, {
|
||||
text: 'Long rest',
|
||||
value: 'longRest',
|
||||
}
|
||||
],
|
||||
attackSwitch: false,
|
||||
};},
|
||||
computed: {
|
||||
isAttack(){
|
||||
return this.attackSwitch || !!this.model.attackRoll?.calculation
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.v-input--checkbox {
|
||||
flex-grow: 0;
|
||||
width: 200px;
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,45 +1,23 @@
|
||||
<template lang="html">
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-badge
|
||||
icon="mdi-pencil"
|
||||
overlap
|
||||
>
|
||||
<v-btn
|
||||
icon
|
||||
:color="model.color"
|
||||
outlined
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>
|
||||
<property-icon
|
||||
:model="model"
|
||||
:color="model.color"
|
||||
/>
|
||||
</v-btn>
|
||||
</v-badge>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-list-item-title>
|
||||
<icon-picker
|
||||
label="Icon"
|
||||
:value="model.icon"
|
||||
:error-messages="errors.icon"
|
||||
@change="(value, ack) =>$emit('change', {path: ['icon'], value, ack})"
|
||||
/>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-list-item-title>
|
||||
<color-picker
|
||||
:value="model.color"
|
||||
@input="value =>$emit('change', {path: ['color'], value})"
|
||||
/>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<div
|
||||
class="d-flex justify-center flex-wrap"
|
||||
>
|
||||
<div class="mx-1">
|
||||
<color-picker
|
||||
label="Color"
|
||||
:value="model.color"
|
||||
@input="value =>$emit('change', {path: ['color'], value})"
|
||||
/>
|
||||
</div>
|
||||
<div class="mx-1">
|
||||
<icon-picker
|
||||
label="Icon"
|
||||
:value="model.icon"
|
||||
:error-messages="errors.icon"
|
||||
@change="(value, ack) =>$emit('change', {path: ['icon'], value, ack})"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
v-if="model.icon"
|
||||
:shape="model.icon.shape"
|
||||
:color="color"
|
||||
:class="{disabled}"
|
||||
/>
|
||||
<v-icon
|
||||
v-else
|
||||
:color="color"
|
||||
:class="{disabled}"
|
||||
>
|
||||
{{ icon }}
|
||||
</v-icon>
|
||||
@@ -25,6 +27,7 @@ export default {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
disabled: Boolean,
|
||||
},
|
||||
computed: {
|
||||
icon(){
|
||||
@@ -33,3 +36,9 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.svg-icon.disabled, .v-icon.disabled {
|
||||
opacity: 0.2;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
:is="treeNodeView"
|
||||
:model="model"
|
||||
:selected="selected"
|
||||
:class="{
|
||||
'inactive': model.inactive,
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -29,3 +32,9 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.inactive {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
>
|
||||
<property-field
|
||||
v-if="context.creatureId"
|
||||
name="Apply action"
|
||||
:name="model.type === 'spell'? 'Cast spell' : 'Apply action'"
|
||||
center
|
||||
>
|
||||
<v-btn
|
||||
outlined
|
||||
style="font-size: 18px;"
|
||||
class="ma-2"
|
||||
data-id="do-action-button"
|
||||
:color="model.color || 'primary'"
|
||||
icon
|
||||
:loading="doActionLoading"
|
||||
@@ -109,12 +110,13 @@
|
||||
|
||||
<script lang="js">
|
||||
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js';
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
import doAction from '/imports/api/engine/actions/doAction.js';
|
||||
import AttributeConsumedView from '/imports/ui/properties/components/actions/AttributeConsumedView.vue';
|
||||
import ItemConsumedView from '/imports/ui/properties/components/actions/ItemConsumedView.vue';
|
||||
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
||||
import updateCreatureProperty from '/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js';
|
||||
import doCastSpell from '/imports/api/engine/actions/doCastSpell.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -173,13 +175,33 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
doAction(){
|
||||
this.doActionLoading = true;
|
||||
doAction.call({actionId: this.model._id}, error => {
|
||||
this.doActionLoading = false;
|
||||
if (error){
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
if (this.model.type === 'action'){
|
||||
this.doActionLoading = true;
|
||||
doAction.call({actionId: this.model._id}, error => {
|
||||
this.doActionLoading = false;
|
||||
if (error){
|
||||
snackbar({text: error.reason});
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
} else if (this.model.type === 'spell') {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'cast-spell-with-slot-dialog',
|
||||
elementId: 'do-action-button',
|
||||
data: {
|
||||
creatureId: this.context.creatureId,
|
||||
spellId: this.model._id,
|
||||
},
|
||||
callback({spellId, slotId} = {}){
|
||||
if (!spellId) return;
|
||||
doCastSpell.call({spellId, slotId}, error => {
|
||||
if (!error) return;
|
||||
snackbar({text: error.reason});
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
resetUses(){
|
||||
updateCreatureProperty.call({
|
||||
|
||||
@@ -31,12 +31,9 @@
|
||||
tile
|
||||
color="primary"
|
||||
:value="model.value"
|
||||
:loading="damagePropertyLoading"
|
||||
@change="damageProperty"
|
||||
>
|
||||
<v-icon>
|
||||
$vuetify.icons.abacus
|
||||
</v-icon>
|
||||
</increment-button>
|
||||
/>
|
||||
</property-field>
|
||||
<property-field
|
||||
v-if="model.modifier !== undefined"
|
||||
@@ -144,6 +141,7 @@
|
||||
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
import IncrementButton from '/imports/ui/components/IncrementButton.vue';
|
||||
import getProficiencyIcon from '/imports/ui/utility/getProficiencyIcon.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -172,6 +170,7 @@
|
||||
0.5: 'Half proficiency bonus rounded up',
|
||||
2: 'Double proficiency bonus',
|
||||
},
|
||||
damagePropertyLoading: false,
|
||||
}},
|
||||
computed: {
|
||||
reset(){
|
||||
@@ -197,10 +196,17 @@
|
||||
});
|
||||
},
|
||||
damageProperty({type, value}) {
|
||||
this.damagePropertyLoading = true;
|
||||
damageProperty.call({
|
||||
_id: this.model._id,
|
||||
operation: type,
|
||||
value: value
|
||||
}, error => {
|
||||
this.damagePropertyLoading = false;
|
||||
if (error){
|
||||
snackbar({text: error.reason});
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
mono
|
||||
:value="model.variableName"
|
||||
/>
|
||||
<property-field
|
||||
v-if="!context.creatureId"
|
||||
name="Condition"
|
||||
mono
|
||||
:value="model.slotFillerCondition"
|
||||
/>
|
||||
<property-description
|
||||
name="Description"
|
||||
:model="model.description"
|
||||
|
||||
@@ -15,11 +15,10 @@
|
||||
large
|
||||
outlined
|
||||
color="primary"
|
||||
:loading="incrementLoading"
|
||||
:value="model.quantity"
|
||||
@change="changeQuantity"
|
||||
>
|
||||
<v-icon>$vuetify.icons.abacus</v-icon>
|
||||
</increment-button>
|
||||
/>
|
||||
</property-field>
|
||||
<property-field
|
||||
v-if="model.value !== undefined"
|
||||
@@ -152,6 +151,7 @@ import CoinValue from '/imports/ui/components/CoinValue.vue';
|
||||
import IncrementButton from '/imports/ui/components/IncrementButton.vue';
|
||||
import adjustQuantity from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
||||
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components:{
|
||||
@@ -162,6 +162,9 @@ export default {
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
data(){return {
|
||||
incrementLoading: false,
|
||||
}},
|
||||
computed:{
|
||||
totalValue(){
|
||||
return stripFloatingPointOddities(this.model.value * this.model.quantity);
|
||||
@@ -182,12 +185,19 @@ export default {
|
||||
return SVG_ICONS[name];
|
||||
},
|
||||
changeQuantity({type, value}) {
|
||||
this.incrementLoading = true;
|
||||
adjustQuantity.call({
|
||||
_id: this.model._id,
|
||||
operation: type,
|
||||
value: value
|
||||
}, error => {
|
||||
this.incrementLoading = false;
|
||||
if (error){
|
||||
snackbar({text: error.reason});
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
:value="model.slotQuantityFilled"
|
||||
/>
|
||||
<property-field
|
||||
v-if="context.creatureId"
|
||||
v-if="!context.creatureId"
|
||||
name="Condition"
|
||||
mono
|
||||
:value="model.slotFillerCondition"
|
||||
|
||||
30
app/package-lock.json
generated
30
app/package-lock.json
generated
@@ -2410,9 +2410,12 @@
|
||||
"integrity": "sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "5.0.0",
|
||||
@@ -2780,7 +2783,7 @@
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
||||
},
|
||||
"simpl-schema": {
|
||||
@@ -3046,6 +3049,11 @@
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
@@ -3221,6 +3229,20 @@
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
|
||||
"integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw=="
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
||||
"requires": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -9,7 +9,7 @@ import '/imports/server/publications/index.js';
|
||||
import '/imports/server/cron/deleteSoftRemovedDocuments.js';
|
||||
import '/imports/api/parenting/organizeMethods.js';
|
||||
import '/imports/api/users/patreon/updatePatreonOnLogin.js';
|
||||
import '/imports/api/engine/actions/doCheck.js';
|
||||
import '/imports/api/engine/actions/index.js';
|
||||
import '/imports/migrations/server/index.js';
|
||||
import '/imports/migrations/methods/index.js'
|
||||
import '/imports/constants/MAINTENANCE_MODE.js';
|
||||
|
||||
Reference in New Issue
Block a user